diff options
Diffstat (limited to 'xpcom')
1097 files changed, 230479 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 +%} diff --git a/xpcom/build/BinaryPath.h b/xpcom/build/BinaryPath.h new file mode 100644 index 0000000000..47bd6940cb --- /dev/null +++ b/xpcom/build/BinaryPath.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 mozilla_BinaryPath_h +#define mozilla_BinaryPath_h + +#include "nsXPCOMPrivate.h" // for MAXPATHLEN +#ifdef XP_WIN +# include <windows.h> +#elif defined(XP_DARWIN) +# include <CoreFoundation/CoreFoundation.h> +#elif defined(XP_UNIX) +# include <unistd.h> +# include <stdlib.h> +# include <string.h> +#endif +#if defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) +# include <sys/sysctl.h> +#endif +#if defined(__OpenBSD__) +# include <sys/stat.h> +#endif +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsCOMPtr.h" +# include "nsIFile.h" +# include "nsString.h" +#endif + +namespace mozilla { + +class BinaryPath { + public: +#ifdef XP_WIN + static nsresult Get(char aResult[MAXPATHLEN]) { + wchar_t wide_path[MAXPATHLEN]; + nsresult rv = GetW(wide_path); + if (NS_FAILED(rv)) { + return rv; + } + WideCharToMultiByte(CP_UTF8, 0, wide_path, -1, aResult, MAXPATHLEN, nullptr, + nullptr); + return NS_OK; + } + + static nsresult GetLong(wchar_t aResult[MAXPATHLEN]) { + static bool cached = false; + static wchar_t exeLongPath[MAXPATHLEN] = L""; + + if (!cached) { + nsresult rv = GetW(exeLongPath); + + if (NS_FAILED(rv)) { + return rv; + } + + if (!::GetLongPathNameW(exeLongPath, exeLongPath, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } + + cached = true; + } + + if (wcscpy_s(aResult, MAXPATHLEN, exeLongPath)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + private: + static nsresult GetW(wchar_t aResult[MAXPATHLEN]) { + static bool cached = false; + static wchar_t moduleFileName[MAXPATHLEN] = L""; + + if (!cached) { + if (!::GetModuleFileNameW(0, moduleFileName, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } + + cached = true; + } + + if (wcscpy_s(aResult, MAXPATHLEN, moduleFileName)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + +#elif defined(XP_DARWIN) + static nsresult Get(char aResult[MAXPATHLEN]) { + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (!appBundle) { + return NS_ERROR_FAILURE; + } + + CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle); + if (!executableURL) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + if (CFURLGetFileSystemRepresentation(executableURL, false, (UInt8*)aResult, + MAXPATHLEN)) { + // Sanitize path in case the app was launched from Terminal via + // './firefox' for example. + size_t readPos = 0; + size_t writePos = 0; + while (aResult[readPos] != '\0') { + if (aResult[readPos] == '.' && aResult[readPos + 1] == '/') { + readPos += 2; + } else { + aResult[writePos] = aResult[readPos]; + readPos++; + writePos++; + } + } + aResult[writePos] = '\0'; + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + } + + CFRelease(executableURL); + return rv; + } + +#elif defined(ANDROID) + static nsresult Get(char aResult[MAXPATHLEN]) { + // On Android, we use the MOZ_ANDROID_LIBDIR variable that is set by the + // Java bootstrap code. + const char* libDir = getenv("MOZ_ANDROID_LIBDIR"); + if (!libDir) { + return NS_ERROR_FAILURE; + } + + snprintf(aResult, MAXPATHLEN, "%s/%s", libDir, "dummy"); + aResult[MAXPATHLEN - 1] = '\0'; + return NS_OK; + } + +#elif defined(XP_LINUX) || defined(XP_SOLARIS) + static nsresult Get(char aResult[MAXPATHLEN]) { +# if defined(XP_SOLARIS) + const char path[] = "/proc/self/path/a.out"; +# else + const char path[] = "/proc/self/exe"; +# endif + + ssize_t len = readlink(path, aResult, MAXPATHLEN - 1); + if (len < 0) { + return NS_ERROR_FAILURE; + } + aResult[len] = '\0'; +# if defined(XP_LINUX) + // Removing suffix " (deleted)" from the binary path + const char suffix[] = " (deleted)"; + const ssize_t suffix_len = sizeof(suffix); + if (len >= suffix_len) { + char* result_end = &aResult[len - (suffix_len - 1)]; + if (memcmp(result_end, suffix, suffix_len) == 0) { + *result_end = '\0'; + } + } +# endif + return NS_OK; + } + +#elif defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) + static nsresult Get(char aResult[MAXPATHLEN]) { + int mib[4]; + mib[0] = CTL_KERN; +# ifdef __NetBSD__ + mib[1] = KERN_PROC_ARGS; + mib[2] = -1; + mib[3] = KERN_PROC_PATHNAME; +# else + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; +# endif + + size_t len = MAXPATHLEN; + if (sysctl(mib, 4, aResult, &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + +#elif defined(__OpenBSD__) + static nsresult Get(char aResult[MAXPATHLEN]) { + static bool cached = false; + static char cachedPath[MAXPATHLEN]; + nsresult r; + int mib[4]; + if (cached) { + if (strlcpy(aResult, cachedPath, MAXPATHLEN) >= MAXPATHLEN) { + return NS_ERROR_FAILURE; + } + return NS_OK; + } + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; + mib[2] = getpid(); + mib[3] = KERN_PROC_ARGV; + + size_t len = 0; + if (sysctl(mib, 4, nullptr, &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + auto argv = MakeUnique<const char*[]>(len / sizeof(const char*)); + if (sysctl(mib, 4, argv.get(), &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + r = GetFromArgv0(argv[0], aResult); + if (NS_SUCCEEDED(r)) { + if (strlcpy(cachedPath, aResult, MAXPATHLEN) >= MAXPATHLEN) { + return NS_ERROR_FAILURE; + } + cached = true; + } + return r; + } + + static nsresult GetFromArgv0(const char* aArgv0, char aResult[MAXPATHLEN]) { + struct stat fileStat; + // 1) use realpath() on argv[0], which works unless we're loaded from the + // PATH. Only do so if argv[0] looks like a path (contains a /). + // 2) manually walk through the PATH and look for ourself + // 3) give up + if (strchr(aArgv0, '/') && realpath(aArgv0, aResult) && + stat(aResult, &fileStat) == 0) { + return NS_OK; + } + + const char* path = getenv("PATH"); + if (!path) { + return NS_ERROR_FAILURE; + } + + char* pathdup = strdup(path); + if (!pathdup) { + return NS_ERROR_OUT_OF_MEMORY; + } + + bool found = false; + char* token = strtok(pathdup, ":"); + while (token) { + char tmpPath[MAXPATHLEN]; + sprintf(tmpPath, "%s/%s", token, aArgv0); + if (realpath(tmpPath, aResult) && stat(aResult, &fileStat) == 0) { + found = true; + break; + } + token = strtok(nullptr, ":"); + } + free(pathdup); + if (found) { + return NS_OK; + } + return NS_ERROR_FAILURE; + } + +#else +# error Oops, you need platform-specific code here +#endif + + public: + static UniqueFreePtr<char> Get() { + char path[MAXPATHLEN]; + if (NS_FAILED(Get(path))) { + return nullptr; + } + UniqueFreePtr<char> result; + result.reset(strdup(path)); + return result; + } + +#ifdef MOZILLA_INTERNAL_API + static nsresult GetFile(nsIFile** aResult) { + nsCOMPtr<nsIFile> lf; +# ifdef XP_WIN + wchar_t exePath[MAXPATHLEN]; + nsresult rv = GetW(exePath); +# else + char exePath[MAXPATHLEN]; + nsresult rv = Get(exePath); +# endif + if (NS_FAILED(rv)) { + return rv; + } +# ifdef XP_WIN + rv = NS_NewLocalFile(nsDependentString(exePath), true, getter_AddRefs(lf)); +# else + rv = NS_NewNativeLocalFile(nsDependentCString(exePath), true, + getter_AddRefs(lf)); +# endif + if (NS_FAILED(rv)) { + return rv; + } + NS_ADDREF(*aResult = lf); + return NS_OK; + } +#endif +}; + +} // namespace mozilla + +#endif /* mozilla_BinaryPath_h */ diff --git a/xpcom/build/FileLocation.cpp b/xpcom/build/FileLocation.cpp new file mode 100644 index 0000000000..5162546f1d --- /dev/null +++ b/xpcom/build/FileLocation.cpp @@ -0,0 +1,215 @@ +/* -*- 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 "FileLocation.h" +#include "nsZipArchive.h" +#include "nsURLHelper.h" + +#include "mozilla/UniquePtrExtensions.h" + +namespace mozilla { + +FileLocation::FileLocation() = default; + +FileLocation::~FileLocation() = default; + +FileLocation::FileLocation(nsIFile* aFile) { Init(aFile); } + +FileLocation::FileLocation(nsIFile* aFile, const char* aPath) { + Init(aFile, aPath); +} + +FileLocation::FileLocation(nsZipArchive* aZip, const char* aPath) { + Init(aZip, aPath); +} + +FileLocation::FileLocation(const FileLocation& aOther) + + = default; + +FileLocation::FileLocation(FileLocation&& aOther) + : mBaseFile(std::move(aOther.mBaseFile)), + mBaseZip(std::move(aOther.mBaseZip)), + mPath(std::move(aOther.mPath)) { + aOther.mPath.Truncate(); +} + +FileLocation::FileLocation(const FileLocation& aFile, const char* aPath) { + if (aFile.IsZip()) { + if (aFile.mBaseFile) { + Init(aFile.mBaseFile, aFile.mPath.get()); + } else { + Init(aFile.mBaseZip, aFile.mPath.get()); + } + if (aPath) { + int32_t i = mPath.RFindChar('/'); + if (kNotFound == i) { + mPath.Truncate(0); + } else { + mPath.Truncate(i + 1); + } + mPath += aPath; + } + } else { + if (aPath) { + nsCOMPtr<nsIFile> cfile; + aFile.mBaseFile->GetParent(getter_AddRefs(cfile)); + +#if defined(XP_WIN) + nsAutoCString pathStr(aPath); + char* p; + uint32_t len = pathStr.GetMutableData(&p); + for (; len; ++p, --len) { + if ('/' == *p) { + *p = '\\'; + } + } + cfile->AppendRelativeNativePath(pathStr); +#else + cfile->AppendRelativeNativePath(nsDependentCString(aPath)); +#endif + Init(cfile); + } else { + Init(aFile.mBaseFile); + } + } +} + +void FileLocation::Init(nsIFile* aFile) { + mBaseZip = nullptr; + mBaseFile = aFile; + mPath.Truncate(); +} + +void FileLocation::Init(nsIFile* aFile, const char* aPath) { + mBaseZip = nullptr; + mBaseFile = aFile; + mPath = aPath; +} + +void FileLocation::Init(nsZipArchive* aZip, const char* aPath) { + mBaseZip = aZip; + mBaseFile = nullptr; + mPath = aPath; +} + +void FileLocation::GetURIString(nsACString& aResult) const { + if (mBaseFile) { + net_GetURLSpecFromActualFile(mBaseFile, aResult); + } else if (mBaseZip) { + RefPtr<nsZipHandle> handler = mBaseZip->GetFD(); + handler->mFile.GetURIString(aResult); + } + if (IsZip()) { + aResult.InsertLiteral("jar:", 0); + aResult += "!/"; + aResult += mPath; + } +} + +already_AddRefed<nsIFile> FileLocation::GetBaseFile() { + if (IsZip() && mBaseZip) { + RefPtr<nsZipHandle> handler = mBaseZip->GetFD(); + if (handler) { + return handler->mFile.GetBaseFile(); + } + return nullptr; + } + + nsCOMPtr<nsIFile> file = mBaseFile; + return file.forget(); +} + +bool FileLocation::Equals(const FileLocation& aFile) const { + if (mPath != aFile.mPath) { + return false; + } + + if (mBaseFile && aFile.mBaseFile) { + bool eq; + return NS_SUCCEEDED(mBaseFile->Equals(aFile.mBaseFile, &eq)) && eq; + } + + const FileLocation* a = this; + const FileLocation* b = &aFile; + if (a->mBaseZip) { + RefPtr<nsZipHandle> handler = a->mBaseZip->GetFD(); + a = &handler->mFile; + } + if (b->mBaseZip) { + RefPtr<nsZipHandle> handler = b->mBaseZip->GetFD(); + b = &handler->mFile; + } + + return a->Equals(*b); +} + +nsresult FileLocation::GetData(Data& aData) { + if (!IsZip()) { + return mBaseFile->OpenNSPRFileDesc(PR_RDONLY, 0444, + getter_Transfers(aData.mFd)); + } + aData.mZip = mBaseZip; + if (!aData.mZip) { + // this can return nullptr + aData.mZip = nsZipArchive::OpenArchive(mBaseFile); + } + if (aData.mZip) { + aData.mItem = aData.mZip->GetItem(mPath.get()); + if (aData.mItem) { + return NS_OK; + } + } + return NS_ERROR_FILE_UNRECOGNIZED_PATH; +} + +nsresult FileLocation::Data::GetSize(uint32_t* aResult) { + if (mFd) { + PRFileInfo64 fileInfo; + if (PR_SUCCESS != PR_GetOpenFileInfo64(mFd.get(), &fileInfo)) { + return NS_ErrorAccordingToNSPR(); + } + + if (fileInfo.size > int64_t(UINT32_MAX)) { + return NS_ERROR_FILE_TOO_BIG; + } + + *aResult = fileInfo.size; + return NS_OK; + } + if (mItem) { + *aResult = mItem->RealSize(); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +nsresult FileLocation::Data::Copy(char* aBuf, uint32_t aLen) { + if (mFd) { + for (uint32_t totalRead = 0; totalRead < aLen;) { + int32_t read = PR_Read(mFd.get(), aBuf + totalRead, + XPCOM_MIN(aLen - totalRead, uint32_t(INT32_MAX))); + if (read < 0) { + return NS_ErrorAccordingToNSPR(); + } + totalRead += read; + } + return NS_OK; + } + if (mItem) { + nsZipCursor cursor(mItem, mZip, reinterpret_cast<uint8_t*>(aBuf), aLen, + true); + uint32_t readLen; + cursor.Copy(&readLen); + if (readLen != aLen) { + return NS_ERROR_FILE_CORRUPTED; + } + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +} /* namespace mozilla */ diff --git a/xpcom/build/FileLocation.h b/xpcom/build/FileLocation.h new file mode 100644 index 0000000000..0988a9595f --- /dev/null +++ b/xpcom/build/FileLocation.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_FileLocation_h +#define mozilla_FileLocation_h + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "FileUtils.h" + +class nsZipArchive; +class nsZipItem; + +namespace mozilla { + +class FileLocation { + public: + /** + * FileLocation is an helper to handle different kind of file locations + * within Gecko: + * - on filesystems + * - in archives + * - in archives within archives + * As such, it stores a path within an archive, as well as the archive + * path itself, or the complete file path alone when on a filesystem. + * When the archive is in an archive, an nsZipArchive is stored instead + * of a file path. + */ + FileLocation(); + ~FileLocation(); + + FileLocation(const FileLocation& aOther); + FileLocation(FileLocation&& aOther); + + FileLocation& operator=(const FileLocation&) = default; + + /** + * Constructor for plain files + */ + explicit FileLocation(nsIFile* aFile); + + /** + * Constructors for path within an archive. The archive can be given either + * as nsIFile or nsZipArchive. + */ + FileLocation(nsIFile* aZip, const char* aPath); + + FileLocation(nsZipArchive* aZip, const char* aPath); + + /** + * Creates a new file location relative to another one. + */ + FileLocation(const FileLocation& aFile, const char* aPath); + + /** + * Initialization functions corresponding to constructors + */ + void Init(nsIFile* aFile); + + void Init(nsIFile* aZip, const char* aPath); + + void Init(nsZipArchive* aZip, const char* aPath); + + /** + * Returns an URI string corresponding to the file location + */ + void GetURIString(nsACString& aResult) const; + + /** + * Returns the base file of the location, where base file is defined as: + * - The file itself when the location is on a filesystem + * - The archive file when the location is in an archive + * - The outer archive file when the location is in an archive in an archive + */ + already_AddRefed<nsIFile> GetBaseFile(); + + nsZipArchive* GetBaseZip() { return mBaseZip; } + + /** + * Returns whether the "base file" (see GetBaseFile) is an archive + */ + bool IsZip() const { return !mPath.IsEmpty(); } + + /** + * Returns the path within the archive, when within an archive + */ + void GetPath(nsACString& aResult) const { aResult = mPath; } + + /** + * Boolean value corresponding to whether the file location is initialized + * or not. + */ + explicit operator bool() const { return mBaseFile || mBaseZip; } + + /** + * Returns whether another FileLocation points to the same resource + */ + bool Equals(const FileLocation& aFile) const; + + /** + * Data associated with a FileLocation. + */ + class Data { + public: + /** + * Returns the data size + */ + nsresult GetSize(uint32_t* aResult); + + /** + * Copies the data in the given buffer + */ + nsresult Copy(char* aBuf, uint32_t aLen); + + protected: + friend class FileLocation; + nsZipItem* mItem; + RefPtr<nsZipArchive> mZip; + mozilla::AutoFDClose mFd; + }; + + /** + * Returns the data associated with the resource pointed at by the file + * location. + */ + nsresult GetData(Data& aData); + + private: + nsCOMPtr<nsIFile> mBaseFile; + RefPtr<nsZipArchive> mBaseZip; + nsCString mPath; +}; /* class FileLocation */ + +} /* namespace mozilla */ + +#endif /* mozilla_FileLocation_h */ diff --git a/xpcom/build/IOInterposer.cpp b/xpcom/build/IOInterposer.cpp new file mode 100644 index 0000000000..0420965b91 --- /dev/null +++ b/xpcom/build/IOInterposer.cpp @@ -0,0 +1,532 @@ +/* -*- 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 <algorithm> +#include <vector> + +#include "IOInterposer.h" + +#include "IOInterposerPrivate.h" +#include "MainThreadIOLogger.h" +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadLocal.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#if !defined(XP_WIN) +# include "NSPRInterposer.h" +#endif // !defined(XP_WIN) +#include "nsXULAppAPI.h" +#include "PoisonIOInterposer.h" +#include "prenv.h" + +namespace { + +/** Find if a vector contains a specific element */ +template <class T> +bool VectorContains(const std::vector<T>& aVector, const T& aElement) { + return std::find(aVector.begin(), aVector.end(), aElement) != aVector.end(); +} + +/** Remove element from a vector */ +template <class T> +void VectorRemove(std::vector<T>& aVector, const T& aElement) { + typename std::vector<T>::iterator newEnd = + std::remove(aVector.begin(), aVector.end(), aElement); + aVector.erase(newEnd, aVector.end()); +} + +/** Lists of Observers */ +struct ObserverLists { + private: + ~ObserverLists() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists) + + ObserverLists() = default; + + ObserverLists(ObserverLists const& aOther) + : mCreateObservers(aOther.mCreateObservers), + mReadObservers(aOther.mReadObservers), + mWriteObservers(aOther.mWriteObservers), + mFSyncObservers(aOther.mFSyncObservers), + mStatObservers(aOther.mStatObservers), + mCloseObservers(aOther.mCloseObservers), + mStageObservers(aOther.mStageObservers) {} + // Lists of observers for I/O events. + // These are implemented as vectors since they are allowed to survive gecko, + // without reporting leaks. This is necessary for the IOInterposer to be used + // for late-write checks. + std::vector<mozilla::IOInterposeObserver*> mCreateObservers; + std::vector<mozilla::IOInterposeObserver*> mReadObservers; + std::vector<mozilla::IOInterposeObserver*> mWriteObservers; + std::vector<mozilla::IOInterposeObserver*> mFSyncObservers; + std::vector<mozilla::IOInterposeObserver*> mStatObservers; + std::vector<mozilla::IOInterposeObserver*> mCloseObservers; + std::vector<mozilla::IOInterposeObserver*> mStageObservers; +}; + +class PerThreadData { + public: + explicit PerThreadData(bool aIsMainThread = false) + : mIsMainThread(aIsMainThread), + mIsHandlingObservation(false), + mCurrentGeneration(0) { + MOZ_COUNT_CTOR(PerThreadData); + } + + MOZ_COUNTED_DTOR(PerThreadData) + + void CallObservers(mozilla::IOInterposeObserver::Observation& aObservation) { + // Prevent recursive reporting. + if (mIsHandlingObservation) { + return; + } + + mIsHandlingObservation = true; + // Decide which list of observers to inform + const std::vector<mozilla::IOInterposeObserver*>* observers = nullptr; + switch (aObservation.ObservedOperation()) { + case mozilla::IOInterposeObserver::OpCreateOrOpen: + observers = &mObserverLists->mCreateObservers; + break; + case mozilla::IOInterposeObserver::OpRead: + observers = &mObserverLists->mReadObservers; + break; + case mozilla::IOInterposeObserver::OpWrite: + observers = &mObserverLists->mWriteObservers; + break; + case mozilla::IOInterposeObserver::OpFSync: + observers = &mObserverLists->mFSyncObservers; + break; + case mozilla::IOInterposeObserver::OpStat: + observers = &mObserverLists->mStatObservers; + break; + case mozilla::IOInterposeObserver::OpClose: + observers = &mObserverLists->mCloseObservers; + break; + case mozilla::IOInterposeObserver::OpNextStage: + observers = &mObserverLists->mStageObservers; + break; + default: { + // Invalid IO operation, see documentation comment for + // IOInterposer::Report() + MOZ_ASSERT(false); + // Just ignore it in non-debug builds. + return; + } + } + MOZ_ASSERT(observers); + + // Inform observers + for (auto i = observers->begin(), e = observers->end(); i != e; ++i) { + (*i)->Observe(aObservation); + } + mIsHandlingObservation = false; + } + + inline uint32_t GetCurrentGeneration() const { return mCurrentGeneration; } + + inline bool IsMainThread() const { return mIsMainThread; } + + inline void SetObserverLists(uint32_t aNewGeneration, + RefPtr<const ObserverLists>& aNewLists) { + mCurrentGeneration = aNewGeneration; + mObserverLists = aNewLists; + } + + inline void ClearObserverLists() { + if (mObserverLists) { + mCurrentGeneration = 0; + mObserverLists = nullptr; + } + } + + private: + bool mIsMainThread; + bool mIsHandlingObservation; + uint32_t mCurrentGeneration; + RefPtr<const ObserverLists> mObserverLists; +}; + +// Thread-safe list of observers, from which `PerThreadData` sources its own +// local list when needed. +class SourceList { + public: + SourceList() + : mObservedOperations(mozilla::IOInterposeObserver::OpNone), + mIsEnabled(true) { + MOZ_COUNT_CTOR(SourceList); + } + + MOZ_COUNTED_DTOR(SourceList) + + inline void Disable() { mIsEnabled = false; } + inline void Enable() { mIsEnabled = true; } + + void Register(mozilla::IOInterposeObserver::Operation aOp, + mozilla::IOInterposeObserver* aStaticObserver) { + mozilla::IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + // You can register to observe multiple types of observations + // but you'll never be registered twice for the same observations. + if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen && + !VectorContains(newLists->mCreateObservers, aStaticObserver)) { + newLists->mCreateObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpRead && + !VectorContains(newLists->mReadObservers, aStaticObserver)) { + newLists->mReadObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpWrite && + !VectorContains(newLists->mWriteObservers, aStaticObserver)) { + newLists->mWriteObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpFSync && + !VectorContains(newLists->mFSyncObservers, aStaticObserver)) { + newLists->mFSyncObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpStat && + !VectorContains(newLists->mStatObservers, aStaticObserver)) { + newLists->mStatObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpClose && + !VectorContains(newLists->mCloseObservers, aStaticObserver)) { + newLists->mCloseObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpNextStage && + !VectorContains(newLists->mStageObservers, aStaticObserver)) { + newLists->mStageObservers.push_back(aStaticObserver); + } + mObserverLists = newLists; + mObservedOperations = + (mozilla::IOInterposeObserver::Operation)(mObservedOperations | aOp); + + mCurrentGeneration++; + } + + void Unregister(mozilla::IOInterposeObserver::Operation aOp, + mozilla::IOInterposeObserver* aStaticObserver) { + mozilla::IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + + if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen) { + VectorRemove(newLists->mCreateObservers, aStaticObserver); + if (newLists->mCreateObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & + ~mozilla::IOInterposeObserver::OpCreateOrOpen); + } + } + if (aOp & mozilla::IOInterposeObserver::OpRead) { + VectorRemove(newLists->mReadObservers, aStaticObserver); + if (newLists->mReadObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpRead); + } + } + if (aOp & mozilla::IOInterposeObserver::OpWrite) { + VectorRemove(newLists->mWriteObservers, aStaticObserver); + if (newLists->mWriteObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpWrite); + } + } + if (aOp & mozilla::IOInterposeObserver::OpFSync) { + VectorRemove(newLists->mFSyncObservers, aStaticObserver); + if (newLists->mFSyncObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpFSync); + } + } + if (aOp & mozilla::IOInterposeObserver::OpStat) { + VectorRemove(newLists->mStatObservers, aStaticObserver); + if (newLists->mStatObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpStat); + } + } + if (aOp & mozilla::IOInterposeObserver::OpClose) { + VectorRemove(newLists->mCloseObservers, aStaticObserver); + if (newLists->mCloseObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpClose); + } + } + if (aOp & mozilla::IOInterposeObserver::OpNextStage) { + VectorRemove(newLists->mStageObservers, aStaticObserver); + if (newLists->mStageObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpNextStage); + } + } + mObserverLists = newLists; + mCurrentGeneration++; + } + + void Update(PerThreadData& aPtd) { + if (mCurrentGeneration == aPtd.GetCurrentGeneration()) { + return; + } + // If the generation counts don't match then we need to update the current + // thread's observer list with the new source list. + mozilla::IOInterposer::AutoLock lock(mLock); + aPtd.SetObserverLists(mCurrentGeneration, mObserverLists); + } + + inline bool IsObservedOperation(mozilla::IOInterposeObserver::Operation aOp) { + // This does not occur inside of a lock, so this makes no guarantees that + // the observers are in sync with this. That is acceptable; it is not a + // problem if we occasionally report more or less IO than is actually + // occurring. + return mIsEnabled && !!(mObservedOperations & aOp); + } + + private: + RefPtr<const ObserverLists> mObserverLists MOZ_GUARDED_BY(mLock); + // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked + // (We want to monitor IO during shutdown). Furthermore, as we may have to + // unregister observers during shutdown an OffTheBooksMutex is not an option + // either, as its base calls into sDeadlockDetector which may be nullptr + // during shutdown. + mozilla::IOInterposer::Mutex mLock; + // Flags tracking which operations are being observed + mozilla::Atomic<mozilla::IOInterposeObserver::Operation, + mozilla::MemoryOrdering::Relaxed> + mObservedOperations; + // Used for quickly disabling everything by IOInterposer::Disable() + mozilla::Atomic<bool> mIsEnabled; + // Used to inform threads that the source observer list has changed + mozilla::Atomic<uint32_t> mCurrentGeneration; +}; + +// Special observation used by IOInterposer::EnteringNextStage() +class NextStageObservation : public mozilla::IOInterposeObserver::Observation { + public: + NextStageObservation() + : mozilla::IOInterposeObserver::Observation( + mozilla::IOInterposeObserver::OpNextStage, "IOInterposer", false) { + mStart = mozilla::TimeStamp::Now(); + mEnd = mStart; + } +}; + +// List of observers registered +static mozilla::StaticAutoPtr<SourceList> sSourceList; +static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData; +static bool sThreadLocalDataInitialized; + +} // anonymous namespace + +namespace mozilla { + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const char* aReference, + bool aShouldReport) + : mOperation(aOperation), + mReference(aReference), + mShouldReport(IOInterposer::IsObservedOperation(aOperation) && + aShouldReport) { + if (mShouldReport) { + mStart = TimeStamp::Now(); + } +} + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const TimeStamp& aStart, + const TimeStamp& aEnd, + const char* aReference) + : mOperation(aOperation), + mStart(aStart), + mEnd(aEnd), + mReference(aReference), + mShouldReport(false) {} + +const char* IOInterposeObserver::Observation::ObservedOperationString() const { + switch (mOperation) { + case OpCreateOrOpen: + return "create/open"; + case OpRead: + return "read"; + case OpWrite: + return "write"; + case OpFSync: + return "fsync"; + case OpStat: + return "stat"; + case OpClose: + return "close"; + case OpNextStage: + return "NextStage"; + default: + return "unknown"; + } +} + +void IOInterposeObserver::Observation::Report() { + if (mShouldReport) { + mEnd = TimeStamp::Now(); + IOInterposer::Report(*this); + } +} + +bool IOInterposer::Init() { + // Don't initialize twice... + if (sSourceList) { + return true; + } + if (!sThreadLocalData.init()) { + return false; + } + sThreadLocalDataInitialized = true; + bool isMainThread = true; + RegisterCurrentThread(isMainThread); + sSourceList = new SourceList(); + + MainThreadIOLogger::Init(); + + // Now we initialize the various interposers depending on platform + + // Under certain conditions it may be unsafe to initialize PoisonIOInterposer, + // such as when a background thread is already running. We set this variable + // elsewhere when such a condition applies. + if (!PR_GetEnv("MOZ_DISABLE_POISON_IO_INTERPOSER")) { + InitPoisonIOInterposer(); + } + + // We don't hook NSPR on Windows because PoisonIOInterposer captures a + // superset of the former's events. +#if !defined(XP_WIN) + InitNSPRIOInterposing(); +#endif + return true; +} + +bool IOInterposeObserver::IsMainThread() { + if (!sThreadLocalDataInitialized) { + return false; + } + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + return false; + } + return ptd->IsMainThread(); +} + +void IOInterposer::Clear() { + /* Clear() is a no-op on release builds so that we may continue to trap I/O + until process termination. In leak-checking builds, we need to shut down + IOInterposer so that all references are properly released. */ +#ifdef NS_FREE_PERMANENT_DATA + UnregisterCurrentThread(); + sSourceList = nullptr; +#endif +} + +void IOInterposer::Disable() { + if (!sSourceList) { + return; + } + sSourceList->Disable(); +} + +void IOInterposer::Enable() { + if (!sSourceList) { + return; + } + sSourceList->Enable(); +} + +void IOInterposer::Report(IOInterposeObserver::Observation& aObservation) { + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + // In this case the current thread is not registered with IOInterposer. + // Alternatively we could take the slow path and just lock everything if + // we're not registered. That could potentially perform poorly, though. + return; + } + + if (!sSourceList) { + // If there is no longer a source list then we should clear the local one. + ptd->ClearObserverLists(); + return; + } + + sSourceList->Update(*ptd); + + // Don't try to report if there's nobody listening. + if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) { + return; + } + + ptd->CallObservers(aObservation); +} + +bool IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp) { + return sSourceList && sSourceList->IsObservedOperation(aOp); +} + +void IOInterposer::Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver) { + MOZ_ASSERT(aStaticObserver); + if (!sSourceList || !aStaticObserver) { + return; + } + + sSourceList->Register(aOp, aStaticObserver); +} + +void IOInterposer::Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver) { + if (!sSourceList) { + return; + } + + sSourceList->Unregister(aOp, aStaticObserver); +} + +void IOInterposer::RegisterCurrentThread(bool aIsMainThread) { + if (!sThreadLocalDataInitialized) { + return; + } + MOZ_ASSERT(!sThreadLocalData.get()); + PerThreadData* curThreadData = new PerThreadData(aIsMainThread); + sThreadLocalData.set(curThreadData); +} + +void IOInterposer::UnregisterCurrentThread() { + if (!sThreadLocalDataInitialized) { + return; + } + if (PerThreadData* curThreadData = sThreadLocalData.get()) { + sThreadLocalData.set(nullptr); + delete curThreadData; + } +} + +void IOInterposer::EnteringNextStage() { + if (!sSourceList) { + return; + } + NextStageObservation observation; + Report(observation); +} + +} // namespace mozilla diff --git a/xpcom/build/IOInterposer.h b/xpcom/build/IOInterposer.h new file mode 100644 index 0000000000..34dd337e11 --- /dev/null +++ b/xpcom/build/IOInterposer.h @@ -0,0 +1,290 @@ +/* -*- 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_IOInterposer_h +#define mozilla_IOInterposer_h + +#include "mozilla/Attributes.h" +#include "mozilla/TimeStamp.h" +#include "nsString.h" + +namespace mozilla { + +/** + * Interface for I/O interposer observers. This is separate from the + * IOInterposer because we have multiple uses for these observations. + */ +class IOInterposeObserver { + public: + enum Operation { + OpNone = 0, + OpCreateOrOpen = (1 << 0), + OpRead = (1 << 1), + OpWrite = (1 << 2), + OpFSync = (1 << 3), + OpStat = (1 << 4), + OpClose = (1 << 5), + OpNextStage = + (1 << 6), // Meta - used when leaving startup, entering shutdown + OpWriteFSync = (OpWrite | OpFSync), + OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose), + OpAllWithStaging = (OpAll | OpNextStage) + }; + + /** A representation of an I/O observation */ + class Observation { + protected: + /** + * This constructor is for use by subclasses that are intended to take + * timing measurements via RAII. The |aShouldReport| parameter may be + * used to make the measurement and reporting conditional on the + * satisfaction of an arbitrary predicate that was evaluated + * in the subclass. Note that IOInterposer::IsObservedOperation() is + * always ANDed with aShouldReport, so the subclass does not need to + * include a call to that function explicitly. + */ + Observation(Operation aOperation, const char* aReference, + bool aShouldReport = true); + + public: + /** + * Since this constructor accepts start and end times, it does *not* take + * its own timings, nor does it report itself. + */ + Observation(Operation aOperation, const TimeStamp& aStart, + const TimeStamp& aEnd, const char* aReference); + + /** + * Operation observed, this is one of the individual Operation values. + * Combinations of these flags are only used when registering observers. + */ + Operation ObservedOperation() const { return mOperation; } + + /** + * Return the observed operation as a human-readable string. + */ + const char* ObservedOperationString() const; + + /** Time at which the I/O operation was started */ + TimeStamp Start() const { return mStart; } + + /** + * Time at which the I/O operation ended, for asynchronous methods this is + * the time at which the call initiating the asynchronous request returned. + */ + TimeStamp End() const { return mEnd; } + + /** + * Duration of the operation, for asynchronous I/O methods this is the + * duration of the call initiating the asynchronous request. + */ + TimeDuration Duration() const { return mEnd - mStart; } + + /** + * IO reference, function name or name of component (sqlite) that did IO + * this is in addition the generic operation. This attribute may be platform + * specific, but should only take a finite number of distinct values. + * E.g. sqlite-commit, CreateFile, NtReadFile, fread, fsync, mmap, etc. + * I.e. typically the platform specific function that did the IO. + */ + const char* Reference() const { return mReference; } + + virtual const char* FileType() const { return "File"; } + + /** Request filename associated with the I/O operation, empty if unknown */ + virtual void Filename(nsAString& aString) { aString.Truncate(); } + + virtual ~Observation() = default; + + protected: + void Report(); + + Operation mOperation; + TimeStamp mStart; + TimeStamp mEnd; + const char* mReference; // Identifies the source of the Observation + bool mShouldReport; // Measure and report if true + }; + + /** + * Invoked whenever an implementation of the IOInterposeObserver should + * observe aObservation. Implement this and do your thing... + * But do consider if it is wise to use IO functions in this method, they are + * likely to cause recursion :) + * At least, see PoisonIOInterposer.h and register your handle as a debug file + * even, if you don't initialize the poison IO interposer, someone else might. + * + * Remark: Observations may occur on any thread. + */ + virtual void Observe(Observation& aObservation) = 0; + + virtual ~IOInterposeObserver() = default; + + protected: + /** + * We don't use NS_IsMainThread() because we need to be able to determine the + * main thread outside of XPCOM Initialization. IOInterposer observers should + * call this function instead. + */ + static bool IsMainThread(); +}; + +/** + * These functions are responsible for ensuring that events are routed to the + * appropriate observers. + */ +namespace IOInterposer { + +/** + * This function must be called from the main-thread when no other threads are + * running before any of the other methods on this class may be used. + * + * IO reports can however, safely assume that IsObservedOperation() will + * return false until the IOInterposer is initialized. + * + * Remark, it's safe to call this method multiple times, so just call it when + * you to utilize IO interposing. + * + * Using the IOInterposerInit class is preferred to calling this directly. + */ +bool Init(); + +/** + * This function must be called from the main thread, and furthermore + * it must be called when no other threads are executing. Effectively + * restricting us to calling it only during shutdown. + * + * Callers should take care that no other consumers are subscribed to events, + * as these events will stop when this function is called. + * + * In practice, we don't use this method as the IOInterposer is used for + * late-write checks. + */ +void Clear(); + +/** + * This function immediately disables IOInterposer functionality in a fast, + * thread-safe manner. Primarily for use by the crash reporter. + */ +void Disable(); + +/** + * This function re-enables IOInterposer functionality in a fast, thread-safe + * manner. Primarily for use by the crash reporter. + */ +void Enable(); + +/** + * Report IO to registered observers. + * Notice that the reported operation must be either OpRead, OpWrite or + * OpFSync. You are not allowed to report an observation with OpWriteFSync or + * OpAll, these are just auxiliary values for use with Register(). + * + * If the IO call you're reporting does multiple things, write and fsync, you + * can choose to call Report() twice once with write and once with FSync. You + * may not call Report() with OpWriteFSync! The Observation::mOperation + * attribute is meant to be generic, not perfect. + * + * Notice that there is no reason to report an observation with an operation + * which is not being observed. Use IsObservedOperation() to check if the + * operation you are about to report is being observed. This is especially + * important if you are constructing expensive observations containing + * filename and full-path. + * + * Remark: Init() must be called before any IO is reported. But + * IsObservedOperation() will return false until Init() is called. + */ +void Report(IOInterposeObserver::Observation& aObservation); + +/** + * Return whether or not an operation is observed. Reporters should not + * report operations that are not being observed by anybody. This mechanism + * allows us to avoid reporting I/O when no observers are registered. + */ +bool IsObservedOperation(IOInterposeObserver::Operation aOp); + +/** + * Register IOInterposeObserver, the observer object will receive all + * observations for the given operation aOp. + * + * Remarks: + * - Init() must be called before observers are registered. + * - The IOInterposeObserver object should be static, because it could still be + * used on another thread shortly after Unregister(). + */ +void Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver); + +/** + * Unregister an IOInterposeObserver for a given operation + * Remark: It is always safe to unregister for all operations, even if yoú + * didn't register for them all. + * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver) + * + * Remarks: + * - Init() must be called before observers are registered. + * - The IOInterposeObserver object should be static, because it could still be + * used on another thread shortly after this Unregister() call. + */ +void Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver); + +/** + * Registers the current thread with the IOInterposer. This must be done to + * ensure that per-thread data is created in an orderly fashion. + * We could have written this to initialize that data lazily, however this + * could have unintended consequences if a thread that is not aware of + * IOInterposer was implicitly registered: its per-thread data would never + * be deleted because it would not know to unregister itself. + * + * @param aIsMainThread true if IOInterposer should treat the current thread + * as the main thread. + */ +void RegisterCurrentThread(bool aIsMainThread = false); + +/** + * Unregisters the current thread with the IOInterposer. This is important + * to call when a thread is shutting down because it cleans up data that + * is stored in a TLS slot. + */ +void UnregisterCurrentThread(); + +/** + * Called to inform observers that the process has transitioned out of the + * startup stage or into the shutdown stage. Main thread only. + */ +void EnteringNextStage(); + +} // namespace IOInterposer + +class MOZ_RAII AutoIOInterposer { + public: + AutoIOInterposer() = default; + + void Init() { +#if defined(EARLY_BETA_OR_EARLIER) + IOInterposer::Init(); +#endif + } + + ~AutoIOInterposer() { +#if defined(EARLY_BETA_OR_EARLIER) + IOInterposer::Clear(); +#endif + } +}; + +class MOZ_RAII AutoIOInterposerDisable final { + public: + explicit AutoIOInterposerDisable() { IOInterposer::Disable(); } + ~AutoIOInterposerDisable() { IOInterposer::Enable(); } + + private: +}; + +} // namespace mozilla + +#endif // mozilla_IOInterposer_h diff --git a/xpcom/build/IOInterposerPrivate.h b/xpcom/build/IOInterposerPrivate.h new file mode 100644 index 0000000000..14f71603b8 --- /dev/null +++ b/xpcom/build/IOInterposerPrivate.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 xpcom_build_IOInterposerPrivate_h +#define xpcom_build_IOInterposerPrivate_h + +/* This header file contains declarations for helper classes that are + to be used exclusively by IOInterposer and its observers. This header + file is not to be used by anything else and MUST NOT be exported! */ + +#include <prcvar.h> +#include <prlock.h> + +#include "mozilla/ThreadSafety.h" + +namespace mozilla { +namespace IOInterposer { + +/** + * The following classes are simple wrappers for PRLock and PRCondVar. + * IOInterposer and friends use these instead of Mozilla::Mutex et al because + * of the fact that IOInterposer is permitted to run until the process + * terminates; we can't use anything that plugs into leak checkers or deadlock + * detectors because IOInterposer will outlive those and generate false + * positives. + */ + +class MOZ_CAPABILITY("monitor") Monitor { + public: + Monitor() : mLock(PR_NewLock()), mCondVar(PR_NewCondVar(mLock)) {} + + ~Monitor() { + PR_DestroyCondVar(mCondVar); + mCondVar = nullptr; + PR_DestroyLock(mLock); + mLock = nullptr; + } + + void Lock() MOZ_CAPABILITY_ACQUIRE() { PR_Lock(mLock); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { PR_Unlock(mLock); } + + bool Wait(PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT) + MOZ_REQUIRES(this) { + return PR_WaitCondVar(mCondVar, aTimeout) == PR_SUCCESS; + } + + bool Notify() { return PR_NotifyCondVar(mCondVar) == PR_SUCCESS; } + + private: + PRLock* mLock; + PRCondVar* mCondVar; +}; + +class MOZ_SCOPED_CAPABILITY MonitorAutoLock { + public: + explicit MonitorAutoLock(Monitor& aMonitor) MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(aMonitor) { + mMonitor.Lock(); + } + + ~MonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor.Unlock(); } + + private: + Monitor& mMonitor; +}; + +class MOZ_SCOPED_CAPABILITY MonitorAutoUnlock { + public: + explicit MonitorAutoUnlock(Monitor& aMonitor) + MOZ_SCOPED_UNLOCK_RELEASE(aMonitor) + : mMonitor(aMonitor) { + mMonitor.Unlock(); + } + + ~MonitorAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mMonitor.Lock(); } + + private: + Monitor& mMonitor; +}; + +class MOZ_CAPABILITY("mutex") Mutex { + public: + Mutex() : mPRLock(PR_NewLock()) {} + + ~Mutex() { + PR_DestroyLock(mPRLock); + mPRLock = nullptr; + } + + void Lock() MOZ_CAPABILITY_ACQUIRE() { PR_Lock(mPRLock); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { PR_Unlock(mPRLock); } + + private: + PRLock* mPRLock; +}; + +class MOZ_SCOPED_CAPABILITY AutoLock { + public: + explicit AutoLock(Mutex& aLock) MOZ_CAPABILITY_ACQUIRE(aLock) : mLock(aLock) { + mLock.Lock(); + } + + ~AutoLock() MOZ_CAPABILITY_RELEASE() { mLock.Unlock(); } + + private: + Mutex& mLock; +}; + +} // namespace IOInterposer +} // namespace mozilla + +#endif // xpcom_build_IOInterposerPrivate_h diff --git a/xpcom/build/LateWriteChecks.cpp b/xpcom/build/LateWriteChecks.cpp new file mode 100644 index 0000000000..515b484aef --- /dev/null +++ b/xpcom/build/LateWriteChecks.cpp @@ -0,0 +1,262 @@ +/* -*- 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 <algorithm> + +#include "mozilla/IOInterposer.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/SHA1.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsLocalFile.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "prio.h" + +#ifdef XP_WIN +# define NS_SLASH "\\" +# include <fcntl.h> +# include <io.h> +# include <stdio.h> +# include <stdlib.h> +# include <sys/stat.h> +# include <windows.h> +#else +# define NS_SLASH "/" +#endif + +#include "LateWriteChecks.h" + +/*************************** Auxiliary Declarations ***************************/ + +static MOZ_THREAD_LOCAL(int) tlsSuspendLateWriteChecks; + +bool SuspendingLateWriteChecksForCurrentThread() { + if (!tlsSuspendLateWriteChecks.init()) { + return true; + } + return tlsSuspendLateWriteChecks.get() > 0; +} + +// This a wrapper over a file descriptor that provides a Printf method and +// computes the sha1 of the data that passes through it. +class SHA1Stream { + public: + explicit SHA1Stream(FILE* aStream) : mFile(aStream) { + MozillaRegisterDebugFILE(mFile); + } + + void Printf(const char* aFormat, ...) MOZ_FORMAT_PRINTF(2, 3) { + MOZ_ASSERT(mFile); + va_list list; + va_start(list, aFormat); + nsAutoCString str; + str.AppendVprintf(aFormat, list); + va_end(list); + mSHA1.update(str.get(), str.Length()); + mozilla::Unused << fwrite(str.get(), 1, str.Length(), mFile); + } + void Finish(mozilla::SHA1Sum::Hash& aHash) { + int fd = fileno(mFile); + fflush(mFile); + MozillaUnRegisterDebugFD(fd); + fclose(mFile); + mSHA1.finish(aHash); + mFile = nullptr; + } + + private: + FILE* mFile; + mozilla::SHA1Sum mSHA1; +}; + +static void RecordStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + std::vector<uintptr_t>* stack = + static_cast<std::vector<uintptr_t>*>(aClosure); + stack->push_back(reinterpret_cast<uintptr_t>(aPC)); +} + +/**************************** Late-Write Observer ****************************/ + +/** + * An implementation of IOInterposeObserver to be registered with IOInterposer. + * This observer logs all writes as late writes. + */ +class LateWriteObserver final : public mozilla::IOInterposeObserver { + using char_type = mozilla::filesystem::Path::value_type; + + public: + explicit LateWriteObserver(const char_type* aProfileDirectory) + : mProfileDirectory(NS_xstrdup(aProfileDirectory)) {} + ~LateWriteObserver() { + free(mProfileDirectory); + mProfileDirectory = nullptr; + } + + void Observe( + mozilla::IOInterposeObserver::Observation& aObservation) override; + + private: + char_type* mProfileDirectory; +}; + +void LateWriteObserver::Observe( + mozilla::IOInterposeObserver::Observation& aOb) { + if (SuspendingLateWriteChecksForCurrentThread()) { + return; + } + +#ifdef DEBUG + MOZ_CRASH(); +#endif + + // If we can't record then abort + if (!mozilla::Telemetry::CanRecordExtended()) { + return; + } + + // Write the stack and loaded libraries to a file. We can get here + // concurrently from many writes, so we use multiple temporary files. + std::vector<uintptr_t> rawStack; + + MozStackWalk(RecordStackWalker, nullptr, /* maxFrames */ 0, &rawStack); + mozilla::Telemetry::ProcessedStack stack = + mozilla::Telemetry::GetStackAndModules(rawStack); + + nsTAutoString<char_type> nameAux(mProfileDirectory); + nameAux.AppendLiteral(NS_SLASH "Telemetry.LateWriteTmpXXXXXX"); + char_type* name = nameAux.BeginWriting(); + + // We want the sha1 of the entire file, so please don't write to fd + // directly; use sha1Stream. + FILE* stream; +#ifdef XP_WIN + HANDLE hFile; + do { + // mkstemp isn't supported so keep trying until we get a file + _wmktemp_s(char16ptr_t(name), NS_strlen(name) + 1); + hFile = CreateFileW(char16ptr_t(name), GENERIC_WRITE, 0, nullptr, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + } while (GetLastError() == ERROR_FILE_EXISTS); + + if (hFile == INVALID_HANDLE_VALUE) { + MOZ_CRASH("Um, how did we get here?"); + } + + // http://support.microsoft.com/kb/139640 + int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND); + if (fd == -1) { + MOZ_CRASH("Um, how did we get here?"); + } + + stream = _fdopen(fd, "w"); +#else + int fd = mkstemp(name); + if (fd == -1) { + MOZ_CRASH("mkstemp failed"); + } + stream = fdopen(fd, "w"); +#endif + + SHA1Stream sha1Stream(stream); + + size_t numModules = stack.GetNumModules(); + sha1Stream.Printf("%u\n", (unsigned)numModules); + for (size_t i = 0; i < numModules; ++i) { + mozilla::Telemetry::ProcessedStack::Module module = stack.GetModule(i); + sha1Stream.Printf("%s %s\n", module.mBreakpadId.get(), + NS_ConvertUTF16toUTF8(module.mName).get()); + } + + size_t numFrames = stack.GetStackSize(); + sha1Stream.Printf("%u\n", (unsigned)numFrames); + for (size_t i = 0; i < numFrames; ++i) { + const mozilla::Telemetry::ProcessedStack::Frame& frame = stack.GetFrame(i); + // NOTE: We write the offsets, while the atos tool expects a value with + // the virtual address added. For example, running otool -l on the the + // firefox binary shows + // cmd LC_SEGMENT_64 + // cmdsize 632 + // segname __TEXT + // vmaddr 0x0000000100000000 + // so to print the line matching the offset 123 one has to run + // atos -o firefox 0x100000123. + sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset); + } + + mozilla::SHA1Sum::Hash sha1; + sha1Stream.Finish(sha1); + + // Note: These files should be deleted by telemetry once it reads them. If + // there were no telemetry runs by the time we shut down, we just add files + // to the existing ones instead of replacing them. Given that each of these + // files is a bug to be fixed, that is probably the right thing to do. + + // We append the sha1 of the contents to the file name. This provides a simple + // client side deduplication. + nsAutoString finalName(u"Telemetry.LateWriteFinal-"_ns); + for (int i = 0; i < 20; ++i) { + finalName.AppendPrintf("%02x", sha1[i]); + } + RefPtr<nsLocalFile> file = new nsLocalFile(nameAux); + file->RenameTo(nullptr, finalName); +} + +/******************************* Setup/Teardown *******************************/ + +static mozilla::StaticAutoPtr<LateWriteObserver> sLateWriteObserver; + +namespace mozilla { + +void InitLateWriteChecks() { + nsCOMPtr<nsIFile> mozFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); + if (mozFile) { + PathString nativePath = mozFile->NativePath(); + if (nativePath.get()) { + sLateWriteObserver = new LateWriteObserver(nativePath.get()); + } + } +} + +void BeginLateWriteChecks() { + if (sLateWriteObserver) { + IOInterposer::Register(IOInterposeObserver::OpWriteFSync, + sLateWriteObserver); + } +} + +void StopLateWriteChecks() { + if (sLateWriteObserver) { + IOInterposer::Unregister(IOInterposeObserver::OpAll, sLateWriteObserver); + // Deallocation would not be thread-safe, and StopLateWriteChecks() is + // called at shutdown and only in special cases. + // sLateWriteObserver = nullptr; + } +} + +void PushSuspendLateWriteChecks() { + if (!tlsSuspendLateWriteChecks.init()) { + return; + } + tlsSuspendLateWriteChecks.set(tlsSuspendLateWriteChecks.get() + 1); +} + +void PopSuspendLateWriteChecks() { + if (!tlsSuspendLateWriteChecks.init()) { + return; + } + int current = tlsSuspendLateWriteChecks.get(); + MOZ_ASSERT(current > 0); + tlsSuspendLateWriteChecks.set(current - 1); +} + +} // namespace mozilla diff --git a/xpcom/build/LateWriteChecks.h b/xpcom/build/LateWriteChecks.h new file mode 100644 index 0000000000..6c9ee419f9 --- /dev/null +++ b/xpcom/build/LateWriteChecks.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_LateWriteChecks_h +#define mozilla_LateWriteChecks_h + +// This file, along with LateWriteChecks.cpp, serves to check for and report +// late writes. The idea is discover writes to the file system that happens +// during shutdown such that these maybe be moved forward and the process may be +// killed without waiting for static destructors. + +namespace mozilla { + +/** Different shutdown check modes */ +enum ShutdownChecksMode { + SCM_CRASH, /** Crash on shutdown check failure */ + SCM_RECORD, /** Record shutdown check violations */ + SCM_NOTHING /** Don't attempt any shutdown checks */ +}; + +/** + * Current shutdown check mode. + * This variable is defined and initialized in nsAppRunner.cpp + */ +extern ShutdownChecksMode gShutdownChecks; + +/** + * Allocate structures and acquire information from XPCOM necessary to do late + * write checks. This function must be invoked before BeginLateWriteChecks() + * and before XPCOM has stopped working. + */ +void InitLateWriteChecks(); + +/** + * Begin recording all writes as late-writes. This function should be called + * when all legitimate writes have occurred. This function does not rely on + * XPCOM as it is designed to be invoked during XPCOM shutdown. + * + * For late-write checks to work you must initialize one or more backends that + * reports IO through the IOInterposer API. PoisonIOInterposer would probably + * be the backend of choice in this case. + * + * Note: BeginLateWriteChecks() must have been invoked before this function. + */ +void BeginLateWriteChecks(); + +/** + * Stop recording all writes as late-writes, call this function when you want + * late-write checks to stop. I.e. exception handling, or the special case on + * Mac described in bug 826029. + */ +void StopLateWriteChecks(); + +/** + * Temporarily suspend late write checks for the current thread. This is useful + * if you're about to perform a write, but it would be fine if this write were + * interrupted or skipped during a fast shutdown. + */ +void PushSuspendLateWriteChecks(); + +/** + * Resume late write checks for the current thread, assuming an ancestor in the + * call stack hasn't also pushed a suspension. + */ +void PopSuspendLateWriteChecks(); + +class MOZ_RAII AutoSuspendLateWriteChecks { + public: + AutoSuspendLateWriteChecks() { PushSuspendLateWriteChecks(); } + ~AutoSuspendLateWriteChecks() { PopSuspendLateWriteChecks(); } +}; + +} // namespace mozilla + +#endif // mozilla_LateWriteChecks_h diff --git a/xpcom/build/MainThreadIOLogger.cpp b/xpcom/build/MainThreadIOLogger.cpp new file mode 100644 index 0000000000..8fe2d9eba5 --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.cpp @@ -0,0 +1,206 @@ +/* -*- 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 "MainThreadIOLogger.h" + +#include "GeckoProfiler.h" +#include "IOInterposerPrivate.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsNativeCharsetUtils.h" +#include "nsThreadUtils.h" + +/** + * This code uses NSPR stuff and STL containers because it must be detached + * from leak checking code; this observer runs until the process terminates. + */ + +#include <prenv.h> +#include <prprf.h> +#include <prthread.h> +#include <vector> + +namespace { + +struct ObservationWithStack { + explicit ObservationWithStack(mozilla::IOInterposeObserver::Observation& aObs, + ProfilerBacktrace* aStack) + : mObservation(aObs), mStack(aStack) { + aObs.Filename(mFilename); + } + + mozilla::IOInterposeObserver::Observation mObservation; + ProfilerBacktrace* mStack; + nsString mFilename; +}; + +class MainThreadIOLoggerImpl final : public mozilla::IOInterposeObserver { + public: + MainThreadIOLoggerImpl(); + ~MainThreadIOLoggerImpl(); + + bool Init(); + + void Observe(Observation& aObservation) override; + + private: + static void sIOThreadFunc(void* aArg); + void IOThreadFunc(); + + mozilla::TimeStamp mLogStartTime; + const char* mFileName; + PRThread* mIOThread; + mozilla::IOInterposer::Monitor mMonitor; + bool mShutdownRequired MOZ_GUARDED_BY(mMonitor); + std::vector<ObservationWithStack> mObservations MOZ_GUARDED_BY(mMonitor); +}; + +static mozilla::StaticAutoPtr<MainThreadIOLoggerImpl> sImpl; + +MainThreadIOLoggerImpl::MainThreadIOLoggerImpl() + : mFileName(nullptr), mIOThread(nullptr), mShutdownRequired(false) {} + +MainThreadIOLoggerImpl::~MainThreadIOLoggerImpl() { + if (!mIOThread) { + return; + } + { + // Scope for lock + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + mMonitor.Notify(); + } + PR_JoinThread(mIOThread); + mIOThread = nullptr; +} + +bool MainThreadIOLoggerImpl::Init() { + if (mFileName) { + // Already initialized + return true; + } + mFileName = PR_GetEnv("MOZ_MAIN_THREAD_IO_LOG"); + if (!mFileName) { + // Can't start + return false; + } + mIOThread = + PR_CreateThread(PR_USER_THREAD, &sIOThreadFunc, this, PR_PRIORITY_LOW, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + if (!mIOThread) { + return false; + } + return true; +} + +/* static */ +void MainThreadIOLoggerImpl::sIOThreadFunc(void* aArg) { + AUTO_PROFILER_REGISTER_THREAD("MainThreadIOLogger"); + + NS_SetCurrentThreadName("MainThreadIOLogger"); + MainThreadIOLoggerImpl* obj = static_cast<MainThreadIOLoggerImpl*>(aArg); + obj->IOThreadFunc(); +} + +void MainThreadIOLoggerImpl::IOThreadFunc() { + PRFileDesc* fd = PR_Open(mFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + PR_IRUSR | PR_IWUSR | PR_IRGRP); + if (!fd) { + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + std::vector<ObservationWithStack>().swap(mObservations); + return; + } + mLogStartTime = mozilla::TimeStamp::Now(); + { + // Scope for lock + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + while (true) { + while (!mShutdownRequired && mObservations.empty()) { + mMonitor.Wait(); + } + if (mShutdownRequired) { + break; + } + // Pull events off the shared array onto a local one + std::vector<ObservationWithStack> observationsToWrite; + observationsToWrite.swap(mObservations); + + // Release the lock so that we're not holding anybody up during I/O + mozilla::IOInterposer::MonitorAutoUnlock unlock(mMonitor); + + // Now write the events. + for (auto i = observationsToWrite.begin(), e = observationsToWrite.end(); + i != e; ++i) { + if (i->mObservation.ObservedOperation() == OpNextStage) { + PR_fprintf( + fd, "%f,NEXT-STAGE\n", + (mozilla::TimeStamp::Now() - mLogStartTime).ToMilliseconds()); + continue; + } + double durationMs = i->mObservation.Duration().ToMilliseconds(); + nsAutoCString nativeFilename; + nativeFilename.AssignLiteral("(not available)"); + if (!i->mFilename.IsEmpty()) { + if (NS_FAILED(NS_CopyUnicodeToNative(i->mFilename, nativeFilename))) { + nativeFilename.AssignLiteral("(conversion failed)"); + } + } + // clang-format off + /** + * Format: + * Start Timestamp (Milliseconds), Operation, Duration (Milliseconds), Event Source, Filename + */ + // clang-format on + if (PR_fprintf( + fd, "%f,%s,%f,%s,%s\n", + (i->mObservation.Start() - mLogStartTime).ToMilliseconds(), + i->mObservation.ObservedOperationString(), durationMs, + i->mObservation.Reference(), nativeFilename.get()) > 0) { + // TODO: Write out the callstack + i->mStack = nullptr; + } + } + } + } + PR_Close(fd); +} + +void MainThreadIOLoggerImpl::Observe(Observation& aObservation) { + if (!mFileName || !IsMainThread()) { + return; + } + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + if (mShutdownRequired) { + // The writer thread isn't running. Don't enqueue any more data. + return; + } + // Passing nullptr as aStack parameter for now + mObservations.push_back(ObservationWithStack(aObservation, nullptr)); + mMonitor.Notify(); +} + +} // namespace + +namespace mozilla { + +namespace MainThreadIOLogger { + +bool Init() { + auto impl = MakeUnique<MainThreadIOLoggerImpl>(); + if (!impl->Init()) { + return false; + } + sImpl = impl.release(); + IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, sImpl); + return true; +} + +} // namespace MainThreadIOLogger + +} // namespace mozilla diff --git a/xpcom/build/MainThreadIOLogger.h b/xpcom/build/MainThreadIOLogger.h new file mode 100644 index 0000000000..cdf3b8728a --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.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_MainThreadIOLogger_h +#define mozilla_MainThreadIOLogger_h + +namespace mozilla { +namespace MainThreadIOLogger { + +bool Init(); + +} // namespace MainThreadIOLogger +} // namespace mozilla + +#endif // mozilla_MainThreadIOLogger_h diff --git a/xpcom/build/NSPRInterposer.cpp b/xpcom/build/NSPRInterposer.cpp new file mode 100644 index 0000000000..184c3793e7 --- /dev/null +++ b/xpcom/build/NSPRInterposer.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 "IOInterposer.h" +#include "NSPRInterposer.h" + +#include "prio.h" +#include "private/pprio.h" +#include "nsDebug.h" +#include "nscore.h" + +#include <sys/param.h> +#ifdef XP_MACOSX +# include <fcntl.h> +#else +# include "prprf.h" +# include <unistd.h> +#endif + +namespace { + +/* Original IO methods */ +PRCloseFN sCloseFn = nullptr; +PRReadFN sReadFn = nullptr; +PRWriteFN sWriteFn = nullptr; +PRFsyncFN sFSyncFn = nullptr; +PRFileInfoFN sFileInfoFn = nullptr; +PRFileInfo64FN sFileInfo64Fn = nullptr; + +static int32_t GetPathFromFd(int32_t aFd, char* aBuf, size_t aBufSize) { +#ifdef XP_MACOSX + NS_ASSERTION(aBufSize >= MAXPATHLEN, + "aBufSize should be a least MAXPATHLEN long"); + + return fcntl(aFd, F_GETPATH, aBuf); +#else + char procPath[32]; + if (PR_snprintf(procPath, sizeof(procPath), "/proc/self/fd/%i", aFd) == + (PRUint32)-1) { + return -1; + } + + int32_t ret = readlink(procPath, aBuf, aBufSize - 1); + if (ret > -1) { + aBuf[ret] = '\0'; + } + + return ret; +#endif +} + +/** + * RAII class for timing the duration of an NSPR I/O call and reporting the + * result to the mozilla::IOInterposeObserver API. + */ +class NSPRIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + explicit NSPRIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + PRFileDesc* aFd) + : mozilla::IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") { + char filename[MAXPATHLEN]; + if (mShouldReport && aFd && + GetPathFromFd(PR_FileDesc2NativeHandle(aFd), filename, + sizeof(filename)) != -1) { + CopyUTF8toUTF16(mozilla::MakeStringSpan(filename), mFilename); + } else { + mFilename.Truncate(); + } + } + + void Filename(nsAString& aFilename) override { aFilename = mFilename; } + + ~NSPRIOAutoObservation() override { Report(); } + + private: + nsString mFilename; +}; + +PRStatus PR_CALLBACK interposedClose(PRFileDesc* aFd) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sCloseFn, "NSPR IO Interposing: sCloseFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpClose, aFd); + return sCloseFn(aFd); +} + +int32_t PR_CALLBACK interposedRead(PRFileDesc* aFd, void* aBuf, int32_t aAmt) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sReadFn, "NSPR IO Interposing: sReadFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFd); + return sReadFn(aFd, aBuf, aAmt); +} + +int32_t PR_CALLBACK interposedWrite(PRFileDesc* aFd, const void* aBuf, + int32_t aAmt) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sWriteFn, "NSPR IO Interposing: sWriteFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd); + return sWriteFn(aFd, aBuf, aAmt); +} + +PRStatus PR_CALLBACK interposedFSync(PRFileDesc* aFd) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFSyncFn, "NSPR IO Interposing: sFSyncFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFd); + return sFSyncFn(aFd); +} + +PRStatus PR_CALLBACK interposedFileInfo(PRFileDesc* aFd, PRFileInfo* aInfo) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFileInfoFn, "NSPR IO Interposing: sFileInfoFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd); + return sFileInfoFn(aFd, aInfo); +} + +PRStatus PR_CALLBACK interposedFileInfo64(PRFileDesc* aFd, + PRFileInfo64* aInfo) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFileInfo64Fn, "NSPR IO Interposing: sFileInfo64Fn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd); + return sFileInfo64Fn(aFd, aInfo); +} + +} // namespace + +namespace mozilla { + +void InitNSPRIOInterposing() { + // Check that we have not interposed any of the IO methods before + MOZ_ASSERT(!sCloseFn && !sReadFn && !sWriteFn && !sFSyncFn && !sFileInfoFn && + !sFileInfo64Fn); + + // We can't actually use this assertion because we initialize this code + // before XPCOM is initialized, so NS_IsMainThread() always returns false. + // MOZ_ASSERT(NS_IsMainThread()); + + // Get IO methods from NSPR and const cast the structure so we can modify it. + PRIOMethods* methods = const_cast<PRIOMethods*>(PR_GetFileMethods()); + + // Something is badly wrong if we don't get IO methods... However, we don't + // want to crash over that in non-debug builds. This is unlikely to happen + // so an assert is enough, no need to report it to the caller. + MOZ_ASSERT(methods); + if (!methods) { + return; + } + + // Store original functions + sCloseFn = methods->close; + sReadFn = methods->read; + sWriteFn = methods->write; + sFSyncFn = methods->fsync; + sFileInfoFn = methods->fileInfo; + sFileInfo64Fn = methods->fileInfo64; + + // Overwrite with our interposed functions + methods->close = &interposedClose; + methods->read = &interposedRead; + methods->write = &interposedWrite; + methods->fsync = &interposedFSync; + methods->fileInfo = &interposedFileInfo; + methods->fileInfo64 = &interposedFileInfo64; +} + +void ClearNSPRIOInterposing() { + // If we have already cleared IO interposing, or not initialized it this is + // actually bad. + MOZ_ASSERT(sCloseFn && sReadFn && sWriteFn && sFSyncFn && sFileInfoFn && + sFileInfo64Fn); + + // Get IO methods from NSPR and const cast the structure so we can modify it. + PRIOMethods* methods = const_cast<PRIOMethods*>(PR_GetFileMethods()); + + // Something is badly wrong if we don't get IO methods... However, we don't + // want to crash over that in non-debug builds. This is unlikely to happen + // so an assert is enough, no need to report it to the caller. + MOZ_ASSERT(methods); + if (!methods) { + return; + } + + // Restore original functions + methods->close = sCloseFn; + methods->read = sReadFn; + methods->write = sWriteFn; + methods->fsync = sFSyncFn; + methods->fileInfo = sFileInfoFn; + methods->fileInfo64 = sFileInfo64Fn; + + // Forget about original functions + sCloseFn = nullptr; + sReadFn = nullptr; + sWriteFn = nullptr; + sFSyncFn = nullptr; + sFileInfoFn = nullptr; + sFileInfo64Fn = nullptr; +} + +} // namespace mozilla diff --git a/xpcom/build/NSPRInterposer.h b/xpcom/build/NSPRInterposer.h new file mode 100644 index 0000000000..3f811df60c --- /dev/null +++ b/xpcom/build/NSPRInterposer.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/. */ + +#ifndef NSPRINTERPOSER_H_ +#define NSPRINTERPOSER_H_ + +namespace mozilla { + +/** + * Initialize IO interposing for NSPR. This will report NSPR read, writes and + * fsyncs to the IOInterposerObserver. It is only safe to call this from the + * main-thread when no other threads are running. + */ +void InitNSPRIOInterposing(); + +/** + * Removes interception of NSPR IO methods as setup by InitNSPRIOInterposing. + * Note, that it is only safe to call this on the main-thread when all other + * threads have stopped. Which is typically the case at shutdown. + */ +void ClearNSPRIOInterposing(); + +} // namespace mozilla + +#endif // NSPRINTERPOSER_H_ diff --git a/xpcom/build/Omnijar.cpp b/xpcom/build/Omnijar.cpp new file mode 100644 index 0000000000..a816cc083f --- /dev/null +++ b/xpcom/build/Omnijar.cpp @@ -0,0 +1,231 @@ +/* -*- 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 "Omnijar.h" + +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "mozilla/GeckoArgs.h" +#include "nsIFile.h" +#include "nsZipArchive.h" +#include "nsNetUtil.h" + +namespace mozilla { + +StaticRefPtr<nsIFile> Omnijar::sPath[2]; +StaticRefPtr<nsZipArchive> Omnijar::sReader[2]; +StaticRefPtr<nsZipArchive> Omnijar::sOuterReader[2]; +bool Omnijar::sInitialized = false; +bool Omnijar::sIsUnified = false; + +static const char* sProp[2] = {NS_GRE_DIR, NS_XPCOM_CURRENT_PROCESS_DIR}; + +#define SPROP(Type) ((Type == mozilla::Omnijar::GRE) ? sProp[GRE] : sProp[APP]) + +void Omnijar::CleanUpOne(Type aType) { + if (sReader[aType]) { + sReader[aType] = nullptr; + } + if (sOuterReader[aType]) { + sOuterReader[aType] = nullptr; + } + sPath[aType] = nullptr; +} + +void Omnijar::InitOne(nsIFile* aPath, Type aType) { + nsCOMPtr<nsIFile> file; + if (aPath) { + file = aPath; + } else { + nsCOMPtr<nsIFile> dir; + nsDirectoryService::gService->Get(SPROP(aType), NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + constexpr auto kOmnijarName = nsLiteralCString{MOZ_STRINGIFY(OMNIJAR_NAME)}; + if (NS_FAILED(dir->Clone(getter_AddRefs(file))) || + NS_FAILED(file->AppendNative(kOmnijarName))) { + return; + } + } + bool isFile; + if (NS_FAILED(file->IsFile(&isFile)) || !isFile) { + // If we're not using an omni.jar for GRE, and we don't have an + // omni.jar for APP, check if both directories are the same. + if ((aType == APP) && (!sPath[GRE])) { + nsCOMPtr<nsIFile> greDir, appDir; + bool equals; + nsDirectoryService::gService->Get(sProp[GRE], NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + nsDirectoryService::gService->Get(sProp[APP], NS_GET_IID(nsIFile), + getter_AddRefs(appDir)); + if (NS_SUCCEEDED(greDir->Equals(appDir, &equals)) && equals) { + sIsUnified = true; + } + } + return; + } + + bool equals; + if ((aType == APP) && (sPath[GRE]) && + NS_SUCCEEDED(sPath[GRE]->Equals(file, &equals)) && equals) { + // If we're using omni.jar on both GRE and APP and their path + // is the same, we're in the unified case. + sIsUnified = true; + return; + } + + RefPtr<nsZipArchive> zipReader = nsZipArchive::OpenArchive(file); + if (!zipReader) { + return; + } + + RefPtr<nsZipArchive> outerReader; + RefPtr<nsZipHandle> handle; + if (NS_SUCCEEDED(nsZipHandle::Init(zipReader, MOZ_STRINGIFY(OMNIJAR_NAME), + getter_AddRefs(handle)))) { + outerReader = zipReader; + zipReader = nsZipArchive::OpenArchive(handle); + if (!zipReader) { + return; + } + } + + CleanUpOne(aType); + sReader[aType] = zipReader; + sOuterReader[aType] = outerReader; + sPath[aType] = file; +} + +void Omnijar::Init(nsIFile* aGrePath, nsIFile* aAppPath) { + InitOne(aGrePath, GRE); + InitOne(aAppPath, APP); + sInitialized = true; +} + +void Omnijar::CleanUp() { + CleanUpOne(GRE); + CleanUpOne(APP); + sInitialized = false; +} + +already_AddRefed<nsZipArchive> Omnijar::GetReader(nsIFile* aPath) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + bool equals; + nsresult rv; + + if (sPath[GRE]) { + rv = sPath[GRE]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(GRE) ? GetOuterReader(GRE) : GetReader(GRE); + } + } + if (sPath[APP]) { + rv = sPath[APP]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(APP) ? GetOuterReader(APP) : GetReader(APP); + } + } + return nullptr; +} + +already_AddRefed<nsZipArchive> Omnijar::GetInnerReader( + nsIFile* aPath, const nsACString& aEntry) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + if (!aEntry.EqualsLiteral(MOZ_STRINGIFY(OMNIJAR_NAME))) { + return nullptr; + } + + bool equals; + nsresult rv; + + if (sPath[GRE]) { + rv = sPath[GRE]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(GRE) ? GetReader(GRE) : nullptr; + } + } + if (sPath[APP]) { + rv = sPath[APP]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(APP) ? GetReader(APP) : nullptr; + } + } + return nullptr; +} + +nsresult Omnijar::GetURIString(Type aType, nsACString& aResult) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + aResult.Truncate(); + + // Return an empty string for APP in the unified case. + if ((aType == APP) && sIsUnified) { + return NS_OK; + } + + nsAutoCString omniJarSpec; + if (sPath[aType]) { + nsresult rv = NS_GetURLSpecFromActualFile(sPath[aType], omniJarSpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aResult = "jar:"; + if (IsNested(aType)) { + aResult += "jar:"; + } + aResult += omniJarSpec; + aResult += "!"; + if (IsNested(aType)) { + aResult += "/" MOZ_STRINGIFY(OMNIJAR_NAME) "!"; + } + } else { + nsCOMPtr<nsIFile> dir; + nsDirectoryService::gService->Get(SPROP(aType), NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + nsresult rv = NS_GetURLSpecFromActualFile(dir, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + aResult += "/"; + return NS_OK; +} + +void Omnijar::ChildProcessInit(int& aArgc, char** aArgv) { + nsCOMPtr<nsIFile> greOmni, appOmni; + + if (auto greOmniStr = geckoargs::sGREOmni.Get(aArgc, aArgv)) { + if (NS_WARN_IF(NS_FAILED( + XRE_GetFileFromPath(*greOmniStr, getter_AddRefs(greOmni))))) { + greOmni = nullptr; + } + } + if (auto appOmniStr = geckoargs::sAppOmni.Get(aArgc, aArgv)) { + if (NS_WARN_IF(NS_FAILED( + XRE_GetFileFromPath(*appOmniStr, getter_AddRefs(appOmni))))) { + appOmni = nullptr; + } + } + + // If we're unified, then only the -greomni flag is present + // (reflecting the state of sPath in the parent process) but that + // path should be used for both (not nullptr, which will try to + // invoke the directory service, which probably isn't up yet.) + if (!appOmni) { + appOmni = greOmni; + } + + if (greOmni) { + Init(greOmni, appOmni); + } else { + // We should never have an appOmni without a greOmni. + MOZ_ASSERT(!appOmni); + } +} + +} /* namespace mozilla */ diff --git a/xpcom/build/Omnijar.h b/xpcom/build/Omnijar.h new file mode 100644 index 0000000000..e334c55478 --- /dev/null +++ b/xpcom/build/Omnijar.h @@ -0,0 +1,180 @@ +/* -*- 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_Omnijar_h +#define mozilla_Omnijar_h + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsZipArchive.h" + +#include "mozilla/StaticPtr.h" + +namespace mozilla { + +class Omnijar { + private: + /** + * Store an nsIFile for an omni.jar. We can store two paths here, one + * for GRE (corresponding to resource://gre/) and one for APP + * (corresponding to resource:/// and resource://app/), but only + * store one when both point to the same location (unified). + */ + static StaticRefPtr<nsIFile> sPath[2]; + + /** + * Cached nsZipArchives for the corresponding sPath + */ + static StaticRefPtr<nsZipArchive> sReader[2]; + + /** + * Cached nsZipArchives for the outer jar, when using nested jars. + * Otherwise nullptr. + */ + static StaticRefPtr<nsZipArchive> sOuterReader[2]; + + /** + * Has Omnijar::Init() been called? + */ + static bool sInitialized; + + /** + * Is using unified GRE/APP jar? + */ + static bool sIsUnified; + + public: + enum Type { GRE = 0, APP = 1 }; + + private: + /** + * Returns whether we are using nested jars. + */ + static inline bool IsNested(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + return !!sOuterReader[aType]; + } + + /** + * Returns a nsZipArchive pointer for the outer jar file when using nested + * jars. Returns nullptr in the same cases GetPath() would, or if not using + * nested jars. + */ + static inline already_AddRefed<nsZipArchive> GetOuterReader(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + RefPtr<nsZipArchive> reader = sOuterReader[aType].get(); + return reader.forget(); + } + + public: + /** + * Returns whether SetBase has been called at least once with + * a valid nsIFile + */ + static inline bool IsInitialized() { return sInitialized; } + + /** + * Initializes the Omnijar API with the given directory or file for GRE and + * APP. Each of the paths given can be: + * - a file path, pointing to the omnijar file, + * - a directory path, pointing to a directory containing an "omni.jar" file, + * - nullptr for autodetection of an "omni.jar" file. + */ + static void Init(nsIFile* aGrePath = nullptr, nsIFile* aAppPath = nullptr); + + /** + * Initializes the Omnijar API for a child process, given its argument + * list, if the `-greomni` flag and optionally also the `-appomni` flag + * is present. (`-appomni` is absent in the case of a unified jar.) If + * neither flag is present, the Omnijar API is not initialized. The + * flags, if present, will be removed from the argument list. + */ + static void ChildProcessInit(int& aArgc, char** aArgv); + + /** + * Cleans up the Omnijar API + */ + static void CleanUp(); + + /** + * Returns an nsIFile pointing to the omni.jar file for GRE or APP. + * Returns nullptr when there is no corresponding omni.jar. + * Also returns nullptr for APP in the unified case. + */ + static inline already_AddRefed<nsIFile> GetPath(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + nsCOMPtr<nsIFile> path = sPath[aType].get(); + return path.forget(); + } + + /** + * Returns whether GRE or APP use an omni.jar. Returns PR_False for + * APP when using an omni.jar in the unified case. + */ + static inline bool HasOmnijar(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + return !!sPath[aType]; + } + + /** + * Returns a nsZipArchive pointer for the omni.jar file for GRE or + * APP. Returns nullptr in the same cases GetPath() would. + */ + static inline already_AddRefed<nsZipArchive> GetReader(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + RefPtr<nsZipArchive> reader = sReader[aType].get(); + return reader.forget(); + } + + /** + * Returns a nsZipArchive pointer for the given path IAOI the given + * path is the omni.jar for either GRE or APP. + */ + static already_AddRefed<nsZipArchive> GetReader(nsIFile* aPath); + + /** + * In the case of a nested omnijar, this returns the inner reader for the + * omnijar if aPath points to the outer archive and aEntry is the omnijar + * entry name. Returns null otherwise. + * In concrete terms: On Android the omnijar is nested inside the apk archive. + * GetReader("path/to.apk") returns the outer reader and GetInnerReader( + * "path/to.apk", "assets/omni.ja") returns the inner reader. + */ + static already_AddRefed<nsZipArchive> GetInnerReader( + nsIFile* aPath, const nsACString& aEntry); + + /** + * Returns the URI string corresponding to the omni.jar or directory + * for GRE or APP. i.e. jar:/path/to/omni.jar!/ for omni.jar and + * /path/to/base/dir/ otherwise. Returns an empty string for APP in + * the unified case. + * The returned URI is guaranteed to end with a slash. + */ + static nsresult GetURIString(Type aType, nsACString& aResult); + + private: + /** + * Used internally, respectively by Init() and CleanUp() + */ + static void InitOne(nsIFile* aPath, Type aType); + static void CleanUpOne(Type aType); +}; /* class Omnijar */ + +/** + * Returns whether or not the currently running build is an unpackaged + * developer build. This check is implemented by looking for omni.ja in the + * the obj/dist dir. We use this routine to detect when the build dir will + * use symlinks to the repo and object dir. + */ +inline bool IsPackagedBuild() { + return Omnijar::HasOmnijar(mozilla::Omnijar::GRE); +} + +} /* namespace mozilla */ + +#endif /* mozilla_Omnijar_h */ diff --git a/xpcom/build/PoisonIOInterposer.h b/xpcom/build/PoisonIOInterposer.h new file mode 100644 index 0000000000..20adeb835b --- /dev/null +++ b/xpcom/build/PoisonIOInterposer.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 mozilla_PoisonIOInterposer_h +#define mozilla_PoisonIOInterposer_h + +#include "mozilla/Types.h" +#include <stdio.h> + +MOZ_BEGIN_EXTERN_C + +/** Register file handle to be ignored by poisoning IO interposer. This function + * and the corresponding UnRegister function are necessary for exchange of + * handles between binaries not using the same CRT on Windows (which happens + * when one of them links the static CRT). In such cases, giving file + * descriptors or FILEs + * doesn't work because _get_osfhandle fails with "invalid parameter". */ +void MozillaRegisterDebugHandle(intptr_t aHandle); + +/** Register file descriptor to be ignored by poisoning IO interposer */ +void MozillaRegisterDebugFD(int aFd); + +/** Register file to be ignored by poisoning IO interposer */ +void MozillaRegisterDebugFILE(FILE* aFile); + +/** Unregister file handle from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugHandle(intptr_t aHandle); + +/** Unregister file descriptor from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugFD(int aFd); + +/** Unregister file from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugFILE(FILE* aFile); + +MOZ_END_EXTERN_C + +#if defined(XP_MACOSX) || defined(XP_WIN) + +# ifdef __cplusplus +namespace mozilla { + +/** + * Check if a file is registered as a debug file. + */ +bool IsDebugFile(intptr_t aFileID); + +/** + * Initialize IO poisoning, this is only safe to do on the main-thread when no + * other threads are running. + * + * Please, note that this probably has performance implications as all + */ +void InitPoisonIOInterposer(); + +# ifdef XP_MACOSX +/** + * Check that writes are dirty before reporting I/O (Mac OS X only) + * This is necessary for late-write checks on Mac OS X, but reading the buffer + * from file to see if we're writing dirty bits is expensive, so we don't want + * to do this for everything else that uses + */ +void OnlyReportDirtyWrites(); +# endif /* XP_MACOSX */ + +/** + * Clear IO poisoning, this is only safe to do on the main-thread when no other + * threads are running. + * Never called! See bug 1647107. + */ +void ClearPoisonIOInterposer(); + +} // namespace mozilla +# endif /* __cplusplus */ + +#else /* defined(XP_MACOSX) || defined(XP_WIN) */ + +# ifdef __cplusplus +namespace mozilla { +inline bool IsDebugFile(intptr_t aFileID) { return true; } +inline void InitPoisonIOInterposer() {} +inline void ClearPoisonIOInterposer() {} +# ifdef XP_MACOSX +inline void OnlyReportDirtyWrites() {} +# endif /* XP_MACOSX */ +} // namespace mozilla +# endif /* __cplusplus */ + +#endif /* XP_WIN || XP_MACOSX */ + +#endif // mozilla_PoisonIOInterposer_h diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp new file mode 100644 index 0000000000..0a25a3d1f8 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerBase.cpp @@ -0,0 +1,244 @@ +/* -*- 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/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" + +#include <algorithm> + +#include "PoisonIOInterposer.h" + +#include "prlock.h" + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +// Auxiliary method to convert file descriptors to ids +#if defined(XP_WIN) +# include <io.h> +inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) { + intptr_t handle = _get_osfhandle(aFd); + if ((handle == -1) || (handle == -2)) { + // -1: Invalid handle. -2: stdin/out/err not associated with a stream. + return mozilla::Nothing(); + } + return mozilla::Some(handle); +} +#else +inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) { + return mozilla::Some<intptr_t>(aFd); +} +#endif /* if not XP_WIN */ + +namespace { + +class DebugFilesAutoLock final { + public: + static PRLock* getDebugFileIDsLock() { + static PRLock* sLock = PR_NewLock(); + return sLock; + } + + DebugFilesAutoLock() { PR_Lock(getDebugFileIDsLock()); } + + ~DebugFilesAutoLock() { PR_Unlock(getDebugFileIDsLock()); } +}; + +// The ChunkedList<T> class implements, at the high level, a non-iterable +// list of instances of T. Its goal is to be somehow minimalist for the +// use case of storing the debug files handles here, with the property of +// not requiring a lock to look up whether it contains a specific value. +// It is also chunked in blocks of chunk_size bytes so that its +// initialization doesn't require a memory allocation, while keeping the +// possibility to increase its size as necessary. Note that chunks are +// never deallocated (except in the destructor). +// All operations are essentially O(N) but N is not expected to be large +// enough to matter. +template <typename T, size_t chunk_size = 64> +class ChunkedList { + struct ListChunk { + static const size_t kLength = + (chunk_size - sizeof(ListChunk*)) / sizeof(mozilla::Atomic<T>); + + mozilla::Atomic<T> mElements[kLength]; + mozilla::UniquePtr<ListChunk> mNext; + + ListChunk() : mNext(nullptr) {} + }; + + ListChunk mList; + mozilla::Atomic<size_t> mLength; + + public: + ChunkedList() : mLength(0) {} + + ~ChunkedList() { + // There can be writes happening after this destructor runs, so keep + // the list contents and don't reset mLength. But if there are more + // elements left than the first chunk can hold, then all hell breaks + // loose for any write that would happen after that because any extra + // chunk would be deallocated, so just crash in that case. + MOZ_RELEASE_ASSERT(mLength <= ListChunk::kLength); + } + + // Add an element at the end of the last chunk of the list. Create a new + // chunk if there is not enough room. + // This is not thread-safe with another thread calling Add or Remove. + void Add(T aValue) { + ListChunk* list = &mList; + size_t position = mLength; + for (; position >= ListChunk::kLength; position -= ListChunk::kLength) { + if (!list->mNext) { + list->mNext.reset(new ListChunk()); + } + list = list->mNext.get(); + } + // Use an order of operations that ensures any racing Contains call + // can't be hurt. + list->mElements[position] = aValue; + mLength++; + } + + // Remove an element from the list by replacing it with the last element + // of the list, and then shrinking the list. + // This is not thread-safe with another thread calling Add or Remove. + void Remove(T aValue) { + if (!mLength) { + return; + } + ListChunk* list = &mList; + size_t last = mLength - 1; + do { + size_t position = 0; + // Look for an element matching the given value. + for (; position < ListChunk::kLength; position++) { + if (aValue == list->mElements[position]) { + ListChunk* last_list = list; + // Look for the last element in the list, starting from where we are + // instead of starting over. + for (; last >= ListChunk::kLength; last -= ListChunk::kLength) { + last_list = last_list->mNext.get(); + } + // Use an order of operations that ensures any racing Contains call + // can't be hurt. + T value = last_list->mElements[last]; + list->mElements[position] = value; + mLength--; + return; + } + } + last -= ListChunk::kLength; + list = list->mNext.get(); + } while (list); + } + + // Returns whether the list contains the given value. It is meant to be safe + // to use without locking, with the tradeoff of being not entirely accurate + // if another thread adds or removes an element while this function runs. + bool Contains(T aValue) { + ListChunk* list = &mList; + // Fix the range of the lookup to whatever the list length is when the + // function is called. + size_t length = mLength; + do { + size_t list_length = ListChunk::kLength; + list_length = std::min(list_length, length); + for (size_t position = 0; position < list_length; position++) { + if (aValue == list->mElements[position]) { + return true; + } + } + length -= ListChunk::kLength; + list = list->mNext.get(); + } while (list); + + return false; + } +}; + +typedef ChunkedList<intptr_t> FdList; + +// Return a list used to hold the IDs of the current debug files. On unix +// an ID is a file descriptor. On Windows it is a file HANDLE. +FdList& getDebugFileIDs() { + static FdList DebugFileIDs; + return DebugFileIDs; +} + +} // namespace + +namespace mozilla { + +// Auxiliary Method to test if a file descriptor is registered to be ignored +// by the poisoning IO interposer +bool IsDebugFile(intptr_t aFileID) { + return getDebugFileIDs().Contains(aFileID); +} + +} // namespace mozilla + +extern "C" { + +void MozillaRegisterDebugHandle(intptr_t aHandle) { + DebugFilesAutoLock lockedScope; + FdList& DebugFileIDs = getDebugFileIDs(); + MOZ_ASSERT(!DebugFileIDs.Contains(aHandle)); + DebugFileIDs.Add(aHandle); +} + +void MozillaRegisterDebugFD(int aFd) { + mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd); + if (!handle.isSome()) { + return; + } + MozillaRegisterDebugHandle(handle.value()); +} + +void MozillaRegisterDebugFILE(FILE* aFile) { + int fd = fileno(aFile); + if (fd == 1 || fd == 2) { + return; + } + MozillaRegisterDebugFD(fd); +} + +void MozillaUnRegisterDebugHandle(intptr_t aHandle) { + DebugFilesAutoLock lockedScope; + FdList& DebugFileIDs = getDebugFileIDs(); + MOZ_ASSERT(DebugFileIDs.Contains(aHandle)); + DebugFileIDs.Remove(aHandle); +} + +void MozillaUnRegisterDebugFD(int aFd) { + mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd); + if (!handle.isSome()) { + return; + } + MozillaUnRegisterDebugHandle(handle.value()); +} + +void MozillaUnRegisterDebugFILE(FILE* aFile) { + int fd = fileno(aFile); + if (fd == 1 || fd == 2) { + return; + } + fflush(aFile); + MozillaUnRegisterDebugFD(fd); +} + +} // extern "C" + +#ifdef MOZ_REPLACE_MALLOC +void mozilla::DebugFdRegistry::RegisterHandle(intptr_t aHandle) { + MozillaRegisterDebugHandle(aHandle); +} + +void mozilla::DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) { + MozillaUnRegisterDebugHandle(aHandle); +} +#endif diff --git a/xpcom/build/PoisonIOInterposerMac.cpp b/xpcom/build/PoisonIOInterposerMac.cpp new file mode 100644 index 0000000000..21eca58757 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerMac.cpp @@ -0,0 +1,348 @@ +/* -*- 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 "PoisonIOInterposer.h" +// Disabled until bug 1658385 is fixed. +#ifndef __aarch64__ +# include "mach_override.h" +#endif + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "nsTraceRefcnt.h" +#include "prio.h" + +#include <algorithm> +#include <vector> + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <aio.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +namespace { + +// Bit tracking if poisoned writes are enabled +static bool sIsEnabled = false; + +// Check if writes are dirty before reporting IO +static bool sOnlyReportDirtyWrites = false; + +// Routines for write validation +bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount); +bool IsIPCWrite(int aFd, const struct stat& aBuf); + +/******************************** IO AutoTimer ********************************/ + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the mozilla::IOInterposeObserver API. + */ +class MacIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, sIsEnabled && !mozilla::IsDebugFile(aFd)), + mFd(aFd), + mHasQueriedFilename(false) {} + + MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd, + const void* aBuf, size_t aCount) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, + sIsEnabled && !mozilla::IsDebugFile(aFd) && + IsValidWrite(aFd, aBuf, aCount)), + mFd(aFd), + mHasQueriedFilename(false) {} + + // Custom implementation of + // mozilla::IOInterposeObserver::Observation::Filename + void Filename(nsAString& aFilename) override; + + ~MacIOAutoObservation() { Report(); } + + private: + int mFd; + bool mHasQueriedFilename; + nsString mFilename; + static const char* sReference; +}; + +const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +void MacIOAutoObservation::Filename(nsAString& aFilename) { + // If mHasQueriedFilename is true, then we already have it + if (mHasQueriedFilename) { + aFilename = mFilename; + return; + } + + char filename[MAXPATHLEN]; + if (fcntl(mFd, F_GETPATH, filename) != -1) { + CopyUTF8toUTF16(filename, mFilename); + } else { + mFilename.Truncate(); + } + mHasQueriedFilename = true; + + aFilename = mFilename; +} + +/****************************** Write Validation ******************************/ + +// We want to detect "actual" writes, not IPC. Some IPC mechanisms are +// implemented with file descriptors, so filter them out. +bool IsIPCWrite(int aFd, const struct stat& aBuf) { + if ((aBuf.st_mode & S_IFMT) == S_IFIFO) { + return true; + } + + if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) { + return false; + } + + sockaddr_storage address; + socklen_t len = sizeof(address); + if (getsockname(aFd, (sockaddr*)&address, &len) != 0) { + return true; // Ignore the aFd if we can't find what it is. + } + + return address.ss_family == AF_UNIX; +} + +// We want to report actual disk IO not things that don't move bits on the disk +bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount) { + // Ignore writes of zero bytes, Firefox does some during shutdown. + if (aCount == 0) { + return false; + } + + { + struct stat buf; + int rv = fstat(aFd, &buf); + if (rv != 0) { + return true; + } + + if (IsIPCWrite(aFd, buf)) { + return false; + } + } + + // For writev we pass a nullptr aWbuf. We should only get here from + // dbm, and it uses write, so assert that we have aWbuf. + if (!aWbuf) { + return true; + } + + // Break, here if we're allowed to report non-dirty writes + if (!sOnlyReportDirtyWrites) { + return true; + } + + // As a really bad hack, accept writes that don't change the on disk + // content. This is needed because dbm doesn't keep track of dirty bits + // and can end up writing the same data to disk twice. Once when the + // user (nss) asks it to sync and once when closing the database. + auto wbuf2 = mozilla::MakeUniqueFallible<char[]>(aCount); + if (!wbuf2) { + return true; + } + off_t pos = lseek(aFd, 0, SEEK_CUR); + if (pos == -1) { + return true; + } + ssize_t r = read(aFd, wbuf2.get(), aCount); + if (r < 0 || (size_t)r != aCount) { + return true; + } + int cmp = memcmp(aWbuf, wbuf2.get(), aCount); + if (cmp != 0) { + return true; + } + off_t pos2 = lseek(aFd, pos, SEEK_SET); + if (pos2 != pos) { + return true; + } + + // Otherwise this is not a valid write + return false; +} + +/*************************** Function Interception ***************************/ + +/** Structure for declaration of function override */ +struct FuncData { + const char* Name; // Name of the function for the ones we use dlsym + const void* Wrapper; // The function that we will replace 'Function' with + void* Function; // The function that will be replaced with 'Wrapper' + void* Buffer; // Will point to the jump buffer that lets us call + // 'Function' after it has been replaced. +}; + +// Wrap aio_write. We have not seen it before, so just assert/report it. +typedef ssize_t (*aio_write_t)(struct aiocb* aAioCbp); +ssize_t wrap_aio_write(struct aiocb* aAioCbp); +FuncData aio_write_data = {0, (void*)wrap_aio_write, (void*)aio_write}; +ssize_t wrap_aio_write(struct aiocb* aAioCbp) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, + aAioCbp->aio_fildes); + + aio_write_t old_write = (aio_write_t)aio_write_data.Buffer; + return old_write(aAioCbp); +} + +// Wrap pwrite-like functions. +// We have not seen them before, so just assert/report it. +typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes, + off_t aOffset); +template <FuncData& foo> +ssize_t wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes, + off_t aOffset) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd); + pwrite_t old_write = (pwrite_t)foo.Buffer; + return old_write(aFd, aBuf, aNumBytes, aOffset); +} + +// Define a FuncData for a pwrite-like functions. +#define DEFINE_PWRITE_DATA(X, NAME) \ + FuncData X##_data = {NAME, (void*)wrap_pwrite_temp<X##_data>}; + +// This exists everywhere. +DEFINE_PWRITE_DATA(pwrite, "pwrite") +// These exist on 32 bit OS X +DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003"); +DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL"); + +typedef ssize_t (*writev_t)(int aFd, const struct iovec* aIov, int aIovCount); +template <FuncData& foo> +ssize_t wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, + nullptr, aIovCount); + writev_t old_write = (writev_t)foo.Buffer; + return old_write(aFd, aIov, aIovCount); +} + +// Define a FuncData for a writev-like functions. +#define DEFINE_WRITEV_DATA(X, NAME) \ + FuncData X##_data = {NAME, (void*)wrap_writev_temp<X##_data>}; + +// This exists everywhere. +DEFINE_WRITEV_DATA(writev, "writev"); +// These exist on 32 bit OS X +DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003"); +DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL"); + +typedef ssize_t (*write_t)(int aFd, const void* aBuf, size_t aCount); +template <FuncData& foo> +ssize_t wrap_write_temp(int aFd, const void* aBuf, size_t aCount) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, aBuf, + aCount); + write_t old_write = (write_t)foo.Buffer; + return old_write(aFd, aBuf, aCount); +} + +// Define a FuncData for a write-like functions. +#define DEFINE_WRITE_DATA(X, NAME) \ + FuncData X##_data = {NAME, (void*)wrap_write_temp<X##_data>}; + +// This exists everywhere. +DEFINE_WRITE_DATA(write, "write"); +// These exist on 32 bit OS X +DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003"); +DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL"); + +FuncData* Functions[] = {&aio_write_data, + + &pwrite_data, &pwrite_NOCANCEL_UNIX2003_data, + &pwrite_UNIX2003_data, &pwrite_NOCANCEL_data, + + &write_data, &write_NOCANCEL_UNIX2003_data, + &write_UNIX2003_data, &write_NOCANCEL_data, + + &writev_data, &writev_NOCANCEL_UNIX2003_data, + &writev_UNIX2003_data, &writev_NOCANCEL_data}; + +const int NumFunctions = mozilla::ArrayLength(Functions); + +} // namespace + +/******************************** IO Poisoning ********************************/ + +namespace mozilla { + +void InitPoisonIOInterposer() { + // Enable reporting from poisoned write methods + sIsEnabled = true; + + // Make sure we only poison writes once! + static bool WritesArePoisoned = false; + if (WritesArePoisoned) { + return; + } + WritesArePoisoned = true; + + // stdout and stderr are OK. + MozillaRegisterDebugFD(1); + MozillaRegisterDebugFD(2); + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + for (int i = 0; i < NumFunctions; ++i) { + FuncData* d = Functions[i]; + if (!d->Function) { + d->Function = dlsym(RTLD_DEFAULT, d->Name); + } + if (!d->Function) { + continue; + } +#ifndef __aarch64__ + DebugOnly<mach_error_t> t = + mach_override_ptr(d->Function, d->Wrapper, &d->Buffer); + MOZ_ASSERT(t == err_none); +#endif + } +} + +void OnlyReportDirtyWrites() { sOnlyReportDirtyWrites = true; } + +// Never called! See bug 1647107. +void ClearPoisonIOInterposer() { + // Not sure how or if we can unpoison the functions. Would be nice, but no + // worries we won't need to do this anyway. + sIsEnabled = false; +} + +} // namespace mozilla diff --git a/xpcom/build/PoisonIOInterposerStub.cpp b/xpcom/build/PoisonIOInterposerStub.cpp new file mode 100644 index 0000000000..1f3daa3539 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerStub.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 <stdio.h> + +extern "C" { + +void MozillaRegisterDebugFD(int aFd) {} +void MozillaRegisterDebugFILE(FILE* aFile) {} +void MozillaUnRegisterDebugFD(int aFd) {} +void MozillaUnRegisterDebugFILE(FILE* aFile) {} + +} // extern "C" diff --git a/xpcom/build/PoisonIOInterposerWin.cpp b/xpcom/build/PoisonIOInterposerWin.cpp new file mode 100644 index 0000000000..ad9a11dbb1 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerWin.cpp @@ -0,0 +1,510 @@ +/* -*- 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 "PoisonIOInterposer.h" + +#include <algorithm> +#include <stdio.h> +#include <vector> + +#include <io.h> +#include <windows.h> +#include <winternl.h> + +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtilsWin.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/NativeNt.h" +#include "mozilla/SmallArrayLRUCache.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" +#include "nsWindowsDllInterceptor.h" + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +namespace { + +// Keep track of poisoned state. Notice that there is no reason to lock access +// to this variable as it's only changed in InitPoisonIOInterposer and +// ClearPoisonIOInterposer which may only be called on the main-thread when no +// other threads are running. +static bool sIOPoisoned = false; + +/************************ Internal NT API Declarations ************************/ + +/* + * Function pointer declaration for internal NT routine to create/open files. + * For documentation on the NtCreateFile routine, see MSDN. + */ +typedef NTSTATUS(NTAPI* NtCreateFileFn)( + PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess, + ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer, + ULONG aEaLength); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * For documentation on the NtReadFile routine, see ZwReadFile on MSDN. + */ +typedef NTSTATUS(NTAPI* NtReadFileFn)(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, PVOID aBuffer, + ULONG aLength, PLARGE_INTEGER aOffset, + PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS(NTAPI* NtReadFileScatterFn)( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN. + */ +typedef NTSTATUS(NTAPI* NtWriteFileFn)(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS(NTAPI* NtWriteFileGatherFn)( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to flush to disk. + * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile + * on MSDN. + */ +typedef NTSTATUS(NTAPI* NtFlushBuffersFileFn)(HANDLE aFileHandle, + PIO_STATUS_BLOCK aIoStatusBlock); + +typedef struct _FILE_NETWORK_OPEN_INFORMATION* PFILE_NETWORK_OPEN_INFORMATION; +/** + * Function pointer delaration for internal NT routine to query file attributes. + * (equivalent to stat) + */ +typedef NTSTATUS(NTAPI* NtQueryFullAttributesFileFn)( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation); + +/*************************** Auxiliary Declarations ***************************/ + +// Cache of filenames associated with handles. +// `static` to be shared between all calls to `Filename()`. +// This assumes handles are not reused, at least within a windows of 32 +// handles. +// Profiling showed that during startup, around half of `Filename()` calls are +// resolved with the first entry (best case), and 32 entries cover >95% of +// cases, reducing the average `Filename()` cost by 5-10x. +using HandleToFilenameCache = mozilla::SmallArrayLRUCache<HANDLE, nsString, 32>; +static mozilla::UniquePtr<HandleToFilenameCache> sHandleToFilenameCache; + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the mozilla::IOInterposeObserver API. + */ +class WinIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + HANDLE aFileHandle, const LARGE_INTEGER* aOffset) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, + !mozilla::IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle))), + mFileHandle(aFileHandle), + mFileHandleType(GetFileType(aFileHandle)), + mHasQueriedFilename(false) { + if (mShouldReport) { + mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0; + } + } + + WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + nsAString& aFilename) + : mozilla::IOInterposeObserver::Observation(aOp, sReference), + mFileHandle(nullptr), + mFileHandleType(FILE_TYPE_UNKNOWN), + mHasQueriedFilename(false) { + if (mShouldReport) { + nsAutoString dosPath; + if (mozilla::NtPathToDosPath(aFilename, dosPath)) { + mFilename = dosPath; + } else { + // If we can't get a dosPath, what we have is better than nothing. + mFilename = aFilename; + } + mHasQueriedFilename = true; + mOffset.QuadPart = 0; + } + } + + void SetHandle(HANDLE aFileHandle) { + mFileHandle = aFileHandle; + if (aFileHandle) { + // Note: `GetFileType()` is fast enough that we don't need to cache it. + mFileHandleType = GetFileType(aFileHandle); + + if (mHasQueriedFilename) { + // `mHasQueriedFilename` indicates we already have a filename, add it to + // the cache with the now-known handle. + sHandleToFilenameCache->Add(aFileHandle, mFilename); + } + } + } + + const char* FileType() const override; + + void Filename(nsAString& aFilename) override; + + ~WinIOAutoObservation() { Report(); } + + private: + HANDLE mFileHandle; + DWORD mFileHandleType; + LARGE_INTEGER mOffset; + bool mHasQueriedFilename; + nsString mFilename; + static const char* sReference; +}; + +const char* WinIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +void WinIOAutoObservation::Filename(nsAString& aFilename) { + // If mHasQueriedFilename is true, then filename is already stored in + // mFilename + if (mHasQueriedFilename) { + aFilename = mFilename; + return; + } + + if (mFileHandle) { + mFilename = sHandleToFilenameCache->FetchOrAdd(mFileHandle, [&]() { + nsString filename; + if (!mozilla::HandleToFilename(mFileHandle, mOffset, filename)) { + // HandleToFilename could fail (return false) but still have added + // something to `filename`, so it should be cleared in this case. + filename.Truncate(); + } + return filename; + }); + } + mHasQueriedFilename = true; + + aFilename = mFilename; +} + +const char* WinIOAutoObservation::FileType() const { + if (mFileHandle) { + switch (mFileHandleType) { + case FILE_TYPE_CHAR: + return "Char"; + case FILE_TYPE_DISK: + return "File"; + case FILE_TYPE_PIPE: + return "Pipe"; + case FILE_TYPE_REMOTE: + return "Remote"; + case FILE_TYPE_UNKNOWN: + default: + break; + } + } + // Fallback to base class default implementation. + return mozilla::IOInterposeObserver::Observation::FileType(); +} + +/*************************** IO Interposing Methods ***************************/ + +// Function pointers to original functions +static mozilla::WindowsDllInterceptor::FuncHookType<NtCreateFileFn> + gOriginalNtCreateFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtReadFileFn> + gOriginalNtReadFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtReadFileScatterFn> + gOriginalNtReadFileScatter; +static mozilla::WindowsDllInterceptor::FuncHookType<NtWriteFileFn> + gOriginalNtWriteFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtWriteFileGatherFn> + gOriginalNtWriteFileGather; +static mozilla::WindowsDllInterceptor::FuncHookType<NtFlushBuffersFileFn> + gOriginalNtFlushBuffersFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtQueryFullAttributesFileFn> + gOriginalNtQueryFullAttributesFile; + +static NTSTATUS NTAPI InterposedNtCreateFile( + PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess, + ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer, + ULONG aEaLength) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtCreateFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtCreateFile( + aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock, + aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition, + aCreateOptions, aEaBuffer, aEaLength); + } + + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = aObjectAttributes + ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) + : 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpCreateOrOpen, + filename); + + // Execute original function + NTSTATUS status = gOriginalNtCreateFile( + aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock, + aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition, + aCreateOptions, aEaBuffer, aEaLength); + if (NT_SUCCESS(status) && aFileHandle) { + timer.SetHandle(*aFileHandle); + } + return status; +} + +static NTSTATUS NTAPI InterposedNtReadFile(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); +} + +static NTSTATUS NTAPI InterposedNtReadFileScatter( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFileScatter); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); +} + +// Interposed NtWriteFile function +static NTSTATUS NTAPI InterposedNtWriteFile(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); +} + +// Interposed NtWriteFileGather function +static NTSTATUS NTAPI InterposedNtWriteFileGather( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFileGather); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); +} + +static NTSTATUS NTAPI InterposedNtFlushBuffersFile( + HANDLE aFileHandle, PIO_STATUS_BLOCK aIoStatusBlock) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtFlushBuffersFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFileHandle, + nullptr); + + // Execute original function + return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock); +} + +static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtQueryFullAttributesFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); + } + + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = aObjectAttributes + ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) + : 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, filename); + + // Execute original function + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); +} + +} // namespace + +/******************************** IO Poisoning ********************************/ + +// Windows DLL interceptor +static mozilla::WindowsDllInterceptor sNtDllInterceptor; + +namespace mozilla { + +void InitPoisonIOInterposer() { + // Currently we hook the functions not early enough to precede third-party + // injections. Until we implement a compatible way e.g. applying a hook + // in the parent process (bug 1646804), we skip interposing functions under + // the known condition(s). + + // Bug 1679741: Kingsoft Internet Security calls NtReadFile in their thread + // simultaneously when we're applying a hook on NtReadFile. + // Bug 1705042: Symantec applies its own hook on NtReadFile, and ends up + // overwriting part of ours in an incompatible way. + if (::GetModuleHandleW(L"kwsui64.dll") || ::GetModuleHandleW(L"ffm64.dll")) { + return; + } + + // Don't poison twice... as this function may only be invoked on the main + // thread when no other threads are running, it safe to allow multiple calls + // to InitPoisonIOInterposer() without complaining (ie. failing assertions). + if (sIOPoisoned) { + return; + } + sIOPoisoned = true; + + MOZ_RELEASE_ASSERT(!sHandleToFilenameCache); + sHandleToFilenameCache = mozilla::MakeUnique<HandleToFilenameCache>(); + mozilla::RunOnShutdown([]() { + // The interposer may still be active after the final shutdown phase + // (especially since ClearPoisonIOInterposer() is never called, see bug + // 1647107), so we cannot just reset the pointer. Instead we put the cache + // in shutdown mode, to clear its memory and stop caching operations. + sHandleToFilenameCache->Shutdown(); + }); + + // Stdout and Stderr are OK. + MozillaRegisterDebugFD(1); + if (::GetStdHandle(STD_OUTPUT_HANDLE) != ::GetStdHandle(STD_ERROR_HANDLE)) { + MozillaRegisterDebugFD(2); + } + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + // Initialize dll interceptor and add hooks + sNtDllInterceptor.Init("ntdll.dll"); + gOriginalNtCreateFile.Set(sNtDllInterceptor, "NtCreateFile", + &InterposedNtCreateFile); + gOriginalNtReadFile.Set(sNtDllInterceptor, "NtReadFile", + &InterposedNtReadFile); + gOriginalNtReadFileScatter.Set(sNtDllInterceptor, "NtReadFileScatter", + &InterposedNtReadFileScatter); + gOriginalNtWriteFile.Set(sNtDllInterceptor, "NtWriteFile", + &InterposedNtWriteFile); + gOriginalNtWriteFileGather.Set(sNtDllInterceptor, "NtWriteFileGather", + &InterposedNtWriteFileGather); + gOriginalNtFlushBuffersFile.Set(sNtDllInterceptor, "NtFlushBuffersFile", + &InterposedNtFlushBuffersFile); + gOriginalNtQueryFullAttributesFile.Set(sNtDllInterceptor, + "NtQueryFullAttributesFile", + &InterposedNtQueryFullAttributesFile); +} + +void ClearPoisonIOInterposer() { + MOZ_ASSERT(false, "Never called! See bug 1647107"); + if (sIOPoisoned) { + // Destroy the DLL interceptor + sIOPoisoned = false; + sNtDllInterceptor.Clear(); + sHandleToFilenameCache->Clear(); + } +} + +} // namespace mozilla diff --git a/xpcom/build/Services.py b/xpcom/build/Services.py new file mode 100644 index 0000000000..43f010d05b --- /dev/null +++ b/xpcom/build/Services.py @@ -0,0 +1,170 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# NOTE: Although this generates headers and code for C++, using Services.h +# is deprecated in favour of Components.h. + + +services = [] + + +def service(name, iface, contractid): + """Define a convenient service getter""" + services.append((name, iface, contractid)) + + +# The `name` parameter is derived from the `iface` by removing the `nsI` +# prefix. (This often matches the `contractid`, but not always.) +service("ChromeRegistry", "nsIChromeRegistry", "@mozilla.org/chrome/chrome-registry;1") +service("IOService", "nsIIOService", "@mozilla.org/network/io-service;1") +service("ObserverService", "nsIObserverService", "@mozilla.org/observer-service;1") +service("PermissionManager", "nsIPermissionManager", "@mozilla.org/permissionmanager;1") +service( + "AsyncShutdownService", + "nsIAsyncShutdownService", + "@mozilla.org/async-shutdown-service;1", +) + +# The definition file needs access to the definitions of the particular +# interfaces. If you add a new interface here, make sure the necessary includes +# are also listed in the following code snippet. +CPP_INCLUDES = """ +#include "mozilla/Likely.h" +#include "mozilla/Services.h" +#include "mozIThirdPartyUtil.h" +#include "nsComponentManager.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" +#include "nsObserverService.h" +#include "nsXPCOMPrivate.h" +#include "nsIIOService.h" +#include "nsIDirectoryService.h" +#include "nsIChromeRegistry.h" +#include "nsIStringBundle.h" +#include "nsIToolkitChromeRegistry.h" +#include "IHistory.h" +#include "nsIXPConnect.h" +#include "nsIPermissionManager.h" +#include "nsIPrefService.h" +#include "nsIServiceWorkerManager.h" +#include "nsICacheStorageService.h" +#include "nsIStreamTransportService.h" +#include "nsISocketTransportService.h" +#include "nsIURIClassifier.h" +#include "nsIHttpActivityObserver.h" +#include "nsIAsyncShutdown.h" +#include "nsIUUIDGenerator.h" +#include "nsIGfxInfo.h" +#include "nsIURIFixup.h" +#include "nsIBits.h" +#include "nsIXULRuntime.h" +""" + + +##### +# Codegen Logic +# +# The following code consumes the data listed above to generate the files +# Services.h, and Services.cpp which provide access to these service getters in +# C++ code. + + +def services_h(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY Services.py - DO NOT EDIT */ + +#ifndef mozilla_Services_h +#define mozilla_Services_h + +#include "nscore.h" +#include "nsCOMPtr.h" +""" + ) + + for name, iface, contractid in services: + # Write out a forward declaration for the type in question + segs = iface.split("::") + for namespace in segs[:-1]: + output.write("namespace %s {\n" % namespace) + output.write("class %s;\n" % segs[-1]) + for namespace in reversed(segs[:-1]): + output.write("} // namespace %s\n" % namespace) + + # Write out the C-style function signature, and the C++ wrapper + output.write( + """ +#ifdef MOZILLA_INTERNAL_API +namespace mozilla { +namespace services { +/** + * Fetch a cached instance of the %(name)s. + * This function will return nullptr during XPCOM shutdown. + * Prefer using static components to this method. + * WARNING: This method is _not_ threadsafe! + */ +already_AddRefed<%(type)s> Get%(name)s(); +} // namespace services +} // namespace mozilla +#endif // defined(MOZILLA_INTERNAL_API) +""" + % { + "name": name, + "type": iface, + } + ) + + output.write("#endif // !defined(mozilla_Services_h)\n") + + +def services_cpp(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY Services.py - DO NOT EDIT */ +""" + ) + output.write(CPP_INCLUDES) + + for name, iface, contractid in services: + output.write( + """ +static %(type)s* g%(name)s = nullptr; + +namespace mozilla { +namespace services { +already_AddRefed<%(type)s> Get%(name)s() +{ + if (MOZ_UNLIKELY(gXPCOMShuttingDown)) { + return nullptr; + } + if (!g%(name)s) { + nsCOMPtr<%(type)s> os = do_GetService("%(contractid)s"); + os.swap(g%(name)s); + } + return do_AddRef(g%(name)s); +} +} +} +""" + % { + "name": name, + "type": iface, + "contractid": contractid, + } + ) + + output.write( + """ +/** + * Clears service cache, sets gXPCOMShuttingDown + */ +void +mozilla::services::Shutdown() +{ + gXPCOMShuttingDown = true; +""" + ) + for name, iface, contractid in services: + output.write(" NS_IF_RELEASE(g%s);\n" % name) + output.write("}\n") diff --git a/xpcom/build/SmallArrayLRUCache.h b/xpcom/build/SmallArrayLRUCache.h new file mode 100644 index 0000000000..e7ac727652 --- /dev/null +++ b/xpcom/build/SmallArrayLRUCache.h @@ -0,0 +1,199 @@ +/* -*- 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 SmallArrayLRUCache_h +#define SmallArrayLRUCache_h + +#include "mozilla/Mutex.h" + +#include <algorithm> +#include <type_traits> +#include <utility> + +// Uncomment the next line to get shutdown stats about cache usage. +// #define SMALLARRAYLRUCACHE_STATS + +#ifdef SMALLARRAYLRUCACHE_STATS +# include <cstdio> +#endif + +namespace mozilla { + +// Array-based LRU cache, thread-safe. +// Optimized for cases where `FetchOrAdd` is used with the same key most +// recently, and assuming the cost of running the value-builder function is much +// more expensive than going through the whole list. +// Note: No time limits on keeping items. +// TODO: Move to more public place, if this could be used elsewhere; make sure +// the cost/benefits are highlighted. +template <typename Key, typename Value, unsigned LRUCapacity> +class SmallArrayLRUCache { + public: + static_assert(std::is_default_constructible_v<Key>); + static_assert(std::is_trivially_constructible_v<Key>); + static_assert(std::is_trivially_copyable_v<Key>); + static_assert(std::is_default_constructible_v<Value>); + static_assert(LRUCapacity >= 2); + static_assert(LRUCapacity <= 1024, + "This seems a bit big, is this the right cache for your use?"); + + // Reset all stored values to their default, and set cache size to zero. + void Clear() { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + if (mSize == ShutdownSize) { + return; + } + Clear(lock); + } + + // Clear the cache, and then prevent further uses (other functions will do + // nothing). + void Shutdown() { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + if (mSize == ShutdownSize) { + return; + } + Clear(lock); + mSize = ShutdownSize; + } + + // Add a key-value. + template <typename ToValue> + void Add(Key aKey, ToValue&& aValue) { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + + if (mSize == ShutdownSize) { + return; + } + + // Quick add to the front, don't remove possible duplicate handles later in + // the list, they will eventually drop off the end. + KeyAndValue* const item0 = &mLRUArray[0]; + mSize = std::min(mSize + 1, LRUCapacity); + if (MOZ_LIKELY(mSize != 1)) { + // List is not empty. + // Make a hole at the start. + std::move_backward(item0, item0 + mSize - 1, item0 + mSize); + } + item0->mKey = aKey; + item0->mValue = std::forward<ToValue>(aValue); + return; + } + + // Look for the value associated with `aKey` in the cache. + // If not found, run `aValueFunction()`, add it in the cache before returning. + // After shutdown, just run `aValueFunction()`. + template <typename ValueFunction> + Value FetchOrAdd(Key aKey, ValueFunction&& aValueFunction) { + Value value; + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + + if (mSize == ShutdownSize) { + value = std::forward<ValueFunction>(aValueFunction)(); + return value; + } + + KeyAndValue* const item0 = &mLRUArray[0]; + if (MOZ_UNLIKELY(mSize == 0)) { + // List is empty. + value = std::forward<ValueFunction>(aValueFunction)(); + item0->mKey = aKey; + item0->mValue = value; + mSize = 1; + return value; + } + + if (MOZ_LIKELY(item0->mKey == aKey)) { + // This is already at the beginning of the list, we're done. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[0]; +#endif // SMALLARRAYLRUCACHE_STATS + value = item0->mValue; + return value; + } + + for (KeyAndValue* item = item0 + 1; item != item0 + mSize; ++item) { + if (item->mKey == aKey) { + // Found handle in the middle. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[unsigned(item - item0)]; +#endif // SMALLARRAYLRUCACHE_STATS + value = item->mValue; + // Move this item to the start of the list. + std::rotate(item0, item, item + 1); + return value; + } + } + + // Handle was not in the list. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[LRUCapacity]; +#endif // SMALLARRAYLRUCACHE_STATS + { + // Don't lock while doing the potentially-expensive ValueFunction(). + // This means that the list could change when we lock again, but + // it's okay because we'll want to add the new entry at the beginning + // anyway, whatever else is in the list then. + // In the worst case, it could be the same handle as another `FetchOrAdd` + // in parallel, it just means it will be duplicated, so it's a little bit + // less efficient (using the extra space), but not wrong (the next + // `FetchOrAdd` will find the first one). + mozilla::OffTheBooksMutexAutoUnlock unlock(mMutex); + value = std::forward<ValueFunction>(aValueFunction)(); + } + // Make a hole at the start, and put the value there. + mSize = std::min(mSize + 1, LRUCapacity); + std::move_backward(item0, item0 + mSize - 1, item0 + mSize); + item0->mKey = aKey; + item0->mValue = value; + return value; + } + +#ifdef SMALLARRAYLRUCACHE_STATS + ~SmallArrayLRUCache() { + if (mSize != 0 && mSize != ShutdownSize) { + fprintf(stderr, + "***** SmallArrayLRUCache stats: (position -> hit count)\n"); + for (unsigned i = 0; i < mSize; ++i) { + fprintf(stderr, "***** %3u -> %6u\n", i, mCacheFoundAt[i]); + } + fprintf(stderr, "***** not found -> %6u\n", mCacheFoundAt[LRUCapacity]); + } + } +#endif // SMALLARRAYLRUCACHE_STATS + + private: + void Clear(const mozilla::OffTheBooksMutexAutoLock&) MOZ_REQUIRES(mMutex) { + for (KeyAndValue* item = &mLRUArray[0]; item != &mLRUArray[mSize]; ++item) { + item->mValue = Value{}; + } + mSize = 0; + } + + struct KeyAndValue { + Key mKey; + Value mValue; + + KeyAndValue() = default; + KeyAndValue(KeyAndValue&&) = default; + KeyAndValue& operator=(KeyAndValue&&) = default; + }; + + // Special size value that indicates the cache is in shutdown mode. + constexpr static unsigned ShutdownSize = unsigned(-1); + + mozilla::OffTheBooksMutex mMutex{"LRU cache"}; + unsigned mSize MOZ_GUARDED_BY(mMutex) = 0; + KeyAndValue mLRUArray[LRUCapacity] MOZ_GUARDED_BY(mMutex); +#ifdef SMALLARRAYLRUCACHE_STATS + // Hit count for each position in the case. +1 for counting not-found cases. + unsigned mCacheFoundAt[LRUCapacity + 1] MOZ_GUARDED_BY(mMutex) = {0u}; +#endif // SMALLARRAYLRUCACHE_STATS +}; + +} // namespace mozilla + +#endif // SmallArrayLRUCache_h diff --git a/xpcom/build/XPCOM.h b/xpcom/build/XPCOM.h new file mode 100644 index 0000000000..1a04091dc4 --- /dev/null +++ b/xpcom/build/XPCOM.h @@ -0,0 +1,167 @@ +/* -*- 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_XPCOM_h +#define mozilla_XPCOM_h + +// NOTE: the following headers are sorted topologically, not alphabetically. +// Do not reorder them without review from bsmedberg. + +// system headers required by XPCOM headers + +#include <string.h> + +#ifndef MOZILLA_INTERNAL_API +# error "MOZILLA_INTERNAL_API must be defined" +#endif + +// core headers required by pretty much everything else + +#include "nscore.h" + +#include "nsXPCOMCID.h" +#include "nsXPCOM.h" + +#include "nsError.h" +#include "nsDebug.h" + +#include "nsID.h" + +#include "nsISupports.h" + +#include "nsTArray.h" + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" + +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsNativeCharsetUtils.h" + +#include "nsISupportsUtils.h" +#include "nsISupportsImpl.h" + +// core data structures + +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsRefPtrHashtable.h" + +// interfaces that inherit directly from nsISupports + +#include "nsIArray.h" +#include "nsAtom.h" +#include "nsICategoryManager.h" +#include "nsIClassInfo.h" +#include "nsIComponentManager.h" +#include "nsIConsoleListener.h" +#include "nsIConsoleMessage.h" +#include "nsIConsoleService.h" +#include "nsIDebug2.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIEnvironment.h" +#include "nsIEventTarget.h" +#include "nsIException.h" +#include "nsIFactory.h" +#include "nsIFile.h" +#include "nsIINIParser.h" +#include "nsIInputStream.h" +#include "nsIInterfaceRequestor.h" +#include "nsILineInputStream.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsIProcess.h" +#include "nsIProperties.h" +#include "nsIRunnable.h" +#include "nsISeekableStream.h" +#include "nsISerializable.h" +#include "nsIServiceManager.h" +#include "nsIScriptableInputStream.h" +#include "nsISimpleEnumerator.h" +#include "nsIStreamBufferAccess.h" +#include "nsIStringEnumerator.h" +#include "nsIStorageStream.h" +#include "nsISupportsIterators.h" +#include "nsISupportsPrimitives.h" +#include "nsISupportsPriority.h" +#include "nsIThreadManager.h" +#include "nsITimer.h" +#include "nsIUUIDGenerator.h" +#include "nsIUnicharInputStream.h" +#include "nsIUnicharOutputStream.h" +#include "nsIUnicharLineInputStream.h" +#include "nsIVariant.h" +#include "nsIVersionComparator.h" +#include "nsIWritablePropertyBag2.h" + +// interfaces that include something above + +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBinaryInputStream.h" +#include "nsIBinaryOutputStream.h" +#include "nsIConverterInputStream.h" +#include "nsIConverterOutputStream.h" +#include "nsIInputStreamTee.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMutableArray.h" +#include "nsIPersistentProperties2.h" +#include "nsIStringStream.h" +#include "nsIThread.h" +#include "nsIThreadPool.h" + +// interfaces that include something above + +#include "nsILocalFileWin.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIPipe.h" + +#ifdef MOZ_WIDGET_COCOA +# include "nsILocalFileMac.h" +#endif + +// xpcom/glue utility headers + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsWeakReference.h" + +#include "nsArrayEnumerator.h" +#include "nsArrayUtils.h" +#include "nsCRTGlue.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDeque.h" +#include "nsEnumeratorUtils.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/ModuleUtils.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsINIParser.h" +#include "nsProxyRelease.h" +#include "nsTObserverArray.h" +#include "nsTextFormatter.h" +#include "nsThreadUtils.h" +#include "nsVersionComparator.h" +#include "nsXPTCUtils.h" + +// xpcom/base utility headers + +#include "nsAutoRef.h" +#include "nsInterfaceRequestorAgg.h" + +// xpcom/io utility headers + +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" + +#endif // mozilla_XPCOM_h diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp new file mode 100644 index 0000000000..4d6572a501 --- /dev/null +++ b/xpcom/build/XPCOMInit.cpp @@ -0,0 +1,819 @@ +/* -*- 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 "ThreadEventTarget.h" +#include "XPCOMModule.h" + +#include "base/basictypes.h" + +#include "mozilla/AbstractThread.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Poison.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/TaskController.h" +#include "mozilla/Unused.h" +#include "mozilla/XPCOM.h" +#include "mozJSModuleLoader.h" +#include "nsXULAppAPI.h" + +#ifndef ANDROID +# include "nsTerminator.h" +#endif + +#include "nsXPCOMPrivate.h" +#include "nsXPCOMCIDInternal.h" + +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" + +#include "prlink.h" + +#include "nsCycleCollector.h" +#include "nsObserverService.h" + +#include "nsDebugImpl.h" +#include "nsSystemInfo.h" + +#include "nsComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsIServiceManager.h" + +#include "nsThreadManager.h" +#include "nsThreadPool.h" + +#include "nsTimerImpl.h" +#include "TimerThread.h" + +#include "nsThread.h" +#include "nsVersionComparatorImpl.h" + +#include "nsIFile.h" +#include "nsLocalFile.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsMultiplexInputStream.h" + +#include "nsAtomTable.h" +#include "nsISupportsImpl.h" +#include "nsLanguageAtomService.h" + +#include "nsSystemInfo.h" +#include "nsMemoryReporterManager.h" +#include "nss.h" +#include "nsNSSComponent.h" + +#include <locale.h> +#include "mozilla/Services.h" +#include "mozilla/Omnijar.h" +#include "mozilla/ScriptPreloader.h" +#include "mozilla/Telemetry.h" +#include "mozilla/BackgroundHangMonitor.h" + +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/LateWriteChecks.h" + +#include "mozilla/scache/StartupCache.h" + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/message_loop.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/AvailableMemoryTracker.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CountingAllocatorBase.h" +#ifdef MOZ_PHC +# include "mozilla/PHCManager.h" +#endif +#include "mozilla/UniquePtr.h" +#include "mozilla/ServoStyleConsts.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" + +#include "ogg/ogg.h" + +#include "GeckoProfiler.h" +#include "ProfilerControl.h" + +#include "jsapi.h" +#include "js/Initialization.h" +#include "js/Prefs.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "XPCSelfHostedShmem.h" + +#include "gfxPlatform.h" + +using base::AtExitManager; +using mozilla::ipc::BrowserProcessSubThread; + +// From toolkit/library/rust/lib.rs +extern "C" void GkRust_Init(); +extern "C" void GkRust_Shutdown(); + +namespace { + +static AtExitManager* sExitManager; +static MessageLoop* sMessageLoop; +static bool sCommandLineWasInitialized; +static BrowserProcessSubThread* sIOThread; +static mozilla::BackgroundHangMonitor* sMainHangMonitor; + +} /* anonymous namespace */ + +// Registry Factory creation function defined in nsRegistry.cpp +// We hook into this function locally to create and register the registry +// Since noone outside xpcom needs to know about this and nsRegistry.cpp +// does not have a local include file, we are putting this definition +// here rather than in nsIRegistry.h +extern nsresult NS_RegistryGetFactory(nsIFactory** aFactory); +extern nsresult NS_CategoryManagerGetFactory(nsIFactory**); + +#ifdef XP_WIN +extern nsresult CreateAnonTempFileRemover(); +#endif + +nsresult nsThreadManagerGetSingleton(const nsIID& aIID, void** aInstancePtr) { + NS_ASSERTION(aInstancePtr, "null outptr"); + return nsThreadManager::get().QueryInterface(aIID, aInstancePtr); +} + +nsresult nsLocalFileConstructor(const nsIID& aIID, void** aInstancePtr) { + return nsLocalFile::nsLocalFileConstructor(aIID, aInstancePtr); +} + +nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = nullptr; +bool gXPCOMShuttingDown = false; +bool gXPCOMMainThreadEventsAreDoomed = false; +char16_t* gGREBinPath = nullptr; + +// gDebug will be freed during shutdown. +static nsIDebug2* gDebug = nullptr; + +EXPORT_XPCOM_API(nsresult) +NS_GetDebug(nsIDebug2** aResult) { + return nsDebugImpl::Create(NS_GET_IID(nsIDebug2), (void**)aResult); +} + +class ICUReporter final : public nsIMemoryReporter, + public mozilla::CountingAllocatorBase<ICUReporter> { + public: + NS_DECL_ISUPPORTS + + static void* Alloc(const void*, size_t aSize) { + void* result = CountingMalloc(aSize); + if (result == nullptr) { + MOZ_CRASH("Ran out of memory while allocating for ICU"); + } + return result; + } + + static void* Realloc(const void*, void* aPtr, size_t aSize) { + void* result = CountingRealloc(aPtr, aSize); + if (result == nullptr) { + MOZ_CRASH("Ran out of memory while reallocating for ICU"); + } + return result; + } + + static void Free(const void*, void* aPtr) { return CountingFree(aPtr); } + + private: + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/icu", KIND_HEAP, UNITS_BYTES, MemoryAllocated(), + "Memory used by ICU, a Unicode and globalization support library."); + + return NS_OK; + } + + ~ICUReporter() = default; +}; + +NS_IMPL_ISUPPORTS(ICUReporter, nsIMemoryReporter) + +class OggReporter final : public nsIMemoryReporter, + public mozilla::CountingAllocatorBase<OggReporter> { + public: + NS_DECL_ISUPPORTS + + private: + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/media/libogg", KIND_HEAP, UNITS_BYTES, MemoryAllocated(), + "Memory allocated through libogg for Ogg, Theora, and related media " + "files."); + + return NS_OK; + } + + ~OggReporter() = default; +}; + +NS_IMPL_ISUPPORTS(OggReporter, nsIMemoryReporter) + +static bool sInitializedJS = false; + +static void InitializeJS() { +#if defined(ENABLE_WASM_SIMD) && \ + (defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)) + // Update static engine preferences, such as AVX, before + // `JS_InitWithFailureDiagnostic` is called. + JS::SetAVXEnabled(mozilla::StaticPrefs::javascript_options_wasm_simd_avx()); +#endif + + // Set all JS::Prefs. + SET_JS_PREFS_FROM_BROWSER_PREFS; + + const char* jsInitFailureReason = JS_InitWithFailureDiagnostic(); + if (jsInitFailureReason) { + MOZ_CRASH_UNSAFE(jsInitFailureReason); + } +} + +// Note that on OSX, aBinDirectory will point to .app/Contents/Resources/browser +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager** aResult, nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider, + bool aInitJSContext) { + static bool sInitialized = false; + if (sInitialized) { + return NS_ERROR_FAILURE; + } + + sInitialized = true; + + NS_LogInit(); + + NS_InitAtomTable(); + + // We don't have the arguments by hand here. If logging has already been + // initialized by a previous call to LogModule::Init with the arguments + // passed, passing (0, nullptr) is alright here. + mozilla::LogModule::Init(0, nullptr); + + GkRust_Init(); + + nsresult rv = NS_OK; + + // We are not shutting down + gXPCOMShuttingDown = false; + +#ifdef XP_UNIX + // Discover the current value of the umask, and save it where + // nsSystemInfo::Init can retrieve it when necessary. There is no way + // to read the umask without changing it, and the setting is process- + // global, so this must be done while we are still single-threaded; the + // nsSystemInfo object is typically created much later, when some piece + // of chrome JS wants it. The system call is specified as unable to fail. + nsSystemInfo::gUserUmask = ::umask(0777); + ::umask(nsSystemInfo::gUserUmask); +#endif + + // Set up chromium libs + NS_ASSERTION(!sExitManager && !sMessageLoop, "Bad logic!"); + + if (!AtExitManager::AlreadyRegistered()) { + sExitManager = new AtExitManager(); + } + + MessageLoop* messageLoop = MessageLoop::current(); + if (!messageLoop) { + sMessageLoop = new MessageLoopForUI(MessageLoop::TYPE_MOZILLA_PARENT); + sMessageLoop->set_thread_name("Gecko"); + // Set experimental values for main thread hangs: + // 128ms for transient hangs and 8192ms for permanent hangs + sMessageLoop->set_hang_timeouts(128, 8192); + } else if (messageLoop->type() == MessageLoop::TYPE_MOZILLA_CHILD) { + messageLoop->set_thread_name("Gecko_Child"); + messageLoop->set_hang_timeouts(128, 8192); + } + + if (XRE_IsParentProcess() && + !BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO)) { + mozilla::UniquePtr<BrowserProcessSubThread> ioThread = + mozilla::MakeUnique<BrowserProcessSubThread>( + BrowserProcessSubThread::IO); + + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + if (NS_WARN_IF(!ioThread->StartWithOptions(options))) { + return NS_ERROR_FAILURE; + } + + sIOThread = ioThread.release(); + } + + // Establish the main thread here. + rv = nsThreadManager::get().Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + AUTO_PROFILER_INIT2; + + // Set up the timer globals/timer thread + rv = nsTimerImpl::Startup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifndef ANDROID + // If the locale hasn't already been setup by our embedder, + // get us out of the "C" locale and into the system + if (strcmp(setlocale(LC_ALL, nullptr), "C") == 0) { + setlocale(LC_ALL, ""); + } +#endif + + nsDirectoryService::RealInit(); + + bool value; + + if (aBinDirectory) { + rv = aBinDirectory->IsDirectory(&value); + + if (NS_SUCCEEDED(rv) && value) { + nsDirectoryService::gService->SetCurrentProcessDirectory(aBinDirectory); + } + } + + if (aAppFileLocationProvider) { + rv = nsDirectoryService::gService->RegisterProvider( + aAppFileLocationProvider); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsCOMPtr<nsIFile> xpcomLib; + nsDirectoryService::gService->Get(NS_GRE_BIN_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(xpcomLib)); + MOZ_ASSERT(xpcomLib); + + // set gGREBinPath + nsAutoString path; + xpcomLib->GetPath(path); + gGREBinPath = ToNewUnicode(path); + + xpcomLib->AppendNative(nsDependentCString(XPCOM_DLL)); + nsDirectoryService::gService->Set(NS_XPCOM_LIBRARY_FILE, xpcomLib); + + if (!mozilla::Omnijar::IsInitialized()) { + // If you added a new process type that uses NS_InitXPCOM, and you're + // *sure* you don't want NS_InitMinimalXPCOM: in addition to everything + // else you'll probably have to do, please add it to the case in + // GeckoChildProcessHost.cpp which sets the greomni/appomni flags. + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsContentProcess()); + mozilla::Omnijar::Init(); + } + + if ((sCommandLineWasInitialized = !CommandLine::IsInitialized())) { +#ifdef XP_WIN + CommandLine::Init(0, nullptr); +#else + nsCOMPtr<nsIFile> binaryFile; + nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(binaryFile)); + if (NS_WARN_IF(!binaryFile)) { + return NS_ERROR_FAILURE; + } + + rv = binaryFile->AppendNative("nonexistent-executable"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString binaryPath; + rv = binaryFile->GetNativePath(binaryPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + static char const* const argv = {strdup(binaryPath.get())}; + CommandLine::Init(1, &argv); +#endif + } + + NS_ASSERTION(nsComponentManagerImpl::gComponentManager == nullptr, + "CompMgr not null at init"); + + // Create the Component/Service Manager + nsComponentManagerImpl::gComponentManager = new nsComponentManagerImpl(); + NS_ADDREF(nsComponentManagerImpl::gComponentManager); + + // Global cycle collector initialization. + if (!nsCycleCollector_init()) { + return NS_ERROR_UNEXPECTED; + } + + // And start it up for this thread too. + nsCycleCollector_startup(); + + // Register ICU memory functions. This really shouldn't be necessary: the + // JS engine should do this on its own inside JS_Init, and memory-reporting + // code should call a JSAPI function to observe ICU memory usage. But we + // can't define the alloc/free functions in the JS engine, because it can't + // depend on the XPCOM-based memory reporting goop. So for now, we have + // this oddness. + mozilla::SetICUMemoryFunctions(); + + // Do the same for libogg. + ogg_set_mem_functions( + OggReporter::CountingMalloc, OggReporter::CountingCalloc, + OggReporter::CountingRealloc, OggReporter::CountingFree); + + // Initialize the JS engine. + InitializeJS(); + sInitializedJS = true; + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + if (aResult) { + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + } + +#ifdef MOZ_PHC + // This is the earliest possible moment we can start PHC while still being + // able to read prefs. + mozilla::InitPHCState(); +#endif + + // After autoreg, but before we actually instantiate any components, + // add any services listed in the "xpcom-directory-providers" category + // to the directory service. + nsDirectoryService::gService->RegisterCategoryProviders(); + + // Init mozilla::SharedThreadPool (which needs the service manager). + mozilla::SharedThreadPool::InitStatics(); + + mozilla::scache::StartupCache::GetSingleton(); + mozilla::AvailableMemoryTracker::Init(); + + // Notify observers of xpcom autoregistration start + NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, nullptr, + NS_XPCOM_STARTUP_OBSERVER_ID); +#ifdef XP_WIN + CreateAnonTempFileRemover(); +#endif + + // The memory reporter manager is up and running -- register our reporters. + RegisterStrongMemoryReporter(new ICUReporter()); + RegisterStrongMemoryReporter(new OggReporter()); + xpc::SelfHostedShmem::GetSingleton().InitMemoryReporter(); + + mozilla::Telemetry::Init(); + + mozilla::BackgroundHangMonitor::Startup(); + + const MessageLoop* const loop = MessageLoop::current(); + sMainHangMonitor = new mozilla::BackgroundHangMonitor( + loop->thread_name().c_str(), loop->transient_hang_timeout(), + loop->permanent_hang_timeout()); + + mozilla::dom::JSExecutionManager::Initialize(); + + if (aInitJSContext) { + xpc::InitializeJSContext(); + } + + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +NS_InitMinimalXPCOM() { + NS_SetMainThread(); + mozilla::TimeStamp::Startup(); + NS_LogInit(); + NS_InitAtomTable(); + + // We don't have the arguments by hand here. If logging has already been + // initialized by a previous call to LogModule::Init with the arguments + // passed, passing (0, nullptr) is alright here. + mozilla::LogModule::Init(0, nullptr); + + GkRust_Init(); + + nsresult rv = nsThreadManager::get().Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Set up the timer globals/timer thread. + rv = nsTimerImpl::Startup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Create the Component/Service Manager + nsComponentManagerImpl::gComponentManager = new nsComponentManagerImpl(); + NS_ADDREF(nsComponentManagerImpl::gComponentManager); + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + // Global cycle collector initialization. + if (!nsCycleCollector_init()) { + return NS_ERROR_UNEXPECTED; + } + + mozilla::SharedThreadPool::InitStatics(); + mozilla::Telemetry::Init(); + mozilla::BackgroundHangMonitor::Startup(); + + return NS_OK; +} + +// +// NS_ShutdownXPCOM() +// +// The shutdown sequence for xpcom would be +// +// - Notify "xpcom-shutdown" for modules to release primary (root) references +// - Shutdown XPCOM timers +// - Notify "xpcom-shutdown-threads" for thread joins +// - Shutdown the event queues +// - Release the Global Service Manager +// - Release all service instances held by the global service manager +// - Release the Global Service Manager itself +// - Release the Component Manager +// - Release all factories cached by the Component Manager +// - Notify module loaders to shut down +// - Unload Libraries +// - Release Contractid Cache held by Component Manager +// - Release dll abstraction held by Component Manager +// - Release the Registry held by Component Manager +// - Finally, release the component manager itself +// +EXPORT_XPCOM_API(nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr) { + return mozilla::ShutdownXPCOM(aServMgr); +} + +namespace mozilla { + +void SetICUMemoryFunctions() { + static bool sICUReporterInitialized = false; + if (!sICUReporterInitialized) { + if (!JS_SetICUMemoryFunctions(ICUReporter::Alloc, ICUReporter::Realloc, + ICUReporter::Free)) { + MOZ_CRASH("JS_SetICUMemoryFunctions failed."); + } + sICUReporterInitialized = true; + } +} + +nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) { + // Make sure the hang monitor is enabled for shutdown. + BackgroundHangMonitor().NotifyActivity(); + + if (!NS_IsMainThread()) { + MOZ_CRASH("Shutdown on wrong thread"); + } + + // Notify observers of xpcom shutting down + { + // Block it so that the COMPtr will get deleted before we hit + // servicemanager shutdown + + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_UNEXPECTED; + } + + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMWillShutdown); + + // We want the service manager to be the subject of notifications + nsCOMPtr<nsIServiceManager> mgr; + Unused << NS_GetServiceManager(getter_AddRefs(mgr)); + MOZ_DIAGNOSTIC_ASSERT(mgr != nullptr, "Service manager not present!"); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMShutdown, nullptr, do_QueryInterface(mgr)); + + // This must happen after the shutdown of media and widgets, which + // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification. + gfxPlatform::ShutdownLayersIPC(); + + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMShutdownThreads); +#ifdef DEBUG + // Prime an assertion at ThreadEventTarget::Dispatch to avoid late + // dispatches to non main-thread threads. + ThreadEventTarget::XPCOMShutdownThreadsNotificationFinished(); +#endif + + // Shutdown the timer thread and all timers that might still be alive + nsTimerImpl::Shutdown(); + + // Have an extra round of processing after the timers went away. + NS_ProcessPendingEvents(thread); + + // Shutdown all remaining threads. This method does not return until + // all threads created using the thread manager (with the exception of + // the main thread) have exited. + nsThreadManager::get().ShutdownNonMainThreads(); + + RefPtr<nsObserverService> observerService; + CallGetService("@mozilla.org/observer-service;1", + (nsObserverService**)getter_AddRefs(observerService)); + if (observerService) { + observerService->Shutdown(); + } + +#ifdef NS_FREE_PERMANENT_DATA + // In leak-checking / ASAN / etc. builds, shut down the Servo thread-pool, + // which will wait for all the work to be done. For other builds, we don't + // really want to wait on shutdown for possibly slow tasks. + Servo_ShutdownThreadPool(); +#endif + + // XPCOMShutdownFinal is the default phase for ClearOnShutdown. + // This AdvanceShutdownPhase will thus free most ClearOnShutdown()'ed + // smart pointers. Some destructors may fire extra main thread runnables + // that will be processed inside AdvanceShutdownPhase. + AppShutdown::AdvanceShutdownPhase(ShutdownPhase::XPCOMShutdownFinal); + + // Shutdown the main thread, processing our very last round of events, and + // then mark that we've finished main thread event processing. + nsThreadManager::get().ShutdownMainThread(); + gXPCOMMainThreadEventsAreDoomed = true; + + BackgroundHangMonitor().NotifyActivity(); + + mozilla::dom::JSExecutionManager::Shutdown(); + } + + // XPCOM is officially in shutdown mode NOW + // Set this only after the observers have been notified as this + // will cause servicemanager to become inaccessible. + mozilla::services::Shutdown(); + + // We may have AddRef'd for the caller of NS_InitXPCOM, so release it + // here again: + NS_IF_RELEASE(aServMgr); + + // Shutdown global servicemanager + if (nsComponentManagerImpl::gComponentManager) { + nsComponentManagerImpl::gComponentManager->FreeServices(); + } + + // Remove the remaining main thread representations + nsThreadManager::get().ReleaseMainThread(); + AbstractThread::ShutdownMainThread(); + + // Release the directory service + nsDirectoryService::gService = nullptr; + + free(gGREBinPath); + gGREBinPath = nullptr; + + // FIXME: This can cause harmless writes from sqlite committing + // log files. We have to ignore them before we can move + // the mozilla::PoisonWrite call before this point. See bug + // 834945 for the details. + mozJSModuleLoader::UnloadLoaders(); + + // Clear the profiler's JS context before cycle collection. The profiler will + // notify the JS engine that it can let go of any data it's holding on to for + // profiling purposes. + PROFILER_CLEAR_JS_CONTEXT(); + + bool shutdownCollect; +#ifdef NS_FREE_PERMANENT_DATA + shutdownCollect = true; +#else + shutdownCollect = !!PR_GetEnv("MOZ_CC_RUN_DURING_SHUTDOWN"); +#endif + nsCycleCollector_shutdown(shutdownCollect); + + // There can be code trying to refer to global objects during the final cc + // shutdown. This is the phase for such global objects to correctly release. + AppShutdown::AdvanceShutdownPhase(ShutdownPhase::CCPostLastCycleCollection); + + mozilla::scache::StartupCache::DeleteSingleton(); + mozilla::ScriptPreloader::DeleteSingleton(); + + PROFILER_MARKER_UNTYPED("Shutdown xpcom", OTHER); + + // Shutdown xpcom. This will release all loaders and cause others holding + // a refcount to the component manager to release it. + if (nsComponentManagerImpl::gComponentManager) { + DebugOnly<nsresult> rv = + (nsComponentManagerImpl::gComponentManager)->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv.value), "Component Manager shutdown failed."); + } else { + NS_WARNING("Component Manager was never created ..."); + } + + if (sInitializedJS) { + // Shut down the JS engine. + JS_ShutDown(); + sInitializedJS = false; + } + + mozilla::ScriptPreloader::DeleteCacheDataSingleton(); + + // Release shared memory which might be borrowed by the JS engine. + xpc::SelfHostedShmem::Shutdown(); + + // After all threads have been joined and the component manager has been shut + // down, any remaining objects that could be holding NSS resources (should) + // have been released, so we can safely shut down NSS. + if (NSS_IsInitialized()) { + nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); + if (NSS_Shutdown() != SECSuccess) { + // If you're seeing this crash and/or warning, some NSS resources are + // still in use (see bugs 1417680 and 1230312). Set the environment + // variable 'MOZ_IGNORE_NSS_SHUTDOWN_LEAKS' to some value to ignore this. + // Also, if leak checking is enabled, report this as a fake leak instead + // of crashing. +#if defined(DEBUG) && !defined(ANDROID) + if (!getenv("MOZ_IGNORE_NSS_SHUTDOWN_LEAKS") && + !getenv("XPCOM_MEM_BLOAT_LOG") && !getenv("XPCOM_MEM_LEAK_LOG") && + !getenv("XPCOM_MEM_REFCNT_LOG") && !getenv("XPCOM_MEM_COMPTR_LOG")) { + MOZ_CRASH("NSS_Shutdown failed"); + } else { +# ifdef NS_BUILD_REFCNT_LOGGING + // Create a fake leak. + NS_LogCtor((void*)0x100, "NSSShutdownFailed", 100); +# endif // NS_BUILD_REFCNT_LOGGING + NS_WARNING("NSS_Shutdown failed"); + } +#else + NS_WARNING("NSS_Shutdown failed"); +#endif // defined(DEBUG) && !defined(ANDROID) + } + } + + // Finally, release the component manager last because it unloads the + // libraries: + if (nsComponentManagerImpl::gComponentManager) { + nsrefcnt cnt; + NS_RELEASE2(nsComponentManagerImpl::gComponentManager, cnt); + NS_ASSERTION(cnt == 0, "Component Manager being held past XPCOM shutdown."); + } + nsComponentManagerImpl::gComponentManager = nullptr; + nsCategoryManager::Destroy(); + + nsLanguageAtomService::Shutdown(); + + GkRust_Shutdown(); + +#ifdef NS_FREE_PERMANENT_DATA + // As we do shutdown Servo only in leak-checking builds, there may still + // be async parse tasks going on in the Servo thread-pool in other builds. + // CSS parsing heavily uses the atom table, so we can safely drop it only + // if Servo has been stopped, too. + NS_ShutdownAtomTable(); +#endif + + NS_IF_RELEASE(gDebug); + + delete sIOThread; + sIOThread = nullptr; + + delete sMessageLoop; + sMessageLoop = nullptr; + + mozilla::TaskController::Shutdown(); + + if (sCommandLineWasInitialized) { + CommandLine::Terminate(); + sCommandLineWasInitialized = false; + } + + delete sExitManager; + sExitManager = nullptr; + + Omnijar::CleanUp(); + + BackgroundHangMonitor::Shutdown(); + + delete sMainHangMonitor; + sMainHangMonitor = nullptr; + + NS_LogTerm(); + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/build/XPCOMModule.h b/xpcom/build/XPCOMModule.h new file mode 100644 index 0000000000..e98afc21c0 --- /dev/null +++ b/xpcom/build/XPCOMModule.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 XPCOMModule_h +#define XPCOMModule_h + +#include "nsID.h" + +class nsISupports; + +nsresult nsThreadManagerGetSingleton(const nsIID& aIID, void** aInstancePtr); + +nsresult nsLocalFileConstructor(const nsIID& aIID, void** aInstancePtr); + +extern nsresult nsStringInputStreamConstructor(const nsID&, void**); + +#endif // XPCOMModule_h diff --git a/xpcom/build/XPCOMModule.inc b/xpcom/build/XPCOMModule.inc new file mode 100644 index 0000000000..4f860fd08e --- /dev/null +++ b/xpcom/build/XPCOMModule.inc @@ -0,0 +1,3 @@ +#if defined(XP_WIN) + COMPONENT(WINDOWSREGKEY, nsWindowsRegKeyConstructor) +#endif diff --git a/xpcom/build/XREAppData.h b/xpcom/build/XREAppData.h new file mode 100644 index 0000000000..61572dfc76 --- /dev/null +++ b/xpcom/build/XREAppData.h @@ -0,0 +1,231 @@ +/* -*- 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 nsXREAppData_h +#define nsXREAppData_h + +#include <stdint.h> +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsCOMPtr.h" +#include "nsCRTGlue.h" +#include "nsStringFwd.h" +#include "nsIFile.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +namespace mozilla { + +struct StaticXREAppData; + +/** + * Application-specific data needed to start the apprunner. + */ +class XREAppData { + public: + XREAppData() = default; + ~XREAppData() = default; + XREAppData(const XREAppData& aOther) { *this = aOther; } + + explicit XREAppData(const StaticXREAppData& aOther) { *this = aOther; } + + XREAppData& operator=(const StaticXREAppData& aOther); + XREAppData& operator=(const XREAppData& aOther); + XREAppData& operator=(XREAppData&& aOther) = default; + + // Lots of code reads these fields directly like a struct, so rather + // than using UniquePtr directly, use an auto-converting wrapper. + class CharPtr { + public: + explicit CharPtr() = default; + explicit CharPtr(const char* v) { *this = v; } + CharPtr(CharPtr&&) = default; + ~CharPtr() = default; + + CharPtr& operator=(const char* v) { + if (v) { + mValue.reset(NS_xstrdup(v)); + } else { + mValue = nullptr; + } + return *this; + } + CharPtr& operator=(const CharPtr& v) { + *this = (const char*)v; + return *this; + } + + operator const char*() const { return mValue.get(); } + + private: + UniqueFreePtr<const char> mValue; + }; + + /** + * The directory of the application to be run. May be null if the + * xulrunner and the app are installed into the same directory. + */ + nsCOMPtr<nsIFile> directory; + + /** + * The name of the application vendor. This must be ASCII, and is normally + * mixed-case, e.g. "Mozilla". Optional (may be null), but highly + * recommended. Must not be the empty string. + */ + CharPtr vendor; + + /** + * The name of the application. This must be ASCII, and is normally + * mixed-case, e.g. "Firefox". Required (must not be null or an empty + * string). + */ + CharPtr name; + + /** + * The internal name of the application for remoting purposes. When left + * unspecified, "name" is used instead. This must be ASCII, and is normally + * lowercase, e.g. "firefox". Optional (may be null but not an empty string). + */ + CharPtr remotingName; + + /** + * The major version, e.g. "0.8.0+". Optional (may be null), but + * required for advanced application features such as the extension + * manager and update service. Must not be the empty string. + */ + CharPtr version; + + /** + * The application's build identifier, e.g. "2004051604" + */ + CharPtr buildID; + + /** + * The application's UUID. Used by the extension manager to determine + * compatible extensions. Optional, but required for advanced application + * features such as the extension manager and update service. + * + * This has traditionally been in the form + * "{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}" but for new applications + * a more readable form is encouraged: "appname@vendor.tld". Only + * the following characters are allowed: a-z A-Z 0-9 - . @ _ { } * + */ + CharPtr ID; + + /** + * The copyright information to print for the -h commandline flag, + * e.g. "Copyright (c) 2003 mozilla.org". + */ + CharPtr copyright; + + /** + * Combination of NS_XRE_ prefixed flags (defined below). + */ + uint32_t flags = 0; + + /** + * The location of the XRE. XRE_main may not be able to figure this out + * programatically. + */ + nsCOMPtr<nsIFile> xreDirectory; + + /** + * The minimum/maximum compatible XRE version. + */ + CharPtr minVersion; + CharPtr maxVersion; + + /** + * The server URL to send crash reports to. + */ + CharPtr crashReporterURL; + + /** + * The profile directory that will be used. Optional (may be null). Must not + * be the empty string, must be ASCII. The path is split into components + * along the path separator characters '/' and '\'. + * + * The application data directory ("UAppData", see below) is normally + * composed as follows, where $HOME is platform-specific: + * + * UAppData = $HOME[/$vendor]/$name + * + * If present, the 'profile' string will be used instead of the combination of + * vendor and name as follows: + * + * UAppData = $HOME/$profile + */ + CharPtr profile; + + /** + * The application name to use in the User Agent string. + */ + CharPtr UAName; + + /** + * The URL to the source revision for this build of the application. + */ + CharPtr sourceURL; + + /** + * The URL to use to check for updates. + */ + CharPtr updateURL; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox BrokerServices. + */ + sandbox::BrokerServices* sandboxBrokerServices = nullptr; +#endif + + // Returns a name suitable for DBUS services. + static void SanitizeNameForDBus(nsACString&); + void GetDBusAppName(nsACString&) const; +}; + +/** + * Indicates whether or not the profile migrator service may be + * invoked at startup when creating a profile. + */ +#define NS_XRE_ENABLE_PROFILE_MIGRATOR (1 << 1) + +/** + * Indicates whether or not to use Breakpad crash reporting. + */ +#define NS_XRE_ENABLE_CRASH_REPORTER (1 << 3) + +/** + * A static version of the XRE app data is compiled into the application + * so that it is not necessary to read application.ini at startup. + * + * This structure is initialized into and matches nsXREAppData + */ +struct StaticXREAppData { + const char* vendor; + const char* name; + const char* remotingName; + const char* version; + const char* buildID; + const char* ID; + const char* copyright; + uint32_t flags; + const char* minVersion; + const char* maxVersion; + const char* crashReporterURL; + const char* profile; + const char* UAName; + const char* sourceURL; + const char* updateURL; +}; + +} // namespace mozilla + +#endif // XREAppData_h diff --git a/xpcom/build/XREChildData.h b/xpcom/build/XREChildData.h new file mode 100644 index 0000000000..8050fb8afc --- /dev/null +++ b/xpcom/build/XREChildData.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 XREChildData_h +#define XREChildData_h + +#include "mozilla/UniquePtr.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxing/loggingTypes.h" + +namespace sandbox { +class BrokerServices; +class TargetServices; +} // namespace sandbox +#endif + +/** + * Data needed to start a child process. + */ +struct XREChildData { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox TargetServices. + */ + sandbox::TargetServices* sandboxTargetServices = nullptr; + + /** + * Function to provide a logging function to the chromium sandbox code. + */ + mozilla::sandboxing::ProvideLogFunctionCb ProvideLogFunction = nullptr; + + /** + * Chromium sandbox broker services; needed by the remote sandbox + * launcher process. + */ + sandbox::BrokerServices* sandboxBrokerServices = nullptr; +#endif +}; + +#endif // XREChildData_h diff --git a/xpcom/build/XREShellData.h b/xpcom/build/XREShellData.h new file mode 100644 index 0000000000..a0f736f658 --- /dev/null +++ b/xpcom/build/XREShellData.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 XREShellData_h +#define XREShellData_h + +#if defined(LIBFUZZER) +# include "FuzzerRegistry.h" // LibFuzzerDriver +#endif + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +/** + * Data needed by XRE_XPCShellMain. + */ +struct XREShellData { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox BrokerServices. + */ + sandbox::BrokerServices* sandboxBrokerServices; +#endif +#if defined(ANDROID) + FILE* outFile; + FILE* errFile; +#endif +#if defined(LIBFUZZER) + LibFuzzerDriver fuzzerDriver; +#endif +}; + +#endif // XREShellData_h diff --git a/xpcom/build/components.conf b/xpcom/build/components.conf new file mode 100644 index 0000000000..a34076a525 --- /dev/null +++ b/xpcom/build/components.conf @@ -0,0 +1,267 @@ +# -*- 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': '{c521a612-2aad-46db-b6ab-3b821fb150b1}', + 'contract_ids': ['@mozilla.org/binaryinputstream;1'], + 'type': 'nsBinaryInputStream', + 'headers': ['/xpcom/io/nsBinaryStream.h'], + }, + { + 'cid': '{86c37b9a-74e7-4672-844e-6e7dd83ba484}', + 'contract_ids': ['@mozilla.org/binaryoutputstream;1'], + 'type': 'nsBinaryOutputStream', + 'headers': ['/xpcom/io/nsBinaryStream.h'], + }, + { + 'cid': '{61ba33c0-3031-11d3-8cd0-0060b0fc14a3}', + 'contract_ids': ['@mozilla.org/network/protocol;1?name=chrome'], + 'type': 'nsChromeProtocolHandler', + 'headers': ['/chrome/nsChromeProtocolHandler.h'], + 'protocol_config': { + 'scheme': 'chrome', + 'flags': [ + 'URI_STD', + 'URI_IS_UI_RESOURCE', + 'URI_IS_LOCAL_RESOURCE', + 'URI_HAS_WEB_EXPOSED_ORIGIN', + ], + }, + }, + { + 'name': 'ChromeRegistry', + 'cid': '{47049e42-1d87-482a-984d-56ae185e367a}', + 'contract_ids': ['@mozilla.org/chrome/chrome-registry;1'], + 'singleton': True, + 'type': 'nsChromeRegistry', + 'headers': ['/chrome/nsChromeRegistry.h'], + 'constructor': 'nsChromeRegistry::GetSingleton', + }, + { + 'js_name': 'console', + 'cid': '{7e3ff85c-1dd2-11b2-8d4b-eb452cb0ff40}', + 'contract_ids': ['@mozilla.org/consoleservice;1'], + 'interfaces': ['nsIConsoleService'], + 'type': 'nsConsoleService', + 'headers': ['/xpcom/base/nsConsoleService.h'], + 'init_method': 'Init', + }, + { + 'cid': '{678c50b8-6bcb-4ad0-b9b8-c81175955199}', + 'contract_ids': ['@mozilla.org/hash-property-bag;1'], + 'type': 'nsHashPropertyBagCC', + 'headers': ['nsHashPropertyBag.h'], + }, + { + 'cid': '{eb833911-4f49-4623-845f-e58a8e6de4c2}', + 'contract_ids': ['@mozilla.org/io-util;1'], + 'type': 'nsIOUtil', + 'headers': ['/xpcom/io/nsIOUtil.h'], + }, + { + 'cid': '{2e23e220-60be-11d3-8c4a-000064657374}', + 'contract_ids': ['@mozilla.org/file/local;1'], + 'legacy_constructor': 'nsLocalFileConstructor', + }, + { + 'cid': '{00bd71fb-7f09-4ec3-96af-a0b522b77969}', + 'contract_ids': ['@mozilla.org/memory-info-dumper;1'], + 'type': 'nsMemoryInfoDumper', + 'headers': ['mozilla/nsMemoryInfoDumper.h'], + }, + { + 'cid': '{fb97e4f5-32dd-497a-baa2-7d1e55079910}', + 'contract_ids': ['@mozilla.org/memory-reporter-manager;1'], + 'type': 'nsMemoryReporterManager', + 'headers': ['/xpcom/base/nsMemoryReporterManager.h'], + 'init_method': 'Init', + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS, + }, + { + 'cid': '{7b4eeb20-d781-11d4-8a83-0010a4e0c9ca}', + 'contract_ids': ['@mozilla.org/process/util;1'], + 'type': 'nsProcess', + 'headers': ['nsProcess.h'], + }, + { + 'cid': '{aaf68860-f849-40ee-bb7a-b229bce036a3}', + 'contract_ids': ['@mozilla.org/scriptablebase64encoder;1'], + 'type': 'nsScriptableBase64Encoder', + 'headers': ['/xpcom/io/nsScriptableBase64Encoder.h'], + }, + { + 'cid': '{43ebf210-8a7b-4ddb-a83d-b87c51a058db}', + 'contract_ids': ['@mozilla.org/securityconsole/message;1'], + 'type': 'nsSecurityConsoleMessage', + 'headers': ['/xpcom/base/nsSecurityConsoleMessage.h'], + }, + { + 'cid': '{669a9795-6ff7-4ed4-9150-c34ce2971b63}', + 'contract_ids': ['@mozilla.org/storagestream;1'], + 'type': 'nsStorageStream', + 'headers': ['nsStorageStream.h'], + }, + { + 'cid': '{acf8dc41-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-cstring;1'], + 'type': 'nsSupportsCString', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4a-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-char;1'], + 'type': 'nsSupportsChar', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{cbf86871-4ac0-11d3-baea-00805f8a5dd7}', + 'contract_ids': ['@mozilla.org/supports-double;1'], + 'type': 'nsSupportsDouble', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{cbf86870-4ac0-11d3-baea-00805f8a5dd7}', + 'contract_ids': ['@mozilla.org/supports-float;1'], + 'type': 'nsSupportsFloat', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{a99febba-1dd1-11b2-a943-b02334a6d083}', + 'contract_ids': ['@mozilla.org/supports-interface-pointer;1'], + 'type': 'nsSupportsInterfacePointer', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc43-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRBool;1'], + 'type': 'nsSupportsPRBool', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4b-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt16;1'], + 'type': 'nsSupportsPRInt16', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4c-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt32;1'], + 'type': 'nsSupportsPRInt32', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4d-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt64;1'], + 'type': 'nsSupportsPRInt64', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc49-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRTime;1'], + 'type': 'nsSupportsPRTime', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc46-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint16;1'], + 'type': 'nsSupportsPRUint16', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc47-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint32;1'], + 'type': 'nsSupportsPRUint32', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc48-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint64;1'], + 'type': 'nsSupportsPRUint64', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc44-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint8;1'], + 'type': 'nsSupportsPRUint8', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc42-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-string;1'], + 'type': 'nsSupportsString', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'js_name': 'sysinfo', + 'cid': '{d962398a-99e5-49b2-857a-c159049c7f6c}', + 'contract_ids': ['@mozilla.org/system-info;1'], + 'interfaces': ['nsIPropertyBag2', 'nsISystemInfo'], + 'type': 'nsSystemInfo', + 'headers': ['nsSystemInfo.h'], + 'init_method': 'Init', + 'overridable': True, + }, + { + 'js_name': 'tm', + 'cid': '{7a4204c6-e45a-4c37-8ebb-6709a22c917c}', + 'contract_ids': ['@mozilla.org/thread-manager;1'], + 'interfaces': ['nsIThreadManager'], + 'legacy_constructor': 'nsThreadManagerGetSingleton', + 'headers': ['/xpcom/build/XPCOMModule.h'], + }, + { + 'js_name': 'uuid', + 'name': 'UUIDGenerator', + 'cid': '{706d36bb-bf79-4293-81f2-8f6828c18f9d}', + 'contract_ids': ['@mozilla.org/uuid-generator;1'], + 'interfaces': ['nsIUUIDGenerator'], + 'type': 'nsUUIDGenerator', + 'headers': ['/xpcom/base/nsUUIDGenerator.h'], + 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS, + }, + { + 'cid': '{0d6ea1d0-879c-11d5-90ef-0010a4e73d9a}', + 'contract_ids': ['@mozilla.org/variant;1'], + 'type': 'nsVariantCC', + 'headers': ['nsVariant.h'], + }, + { + 'js_name': 'vc', + 'cid': '{c6e47036-ca94-4be3-963a-9abd8705f7a8}', + 'contract_ids': ['@mozilla.org/xpcom/version-comparator;1'], + 'interfaces': ['nsIVersionComparator'], + 'type': 'nsVersionComparatorImpl', + 'headers': ['/xpcom/base/nsVersionComparatorImpl.h'], + }, + { + 'cid': '{dfac10a9-dd24-43cf-a095-6ffa2e4b6a6c}', + 'contract_ids': ['@mozilla.org/xpcom/ini-parser-factory;1'], + 'type': 'nsINIParserFactory', + 'headers': ['/xpcom/ds/nsINIParserImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, +] + +if buildconfig.substs['OS_ARCH'] == 'WINNT': + Classes += [ + { + 'cid': '{a53bc624-d577-4839-b8ec-bb5040a52ff4}', + 'contract_ids': ['@mozilla.org/windows-registry-key;1'], + 'legacy_constructor': 'nsWindowsRegKeyConstructor', + 'headers': ['nsWindowsRegKey.h'], + }, + ] + +if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + Classes += [ + { + 'cid': '{b0f20595-88ce-4738-a1a4-24de78eb8051}', + 'contract_ids': ['@mozilla.org/mac-preferences-reader;1'], + 'type': 'nsMacPreferencesReader', + 'headers': ['mozilla/nsMacPreferencesReader.h'], + }, + ] diff --git a/xpcom/build/gen_process_types.py b/xpcom/build/gen_process_types.py new file mode 100644 index 0000000000..9e16ecfd4c --- /dev/null +++ b/xpcom/build/gen_process_types.py @@ -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/. + +from geckoprocesstypes import process_types + + +def main(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY gen_process_types.py - DO NOT EDIT */ +""" + ) + + for p in process_types: + output.write( + """ +#ifndef SKIP_PROCESS_TYPE_%(allcaps_name)s +GECKO_PROCESS_TYPE(%(enum_value)d, %(enum_name)s, "%(string_name)s", """ + """%(proc_typename)s, %(process_bin_type)s, %(procinfo_typename)s, """ + """%(webidl_typename)s, %(allcaps_name)s) +#endif // SKIP_PROCESS_TYPE_%(allcaps_name)s +""" + % { + "enum_value": p.enum_value, + "enum_name": p.enum_name, + "string_name": p.string_name, + "proc_typename": p.proc_typename, + "process_bin_type": p.process_bin_type, + "procinfo_typename": p.procinfo_typename, + "webidl_typename": p.webidl_typename, + "allcaps_name": p.allcaps_name, + } + ) diff --git a/xpcom/build/mach_override.c b/xpcom/build/mach_override.c new file mode 100644 index 0000000000..1ece045e2c --- /dev/null +++ b/xpcom/build/mach_override.c @@ -0,0 +1,793 @@ +// Copied from upstream at revision 195c13743fe0ebc658714e2a9567d86529f20443. +// mach_override.c semver:1.2.0 +// Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/mit +// https://github.com/rentzsch/mach_override + +#include "mach_override.h" + +#include <mach-o/dyld.h> +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <mach/vm_map.h> +#include <sys/mman.h> + +#include <CoreServices/CoreServices.h> + +/************************** +* +* Constants +* +**************************/ +#pragma mark - +#pragma mark (Constants) + +#define kPageSize 4096 +#if defined(__ppc__) || defined(__POWERPC__) + +long kIslandTemplate[] = { + 0x9001FFFC, // stw r0,-4(SP) + 0x3C00DEAD, // lis r0,0xDEAD + 0x6000BEEF, // ori r0,r0,0xBEEF + 0x7C0903A6, // mtctr r0 + 0x8001FFFC, // lwz r0,-4(SP) + 0x60000000, // nop ; optionally replaced + 0x4E800420 // bctr +}; + +#define kAddressHi 3 +#define kAddressLo 5 +#define kInstructionHi 10 +#define kInstructionLo 11 + +#elif defined(__i386__) + +#define kOriginalInstructionsSize 16 +// On X86 we migh need to instert an add with a 32 bit immediate after the +// original instructions. +#define kMaxFixupSizeIncrease 5 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xE9, 0xEF, 0xBE, 0xAD, 0xDE +}; + +#define kInstructions 0 +#define kJumpAddress kInstructions + kOriginalInstructionsSize + 1 +#elif defined(__x86_64__) + +#define kOriginalInstructionsSize 32 +// On X86-64 we never need to instert a new instruction. +#define kMaxFixupSizeIncrease 0 + +#define kJumpAddress kOriginalInstructionsSize + 6 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +#endif + +/************************** +* +* Data Types +* +**************************/ +#pragma mark - +#pragma mark (Data Types) + +typedef struct { + char instructions[sizeof(kIslandTemplate)]; +} BranchIsland; + +/************************** +* +* Funky Protos +* +**************************/ +#pragma mark - +#pragma mark (Funky Protos) + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress); + + mach_error_t +freeBranchIsland( + BranchIsland *island ); + +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ); +#endif + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ); +void +atomic_mov64( + uint64_t *targetAddress, + uint64_t value ); + + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ); + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ); +#endif + +/******************************************************************************* +* +* Interface +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Interface) + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t makeIslandExecutable(void *address) { + mach_error_t err = err_none; + uintptr_t page = (uintptr_t)address & ~(uintptr_t)(kPageSize-1); + int e = err_none; + e |= mprotect((void *)page, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE); + e |= msync((void *)page, kPageSize, MS_INVALIDATE ); + if (e) { + err = err_cannot_override; + } + return err; +} +#endif + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ) +{ + assert( originalFunctionAddress ); + assert( overrideFunctionAddress ); + + // this addresses overriding such functions as AudioOutputUnitStart() + // test with modified DefaultOutputUnit project +#if defined(__x86_64__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp qword near [rip+0x????????] + originalFunctionAddress=*(void**)((char*)originalFunctionAddress+6+*(int32_t *)((uint16_t*)originalFunctionAddress+1)); + else break; + } +#elif defined(__i386__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp *0x???????? + originalFunctionAddress=**(void***)((uint16_t*)originalFunctionAddress+1); + else break; + } +#endif + + long *originalFunctionPtr = (long*) originalFunctionAddress; + mach_error_t err = err_none; + +#if defined(__ppc__) || defined(__POWERPC__) + // Ensure first instruction isn't 'mfctr'. + #define kMFCTRMask 0xfc1fffff + #define kMFCTRInstruction 0x7c0903a6 + + long originalInstruction = *originalFunctionPtr; + if( !err && ((originalInstruction & kMFCTRMask) == kMFCTRInstruction) ) + err = err_cannot_override; +#elif defined(__i386__) || defined(__x86_64__) + int eatenCount = 0; + int originalInstructionCount = 0; + char originalInstructions[kOriginalInstructionsSize]; + uint8_t originalInstructionSizes[kOriginalInstructionsSize]; + uint64_t jumpRelativeInstruction = 0; // JMP + + Boolean overridePossible = eatKnownInstructions ((unsigned char *)originalFunctionPtr, + &jumpRelativeInstruction, &eatenCount, + originalInstructions, &originalInstructionCount, + originalInstructionSizes ); + if (eatenCount + kMaxFixupSizeIncrease > kOriginalInstructionsSize) { + //printf ("Too many instructions eaten\n"); + overridePossible = false; + } + if (!overridePossible) err = err_cannot_override; + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); +#endif + + // Make the original function implementation writable. + if( !err ) { + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_ALL | VM_PROT_COPY) ); + if( err ) + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_DEFAULT | VM_PROT_COPY) ); + } + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + // Allocate and target the escape island to the overriding function. + BranchIsland *escapeIsland = NULL; + if( !err ) { + err = allocateBranchIsland( &escapeIsland, originalFunctionAddress ); + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + } + +#if defined(__ppc__) || defined(__POWERPC__) + if( !err ) + err = setBranchIslandTarget( escapeIsland, overrideFunctionAddress, 0 ); + + // Build the branch absolute instruction to the escape island. + long branchAbsoluteInstruction = 0; // Set to 0 just to silence warning. + if( !err ) { + long escapeIslandAddress = ((long) escapeIsland) & 0x3FFFFFF; + branchAbsoluteInstruction = 0x48000002 | escapeIslandAddress; + } +#elif defined(__i386__) || defined(__x86_64__) + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + if( !err ) + err = setBranchIslandTarget_i386( escapeIsland, overrideFunctionAddress, 0 ); + + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + // Build the jump relative instruction to the escape island +#endif + + +#if defined(__i386__) || defined(__x86_64__) + if (!err) { + uint32_t addressOffset = ((char*)escapeIsland - (char*)originalFunctionPtr - 5); + addressOffset = OSSwapInt32(addressOffset); + + jumpRelativeInstruction |= 0xE900000000000000LL; + jumpRelativeInstruction |= ((uint64_t)addressOffset & 0xffffffff) << 24; + jumpRelativeInstruction = OSSwapInt64(jumpRelativeInstruction); + } +#endif + + // Optionally allocate & return the reentry island. This may contain relocated + // jmp instructions and so has all the same addressing reachability requirements + // the escape island has to the original function, except the escape island is + // technically our original function. + BranchIsland *reentryIsland = NULL; + if( !err && originalFunctionReentryIsland ) { + err = allocateBranchIsland( &reentryIsland, escapeIsland); + if( !err ) + *originalFunctionReentryIsland = reentryIsland; + } + +#if defined(__ppc__) || defined(__POWERPC__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instruction into the reentry island. + // o Target the reentry island at the 2nd instruction of the + // original function. + // o Replace the original instruction with the branch absolute. + if( !err ) { + int escapeIslandEngaged = false; + do { + if( reentryIsland ) + err = setBranchIslandTarget( reentryIsland, + (void*) (originalFunctionPtr+1), originalInstruction ); + if( !err ) { + escapeIslandEngaged = CompareAndSwap( originalInstruction, + branchAbsoluteInstruction, + (UInt32*)originalFunctionPtr ); + if( !escapeIslandEngaged ) { + // Someone replaced the instruction out from under us, + // re-read the instruction, make sure it's still not + // 'mfctr' and try again. + originalInstruction = *originalFunctionPtr; + if( (originalInstruction & kMFCTRMask) == kMFCTRInstruction) + err = err_cannot_override; + } + } + } while( !err && !escapeIslandEngaged ); + } +#elif defined(__i386__) || defined(__x86_64__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instructions into the reentry island. + // o Target the reentry island at the first non-replaced + // instruction of the original function. + // o Replace the original first instructions with the jump relative. + // + // Note that on i386, we do not support someone else changing the code under our feet + if ( !err ) { + uint32_t offset = (uintptr_t)originalFunctionPtr - (uintptr_t)reentryIsland; + fixupInstructions(offset, originalInstructions, + originalInstructionCount, originalInstructionSizes ); + + if( reentryIsland ) + err = setBranchIslandTarget_i386( reentryIsland, + (void*) ((char *)originalFunctionPtr+eatenCount), originalInstructions ); + // try making islands executable before planting the jmp +#if defined(__x86_64__) || defined(__i386__) + if( !err ) + err = makeIslandExecutable(escapeIsland); + if( !err && reentryIsland ) + err = makeIslandExecutable(reentryIsland); +#endif + if ( !err ) + atomic_mov64((uint64_t *)originalFunctionPtr, jumpRelativeInstruction); + } +#endif + + // Clean up on error. + if( err ) { + if( reentryIsland ) + freeBranchIsland( reentryIsland ); + if( escapeIsland ) + freeBranchIsland( escapeIsland ); + } + + return err; +} + +/******************************************************************************* +* +* Implementation +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Implementation) + +static bool jump_in_range(intptr_t from, intptr_t to) { + intptr_t field_value = to - from - 5; + int32_t field_value_32 = field_value; + return field_value == field_value_32; +} + +/******************************************************************************* + Implementation: Allocates memory for a branch island. + + @param island <- The allocated island. + @result <- mach_error_t + + ***************************************************************************/ + +static mach_error_t +allocateBranchIslandAux( + BranchIsland **island, + void *originalFunctionAddress, + bool forward) +{ + assert( island ); + assert( sizeof( BranchIsland ) <= kPageSize ); + + vm_map_t task_self = mach_task_self(); + vm_address_t original_address = (vm_address_t) originalFunctionAddress; + vm_address_t address = original_address; + + for (;;) { + vm_size_t vmsize = 0; + memory_object_name_t object = 0; + kern_return_t kr = 0; + vm_region_flavor_t flavor = VM_REGION_BASIC_INFO; + // Find the region the address is in. +#if __WORDSIZE == 32 + vm_region_basic_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; + kr = vm_region(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#else + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + kr = vm_region_64(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#endif + if (kr != KERN_SUCCESS) + return kr; + assert((address & (kPageSize - 1)) == 0); + + // Go to the first page before or after this region + vm_address_t new_address = forward ? address + vmsize : address - kPageSize; +#if __WORDSIZE == 64 + if(!jump_in_range(original_address, new_address)) + break; +#endif + address = new_address; + + // Try to allocate this page. + kr = vm_allocate(task_self, &address, kPageSize, 0); + if (kr == KERN_SUCCESS) { + *island = (BranchIsland*) address; + return err_none; + } + if (kr != KERN_NO_SPACE) + return kr; + } + + return KERN_NO_SPACE; +} + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress) +{ + mach_error_t err = + allocateBranchIslandAux(island, originalFunctionAddress, true); + if (!err) + return err; + return allocateBranchIslandAux(island, originalFunctionAddress, false); +} + + +/******************************************************************************* + Implementation: Deallocates memory for a branch island. + + @param island -> The island to deallocate. + @result <- mach_error_t + + ***************************************************************************/ + + mach_error_t +freeBranchIsland( + BranchIsland *island ) +{ + assert( island ); + assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] ); + assert( sizeof( BranchIsland ) <= kPageSize ); + return vm_deallocate( mach_task_self(), (vm_address_t) island, + kPageSize ); +} + +/******************************************************************************* + Implementation: Sets the branch island's target, with an optional + instruction. + + @param island -> The branch island to insert target into. + @param branchTo -> The address of the target. + @param instruction -> Optional instruction to execute prior to branch. Set + to zero for nop. + @result <- mach_error_t + + ***************************************************************************/ +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Fill in the address. + ((short*)island->instructions)[kAddressLo] = ((long) branchTo) & 0x0000FFFF; + ((short*)island->instructions)[kAddressHi] + = (((long) branchTo) >> 16) & 0x0000FFFF; + + // Fill in the (optional) instuction. + if( instruction != 0 ) { + ((short*)island->instructions)[kInstructionLo] + = instruction & 0x0000FFFF; + ((short*)island->instructions)[kInstructionHi] + = (instruction >> 16) & 0x0000FFFF; + } + + //MakeDataExecutable( island->instructions, sizeof( kIslandTemplate ) ); + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + +#if defined(__i386__) + mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // copy original instructions + if (instructions) { + bcopy (instructions, island->instructions + kInstructions, kOriginalInstructionsSize); + } + + // Fill in the address. + int32_t addressOffset = (char *)branchTo - (island->instructions + kJumpAddress + 4); + *((int32_t *)(island->instructions + kJumpAddress)) = addressOffset; + + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + return err_none; +} + +#elif defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Copy original instructions. + if (instructions) { + bcopy (instructions, island->instructions, kOriginalInstructionsSize); + } + + // Fill in the address. + *((uint64_t *)(island->instructions + kJumpAddress)) = (uint64_t)branchTo; + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + + +#if defined(__i386__) || defined(__x86_64__) +// simplistic instruction matching +typedef struct { + unsigned int length; // max 15 + unsigned char mask[15]; // sequence of bytes in memory order + unsigned char constraint[15]; // sequence of bytes in memory order +} AsmInstructionMatch; + +#if defined(__i386__) +static AsmInstructionMatch possibleInstructions[] = { + // clang-format off + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x5, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x55, 0x89, 0xe5, 0xc9, 0xc3} }, // push %ebp; mov %esp,%ebp; leave; ret + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xFF}, {0x55} }, // push %esp + { 0x2, {0xFF, 0xFF}, {0x89, 0xE5} }, // mov %esp,%ebp + { 0x1, {0xFF}, {0x53} }, // push %ebx + { 0x3, {0xFF, 0xFF, 0x00}, {0x83, 0xEC, 0x00} }, // sub 0x??, %esp + { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x81, 0xEC, 0x00, 0x00, 0x00, 0x00} }, // sub 0x??, %esp with 32bit immediate + { 0x1, {0xFF}, {0x57} }, // push %edi + { 0x1, {0xFF}, {0x56} }, // push %esi + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x3, {0xFF, 0x4F, 0x00}, {0x8B, 0x45, 0x00} }, // mov $imm(%ebp), %reg + { 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax + { 0x6, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0xE8, 0x00, 0x00, 0x00, 0x00, 0x58} }, // call $imm; pop %eax + { 0x0 } + // clang-format on +}; +#elif defined(__x86_64__) +static AsmInstructionMatch possibleInstructions[] = { + // clang-format off + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xF8}, {0x50} }, // push %rX + { 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x89, 0xE5} }, // mov %rsp,%rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xEC, 0x00} }, // sub 0x??, %rsp + { 0x4, {0xFB, 0xFF, 0x00, 0x00}, {0x48, 0x89, 0x00, 0x00} }, // move onto rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0xFF}, {0x40, 0x0f, 0xbe, 0xce} }, // movsbl %sil, %ecx + { 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX + { 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX + { 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg + { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x2, {0xFF, 0xFF}, {0x89, 0xF8} }, // mov %edi, %eax + + //leaq offset(%rip),%rax + { 0x7, {0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00} }, + + { 0x0 } + // clang-format on +}; +#endif + +static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* instruction) +{ + Boolean match = true; + + size_t i; + for (i=0; i<instruction->length; i++) { + unsigned char mask = instruction->mask[i]; + unsigned char constraint = instruction->constraint[i]; + unsigned char codeValue = code[i]; + + match = ((codeValue & mask) == constraint); + if (!match) break; + } + + return match; +} + +#if defined(__i386__) || defined(__x86_64__) + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ) +{ + Boolean allInstructionsKnown = true; + int totalEaten = 0; + unsigned char* ptr = code; + int remainsToEat = 5; // a JMP instruction takes 5 bytes + int instructionIndex = 0; + + if (howManyEaten) *howManyEaten = 0; + if (originalInstructionCount) *originalInstructionCount = 0; + while (remainsToEat > 0) { + Boolean curInstructionKnown = false; + + // See if instruction matches one we know + AsmInstructionMatch* curInstr = possibleInstructions; + do { + if ((curInstructionKnown = codeMatchesInstruction(ptr, curInstr))) break; + curInstr++; + } while (curInstr->length > 0); + + // if all instruction matches failed, we don't know current instruction then, stop here + if (!curInstructionKnown) { + allInstructionsKnown = false; + fprintf(stderr, "mach_override: some instructions unknown! Need to update mach_override.c\n"); + break; + } + + // At this point, we've matched curInstr + int eaten = curInstr->length; + ptr += eaten; + remainsToEat -= eaten; + totalEaten += eaten; + + if (originalInstructionSizes) originalInstructionSizes[instructionIndex] = eaten; + instructionIndex += 1; + if (originalInstructionCount) *originalInstructionCount = instructionIndex; + } + + + if (howManyEaten) *howManyEaten = totalEaten; + + if (originalInstructions) { + Boolean enoughSpaceForOriginalInstructions = (totalEaten < kOriginalInstructionsSize); + + if (enoughSpaceForOriginalInstructions) { + memset(originalInstructions, 0x90 /* NOP */, kOriginalInstructionsSize); // fill instructions with NOP + bcopy(code, originalInstructions, totalEaten); + } else { + // printf ("Not enough space in island to store original instructions. Adapt the island definition and kOriginalInstructionsSize\n"); + return false; + } + } + + if (allInstructionsKnown) { + // save last 3 bytes of first 64bits of codre we'll replace + uint64_t currentFirst64BitsOfCode = *((uint64_t *)code); + currentFirst64BitsOfCode = OSSwapInt64(currentFirst64BitsOfCode); // back to memory representation + currentFirst64BitsOfCode &= 0x0000000000FFFFFFLL; + + // keep only last 3 instructions bytes, first 5 will be replaced by JMP instr + *newInstruction &= 0xFFFFFFFFFF000000LL; // clear last 3 bytes + *newInstruction |= (currentFirst64BitsOfCode & 0x0000000000FFFFFFLL); // set last 3 bytes + } + + return allInstructionsKnown; +} + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ) +{ + // The start of "leaq offset(%rip),%rax" + static const uint8_t LeaqHeader[] = {0x48, 0x8d, 0x05}; + + int index; + for (index = 0;index < instructionCount;index += 1) + { + if (*(uint8_t*)instructionsToFix == 0xE9) // 32-bit jump relative + { + uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1); + *jumpOffsetPtr += offset; + } + + // leaq offset(%rip),%rax + if (memcmp(instructionsToFix, LeaqHeader, 3) == 0) { + uint32_t *LeaqOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 3); + *LeaqOffsetPtr += offset; + } + + // 32-bit call relative to the next addr; pop %eax + if (*(uint8_t*)instructionsToFix == 0xE8) + { + // Just this call is larger than the jump we use, so we + // know this is the last instruction. + assert(index == (instructionCount - 1)); + assert(instructionSizes[index] == 6); + + // Insert "addl $offset, %eax" in the end so that when + // we jump to the rest of the function %eax has the + // value it would have if eip had been pushed by the + // call in its original position. + uint8_t *op = instructionsToFix; + op += 6; + *op = 0x05; // addl + uint32_t *addImmPtr = (uint32_t*)(op + 1); + *addImmPtr = offset; + } + + instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]); + } +} +#endif + +#if defined(__i386__) +__asm( + ".text;" + ".align 2, 0x90;" + "_atomic_mov64:;" + " pushl %ebp;" + " movl %esp, %ebp;" + " pushl %esi;" + " pushl %ebx;" + " pushl %ecx;" + " pushl %eax;" + " pushl %edx;" + + // atomic push of value to an address + // we use cmpxchg8b, which compares content of an address with + // edx:eax. If they are equal, it atomically puts 64bit value + // ecx:ebx in address. + // We thus put contents of address in edx:eax to force ecx:ebx + // in address + " mov 8(%ebp), %esi;" // esi contains target address + " mov 12(%ebp), %ebx;" + " mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address + " mov (%esi), %eax;" + " mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address + " lock; cmpxchg8b (%esi);" // atomic move. + + // restore registers + " popl %edx;" + " popl %eax;" + " popl %ecx;" + " popl %ebx;" + " popl %esi;" + " popl %ebp;" + " ret" +); +#elif defined(__x86_64__) +void atomic_mov64( + uint64_t *targetAddress, + uint64_t value ) +{ + *targetAddress = value; +} +#endif +#endif diff --git a/xpcom/build/mach_override.h b/xpcom/build/mach_override.h new file mode 100644 index 0000000000..21fbc7ff4d --- /dev/null +++ b/xpcom/build/mach_override.h @@ -0,0 +1,121 @@ +/******************************************************************************* + mach_override.h + Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + Some rights reserved: <http://opensource.org/licenses/mit-license.php> + + ***************************************************************************/ + +/***************************************************************************//** + @mainpage mach_override + @author Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + + This package, coded in C to the Mach API, allows you to override ("patch") + program- and system-supplied functions at runtime. You can fully replace + functions with your implementations, or merely head- or tail-patch the + original implementations. + + Use it by #include'ing mach_override.h from your .c, .m or .mm file(s). + + @todo Discontinue use of Carbon's MakeDataExecutable() and + CompareAndSwap() calls and start using the Mach equivalents, if they + exist. If they don't, write them and roll them in. That way, this + code will be pure Mach, which will make it easier to use everywhere. + Update: MakeDataExecutable() has been replaced by + msync(MS_INVALIDATE). There is an OSCompareAndSwap in libkern, but + I'm currently unsure if I can link against it. May have to roll in + my own version... + @todo Stop using an entire 4K high-allocated VM page per 28-byte escape + branch island. Done right, this will dramatically speed up escape + island allocations when they number over 250. Then again, if you're + overriding more than 250 functions, maybe speed isn't your main + concern... + @todo Add detection of: b, bl, bla, bc, bcl, bcla, bcctrl, bclrl + first-instructions. Initially, we should refuse to override + functions beginning with these instructions. Eventually, we should + dynamically rewrite them to make them position-independent. + @todo Write mach_unoverride(), which would remove an override placed on a + function. Must be multiple-override aware, which means an almost + complete rewrite under the covers, because the target address can't + be spread across two load instructions like it is now since it will + need to be atomically updatable. + @todo Add non-rentry variants of overrides to test_mach_override. + + ***************************************************************************/ + +#ifndef _mach_override_ +#define _mach_override_ + +#include <sys/types.h> +#include <mach/error.h> + +#ifdef __cplusplus + extern "C" { +#endif + +/** + Returned if the function to be overrided begins with a 'mfctr' instruction. +*/ +#define err_cannot_override (err_local|1) + +/************************************************************************************//** + Dynamically overrides the function implementation referenced by + originalFunctionAddress with the implentation pointed to by overrideFunctionAddress. + Optionally returns a pointer to a "reentry island" which, if jumped to, will resume + the original implementation. + + @param originalFunctionAddress -> Required address of the function to + override (with overrideFunctionAddress). + @param overrideFunctionAddress -> Required address to the overriding + function. + @param originalFunctionReentryIsland <- Optional pointer to pointer to the + reentry island. Can be nullptr. + @result <- err_cannot_override if the original + function's implementation begins with + the 'mfctr' instruction. + + ************************************************************************************/ + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ); + +/************************************************************************************//** + + + ************************************************************************************/ + +#ifdef __cplusplus + +#define MACH_OVERRIDE( ORIGINAL_FUNCTION_RETURN_TYPE, ORIGINAL_FUNCTION_NAME, ORIGINAL_FUNCTION_ARGS, ERR ) \ + { \ + static ORIGINAL_FUNCTION_RETURN_TYPE (*ORIGINAL_FUNCTION_NAME##_reenter)ORIGINAL_FUNCTION_ARGS; \ + static bool ORIGINAL_FUNCTION_NAME##_overriden = false; \ + class mach_override_class__##ORIGINAL_FUNCTION_NAME { \ + public: \ + static kern_return_t override(void *originalFunctionPtr) { \ + kern_return_t result = err_none; \ + if (!ORIGINAL_FUNCTION_NAME##_overriden) { \ + ORIGINAL_FUNCTION_NAME##_overriden = true; \ + result = mach_override_ptr( (void*)originalFunctionPtr, \ + (void*)mach_override_class__##ORIGINAL_FUNCTION_NAME::replacement, \ + (void**)&ORIGINAL_FUNCTION_NAME##_reenter ); \ + } \ + return result; \ + } \ + static ORIGINAL_FUNCTION_RETURN_TYPE replacement ORIGINAL_FUNCTION_ARGS { + +#define END_MACH_OVERRIDE( ORIGINAL_FUNCTION_NAME ) \ + } \ + }; \ + \ + err = mach_override_class__##ORIGINAL_FUNCTION_NAME::override((void*)ORIGINAL_FUNCTION_NAME); \ + } + +#endif + +#ifdef __cplusplus + } +#endif +#endif // _mach_override_ diff --git a/xpcom/build/moz.build b/xpcom/build/moz.build new file mode 100644 index 0000000000..5261c04523 --- /dev/null +++ b/xpcom/build/moz.build @@ -0,0 +1,108 @@ +# -*- 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/. + +EXPORTS += [ + "nsXPCOM.h", + "nsXPCOMCID.h", + "nsXPCOMCIDInternal.h", + "nsXULAppAPI.h", + "XREChildData.h", + "XREShellData.h", +] + +EXPORTS.mozilla += [ + "!GeckoProcessTypes.h", + "!Services.h", + "FileLocation.h", + "IOInterposer.h", + "LateWriteChecks.h", + "Omnijar.h", + "PoisonIOInterposer.h", + "SmallArrayLRUCache.h", + "XPCOM.h", + "XREAppData.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla += [ + "perfprobe.h", + ] + SOURCES += [ + "perfprobe.cpp", + "PoisonIOInterposerBase.cpp", + "PoisonIOInterposerWin.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "PoisonIOInterposerBase.cpp", + "PoisonIOInterposerMac.cpp", + ] + if CONFIG["TARGET_CPU"] != "aarch64": + SOURCES += ["mach_override.c"] + SOURCES["mach_override.c"].flags += ["-Wno-unused-function"] +else: + SOURCES += ["PoisonIOInterposerStub.cpp"] + +include("../glue/objs.mozbuild") + +XPCOM_MANIFESTS += [ + "components.conf", +] + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + "FileLocation.cpp", + "IOInterposer.cpp", + "LateWriteChecks.cpp", + "MainThreadIOLogger.cpp", + "Omnijar.cpp", + "XPCOMInit.cpp", +] + +SOURCES += ["!Services.cpp"] + +if CONFIG["OS_ARCH"] != "WINNT": + SOURCES += [ + "NSPRInterposer.cpp", + ] + +GeneratedFile("Services.cpp", script="Services.py", entry_point="services_cpp") +GeneratedFile("Services.h", script="Services.py", entry_point="services_h") +GeneratedFile( + "GeckoProcessTypes.h", + script="gen_process_types.py", + entry_point="main", +) + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +DEFINES["_IMPL_NS_STRINGAPI"] = True +DEFINES["OMNIJAR_NAME"] = CONFIG["OMNIJAR_NAME"] + +LOCAL_INCLUDES += [ + "!..", + "../base", + "../components", + "../ds", + "../glue", + "../io", + "../threads", + "/chrome", + "/docshell/base", + "/js/xpconnect/loader", +] + +if CONFIG["MOZ_VPX"]: + LOCAL_INCLUDES += [ + "/media/libvpx", + ] + +if CONFIG["MOZ_PHC"]: + DEFINES["MOZ_PHC"] = 1 diff --git a/xpcom/build/nsXPCOM.h b/xpcom/build/nsXPCOM.h new file mode 100644 index 0000000000..8c3d66fd10 --- /dev/null +++ b/xpcom/build/nsXPCOM.h @@ -0,0 +1,377 @@ +/* -*- 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 nsXPCOM_h__ +#define nsXPCOM_h__ + +#include "nscore.h" +#include "nsXPCOMCID.h" +#include "mozilla/Attributes.h" +#include "mozilla/Atomics.h" + +#ifdef __cplusplus +# define DECL_CLASS(c) class c +# define DECL_STRUCT(c) struct c +#else +# define DECL_CLASS(c) typedef struct c c +# define DECL_STRUCT(c) typedef struct c c +#endif + +DECL_CLASS(nsISupports); +DECL_CLASS(nsIComponentManager); +DECL_CLASS(nsIComponentRegistrar); +DECL_CLASS(nsIServiceManager); +DECL_CLASS(nsIFile); +DECL_CLASS(nsIDirectoryServiceProvider); +DECL_CLASS(nsIMemory); +DECL_CLASS(nsIDebug2); + +#ifdef __cplusplus +extern bool gXPCOMShuttingDown; +extern bool gXPCOMMainThreadEventsAreDoomed; +#endif + +#ifdef __cplusplus +# include "nsStringFwd.h" +#endif + +/** + * Initialises XPCOM. You must call one of the NS_InitXPCOM methods + * before proceeding to use xpcom. The one exception is that you may + * call NS_NewLocalFile to create a nsIFile. + * + * @note Use <CODE>NS_NewLocalFile</CODE> or <CODE>NS_NewNativeLocalFile</CODE> + * to create the file object you supply as the bin directory path in this + * call. The function may be safely called before the rest of XPCOM or + * embedding has been initialised. + * + * @param aResult The service manager. You may pass null. + * + * @param aBinDirectory The directory containing the component + * registry and runtime libraries; + * or use <CODE>nullptr</CODE> to use the working + * directory. + * + * @param aAppFileLocationProvider The object to be used by Gecko that + * specifies to Gecko where to find profiles, the + * component registry preferences and so on; or use + * <CODE>nullptr</CODE> for the default behaviour. + * + * @param aInitJSContext Whether the nsXPCJSContext should be initialized at + * this point. + * + * @see NS_NewLocalFile + * @see nsIFile + * @see nsIDirectoryServiceProvider + * + * @return NS_OK for success; + * NS_ERROR_NOT_INITIALIZED if static globals were not initialized, + * which can happen if XPCOM is reloaded, but did not completly + * shutdown. Other error codes indicate a failure during + * initialisation. + */ +XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager** aResult, nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider, + bool aInitJSContext = true); + +/** + * Initialize only minimal components of XPCOM. This ensures nsThreadManager, + * logging, and timers will work. + */ +XPCOM_API(nsresult) +NS_InitMinimalXPCOM(); + +/** + * Shutdown XPCOM. You must call this method after you are finished + * using xpcom. + * + * @param aServMgr The service manager which was returned by + * NS_InitXPCOM. This will release servMgr. You may pass null. + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + * + * MOZ_CAN_RUN_SCRIPT_BOUNDARY for now, but really it should maybe be + * MOZ_CAN_RUN_SCRIPT. + */ +XPCOM_API(MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr); + +/** + * Public Method to access to the service manager. + * + * @param aResult Interface pointer to the service manager + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetServiceManager(nsIServiceManager** aResult); + +/** + * Public Method to access to the component manager. + * + * @param aResult Interface pointer to the service + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetComponentManager(nsIComponentManager** aResult); + +/** + * Public Method to access to the component registration manager. + * + * @param aResult Interface pointer to the service + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetComponentRegistrar(nsIComponentRegistrar** aResult); + +/** + * Public Method to create an instance of a nsIFile. This function + * may be called prior to NS_InitXPCOM. + * + * @param aPath + * A string which specifies a full file path to a + * location. Relative paths will be treated as an + * error (NS_ERROR_FILE_UNRECOGNIZED_PATH). + * |NS_NewNativeLocalFile|'s path must be in the + * filesystem charset. + * @param aFollowLinks + * This attribute will determine if the nsLocalFile will auto + * resolve symbolic links. By default, this value will be false + * on all non unix systems. On unix, this attribute is effectively + * a noop. + * @param aResult Interface pointer to a new instance of an nsIFile + * + * @return NS_OK for success; + * other error codes indicate a failure. + */ + +#ifdef __cplusplus + +XPCOM_API(nsresult) +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult); + +XPCOM_API(nsresult) +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult); + +// Use NS_NewLocalFile if you already have a UTF-16 string. +// Otherwise non-ASCII paths will break on some platforms +// including Windows. +class NS_ConvertUTF16toUTF8; +nsresult NS_NewNativeLocalFile(const NS_ConvertUTF16toUTF8& aPath, + bool aFollowLinks, nsIFile** aResult) = delete; + +#endif + +/** + * Support for warnings, assertions, and debugging breaks. + */ + +enum { + NS_DEBUG_WARNING = 0, + NS_DEBUG_ASSERTION = 1, + NS_DEBUG_BREAK = 2, + NS_DEBUG_ABORT = 3 +}; + +/** + * Print a runtime assertion. This function is available in both debug and + * release builds. + * + * @note Based on the value of aSeverity and the XPCOM_DEBUG_BREAK + * environment variable, this function may cause the application to + * print the warning, print a stacktrace, break into a debugger, or abort + * immediately. + * + * @param aSeverity A NS_DEBUG_* value + * @param aStr A readable error message (ASCII, may be null) + * @param aExpr The expression evaluated (may be null) + * @param aFile The source file containing the assertion (may be null) + * @param aLine The source file line number (-1 indicates no line number) + */ +XPCOM_API(void) +NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine); + +/** + * Perform a stack-walk to a debugging log under various + * circumstances. Used to aid debugging of leaked object graphs. + * + * The NS_Log* functions are available in both debug and release + * builds of XPCOM, but the output will be useless unless binary + * debugging symbols for all modules in the stacktrace are available. + */ + +/** + * By default, refcount logging is enabled at NS_InitXPCOM and + * refcount statistics are printed at NS_ShutdownXPCOM. NS_LogInit and + * NS_LogTerm allow applications to enable logging earlier and delay + * printing of logging statistics. They should always be used as a + * matched pair. + */ +XPCOM_API(void) NS_LogInit(); + +XPCOM_API(void) NS_LogTerm(); + +#ifdef __cplusplus +/** + * A helper class that calls NS_LogInit in its constructor and + * NS_LogTerm in its destructor. + */ + +class ScopedLogging { + public: + ScopedLogging() { NS_LogInit(); } + + ~ScopedLogging() { NS_LogTerm(); } +}; +#endif + +/** + * Log construction and destruction of objects. Processing tools can use the + * stacktraces printed by these functions to identify objects that are being + * leaked. + * + * @param aPtr A pointer to the concrete object. + * @param aTypeName The class name of the type + * @param aInstanceSize The size of the type + */ + +XPCOM_API(void) +NS_LogCtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize); + +XPCOM_API(void) +NS_LogDtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize); + +/** + * Log a stacktrace when an XPCOM object's refcount is incremented or + * decremented. Processing tools can use the stacktraces printed by these + * functions to identify objects that were leaked due to XPCOM references. + * + * @param aPtr A pointer to the concrete object + * @param aNewRefCnt The new reference count. + * @param aTypeName The class name of the type + * @param aInstanceSize The size of the type + */ +XPCOM_API(void) +NS_LogAddRef(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName, + uint32_t aInstanceSize); + +XPCOM_API(void) +NS_LogRelease(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName); + +/** + * Log reference counting performed by COMPtrs. Processing tools can + * use the stacktraces printed by these functions to simplify reports + * about leaked objects generated from the data printed by + * NS_LogAddRef/NS_LogRelease. + * + * @param aCOMPtr the address of the COMPtr holding a strong reference + * @param aObject the object being referenced by the COMPtr + */ + +XPCOM_API(void) NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject); + +XPCOM_API(void) NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject); + +/** + * The XPCOM cycle collector analyzes and breaks reference cycles between + * participating XPCOM objects. All objects in the cycle must implement + * nsCycleCollectionParticipant to break cycles correctly. + */ + +#ifdef __cplusplus + +class nsCycleCollectionParticipant; +class nsCycleCollectingAutoRefCnt; + +XPCOM_API(void) +NS_CycleCollectorSuspect3(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete); + +#endif + +/** + * Categories (in the category manager service) used by XPCOM: + */ + +/** + * A category which is read after component registration but before + * the "xpcom-startup" notifications. Each category entry is treated + * as the contract ID of a service which implements + * nsIDirectoryServiceProvider. Each directory service provider is + * installed in the global directory service. + */ +#define XPCOM_DIRECTORY_PROVIDER_CATEGORY "xpcom-directory-providers" + +/** + * A category which is read after component registration but before + * NS_InitXPCOM returns. Each category entry is treated as the contractID of + * a service: each service is instantiated, and if it implements nsIObserver + * the nsIObserver.observe method is called with the "xpcom-startup" topic. + */ +#define NS_XPCOM_STARTUP_CATEGORY "xpcom-startup" + +/** + * Observer topics (in the observer service) used by XPCOM: + */ + +/** + * At XPCOM startup after component registration is complete, the + * following topic is notified. In order to receive this notification, + * component must register their contract ID in the category manager, + * + * @see NS_XPCOM_STARTUP_CATEGORY + */ +#define NS_XPCOM_STARTUP_OBSERVER_ID "xpcom-startup" + +/** + * At XPCOM shutdown, this topic is notified just before "xpcom-shutdown". + * Components should only use this to mark themselves as 'being destroyed'. + * Nothing should be dispatched to any event loop. + */ +#define NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID "xpcom-will-shutdown" + +/** + * At XPCOM shutdown, this topic is notified. All components must + * release any interface references to objects in other modules when + * this topic is notified. + */ +#define NS_XPCOM_SHUTDOWN_OBSERVER_ID "xpcom-shutdown" + +/** + * This topic is notified when an entry was added to a category in the + * category manager. The subject of the notification will be the name of + * the added entry as an nsISupportsCString, and the data will be the + * name of the category. The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID "xpcom-category-entry-added" + +/** + * This topic is notified when an entry was removed from a category in the + * category manager. The subject of the notification will be the name of + * the removed entry as an nsISupportsCString, and the data will be the + * name of the category. The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID \ + "xpcom-category-entry-removed" + +/** + * This topic is notified when an a category was cleared in the category + * manager. The subject of the notification will be the category manager, + * and the data will be the name of the cleared category. + * The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID "xpcom-category-cleared" + +XPCOM_API(nsresult) NS_GetDebug(nsIDebug2** aResult); + +#endif diff --git a/xpcom/build/nsXPCOMCID.h b/xpcom/build/nsXPCOMCID.h new file mode 100644 index 0000000000..a5df26717a --- /dev/null +++ b/xpcom/build/nsXPCOMCID.h @@ -0,0 +1,220 @@ +/* -*- 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 nsXPCOMCID_h__ +#define nsXPCOMCID_h__ + +/** + * XPCOM Directory Service Contract ID + * The directory service provides ways to obtain file system locations. The + * directory service is a singleton. + * + * This contract supports the nsIDirectoryService and the nsIProperties + * interfaces. + * + */ +#define NS_DIRECTORY_SERVICE_CONTRACTID "@mozilla.org/file/directory_service;1" + +/** + * XPCOM File + * The file abstraction provides ways to obtain and access files and + * directories located on the local system. + * + * This contract supports the nsIFile interface. + * This contract may also support platform specific interfaces such as + * nsILocalFileMac on platforms where additional interfaces are required. + * + */ +#define NS_LOCAL_FILE_CONTRACTID "@mozilla.org/file/local;1" + +/** + * XPCOM Category Manager Contract ID + * The contract supports the nsICategoryManager interface. The + * category manager is a singleton. + * The "enumerateCategory" method of nsICategoryManager will return an object + * that implements nsIUTF8StringEnumerator. In addition, the enumerator will + * return the entries in sorted order (sorted by byte comparison). + */ +#define NS_CATEGORYMANAGER_CONTRACTID "@mozilla.org/categorymanager;1" + +/** + * XPCOM Properties Object Contract ID + * Simple mapping object which supports the nsIProperties interface. + */ +#define NS_PROPERTIES_CONTRACTID "@mozilla.org/properties;1" + +/** + * XPCOM Array Object ContractID + * Simple array implementation which supports the nsIArray and + * nsIMutableArray interfaces. + */ +#define NS_ARRAY_CONTRACTID "@mozilla.org/array;1" + +/** + * Observer Service ContractID + * The observer service implements the global nsIObserverService object. + * It should be used from the main thread only. + */ +#define NS_OBSERVERSERVICE_CONTRACTID "@mozilla.org/observer-service;1" + +/** + * IO utilities service contract id. + * This guarantees implementation of nsIIOUtil. Usable from any thread. + */ +#define NS_IOUTIL_CONTRACTID "@mozilla.org/io-util;1" + +/** + * Memory reporter service CID + */ +#define NS_MEMORY_REPORTER_MANAGER_CONTRACTID \ + "@mozilla.org/memory-reporter-manager;1" + +/** + * Memory info dumper service CID + */ +#define NS_MEMORY_INFO_DUMPER_CONTRACTID "@mozilla.org/memory-info-dumper;1" + +/** + * nsMessageLoop contract id + */ +#define NS_MESSAGE_LOOP_CONTRACTID "@mozilla.org/message-loop;1" + +#define NS_COMPARTMENT_INFO_CONTRACTID "@mozilla.org/compartment-info;1" + +/** + * The following are the CIDs and Contract IDs of the nsISupports wrappers for + * primative types. + */ +#define NS_SUPPORTS_ID_CID \ + { \ + 0xacf8dc40, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_ID_CONTRACTID "@mozilla.org/supports-id;1" + +#define NS_SUPPORTS_CSTRING_CID \ + { \ + 0xacf8dc41, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_CSTRING_CONTRACTID "@mozilla.org/supports-cstring;1" + +#define NS_SUPPORTS_STRING_CID \ + { \ + 0xacf8dc42, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_STRING_CONTRACTID "@mozilla.org/supports-string;1" + +#define NS_SUPPORTS_PRBOOL_CID \ + { \ + 0xacf8dc43, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRBOOL_CONTRACTID "@mozilla.org/supports-PRBool;1" + +#define NS_SUPPORTS_PRUINT8_CID \ + { \ + 0xacf8dc44, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT8_CONTRACTID "@mozilla.org/supports-PRUint8;1" + +#define NS_SUPPORTS_PRUINT16_CID \ + { \ + 0xacf8dc46, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT16_CONTRACTID "@mozilla.org/supports-PRUint16;1" + +#define NS_SUPPORTS_PRUINT32_CID \ + { \ + 0xacf8dc47, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT32_CONTRACTID "@mozilla.org/supports-PRUint32;1" + +#define NS_SUPPORTS_PRUINT64_CID \ + { \ + 0xacf8dc48, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT64_CONTRACTID "@mozilla.org/supports-PRUint64;1" + +#define NS_SUPPORTS_PRTIME_CID \ + { \ + 0xacf8dc49, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRTIME_CONTRACTID "@mozilla.org/supports-PRTime;1" + +#define NS_SUPPORTS_CHAR_CID \ + { \ + 0xacf8dc4a, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_CHAR_CONTRACTID "@mozilla.org/supports-char;1" + +#define NS_SUPPORTS_PRINT16_CID \ + { \ + 0xacf8dc4b, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT16_CONTRACTID "@mozilla.org/supports-PRInt16;1" + +#define NS_SUPPORTS_PRINT32_CID \ + { \ + 0xacf8dc4c, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT32_CONTRACTID "@mozilla.org/supports-PRInt32;1" + +#define NS_SUPPORTS_PRINT64_CID \ + { \ + 0xacf8dc4d, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT64_CONTRACTID "@mozilla.org/supports-PRInt64;1" + +#define NS_SUPPORTS_FLOAT_CID \ + { \ + 0xcbf86870, 0x4ac0, 0x11d3, { \ + 0xba, 0xea, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 \ + } \ + } +#define NS_SUPPORTS_FLOAT_CONTRACTID "@mozilla.org/supports-float;1" + +#define NS_SUPPORTS_DOUBLE_CID \ + { \ + 0xcbf86871, 0x4ac0, 0x11d3, { \ + 0xba, 0xea, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 \ + } \ + } +#define NS_SUPPORTS_DOUBLE_CONTRACTID "@mozilla.org/supports-double;1" + +#define NS_SUPPORTS_INTERFACE_POINTER_CID \ + { \ + 0xA99FEBBA, 0x1DD1, 0x11B2, { \ + 0xA9, 0x43, 0xB0, 0x23, 0x34, 0xA6, 0xD0, 0x83 \ + } \ + } +#define NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID \ + "@mozilla.org/supports-interface-pointer;1" + +#endif diff --git a/xpcom/build/nsXPCOMCIDInternal.h b/xpcom/build/nsXPCOMCIDInternal.h new file mode 100644 index 0000000000..d07fcb1c4d --- /dev/null +++ b/xpcom/build/nsXPCOMCIDInternal.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 nsXPCOMCIDInternal_h__ +#define nsXPCOMCIDInternal_h__ + +#include "nsXPCOMCID.h" + +/** + * A hashtable-based property bag component. + * @implements nsIWritablePropertyBag, nsIWritablePropertyBag2 + */ +#define NS_HASH_PROPERTY_BAG_CID \ + { \ + 0x678c50b8, 0x6bcb, 0x4ad0, { \ + 0xb9, 0xb8, 0xc8, 0x11, 0x75, 0x95, 0x51, 0x99 \ + } \ + } +#define NS_HASH_PROPERTY_BAG_CONTRACTID "@mozilla.org/hash-property-bag;1" + +/** + * Factory for creating nsIUnicharInputStream + * @implements nsIUnicharInputStreamFactory + * @note nsIUnicharInputStream instances cannot be created via + * createInstance. Code must use one of the custom factory methods. + */ +#define NS_SIMPLE_UNICHAR_STREAM_FACTORY_CONTRACTID \ + "@mozilla.org/xpcom/simple-unichar-stream-factory;1" + +/** + * The global thread manager service. This component is a singleton. + * @implements nsIThreadManager + */ +#define NS_THREADMANAGER_CONTRACTID "@mozilla.org/thread-manager;1" + +/** + * The contract id for the nsIXULAppInfo service. + */ +#define XULAPPINFO_SERVICE_CONTRACTID "@mozilla.org/xre/app-info;1" + +/** + * The contract id for the nsIXULRuntime service. + */ +#define XULRUNTIME_SERVICE_CONTRACTID "@mozilla.org/xre/runtime;1" + +#endif // nsXPCOMCIDInternal_h__ diff --git a/xpcom/build/nsXPCOMPrivate.h b/xpcom/build/nsXPCOMPrivate.h new file mode 100644 index 0000000000..49f36c709c --- /dev/null +++ b/xpcom/build/nsXPCOMPrivate.h @@ -0,0 +1,126 @@ +/* -*- 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 nsXPCOMPrivate_h__ +#define nsXPCOMPrivate_h__ + +#include "nscore.h" +#include "nsXPCOM.h" +#include "mozilla/Attributes.h" + +/** + * During this shutdown notification all threads which run XPCOM code must + * be joined. + */ +#define NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID "xpcom-shutdown-threads" + +// PUBLIC +namespace mozilla { + +/** + * Shutdown XPCOM. You must call this method after you are finished + * using xpcom. + * + * @param aServMgr The service manager which was returned by + * NS_InitXPCOM. This will release servMgr. You may pass null. + * + * @return NS_OK for success; + * other error codes indicate a failure during shutdown + * + */ +MOZ_CAN_RUN_SCRIPT +nsresult ShutdownXPCOM(nsIServiceManager* aServMgr); + +void SetICUMemoryFunctions(); + +/** + * C++ namespaced version of NS_LogTerm. + */ +void LogTerm(); + +} // namespace mozilla + +/* XPCOM Specific Defines + * + * XPCOM_DLL - name of the loadable xpcom library on disk. + * XUL_DLL - name of the loadable XUL library on disk + * XPCOM_SEARCH_KEY - name of the environment variable that can be + * modified to include additional search paths. + * GRE_CONF_NAME - Name of the GRE Configuration file + */ + +#if defined(XP_WIN) + +# define XPCOM_SEARCH_KEY "PATH" +# define GRE_CONF_NAME "gre.config" +# define GRE_WIN_REG_LOC L"Software\\mozilla.org\\GRE" +# define XPCOM_DLL XUL_DLL +# define LXPCOM_DLL LXUL_DLL +# define XUL_DLL "xul.dll" +# define LXUL_DLL L"xul.dll" + +#else // Unix +# include <limits.h> // for PATH_MAX + +# define XPCOM_DLL XUL_DLL + +// you have to love apple.. +# ifdef XP_MACOSX +# define XPCOM_SEARCH_KEY "DYLD_LIBRARY_PATH" +# define GRE_FRAMEWORK_NAME "XUL.framework" +# define XUL_DLL "XUL" +# else +# define XPCOM_SEARCH_KEY "LD_LIBRARY_PATH" +# define XUL_DLL "libxul" MOZ_DLL_SUFFIX +# endif + +# define GRE_CONF_NAME ".gre.config" +# define GRE_CONF_PATH "/etc/gre.conf" +# define GRE_CONF_DIR "/etc/gre.d" +# define GRE_USER_CONF_DIR ".gre.d" +#endif + +#if defined(XP_WIN) +# define XPCOM_FILE_PATH_SEPARATOR "\\" +# define XPCOM_ENV_PATH_SEPARATOR ";" +#elif defined(XP_UNIX) +# define XPCOM_FILE_PATH_SEPARATOR "/" +# define XPCOM_ENV_PATH_SEPARATOR ":" +#else +# error need_to_define_your_file_path_separator_and_illegal_characters +#endif + +#ifdef AIX +# include <sys/param.h> +#endif + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +// Needed by the IPC layer from off the main thread +extern char16_t* gGREBinPath; + +namespace mozilla { +namespace services { + +/** + * Clears service cache, sets gXPCOMShuttingDown + */ +void Shutdown(); + +} // namespace services +} // namespace mozilla + +#endif diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h new file mode 100644 index 0000000000..d15944f8e4 --- /dev/null +++ b/xpcom/build/nsXULAppAPI.h @@ -0,0 +1,386 @@ +/* -*- 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 _nsXULAppAPI_h__ +#define _nsXULAppAPI_h__ + +#include "js/TypeDecls.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ProcessType.h" +#include "mozilla/TimeStamp.h" +#include "nscore.h" + +#if defined(MOZ_WIDGET_ANDROID) +# include <jni.h> +#endif + +class JSString; +class MessageLoop; +class nsIDirectoryServiceProvider; +class nsIFile; +class nsISupports; +struct JSContext; +struct XREChildData; +struct XREShellData; + +namespace mozilla { +class XREAppData; +struct BootstrapConfig; +} // namespace mozilla + +/** + * A directory service key which provides the platform-correct "application + * data" directory as follows, where $name and $vendor are as defined above and + * $vendor is optional: + * + * Windows: + * HOME = Documents and Settings\$USER\Application Data + * UAppData = $HOME[\$vendor]\$name + * + * Unix: + * HOME = ~ + * UAppData = $HOME/.[$vendor/]$name + * + * Mac: + * HOME = ~ + * UAppData = $HOME/Library/Application Support/$name + * + * Note that the "profile" member above will change the value of UAppData as + * follows: + * + * Windows: + * UAppData = $HOME\$profile + * + * Unix: + * UAppData = $HOME/.$profile + * + * Mac: + * UAppData = $HOME/Library/Application Support/$profile + */ +#define XRE_USER_APP_DATA_DIR "UAppData" + +/** + * A directory service key which provides the executable file used to + * launch the current process. This is the same value returned by the + * XRE_GetBinaryPath function defined below. + */ +#define XRE_EXECUTABLE_FILE "XREExeF" + +/** + * A directory service key which specifies the profile + * directory. Unlike the NS_APP_USER_PROFILE_50_DIR key, this key may + * be available when the profile hasn't been "started", or after is + * has been shut down. If the application is running without a + * profile, such as when showing the profile manager UI, this key will + * not be available. This key is provided by the XUL apprunner or by + * the aAppDirProvider object passed to XRE_InitEmbedding. + */ +#define NS_APP_PROFILE_DIR_STARTUP "ProfDS" + +/** + * A directory service key which specifies the profile + * directory. Unlike the NS_APP_USER_PROFILE_LOCAL_50_DIR key, this key may + * be available when the profile hasn't been "started", or after is + * has been shut down. If the application is running without a + * profile, such as when showing the profile manager UI, this key will + * not be available. This key is provided by the XUL apprunner or by + * the aAppDirProvider object passed to XRE_InitEmbedding. + */ +#define NS_APP_PROFILE_LOCAL_DIR_STARTUP "ProfLDS" + +/** + * A directory service key which specifies the system extension + * parent directory containing platform-specific extensions. + * This key may not be available on all platforms. + */ +#define XRE_SYS_LOCAL_EXTENSION_PARENT_DIR "XRESysLExtPD" + +/** + * A directory service key which specifies the system extension + * parent directory containing platform-independent extensions. + * This key may not be available on all platforms. + * Additionally, the directory may be equal to that returned by + * XRE_SYS_LOCAL_EXTENSION_PARENT_DIR on some platforms. + */ +#define XRE_SYS_SHARE_EXTENSION_PARENT_DIR "XRESysSExtPD" + +#if defined(XP_UNIX) || defined(XP_MACOSX) +/** + * Directory service keys for the system-wide and user-specific + * directories where native manifests used by the WebExtensions + * native messaging and managed storage features are found. + */ +# define XRE_SYS_NATIVE_MANIFESTS "XRESysNativeManifests" +# define XRE_USER_NATIVE_MANIFESTS "XREUserNativeManifests" +#endif + +/** + * A directory service key which specifies the user system extension + * parent directory. + */ +#define XRE_USER_SYS_EXTENSION_DIR "XREUSysExt" + +/** + * A directory service key which specifies the distribution specific files for + * the application. + */ +#define XRE_APP_DISTRIBUTION_DIR "XREAppDist" + +/** + * A directory service key which specifies the location for system add-ons. + */ +#define XRE_APP_FEATURES_DIR "XREAppFeat" + +/** + * A directory service key which specifies the location for app dir add-ons. + * Should be a synonym for XCurProcD everywhere except in tests. + */ +#define XRE_ADDON_APP_DIR "XREAddonAppDir" + +/** + * A directory service key which specifies the distribution specific files for + * the application unique for each user. + * It's located at /run/user/$UID/<product name>/ + */ +#define XRE_USER_RUNTIME_DIR "XREUserRunTimeDir" + +/** + * A directory service key which provides the update directory. Callers should + * fall back to appDir. + * Windows: If vendor name exists: + * ProgramData\<vendor name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If vendor name doesn't exist, but product name exists: + * ProgramData\<product name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If neither vendor nor product name exists: + * ProgramData\Mozilla\updates + * + * Mac: ~/Library/Caches/Mozilla/updates/<absolute path to app dir> + * + * All others: Parent directory of XRE_EXECUTABLE_FILE. + */ +#define XRE_UPDATE_ROOT_DIR "UpdRootD" + +/** + * A directory service key which provides the *old* update directory. This + * path should only be used when data needs to be migrated from the old update + * directory. + * Windows: If vendor name exists: + * Documents and Settings\<User>\Local Settings\Application Data\ + * <vendor name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If vendor name doesn't exist, but product name exists: + * Documents and Settings\<User>\Local Settings\Application Data\ + * <product name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If neither vendor nor product name exists: + * Documents and Settings\<User>\Local Settings\Application Data\ + * Mozilla\updates + * + * This path does not exist on other operating systems + */ +#define XRE_OLD_UPDATE_ROOT_DIR "OldUpdRootD" + +/** + * Begin an XUL application. Does not return until the user exits the + * application. + * + * @param argc/argv Command-line parameters to pass to the application. On + * Windows, these should be in UTF8. On unix-like platforms + * these are in the "native" character set. + * + * @param aConfig Information about the application to be run. + * + * @return A native result code suitable for returning from main(). + * + * @note If the binary is linked against the standalone XPCOM glue, + * XPCOMGlueStartup() should be called before this method. + */ +int XRE_main(int argc, char* argv[], const mozilla::BootstrapConfig& aConfig); + +/** + * Given a path relative to the current working directory (or an absolute + * path), return an appropriate nsIFile object. + * + * @note Pass UTF8 strings on Windows... native charset on other platforms. + */ +nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult); + +/** + * Get the path of the running application binary and store it in aResult. + */ +nsresult XRE_GetBinaryPath(nsIFile** aResult); + +/** + * Register XPCOM components found in an array of files/directories. + * This method may be called at any time before or after XRE_main or + * XRE_InitEmbedding. + * + * @param aFiles An array of files or directories. + * @param aFileCount the number of items in the aFiles array. + * @note appdir/components is registered automatically. + * + * NS_APP_LOCATION specifies a location to search for binary XPCOM + * components as well as component/chrome manifest files. + * + * NS_EXTENSION_LOCATION excludes binary XPCOM components but allows other + * manifest instructions. + * + * NS_SKIN_LOCATION specifies a location to search for chrome manifest files + * which are only allowed to register skin packages. + */ +enum NSLocationType { + NS_APP_LOCATION, + NS_EXTENSION_LOCATION, + NS_SKIN_LOCATION, + NS_BOOTSTRAPPED_LOCATION +}; + +nsresult XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation); + +/** + * Register XPCOM components found in a JAR. + * This is similar to XRE_AddManifestLocation except the file specified + * must be a zip archive with a manifest named chrome.manifest + * This method may be called at any time before or after XRE_main or + * XRE_InitEmbedding. + * + * @param aFiles An array of files or directories. + * @param aFileCount the number of items in the aFiles array. + * @note appdir/components is registered automatically. + * + * NS_COMPONENT_LOCATION specifies a location to search for binary XPCOM + * components as well as component/chrome manifest files. + * + * NS_SKIN_LOCATION specifies a location to search for chrome manifest files + * which are only allowed to register skin packages. + */ +nsresult XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation); + +/** + * Parse an INI file (application.ini or override.ini) into an existing + * nsXREAppData structure. + * + * @param aINIFile The INI file to parse + * @param aAppData The nsXREAppData structure to fill. + */ +nsresult XRE_ParseAppData(nsIFile* aINIFile, mozilla::XREAppData& aAppData); + +const char* XRE_GeckoProcessTypeToString(GeckoProcessType aProcessType); +const char* XRE_ChildProcessTypeToAnnotation(GeckoProcessType aProcessType); + +#if defined(MOZ_WIDGET_ANDROID) +struct XRE_AndroidChildFds { + int mPrefsFd; + int mPrefMapFd; + int mIpcFd; + int mCrashFd; +}; + +void XRE_SetAndroidChildFds(JNIEnv* env, const XRE_AndroidChildFds& fds); +#endif // defined(MOZ_WIDGET_ANDROID) + +void XRE_SetProcessType(const char* aProcessTypeString); + +nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], + const XREChildData* aChildData); + +/** + * Return the GeckoProcessType of the current process. + */ +GeckoProcessType XRE_GetProcessType(); + +/** + * Return the string representation of the GeckoProcessType of the current + * process. + */ +const char* XRE_GetProcessTypeString(); + +/** + * Returns true when called in the e10s parent process. Does *NOT* return true + * when called in the main process if e10s is disabled. + */ +bool XRE_IsE10sParentProcess(); + +/** + * Defines XRE_IsParentProcess, XRE_IsContentProcess, etc. + * + * XRE_IsParentProcess is unique in that it returns true when called in + * the e10s parent process or called in the main process when e10s is + * disabled. + */ +#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \ + process_bin_type, procinfo_typename, \ + webidl_typename, allcaps_name) \ + bool XRE_Is##proc_typename##Process(); +#include "mozilla/GeckoProcessTypes.h" +#undef GECKO_PROCESS_TYPE + +bool XRE_IsSocketProcess(); + +/** + * Returns true if the appshell should run its own native event loop. Returns + * false if we should rely solely on the Gecko event loop. + */ +bool XRE_UseNativeEventProcessing(); + +typedef void (*MainFunction)(void* aData); + +int XRE_RunIPDLTest(int aArgc, char* aArgv[]); + +nsresult XRE_RunAppShell(); + +nsresult XRE_InitCommandLine(int aArgc, char* aArgv[]); + +nsresult XRE_DeinitCommandLine(); + +void XRE_ShutdownChildProcess(); + +MessageLoop* XRE_GetIOMessageLoop(); + +bool XRE_SendTestShellCommand(JSContext* aCx, JSString* aCommand, + JS::Value* aCallback); +bool XRE_ShutdownTestShell(); + +void XRE_InstallX11ErrorHandler(); +void XRE_CleanupX11ErrorHandler(); + +void XRE_TelemetryAccumulate(int aID, uint32_t aSample); + +void XRE_StartupTimelineRecord(int aEvent, mozilla::TimeStamp aWhen); + +void XRE_InitOmnijar(nsIFile* aGreOmni, nsIFile* aAppOmni); +void XRE_StopLateWriteChecks(void); + +void XRE_EnableSameExecutableForContentProc(); + +namespace mozilla { +enum class BinPathType { Self, PluginContainer }; +} +mozilla::BinPathType XRE_GetChildProcBinPathType(GeckoProcessType aProcessType); + +int XRE_XPCShellMain(int argc, char** argv, char** envp, + const XREShellData* aShellData); + +#ifdef LIBFUZZER +# include "FuzzerRegistry.h" + +void XRE_LibFuzzerSetDriver(LibFuzzerDriver); + +#endif // LIBFUZZER + +#ifdef MOZ_ENABLE_FORKSERVER + +int XRE_ForkServer(int* aArgc, char*** aArgv); + +#endif // MOZ_ENABLE_FORKSERVER + +#endif // _nsXULAppAPI_h__ diff --git a/xpcom/build/perfprobe.cpp b/xpcom/build/perfprobe.cpp new file mode 100644 index 0000000000..d5a82be59d --- /dev/null +++ b/xpcom/build/perfprobe.cpp @@ -0,0 +1,215 @@ +/* -*- 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/. */ + +/***************************** + Windows implementation of probes, using xperf + *****************************/ +#include <windows.h> +#include <wmistr.h> +#include <evntrace.h> + +#include "perfprobe.h" + +namespace mozilla { +namespace probes { + +#if defined(MOZ_LOGGING) +static LazyLogModule sProbeLog("SysProbe"); +# define LOG(x) MOZ_LOG(sProbeLog, mozilla::LogLevel::Debug, x) +#else +# define LOG(x) +#endif + +// Utility function +GUID CID_to_GUID(const nsCID& aCID) { + GUID result; + result.Data1 = aCID.m0; + result.Data2 = aCID.m1; + result.Data3 = aCID.m2; + for (int i = 0; i < 8; ++i) { + result.Data4[i] = aCID.m3[i]; + } + return result; +} + +// Implementation of Probe + +Probe::Probe(const nsCID& aGUID, const nsACString& aName, + ProbeManager* aManager) + : mGUID(CID_to_GUID(aGUID)), mName(aName), mManager(aManager) {} + +nsresult Probe::Trigger() { + if (!(mManager->mIsActive)) { + // Do not trigger if there is no session + return NS_OK; + } + + _EVENT_TRACE_HEADER event; + ZeroMemory(&event, sizeof(event)); + event.Size = sizeof(event); + event.Flags = WNODE_FLAG_TRACED_GUID; + event.Guid = (const GUID)mGUID; + event.Class.Type = 1; + event.Class.Version = 0; + event.Class.Level = TRACE_LEVEL_INFORMATION; + + ULONG result = TraceEvent(mManager->mSessionHandle, &event); + + LOG(("Probes: Triggered %s, %s, %ld", mName.Data(), + result == ERROR_SUCCESS ? "success" : "failure", result)); + + nsresult rv; + switch (result) { + case ERROR_SUCCESS: + rv = NS_OK; + break; + case ERROR_INVALID_FLAG_NUMBER: + case ERROR_MORE_DATA: + case ERROR_INVALID_PARAMETER: + rv = NS_ERROR_INVALID_ARG; + break; + case ERROR_INVALID_HANDLE: + rv = NS_ERROR_FAILURE; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + default: + rv = NS_ERROR_UNEXPECTED; + } + return rv; +} + +// Implementation of ProbeManager + +ProbeManager::~ProbeManager() { + // If the manager goes out of scope, stop the session. + if (mIsActive && mRegistrationHandle) { + StopSession(); + } +} + +ProbeManager::ProbeManager(const nsCID& aApplicationUID, + const nsACString& aApplicationName) + : mIsActive(false), + mApplicationUID(aApplicationUID), + mApplicationName(aApplicationName), + mSessionHandle(0), + mRegistrationHandle(0), + mInitialized(false) { +#if defined(MOZ_LOGGING) + char cidStr[NSID_LENGTH]; + aApplicationUID.ToProvidedString(cidStr); + LOG(("ProbeManager::Init for application %s, %s", aApplicationName.Data(), + cidStr)); +#endif +} + +// Note: The Windows API is just a little bit scary there. +// The only way to obtain the session handle is to +//- ignore the session handle obtained from RegisterTraceGuids +//- pass a callback +//- in that callback, request the session handle through +// GetTraceLoggerHandle and some opaque value received by the callback + +ULONG WINAPI ControlCallback(WMIDPREQUESTCODE aRequestCode, PVOID aContext, + ULONG* aReserved, PVOID aBuffer) { + ProbeManager* context = (ProbeManager*)aContext; + switch (aRequestCode) { + case WMI_ENABLE_EVENTS: { + context->mIsActive = true; + TRACEHANDLE sessionHandle = GetTraceLoggerHandle(aBuffer); + // Note: We only accept one handle + if ((HANDLE)sessionHandle == INVALID_HANDLE_VALUE) { + ULONG result = GetLastError(); + LOG(("Probes: ControlCallback failed, %lu", result)); + return result; + } else if (context->mIsActive && context->mSessionHandle && + context->mSessionHandle != sessionHandle) { + LOG( + ("Probes: Can only handle one context at a time, " + "ignoring activation")); + return ERROR_SUCCESS; + } else { + context->mSessionHandle = sessionHandle; + LOG(("Probes: ControlCallback activated")); + return ERROR_SUCCESS; + } + } + + case WMI_DISABLE_EVENTS: + context->mIsActive = false; + context->mSessionHandle = 0; + LOG(("Probes: ControlCallback deactivated")); + return ERROR_SUCCESS; + + default: + LOG(("Probes: ControlCallback does not know what to do with %d", + aRequestCode)); + return ERROR_INVALID_PARAMETER; + } +} + +already_AddRefed<Probe> ProbeManager::GetProbe(const nsCID& aEventUID, + const nsACString& aEventName) { + RefPtr<Probe> result(new Probe(aEventUID, aEventName, this)); + mAllProbes.AppendElement(result); + return result.forget(); +} + +nsresult ProbeManager::StartSession() { return StartSession(mAllProbes); } + +nsresult ProbeManager::StartSession(nsTArray<RefPtr<Probe>>& aProbes) { + const size_t probesCount = aProbes.Length(); + _TRACE_GUID_REGISTRATION* probes = new _TRACE_GUID_REGISTRATION[probesCount]; + for (unsigned int i = 0; i < probesCount; ++i) { + const Probe* probe = aProbes[i]; + const Probe* probeX = static_cast<const Probe*>(probe); + probes[i].Guid = (LPCGUID)&probeX->mGUID; + } + ULONG result = + RegisterTraceGuids(&ControlCallback + /*RequestAddress: Sets mSessions appropriately.*/, + this + /*RequestContext: Passed to ControlCallback*/, + (LPGUID)&mApplicationUID + /*ControlGuid: Tracing GUID + the cast comes from MSDN examples*/ + , + probesCount + /*GuidCount: Number of probes*/, + probes + /*TraceGuidReg: Probes registration*/, + nullptr + /*MofImagePath: Must be nullptr, says MSDN*/, + nullptr + /*MofResourceName:Must be nullptr, says MSDN*/, + &mRegistrationHandle + /*RegistrationHandle: Handler. + used only for unregistration*/ + ); + delete[] probes; + if (NS_WARN_IF(result != ERROR_SUCCESS)) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult ProbeManager::StopSession() { + LOG(("Probes: Stopping measures")); + if (mSessionHandle != 0) { + ULONG result = UnregisterTraceGuids(mSessionHandle); + mSessionHandle = 0; + if (result != ERROR_SUCCESS) { + return NS_ERROR_INVALID_ARG; + } + } + return NS_OK; +} + +} // namespace probes +} // namespace mozilla diff --git a/xpcom/build/perfprobe.h b/xpcom/build/perfprobe.h new file mode 100644 index 0000000000..e5a0382322 --- /dev/null +++ b/xpcom/build/perfprobe.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * A mechanism for interacting with operating system-provided + * debugging/profiling tools such as Microsoft EWT/Windows Performance Toolkit. + */ + +#ifndef mozilla_perfprobe_h +#define mozilla_perfprobe_h + +#if !defined(XP_WIN) +# error "For the moment, perfprobe.h is defined only for Windows platforms" +#endif + +#include "nsError.h" +#include "nsString.h" +#include "mozilla/Logging.h" +#include "nsTArray.h" +#include <windows.h> +#undef GetStartupInfo // Prevent Windows from polluting global namespace +#include <wmistr.h> +#include <evntrace.h> + +namespace mozilla { +namespace probes { + +class ProbeManager; + +/** + * A data structure supporting a trigger operation that can be used to + * send information to the operating system. + */ + +class Probe { + public: + NS_INLINE_DECL_REFCOUNTING(Probe) + + /** + * Trigger the event. + * + * Note: Can be called from any thread. + */ + nsresult Trigger(); + + protected: + ~Probe(){}; + + Probe(const nsCID& aGUID, const nsACString& aName, ProbeManager* aManager); + friend class ProbeManager; + + protected: + /** + * The system GUID associated to this probe. See the documentation + * of |ProbeManager::Make| for more details. + */ + const GUID mGUID; + + /** + * The name of this probe. See the documentation + * of |ProbeManager::Make| for more details. + */ + const nsCString mName; + + /** + * The ProbeManager managing this probe. + * + * Note: This is a weak reference to avoid a useless cycle. + */ + class ProbeManager* mManager; +}; + +/** + * A manager for a group of probes. + * + * You can have several managers in one application, provided that they all + * have distinct IDs and names. However, having more than 2 is considered a bad + * practice. + */ +class ProbeManager { + public: + NS_INLINE_DECL_REFCOUNTING(ProbeManager) + + /** + * Create a new probe manager. + * + * This constructor should be called from the main thread. + * + * @param aApplicationUID The unique ID of the probe. Under Windows, this + * unique ID must have been previously registered using an external tool. + * See MyCategory on http://msdn.microsoft.com/en-us/library/aa364100.aspx + * @param aApplicationName A name for the probe. Currently used only for + * logging purposes. In the future, may be attached to the data sent to the + * operating system. + * + * Note: If two ProbeManagers are constructed with the same uid and/or name, + * behavior is unspecified. + */ + ProbeManager(const nsCID& aApplicationUID, + const nsACString& aApplicationName); + + /** + * Acquire a probe. + * + * Note: Only probes acquired before the call to SetReady are taken into + * account + * Note: Can be called only from the main thread. + * + * @param aEventUID The unique ID of the probe. Under Windows, this unique + * ID must have been previously registered using an external tool. + * See MyCategory on http://msdn.microsoft.com/en-us/library/aa364100.aspx + * @param aEventName A name for the probe. Currently used only for logging + * purposes. In the + * future, may be attached to the data sent to the operating system. + * @return Either |null| in case of error or a valid |Probe*|. + * + * Note: If this method is called twice with the same uid and/or name, + * behavior is undefined. + */ + already_AddRefed<Probe> GetProbe(const nsCID& aEventUID, + const nsACString& aEventName); + + /** + * Start/stop the measuring session. + * + * This method should be called from the main thread. + * + * Note that starting an already started probe manager has no effect, + * nor does stopping an already stopped probe manager. + */ + nsresult StartSession(); + nsresult StopSession(); + + /** + * @return true If measures are currently on, i.e. if triggering probes is any + * is useful. You do not have to check this before triggering a probe, unless + * this can avoid complex computations. + */ + bool IsActive(); + + protected: + ~ProbeManager(); + + nsresult StartSession(nsTArray<RefPtr<Probe>>& aProbes); + nsresult Init(const nsCID& aApplicationUID, + const nsACString& aApplicationName); + + protected: + /** + * `true` if a session is in activity, `false` otherwise. + */ + bool mIsActive; + + /** + * The UID of this manager. + * See documentation above for registration steps that you + * may have to take. + */ + nsCID mApplicationUID; + + /** + * The name of the application. + */ + nsCString mApplicationName; + + /** + * All the probes that have been created for this manager. + */ + nsTArray<RefPtr<Probe>> mAllProbes; + + /** + * Handle used for triggering events + */ + TRACEHANDLE mSessionHandle; + + /** + * Handle used for registration/unregistration + */ + TRACEHANDLE mRegistrationHandle; + + /** + * `true` if initialization has been performed, `false` until then. + */ + bool mInitialized; + + friend class Probe; // Needs to access |mSessionHandle| + friend ULONG WINAPI ControlCallback(WMIDPREQUESTCODE aRequestCode, + PVOID aContext, ULONG* aReserved, + PVOID aBuffer); // Sets |mSessionHandle| +}; + +} // namespace probes +} // namespace mozilla + +#endif // mozilla_perfprobe_h diff --git a/xpcom/build/xpcom_alpha.def b/xpcom/build/xpcom_alpha.def new file mode 100644 index 0000000000..38fedfa17f --- /dev/null +++ b/xpcom/build/xpcom_alpha.def @@ -0,0 +1,256 @@ +;+# This Source Code Form is subject to the terms of the Mozilla Public +;+# License, v. 2.0. If a copy of the MPL was not distributed with this +;+# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LIBRARY xpcom +DESCRIPTION "xpcom library" + +EXPORTS + ?Stub3@nsXPTCStubBase@@UAAIXZ + ?Stub4@nsXPTCStubBase@@UAAIXZ + ?Stub5@nsXPTCStubBase@@UAAIXZ + ?Stub6@nsXPTCStubBase@@UAAIXZ + ?Stub7@nsXPTCStubBase@@UAAIXZ + ?Stub8@nsXPTCStubBase@@UAAIXZ + ?Stub9@nsXPTCStubBase@@UAAIXZ + ?Stub10@nsXPTCStubBase@@UAAIXZ + ?Stub11@nsXPTCStubBase@@UAAIXZ + ?Stub12@nsXPTCStubBase@@UAAIXZ + ?Stub13@nsXPTCStubBase@@UAAIXZ + ?Stub14@nsXPTCStubBase@@UAAIXZ + ?Stub15@nsXPTCStubBase@@UAAIXZ + ?Stub16@nsXPTCStubBase@@UAAIXZ + ?Stub17@nsXPTCStubBase@@UAAIXZ + ?Stub18@nsXPTCStubBase@@UAAIXZ + ?Stub19@nsXPTCStubBase@@UAAIXZ + ?Stub20@nsXPTCStubBase@@UAAIXZ + ?Stub21@nsXPTCStubBase@@UAAIXZ + ?Stub22@nsXPTCStubBase@@UAAIXZ + ?Stub23@nsXPTCStubBase@@UAAIXZ + ?Stub24@nsXPTCStubBase@@UAAIXZ + ?Stub25@nsXPTCStubBase@@UAAIXZ + ?Stub26@nsXPTCStubBase@@UAAIXZ + ?Stub27@nsXPTCStubBase@@UAAIXZ + ?Stub28@nsXPTCStubBase@@UAAIXZ + ?Stub29@nsXPTCStubBase@@UAAIXZ + ?Stub30@nsXPTCStubBase@@UAAIXZ + ?Stub31@nsXPTCStubBase@@UAAIXZ + ?Stub32@nsXPTCStubBase@@UAAIXZ + ?Stub33@nsXPTCStubBase@@UAAIXZ + ?Stub34@nsXPTCStubBase@@UAAIXZ + ?Stub35@nsXPTCStubBase@@UAAIXZ + ?Stub36@nsXPTCStubBase@@UAAIXZ + ?Stub37@nsXPTCStubBase@@UAAIXZ + ?Stub38@nsXPTCStubBase@@UAAIXZ + ?Stub39@nsXPTCStubBase@@UAAIXZ + ?Stub40@nsXPTCStubBase@@UAAIXZ + ?Stub41@nsXPTCStubBase@@UAAIXZ + ?Stub42@nsXPTCStubBase@@UAAIXZ + ?Stub43@nsXPTCStubBase@@UAAIXZ + ?Stub44@nsXPTCStubBase@@UAAIXZ + ?Stub45@nsXPTCStubBase@@UAAIXZ + ?Stub46@nsXPTCStubBase@@UAAIXZ + ?Stub47@nsXPTCStubBase@@UAAIXZ + ?Stub48@nsXPTCStubBase@@UAAIXZ + ?Stub49@nsXPTCStubBase@@UAAIXZ + ?Stub50@nsXPTCStubBase@@UAAIXZ + ?Stub51@nsXPTCStubBase@@UAAIXZ + ?Stub52@nsXPTCStubBase@@UAAIXZ + ?Stub53@nsXPTCStubBase@@UAAIXZ + ?Stub54@nsXPTCStubBase@@UAAIXZ + ?Stub55@nsXPTCStubBase@@UAAIXZ + ?Stub56@nsXPTCStubBase@@UAAIXZ + ?Stub57@nsXPTCStubBase@@UAAIXZ + ?Stub58@nsXPTCStubBase@@UAAIXZ + ?Stub59@nsXPTCStubBase@@UAAIXZ + ?Stub60@nsXPTCStubBase@@UAAIXZ + ?Stub61@nsXPTCStubBase@@UAAIXZ + ?Stub62@nsXPTCStubBase@@UAAIXZ + ?Stub63@nsXPTCStubBase@@UAAIXZ + ?Stub64@nsXPTCStubBase@@UAAIXZ + ?Stub65@nsXPTCStubBase@@UAAIXZ + ?Stub66@nsXPTCStubBase@@UAAIXZ + ?Stub67@nsXPTCStubBase@@UAAIXZ + ?Stub68@nsXPTCStubBase@@UAAIXZ + ?Stub69@nsXPTCStubBase@@UAAIXZ + ?Stub70@nsXPTCStubBase@@UAAIXZ + ?Stub71@nsXPTCStubBase@@UAAIXZ + ?Stub72@nsXPTCStubBase@@UAAIXZ + ?Stub73@nsXPTCStubBase@@UAAIXZ + ?Stub74@nsXPTCStubBase@@UAAIXZ + ?Stub75@nsXPTCStubBase@@UAAIXZ + ?Stub76@nsXPTCStubBase@@UAAIXZ + ?Stub77@nsXPTCStubBase@@UAAIXZ + ?Stub78@nsXPTCStubBase@@UAAIXZ + ?Stub79@nsXPTCStubBase@@UAAIXZ + ?Stub80@nsXPTCStubBase@@UAAIXZ + ?Stub81@nsXPTCStubBase@@UAAIXZ + ?Stub82@nsXPTCStubBase@@UAAIXZ + ?Stub83@nsXPTCStubBase@@UAAIXZ + ?Stub84@nsXPTCStubBase@@UAAIXZ + ?Stub85@nsXPTCStubBase@@UAAIXZ + ?Stub86@nsXPTCStubBase@@UAAIXZ + ?Stub87@nsXPTCStubBase@@UAAIXZ + ?Stub88@nsXPTCStubBase@@UAAIXZ + ?Stub89@nsXPTCStubBase@@UAAIXZ + ?Stub90@nsXPTCStubBase@@UAAIXZ + ?Stub91@nsXPTCStubBase@@UAAIXZ + ?Stub92@nsXPTCStubBase@@UAAIXZ + ?Stub93@nsXPTCStubBase@@UAAIXZ + ?Stub94@nsXPTCStubBase@@UAAIXZ + ?Stub95@nsXPTCStubBase@@UAAIXZ + ?Stub96@nsXPTCStubBase@@UAAIXZ + ?Stub97@nsXPTCStubBase@@UAAIXZ + ?Stub98@nsXPTCStubBase@@UAAIXZ + ?Stub99@nsXPTCStubBase@@UAAIXZ + ?Stub100@nsXPTCStubBase@@UAAIXZ + ?Stub101@nsXPTCStubBase@@UAAIXZ + ?Stub102@nsXPTCStubBase@@UAAIXZ + ?Stub103@nsXPTCStubBase@@UAAIXZ + ?Stub104@nsXPTCStubBase@@UAAIXZ + ?Stub105@nsXPTCStubBase@@UAAIXZ + ?Stub106@nsXPTCStubBase@@UAAIXZ + ?Stub107@nsXPTCStubBase@@UAAIXZ + ?Stub108@nsXPTCStubBase@@UAAIXZ + ?Stub109@nsXPTCStubBase@@UAAIXZ + ?Stub110@nsXPTCStubBase@@UAAIXZ + ?Stub111@nsXPTCStubBase@@UAAIXZ + ?Stub112@nsXPTCStubBase@@UAAIXZ + ?Stub113@nsXPTCStubBase@@UAAIXZ + ?Stub114@nsXPTCStubBase@@UAAIXZ + ?Stub115@nsXPTCStubBase@@UAAIXZ + ?Stub116@nsXPTCStubBase@@UAAIXZ + ?Stub117@nsXPTCStubBase@@UAAIXZ + ?Stub118@nsXPTCStubBase@@UAAIXZ + ?Stub119@nsXPTCStubBase@@UAAIXZ + ?Stub120@nsXPTCStubBase@@UAAIXZ + ?Stub121@nsXPTCStubBase@@UAAIXZ + ?Stub122@nsXPTCStubBase@@UAAIXZ + ?Stub123@nsXPTCStubBase@@UAAIXZ + ?Stub124@nsXPTCStubBase@@UAAIXZ + ?Stub125@nsXPTCStubBase@@UAAIXZ + ?Stub126@nsXPTCStubBase@@UAAIXZ + ?Stub127@nsXPTCStubBase@@UAAIXZ + ?Stub128@nsXPTCStubBase@@UAAIXZ + ?Stub129@nsXPTCStubBase@@UAAIXZ + ?Stub130@nsXPTCStubBase@@UAAIXZ + ?Stub131@nsXPTCStubBase@@UAAIXZ + ?Stub132@nsXPTCStubBase@@UAAIXZ + ?Stub133@nsXPTCStubBase@@UAAIXZ + ?Stub134@nsXPTCStubBase@@UAAIXZ + ?Stub135@nsXPTCStubBase@@UAAIXZ + ?Stub136@nsXPTCStubBase@@UAAIXZ + ?Stub137@nsXPTCStubBase@@UAAIXZ + ?Stub138@nsXPTCStubBase@@UAAIXZ + ?Stub139@nsXPTCStubBase@@UAAIXZ + ?Stub140@nsXPTCStubBase@@UAAIXZ + ?Stub141@nsXPTCStubBase@@UAAIXZ + ?Stub142@nsXPTCStubBase@@UAAIXZ + ?Stub143@nsXPTCStubBase@@UAAIXZ + ?Stub144@nsXPTCStubBase@@UAAIXZ + ?Stub145@nsXPTCStubBase@@UAAIXZ + ?Stub146@nsXPTCStubBase@@UAAIXZ + ?Stub147@nsXPTCStubBase@@UAAIXZ + ?Stub148@nsXPTCStubBase@@UAAIXZ + ?Stub149@nsXPTCStubBase@@UAAIXZ + ?Stub150@nsXPTCStubBase@@UAAIXZ + ?Stub151@nsXPTCStubBase@@UAAIXZ + ?Stub152@nsXPTCStubBase@@UAAIXZ + ?Stub153@nsXPTCStubBase@@UAAIXZ + ?Stub154@nsXPTCStubBase@@UAAIXZ + ?Stub155@nsXPTCStubBase@@UAAIXZ + ?Stub156@nsXPTCStubBase@@UAAIXZ + ?Stub157@nsXPTCStubBase@@UAAIXZ + ?Stub158@nsXPTCStubBase@@UAAIXZ + ?Stub159@nsXPTCStubBase@@UAAIXZ + ?Stub160@nsXPTCStubBase@@UAAIXZ + ?Stub161@nsXPTCStubBase@@UAAIXZ + ?Stub162@nsXPTCStubBase@@UAAIXZ + ?Stub163@nsXPTCStubBase@@UAAIXZ + ?Stub164@nsXPTCStubBase@@UAAIXZ + ?Stub165@nsXPTCStubBase@@UAAIXZ + ?Stub166@nsXPTCStubBase@@UAAIXZ + ?Stub167@nsXPTCStubBase@@UAAIXZ + ?Stub168@nsXPTCStubBase@@UAAIXZ + ?Stub169@nsXPTCStubBase@@UAAIXZ + ?Stub170@nsXPTCStubBase@@UAAIXZ + ?Stub171@nsXPTCStubBase@@UAAIXZ + ?Stub172@nsXPTCStubBase@@UAAIXZ + ?Stub173@nsXPTCStubBase@@UAAIXZ + ?Stub174@nsXPTCStubBase@@UAAIXZ + ?Stub175@nsXPTCStubBase@@UAAIXZ + ?Stub176@nsXPTCStubBase@@UAAIXZ + ?Stub177@nsXPTCStubBase@@UAAIXZ + ?Stub178@nsXPTCStubBase@@UAAIXZ + ?Stub179@nsXPTCStubBase@@UAAIXZ + ?Stub180@nsXPTCStubBase@@UAAIXZ + ?Stub181@nsXPTCStubBase@@UAAIXZ + ?Stub182@nsXPTCStubBase@@UAAIXZ + ?Stub183@nsXPTCStubBase@@UAAIXZ + ?Stub184@nsXPTCStubBase@@UAAIXZ + ?Stub185@nsXPTCStubBase@@UAAIXZ + ?Stub186@nsXPTCStubBase@@UAAIXZ + ?Stub187@nsXPTCStubBase@@UAAIXZ + ?Stub188@nsXPTCStubBase@@UAAIXZ + ?Stub189@nsXPTCStubBase@@UAAIXZ + ?Stub190@nsXPTCStubBase@@UAAIXZ + ?Stub191@nsXPTCStubBase@@UAAIXZ + ?Stub192@nsXPTCStubBase@@UAAIXZ + ?Stub193@nsXPTCStubBase@@UAAIXZ + ?Stub194@nsXPTCStubBase@@UAAIXZ + ?Stub195@nsXPTCStubBase@@UAAIXZ + ?Stub196@nsXPTCStubBase@@UAAIXZ + ?Stub197@nsXPTCStubBase@@UAAIXZ + ?Stub198@nsXPTCStubBase@@UAAIXZ + ?Stub199@nsXPTCStubBase@@UAAIXZ + ?Stub200@nsXPTCStubBase@@UAAIXZ + ?Stub201@nsXPTCStubBase@@UAAIXZ + ?Stub202@nsXPTCStubBase@@UAAIXZ + ?Stub203@nsXPTCStubBase@@UAAIXZ + ?Stub204@nsXPTCStubBase@@UAAIXZ + ?Stub205@nsXPTCStubBase@@UAAIXZ + ?Stub206@nsXPTCStubBase@@UAAIXZ + ?Stub207@nsXPTCStubBase@@UAAIXZ + ?Stub208@nsXPTCStubBase@@UAAIXZ + ?Stub209@nsXPTCStubBase@@UAAIXZ + ?Stub210@nsXPTCStubBase@@UAAIXZ + ?Stub211@nsXPTCStubBase@@UAAIXZ + ?Stub212@nsXPTCStubBase@@UAAIXZ + ?Stub213@nsXPTCStubBase@@UAAIXZ + ?Stub214@nsXPTCStubBase@@UAAIXZ + ?Stub215@nsXPTCStubBase@@UAAIXZ + ?Stub216@nsXPTCStubBase@@UAAIXZ + ?Stub217@nsXPTCStubBase@@UAAIXZ + ?Stub218@nsXPTCStubBase@@UAAIXZ + ?Stub219@nsXPTCStubBase@@UAAIXZ + ?Stub220@nsXPTCStubBase@@UAAIXZ + ?Stub221@nsXPTCStubBase@@UAAIXZ + ?Stub222@nsXPTCStubBase@@UAAIXZ + ?Stub223@nsXPTCStubBase@@UAAIXZ + ?Stub224@nsXPTCStubBase@@UAAIXZ + ?Stub225@nsXPTCStubBase@@UAAIXZ + ?Stub226@nsXPTCStubBase@@UAAIXZ + ?Stub227@nsXPTCStubBase@@UAAIXZ + ?Stub228@nsXPTCStubBase@@UAAIXZ + ?Stub229@nsXPTCStubBase@@UAAIXZ + ?Stub230@nsXPTCStubBase@@UAAIXZ + ?Stub231@nsXPTCStubBase@@UAAIXZ + ?Stub232@nsXPTCStubBase@@UAAIXZ + ?Stub233@nsXPTCStubBase@@UAAIXZ + ?Stub234@nsXPTCStubBase@@UAAIXZ + ?Stub235@nsXPTCStubBase@@UAAIXZ + ?Stub236@nsXPTCStubBase@@UAAIXZ + ?Stub237@nsXPTCStubBase@@UAAIXZ + ?Stub238@nsXPTCStubBase@@UAAIXZ + ?Stub239@nsXPTCStubBase@@UAAIXZ + ?Stub240@nsXPTCStubBase@@UAAIXZ + ?Stub241@nsXPTCStubBase@@UAAIXZ + ?Stub242@nsXPTCStubBase@@UAAIXZ + ?Stub243@nsXPTCStubBase@@UAAIXZ + ?Stub244@nsXPTCStubBase@@UAAIXZ + ?Stub245@nsXPTCStubBase@@UAAIXZ + ?Stub246@nsXPTCStubBase@@UAAIXZ + ?Stub247@nsXPTCStubBase@@UAAIXZ + ?Stub248@nsXPTCStubBase@@UAAIXZ + ?Stub249@nsXPTCStubBase@@UAAIXZ + diff --git a/xpcom/components/GenericFactory.cpp b/xpcom/components/GenericFactory.cpp new file mode 100644 index 0000000000..5eb88e8ba4 --- /dev/null +++ b/xpcom/components/GenericFactory.cpp @@ -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/. */ + +#include "mozilla/GenericFactory.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(GenericFactory, nsIFactory) + +NS_IMETHODIMP +GenericFactory::CreateInstance(REFNSIID aIID, void** aResult) { + return mCtor(aIID, aResult); +} + +} // namespace mozilla diff --git a/xpcom/components/GenericFactory.h b/xpcom/components/GenericFactory.h new file mode 100644 index 0000000000..3fb2186e12 --- /dev/null +++ b/xpcom/components/GenericFactory.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 mozilla_GenericFactory_h +#define mozilla_GenericFactory_h + +#include "nsIFactory.h" + +namespace mozilla { + +/** + * A generic factory which uses a constructor function to create instances. + * This class is intended for use by the component manager and the generic + * module. + */ +class GenericFactory final : public nsIFactory { + ~GenericFactory() = default; + + public: + typedef nsresult (*ConstructorProcPtr)(const nsIID& aIID, void** aResult); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit GenericFactory(ConstructorProcPtr aCtor) : mCtor(aCtor) { + NS_ASSERTION(mCtor, "GenericFactory with no constructor"); + } + + private: + ConstructorProcPtr mCtor; +}; + +} // namespace mozilla + +#endif // mozilla_GenericFactory_h diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp new file mode 100644 index 0000000000..88ee06d78d --- /dev/null +++ b/xpcom/components/ManifestParser.cpp @@ -0,0 +1,678 @@ +/* -*- 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/Printf.h" +#include "mozilla/UniquePtr.h" + +#include "ManifestParser.h" + +#include <string.h> + +#include "prio.h" +#if defined(XP_WIN) +# include <windows.h> +#elif defined(MOZ_WIDGET_COCOA) +# include <CoreServices/CoreServices.h> +# include "nsCocoaFeatures.h" +#elif defined(MOZ_WIDGET_GTK) +# include <gtk/gtk.h> +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBuild.h" +# include "mozilla/java/GeckoAppShellWrappers.h" +#endif + +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif + +#include "mozilla/Services.h" + +#include "nsCRT.h" +#include "nsConsoleMessage.h" +#include "nsTextFormatter.h" +#include "nsVersionComparator.h" +#include "nsXPCOMCIDInternal.h" + +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsIXULAppInfo.h" +#include "nsIXULRuntime.h" + +using namespace mozilla; + +struct ManifestDirective { + const char* directive; + int argc; + + bool ischrome; + + // The contentaccessible flags only apply to content/resource directives. + bool contentflags; + + // Function to handle this directive. This isn't a union because C++ still + // hasn't learned how to initialize unions in a sane way. + void (nsComponentManagerImpl::*mgrfunc)( + nsComponentManagerImpl::ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void (nsChromeRegistry::*regfunc)( + nsChromeRegistry::ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv, int aFlags); +}; +static const ManifestDirective kParsingTable[] = { + // clang-format off + { + "manifest", 1, true, false, + &nsComponentManagerImpl::ManifestManifest, nullptr, + }, + { + "category", 3, false, false, + &nsComponentManagerImpl::ManifestCategory, nullptr, + }, + { + "content", 2, true, true, + nullptr, &nsChromeRegistry::ManifestContent, + }, + { + "locale", 3, true, false, + nullptr, &nsChromeRegistry::ManifestLocale, + }, + { + "skin", 3, true, false, + nullptr, &nsChromeRegistry::ManifestSkin, + }, + { + // NB: note that while skin manifests can use this, they are only allowed + // to use it for chrome://../skin/ URLs + "override", 2, true, false, + nullptr, &nsChromeRegistry::ManifestOverride, + }, + { + "resource", 2, false, true, + nullptr, &nsChromeRegistry::ManifestResource, + } + // clang-format on +}; + +static const char kWhitespace[] = "\t "; + +static bool IsNewline(char aChar) { return aChar == '\n' || aChar == '\r'; } + +void LogMessage(const char* aMsg, ...) { + MOZ_ASSERT(nsComponentManagerImpl::gComponentManager); + + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + va_list args; + va_start(args, aMsg); + SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args)); + va_end(args); + + nsCOMPtr<nsIConsoleMessage> error = + new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted.get())); + console->LogMessage(error); +} + +void LogMessageWithContext(FileLocation& aFile, uint32_t aLineNumber, + const char* aMsg, ...) { + va_list args; + va_start(args, aMsg); + SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args)); + va_end(args); + if (!formatted) { + return; + } + + MOZ_ASSERT(nsComponentManagerImpl::gComponentManager); + + nsCString file; + aFile.GetURIString(file); + + nsCOMPtr<nsIScriptError> error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + if (!error) { + // This can happen early in component registration. Fall back to a + // generic console message. + LogMessage("Warning: in '%s', line %i: %s", file.get(), aLineNumber, + formatted.get()); + return; + } + + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + nsresult rv = error->Init( + NS_ConvertUTF8toUTF16(formatted.get()), NS_ConvertUTF8toUTF16(file), + u""_ns, aLineNumber, 0, nsIScriptError::warningFlag, + "chrome registration"_ns, false /* from private window */, + true /* from chrome context */); + if (NS_FAILED(rv)) { + return; + } + + console->LogMessage(error); +} + +/** + * Check for a modifier flag of the following forms: + * "flag" (same as "true") + * "flag=yes|true|1" + * "flag="no|false|0" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aResult If the flag is found, the value is assigned here. + * @return Whether the flag was handled. + */ +static bool CheckFlag(const nsAString& aFlag, const nsAString& aData, + bool& aResult) { + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + if (aFlag.Length() == aData.Length()) { + // the data is simply "flag", which is the same as "flag=yes" + aResult = true; + return true; + } + + if (aData.CharAt(aFlag.Length()) != '=') { + // the data is "flag2=", which is not anything we care about + return false; + } + + if (aData.Length() == aFlag.Length() + 1) { + aResult = false; + return true; + } + + switch (aData.CharAt(aFlag.Length() + 1)) { + case '1': + case 't': // true + case 'y': // yes + aResult = true; + return true; + + case '0': + case 'f': // false + case 'n': // no + aResult = false; + return true; + } + + return false; +} + +enum TriState { eUnspecified, eBad, eOK }; + +/** + * Check for a modifier flag of the following form: + * "flag=string" + * "flag!=string" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. + * @param aResult If this is "ok" when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ +static bool CheckStringFlag(const nsAString& aFlag, const nsAString& aData, + const nsAString& aValue, TriState& aResult) { + if (aData.Length() < aFlag.Length() + 1) { + return false; + } + + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + bool comparison = true; + if (aData[aFlag.Length()] != '=') { + if (aData[aFlag.Length()] == '!' && aData.Length() >= aFlag.Length() + 2 && + aData[aFlag.Length() + 1] == '=') { + comparison = false; + } else { + return false; + } + } + + if (aResult != eOK) { + nsDependentSubstring testdata = + Substring(aData, aFlag.Length() + (comparison ? 1 : 2)); + if (testdata.Equals(aValue)) { + aResult = comparison ? eOK : eBad; + } else { + aResult = comparison ? eBad : eOK; + } + } + + return true; +} + +static bool CheckOsFlag(const nsAString& aFlag, const nsAString& aData, + const nsAString& aValue, TriState& aResult) { + bool result = CheckStringFlag(aFlag, aData, aValue, aResult); +#if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(ANDROID) + if (result && aResult == eBad) { + result = CheckStringFlag(aFlag, aData, u"likeunix"_ns, aResult); + } +#endif + return result; +} + +/** + * Check for a modifier flag of the following form: + * "flag=version" + * "flag<=version" + * "flag<version" + * "flag>=version" + * "flag>version" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. If this is empty then no + * comparison will match. + * @param aResult If this is eOK when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ + +#define COMPARE_EQ 1 << 0 +#define COMPARE_LT 1 << 1 +#define COMPARE_GT 1 << 2 + +static bool CheckVersionFlag(const nsString& aFlag, const nsString& aData, + const nsString& aValue, TriState& aResult) { + if (aData.Length() < aFlag.Length() + 2) { + return false; + } + + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + if (aValue.Length() == 0) { + if (aResult != eOK) { + aResult = eBad; + } + return true; + } + + uint32_t comparison; + nsAutoString testdata; + + switch (aData[aFlag.Length()]) { + case '=': + comparison = COMPARE_EQ; + testdata = Substring(aData, aFlag.Length() + 1); + break; + + case '<': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 2); + } else { + comparison = COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + case '>': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 2); + } else { + comparison = COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + default: + return false; + } + + if (testdata.Length() == 0) { + return false; + } + + if (aResult != eOK) { + int32_t c = mozilla::CompareVersions(NS_ConvertUTF16toUTF8(aValue).get(), + NS_ConvertUTF16toUTF8(testdata).get()); + if ((c == 0 && comparison & COMPARE_EQ) || + (c < 0 && comparison & COMPARE_LT) || + (c > 0 && comparison & COMPARE_GT)) { + aResult = eOK; + } else { + aResult = eBad; + } + } + + return true; +} + +// In-place conversion of ascii characters to lower case +static void ToLowerCase(char* aToken) { + for (; *aToken; ++aToken) { + *aToken = NS_ToLower(*aToken); + } +} + +namespace { + +struct CachedDirective { + int lineno; + char* argv[4]; +}; + +} // namespace + +void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, + bool aChromeOnly) { + nsComponentManagerImpl::ManifestProcessingContext mgrcx(aType, aFile, + aChromeOnly); + nsChromeRegistry::ManifestProcessingContext chromecx(aType, aFile); + nsresult rv; + + constexpr auto kContentAccessible = u"contentaccessible"_ns; + constexpr auto kRemoteEnabled = u"remoteenabled"_ns; + constexpr auto kRemoteRequired = u"remoterequired"_ns; + constexpr auto kApplication = u"application"_ns; + constexpr auto kAppVersion = u"appversion"_ns; + constexpr auto kGeckoVersion = u"platformversion"_ns; + constexpr auto kOs = u"os"_ns; + constexpr auto kOsVersion = u"osversion"_ns; + constexpr auto kABI = u"abi"_ns; + constexpr auto kProcess = u"process"_ns; +#if defined(MOZ_WIDGET_ANDROID) + constexpr auto kTablet = u"tablet"_ns; +#endif + // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, but it's not + // possible to have conditional manifest contents, so we need to recognize and + // discard these tokens even when MOZ_BACKGROUNDTASKS is not set. + constexpr auto kBackgroundTask = u"backgroundtask"_ns; + + constexpr auto kMain = u"main"_ns; + constexpr auto kContent = u"content"_ns; + + // Obsolete + constexpr auto kXPCNativeWrappers = u"xpcnativewrappers"_ns; + + nsAutoString appID; + nsAutoString appVersion; + nsAutoString geckoVersion; + nsAutoString osTarget; + nsAutoString abi; + nsAutoString process; + + nsCOMPtr<nsIXULAppInfo> xapp(do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); + if (xapp) { + nsAutoCString s; + rv = xapp->GetID(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, appID); + } + + rv = xapp->GetVersion(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, appVersion); + } + + rv = xapp->GetPlatformVersion(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, geckoVersion); + } + + nsCOMPtr<nsIXULRuntime> xruntime(do_QueryInterface(xapp)); + if (xruntime) { + rv = xruntime->GetOS(s); + if (NS_SUCCEEDED(rv)) { + ToLowerCase(s); + CopyUTF8toUTF16(s, osTarget); + } + + rv = xruntime->GetXPCOMABI(s); + if (NS_SUCCEEDED(rv) && osTarget.Length()) { + ToLowerCase(s); + CopyUTF8toUTF16(s, abi); + abi.Insert(char16_t('_'), 0); + abi.Insert(osTarget, 0); + } + } + } + + nsAutoString osVersion; +#if defined(XP_WIN) +# pragma warning(push) +# pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx + OSVERSIONINFO info = {sizeof(OSVERSIONINFO)}; + if (GetVersionEx(&info)) { + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion, + info.dwMinorVersion); + } +# pragma warning(pop) +#elif defined(MOZ_WIDGET_COCOA) + SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor(); + SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor(); + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", majorVersion, minorVersion); +#elif defined(MOZ_WIDGET_GTK) + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", gtk_major_version, + gtk_minor_version); +#elif defined(MOZ_WIDGET_ANDROID) + bool isTablet = false; + if (jni::IsAvailable()) { + jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE(); + osVersion.Assign(release->ToString()); + isTablet = java::GeckoAppShell::IsTablet(); + } +#endif + + if (XRE_IsContentProcess()) { + process = kContent; + } else { + process = kMain; + } + + char* token; + char* newline = aBuf; + uint32_t line = 0; + + // outer loop tokenizes by newline + while (*newline) { + while (*newline && IsNewline(*newline)) { + ++newline; + ++line; + } + if (!*newline) { + break; + } + + token = newline; + while (*newline && !IsNewline(*newline)) { + ++newline; + } + + if (*newline) { + *newline = '\0'; + ++newline; + } + ++line; + + if (*token == '#') { // ignore lines that begin with # as comments + continue; + } + + char* whitespace = token; + token = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + if (!token) { + continue; + } + + const ManifestDirective* directive = nullptr; + for (const ManifestDirective* d = kParsingTable; + d < ArrayEnd(kParsingTable); ++d) { + if (!strcmp(d->directive, token)) { + directive = d; + break; + } + } + + if (!directive) { + LogMessageWithContext( + aFile, line, "Ignoring unrecognized chrome manifest directive '%s'.", + token); + continue; + } + + if (!directive->ischrome && NS_BOOTSTRAPPED_LOCATION == aType) { + LogMessageWithContext( + aFile, line, + "Bootstrapped manifest not allowed to use '%s' directive.", token); + continue; + } + + NS_ASSERTION(directive->argc < 4, "Need to reset argv array length"); + char* argv[4]; + for (int i = 0; i < directive->argc; ++i) { + argv[i] = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + } + + if (!argv[directive->argc - 1]) { + LogMessageWithContext(aFile, line, + "Not enough arguments for chrome manifest " + "directive '%s', expected %i.", + token, directive->argc); + continue; + } + + bool ok = true; + TriState stAppVersion = eUnspecified; + TriState stGeckoVersion = eUnspecified; + TriState stApp = eUnspecified; + TriState stOsVersion = eUnspecified; + TriState stOs = eUnspecified; + TriState stABI = eUnspecified; + TriState stProcess = eUnspecified; +#if defined(MOZ_WIDGET_ANDROID) + TriState stTablet = eUnspecified; +#endif +#ifdef MOZ_BACKGROUNDTASKS + // When in background task mode, default to not registering + // category directivies unless backgroundtask=1 is specified. + TriState stBackgroundTask = (BackgroundTasks::IsBackgroundTaskMode() && + strcmp("category", directive->directive) == 0) + ? eBad + : eUnspecified; +#endif + int flags = 0; + + while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && + ok) { + ToLowerCase(token); + NS_ConvertASCIItoUTF16 wtoken(token); + + if (CheckStringFlag(kApplication, wtoken, appID, stApp) || + CheckOsFlag(kOs, wtoken, osTarget, stOs) || + CheckStringFlag(kABI, wtoken, abi, stABI) || + CheckStringFlag(kProcess, wtoken, process, stProcess) || + CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || + CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || + CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, + stGeckoVersion)) { + continue; + } + +#if defined(MOZ_WIDGET_ANDROID) + bool tablet = false; + if (CheckFlag(kTablet, wtoken, tablet)) { + stTablet = (tablet == isTablet) ? eOK : eBad; + continue; + } +#endif + + // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, it's not + // possible to have conditional manifest contents. + bool flag; + if (CheckFlag(kBackgroundTask, wtoken, flag)) { +#if defined(MOZ_BACKGROUNDTASKS) + // Background task mode is active: filter. + stBackgroundTask = + (flag == BackgroundTasks::IsBackgroundTaskMode()) ? eOK : eBad; +#endif /* defined(MOZ_BACKGROUNDTASKS) */ + continue; + } + + if (directive->contentflags) { + bool flag; + if (CheckFlag(kContentAccessible, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::CONTENT_ACCESSIBLE; + continue; + } + if (CheckFlag(kRemoteEnabled, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::REMOTE_ALLOWED; + continue; + } + if (CheckFlag(kRemoteRequired, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::REMOTE_REQUIRED; + continue; + } + } + + bool xpcNativeWrappers = true; // Dummy for CheckFlag. + if (CheckFlag(kXPCNativeWrappers, wtoken, xpcNativeWrappers)) { + LogMessageWithContext( + aFile, line, "Ignoring obsolete chrome registration modifier '%s'.", + token); + continue; + } + + LogMessageWithContext( + aFile, line, "Unrecognized chrome manifest modifier '%s'.", token); + ok = false; + } + + if (!ok || stApp == eBad || stAppVersion == eBad || + stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || +#ifdef MOZ_WIDGET_ANDROID + stTablet == eBad || +#endif +#ifdef MOZ_BACKGROUNDTASKS + stBackgroundTask == eBad || +#endif + stABI == eBad || stProcess == eBad) { + continue; + } + + if (directive->regfunc) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + continue; + } + + if (!nsChromeRegistry::gChromeRegistry) { + nsCOMPtr<nsIChromeRegistry> cr = mozilla::services::GetChromeRegistry(); + if (!nsChromeRegistry::gChromeRegistry) { + LogMessageWithContext(aFile, line, + "Chrome registry isn't available yet."); + continue; + } + } + + (nsChromeRegistry::gChromeRegistry->*(directive->regfunc))(chromecx, line, + argv, flags); + } else if (directive->ischrome || !aChromeOnly) { + (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc))( + mgrcx, line, argv); + } + } +} diff --git a/xpcom/components/ManifestParser.h b/xpcom/components/ManifestParser.h new file mode 100644 index 0000000000..e66cb58bcb --- /dev/null +++ b/xpcom/components/ManifestParser.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 ManifestParser_h +#define ManifestParser_h + +#include "nsComponentManager.h" +#include "nsChromeRegistry.h" +#include "mozilla/Attributes.h" +#include "mozilla/FileLocation.h" + +void ParseManifest(NSLocationType aType, mozilla::FileLocation& aFile, + char* aBuf, bool aChromeOnly); + +void LogMessage(const char* aMsg, ...) MOZ_FORMAT_PRINTF(1, 2); + +void LogMessageWithContext(mozilla::FileLocation& aFile, uint32_t aLineNumber, + const char* aMsg, ...) MOZ_FORMAT_PRINTF(3, 4); + +#endif // ManifestParser_h diff --git a/xpcom/components/Module.h b/xpcom/components/Module.h new file mode 100644 index 0000000000..b8fae64356 --- /dev/null +++ b/xpcom/components/Module.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_Module_h +#define mozilla_Module_h + +#include "nscore.h" + +namespace mozilla { + +namespace Module { +/** + * This selector allows components to be marked so that they're only loaded + * into certain kinds of processes. Selectors can be combined. + */ +// Note: This must be kept in sync with the selector matching in +// nsComponentManager.cpp. +enum ProcessSelector { + ANY_PROCESS = 0, + MAIN_PROCESS_ONLY = 1 << 0, + CONTENT_PROCESS_ONLY = 1 << 1, + + /** + * By default, modules are not loaded in the GPU, VR, Socket, RDD, Utility, + * GMPlugin and IPDLUnitTest processes, even if ANY_PROCESS is specified. + * This flag enables a module in the relevant process. + * + * NOTE: IPDLUnitTest does not have its own flag, and will only load a + * module if it is enabled in all processes. + */ + ALLOW_IN_GPU_PROCESS = 1 << 2, + ALLOW_IN_VR_PROCESS = 1 << 3, + ALLOW_IN_SOCKET_PROCESS = 1 << 4, + ALLOW_IN_RDD_PROCESS = 1 << 5, + ALLOW_IN_UTILITY_PROCESS = 1 << 6, + ALLOW_IN_GMPLUGIN_PROCESS = 1 << 7, + ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY, + ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS, + ALLOW_IN_GPU_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_RDD_AND_SOCKET_PROCESS = + ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | + ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | + ALLOW_IN_SOCKET_PROCESS | ALLOW_IN_UTILITY_PROCESS, + ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | + ALLOW_IN_SOCKET_PROCESS | ALLOW_IN_UTILITY_PROCESS | + ALLOW_IN_GMPLUGIN_PROCESS +}; + +static constexpr size_t kMaxProcessSelector = size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS); + +/** + * This allows category entries to be marked so that they are or are + * not loaded when in backgroundtask mode. + */ +// Note: This must be kept in sync with the selector matching in +// StaticComponents.cpp.in. +enum BackgroundTasksSelector { + NO_TASKS = 0x0, + ALL_TASKS = 0xFFFF, +}; +}; // namespace Module + +} // namespace mozilla + +#endif // mozilla_Module_h diff --git a/xpcom/components/ModuleUtils.h b/xpcom/components/ModuleUtils.h new file mode 100644 index 0000000000..92ec8aa173 --- /dev/null +++ b/xpcom/components/ModuleUtils.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_GenericModule_h +#define mozilla_GenericModule_h + +#include <type_traits> + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/Module.h" + +#define NS_GENERIC_FACTORY_CONSTRUCTOR(_InstanceClass) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + inst = new _InstanceClass(); \ + return inst->QueryInterface(aIID, aResult); \ + } + +#define NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(_InstanceClass, _InitMethod) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + nsresult rv; \ + \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + inst = new _InstanceClass(); \ + rv = inst->_InitMethod(); \ + if (NS_SUCCEEDED(rv)) { \ + rv = inst->QueryInterface(aIID, aResult); \ + } \ + \ + return rv; \ + } + +namespace mozilla { +namespace detail { + +template <typename T> +struct RemoveAlreadyAddRefed { + using Type = T; +}; + +template <typename T> +struct RemoveAlreadyAddRefed<already_AddRefed<T>> { + using Type = T; +}; + +} // namespace detail +} // namespace mozilla + +// 'Constructor' that uses an existing getter function that gets a singleton. +#define NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(_InstanceClass, _GetterProc) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + using T = \ + mozilla::detail::RemoveAlreadyAddRefed<decltype(_GetterProc())>::Type; \ + static_assert( \ + std::is_same_v<already_AddRefed<T>, decltype(_GetterProc())>, \ + "Singleton constructor must return already_AddRefed"); \ + static_assert( \ + std::is_base_of<_InstanceClass, T>::value, \ + "Singleton constructor must return correct already_AddRefed"); \ + inst = _GetterProc(); \ + if (nullptr == inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + return inst->QueryInterface(aIID, aResult); \ + } + +#endif // mozilla_GenericModule_h diff --git a/xpcom/components/StaticComponents.cpp.in b/xpcom/components/StaticComponents.cpp.in new file mode 100644 index 0000000000..7f3fee6859 --- /dev/null +++ b/xpcom/components/StaticComponents.cpp.in @@ -0,0 +1,410 @@ +/* -*- 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 "StaticComponents.h" + +#include "mozilla/ArrayUtils.h" +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif +#include "mozilla/PerfectHash.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozJSModuleLoader.h" +#include "nsCOMPtr.h" +#include "nsComponentManager.h" +#include "nsContentUtils.h" +#include "nsIFactory.h" +#include "nsISupports.h" +#include "nsIXPConnect.h" +#include "nsString.h" +#include "nsStringEnumerator.h" +#include "nsTArray.h" +#include "xptdata.h" +#include "xptinfo.h" +#include "js/PropertyAndElement.h" // JS_GetProperty + +// Cleanup pollution from zipstruct.h +#undef UNSUPPORTED + +// Public includes +//# @includes@ + +// Relative includes +//# @relative_includes@ + +//# @decls@ + +namespace mozilla { + +using dom::AutoJSAPI; + +namespace xpcom { + +static constexpr uint32_t kNoContractID = 0xffffffff; + +namespace { +// Template helpers for constructor function sanity checks. +template <typename T> +struct RemoveAlreadyAddRefed { + using Type = T; +}; + +template <typename T> +struct RemoveAlreadyAddRefed<already_AddRefed<T>> { + using Type = T; +}; +} // anonymous namespace + + +uint8_t gInvalidContracts[kContractCount / 8 + 1]; + +static StaticRefPtr<nsISupports> gServiceInstances[kStaticModuleCount]; + +uint8_t gInitCalled[kModuleInitCount / 8 + 1]; + +static const char gStrings[] = +//# @strings@ + ""; + +const StaticCategory gStaticCategories[kStaticCategoryCount] = { +//# @categories@ +}; +const StaticCategoryEntry gStaticCategoryEntries[] = { +//# @category_entries@ +}; + +const nsXPTInterface gInterfaces[] = { +//# @interfaces@ +}; + +const StringOffset gComponentJSMs[] = { +//# @component_jsms@ +}; + +const StringOffset gComponentESModules[] = { +//# @component_esmodules@ +}; + +/** + * Returns a nsCString corresponding to the given entry in the `gStrings` string + * table. The resulting nsCString points directly to static storage, and does + * not incur any memory allocation overhead. + */ +static inline nsCString GetString(const StringOffset& aOffset) { + const char* str = &gStrings[aOffset.mOffset]; + nsCString result; + result.AssignLiteral(str, strlen(str)); + return result; +} + +nsCString ContractEntry::ContractID() const { + return GetString(mContractID); +} + +bool ContractEntry::Matches(const nsACString& aContractID) const { + return aContractID == ContractID() && Module().Active(); +} + +enum class ComponentType { JSM, ESM }; + +template <ComponentType type> +static nsresult ConstructJSMOrESMComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + if (!nsComponentManagerImpl::JSLoaderReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsapi.cx(); + + JS::Rooted<JSObject*> exports(cx); + if constexpr (type == ComponentType::JSM) { + JS::Rooted<JSObject*> global(cx); + MOZ_TRY(mozJSModuleLoader::Get()->Import(cx, aURI, &global, &exports)); + } else { + MOZ_TRY(mozJSModuleLoader::Get()->ImportESModule(cx, aURI, &exports)); + } + + JS::Rooted<JS::Value> ctor(cx); + if (!JS_GetProperty(cx, exports, aConstructor, &ctor) || + !ctor.isObject()) { + return NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED; + } + + JS::Rooted<JSObject*> inst(cx); + if (!JS::Construct(cx, ctor, JS::HandleValueArray::empty(), &inst)) { + return NS_ERROR_FAILURE; + } + + return nsContentUtils::XPConnect()->WrapJS(cx, inst, NS_GET_IID(nsISupports), + (void**)aResult); +} + +static nsresult ConstructJSMComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + return ConstructJSMOrESMComponent<ComponentType::JSM>( + aURI, aConstructor, aResult); +} + +static nsresult ConstructESModuleComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + return ConstructJSMOrESMComponent<ComponentType::ESM>( + aURI, aConstructor, aResult); +} + +//# @module_cid_table@ + +//# @module_contract_id_table@ + +//# @js_services_table@ + +//# @protocol_handlers_table@ + +static inline bool CalledInit(size_t aIdx) { + return GetBit(gInitCalled, aIdx); +} + +static nsresult CallInitFunc(size_t aIdx) { + if (CalledInit(aIdx)) { + return NS_OK; + } + + nsresult rv = NS_OK; + switch (aIdx) { +//# @init_funcs@ + } + + SetBit(gInitCalled, aIdx); + + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return rv; +} + +static void CallUnloadFuncs() { +//# @unload_funcs@ +} + +nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult) { + // The full set of constructors for all static modules. + // This switch statement will be compiled to a relative address jump table + // with no runtime relocations and a single indirect jump. + switch (aID) { +//# @constructors@ + } + + MOZ_ASSERT_UNREACHABLE("Constructor didn't return"); + return NS_ERROR_FAILURE; +} + + +namespace { + +class StaticModuleFactory final : public nsIFactory { + NS_DECL_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit StaticModuleFactory(ModuleID aID) : mID(aID) {} + +private: + ~StaticModuleFactory() = default; + + const ModuleID mID; +}; + +NS_IMPL_ISUPPORTS(StaticModuleFactory, nsIFactory) + +NS_IMETHODIMP StaticModuleFactory::CreateInstance(const nsIID& aIID, + void** aResult) { + return CreateInstanceImpl(mID, aIID, aResult); +} + +} // anonymous namespace + + +already_AddRefed<nsIFactory> StaticModule::GetFactory() const { + return do_AddRef(new StaticModuleFactory(ID())); +} + +bool StaticModule::Active() const { + return FastProcessSelectorMatches(mProcessSelector); +} + +bool StaticModule::Overridable() const { + return mContractID.mOffset != kNoContractID; +} + +nsCString StaticModule::ContractID() const { + MOZ_ASSERT(Overridable()); + return GetString(mContractID); +} + +nsresult StaticModule::CreateInstance(const nsIID& aIID, void** aResult) const { + return CreateInstanceImpl(ID(), aIID, aResult); +} + +GetServiceHelper StaticModule::GetService() const { + return { ID(), nullptr }; +} + +GetServiceHelper StaticModule::GetService(nsresult* aRv) const { + return { ID(), aRv }; +} + + +nsISupports* StaticModule::ServiceInstance() const { + return gServiceInstances[Idx()]; +} + +void StaticModule::SetServiceInstance( + already_AddRefed<nsISupports> aInst) const { + gServiceInstances[Idx()] = aInst; +} + + +nsCString StaticCategoryEntry::Entry() const { + return GetString(mEntry); +} + +nsCString StaticCategoryEntry::Value() const { + return GetString(mValue); +} + +bool StaticCategoryEntry::Active() const { + if (!FastProcessSelectorMatches(mProcessSelector)) { + return false; + } +#ifdef MOZ_BACKGROUNDTASKS + if (MOZ_UNLIKELY(BackgroundTasks::IsBackgroundTaskMode())) { + return mBackgroundTasksSelector != Module::BackgroundTasksSelector::NO_TASKS; + } +#endif /* MOZ_BACKGROUNDTASKS */ + return true; +} + +nsCString StaticCategory::Name() const { + return GetString(mName); +} + +nsCString JSServiceEntry::Name() const { + return GetString(mName); +} + +JSServiceEntry::InterfaceList JSServiceEntry::Interfaces() const { + InterfaceList iids; + iids.SetCapacity(mInterfaceCount); + + for (size_t i = 0; i < mInterfaceCount; i++) { + nsXPTInterface ifaceID = gInterfaces[mInterfaceOffset.mOffset + i]; + iids.AppendElement(&nsXPTInterfaceInfo::Get(ifaceID)->IID()); + } + return iids; +} + + +/* static */ +const JSServiceEntry* JSServiceEntry::Lookup(const nsACString& aName) { + return LookupJSService(aName); +} + +nsCString StaticProtocolHandler::Scheme() const { + return GetString(mScheme); +} + +/* static */ +const StaticProtocolHandler* StaticProtocolHandler::Lookup(const nsACString& aScheme) { + return LookupProtocolHandler(aScheme); +} + +/* static */ const StaticModule* StaticComponents::LookupByCID( + const nsID& aCID) { + return ModuleByCID(aCID); +} + +/* static */ const StaticModule* StaticComponents::LookupByContractID( + const nsACString& aContractID) { + if (const ContractEntry* entry = LookupContractID(aContractID)) { + if (!entry->Invalid()) { + return &entry->Module(); + } + } + return nullptr; +} + +/* static */ bool StaticComponents::InvalidateContractID( + const nsACString& aContractID, bool aInvalid) { + if (const ContractEntry* entry = LookupContractID(aContractID)) { + entry->SetInvalid(aInvalid); + return true; + } + return false; +} + +/* static */ already_AddRefed<nsIUTF8StringEnumerator> +StaticComponents::GetComponentJSMs() { + auto jsms = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentJSMs)); + + for (const auto& entry : gComponentJSMs) { + jsms->AppendElement(GetString(entry)); + } + + nsCOMPtr<nsIUTF8StringEnumerator> result; + MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result), + jsms.release())); + return result.forget(); +} + +/* static */ already_AddRefed<nsIUTF8StringEnumerator> +StaticComponents::GetComponentESModules() { + auto esModules = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentESModules)); + + for (const auto& entry : gComponentESModules) { + esModules->AppendElement(GetString(entry)); + } + + nsCOMPtr<nsIUTF8StringEnumerator> result; + MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result), + esModules.release())); + return result.forget(); +} + +/* static */ Span<const JSServiceEntry> StaticComponents::GetJSServices() { + return { gJSServices, ArrayLength(gJSServices) }; +} + +/* static */ void StaticComponents::Shutdown() { + CallUnloadFuncs(); +} + +/* static */ const nsID& Components::GetCID(ModuleID aID) { + return gStaticModules[size_t(aID)].CID(); +} + +nsresult GetServiceHelper::operator()(const nsIID& aIID, void** aResult) const { + nsresult rv = + nsComponentManagerImpl::gComponentManager->GetService(mId, aIID, aResult); + return SetResult(rv); +} + +nsresult CreateInstanceHelper::operator()(const nsIID& aIID, + void** aResult) const { + const auto& entry = gStaticModules[size_t(mId)]; + if (!entry.Active()) { + return SetResult(NS_ERROR_FACTORY_NOT_REGISTERED); + } + + nsresult rv = entry.CreateInstance(aIID, aResult); + return SetResult(rv); +} + +} // namespace xpcom +} // namespace mozilla diff --git a/xpcom/components/StaticComponents.h b/xpcom/components/StaticComponents.h new file mode 100644 index 0000000000..ac4095f2f3 --- /dev/null +++ b/xpcom/components/StaticComponents.h @@ -0,0 +1,284 @@ +/* -*- 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 StaticComponents_h +#define StaticComponents_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Module.h" +#include "mozilla/Span.h" +#include "nsID.h" +#include "nsStringFwd.h" +#include "nscore.h" + +#include "mozilla/Components.h" +#include "StaticComponentData.h" + +class nsIFactory; +class nsIUTF8StringEnumerator; +class nsISupports; +template <typename T, size_t N> +class AutoTArray; + +namespace mozilla { +namespace xpcom { + +struct ContractEntry; +struct StaticModule; + +struct StaticCategoryEntry; +struct StaticCategory; + +struct StaticProtocolHandler; + +extern const StaticModule gStaticModules[kStaticModuleCount]; + +extern const ContractEntry gContractEntries[kContractCount]; +extern uint8_t gInvalidContracts[kContractCount / 8 + 1]; + +extern const StaticCategory gStaticCategories[kStaticCategoryCount]; +extern const StaticCategoryEntry gStaticCategoryEntries[]; + +extern const StaticProtocolHandler + gStaticProtocolHandlers[kStaticProtocolHandlerCount]; + +template <size_t N> +static inline bool GetBit(const uint8_t (&aBits)[N], size_t aBit) { + static constexpr size_t width = sizeof(aBits[0]) * 8; + + size_t idx = aBit / width; + MOZ_ASSERT(idx < N); + return aBits[idx] & (1 << (aBit % width)); +} + +template <size_t N> +static inline void SetBit(uint8_t (&aBits)[N], size_t aBit, + bool aValue = true) { + static constexpr size_t width = sizeof(aBits[0]) * 8; + + size_t idx = aBit / width; + MOZ_ASSERT(idx < N); + if (aValue) { + aBits[idx] |= 1 << (aBit % width); + } else { + aBits[idx] &= ~(1 << (aBit % width)); + } +} + +/** + * Represents a string entry in the static string table. Can be converted to a + * nsCString using GetString() in StaticComponents.cpp. + * + * This is a struct rather than a pure offset primarily for the purposes of type + * safety, but also so that it can easily be extended to include a static length + * in the future, if efficiency concerns warrant it. + */ +struct StringOffset final { + uint32_t mOffset; +}; + +/** + * Represents an offset into the interfaces table. + */ +struct InterfaceOffset final { + uint16_t mOffset; +}; + +/** + * Represents a static component entry defined in a `Classes` list in an XPCOM + * manifest. Handles creating instances of and caching service instances for + * that class. + */ +struct StaticModule { + nsID mCID; + StringOffset mContractID; + Module::ProcessSelector mProcessSelector; + + const nsID& CID() const { return mCID; } + + ModuleID ID() const { return ModuleID(this - gStaticModules); } + + /** + * Returns this entry's index in the gStaticModules array. + */ + size_t Idx() const { return size_t(ID()); } + + /** + * Returns true if this component's corresponding contract ID is expected to + * be overridden at runtime. If so, it should always be looked up by its + * ContractID() when retrieving its service instance. + */ + bool Overridable() const; + + /** + * If this entry is overridable, returns its associated contract ID string. + * The component should always be looked up by this contract ID when + * retrieving its service instance. + * + * Note: This may *only* be called if Overridable() returns true. + */ + nsCString ContractID() const; + + /** + * Returns true if this entry is active. Typically this will only return false + * if the entry's process selector does not match this process. + */ + bool Active() const; + + already_AddRefed<nsIFactory> GetFactory() const; + + nsresult CreateInstance(const nsIID& aIID, void** aResult) const; + + GetServiceHelper GetService() const; + GetServiceHelper GetService(nsresult*) const; + + nsISupports* ServiceInstance() const; + void SetServiceInstance(already_AddRefed<nsISupports> aInst) const; +}; + +/** + * Represents a static mapping between a contract ID string and a StaticModule + * entry. + */ +struct ContractEntry final { + StringOffset mContractID; + ModuleID mModuleID; + + size_t Idx() const { return this - gContractEntries; } + + nsCString ContractID() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } + + /** + * Returns true if this entry's underlying module is active, and its contract + * ID matches the given contract ID string. This is used by the PerfectHash + * function to determine whether to return a result for this entry. + */ + bool Matches(const nsACString& aContractID) const; + + /** + * Returns true if this entry has been invalidated, and should be ignored. + * + * Contract IDs may be overwritten at runtime. When that happens for a static + * contract ID, we mark its entry invalid, and ignore it thereafter. + */ + bool Invalid() const { return GetBit(gInvalidContracts, Idx()); } + + /** + * Marks this entry invalid (or unsets the invalid bit if aInvalid is false), + * after which it will be ignored in contract ID lookup attempts. See + * `Invalid()` above. + */ + void SetInvalid(bool aInvalid = true) const { + return SetBit(gInvalidContracts, Idx(), aInvalid); + } +}; + +/** + * Represents a declared category manager entry declared in an XPCOM manifest. + * + * The entire set of static category entries is read at startup and loaded into + * the category manager's dynamic hash tables, so there is memory and + * initialization overhead for each entry in these tables. This may be further + * optimized in the future to reduce some of that overhead. + */ +struct StaticCategoryEntry final { + StringOffset mEntry; + StringOffset mValue; + Module::BackgroundTasksSelector mBackgroundTasksSelector; + Module::ProcessSelector mProcessSelector; + + nsCString Entry() const; + nsCString Value() const; + bool Active() const; +}; + +struct StaticCategory final { + StringOffset mName; + uint16_t mStart; + uint16_t mCount; + + nsCString Name() const; + + const StaticCategoryEntry* begin() const { + return &gStaticCategoryEntries[mStart]; + } + const StaticCategoryEntry* end() const { + return &gStaticCategoryEntries[mStart + mCount]; + } +}; + +struct JSServiceEntry final { + using InterfaceList = AutoTArray<const nsIID*, 4>; + + static const JSServiceEntry* Lookup(const nsACString& aName); + + StringOffset mName; + ModuleID mModuleID; + + InterfaceOffset mInterfaceOffset; + + uint8_t mInterfaceCount; + + nsCString Name() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } + + InterfaceList Interfaces() const; +}; + +struct StaticProtocolHandler final { + static const StaticProtocolHandler* Lookup(const nsACString& aScheme); + static const StaticProtocolHandler& Default() { + return gStaticProtocolHandlers[kDefaultProtocolHandlerIndex]; + } + + StringOffset mScheme; + uint32_t mProtocolFlags; + int32_t mDefaultPort; + ModuleID mModuleID; + bool mHasDynamicFlags; + + nsCString Scheme() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } +}; + +class StaticComponents final { + public: + static const StaticModule* LookupByCID(const nsID& aCID); + + static const StaticModule* LookupByContractID(const nsACString& aContractID); + + /** + * Marks a static contract ID entry invalid (or unsets the invalid bit if + * aInvalid is false). See `CategoryEntry::Invalid()`. + */ + static bool InvalidateContractID(const nsACString& aContractID, + bool aInvalid = true); + + static already_AddRefed<nsIUTF8StringEnumerator> GetComponentJSMs(); + static already_AddRefed<nsIUTF8StringEnumerator> GetComponentESModules(); + + static Span<const JSServiceEntry> GetJSServices(); + + /** + * Calls any module unload from manifests whose components have been loaded. + */ + static void Shutdown(); +}; + +} // namespace xpcom +} // namespace mozilla + +#endif // defined StaticComponents_h diff --git a/xpcom/components/components.conf b/xpcom/components/components.conf new file mode 100644 index 0000000000..9938fb104f --- /dev/null +++ b/xpcom/components/components.conf @@ -0,0 +1,23 @@ +# -*- 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 = [ + { + 'js_name': 'catMan', + 'cid': '{16d222a6-1dd2-11b2-b693-f38b02c021b2}', + 'contract_ids': ['@mozilla.org/categorymanager;1'], + 'interfaces': ['nsICategoryManager'], + 'legacy_constructor': 'nsCategoryManager::Create', + 'headers': ['/xpcom/components/nsCategoryManager.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{91775d60-d5dc-11d2-92fb-00e09805570f}', + 'legacy_constructor': 'nsComponentManagerImpl::Create', + 'headers': ['/xpcom/components/nsComponentManager.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS, + }, +] diff --git a/xpcom/components/gen_static_components.py b/xpcom/components/gen_static_components.py new file mode 100644 index 0000000000..f759dc3132 --- /dev/null +++ b/xpcom/components/gen_static_components.py @@ -0,0 +1,1216 @@ +import json +import os +import re +import struct +from collections import defaultdict +from uuid import UUID + +import buildconfig +from mozbuild.util import FileAvoidWrite +from perfecthash import PerfectHash + +NO_CONTRACT_ID = 0xFFFFFFFF + +PHF_SIZE = 512 + +TINY_PHF_SIZE = 16 + +# In tests, we might not have a (complete) buildconfig. +ENDIAN = ( + "<" if buildconfig.substs.get("TARGET_ENDIANNESS", "little") == "little" else ">" +) + + +# Represents a UUID in the format used internally by Gecko, and supports +# serializing it in that format to both C++ source and raw byte arrays. +class UUIDRepr(object): + def __init__(self, uuid): + self.uuid = uuid + + fields = uuid.fields + + self.a = fields[0] + self.b = fields[1] + self.c = fields[2] + + d = list(fields[3:5]) + for i in range(0, 6): + d.append(fields[5] >> (8 * (5 - i)) & 0xFF) + + self.d = tuple(d) + + def __str__(self): + return str(self.uuid) + + @property + def bytes(self): + return struct.pack(ENDIAN + "IHHBBBBBBBB", self.a, self.b, self.c, *self.d) + + def to_cxx(self): + rest = ", ".join("0x%02x" % b for b in self.d) + + return "{ 0x%x, 0x%x, 0x%x, { %s } }" % (self.a, self.b, self.c, rest) + + +# Corresponds to the Module::ProcessSelector enum in Module.h. The actual +# values don't matter, since the code generator emits symbolic constants for +# these values, but we use the same values as the enum constants for clarity. +class ProcessSelector: + ANY_PROCESS = 0 + MAIN_PROCESS_ONLY = 1 << 0 + CONTENT_PROCESS_ONLY = 1 << 1 + ALLOW_IN_GPU_PROCESS = 1 << 2 + ALLOW_IN_VR_PROCESS = 1 << 3 + ALLOW_IN_SOCKET_PROCESS = 1 << 4 + ALLOW_IN_RDD_PROCESS = 1 << 5 + ALLOW_IN_UTILITY_PROCESS = 1 << 6 + ALLOW_IN_GMPLUGIN_PROCESS = 1 << 7 + ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY + ALLOW_IN_GPU_AND_SOCKET_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS + ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_RDD_AND_SOCKET_PROCESS = ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + | ALLOW_IN_GMPLUGIN_PROCESS + ) + + +# Maps ProcessSelector constants to the name of the corresponding +# Module::ProcessSelector enum value. +PROCESSES = { + ProcessSelector.ANY_PROCESS: "ANY_PROCESS", + ProcessSelector.MAIN_PROCESS_ONLY: "MAIN_PROCESS_ONLY", + ProcessSelector.CONTENT_PROCESS_ONLY: "CONTENT_PROCESS_ONLY", + ProcessSelector.ALLOW_IN_GPU_PROCESS: "ALLOW_IN_GPU_PROCESS", + ProcessSelector.ALLOW_IN_VR_PROCESS: "ALLOW_IN_VR_PROCESS", + ProcessSelector.ALLOW_IN_SOCKET_PROCESS: "ALLOW_IN_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_PROCESS: "ALLOW_IN_RDD_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS: "ALLOW_IN_GPU_AND_MAIN_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_VR_PROCESS: "ALLOW_IN_GPU_AND_VR_PROCESS", + ProcessSelector.ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS", # NOQA: E501 +} + + +# Emits the C++ symbolic constant corresponding to a ProcessSelector constant. +def lower_processes(processes): + return "Module::ProcessSelector::%s" % PROCESSES[processes] + + +# Emits the C++ symbolic constant for a ModuleEntry's ModuleID enum entry. +def lower_module_id(module): + return "ModuleID::%s" % module.name + + +# Corresponds to the Module::BackgroundTasksSelector enum in Module.h. The +# actual values don't matter, since the code generator emits symbolic constants +# for these values, but we use the same values as the enum constants for +# clarity. +class BackgroundTasksSelector: + NO_TASKS = 0x0 + ALL_TASKS = 0xFFFF + + +# Maps BackgroundTasksSelector constants to the name of the corresponding +# Module::BackgroundTasksSelector enum value. +BACKGROUNDTASKS = { + BackgroundTasksSelector.ALL_TASKS: "ALL_TASKS", + BackgroundTasksSelector.NO_TASKS: "NO_TASKS", +} + + +# Emits the C++ symbolic constant corresponding to a BackgroundTasks constant. +def lower_backgroundtasks(backgroundtasks): + return "Module::BackgroundTasksSelector::%s" % BACKGROUNDTASKS[backgroundtasks] + + +# Represents a static string table, indexed by offset. This allows us to +# reference strings from static data structures without requiring runtime +# relocations. +class StringTable(object): + def __init__(self): + self.entries = {} + self.entry_list = [] + self.size = 0 + + self._serialized = False + + # Returns the index of the given string in the `entry_list` array. If + # no entry for the string exists, it first creates one. + def get_idx(self, string): + idx = self.entries.get(string, None) + if idx is not None: + return idx + + assert not self._serialized + + assert len(string) == len(string.encode("utf-8")) + + idx = self.size + self.size += len(string) + 1 + + self.entries[string] = idx + self.entry_list.append(string) + return idx + + # Returns the C++ code representing string data of this string table, as a + # single string literal. This must only be called after the last call to + # `get_idx()` or `entry_to_cxx()` for this instance. + def to_cxx(self): + self._serialized = True + + lines = [] + + idx = 0 + for entry in self.entry_list: + str_ = entry.replace("\\", "\\\\").replace('"', r"\"").replace("\n", r"\n") + + lines.append(' /* 0x%x */ "%s\\0"\n' % (idx, str_)) + + idx += len(entry) + 1 + + return "".join(lines) + + # Returns a `StringEntry` struct initializer for the string table entry + # corresponding to the given string. If no matching entry exists, it is + # first created. + def entry_to_cxx(self, string): + idx = self.get_idx(string) + return "{ 0x%x } /* %s */" % (idx, pretty_string(string)) + + +strings = StringTable() + +interfaces = [] + + +# Represents a C++ namespace, containing a set of classes and potentially +# sub-namespaces. This is used to generate pre-declarations for incomplete +# types referenced in XPCOM manifests. +class Namespace(object): + def __init__(self, name=None): + self.name = name + self.classes = set() + self.namespaces = {} + + # Returns a Namespace object for the sub-namespace with the given name. + def sub(self, name): + assert name not in self.classes + + if name not in self.namespaces: + self.namespaces[name] = Namespace(name) + return self.namespaces[name] + + # Generates C++ code to pre-declare all classes in this namespace and all + # of its sub-namespaces. + def to_cxx(self): + res = "" + if self.name: + res += "namespace %s {\n" % self.name + + for clas in sorted(self.classes): + res += "class %s;\n" % clas + + for ns in sorted(self.namespaces.keys()): + res += self.namespaces[ns].to_cxx() + + if self.name: + res += "} // namespace %s\n" % self.name + + return res + + +# Represents a component defined in an XPCOM manifest's `Classes` array. +class ModuleEntry(object): + next_anon_id = 0 + + def __init__(self, data, init_idx): + self.cid = UUIDRepr(UUID(data["cid"])) + self.contract_ids = data.get("contract_ids", []) + self.type = data.get("type", "nsISupports") + self.categories = data.get("categories", {}) + self.processes = data.get("processes", 0) + self.headers = data.get("headers", []) + + self.js_name = data.get("js_name", None) + self.interfaces = data.get("interfaces", []) + + if len(self.interfaces) > 255: + raise Exception( + "JS service %s may not have more than 255 " "interfaces" % self.js_name + ) + + self.interfaces_offset = len(interfaces) + for iface in self.interfaces: + interfaces.append(iface) + + # If the manifest declares Init or Unload functions, this contains its + # index, as understood by the `CallInitFunc()` function. + # + # If it contains any value other than `None`, a corresponding + # `CallInitFunc(init_idx)` call will be genrated before calling this + # module's constructor. + self.init_idx = init_idx + + self.constructor = data.get("constructor", None) + self.legacy_constructor = data.get("legacy_constructor", None) + self.init_method = data.get("init_method", []) + + self.jsm = data.get("jsm", None) + self.esModule = data.get("esModule", None) + + self.external = data.get( + "external", not (self.headers or self.legacy_constructor) + ) + self.singleton = data.get("singleton", False) + self.overridable = data.get("overridable", False) + + self.protocol_config = data.get("protocol_config", None) + + if "name" in data: + self.anonymous = False + self.name = data["name"] + else: + self.anonymous = True + self.name = "Anonymous%03d" % ModuleEntry.next_anon_id + ModuleEntry.next_anon_id += 1 + + def error(str_): + raise Exception( + "Error defining component %s (%s): %s" + % (str(self.cid), ", ".join(map(repr, self.contract_ids)), str_) + ) + + if self.jsm: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.esModule: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.external: + if self.constructor or self.legacy_constructor: + error( + "Externally-constructed components may not specify " + "'constructor' or 'legacy_constructor' properties" + ) + if self.init_method: + error( + "Externally-constructed components may not specify " + "'init_method' properties" + ) + if self.type == "nsISupports": + error( + "Externally-constructed components must specify a type " + "other than nsISupports" + ) + + if self.constructor and self.legacy_constructor: + error( + "The 'constructor' and 'legacy_constructor' properties " + "are mutually exclusive" + ) + + if self.overridable and not self.contract_ids: + error("Overridable components must specify at least one contract " "ID") + + @property + def contract_id(self): + return self.contract_ids[0] + + # Generates the C++ code for a StaticModule struct initializer + # representing this component. + def to_cxx(self): + contract_id = ( + strings.entry_to_cxx(self.contract_id) + if self.overridable + else "{ 0x%x }" % NO_CONTRACT_ID + ) + + return """ + /* {name} */ {{ + /* {{{cid_string}}} */ + {cid}, + {contract_id}, + {processes}, + }}""".format( + name=self.name, + cid=self.cid.to_cxx(), + cid_string=str(self.cid), + contract_id=contract_id, + processes=lower_processes(self.processes), + ) + + # Generates the C++ code for a JSServiceEntry representing this module. + def lower_js_service(self): + return """ + {{ + {js_name}, + ModuleID::{name}, + {{ {iface_offset} }}, + {iface_count} + }}""".format( + js_name=strings.entry_to_cxx(self.js_name), + name=self.name, + iface_offset=self.interfaces_offset, + iface_count=len(self.interfaces), + ) + + # Generates the C++ code necessary to construct an instance of this + # component. + # + # This code lives in a function with the following arguments: + # + # - aIID: The `const nsIID&` interface ID that the resulting instance + # will be queried to. + # + # - aResult: The `void**` pointer in which to store the result. + # + # And which returns an `nsresult` indicating success or failure. + def lower_constructor(self): + res = "" + + if self.init_idx is not None: + res += " MOZ_TRY(CallInitFunc(%d));\n" % self.init_idx + + if self.legacy_constructor: + res += ( + " return /* legacy */ %s(aIID, aResult);\n" + % self.legacy_constructor + ) + return res + + if self.jsm: + res += ( + " nsCOMPtr<nsISupports> inst;\n" + " MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.jsm), json.dumps(self.constructor)) + ) + elif self.esModule: + res += ( + " nsCOMPtr<nsISupports> inst;\n" + " MOZ_TRY(ConstructESModuleComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.esModule), json.dumps(self.constructor)) + ) + elif self.external: + res += ( + " nsCOMPtr<nsISupports> inst = " + "mozCreateComponent<%s>();\n" % self.type + ) + # The custom constructor may return null, so check before calling + # any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_FAILURE);\n" + else: + res += " RefPtr<%s> inst = " % self.type + + if not self.constructor: + res += "new %s();\n" % self.type + else: + res += "%s();\n" % self.constructor + # The `new` operator is infallible, so we don't need to worry + # about it returning null, but custom constructors may, so + # check before calling any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);\n" + + # Check that the constructor function returns an appropriate + # `already_AddRefed` value for our declared type. + res += """ + using T = + RemoveAlreadyAddRefed<decltype(%(constructor)s())>::Type; + static_assert( + std::is_same_v<already_AddRefed<T>, decltype(%(constructor)s())>, + "Singleton constructor must return already_AddRefed"); + static_assert( + std::is_base_of<%(type)s, T>::value, + "Singleton constructor must return correct already_AddRefed"); + +""" % { + "type": self.type, + "constructor": self.constructor, + } + + if self.init_method: + res += " MOZ_TRY(inst->%s());\n" % self.init_method + + res += " return inst->QueryInterface(aIID, aResult);\n" + + return res + + # Generates the C++ code for the `mozilla::components::<name>` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "::mozilla::xpcom::ModuleID::%s" % self.name, + } + + res = ( + """ +namespace %(name)s { +static inline const nsID& CID() { + return ::mozilla::xpcom::Components::GetCID(%(id)s); +} + +static inline ::mozilla::xpcom::GetServiceHelper Service(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + if not self.singleton: + res += ( + """ +static inline ::mozilla::xpcom::CreateInstanceHelper Create(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + res += ( + """\ +} // namespace %(name)s +""" + % substs + ) + + return res + + # Generates the rust code for the `xpcom::components::<name>` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters_rust(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "super::ModuleID::%s" % self.name, + } + + res = ( + """ +#[allow(non_snake_case)] +pub mod %(name)s { + /// Get the singleton service instance for this component. + pub fn service<T: crate::XpCom>() -> Result<crate::RefPtr<T>, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::<T>::new(); + let rv = unsafe { super::Gecko_GetServiceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + if not self.singleton: + res += ( + """ + /// Create a new instance of this component. + pub fn create<T: crate::XpCom>() -> Result<crate::RefPtr<T>, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::<T>::new(); + let rv = unsafe { super::Gecko_CreateInstanceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + res += """\ +} +""" + + return res + + +# Returns a quoted string literal representing the given raw string, with +# certain special characters replaced so that it can be used in a C++-style +# (/* ... */) comment. +def pretty_string(string): + return json.dumps(string).replace("*/", r"*\/").replace("/*", r"/\*") + + +# Represents a static contract ID entry, corresponding to a C++ ContractEntry +# struct, mapping a contract ID to a static module entry. +class ContractEntry(object): + def __init__(self, contract, module): + self.contract = contract + self.module = module + + def to_cxx(self): + return """ + {{ + {contract}, + {module_id}, + }}""".format( + contract=strings.entry_to_cxx(self.contract), + module_id=lower_module_id(self.module), + ) + + +# Represents a static ProtocolHandler entry, corresponding to a C++ +# ProtocolEntry struct, mapping a scheme to a static module entry and metadata. +class ProtocolHandler(object): + def __init__(self, config, module): + def error(str_): + raise Exception( + "Error defining protocol handler %s (%s): %s" + % (str(module.cid), ", ".join(map(repr, module.contract_ids)), str_) + ) + + self.module = module + self.scheme = config.get("scheme", None) + if self.scheme is None: + error("No scheme defined for protocol component") + self.flags = config.get("flags", None) + if self.flags is None: + error("No flags defined for protocol component") + self.default_port = config.get("default_port", -1) + self.has_dynamic_flags = config.get("has_dynamic_flags", False) + + def to_cxx(self): + return """ + {{ + .mScheme = {scheme}, + .mProtocolFlags = {flags}, + .mDefaultPort = {default_port}, + .mModuleID = {module_id}, + .mHasDynamicFlags = {has_dynamic_flags}, + }} + """.format( + scheme=strings.entry_to_cxx(self.scheme), + module_id=lower_module_id(self.module), + flags=" | ".join("nsIProtocolHandler::%s" % flag for flag in self.flags), + default_port=self.default_port, + has_dynamic_flags="true" if self.has_dynamic_flags else "false", + ) + + +# Generates the C++ code for the StaticCategoryEntry and StaticCategory +# structs for all category entries declared in XPCOM manifests. +def gen_categories(substs, categories): + cats = [] + ents = [] + + count = 0 + for category, entries in sorted(categories.items()): + + def k(entry): + return tuple(entry[0]["name"]) + entry[1:] + + entries.sort(key=k) + + cats.append( + " { %s,\n" + " %d, %d },\n" % (strings.entry_to_cxx(category), count, len(entries)) + ) + count += len(entries) + + ents.append(" /* %s */\n" % pretty_string(category)) + for entry, value, processes in entries: + name = entry["name"] + backgroundtasks = entry.get( + "backgroundtasks", BackgroundTasksSelector.NO_TASKS + ) + + ents.append( + " { %s,\n" + " %s,\n" + " %s,\n" + " %s },\n" + % ( + strings.entry_to_cxx(name), + strings.entry_to_cxx(value), + lower_backgroundtasks(backgroundtasks), + lower_processes(processes), + ) + ) + ents.append("\n") + ents.pop() + + substs["category_count"] = len(cats) + substs["categories"] = "".join(cats) + substs["category_entries"] = "".join(ents) + + +# Generates the C++ code for all Init and Unload functions declared in XPCOM +# manifests. These form the bodies of the `CallInitFunc()` and `CallUnload` +# functions in StaticComponents.cpp. +def gen_module_funcs(substs, funcs): + inits = [] + unloads = [] + + template = """\ + case %d: + %s + break; +""" + + for i, (init, unload) in enumerate(funcs): + init_code = "%s();" % init if init else "/* empty */" + inits.append(template % (i, init_code)) + + if unload: + unloads.append( + """\ + if (CalledInit(%d)) { + %s(); + } +""" + % (i, unload) + ) + + substs["init_funcs"] = "".join(inits) + substs["unload_funcs"] = "".join(unloads) + substs["init_count"] = len(funcs) + + +def gen_interfaces(ifaces): + res = [] + for iface in ifaces: + res.append(" nsXPTInterface::%s,\n" % iface) + return "".join(res) + + +# Generates class pre-declarations for any types referenced in `Classes` array +# entries which do not have corresponding `headers` entries to fully declare +# their types. +def gen_decls(types): + root_ns = Namespace() + + for type_ in sorted(types): + parts = type_.split("::") + + ns = root_ns + for part in parts[:-1]: + ns = ns.sub(part) + ns.classes.add(parts[-1]) + + return root_ns.to_cxx() + + +# Generates the `switch` body for the `CreateInstanceImpl()` function, with a +# `case` for each value in ModuleID to construct an instance of the +# corresponding component. +def gen_constructors(entries): + constructors = [] + for entry in entries: + constructors.append( + """\ + case {id}: {{ +{constructor}\ + }} +""".format( + id=lower_module_id(entry), constructor=entry.lower_constructor() + ) + ) + + return "".join(constructors) + + +# Generates the getter code for each named component entry in the +# `mozilla::components::` namespace. +def gen_getters(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join(entry.lower_getters() for entry in entries if not entry.anonymous) + + +# Generates the rust getter code for each named component entry in the +# `xpcom::components::` module. +def gen_getters_rust(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join( + entry.lower_getters_rust() for entry in entries if not entry.anonymous + ) + + +def gen_includes(substs, all_headers): + headers = set() + absolute_headers = set() + + for header in all_headers: + if header.startswith("/"): + absolute_headers.add(header) + else: + headers.add(header) + + includes = ['#include "%s"' % header for header in sorted(headers)] + substs["includes"] = "\n".join(includes) + "\n" + + relative_includes = [ + '#include "../..%s"' % header for header in sorted(absolute_headers) + ] + substs["relative_includes"] = "\n".join(relative_includes) + "\n" + + +def to_category_list(val): + # Entries can be bare strings (like `"m-browser"`), lists of bare strings, + # or dictionaries (like `{"name": "m-browser", "backgroundtasks": + # BackgroundTasksSelector.ALL_TASKS}`), somewhat recursively. + + def ensure_dict(v): + # Turn `v` into `{"name": v}` if it's not already a dict. + if isinstance(v, dict): + return v + return {"name": v} + + if isinstance(val, (list, tuple)): + return tuple(ensure_dict(v) for v in val) + + if isinstance(val, dict): + # Explode `{"name": ["x", "y"], "backgroundtasks": ...}` into + # `[{"name": "x", "backgroundtasks": ...}, {"name": "y", "backgroundtasks": ...}]`. + names = val.pop("name") + + vals = [] + for entry in to_category_list(names): + d = dict(val) + d["name"] = entry["name"] + vals.append(d) + + return tuple(vals) + + return (ensure_dict(val),) + + +def gen_substs(manifests): + module_funcs = [] + + headers = set() + + modules = [] + categories = defaultdict(list) + + for manifest in manifests: + headers |= set(manifest.get("Headers", [])) + + init_idx = None + init = manifest.get("InitFunc") + unload = manifest.get("UnloadFunc") + if init or unload: + init_idx = len(module_funcs) + module_funcs.append((init, unload)) + + for clas in manifest["Classes"]: + modules.append(ModuleEntry(clas, init_idx)) + + for category, entries in manifest.get("Categories", {}).items(): + for key, entry in entries.items(): + if isinstance(entry, tuple): + value, process = entry + else: + value, process = entry, 0 + categories[category].append(({"name": key}, value, process)) + + cids = set() + contracts = [] + contract_map = {} + js_services = {} + protocol_handlers = {} + + jsms = set() + esModules = set() + + types = set() + + for mod in modules: + headers |= set(mod.headers) + + for contract_id in mod.contract_ids: + if contract_id in contract_map: + raise Exception("Duplicate contract ID: %s" % contract_id) + + entry = ContractEntry(contract_id, mod) + contracts.append(entry) + contract_map[contract_id] = entry + + for category, entries in mod.categories.items(): + for entry in to_category_list(entries): + categories[category].append((entry, mod.contract_id, mod.processes)) + + if mod.type and not mod.headers: + types.add(mod.type) + + if mod.jsm: + jsms.add(mod.jsm) + + if mod.esModule: + esModules.add(mod.esModule) + + if mod.js_name: + if mod.js_name in js_services: + raise Exception("Duplicate JS service name: %s" % mod.js_name) + js_services[mod.js_name] = mod + + if mod.protocol_config: + handler = ProtocolHandler(mod.protocol_config, mod) + if handler.scheme in protocol_handlers: + raise Exception("Duplicate protocol handler: %s" % handler.scheme) + protocol_handlers[handler.scheme] = handler + + if str(mod.cid) in cids: + raise Exception("Duplicate cid: %s" % str(mod.cid)) + cids.add(str(mod.cid)) + + cid_phf = PerfectHash(modules, PHF_SIZE, key=lambda module: module.cid.bytes) + + contract_phf = PerfectHash(contracts, PHF_SIZE, key=lambda entry: entry.contract) + + js_services_phf = PerfectHash( + list(js_services.values()), PHF_SIZE, key=lambda entry: entry.js_name + ) + + protocol_handlers_phf = PerfectHash( + list(protocol_handlers.values()), TINY_PHF_SIZE, key=lambda entry: entry.scheme + ) + + js_services_json = {} + for entry in js_services.values(): + for iface in entry.interfaces: + js_services_json[iface] = entry.js_name + + substs = {} + + gen_categories(substs, categories) + + substs["module_ids"] = "".join(" %s,\n" % entry.name for entry in cid_phf.entries) + + substs["module_count"] = len(modules) + substs["contract_count"] = len(contracts) + substs["protocol_handler_count"] = len(protocol_handlers) + + substs["default_protocol_handler_idx"] = protocol_handlers_phf.get_index("default") + + gen_module_funcs(substs, module_funcs) + + gen_includes(substs, headers) + + substs["component_jsms"] = ( + "\n".join(" %s," % strings.entry_to_cxx(jsm) for jsm in sorted(jsms)) + "\n" + ) + substs["component_esmodules"] = ( + "\n".join( + " %s," % strings.entry_to_cxx(esModule) for esModule in sorted(esModules) + ) + + "\n" + ) + + substs["interfaces"] = gen_interfaces(interfaces) + + substs["decls"] = gen_decls(types) + + substs["constructors"] = gen_constructors(cid_phf.entries) + + substs["component_getters"] = gen_getters(cid_phf.entries) + + substs["component_getters_rust"] = gen_getters_rust(cid_phf.entries) + + substs["module_cid_table"] = cid_phf.cxx_codegen( + name="ModuleByCID", + entry_type="StaticModule", + entries_name="gStaticModules", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticModule*", + return_entry=( + "return entry.CID().Equals(aKey) && entry.Active()" " ? &entry : nullptr;" + ), + key_type="const nsID&", + key_bytes="reinterpret_cast<const char*>(&aKey)", + key_length="sizeof(nsID)", + ) + + substs["module_contract_id_table"] = contract_phf.cxx_codegen( + name="LookupContractID", + entry_type="ContractEntry", + entries_name="gContractEntries", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const ContractEntry*", + return_entry="return entry.Matches(aKey) ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_table"] = js_services_phf.cxx_codegen( + name="LookupJSService", + entry_type="JSServiceEntry", + entries_name="gJSServices", + lower_entry=lambda entry: entry.lower_js_service(), + return_type="const JSServiceEntry*", + return_entry="return entry.Name() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["protocol_handlers_table"] = protocol_handlers_phf.cxx_codegen( + name="LookupProtocolHandler", + entry_type="StaticProtocolHandler", + entries_name="gStaticProtocolHandlers", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticProtocolHandler*", + return_entry="return entry.Scheme() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_json"] = json.dumps(js_services_json, sort_keys=True, indent=4) + + # Do this only after everything else has been emitted so we're sure the + # string table is complete. + substs["strings"] = strings.to_cxx() + return substs + + +# Returns true if the given build config substitution is defined and truthy. +def defined(subst): + return bool(buildconfig.substs.get(subst)) + + +def read_manifest(filename): + glbl = { + "buildconfig": buildconfig, + "defined": defined, + "ProcessSelector": ProcessSelector, + "BackgroundTasksSelector": BackgroundTasksSelector, + } + code = compile(open(filename).read(), filename, "exec") + exec(code, glbl) + return glbl + + +def main(fd, conf_file, template_file): + def open_output(filename): + return FileAvoidWrite(os.path.join(os.path.dirname(fd.name), filename)) + + conf = json.load(open(conf_file, "r")) + + deps = set() + + manifests = [] + for filename in conf["manifests"]: + deps.add(filename) + manifest = read_manifest(filename) + manifests.append(manifest) + manifest.setdefault("Priority", 50) + manifest["__filename__"] = filename + + manifests.sort(key=lambda man: (man["Priority"], man["__filename__"])) + + substs = gen_substs(manifests) + + def replacer(match): + return substs[match.group(1)] + + with open_output("StaticComponents.cpp") as fh: + with open(template_file, "r") as tfh: + template = tfh.read() + + fh.write(re.sub(r"//# @([a-zA-Z_]+)@\n", replacer, template)) + + with open_output("StaticComponentData.h") as fh: + fh.write( + """\ +/* -*- 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 StaticComponentData_h +#define StaticComponentData_h + +#include <stddef.h> + +namespace mozilla { +namespace xpcom { + +static constexpr size_t kStaticModuleCount = %(module_count)d; + +static constexpr size_t kContractCount = %(contract_count)d; + +static constexpr size_t kStaticCategoryCount = %(category_count)d; + +static constexpr size_t kModuleInitCount = %(init_count)d; + +static constexpr size_t kStaticProtocolHandlerCount = %(protocol_handler_count)d; + +static constexpr size_t kDefaultProtocolHandlerIndex = %(default_protocol_handler_idx)d; + +} // namespace xpcom +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("components.rs") as fh: + fh.write( + """\ +/// Unique IDs for each statically-registered module. +#[repr(u16)] +pub enum ModuleID { +%(module_ids)s +} + +%(component_getters_rust)s +""" + % substs + ) + + fd.write( + """\ +/* -*- 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_Components_h +#define mozilla_Components_h + +#include "nsCOMPtr.h" + +struct nsID; + +#define NS_IMPL_COMPONENT_FACTORY(iface) \\ + template <> \\ + already_AddRefed<nsISupports> mozCreateComponent<iface>() + +template <typename T> +already_AddRefed<nsISupports> mozCreateComponent(); + +namespace mozilla { +namespace xpcom { + +enum class ModuleID : uint16_t { +%(module_ids)s +}; + +// May be added as a friend function to allow constructing services via +// private constructors and init methods. +nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult); + +class MOZ_STACK_CLASS StaticModuleHelper : public nsCOMPtr_helper { + public: + StaticModuleHelper(ModuleID aId, nsresult* aErrorPtr) + : mId(aId), mErrorPtr(aErrorPtr) {} + + protected: + nsresult SetResult(nsresult aRv) const { + if (mErrorPtr) { + *mErrorPtr = aRv; + } + return aRv; + } + + ModuleID mId; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS GetServiceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class MOZ_STACK_CLASS CreateInstanceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class Components final { + public: + static const nsID& GetCID(ModuleID aID); +}; + +} // namespace xpcom + +namespace components { +%(component_getters)s +} // namespace components + +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("services.json") as fh: + fh.write(substs["js_services_json"]) + + return deps diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build new file mode 100644 index 0000000000..e62b56b44a --- /dev/null +++ b/xpcom/components/moz.build @@ -0,0 +1,84 @@ +# -*- 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 += [ + "nsICategoryManager.idl", + "nsIClassInfo.idl", + "nsIComponentManager.idl", + "nsIComponentRegistrar.idl", + "nsIFactory.idl", + "nsIServiceManager.idl", +] + +XPIDL_MODULE = "xpcom_components" + +EXPORTS += [ + "nsCategoryCache.h", + "nsCategoryManagerUtils.h", + "nsComponentManagerUtils.h", + "nsServiceManagerUtils.h", +] + +EXPORTS.mozilla += [ + "GenericFactory.h", + "Module.h", + "ModuleUtils.h", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +if CONFIG["COMPILE_ENVIRONMENT"]: + EXPORTS.mozilla += [ + "!Components.h", + ] + + GeneratedFile( + "Components.h", + "StaticComponentData.h", + "StaticComponents.cpp", + "services.json", + "components.rs", + script="gen_static_components.py", + inputs=["!manifest-lists.json", "StaticComponents.cpp.in"], + ) + +UNIFIED_SOURCES += [ + "GenericFactory.cpp", + "ManifestParser.cpp", + "nsCategoryCache.cpp", + "nsCategoryManager.cpp", + "nsComponentManager.cpp", + "nsComponentManagerUtils.cpp", +] + +SOURCES += [ + "!StaticComponents.cpp", +] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!..", + "../base", + "../build", + "../ds", + "/chrome", + "/js/xpconnect/loader", + "/js/xpconnect/src", + "/layout/build", + "/modules/libjar", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +include("/ipc/chromium/chromium-config.mozbuild") + +PYTHON_UNITTEST_MANIFESTS += [ + "test/python.toml", +] diff --git a/xpcom/components/nsCategoryCache.cpp b/xpcom/components/nsCategoryCache.cpp new file mode 100644 index 0000000000..35f555fa51 --- /dev/null +++ b/xpcom/components/nsCategoryCache.cpp @@ -0,0 +1,162 @@ +/* -*- 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 "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/SimpleEnumerator.h" + +#include "nsICategoryManager.h" +#include "nsISupportsPrimitives.h" + +#include "nsXPCOMCID.h" + +#include "nsCategoryCache.h" + +using mozilla::SimpleEnumerator; + +nsCategoryObserver::nsCategoryObserver(const nsACString& aCategory) + : mCategory(aCategory), + mCallback(nullptr), + mClosure(nullptr), + mObserversRemoved(false) { + MOZ_ASSERT(NS_IsMainThread()); + // First, enumerate the currently existing entries + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> enumerator; + nsresult rv = + catMan->EnumerateCategory(aCategory, getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) { + nsAutoCString entryValue; + categoryEntry->GetValue(entryValue); + + if (nsCOMPtr<nsISupports> service = do_GetService(entryValue.get())) { + nsAutoCString entryName; + categoryEntry->GetEntry(entryName); + + mHash.InsertOrUpdate(entryName, service); + } + } + + // Now, listen for changes + nsCOMPtr<nsIObserverService> serv = mozilla::services::GetObserverService(); + if (serv) { + serv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, false); + } +} + +nsCategoryObserver::~nsCategoryObserver() = default; + +NS_IMPL_ISUPPORTS(nsCategoryObserver, nsIObserver) + +void nsCategoryObserver::ListenerDied() { + MOZ_ASSERT(NS_IsMainThread()); + RemoveObservers(); + mCallback = nullptr; + mClosure = nullptr; +} + +void nsCategoryObserver::SetListener(void(aCallback)(void*), void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + mCallback = aCallback; + mClosure = aClosure; +} + +void nsCategoryObserver::RemoveObservers() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mObserversRemoved) { + return; + } + + if (mCallback) { + mCallback(mClosure); + } + + mObserversRemoved = true; + nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID); + } +} + +NS_IMETHODIMP +nsCategoryObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + mHash.Clear(); + RemoveObservers(); + + return NS_OK; + } + + if (!aData || + !nsDependentString(aData).Equals(NS_ConvertASCIItoUTF16(mCategory))) { + return NS_OK; + } + + nsAutoCString str; + nsCOMPtr<nsISupportsCString> strWrapper(do_QueryInterface(aSubject)); + if (strWrapper) { + strWrapper->GetData(str); + } + + if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID) == 0) { + // We may get an add notification even when we already have an entry. This + // is due to the notification happening asynchronously, so if the entry gets + // added and an nsCategoryObserver gets instantiated before events get + // processed, we'd get the notification for an existing entry. + // Do nothing in that case. + if (mHash.GetWeak(str)) { + return NS_OK; + } + + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return NS_OK; + } + + nsCString entryValue; + catMan->GetCategoryEntry(mCategory, str, entryValue); + + nsCOMPtr<nsISupports> service = do_GetService(entryValue.get()); + + if (service) { + mHash.InsertOrUpdate(str, service); + } + if (mCallback) { + mCallback(mClosure); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID) == 0) { + mHash.Remove(str); + if (mCallback) { + mCallback(mClosure); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID) == 0) { + mHash.Clear(); + if (mCallback) { + mCallback(mClosure); + } + } + return NS_OK; +} diff --git a/xpcom/components/nsCategoryCache.h b/xpcom/components/nsCategoryCache.h new file mode 100644 index 0000000000..f2d7ba32a3 --- /dev/null +++ b/xpcom/components/nsCategoryCache.h @@ -0,0 +1,123 @@ +/* -*- 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 nsCategoryCache_h_ +#define nsCategoryCache_h_ + +#include "mozilla/Attributes.h" + +#include "nsIObserver.h" + +#include "nsServiceManagerUtils.h" + +#include "nsCOMArray.h" +#include "nsInterfaceHashtable.h" + +#include "nsXPCOM.h" +#include "MainThreadUtils.h" + +class nsCategoryObserver final : public nsIObserver { + ~nsCategoryObserver(); + + public: + explicit nsCategoryObserver(const nsACString& aCategory); + + void ListenerDied(); + void SetListener(void(aCallback)(void*), void* aClosure); + nsInterfaceHashtable<nsCStringHashKey, nsISupports>& GetHash() { + return mHash; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + private: + void RemoveObservers(); + + nsInterfaceHashtable<nsCStringHashKey, nsISupports> mHash; + nsCString mCategory; + void (*mCallback)(void*); + void* mClosure; + bool mObserversRemoved; +}; + +/** + * This is a helper class that caches services that are registered in a certain + * category. The intended usage is that a service stores a variable of type + * nsCategoryCache<nsIFoo> in a member variable, where nsIFoo is the interface + * that these services should implement. The constructor of this class should + * then get the name of the category. + */ +template <class T> +class nsCategoryCache final { + public: + explicit nsCategoryCache(const char* aCategory) : mCategoryName(aCategory) { + MOZ_ASSERT(NS_IsMainThread()); + } + ~nsCategoryCache() { + MOZ_ASSERT(NS_IsMainThread()); + if (mObserver) { + mObserver->ListenerDied(); + } + } + + void GetEntries(nsCOMArray<T>& aResult) { + MOZ_ASSERT(NS_IsMainThread()); + LazyInit(); + + AddEntries(aResult); + } + + /** + * This function returns an nsCOMArray of interface pointers to the cached + * category enries for the requested category. This was added in order to be + * used in call sites where the overhead of excessive allocations can be + * unacceptable. See bug 1360971 for an example. + */ + const nsCOMArray<T>& GetCachedEntries() { + MOZ_ASSERT(NS_IsMainThread()); + LazyInit(); + + if (mCachedEntries.IsEmpty()) { + AddEntries(mCachedEntries); + } + return mCachedEntries; + } + + private: + void LazyInit() { + // Lazy initialization, so that services in this category can't + // cause reentrant getService (bug 386376) + if (!mObserver) { + mObserver = new nsCategoryObserver(mCategoryName); + mObserver->SetListener(nsCategoryCache<T>::OnCategoryChanged, this); + } + } + + void AddEntries(nsCOMArray<T>& aResult) { + for (nsISupports* entry : mObserver->GetHash().Values()) { + nsCOMPtr<T> service = do_QueryInterface(entry); + if (service) { + aResult.AppendElement(service.forget()); + } + } + } + + static void OnCategoryChanged(void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + auto self = static_cast<nsCategoryCache<T>*>(aClosure); + self->mCachedEntries.Clear(); + } + + private: + // Not to be implemented + nsCategoryCache(const nsCategoryCache<T>&); + + nsCString mCategoryName; + RefPtr<nsCategoryObserver> mObserver; + nsCOMArray<T> mCachedEntries; +}; + +#endif diff --git a/xpcom/components/nsCategoryManager.cpp b/xpcom/components/nsCategoryManager.cpp new file mode 100644 index 0000000000..5602f0001f --- /dev/null +++ b/xpcom/components/nsCategoryManager.cpp @@ -0,0 +1,690 @@ +/* -*- 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 "nsCategoryManager.h" +#include "nsCategoryManagerUtils.h" + +#include "prio.h" +#include "prlock.h" +#include "nsArrayEnumerator.h" +#include "nsCOMPtr.h" +#include "nsTHashtable.h" +#include "nsClassHashtable.h" +#include "nsStringEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsPrintfCString.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/ArenaAllocatorExtensions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Services.h" +#include "mozilla/SimpleEnumerator.h" + +#include "ManifestParser.h" +#include "nsSimpleEnumerator.h" + +using namespace mozilla; +class nsIComponentLoaderManager; + +/* + CategoryDatabase + contains 0 or more 1-1 mappings of string to Category + each Category contains 0 or more 1-1 mappings of string keys to string values + + In other words, the CategoryDatabase is a tree, whose root is a hashtable. + Internal nodes (or Categories) are hashtables. Leaf nodes are strings. + + The leaf strings are allocated in an arena, because we assume they're not + going to change much ;) +*/ + +// +// CategoryEnumerator class +// + +class CategoryEnumerator : public nsSimpleEnumerator, + private nsStringEnumeratorBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + + using nsStringEnumeratorBase::GetNext; + + const nsID& DefaultInterface() override { + return NS_GET_IID(nsISupportsCString); + } + + static CategoryEnumerator* Create( + nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable); + + protected: + CategoryEnumerator() + : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {} + + ~CategoryEnumerator() override { delete[] mArray; } + + const char** mArray; + uint32_t mCount; + uint32_t mSimpleCurItem; + uint32_t mStringCurItem; +}; + +NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator, nsSimpleEnumerator, + nsIUTF8StringEnumerator, nsIStringEnumerator) + +NS_IMETHODIMP +CategoryEnumerator::HasMoreElements(bool* aResult) { + *aResult = (mSimpleCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsISupports** aResult) { + if (mSimpleCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + auto* str = new nsSupportsDependentCString(mArray[mSimpleCurItem++]); + + *aResult = str; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::HasMore(bool* aResult) { + *aResult = (mStringCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsACString& aResult) { + if (mStringCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + aResult = nsDependentCString(mArray[mStringCurItem++]); + return NS_OK; +} + +CategoryEnumerator* CategoryEnumerator::Create( + nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable) { + auto* enumObj = new CategoryEnumerator(); + if (!enumObj) { + return nullptr; + } + + enumObj->mArray = new const char*[aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + for (const auto& entry : aTable) { + // if a category has no entries, we pretend it doesn't exist + CategoryNode* aNode = entry.GetWeak(); + if (aNode->Count()) { + enumObj->mArray[enumObj->mCount++] = entry.GetKey(); + } + } + + return enumObj; +} + +class CategoryEntry final : public nsICategoryEntry { + NS_DECL_ISUPPORTS + NS_DECL_NSICATEGORYENTRY + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSISUPPORTSPRIMITIVE + + CategoryEntry(const char* aKey, const char* aValue) + : mKey(aKey), mValue(aValue) {} + + const char* Key() const { return mKey; } + + static CategoryEntry* Cast(nsICategoryEntry* aEntry) { + return static_cast<CategoryEntry*>(aEntry); + } + + private: + ~CategoryEntry() = default; + + const char* mKey; + const char* mValue; +}; + +NS_IMPL_ISUPPORTS(CategoryEntry, nsICategoryEntry, nsISupportsCString) + +nsresult CategoryEntry::ToString(char** aResult) { + *aResult = moz_xstrdup(mKey); + return NS_OK; +} + +nsresult CategoryEntry::GetType(uint16_t* aType) { + *aType = TYPE_CSTRING; + return NS_OK; +} + +nsresult CategoryEntry::GetData(nsACString& aData) { + aData = mKey; + return NS_OK; +} + +nsresult CategoryEntry::SetData(const nsACString& aData) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult CategoryEntry::GetEntry(nsACString& aEntry) { + aEntry = mKey; + return NS_OK; +} + +nsresult CategoryEntry::GetValue(nsACString& aValue) { + aValue = mValue; + return NS_OK; +} + +static nsresult CreateEntryEnumerator(nsTHashtable<CategoryLeaf>& aTable, + nsISimpleEnumerator** aResult) { + nsCOMArray<nsICategoryEntry> entries(aTable.Count()); + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + CategoryLeaf* leaf = iter.Get(); + if (leaf->value) { + entries.AppendElement(new CategoryEntry(leaf->GetKey(), leaf->value)); + } + } + + entries.Sort([](nsICategoryEntry* aA, nsICategoryEntry* aB) { + return strcmp(CategoryEntry::Cast(aA)->Key(), + CategoryEntry::Cast(aB)->Key()); + }); + + return NS_NewArrayEnumerator(aResult, entries, NS_GET_IID(nsICategoryEntry)); +} + +// +// CategoryNode implementations +// + +CategoryNode* CategoryNode::Create(CategoryAllocator* aArena) { + return new (aArena) CategoryNode(); +} + +CategoryNode::~CategoryNode() = default; + +void* CategoryNode::operator new(size_t aSize, CategoryAllocator* aArena) { + return aArena->Allocate(aSize, mozilla::fallible); +} + +static inline const char* MaybeStrdup(const nsACString& aStr, + CategoryAllocator* aArena) { + if (aStr.IsLiteral()) { + return aStr.BeginReading(); + } + return ArenaStrdup(PromiseFlatCString(aStr).get(), *aArena); +} + +nsresult CategoryNode::GetLeaf(const nsACString& aEntryName, + nsACString& aResult) { + MutexAutoLock lock(mLock); + nsresult rv = NS_ERROR_NOT_AVAILABLE; + CategoryLeaf* ent = mTable.GetEntry(PromiseFlatCString(aEntryName).get()); + + if (ent && ent->value) { + aResult.Assign(ent->value); + return NS_OK; + } + + return rv; +} + +nsresult CategoryNode::AddLeaf(const nsACString& aEntryName, + const nsACString& aValue, bool aReplace, + nsACString& aResult, CategoryAllocator* aArena) { + aResult.SetIsVoid(true); + + auto entryName = PromiseFlatCString(aEntryName); + + MutexAutoLock lock(mLock); + CategoryLeaf* leaf = mTable.GetEntry(entryName.get()); + + if (!leaf) { + leaf = mTable.PutEntry(MaybeStrdup(aEntryName, aArena)); + if (!leaf) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (leaf->value && !aReplace) { + return NS_ERROR_INVALID_ARG; + } + + if (leaf->value) { + aResult.AssignLiteral(leaf->value, strlen(leaf->value)); + } else { + aResult.SetIsVoid(true); + } + leaf->value = MaybeStrdup(aValue, aArena); + return NS_OK; +} + +void CategoryNode::DeleteLeaf(const nsACString& aEntryName) { + // we don't throw any errors, because it normally doesn't matter + // and it makes JS a lot cleaner + MutexAutoLock lock(mLock); + + // we can just remove the entire hash entry without introspection + mTable.RemoveEntry(PromiseFlatCString(aEntryName).get()); +} + +nsresult CategoryNode::Enumerate(nsISimpleEnumerator** aResult) { + MutexAutoLock lock(mLock); + return CreateEntryEnumerator(mTable, aResult); +} + +size_t CategoryNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { + // We don't measure the strings pointed to by the entries because the + // pointers are non-owning. + MutexAutoLock lock(mLock); + return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +// +// nsCategoryManager implementations +// + +NS_IMPL_QUERY_INTERFACE(nsCategoryManager, nsICategoryManager, + nsIMemoryReporter) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::Release() { return 1; } + +nsCategoryManager* nsCategoryManager::gCategoryManager; + +/* static */ +nsCategoryManager* nsCategoryManager::GetSingleton() { + if (!gCategoryManager) { + gCategoryManager = new nsCategoryManager(); + } + return gCategoryManager; +} + +/* static */ +void nsCategoryManager::Destroy() { + // The nsMemoryReporterManager gets destroyed before the nsCategoryManager, + // so we don't need to unregister the nsCategoryManager as a memory reporter. + // In debug builds we assert that unregistering fails, as a way (imperfect + // but better than nothing) of testing the "destroyed before" part. + MOZ_ASSERT(NS_FAILED(UnregisterWeakMemoryReporter(gCategoryManager))); + + delete gCategoryManager; + gCategoryManager = nullptr; +} + +nsresult nsCategoryManager::Create(REFNSIID aIID, void** aResult) { + return GetSingleton()->QueryInterface(aIID, aResult); +} + +nsCategoryManager::nsCategoryManager() + : mLock("nsCategoryManager"), mSuppressNotifications(false) {} + +void nsCategoryManager::InitMemoryReporter() { + RegisterWeakMemoryReporter(this); +} + +nsCategoryManager::~nsCategoryManager() { + // the hashtable contains entries that must be deleted before the arena is + // destroyed, or else you will have PRLocks undestroyed and other Really + // Bad Stuff (TM) + mTable.Clear(); +} + +inline CategoryNode* nsCategoryManager::get_category(const nsACString& aName) { + CategoryNode* node; + if (!mTable.Get(PromiseFlatCString(aName).get(), &node)) { + return nullptr; + } + return node; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf) + +NS_IMETHODIMP +nsCategoryManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/xpcom/category-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(CategoryManagerMallocSizeOf), + "Memory used for the XPCOM category manager."); + + return NS_OK; +} + +size_t nsCategoryManager::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mLock); + size_t n = aMallocSizeOf(this); + + n += mArena.SizeOfExcludingThis(aMallocSizeOf); + + n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mTable.Values()) { + // We don't measure the key string because it's a non-owning pointer. + n += data->SizeOfExcludingThis(aMallocSizeOf); + } + + return n; +} + +namespace { + +class CategoryNotificationRunnable : public Runnable { + public: + CategoryNotificationRunnable(nsISupports* aSubject, const char* aTopic, + const nsACString& aData) + : Runnable("CategoryNotificationRunnable"), + mSubject(aSubject), + mTopic(aTopic), + mData(aData) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsISupports> mSubject; + const char* mTopic; + NS_ConvertUTF8toUTF16 mData; +}; + +NS_IMETHODIMP +CategoryNotificationRunnable::Run() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(mSubject, mTopic, mData.get()); + } + + return NS_OK; +} + +} // namespace + +void nsCategoryManager::NotifyObservers(const char* aTopic, + const nsACString& aCategoryName, + const nsACString& aEntryName) { + if (mSuppressNotifications) { + return; + } + + RefPtr<CategoryNotificationRunnable> r; + + if (aEntryName.Length()) { + nsCOMPtr<nsISupportsCString> entry = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (!entry) { + return; + } + + nsresult rv = entry->SetData(aEntryName); + if (NS_FAILED(rv)) { + return; + } + + r = new CategoryNotificationRunnable(entry, aTopic, aCategoryName); + } else { + r = new CategoryNotificationRunnable( + NS_ISUPPORTS_CAST(nsICategoryManager*, this), aTopic, aCategoryName); + } + + NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +nsCategoryManager::GetCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + nsACString& aResult) { + nsresult status = NS_ERROR_NOT_AVAILABLE; + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + status = category->GetLeaf(aEntryName, aResult); + } + + return status; +} + +NS_IMETHODIMP +nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, bool aPersist, + bool aReplace, nsACString& aResult) { + if (aPersist) { + NS_ERROR("Category manager doesn't support persistence."); + return NS_ERROR_INVALID_ARG; + } + + AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, aResult); + return NS_OK; +} + +void nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, + bool aReplace, nsACString& aOldValue) { + MOZ_ASSERT(NS_IsMainThread()); + aOldValue.SetIsVoid(true); + + // Before we can insert a new entry, we'll need to + // find the |CategoryNode| to put it in... + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + + if (!category) { + // That category doesn't exist yet; let's make it. + category = mTable + .InsertOrUpdate( + MaybeStrdup(aCategoryName, &mArena), + UniquePtr<CategoryNode>{CategoryNode::Create(&mArena)}) + .get(); + } + } + + if (!category) { + return; + } + + nsresult rv = + category->AddLeaf(aEntryName, aValue, aReplace, aOldValue, &mArena); + + if (NS_SUCCEEDED(rv)) { + if (!aOldValue.IsEmpty()) { + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, + aCategoryName, aEntryName); + } + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, aCategoryName, + aEntryName); + } +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + bool aDontPersist) { + /* + Note: no errors are reported since failure to delete + probably won't hurt you, and returning errors seriously + inconveniences JS clients + */ + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->DeleteLeaf(aEntryName); + + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, aCategoryName, + aEntryName); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategory(const nsACString& aCategoryName) { + // the categories are arena-allocated, so we don't + // actually delete them. We just remove all of the + // leaf nodes. + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->Clear(); + NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, aCategoryName, + VoidCString()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategory(const nsACString& aCategoryName, + nsISimpleEnumerator** aResult) { + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (!category) { + return NS_NewEmptyEnumerator(aResult); + } + + return category->Enumerate(aResult); +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategories(nsISimpleEnumerator** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mLock); + CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable); + + if (!enumObj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + *aResult = enumObj; + NS_ADDREF(*aResult); + return NS_OK; +} + +struct writecat_struct { + PRFileDesc* fd; + bool success; +}; + +nsresult nsCategoryManager::SuppressNotifications(bool aSuppress) { + mSuppressNotifications = aSuppress; + return NS_OK; +} + +/* + * CreateServicesFromCategory() + * + * Given a category, this convenience functions enumerates the category and + * creates a service of every CID or ContractID registered under the category. + * If observerTopic is non null and the service implements nsIObserver, + * this will attempt to notify the observer with the origin, observerTopic + * string as parameter. + */ +void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin, + const char* aObserverTopic, + const char16_t* aObserverData) { + nsresult rv; + + nsCOMPtr<nsICategoryManager> categoryManager = + do_GetService("@mozilla.org/categorymanager;1"); + if (!categoryManager) { + return; + } + + nsDependentCString category(aCategory); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = categoryManager->EnumerateCategory(category, getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) { + // From here on just skip any error we get. + nsAutoCString entryString; + categoryEntry->GetEntry(entryString); + + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + nsCOMPtr<nsISupports> instance = do_GetService(contractID.get()); + if (!instance) { + LogMessage( + "While creating services from category '%s', could not create " + "service for entry '%s', contract ID '%s'", + aCategory, entryString.get(), contractID.get()); + continue; + } + + if (aObserverTopic) { + // try an observer, if it implements it. + nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance); + if (observer) { + nsPrintfCString profilerStr("%s (%s)", aObserverTopic, + entryString.get()); + AUTO_PROFILER_MARKER_TEXT("Category observer notification", OTHER, + MarkerStack::Capture(), profilerStr); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "Category observer notification -", OTHER, profilerStr); + + observer->Observe(aOrigin, aObserverTopic, + aObserverData ? aObserverData : u""); + } else { + LogMessage( + "While creating services from category '%s', service for entry " + "'%s', contract ID '%s' does not implement nsIObserver.", + aCategory, entryString.get(), contractID.get()); + } + } + } +} diff --git a/xpcom/components/nsCategoryManager.h b/xpcom/components/nsCategoryManager.h new file mode 100644 index 0000000000..765882fe47 --- /dev/null +++ b/xpcom/components/nsCategoryManager.h @@ -0,0 +1,145 @@ +/* -*- 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 NSCATEGORYMANAGER_H +#define NSCATEGORYMANAGER_H + +#include "prio.h" +#include "nsClassHashtable.h" +#include "nsICategoryManager.h" +#include "nsIMemoryReporter.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" + +class nsIMemoryReporter; + +typedef mozilla::ArenaAllocator<1024 * 8, 8> CategoryAllocator; + +/* 16d222a6-1dd2-11b2-b693-f38b02c021b2 */ +#define NS_CATEGORYMANAGER_CID \ + { \ + 0x16d222a6, 0x1dd2, 0x11b2, { \ + 0xb6, 0x93, 0xf3, 0x8b, 0x02, 0xc0, 0x21, 0xb2 \ + } \ + } + +/** + * a "leaf-node", managed by the nsCategoryNode hashtable. + * + * we need to keep a "persistent value" (which will be written to the registry) + * and a non-persistent value (for the current runtime): these are usually + * the same, except when aPersist==false. The strings are permanently arena- + * allocated, and will never go away. + */ +class CategoryLeaf : public nsDepCharHashKey { + public: + explicit CategoryLeaf(const char* aKey) + : nsDepCharHashKey(aKey), value(nullptr) {} + const char* value; +}; + +/** + * CategoryNode keeps a hashtable of its entries. + * the CategoryNode itself is permanently allocated in + * the arena. + */ +class CategoryNode { + public: + nsresult GetLeaf(const nsACString& aEntryName, nsACString& aResult); + + nsresult AddLeaf(const nsACString& aEntryName, const nsACString& aValue, + bool aReplace, nsACString& aResult, + CategoryAllocator* aArena); + + void DeleteLeaf(const nsACString& aEntryName); + + void Clear() { + mozilla::MutexAutoLock lock(mLock); + mTable.Clear(); + } + + uint32_t Count() { + mozilla::MutexAutoLock lock(mLock); + uint32_t tCount = mTable.Count(); + return tCount; + } + + nsresult Enumerate(nsISimpleEnumerator** aResult); + + // CategoryNode is arena-allocated, with the strings + static CategoryNode* Create(CategoryAllocator* aArena); + ~CategoryNode(); + void operator delete(void*) {} + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + private: + CategoryNode() : mLock("CategoryLeaf") {} + + void* operator new(size_t aSize, CategoryAllocator* aArena); + + nsTHashtable<CategoryLeaf> mTable MOZ_GUARDED_BY(mLock); + mozilla::Mutex mLock; +}; + +/** + * The main implementation of nsICategoryManager. + * + * This implementation is thread-safe. + */ +class nsCategoryManager final : public nsICategoryManager, + public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICATEGORYMANAGER + NS_DECL_NSIMEMORYREPORTER + + /** + * Suppress or unsuppress notifications of category changes to the + * observer service. This is to be used by nsComponentManagerImpl + * on startup while reading the stored category list. + */ + nsresult SuppressNotifications(bool aSuppress); + + void AddCategoryEntry(const nsACString& aCategory, const nsACString& aKey, + const nsACString& aValue, bool aReplace, + nsACString& aOldValue); + + void AddCategoryEntry(const nsACString& aCategory, const nsACString& aKey, + const nsACString& aValue, bool aReplace = true) { + nsCString oldValue; + return AddCategoryEntry(aCategory, aKey, aValue, aReplace, oldValue); + } + + static nsresult Create(REFNSIID aIID, void** aResult); + void InitMemoryReporter(); + + static nsCategoryManager* GetSingleton(); + static void Destroy(); + + private: + static nsCategoryManager* gCategoryManager; + + nsCategoryManager(); + ~nsCategoryManager(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + CategoryNode* get_category(const nsACString& aName) MOZ_REQUIRES(mLock); + void NotifyObservers( + const char* aTopic, + const nsACString& aCategoryName, // must be a static string + const nsACString& aEntryName); + + CategoryAllocator mArena; // Mainthread only + nsClassHashtable<nsDepCharHashKey, CategoryNode> mTable MOZ_GUARDED_BY(mLock); + mozilla::Mutex mLock; + bool mSuppressNotifications; // Mainthread only +}; + +#endif diff --git a/xpcom/components/nsCategoryManagerUtils.h b/xpcom/components/nsCategoryManagerUtils.h new file mode 100644 index 0000000000..30db31bab6 --- /dev/null +++ b/xpcom/components/nsCategoryManagerUtils.h @@ -0,0 +1,14 @@ +/* -*- 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 nsCategoryManagerUtils_h__ +#define nsCategoryManagerUtils_h__ + +void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin, + const char* aObserverTopic, + const char16_t* aObserverData = nullptr); + +#endif diff --git a/xpcom/components/nsComponentManager.cpp b/xpcom/components/nsComponentManager.cpp new file mode 100644 index 0000000000..515fc82312 --- /dev/null +++ b/xpcom/components/nsComponentManager.cpp @@ -0,0 +1,1591 @@ +/* -*- 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 <stdlib.h> +#include "nscore.h" +#include "nsISupports.h" +#include "nspr.h" +#include "nsCRT.h" // for atoll + +#include "StaticComponents.h" + +#include "nsCategoryManager.h" +#include "nsCOMPtr.h" +#include "nsComponentManager.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsLayoutModule.h" +#include "mozilla/MemoryReporting.h" +#include "nsIObserverService.h" +#include "nsIStringEnumerator.h" +#include "nsXPCOM.h" +#include "nsXPCOMPrivate.h" +#include "nsISupportsPrimitives.h" +#include "nsLocalFile.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "prcmon.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "prthread.h" +#include "private/pprthred.h" +#include "nsTArray.h" +#include "prio.h" +#include "ManifestParser.h" +#include "nsNetUtil.h" +#include "mozilla/Services.h" + +#include "mozilla/GenericFactory.h" +#include "nsSupportsPrimitives.h" +#include "nsArray.h" +#include "nsIMutableArray.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FileUtils.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/URLPreloader.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" + +#include <new> // for placement new + +#include "mozilla/Omnijar.h" + +#include "mozilla/Logging.h" +#include "LogModulePrefWatcher.h" +#include "xpcpublic.h" + +#ifdef MOZ_MEMORY +# include "mozmemory.h" +#endif + +using namespace mozilla; +using namespace mozilla::xpcom; + +static LazyLogModule nsComponentManagerLog("nsComponentManager"); + +#if 0 +# define SHOW_DENIED_ON_SHUTDOWN +# define SHOW_CI_ON_EXISTING_SERVICE +#endif + +namespace { + +class AutoIDString : public nsAutoCStringN<NSID_LENGTH> { + public: + explicit AutoIDString(const nsID& aID) { + SetLength(NSID_LENGTH - 1); + aID.ToProvidedString( + *reinterpret_cast<char(*)[NSID_LENGTH]>(BeginWriting())); + } +}; + +} // namespace + +namespace mozilla { +namespace xpcom { + +using ProcessSelector = Module::ProcessSelector; + +// Note: These must be kept in sync with the ProcessSelector definition in +// Module.h. +bool ProcessSelectorMatches(ProcessSelector aSelector) { + GeckoProcessType type = XRE_GetProcessType(); + if (type == GeckoProcessType_GPU) { + return !!(aSelector & Module::ALLOW_IN_GPU_PROCESS); + } + + if (type == GeckoProcessType_RDD) { + return !!(aSelector & Module::ALLOW_IN_RDD_PROCESS); + } + + if (type == GeckoProcessType_Socket) { + return !!(aSelector & (Module::ALLOW_IN_SOCKET_PROCESS)); + } + + if (type == GeckoProcessType_VR) { + return !!(aSelector & Module::ALLOW_IN_VR_PROCESS); + } + + if (type == GeckoProcessType_Utility) { + return !!(aSelector & Module::ALLOW_IN_UTILITY_PROCESS); + } + + if (type == GeckoProcessType_GMPlugin) { + return !!(aSelector & Module::ALLOW_IN_GMPLUGIN_PROCESS); + } + + // Only allow XPCOM modules which can be loaded in all processes to be loaded + // in the IPDLUnitTest process. + if (type == GeckoProcessType_IPDLUnitTest) { + return size_t(aSelector) == Module::kMaxProcessSelector; + } + + if (aSelector & Module::MAIN_PROCESS_ONLY) { + return type == GeckoProcessType_Default; + } + if (aSelector & Module::CONTENT_PROCESS_ONLY) { + return type == GeckoProcessType_Content; + } + return true; +} + +static bool gProcessMatchTable[Module::kMaxProcessSelector + 1]; + +bool FastProcessSelectorMatches(ProcessSelector aSelector) { + return gProcessMatchTable[size_t(aSelector)]; +} + +} // namespace xpcom +} // namespace mozilla + +namespace { + +/** + * A wrapper simple wrapper class, which can hold either a dynamic + * nsFactoryEntry instance, or a static StaticModule entry, and transparently + * forwards method calls to the wrapped object. + * + * This allows the same code to work with either static or dynamic modules + * without caring about the difference. + */ +class MOZ_STACK_CLASS EntryWrapper final { + public: + explicit EntryWrapper(nsFactoryEntry* aEntry) : mEntry(aEntry) {} + + explicit EntryWrapper(const StaticModule* aEntry) : mEntry(aEntry) {} + +#define MATCH(type, ifFactory, ifStatic) \ + struct Matcher { \ + type operator()(nsFactoryEntry* entry) { ifFactory; } \ + type operator()(const StaticModule* entry) { ifStatic; } \ + }; \ + return mEntry.match((Matcher())) + + const nsID& CID() { + MATCH(const nsID&, return entry->mCID, return entry->CID()); + } + + already_AddRefed<nsIFactory> GetFactory() { + MATCH(already_AddRefed<nsIFactory>, return entry->GetFactory(), + return entry->GetFactory()); + } + + /** + * Creates an instance of the underlying component. This should be used in + * preference to GetFactory()->CreateInstance() where appropriate, since it + * side-steps the necessity of creating a nsIFactory instance for static + * modules. + */ + nsresult CreateInstance(const nsIID& aIID, void** aResult) { + if (mEntry.is<nsFactoryEntry*>()) { + return mEntry.as<nsFactoryEntry*>()->CreateInstance(aIID, aResult); + } + return mEntry.as<const StaticModule*>()->CreateInstance(aIID, aResult); + } + + /** + * Returns the cached service instance for this entry, if any. This should + * only be accessed while mLock is held. + */ + nsISupports* ServiceInstance() { + MATCH(nsISupports*, return entry->mServiceObject, + return entry->ServiceInstance()); + } + void SetServiceInstance(already_AddRefed<nsISupports> aInst) { + if (mEntry.is<nsFactoryEntry*>()) { + mEntry.as<nsFactoryEntry*>()->mServiceObject = aInst; + } else { + return mEntry.as<const StaticModule*>()->SetServiceInstance( + std::move(aInst)); + } + } + + /** + * Returns the description string for the module this entry belongs to. + * Currently always returns "<unknown module>". + */ + nsCString ModuleDescription() { return "<unknown module>"_ns; } + + private: + Variant<nsFactoryEntry*, const StaticModule*> mEntry; +}; + +} // namespace + +// this is safe to call during InitXPCOM +static already_AddRefed<nsIFile> GetLocationFromDirectoryService( + const char* aProp) { + nsCOMPtr<nsIProperties> directoryService; + nsDirectoryService::Create(NS_GET_IID(nsIProperties), + getter_AddRefs(directoryService)); + + if (!directoryService) { + return nullptr; + } + + nsCOMPtr<nsIFile> file; + nsresult rv = + directoryService->Get(aProp, NS_GET_IID(nsIFile), getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return file.forget(); +} + +static already_AddRefed<nsIFile> CloneAndAppend(nsIFile* aBase, + const nsACString& aAppend) { + nsCOMPtr<nsIFile> f; + aBase->Clone(getter_AddRefs(f)); + if (!f) { + return nullptr; + } + + f->AppendNative(aAppend); + return f.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsComponentManagerImpl +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsComponentManagerImpl::Create(REFNSIID aIID, void** aResult) { + if (!gComponentManager) { + return NS_ERROR_FAILURE; + } + + return gComponentManager->QueryInterface(aIID, aResult); +} + +static const int CONTRACTID_HASHTABLE_INITIAL_LENGTH = 8; + +nsComponentManagerImpl::nsComponentManagerImpl() + : mFactories(CONTRACTID_HASHTABLE_INITIAL_LENGTH), + mContractIDs(CONTRACTID_HASHTABLE_INITIAL_LENGTH), + mLock("nsComponentManagerImpl.mLock"), + mStatus(NOT_INITIALIZED) {} + +nsTArray<nsComponentManagerImpl::ComponentLocation>* + nsComponentManagerImpl::sModuleLocations; + +/* static */ +void nsComponentManagerImpl::InitializeModuleLocations() { + if (sModuleLocations) { + return; + } + + sModuleLocations = new nsTArray<ComponentLocation>; +} + +nsresult nsComponentManagerImpl::Init() { + { + gProcessMatchTable[size_t(ProcessSelector::ANY_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ANY_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::MAIN_PROCESS_ONLY)] = + ProcessSelectorMatches(ProcessSelector::MAIN_PROCESS_ONLY); + gProcessMatchTable[size_t(ProcessSelector::CONTENT_PROCESS_ONLY)] = + ProcessSelectorMatches(ProcessSelector::CONTENT_PROCESS_ONLY); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_VR_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_VR_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_SOCKET_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_SOCKET_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_RDD_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_RDD_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GMPLUGIN_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GMPLUGIN_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_AND_MAIN_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_AND_MAIN_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_AND_VR_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_AND_VR_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_RDD_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_RDD_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector:: + ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector:: + ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS); + } + + MOZ_ASSERT(NOT_INITIALIZED == mStatus); + + nsCOMPtr<nsIFile> greDir = GetLocationFromDirectoryService(NS_GRE_DIR); + nsCOMPtr<nsIFile> appDir = + GetLocationFromDirectoryService(NS_XPCOM_CURRENT_PROCESS_DIR); + + nsCategoryManager::GetSingleton()->SuppressNotifications(true); + + auto* catMan = nsCategoryManager::GetSingleton(); + for (const auto& cat : gStaticCategories) { + for (const auto& entry : cat) { + if (entry.Active()) { + catMan->AddCategoryEntry(cat.Name(), entry.Entry(), entry.Value()); + } + } + } + + // This needs to be initialized late enough, so that preferences service can + // be accessed but before the IO service, and we want it in all process types. + xpc::ReadOnlyPage::Init(); + + bool loadChromeManifests; + switch (XRE_GetProcessType()) { + // We are going to assume that only a select few (see below) process types + // want to load chrome manifests, and that any new process types will not + // want to load them, because they're not going to be executing JS. + case GeckoProcessType_RemoteSandboxBroker: + default: + loadChromeManifests = false; + break; + + // XXX The check this code replaced implicitly let through all of these + // process types, but presumably only the default (parent) and content + // processes really need chrome manifests...? + case GeckoProcessType_Default: + case GeckoProcessType_Content: + loadChromeManifests = true; + break; + } + + if (loadChromeManifests) { + // This needs to be called very early, before anything in nsLayoutModule is + // used, and before any calls are made into the JS engine. + nsLayoutModuleInitialize(); + + mJSLoaderReady = true; + + // The overall order in which chrome.manifests are expected to be treated + // is the following: + // - greDir's omni.ja or greDir + // - appDir's omni.ja or appDir + + InitializeModuleLocations(); + ComponentLocation* cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + RefPtr<nsZipArchive> greOmnijar = + mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (greOmnijar) { + cl->location.Init(greOmnijar, "chrome.manifest"); + } else { + nsCOMPtr<nsIFile> lf = CloneAndAppend(greDir, "chrome.manifest"_ns); + cl->location.Init(lf); + } + + RefPtr<nsZipArchive> appOmnijar = + mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); + if (appOmnijar) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + cl->location.Init(appOmnijar, "chrome.manifest"); + } else { + bool equals = false; + appDir->Equals(greDir, &equals); + if (!equals) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + nsCOMPtr<nsIFile> lf = CloneAndAppend(appDir, "chrome.manifest"_ns); + cl->location.Init(lf); + } + } + + RereadChromeManifests(false); + } + + nsCategoryManager::GetSingleton()->SuppressNotifications(false); + + RegisterWeakMemoryReporter(this); + + // NB: The logging preference watcher needs to be registered late enough in + // startup that it's okay to use the preference system, but also as soon as + // possible so that log modules enabled via preferences are turned on as + // early as possible. + // + // We can't initialize the preference watcher when the log module manager is + // initialized, as a number of things attempt to start logging before the + // preference system is initialized. + // + // The preference system is registered as a component so at this point during + // component manager initialization we know it is setup and we can register + // for notifications. + LogModulePrefWatcher::RegisterPrefWatcher(); + + // Unfortunately, we can't register the nsCategoryManager memory reporter + // in its constructor (which is triggered by the GetSingleton() call + // above) because the memory reporter manager isn't initialized at that + // point. So we wait until now. + nsCategoryManager::GetSingleton()->InitMemoryReporter(); + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Initialized.")); + + mStatus = NORMAL; + + MOZ_ASSERT(!XRE_IsContentProcess() || + CONTRACTID_HASHTABLE_INITIAL_LENGTH <= 8 || + mFactories.Count() > CONTRACTID_HASHTABLE_INITIAL_LENGTH / 3, + "Initial component hashtable size is too large"); + + return NS_OK; +} + +template <typename T> +static void AssertNotMallocAllocated(T* aPtr) { +#if defined(DEBUG) && defined(MOZ_MEMORY) + jemalloc_ptr_info_t info; + jemalloc_ptr_info((void*)aPtr, &info); + MOZ_ASSERT(info.tag == TagUnknown); +#endif +} + +template <typename T> +static void AssertNotStackAllocated(T* aPtr) { + // On all of our supported platforms, the stack grows down. Any address + // located below the address of our argument is therefore guaranteed not to be + // stack-allocated by the caller. + // + // For addresses above our argument, things get trickier. The main thread + // stack is traditionally placed at the top of the program's address space, + // but that is becoming less reliable as more and more systems adopt address + // space layout randomization strategies, so we have to guess how much space + // above our argument pointer we need to care about. + // + // On most systems, we're guaranteed at least several KiB at the top of each + // stack for TLS. We'd probably be safe assuming at least 4KiB in the stack + // segment above our argument address, but safer is... well, safer. + // + // For threads with huge stacks, it's theoretically possible that we could + // wind up being passed a stack-allocated string from farther up the stack, + // but this is a best-effort thing, so we'll assume we only care about the + // immediate caller. For that case, max 2KiB per stack frame is probably a + // reasonable guess most of the time, and is less than the ~4KiB that we + // expect for TLS, so go with that to avoid the risk of bumping into heap + // data just above the stack. +#ifdef DEBUG + static constexpr size_t kFuzz = 2048; + + MOZ_ASSERT(uintptr_t(aPtr) < uintptr_t(&aPtr) || + uintptr_t(aPtr) > uintptr_t(&aPtr) + kFuzz); +#endif +} + +static void DoRegisterManifest(NSLocationType aType, FileLocation& aFile, + bool aChromeOnly) { + auto result = URLPreloader::Read(aFile); + if (result.isOk()) { + nsCString buf(result.unwrap()); + ParseManifest(aType, aFile, buf.BeginWriting(), aChromeOnly); + } else if (NS_BOOTSTRAPPED_LOCATION != aType) { + nsCString uri; + aFile.GetURIString(uri); + LogMessage("Could not read chrome manifest '%s'.", uri.get()); + } +} + +void nsComponentManagerImpl::RegisterManifest(NSLocationType aType, + FileLocation& aFile, + bool aChromeOnly) { + DoRegisterManifest(aType, aFile, aChromeOnly); +} + +void nsComponentManagerImpl::ManifestManifest(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) { + char* file = aArgv[0]; + FileLocation f(aCx.mFile, file); + RegisterManifest(aCx.mType, f, aCx.mChromeOnly); +} + +void nsComponentManagerImpl::ManifestCategory(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) { + char* category = aArgv[0]; + char* key = aArgv[1]; + char* value = aArgv[2]; + + nsCategoryManager::GetSingleton()->AddCategoryEntry( + nsDependentCString(category), nsDependentCString(key), + nsDependentCString(value)); +} + +void nsComponentManagerImpl::RereadChromeManifests(bool aChromeOnly) { + for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) { + ComponentLocation& l = sModuleLocations->ElementAt(i); + RegisterManifest(l.type, l.location, aChromeOnly); + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "chrome-manifests-loaded", nullptr); + } +} + +nsresult nsComponentManagerImpl::Shutdown(void) { + MOZ_ASSERT(NORMAL == mStatus); + + mStatus = SHUTDOWN_IN_PROGRESS; + + // Shutdown the component manager + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Beginning Shutdown.")); + + UnregisterWeakMemoryReporter(this); + + // Release all cached factories + mContractIDs.Clear(); + mFactories.Clear(); // XXX release the objects, don't just clear + + StaticComponents::Shutdown(); + + delete sModuleLocations; + + mStatus = SHUTDOWN_COMPLETE; + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Shutdown complete.")); + + return NS_OK; +} + +nsComponentManagerImpl::~nsComponentManagerImpl() { + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Beginning destruction.")); + + if (SHUTDOWN_COMPLETE != mStatus) { + Shutdown(); + } + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Destroyed.")); +} + +NS_IMPL_ISUPPORTS(nsComponentManagerImpl, nsIComponentManager, + nsIServiceManager, nsIComponentRegistrar, + nsISupportsWeakReference, nsIInterfaceRequestor, + nsIMemoryReporter) + +nsresult nsComponentManagerImpl::GetInterface(const nsIID& aUuid, + void** aResult) { + NS_WARNING("This isn't supported"); + // fall through to QI as anything QIable is a superset of what can be + // got via the GetInterface() + return QueryInterface(aUuid, aResult); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByCID(const nsID& aCID) { + return LookupByCID(MonitorAutoLock(mLock), aCID); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByCID(const MonitorAutoLock&, + const nsID& aCID) { + if (const StaticModule* module = StaticComponents::LookupByCID(aCID)) { + return Some(EntryWrapper(module)); + } + if (nsFactoryEntry* entry = mFactories.Get(&aCID)) { + return Some(EntryWrapper(entry)); + } + return Nothing(); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByContractID( + const nsACString& aContractID) { + return LookupByContractID(MonitorAutoLock(mLock), aContractID); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByContractID( + const MonitorAutoLock&, const nsACString& aContractID) { + if (const StaticModule* module = + StaticComponents::LookupByContractID(aContractID)) { + return Some(EntryWrapper(module)); + } + if (nsFactoryEntry* entry = mContractIDs.Get(aContractID)) { + // UnregisterFactory might have left a stale nsFactoryEntry in + // mContractIDs, so we should check to see whether this entry has + // anything useful. + if (entry->mFactory || entry->mServiceObject) { + return Some(EntryWrapper(entry)); + } + } + return Nothing(); +} + +already_AddRefed<nsIFactory> nsComponentManagerImpl::FindFactory( + const nsCID& aClass) { + Maybe<EntryWrapper> e = LookupByCID(aClass); + if (!e) { + return nullptr; + } + + return e->GetFactory(); +} + +already_AddRefed<nsIFactory> nsComponentManagerImpl::FindFactory( + const char* aContractID, uint32_t aContractIDLen) { + Maybe<EntryWrapper> entry = + LookupByContractID(nsDependentCString(aContractID, aContractIDLen)); + if (!entry) { + return nullptr; + } + + return entry->GetFactory(); +} + +/** + * GetClassObject() + * + * Given a classID, this finds the singleton ClassObject that implements the + * CID. Returns an interface of type aIID off the singleton classobject. + */ +NS_IMETHODIMP +nsComponentManagerImpl::GetClassObject(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + nsresult rv; + + if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Debug)) { + char buf[NSID_LENGTH]; + aClass.ToProvidedString(buf); + PR_LogPrint("nsComponentManager: GetClassObject(%s)", buf); + } + + MOZ_ASSERT(aResult != nullptr); + + nsCOMPtr<nsIFactory> factory = FindFactory(aClass); + if (!factory) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + rv = factory->QueryInterface(aIID, aResult); + + MOZ_LOG( + nsComponentManagerLog, LogLevel::Warning, + ("\t\tGetClassObject() %s", NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetClassObjectByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aContractID)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: GetClassObjectByContractID(%s)", aContractID)); + + nsCOMPtr<nsIFactory> factory = FindFactory(aContractID, strlen(aContractID)); + if (!factory) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + rv = factory->QueryInterface(aIID, aResult); + + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("\t\tGetClassObjectByContractID() %s", + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +/** + * CreateInstance() + * + * Create an instance of an object that implements an interface and belongs + * to the implementation aClass using the factory. The factory is immediately + * released and not held onto for any longer. + */ +NS_IMETHODIMP +nsComponentManagerImpl::CreateInstance(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in creating a component during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Creating new instance on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + Maybe<EntryWrapper> entry = LookupByCID(aClass); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->ServiceInstance()) { + nsAutoCString message; + message = "You are calling CreateInstance \""_ns + AutoIDString(aClass) + + "\" when a service for this CID already exists!"_ns; + NS_ERROR(message.get()); + } +#endif + + nsresult rv; + nsCOMPtr<nsIFactory> factory = entry->GetFactory(); + if (factory) { + rv = factory->CreateInstance(aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + } + } else { + // Translate error values + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Warning)) { + char buf[NSID_LENGTH]; + aClass.ToProvidedString(buf); + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("nsComponentManager: CreateInstance(%s) %s", buf, + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + } + + return rv; +} + +/** + * CreateInstanceByContractID() + * + * A variant of CreateInstance() that creates an instance of the object that + * implements the interface aIID and whose implementation has a contractID + * aContractID. + * + * This is only a convenience routine that turns around can calls the + * CreateInstance() with classid and iid. + */ +NS_IMETHODIMP +nsComponentManagerImpl::CreateInstanceByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + if (NS_WARN_IF(!aContractID)) { + return NS_ERROR_INVALID_ARG; + } + + // test this first, since there's no point in creating a component during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Creating new instance on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + Maybe<EntryWrapper> entry = + LookupByContractID(nsDependentCString(aContractID)); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->ServiceInstance()) { + nsAutoCString message; + message = + "You are calling CreateInstance \""_ns + + nsDependentCString(aContractID) + + nsLiteralCString( + "\" when a service for this CID already exists! " + "Add it to abusedContracts to track down the service consumer."); + NS_ERROR(message.get()); + } +#endif + + nsresult rv; + nsCOMPtr<nsIFactory> factory = entry->GetFactory(); + if (factory) { + rv = factory->CreateInstance(aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + } + } else { + // Translate error values + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("nsComponentManager: CreateInstanceByContractID(%s) %s", aContractID, + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +nsresult nsComponentManagerImpl::FreeServices() { + NS_ASSERTION(gXPCOMShuttingDown, + "Must be shutting down in order to free all services"); + + if (!gXPCOMShuttingDown) { + return NS_ERROR_FAILURE; + } + + for (nsFactoryEntry* entry : mFactories.Values()) { + entry->mFactory = nullptr; + entry->mServiceObject = nullptr; + } + + for (const auto& module : gStaticModules) { + module.SetServiceInstance(nullptr); + } + + return NS_OK; +} + +// This should only ever be called within the monitor! +nsComponentManagerImpl::PendingServiceInfo* +nsComponentManagerImpl::AddPendingService(const nsCID& aServiceCID, + PRThread* aThread) { + PendingServiceInfo* newInfo = mPendingServices.AppendElement(); + if (newInfo) { + newInfo->cid = &aServiceCID; + newInfo->thread = aThread; + } + return newInfo; +} + +// This should only ever be called within the monitor! +void nsComponentManagerImpl::RemovePendingService(MonitorAutoLock& aLock, + const nsCID& aServiceCID) { + uint32_t pendingCount = mPendingServices.Length(); + for (uint32_t index = 0; index < pendingCount; ++index) { + const PendingServiceInfo& info = mPendingServices.ElementAt(index); + if (info.cid->Equals(aServiceCID)) { + mPendingServices.RemoveElementAt(index); + aLock.NotifyAll(); + return; + } + } +} + +// This should only ever be called within the monitor! +PRThread* nsComponentManagerImpl::GetPendingServiceThread( + const nsCID& aServiceCID) const { + uint32_t pendingCount = mPendingServices.Length(); + for (uint32_t index = 0; index < pendingCount; ++index) { + const PendingServiceInfo& info = mPendingServices.ElementAt(index); + if (info.cid->Equals(aServiceCID)) { + return info.thread; + } + } + return nullptr; +} + +nsresult nsComponentManagerImpl::GetServiceLocked(Maybe<MonitorAutoLock>& aLock, + EntryWrapper& aEntry, + const nsIID& aIID, + void** aResult) { + MOZ_ASSERT(aLock.isSome()); + if (!aLock.isSome()) { + return NS_ERROR_INVALID_ARG; + } + + if (auto* service = aEntry.ServiceInstance()) { + aLock.reset(); + return service->QueryInterface(aIID, aResult); + } + + PRThread* currentPRThread = PR_GetCurrentThread(); + MOZ_ASSERT(currentPRThread, "This should never be null!"); + + PRThread* pendingPRThread; + while ((pendingPRThread = GetPendingServiceThread(aEntry.CID()))) { + if (pendingPRThread == currentPRThread) { + NS_ERROR("Recursive GetService!"); + return NS_ERROR_NOT_AVAILABLE; + } + + aLock->Wait(); + } + + // It's still possible that the other thread failed to create the + // service so we're not guaranteed to have an entry or service yet. + if (auto* service = aEntry.ServiceInstance()) { + aLock.reset(); + return service->QueryInterface(aIID, aResult); + } + + DebugOnly<PendingServiceInfo*> newInfo = + AddPendingService(aEntry.CID(), currentPRThread); + NS_ASSERTION(newInfo, "Failed to add info to the array!"); + + // We need to not be holding the service manager's lock while calling + // CreateInstance, because it invokes user code which could try to re-enter + // the service manager: + + nsCOMPtr<nsISupports> service; + auto cleanup = MakeScopeExit([&]() { + // `service` must be released after the lock is released, so if we fail and + // still have a reference, release the lock before releasing it. + if (service) { + MOZ_ASSERT(aLock.isSome()); + aLock.reset(); + service = nullptr; + } + }); + nsresult rv; + mLock.AssertCurrentThreadOwns(); + { + MonitorAutoUnlock unlock(mLock); + AUTO_PROFILER_MARKER_TEXT( + "GetService", OTHER, MarkerStack::Capture(), + nsDependentCString(nsIDToCString(aEntry.CID()).get())); + rv = aEntry.CreateInstance(aIID, getter_AddRefs(service)); + } + if (NS_SUCCEEDED(rv) && !service) { + NS_ERROR("Factory did not return an object but returned success"); + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + +#ifdef DEBUG + pendingPRThread = GetPendingServiceThread(aEntry.CID()); + MOZ_ASSERT(pendingPRThread == currentPRThread, + "Pending service array has been changed!"); +#endif + MOZ_ASSERT(aLock.isSome()); + RemovePendingService(*aLock, aEntry.CID()); + + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!aEntry.ServiceInstance(), + "Created two instances of a service!"); + + aEntry.SetServiceInstance(service.forget()); + + aLock.reset(); + + *aResult = do_AddRef(aEntry.ServiceInstance()).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetService(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + Maybe<MonitorAutoLock> lock(std::in_place, mLock); + + Maybe<EntryWrapper> entry = LookupByCID(*lock, aClass); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + return GetServiceLocked(lock, *entry, aIID, aResult); +} + +nsresult nsComponentManagerImpl::GetService(ModuleID aId, const nsIID& aIID, + void** aResult) { + const auto& entry = gStaticModules[size_t(aId)]; + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(entry.CID()).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + Maybe<MonitorAutoLock> lock(std::in_place, mLock); + + Maybe<EntryWrapper> wrapper; + if (entry.Overridable()) { + // If we expect this service to be overridden by test code, we need to look + // it up by contract ID every time. + wrapper = LookupByContractID(*lock, entry.ContractID()); + if (!wrapper) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + } else if (!entry.Active()) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } else { + wrapper.emplace(&entry); + } + return GetServiceLocked(lock, *wrapper, aIID, aResult); +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsServiceInstantiated(const nsCID& aClass, + const nsIID& aIID, + bool* aResult) { + // Now we want to get the service if we already got it. If not, we don't want + // to create an instance of it. mmh! + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Checking for service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (Maybe<EntryWrapper> entry = LookupByCID(aClass)) { + if (auto* service = entry->ServiceInstance()) { + nsCOMPtr<nsISupports> instance; + nsresult rv = service->QueryInterface(aIID, getter_AddRefs(instance)); + *aResult = (instance != nullptr); + return rv; + } + } + + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsServiceInstantiatedByContractID( + const char* aContractID, const nsIID& aIID, bool* aResult) { + // Now we want to get the service if we already got it. If not, we don't want + // to create an instance of it. mmh! + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Checking for service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (Maybe<EntryWrapper> entry = + LookupByContractID(nsDependentCString(aContractID))) { + if (auto* service = entry->ServiceInstance()) { + nsCOMPtr<nsISupports> instance; + nsresult rv = service->QueryInterface(aIID, getter_AddRefs(instance)); + *aResult = (instance != nullptr); + return rv; + } + } + + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetServiceByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE("GetServiceByContractID", OTHER, + aContractID); + Maybe<MonitorAutoLock> lock(std::in_place, mLock); + + Maybe<EntryWrapper> entry = + LookupByContractID(*lock, nsDependentCString(aContractID)); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + return GetServiceLocked(lock, *entry, aIID, aResult); +} + +NS_IMETHODIMP +nsComponentManagerImpl::RegisterFactory(const nsCID& aClass, const char* aName, + const char* aContractID, + nsIFactory* aFactory) { + if (!aFactory) { + // If a null factory is passed in, this call just wants to reset + // the contract ID to point to an existing CID entry. + if (!aContractID) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString contractID(aContractID); + + MonitorAutoLock lock(mLock); + nsFactoryEntry* oldf = mFactories.Get(&aClass); + if (oldf) { + StaticComponents::InvalidateContractID(contractID); + mContractIDs.InsertOrUpdate(contractID, oldf); + return NS_OK; + } + + if (StaticComponents::LookupByCID(aClass)) { + // If this is the CID of a static module, just reset the invalid bit of + // the static entry for this contract ID, and assume it points to the + // correct class. + if (StaticComponents::InvalidateContractID(contractID, false)) { + mContractIDs.Remove(contractID); + return NS_OK; + } + } + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + auto f = MakeUnique<nsFactoryEntry>(aClass, aFactory); + + MonitorAutoLock lock(mLock); + return mFactories.WithEntryHandle(&f->mCID, [&](auto&& entry) { + if (entry) { + return NS_ERROR_FACTORY_EXISTS; + } + if (StaticComponents::LookupByCID(f->mCID)) { + return NS_ERROR_FACTORY_EXISTS; + } + if (aContractID) { + nsDependentCString contractID(aContractID); + mContractIDs.InsertOrUpdate(contractID, f.get()); + // We allow dynamically-registered contract IDs to override static + // entries, so invalidate any static entry for this contract ID. + StaticComponents::InvalidateContractID(contractID); + } + entry.Insert(f.release()); + + return NS_OK; + }); +} + +NS_IMETHODIMP +nsComponentManagerImpl::UnregisterFactory(const nsCID& aClass, + nsIFactory* aFactory) { + // Don't release the dying factory or service object until releasing + // the component manager monitor. + nsCOMPtr<nsIFactory> dyingFactory; + nsCOMPtr<nsISupports> dyingServiceObject; + + { + MonitorAutoLock lock(mLock); + auto entry = mFactories.Lookup(&aClass); + nsFactoryEntry* f = entry ? entry.Data() : nullptr; + if (!f || f->mFactory != aFactory) { + // Note: We do not support unregistering static factories. + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + entry.Remove(); + + // This might leave a stale contractid -> factory mapping in + // place, so null out the factory entry (see + // nsFactoryEntry::GetFactory) + f->mFactory.swap(dyingFactory); + f->mServiceObject.swap(dyingServiceObject); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::AutoRegister(nsIFile* aLocation) { + XRE_AddManifestLocation(NS_EXTENSION_LOCATION, aLocation); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsCIDRegistered(const nsCID& aClass, bool* aResult) { + *aResult = LookupByCID(aClass).isSome(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsContractIDRegistered(const char* aClass, + bool* aResult) { + if (NS_WARN_IF(!aClass)) { + return NS_ERROR_INVALID_ARG; + } + + Maybe<EntryWrapper> entry = LookupByContractID(nsDependentCString(aClass)); + + *aResult = entry.isSome(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetContractIDs(nsTArray<nsCString>& aResult) { + aResult = ToTArray<nsTArray<nsCString>>(mContractIDs.Keys()); + + for (const auto& entry : gContractEntries) { + if (!entry.Invalid()) { + aResult.AppendElement(entry.ContractID()); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::ContractIDToCID(const char* aContractID, + nsCID** aResult) { + { + MonitorAutoLock lock(mLock); + Maybe<EntryWrapper> entry = + LookupByContractID(lock, nsDependentCString(aContractID)); + if (entry) { + *aResult = (nsCID*)moz_xmalloc(sizeof(nsCID)); + **aResult = entry->CID(); + return NS_OK; + } + } + *aResult = nullptr; + return NS_ERROR_FACTORY_NOT_REGISTERED; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(ComponentManagerMallocSizeOf) + +NS_IMETHODIMP +nsComponentManagerImpl::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/xpcom/component-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(ComponentManagerMallocSizeOf), + "Memory used for the XPCOM component manager."); + + return NS_OK; +} + +size_t nsComponentManagerImpl::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + + n += mFactories.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mFactories.Values()) { + n += data->SizeOfIncludingThis(aMallocSizeOf); + } + + n += mContractIDs.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& key : mContractIDs.Keys()) { + // We don't measure the nsFactoryEntry data because it's owned by + // mFactories (which is measured above). + n += key.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + if (sModuleLocations) { + n += sModuleLocations->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + + n += mPendingServices.ShallowSizeOfExcludingThis(aMallocSizeOf); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mMon + // - sModuleLocations' entries + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFactoryEntry +//////////////////////////////////////////////////////////////////////////////// + +nsFactoryEntry::nsFactoryEntry(const nsCID& aCID, nsIFactory* aFactory) + : mCID(aCID), mFactory(aFactory) {} + +already_AddRefed<nsIFactory> nsFactoryEntry::GetFactory() { + nsComponentManagerImpl::gComponentManager->mLock.AssertNotCurrentThreadOwns(); + + nsCOMPtr<nsIFactory> factory = mFactory; + return factory.forget(); +} + +nsresult nsFactoryEntry::CreateInstance(const nsIID& aIID, void** aResult) { + nsCOMPtr<nsIFactory> factory = GetFactory(); + NS_ENSURE_TRUE(factory, NS_ERROR_FAILURE); + return factory->CreateInstance(aIID, aResult); +} + +size_t nsFactoryEntry::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mCID; + // - mFactory; + // - mServiceObject; + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// +// Static Access Functions +//////////////////////////////////////////////////////////////////////////////// + +nsresult NS_GetComponentManager(nsIComponentManager** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +nsresult NS_GetServiceManager(nsIServiceManager** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +nsresult NS_GetComponentRegistrar(nsIComponentRegistrar** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::AddBootstrappedManifestLocation(nsIFile* aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsString path; + nsresult rv = aLocation->GetPath(path); + if (NS_FAILED(rv)) { + return rv; + } + + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + return XRE_AddJarManifestLocation(NS_BOOTSTRAPPED_LOCATION, aLocation); + } + + nsCOMPtr<nsIFile> manifest = CloneAndAppend(aLocation, "chrome.manifest"_ns); + return XRE_AddManifestLocation(NS_BOOTSTRAPPED_LOCATION, manifest); +} + +NS_IMETHODIMP +nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsCOMPtr<nsIChromeRegistry> cr = mozilla::services::GetChromeRegistry(); + if (!cr) { + return NS_ERROR_FAILURE; + } + + nsString path; + nsresult rv = aLocation->GetPath(path); + if (NS_FAILED(rv)) { + return rv; + } + + nsComponentManagerImpl::ComponentLocation elem; + elem.type = NS_BOOTSTRAPPED_LOCATION; + + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + elem.location.Init(aLocation, "chrome.manifest"); + } else { + nsCOMPtr<nsIFile> lf = CloneAndAppend(aLocation, "chrome.manifest"_ns); + elem.location.Init(lf); + } + + // Remove reference. + nsComponentManagerImpl::sModuleLocations->RemoveElement( + elem, ComponentLocationComparator()); + + rv = cr->CheckForNewChrome(); + return rv; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetComponentJSMs(nsIUTF8StringEnumerator** aJSMs) { + nsCOMPtr<nsIUTF8StringEnumerator> result = + StaticComponents::GetComponentJSMs(); + result.forget(aJSMs); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetComponentESModules( + nsIUTF8StringEnumerator** aESModules) { + nsCOMPtr<nsIUTF8StringEnumerator> result = + StaticComponents::GetComponentESModules(); + result.forget(aESModules); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetManifestLocations(nsIArray** aLocations) { + NS_ENSURE_ARG_POINTER(aLocations); + *aLocations = nullptr; + + if (!sModuleLocations) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr<nsIMutableArray> locations = nsArray::Create(); + nsresult rv; + for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) { + ComponentLocation& l = sModuleLocations->ElementAt(i); + FileLocation loc = l.location; + nsCString uriString; + loc.GetURIString(uriString); + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), uriString); + if (NS_SUCCEEDED(rv)) { + locations->AppendElement(uri); + } + } + + locations.forget(aLocations); + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation) { + nsComponentManagerImpl::InitializeModuleLocations(); + nsComponentManagerImpl::ComponentLocation* c = + nsComponentManagerImpl::sModuleLocations->AppendElement(); + c->type = aType; + c->location.Init(aLocation); + + if (nsComponentManagerImpl::gComponentManager && + nsComponentManagerImpl::NORMAL == + nsComponentManagerImpl::gComponentManager->mStatus) { + nsComponentManagerImpl::gComponentManager->RegisterManifest( + aType, c->location, false); + } + + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation) { + nsComponentManagerImpl::InitializeModuleLocations(); + nsComponentManagerImpl::ComponentLocation* c = + nsComponentManagerImpl::sModuleLocations->AppendElement(); + + c->type = aType; + c->location.Init(aLocation, "chrome.manifest"); + + if (nsComponentManagerImpl::gComponentManager && + nsComponentManagerImpl::NORMAL == + nsComponentManagerImpl::gComponentManager->mStatus) { + nsComponentManagerImpl::gComponentManager->RegisterManifest( + aType, c->location, false); + } + + return NS_OK; +} + +// Expose some important global interfaces to rust for the rust xpcom API. These +// methods return a non-owning reference to the component manager, which should +// live for the lifetime of XPCOM. +extern "C" { + +const nsIComponentManager* Gecko_GetComponentManager() { + return nsComponentManagerImpl::gComponentManager; +} + +const nsIServiceManager* Gecko_GetServiceManager() { + return nsComponentManagerImpl::gComponentManager; +} + +const nsIComponentRegistrar* Gecko_GetComponentRegistrar() { + return nsComponentManagerImpl::gComponentManager; +} + +// FFI-compatible version of `GetServiceHelper::operator()`. +nsresult Gecko_GetServiceByModuleID(ModuleID aId, const nsIID* aIID, + void** aResult) { + return nsComponentManagerImpl::gComponentManager->GetService(aId, *aIID, + aResult); +} + +// FFI-compatible version of `CreateInstanceHelper::operator()`. +nsresult Gecko_CreateInstanceByModuleID(ModuleID aId, const nsIID* aIID, + void** aResult) { + const auto& entry = gStaticModules[size_t(aId)]; + if (!entry.Active()) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + nsresult rv = entry.CreateInstance(*aIID, aResult); + return rv; +} +} diff --git a/xpcom/components/nsComponentManager.h b/xpcom/components/nsComponentManager.h new file mode 100644 index 0000000000..5ad555b53b --- /dev/null +++ b/xpcom/components/nsComponentManager.h @@ -0,0 +1,218 @@ +/* -*- 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 nsComponentManager_h__ +#define nsComponentManager_h__ + +#include "nsXPCOM.h" + +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsIMemoryReporter.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Module.h" +#include "mozilla/Monitor.h" +#include "mozilla/UniquePtr.h" +#include "nsXULAppAPI.h" +#include "nsIFactory.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "PLDHashTable.h" +#include "prtime.h" +#include "nsCOMPtr.h" +#include "nsWeakReference.h" +#include "nsCOMArray.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsTArray.h" + +#include "mozilla/Components.h" +#include "mozilla/Maybe.h" +#include "mozilla/Omnijar.h" +#include "mozilla/Attributes.h" + +struct nsFactoryEntry; +struct PRThread; + +#define NS_COMPONENTMANAGER_CID \ + { /* 91775d60-d5dc-11d2-92fb-00e09805570f */ \ + 0x91775d60, 0xd5dc, 0x11d2, { \ + 0x92, 0xfb, 0x00, 0xe0, 0x98, 0x05, 0x57, 0x0f \ + } \ + } + +//////////////////////////////////////////////////////////////////////////////// + +namespace { +class EntryWrapper; +} // namespace + +namespace mozilla { +namespace xpcom { + +bool ProcessSelectorMatches(Module::ProcessSelector aSelector); +bool FastProcessSelectorMatches(Module::ProcessSelector aSelector); + +} // namespace xpcom +} // namespace mozilla + +class nsComponentManagerImpl final : public nsIComponentManager, + public nsIServiceManager, + public nsSupportsWeakReference, + public nsIComponentRegistrar, + public nsIInterfaceRequestor, + public nsIMemoryReporter { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICOMPONENTMANAGER + NS_DECL_NSICOMPONENTREGISTRAR + NS_DECL_NSIMEMORYREPORTER + + static nsresult Create(REFNSIID aIID, void** aResult); + + nsresult RegistryLocationForFile(nsIFile* aFile, nsCString& aResult); + nsresult FileForRegistryLocation(const nsCString& aLocation, nsIFile** aSpec); + + NS_DECL_NSISERVICEMANAGER + + // nsComponentManagerImpl methods: + nsComponentManagerImpl(); + + static nsComponentManagerImpl* gComponentManager; + nsresult Init(); + + nsresult Shutdown(void); + + nsresult FreeServices(); + + already_AddRefed<nsIFactory> FindFactory(const nsCID& aClass); + already_AddRefed<nsIFactory> FindFactory(const char* aContractID, + uint32_t aContractIDLen); + + already_AddRefed<nsIFactory> LoadFactory(nsFactoryEntry* aEntry); + + nsTHashMap<nsIDPointerHashKey, nsFactoryEntry*> mFactories; + nsTHashMap<nsCStringHashKey, nsFactoryEntry*> mContractIDs; + + mozilla::Monitor mLock MOZ_UNANNOTATED; + + mozilla::Maybe<EntryWrapper> LookupByCID(const nsID& aCID); + mozilla::Maybe<EntryWrapper> LookupByCID(const mozilla::MonitorAutoLock&, + const nsID& aCID); + + mozilla::Maybe<EntryWrapper> LookupByContractID( + const nsACString& aContractID); + mozilla::Maybe<EntryWrapper> LookupByContractID( + const mozilla::MonitorAutoLock&, const nsACString& aContractID); + + nsresult GetService(mozilla::xpcom::ModuleID, const nsIID& aIID, + void** aResult); + + static bool JSLoaderReady() { return gComponentManager->mJSLoaderReady; } + + static void InitializeStaticModules(); + static void InitializeModuleLocations(); + + struct ComponentLocation { + NSLocationType type; + mozilla::FileLocation location; + }; + + class ComponentLocationComparator { + public: + bool Equals(const ComponentLocation& aA, + const ComponentLocation& aB) const { + return (aA.type == aB.type && aA.location.Equals(aB.location)); + } + }; + + static nsTArray<ComponentLocation>* sModuleLocations; + + // Mutex not held + void RegisterManifest(NSLocationType aType, mozilla::FileLocation& aFile, + bool aChromeOnly); + + struct ManifestProcessingContext { + ManifestProcessingContext(NSLocationType aType, + mozilla::FileLocation& aFile, bool aChromeOnly) + : mType(aType), mFile(aFile), mChromeOnly(aChromeOnly) {} + + ~ManifestProcessingContext() = default; + + NSLocationType mType; + mozilla::FileLocation mFile; + bool mChromeOnly; + }; + + void ManifestManifest(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void ManifestCategory(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + + void RereadChromeManifests(bool aChromeOnly = true); + + // Shutdown + enum { + NOT_INITIALIZED, + NORMAL, + SHUTDOWN_IN_PROGRESS, + SHUTDOWN_COMPLETE + } mStatus; + + struct PendingServiceInfo { + const nsCID* cid; + PRThread* thread; + }; + + inline PendingServiceInfo* AddPendingService(const nsCID& aServiceCID, + PRThread* aThread); + inline void RemovePendingService(mozilla::MonitorAutoLock& aLock, + const nsCID& aServiceCID); + inline PRThread* GetPendingServiceThread(const nsCID& aServiceCID) const; + + nsTArray<PendingServiceInfo> mPendingServices; + + bool mJSLoaderReady = false; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + ~nsComponentManagerImpl(); + + nsresult GetServiceLocked(mozilla::Maybe<mozilla::MonitorAutoLock>& aLock, + EntryWrapper& aEntry, const nsIID& aIID, + void** aResult); +}; + +#define NS_MAX_FILENAME_LEN 1024 + +#define NS_ERROR_IS_DIR NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_XPCOM, 24) + +struct nsFactoryEntry { + // nsIComponentRegistrar.registerFactory support + nsFactoryEntry(const nsCID& aClass, nsIFactory* aFactory); + + ~nsFactoryEntry() = default; + + already_AddRefed<nsIFactory> GetFactory(); + + nsresult CreateInstance(const nsIID& aIID, void** aResult); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + const nsCID mCID; + + nsCOMPtr<nsIFactory> mFactory; + nsCOMPtr<nsISupports> mServiceObject; +}; + +#endif // nsComponentManager_h__ diff --git a/xpcom/components/nsComponentManagerUtils.cpp b/xpcom/components/nsComponentManagerUtils.cpp new file mode 100644 index 0000000000..569c97060c --- /dev/null +++ b/xpcom/components/nsComponentManagerUtils.cpp @@ -0,0 +1,259 @@ +/* -*- 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 nsXPCOM_h__ +# include "nsXPCOM.h" +#endif + +#ifndef nsCOMPtr_h__ +# include "nsCOMPtr.h" +#endif + +#ifdef MOZILLA_INTERNAL_API +# include "nsComponentManager.h" +#endif + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIComponentManager.h" + +#ifndef MOZILLA_INTERNAL_API + +nsresult CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) { + nsCOMPtr<nsIServiceManager> servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetService(aCID, aIID, aResult); + } + return status; +} + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIServiceManager> servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetServiceByContractID(aContractID, aIID, aResult); + } + return status; +} + +#else + +nsresult CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (!compMgr) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetService(aCID, aIID, aResult); +} + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (!compMgr) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetServiceByContractID(aContractID, + aIID, aResult); +} + +#endif + +#ifndef MOZILLA_INTERNAL_API + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->CreateInstance(aCID, aIID, aResult); + } + return status; +} + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->CreateInstanceByContractID(aContractID, aIID, aResult); + return status; +} + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->GetClassObject(aCID, aIID, aResult); + } + return status; +} + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->GetClassObjectByContractID(aContractID, aIID, aResult); + return status; +} + +#else + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstance(aCID, aIID, aResult); +} + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstanceByContractID( + aContractID, aIID, aResult); +} + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetClassObject(aCID, aIID, aResult); +} + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetClassObjectByContractID( + aContractID, aIID, aResult); +} + +#endif + +nsresult nsCreateInstanceByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallCreateInstance(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsCreateInstanceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallCreateInstance(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsCreateInstanceFromFactory::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = mFactory->CreateInstance(aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetClassObjectByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetClassObject(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetClassObjectByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetClassObject(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetServiceByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult nsGetServiceByCIDWithError::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetServiceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult nsGetServiceByContractIDWithError::operator()( + const nsIID& aIID, void** aInstancePtr) const { + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/components/nsComponentManagerUtils.h b/xpcom/components/nsComponentManagerUtils.h new file mode 100644 index 0000000000..b6f15aabaa --- /dev/null +++ b/xpcom/components/nsComponentManagerUtils.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsComponentManagerUtils_h__ +#define nsComponentManagerUtils_h__ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIFactory.h" + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult); + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult); + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult); + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult); + +class MOZ_STACK_CLASS nsCreateInstanceByCID final : public nsCOMPtr_helper { + public: + nsCreateInstanceByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceByContractID final + : public nsCOMPtr_helper { + public: + nsCreateInstanceByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceFromFactory final + : public nsCOMPtr_helper { + public: + nsCreateInstanceFromFactory(nsIFactory* aFactory, nsresult* aErrorPtr) + : mFactory(aFactory), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + nsIFactory* MOZ_NON_OWNING_REF mFactory; + nsresult* mErrorPtr; +}; + +inline const nsCreateInstanceByCID do_CreateInstance(const nsCID& aCID, + nsresult* aError = 0) { + return nsCreateInstanceByCID(aCID, aError); +} + +inline const nsCreateInstanceByContractID do_CreateInstance( + const char* aContractID, nsresult* aError = 0) { + return nsCreateInstanceByContractID(aContractID, aError); +} + +inline const nsCreateInstanceFromFactory do_CreateInstance( + nsIFactory* aFactory, nsresult* aError = 0) { + return nsCreateInstanceFromFactory(aFactory, aError); +} + +class MOZ_STACK_CLASS nsGetClassObjectByCID final : public nsCOMPtr_helper { + public: + nsGetClassObjectByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsGetClassObjectByContractID final + : public nsCOMPtr_helper { + public: + nsGetClassObjectByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +/** + * do_GetClassObject can be used to improve performance of callers + * that call |CreateInstance| many times. They can cache the factory + * and call do_CreateInstance or CallCreateInstance with the cached + * factory rather than having the component manager retrieve it every + * time. + */ +inline const nsGetClassObjectByCID do_GetClassObject(const nsCID& aCID, + nsresult* aError = 0) { + return nsGetClassObjectByCID(aCID, aError); +} + +inline const nsGetClassObjectByContractID do_GetClassObject( + const char* aContractID, nsresult* aError = 0) { + return nsGetClassObjectByContractID(aContractID, aError); +} + +// type-safe shortcuts for calling |CreateInstance| +template <class DestinationType> +inline nsresult CallCreateInstance(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallCreateInstance(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallCreateInstance(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aContractID, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return CallCreateInstance(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallCreateInstance(nsIFactory* aFactory, + DestinationType** aDestination) { + MOZ_ASSERT(aFactory, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aFactory->CreateInstance(nullptr, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallGetClassObject(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetClassObject(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallGetClassObject(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetClassObject(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +#endif /* nsComponentManagerUtils_h__ */ diff --git a/xpcom/components/nsICategoryManager.idl b/xpcom/components/nsICategoryManager.idl new file mode 100644 index 0000000000..12c1a9e4a9 --- /dev/null +++ b/xpcom/components/nsICategoryManager.idl @@ -0,0 +1,137 @@ +/* -*- 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" +#include "nsISupportsPrimitives.idl" + +interface nsISimpleEnumerator; + +%{C++ +#include "nsString.h" +%} + +/* + * nsICategoryManager + */ + +[scriptable, builtinclass, uuid(de021d54-57a3-4025-ae63-4c8eedbe74c0)] +interface nsICategoryEntry : nsISupportsCString +{ + readonly attribute ACString entry; + + readonly attribute ACString value; +}; + +[builtinclass, scriptable, uuid(3275b2cd-af6d-429a-80d7-f0c5120342ac)] +interface nsICategoryManager : nsISupports +{ + /** + * Get the value for the given category's entry. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry you're looking for ("http") + * @return The value. + */ + ACString getCategoryEntry(in ACString aCategory, in ACString aEntry); + + /** + * Add an entry to a category. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry to be added ("http") + * @param aValue The value for the entry ("moz.httprulez.1") + * @param aPersist Should this data persist between invocations? + * @param aReplace Should we replace an existing entry? + * @return Previous entry, if any + */ + ACString addCategoryEntry(in ACString aCategory, in ACString aEntry, + in ACString aValue, in boolean aPersist, + in boolean aReplace); + + /** + * Delete an entry from the category. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry to be added ("http") + * @param aPersist Delete persistent data from registry, if present? + */ + void deleteCategoryEntry(in ACString aCategory, in ACString aEntry, + in boolean aPersist); + + /** + * Delete a category and all entries. + * @param aCategory The category to be deleted. + */ + void deleteCategory(in ACString aCategory); + + /** + * Enumerate the entries in a category. + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsICategoryEntry. + */ + nsISimpleEnumerator enumerateCategory(in ACString aCategory); + + + /** + * Enumerate all existing categories + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsISupportsCString. + */ + nsISimpleEnumerator enumerateCategories(); + + %{C++ + template<size_t N> + nsresult + GetCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, + nsACString& aResult) + { + return GetCategoryEntry(nsLiteralCString(aCategory), + aEntry, aResult); + } + + template<size_t N, size_t M> + nsresult + GetCategoryEntry(const char (&aCategory)[N], const char (&aEntry)[M], + nsACString& aResult) + { + return GetCategoryEntry(nsLiteralCString(aCategory), + nsLiteralCString(aEntry), + aResult); + } + + nsresult + AddCategoryEntry(const nsACString& aCategory, const nsACString& aEntry, + const nsACString& aValue, bool aPersist, bool aReplace) + { + nsCString oldValue; + return AddCategoryEntry(aCategory, aEntry, aValue, aPersist, aReplace, + oldValue); + } + + template<size_t N> + nsresult + AddCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, + const nsACString& aValue, bool aPersist, bool aReplace) + { + nsCString oldValue; + return AddCategoryEntry(nsLiteralCString(aCategory), aEntry, aValue, + aPersist, aReplace, oldValue); + } + + template<size_t N> + nsresult + DeleteCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, bool aPersist) + { + return DeleteCategoryEntry(nsLiteralCString(aCategory), aEntry, aPersist); + } + + + template<size_t N> + nsresult + EnumerateCategory(const char (&aCategory)[N], nsISimpleEnumerator** aResult) + { + return EnumerateCategory(nsLiteralCString(aCategory), aResult); + } + %} +}; diff --git a/xpcom/components/nsIClassInfo.idl b/xpcom/components/nsIClassInfo.idl new file mode 100644 index 0000000000..1ebef34ecf --- /dev/null +++ b/xpcom/components/nsIClassInfo.idl @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIXPCScriptable; + +/** + * Provides information about a specific implementation class. If you want + * your class to implement nsIClassInfo, see nsIClassInfoImpl.h for + * instructions--you most likely do not want to inherit from nsIClassInfo. + */ + +[scriptable, uuid(a60569d7-d401-4677-ba63-2aa5971af25d)] +interface nsIClassInfo : nsISupports +{ + /** + * Returns a list of the interfaces which instances of this class promise + * to implement. Note that nsISupports is an implicit member of any such + * list, and need not be included. + */ + readonly attribute Array<nsIIDRef> interfaces; + + /** + * Return an object to assist XPConnect in supplying JavaScript-specific + * behavior to callers of the instance object, or null if not needed. + */ + nsIXPCScriptable getScriptableHelper(); + + /** + * A contract ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|), or null/void. + */ + readonly attribute AUTF8String contractID; + + /** + * A human readable string naming the class, or null/void. + */ + readonly attribute AUTF8String classDescription; + + /** + * A class ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|), or null. + */ + readonly attribute nsCIDPtr classID; + + /** + * Bitflags for 'flags' attribute. + */ + const uint32_t SINGLETON = 1 << 0; + const uint32_t THREADSAFE = 1 << 1; + const uint32_t SINGLETON_CLASSINFO = 1 << 5; + + // The high order bit is RESERVED for consumers of these flags. + // No implementor of this interface should ever return flags + // with this bit set. + const uint32_t RESERVED = 1 << 31; + + + readonly attribute uint32_t flags; + + /** + * Also a class ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|). If the class does + * not have a CID, it should return NS_ERROR_NOT_AVAILABLE. This attribute + * exists so C++ callers can avoid allocating and freeing a CID, as would + * happen if they used classID. + */ + [noscript] readonly attribute nsCID classIDNoAlloc; + +}; diff --git a/xpcom/components/nsIComponentManager.idl b/xpcom/components/nsIComponentManager.idl new file mode 100644 index 0000000000..9abde9960c --- /dev/null +++ b/xpcom/components/nsIComponentManager.idl @@ -0,0 +1,117 @@ +/* -*- 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 nsIComponentManager interface. + */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIFactory; +interface nsIArray; +interface nsIUTF8StringEnumerator; + +[scriptable, builtinclass, uuid(d604ffc3-1ba3-4f6c-b65f-1ed4199364c3)] +interface nsIComponentManager : nsISupports +{ + /** + * getClassObject + * + * Returns the factory object that can be used to create instances of + * CID aClass + * + * @param aClass The classid of the factory that is being requested + */ + void getClassObject(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * getClassObjectByContractID + * + * Returns the factory object that can be used to create instances of + * CID aClass + * + * @param aClass The classid of the factory that is being requested + */ + void getClassObjectByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + + /** + * createInstance + * + * Create an instance of the CID aClass and return the interface aIID. + * + * @param aClass : ClassID of object instance requested + * @param aIID : IID of interface requested + */ + [noscript] + void createInstance(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * createInstanceByContractID + * + * Create an instance of the CID that implements aContractID and return the + * interface aIID. + * + * @param aContractID : aContractID of object instance requested + * @param aIID : IID of interface requested + */ + [noscript] + void createInstanceByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * addBootstrappedManifestLocation + * + * Adds a bootstrapped manifest location on runtime. + * + * @param aLocation : A directory where chrome.manifest resides, + * or an XPI with it on the root. + */ + void addBootstrappedManifestLocation(in nsIFile aLocation); + + /** + * removeBootstrappedManifestLocation + * + * Removes a bootstrapped manifest location on runtime. + * + * @param aLocation : A directory where chrome.manifest resides, + * or an XPI with it on the root. + */ + void removeBootstrappedManifestLocation(in nsIFile aLocation); + + /** + * getManifestLocations + * + * Get an array of nsIURIs of all registered and builtin manifest locations. + */ + nsIArray getManifestLocations(); + + /** + * Returns a list of JSM URLs which are used to create components. This + * should only be used in automation. + */ + nsIUTF8StringEnumerator getComponentJSMs(); + + /** + * Returns a list of ESM URLs which are used to create components. This + * should only be used in automation. + */ + nsIUTF8StringEnumerator getComponentESModules(); +}; + + +%{ C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsComponentManagerUtils.h" +#endif +%} C++ diff --git a/xpcom/components/nsIComponentRegistrar.idl b/xpcom/components/nsIComponentRegistrar.idl new file mode 100644 index 0000000000..6b85caffab --- /dev/null +++ b/xpcom/components/nsIComponentRegistrar.idl @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIComponentRegistrar interface. + */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIFactory; + +[builtinclass, scriptable, uuid(2417cbfe-65ad-48a6-b4b6-eb84db174392)] +interface nsIComponentRegistrar : nsISupports +{ + /** + * autoRegister + * + * Register a .manifest file, or an entire directory containing + * these files. Registration lasts for this run only, and is not cached. + * + * @note Formerly this method would register component files directly. This + * is no longer supported. + */ + void autoRegister(in nsIFile aSpec); + + /** + * registerFactory + * + * Register a factory with a given ContractID, CID and Class Name. + * + * @param aClass : CID of object + * @param aClassName : Class Name of CID (unused) + * @param aContractID : ContractID associated with CID aClass. May be null + * if no contract ID is needed. + * @param aFactory : Factory that will be registered for CID aClass. + * If aFactory is null, the contract will be associated + * with a previously registered CID. + */ + void registerFactory(in nsCIDRef aClass, + in string aClassName, + in string aContractID, + in nsIFactory aFactory); + + /** + * unregisterFactory + * + * Unregister a factory associated with CID aClass. + * + * @param aClass : CID being unregistered + * @param aFactory : Factory previously registered to create instances of + * CID aClass. + * + * @throws NS_ERROR* Method failure. + */ + void unregisterFactory(in nsCIDRef aClass, + in nsIFactory aFactory); + + /** + * isCIDRegistered + * + * Returns true if a factory is registered for the CID. + * + * @param aClass : CID queried for registeration + * @return : true if a factory is registered for CID + * false otherwise. + */ + boolean isCIDRegistered(in nsCIDRef aClass); + + /** + * isContractIDRegistered + * + * Returns true if a factory is registered for the contract id. + * + * @param aClass : contract id queried for registeration + * @return : true if a factory is registered for contract id + * false otherwise. + */ + boolean isContractIDRegistered(in string aContractID); + + /** + * getContractIDs + * + * Return the list of all registered ContractIDs. + * + * @return : Array of ContractIDs. Elements of the array are the string + * encoding of Contract IDs. + */ + Array<ACString> getContractIDs(); + + /** + * contractIDToCID + * + * Returns the CID for a given Contract ID, if one exists and is registered. + * + * @return : Contract ID. + */ + nsCIDPtr contractIDToCID(in string aContractID); +}; diff --git a/xpcom/components/nsIFactory.idl b/xpcom/components/nsIFactory.idl new file mode 100644 index 0000000000..e0b3e6e756 --- /dev/null +++ b/xpcom/components/nsIFactory.idl @@ -0,0 +1,27 @@ +/* -*- 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/. */ + +#include "nsISupports.idl" + +/** + * A class factory allows the creation of nsISupports derived + * components without specifying a concrete base class. + */ + +[scriptable, object, uuid(1bb40a56-9223-41e6-97d4-da97bdeb6a4d)] +interface nsIFactory : nsISupports { + /** + * Creates an instance of a component. + * + * @param iid The IID of the interface being requested in + * the component which is being currently created. + * @param result [out] Pointer to the newly created instance, if successful. + * @throws NS_NOINTERFACE - Interface not accessible. + * NS_ERROR* - Method failure. + */ + void createInstance(in nsIIDRef iid, + [retval, iid_is(iid)] out nsQIResult result); + +}; diff --git a/xpcom/components/nsIServiceManager.idl b/xpcom/components/nsIServiceManager.idl new file mode 100644 index 0000000000..dd613070e9 --- /dev/null +++ b/xpcom/components/nsIServiceManager.idl @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIServiceManager manager interface provides a means to obtain + * global services in an application. The service manager depends on the + * repository to find and instantiate factories to obtain services. + * + * Users of the service manager must first obtain a pointer to the global + * service manager by calling NS_GetServiceManager. After that, + * they can request specific services by calling GetService. When they are + * finished they can NS_RELEASE() the service as usual. + * + * A user of a service may keep references to particular services indefinitely + * and only must call Release when it shuts down. + */ + +[builtinclass, scriptable, uuid(8bb35ed9-e332-462d-9155-4a002ab5c958)] +interface nsIServiceManager : nsISupports +{ + /** + * getServiceByContractID + * + * Returns the instance that implements aClass or aContractID and the + * interface aIID. This may result in the instance being created. + * + * @param aClass or aContractID : aClass or aContractID of object + * instance requested + * @param aIID : IID of interface requested + * @param result : resulting service + */ + void getService(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + void getServiceByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * isServiceInstantiated + * + * isServiceInstantiated will return a true if the service has already + * been created, or false otherwise. Throws if the service does not + * implement the given IID. + * + * @param aClass or aContractID : aClass or aContractID of object + * instance requested + * @param aIID : IID of interface requested + * @throws NS_NOINTERFACE if the IID given isn't supported by the object + */ + boolean isServiceInstantiated(in nsCIDRef aClass, in nsIIDRef aIID); + boolean isServiceInstantiatedByContractID(in string aContractID, in nsIIDRef aIID); +}; + + +%{C++ +// Observing xpcom autoregistration. Topics will be 'start' and 'stop'. +#define NS_XPCOM_AUTOREGISTRATION_OBSERVER_ID "xpcom-autoregistration" + +#ifdef MOZILLA_INTERNAL_API +#include "nsXPCOM.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#endif +%} diff --git a/xpcom/components/nsServiceManagerUtils.h b/xpcom/components/nsServiceManagerUtils.h new file mode 100644 index 0000000000..4bfbc804cc --- /dev/null +++ b/xpcom/components/nsServiceManagerUtils.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 nsServiceManagerUtils_h__ +#define nsServiceManagerUtils_h__ + +#include "nsCOMPtr.h" +#include "nsString.h" + +inline nsGetServiceByCID do_GetService(const nsCID& aCID) { + return nsGetServiceByCID(aCID); +} + +inline nsGetServiceByCIDWithError do_GetService(const nsCID& aCID, + nsresult* aError) { + return nsGetServiceByCIDWithError(aCID, aError); +} + +inline nsGetServiceByContractID do_GetService(const char* aContractID) { + return nsGetServiceByContractID(aContractID); +} + +inline nsGetServiceByContractIDWithError do_GetService(const char* aContractID, + nsresult* aError) { + return nsGetServiceByContractIDWithError(aContractID, aError); +} + +nsresult CallGetService(const nsCID& aClass, const nsIID& aIID, void** aResult); + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult); + +// type-safe shortcuts for calling |GetService| +template <class DestinationType> +inline nsresult CallGetService(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetService(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallGetService(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aContractID, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetService(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +#endif diff --git a/xpcom/components/test/python.toml b/xpcom/components/test/python.toml new file mode 100644 index 0000000000..6ce1319672 --- /dev/null +++ b/xpcom/components/test/python.toml @@ -0,0 +1,4 @@ +[DEFAULT] +subsuite = "xpcom" + +["test_gen_static_components.py"] diff --git a/xpcom/components/test/test_gen_static_components.py b/xpcom/components/test/test_gen_static_components.py new file mode 100644 index 0000000000..85f731c082 --- /dev/null +++ b/xpcom/components/test/test_gen_static_components.py @@ -0,0 +1,150 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import sys +import unittest + +import mozunit + +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import gen_static_components +from gen_static_components import BackgroundTasksSelector + + +class TestGenStaticComponents(unittest.TestCase): + def test_string(self): + # A string: we default to NO_TASKS. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": ["m-dummy1", "m-dummy2"], + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + def test_dict(self): + # A dict, but no backgroundtasks selector: we default to NO_TASKS. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": { + "name": ["m-dummy1", "m-dummy2"], + }, + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + def test_dict_with_selector(self): + # A dict with a selector. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": { + "name": ["m-dummy1", "m-dummy2"], + "backgroundtasks": BackgroundTasksSelector.ALL_TASKS, + }, + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::ALL_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::ALL_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + +if __name__ == "__main__": + mozunit.main() diff --git a/xpcom/docs/cc-macros.rst b/xpcom/docs/cc-macros.rst new file mode 100644 index 0000000000..7877b3aabe --- /dev/null +++ b/xpcom/docs/cc-macros.rst @@ -0,0 +1,190 @@ +======================================= +How to make a C++ class cycle collected +======================================= + +Should my class be cycle collected? +=================================== + +First, you need to decide if your class should be cycle +collected. There are three main criteria: + +* It can be part of a cycle of strong references, including + refcounted objects and JS. Usually, this happens when it can hold + alive and be held alive by cycle collected objects or JS. + +* It must be refcounted. + +* It must be single threaded. The cycle collector can only work with + objects that are used on a single thread. The main thread and DOM + worker and worklet threads each have their own cycle collectors. + +If your class meets the first criteria but not the second, then +whatever class uniquely owns it should be cycle collected, assuming +that is refcounted, and this class should be traversed and unlinked as +part of that. + +The cycle collector supports both nsISupports and non-nsISupports +(known as "native" in CC nomenclature) refcounting. However, we do not +support native cycle collection in the presence of inheritance, if two +classes related by inheritance need different CC +implementations. (This is because we use QueryInterface to find the +right CC implementation for an object.) + +Once you've decided to make a class cycle collected, there are a few +things you need to add to your implementation: + +* Cycle collected refcounting. Special refcounting is needed so that + the CC can tell when an object is created, used, or destroyed, so + that it can determine if an object is potentially part of a garbage + cycle. + +* Traversal. Once the CC has decided an object **might** be garbage, + it needs to know what other cycle collected objects it holds strong + references to. This is done with a "traverse" method. + +* Unlinking. Once the CC has decided that an object **is** garbage, it + needs to break the cycles by clearing out all strong references to + other cycle collected objects. This is done with an "unlink" + method. This usually looks very similar to the traverse method. + +The traverse and unlink methods, along with other methods needed for +cycle collection, are defined on a special inner class object, called a +"participant", for performance reasons. The existence of the +participant is mostly hidden behind macros so you shouldn't need +to worry about it. + +Next, we'll go over what the declaration and definition of these parts +look like. (Spoiler: there are lots of `ALL_CAPS` macros.) This will +mostly cover the most common variants. If you need something slightly +different, you should look at the location of the declaration of the +macros we mention here and see if the variants already exist. + + +Reference counting +================== + +nsISupports +----------- + +If your class inherits from nsISupports, you'll need to add +`NS_DECL_CYCLE_COLLECTING_ISUPPORTS` to the class declaration. This +will declare the QueryInterface (QI), AddRef and Release methods you +need to implement nsISupports, as well as the actual refcount field. + +In the `.cpp` file for your class you'll have to define the +QueryInterface, AddRef and Release methods. The ins and outs of +defining the QI method is out-of-scope for this document, but you'll +need to use the special cycle collection variants of the macros, like +`NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION`. (This is because we use +the nsISupports system to define a special interface used to +dynamically find the correct CC participant for the object.) + +Finally, you'll have to actually define the AddRef and Release methods +for your class. If your class is called `MyClass`, then you'd do this +with the declarations `NS_IMPL_CYCLE_COLLECTING_ADDREF(MyClass)` and +`NS_IMPL_CYCLE_COLLECTING_RELEASE(MyClass)`. + +non-nsISupports +--------------- + +If your class does **not** inherit from nsISupports, you'll need to +add `NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING` to the class +declaration. This will give inline definitions for the AddRef and +Release methods, as well as the actual refcount field. + +Cycle collector participant +=========================== + +Next we need to declare and define the cycle collector +participant. This is mostly boilerplate hidden behind macros, but you +will need to specify which fields are to be traversed and unlinked +because they are strong references to cycle collected objects. + +Declaration +----------- + +First, we need to add a declaration for the participant. As before, +let's say your class is `MyClass`. + +The basic way to declare this for an nsISupports class is +`NS_DECL_CYCLE_COLLECTION_CLASS(MyClass)`. + +If your class inherits from multiple classes that inherit from +nsISupports classes, say `Parent1` and `Parent2`, then you'll need to +use `NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MyClass, Parent1)` to +tell the CC to cast to nsISupports via `Parent1`. You probably want to +pick the first class it inherits from. (The cycle collector needs to be +able to cast `MyClass*` to `nsISupports*`.) + +Another situation you might encounter is that your nsISupports class +inherits from another cycle collected class `CycleCollectedParent`. In +that case, your participant declaration will look like +`NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MyClass, +CycleCollectedParent)`. (This is needed so that the CC can cast from +nsISupports down to `MyClass`.) Note that we do not support inheritance +for non-nsISupports classes. + +If your class is non-nsISupports, then you'll need to use the `NATIVE` +family of macros, like +`NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(MyClass)`. + +In addition to these modifiers, these different variations have +further `SCRIPT_HOLDER` variations which are needed if your class +holds alive JavaScript objects. This is because the tracing of JS +objects held alive by this class must be declared separately from the +tracing of C++ objects held alive by this class so that the garbage +collector can also use the tracing. For example, +`NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(MyClass, +Parent1)`. + +There are also `WRAPPERCACHE` variants of the macros which you need to +use if your class is wrapper cached. These are effectively a +specialized form of `SCRIPT_HOLDER`, as a cached wrapper is a +JS object held alive by the C++ object. For example, +`NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(MyClass, +Parent1)`. + +There is yet another variant of these macros, `SKIPPABLE`. This +document won't go into detail here about how this works, but the basic +idea is that a class can tell the CC when it is definitely alive, +which lets the CC skip it. This is a very important optimization for +things like DOM elements in active documents, but a new class you are +making cycle collected is likely not common enough to worry about. + +Implementation +-------------- + +Finally, you must write the actual implementation of the CC +participant, in the .cpp file for your class. This will define the +traverse and unlink methods, and some other random helper +functions. In the simplest case, this can be done with a single macro +like this: `NS_IMPL_CYCLE_COLLECTION(MyClass, mField1, mField2, +mField3)`, where `mField1` and the rest are the names of the fields of +your class that are strong references to cycle collected +objects. There is some template magic that says how many common types +like RefPtr, nsCOMPtr, and even some arrays, should be traversed and +unlinked. There’s also a variant `NS_IMPL_CYCLE_COLLECTION_INHERITED`, +which you should use when there’s a parent class that is also cycle +collected, to ensure that fields of the parent class are traversed and +unlinked. The name of that parent class is passed in as the second +argument. If either of these work, then you are done. Your class is +now cycle collected. Note that this does not work for fields that are +JS objects. + +However, if that doesn’t work, you’ll have to get into the details a +bit more. A good place to start is by copying the definition of +`NS_IMPL_CYCLE_COLLECTION`. + +For a script holder method, you also need to define a trace method in +addition to the traverse and unlink, using +`NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN` and other similar +macros. You'll need to include all of the JS fields that your class +holds alive. The trace method will be used by the GC as well as the +CC, so if you miss something you can end up with use-after-free +crashes. You'll also need to call `mozilla::HoldJSObjects(this);` in +the ctor for your class, and `mozilla::DropJSObjects(this);` in the +dtor. This will register (and unregister) each instance of your object +with the JS runtime, to ensure that it gets traced properly. This +does not apply if you have a wrapper cached class that does not have +any additional JS fields, as nsWrapperCache deals with all of that +for you. diff --git a/xpcom/docs/collections.rst b/xpcom/docs/collections.rst new file mode 100644 index 0000000000..8c9356e499 --- /dev/null +++ b/xpcom/docs/collections.rst @@ -0,0 +1,114 @@ +XPCOM Collections +================= + +``nsTArray`` and ``AutoTArray`` +------------------------------- + +``nsTArray<T>`` is a typesafe array for holding various objects, similar to ``std::vector<T>``. (note that +``nsTArray<T>`` is dynamically-sized, unlike ``std::array<T>``) Here's an +incomplete list of mappings between the two: + +================== ================================================== +std::vector<T> nsTArray<T> +================== ================================================== +``size()`` ``Length()`` +``empty()`` ``IsEmpty()`` +``resize()`` ``SetLength()`` or ``SetLengthAndRetainStorage()`` +``capacity()`` ``Capacity()`` +``reserve()`` ``SetCapacity()`` +``push_back()`` ``AppendElement()`` +``insert()`` ``AppendElements()`` +``emplace_back()`` ``EmplaceBack()`` +``clear()`` ``Clear()`` or ``ClearAndRetainStorage()`` +``data()`` ``Elements()`` +``at()`` ``ElementAt()`` +``back()`` ``LastElement()`` +================== ================================================== + +Rust Bindings +~~~~~~~~~~~~~ + +When the ``thin_vec`` crate is built in Gecko, ``thin_vec::ThinVec<T>`` is +guaranteed to have the same memory layout and allocation strategy as +``nsTArray``, meaning that the two types may be used interchangeably across +FFI boundaries. The type is **not** safe to pass by-value over FFI +boundaries, due to Rust and C++ differing in when they run destructors. + +The element type ``T`` must be memory-compatible with both Rust and C++ code +to use over FFI. + +``nsTHashMap`` and ``nsTHashSet`` +--------------------------------- + +These types are the recommended interface for writing new XPCOM hashmaps and +hashsets in XPCOM code. + +Supported Hash Keys +~~~~~~~~~~~~~~~~~~~ + +The following types are supported as the key parameter to ``nsTHashMap`` and +``nsTHashSet``. + +========================== ====================== +Type Hash Key +========================== ====================== +``T*`` ``nsPtrHashKey<T>`` +``T*`` ``nsPtrHashKey<T>`` +``nsCString`` ``nsCStringHashKey`` +``nsString`` ``nsStringHashKey`` +``uint32_t`` ``nsUint32HashKey`` +``uint64_t`` ``nsUint64HashKey`` +``intptr_t`` ``IntPtrHashKey`` +``nsCOMPtr<nsISupports>`` ``nsISupportsHashKey`` +``RefPtr<T>`` ``nsRefPtrHashKey<T>`` +``nsID`` ``nsIDHashKey`` +========================== ====================== + +Any key not in this list must inherit from the ``PLDHashEntryHdr`` class to +implement manual hashing behaviour. + +Class Reference +~~~~~~~~~~~~~~~ + +.. note:: + + The ``nsTHashMap`` and ``nsTHashSet`` types are not declared exactly like + this in code. This is intended largely as a practical reference. + +.. cpp:class:: template<K, V> nsTHashMap<K, V> + + The ``nsTHashMap<K, V>`` class is currently defined as a thin type alias + around ``nsBaseHashtable``. See the methods defined on that class for + more detailed documentation. + + https://searchfox.org/mozilla-central/source/xpcom/ds/nsBaseHashtable.h + + .. cpp:function:: uint32_t Count() const + + .. cpp:function:: bool IsEmpty() const + + .. cpp:function:: bool Get(KeyType aKey, V* aData) const + + Get the value, returning a flag indicating the presence of the entry + in the table. + + .. cpp:function:: V Get(KeyType aKey) const + + Get the value, returning a default-initialized object if the entry is + not present in the table. + + .. cpp:function:: Maybe<V> MaybeGet(KeyType aKey) const + + Get the value, returning Nothing if the entry is not present in the table. + + .. cpp:function:: V& LookupOrInsert(KeyType aKey, Args&&... aArgs) const + + .. cpp:function:: V& LookupOrInsertWith(KeyType aKey, F&& aFunc) const + +.. cpp:class:: template<K> nsTHashSet<K> + + The ``nsTHashSet<K>`` class is currently defined as a thin type alias + around ``nsTBaseHashSet``. See the methods defined on that class for + more detailed documentation. + + https://searchfox.org/mozilla-central/source/xpcom/ds/nsTHashSet.h diff --git a/xpcom/docs/hashtables.rst b/xpcom/docs/hashtables.rst new file mode 100644 index 0000000000..8debd11eb7 --- /dev/null +++ b/xpcom/docs/hashtables.rst @@ -0,0 +1,141 @@ +XPCOM Hashtable Guide +===================== + +.. note:: + + For a deep-dive into the underlying mechanisms that power our hashtables, + check out the :ref:`XPCOM Hashtable Technical Details` + document. + +What Is a Hashtable? +-------------------- + +A hashtable is a data construct that stores a set of **items**. Each +item has a **key** that identifies the item. Items are found, added, and +removed from the hashtable by using the key. Hashtables may seem like +arrays, but there are important differences: + ++-------------------------+----------------------+----------------------+ +| | Array | Hashtable | ++=========================+======================+======================+ +| **Keys** | *integer:* arrays | *any type:* almost | +| | are always keyed on | any datatype can be | +| | integers and must | used as key, | +| | be contiguous. | including strings, | +| | | integers, XPCOM | +| | | interface pointers, | +| | | IIDs, and almost | +| | | anything else. Keys | +| | | can be disjunct | +| | | (i.e. you can store | +| | | entries with keys 1, | +| | | 5, and 3000). | ++-------------------------+----------------------+----------------------+ +| **Lookup Time** | *O(1):* lookup time | *O(1):* lookup time | +| | is a simple constant | is mostly-constant, | +| | | but the constant | +| | | time can be larger | +| | | than an array lookup | ++-------------------------+----------------------+----------------------+ +| **Sorting** | *sorted:* stored | *unsorted:* stored | +| | sorted; iterated | unsorted; cannot be | +| | over in a sorted | iterated over in a | +| | fashion. | sorted manner. | ++-------------------------+----------------------+----------------------+ +| **Inserting/Removing** | *O(n):* adding and | *O(1):* adding and | +| | removing items from | removing items from | +| | a large array can be | hashtables is a | +| | time-consuming | quick operation | ++-------------------------+----------------------+----------------------+ +| **Wasted space** | *none:* Arrays are | *some:* hashtables | +| | packed structures, | are not packed | +| | so there is no | structures; | +| | wasted space. | depending on the | +| | | implementation, | +| | | there may be | +| | | significant wasted | +| | | memory. | ++-------------------------+----------------------+----------------------+ + +In their implementation, hashtables take the key and apply a +mathematical **hash function** to **randomize** the key and then use the +hash to find the location in the hashtable. Good hashtable +implementations will automatically resize the hashtable in memory if +extra space is needed, or if too much space has been allocated. + +.. _When_Should_I_Use_a_Hashtable.3F: + +When Should I Use a Hashtable? +------------------------------ + +Hashtables are useful for + +- sets of data that need swift **random access** +- with **non-integral keys** or **non-contiguous integral keys** +- or where **items will be frequently added or removed** + +Hashtables should *not* be used for + +- Sets that need to be **sorted** +- Very small datasets (less than 12-16 items) +- Data that does not need random access + +In these situations, an array, a linked-list, or various tree data +structures are more efficient. + +.. _Which_Hashtable_Should_I_Use.3F: + +Which Hashtable Should I Use? +----------------------------- + +If there is **no** key type, you should use an ``nsTHashSet``. + +If there is a key type, you should use an ``nsTHashMap``. + +``nsTHashMap`` is a template with two parameters. The first is the hash key +and the second is the data to be stored as the value in the map. Most of +the time, you can simply pass the raw key type as the first parameter, +so long as its supported by `nsTHashMap.h <https://searchfox.org/mozilla-central/source/xpcom/ds/nsTHashMap.h>`_. +It is also possible to specify custom keys if necessary. See `nsHashKeys.h +<https://searchfox.org/mozilla-central/source/xpcom/ds/nsHashKeys.h>`_ for examples. + +There are a number of more esoteric hashkey classes in nsHashKeys.h, and +you can always roll your own if none of these fit your needs (make sure +you're not duplicating an existing hashkey class though!) + +Once you've determined what hashtable and hashkey classes you need, you +can put it all together. A few examples: + +- A hashtable that maps UTF-8 origin names to a DOM Window - + ``nsTHashMap<nsCString, nsCOMPtr<nsIDOMWindow>>`` +- A hashtable that maps 32-bit integers to floats - + ``nsTHashMap<uint32_t, float>`` +- A hashtable that maps ``nsISupports`` pointers to reference counted + ``CacheEntry``\ s - + ``nsTHashMap<nsCOMPtr<nsISupports>, RefPtr<CacheEntry>>`` +- A hashtable that maps ``JSContext`` pointers to a ``ContextInfo`` + struct - ``nsTHashMap<JSContext*, UniquePtr<ContextInfo>>`` +- A hashset of strings - ``nsTHashSet<nsString>`` + +.. _nsBaseHashtable_and_friends:_nsDataHashtable.2C_nsInterfaceHashtable.2C_and_nsClassHashtable: + +Hashtable API +------------- + +The hashtable classes all expose the same basic API. There are three +key methods, ``Get``, ``InsertOrUpdate``, and ``Remove``, which retrieve entries from the +hashtable, write entries into the hashtable, and remove entries from the +hashtable respectively. See `nsBaseHashtable.h <https://searchfox.org/mozilla-central/source/xpcom/ds/nsBaseHashtable.h>`_ +for more details. + +The hashtables that hold references to pointers (nsRefPtrHashtable and +nsInterfaceHashtable) also have GetWeak methods that return non-AddRefed +pointers. + +Note that ``nsRefPtrHashtable``, ``nsInterfaceHashtable`` and ``nsClassHashtable`` +are legacy hashtable types which have some extra methods, and don't have automatic +key type handling. + +All of these hashtable classes can be iterated over via the ``Iterator`` +class, with normal C++11 iterators or using the ``Keys()`` / ``Values()`` ranges, +and all can be cleared via the ``Clear`` method. diff --git a/xpcom/docs/hashtables_detailed.rst b/xpcom/docs/hashtables_detailed.rst new file mode 100644 index 0000000000..200c47490d --- /dev/null +++ b/xpcom/docs/hashtables_detailed.rst @@ -0,0 +1,121 @@ +XPCOM Hashtable Technical Details +================================= + +.. note:: + + This is a deep-dive into the underlying mechanisms that power the XPCOM + hashtables. Some of this information is quite old and may be out of date. If + you're looking for how to use XPCOM hashtables, you should consider reading + the :ref:`XPCOM Hashtable Guide` instead. + +Mozilla's Hashtable Implementations +----------------------------------- + +Mozilla has several hashtable implementations, which have been tested +and tuned, and hide the inner complexities of hashtable implementations: + +- ``PLHashTable`` - low-level C API; entry class pointers are constant; + more efficient for large entry structures; often wastes memory making + many small heap allocations. +- ``nsTHashtable`` - low-level C++ wrapper around ``PLDHash``; + generates callback functions and handles most casting automagically. + Client writes their own entry class which can include complex key and + data types. +- ``nsTHashMap/nsInterfaceHashtable/nsClassHashtable`` - + simplifies the common usage pattern mapping a simple keytype to a + simple datatype; client does not need to declare or manage an entry class; + ``nsTHashMap`` datatype is a scalar such as ``uint64_t``; + ``nsInterfaceHashtable`` datatype is an XPCOM interface; + ``nsClassHashtable`` datatype is a class pointer owned by the + hashtable. + +.. _PLHashTable: + +PLHashTable +~~~~~~~~~~~ + +``PLHashTable`` is a part of NSPR. The header file can be found at `plhash.h +<https://searchfox.org/mozilla-central/source/nsprpub/lib/ds/plhash.h>`_. + +There are two situations where ``PLHashTable`` may be preferable: + +- You need entry-pointers to remain constant. +- The entries stored in the table are very large (larger than 12 + words). + +.. _nsTHashtable: + +nsTHashtable +~~~~~~~~~~~~ + +To use ``nsTHashtable``, you must declare an entry-class. This +entry class contains the key and the data that you are hashing. It also +declares functions that manipulate the key. In most cases, the functions +of this entry class can be entirely inline. For examples of entry classes, +see the declarations at `nsHashKeys.h +<https://searchfox.org/mozilla-central/source/xpcom/ds/nsHashKeys.h>`_. + +The template parameter is the entry class. After construction, use the +functions ``PutEntry/GetEntry/RemoveEntry`` to alter the hashtable. The +``Iterator`` class will do iteration, but beware that the iteration will +occur in a seemingly-random order (no sorting). + +- ``nsTHashtable``\ s can be allocated on the stack, as class members, + or on the heap. +- Entry pointers can and do change when items are added to or removed + from the hashtable. Do not keep long-lasting pointers to entries. +- because of this, ``nsTHashtable`` is not inherently thread-safe. If + you use a hashtable in a multi-thread environment, you must provide + locking as appropriate. + +Before using ``nsTHashtable``, see if ``nsBaseHashtable`` and relatives +will work for you. They are much easier to use, because you do not have +to declare an entry class. If you are hashing a simple key type to a +simple data type, they are generally a better choice. + +.. _nsBaseHashtable_and_friends:nsTHashMap.2C_nsInterfaceHashtable.2C_and_nsClassHashtable: + +nsBaseHashtable and friends: nsTHashMap, nsInterfaceHashtable, and nsClassHashtable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These C++ templates provide a high-level interface for using hashtables +that hides most of the complexities of the underlying implementation. They +provide the following features: + +- hashtable operations can be completed without using an entry class, + making code easier to read +- optional thread-safety: the hashtable can manage a read-write lock + around the table +- predefined key classes provide automatic cleanup of + strings/interfaces +- ``nsInterfaceHashtable`` and ``nsClassHashtable`` automatically + release/delete objects to avoid leaks. + +``nsBaseHashtable`` is not used directly; choose one of the three +derivative classes based on the data type you want to store. The +``KeyClass`` is taken from `nsHashKeys.h +<https://searchfox.org/mozilla-central/source/xpcom/ds/nsHashKeys.h>`_ and is the same for all +three classes: + +- ``nsTHashMap<KeyClass, DataType>`` - ``DataType`` is a simple + type such as ``uint32_t`` or ``bool``. +- ``nsInterfaceHashtable<KeyClass, Interface>`` - ``Interface`` is an + XPCOM interface such as ``nsISupports`` or ``nsIDocShell`` +- ``nsClassHashtable<KeyClass, T>`` - ``T`` is any C++ class. The + hashtable stores a pointer to the object, and deletes that object + when the entry is removed. + +The important files to read are +`nsBaseHashtable.h <https://searchfox.org/mozilla-central/source/xpcom/ds/nsBaseHashtable.h>`_ +and +`nsHashKeys.h <https://searchfox.org/mozilla-central/source/xpcom/ds/nsHashKeys.h>`_. +These classes can be used on the stack, as a class member, or on the heap. + +.. _Using_nsTHashtable_as_a_hash-set: + +Using nsTHashtable as a hash-set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A hash set only tracks the existence of keys: it does not associate data +with the keys. This can be done using ``nsTHashtable<nsSomeHashKey>``. +The appropriate entries are GetEntry and PutEntry. diff --git a/xpcom/docs/huntingleaks.rst b/xpcom/docs/huntingleaks.rst new file mode 100644 index 0000000000..9e0585da4e --- /dev/null +++ b/xpcom/docs/huntingleaks.rst @@ -0,0 +1,22 @@ +Hunting Leaks +============= + +.. contents:: Table of Contents + :local: + :depth: 2 + +Different tools and techniques are used to hunt leaks: + +.. list-table:: + :header-rows: 1 + + * - Tools + - Description + * - :ref:`Bloatview` + - BloatView is a tool that shows information about cumulative memory usage and leaks. + * - :ref:`Refcount Tracing and Balancing` + - Refcount tracing and balancing are advanced techniques for tracking down leak of refcounted objects found with BloatView. + * - `GC and CC logs </performance/memory/gc_and_cc_logs.html>`_ + - Garbage collector (GC) and cycle collector (CC) logs give information about why various JS and C++ objects are alive in the heap. + * - :ref:`DMD Heap Scan Mode` + - Heap profiler within Firefox diff --git a/xpcom/docs/index.rst b/xpcom/docs/index.rst new file mode 100644 index 0000000000..bf4e3157c4 --- /dev/null +++ b/xpcom/docs/index.rst @@ -0,0 +1,19 @@ +XPCOM +===== + +These pages contain documentation for Mozilla's Cross-Platform Component Object Model (XPCOM) module. It abstracts core systems functionality for cross-platform use. The component architecture follows the standard COM approach. + +.. toctree:: + :maxdepth: 1 + + logging + stringguide + refptr + thread-safety + huntingleaks + collections + xpidl + writing-xpcom-interface + hashtables + hashtables_detailed + cc-macros diff --git a/xpcom/docs/logging.rst b/xpcom/docs/logging.rst new file mode 100644 index 0000000000..0105f018ad --- /dev/null +++ b/xpcom/docs/logging.rst @@ -0,0 +1,488 @@ +Gecko Logging +============= + +A minimal C++ logging framework is provided for use in core Gecko code. It is +enabled for all builds and is thread-safe. + +This page covers enabling logging for particular logging module, configuring +the logging output, and how to use the logging facilities in native code. + +Enabling and configuring logging +++++++++++++++++++++++++++++++++ + +Caveat: sandboxing when logging to a file +----------------------------------------- + +A sandboxed content process cannot write to ``stderr`` or any file. The easiest +way to log these processes is to disable the content sandbox by setting the +preference ``security.sandbox.content.level`` to ``0``, or setting the environment +variable ``MOZ_DISABLE_CONTENT_SANDBOX`` to ``1``. + +On Windows, you can still see child process messages by using DOS (not the +``MOZ_LOG_FILE`` variable defined below) to redirect output to a file. For +example: ``MOZ_LOG="CameraChild:5" mach run >& my_log_file.txt`` will include +debug messages from the camera's child actor that lives in a (sandboxed) content +process. + +Logging to the Firefox Profiler +------------------------------- + +When a log statement is logged on a thread and the `Firefox Profiler +<https://profiler.firefox.com>`_ is profiling that thread, the log statements is +recorded as a profiler marker. + +This allows getting logs alongside profiler markers and lots of performance +and contextual information, in a way that doesn't require disabling the +sandbox, and works across all processes. + +The profile can be downloaded and shared e.g. via Bugzilla or email, or +uploaded, and the logging statements will be visible either in the marker chart +or the marker table. + +While it is possible to manually configure logging module and start the profiler +with the right set of threads to profile, ``about:logging`` makes this task a lot +simpler and error-proof. + + +The ``MOZ_LOG`` syntax +---------------------- + +Logging is configured using a special but simple syntax: which module should be +enabled, at which level, and what logging options should be enabled or disabled. + +The syntax is a list of terms, separated by commas. There are two types of +terms: + +- A log module and its level, separated by a colon (``:``), such as + ``example_module:5`` to enable the module ``example_module`` at logging level + ``5`` (verbose). This `searchfox query + <https://searchfox.org/mozilla-central/search?q=LazyLogModule+.*%5C%28%22&path=&case=true®exp=true>`_ + returns the complete list of modules available. +- A special string in the following table, to configure the logging behaviour. + Some configuration switch take an integer parameter, in which case it's + separated from the string by a colon (``:``). Most switches only apply in a + specific output context, noted in the **Context** column. + ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| Special module name | Context | Action | ++======================+=========+===========================================================================================+ +| append | File | Append new logs to existing log file. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| sync | File | Print each log synchronously, this is useful to check behavior in real time or get logs | +| | | immediately before crash. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| raw | File | Print exactly what has been specified in the format string, without the | +| | | process/thread/timestamp, etc. prefix. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| timestamp | File | Insert timestamp at start of each log line. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| rotate:**N** | File | | This limits the produced log files' size. Only most recent **N megabytes** of log data | +| | | | is saved. We rotate four log files with .0, .1, .2, .3 extensions. Note: this option | +| | | | disables 'append' and forces 'timestamp'. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| maxsize:**N** | File | Limit the log to **N** MB. Only work in append mode. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| prependheader | File | Prepend a simple header while distinguishing logging. Useful in append mode. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| profilerstacks | Profiler| | When profiling with the Firefox Profiler and log modules are enabled, capture the call | +| | | | stack for each log statement. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ + +This syntax is used for most methods of enabling logging, with the exception of +settings preferences directly, see :ref:`this section <Enabling logging using preferences>` for directions. + + +Enabling Logging +---------------- + +Enabling logging can be done in a variety of ways: + +- via environment variables +- via command line switches +- using ``about:config`` preferences +- using ``about:logging`` + +The first two allow logging from the start of the application and are also +useful in case of a crash (when ``sync`` output is requested, this can also be +done with ``about:config`` as well to a certain extent). The last two +allow enabling and disabling logging at runtime and don't require using the +command-line. + +By default all logging output is disabled. + +Enabling logging using ``about:logging`` +'''''''''''''''''''''''''''''''''''''''' + +``about:logging`` allows enabling logging by entering a ``MOZ_LOG`` string in the +text input, and validating. + +Options allow logging to a file or using the Firefox Profiler, that can be +started and stopped right from the page. + +Logging presets for common scenarios are available in a drop-down. They can be +associated with a profiler preset. + +It is possible, via URL parameters, to select a particular logging +configuration, or to override certain parameters in a preset. This is useful to +ask a user to gather logs efficiently without having to fiddle with prefs and/or +environment variable. + +URL parameters are described in the following table: + ++---------------------+---------------------------------------------------------------------------------------------+ +| Parameter | Description | ++=====================+=============================================================================================+ +| ``preset`` | a `logging preset <https://searchfox.org/mozilla-central/search?q=gLoggingPresets>`_ | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``logging-preset`` | alias for ``preset`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``modules`` | a string in ``MOZ_LOG`` syntax | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``module`` | alias for ``modules`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``threads`` | a list of threads to profile, overrides what a profiler preset would have picked | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``thread`` | alias for ``threads`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``output`` | either ``profiler`` or ``file`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``output-type`` | alias for ``output`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``profiler-preset`` | a `profiler preset <https://searchfox.org/mozilla-central/search?q=%40type+{Presets}>`_ | ++---------------------+---------------------------------------------------------------------------------------------+ + +If a preset is selected, then ``threads`` or ``modules`` can be used to override the +profiled threads or logging modules enabled, but keeping other aspects of the +preset. If no preset is selected, then a generic profiling preset is used, +``firefox-platform``. For example: + +:: + + about:logging?output=profiler&preset=media-playback&modules=cubeb:4,AudioSinkWrapper:4:AudioSink:4 + +will profile the threads in the ``Media`` profiler preset, but will only log +specific log modules (instead of the `long list +<https://searchfox.org/mozilla-central/search?q="media-playback"&path=toolkit%2Fcontent%2FaboutLogging.js>`_ +in the ``media-playback`` preset). In addition, it disallows logging to a file. + +Enabling logging using environment variables +'''''''''''''''''''''''''''''''''''''''''''' + +On UNIX, setting and environment variable can be done in a variety of ways + +:: + + set MOZ_LOG="example_logger:3" + export MOZ_LOG="example_logger:3" + MOZ_LOG="example_logger:3" ./mach run + +In the Windows Command Prompt (``cmd.exe``), don't use quotes: + +:: + + set MOZ_LOG=example_logger:3 + +If you want this on GeckoView example, use the following adb command to launch process: + +:: + + adb shell am start -n org.mozilla.geckoview_example/.GeckoViewActivity --es env0 "MOZ_LOG=example_logger:3" + +There are special module names to change logging behavior. You can specify one or more special module names without logging level. + +For example, if you want to specify ``sync``, ``timestamp`` and ``rotate``: + +:: + + set MOZ_LOG="example_logger:3,timestamp,sync,rotate:10" + +Enabling logging usually outputs the logging statements to the terminal. To +have the logs written to a file instead (one file per process), the environment +variable ``MOZ_LOG_FILE`` can be used. Logs will be written at this path +(either relative or absolute), suffixed by a process type and its PID. +``MOZ_LOG`` files are text files and have the extension ``.moz_log``. + +For example, setting: + +:: + + set MOZ_LOG_FILE="firefox-logs" + +can create a number of files like so: + +:: + + firefox-log-main.96353.moz_log + firefox-log-child.96354.moz_log + +respectively for a parent process of PID 96353 and a child process of PID +96354. + +Enabling logging using command-line flags +''''''''''''''''''''''''''''''''''''''''' + +The ``MOZ_LOG`` syntax can be used with the command line switch on the same +name, and specifying a file with ``MOZ_LOG_FILE`` works in the same way: + +:: + + ./mach run -MOZ_LOG=timestamp,rotate:200,example_module:5 -MOZ_LOG_FILE=%TEMP%\firefox-logs + +will enable verbose (``5``) logging for the module ``example_module``, with +timestamp prepended to each line, rotate the logs with 4 files of each 50MB +(for a total of 200MB), and write the output to the temporary directory on +Windows, with name starting with ``firefox-logs``. + +.. _Enabling logging using preferences: + +Enabling logging using preferences +'''''''''''''''''''''''''''''''''' + +To adjust the logging after Firefox has started, you can set prefs under the +`logging.` prefix. For example, setting `logging.foo` to `3` will set the log +module `foo` to start logging at level 3. A number of special prefs can be set, +described in the table below: + ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| Preference name | Preference | Preference value | Description | ++=====================================+============+===============================+========================================================+ +| ``logging.config.clear_on_startup`` | bool | -- | Whether to clear all prefs under ``logging.`` | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.LOG_FILE`` | string | A path (relative or absolute) | The path to which the log files will be written. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.add_timestamp`` | bool | -- | Whether to prefix all lines by a timestamp. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.sync`` | bool | -- | Whether to flush the stream after each log statements. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.profilerstacks`` | bool | -- | | When logging to the Firefox Profiler, whether to | +| | | | | include the call stack in each logging statement. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ + +Enabling logging in Rust code +----------------------------- + +We're gradually adding more Rust code to Gecko, and Rust crates typically use a +different approach to logging. Many Rust libraries use the `log +<https://docs.rs/log>`_ crate to log messages, which works together with +`env_logger <https://docs.rs/env_logger>`_ at the application level to control +what's actually printed via `RUST_LOG`. + +You can set an overall logging level, though it could be quite verbose: + +:: + + set RUST_LOG="debug" + +You can also target individual modules by path: + +:: + + set RUST_LOG="style::style_resolver=debug" + +.. note:: + For Linux/MacOS users, you need to use `export` rather than `set`. + +.. note:: + Sometimes it can be useful to only log child processes and ignore the parent + process. In Firefox 57 and later, you can use `RUST_LOG_CHILD` instead of + `RUST_LOG` to specify log settings that will only apply to child processes. + +The `log` crate lists the available `log levels <https://docs.rs/log/0.3.8/log/enum.LogLevel.html>`_: + ++-----------+---------------------------------------------------------------------------------------------------------+ +| Log Level | Purpose | ++===========+=========================================================================================================+ +| error | Designates very serious errors. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| warn | Designates hazardous situations. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| info | Designates useful information. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| debug | Designates lower priority information. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| trace | Designates very low priority, often extremely verbose, information. | ++-----------+---------------------------------------------------------------------------------------------------------+ + +It is common for debug and trace to be disabled at compile time in release builds, so you may need a debug build if you want logs from those levels. + +Check the `env_logger <https://docs.rs/env_logger>`_ docs for more details on logging options. + +Additionally, a mapping from `RUST_LOG` is available. When using the `MOZ_LOG` +syntax, it is possible to enable logging in rust crate using a similar syntax: + +:: + + MOZ_LOG=rust_crate_name::*:4 + +will enable `debug` logging for all log statements in the crate +``rust_crate_name``. + +`*` can be replaced by a series of modules if more specificity is needed: + +:: + + MOZ_LOG=rust_crate_name::module::submodule:4 + +will enable `debug` logging for all log statements in the sub-module +``submodule`` of the module ``module`` of the crate ``rust_crate_name``. + + +A table mapping Rust log levels to `MOZ_LOG` log level is available below: + ++----------------+---------------+-----------------+ +| Rust log level | MOZ_LOG level | Numerical value | ++================+===============+=================+ +| off | Disabled | 0 | ++----------------+---------------+-----------------+ +| error | Error | 1 | ++----------------+---------------+-----------------+ +| warn | Warning | 2 | ++----------------+---------------+-----------------+ +| info | Info | 3 | ++----------------+---------------+-----------------+ +| debug | Debug | 4 | ++----------------+---------------+-----------------+ +| trace | Verbose | 5 | ++----------------+---------------+-----------------+ + + +Enabling logging on Android, interleaved with system logs (``logcat``) +---------------------------------------------------------------------- + +While logging to the Firefox Profiler works it's sometimes useful to have +system logs (``adb logcat``) interleaved with application logging. With a +device (or emulator) that ``adb devices`` sees, it's possible to set +environment variables like so, for e.g. ``GeckoView_example``: + + +.. code-block:: sh + + adb shell am start -n org.mozilla.geckoview_example/.GeckoViewActivity --es env0 MOZ_LOG=MediaDemuxer:4 + + +It is then possible to see the logging statements like so, to display all logs, +including ``MOZ_LOG``: + +.. code-block:: sh + + adb logcat + +and to only see ``MOZ_LOG`` like so: + +.. code-block:: sh + + adb logcat Gecko:V '*:S' + +This expression means: print log module ``Gecko`` from log level ``Verbose`` +(lowest level, this means that all levels are printed), and filter out (``S`` +for silence) all other logging (``*``, be careful to quote it or escape it +appropriately, it so that it's not expanded by the shell). + +While interactive with e.g. ``GeckoView`` code, it can be useful to specify +more logging tags like so: + +.. code-block:: sh + + adb logcat GeckoViewActivity:V Gecko:V '*:S' + + +Enabling logging on Android, using the Firefox Profiler +------------------------------------------------------- + +Set the logging modules using `about:config` (this requires a Nightly build) +using the instructions outlined above, and start the profile using an +appropriate profiling preset to profile the correct threads using the instructions +written in Firefox Profiler documentation's `dedicated page +<https://profiler.firefox.com/docs/#/./guide-profiling-android-directly-on-device>`_. + +`Bug 1803607 <https://bugzilla.mozilla.org/show_bug.cgi?id=1803607>`_ tracks +improving the logging experience on mobile. + +Working with ``MOZ_LOG`` in the code +++++++++++++++++++++++++++++++++++++ + +Declaring a Log Module +---------------------- + +``LazyLogModule`` defers the creation the backing ``LogModule`` in a thread-safe manner and is the preferred method to declare a log module. Multiple ``LazyLogModules`` with the same name can be declared, all will share the same backing ``LogModule``. This makes it much simpler to share a log module across multiple translation units. ``LazyLogLodule`` provides a conversion operator to ``LogModule*`` and is suitable for passing into the logging macros detailed below. + +Note: Log module names can only contain specific characters. The first character must be a lowercase or uppercase ASCII char, underscore, dash, or dot. Subsequent characters may be any of those, or an ASCII digit. + +.. code-block:: cpp + + #include "mozilla/Logging.h" + + static mozilla::LazyLogModule sFooLog("foo"); + + +Logging interface +----------------- + +A basic interface is provided in the form of 2 macros and an enum class. + ++----------------------------------------+----------------------------------------------------------------------------+ +| MOZ_LOG(module, level, message) | Outputs the given message if the module has the given log level enabled: | +| | | +| | * module: The log module to use. | +| | * level: The log level of the message. | +| | * message: A printf-style message to output. Must be enclosed in | +| | parentheses. | ++----------------------------------------+----------------------------------------------------------------------------+ +| MOZ_LOG_TEST(module, level) | Checks if the module has the given level enabled: | +| | | +| | * module: The log module to use. | +| | * level: The output level of the message. | ++----------------------------------------+----------------------------------------------------------------------------+ + + ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Log Level | Numeric Value | Purpose | ++===========+===============+=========================================================================================+ +| Disabled | 0 | Indicates logging is disabled. This should not be used directly in code. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Error | 1 | An error occurred, generally something you would consider asserting in a debug build. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Warning | 2 | A warning often indicates an unexpected state. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Info | 3 | An informational message, often indicates the current program state. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Debug | 4 | A debug message, useful for debugging but too verbose to be turned on normally. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Verbose | 5 | A message that will be printed a lot, useful for debugging program flow and will | +| | | probably impact performance. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ + +Example Usage +------------- + +.. code-block:: cpp + + #include "mozilla/Logging.h" + + using mozilla::LogLevel; + + static mozilla::LazyLogModule sLogger("example_logger"); + + static void DoStuff() + { + MOZ_LOG(sLogger, LogLevel::Info, ("Doing stuff.")); + + int i = 0; + int start = Time::NowMS(); + MOZ_LOG(sLogger, LogLevel::Debug, ("Starting loop.")); + while (i++ < 10) { + MOZ_LOG(sLogger, LogLevel::Verbose, ("i = %d", i)); + } + + // Only calculate the elapsed time if the Warning level is enabled. + if (MOZ_LOG_TEST(sLogger, LogLevel::Warning)) { + int elapsed = Time::NowMS() - start; + if (elapsed > 1000) { + MOZ_LOG(sLogger, LogLevel::Warning, ("Loop took %dms!", elapsed)); + } + } + + if (i != 10) { + MOZ_LOG(sLogger, LogLevel::Error, ("i should be 10!")); + } + } diff --git a/xpcom/docs/refptr.rst b/xpcom/docs/refptr.rst new file mode 100644 index 0000000000..f71acc6828 --- /dev/null +++ b/xpcom/docs/refptr.rst @@ -0,0 +1,81 @@ +Reference Counting Helpers +========================== + +RefPtr versus nsCOMPtr +---------------------- + +The general rule of thumb is to use ``nsCOMPtr<T>`` when ``T`` is an +interface type which inherits from ``nsISupports``, and ``RefPtr<T>`` when +``T`` is a concrete type. + +This basic rule derives from some ``nsCOMPtr<T>`` code being factored into +the ``nsCOMPtr_base`` base class, which stores the pointer as a +``nsISupports*``. This design was intended to save some space in the binary +(though it is unclear if it still does). Since ``nsCOMPtr`` stores the +pointer as ``nsISupports*``, it must be possible to unambiguously cast from +``T*`` to ``nsISupports**``. Many concrete classes inherit from more than +one XPCOM interface, meaning that they cannot be used with ``nsCOMPtr``, +which leads to the suggestion to use ``RefPtr`` for these classes. + +``nsCOMPtr<T>`` also requires that the target type ``T`` be a valid target +for ``QueryInterface`` so that it can assert that the stored pointer is a +canonical ``T`` pointer (i.e. that ``mRawPtr->QueryInterface(T_IID) == +mRawPtr``). + +do_XXX() nsCOMPtr helpers +------------------------- + +There are a number of ``do_XXX`` helper methods across the codebase which can +be assigned into ``nsCOMPtr`` (and sometimes ``RefPtr``) to perform explicit +operations based on the target pointer type. + +In general, when these operations succeed, they will initialize the smart +pointer with a valid value, and otherwise they will silently initialize the +smart pointer to ``nullptr``. + +``do_QueryInterface`` and ``do_QueryObject`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Attempts to cast the provided object to the target class using the XPCOM +``QueryInterface`` mechanism. In general, use ``do_QueryInterface`` may only +be used to cast between interface types in a ``nsCOMPtr<T>``, and +``do_QueryObject`` in situations when downcasting to concrete types. + + +``do_GetInterface`` +~~~~~~~~~~~~~~~~~~~ + +Looks up an object implementing the requested interface using the +``nsIInterfaceRequestor`` interface. If the target object doesn't implement +``nsIInterfaceRequestor`` or doesn't provide the given interface, initializes +the smart pointer with ``nullptr``. + + +``do_GetService`` +~~~~~~~~~~~~~~~~~ + +Looks up the component defined by the passed-in CID or ContractID string in +the component manager, and returns a pointer to the service instance. This +may start the service if it hasn't been started already. The resulting +service will be cast to the target interface type using ``QueryInterface``. + + +``do_CreateInstance`` +~~~~~~~~~~~~~~~~~~~~~ + +Looks up the component defined by the passed-in CID or ContractID string in +the component manager, creates and returns a new instance. The resulting +object will be cast to the target interface type using ``QueryInterface``. + + +``do_QueryReferent`` and ``do_GetWeakReference`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When passed a ``nsIWeakReference*`` (e.g. from a ``nsWeakPtr``), +``do_QueryReferent`` attempts to re-acquire a strong reference to the held +type, and cast it to the target type with ``QueryInterface``. Initializes the +smart pointer with ``nullptr`` if either of these steps fail. + +In contrast ``do_GetWeakReference`` does the opposite, using +``QueryInterface`` to cast the type to ``nsISupportsWeakReference*``, and +acquire a ``nsIWeakReference*`` to the passed-in object. diff --git a/xpcom/docs/stringguide.rst b/xpcom/docs/stringguide.rst new file mode 100644 index 0000000000..a3266d9604 --- /dev/null +++ b/xpcom/docs/stringguide.rst @@ -0,0 +1,1094 @@ +String Guide +============ + +Most of the Mozilla code uses a C++ class hierarchy to pass string data, +rather than using raw pointers. This guide documents the string classes which +are visible to code within the Mozilla codebase (code which is linked into +``libxul``). + +Introduction +------------ + +The string classes are a library of C++ classes which are used to manage +buffers of wide (16-bit) and narrow (8-bit) character strings. The headers +and implementation are in the `xpcom/string +<https://searchfox.org/mozilla-central/source/xpcom/string>`_ directory. All +strings are stored as a single contiguous buffer of characters. + +The 8-bit and 16-bit string classes have completely separate base classes, +but share the same APIs. As a result, you cannot assign a 8-bit string to a +16-bit string without some kind of conversion helper class or routine. For +the purpose of this document, we will refer to the 16-bit string classes in +class documentation. Every 16-bit class has an equivalent 8-bit class: + +===================== ====================== +Wide Narrow +===================== ====================== +``nsAString`` ``nsACString`` +``nsString`` ``nsCString`` +``nsAutoString`` ``nsAutoCString`` +``nsDependentString`` ``nsDependentCString`` +===================== ====================== + +The string classes distinguish, as part of the type hierarchy, between +strings that must have a null-terminator at the end of their buffer +(``ns[C]String``) and strings that are not required to have a null-terminator +(``nsA[C]String``). nsA[C]String is the base of the string classes (since it +imposes fewer requirements) and ``ns[C]String`` is a class derived from it. +Functions taking strings as parameters should generally take one of these +four types. + +In order to avoid unnecessary copying of string data (which can have +significant performance cost), the string classes support different ownership +models. All string classes support the following three ownership models +dynamically: + +* reference counted, copy-on-write, buffers (the default) + +* adopted buffers (a buffer that the string class owns, but is not reference + counted, because it came from somewhere else) + +* dependent buffers, that is, an underlying buffer that the string class does + not own, but that the caller that constructed the string guarantees will + outlive the string instance + +Auto strings will prefer reference counting an existing reference-counted +buffer over their stack buffer, but will otherwise use their stack buffer for +anything that will fit in it. + +There are a number of additional string classes: + + +* Classes which exist primarily as constructors for the other types, + particularly ``nsDependent[C]String`` and ``nsDependent[C]Substring``. These + types are really just convenient notation for constructing an + ``nsA[C]String`` with a non-default ownership mode; they should not be + thought of as different types. + +* ``nsLiteral[C]String`` which should rarely be constructed explicitly but + usually through the ``""_ns`` and ``u""_ns`` user-defined string literals. + ``nsLiteral[C]String`` is trivially constructible and destructible, and + therefore does not emit construction/destruction code when stored in static, + as opposed to the other string classes. + +The Major String Classes +------------------------ + +The list below describes the main base classes. Once you are familiar with +them, see the appendix describing What Class to Use When. + + +* **nsAString**/**nsACString**: the abstract base class for all strings. It + provides an API for assignment, individual character access, basic + manipulation of characters in the string, and string comparison. This class + corresponds to the XPIDL ``AString`` or ``ACString`` parameter types. + ``nsA[C]String`` is not necessarily null-terminated. + +* **nsString**/**nsCString**: builds on ``nsA[C]String`` by guaranteeing a + null-terminated storage. This allows for a method (``.get()``) to access the + underlying character buffer. + +The remainder of the string classes inherit from either ``nsA[C]String`` or +``ns[C]String``. Thus, every string class is compatible with ``nsA[C]String``. + +.. note:: + + In code which is generic over string width, ``nsA[C]String`` is sometimes + known as ``nsTSubstring<CharT>``. ``nsAString`` is a type alias for + ``nsTSubstring<char16_t>``, and ``nsACString`` is a type alias for + ``nsTSubstring<char>``. + +.. note:: + + The type ``nsLiteral[C]String`` technically does not inherit from + ``nsA[C]String``, but instead inherits from ``nsStringRepr<CharT>``. This + allows the type to not generate destructors when stored in static + storage. + + It can be implicitly coerced to ``const ns[C]String&`` (though can never + be accessed mutably) and generally acts as-if it was a subclass of + ``ns[C]String`` in most cases. + +Since every string derives from ``nsAString`` (or ``nsACString``), they all +share a simple API. Common read-only methods include: + +* ``.Length()`` - the number of code units (bytes for 8-bit string classes and ``char16_t`` for 16-bit string classes) in the string. +* ``.IsEmpty()`` - the fastest way of determining if the string has any value. Use this instead of testing ``string.Length() == 0`` +* ``.Equals(string)`` - ``true`` if the given string has the same value as the current string. Approximately the same as ``operator==``. + +Common methods that modify the string: + +* ``.Assign(string)`` - Assigns a new value to the string. Approximately the same as ``operator=``. +* ``.Append(string)`` - Appends a value to the string. +* ``.Insert(string, position)`` - Inserts the given string before the code unit at position. +* ``.Truncate(length)`` - shortens the string to the given length. + +More complete documentation can be found in the `Class Reference`_. + +As function parameters +~~~~~~~~~~~~~~~~~~~~~~ + +In general, use ``nsA[C]String`` references to pass strings across modules. For example: + +.. code-block:: cpp + + // when passing a string to a method, use const nsAString& + nsFoo::PrintString(const nsAString& str); + + // when getting a string from a method, use nsAString& + nsFoo::GetString(nsAString& result); + +The Concrete Classes - which classes to use when +------------------------------------------------ + +The concrete classes are for use in code that actually needs to store string +data. The most common uses of the concrete classes are as local variables, +and members in classes or structs. + +.. digraph:: concreteclasses + + node [shape=rectangle] + + "nsA[C]String" -> "ns[C]String"; + "ns[C]String" -> "nsDependent[C]String"; + "nsA[C]String" -> "nsDependent[C]Substring"; + "nsA[C]String" -> "ns[C]SubstringTuple"; + "ns[C]String" -> "nsAuto[C]StringN"; + "ns[C]String" -> "nsLiteral[C]String" [style=dashed]; + "nsAuto[C]StringN" -> "nsPromiseFlat[C]String"; + "nsAuto[C]StringN" -> "nsPrintfCString"; + +The following is a list of the most common concrete classes. Once you are +familiar with them, see the appendix describing What Class to Use When. + +* ``ns[C]String`` - a null-terminated string whose buffer is allocated on the + heap. Destroys its buffer when the string object goes away. + +* ``nsAuto[C]String`` - derived from ``nsString``, a string which owns a 64 + code unit buffer in the same storage space as the string itself. If a string + less than 64 code units is assigned to an ``nsAutoString``, then no extra + storage will be allocated. For larger strings, a new buffer is allocated on + the heap. + + If you want a number other than 64, use the templated types ``nsAutoStringN`` + / ``nsAutoCStringN``. (``nsAutoString`` and ``nsAutoCString`` are just + typedefs for ``nsAutoStringN<64>`` and ``nsAutoCStringN<64>``, respectively.) + +* ``nsDependent[C]String`` - derived from ``nsString``, this string does not + own its buffer. It is useful for converting a raw string pointer (``const + char16_t*`` or ``const char*``) into a class of type ``nsAString``. Note that + you must null-terminate buffers used by to ``nsDependentString``. If you + don't want to or can't null-terminate the buffer, use + ``nsDependentSubstring``. + +* ``nsPrintfCString`` - derived from ``nsCString``, this string behaves like an + ``nsAutoCString``. The constructor takes parameters which allows it to + construct a 8-bit string from a printf-style format string and parameter + list. + +There are also a number of concrete classes that are created as a side-effect +of helper routines, etc. You should avoid direct use of these classes. Let +the string library create the class for you. + +* ``ns[C]SubstringTuple`` - created via string concatenation +* ``nsDependent[C]Substring`` - created through ``Substring()`` +* ``nsPromiseFlat[C]String`` - created through ``PromiseFlatString()`` +* ``nsLiteral[C]String`` - created through the ``""_ns`` and ``u""_ns`` user-defined literals + +Of course, there are times when it is necessary to reference these string +classes in your code, but as a general rule they should be avoided. + +Iterators +--------- + +Because Mozilla strings are always a single buffer, iteration over the +characters in the string is done using raw pointers: + +.. code-block:: cpp + + /** + * Find whether there is a tab character in `data` + */ + bool HasTab(const nsAString& data) { + const char16_t* cur = data.BeginReading(); + const char16_t* end = data.EndReading(); + + for (; cur < end; ++cur) { + if (char16_t('\t') == *cur) { + return true; + } + } + return false; + } + +Note that ``end`` points to the character after the end of the string buffer. +It should never be dereferenced. + +Writing to a mutable string is also simple: + +.. code-block:: cpp + + /** + * Replace every tab character in `data` with a space. + */ + void ReplaceTabs(nsAString& data) { + char16_t* cur = data.BeginWriting(); + char16_t* end = data.EndWriting(); + + for (; cur < end; ++cur) { + if (char16_t('\t') == *cur) { + *cur = char16_t(' '); + } + } + } + +You may change the length of a string via ``SetLength()``. Note that +Iterators become invalid after changing the length of a string. If a string +buffer becomes smaller while writing it, use ``SetLength`` to inform the +string class of the new size: + +.. code-block:: cpp + + /** + * Remove every tab character from `data` + */ + void RemoveTabs(nsAString& data) { + int len = data.Length(); + char16_t* cur = data.BeginWriting(); + char16_t* end = data.EndWriting(); + + while (cur < end) { + if (char16_t('\t') == *cur) { + len -= 1; + end -= 1; + if (cur < end) + memmove(cur, cur + 1, (end - cur) * sizeof(char16_t)); + } else { + cur += 1; + } + } + + data.SetLength(len); + } + +Note that using ``BeginWriting()`` to make a string longer is not OK. +``BeginWriting()`` must not be used to write past the logical length of the +string indicated by ``EndWriting()`` or ``Length()``. Calling +``SetCapacity()`` before ``BeginWriting()`` does not affect what the previous +sentence says. To make the string longer, call ``SetLength()`` before +``BeginWriting()`` or use the ``BulkWrite()`` API described below. + +Bulk Write +---------- + +``BulkWrite()`` allows capacity-aware cache-friendly low-level writes to the +string's buffer. + +Capacity-aware means that the caller is made aware of how the +caller-requested buffer capacity was rounded up to mozjemalloc buckets. This +is useful when initially requesting best-case buffer size without yet knowing +the true size need. If the data that actually needs to be written is larger +than the best-case estimate but still fits within the rounded-up capacity, +there is no need to reallocate despite requesting the best-case capacity. + +Cache-friendly means that the zero terminator for C compatibility is written +after the new content of the string has been written, so the result is a +forward-only linear write access pattern instead of a non-linear +back-and-forth sequence resulting from using ``SetLength()`` followed by +``BeginWriting()``. + +Low-level means that writing via a raw pointer is possible as with +``BeginWriting()``. + +``BulkWrite()`` takes three arguments: The new capacity (which may be rounded +up), the number of code units at the beginning of the string to preserve +(typically the old logical length), and a boolean indicating whether +reallocating a smaller buffer is OK if the requested capacity would fit in a +buffer that's smaller than current one. It returns a ``mozilla::Result`` which +contains either a usable ``mozilla::BulkWriteHandle<T>`` (where ``T`` is the +string's ``char_type``) or an ``nsresult`` explaining why none can be had +(presumably OOM). + +The actual writes are performed through the returned +``mozilla::BulkWriteHandle<T>``. You must not access the string except via this +handle until you call ``Finish()`` on the handle in the success case or you let +the handle go out of scope without calling ``Finish()`` in the failure case, in +which case the destructor of the handle puts the string in a mostly harmless but +consistent state (containing a single REPLACEMENT CHARACTER if a capacity +greater than 0 was requested, or in the ``char`` case if the three-byte UTF-8 +representation of the REPLACEMENT CHARACTER doesn't fit, an ASCII SUBSTITUTE). + +``mozilla::BulkWriteHandle<T>`` autoconverts to a writable +``mozilla::Span<T>`` and also provides explicit access to itself as ``Span`` +(``AsSpan()``) or via component accessors named consistently with those on +``Span``: ``Elements()`` and ``Length()``. (The latter is not the logical +length of the string but the writable length of the buffer.) The buffer +exposed via these methods includes the prefix that you may have requested to +be preserved. It's up to you to skip past it so as to not overwrite it. + +If there's a need to request a different capacity before you are ready to +call ``Finish()``, you can call ``RestartBulkWrite()`` on the handle. It +takes three arguments that match the first three arguments of +``BulkWrite()``. It returns ``mozilla::Result<mozilla::Ok, nsresult>`` to +indicate success or OOM. Calling ``RestartBulkWrite()`` invalidates +previously-obtained span, raw pointer or length. + +Once you are done writing, call ``Finish()``. It takes two arguments: the new +logical length of the string (which must not exceed the capacity returned by +the ``Length()`` method of the handle) and a boolean indicating whether it's +OK to attempt to reallocate a smaller buffer in case a smaller mozjemalloc +bucket could accommodate the new logical length. + +Helper Classes and Functions +---------------------------- + +Converting Cocoa strings +~~~~~~~~~~~~~~~~~~~~~~~~ + +Use ``mozilla::CopyCocoaStringToXPCOMString()`` in +``mozilla/MacStringHelpers.h`` to convert Cocoa strings to XPCOM strings. + +Searching strings - looking for substrings, characters, etc. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``nsReadableUtils.h`` header provides helper methods for searching in runnables. + +.. code-block:: cpp + + bool FindInReadable(const nsAString& pattern, + nsAString::const_iterator start, nsAString::const_iterator end, + nsStringComparator& aComparator = nsDefaultStringComparator()); + +To use this, ``start`` and ``end`` should point to the beginning and end of a +string that you would like to search. If the search string is found, +``start`` and ``end`` will be adjusted to point to the beginning and end of +the found pattern. The return value is ``true`` or ``false``, indicating +whether or not the string was found. + +An example: + +.. code-block:: cpp + + const nsAString& str = GetSomeString(); + nsAString::const_iterator start, end; + + str.BeginReading(start); + str.EndReading(end); + + constexpr auto valuePrefix = u"value="_ns; + + if (FindInReadable(valuePrefix, start, end)) { + // end now points to the character after the pattern + valueStart = end; + } + +Checking for Memory Allocation failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Like other types in Gecko, the string classes use infallible memory +allocation by default, so you do not need to check for success when +allocating/resizing "normal" strings. + +Most functions that modify strings (``Assign()``, ``SetLength()``, etc.) also +have an overload that takes a ``mozilla::fallible_t`` parameter. These +overloads return ``false`` instead of aborting if allocation fails. Use them +when creating/allocating strings which may be very large, and which the +program could recover from if the allocation fails. + +Substrings (string fragments) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is very simple to refer to a substring of an existing string without +actually allocating new space and copying the characters into that substring. +``Substring()`` is the preferred method to create a reference to such a +string. + +.. code-block:: cpp + + void ProcessString(const nsAString& str) { + const nsAString& firstFive = Substring(str, 0, 5); // from index 0, length 5 + // firstFive is now a string representing the first 5 characters + } + +Unicode Conversion +------------------ + +Strings can be stored in two basic formats: 8-bit code unit (byte/``char``) +strings, or 16-bit code unit (``char16_t``) strings. Any string class with a +capital "C" in the classname contains 8-bit bytes. These classes include +``nsCString``, ``nsDependentCString``, and so forth. Any string class without +the "C" contains 16-bit code units. + +A 8-bit string can be in one of many character encodings while a 16-bit +string is always in potentially-invalid UTF-16. (You can make a 16-bit string +guaranteed-valid UTF-16 by passing it to ``EnsureUTF16Validity()``.) The most +common encodings are: + + +* ASCII - 7-bit encoding for basic English-only strings. Each ASCII value + is stored in exactly one byte in the array with the most-significant 8th bit + set to zero. + +* `UCS2 <http://www.unicode.org/glossary/#UCS_2>`_ - 16-bit encoding for a + subset of Unicode, `BMP <http://www.unicode.org/glossary/#BMP>`_. The Unicode + value of a character stored in UCS2 is stored in exactly one 16-bit + ``char16_t`` in a string class. + +* `UTF-8 <http://www.faqs.org/rfcs/rfc3629.html>`_ - 8-bit encoding for + Unicode characters. Each Unicode characters is stored in up to 4 bytes in a + string class. UTF-8 is capable of representing the entire Unicode character + repertoire, and it efficiently maps to `UTF-32 + <http://www.unicode.org/glossary/#UTF_32>`_. (Gtk and Rust natively use + UTF-8.) + +* `UTF-16 <http://www.unicode.org/glossary/#UTF_16>`_ - 16-bit encoding for + Unicode storage, backwards compatible with UCS2. The Unicode value of a + character stored in UTF-16 may require one or two 16-bit ``char16_t`` in a + string class. The contents of ``nsAString`` always has to be regarded as in + this encoding instead of UCS2. UTF-16 is capable of representing the entire + Unicode character repertoire, and it efficiently maps to UTF-32. (Win32 W + APIs and Mac OS X natively use UTF-16.) + +* Latin1 - 8-bit encoding for the first 256 Unicode code points. Used for + HTTP headers and for size-optimized storage in text node and SpiderMonkey + strings. Latin1 converts to UTF-16 by zero-extending each byte to a 16-bit + code unit. Note that this kind of "Latin1" is not available for encoding + HTML, CSS, JS, etc. Specifying ``charset=latin1`` means the same as + ``charset=windows-1252``. Windows-1252 is a similar but different encoding + used for interchange. + +In addition, there exist multiple other (legacy) encodings. The Web-relevant +ones are defined in the `Encoding Standard <https://encoding.spec.whatwg.org/>`_. +Conversions from these encodings to +UTF-8 and UTF-16 are provided by `mozilla::Encoding +<https://searchfox.org/mozilla-central/source/intl/Encoding.h#109>`_. +Additionally, on Windows the are some rare cases (e.g. drag&drop) where it's +necessary to call a system API with data encoded in the Windows +locale-dependent legacy encoding instead of UTF-16. In those rare cases, use +``MultiByteToWideChar``/``WideCharToMultiByte`` from kernel32.dll. Do not use +``iconv`` on *nix. We only support UTF-8-encoded file paths on *nix, non-path +Gtk strings are always UTF-8 and Cocoa and Java strings are always UTF-16. + +When working with existing code, it is important to examine the current usage +of the strings that you are manipulating, to determine the correct conversion +mechanism. + +When writing new code, it can be confusing to know which storage class and +encoding is the most appropriate. There is no single answer to this question, +but the important points are: + + +* **Surprisingly many strings are very often just ASCII.** ASCII is a subset of + UTF-8 and is, therefore, efficient to represent as UTF-8. Representing ASCII + as UTF-16 bad both for memory usage and cache locality. + +* **Rust strongly prefers UTF-8.** If your C++ code is interacting with Rust + code, using UTF-8 in ``nsACString`` and merely validating it when converting + to Rust strings is more efficient than using ``nsAString`` on the C++ side. + +* **Networking code prefers 8-bit strings.** Networking code tends to use 8-bit + strings: either with UTF-8 or Latin1 (byte value is the Unicode scalar value) + semantics. + +* **JS and DOM prefer UTF-16.** Most Gecko code uses UTF-16 for compatibility + with JS strings and DOM string which are potentially-invalid UTF-16. However, + both DOM text nodes and JS strings store strings that only contain code points + below U+0100 as Latin1 (byte value is the Unicode scalar value). + +* **Windows and Cocoa use UTF-16.** Windows system APIs take UTF-16. Cocoa + ``NSString`` is UTF-16. + +* **Gtk uses UTF-8.** Gtk APIs take UTF-8 for non-file paths. In the Gecko + case, we support only UTF-8 file paths outside Windows, so all Gtk strings + are UTF-8 for our purposes though file paths received from Gtk may not be + valid UTF-8. + +To assist with ASCII, Latin1, UTF-8, and UTF-16 conversions, there are some +helper methods and classes. Some of these classes look like functions, +because they are most often used as temporary objects on the stack. + +Short zero-terminated ASCII strings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have a short zero-terminated string that you are certain is always +ASCII, use these special-case methods instead of the conversions described in +the later sections. + +* If you are assigning an ASCII literal to an ``nsACString``, use + ``AssignLiteral()``. +* If you are assigning a literal to an ``nsAString``, use ``AssignLiteral()`` + and make the literal a ``u""`` literal. If the literal has to be a ``""`` + literal (as opposed to ``u""``) and is ASCII, still use ``AppendLiteral()``, + but be aware that this involves a run-time inflation. +* If you are assigning a zero-terminated ASCII string that's not a literal from + the compiler's point of view at the call site and you don't know the length + of the string either (e.g. because it was looked up from an array of literals + of varying lengths), use ``AssignASCII()``. + +UTF-8 / UTF-16 conversion +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. cpp:function:: NS_ConvertUTF8toUTF16(const nsACString&) + + a ``nsAutoString`` subclass that converts a UTF-8 encoded ``nsACString`` + or ``const char*`` to a 16-bit UTF-16 string. If you need a ``const + char16_t*`` buffer, you can use the ``.get()`` method. For example: + + .. code-block:: cpp + + /* signature: void HandleUnicodeString(const nsAString& str); */ + object->HandleUnicodeString(NS_ConvertUTF8toUTF16(utf8String)); + + /* signature: void HandleUnicodeBuffer(const char16_t* str); */ + object->HandleUnicodeBuffer(NS_ConvertUTF8toUTF16(utf8String).get()); + +.. cpp:function:: NS_ConvertUTF16toUTF8(const nsAString&) + + a ``nsAutoCString`` which converts a 16-bit UTF-16 string (``nsAString``) + to a UTF-8 encoded string. As above, you can use ``.get()`` to access a + ``const char*`` buffer. + + .. code-block:: cpp + + /* signature: void HandleUTF8String(const nsACString& str); */ + object->HandleUTF8String(NS_ConvertUTF16toUTF8(utf16String)); + + /* signature: void HandleUTF8Buffer(const char* str); */ + object->HandleUTF8Buffer(NS_ConvertUTF16toUTF8(utf16String).get()); + +.. cpp:function:: CopyUTF8toUTF16(const nsACString&, nsAString&) + + converts and copies: + + .. code-block:: cpp + + // return a UTF-16 value + void Foo::GetUnicodeValue(nsAString& result) { + CopyUTF8toUTF16(mLocalUTF8Value, result); + } + +.. cpp:function:: AppendUTF8toUTF16(const nsACString&, nsAString&) + + converts and appends: + + .. code-block:: cpp + + // return a UTF-16 value + void Foo::GetUnicodeValue(nsAString& result) { + result.AssignLiteral("prefix:"); + AppendUTF8toUTF16(mLocalUTF8Value, result); + } + +.. cpp:function:: CopyUTF16toUTF8(const nsAString&, nsACString&) + + converts and copies: + + .. code-block:: cpp + + // return a UTF-8 value + void Foo::GetUTF8Value(nsACString& result) { + CopyUTF16toUTF8(mLocalUTF16Value, result); + } + +.. cpp:function:: AppendUTF16toUTF8(const nsAString&, nsACString&) + + converts and appends: + + .. code-block:: cpp + + // return a UTF-8 value + void Foo::GetUnicodeValue(nsACString& result) { + result.AssignLiteral("prefix:"); + AppendUTF16toUTF8(mLocalUTF16Value, result); + } + + +Latin1 / UTF-16 Conversion +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following should only be used when you can guarantee that the original +string is ASCII or Latin1 (in the sense that the byte value is the Unicode +scalar value; not in the windows-1252 sense). These helpers are very similar +to the UTF-8 / UTF-16 conversion helpers above. + + +UTF-16 to Latin1 converters +``````````````````````````` + +These converters are **very dangerous** because they **lose information** +during the conversion process. You should **avoid UTF-16 to Latin1 +conversions** unless your strings are guaranteed to be Latin1 or ASCII. (In +the future, these conversions may start asserting in debug builds that their +input is in the permissible range.) If the input is actually in the Latin1 +range, each 16-bit code unit in narrowed to an 8-bit byte by removing the +high half. Unicode code points above U+00FF result in garbage whose nature +must not be relied upon. (In the future the nature of the garbage will be CPU +architecture-dependent.) If you want to ``printf()`` something and don't care +what happens to non-ASCII, please convert to UTF-8 instead. + + +.. cpp:function:: NS_LossyConvertUTF16toASCII(const nsAString&) + + A ``nsAutoCString`` which holds a temporary buffer containing the Latin1 + value of the string. + +.. cpp:function:: void LossyCopyUTF16toASCII(Span<const char16_t>, nsACString&) + + Does an in-place conversion from UTF-16 into an Latin1 string object. + +.. cpp:function:: void LossyAppendUTF16toASCII(Span<const char16_t>, nsACString&) + + Appends a UTF-16 string to a Latin1 string. + +Latin1 to UTF-16 converters +``````````````````````````` + +These converters are very dangerous because they will **produce wrong results +for non-ASCII UTF-8 or windows-1252 input** into a meaningless UTF-16 string. +You should **avoid ASCII to UTF-16 conversions** unless your strings are +guaranteed to be ASCII or Latin1 in the sense of the byte value being the +Unicode scalar value. Every byte is zero-extended into a 16-bit code unit. + +It is correct to use these on most HTTP header values, but **it's always +wrong to use these on HTTP response bodies!** (Use ``mozilla::Encoding`` to +deal with response bodies.) + +.. cpp:function:: NS_ConvertASCIItoUTF16(const nsACString&) + + A ``nsAutoString`` which holds a temporary buffer containing the value of + the Latin1 to UTF-16 conversion. + +.. cpp:function:: void CopyASCIItoUTF16(Span<const char>, nsAString&) + + does an in-place conversion from Latin1 to UTF-16. + +.. cpp:function:: void AppendASCIItoUTF16(Span<const char>, nsAString&) + + appends a Latin1 string to a UTF-16 string. + +Comparing ns*Strings with C strings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can compare ``ns*Strings`` with C strings by converting the ``ns*String`` +to a C string, or by comparing directly against a C String. + +.. cpp:function:: bool nsAString::EqualsASCII(const char*) + + Compares with an ASCII C string. + +.. cpp:function:: bool nsAString::EqualsLiteral(...) + + Compares with a string literal. + +Common Patterns +--------------- + +Literal Strings +~~~~~~~~~~~~~~~ + +A literal string is a raw string value that is written in some C++ code. For +example, in the statement ``printf("Hello World\n");`` the value ``"Hello +World\n"`` is a literal string. It is often necessary to insert literal +string values when an ``nsAString`` or ``nsACString`` is required. Two +user-defined literals are provided that implicitly convert to ``const +nsString&`` resp. ``const nsCString&``: + +* ``""_ns`` for 8-bit literals, converting implicitly to ``const nsCString&`` +* ``u""_ns`` for 16-bit literals, converting implicitly to ``const nsString&`` + +The benefits of the user-defined literals may seem unclear, given that +``nsDependentCString`` will also wrap a string value in an ``nsCString``. The +advantage of the user-defined literals is twofold. + +* The length of these strings is calculated at compile time, so the string does + not need to be scanned at runtime to determine its length. + +* Literal strings live for the lifetime of the binary, and can be moved between + the ``ns[C]String`` classes without being copied or freed. + +Here are some examples of proper usage of the literals (both standard and +user-defined): + +.. code-block:: cpp + + // call Init(const nsLiteralString&) - enforces that it's only called with literals + Init(u"start value"_ns); + + // call Init(const nsAString&) + Init(u"start value"_ns); + + // call Init(const nsACString&) + Init("start value"_ns); + +In case a literal is defined via a macro, you can just convert it to +``nsLiteralString`` or ``nsLiteralCString`` using their constructor. You +could consider not using a macro at all but a named ``constexpr`` constant +instead. + +In some cases, an 8-bit literal is defined via a macro, either within code or +from the environment, but it can't be changed or is used both as an 8-bit and +a 16-bit string. In these cases, you can use the +``NS_LITERAL_STRING_FROM_CSTRING`` macro to construct a ``nsLiteralString`` +and do the conversion at compile-time. + +String Concatenation +~~~~~~~~~~~~~~~~~~~~ + +Strings can be concatenated together using the + operator. The resulting +string is a ``const nsSubstringTuple`` object. The resulting object can be +treated and referenced similarly to a ``nsAString`` object. Concatenation *does +not copy the substrings*. The strings are only copied when the concatenation +is assigned into another string object. The ``nsSubstringTuple`` object holds +pointers to the original strings. Therefore, the ``nsSubstringTuple`` object is +dependent on all of its substrings, meaning that their lifetime must be at +least as long as the ``nsSubstringTuple`` object. + +For example, you can use the value of two strings and pass their +concatenation on to another function which takes an ``const nsAString&``: + +.. code-block:: cpp + + void HandleTwoStrings(const nsAString& one, const nsAString& two) { + // call HandleString(const nsAString&) + HandleString(one + two); + } + +NOTE: The two strings are implicitly combined into a temporary ``nsString`` +in this case, and the temporary string is passed into ``HandleString``. If +``HandleString`` assigns its input into another ``nsString``, then the string +buffer will be shared in this case negating the cost of the intermediate +temporary. You can concatenate N strings and store the result in a temporary +variable: + +.. code-block:: cpp + + constexpr auto start = u"start "_ns; + constexpr auto middle = u"middle "_ns; + constexpr auto end = u"end"_ns; + // create a string with 3 dependent fragments - no copying involved! + nsString combinedString = start + middle + end; + + // call void HandleString(const nsAString&); + HandleString(combinedString); + +It is safe to concatenate user-defined literals because the temporary +``nsLiteral[C]String`` objects will live as long as the temporary +concatenation object (of type ``nsSubstringTuple``). + +.. code-block:: cpp + + // call HandlePage(const nsAString&); + // safe because the concatenated-string will live as long as its substrings + HandlePage(u"start "_ns + u"end"_ns); + +Local Variables +~~~~~~~~~~~~~~~ + +Local variables within a function are usually stored on the stack. The +``nsAutoString``/``nsAutoCString`` classes are subclasses of the +``nsString``/``nsCString`` classes. They own a 64-character buffer allocated +in the same storage space as the string itself. If the ``nsAutoString`` is +allocated on the stack, then it has at its disposal a 64-character stack +buffer. This allows the implementation to avoid allocating extra memory when +dealing with small strings. ``nsAutoStringN``/``nsAutoCStringN`` are more +general alternatives that let you choose the number of characters in the +inline buffer. + +.. code-block:: cpp + + ... + nsAutoString value; + GetValue(value); // if the result is less than 64 code units, + // then this just saved us an allocation + ... + +Member Variables +~~~~~~~~~~~~~~~~ + +In general, you should use the concrete classes ``nsString`` and +``nsCString`` for member variables. + +.. code-block:: cpp + + class Foo { + ... + // these store UTF-8 and UTF-16 values respectively + nsCString mLocalName; + nsString mTitle; + }; + +A common incorrect pattern is to use ``nsAutoString``/``nsAutoCString`` +for member variables. As described in `Local Variables`_, these classes have +a built in buffer that make them very large. This means that if you include +them in a class, they bloat the class by 64 bytes (``nsAutoCString``) or 128 +bytes (``nsAutoString``). + + +Raw Character Pointers +~~~~~~~~~~~~~~~~~~~~~~ + +``PromiseFlatString()`` and ``PromiseFlatCString()`` can be used to create a +temporary buffer which holds a null-terminated buffer containing the same +value as the source string. ``PromiseFlatString()`` will create a temporary +buffer if necessary. This is most often used in order to pass an +``nsAString`` to an API which requires a null-terminated string. + +In the following example, an ``nsAString`` is combined with a literal string, +and the result is passed to an API which requires a simple character buffer. + +.. code-block:: cpp + + // Modify the URL and pass to AddPage(const char16_t* url) + void AddModifiedPage(const nsAString& url) { + constexpr auto httpPrefix = u"http://"_ns; + const nsAString& modifiedURL = httpPrefix + url; + + // creates a temporary buffer + AddPage(PromiseFlatString(modifiedURL).get()); + } + +``PromiseFlatString()`` is smart when handed a string that is already +null-terminated. It avoids creating the temporary buffer in such cases. + +.. code-block:: cpp + + // Modify the URL and pass to AddPage(const char16_t* url) + void AddModifiedPage(const nsAString& url, PRBool addPrefix) { + if (addPrefix) { + // MUST create a temporary buffer - string is multi-fragmented + constexpr auto httpPrefix = u"http://"_ns; + AddPage(PromiseFlatString(httpPrefix + modifiedURL)); + } else { + // MIGHT create a temporary buffer, does a runtime check + AddPage(PromiseFlatString(url).get()); + } + } + +.. note:: + + It is **not** possible to efficiently transfer ownership of a string + class' internal buffer into an owned ``char*`` which can be safely + freed by other components due to the COW optimization. + + If working with a legacy API which requires malloced ``char*`` buffers, + prefer using ``ToNewUnicode``, ``ToNewCString`` or ``ToNewUTF8String`` + over ``strdup`` to create owned ``char*`` pointers. + +``printf`` and a UTF-16 string +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For debugging, it's useful to ``printf`` a UTF-16 string (``nsString``, +``nsAutoString``, etc). To do this usually requires converting it to an 8-bit +string, because that's what ``printf`` expects. Use: + +.. code-block:: cpp + + printf("%s\n", NS_ConvertUTF16toUTF8(yourString).get()); + +Sequence of appends without reallocating +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``SetCapacity()`` allows you to give the string a hint of the future string +length caused by a sequence of appends (excluding appends that convert +between UTF-16 and UTF-8 in either direction) in order to avoid multiple +allocations during the sequence of appends. However, the other +allocation-avoidance features of XPCOM strings interact badly with +``SetCapacity()`` making it something of a footgun. + +``SetCapacity()`` is appropriate to use before a sequence of multiple +operations from the following list (without operations that are not on the +list between the ``SetCapacity()`` call and operations from the list): + +* ``Append()`` +* ``AppendASCII()`` +* ``AppendLiteral()`` +* ``AppendPrintf()`` +* ``AppendInt()`` +* ``AppendFloat()`` +* ``LossyAppendUTF16toASCII()`` +* ``AppendASCIItoUTF16()`` + +**DO NOT** call ``SetCapacity()`` if the subsequent operations on the string +do not meet the criteria above. Operations that undo the benefits of +``SetCapacity()`` include but are not limited to: + +* ``SetLength()`` +* ``Truncate()`` +* ``Assign()`` +* ``AssignLiteral()`` +* ``Adopt()`` +* ``CopyASCIItoUTF16()`` +* ``LossyCopyUTF16toASCII()`` +* ``AppendUTF16toUTF8()`` +* ``AppendUTF8toUTF16()`` +* ``CopyUTF16toUTF8()`` +* ``CopyUTF8toUTF16()`` + +If your string is an ``nsAuto[C]String`` and you are calling +``SetCapacity()`` with a constant ``N``, please instead declare the string as +``nsAuto[C]StringN<N+1>`` without calling ``SetCapacity()`` (while being +mindful of not using such a large ``N`` as to overflow the run-time stack). + +There is no need to include room for the null terminator: it is the job of +the string class. + +Note: Calling ``SetCapacity()`` does not give you permission to use the +pointer obtained from ``BeginWriting()`` to write past the current length (as +returned by ``Length()``) of the string. Please use either ``BulkWrite()`` or +``SetLength()`` instead. + +.. _stringguide.xpidl: + +XPIDL +----- + +The string library is also available through IDL. By declaring attributes and +methods using the specially defined IDL types, string classes are used as +parameters to the corresponding methods. + +XPIDL String types +~~~~~~~~~~~~~~~~~~ + +The C++ signatures follow the abstract-type convention described above, such +that all method parameters are based on the abstract classes. The following +table describes the purpose of each string type in IDL. + ++-----------------+----------------+----------------------------------------------------------------------------------+ +| XPIDL Type | C++ Type | Purpose | ++=================+================+==================================================================================+ +| ``string`` | ``char*`` | Raw character pointer to ASCII (7-bit) string, no string classes used. | +| | | | +| | | High bit is not guaranteed across XPConnect boundaries. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``wstring`` | ``char16_t*`` | Raw character pointer to UTF-16 string, no string classes used. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``AString`` | ``nsAString`` | UTF-16 string. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``ACString`` | ``nsACString`` | 8-bit string. All bits are preserved across XPConnect boundaries. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``AUTF8String`` | ``nsACString`` | UTF-8 string. | +| | | | +| | | Converted to UTF-16 as necessary when value is used across XPConnect boundaries. | ++-----------------+----------------+----------------------------------------------------------------------------------+ + +Callers should prefer using the string classes ``AString``, ``ACString`` and +``AUTF8String`` over the raw pointer types ``string`` and ``wstring`` in +almost all situations. + +C++ Signatures +~~~~~~~~~~~~~~ + +In XPIDL, ``in`` parameters are read-only, and the C++ signatures for +``*String`` parameters follows the above guidelines by using ``const +nsAString&`` for these parameters. ``out`` and ``inout`` parameters are +defined simply as ``nsAString&`` so that the callee can write to them. + +.. code-block:: cpp + + interface nsIFoo : nsISupports { + attribute AString utf16String; + AUTF8String getValue(in ACString key); + }; + +.. code-block:: cpp + + class nsIFoo : public nsISupports { + NS_IMETHOD GetUtf16String(nsAString& aResult) = 0; + NS_IMETHOD SetUtf16String(const nsAString& aValue) = 0; + NS_IMETHOD GetValue(const nsACString& aKey, nsACString& aResult) = 0; + }; + +In the above example, ``utf16String`` is treated as a UTF-16 string. The +implementation of ``GetUtf16String()`` will use ``aResult.Assign`` to +"return" the value. In ``SetUtf16String()`` the value of the string can be +used through a variety of methods including `Iterators`_, +``PromiseFlatString``, and assignment to other strings. + +In ``GetValue()``, the first parameter, ``aKey``, is treated as a raw +sequence of 8-bit values. Any non-ASCII characters in ``aKey`` will be +preserved when crossing XPConnect boundaries. The implementation of +``GetValue()`` will assign a UTF-8 encoded 8-bit string into ``aResult``. If +the this method is called across XPConnect boundaries, such as from a script, +then the result will be decoded from UTF-8 into UTF-16 and used as a Unicode +value. + +String Guidelines +----------------- + +Follow these simple rules in your code to keep your fellow developers, +reviewers, and users happy. + +* Use the most abstract string class that you can. Usually this is: + * ``nsAString`` for function parameters + * ``nsString`` for member variables + * ``nsAutoString`` for local (stack-based) variables +* Use the ``""_ns`` and ``u""_ns`` user-defined literals to represent literal strings (e.g. ``"foo"_ns``) as nsAString-compatible objects. +* Use string concatenation (i.e. the "+" operator) when combining strings. +* Use ``nsDependentString`` when you have a raw character pointer that you need to convert to an nsAString-compatible string. +* Use ``Substring()`` to extract fragments of existing strings. +* Use `iterators`_ to parse and extract string fragments. + +Class Reference +--------------- + +.. cpp:class:: template<T> nsTSubstring<T> + + .. note:: + + The ``nsTSubstring<char_type>`` class is usually written as + ``nsAString`` or ``nsACString``. + + .. cpp:function:: size_type Length() const + + .. cpp:function:: bool IsEmpty() const + + .. cpp:function:: bool IsVoid() const + + .. cpp:function:: const char_type* BeginReading() const + + .. cpp:function:: const char_type* EndReading() const + + .. cpp:function:: bool Equals(const self_type&, comparator_type = ...) const + + .. cpp:function:: char_type First() const + + .. cpp:function:: char_type Last() const + + .. cpp:function:: size_type CountChar(char_type) const + + .. cpp:function:: int32_t FindChar(char_type, index_type aOffset = 0) const + + .. cpp:function:: void Assign(const self_type&) + + .. cpp:function:: void Append(const self_type&) + + .. cpp:function:: void Insert(const self_type&, index_type aPos) + + .. cpp:function:: void Cut(index_type aCutStart, size_type aCutLength) + + .. cpp:function:: void Replace(index_type aCutStart, size_type aCutLength, const self_type& aStr) + + .. cpp:function:: void Truncate(size_type aLength) + + .. cpp:function:: void SetIsVoid(bool) + + Make it null. XPConnect and WebIDL will convert void nsAStrings to + JavaScript ``null``. + + .. cpp:function:: char_type* BeginWriting() + + .. cpp:function:: char_type* EndWriting() + + .. cpp:function:: void SetCapacity(size_type) + + Inform the string about buffer size need before a sequence of calls + to ``Append()`` or converting appends that convert between UTF-16 and + Latin1 in either direction. (Don't use if you use appends that + convert between UTF-16 and UTF-8 in either direction.) Calling this + method does not give you permission to use ``BeginWriting()`` to + write past the logical length of the string. Use ``SetLength()`` or + ``BulkWrite()`` as appropriate. + + .. cpp:function:: void SetLength(size_type) + + .. cpp:function:: Result<BulkWriteHandle<char_type>, nsresult> BulkWrite(size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) diff --git a/xpcom/docs/thread-safety.rst b/xpcom/docs/thread-safety.rst new file mode 100644 index 0000000000..be2f156804 --- /dev/null +++ b/xpcom/docs/thread-safety.rst @@ -0,0 +1,354 @@ +**Thread safety analysis in Gecko** +=================================== + +Clang thread-safety analysis is supported in Gecko. This means +builds will generate warnings when static analysis detects an issue with +locking of mutex/monitor-protected members and data structures. Note +that Chrome uses the same feature. An example warning: :: + + warning: dom/media/AudioStream.cpp:504:22 [-Wthread-safety-analysis] + reading variable 'mDataSource' requires holding mutex 'mMonitor' + +If your patch causes warnings like this, you’ll need to resolve them; +they will be errors on checkin. + +This analysis depends on thread-safety attributions in the source. These +have been added to Mozilla’s Mutex and Monitor classes and subclasses, +but in practice the analysis is largely dependent on additions to the +code being checked, in particular adding MOZ_GUARDED_BY(mutex) attributions +on the definitions of member variables. Like this: :: + + mozilla::Mutex mLock; + bool mShutdown MOZ_GUARDED_BY(mLock); + +For background on Clang’s thread-safety support, see `their +documentation <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`__. + +Newly added Mutexes and Monitors **MUST** use thread-safety annotations, +and we are enabling static checks to verify this. Legacy uses of Mutexes +and Monitors are marked with MOZ_UNANNOTATED. + +If you’re modifying code that has been annotated with +MOZ_GUARDED_BY()/MOZ_REQUIRES()/etc, you should **make sure that the annotations +are updated properly**; e.g. if you change the thread-usage of a member +variable or method you should mark it accordingly, comment, and resolve +any warnings. Since the warnings will be errors in autoland/m-c, you +won’t be able to land code with active warnings. + +**Annotating locking and usage requirements in class definitions** +------------------------------------------------------------------ + +Values that require a lock to access, or which are simply used from more +than one thread, should always have documentation in the definition +about the locking requirements and/or what threads it’s touched from: :: + + // This array is accessed from both the direct video thread, and the graph + // thread. Protected by mMutex. + + nsTArray<std::pair<ImageContainer::FrameID, VideoChunk>> mFrames + MOZ_GUARDED_BY(mMutex); + + // Set on MainThread, deleted on either MainThread mainthread, used on + // MainThread or IO Thread in DoStopSession + nsCOMPtr<nsITimer> mReconnectDelayTimer MOZ_GUARDED_BY(mMutex); + +It’s strongly recommended to group values by access pattern, but it’s +**critical** to make it clear what the requirements to access a value +are. With values protected by Mutexes and Monitors, adding a +MOZ_GUARDED_BY(mutex/monitor) should be sufficient, though you may want to +also document what threads access it, and if they read or write to it. + +Values which have more complex access requirements (see single-writer +and time-based-locking below) need clear documentation where they’re +defined: :: + + MutexSingleWriter mMutex; + + // mResource should only be modified on the main thread with the lock. + // So it can be read without lock on the main thread or on other threads + // with the lock. + RefPtr<ChannelMediaResource> mResource MOZ_GUARDED_BY(mMutex); + +**WARNING:** thread-safety analysis is not magic; it depends on you telling +it the requirements around access. If you don’t mark something as +MOZ_GUARDED_BY() it won’t figure it out for you, and you can end up with a data +race. When writing multithreaded code, you should always be thinking about +which threads can access what and when, and document this. + +**How to annotate different locking patterns in Gecko** +------------------------------------------------------- + +Gecko uses a number of different locking patterns. They include: + +- **Always Lock** - + Multiple threads may read and write the value + +- **Single Writer** - + One thread does all the writing, other threads + read the value, but code on the writing thread also reads it + without the lock + +- **Out-of-band invariants** - + A value may be accessed from other threads, + but only after or before certain other events or in a certain state, + like when after a listener has been added or before a processing + thread has been shut down. + +The simplest and easiest to check with static analysis is **Always +Lock**, and generally you should prefer this pattern. This is very +simple; you add MOZ_GUARDED_BY(mutex/monitor), and must own the lock to +access the value. This can be implemented by some combination of direct +Lock/AutoLock calls in the method; an assertion that the lock is already +held by the current thread, or annotating the method as requiring the +lock (MOZ_REQUIRES(mutex)) in the method definition: :: + + // Ensures mSize is initialized, if it can be. + // mLock must be held when this is called, and mInput must be non-null. + void EnsureSizeInitialized() MOZ_REQUIRES(mLock); + ... + // This lock protects mSeekable, mInput, mSize, and mSizeInitialized. + Mutex mLock; + int64_t mSize MOZ_GUARDED_BY(mLock); + +**Single Writer** is tricky for static analysis normally, since it +doesn’t know what thread an access will occur on. In general, you should +prefer using Always Lock in non-performance-sensitive code, especially +since these mutexes are almost always uncontended and therefore cheap to +lock. + +To support this fairly common pattern in Mozilla code, we’ve added +MutexSingleWriter and MonitorSingleWriter subclasses. To use these, you +need to subclass SingleWriterLockOwner on one object (typically the +object containing the Mutex), implement ::OnWritingThread(), and pass +the object to the constructor for MutexSingleWriter. In code that +accesses the guarded value from the writing thread, you need to add +mMutex.AssertIsOnWritingThread(), which both does a debug-only runtime +assertion by calling OnWritingThread(), and also asserts to the static +analyzer that the lock is held (which it isn’t). + +There is one case this causes problems with: when a method needs to +access the value (without the lock), and then decides to write to the +value from the same method, taking the lock. To the static analyzer, +this looks like a double-lock. Either you will need to add +MOZ_NO_THREAD_SAFETY_ANALYSIS to the method, move the write into another +method you call, or locally disable the warning with +MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY. We’re discussing with +the clang static analysis developers how to better handle this. + +Note also that this provides no checking that the lock is taken to write +to the value: :: + + MutexSingleWriter mMutex; + // mResource should only be modified on the main thread with the lock. + // So it can be read without lock on the main thread or on other threads + // with the lock. + RefPtr<ChannelMediaResource> mResource MOZ_GUARDED_BY(mMutex); + ... + nsresult ChannelMediaResource::Listener::OnStartRequest(nsIRequest *aRequest) { + mMutex.AssertOnWritingThread(); + + // Read from the only writing thread; no lock needed + if (!mResource) { + return NS_OK; + } + return mResource->OnStartRequest(aRequest, mOffset); + } + +If you need to assert you’re on the writing thread, then later take a +lock to modify a value, it will cause a warning: â€acquiring mutex +'mMutex' that is already heldâ€. You can resolve this by turning off +thread-safety analysis for the lock: :: + + mMutex.AssertOnWritingThread(); + ... + { + MOZ_PUSH_IGNORE_THREAD_SAFETY + MutexSingleWriterAutoLock lock(mMutex); + MOZ_POP_THREAD_SAFETY + +**Out-of-band Invariants** is used in a number of places (and may be +combined with either of the above patterns). It's using other knowledge +about the execution pattern of the code to assert that it's safe to avoid +taking certain locks. A primary example is when a value can +only be accessed from a single thread for part of its lifetime (this can +also be referred to as "time-based locking"). + +Note that thread-safety analysis always ignores constructors and destructors +(which shouldn’t have races with other threads barring really odd usages). +Since only a single thread can access during those time periods, locking is +not required there. However, if a method is called from a constructor, +that method may generate warnings since the compiler doesn't know if it +might be called from elsewhere: :: + + ... + class nsFoo { + public: + nsFoo() { + mBar = true; // Ok since we're in the constructor, no warning + Init(); + } + void Init() { // we're only called from the constructor + // This causes a thread-safety warning, since the compiler + // can't prove that Init() is only called from the constructor + mQuerty = true; + } + ... + mMutex mMutex; + uint32_t mBar MOZ_GUARDED_BY(mMutex); + uint32_t mQuerty MOZ_GUARDED_BY(mMutex); + } + +Another example might be a value that’s used from other threads, but only +if an observer has been installed. Thus code that always runs before the +observer is installed, or after it’s removed, does not need to lock. + +These patterns are impossible to statically check in most cases. If all +the periods where it’s accessed from one thread only are on the same +thread, you could use the Single Writer pattern support to cover this +case. You would add AssertIsOnWritingThread() calls to methods that meet +the criteria that only a single thread can access the value (but only if +that holds). Unlike regular uses of SingleWriter, however, there’s no way +to check if you added such an assertion to code that runs on the “right†+thread, but during a period where another thread might modify it. + +For this reason, we **strongly** suggest that you convert cases of +Out-of-band-invariants/Time-based-locking to Always Lock if you’re +refactoring the code or making major modifications. This is far less prone +to error, and also to future changes breaking the assumptions about other +threads accessing the value. In all but a few cases where code is on a very +‘hot’ path, this will have no impact on performance - taking an uncontended +lock is cheap. + +To quiet warnings where these patterns are in use, you'll need to either +add locks (preferred), or suppress the warnings with MOZ_NO_THREAD_SAFETY_ANALYSIS or +MOZ_PUSH_IGNORE_THREAD_SAFETY/MOZ_POP_THREAD_SAFETY. + +**This pattern especially needs good documentation in the code as to what +threads will access what members under what conditions!**:: + + // Can't be accessed by multiple threads yet + nsresult nsAsyncStreamCopier::InitInternal(nsIInputStream* source, + nsIOutputStream* sink, + nsIEventTarget* target, + uint32_t chunkSize, + bool closeSource, + bool closeSink) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + +and:: + + // We can't be accessed by another thread because this hasn't been + // added to the public list yet + MOZ_PUSH_IGNORE_THREAD_SAFETY + mRestrictedPortList.AppendElement(gBadPortList[i]); + MOZ_POP_THREAD_SAFETY + +and:: + + // This is called on entries in another entry's mCallback array, under the lock + // of that other entry. No other threads can access this entry at this time. + bool CacheEntry::Callback::DeferDoom(bool* aDoom) const { + +**Known limitations** +--------------------- + +**Static analysis can’t handle all reasonable patterns.** In particular, +per their documentation, it can’t handle conditional locks, like: :: + + if (OnMainThread()) { + mMutex.Lock(); + } + +You should resolve this either via MOZ_NO_THREAD_SAFETY_ANALYSIS on the +method, or MOZ_PUSH_IGNORE_THREAD_SAFETY/MOZ_POP_THREAD_SAFETY. + +**Sometimes the analyzer can’t figure out that two objects are both the +same Mutex**, and it will warn you. You may be able to resolve this by +making sure you’re using the same pattern to access the mutex: :: + + mChan->mMonitor->AssertCurrentThreadOwns(); + ... + { + - MonitorAutoUnlock guard(*monitor); + + MonitorAutoUnlock guard(*(mChan->mMonitor.get())); // avoids mutex warning + ok = node->SendUserMessage(port, std::move(aMessage)); + } + +**Maybe<MutexAutoLock>** doesn’t tell the static analyzer when the mutex +is owned or freed; follow locking via the MayBe<> by +**mutex->AssertCurrentThreadOwns();** (and ditto for Monitors): :: + + Maybe<MonitorAutoLock> lock(std::in_place, *mMonitor); + mMonitor->AssertCurrentThreadOwns(); // for threadsafety analysis + +If you reset() the Maybe<>, you may need to surround it with +MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY macros: :: + + MOZ_PUSH_IGNORE_THREAD_SAFETY + aLock.reset(); + MOZ_POP_THREAD_SAFETY + +**Passing a protected value by-reference** sometimes will confuse the +analyzer. Use MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY macros to +resolve this. + +**Classes which need thread-safety annotations** +------------------------------------------------ + +- Mutex + +- StaticMutex + +- RecursiveMutex + +- BaseProfilerMutex + +- Monitor + +- StaticMonitor + +- ReentrantMonitor + +- RWLock + +- Anything that hides an internal Mutex/etc and presents a Mutex-like + API (::Lock(), etc). + +**Additional Notes** +-------------------- + +Some code passes **Proof-of-Lock** AutoLock parameters, as a poor form of +static analysis. While it’s hard to make mistakes if you pass an AutoLock +reference, it is possible to pass a lock to the wrong Mutex/Monitor. + +Proof-of-lock is basically redundant to MOZ_REQUIRES() and obsolete, and +depends on the optimizer to remove it, and per above it can be misused, +with effort. With MOZ_REQUIRES(), any proof-of-lock parameters can be removed, +though you don't have to do so immediately. + +In any method taking an aProofOfLock parameter, add a MOZ_REQUIRES(mutex) to +the definition (and optionally remove the proof-of-lock), or add a +mMutex.AssertCurrentThreadOwns() to the method: :: + + nsresult DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable, + - nsIEventTarget* aSyncLoopTarget, + - const MutexAutoLock& aProofOfLock); + + nsIEventTarget* aSyncLoopTarget) MOZ_REQUIRES(mMutex); + +or (if for some reason it's hard to specify the mutex in the header):: + + nsresult DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable, + - nsIEventTarget* aSyncLoopTarget, + - const MutexAutoLock& aProofOfLock); + + nsIEventTarget* aSyncLoopTarget) { + + mMutex.AssertCurrentThreadOwns(); + +In addition to MOZ_GUARDED_BY() there’s also MOZ_PT_GUARDED_BY(), which says +that the pointer isn’t guarded, but the data pointed to by the pointer +is. + +Classes that expose a Mutex-like interface can be annotated like Mutex; +see some of the examples in the tree that use MOZ_CAPABILITY and +MOZ_ACQUIRE()/MOZ_RELEASE(). + +Shared locks are supported, though we don’t use them much. See RWLock. diff --git a/xpcom/docs/writing-xpcom-interface.rst b/xpcom/docs/writing-xpcom-interface.rst new file mode 100644 index 0000000000..9eeb1c72a2 --- /dev/null +++ b/xpcom/docs/writing-xpcom-interface.rst @@ -0,0 +1,287 @@ +.. _writing_xpcom_interface: + +Tutorial for Writing a New XPCOM Interface +========================================== + +High Level Overview +------------------- + +In order to write code that works in native code (C++, Rust), and JavaScript contexts, it's necessary to have a mechanism to do so. For chrome privileged contexts, this is the XPCOM Interface Class. + +This mechanism starts with an :ref:`XPIDL` file to define the shape of the interface. In the `build system`_, this file is processed, and `Rust`_ and `C++`_ code is automatically generated. + +.. _build system: https://searchfox.org/mozilla-central/source/xpcom/idl-parser/xpidl +.. _Rust: https://searchfox.org/mozilla-central/source/__GENERATED__/dist/xpcrs/rt +.. _C++: https://searchfox.org/mozilla-central/source/__GENERATED__/dist/include + +Next, the interface's methods and attributes must be implemented. This can be done through either a JSM module, or through a C++ interface class. Once these steps are done, the new files must be added to the appropriate :code:`moz.build` files to ensure the build system knows how to find them and process them. + +Often these XPCOM components are wired into the :code:`Services` JavaScript object to allow for ergonomic access to the interface. For example, open the `Browser Console`_ and type :code:`Services.` to interactively access these components. + +.. _Browser Console: https://developer.mozilla.org/en-US/docs/Tools/Browser_Console + +From C++, components can be accessed via :code:`mozilla::components::ComponentName::Create()` using the :code:`name` option in the :code:`components.conf`. + +While :code:`Services` and :code:`mozilla::components` are the preferred means of accessing components, many are accessed through the historical (and somewhat arcane) :code:`createInstance` mechanism. New usage of these mechanisms should be avoided if possible. + +.. code:: javascript + + let component = Cc["@mozilla.org/component-name;1"].createInstance( + Ci.nsIComponentName + ); + +.. code:: c++ + + nsCOMPtr<nsIComponentName> component = do_CreateInstance( + "@mozilla.org/component-name;1"); + +Writing an XPIDL +---------------- + +First decide on a name. Conventionally the interfaces are prefixed with :code:`nsI` (historically Netscape) or :code:`mozI` as they are defined in the global namespace. While the interface is global, the implementation of an interface can be defined in a namespace with no prefix. Historically many component implementations still use the :code:`ns` prefixes (notice that the :code:`I` was dropped), but this convention is no longer needed. + +This tutorial assumes the component is located at :code:`path/to` with the name :code:`ComponentName`. The interface name will be :code:`nsIComponentName`, while the implementation will be :code:`mozilla::ComponentName`. + +To start, create an :ref:`XPIDL` file: + +.. code:: bash + + touch path/to/nsIComponentName.idl + +And hook it up to the :code:`path/to/moz.build` + +.. code:: python + + XPIDL_SOURCES += [ + "nsIComponentName.idl", + ] + +Next write the initial :code:`.idl` file: :code:`path/to/nsIComponentName.idl` + +.. _contract_ids: +.. code:: c++ + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 is the base include which defines nsISupports. This class defines + // the QueryInterface method. + #include "nsISupports.idl" + + // `scriptable` designates that this object will be used with JavaScript + // `uuid` The example below uses a UUID with all Xs. Replace the Xs with + // your own UUID generated here: + // http://mozilla.pettay.fi/cgi-bin/mozuuid.pl + + /** + * Make sure to document your interface. + */ + [scriptable, uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)] + interface nsIComponentName : nsISupports { + + // Fill out your definition here. This example attribute only returns a bool. + + /** + * Make sure to document your attributes. + */ + readonly attribute bool isAlive; + }; + +This definition only includes one attribute, :code:`isAlive`, which will demonstrate that we've done our work correctly at the end. For a more comprehensive guide for this syntax, see the :ref:`XPIDL` docs. + +Once :code:`./mach build` is run, the XPIDL parser will read this file, and give any warnings if the syntax is wrong. It will then auto-generate the C++ (or Rust) code for us. For this example the generated :code:`nsIComponentName` class will be located in: + +:code:`{obj-directory}/dist/include/nsIComponentName.h` + +It might be useful to check out what was automatically generated here, or see the existing `generated C++ header files on SearchFox <https://searchfox.org/mozilla-central/source/__GENERATED__/dist/>`_. + +Writing the C++ implementation +------------------------------ + +Now we have a definition for an interface, but no implementation. The interface could be backed by a JavaScript implementation using a JSM, but for this example we'll use a C++ implementation. + +Add the C++ sources to :code:`path/to/moz.build` + +.. code:: python + + EXPORTS.mozilla += [ + "ComponentName.h", + ] + + UNIFIED_SOURCES += [ + "ComponentName.cpp", + ] + +Now write the header: :code:`path/to/ComponentName.h` + +.. code:: c++ + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_nsComponentName_h__ + #define mozilla_nsComponentName_h__ + + // This will pull in the header auto-generated by the .idl file: + // {obj-directory}/dist/include/nsIComponentName.h + #include "nsIComponentName.h" + + // The implementation can be namespaced, while the XPCOM interface is globally namespaced. + namespace mozilla { + + // Notice how the class name does not need to be prefixed, as it is defined in the + // `mozilla` namespace. + class ComponentName final : public nsIComponentName { + // This first macro includes the necessary information to use the base nsISupports. + // This includes the QueryInterface method. + NS_DECL_ISUPPORTS + + // This second macro includes the declarations for the attributes. There is + // no need to duplicate these declarations. + // + // In our case it includes a declaration for the isAlive attribute: + // GetIsAlive(bool *aIsAlive) + NS_DECL_NSICOMPONENTNAME + + public: + ComponentName() = default; + + private: + // A private destructor must be declared. + ~ComponentName() = default; + }; + + } // namespace mozilla + + #endif + +Now write the definitions: :code:`path/to/ComponentName.cpp` + +.. code:: c++ + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "ComponentName.h" + + namespace mozilla { + + // Use the macro to inject all of the definitions for nsISupports. + NS_IMPL_ISUPPORTS(ComponentName, nsIComponentName) + + // This is the actual implementation of the `isAlive` attribute. Note that the + // method name is somewhat different than the attribute. We specified "read-only" + // in the attribute, so only a getter, not a setter was defined for us. Here + // the name was adjusted to be `GetIsAlive`. + // + // Another common detail of implementing an XPIDL interface is that the return values + // are passed as out parameters. The methods are treated as fallible, and the return + // value is an `nsresult`. See the XPIDL documentation for the full nitty gritty + // details. + // + // A common way to know the exact function signature for a method implementation is + // to copy and paste from existing examples, or inspecting the generated file + // directly: {obj-directory}/dist/include/nsIComponentName.h + NS_IMETHODIMP + ComponentName::GetIsAlive(bool* aIsAlive) { + *aIsAlive = true; + return NS_OK; + } + + } // namespace: mozilla + +Registering the component +------------------------- + +At this point, the component should be correctly written, but it's not registered with the component system. In order to this, we'll need to create or modify the :code:`components.conf`. + +.. code:: bash + + touch path/to/components.conf + + +Now update the :code:`moz.build` to point to it. + +.. code:: python + + XPCOM_MANIFESTS += [ + "components.conf", + ] + +It is probably worth reading over :ref:`defining_xpcom_components`, but the following config will be sufficient to hook up our component to the :code:`Services` object. +Services should also be added to ``tools/lint/eslint/eslint-plugin-mozilla/lib/services.json``. +The easiest way to do that is to copy from ``<objdir>/xpcom/components/services.json``. + +.. code:: python + + Classes = [ + { + # This CID is the ID for component entries, and needs a separate UUID from + # the .idl file. Replace the Xs with a uuid from: + # http://mozilla.pettay.fi/cgi-bin/mozuuid.pl + 'cid': '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}', + 'interfaces': ['nsIComponentName'], + + # A contract ID is a human-readable identifier for an _implementation_ of + # an XPCOM interface. + # + # "@mozilla.org/process/environment;1" + # ^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^ ^ + # | | | | + # | | | The version number, usually just 1. + # | | Component name + # | Module + # Domain + # + # This design goes back to a time when XPCOM was intended to be a generalized + # solution for the Gecko Runtime Environment (GRE). At this point most (if + # not all) of mozilla-central has an @mozilla domain. + 'contract_ids': ['@mozilla.org/component-name;1'], + + # This is the name of the C++ type that implements the interface. + 'type': 'mozilla::ComponentName', + + # The header file to pull in for the implementation of the interface. + 'headers': ['path/to/ComponentName.h'], + + # In order to hook up this interface to the `Services` object, we can + # provide the "js_name" parameter. This is an ergonomic way to access + # the component. + 'js_name': 'componentName', + }, + ] + +At this point the full :code:`moz.build` file should look like: + +.. code:: python + + # -*- 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 += [ + "nsIComponentName.idl", + ] + + XPCOM_MANIFESTS += [ + "components.conf", + ] + + EXPORTS.mozilla += [ + "ComponentName.h", + ] + + UNIFIED_SOURCES += [ + "ComponentName.cpp", + ] + +This completes the implementation of a basic XPCOM Interface using C++. The component should be available via the `Browser Console`_ or other chrome contexts. + +.. code:: javascript + + console.log(Services.componentName.isAlive); + > true diff --git a/xpcom/docs/xpidl.rst b/xpcom/docs/xpidl.rst new file mode 100644 index 0000000000..41a039710b --- /dev/null +++ b/xpcom/docs/xpidl.rst @@ -0,0 +1,402 @@ +XPIDL +===== + +**XPIDL** is an Interface Description Language used to specify XPCOM interface +classes. + +Interface Description Languages (IDL) are used to describe interfaces in a +language- and machine-independent way. IDLs make it possible to define +interfaces which can then be processed by tools to autogenerate +language-dependent interface specifications. + +An xpidl file is essentially just a series of declarations. At the top level, +we can define typedefs, native types, or interfaces. Interfaces may +furthermore contain typedefs, natives, methods, constants, or attributes. +Most declarations can have properties applied to them. + +Types +----- + +There are three ways to make types: a typedef, a native, or an interface. In +addition, there are a few built-in native types. The built-in native types +are those listed under the type_spec production above. The following is the +correspondence table: + +=========================== =============== =========================== ============================ ======================= ======================= +IDL Type Javascript Type C++ in parameter C++ out parameter Rust in parameter Rust out parameter +=========================== =============== =========================== ============================ ======================= ======================= +``boolean`` boolean ``bool`` ``bool*`` ``bool`` ``*mut bool`` +``char`` string ``char`` ``char*`` ``c_char`` ``*mut c_char`` +``double`` number ``double`` ``double*`` ``f64`` ``*mut f64`` +``float`` number ``float`` ``float*`` ``f32`` ``*mut f32`` +``long`` number ``int32_t`` ``int32_t*`` ``i32`` ``*mut i32`` +``long long`` number ``int64_t`` ``int64_t*`` ``i64`` ``*mut i64`` +``octet`` number ``uint8_t`` ``uint8_t*`` ``u8`` ``*mut u8`` +``short`` number ``uint16_t`` ``uint16_t*`` ``u16`` ``*mut u16`` +``string`` [#strptr]_ string ``const char*`` ``char**`` ``*const c_char`` ``*mut *mut c_char`` +``unsigned long`` number ``uint32_t`` ``uint32_t*`` ``u32`` ``*mut u32`` +``unsigned long long`` number ``uint64_t`` ``uint64_t*`` ``u64`` ``*mut u64`` +``unsigned short`` number ``uint16_t`` ``uint16_t*`` ``u16`` ``*mut u16`` +``wchar`` string ``char16_t`` ``char16_t*`` ``i16`` ``*mut i16`` +``wstring`` [#strptr]_ string ``const char16_t*`` ``char16_t**`` ``*const i16`` ``*mut *mut i16`` +``MozExternalRefCountType`` number ``MozExternalRefCountType`` ``MozExternalRefCountType*`` ``u32`` ``*mut u32`` +``Array<T>`` [#array]_ array ``const nsTArray<T>&`` ``nsTArray<T>&`` ``*const ThinVec<T>`` ``*mut ThinVec<T>`` +=========================== =============== =========================== ============================ ======================= ======================= + +.. [#strptr] + + Prefer using the string class types such as ``AString``, ``AUTF8String`` + or ``ACString`` to this type. The behaviour of these types is documented + more in the :ref:`String Guide <stringguide.xpidl>` + +.. [#array] + + The C++ or Rust exposed type ``T`` will be an owned variant. (e.g. + ``ns[C]String``, ``RefPtr<T>``, or ``uint32_t``) + + ``string``, ``wstring``, ``[ptr] native`` and ``[ref] native`` are + unsupported as element types. + + +In addition to this list, nearly every IDL file includes ``nsrootidl.idl`` in +some fashion, which also defines the following types: + +======================= ======================= ======================= ======================= ======================= ======================= +IDL Type Javascript Type C++ in parameter C++ out parameter Rust in parameter Rust out parameter +======================= ======================= ======================= ======================= ======================= ======================= +``PRTime`` number ``uint64_t`` ``uint64_t*`` ``u64`` ``*mut u64`` +``nsresult`` number ``nsresult`` ``nsresult*`` ``u32`` [#rsresult]_ ``*mut u32`` +``size_t`` number ``uint32_t`` ``uint32_t*`` ``u32`` ``*mut u32`` +``voidPtr`` N/A ``void*`` ``void**`` ``*mut c_void`` ``*mut *mut c_void`` +``charPtr`` N/A ``char*`` ``char**`` ``*mut c_char`` ``*mut *mut c_char`` +``unicharPtr`` N/A ``char16_t*`` ``char16_t**`` ``*mut i16`` ``*mut *mut i16`` +``nsIDRef`` ID object ``const nsID&`` ``nsID*`` ``*const nsID`` ``*mut nsID`` +``nsIIDRef`` ID object ``const nsIID&`` ``nsIID*`` ``*const nsIID`` ``*mut nsIID`` +``nsCIDRef`` ID object ``const nsCID&`` ``nsCID*`` ``*const nsCID`` ``*mut nsCID`` +``nsIDPtr`` ID object ``const nsID*`` ``nsID**`` ``*const nsID`` ``*mut *mut nsID`` +``nsIIDPtr`` ID object ``const nsIID*`` ``nsIID**`` ``*const nsIID`` ``*mut *mut nsIID`` +``nsCIDPtr`` ID object ``const nsCID*`` ``nsCID**`` ``*const nsCID`` ``*mut *mut nsCID`` +``nsID`` N/A ``nsID`` ``nsID*`` N/A N/A +``nsIID`` N/A ``nsIID`` ``nsIID*`` N/A N/A +``nsCID`` N/A ``nsCID`` ``nsCID*`` N/A N/A +``nsQIResult`` object ``void*`` ``void**`` ``*mut c_void`` ``*mut *mut c_void`` +``AUTF8String`` [#str]_ string ``const nsACString&`` ``nsACString&`` ``*const nsACString`` ``*mut nsACString`` +``ACString`` [#str]_ string ``const nsACString&`` ``nsACString&`` ``*const nsACString`` ``*mut nsACString`` +``AString`` [#str]_ string ``const nsAString&`` ``nsAString&`` ``*const nsAString`` ``*mut nsAString`` +``jsval`` any ``HandleValue`` ``MutableHandleValue`` N/A N/A +``jsid`` N/A ``jsid`` ``jsid*`` N/A N/A +``Promise`` Promise object ``dom::Promise*`` ``dom::Promise**`` N/A N/A +======================= ======================= ======================= ======================= ======================= ======================= + +.. [#rsresult] + + A bare ``u32`` is only for bare ``nsresult`` in/outparams in XPIDL. The + result should be wrapped as the ``nserror::nsresult`` type. + +.. [#str] + + The behaviour of these types is documented more in the :ref:`String Guide + <stringguide.xpidl>` + +Typedefs in IDL are basically as they are in C or C++: you define first the +type that you want to refer to and then the name of the type. Types can of +course be one of the fundamental types, or any other type declared via a +typedef, interface, or a native type. + +Native types are types which correspond to a given C++ type. Most native +types are not scriptable: if it is not present in the list above, then it is +certainly not scriptable (some of the above, particularly jsid, are not +scriptable). + +The contents of the parentheses of a native type declaration (although native +declarations without parentheses are parsable, I do not trust that they are +properly handled by the xpidl handlers) is a string equivalent to the C++ +type. XPIDL itself does not interpret this string, it just literally pastes +it anywhere the native type is used. The interpretation of the type can be +modified by using the ``[ptr]`` or ``[ref]`` attributes on the native +declaration. Other attributes are only intended for use in ``nsrootidl.idl``. + +WebIDL Interfaces +~~~~~~~~~~~~~~~~~ + +WebIDL interfaces are also valid XPIDL types. To declare a WebIDL interface in +XPIDL, write: + +.. code-block:: JavaScript + + webidl InterfaceName; + +WebIDL types will be passed as ``mozilla::dom::InterfaceName*`` when used as +in-parameters, as ``mozilla::dom::InterfaceName**`` when used as out or +inout-parameters, and as ``RefPtr<mozilla::dom::InterfaceName>`` when used as +an array element. + +.. note:: + + Other WebIDL types (e.g. dictionaries, enums, and unions) are not currently + supported. + +Constants and CEnums +~~~~~~~~~~~~~~~~~~~~ + +Constants must be attached to an interface. The only constants supported are +those which become integer types when compiled to source code; string constants +and floating point constants are currently not supported. + +Often constants are used to describe a set of enum values. In cases like this +the ``cenum`` construct can be used to group constants together. Constants +grouped in a ``cenum`` will be reflected as-if they were declared directly on +the interface, in Rust and Javascript code. + +.. code-block:: JavaScript + + cenum MyCEnum : 8 { + eSomeValue, // starts at 0 + eSomeOtherValue, + }; + +The number after the enum name, like ``: 8`` in the example above, defines the +width of enum values with the given type. The cenum's type may be referenced in +xpidl as ``nsIInterfaceName_MyCEnum``. + +Interfaces +---------- + +Interfaces are basically a collection of constants, methods, and attributes. +Interfaces can inherit from one-another, and every interface must eventually +inherit from ``nsISupports``. + +Interface Attributes +~~~~~~~~~~~~~~~~~~~~ + +Interfaces may have the following attributes: + +``uuid`` +```````` + +The internal unique identifier for the interface. it must be unique, and the +uuid must be generated when creating the interface. After that, it doesn't need +to be changed any more. + +Online tools such as http://mozilla.pettay.fi/cgi-bin/mozuuid.pl can help +generate UUIDs for new interfaces. + +``builtinclass`` +```````````````` + +JavaScript classes are forbidden from implementing this interface. All child +interfaces must also be marked with this property. + +``function`` +```````````` + +The JavaScript implementation of this interface may be a function that is +invoked on property calls instead of an object with the given property + +``scriptable`` +`````````````` + +This interface is usable by JavaScript classes. Must inherit from a +``scriptable`` interface. + +``rust_sync`` +````````````` + +This interface is safe to use from multiple threads concurrently. All child +interfaces must also be marked with this property. Interfaces marked this way +must be either non-scriptable or ``builtinclass``, and must use threadsafe +reference counting. + +Interfaces marked as ``rust_sync`` will implement the ``Sync`` trait in Rust. +For more details on what that means, read the trait's documentation: +https://doc.rust-lang.org/nightly/std/marker/trait.Sync.html. + +Methods and Attributes +~~~~~~~~~~~~~~~~~~~~~~ + +Interfaces declare a series of attributes and methods. Attributes in IDL are +akin to JavaScript properties, in that they are a getter and (optionally) a +setter pair. In JavaScript contexts, attributes are exposed as a regular +property access, while native code sees attributes as a Get and possibly a Set +method. + +Attributes can be declared readonly, in which case setting causes an error to +be thrown in script contexts and native contexts lack the Set method, by using +the ``readonly`` keyword. + +To native code, on attribute declared ``attribute type foo;`` is syntactic +sugar for the declaration of two methods ``type getFoo();`` and ``void +setFoo(in type foo);``. If ``foo`` were declared readonly, the latter method +would not be present. Attributes support all of the properties of methods with +the exception of ``optional_argc``, as this does not make sense for attributes. + +There are some special rules for attribute naming. As a result of vtable +munging by the MSVC++ compiler, an attribute with the name ``IID`` is +forbidden. Also like methods, if the first character of an attribute is +lowercase in IDL, it is made uppercase in native code only. + +Methods define a return type and a series of in and out parameters. When called +from a JavaScript context, they invocation looks as it is declared for the most +part; some parameter properties can adjust what the code looks like. The calls +are more mangled in native contexts. + +An important attribute for methods and attributes is scriptability. A method or +attribute is scriptable if it is declared in a ``scriptable`` interface and it +lacks a ``noscript`` or ``notxpcom`` property. Any method that is not +scriptable can only be accessed by native code. However, ``scriptable`` methods +must contain parameters and a return type that can be translated to script: any +native type, save a few declared in ``nsrootidl.idl`` (see above), may not be +used in a scriptable method or attribute. An exception to the above rule is if +a ``nsQIResult`` parameter has the ``iid_is`` property (a special case for some +QueryInterface-like operations). + +Methods and attributes are mangled on conversion to native code. If a method is +declared ``notxpcom``, the mangling of the return type is prevented, so it is +called mostly as it looks. Otherwise, the return type of the native method is +``nsresult``, and the return type acts as a final outparameter if it is not +``void``. The name is translated so that the first character is +unconditionally uppercase; subsequent characters are unaffected. However, the +presence of the ``binaryname`` property allows the user to select another name +to use in native code (to avoid conflicts with other functions). For example, +the method ``[binaryname(foo)] void bar();`` becomes ``nsresult Foo()`` in +native code (note that capitalization is still applied). However, the +capitalization is not applied when using ``binaryname`` with attributes; i.e., +``[binaryname(foo)] readonly attribute Quux bar;`` becomes ``Getfoo(Quux**)`` +in native code. + +The ``implicit_jscontext`` and ``optional_argc`` parameters are properties +which help native code implementations determine how the call was made from +script. If ``implicit_jscontext`` is present on a method, then an additional +``JSContext* cx`` parameter is added just after the regular list which receives +the context of the caller. If ``optional_argc`` is present, then an additional +``uint8_t _argc`` parameter is added at the end which receives the number of +optional arguments that were actually used (obviously, you need to have an +optional argument in the first place). Note that if both properties are set, +the ``JSContext* cx`` is added first, followed by the ``uint8_t _argc``, and +then ending with return value parameter. Finally, as an exception to everything +already mentioned, for attribute getters and setters the ``JSContext *cx`` +comes before any other arguments. + +Another native-only property is ``nostdcall``. Normally, declarations are made +in the stdcall ABI on Windows to be ABI-compatible with COM interfaces. Any +non-scriptable method or attribute with ``nostdcall`` instead uses the +``thiscall`` ABI convention. Methods without this property generally use +``NS_IMETHOD`` in their declarations and ``NS_IMETHODIMP`` in their definitions +to automatically add in the stdcall declaration specifier on requisite +compilers; those that use this method may use a plain ``nsresult`` instead. + +Another property, ``infallible``, is attribute-only. When present, it causes an +infallible C++ getter function definition to be generated for the attribute +alongside the normal fallible C++ getter declaration. It should only be used if +the fallible getter will be infallible in practice (i.e. always return +``NS_OK``) for all possible implementations. This infallible getter contains +code that calls the fallible getter, asserts success, and returns the gotten +value directly. The point of using this property is to make C++ code nicer -- a +call to the infallible getter is more concise and readable than a call to the +fallible getter. This property can only be used for attributes having built-in +or interface types, and within classes that are marked with ``builtinclass``. +The latter restriction is because C++ implementations of fallible getters can +be audited for infallibility, but JS implementations can always throw (e.g. due +to OOM). + +The ``must_use`` property is useful if the result of a method call or an +attribute get/set should always (or usually) be checked, which is frequently +the case. (e.g. a method that opens a file should almost certainly have its +result checked.) This property will cause ``[[nodiscard]]`` to be added to the +generated function declarations, which means certain compilers (e.g. clang and +GCC) will reports errors if these results are not used. + +Method Parameters +~~~~~~~~~~~~~~~~~ + +Each method parameter can be specified in one of three modes: ``in``, ``out``, +or ``inout``. An ``out`` parameter is essentially an auxiliary return value, +although these are moderately cumbersome to use from script contexts and should +therefore be avoided if reasonable. An ``inout`` parameter is an in parameter +whose value may be changed as a result of the method; these parameters are +rather annoying to use and should generally be avoided if at all possible. + +``out`` and ``inout`` parameters are reflected as objects having the ``.value`` +property which contains the real value of the parameter; the ``value`` +attribute is missing in the case of ``out`` parameters and is initialized to +the passed-in-value for ``inout`` parameters. The script code needs to set this +property to assign a value to the parameter. Regular ``in`` parameters are +reflected more or less normally, with numeric types all representing numbers, +booleans as ``true`` or ``false``, the various strings (including ``AString`` +etc.) as a JavaScript string, and ``nsID`` types as a ``Components.ID`` +instance. In addition, the ``jsval`` type is translated as the appropriate +JavaScript value (since a ``jsval`` is the internal representation of all +JavaScript values), and parameters with the ``nsIVeriant`` interface have their +types automatically boxed and unboxed as appropriate. + +The equivalent representations of all IDL types in native code is given in the +earlier tables; parameters of type ``inout`` follow their ``out`` form. Native +code should pay particular attention to not passing in null values for out +parameters (although some parts of the codebase are known to violate this, it +is strictly enforced at the JS<->native barrier). + +Representations of types additionally depend on some of the many types of +properties they may have. The ``array`` property turns the parameter into an array; +the parameter must also have a corresponding ``size_is`` property whose argument is +the parameter that has the size of the array. In native code, the type gains +another pointer indirection, and JavaScript arrays are used in script code. +Script code callers can ignore the value of array parameter, but implementers +must still set the values appropriately. + +.. note:: + + Prefer using the ``Array<T>`` builtin over the ``[array]`` attribute for + new code. It is more ergonomic to use from both JS and C++. In the future, + ``[array]`` may be deprecated and removed. + +The ``const`` and ``shared`` properties are special to native code. As its name +implies, the ``const`` property makes its corresponding argument ``const``. The +``shared`` property is only meaningful for ``out`` or ``inout`` parameters and +it means that the pointer value should not be freed by the caller. Only simple +native pointer types like ``string``, ``wstring``, and ``octetPtr`` may be +declared shared. The shared property also makes its corresponding argument +const. + +The ``retval`` property indicates that the parameter is actually acting as the +return value, and it is only the need to assign properties to the parameter +that is causing it to be specified as a parameter. It has no effect on native +code, but script code uses it like a regular return value. Naturally, a method +which contains a ``retval`` parameter must be declared ``void``, and the +parameter itself must be an ``out`` parameter and the last parameter. + +Other properties are the ``optional`` and ``iid_is`` property. The ``optional`` +property indicates that script code may omit the property without problems; all +subsequent parameters must either by optional themselves or the retval +parameter. Note that optional out parameters still pass in a variable for the +parameter, but its value will be ignored. The ``iid_is`` parameter indicates +that the real IID of an ``nsQIResult`` parameter may be found in the +corresponding parameter, to allow script code to automatically unbox the type. + +Not all type combinations are possible. Native types with the various string +properties are all forbidden from being used as an ``inout`` parameter or as an +``array`` parameter. In addition, native types with the ``nsid`` property but +lacking either a ``ptr`` or ``ref`` property are forbidden unless the method is +``notxpcom`` and it is used as an ``in`` parameter. + +Ownership Rules +``````````````` + +For types that reference heap-allocated data (strings, arrays, interface +pointers, etc), you must follow the XPIDL data ownership conventions in order +to avoid memory corruption and security vulnerabilities: + +* For ``in`` parameters, the caller allocates and deallocates all data. If the + callee needs to use the data after the call completes, it must make a private + copy of the data, or, in the case of interface pointers, ``AddRef`` it. +* For ``out`` parameters, the callee creates the data, and transfers ownership + to the caller. For buffers, the callee allocates the buffer with ``malloc``, + and the caller frees the buffer with ``free``. For interface pointers, the + callee does the ``AddRef`` on behalf of the caller, and the caller must call + ``Release``. This manual reference/memory management should be performed + using the ``getter_AddRefs`` and ``getter_Transfers`` helpers in new code. +* For ``inout`` parameters, the callee must clean up the old data if it chooses + to replace it. Buffers must be deallocated with ``free``, and interface + pointers must be ``Release``'d. Afterwards, the above rules for ``out`` + apply. +* ``shared`` out-parameters should not be freed, as they are intended to refer + to constant string literals. diff --git a/xpcom/ds/ArenaAllocator.h b/xpcom/ds/ArenaAllocator.h new file mode 100644 index 0000000000..76498986ce --- /dev/null +++ b/xpcom/ds/ArenaAllocator.h @@ -0,0 +1,214 @@ +/* -*- 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_ArenaAllocator_h +#define mozilla_ArenaAllocator_h + +#include <algorithm> +#include <cstdint> + +#include "mozilla/Assertions.h" +#include "mozilla/fallible.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/Poison.h" +#include "mozilla/TemplateLib.h" +#include "nsDebug.h" + +namespace mozilla { + +/** + * A very simple arena allocator based on NSPR's PLArena. + * + * The arena allocator only provides for allocation, all memory is retained + * until the allocator is destroyed. It's useful for situations where a large + * amount of small transient allocations are expected. + * + * Example usage: + * + * // Define an allocator that is page sized and returns allocations that are + * // 8-byte aligned. + * ArenaAllocator<4096, 8> a; + * for (int i = 0; i < 1000; i++) { + * DoSomething(a.Allocate(i)); + * } + */ +template <size_t ArenaSize, size_t Alignment = 1> +class ArenaAllocator { + public: + constexpr ArenaAllocator() : mHead(), mCurrent(nullptr) { + static_assert(mozilla::tl::FloorLog2<Alignment>::value == + mozilla::tl::CeilingLog2<Alignment>::value, + "ArenaAllocator alignment must be a power of two"); + } + + ArenaAllocator(const ArenaAllocator&) = delete; + ArenaAllocator& operator=(const ArenaAllocator&) = delete; + + /** + * Frees all internal arenas but does not call destructors for objects + * allocated out of the arena. + */ + ~ArenaAllocator() { Clear(); } + + /** + * Fallibly allocates a chunk of memory with the given size from the internal + * arenas. If the allocation size is larger than the chosen arena a size an + * entire arena is allocated and used. + */ + MOZ_ALWAYS_INLINE void* Allocate(size_t aSize, const fallible_t&) { + MOZ_RELEASE_ASSERT(aSize, "Allocation size must be non-zero"); + return InternalAllocate(AlignedSize(aSize)); + } + + void* Allocate(size_t aSize) { + void* p = Allocate(aSize, fallible); + if (MOZ_UNLIKELY(!p)) { + NS_ABORT_OOM(std::max(aSize, ArenaSize)); + } + + return p; + } + + /** + * Frees all entries. The allocator can be reused after this is called. + * + * NB: This will not run destructors of any objects that were allocated from + * the arena. + */ + void Clear() { + // Free all chunks. + auto a = mHead.next; + while (a) { + auto tmp = a; + a = a->next; + free(tmp); + } + + // Reset the list head. + mHead.next = nullptr; + mCurrent = nullptr; + } + + /** + * Adjusts the given size to the required alignment. + */ + static constexpr size_t AlignedSize(size_t aSize) { + return (aSize + (Alignment - 1)) & ~(Alignment - 1); + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t s = 0; + for (auto arena = mHead.next; arena; arena = arena->next) { + s += aMallocSizeOf(arena); + } + + return s; + } + + void Check() { + if (mCurrent) { + mCurrent->canary.Check(); + } + } + + private: + struct ArenaHeader { + /** + * The location in memory of the data portion of the arena. + */ + uintptr_t offset; + /** + * The location in memory of the end of the data portion of the arena. + */ + uintptr_t tail; + }; + + struct ArenaChunk { + constexpr ArenaChunk() : header{0, 0}, next(nullptr) {} + + explicit ArenaChunk(size_t aSize) + : header{AlignedSize(uintptr_t(this + 1)), uintptr_t(this) + aSize}, + next(nullptr) {} + + CorruptionCanary canary; + ArenaHeader header; + ArenaChunk* next; + + /** + * Allocates a chunk of memory out of the arena and advances the offset. + */ + void* Allocate(size_t aSize) { + MOZ_ASSERT(aSize <= Available()); + char* p = reinterpret_cast<char*>(header.offset); + MOZ_RELEASE_ASSERT(p); + header.offset += aSize; + canary.Check(); + MOZ_MAKE_MEM_UNDEFINED(p, aSize); + return p; + } + + /** + * Calculates the amount of space available for allocation in this chunk. + */ + size_t Available() const { return header.tail - header.offset; } + }; + + /** + * Allocates an arena chunk of the given size and initializes its header. + */ + ArenaChunk* AllocateChunk(size_t aSize) { + static const size_t kOffset = AlignedSize(sizeof(ArenaChunk)); + MOZ_ASSERT(kOffset < aSize); + + const size_t chunkSize = aSize + kOffset; + void* p = malloc(chunkSize); + if (!p) { + return nullptr; + } + + ArenaChunk* arena = new (KnownNotNull, p) ArenaChunk(chunkSize); + MOZ_MAKE_MEM_NOACCESS((void*)arena->header.offset, + arena->header.tail - arena->header.offset); + + // Insert into the head of the list. + arena->next = mHead.next; + mHead.next = arena; + + // Only update |mCurrent| if this is a standard allocation, large + // allocations will always end up full so there's no point in updating + // |mCurrent| in that case. + if (aSize == ArenaSize - kOffset) { + mCurrent = arena; + } + + return arena; + } + + MOZ_ALWAYS_INLINE void* InternalAllocate(size_t aSize) { + static_assert(ArenaSize > AlignedSize(sizeof(ArenaChunk)), + "Arena size must be greater than the header size"); + + static const size_t kMaxArenaCapacity = + ArenaSize - AlignedSize(sizeof(ArenaChunk)); + + if (mCurrent && aSize <= mCurrent->Available()) { + return mCurrent->Allocate(aSize); + } + + ArenaChunk* arena = AllocateChunk(std::max(kMaxArenaCapacity, aSize)); + return arena ? arena->Allocate(aSize) : nullptr; + } + + ArenaChunk mHead; + ArenaChunk* mCurrent; +}; + +} // namespace mozilla + +#endif // mozilla_ArenaAllocator_h diff --git a/xpcom/ds/ArenaAllocatorExtensions.h b/xpcom/ds/ArenaAllocatorExtensions.h new file mode 100644 index 0000000000..60819381d5 --- /dev/null +++ b/xpcom/ds/ArenaAllocatorExtensions.h @@ -0,0 +1,76 @@ +/* -*- 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_ArenaAllocatorExtensions_h +#define mozilla_ArenaAllocatorExtensions_h + +#include "mozilla/ArenaAllocator.h" +#include "mozilla/CheckedInt.h" +#include "nsAString.h" + +/** + * Extensions to the ArenaAllocator class. + */ +namespace mozilla { + +namespace detail { + +template <typename T, size_t ArenaSize, size_t Alignment> +T* DuplicateString(const T* aSrc, const CheckedInt<size_t>& aLen, + ArenaAllocator<ArenaSize, Alignment>& aArena); + +} // namespace detail + +/** + * Makes an arena allocated null-terminated copy of the source string. The + * source string must be null-terminated. + * + * @param aSrc String to copy. + * @param aArena The arena to allocate the string copy out of. + * @return An arena allocated null-terminated string. + */ +template <typename T, size_t ArenaSize, size_t Alignment> +T* ArenaStrdup(const T* aStr, ArenaAllocator<ArenaSize, Alignment>& aArena) { + return detail::DuplicateString(aStr, nsCharTraits<T>::length(aStr), aArena); +} + +/** + * Makes an arena allocated null-terminated copy of the source string. + * + * @param aSrc String to copy. + * @param aArena The arena to allocate the string copy out of. + * @return An arena allocated null-terminated string. + */ +template <typename T, size_t ArenaSize, size_t Alignment> +T* ArenaStrdup(const detail::nsTStringRepr<T>& aStr, + ArenaAllocator<ArenaSize, Alignment>& aArena) { + return detail::DuplicateString(aStr.BeginReading(), aStr.Length(), aArena); +} + +/** + * Copies the source string and adds a null terminator. Source string does not + * have to be null terminated. + */ +template <typename T, size_t ArenaSize, size_t Alignment> +T* detail::DuplicateString(const T* aSrc, const CheckedInt<size_t>& aLen, + ArenaAllocator<ArenaSize, Alignment>& aArena) { + const auto byteLen = (aLen + 1) * sizeof(T); + if (!byteLen.isValid()) { + return nullptr; + } + + T* p = static_cast<T*>(aArena.Allocate(byteLen.value(), mozilla::fallible)); + if (p) { + memcpy(p, aSrc, byteLen.value() - sizeof(T)); + p[aLen.value()] = T(0); + } + + return p; +} + +} // namespace mozilla + +#endif // mozilla_ArenaAllocatorExtensions_h diff --git a/xpcom/ds/ArrayAlgorithm.h b/xpcom/ds/ArrayAlgorithm.h new file mode 100644 index 0000000000..2556eef465 --- /dev/null +++ b/xpcom/ds/ArrayAlgorithm.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ArrayAlgorithm_h___ +#define ArrayAlgorithm_h___ + +#include "nsTArray.h" + +#include "mozilla/Algorithm.h" +#include "mozilla/ResultExtensions.h" + +namespace mozilla { + +// An algorithm similar to TransformAbortOnErr, which creates a new nsTArray +// rather than inserting into an output iterator. The capacity of the result +// array is set to the number of elements that will be inserted if all +// transformations are successful. This variant is fallible, i.e. if setting the +// capacity fails, NS_ERROR_OUT_OF_MEMORY is returned as an error value. This +// variant only works with nsresult errors. +template < + typename SrcIter, typename Transform, + typename = std::enable_if_t<std::is_same_v< + typename detail::TransformTraits<Transform, SrcIter>::result_err_type, + nsresult>>> +Result<nsTArray<typename detail::TransformTraits<Transform, + SrcIter>::result_ok_type>, + nsresult> +TransformIntoNewArrayAbortOnErr(SrcIter aIter, SrcIter aEnd, + Transform aTransform, fallible_t) { + nsTArray<typename detail::TransformTraits<Transform, SrcIter>::result_ok_type> + res; + if (!res.SetCapacity(std::distance(aIter, aEnd), fallible)) { + return Err(NS_ERROR_OUT_OF_MEMORY); + } + + auto transformRes = TransformAbortOnErr(aIter, aEnd, MakeBackInserter(res), + std::move(aTransform)); + if (NS_WARN_IF(transformRes.isErr())) { + return Err(transformRes.unwrapErr()); + } + + return res; +} + +template <typename SrcRange, typename Transform> +auto TransformIntoNewArrayAbortOnErr(SrcRange& aRange, Transform aTransform, + fallible_t) { + using std::begin; + using std::end; + return TransformIntoNewArrayAbortOnErr(begin(aRange), end(aRange), aTransform, + fallible); +} + +// An algorithm similar to std::transform, which creates a new nsTArray +// rather than inserting into an output iterator. The capacity of the result +// array is set to the number of elements that will be inserted. This variant is +// fallible, i.e. if setting the capacity fails, NS_ERROR_OUT_OF_MEMORY is +// returned as an error value. This variant only works with nsresult errors. +template <typename SrcIter, typename Transform> +Result<nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>>, + nsresult> +TransformIntoNewArray(SrcIter aIter, SrcIter aEnd, Transform aTransform, + fallible_t) { + nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>> res; + if (!res.SetCapacity(std::distance(aIter, aEnd), fallible)) { + return Err(NS_ERROR_OUT_OF_MEMORY); + } + + std::transform(aIter, aEnd, MakeBackInserter(res), std::move(aTransform)); + + return res; +} + +template <typename SrcRange, typename Transform> +auto TransformIntoNewArray(SrcRange& aRange, Transform aTransform, fallible_t) { + using std::begin; + using std::end; + return TransformIntoNewArray(begin(aRange), end(aRange), aTransform, + fallible); +} + +// An algorithm similar to std::transform, which creates a new nsTArray +// rather than inserting into an output iterator. The capacity of the result +// array is set to the number of elements that will be inserted. This variant is +// infallible, i.e. if setting the capacity fails, the process is aborted. +template <typename SrcIter, typename Transform> +nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>> +TransformIntoNewArray(SrcIter aIter, SrcIter aEnd, Transform aTransform) { + nsTArray<detail::ArrayElementTransformType<Transform, SrcIter>> res; + res.SetCapacity(std::distance(aIter, aEnd)); + + std::transform(aIter, aEnd, MakeBackInserter(res), std::move(aTransform)); + + return res; +} + +template <typename SrcRange, typename Transform> +auto TransformIntoNewArray(SrcRange& aRange, Transform aTransform) { + using std::begin; + using std::end; + return TransformIntoNewArray(begin(aRange), end(aRange), aTransform); +} + +} // namespace mozilla + +#endif // !defined(ArrayAlgorithm_h___) diff --git a/xpcom/ds/ArrayIterator.h b/xpcom/ds/ArrayIterator.h new file mode 100644 index 0000000000..cc6c8d1cb4 --- /dev/null +++ b/xpcom/ds/ArrayIterator.h @@ -0,0 +1,165 @@ +/* -*- 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/. */ + +// Common iterator implementation for array classes e.g. nsTArray. + +#ifndef mozilla_ArrayIterator_h +#define mozilla_ArrayIterator_h + +#include <iterator> +#include <type_traits> + +namespace mozilla { + +namespace detail { +template <typename T> +struct AddInnerConst; + +template <typename T> +struct AddInnerConst<T&> { + using Type = const T&; +}; + +template <typename T> +struct AddInnerConst<T*> { + using Type = const T*; +}; + +template <typename T> +using AddInnerConstT = typename AddInnerConst<T>::Type; +} // namespace detail + +// We have implemented a custom iterator class for array rather than using +// raw pointers into the backing storage to improve the safety of C++11-style +// range based iteration in the presence of array mutation, or script execution +// (bug 1299489). +// +// Mutating an array which is being iterated is still wrong, and will either +// cause elements to be missed or firefox to crash, but will not trigger memory +// safety problems due to the release-mode bounds checking found in ElementAt. +// +// Dereferencing this iterator returns type Element. When Element is a reference +// type, this iterator implements the full standard random access iterator spec, +// and can be treated in many ways as though it is a pointer. Otherwise, it is +// just enough to be used in range-based for loop. +template <class Element, class ArrayType> +class ArrayIterator { + public: + typedef ArrayType array_type; + typedef ArrayIterator<Element, ArrayType> iterator_type; + typedef typename array_type::index_type index_type; + typedef std::remove_reference_t<Element> value_type; + typedef ptrdiff_t difference_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef std::random_access_iterator_tag iterator_category; + typedef ArrayIterator<detail::AddInnerConstT<Element>, ArrayType> + const_iterator_type; + + private: + const array_type* mArray; + index_type mIndex; + + public: + ArrayIterator() : mArray(nullptr), mIndex(0) {} + ArrayIterator(const iterator_type& aOther) + : mArray(aOther.mArray), mIndex(aOther.mIndex) {} + ArrayIterator(const array_type& aArray, index_type aIndex) + : mArray(&aArray), mIndex(aIndex) {} + + iterator_type& operator=(const iterator_type& aOther) { + mArray = aOther.mArray; + mIndex = aOther.mIndex; + return *this; + } + + constexpr operator const_iterator_type() const { + return mArray ? const_iterator_type{*mArray, mIndex} + : const_iterator_type{}; + } + + bool operator==(const iterator_type& aRhs) const { + return mIndex == aRhs.mIndex; + } + bool operator!=(const iterator_type& aRhs) const { return !(*this == aRhs); } + bool operator<(const iterator_type& aRhs) const { + return mIndex < aRhs.mIndex; + } + bool operator>(const iterator_type& aRhs) const { + return mIndex > aRhs.mIndex; + } + bool operator<=(const iterator_type& aRhs) const { + return mIndex <= aRhs.mIndex; + } + bool operator>=(const iterator_type& aRhs) const { + return mIndex >= aRhs.mIndex; + } + + // These operators depend on the release mode bounds checks in + // ArrayIterator::ElementAt for safety. + value_type* operator->() const { + return const_cast<value_type*>(&mArray->ElementAt(mIndex)); + } + Element operator*() const { + return const_cast<Element>(mArray->ElementAt(mIndex)); + } + + iterator_type& operator++() { + ++mIndex; + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } + iterator_type& operator--() { + --mIndex; + return *this; + } + iterator_type operator--(int) { + iterator_type it = *this; + --*this; + return it; + } + + iterator_type& operator+=(difference_type aDiff) { + mIndex += aDiff; + return *this; + } + iterator_type& operator-=(difference_type aDiff) { + mIndex -= aDiff; + return *this; + } + + iterator_type operator+(difference_type aDiff) const { + iterator_type it = *this; + it += aDiff; + return it; + } + iterator_type operator-(difference_type aDiff) const { + iterator_type it = *this; + it -= aDiff; + return it; + } + + difference_type operator-(const iterator_type& aOther) const { + return static_cast<difference_type>(mIndex) - + static_cast<difference_type>(aOther.mIndex); + } + + Element operator[](difference_type aIndex) const { + return *this->operator+(aIndex); + } + + constexpr const array_type* GetArray() const { return mArray; } + + constexpr index_type GetIndex() const { return mIndex; } +}; + +} // namespace mozilla + +#endif // mozilla_ArrayIterator_h diff --git a/xpcom/ds/Atom.py b/xpcom/ds/Atom.py new file mode 100644 index 0000000000..1399ba840a --- /dev/null +++ b/xpcom/ds/Atom.py @@ -0,0 +1,64 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +class Atom: + def __init__(self, ident, string, ty="nsStaticAtom"): + self.ident = ident + self.string = string + self.ty = ty + self.atom_type = self.__class__.__name__ + self.hash = hash_string(string) + self.is_ascii_lowercase = is_ascii_lowercase(string) + + +class PseudoElementAtom(Atom): + def __init__(self, ident, string): + Atom.__init__(self, ident, string, ty="nsCSSPseudoElementStaticAtom") + + +class AnonBoxAtom(Atom): + def __init__(self, ident, string): + Atom.__init__(self, ident, string, ty="nsCSSAnonBoxPseudoStaticAtom") + + +class NonInheritingAnonBoxAtom(AnonBoxAtom): + def __init__(self, ident, string): + AnonBoxAtom.__init__(self, ident, string) + + +class InheritingAnonBoxAtom(AnonBoxAtom): + def __init__(self, ident, string): + AnonBoxAtom.__init__(self, ident, string) + + +GOLDEN_RATIO_U32 = 0x9E3779B9 + + +def rotate_left_5(value): + return ((value << 5) | (value >> 27)) & 0xFFFFFFFF + + +def wrapping_multiply(x, y): + return (x * y) & 0xFFFFFFFF + + +# Calculate the precomputed hash of the static atom. This is a port of +# mozilla::HashString(const char16_t*), which is what we use for atomizing +# strings. An assertion in nsAtomTable::RegisterStaticAtoms ensures that +# the value we compute here matches what HashString() would produce. +def hash_string(s): + h = 0 + for c in s: + h = wrapping_multiply(GOLDEN_RATIO_U32, rotate_left_5(h) ^ ord(c)) + return h + + +# Returns true if lowercasing this string in an ascii-case-insensitive way +# would leave the string unchanged. +def is_ascii_lowercase(s): + for c in s: + if c >= "A" and c <= "Z": + return False + return True diff --git a/xpcom/ds/AtomArray.h b/xpcom/ds/AtomArray.h new file mode 100644 index 0000000000..5b1e0a0eae --- /dev/null +++ b/xpcom/ds/AtomArray.h @@ -0,0 +1,19 @@ +/* -*- 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_AtomArray_h +#define mozilla_AtomArray_h + +#include "mozilla/RefPtr.h" +#include "nsTArray.h" + +class nsAtom; + +namespace mozilla { +typedef AutoTArray<RefPtr<nsAtom>, 4> AtomArray; +} + +#endif // mozilla_AtomArray_h diff --git a/xpcom/ds/Dafsa.cpp b/xpcom/ds/Dafsa.cpp new file mode 100644 index 0000000000..8854e0ff1d --- /dev/null +++ b/xpcom/ds/Dafsa.cpp @@ -0,0 +1,153 @@ +/* -*- 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/. */ + +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "Dafsa.h" + +#include "mozilla/Assertions.h" +#include "nsAString.h" + +const int mozilla::Dafsa::kKeyNotFound = -1; + +// Note the DAFSA implementation was lifted from eTLD code in Chromium that was +// originally lifted from Firefox. + +// Read next offset from pos. +// Returns true if an offset could be read, false otherwise. +static bool GetNextOffset(const unsigned char** pos, const unsigned char* end, + const unsigned char** offset) { + if (*pos == end) return false; + + // When reading an offset the byte array must always contain at least + // three more bytes to consume. First the offset to read, then a node + // to skip over and finally a destination node. No object can be smaller + // than one byte. + MOZ_ASSERT(*pos + 2 < end); + size_t bytes_consumed; + switch (**pos & 0x60) { + case 0x60: // Read three byte offset + *offset += (((*pos)[0] & 0x1F) << 16) | ((*pos)[1] << 8) | (*pos)[2]; + bytes_consumed = 3; + break; + case 0x40: // Read two byte offset + *offset += (((*pos)[0] & 0x1F) << 8) | (*pos)[1]; + bytes_consumed = 2; + break; + default: + *offset += (*pos)[0] & 0x3F; + bytes_consumed = 1; + } + if ((**pos & 0x80) != 0) { + *pos = end; + } else { + *pos += bytes_consumed; + } + return true; +} + +// Check if byte at offset is last in label. +static bool IsEOL(const unsigned char* offset, const unsigned char* end) { + MOZ_ASSERT(offset < end); + return (*offset & 0x80) != 0; +} + +// Check if byte at offset matches first character in key. +// This version matches characters not last in label. +static bool IsMatch(const unsigned char* offset, const unsigned char* end, + const char* key) { + MOZ_ASSERT(offset < end); + return *offset == *key; +} + +// Check if byte at offset matches first character in key. +// This version matches characters last in label. +static bool IsEndCharMatch(const unsigned char* offset, + const unsigned char* end, const char* key) { + MOZ_ASSERT(offset < end); + return *offset == (*key | 0x80); +} + +// Read return value at offset. +// Returns true if a return value could be read, false otherwise. +static bool GetReturnValue(const unsigned char* offset, + const unsigned char* end, int* return_value) { + MOZ_ASSERT(offset < end); + if ((*offset & 0xE0) == 0x80) { + *return_value = *offset & 0x0F; + return true; + } + return false; +} + +// Lookup a domain key in a byte array generated by make_dafsa.py. +// The rule type is returned if key is found, otherwise kKeyNotFound is +// returned. +static int LookupString(const unsigned char* graph, size_t length, + const char* key, size_t key_length) { + const unsigned char* pos = graph; + const unsigned char* end = graph + length; + const unsigned char* offset = pos; + const char* key_end = key + key_length; + while (GetNextOffset(&pos, end, &offset)) { + // char <char>+ end_char offsets + // char <char>+ return value + // char end_char offsets + // char return value + // end_char offsets + // return_value + bool did_consume = false; + if (key != key_end && !IsEOL(offset, end)) { + // Leading <char> is not a match. Don't dive into this child + if (!IsMatch(offset, end, key)) continue; + did_consume = true; + ++offset; + ++key; + // Possible matches at this point: + // <char>+ end_char offsets + // <char>+ return value + // end_char offsets + // return value + // Remove all remaining <char> nodes possible + while (!IsEOL(offset, end) && key != key_end) { + if (!IsMatch(offset, end, key)) return mozilla::Dafsa::kKeyNotFound; + ++key; + ++offset; + } + } + // Possible matches at this point: + // end_char offsets + // return_value + // If one or more <char> elements were consumed, a failure + // to match is terminal. Otherwise, try the next node. + if (key == key_end) { + int return_value; + if (GetReturnValue(offset, end, &return_value)) return return_value; + // The DAFSA guarantees that if the first char is a match, all + // remaining char elements MUST match if the key is truly present. + if (did_consume) return mozilla::Dafsa::kKeyNotFound; + continue; + } + if (!IsEndCharMatch(offset, end, key)) { + if (did_consume) return mozilla::Dafsa::kKeyNotFound; // Unexpected + continue; + } + ++key; + pos = ++offset; // Dive into child + } + return mozilla::Dafsa::kKeyNotFound; // No match +} + +namespace mozilla { + +int Dafsa::Lookup(const nsACString& aKey) const { + return LookupString(mData.Elements(), mData.Length(), aKey.BeginReading(), + aKey.Length()); +} + +} // namespace mozilla diff --git a/xpcom/ds/Dafsa.h b/xpcom/ds/Dafsa.h new file mode 100644 index 0000000000..1a5584f632 --- /dev/null +++ b/xpcom/ds/Dafsa.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 mozilla_Dafsa_h +#define mozilla_Dafsa_h + +#include "stdint.h" + +#include "mozilla/Span.h" +#include "nsStringFwd.h" + +namespace mozilla { + +/** + * A deterministic acyclic finite state automaton suitable for storing static + * dictionaries of tagged ascii strings. Consider using this if you have a very + * large set of strings that need an associated enum value. + * + * Currently the string tag is limited by `make_dafsa.py` to a value of [0-4]. + * In theory [0-15] can easily be supported. + * + * See `make_dafsa.py` for more details. + */ +class Dafsa { + public: + using Graph = Span<const uint8_t>; + + /** + * Initializes the DAFSA with a binary encoding generated by `make_dafsa.py`. + */ + explicit Dafsa(const Graph& aData) : mData(aData) {} + + ~Dafsa() = default; + + /** + * Searches for the given string in the DAFSA. + * + * @param aKey The string to search for. + * @returns kKeyNotFound if not found, otherwise the associated tag. + */ + int Lookup(const nsACString& aKey) const; + + static const int kKeyNotFound; + + private: + const Graph mData; +}; + +} // namespace mozilla + +#endif // mozilla_Dafsa_h diff --git a/xpcom/ds/HTMLAtoms.py b/xpcom/ds/HTMLAtoms.py new file mode 100644 index 0000000000..ddaf52ae9f --- /dev/null +++ b/xpcom/ds/HTMLAtoms.py @@ -0,0 +1,261 @@ +# THIS FILE IS GENERATED BY THE HTML PARSER TRANSLATOR AND WILL BE OVERWRITTEN! +from Atom import Atom + +HTML_PARSER_ATOMS = [ + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink", "xlink"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xml_space", "xml:space"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xml_lang", "xml:lang"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_grab", "aria-grab"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_channel", "aria-channel"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_secret", "aria-secret"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_templateid", "aria-templateid"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_datatype", "aria-datatype"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("local", "local"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xchannelselector", "xchannelselector"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("ychannelselector", "ychannelselector"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("enable_background", "enable-background"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("calcmode", "calcmode"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("specularexponent", "specularexponent"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("specularconstant", "specularconstant"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("gradienttransform", "gradienttransform"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("gradientunits", "gradientunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("rendering_intent", "rendering-intent"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("shadowrootmode", "shadowrootmode"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("stddeviation", "stddeviation"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("shadowrootdelegatesfocus", "shadowrootdelegatesfocus"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("basefrequency", "basefrequency"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("baseprofile", "baseprofile"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("baseProfile", "baseProfile"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("edgemode", "edgemode"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("repeatcount", "repeatcount"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("repeatdur", "repeatdur"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("spreadmethod", "spreadmethod"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("diffuseconstant", "diffuseconstant"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("surfacescale", "surfacescale"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("lengthadjust", "lengthadjust"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("origin", "origin"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("targetx", "targetx"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("targety", "targety"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pathlength", "pathlength"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("definitionurl", "definitionurl"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("limitingconeangle", "limitingconeangle"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("markerheight", "markerheight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("markerwidth", "markerwidth"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("maskunits", "maskunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("markerunits", "markerunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("maskcontentunits", "maskcontentunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("tablevalues", "tablevalues"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("primitiveunits", "primitiveunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("zoomandpan", "zoomandpan"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("kernelmatrix", "kernelmatrix"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("kerning", "kerning"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("kernelunitlength", "kernelunitlength"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pointsatx", "pointsatx"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pointsaty", "pointsaty"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pointsatz", "pointsatz"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_href", "xlink:href"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_title", "xlink:title"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_role", "xlink:role"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_arcrole", "xlink:arcrole"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("arcrole", "arcrole"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xmlns_xlink", "xmlns:xlink"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_type", "xlink:type"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_show", "xlink:show"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_actuate", "xlink:actuate"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("color_rendering", "color-rendering"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("numoctaves", "numoctaves"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("onmousewheel", "onmousewheel"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("clippathunits", "clippathunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("glyph_orientation_vertical", "glyph-orientation-vertical"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("glyph_orientation_horizontal", "glyph-orientation-horizontal"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("glyphref", "glyphref"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("keypoints", "keypoints"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("attributename", "attributename"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("attributetype", "attributetype"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("startoffset", "startoffset"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("keysplines", "keysplines"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("preservealpha", "preservealpha"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("preserveaspectratio", "preserveaspectratio"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("alttext", "alttext"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("filterunits", "filterunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("keytimes", "keytimes"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("patterntransform", "patterntransform"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("patternunits", "patternunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("patterncontentunits", "patterncontentunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("stitchtiles", "stitchtiles"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("systemlanguage", "systemlanguage"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("textlength", "textlength"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("requiredfeatures", "requiredfeatures"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("requiredextensions", "requiredextensions"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("viewtarget", "viewtarget"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("viewbox", "viewbox"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("refx", "refx"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("refy", "refy"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefunca", "fefunca"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefuncb", "fefuncb"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feblend", "feblend"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feflood", "feflood"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feturbulence", "feturbulence"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("femergenode", "femergenode"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feimage", "feimage"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("femerge", "femerge"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fetile", "fetile"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fecomposite", "fecomposite"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altglyphdef", "altglyphdef"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altGlyphDef", "altGlyphDef"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefuncg", "fefuncg"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fediffuselighting", "fediffuselighting"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fespecularlighting", "fespecularlighting"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altglyph", "altglyph"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altGlyph", "altGlyph"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("clippath", "clippath"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("textpath", "textpath"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altglyphitem", "altglyphitem"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altGlyphItem", "altGlyphItem"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("animatetransform", "animatetransform"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("animatemotion", "animatemotion"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fedisplacementmap", "fedisplacementmap"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("animatecolor", "animatecolor"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefuncr", "fefuncr"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fecomponenttransfer", "fecomponenttransfer"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fegaussianblur", "fegaussianblur"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("foreignobject", "foreignobject"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feoffset", "feoffset"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fespotlight", "fespotlight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fepointlight", "fepointlight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fedistantlight", "fedistantlight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("lineargradient", "lineargradient"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("radialgradient", "radialgradient"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fedropshadow", "fedropshadow"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fecolormatrix", "fecolormatrix"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feconvolvematrix", "feconvolvematrix"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("femorphology", "femorphology"), +] diff --git a/xpcom/ds/IncrementalTokenizer.cpp b/xpcom/ds/IncrementalTokenizer.cpp new file mode 100644 index 0000000000..db6fddb82b --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.cpp @@ -0,0 +1,190 @@ +/* -*- 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/IncrementalTokenizer.h" + +#include "mozilla/AutoRestore.h" + +#include "nsIInputStream.h" +#include "IncrementalTokenizer.h" +#include <algorithm> + +namespace mozilla { + +IncrementalTokenizer::IncrementalTokenizer(Consumer&& aConsumer, + const char* aWhitespaces, + const char* aAdditionalWordChars, + uint32_t aRawMinBuffered) + : TokenizerBase(aWhitespaces, aAdditionalWordChars) +#ifdef DEBUG + , + mConsuming(false) +#endif + , + mNeedMoreInput(false), + mRollback(false), + mInputCursor(0), + mConsumer(std::move(aConsumer)) { + mInputFinished = false; + mMinRawDelivery = aRawMinBuffered; +} + +nsresult IncrementalTokenizer::FeedInput(const nsACString& aInput) { + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + mInput.Append(aInput); + + return Process(); +} + +nsresult IncrementalTokenizer::FeedInput(nsIInputStream* aInput, + uint32_t aCount) { + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + MOZ_ASSERT(!mConsuming); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv) && aCount) { + nsCString::index_type remainder = mInput.Length(); + nsCString::index_type load = + std::min<nsCString::index_type>(aCount, PR_UINT32_MAX - remainder); + + if (!load) { + // To keep the API simple, we fail if the input data buffer if filled. + // It's highly unlikely there will ever be such amout of data cumulated + // unless a logic fault in the consumer code. + NS_ERROR("IncrementalTokenizer consumer not reading data?"); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!mInput.SetLength(remainder + load, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + auto buffer = mInput.BeginWriting() + remainder; + + uint32_t read; + rv = aInput->Read(buffer, load, &read); + if (NS_SUCCEEDED(rv)) { + // remainder + load fits the uint32_t size, so must remainder + read. + mInput.SetLength(remainder + read); + aCount -= read; + + rv = Process(); + } + } + + return rv; +} + +nsresult IncrementalTokenizer::FinishInput() { + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + MOZ_ASSERT(!mConsuming); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + mInputFinished = true; + nsresult rv = Process(); + mConsumer = nullptr; + return rv; +} + +bool IncrementalTokenizer::Next(Token& aToken) { + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + if (mPastEof) { + return false; + } + + nsACString::const_char_iterator next = Parse(aToken); + mPastEof = aToken.Type() == TOKEN_EOF; + if (next == mCursor && !mPastEof) { + // Not enough input to make a deterministic decision. + return false; + } + + AssignFragment(aToken, mCursor, next); + mCursor = next; + return true; +} + +void IncrementalTokenizer::NeedMoreInput() { + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + // When the input has been finished, we can't set the flag to prevent + // indefinite wait for more input (that will never come) + mNeedMoreInput = !mInputFinished; +} + +void IncrementalTokenizer::Rollback() { + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + mRollback = true; +} + +nsresult IncrementalTokenizer::Process() { +#ifdef DEBUG + // Assert we are not re-entered + MOZ_ASSERT(!mConsuming); + + AutoRestore<bool> consuming(mConsuming); + mConsuming = true; +#endif + + MOZ_ASSERT(!mPastEof); + + nsresult rv = NS_OK; + + mInput.BeginReading(mCursor); + mCursor += mInputCursor; + mInput.EndReading(mEnd); + + while (NS_SUCCEEDED(rv) && !mPastEof) { + Token token; + nsACString::const_char_iterator next = Parse(token); + mPastEof = token.Type() == TOKEN_EOF; + if (next == mCursor && !mPastEof) { + // Not enough input to make a deterministic decision. + break; + } + + AssignFragment(token, mCursor, next); + + nsACString::const_char_iterator rollback = mCursor; + mCursor = next; + + mNeedMoreInput = mRollback = false; + + rv = mConsumer(token, *this); + if (NS_FAILED(rv)) { + break; + } + if (mNeedMoreInput || mRollback) { + mCursor = rollback; + mPastEof = false; + if (mNeedMoreInput) { + break; + } + } + } + + mInputCursor = mCursor - mInput.BeginReading(); + return rv; +} + +} // namespace mozilla diff --git a/xpcom/ds/IncrementalTokenizer.h b/xpcom/ds/IncrementalTokenizer.h new file mode 100644 index 0000000000..c2647052c9 --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.h @@ -0,0 +1,125 @@ +/* -*- 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 INCREMENTAL_TOKENIZER_H__ +#define INCREMENTAL_TOKENIZER_H__ + +#include "mozilla/Tokenizer.h" + +#include "nsError.h" +#include <functional> + +class nsIInputStream; + +namespace mozilla { + +class IncrementalTokenizer : public TokenizerBase<char> { + public: + /** + * The consumer callback. The function is called for every single token + * as found in the input. Failure result returned by this callback stops + * the tokenization immediately and bubbles to result of Feed/FinishInput. + * + * Fragment()s of consumed tokens are ensured to remain valid until next call + * to Feed/FinishInput and are pointing to a single linear buffer. Hence, + * those can be safely used to accumulate the data for processing after + * Feed/FinishInput returned. + */ + typedef std::function<nsresult(Token const&, IncrementalTokenizer& i)> + Consumer; + + /** + * For aWhitespaces and aAdditionalWordChars arguments see TokenizerBase. + * + * @param aConsumer + * A mandatory non-null argument, a function that consumes the tokens as + * they come when the tokenizer is fed. + * @param aRawMinBuffered + * When we have buffered at least aRawMinBuffered data, but there was no + * custom token found so far because of too small incremental feed chunks, + * deliver the raw data to preserve streaming and to save memory. This only + * has effect in OnlyCustomTokenizing mode. + */ + explicit IncrementalTokenizer(Consumer&& aConsumer, + const char* aWhitespaces = nullptr, + const char* aAdditionalWordChars = nullptr, + uint32_t aRawMinBuffered = 1024); + + /** + * Pushes the input to be tokenized. These directly call the Consumer + * callback on every found token. Result of the Consumer callback is returned + * here. + * + * The tokenizer must be initialized with a valid consumer prior call to these + * methods. It's not allowed to call Feed/FinishInput from inside the + * Consumer callback. + */ + nsresult FeedInput(const nsACString& aInput); + nsresult FeedInput(nsIInputStream* aInput, uint32_t aCount); + nsresult FinishInput(); + + /** + * Can only be called from inside the consumer callback. + * + * When there is still anything to read from the input, tokenize it, store + * the token type and value to aToken result and shift the cursor past this + * just parsed token. Each call to Next() reads another token from + * the input and shifts the cursor. + * + * Returns false if there is not enough data to deterministically recognize + * tokens or when the last returned token was EOF. + */ + [[nodiscard]] bool Next(Token& aToken); + + /** + * Can only be called from inside the consumer callback. + * + * Tells the tokenizer to revert the cursor and stop the async parsing until + * next feed of the input. This is useful when more than one token is needed + * to decide on the syntax but there is not enough input to get a next token + * (Next() returned false.) + */ + void NeedMoreInput(); + + /** + * Can only be called from inside the consumer callback. + * + * This makes the consumer callback be called again while parsing + * the input at the previous cursor position again. This is useful when + * the tokenizer state (custom tokens, tokenization mode) has changed and + * we want to re-parse the input again. + */ + void Rollback(); + + private: + // Loops over the input with TokenizerBase::Parse and calls the Consumer + // callback. + nsresult Process(); + +#ifdef DEBUG + // True when inside the consumer callback, used only for assertions. + bool mConsuming; +#endif // DEBUG + // Modifyable only from the Consumer callback, tells the parser to break, + // rollback and wait for more input. + bool mNeedMoreInput; + // Modifyable only from the Consumer callback, tells the parser to rollback + // and parse the input again, with (if modified) new settings of the + // tokenizer. + bool mRollback; + // The input buffer. Updated with each call to Feed/FinishInput. + nsCString mInput; + // Numerical index pointing at the current cursor position. We don't keep + // direct reference to the string buffer since the buffer gets often + // reallocated. + nsCString::index_type mInputCursor; + // Refernce to the consumer function. + Consumer mConsumer; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/ds/Observer.h b/xpcom/ds/Observer.h new file mode 100644 index 0000000000..3d189603eb --- /dev/null +++ b/xpcom/ds/Observer.h @@ -0,0 +1,76 @@ +/* -*- 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_Observer_h +#define mozilla_Observer_h + +#include "nsTObserverArray.h" + +namespace mozilla { + +/** + * Observer<T> provides a way for a class to observe something. + * When an event has to be broadcasted to all Observer<T>, Notify() method + * is called. + * T represents the type of the object passed in argument to Notify(). + * + * @see ObserverList. + */ +template <class T> +class Observer { + public: + virtual ~Observer() = default; + virtual void Notify(const T& aParam) = 0; +}; + +/** + * ObserverList<T> tracks Observer<T> and can notify them when Broadcast() is + * called. + * T represents the type of the object passed in argument to Broadcast() and + * sent to Observer<T> objects through Notify(). + * + * @see Observer. + */ +template <class T> +class ObserverList { + public: + /** + * Note: When calling AddObserver, it's up to the caller to make sure the + * object isn't going to be release as long as RemoveObserver hasn't been + * called. + * + * @see RemoveObserver() + */ + void AddObserver(Observer<T>* aObserver) { + mObservers.AppendElementUnlessExists(aObserver); + } + + /** + * Remove the observer from the observer list. + * @return Whether the observer has been found in the list. + */ + bool RemoveObserver(Observer<T>* aObserver) { + return mObservers.RemoveElement(aObserver); + } + + uint32_t Length() { return mObservers.Length(); } + + /** + * Call Notify() on each item in the list. + */ + void Broadcast(const T& aParam) { + for (Observer<T>* obs : mObservers.ForwardRange()) { + obs->Notify(aParam); + } + } + + protected: + nsTObserverArray<Observer<T>*> mObservers; +}; + +} // namespace mozilla + +#endif // mozilla_Observer_h diff --git a/xpcom/ds/PLDHashTable.cpp b/xpcom/ds/PLDHashTable.cpp new file mode 100644 index 0000000000..75a512ba7a --- /dev/null +++ b/xpcom/ds/PLDHashTable.cpp @@ -0,0 +1,869 @@ +/* -*- 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 <new> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "PLDHashTable.h" +#include "nsDebug.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/ScopeExit.h" +#include "nsAlgorithm.h" +#include "nsPointerHashKeys.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Maybe.h" +#include "mozilla/ChaosMode.h" + +using namespace mozilla; + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + +class AutoReadOp { + Checker& mChk; + + public: + explicit AutoReadOp(Checker& aChk) : mChk(aChk) { mChk.StartReadOp(); } + ~AutoReadOp() { mChk.EndReadOp(); } +}; + +class AutoWriteOp { + Checker& mChk; + + public: + explicit AutoWriteOp(Checker& aChk) : mChk(aChk) { mChk.StartWriteOp(); } + ~AutoWriteOp() { mChk.EndWriteOp(); } +}; + +class AutoIteratorRemovalOp { + Checker& mChk; + + public: + explicit AutoIteratorRemovalOp(Checker& aChk) : mChk(aChk) { + mChk.StartIteratorRemovalOp(); + } + ~AutoIteratorRemovalOp() { mChk.EndIteratorRemovalOp(); } +}; + +class AutoDestructorOp { + Checker& mChk; + + public: + explicit AutoDestructorOp(Checker& aChk) : mChk(aChk) { + mChk.StartDestructorOp(); + } + ~AutoDestructorOp() { mChk.EndDestructorOp(); } +}; + +#endif + +/* static */ +PLDHashNumber PLDHashTable::HashStringKey(const void* aKey) { + return HashString(static_cast<const char*>(aKey)); +} + +/* static */ +PLDHashNumber PLDHashTable::HashVoidPtrKeyStub(const void* aKey) { + return nsPtrHashKey<void>::HashKey(aKey); +} + +/* static */ +bool PLDHashTable::MatchEntryStub(const PLDHashEntryHdr* aEntry, + const void* aKey) { + const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry; + + return stub->key == aKey; +} + +/* static */ +bool PLDHashTable::MatchStringKey(const PLDHashEntryHdr* aEntry, + const void* aKey) { + const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry; + + // XXX tolerate null keys on account of sloppy Mozilla callers. + return stub->key == aKey || + (stub->key && aKey && + strcmp((const char*)stub->key, (const char*)aKey) == 0); +} + +/* static */ +void PLDHashTable::MoveEntryStub(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + memcpy(aTo, aFrom, aTable->mEntrySize); +} + +/* static */ +void PLDHashTable::ClearEntryStub(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) { + memset(aEntry, 0, aTable->mEntrySize); +} + +static const PLDHashTableOps gStubOps = { + PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub, + PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub, nullptr}; + +/* static */ const PLDHashTableOps* PLDHashTable::StubOps() { + return &gStubOps; +} + +static bool SizeOfEntryStore(uint32_t aCapacity, uint32_t aEntrySize, + uint32_t* aNbytes) { + uint32_t slotSize = aEntrySize + sizeof(PLDHashNumber); + uint64_t nbytes64 = uint64_t(aCapacity) * uint64_t(slotSize); + *aNbytes = aCapacity * slotSize; + return uint64_t(*aNbytes) == nbytes64; // returns false on overflow +} + +// Compute max and min load numbers (entry counts). We have a secondary max +// that allows us to overload a table reasonably if it cannot be grown further +// (i.e. if ChangeTable() fails). The table slows down drastically if the +// secondary max is too close to 1, but 0.96875 gives only a slight slowdown +// while allowing 1.3x more elements. +static inline uint32_t MaxLoad(uint32_t aCapacity) { + return aCapacity - (aCapacity >> 2); // == aCapacity * 0.75 +} +static inline uint32_t MaxLoadOnGrowthFailure(uint32_t aCapacity) { + return aCapacity - (aCapacity >> 5); // == aCapacity * 0.96875 +} +static inline uint32_t MinLoad(uint32_t aCapacity) { + return aCapacity >> 2; // == aCapacity * 0.25 +} + +// Compute the minimum capacity (and the Log2 of that capacity) for a table +// containing |aLength| elements while respecting the following contraints: +// - table must be at most 75% full; +// - capacity must be a power of two; +// - capacity cannot be too small. +static inline void BestCapacity(uint32_t aLength, uint32_t* aCapacityOut, + uint32_t* aLog2CapacityOut) { + // Callers should ensure this is true. + MOZ_ASSERT(aLength <= PLDHashTable::kMaxInitialLength); + + // Compute the smallest capacity allowing |aLength| elements to be inserted + // without rehashing. + uint32_t capacity = (aLength * 4 + (3 - 1)) / 3; // == ceil(aLength * 4 / 3) + if (capacity < PLDHashTable::kMinCapacity) { + capacity = PLDHashTable::kMinCapacity; + } + + // Round up capacity to next power-of-two. + uint32_t log2 = CeilingLog2(capacity); + capacity = 1u << log2; + MOZ_ASSERT(capacity <= PLDHashTable::kMaxCapacity); + + *aCapacityOut = capacity; + *aLog2CapacityOut = log2; +} + +/* static */ MOZ_ALWAYS_INLINE uint32_t +PLDHashTable::HashShift(uint32_t aEntrySize, uint32_t aLength) { + if (aLength > kMaxInitialLength) { + MOZ_CRASH("Initial length is too large"); + } + + uint32_t capacity, log2; + BestCapacity(aLength, &capacity, &log2); + + uint32_t nbytes; + if (!SizeOfEntryStore(capacity, aEntrySize, &nbytes)) { + MOZ_CRASH("Initial entry store size is too large"); + } + + // Compute the hashShift value. + return kPLDHashNumberBits - log2; +} + +PLDHashTable::PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, + uint32_t aLength) + : mOps(aOps), + mGeneration(0), + mHashShift(HashShift(aEntrySize, aLength)), + mEntrySize(aEntrySize), + mEntryCount(0), + mRemovedCount(0) { + // An entry size greater than 0xff is unlikely, but let's check anyway. If + // you hit this, your hashtable would waste lots of space for unused entries + // and you should change your hash table's entries to pointers. + if (aEntrySize != uint32_t(mEntrySize)) { + MOZ_CRASH("Entry size is too large"); + } +} + +PLDHashTable& PLDHashTable::operator=(PLDHashTable&& aOther) { + if (this == &aOther) { + return *this; + } + + // |mOps| and |mEntrySize| are required to stay the same, they're + // conceptually part of the type -- indeed, if PLDHashTable was a templated + // type like nsTHashtable, they *would* be part of the type -- so it only + // makes sense to assign in cases where they match. + MOZ_RELEASE_ASSERT(mOps == aOther.mOps || !mOps); + MOZ_RELEASE_ASSERT(mEntrySize == aOther.mEntrySize || !mEntrySize); + + // Reconstruct |this|. + const PLDHashTableOps* ops = aOther.mOps; + this->~PLDHashTable(); + new (KnownNotNull, this) PLDHashTable(ops, aOther.mEntrySize, 0); + + // Move non-const pieces over. + mHashShift = std::move(aOther.mHashShift); + mEntryCount = std::move(aOther.mEntryCount); + mRemovedCount = std::move(aOther.mRemovedCount); + mEntryStore.Set(aOther.mEntryStore.Get(), &mGeneration); +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mChecker = std::move(aOther.mChecker); +#endif + + // Clear up |aOther| so its destruction will be a no-op and it reports being + // empty. + { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoDestructorOp op(mChecker); +#endif + aOther.mEntryCount = 0; + aOther.mEntryStore.Set(nullptr, &aOther.mGeneration); + } + + return *this; +} + +PLDHashNumber PLDHashTable::Hash1(PLDHashNumber aHash0) const { + return aHash0 >> mHashShift; +} + +void PLDHashTable::Hash2(PLDHashNumber aHash0, uint32_t& aHash2Out, + uint32_t& aSizeMaskOut) const { + uint32_t sizeLog2 = kPLDHashNumberBits - mHashShift; + uint32_t sizeMask = (PLDHashNumber(1) << sizeLog2) - 1; + aSizeMaskOut = sizeMask; + + // The incoming aHash0 always has the low bit unset (since we leave it + // free for the collision flag), and should have reasonably random + // data in the other 31 bits. We used the high bits of aHash0 for + // Hash1, so we use the low bits here. If the table size is large, + // the bits we use may overlap, but that's still more random than + // filling with 0s. + // + // Double hashing needs the second hash code to be relatively prime to table + // size, so we simply make hash2 odd. + // + // This also conveniently covers up the fact that we have the low bit + // unset since aHash0 has the low bit unset. + aHash2Out = (aHash0 & sizeMask) | 1; +} + +// Reserve mKeyHash 0 for free entries and 1 for removed-entry sentinels. Note +// that a removed-entry sentinel need be stored only if the removed entry had +// a colliding entry added after it. Therefore we can use 1 as the collision +// flag in addition to the removed-entry sentinel value. Multiplicative hash +// uses the high order bits of mKeyHash, so this least-significant reservation +// should not hurt the hash function's effectiveness much. + +// Match an entry's mKeyHash against an unstored one computed from a key. +/* static */ +bool PLDHashTable::MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aKeyHash) { + return (aSlot.KeyHash() & ~kCollisionFlag) == aKeyHash; +} + +// Compute the address of the indexed entry in table. +auto PLDHashTable::SlotForIndex(uint32_t aIndex) const -> Slot { + return mEntryStore.SlotForIndex(aIndex, mEntrySize, CapacityFromHashShift()); +} + +PLDHashTable::~PLDHashTable() { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoDestructorOp op(mChecker); +#endif + + if (!mEntryStore.IsAllocated()) { + return; + } + + // Clear any remaining live entries (if not trivially destructible). + if (mOps->clearEntry) { + mEntryStore.ForEachSlot(Capacity(), mEntrySize, [&](const Slot& aSlot) { + if (aSlot.IsLive()) { + mOps->clearEntry(this, aSlot.ToEntry()); + } + }); + } + + // Entry storage is freed last, by ~EntryStore(). +} + +void PLDHashTable::ClearAndPrepareForLength(uint32_t aLength) { + // Get these values before the destructor clobbers them. + const PLDHashTableOps* ops = mOps; + uint32_t entrySize = mEntrySize; + + this->~PLDHashTable(); + new (KnownNotNull, this) PLDHashTable(ops, entrySize, aLength); +} + +void PLDHashTable::Clear() { ClearAndPrepareForLength(kDefaultInitialLength); } + +// If |Reason| is |ForAdd|, the return value is always non-null and it may be +// a previously-removed entry. If |Reason| is |ForSearchOrRemove|, the return +// value is null on a miss, and will never be a previously-removed entry on a +// hit. This distinction is a bit grotty but this function is hot enough that +// these differences are worthwhile. (It's also hot enough that +// MOZ_ALWAYS_INLINE makes a significant difference.) +template <PLDHashTable::SearchReason Reason, typename Success, typename Failure> +MOZ_ALWAYS_INLINE auto PLDHashTable::SearchTable(const void* aKey, + PLDHashNumber aKeyHash, + Success&& aSuccess, + Failure&& aFailure) const { + MOZ_ASSERT(mEntryStore.IsAllocated()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + Slot slot = SlotForIndex(hash1); + + // Miss: return space for a new entry. + if (slot.IsFree()) { + return (Reason == ForAdd) ? aSuccess(slot) : aFailure(); + } + + // Hit: return entry. + PLDHashMatchEntry matchEntry = mOps->matchEntry; + if (MatchSlotKeyhash(slot, aKeyHash)) { + PLDHashEntryHdr* e = slot.ToEntry(); + if (matchEntry(e, aKey)) { + return aSuccess(slot); + } + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + // Save the first removed entry slot so Add() can recycle it. (Only used + // if Reason==ForAdd.) + Maybe<Slot> firstRemoved; + + for (;;) { + if (Reason == ForAdd && !firstRemoved) { + if (MOZ_UNLIKELY(slot.IsRemoved())) { + firstRemoved.emplace(slot); + } else { + slot.MarkColliding(); + } + } + + hash1 -= hash2; + hash1 &= sizeMask; + + slot = SlotForIndex(hash1); + if (slot.IsFree()) { + if (Reason != ForAdd) { + return aFailure(); + } + return aSuccess(firstRemoved.refOr(slot)); + } + + if (MatchSlotKeyhash(slot, aKeyHash)) { + PLDHashEntryHdr* e = slot.ToEntry(); + if (matchEntry(e, aKey)) { + return aSuccess(slot); + } + } + } + + // NOTREACHED + return aFailure(); +} + +// This is a copy of SearchTable(), used by ChangeTable(), hardcoded to +// 1. assume |Reason| is |ForAdd|, +// 2. assume that |aKey| will never match an existing entry, and +// 3. assume that no entries have been removed from the current table +// structure. +// Avoiding the need for |aKey| means we can avoid needing a way to map entries +// to keys, which means callers can use complex key types more easily. +MOZ_ALWAYS_INLINE auto PLDHashTable::FindFreeSlot(PLDHashNumber aKeyHash) const + -> Slot { + MOZ_ASSERT(mEntryStore.IsAllocated()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + Slot slot = SlotForIndex(hash1); + + // Miss: return space for a new entry. + if (slot.IsFree()) { + return slot; + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + for (;;) { + MOZ_ASSERT(!slot.IsRemoved()); + slot.MarkColliding(); + + hash1 -= hash2; + hash1 &= sizeMask; + + slot = SlotForIndex(hash1); + if (slot.IsFree()) { + return slot; + } + } + + // NOTREACHED +} + +bool PLDHashTable::ChangeTable(int32_t aDeltaLog2) { + MOZ_ASSERT(mEntryStore.IsAllocated()); + + // Look, but don't touch, until we succeed in getting new entry store. + int32_t oldLog2 = kPLDHashNumberBits - mHashShift; + int32_t newLog2 = oldLog2 + aDeltaLog2; + uint32_t newCapacity = 1u << newLog2; + if (newCapacity > kMaxCapacity) { + return false; + } + + uint32_t nbytes; + if (!SizeOfEntryStore(newCapacity, mEntrySize, &nbytes)) { + return false; // overflowed + } + + char* newEntryStore = (char*)calloc(1, nbytes); + if (!newEntryStore) { + return false; + } + + // We can't fail from here on, so update table parameters. + mHashShift = kPLDHashNumberBits - newLog2; + mRemovedCount = 0; + + // Assign the new entry store to table. + char* oldEntryStore = mEntryStore.Get(); + mEntryStore.Set(newEntryStore, &mGeneration); + PLDHashMoveEntry moveEntry = mOps->moveEntry; + + // Copy only live entries, leaving removed ones behind. + uint32_t oldCapacity = 1u << oldLog2; + EntryStore::ForEachSlot( + oldEntryStore, oldCapacity, mEntrySize, [&](const Slot& slot) { + if (slot.IsLive()) { + const PLDHashNumber key = slot.KeyHash() & ~kCollisionFlag; + Slot newSlot = FindFreeSlot(key); + MOZ_ASSERT(newSlot.IsFree()); + moveEntry(this, slot.ToEntry(), newSlot.ToEntry()); + newSlot.SetKeyHash(key); + } + }); + + free(oldEntryStore); + return true; +} + +MOZ_ALWAYS_INLINE PLDHashNumber +PLDHashTable::ComputeKeyHash(const void* aKey) const { + MOZ_ASSERT(mEntryStore.IsAllocated()); + + PLDHashNumber keyHash = mozilla::ScrambleHashCode(mOps->hashKey(aKey)); + + // Avoid 0 and 1 hash codes, they indicate free and removed entries. + if (keyHash < 2) { + keyHash -= 2; + } + keyHash &= ~kCollisionFlag; + + return keyHash; +} + +PLDHashEntryHdr* PLDHashTable::Search(const void* aKey) const { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoReadOp op(mChecker); +#endif + + if (!mEntryStore.IsAllocated()) { + return nullptr; + } + + return SearchTable<ForSearchOrRemove>( + aKey, ComputeKeyHash(aKey), + [&](Slot& slot) -> PLDHashEntryHdr* { return slot.ToEntry(); }, + [&]() -> PLDHashEntryHdr* { return nullptr; }); +} + +PLDHashEntryHdr* PLDHashTable::Add(const void* aKey, + const mozilla::fallible_t& aFallible) { + auto maybeEntryHandle = MakeEntryHandle(aKey, aFallible); + if (!maybeEntryHandle) { + return nullptr; + } + return maybeEntryHandle->OrInsert([&aKey, this](PLDHashEntryHdr* entry) { + if (mOps->initEntry) { + mOps->initEntry(entry, aKey); + } + }); +} + +PLDHashEntryHdr* PLDHashTable::Add(const void* aKey) { + return MakeEntryHandle(aKey).OrInsert([&aKey, this](PLDHashEntryHdr* entry) { + if (mOps->initEntry) { + mOps->initEntry(entry, aKey); + } + }); +} + +void PLDHashTable::Remove(const void* aKey) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoWriteOp op(mChecker); +#endif + + if (!mEntryStore.IsAllocated()) { + return; + } + + PLDHashNumber keyHash = ComputeKeyHash(aKey); + SearchTable<ForSearchOrRemove>( + aKey, keyHash, + [&](Slot& slot) { + RawRemove(slot); + ShrinkIfAppropriate(); + }, + [&]() { + // Do nothing. + }); +} + +void PLDHashTable::RemoveEntry(PLDHashEntryHdr* aEntry) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoWriteOp op(mChecker); +#endif + + RawRemove(aEntry); + ShrinkIfAppropriate(); +} + +void PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry) { + Slot slot(mEntryStore.SlotForPLDHashEntry(aEntry, Capacity(), mEntrySize)); + RawRemove(slot); +} + +void PLDHashTable::RawRemove(Slot& aSlot) { + // Unfortunately, we can only do weak checking here. That's because + // RawRemove() can be called legitimately while an Enumerate() call is + // active, which doesn't fit well into how Checker's mState variable works. + MOZ_ASSERT(mChecker.IsWritable()); + + MOZ_ASSERT(mEntryStore.IsAllocated()); + + MOZ_ASSERT(aSlot.IsLive()); + + // Load keyHash first in case clearEntry() goofs it. + PLDHashNumber keyHash = aSlot.KeyHash(); + if (mOps->clearEntry) { + PLDHashEntryHdr* entry = aSlot.ToEntry(); + mOps->clearEntry(this, entry); + } + if (keyHash & kCollisionFlag) { + aSlot.MarkRemoved(); + mRemovedCount++; + } else { + aSlot.MarkFree(); + } + mEntryCount--; +} + +// Shrink or compress if a quarter or more of all entries are removed, or if the +// table is underloaded according to the minimum alpha, and is not minimal-size +// already. +void PLDHashTable::ShrinkIfAppropriate() { + uint32_t capacity = Capacity(); + if (mRemovedCount >= capacity >> 2 || + (capacity > kMinCapacity && mEntryCount <= MinLoad(capacity))) { + uint32_t log2; + BestCapacity(mEntryCount, &capacity, &log2); + + int32_t deltaLog2 = log2 - (kPLDHashNumberBits - mHashShift); + MOZ_ASSERT(deltaLog2 <= 0); + + (void)ChangeTable(deltaLog2); + } +} + +size_t PLDHashTable::ShallowSizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoReadOp op(mChecker); +#endif + + return aMallocSizeOf(mEntryStore.Get()); +} + +size_t PLDHashTable::ShallowSizeOfIncludingThis( + MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +mozilla::Maybe<PLDHashTable::EntryHandle> PLDHashTable::MakeEntryHandle( + const void* aKey, const mozilla::fallible_t&) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mChecker.StartWriteOp(); + auto endWriteOp = MakeScopeExit([&] { mChecker.EndWriteOp(); }); +#endif + + // Allocate the entry storage if it hasn't already been allocated. + if (!mEntryStore.IsAllocated()) { + uint32_t nbytes; + // We already checked this in the constructor, so it must still be true. + MOZ_RELEASE_ASSERT( + SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes)); + mEntryStore.Set((char*)calloc(1, nbytes), &mGeneration); + if (!mEntryStore.IsAllocated()) { + return Nothing(); + } + } + + // If alpha is >= .75, grow or compress the table. If aKey is already in the + // table, we may grow once more than necessary, but only if we are on the + // edge of being overloaded. + uint32_t capacity = Capacity(); + if (mEntryCount + mRemovedCount >= MaxLoad(capacity)) { + // Compress if a quarter or more of all entries are removed. + int deltaLog2 = 1; + if (mRemovedCount >= capacity >> 2) { + deltaLog2 = 0; + } + + // Grow or compress the table. If ChangeTable() fails, allow overloading up + // to the secondary max. Once we hit the secondary max, return null. + if (!ChangeTable(deltaLog2) && + mEntryCount + mRemovedCount >= MaxLoadOnGrowthFailure(capacity)) { + return Nothing(); + } + } + + // Look for entry after possibly growing, so we don't have to add it, + // then skip it while growing the table and re-add it after. + PLDHashNumber keyHash = ComputeKeyHash(aKey); + Slot slot = SearchTable<ForAdd>( + aKey, keyHash, [](Slot& found) -> Slot { return found; }, + []() -> Slot { + MOZ_CRASH("Nope"); + return Slot(nullptr, nullptr); + }); + + // The `EntryHandle` will handle ending the write op when it is destroyed. +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + endWriteOp.release(); +#endif + + return Some(EntryHandle{this, keyHash, slot}); +} + +PLDHashTable::EntryHandle PLDHashTable::MakeEntryHandle(const void* aKey) { + auto res = MakeEntryHandle(aKey, fallible); + if (!res) { + if (!mEntryStore.IsAllocated()) { + // We OOM'd while allocating the initial entry storage. + uint32_t nbytes; + (void)SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes); + NS_ABORT_OOM(nbytes); + } else { + // We failed to resize the existing entry storage, either due to OOM or + // because we exceeded the maximum table capacity or size; report it as + // an OOM. The multiplication by 2 gets us the size we tried to allocate, + // which is double the current size. + NS_ABORT_OOM(2 * EntrySize() * EntryCount()); + } + } + return res.extract(); +} + +PLDHashTable::EntryHandle::EntryHandle(PLDHashTable* aTable, + PLDHashNumber aKeyHash, Slot aSlot) + : mTable(aTable), mKeyHash(aKeyHash), mSlot(aSlot) {} + +PLDHashTable::EntryHandle::EntryHandle(EntryHandle&& aOther) noexcept + : mTable(std::exchange(aOther.mTable, nullptr)), + mKeyHash(aOther.mKeyHash), + mSlot(aOther.mSlot) {} + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED +PLDHashTable::EntryHandle::~EntryHandle() { + if (!mTable) { + return; + } + + mTable->mChecker.EndWriteOp(); +} +#endif + +void PLDHashTable::EntryHandle::Remove() { + MOZ_ASSERT(HasEntry()); + + mTable->RawRemove(mSlot); +} + +void PLDHashTable::EntryHandle::OrRemove() { + if (HasEntry()) { + Remove(); + } +} + +void PLDHashTable::EntryHandle::OccupySlot() { + MOZ_ASSERT(!HasEntry()); + + PLDHashNumber keyHash = mKeyHash; + if (mSlot.IsRemoved()) { + mTable->mRemovedCount--; + keyHash |= kCollisionFlag; + } + mSlot.SetKeyHash(keyHash); + mTable->mEntryCount++; +} + +PLDHashTable::Iterator::Iterator(Iterator&& aOther) + : mTable(aOther.mTable), + mCurrent(aOther.mCurrent), + mNexts(aOther.mNexts), + mNextsLimit(aOther.mNextsLimit), + mHaveRemoved(aOther.mHaveRemoved), + mEntrySize(aOther.mEntrySize) { + // No need to change |mChecker| here. + aOther.mTable = nullptr; + // We don't really have the concept of a null slot, so leave mCurrent. + aOther.mNexts = 0; + aOther.mNextsLimit = 0; + aOther.mHaveRemoved = false; + aOther.mEntrySize = 0; +} + +PLDHashTable::Iterator::Iterator(PLDHashTable* aTable) + : mTable(aTable), + mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize, + mTable->Capacity())), + mNexts(0), + mNextsLimit(mTable->EntryCount()), + mHaveRemoved(false), + mEntrySize(aTable->mEntrySize) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.StartReadOp(); +#endif + + if (ChaosMode::isActive(ChaosFeature::HashTableIteration) && + mTable->Capacity() > 0) { + // Start iterating at a random entry. It would be even more chaotic to + // iterate in fully random order, but that's harder. + uint32_t capacity = mTable->CapacityFromHashShift(); + uint32_t i = ChaosMode::randomUint32LessThan(capacity); + mCurrent = + mTable->mEntryStore.SlotForIndex(i, mTable->mEntrySize, capacity); + } + + // Advance to the first live entry, if there is one. + if (!Done() && IsOnNonLiveEntry()) { + MoveToNextLiveEntry(); + } +} + +PLDHashTable::Iterator::Iterator(PLDHashTable* aTable, EndIteratorTag aTag) + : mTable(aTable), + mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize, + mTable->Capacity())), + mNexts(mTable->EntryCount()), + mNextsLimit(mTable->EntryCount()), + mHaveRemoved(false), + mEntrySize(aTable->mEntrySize) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.StartReadOp(); +#endif + + MOZ_ASSERT(Done()); +} + +PLDHashTable::Iterator::Iterator(const Iterator& aOther) + : mTable(aOther.mTable), + mCurrent(aOther.mCurrent), + mNexts(aOther.mNexts), + mNextsLimit(aOther.mNextsLimit), + mHaveRemoved(aOther.mHaveRemoved), + mEntrySize(aOther.mEntrySize) { + // TODO: Is this necessary? + MOZ_ASSERT(!mHaveRemoved); + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.StartReadOp(); +#endif +} + +PLDHashTable::Iterator::~Iterator() { + if (mTable) { + if (mHaveRemoved) { + mTable->ShrinkIfAppropriate(); + } +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.EndReadOp(); +#endif + } +} + +MOZ_ALWAYS_INLINE bool PLDHashTable::Iterator::IsOnNonLiveEntry() const { + MOZ_ASSERT(!Done()); + return !mCurrent.IsLive(); +} + +void PLDHashTable::Iterator::Next() { + MOZ_ASSERT(!Done()); + + mNexts++; + + // Advance to the next live entry, if there is one. + if (!Done()) { + MoveToNextLiveEntry(); + } +} + +MOZ_ALWAYS_INLINE void PLDHashTable::Iterator::MoveToNextLiveEntry() { + // Chaos mode requires wraparound to cover all possible entries, so we can't + // simply move to the next live entry and stop when we hit the end of the + // entry store. But we don't want to introduce extra branches into our inner + // loop. So we are going to exploit the structure of the entry store in this + // method to implement an efficient inner loop. + // + // The idea is that since we are really only iterating through the stored + // hashes and because we know that there are a power-of-two number of + // hashes, we can use masking to implement the wraparound for us. This + // method does have the downside of needing to recalculate where the + // associated entry is once we've found it, but that seems OK. + + // Our current slot and its associated hash. + Slot slot = mCurrent; + PLDHashNumber* p = slot.HashPtr(); + const uint32_t capacity = mTable->CapacityFromHashShift(); + const uint32_t mask = capacity - 1; + auto hashes = reinterpret_cast<PLDHashNumber*>(mTable->mEntryStore.Get()); + uint32_t slotIndex = p - hashes; + + do { + slotIndex = (slotIndex + 1) & mask; + } while (!Slot::IsLiveHash(hashes[slotIndex])); + + // slotIndex now indicates where a live slot is. Rematerialize the entry + // and the slot. + mCurrent = mTable->mEntryStore.SlotForIndex(slotIndex, mEntrySize, capacity); +} + +void PLDHashTable::Iterator::Remove() { + mTable->RawRemove(mCurrent); + mHaveRemoved = true; +} diff --git a/xpcom/ds/PLDHashTable.h b/xpcom/ds/PLDHashTable.h new file mode 100644 index 0000000000..f1d741791c --- /dev/null +++ b/xpcom/ds/PLDHashTable.h @@ -0,0 +1,807 @@ +/* -*- 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/. */ + +// See the comment at the top of mfbt/HashTable.h for a comparison between +// PLDHashTable and mozilla::HashTable. + +#ifndef PLDHashTable_h +#define PLDHashTable_h + +#include <utility> + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/fallible.h" +#include "nscore.h" + +using PLDHashNumber = mozilla::HashNumber; +static const uint32_t kPLDHashNumberBits = mozilla::kHashNumberBits; + +#if defined(DEBUG) || defined(FUZZING) +# define MOZ_HASH_TABLE_CHECKS_ENABLED 1 +#endif + +class PLDHashTable; +struct PLDHashTableOps; + +// Table entry header structure. +// +// In order to allow in-line allocation of key and value, we do not declare +// either here. Instead, the API uses const void *key as a formal parameter. +// The key need not be stored in the entry; it may be part of the value, but +// need not be stored at all. +// +// Callback types are defined below and grouped into the PLDHashTableOps +// structure, for single static initialization per hash table sub-type. +// +// Each hash table sub-type should make its entry type a subclass of +// PLDHashEntryHdr. PLDHashEntryHdr is merely a common superclass to present a +// uniform interface to PLDHashTable clients. The zero-sized base class +// optimization, employed by all of our supported C++ compilers, will ensure +// that this abstraction does not make objects needlessly larger. +struct PLDHashEntryHdr { + PLDHashEntryHdr() = default; + PLDHashEntryHdr(const PLDHashEntryHdr&) = delete; + PLDHashEntryHdr& operator=(const PLDHashEntryHdr&) = delete; + PLDHashEntryHdr(PLDHashEntryHdr&&) = default; + PLDHashEntryHdr& operator=(PLDHashEntryHdr&&) = default; + + private: + friend class PLDHashTable; +}; + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + +// This class does three kinds of checking: +// +// - that calls to one of |mOps| or to an enumerator do not cause re-entry into +// the table in an unsafe way; +// +// - that multiple threads do not access the table in an unsafe way; +// +// - that a table marked as immutable is not modified. +// +// "Safe" here means that multiple concurrent read operations are ok, but a +// write operation (i.e. one that can cause the entry storage to be reallocated +// or destroyed) cannot safely run concurrently with another read or write +// operation. This meaning of "safe" is only partial; for example, it does not +// cover whether a single entry in the table is modified by two separate +// threads. (Doing such checking would be much harder.) +// +// It does this with two variables: +// +// - mState, which embodies a tri-stage tagged union with the following +// variants: +// - Idle +// - Read(n), where 'n' is the number of concurrent read operations +// - Write +// +// - mIsWritable, which indicates if the table is mutable. +// +class Checker { + public: + constexpr Checker() : mState(kIdle), mIsWritable(true) {} + + Checker& operator=(Checker&& aOther) { + // Atomic<> doesn't have an |operator=(Atomic<>&&)|. + mState = uint32_t(aOther.mState); + mIsWritable = bool(aOther.mIsWritable); + + aOther.mState = kIdle; + // XXX Shouldn't we set mIsWritable to true here for consistency? + + return *this; + } + + static bool IsIdle(uint32_t aState) { return aState == kIdle; } + static bool IsRead(uint32_t aState) { + return kRead1 <= aState && aState <= kReadMax; + } + static bool IsRead1(uint32_t aState) { return aState == kRead1; } + static bool IsWrite(uint32_t aState) { return aState == kWrite; } + + bool IsIdle() const { return mState == kIdle; } + + bool IsWritable() const { return mIsWritable; } + + void SetNonWritable() { mIsWritable = false; } + + // NOTE: the obvious way to implement these functions is to (a) check + // |mState| is reasonable, and then (b) update |mState|. But the lack of + // atomicity in such an implementation can cause problems if we get unlucky + // thread interleaving between (a) and (b). + // + // So instead for |mState| we are careful to (a) first get |mState|'s old + // value and assign it a new value in single atomic operation, and only then + // (b) check the old value was reasonable. This ensures we don't have + // interleaving problems. + // + // For |mIsWritable| we don't need to be as careful because it can only in + // transition in one direction (from writable to non-writable). + + void StartReadOp() { + uint32_t oldState = mState++; // this is an atomic increment + MOZ_RELEASE_ASSERT(IsIdle(oldState) || IsRead(oldState)); + MOZ_RELEASE_ASSERT(oldState < kReadMax); // check for overflow + } + + void EndReadOp() { + uint32_t oldState = mState--; // this is an atomic decrement + MOZ_RELEASE_ASSERT(IsRead(oldState)); + } + + void StartWriteOp() { + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_RELEASE_ASSERT(IsIdle(oldState)); + } + + void EndWriteOp() { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in StartWriteOp() + // occurred. + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kIdle); + MOZ_RELEASE_ASSERT(IsWrite(oldState)); + } + + void StartIteratorRemovalOp() { + // When doing removals at the end of iteration, we go from Read1 state to + // Write and then back. + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_RELEASE_ASSERT(IsRead1(oldState)); + } + + void EndIteratorRemovalOp() { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in + // StartIteratorRemovalOp() occurred. + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kRead1); + MOZ_RELEASE_ASSERT(IsWrite(oldState)); + } + + void StartDestructorOp() { + // A destructor op is like a write, but the table doesn't need to be + // writable. + uint32_t oldState = mState.exchange(kWrite); + MOZ_RELEASE_ASSERT(IsIdle(oldState)); + } + + void EndDestructorOp() { + uint32_t oldState = mState.exchange(kIdle); + MOZ_RELEASE_ASSERT(IsWrite(oldState)); + } + + private: + // Things of note about the representation of |mState|. + // - The values between kRead1..kReadMax represent valid Read(n) values. + // - kIdle and kRead1 are deliberately chosen so that incrementing the - + // former gives the latter. + // - 9999 concurrent readers should be enough for anybody. + static const uint32_t kIdle = 0; + static const uint32_t kRead1 = 1; + static const uint32_t kReadMax = 9999; + static const uint32_t kWrite = 10000; + + mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> mState; + mozilla::Atomic<bool, mozilla::SequentiallyConsistent> mIsWritable; +}; +#endif + +// A PLDHashTable may be allocated on the stack or within another structure or +// class. No entry storage is allocated until the first element is added. This +// means that empty hash tables are cheap, which is good because they are +// common. +// +// There used to be a long, math-heavy comment here about the merits of +// double hashing vs. chaining; it was removed in bug 1058335. In short, double +// hashing is more space-efficient unless the element size gets large (in which +// case you should keep using double hashing but switch to using pointer +// elements). Also, with double hashing, you can't safely hold an entry pointer +// and use it after an add or remove operation, unless you sample Generation() +// before adding or removing, and compare the sample after, dereferencing the +// entry pointer only if Generation() has not changed. +class PLDHashTable { + private: + // A slot represents a cached hash value and its associated entry stored in + // the hash table. The hash value and the entry are not stored contiguously. + struct Slot { + Slot(PLDHashEntryHdr* aEntry, PLDHashNumber* aKeyHash) + : mEntry(aEntry), mKeyHash(aKeyHash) {} + + Slot(const Slot&) = default; + Slot(Slot&& aOther) = default; + + Slot& operator=(Slot&& aOther) = default; + + bool operator==(const Slot& aOther) const { + return mEntry == aOther.mEntry; + } + + PLDHashNumber KeyHash() const { return *HashPtr(); } + void SetKeyHash(PLDHashNumber aHash) { *HashPtr() = aHash; } + + PLDHashEntryHdr* ToEntry() const { return mEntry; } + + bool IsFree() const { return KeyHash() == 0; } + bool IsRemoved() const { return KeyHash() == 1; } + bool IsLive() const { return IsLiveHash(KeyHash()); } + static bool IsLiveHash(uint32_t aHash) { return aHash >= 2; } + + void MarkFree() { *HashPtr() = 0; } + void MarkRemoved() { *HashPtr() = 1; } + void MarkColliding() { *HashPtr() |= kCollisionFlag; } + + void Next(uint32_t aEntrySize) { + char* p = reinterpret_cast<char*>(mEntry); + p += aEntrySize; + mEntry = reinterpret_cast<PLDHashEntryHdr*>(p); + mKeyHash++; + } + PLDHashNumber* HashPtr() const { return mKeyHash; } + + private: + PLDHashEntryHdr* mEntry; + PLDHashNumber* mKeyHash; + }; + + // This class maintains the invariant that every time the entry store is + // changed, the generation is updated. + // + // The data layout separates the cached hashes of entries and the entries + // themselves to save space. We could store the entries thusly: + // + // +--------+--------+---------+ + // | entry0 | entry1 | ... | + // +--------+--------+---------+ + // + // where the entries themselves contain the cached hash stored as their + // first member. PLDHashTable did this for a long time, with entries looking + // like: + // + // class PLDHashEntryHdr + // { + // PLDHashNumber mKeyHash; + // }; + // + // class MyEntry : public PLDHashEntryHdr + // { + // ... + // }; + // + // The problem with this setup is that, depending on the layout of + // `MyEntry`, there may be platform ABI-mandated padding between `mKeyHash` + // and the first member of `MyEntry`. This ABI-mandated padding is wasted + // space, and was surprisingly common, e.g. when MyEntry contained a single + // pointer on 64-bit platforms. + // + // As previously alluded to, the current setup stores things thusly: + // + // +-------+-------+-------+-------+--------+--------+---------+ + // | hash0 | hash1 | ..... | hashN | entry0 | entry1 | ... | + // +-------+-------+-------+-------+--------+--------+---------+ + // + // which contains no wasted space between the hashes themselves, and no + // wasted space between the entries themselves. malloc is guaranteed to + // return blocks of memory with at least word alignment on all of our major + // platforms. PLDHashTable mandates that the size of the hash table is + // always a power of two, so the alignment of the memory containing the + // first entry is always at least the alignment of the entire entry store. + // That means the alignment of `entry0` should be its natural alignment. + // Entries may have problems if they contain over-aligned members such as + // SIMD vector types, but this has not been a problem in practice. + // + // Note: It would be natural to store the generation within this class, but + // we can't do that without bloating sizeof(PLDHashTable) on 64-bit machines. + // So instead we store it outside this class, and Set() takes a pointer to it + // and ensures it is updated as necessary. + class EntryStore { + private: + char* mEntryStore; + + static char* Entries(char* aStore, uint32_t aCapacity) { + return aStore + aCapacity * sizeof(PLDHashNumber); + } + + char* Entries(uint32_t aCapacity) const { + return Entries(Get(), aCapacity); + } + + public: + EntryStore() : mEntryStore(nullptr) {} + + ~EntryStore() { + free(mEntryStore); + mEntryStore = nullptr; + } + + char* Get() const { return mEntryStore; } + bool IsAllocated() const { return !!mEntryStore; } + + Slot SlotForIndex(uint32_t aIndex, uint32_t aEntrySize, + uint32_t aCapacity) const { + char* entries = Entries(aCapacity); + auto entry = + reinterpret_cast<PLDHashEntryHdr*>(entries + aIndex * aEntrySize); + auto hashes = reinterpret_cast<PLDHashNumber*>(Get()); + return Slot(entry, &hashes[aIndex]); + } + + Slot SlotForPLDHashEntry(PLDHashEntryHdr* aEntry, uint32_t aCapacity, + uint32_t aEntrySize) { + char* entries = Entries(aCapacity); + char* entry = reinterpret_cast<char*>(aEntry); + uint32_t entryOffset = entry - entries; + uint32_t slotIndex = entryOffset / aEntrySize; + return SlotForIndex(slotIndex, aEntrySize, aCapacity); + } + + template <typename F> + void ForEachSlot(uint32_t aCapacity, uint32_t aEntrySize, F&& aFunc) { + ForEachSlot(Get(), aCapacity, aEntrySize, std::move(aFunc)); + } + + template <typename F> + static void ForEachSlot(char* aStore, uint32_t aCapacity, + uint32_t aEntrySize, F&& aFunc) { + char* entries = Entries(aStore, aCapacity); + Slot slot(reinterpret_cast<PLDHashEntryHdr*>(entries), + reinterpret_cast<PLDHashNumber*>(aStore)); + for (size_t i = 0; i < aCapacity; ++i) { + aFunc(slot); + slot.Next(aEntrySize); + } + } + + void Set(char* aEntryStore, uint16_t* aGeneration) { + mEntryStore = aEntryStore; + *aGeneration += 1; + } + }; + + // These fields are packed carefully. On 32-bit platforms, + // sizeof(PLDHashTable) is 20. On 64-bit platforms, sizeof(PLDHashTable) is + // 32; 28 bytes of data followed by 4 bytes of padding for alignment. + const PLDHashTableOps* const mOps; // Virtual operations; see below. + EntryStore mEntryStore; // (Lazy) entry storage and generation. + uint16_t mGeneration; // The storage generation. + uint8_t mHashShift; // Multiplicative hash shift. + const uint8_t mEntrySize; // Number of bytes in an entry. + uint32_t mEntryCount; // Number of entries in table. + uint32_t mRemovedCount; // Removed entry sentinels in table. + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mutable Checker mChecker; +#endif + + public: + // Table capacity limit; do not exceed. The max capacity used to be 1<<23 but + // that occasionally that wasn't enough. Making it much bigger than 1<<26 + // probably isn't worthwhile -- tables that big are kind of ridiculous. + // Also, the growth operation will (deliberately) fail if |capacity * + // mEntrySize| overflows a uint32_t, and mEntrySize is always at least 8 + // bytes. + static const uint32_t kMaxCapacity = ((uint32_t)1 << 26); + + static const uint32_t kMinCapacity = 8; + + // Making this half of kMaxCapacity ensures it'll fit. Nobody should need an + // initial length anywhere nearly this large, anyway. + static const uint32_t kMaxInitialLength = kMaxCapacity / 2; + + // This gives a default initial capacity of 8. + static const uint32_t kDefaultInitialLength = 4; + + // Initialize the table with |aOps| and |aEntrySize|. The table's initial + // capacity is chosen such that |aLength| elements can be inserted without + // rehashing; if |aLength| is a power-of-two, this capacity will be + // |2*length|. However, because entry storage is allocated lazily, this + // initial capacity won't be relevant until the first element is added; prior + // to that the capacity will be zero. + // + // This will crash if |aEntrySize| and/or |aLength| are too large. + PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, + uint32_t aLength = kDefaultInitialLength); + + PLDHashTable(PLDHashTable&& aOther) + // Initialize fields which are checked by the move assignment operator + // and the destructor (which the move assignment operator calls). + : mOps(nullptr), mGeneration(0), mEntrySize(0) { + *this = std::move(aOther); + } + + PLDHashTable& operator=(PLDHashTable&& aOther); + + ~PLDHashTable(); + + // This should be used rarely. + const PLDHashTableOps* Ops() const { return mOps; } + + // Size in entries (gross, not net of free and removed sentinels) for table. + // This can be zero if no elements have been added yet, in which case the + // entry storage will not have yet been allocated. + uint32_t Capacity() const { + return mEntryStore.IsAllocated() ? CapacityFromHashShift() : 0; + } + + uint32_t EntrySize() const { return mEntrySize; } + uint32_t EntryCount() const { return mEntryCount; } + uint32_t Generation() const { return mGeneration; } + + // To search for a |key| in |table|, call: + // + // entry = table.Search(key); + // + // If |entry| is non-null, |key| was found. If |entry| is null, key was not + // found. + PLDHashEntryHdr* Search(const void* aKey) const; + + // To add an entry identified by |key| to table, call: + // + // entry = table.Add(key, mozilla::fallible); + // + // If |entry| is null upon return, then the table is severely overloaded and + // memory can't be allocated for entry storage. + // + // Otherwise, if the initEntry hook was provided, |entry| will be + // initialized. If the initEntry hook was not provided, the caller + // should initialize |entry| as appropriate. + PLDHashEntryHdr* Add(const void* aKey, const mozilla::fallible_t&); + + // This is like the other Add() function, but infallible, and so never + // returns null. + PLDHashEntryHdr* Add(const void* aKey); + + // To remove an entry identified by |key| from table, call: + // + // table.Remove(key); + // + // If |key|'s entry is found, it is cleared (via table->mOps->clearEntry). + // The table's capacity may be reduced afterwards. + void Remove(const void* aKey); + + // To remove an entry found by a prior search, call: + // + // table.RemoveEntry(entry); + // + // The entry, which must be present and in use, is cleared (via + // table->mOps->clearEntry). The table's capacity may be reduced afterwards. + void RemoveEntry(PLDHashEntryHdr* aEntry); + + // Remove an entry already accessed via Search() or Add(). + // + // NB: this is a "raw" or low-level method. It does not shrink the table if + // it is underloaded. Don't use it unless necessary and you know what you are + // doing, and if so, please explain in a comment why it is necessary instead + // of RemoveEntry(). + void RawRemove(PLDHashEntryHdr* aEntry); + + // This function is equivalent to + // ClearAndPrepareForLength(kDefaultInitialLength). + void Clear(); + + // This function clears the table's contents and frees its entry storage, + // leaving it in a empty state ready to be used again. Afterwards, when the + // first element is added the entry storage that gets allocated will have a + // capacity large enough to fit |aLength| elements without rehashing. + // + // It's conceptually the same as calling the destructor and then re-calling + // the constructor with the original |aOps| and |aEntrySize| arguments, and + // a new |aLength| argument. + void ClearAndPrepareForLength(uint32_t aLength); + + // Measure the size of the table's entry storage. If the entries contain + // pointers to other heap blocks, you have to iterate over the table and + // measure those separately; hence the "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Like ShallowSizeOfExcludingThis(), but includes sizeof(*this). + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Mark a table as immutable for the remainder of its lifetime. This + // changes the implementation from asserting one set of invariants to + // asserting a different set. + void MarkImmutable() { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mChecker.SetNonWritable(); +#endif + } + + // If you use PLDHashEntryStub or a subclass of it as your entry struct, and + // if your entries move via memcpy and clear via memset(0), you can use these + // stub operations. + static const PLDHashTableOps* StubOps(); + + // The individual stub operations in StubOps(). + static PLDHashNumber HashVoidPtrKeyStub(const void* aKey); + static bool MatchEntryStub(const PLDHashEntryHdr* aEntry, const void* aKey); + static void MoveEntryStub(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + static void ClearEntryStub(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + // Hash/match operations for tables holding C strings. + static PLDHashNumber HashStringKey(const void* aKey); + static bool MatchStringKey(const PLDHashEntryHdr* aEntry, const void* aKey); + + class EntryHandle { + public: + EntryHandle(EntryHandle&& aOther) noexcept; +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + ~EntryHandle(); +#endif + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(EntryHandle&& aOther) = delete; + + // Is this slot currently occupied? + bool HasEntry() const { return mSlot.IsLive(); } + + explicit operator bool() const { return HasEntry(); } + + // Get the entry stored in this slot. May not be called unless the slot is + // currently occupied. + PLDHashEntryHdr* Entry() { + MOZ_ASSERT(HasEntry()); + return mSlot.ToEntry(); + } + + template <class F> + void Insert(F&& aInitEntry) { + MOZ_ASSERT(!HasEntry()); + OccupySlot(); + std::forward<F>(aInitEntry)(Entry()); + } + + // If the slot is currently vacant, the slot is occupied and `initEntry` is + // invoked to initialize the entry. Returns the entry stored in now-occupied + // slot. + template <class F> + PLDHashEntryHdr* OrInsert(F&& aInitEntry) { + if (!HasEntry()) { + Insert(std::forward<F>(aInitEntry)); + } + return Entry(); + } + + /** Removes the entry. Note that the table won't shrink on destruction of + * the EntryHandle. + * + * \pre HasEntry() + * \post !HasEntry() + */ + void Remove(); + + /** Removes the entry, if it exists. Note that the table won't shrink on + * destruction of the EntryHandle. + * + * \post !HasEntry() + */ + void OrRemove(); + + private: + friend class PLDHashTable; + + EntryHandle(PLDHashTable* aTable, PLDHashNumber aKeyHash, Slot aSlot); + + void OccupySlot(); + + PLDHashTable* mTable; + PLDHashNumber mKeyHash; + Slot mSlot; + }; + + template <class F> + auto WithEntryHandle(const void* aKey, F&& aFunc) + -> std::invoke_result_t<F, EntryHandle&&> { + return std::forward<F>(aFunc)(MakeEntryHandle(aKey)); + } + + template <class F> + auto WithEntryHandle(const void* aKey, const mozilla::fallible_t& aFallible, + F&& aFunc) + -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> { + return std::forward<F>(aFunc)(MakeEntryHandle(aKey, aFallible)); + } + + // This is an iterator for PLDHashtable. Assertions will detect some, but not + // all, mid-iteration table modifications that might invalidate (e.g. + // reallocate) the entry storage. + // + // Any element can be removed during iteration using Remove(). If any + // elements are removed, the table may be resized once iteration ends. + // + // Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // auto entry = static_cast<FooEntry*>(iter.Get()); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + // or: + // + // for (PLDHashTable::Iterator iter(&table); !iter.Done(); iter.Next()) { + // auto entry = static_cast<FooEntry*>(iter.Get()); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + // The latter form is more verbose but is easier to work with when + // making subclasses of Iterator. + // + class Iterator { + public: + explicit Iterator(PLDHashTable* aTable); + struct EndIteratorTag {}; + Iterator(PLDHashTable* aTable, EndIteratorTag aTag); + Iterator(Iterator&& aOther); + ~Iterator(); + + // Have we finished? + bool Done() const { return mNexts == mNextsLimit; } + + // Get the current entry. + PLDHashEntryHdr* Get() const { + MOZ_ASSERT(!Done()); + MOZ_ASSERT(mCurrent.IsLive()); + return mCurrent.ToEntry(); + } + + // Advance to the next entry. + void Next(); + + // Remove the current entry. Must only be called once per entry, and Get() + // must not be called on that entry afterwards. + void Remove(); + + bool operator==(const Iterator& aOther) const { + MOZ_ASSERT(mTable == aOther.mTable); + return mNexts == aOther.mNexts; + } + + Iterator Clone() const { return {*this}; } + + protected: + PLDHashTable* mTable; // Main table pointer. + + private: + Slot mCurrent; // Pointer to the current entry. + uint32_t mNexts; // Number of Next() calls. + uint32_t mNextsLimit; // Next() call limit. + + bool mHaveRemoved; // Have any elements been removed? + uint8_t mEntrySize; // Size of entries. + + bool IsOnNonLiveEntry() const; + + void MoveToNextLiveEntry(); + + Iterator() = delete; + Iterator(const Iterator&); + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(const Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + // Use this if you need to initialize an Iterator in a const method. If you + // use this case, you should not call Remove() on the iterator. + Iterator ConstIter() const { + return Iterator(const_cast<PLDHashTable*>(this)); + } + + private: + static uint32_t HashShift(uint32_t aEntrySize, uint32_t aLength); + + static const PLDHashNumber kCollisionFlag = 1; + + PLDHashNumber Hash1(PLDHashNumber aHash0) const; + void Hash2(PLDHashNumber aHash, uint32_t& aHash2Out, + uint32_t& aSizeMaskOut) const; + + static bool MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aHash); + Slot SlotForIndex(uint32_t aIndex) const; + + // We store mHashShift rather than sizeLog2 to optimize the collision-free + // case in SearchTable. + uint32_t CapacityFromHashShift() const { + return ((uint32_t)1 << (kPLDHashNumberBits - mHashShift)); + } + + PLDHashNumber ComputeKeyHash(const void* aKey) const; + + enum SearchReason { ForSearchOrRemove, ForAdd }; + + // Avoid using bare `Success` and `Failure`, as those names are commonly + // defined as macros. + template <SearchReason Reason, typename PLDSuccess, typename PLDFailure> + auto SearchTable(const void* aKey, PLDHashNumber aKeyHash, + PLDSuccess&& aSucess, PLDFailure&& aFailure) const; + + Slot FindFreeSlot(PLDHashNumber aKeyHash) const; + + bool ChangeTable(int aDeltaLog2); + + void RawRemove(Slot& aSlot); + void ShrinkIfAppropriate(); + + mozilla::Maybe<EntryHandle> MakeEntryHandle(const void* aKey, + const mozilla::fallible_t&); + + EntryHandle MakeEntryHandle(const void* aKey); + + PLDHashTable(const PLDHashTable& aOther) = delete; + PLDHashTable& operator=(const PLDHashTable& aOther) = delete; +}; + +// Compute the hash code for a given key to be looked up, added, or removed. +// A hash code may have any PLDHashNumber value. +typedef PLDHashNumber (*PLDHashHashKey)(const void* aKey); + +// Compare the key identifying aEntry with the provided key parameter. Return +// true if keys match, false otherwise. +typedef bool (*PLDHashMatchEntry)(const PLDHashEntryHdr* aEntry, + const void* aKey); + +// Copy the data starting at aFrom to the new entry storage at aTo. Do not add +// reference counts for any strong references in the entry, however, as this +// is a "move" operation: the old entry storage at from will be freed without +// any reference-decrementing callback shortly. +typedef void (*PLDHashMoveEntry)(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + +// Clear the entry and drop any strong references it holds. This callback is +// invoked by Remove(), but only if the given key is found in the table. +typedef void (*PLDHashClearEntry)(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry); + +// Initialize a new entry. This function is called when +// Add() finds no existing entry for the given key, and must add a new one. +typedef void (*PLDHashInitEntry)(PLDHashEntryHdr* aEntry, const void* aKey); + +// Finally, the "vtable" structure for PLDHashTable. The first four hooks +// must be provided by implementations; they're called unconditionally by the +// generic PLDHashTable.cpp code. Hooks after these may be null. +// +// Summary of allocation-related hook usage with C++ placement new emphasis: +// initEntry Call placement new using default key-based ctor. +// moveEntry Call placement new using copy ctor, run dtor on old +// entry storage. +// clearEntry Run dtor on entry. +// +// Note the reason why initEntry is optional: the default hooks (stubs) clear +// entry storage. On a successful Add(tbl, key), the returned entry pointer +// addresses an entry struct whose entry members are still clear (null). Add() +// callers can test such members to see whether the entry was newly created by +// the Add() call that just succeeded. If placement new or similar +// initialization is required, define an |initEntry| hook. Of course, the +// |clearEntry| hook must zero or null appropriately. +// +// XXX assumes 0 is null for pointer types. +struct PLDHashTableOps { + // Mandatory hooks. All implementations must provide these. + PLDHashHashKey hashKey; + PLDHashMatchEntry matchEntry; + PLDHashMoveEntry moveEntry; + + // Optional hooks start here. If null, these are not called. + PLDHashClearEntry clearEntry; + PLDHashInitEntry initEntry; +}; + +// A minimal entry is a subclass of PLDHashEntryHdr and has a void* key pointer. +struct PLDHashEntryStub : public PLDHashEntryHdr { + const void* key; +}; + +#endif /* PLDHashTable_h */ diff --git a/xpcom/ds/PerfectHash.h b/xpcom/ds/PerfectHash.h new file mode 100644 index 0000000000..1e75855462 --- /dev/null +++ b/xpcom/ds/PerfectHash.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Helper routines for perfecthash.py. Not to be used directly. */ + +#ifndef mozilla_PerfectHash_h +#define mozilla_PerfectHash_h + +#include <type_traits> + +namespace mozilla { +namespace perfecthash { + +// 32-bit FNV offset basis and prime value. +// NOTE: Must match values in |perfecthash.py| +constexpr uint32_t FNV_OFFSET_BASIS = 0x811C9DC5; +constexpr uint32_t FNV_PRIME = 16777619; + +/** + * Basic FNV hasher function used by perfecthash. Generic over the unit type. + */ +template <typename C> +inline uint32_t Hash(uint32_t aBasis, const C* aKey, size_t aLen) { + for (size_t i = 0; i < aLen; ++i) { + aBasis = + (aBasis ^ static_cast<std::make_unsigned_t<C>>(aKey[i])) * FNV_PRIME; + } + return aBasis; +} + +/** + * Helper method for getting the index from a perfect hash. + * Called by code generated from |perfecthash.py|. + */ +template <typename C, typename Base, size_t NBases, typename Entry, + size_t NEntries> +inline const Entry& Lookup(const C* aKey, size_t aLen, + const Base (&aTable)[NBases], + const Entry (&aEntries)[NEntries]) { + uint32_t basis = aTable[Hash(FNV_OFFSET_BASIS, aKey, aLen) % NBases]; + return aEntries[Hash(basis, aKey, aLen) % NEntries]; +} + +} // namespace perfecthash +} // namespace mozilla + +#endif // !defined(mozilla_PerfectHash_h) diff --git a/xpcom/ds/SimpleEnumerator.h b/xpcom/ds/SimpleEnumerator.h new file mode 100644 index 0000000000..463b9ecaed --- /dev/null +++ b/xpcom/ds/SimpleEnumerator.h @@ -0,0 +1,73 @@ +/* -*- 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_SimpleEnumerator_h +#define mozilla_SimpleEnumerator_h + +#include "nsCOMPtr.h" +#include "nsISimpleEnumerator.h" + +namespace mozilla { + +/** + * A wrapper class around nsISimpleEnumerator to support ranged iteration. This + * requires every element in the enumeration to implement the same interface, T. + * If any element does not implement this interface, the enumeration ends at + * that element, and triggers an assertion in debug builds. + * + * Typical usage looks something like: + * + * for (auto& docShell : SimpleEnumerator<nsIDocShell>(docShellEnum)) { + * docShell.LoadURI(...); + * } + */ + +template <typename T> +class SimpleEnumerator final { + public: + explicit SimpleEnumerator(nsISimpleEnumerator* aEnum) : mEnum(aEnum) {} + + class Entry { + public: + explicit Entry(T* aPtr) : mPtr(aPtr) {} + + explicit Entry(nsISimpleEnumerator& aEnum) : mEnum(&aEnum) { ++*this; } + + const nsCOMPtr<T>& operator*() { + MOZ_ASSERT(mPtr); + return mPtr; + } + + Entry& operator++() { + MOZ_ASSERT(mEnum); + nsCOMPtr<nsISupports> next; + if (NS_SUCCEEDED(mEnum->GetNext(getter_AddRefs(next)))) { + mPtr = do_QueryInterface(next); + MOZ_ASSERT(mPtr); + } else { + mPtr = nullptr; + } + return *this; + } + + bool operator!=(const Entry& aOther) const { return mPtr != aOther.mPtr; } + + private: + nsCOMPtr<T> mPtr; + nsCOMPtr<nsISimpleEnumerator> mEnum; + }; + + Entry begin() { return Entry(*mEnum); } + + Entry end() { return Entry(nullptr); } + + private: + nsCOMPtr<nsISimpleEnumerator> mEnum; +}; + +} // namespace mozilla + +#endif // mozilla_SimpleEnumerator_h diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py new file mode 100644 index 0000000000..ca4ac97813 --- /dev/null +++ b/xpcom/ds/StaticAtoms.py @@ -0,0 +1,2645 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# flake8: noqa + +import sys + +from Atom import ( + Atom, + InheritingAnonBoxAtom, + NonInheritingAnonBoxAtom, + PseudoElementAtom, +) +from HTMLAtoms import HTML_PARSER_ATOMS + +# Static atom definitions, used to generate nsGkAtomList.h. +# +# Each atom is defined by a call to Atom, PseudoElementAtom, +# NonInheritingAnonBoxAtom or InheritingAnonBoxAtom. +# +# The first argument is the atom's identifier. +# The second argument is the atom's string value. +# +# Please keep the Atom() definitions on one line as this is parsed by the +# htmlparser: parser/html/java/htmlparser +# Please keep "START ATOMS" and "END ATOMS" comments as the parser uses them. +# +# It is not possible to conditionally define static atoms with #ifdef etc. +# fmt: off +STATIC_ATOMS = [ + # START ATOMS + # -------------------------------------------------------------------------- + # Generic atoms + # -------------------------------------------------------------------------- + Atom("SystemPrincipal", "[System Principal]"), + Atom("_empty", ""), + Atom("_0", "0"), + Atom("_1", "1"), + Atom("mozframetype", "mozframetype"), + Atom("_moz_abspos", "_moz_abspos"), + Atom("_moz_activated", "_moz_activated"), + Atom("_moz_anonclass", "_moz_anonclass"), + Atom("_moz_resizing", "_moz_resizing"), + Atom("moztype", "_moz-type"), + Atom("mozdirty", "_moz_dirty"), + Atom("mozdisallowselectionprint", "mozdisallowselectionprint"), + Atom("mozdonotsend", "moz-do-not-send"), + Atom("mozfwcontainer", "moz-forward-container"), # Used by MailNews. + Atom("mozgeneratedcontentbefore", "_moz_generated_content_before"), + Atom("mozgeneratedcontentafter", "_moz_generated_content_after"), + Atom("mozgeneratedcontentmarker", "_moz_generated_content_marker"), + Atom("mozgeneratedcontentimage", "_moz_generated_content_image"), + Atom("mozquote", "_moz_quote"), + Atom("mozsignature", "moz-signature"), # Used by MailNews. + Atom("_moz_bullet_font", "-moz-bullet-font"), + Atom("_moz_is_glyph", "-moz-is-glyph"), + Atom("_moz_original_size", "_moz_original_size"), + Atom("_moz_print_preview", "-moz-print-preview"), + Atom("menuactive", "_moz-menuactive"), + Atom("_poundDefault", "#default"), + Atom("_asterisk", "*"), + Atom("a", "a"), + Atom("abbr", "abbr"), + Atom("abort", "abort"), + Atom("above", "above"), + Atom("acceltext", "acceltext"), + Atom("accept", "accept"), + Atom("acceptcharset", "accept-charset"), + Atom("accessiblenode", "accessible-node"), + Atom("accesskey", "accesskey"), + Atom("acronym", "acronym"), + Atom("action", "action"), + Atom("active", "active"), + Atom("activateontab", "activateontab"), + Atom("actuate", "actuate"), + Atom("address", "address"), + Atom("adoptedsheetclones", "adoptedsheetclones"), + Atom("after", "after"), + Atom("align", "align"), + Atom("alink", "alink"), + Atom("all", "all"), + Atom("allow", "allow"), + Atom("allowdownloads", "allow-downloads"), + Atom("allowevents", "allowevents"), + Atom("allowforms", "allow-forms"), + Atom("allowfullscreen", "allowfullscreen"), + Atom("allowmodals", "allow-modals"), + Atom("alloworientationlock", "allow-orientation-lock"), + Atom("allowpointerlock", "allow-pointer-lock"), + Atom("allowpopupstoescapesandbox", "allow-popups-to-escape-sandbox"), + Atom("allowpopups", "allow-popups"), + Atom("allowpresentation", "allow-presentation"), + Atom("allowstorageaccessbyuseractivatetion", "allow-storage-access-by-user-activation"), + Atom("allowsameorigin", "allow-same-origin"), + Atom("allowscripts", "allow-scripts"), + Atom("allowscriptstoclose", "allowscriptstoclose"), + Atom("allowtopnavigation", "allow-top-navigation"), + Atom("allowtopnavigationbyuseractivation", "allow-top-navigation-by-user-activation"), + Atom("allowtopnavigationcustomprotocols", "allow-top-navigation-to-custom-protocols"), + Atom("allowuntrusted", "allowuntrusted"), + Atom("alt", "alt"), + Atom("alternate", "alternate"), + Atom("always", "always"), + Atom("ancestor", "ancestor"), + Atom("ancestorOrSelf", "ancestor-or-self"), + Atom("anchor", "anchor"), + Atom("_and", "and"), + Atom("animations", "animations"), + Atom("anonid", "anonid"), + Atom("anonlocation", "anonlocation"), + Atom("any", "any"), + Atom("any_hover", "any-hover"), + Atom("any_pointer", "any-pointer"), + Atom("applet", "applet"), + Atom("applyImports", "apply-imports"), + Atom("applyTemplates", "apply-templates"), + Atom("archive", "archive"), + Atom("area", "area"), + Atom("aria", "aria"), + Atom("aria_activedescendant", "aria-activedescendant"), + Atom("aria_atomic", "aria-atomic"), + Atom("aria_autocomplete", "aria-autocomplete"), + Atom("aria_busy", "aria-busy"), + Atom("aria_checked", "aria-checked"), + Atom("aria_controls", "aria-controls"), + Atom("aria_current", "aria-current"), + Atom("aria_describedby", "aria-describedby"), + Atom("aria_description", "aria-description"), + Atom("aria_disabled", "aria-disabled"), + Atom("aria_dropeffect", "aria-dropeffect"), + Atom("aria_expanded", "aria-expanded"), + Atom("aria_flowto", "aria-flowto"), + Atom("aria_haspopup", "aria-haspopup"), + Atom("aria_hidden", "aria-hidden"), + Atom("aria_invalid", "aria-invalid"), + Atom("aria_labelledby", "aria-labelledby"), + Atom("aria_level", "aria-level"), + Atom("aria_live", "aria-live"), + Atom("aria_multiline", "aria-multiline"), + Atom("aria_multiselectable", "aria-multiselectable"), + Atom("aria_owns", "aria-owns"), + Atom("aria_posinset", "aria-posinset"), + Atom("aria_pressed", "aria-pressed"), + Atom("aria_readonly", "aria-readonly"), + Atom("aria_relevant", "aria-relevant"), + Atom("aria_required", "aria-required"), + Atom("aria_selected", "aria-selected"), + Atom("aria_setsize", "aria-setsize"), + Atom("aria_sort", "aria-sort"), + Atom("aria_valuemax", "aria-valuemax"), + Atom("aria_valuemin", "aria-valuemin"), + Atom("aria_valuenow", "aria-valuenow"), + Atom("arrow", "arrow"), + Atom("article", "article"), + Atom("as", "as"), + Atom("ascending", "ascending"), + Atom("aside", "aside"), + Atom("aspectRatio", "aspect-ratio"), + Atom("async", "async"), + Atom("attribute", "attribute"), + Atom("attributes", "attributes"), + Atom("attributeSet", "attribute-set"), + Atom("_auto", "auto"), + Atom("autocapitalize", "autocapitalize"), + Atom("autocheck", "autocheck"), + Atom("autocomplete", "autocomplete"), + Atom("autocomplete_richlistbox", "autocomplete-richlistbox"), + Atom("autofocus", "autofocus"), + Atom("autoplay", "autoplay"), + Atom("axis", "axis"), + Atom("b", "b"), + Atom("background", "background"), + Atom("bar", "bar"), + Atom("base", "base"), + Atom("basefont", "basefont"), + Atom("baseline", "baseline"), + Atom("bdi", "bdi"), + Atom("bdo", "bdo"), + Atom("before", "before"), + Atom("behavior", "behavior"), + Atom("below", "below"), + Atom("bgcolor", "bgcolor"), + Atom("bgsound", "bgsound"), + Atom("big", "big"), + Atom("binding", "binding"), + Atom("bindings", "bindings"), + Atom("bindToUntrustedContent", "bindToUntrustedContent"), + Atom("black", "black"), + Atom("block", "block"), + Atom("block_size", "block-size"), + Atom("blocking", "blocking"), + Atom("blockquote", "blockquote"), + Atom("blur", "blur"), + Atom("body", "body"), + Atom("boolean", "boolean"), + Atom("border", "border"), + Atom("bordercolor", "bordercolor"), + Atom("both", "both"), + Atom("bottom", "bottom"), + Atom("bottomend", "bottomend"), + Atom("bottomstart", "bottomstart"), + Atom("bottomleft", "bottomleft"), + Atom("bottommargin", "bottommargin"), + Atom("bottomright", "bottomright"), + Atom("box", "box"), + Atom("br", "br"), + Atom("browser", "browser"), + Atom("mozbrowser", "mozbrowser"), + Atom("button", "button"), + Atom("callTemplate", "call-template"), + Atom("canvas", "canvas"), + Atom("caption", "caption"), + Atom("captionBox", "caption-box"), + Atom("capture", "capture"), + Atom("caseOrder", "case-order"), + Atom("cdataSectionElements", "cdata-section-elements"), + Atom("ceiling", "ceiling"), + Atom("cell", "cell"), + Atom("cellpadding", "cellpadding"), + Atom("cellspacing", "cellspacing"), + Atom("center", "center"), + Atom("change", "change"), + Atom("_char", "char"), + Atom("characterData", "characterData"), + Atom("charcode", "charcode"), + Atom("charoff", "charoff"), + Atom("charset", "charset"), + Atom("checkbox", "checkbox"), + Atom("checkboxLabel", "checkbox-label"), + Atom("checked", "checked"), + Atom("child", "child"), + Atom("children", "children"), + Atom("childList", "childList"), + Atom("child_item_count", "child-item-count"), + Atom("choose", "choose"), + Atom("chromemargin", "chromemargin"), + Atom("exposeToUntrustedContent", "exposeToUntrustedContent"), + Atom("circ", "circ"), + Atom("circle", "circle"), + Atom("cite", "cite"), + Atom("_class", "class"), + Atom("classid", "classid"), + Atom("clear", "clear"), + Atom("click", "click"), + Atom("clickcount", "clickcount"), + Atom("movetoclick", "movetoclick"), + Atom("clip", "clip"), + Atom("close", "close"), + Atom("closed", "closed"), + Atom("closemenu", "closemenu"), + Atom("code", "code"), + Atom("codebase", "codebase"), + Atom("codetype", "codetype"), + Atom("col", "col"), + Atom("colgroup", "colgroup"), + Atom("collapse", "collapse"), + Atom("collapsed", "collapsed"), + Atom("color", "color"), + Atom("color_gamut", "color-gamut"), + Atom("color_index", "color-index"), + Atom("color_scheme", "color-scheme"), + Atom("cols", "cols"), + Atom("colspan", "colspan"), + Atom("combobox", "combobox"), + Atom("command", "command"), + Atom("commandupdater", "commandupdater"), + Atom("comment", "comment"), + Atom("compact", "compact"), + Atom("concat", "concat"), + Atom("constructor", "constructor"), + Atom("consumeoutsideclicks", "consumeoutsideclicks"), + Atom("container", "container"), + Atom("contains", "contains"), + Atom("content", "content"), + Atom("contenteditable", "contenteditable"), + Atom("headerContentDisposition", "content-disposition"), + Atom("headerContentLanguage", "content-language"), + Atom("contentLocation", "content-location"), + Atom("headerContentScriptType", "content-script-type"), + Atom("headerContentStyleType", "content-style-type"), + Atom("headerContentType", "content-type"), + Atom("consumeanchor", "consumeanchor"), + Atom("context", "context"), + Atom("contextmenu", "contextmenu"), + Atom("control", "control"), + Atom("controls", "controls"), + Atom("coords", "coords"), + Atom("copy", "copy"), + Atom("copyOf", "copy-of"), + Atom("count", "count"), + Atom("crop", "crop"), + Atom("crossorigin", "crossorigin"), + Atom("curpos", "curpos"), + Atom("current", "current"), + Atom("cutoutregion", "cutoutregion"), + Atom("cycler", "cycler"), + Atom("dashed", "dashed"), + Atom("data", "data"), + Atom("dataAtShortcutkeys", "data-at-shortcutkeys"), + Atom("datalist", "datalist"), + Atom("datal10nid", "data-l10n-id"), + Atom("datal10nargs", "data-l10n-args"), + Atom("datal10nattrs", "data-l10n-attrs"), + Atom("datal10nname", "data-l10n-name"), + Atom("datal10nsync", "data-l10n-sync"), + Atom("dataType", "data-type"), + Atom("dateTime", "date-time"), + Atom("date", "date"), + Atom("datetime", "datetime"), + Atom("datetime_local", "datetime-local"), + Atom("datetimeInputBoxWrapper", "datetime-input-box-wrapper"), + Atom("dd", "dd"), + Atom("decimal", "decimal"), + Atom("decimalFormat", "decimal-format"), + Atom("decimalSeparator", "decimal-separator"), + Atom("declare", "declare"), + Atom("decoderDoctor", "decoder-doctor"), + Atom("decoding", "decoding"), + Atom("decrement", "decrement"), + Atom("_default", "default"), + Atom("headerDefaultStyle", "default-style"), + Atom("defer", "defer"), + Atom("del", "del"), + Atom("delegatesanchor", "delegatesanchor"), + Atom("deletion", "deletion"), + Atom("deprecation", "deprecation"), + Atom("descendant", "descendant"), + Atom("descendantOrSelf", "descendant-or-self"), + Atom("descending", "descending"), + Atom("description", "description"), + Atom("destructor", "destructor"), + Atom("details", "details"), + Atom("deviceAspectRatio", "device-aspect-ratio"), + Atom("deviceHeight", "device-height"), + Atom("devicePixelRatio", "device-pixel-ratio"), + Atom("deviceWidth", "device-width"), + Atom("dfn", "dfn"), + Atom("dialog", "dialog"), + Atom("difference", "difference"), + Atom("digit", "digit"), + Atom("dir", "dir"), + Atom("dirAutoSetBy", "dirAutoSetBy"), + Atom("directory", "directory"), + Atom("dirname", "dirname"), + Atom("disableOutputEscaping", "disable-output-escaping"), + Atom("disabled", "disabled"), + Atom("disableglobalhistory", "disableglobalhistory"), + Atom("disablehistory", "disablehistory"), + Atom("disablefullscreen", "disablefullscreen"), + Atom("disablepictureinpicture", "disablepictureinpicture"), + Atom("disclosure_closed", "disclosure-closed"), + Atom("disclosure_open", "disclosure-open"), + Atom("display", "display"), + Atom("displayMode", "display-mode"), + Atom("distinct", "distinct"), + Atom("div", "div"), + Atom("dl", "dl"), + Atom("docAbstract", "doc-abstract"), + Atom("docAcknowledgments", "doc-acknowledgments"), + Atom("docAfterword", "doc-afterword"), + Atom("docAppendix", "doc-appendix"), + Atom("docBacklink", "doc-backlink"), + Atom("docBiblioentry", "doc-biblioentry"), + Atom("docBibliography", "doc-bibliography"), + Atom("docBiblioref", "doc-biblioref"), + Atom("docChapter", "doc-chapter"), + Atom("docColophon", "doc-colophon"), + Atom("docConclusion", "doc-conclusion"), + Atom("docCover", "doc-cover"), + Atom("docCredit", "doc-credit"), + Atom("docCredits", "doc-credits"), + Atom("docDedication", "doc-dedication"), + Atom("docEndnote", "doc-endnote"), + Atom("docEndnotes", "doc-endnotes"), + Atom("docEpigraph", "doc-epigraph"), + Atom("docEpilogue", "doc-epilogue"), + Atom("docErrata", "doc-errata"), + Atom("docExample", "doc-example"), + Atom("docFootnote", "doc-footnote"), + Atom("docForeword", "doc-foreword"), + Atom("docGlossary", "doc-glossary"), + Atom("docGlossref", "doc-glossref"), + Atom("docIndex", "doc-index"), + Atom("docIntroduction", "doc-introduction"), + Atom("docNoteref", "doc-noteref"), + Atom("docNotice", "doc-notice"), + Atom("docPagebreak", "doc-pagebreak"), + Atom("docPagelist", "doc-pagelist"), + Atom("docPart", "doc-part"), + Atom("docPreface", "doc-preface"), + Atom("docPrologue", "doc-prologue"), + Atom("docPullquote", "doc-pullquote"), + Atom("docQna", "doc-qna"), + Atom("docSubtitle", "doc-subtitle"), + Atom("docTip", "doc-tip"), + Atom("docToc", "doc-toc"), + Atom("doctypePublic", "doctype-public"), + Atom("doctypeSystem", "doctype-system"), + Atom("document", "document"), + Atom("down", "down"), + Atom("download", "download"), + Atom("drag", "drag"), + Atom("draggable", "draggable"), + Atom("dragging", "dragging"), + Atom("dragSession", "dragSession"), + Atom("drawtitle", "drawtitle"), + Atom("dropAfter", "dropAfter"), + Atom("dropBefore", "dropBefore"), + Atom("dropOn", "dropOn"), + Atom("dropMarker", "dropmarker"), + Atom("dt", "dt"), + Atom("e", "e"), + Atom("editable", "editable"), + Atom("editing", "editing"), + Atom("editor", "editor"), + Atom("element", "element"), + Atom("elementAvailable", "element-available"), + Atom("elements", "elements"), + Atom("em", "em"), + Atom("embed", "embed"), + Atom("emphasis", "emphasis"), + Atom("empty", "empty"), + Atom("encoding", "encoding"), + Atom("enctype", "enctype"), + Atom("end", "end"), + Atom("endEvent", "endEvent"), + Atom("enterkeyhint", "enterkeyhint"), + Atom("error", "error"), + Atom("ethiopic_numeric", "ethiopic-numeric"), + Atom("even", "even"), + Atom("event", "event"), + Atom("events", "events"), + Atom("excludeResultPrefixes", "exclude-result-prefixes"), + Atom("exportparts", "exportparts"), + Atom("explicit_name", "explicit-name"), + Atom("extends", "extends"), + Atom("extensionElementPrefixes", "extension-element-prefixes"), + Atom("face", "face"), + Atom("fallback", "fallback"), + Atom("_false", "false"), + Atom("farthest", "farthest"), + Atom("featurePolicyViolation", "feature-policy-violation"), + Atom("fetchpriority", "fetchpriority"), + Atom("field", "field"), + Atom("fieldset", "fieldset"), + Atom("file", "file"), + Atom("figcaption", "figcaption"), + Atom("figure", "figure"), + Atom("findbar", "findbar"), + Atom("firstInput", "first-input"), + Atom("fixed", "fixed"), + Atom("flags", "flags"), + Atom("flex", "flex"), + Atom("flip", "flip"), + Atom("floating", "floating"), + Atom("floor", "floor"), + Atom("flowlength", "flowlength"), + Atom("focus", "focus"), + Atom("focused", "focused"), + Atom("followanchor", "followanchor"), + Atom("following", "following"), + Atom("followingSibling", "following-sibling"), + Atom("font", "font"), + Atom("fontWeight", "font-weight"), + Atom("footer", "footer"), + Atom("_for", "for"), + Atom("forEach", "for-each"), + Atom("forcedColors", "forced-colors"), + Atom("invertedColors", "inverted-colors"), + Atom("forceOwnRefreshDriver", "forceOwnRefreshDriver"), + Atom("form", "form"), + Atom("formaction", "formaction"), + Atom("format", "format"), + Atom("formatNumber", "format-number"), + Atom("formenctype", "formenctype"), + Atom("formmethod", "formmethod"), + Atom("formnovalidate", "formnovalidate"), + Atom("formtarget", "formtarget"), + Atom("frame", "frame"), + Atom("frameborder", "frameborder"), + Atom("frameset", "frameset"), + Atom("from", "from"), + Atom("fullscreenchange", "fullscreenchange"), + Atom("fullscreenerror", "fullscreenerror"), + Atom("functionAvailable", "function-available"), + Atom("generateId", "generate-id"), + Atom("generic", "generic"), + Atom("getter", "getter"), + Atom("graphicsDocument", "graphics-document"), + Atom("graphicsObject", "graphics-object"), + Atom("graphicsSymbol", "graphics-symbol"), + Atom("grid", "grid"), + Atom("group", "group"), + Atom("groups", "groups"), + Atom("groupbox", "groupbox"), + Atom("groupingSeparator", "grouping-separator"), + Atom("groupingSize", "grouping-size"), + Atom("grow", "grow"), + Atom("h1", "h1"), + Atom("h2", "h2"), + Atom("h3", "h3"), + Atom("h4", "h4"), + Atom("h5", "h5"), + Atom("h6", "h6"), + Atom("handheldFriendly", "HandheldFriendly"), + Atom("handler", "handler"), + Atom("handlers", "handlers"), + Atom("HARD", "HARD"), + Atom("hasSameNode", "has-same-node"), + Atom("hbox", "hbox"), + Atom("head", "head"), + Atom("header", "header"), + Atom("headers", "headers"), + Atom("hebrew", "hebrew"), + Atom("height", "height"), + Atom("hgroup", "hgroup"), + Atom("hidden", "hidden"), + Atom("hidechrome", "hidechrome"), + Atom("hidecolumnpicker", "hidecolumnpicker"), + Atom("high", "high"), + Atom("highest", "highest"), + Atom("horizontal", "horizontal"), + Atom("hover", "hover"), + Atom("hr", "hr"), + Atom("href", "href"), + Atom("hreflang", "hreflang"), + Atom("hsides", "hsides"), + Atom("hspace", "hspace"), + Atom("html", "html"), + Atom("httpEquiv", "http-equiv"), + Atom("i", "i"), + Atom("icon", "icon"), + Atom("id", "id"), + Atom("_if", "if"), + Atom("iframe", "iframe"), + Atom("ignorekeys", "ignorekeys"), + Atom("ignoreuserfocus", "ignoreuserfocus"), + Atom("image", "image"), + Atom("imageClickedPoint", "image-clicked-point"), + Atom("imagesizes", "imagesizes"), + Atom("imagesrcset", "imagesrcset"), + Atom("img", "img"), + Atom("implementation", "implementation"), + Atom("implements", "implements"), + Atom("import", "import"), + Atom("include", "include"), + Atom("includes", "includes"), + Atom("incontentshell", "incontentshell"), + Atom("increment", "increment"), + Atom("indent", "indent"), + Atom("indeterminate", "indeterminate"), + Atom("index", "index"), + Atom("inert", "inert"), + Atom("infinity", "infinity"), + Atom("inherits", "inherits"), + Atom("inheritOverflow", "inherit-overflow"), + Atom("inheritstyle", "inheritstyle"), + Atom("initial_scale", "initial-scale"), + Atom("input", "input"), + Atom("inputmode", "inputmode"), + Atom("ins", "ins"), + Atom("insertafter", "insertafter"), + Atom("insertbefore", "insertbefore"), + Atom("insertion", "insertion"), + Atom("integer", "integer"), + Atom("integrity", "integrity"), + Atom("internal", "internal"), + Atom("internals", "internals"), + Atom("intersection", "intersection"), + Atom("intersectionobserverlist", "intersectionobserverlist"), + Atom("invoketarget", "invoketarget"), + Atom("invokeaction", "invokeaction"), + Atom("is", "is"), + Atom("ismap", "ismap"), + Atom("ispopup", "ispopup"), + Atom("itemid", "itemid"), + Atom("itemprop", "itemprop"), + Atom("itemref", "itemref"), + Atom("itemscope", "itemscope"), + Atom("itemtype", "itemtype"), + Atom("japanese_formal", "japanese-formal"), + Atom("japanese_informal", "japanese-informal"), + Atom("kbd", "kbd"), + Atom("keepcurrentinview", "keepcurrentinview"), + Atom("keepobjectsalive", "keepobjectsalive"), + Atom("key", "key"), + Atom("keycode", "keycode"), + Atom("keydown", "keydown"), + Atom("keygen", "keygen"), + Atom("keypress", "keypress"), + Atom("keyset", "keyset"), + Atom("keysystem", "keysystem"), + Atom("keyup", "keyup"), + Atom("kind", "kind"), + Atom("korean_hangul_formal", "korean-hangul-formal"), + Atom("korean_hanja_formal", "korean-hanja-formal"), + Atom("korean_hanja_informal", "korean-hanja-informal"), + Atom("label", "label"), + Atom("lang", "lang"), + Atom("language", "language"), + Atom("last", "last"), + Atom("layer", "layer"), + Atom("LayerActivity", "LayerActivity"), + Atom("layout_guess", "layout-guess"), + Atom("leading", "leading"), + Atom("leaf", "leaf"), + Atom("left", "left"), + Atom("leftmargin", "leftmargin"), + Atom("legend", "legend"), + Atom("length", "length"), + Atom("letterValue", "letter-value"), + Atom("level", "level"), + Atom("lhs", "lhs"), + Atom("li", "li"), + Atom("line", "line"), + Atom("link", "link"), + Atom("linkset", "linkset"), + # Atom("list", "list"), # "list" is present below + Atom("listbox", "listbox"), + Atom("listener", "listener"), + Atom("listheader", "listheader"), + Atom("listing", "listing"), + Atom("listitem", "listitem"), + Atom("load", "load"), + Atom("loading", "loading"), + Atom("triggeringprincipal", "triggeringprincipal"), + Atom("localedir", "localedir"), + Atom("localName", "local-name"), + Atom("localization", "localization"), + Atom("longdesc", "longdesc"), + Atom("loop", "loop"), + Atom("low", "low"), + Atom("lowerFirst", "lower-first"), + Atom("lowest", "lowest"), + Atom("lowsrc", "lowsrc"), + Atom("ltr", "ltr"), + Atom("lwtheme", "lwtheme"), + Atom("main", "main"), + Atom("map", "map"), + Atom("manifest", "manifest"), + Atom("marginBottom", "margin-bottom"), + Atom("marginLeft", "margin-left"), + Atom("marginRight", "margin-right"), + Atom("marginTop", "margin-top"), + Atom("marginheight", "marginheight"), + Atom("marginwidth", "marginwidth"), + Atom("mark", "mark"), + Atom("marquee", "marquee"), + Atom("match", "match"), + Atom("max", "max"), + Atom("maxheight", "maxheight"), + Atom("maximum_scale", "maximum-scale"), + Atom("maxlength", "maxlength"), + Atom("maxpos", "maxpos"), + Atom("maxwidth", "maxwidth"), + Atom("measure", "measure"), + Atom("media", "media"), + Atom("mediaType", "media-type"), + Atom("menu", "menu"), + Atom("menubar", "menubar"), + Atom("menucaption", "menucaption"), + Atom("menugroup", "menugroup"), + Atom("menuitem", "menuitem"), + Atom("menulist", "menulist"), + Atom("menupopup", "menupopup"), + Atom("menuseparator", "menuseparator"), + Atom("mesh", "mesh"), + Atom("message", "message"), + Atom("meta", "meta"), + Atom("referrer", "referrer"), + Atom("referrerpolicy", "referrerpolicy"), + Atom("renderroot", "renderroot"), + Atom("headerReferrerPolicy", "referrer-policy"), + Atom("meter", "meter"), + Atom("method", "method"), + Atom("middle", "middle"), + Atom("min", "min"), + Atom("minheight", "minheight"), + Atom("minimum_scale", "minimum-scale"), + Atom("minlength", "minlength"), + Atom("minpos", "minpos"), + Atom("minusSign", "minus-sign"), + Atom("minwidth", "minwidth"), + Atom("mixed", "mixed"), + Atom("messagemanagergroup", "messagemanagergroup"), + Atom("mod", "mod"), + Atom("_module", "module"), + Atom("mode", "mode"), + Atom("modifiers", "modifiers"), + Atom("monochrome", "monochrome"), + Atom("mouseover", "mouseover"), + Atom("mozAccessiblecaret", "moz-accessiblecaret"), + Atom("mozCustomContentContainer", "moz-custom-content-container"), + Atom("mozGrabber", "mozGrabber"), + Atom("mozNativeAnonymous", "-moz-native-anonymous"), + Atom("mozprivatebrowsing", "mozprivatebrowsing"), + Atom("mozResizer", "mozResizer"), + Atom("mozResizingInfo", "mozResizingInfo"), + Atom("mozResizingShadow", "mozResizingShadow"), + Atom("mozTableAddColumnAfter", "mozTableAddColumnAfter"), + Atom("mozTableAddColumnBefore", "mozTableAddColumnBefore"), + Atom("mozTableAddRowAfter", "mozTableAddRowAfter"), + Atom("mozTableAddRowBefore", "mozTableAddRowBefore"), + Atom("mozTableRemoveRow", "mozTableRemoveRow"), + Atom("mozTableRemoveColumn", "mozTableRemoveColumn"), + Atom("moz_opaque", "moz-opaque"), + Atom("multicol", "multicol"), + Atom("multiple", "multiple"), + Atom("muted", "muted"), + Atom("name", "name"), + Atom("_namespace", "namespace"), + Atom("namespaceAlias", "namespace-alias"), + Atom("namespaceUri", "namespace-uri"), + Atom("NaN", "NaN"), + Atom("n", "n"), + Atom("nav", "nav"), + Atom("ne", "ne"), + Atom("never", "never"), + Atom("_new", "new"), + Atom("newline", "newline"), + Atom("nextRemoteTabId", "nextRemoteTabId"), + Atom("no", "no"), + Atom("noautofocus", "noautofocus"), + Atom("noautohide", "noautohide"), + Atom("norolluponanchor", "norolluponanchor"), + Atom("noBar", "no-bar"), + Atom("nobr", "nobr"), + Atom("nodefaultsrc", "nodefaultsrc"), + Atom("nodeSet", "node-set"), + Atom("noembed", "noembed"), + Atom("noframes", "noframes"), + Atom("nohref", "nohref"), + Atom("nomodule", "nomodule"), + Atom("nonce", "nonce"), + Atom("none", "none"), + Atom("noresize", "noresize"), + Atom("normal", "normal"), + Atom("normalizeSpace", "normalize-space"), + Atom("noscript", "noscript"), + Atom("noshade", "noshade"), + Atom("notification", "notification"), + Atom("novalidate", "novalidate"), + Atom("_not", "not"), + Atom("nowrap", "nowrap"), + Atom("number", "number"), + Atom("nw", "nw"), + Atom("object", "object"), + Atom("objectType", "object-type"), + Atom("observes", "observes"), + Atom("odd", "odd"), + Atom("OFF", "OFF"), + Atom("ol", "ol"), + Atom("omitXmlDeclaration", "omit-xml-declaration"), + Atom("onabort", "onabort"), + Atom("onmozaccesskeynotfound", "onmozaccesskeynotfound"), + Atom("onactivate", "onactivate"), + Atom("onafterprint", "onafterprint"), + Atom("onafterscriptexecute", "onafterscriptexecute"), + Atom("onanimationcancel", "onanimationcancel"), + Atom("onanimationend", "onanimationend"), + Atom("onanimationiteration", "onanimationiteration"), + Atom("onanimationstart", "onanimationstart"), + Atom("onAppCommand", "onAppCommand"), + Atom("onaudioprocess", "onaudioprocess"), + Atom("onauxclick", "onauxclick"), + Atom("onbeforecopy", "onbeforecopy"), + Atom("onbeforecut", "onbeforecut"), + Atom("onbeforeinput", "onbeforeinput"), + Atom("onbeforepaste", "onbeforepaste"), + Atom("onbeforeprint", "onbeforeprint"), + Atom("onbeforescriptexecute", "onbeforescriptexecute"), + Atom("onbeforeunload", "onbeforeunload"), + Atom("onblocked", "onblocked"), + Atom("onblur", "onblur"), + Atom("onbounce", "onbounce"), + Atom("onboundschange", "onboundschange"), + Atom("onbroadcast", "onbroadcast"), + Atom("onbufferedamountlow", "onbufferedamountlow"), + Atom("oncached", "oncached"), + Atom("oncancel", "oncancel"), + Atom("onchange", "onchange"), + Atom("onchargingchange", "onchargingchange"), + Atom("onchargingtimechange", "onchargingtimechange"), + Atom("onchecking", "onchecking"), + Atom("onCheckboxStateChange", "onCheckboxStateChange"), + Atom("onCheckKeyPressEventModel", "onCheckKeyPressEventModel"), + Atom("onclick", "onclick"), + Atom("onclose", "onclose"), + Atom("oncommand", "oncommand"), + Atom("oncommandupdate", "oncommandupdate"), + Atom("oncomplete", "oncomplete"), + Atom("oncompositionend", "oncompositionend"), + Atom("oncompositionstart", "oncompositionstart"), + Atom("oncompositionupdate", "oncompositionupdate"), + Atom("onconnect", "onconnect"), + Atom("onconnectionavailable", "onconnectionavailable"), + Atom("oncontextmenu", "oncontextmenu"), + Atom("oncontextlost", "oncontextlost"), + Atom("oncontextrestored", "oncontextrestored"), + Atom("oncopy", "oncopy"), + Atom("oncut", "oncut"), + Atom("ondblclick", "ondblclick"), + Atom("ondischargingtimechange", "ondischargingtimechange"), + Atom("ondownloading", "ondownloading"), + Atom("onDOMActivate", "onDOMActivate"), + Atom("onDOMAttrModified", "onDOMAttrModified"), + Atom("onDOMCharacterDataModified", "onDOMCharacterDataModified"), + Atom("onDOMFocusIn", "onDOMFocusIn"), + Atom("onDOMFocusOut", "onDOMFocusOut"), + Atom("onDOMMouseScroll", "onDOMMouseScroll"), + Atom("onDOMNodeInserted", "onDOMNodeInserted"), + Atom("onDOMNodeInsertedIntoDocument", "onDOMNodeInsertedIntoDocument"), + Atom("onDOMNodeRemoved", "onDOMNodeRemoved"), + Atom("onDOMNodeRemovedFromDocument", "onDOMNodeRemovedFromDocument"), + Atom("onDOMSubtreeModified", "onDOMSubtreeModified"), + Atom("ondata", "ondata"), + Atom("ondrag", "ondrag"), + Atom("ondragdrop", "ondragdrop"), + Atom("ondragend", "ondragend"), + Atom("ondragenter", "ondragenter"), + Atom("ondragexit", "ondragexit"), + Atom("ondragleave", "ondragleave"), + Atom("ondragover", "ondragover"), + Atom("ondragstart", "ondragstart"), + Atom("ondrain", "ondrain"), + Atom("ondrop", "ondrop"), + Atom("onerror", "onerror"), + Atom("onfinish", "onfinish"), + Atom("onfocus", "onfocus"), + Atom("onfocusin", "onfocusin"), + Atom("onfocusout", "onfocusout"), + Atom("onfullscreenchange", "onfullscreenchange"), + Atom("onfullscreenerror", "onfullscreenerror"), + Atom("onget", "onget"), + Atom("onhashchange", "onhashchange"), + Atom("oninput", "oninput"), + Atom("oninputsourceschange", "oninputsourceschange"), + Atom("oninstall", "oninstall"), + Atom("oninvalid", "oninvalid"), + Atom("onkeydown", "onkeydown"), + Atom("onkeypress", "onkeypress"), + Atom("onkeyup", "onkeyup"), + Atom("onlanguagechange", "onlanguagechange"), + Atom("onlevelchange", "onlevelchange"), + Atom("onload", "onload"), + Atom("onloading", "onloading"), + Atom("onloadingdone", "onloadingdone"), + Atom("onloadingerror", "onloadingerror"), + Atom("onpopstate", "onpopstate"), + Atom("only", "only"), # this one is not an event + Atom("onmerchantvalidation", "onmerchantvalidation"), + Atom("onmessage", "onmessage"), + Atom("onmessageerror", "onmessageerror"), + Atom("onmidimessage", "onmidimessage"), + Atom("onmousedown", "onmousedown"), + Atom("onmouseenter", "onmouseenter"), + Atom("onmouseleave", "onmouseleave"), + Atom("onmouselongtap", "onmouselongtap"), + Atom("onmousemove", "onmousemove"), + Atom("onmouseout", "onmouseout"), + Atom("onmouseover", "onmouseover"), + Atom("onMozMouseHittest", "onMozMouseHittest"), + Atom("onMozMouseExploreByTouch", "onMozMouseExploreByTouch"), + Atom("onmouseup", "onmouseup"), + Atom("onMozAfterPaint", "onMozAfterPaint"), + Atom("onmozfullscreenchange", "onmozfullscreenchange"), + Atom("onmozfullscreenerror", "onmozfullscreenerror"), + Atom("onmozpointerlockchange", "onmozpointerlockchange"), + Atom("onmozpointerlockerror", "onmozpointerlockerror"), + Atom("onMozMousePixelScroll", "onMozMousePixelScroll"), + Atom("onMozScrolledAreaChanged", "onMozScrolledAreaChanged"), + Atom("onmute", "onmute"), + Atom("onnotificationclick", "onnotificationclick"), + Atom("onnotificationclose", "onnotificationclose"), + Atom("onnoupdate", "onnoupdate"), + Atom("onobsolete", "onobsolete"), + Atom("ononline", "ononline"), + Atom("onoffline", "onoffline"), + Atom("onopen", "onopen"), + Atom("onorientationchange", "onorientationchange"), + Atom("onoverflow", "onoverflow"), + Atom("onpagehide", "onpagehide"), + Atom("onpageshow", "onpageshow"), + Atom("onpaste", "onpaste"), + Atom("onpayerdetailchange", "onpayerdetailchange"), + Atom("onpaymentmethodchange", "onpaymentmethodchange"), + Atom("onpointerlockchange", "onpointerlockchange"), + Atom("onpointerlockerror", "onpointerlockerror"), + Atom("onpopuphidden", "onpopuphidden"), + Atom("onpopuphiding", "onpopuphiding"), + Atom("onpopuppositioned", "onpopuppositioned"), + Atom("onpopupshowing", "onpopupshowing"), + Atom("onpopupshown", "onpopupshown"), + Atom("onprocessorerror", "onprocessorerror"), + Atom("onprioritychange", "onprioritychange"), + Atom("onpush", "onpush"), + Atom("onpushsubscriptionchange", "onpushsubscriptionchange"), + Atom("onRadioStateChange", "onRadioStateChange"), + Atom("onreadystatechange", "onreadystatechange"), + Atom("onrejectionhandled", "onrejectionhandled"), + Atom("onremove", "onremove"), + Atom("onrequestprogress", "onrequestprogress"), + Atom("onresourcetimingbufferfull", "onresourcetimingbufferfull"), + Atom("onresponseprogress", "onresponseprogress"), + Atom("onRequest", "onRequest"), + Atom("onreset", "onreset"), + Atom("onresize", "onresize"), + Atom("onscroll", "onscroll"), + Atom("onsecuritypolicyviolation", "onsecuritypolicyviolation"), + Atom("onselect", "onselect"), + Atom("onselectionchange", "onselectionchange"), + Atom("onselectend", "onselectend"), + Atom("onselectstart", "onselectstart"), + Atom("onset", "onset"), + Atom("onshippingaddresschange", "onshippingaddresschange"), + Atom("onshippingoptionchange", "onshippingoptionchange"), + Atom("onshow", "onshow"), + Atom("onslotchange", "onslotchange"), + Atom("onsqueeze", "onsqueeze"), + Atom("onsqueezeend", "onsqueezeend"), + Atom("onsqueezestart", "onsqueezestart"), + Atom("onstatechange", "onstatechange"), + Atom("onstorage", "onstorage"), + Atom("onsubmit", "onsubmit"), + Atom("onsuccess", "onsuccess"), + Atom("onsystemstatusbarclick", "onsystemstatusbarclick"), + Atom("ontypechange", "ontypechange"), + Atom("onterminate", "onterminate"), + Atom("ontext", "ontext"), + Atom("ontoggle", "ontoggle"), + Atom("ontonechange", "ontonechange"), + Atom("ontouchstart", "ontouchstart"), + Atom("ontouchend", "ontouchend"), + Atom("ontouchmove", "ontouchmove"), + Atom("ontouchcancel", "ontouchcancel"), + Atom("ontransitioncancel", "ontransitioncancel"), + Atom("ontransitionend", "ontransitionend"), + Atom("ontransitionrun", "ontransitionrun"), + Atom("ontransitionstart", "ontransitionstart"), + Atom("onuncapturederror", "onuncapturederror"), + Atom("onunderflow", "onunderflow"), + Atom("onunhandledrejection", "onunhandledrejection"), + Atom("onunload", "onunload"), + Atom("onunmute", "onunmute"), + Atom("onupdatefound", "onupdatefound"), + Atom("onupdateready", "onupdateready"), + Atom("onupgradeneeded", "onupgradeneeded"), + Atom("onversionchange", "onversionchange"), + Atom("onvisibilitychange", "onvisibilitychange"), + Atom("onvoiceschanged", "onvoiceschanged"), + Atom("onvrdisplayactivate", "onvrdisplayactivate"), + Atom("onvrdisplayconnect", "onvrdisplayconnect"), + Atom("onvrdisplaydeactivate", "onvrdisplaydeactivate"), + Atom("onvrdisplaydisconnect", "onvrdisplaydisconnect"), + Atom("onvrdisplaypresentchange", "onvrdisplaypresentchange"), + Atom("onwebkitAnimationEnd", "onwebkitAnimationEnd"), + Atom("onwebkitAnimationIteration", "onwebkitAnimationIteration"), + Atom("onwebkitAnimationStart", "onwebkitAnimationStart"), + Atom("onwebkitTransitionEnd", "onwebkitTransitionEnd"), + Atom("onwebkitanimationend", "onwebkitanimationend"), + Atom("onwebkitanimationiteration", "onwebkitanimationiteration"), + Atom("onwebkitanimationstart", "onwebkitanimationstart"), + Atom("onwebkittransitionend", "onwebkittransitionend"), + Atom("onwheel", "onwheel"), + Atom("open", "open"), + Atom("optgroup", "optgroup"), + Atom("optimum", "optimum"), + Atom("option", "option"), + Atom("_or", "or"), + Atom("order", "order"), + Atom("orient", "orient"), + Atom("orientation", "orientation"), + Atom("origin_trial", "origin-trial"), + Atom("otherwise", "otherwise"), + Atom("output", "output"), + Atom("overflow", "overflow"), + Atom("overflowBlock", "overflow-block"), + Atom("overflowInline", "overflow-inline"), + Atom("overlay", "overlay"), + Atom("p", "p"), + Atom("pack", "pack"), + Atom("page", "page"), + Atom("pageincrement", "pageincrement"), + Atom("paint", "paint"), + Atom("paint_order", "paint-order"), + Atom("panel", "panel"), + Atom("paragraph", "paragraph"), + Atom("param", "param"), + Atom("parameter", "parameter"), + Atom("parent", "parent"), + Atom("parsererror", "parsererror"), + Atom("part", "part"), + Atom("password", "password"), + Atom("pattern", "pattern"), + Atom("patternSeparator", "pattern-separator"), + Atom("perMille", "per-mille"), + Atom("percent", "percent"), + Atom("persist", "persist"), + Atom("phase", "phase"), + Atom("picture", "picture"), + Atom("ping", "ping"), + Atom("pinned", "pinned"), + Atom("placeholder", "placeholder"), + Atom("plaintext", "plaintext"), + Atom("playbackrate", "playbackrate"), + Atom("pointSize", "point-size"), + Atom("poly", "poly"), + Atom("polygon", "polygon"), + Atom("popover", "popover"), + Atom("popovertarget", "popovertarget"), + Atom("popovertargetaction", "popovertargetaction"), + Atom("popup", "popup"), + Atom("popupalign", "popupalign"), + Atom("popupanchor", "popupanchor"), + Atom("popupgroup", "popupgroup"), + Atom("popupset", "popupset"), + Atom("popupsinherittooltip", "popupsinherittooltip"), + Atom("portal", "portal"), + Atom("position", "position"), + Atom("poster", "poster"), + Atom("pre", "pre"), + Atom("preceding", "preceding"), + Atom("precedingSibling", "preceding-sibling"), + Atom("prefersReducedMotion", "prefers-reduced-motion"), + Atom("prefersReducedTransparency", "prefers-reduced-transparency"), + Atom("prefersColorScheme", "prefers-color-scheme"), + Atom("prefersContrast", "prefers-contrast"), + Atom("prefix", "prefix"), + Atom("prefwidth", "prefwidth"), + Atom("dynamicRange", "dynamic-range"), + Atom("videoDynamicRange", "video-dynamic-range"), + Atom("scripting", "scripting"), + Atom("preload", "preload"), + Atom("preserve", "preserve"), + Atom("preserveSpace", "preserve-space"), + Atom("preventdefault", "preventdefault"), + Atom("previewDiv", "preview-div"), + Atom("primary", "primary"), + Atom("print", "print"), + Atom("printisfocuseddoc", "printisfocuseddoc"), + Atom("printselectionranges", "printselectionranges"), + Atom("priority", "priority"), + Atom("processingInstruction", "processing-instruction"), + Atom("profile", "profile"), + Atom("progress", "progress"), + Atom("prompt", "prompt"), + Atom("properties", "properties"), + Atom("property", "property"), + Atom("pubdate", "pubdate"), + Atom("q", "q"), + Atom("radio", "radio"), + Atom("radioLabel", "radio-label"), + Atom("radiogroup", "radiogroup"), + Atom("range", "range"), + Atom("readonly", "readonly"), + Atom("rect", "rect"), + Atom("rectangle", "rectangle"), + Atom("refresh", "refresh"), + Atom("rel", "rel"), + Atom("relativeBounds", "relative-bounds"), + Atom("rem", "rem"), + Atom("remote", "remote"), + Atom("removeelement", "removeelement"), + Atom("renderingobserverset", "renderingobserverset"), + Atom("repeat", "repeat"), + Atom("replace", "replace"), + Atom("requestcontextid", "requestcontextid"), + Atom("required", "required"), + Atom("reserved", "reserved"), + Atom("reset", "reset"), + Atom("resizeafter", "resizeafter"), + Atom("resizebefore", "resizebefore"), + Atom("resizer", "resizer"), + Atom("resolution", "resolution"), + Atom("resources", "resources"), + Atom("result", "result"), + Atom("resultPrefix", "result-prefix"), + Atom("retargetdocumentfocus", "retargetdocumentfocus"), + Atom("rev", "rev"), + Atom("reverse", "reverse"), + Atom("reversed", "reversed"), + Atom("rhs", "rhs"), + Atom("richlistbox", "richlistbox"), + Atom("richlistitem", "richlistitem"), + Atom("right", "right"), + Atom("rightmargin", "rightmargin"), + Atom("role", "role"), + Atom("rolluponmousewheel", "rolluponmousewheel"), + Atom("round", "round"), + Atom("row", "row"), + Atom("rows", "rows"), + Atom("rowspan", "rowspan"), + Atom("rb", "rb"), + Atom("rp", "rp"), + Atom("rt", "rt"), + Atom("rtc", "rtc"), + Atom("rtl", "rtl"), + Atom("ruby", "ruby"), + Atom("rubyBase", "ruby-base"), + Atom("rubyBaseContainer", "ruby-base-container"), + Atom("rubyText", "ruby-text"), + Atom("rubyTextContainer", "ruby-text-container"), + Atom("rules", "rules"), + Atom("s", "s"), + Atom("safe_area_inset_top", "safe-area-inset-top"), + Atom("safe_area_inset_bottom", "safe-area-inset-bottom"), + Atom("safe_area_inset_left", "safe-area-inset-left"), + Atom("safe_area_inset_right", "safe-area-inset-right"), + Atom("samp", "samp"), + Atom("sandbox", "sandbox"), + Atom("sbattr", "sbattr"), + Atom("scale", "scale"), + Atom("scan", "scan"), + Atom("scheme", "scheme"), + Atom("scope", "scope"), + Atom("scoped", "scoped"), + Atom("screen", "screen"), + Atom("screenX", "screenX"), + Atom("screenx", "screenx"), + Atom("screenY", "screenY"), + Atom("screeny", "screeny"), + Atom("script", "script"), + Atom("scrollbar", "scrollbar"), + Atom("scrollbarThumb", "scrollbar-thumb"), + Atom("scrollamount", "scrollamount"), + Atom("scrollbarbutton", "scrollbarbutton"), + Atom("scrollbarDownBottom", "scrollbar-down-bottom"), + Atom("scrollbarDownTop", "scrollbar-down-top"), + Atom("scrollbarInlineSize", "scrollbar-inline-size"), + Atom("scrollbarUpBottom", "scrollbar-up-bottom"), + Atom("scrollbarUpTop", "scrollbar-up-top"), + Atom("scrollbox", "scrollbox"), + Atom("scrollcorner", "scrollcorner"), + Atom("scrolldelay", "scrolldelay"), + Atom("scrolling", "scrolling"), + Atom("scrollPosition", "scroll-position"), + Atom("se", "se"), + Atom("section", "section"), + Atom("select", "select"), + Atom("selected", "selected"), + Atom("selectedIndex", "selectedIndex"), + Atom("selectedindex", "selectedindex"), + Atom("selectmenu", "selectmenu"), + Atom("self", "self"), + Atom("seltype", "seltype"), + Atom("setcookie", "set-cookie"), + Atom("setter", "setter"), + Atom("shadow", "shadow"), + Atom("shape", "shape"), + Atom("show", "show"), + Atom("showcaret", "showcaret"), + Atom("showservicesmenu", "showservicesmenu"), + Atom("sibling", "sibling"), + Atom("simple", "simple"), + Atom("simp_chinese_formal", "simp-chinese-formal"), + Atom("simp_chinese_informal", "simp-chinese-informal"), + Atom("single", "single"), + Atom("size", "size"), + Atom("sizes", "sizes"), + Atom("sizemode", "sizemode"), + Atom("sizetopopup", "sizetopopup"), + Atom("slider", "slider"), + Atom("small", "small"), + Atom("smooth", "smooth"), + Atom("snap", "snap"), + Atom("solid", "solid"), + Atom("sort", "sort"), + Atom("sortActive", "sortActive"), + Atom("sortDirection", "sortDirection"), + Atom("sorted", "sorted"), + Atom("sorthints", "sorthints"), + Atom("source", "source"), + Atom("sourcetext", "sourcetext"), + Atom("space", "space"), + Atom("spacer", "spacer"), + Atom("span", "span"), + Atom("spellcheck", "spellcheck"), + Atom("split", "split"), + Atom("splitter", "splitter"), + Atom("square", "square"), + Atom("src", "src"), + Atom("srcdoc", "srcdoc"), + Atom("srclang", "srclang"), + Atom("srcset", "srcset"), + Atom("standalone", "standalone"), + Atom("standby", "standby"), + Atom("start", "start"), + Atom("startsWith", "starts-with"), + Atom("state", "state"), + Atom("statusbar", "statusbar"), + Atom("step", "step"), + Atom("stop", "stop"), + Atom("stretch", "stretch"), + Atom("strike", "strike"), + Atom("string", "string"), + Atom("stringLength", "string-length"), + Atom("stripSpace", "strip-space"), + Atom("strong", "strong"), + Atom("style", "style"), + Atom("stylesheet", "stylesheet"), + Atom("stylesheetPrefix", "stylesheet-prefix"), + Atom("submit", "submit"), + Atom("substate", "substate"), + Atom("substring", "substring"), + Atom("substringAfter", "substring-after"), + Atom("substringBefore", "substring-before"), + Atom("sub", "sub"), + Atom("suggestion", "suggestion"), + Atom("sum", "sum"), + Atom("sup", "sup"), + Atom("summary", "summary"), + Atom("sw", "sw"), + # Atom("_switch", "switch"), # "switch" is present below + Atom("systemProperty", "system-property"), + Atom("tab", "tab"), + Atom("tabindex", "tabindex"), + Atom("table", "table"), + Atom("tabpanel", "tabpanel"), + Atom("tabpanels", "tabpanels"), + Atom("tag", "tag"), + Atom("target", "target"), + Atom("targets", "targets"), + Atom("tbody", "tbody"), + Atom("td", "td"), + Atom("tel", "tel"), + Atom("_template", "template"), + Atom("text_decoration", "text-decoration"), + Atom("terminate", "terminate"), + Atom("term", "term"), + Atom("test", "test"), + Atom("text", "text"), + Atom("textAlign", "text-align"), + Atom("textarea", "textarea"), + Atom("textbox", "textbox"), + Atom("textLink", "text-link"), + Atom("textNodeDirectionalityMap", "textNodeDirectionalityMap"), + Atom("textOverlay", "text-overlay"), + Atom("tfoot", "tfoot"), + Atom("th", "th"), + Atom("thead", "thead"), + Atom("thumb", "thumb"), + Atom("time", "time"), + Atom("title", "title"), + Atom("titlebar", "titlebar"), + Atom("titletip", "titletip"), + Atom("toggle", "toggle"), + Atom("token", "token"), + Atom("tokenize", "tokenize"), + Atom("toolbar", "toolbar"), + Atom("toolbarbutton", "toolbarbutton"), + Atom("toolbaritem", "toolbaritem"), + Atom("toolbarpaletteitem", "toolbarpaletteitem"), + Atom("toolbox", "toolbox"), + Atom("tooltip", "tooltip"), + Atom("tooltiptext", "tooltiptext"), + Atom("top", "top"), + Atom("topleft", "topleft"), + Atom("topmargin", "topmargin"), + Atom("topright", "topright"), + Atom("tr", "tr"), + Atom("track", "track"), + Atom("trad_chinese_formal", "trad-chinese-formal"), + Atom("trad_chinese_informal", "trad-chinese-informal"), + Atom("trailing", "trailing"), + Atom("transform", "transform"), + Atom("transform_3d", "transform-3d"), + Atom("transformiix", "transformiix"), + Atom("translate", "translate"), + Atom("transparent", "transparent"), + Atom("tree", "tree"), + Atom("treecell", "treecell"), + Atom("treechildren", "treechildren"), + Atom("treecol", "treecol"), + Atom("treecolpicker", "treecolpicker"), + Atom("treecols", "treecols"), + Atom("treeitem", "treeitem"), + Atom("treerow", "treerow"), + Atom("treeseparator", "treeseparator"), + Atom("_true", "true"), + Atom("truespeed", "truespeed"), + Atom("tt", "tt"), + Atom("type", "type"), + Atom("u", "u"), + Atom("ul", "ul"), + Atom("unparsedEntityUri", "unparsed-entity-uri"), + Atom("up", "up"), + Atom("update", "update"), + Atom("upperFirst", "upper-first"), + Atom("use", "use"), + Atom("useAttributeSets", "use-attribute-sets"), + Atom("usemap", "usemap"), + Atom("user_scalable", "user-scalable"), + Atom("validate", "validate"), + Atom("valign", "valign"), + Atom("value", "value"), + Atom("values", "values"), + Atom("valueOf", "value-of"), + Atom("valuetype", "valuetype"), + Atom("var", "var"), + Atom("variable", "variable"), + Atom("vendor", "vendor"), + Atom("vendorUrl", "vendor-url"), + Atom("version", "version"), + Atom("vertical", "vertical"), + Atom("audio", "audio"), + Atom("video", "video"), + Atom("viewport", "viewport"), + Atom("viewport_fit", "viewport-fit"), + Atom("viewport_height", "viewport-height"), + Atom("viewport_initial_scale", "viewport-initial-scale"), + Atom("viewport_maximum_scale", "viewport-maximum-scale"), + Atom("viewport_minimum_scale", "viewport-minimum-scale"), + Atom("viewport_user_scalable", "viewport-user-scalable"), + Atom("viewport_width", "viewport-width"), + Atom("visibility", "visibility"), + Atom("visuallyselected", "visuallyselected"), + Atom("vlink", "vlink"), + Atom("_void", "void"), + Atom("vsides", "vsides"), + Atom("vspace", "vspace"), + Atom("w", "w"), + Atom("wbr", "wbr"), + Atom("webkitdirectory", "webkitdirectory"), + Atom("when", "when"), + Atom("white", "white"), + Atom("width", "width"), + Atom("willChange", "will-change"), + Atom("window", "window"), + Atom("headerWindowTarget", "window-target"), + Atom("windowtype", "windowtype"), + Atom("withParam", "with-param"), + Atom("wrap", "wrap"), + Atom("headerDNSPrefetchControl", "x-dns-prefetch-control"), + Atom("headerCSP", "content-security-policy"), + Atom("headerCSPReportOnly", "content-security-policy-report-only"), + Atom("headerXFO", "x-frame-options"), + Atom("x_western", "x-western"), + Atom("xml", "xml"), + Atom("xml_stylesheet", "xml-stylesheet"), + Atom("xmlns", "xmlns"), + Atom("xmp", "xmp"), + Atom("xul", "xul"), + Atom("yes", "yes"), + Atom("z_index", "z-index"), + Atom("zeroDigit", "zero-digit"), + Atom("zlevel", "zlevel"), + Atom("percentage", "%"), + Atom("A", "A"), + Atom("alignment_baseline", "alignment-baseline"), + Atom("amplitude", "amplitude"), + Atom("animate", "animate"), + Atom("animateColor", "animateColor"), + Atom("animateMotion", "animateMotion"), + Atom("animateTransform", "animateTransform"), + Atom("arithmetic", "arithmetic"), + Atom("atop", "atop"), + Atom("azimuth", "azimuth"), + Atom("B", "B"), + Atom("backgroundColor", "background-color"), + Atom("background_image", "background-image"), + Atom("baseFrequency", "baseFrequency"), + Atom("baseline_shift", "baseline-shift"), + Atom("bias", "bias"), + Atom("caption_side", "caption-side"), + Atom("clip_path", "clip-path"), + Atom("clip_rule", "clip-rule"), + Atom("clipPath", "clipPath"), + Atom("clipPathUnits", "clipPathUnits"), + Atom("cm", "cm"), + Atom("colorBurn", "color-burn"), + Atom("colorDodge", "color-dodge"), + Atom("colorInterpolation", "color-interpolation"), + Atom("colorInterpolationFilters", "color-interpolation-filters"), + Atom("colorProfile", "color-profile"), + Atom("cursor", "cursor"), + Atom("cx", "cx"), + Atom("cy", "cy"), + Atom("d", "d"), + Atom("darken", "darken"), + Atom("defs", "defs"), + Atom("deg", "deg"), + Atom("desc", "desc"), + Atom("diffuseConstant", "diffuseConstant"), + Atom("dilate", "dilate"), + Atom("direction", "direction"), + Atom("disable", "disable"), + Atom("disc", "disc"), + Atom("discrete", "discrete"), + Atom("divisor", "divisor"), + Atom("dominant_baseline", "dominant-baseline"), + Atom("duplicate", "duplicate"), + Atom("dx", "dx"), + Atom("dy", "dy"), + Atom("edgeMode", "edgeMode"), + Atom("ellipse", "ellipse"), + Atom("elevation", "elevation"), + Atom("erode", "erode"), + Atom("ex", "ex"), + Atom("exact", "exact"), + Atom("exclusion", "exclusion"), + Atom("exponent", "exponent"), + Atom("feBlend", "feBlend"), + Atom("feColorMatrix", "feColorMatrix"), + Atom("feComponentTransfer", "feComponentTransfer"), + Atom("feComposite", "feComposite"), + Atom("feConvolveMatrix", "feConvolveMatrix"), + Atom("feDiffuseLighting", "feDiffuseLighting"), + Atom("feDisplacementMap", "feDisplacementMap"), + Atom("feDistantLight", "feDistantLight"), + Atom("feDropShadow", "feDropShadow"), + Atom("feFlood", "feFlood"), + Atom("feFuncA", "feFuncA"), + Atom("feFuncB", "feFuncB"), + Atom("feFuncG", "feFuncG"), + Atom("feFuncR", "feFuncR"), + Atom("feGaussianBlur", "feGaussianBlur"), + Atom("feImage", "feImage"), + Atom("feMerge", "feMerge"), + Atom("feMergeNode", "feMergeNode"), + Atom("feMorphology", "feMorphology"), + Atom("feOffset", "feOffset"), + Atom("fePointLight", "fePointLight"), + Atom("feSpecularLighting", "feSpecularLighting"), + Atom("feSpotLight", "feSpotLight"), + Atom("feTile", "feTile"), + Atom("feTurbulence", "feTurbulence"), + Atom("fill", "fill"), + Atom("fill_opacity", "fill-opacity"), + Atom("fill_rule", "fill-rule"), + Atom("filter", "filter"), + Atom("filterUnits", "filterUnits"), + Atom("_float", "float"), + Atom("flood_color", "flood-color"), + Atom("flood_opacity", "flood-opacity"), + Atom("font_face", "font-face"), + Atom("font_face_format", "font-face-format"), + Atom("font_face_name", "font-face-name"), + Atom("font_face_src", "font-face-src"), + Atom("font_face_uri", "font-face-uri"), + Atom("font_family", "font-family"), + Atom("font_size", "font-size"), + Atom("font_size_adjust", "font-size-adjust"), + Atom("font_stretch", "font-stretch"), + Atom("font_style", "font-style"), + Atom("font_variant", "font-variant"), + Atom("formatting", "formatting"), + Atom("foreignObject", "foreignObject"), + Atom("fractalNoise", "fractalNoise"), + Atom("fr", "fr"), + Atom("fx", "fx"), + Atom("fy", "fy"), + Atom("G", "G"), + Atom("g", "g"), + Atom("gamma", "gamma"), + Atom("glyphRef", "glyphRef"), + Atom("grad", "grad"), + Atom("gradientTransform", "gradientTransform"), + Atom("gradientUnits", "gradientUnits"), + Atom("hardLight", "hard-light"), + Atom("hue", "hue"), + Atom("hueRotate", "hueRotate"), + Atom("identity", "identity"), + Atom("image_rendering", "image-rendering"), + Atom("in", "in"), + Atom("in2", "in2"), + Atom("intercept", "intercept"), + Atom("k1", "k1"), + Atom("k2", "k2"), + Atom("k3", "k3"), + Atom("k4", "k4"), + Atom("kernelMatrix", "kernelMatrix"), + Atom("kernelUnitLength", "kernelUnitLength"), + Atom("lengthAdjust", "lengthAdjust"), + Atom("letter_spacing", "letter-spacing"), + Atom("lighten", "lighten"), + Atom("lighter", "lighter"), + Atom("lighting_color", "lighting-color"), + Atom("limitingConeAngle", "limitingConeAngle"), + Atom("linear", "linear"), + Atom("linearGradient", "linearGradient"), + Atom("list_item", "list-item"), + Atom("list_style_type", "list-style-type"), + Atom("luminanceToAlpha", "luminanceToAlpha"), + Atom("luminosity", "luminosity"), + Atom("magnify", "magnify"), + Atom("marker", "marker"), + Atom("marker_end", "marker-end"), + Atom("marker_mid", "marker-mid"), + Atom("marker_start", "marker-start"), + Atom("markerHeight", "markerHeight"), + Atom("markerUnits", "markerUnits"), + Atom("markerWidth", "markerWidth"), + Atom("mask", "mask"), + Atom("maskContentUnits", "maskContentUnits"), + Atom("mask_type", "mask-type"), + Atom("maskUnits", "maskUnits"), + Atom("matrix", "matrix"), + Atom("metadata", "metadata"), + Atom("missingGlyph", "missing-glyph"), + Atom("mm", "mm"), + Atom("mpath", "mpath"), + Atom("noStitch", "noStitch"), + Atom("numOctaves", "numOctaves"), + Atom("multiply", "multiply"), + Atom("objectBoundingBox", "objectBoundingBox"), + Atom("offset", "offset"), + Atom("onSVGLoad", "onSVGLoad"), + Atom("onSVGScroll", "onSVGScroll"), + Atom("onzoom", "onzoom"), + Atom("opacity", "opacity"), + Atom("_operator", "operator"), + Atom("out", "out"), + Atom("over", "over"), + Atom("overridePreserveAspectRatio", "overridePreserveAspectRatio"), + Atom("pad", "pad"), + Atom("path", "path"), + Atom("pathLength", "pathLength"), + Atom("patternContentUnits", "patternContentUnits"), + Atom("patternTransform", "patternTransform"), + Atom("patternUnits", "patternUnits"), + Atom("pc", "pc"), + Atom("pointer", "pointer"), + Atom("pointer_events", "pointer-events"), + Atom("points", "points"), + Atom("pointsAtX", "pointsAtX"), + Atom("pointsAtY", "pointsAtY"), + Atom("pointsAtZ", "pointsAtZ"), + Atom("polyline", "polyline"), + Atom("preserveAlpha", "preserveAlpha"), + Atom("preserveAspectRatio", "preserveAspectRatio"), + Atom("primitiveUnits", "primitiveUnits"), + Atom("pt", "pt"), + Atom("px", "px"), + Atom("R", "R"), + Atom("r", "r"), + Atom("rad", "rad"), + Atom("radialGradient", "radialGradient"), + Atom("radius", "radius"), + Atom("reflect", "reflect"), + Atom("refX", "refX"), + Atom("refY", "refY"), + Atom("requiredExtensions", "requiredExtensions"), + Atom("requiredFeatures", "requiredFeatures"), + Atom("rotate", "rotate"), + Atom("rx", "rx"), + Atom("ry", "ry"), + Atom("saturate", "saturate"), + Atom("saturation", "saturation"), + Atom("set", "set"), + Atom("seed", "seed"), + Atom("shape_rendering", "shape-rendering"), + Atom("simpleScopeChain", "simpleScopeChain"), + Atom("skewX", "skewX"), + Atom("skewY", "skewY"), + Atom("slope", "slope"), + Atom("slot", "slot"), + Atom("softLight", "soft-light"), + Atom("spacing", "spacing"), + Atom("spacingAndGlyphs", "spacingAndGlyphs"), + Atom("specularConstant", "specularConstant"), + Atom("specularExponent", "specularExponent"), + Atom("spreadMethod", "spreadMethod"), + Atom("startOffset", "startOffset"), + Atom("stdDeviation", "stdDeviation"), + Atom("stitch", "stitch"), + Atom("stitchTiles", "stitchTiles"), + Atom("stop_color", "stop-color"), + Atom("stop_opacity", "stop-opacity"), + Atom("stroke", "stroke"), + Atom("stroke_dasharray", "stroke-dasharray"), + Atom("stroke_dashoffset", "stroke-dashoffset"), + Atom("stroke_linecap", "stroke-linecap"), + Atom("stroke_linejoin", "stroke-linejoin"), + Atom("stroke_miterlimit", "stroke-miterlimit"), + Atom("stroke_opacity", "stroke-opacity"), + Atom("stroke_width", "stroke-width"), + Atom("strokeWidth", "strokeWidth"), + Atom("surfaceScale", "surfaceScale"), + Atom("svg", "svg"), + Atom("svgSwitch", "switch"), + Atom("symbol", "symbol"), + Atom("systemLanguage", "systemLanguage"), + Atom("tableValues", "tableValues"), + Atom("targetX", "targetX"), + Atom("targetY", "targetY"), + Atom("text_anchor", "text-anchor"), + Atom("text_rendering", "text-rendering"), + Atom("textLength", "textLength"), + Atom("textPath", "textPath"), + Atom("transform_origin", "transform-origin"), + Atom("tref", "tref"), + Atom("tspan", "tspan"), + Atom("turbulence", "turbulence"), + Atom("unicode_bidi", "unicode-bidi"), + Atom("userSpaceOnUse", "userSpaceOnUse"), + Atom("view", "view"), + Atom("viewBox", "viewBox"), + Atom("viewTarget", "viewTarget"), + Atom("white_space", "white-space"), + Atom("word_spacing", "word-spacing"), + Atom("writing_mode", "writing-mode"), + Atom("x", "x"), + Atom("x1", "x1"), + Atom("x2", "x2"), + Atom("xChannelSelector", "xChannelSelector"), + Atom("xor_", "xor"), + Atom("y", "y"), + Atom("y1", "y1"), + Atom("y2", "y2"), + Atom("yChannelSelector", "yChannelSelector"), + Atom("z", "z"), + Atom("zoomAndPan", "zoomAndPan"), + Atom("vector_effect", "vector-effect"), + Atom("vertical_align", "vertical-align"), + Atom("accumulate", "accumulate"), + Atom("additive", "additive"), + Atom("attributeName", "attributeName"), + Atom("attributeType", "attributeType"), + Atom("auto_reverse", "auto-reverse"), + Atom("begin", "begin"), + Atom("beginEvent", "beginEvent"), + Atom("by", "by"), + Atom("calcMode", "calcMode"), + Atom("dur", "dur"), + Atom("keyPoints", "keyPoints"), + Atom("keySplines", "keySplines"), + Atom("keyTimes", "keyTimes"), + Atom("mozAnimateMotionDummyAttr", "_mozAnimateMotionDummyAttr"), + Atom("onbegin", "onbegin"), + Atom("onbeginEvent", "onbeginEvent"), + Atom("onend", "onend"), + Atom("onendEvent", "onendEvent"), + Atom("onrelease", "onrelease"), + Atom("onrepeat", "onrepeat"), + Atom("onrepeatEvent", "onrepeatEvent"), + Atom("repeatCount", "repeatCount"), + Atom("repeatDur", "repeatDur"), + Atom("repeatEvent", "repeatEvent"), + Atom("restart", "restart"), + Atom("to", "to"), + Atom("abs_", "abs"), + Atom("accent_", "accent"), + Atom("accentunder_", "accentunder"), + Atom("actiontype_", "actiontype"), + Atom("alignmentscope_", "alignmentscope"), + Atom("altimg_", "altimg"), + Atom("altimg_height_", "altimg-height"), + Atom("altimg_valign_", "altimg-valign"), + Atom("altimg_width_", "altimg-width"), + Atom("annotation_", "annotation"), + Atom("annotation_xml_", "annotation-xml"), + Atom("apply_", "apply"), + Atom("approx_", "approx"), + Atom("arccos_", "arccos"), + Atom("arccosh_", "arccosh"), + Atom("arccot_", "arccot"), + Atom("arccoth_", "arccoth"), + Atom("arccsc_", "arccsc"), + Atom("arccsch_", "arccsch"), + Atom("arcsec_", "arcsec"), + Atom("arcsech_", "arcsech"), + Atom("arcsin_", "arcsin"), + Atom("arcsinh_", "arcsinh"), + Atom("arctan_", "arctan"), + Atom("arctanh_", "arctanh"), + Atom("arg_", "arg"), + Atom("bevelled_", "bevelled"), + Atom("bind_", "bind"), + Atom("bvar_", "bvar"), + Atom("card_", "card"), + Atom("cartesianproduct_", "cartesianproduct"), + Atom("cbytes_", "cbytes"), + Atom("cd_", "cd"), + Atom("cdgroup_", "cdgroup"), + Atom("cerror_", "cerror"), + Atom("charalign_", "charalign"), + Atom("ci_", "ci"), + Atom("closure_", "closure"), + Atom("cn_", "cn"), + Atom("codomain_", "codomain"), + Atom("columnalign_", "columnalign"), + Atom("columnalignment_", "columnalignment"), + Atom("columnlines_", "columnlines"), + Atom("columnspacing_", "columnspacing"), + Atom("columnspan_", "columnspan"), + Atom("columnwidth_", "columnwidth"), + Atom("complexes_", "complexes"), + Atom("compose_", "compose"), + Atom("condition_", "condition"), + Atom("conjugate_", "conjugate"), + Atom("cos_", "cos"), + Atom("cosh_", "cosh"), + Atom("cot_", "cot"), + Atom("coth_", "coth"), + Atom("crossout_", "crossout"), + Atom("csc_", "csc"), + Atom("csch_", "csch"), + Atom("cs_", "cs"), + Atom("csymbol_", "csymbol"), + Atom("csp", "csp"), + Atom("curl_", "curl"), + Atom("decimalpoint_", "decimalpoint"), + Atom("definition", "definition"), + Atom("definitionURL_", "definitionURL"), + Atom("degree_", "degree"), + Atom("denomalign_", "denomalign"), + Atom("depth_", "depth"), + Atom("determinant_", "determinant"), + Atom("diff_", "diff"), + Atom("displaystyle_", "displaystyle"), + Atom("divergence_", "divergence"), + Atom("divide_", "divide"), + Atom("domain_", "domain"), + Atom("domainofapplication_", "domainofapplication"), + Atom("edge_", "edge"), + Atom("el", "el"), + Atom("emptyset_", "emptyset"), + Atom("eq_", "eq"), + Atom("equalcolumns_", "equalcolumns"), + Atom("equalrows_", "equalrows"), + Atom("equivalent_", "equivalent"), + Atom("eulergamma_", "eulergamma"), + Atom("exists_", "exists"), + Atom("exp_", "exp"), + Atom("exponentiale_", "exponentiale"), + Atom("factorial_", "factorial"), + Atom("factorof_", "factorof"), + Atom("fence_", "fence"), + Atom("fn_", "fn"), + Atom("fontfamily_", "fontfamily"), + Atom("fontsize_", "fontsize"), + Atom("fontstyle_", "fontstyle"), + Atom("fontweight_", "fontweight"), + Atom("forall_", "forall"), + Atom("framespacing_", "framespacing"), + Atom("gcd_", "gcd"), + Atom("geq_", "geq"), + Atom("groupalign_", "groupalign"), + Atom("gt_", "gt"), + Atom("ident_", "ident"), + Atom("imaginaryi_", "imaginaryi"), + Atom("imaginary_", "imaginary"), + Atom("implies_", "implies"), + Atom("indentalignfirst_", "indentalignfirst"), + Atom("indentalign_", "indentalign"), + Atom("indentalignlast_", "indentalignlast"), + Atom("indentshiftfirst_", "indentshiftfirst"), + Atom("indentshift_", "indentshift"), + Atom("indenttarget_", "indenttarget"), + Atom("integers_", "integers"), + Atom("intersect_", "intersect"), + Atom("interval_", "interval"), + Atom("int_", "int"), + Atom("inverse_", "inverse"), + Atom("lambda_", "lambda"), + Atom("laplacian_", "laplacian"), + Atom("largeop_", "largeop"), + Atom("lcm_", "lcm"), + Atom("leq_", "leq"), + Atom("limit_", "limit"), + Atom("linebreak_", "linebreak"), + Atom("linebreakmultchar_", "linebreakmultchar"), + Atom("linebreakstyle_", "linebreakstyle"), + Atom("linethickness_", "linethickness"), + Atom("list_", "list"), + Atom("ln_", "ln"), + Atom("location_", "location"), + Atom("logbase_", "logbase"), + Atom("log_", "log"), + Atom("longdivstyle_", "longdivstyle"), + Atom("lowlimit_", "lowlimit"), + Atom("lquote_", "lquote"), + Atom("lspace_", "lspace"), + Atom("lt_", "lt"), + Atom("maction_", "maction"), + Atom("maligngroup_", "maligngroup"), + Atom("malignmark_", "malignmark"), + Atom("mathbackground_", "mathbackground"), + Atom("mathcolor_", "mathcolor"), + Atom("mathsize_", "mathsize"), + Atom("mathvariant_", "mathvariant"), + Atom("matrixrow_", "matrixrow"), + Atom("maxsize_", "maxsize"), + Atom("mean_", "mean"), + Atom("median_", "median"), + Atom("menclose_", "menclose"), + Atom("merror_", "merror"), + Atom("mfenced_", "mfenced"), + Atom("mfrac_", "mfrac"), + Atom("mglyph_", "mglyph"), + Atom("mi_", "mi"), + Atom("minlabelspacing_", "minlabelspacing"), + Atom("minsize_", "minsize"), + Atom("minus_", "minus"), + Atom("mlabeledtr_", "mlabeledtr"), + Atom("mlongdiv_", "mlongdiv"), + Atom("mmultiscripts_", "mmultiscripts"), + Atom("mn_", "mn"), + Atom("momentabout_", "momentabout"), + Atom("moment_", "moment"), + Atom("mo_", "mo"), + Atom("movablelimits_", "movablelimits"), + Atom("mover_", "mover"), + Atom("mpadded_", "mpadded"), + Atom("mphantom_", "mphantom"), + Atom("mprescripts_", "mprescripts"), + Atom("mroot_", "mroot"), + Atom("mrow_", "mrow"), + Atom("mscarries_", "mscarries"), + Atom("mscarry_", "mscarry"), + Atom("msgroup_", "msgroup"), + Atom("msline_", "msline"), + Atom("ms_", "ms"), + Atom("mspace_", "mspace"), + Atom("msqrt_", "msqrt"), + Atom("msrow_", "msrow"), + Atom("mstack_", "mstack"), + Atom("mstyle_", "mstyle"), + Atom("msub_", "msub"), + Atom("msubsup_", "msubsup"), + Atom("msup_", "msup"), + Atom("mtable_", "mtable"), + Atom("mtd_", "mtd"), + Atom("mtext_", "mtext"), + Atom("mtr_", "mtr"), + Atom("munder_", "munder"), + Atom("munderover_", "munderover"), + Atom("naturalnumbers_", "naturalnumbers"), + Atom("neq_", "neq"), + Atom("notanumber_", "notanumber"), + Atom("notation_", "notation"), + Atom("note_", "note"), + Atom("notin_", "notin"), + Atom("notprsubset_", "notprsubset"), + Atom("notsubset_", "notsubset"), + Atom("numalign_", "numalign"), + Atom("other", "other"), + Atom("outerproduct_", "outerproduct"), + Atom("partialdiff_", "partialdiff"), + Atom("piece_", "piece"), + Atom("piecewise_", "piecewise"), + Atom("pi_", "pi"), + Atom("plus_", "plus"), + Atom("power_", "power"), + Atom("primes_", "primes"), + Atom("product_", "product"), + Atom("prsubset_", "prsubset"), + Atom("quotient_", "quotient"), + Atom("rationals_", "rationals"), + Atom("real_", "real"), + Atom("reals_", "reals"), + Atom("reln_", "reln"), + Atom("root_", "root"), + Atom("rowalign_", "rowalign"), + Atom("rowlines_", "rowlines"), + Atom("rowspacing_", "rowspacing"), + Atom("rquote_", "rquote"), + Atom("rspace_", "rspace"), + Atom("scalarproduct_", "scalarproduct"), + Atom("schemaLocation_", "schemaLocation"), + Atom("scriptlevel_", "scriptlevel"), + Atom("scriptminsize_", "scriptminsize"), + Atom("scriptsizemultiplier_", "scriptsizemultiplier"), + Atom("scriptsize_", "scriptsize"), + Atom("sdev_", "sdev"), + Atom("sech_", "sech"), + Atom("sec_", "sec"), + Atom("selection_", "selection"), + Atom("selector_", "selector"), + Atom("semantics_", "semantics"), + Atom("separator_", "separator"), + Atom("separators_", "separators"), + Atom("sep_", "sep"), + Atom("setdiff_", "setdiff"), + # Atom("set_", "set"), # "set" is present above + Atom("share_", "share"), + Atom("shift_", "shift"), + Atom("side_", "side"), + Atom("sinh_", "sinh"), + Atom("sin_", "sin"), + Atom("stackalign_", "stackalign"), + Atom("stretchy_", "stretchy"), + Atom("subscriptshift_", "subscriptshift"), + Atom("subset_", "subset"), + Atom("superscriptshift_", "superscriptshift"), + Atom("symmetric_", "symmetric"), + Atom("tanh_", "tanh"), + Atom("tan_", "tan"), + Atom("tendsto_", "tendsto"), + Atom("times_", "times"), + Atom("transpose_", "transpose"), + Atom("union_", "union"), + Atom("uplimit_", "uplimit"), + Atom("variance_", "variance"), + Atom("vectorproduct_", "vectorproduct"), + Atom("vector_", "vector"), + Atom("voffset_", "voffset"), + Atom("xref_", "xref"), + Atom("math", "math"), # the only one without an underscore + Atom("booleanFromString", "boolean-from-string"), + Atom("countNonEmpty", "count-non-empty"), + Atom("daysFromDate", "days-from-date"), + Atom("secondsFromDateTime", "seconds-from-dateTime"), + Atom("tabbrowser_arrowscrollbox", "tabbrowser-arrowscrollbox"), + # Simple gestures support + Atom("onMozSwipeGestureMayStart", "onMozSwipeGestureMayStart"), + Atom("onMozSwipeGestureStart", "onMozSwipeGestureStart"), + Atom("onMozSwipeGestureUpdate", "onMozSwipeGestureUpdate"), + Atom("onMozSwipeGestureEnd", "onMozSwipeGestureEnd"), + Atom("onMozSwipeGesture", "onMozSwipeGesture"), + Atom("onMozMagnifyGestureStart", "onMozMagnifyGestureStart"), + Atom("onMozMagnifyGestureUpdate", "onMozMagnifyGestureUpdate"), + Atom("onMozMagnifyGesture", "onMozMagnifyGesture"), + Atom("onMozRotateGestureStart", "onMozRotateGestureStart"), + Atom("onMozRotateGestureUpdate", "onMozRotateGestureUpdate"), + Atom("onMozRotateGesture", "onMozRotateGesture"), + Atom("onMozTapGesture", "onMozTapGesture"), + Atom("onMozPressTapGesture", "onMozPressTapGesture"), + Atom("onMozEdgeUIStarted", "onMozEdgeUIStarted"), + Atom("onMozEdgeUICanceled", "onMozEdgeUICanceled"), + Atom("onMozEdgeUICompleted", "onMozEdgeUICompleted"), + # Pointer events + Atom("onpointerdown", "onpointerdown"), + Atom("onpointermove", "onpointermove"), + Atom("onpointerup", "onpointerup"), + Atom("onpointercancel", "onpointercancel"), + Atom("onpointerover", "onpointerover"), + Atom("onpointerout", "onpointerout"), + Atom("onpointerenter", "onpointerenter"), + Atom("onpointerleave", "onpointerleave"), + Atom("ongotpointercapture", "ongotpointercapture"), + Atom("onlostpointercapture", "onlostpointercapture"), + # orientation support + Atom("ondevicemotion", "ondevicemotion"), + Atom("ondeviceorientation", "ondeviceorientation"), + Atom("ondeviceorientationabsolute", "ondeviceorientationabsolute"), + Atom("onmozorientationchange", "onmozorientationchange"), + Atom("onuserproximity", "onuserproximity"), + # light sensor support + Atom("ondevicelight", "ondevicelight"), + # MediaDevices device change event + Atom("ondevicechange", "ondevicechange"), + # WebRTC events + Atom("onrtctransform", "onrtctransform"), + # Internal Visual Viewport events + Atom("onmozvisualresize", "onmozvisualresize"), + Atom("onmozvisualscroll", "onmozvisualscroll"), + # Miscellaneous events included for memory usage optimization (see bug 1542885) + Atom("onDOMContentLoaded", "onDOMContentLoaded"), + Atom("onDOMDocElementInserted", "onDOMDocElementInserted"), + Atom("onDOMFormBeforeSubmit", "onDOMFormBeforeSubmit"), + Atom("onDOMFormHasPassword", "onDOMFormHasPassword"), + Atom("onDOMFrameContentLoaded", "onDOMFrameContentLoaded"), + Atom("onDOMHeadElementParsed", "onDOMHeadElementParsed"), + Atom("onDOMInputPasswordAdded", "onDOMInputPasswordAdded"), + Atom("onDOMLinkAdded", "onDOMLinkAdded"), + Atom("onDOMLinkChanged", "onDOMLinkChanged"), + Atom("onDOMMetaAdded", "onDOMMetaAdded"), + Atom("onDOMMetaChanged", "onDOMMetaChanged"), + Atom("onDOMMetaRemoved", "onDOMMetaRemoved"), + Atom("onDOMPopupBlocked", "onDOMPopupBlocked"), + Atom("onDOMTitleChanged", "onDOMTitleChanged"), + Atom("onDOMWindowClose", "onDOMWindowClose"), + Atom("onDOMWindowCreated", "onDOMWindowCreated"), + Atom("onDOMWindowFocus", "onDOMWindowFocus"), + Atom("onFullZoomChange", "onFullZoomChange"), + Atom("onGloballyAutoplayBlocked", "onGloballyAutoplayBlocked"), + Atom("onMozDOMFullscreen_Entered", "onMozDOMFullscreen:Entered"), + Atom("onMozDOMFullscreen_Exit", "onMozDOMFullscreen:Exit"), + Atom("onMozDOMFullscreen_Exited", "onMozDOMFullscreen:Exited"), + Atom("onMozDOMFullscreen_NewOrigin", "onMozDOMFullscreen:NewOrigin"), + Atom("onMozDOMFullscreen_Request", "onMozDOMFullscreen:Request"), + Atom("onMozDOMPointerLock_Entered", "onMozDOMPointerLock:Entered"), + Atom("onMozDOMPointerLock_Exited", "onMozDOMPointerLock:Exited"), + Atom("onMozInvalidForm", "onMozInvalidForm"), + Atom("onMozLocalStorageChanged", "onMozLocalStorageChanged"), + Atom("onMozOpenDateTimePicker", "onMozOpenDateTimePicker"), + Atom("onMozSessionStorageChanged", "onMozSessionStorageChanged"), + Atom("onMozTogglePictureInPicture", "onMozTogglePictureInPicture"), + Atom("onPluginCrashed", "onPluginCrashed"), + Atom("onPrintingError", "onPrintingError"), + Atom("onTextZoomChange", "onTextZoomChange"), + Atom("onUAWidgetSetupOrChange", "onUAWidgetSetupOrChange"), + Atom("onUAWidgetTeardown", "onUAWidgetTeardown"), + Atom("onUnselectedTabHover_Disable", "onUnselectedTabHover:Disable"), + Atom("onUnselectedTabHover_Enable", "onUnselectedTabHover:Enable"), + Atom("onmozshowdropdown", "onmozshowdropdown"), + Atom("onmozshowdropdown_sourcetouch", "onmozshowdropdown-sourcetouch"), + Atom("onprintPreviewUpdate", "onprintPreviewUpdate"), + Atom("onscrollend", "onscrollend"), + Atom("onbeforetoggle", "onbeforetoggle"), + # WebExtensions + Atom("moz_extension", "moz-extension"), + Atom("all_urlsPermission", "<all_urls>"), + Atom("clipboardRead", "clipboardRead"), + Atom("clipboardWrite", "clipboardWrite"), + Atom("debugger", "debugger"), + Atom("mozillaAddons", "mozillaAddons"), + Atom("tabs", "tabs"), + Atom("webRequestBlocking", "webRequestBlocking"), + Atom("webRequestFilterResponse_serviceWorkerScript", "webRequestFilterResponse.serviceWorkerScript"), + Atom("http", "http"), + Atom("https", "https"), + Atom("ws", "ws"), + Atom("wss", "wss"), + Atom("ftp", "ftp"), + Atom("chrome", "chrome"), + Atom("moz", "moz"), + Atom("moz_icon", "moz-icon"), + Atom("moz_gio", "moz-gio"), + Atom("proxy", "proxy"), + Atom("privateBrowsingAllowedPermission", "internal:privateBrowsingAllowed"), + Atom("svgContextPropertiesAllowedPermission", "internal:svgContextPropertiesAllowed"), + Atom("theme", "theme"), + # CSS Counter Styles + Atom("decimal_leading_zero", "decimal-leading-zero"), + Atom("arabic_indic", "arabic-indic"), + Atom("armenian", "armenian"), + Atom("upper_armenian", "upper-armenian"), + Atom("lower_armenian", "lower-armenian"), + Atom("bengali", "bengali"), + Atom("cambodian", "cambodian"), + Atom("khmer", "khmer"), + Atom("cjk_decimal", "cjk-decimal"), + Atom("devanagari", "devanagari"), + Atom("georgian", "georgian"), + Atom("gujarati", "gujarati"), + Atom("gurmukhi", "gurmukhi"), + Atom("kannada", "kannada"), + Atom("lao", "lao"), + Atom("malayalam", "malayalam"), + Atom("mongolian", "mongolian"), + Atom("myanmar", "myanmar"), + Atom("oriya", "oriya"), + Atom("persian", "persian"), + Atom("lower_roman", "lower-roman"), + Atom("upper_roman", "upper-roman"), + Atom("tamil", "tamil"), + Atom("telugu", "telugu"), + Atom("thai", "thai"), + Atom("tibetan", "tibetan"), + Atom("lower_alpha", "lower-alpha"), + Atom("lower_latin", "lower-latin"), + Atom("upper_alpha", "upper-alpha"), + Atom("upper_latin", "upper-latin"), + Atom("cjk_heavenly_stem", "cjk-heavenly-stem"), + Atom("cjk_earthly_branch", "cjk-earthly-branch"), + Atom("lower_greek", "lower-greek"), + Atom("hiragana", "hiragana"), + Atom("hiragana_iroha", "hiragana-iroha"), + Atom("katakana", "katakana"), + Atom("katakana_iroha", "katakana-iroha"), + Atom("cjk_ideographic", "cjk-ideographic"), + Atom("_moz_arabic_indic", "-moz-arabic-indic"), + Atom("_moz_persian", "-moz-persian"), + Atom("_moz_urdu", "-moz-urdu"), + Atom("_moz_devanagari", "-moz-devanagari"), + Atom("_moz_bengali", "-moz-bengali"), + Atom("_moz_gurmukhi", "-moz-gurmukhi"), + Atom("_moz_gujarati", "-moz-gujarati"), + Atom("_moz_oriya", "-moz-oriya"), + Atom("_moz_tamil", "-moz-tamil"), + Atom("_moz_telugu", "-moz-telugu"), + Atom("_moz_kannada", "-moz-kannada"), + Atom("_moz_malayalam", "-moz-malayalam"), + Atom("_moz_thai", "-moz-thai"), + Atom("_moz_lao", "-moz-lao"), + Atom("_moz_myanmar", "-moz-myanmar"), + Atom("_moz_khmer", "-moz-khmer"), + Atom("_moz_cjk_heavenly_stem", "-moz-cjk-heavenly-stem"), + Atom("_moz_cjk_earthly_branch", "-moz-cjk-earthly-branch"), + Atom("_moz_hangul", "-moz-hangul"), + Atom("_moz_hangul_consonant", "-moz-hangul-consonant"), + Atom("_moz_ethiopic_halehame", "-moz-ethiopic-halehame"), + Atom("_moz_ethiopic_halehame_am", "-moz-ethiopic-halehame-am"), + Atom("_moz_ethiopic_halehame_ti_er", "-moz-ethiopic-halehame-ti-er"), + Atom("_moz_ethiopic_halehame_ti_et", "-moz-ethiopic-halehame-ti-et"), + Atom("_moz_trad_chinese_informal", "-moz-trad-chinese-informal"), + Atom("_moz_trad_chinese_formal", "-moz-trad-chinese-formal"), + Atom("_moz_simp_chinese_informal", "-moz-simp-chinese-informal"), + Atom("_moz_simp_chinese_formal", "-moz-simp-chinese-formal"), + Atom("_moz_japanese_informal", "-moz-japanese-informal"), + Atom("_moz_japanese_formal", "-moz-japanese-formal"), + Atom("_moz_ethiopic_numeric", "-moz-ethiopic-numeric"), + # -------------------------------------------------------------------------- + # Special atoms + # -------------------------------------------------------------------------- + # Node types + Atom("cdataTagName", "#cdata-section"), + Atom("commentTagName", "#comment"), + Atom("documentNodeName", "#document"), + Atom("documentFragmentNodeName", "#document-fragment"), + Atom("documentTypeNodeName", "#document-type"), + Atom("processingInstructionTagName", "#processing-instruction"), + Atom("textTagName", "#text"), + # Frame types + # + # TODO(emilio): Rename this? This is only used now to mark the style context of + # the placeholder with a dummy pseudo. + Atom("placeholderFrame", "PlaceholderFrame"), + Atom("onloadend", "onloadend"), + Atom("onloadstart", "onloadstart"), + Atom("onprogress", "onprogress"), + Atom("onsuspend", "onsuspend"), + Atom("onemptied", "onemptied"), + Atom("onstalled", "onstalled"), + Atom("onplay", "onplay"), + Atom("onpause", "onpause"), + Atom("onloadedmetadata", "onloadedmetadata"), + Atom("onloadeddata", "onloadeddata"), + Atom("onwaiting", "onwaiting"), + Atom("onplaying", "onplaying"), + Atom("oncanplay", "oncanplay"), + Atom("oncanplaythrough", "oncanplaythrough"), + Atom("onseeking", "onseeking"), + Atom("onseeked", "onseeked"), + Atom("ontimeout", "ontimeout"), + Atom("ontimeupdate", "ontimeupdate"), + Atom("onended", "onended"), + Atom("onformdata", "onformdata"), + Atom("onratechange", "onratechange"), + Atom("ondurationchange", "ondurationchange"), + Atom("onvolumechange", "onvolumechange"), + Atom("onaddtrack", "onaddtrack"), + Atom("oncontrollerchange", "oncontrollerchange"), + Atom("oncuechange", "oncuechange"), + Atom("onenter", "onenter"), + Atom("onexit", "onexit"), + Atom("onencrypted", "onencrypted"), + Atom("onwaitingforkey", "onwaitingforkey"), + Atom("onkeystatuseschange", "onkeystatuseschange"), + Atom("onremovetrack", "onremovetrack"), + Atom("loadstart", "loadstart"), + Atom("suspend", "suspend"), + Atom("emptied", "emptied"), + Atom("play", "play"), + Atom("pause", "pause"), + Atom("loadedmetadata", "loadedmetadata"), + Atom("loadeddata", "loadeddata"), + Atom("waiting", "waiting"), + Atom("playing", "playing"), + Atom("timeupdate", "timeupdate"), + Atom("canplay", "canplay"), + Atom("canplaythrough", "canplaythrough"), + Atom("ondataavailable", "ondataavailable"), + Atom("onwarning", "onwarning"), + Atom("onstart", "onstart"), + Atom("onstop", "onstop"), + Atom("onphoto", "onphoto"), + Atom("ongamepadbuttondown", "ongamepadbuttondown"), + Atom("ongamepadbuttonup", "ongamepadbuttonup"), + Atom("ongamepadaxismove", "ongamepadaxismove"), + Atom("ongamepadconnected", "ongamepadconnected"), + Atom("ongamepaddisconnected", "ongamepaddisconnected"), + Atom("onfetch", "onfetch"), + # Content property names + Atom("afterPseudoProperty", "afterPseudoProperty"), # nsXMLElement* + Atom("beforePseudoProperty", "beforePseudoProperty"), # nsXMLElement* + Atom("cssPseudoElementBeforeProperty", "CSSPseudoElementBeforeProperty"), # CSSPseudoElement* + Atom("cssPseudoElementAfterProperty", "CSSPseudoElementAfterProperty"), # CSSPseudoElement* + Atom("cssPseudoElementMarkerProperty", "CSSPseudoElementMarkerProperty"), # CSSPseudoElement* + Atom("genConInitializerProperty", "QuoteNodeProperty"), + Atom("labelMouseDownPtProperty", "LabelMouseDownPtProperty"), + Atom("lockedStyleStates", "lockedStyleStates"), + Atom("apzCallbackTransform", "apzCallbackTransform"), + Atom("apzDisabled", "ApzDisabled"), # bool + Atom("restylableAnonymousNode", "restylableAnonymousNode"), # bool + Atom("docLevelNativeAnonymousContent", "docLevelNativeAnonymousContent"), # bool + Atom("paintRequestTime", "PaintRequestTime"), + Atom("pseudoProperty", "PseudoProperty"), # PseudoStyleType + Atom("manualNACProperty", "ManualNACProperty"), # ManualNAC* + Atom("markerPseudoProperty", "markerPseudoProperty"), # nsXMLElement* + # Languages for lang-specific transforms + Atom("Japanese", "ja"), + Atom("Chinese", "zh-CN"), + Atom("Taiwanese", "zh-TW"), + Atom("HongKongChinese", "zh-HK"), + Atom("Unicode", "x-unicode"), + # language codes specifically referenced in the gfx code + Atom("ko", "ko"), + Atom("zh_cn", "zh-cn"), + Atom("zh_tw", "zh-tw"), + # additional codes used in nsUnicodeRange.cpp + Atom("x_cyrillic", "x-cyrillic"), + Atom("he", "he"), + Atom("ar", "ar"), + Atom("x_devanagari", "x-devanagari"), + Atom("x_tamil", "x-tamil"), + Atom("x_armn", "x-armn"), + Atom("x_beng", "x-beng"), + Atom("x_cans", "x-cans"), + Atom("x_ethi", "x-ethi"), + Atom("x_geor", "x-geor"), + Atom("x_gujr", "x-gujr"), + Atom("x_guru", "x-guru"), + Atom("x_khmr", "x-khmr"), + Atom("x_knda", "x-knda"), + Atom("x_mlym", "x-mlym"), + Atom("x_orya", "x-orya"), + Atom("x_sinh", "x-sinh"), + Atom("x_telu", "x-telu"), + Atom("x_tibt", "x-tibt"), + # additional languages that have special case transformations + Atom("az", "az"), + Atom("ba", "ba"), + Atom("crh", "crh"), + # Atom("el", "el"), # "el" is present above + Atom("ga", "ga"), + # Atom("lt", "lt"), # "lt" is present above (atom name "lt_") + Atom("nl", "nl"), + # mathematical language, used for MathML + Atom("x_math", "x-math"), + # other languages mentioned in :lang() rules in UA style sheets + Atom("zh", "zh"), + # Names for editor transactions + Atom("TypingTxnName", "Typing"), + Atom("IMETxnName", "IME"), + Atom("DeleteTxnName", "Deleting"), + # Font families + Atom("serif", "serif"), + Atom("sans_serif", "sans-serif"), + Atom("cursive", "cursive"), + Atom("fantasy", "fantasy"), + Atom("monospace", "monospace"), + Atom("mozfixed", "-moz-fixed"), + Atom("moz_fixed_pos_containing_block", "-moz-fixed-pos-containing-block"), + # Standard font-palette identifiers + Atom("light", "light"), + Atom("dark", "dark"), + # IPC stuff + # Atom("Remote", "remote"), # "remote" is present above + Atom("RemoteId", "_remote_id"), + Atom("RemoteType", "remoteType"), + Atom("DisplayPort", "_displayport"), + Atom("DisplayPortMargins", "_displayportmargins"), + Atom("DisplayPortBase", "_displayportbase"), + Atom("MinimalDisplayPort", "_minimaldisplayport"), + Atom("forceMousewheelAutodir", "_force_mousewheel_autodir"), + Atom("forceMousewheelAutodirHonourRoot", "_force_mousewheel_autodir_honourroot"), + Atom("forcemessagemanager", "forcemessagemanager"), + Atom("initialBrowsingContextGroupId", "initialBrowsingContextGroupId"), + Atom("manualactiveness", "manualactiveness"), + # Names for system metrics. + Atom("_moz_bool_pref", "-moz-bool-pref"), + Atom("_moz_scrollbar_start_backward", "-moz-scrollbar-start-backward"), + Atom("_moz_scrollbar_start_forward", "-moz-scrollbar-start-forward"), + Atom("_moz_scrollbar_end_backward", "-moz-scrollbar-end-backward"), + Atom("_moz_scrollbar_end_forward", "-moz-scrollbar-end-forward"), + Atom("_moz_overlay_scrollbars", "-moz-overlay-scrollbars"), + Atom("_moz_overlay_scrollbar_fade_duration", "-moz-overlay-scrollbar-fade-duration"), + Atom("_moz_windows_accent_color_in_titlebar", "-moz-windows-accent-color-in-titlebar"), + Atom("_moz_windows_accent_color_in_tabs", "-moz-windows-accent-color-in-tabs"), + Atom("_moz_mac_big_sur_theme", "-moz-mac-big-sur-theme"), + Atom("_moz_mac_rtl", "-moz-mac-rtl"), + Atom("_moz_platform", "-moz-platform"), + Atom("_moz_gtk_theme_family", "-moz-gtk-theme-family"), + Atom("_moz_menubar_drag", "-moz-menubar-drag"), + Atom("_moz_device_pixel_ratio", "-moz-device-pixel-ratio"), + Atom("_moz_device_orientation", "-moz-device-orientation"), + Atom("_moz_is_resource_document", "-moz-is-resource-document"), + Atom("_moz_swipe_animation_enabled", "-moz-swipe-animation-enabled"), + Atom("_moz_gtk_csd_available", "-moz-gtk-csd-available"), + Atom("_moz_gtk_csd_titlebar_radius", "-moz-gtk-csd-titlebar-radius"), + Atom("_moz_gtk_csd_minimize_button", "-moz-gtk-csd-minimize-button"), + Atom("_moz_gtk_csd_minimize_button_position", "-moz-gtk-csd-minimize-button-position"), + Atom("_moz_gtk_csd_maximize_button", "-moz-gtk-csd-maximize-button"), + Atom("_moz_gtk_csd_maximize_button_position", "-moz-gtk-csd-maximize-button-position"), + Atom("_moz_gtk_csd_close_button", "-moz-gtk-csd-close-button"), + Atom("_moz_gtk_csd_close_button_position", "-moz-gtk-csd-close-button-position"), + Atom("_moz_gtk_csd_reversed_placement", "-moz-gtk-csd-reversed-placement"), + Atom("_moz_gtk_csd_rounded_bottom_corners", "-moz-gtk-csd-rounded-bottom-corners"), + Atom("_moz_content_prefers_color_scheme", "-moz-content-prefers-color-scheme"), + Atom("_moz_content_preferred_color_scheme", "-moz-content-preferred-color-scheme"), + Atom("_moz_system_dark_theme", "-moz-system-dark-theme"), + Atom("_moz_panel_animations", "-moz-panel-animations"), + # application commands + Atom("Back", "Back"), + Atom("Forward", "Forward"), + Atom("Reload", "Reload"), + Atom("Stop", "Stop"), + Atom("Search", "Search"), + Atom("Bookmarks", "Bookmarks"), + Atom("Home", "Home"), + Atom("NextTrack", "NextTrack"), + Atom("PreviousTrack", "PreviousTrack"), + Atom("MediaStop", "MediaStop"), + Atom("PlayPause", "PlayPause"), + Atom("New", "New"), + Atom("Open", "Open"), + Atom("Close", "Close"), + Atom("Save", "Save"), + Atom("Find", "Find"), + Atom("Help", "Help"), + Atom("Print", "Print"), + Atom("SendMail", "SendMail"), + Atom("ForwardMail", "ForwardMail"), + Atom("ReplyToMail", "ReplyToMail"), + Atom("alert", "alert"), + Atom("alertdialog", "alertdialog"), + Atom("application", "application"), + Atom("aria_colcount", "aria-colcount"), + Atom("aria_colindex", "aria-colindex"), + Atom("aria_colindextext", "aria-colindextext"), + Atom("aria_colspan", "aria-colspan"), + Atom("aria_details", "aria-details"), + Atom("aria_errormessage", "aria-errormessage"), + Atom("aria_grabbed", "aria-grabbed"), + Atom("aria_keyshortcuts", "aria-keyshortcuts"), + Atom("aria_label", "aria-label"), + Atom("aria_modal", "aria-modal"), + Atom("aria_orientation", "aria-orientation"), + Atom("aria_placeholder", "aria-placeholder"), + Atom("aria_roledescription", "aria-roledescription"), + Atom("aria_rowcount", "aria-rowcount"), + Atom("aria_rowindex", "aria-rowindex"), + Atom("aria_rowindextext", "aria-rowindextext"), + Atom("aria_rowspan", "aria-rowspan"), + Atom("aria_valuetext", "aria-valuetext"), + Atom("assertive", "assertive"), + Atom("auto_generated", "auto-generated"), + Atom("banner", "banner"), + Atom("checkable", "checkable"), + Atom("columnheader", "columnheader"), + Atom("complementary", "complementary"), + Atom("containerAtomic", "container-atomic"), + Atom("containerBusy", "container-busy"), + Atom("containerLive", "container-live"), + Atom("containerLiveRole", "container-live-role"), + Atom("containerRelevant", "container-relevant"), + Atom("contentinfo", "contentinfo"), + Atom("cycles", "cycles"), + Atom("datatable", "datatable"), + Atom("eventFromInput", "event-from-input"), + Atom("feed", "feed"), + Atom("grammar", "grammar"), + Atom("gridcell", "gridcell"), + Atom("heading", "heading"), + Atom("inlinevalue", "inline"), + Atom("inline_size", "inline-size"), + Atom("invalid", "invalid"), + Atom("lineNumber", "line-number"), + Atom("menuitemcheckbox", "menuitemcheckbox"), + Atom("menuitemradio", "menuitemradio"), + # Atom("mixed", "mixed"), # "mixed" is present above + Atom("navigation", "navigation"), + Atom("polite", "polite"), + Atom("posinset", "posinset"), + Atom("presentation", "presentation"), + Atom("progressbar", "progressbar"), + Atom("region", "region"), + Atom("rowgroup", "rowgroup"), + Atom("rowheader", "rowheader"), + Atom("search", "search"), + Atom("searchbox", "searchbox"), + Atom("setsize", "setsize"), + Atom("spelling", "spelling"), + Atom("spinbutton", "spinbutton"), + Atom("status", "status"), + Atom("tableCellIndex", "table-cell-index"), + Atom("tablist", "tablist"), + Atom("textIndent", "text-indent"), + Atom("textInputType", "text-input-type"), + Atom("textLineThroughColor", "text-line-through-color"), + Atom("textLineThroughStyle", "text-line-through-style"), + Atom("textPosition", "text-position"), + Atom("textUnderlineColor", "text-underline-color"), + Atom("textUnderlineStyle", "text-underline-style"), + Atom("timer", "timer"), + Atom("toolbarname", "toolbarname"), + Atom("toolbarseparator", "toolbarseparator"), + Atom("toolbarspacer", "toolbarspacer"), + Atom("toolbarspring", "toolbarspring"), + Atom("treegrid", "treegrid"), + Atom("_undefined", "undefined"), + Atom("xmlroles", "xml-roles"), + # MathML xml roles + Atom("close_fence", "close-fence"), + Atom("denominator", "denominator"), + Atom("numerator", "numerator"), + Atom("open_fence", "open-fence"), + Atom("overscript", "overscript"), + Atom("presubscript", "presubscript"), + Atom("presuperscript", "presuperscript"), + Atom("root_index", "root-index"), + Atom("subscript", "subscript"), + Atom("superscript", "superscript"), + Atom("underscript", "underscript"), + Atom("onaudiostart", "onaudiostart"), + Atom("onaudioend", "onaudioend"), + Atom("onsoundstart", "onsoundstart"), + Atom("onsoundend", "onsoundend"), + Atom("onspeechstart", "onspeechstart"), + Atom("onspeechend", "onspeechend"), + Atom("onresult", "onresult"), + Atom("onnomatch", "onnomatch"), + Atom("onresume", "onresume"), + Atom("onmark", "onmark"), + Atom("onboundary", "onboundary"), + # Media Controller + Atom("onactivated", "onactivated"), + Atom("ondeactivated", "ondeactivated"), + Atom("onmetadatachange", "onmetadatachange"), + Atom("onplaybackstatechange", "onplaybackstatechange"), + Atom("onpositionstatechange", "onpositionstatechange"), + Atom("onsupportedkeyschange", "onsupportedkeyschange"), + # Media query prefs for UA sheets. + Atom("dom_element_popover_enabled", "dom.element.popover.enabled"), + Atom("mathml_legacy_mathvariant_attribute_disabled", "mathml.legacy_mathvariant_attribute.disabled"), + Atom("layout_css_always_underline_links", "layout.css.always_underline_links"), + Atom("layout_css_cached_scrollbar_styles_enabled", "layout.css.cached-scrollbar-styles.enabled"), + # Contextual Identity / Containers + Atom("usercontextid", "usercontextid"), + Atom("geckoViewSessionContextId", "geckoViewSessionContextId"), + # Namespaces + Atom("nsuri_xmlns", "http://www.w3.org/2000/xmlns/"), + Atom("nsuri_xml", "http://www.w3.org/XML/1998/namespace"), + Atom("nsuri_xhtml", "http://www.w3.org/1999/xhtml"), + Atom("nsuri_xlink", "http://www.w3.org/1999/xlink"), + Atom("nsuri_xslt", "http://www.w3.org/1999/XSL/Transform"), + Atom("nsuri_mathml", "http://www.w3.org/1998/Math/MathML"), + Atom("nsuri_rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"), + Atom("nsuri_xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"), + Atom("nsuri_svg", "http://www.w3.org/2000/svg"), + Atom("nsuri_parsererror", "http://www.mozilla.org/newlayout/xml/parsererror.xml"), + # MSE + Atom("onsourceopen", "onsourceopen"), + Atom("onsourceended", "onsourceended"), + Atom("onsourceclose", "onsourceclose"), + Atom("onupdatestart", "onupdatestart"), + Atom("onupdate", "onupdate"), + Atom("onupdateend", "onupdateend"), + Atom("onaddsourcebuffer", "onaddsourcebuffer"), + Atom("onremovesourcebuffer", "onremovesourcebuffer"), + # WebCodecs + Atom("ondequeue", "ondequeue"), + # RDF (not used by mozilla-central, but still used by comm-central) + Atom("about", "about"), + Atom("ID", "ID"), + Atom("nodeID", "nodeID"), + Atom("aboutEach", "aboutEach"), + Atom("resource", "resource"), + Atom("RDF", "RDF"), + Atom("Description", "Description"), + Atom("Bag", "Bag"), + Atom("Seq", "Seq"), + Atom("Alt", "Alt"), + # Atom("kLiAtom", "li"), # "li" is present above + # Atom("kXMLNSAtom", "xmlns"), # "xmlns" is present above + Atom("parseType", "parseType"), + # Directory service + Atom("DirectoryService_CurrentProcess", "XCurProcD"), + Atom("DirectoryService_GRE_Directory", "GreD"), + Atom("DirectoryService_GRE_BinDirectory", "GreBinD"), + Atom("DirectoryService_OS_TemporaryDirectory", "TmpD"), + Atom("DirectoryService_OS_CurrentProcessDirectory", "CurProcD"), + Atom("DirectoryService_OS_CurrentWorkingDirectory", "CurWorkD"), + Atom("DirectoryService_OS_SystemConfigDir", "SysConfD"), + # Atom("DirectoryService_OS_HomeDirectory", "Home"), # "Home" is present above + Atom("DirectoryService_OS_DesktopDirectory", "Desk"), + Atom("DirectoryService_InitCurrentProcess_dummy", "MozBinD"), + Atom("DirectoryService_SystemDirectory", "SysD"), + Atom("DirectoryService_UserLibDirectory", "ULibDir"), + Atom("DirectoryService_DefaultDownloadDirectory", "DfltDwnld"), + Atom("DirectoryService_LocalApplicationsDirectory", "LocApp"), + Atom("DirectoryService_UserPreferencesDirectory", "UsrPrfs"), + Atom("DirectoryService_PictureDocumentsDirectory", "Pct"), + Atom("DirectoryService_DefaultScreenshotDirectory", "Scrnshts"), + Atom("DirectoryService_WindowsDirectory", "WinD"), + Atom("DirectoryService_WindowsProgramFiles", "ProgF"), + Atom("DirectoryService_Programs", "Progs"), + Atom("DirectoryService_Favorites", "Favs"), + Atom("DirectoryService_Appdata", "AppData"), + Atom("DirectoryService_LocalAppdata", "LocalAppData"), + Atom("DirectoryService_WinCookiesDirectory", "CookD"), + # CSS pseudo-elements -- these must appear in the same order as + # in nsCSSPseudoElementList.h + PseudoElementAtom("PseudoElement_after", ":after"), + PseudoElementAtom("PseudoElement_before", ":before"), + PseudoElementAtom("PseudoElement_marker", ":marker"), + PseudoElementAtom("PseudoElement_backdrop", ":backdrop"), + PseudoElementAtom("PseudoElement_cue", ":cue"), + PseudoElementAtom("PseudoElement_firstLetter", ":first-letter"), + PseudoElementAtom("PseudoElement_firstLine", ":first-line"), + PseudoElementAtom("PseudoElement_highlight", ":highlight"), + PseudoElementAtom("PseudoElement_selection", ":selection"), + PseudoElementAtom("PseudoElement_mozFocusInner", ":-moz-focus-inner"), + PseudoElementAtom("PseudoElement_mozNumberSpinBox", ":-moz-number-spin-box"), + PseudoElementAtom("PseudoElement_mozNumberSpinUp", ":-moz-number-spin-up"), + PseudoElementAtom("PseudoElement_mozNumberSpinDown", ":-moz-number-spin-down"), + PseudoElementAtom("PseudoElement_mozSearchClearButton", ":-moz-search-clear-button"), + PseudoElementAtom("PseudoElement_mozProgressBar", ":-moz-progress-bar"), + PseudoElementAtom("PseudoElement_mozRangeTrack", ":-moz-range-track"), + PseudoElementAtom("PseudoElement_mozRangeProgress", ":-moz-range-progress"), + PseudoElementAtom("PseudoElement_mozRangeThumb", ":-moz-range-thumb"), + PseudoElementAtom("PseudoElement_mozMeterBar", ":-moz-meter-bar"), + PseudoElementAtom("PseudoElement_placeholder", ":placeholder"), + PseudoElementAtom("PseudoElement_mozColorSwatch", ":-moz-color-swatch"), + PseudoElementAtom("PseudoElement_mozTextControlEditingRoot", ":-moz-text-control-editing-root"), + PseudoElementAtom("PseudoElement_mozTextControlPreview", ":-moz-text-control-preview"), + PseudoElementAtom("PseudoElement_mozReveal", ":-moz-reveal"), + PseudoElementAtom("PseudoElement_fileSelectorButton", ":file-selector-button"), + PseudoElementAtom("PseudoElement_sliderTrack", ":slider-track"), + PseudoElementAtom("PseudoElement_sliderThumb", ":slider-thumb"), + PseudoElementAtom("PseudoElement_sliderFill", ":slider-fill"), + # CSS anonymous boxes -- these must appear in the same order as + # in nsCSSAnonBoxList.h + NonInheritingAnonBoxAtom("AnonBox_oofPlaceholder", ":-moz-oof-placeholder"), + NonInheritingAnonBoxAtom("AnonBox_horizontalFramesetBorder", ":-moz-hframeset-border"), + NonInheritingAnonBoxAtom("AnonBox_verticalFramesetBorder", ":-moz-vframeset-border"), + NonInheritingAnonBoxAtom("AnonBox_framesetBlank", ":-moz-frameset-blank"), + NonInheritingAnonBoxAtom("AnonBox_tableColGroup", ":-moz-table-column-group"), + NonInheritingAnonBoxAtom("AnonBox_tableCol", ":-moz-table-column"), + NonInheritingAnonBoxAtom("AnonBox_page", ":-moz-page"), + NonInheritingAnonBoxAtom("AnonBox_pageBreak", ":-moz-page-break"), + NonInheritingAnonBoxAtom("AnonBox_pageContent", ":-moz-page-content"), + NonInheritingAnonBoxAtom("AnonBox_printedSheet", ":-moz-printed-sheet"), + NonInheritingAnonBoxAtom("AnonBox_columnSpanWrapper", ":-moz-column-span-wrapper"), + InheritingAnonBoxAtom("AnonBox_mozText", ":-moz-text"), + InheritingAnonBoxAtom("AnonBox_firstLetterContinuation", ":-moz-first-letter-continuation"), + InheritingAnonBoxAtom("AnonBox_mozBlockInsideInlineWrapper", ":-moz-block-inside-inline-wrapper"), + InheritingAnonBoxAtom("AnonBox_mozMathMLAnonymousBlock", ":-moz-mathml-anonymous-block"), + InheritingAnonBoxAtom("AnonBox_mozLineFrame", ":-moz-line-frame"), + InheritingAnonBoxAtom("AnonBox_buttonContent", ":-moz-button-content"), + InheritingAnonBoxAtom("AnonBox_cellContent", ":-moz-cell-content"), + InheritingAnonBoxAtom("AnonBox_dropDownList", ":-moz-dropdown-list"), + InheritingAnonBoxAtom("AnonBox_fieldsetContent", ":-moz-fieldset-content"), + InheritingAnonBoxAtom("AnonBox_mozDisplayComboboxControlFrame", ":-moz-display-comboboxcontrol-frame"), + InheritingAnonBoxAtom("AnonBox_htmlCanvasContent", ":-moz-html-canvas-content"), + InheritingAnonBoxAtom("AnonBox_inlineTable", ":-moz-inline-table"), + InheritingAnonBoxAtom("AnonBox_table", ":-moz-table"), + InheritingAnonBoxAtom("AnonBox_tableCell", ":-moz-table-cell"), + InheritingAnonBoxAtom("AnonBox_tableWrapper", ":-moz-table-wrapper"), + InheritingAnonBoxAtom("AnonBox_tableRowGroup", ":-moz-table-row-group"), + InheritingAnonBoxAtom("AnonBox_tableRow", ":-moz-table-row"), + InheritingAnonBoxAtom("AnonBox_canvas", ":-moz-canvas"), + InheritingAnonBoxAtom("AnonBox_pageSequence", ":-moz-page-sequence"), + InheritingAnonBoxAtom("AnonBox_scrolledContent", ":-moz-scrolled-content"), + InheritingAnonBoxAtom("AnonBox_scrolledCanvas", ":-moz-scrolled-canvas"), + InheritingAnonBoxAtom("AnonBox_columnSet", ":-moz-column-set"), + InheritingAnonBoxAtom("AnonBox_columnContent", ":-moz-column-content"), + InheritingAnonBoxAtom("AnonBox_viewport", ":-moz-viewport"), + InheritingAnonBoxAtom("AnonBox_viewportScroll", ":-moz-viewport-scroll"), + InheritingAnonBoxAtom("AnonBox_anonymousItem", ":-moz-anonymous-item"), + InheritingAnonBoxAtom("AnonBox_blockRubyContent", ":-moz-block-ruby-content"), + InheritingAnonBoxAtom("AnonBox_ruby", ":-moz-ruby"), + InheritingAnonBoxAtom("AnonBox_rubyBase", ":-moz-ruby-base"), + InheritingAnonBoxAtom("AnonBox_rubyBaseContainer", ":-moz-ruby-base-container"), + InheritingAnonBoxAtom("AnonBox_rubyText", ":-moz-ruby-text"), + InheritingAnonBoxAtom("AnonBox_rubyTextContainer", ":-moz-ruby-text-container"), + InheritingAnonBoxAtom("AnonBox_mozTreeColumn", ":-moz-tree-column"), + InheritingAnonBoxAtom("AnonBox_mozTreeRow", ":-moz-tree-row"), + InheritingAnonBoxAtom("AnonBox_mozTreeSeparator", ":-moz-tree-separator"), + InheritingAnonBoxAtom("AnonBox_mozTreeCell", ":-moz-tree-cell"), + InheritingAnonBoxAtom("AnonBox_mozTreeIndentation", ":-moz-tree-indentation"), + InheritingAnonBoxAtom("AnonBox_mozTreeLine", ":-moz-tree-line"), + InheritingAnonBoxAtom("AnonBox_mozTreeTwisty", ":-moz-tree-twisty"), + InheritingAnonBoxAtom("AnonBox_mozTreeImage", ":-moz-tree-image"), + InheritingAnonBoxAtom("AnonBox_mozTreeCellText", ":-moz-tree-cell-text"), + InheritingAnonBoxAtom("AnonBox_mozTreeCheckbox", ":-moz-tree-checkbox"), + InheritingAnonBoxAtom("AnonBox_mozTreeDropFeedback", ":-moz-tree-drop-feedback"), + InheritingAnonBoxAtom("AnonBox_mozSVGMarkerAnonChild", ":-moz-svg-marker-anon-child"), + InheritingAnonBoxAtom("AnonBox_mozSVGOuterSVGAnonChild", ":-moz-svg-outer-svg-anon-child"), + InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"), + InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"), + # END ATOMS +] + HTML_PARSER_ATOMS +# fmt: on + + +def verify(): + idents = set() + strings = set() + failed = False + for atom in STATIC_ATOMS: + if atom.ident in idents: + print("StaticAtoms.py: duplicate static atom ident: %s" % atom.ident) + failed = True + if atom.string in strings: + print('StaticAtoms.py: duplicate static atom string: "%s"' % atom.string) + failed = True + idents.add(atom.ident) + strings.add(atom.string) + if failed: + sys.exit(1) + + +def generate_nsgkatomlist_h(output, *ignore): + verify() + output.write( + "/* THIS FILE IS AUTOGENERATED BY StaticAtoms.py. DO NOT EDIT */\n\n" + "#ifdef small\n" + "#undef small\n" + "#endif\n\n" + "// GK_ATOM(identifier, string, hash, is_ascii_lower, gecko_type, atom_type)\n" + + "".join( + [ + 'GK_ATOM(%s, "%s", 0x%08x, %s, %s, %s)\n' + % ( + a.ident, + a.string, + a.hash, + str(a.is_ascii_lowercase).lower(), + a.ty, + a.atom_type, + ) + for a in STATIC_ATOMS + ] + ) + ) + + +def generate_nsgkatomconsts_h(output, *ignore): + pseudo_index = None + anon_box_index = None + pseudo_count = 0 + anon_box_count = 0 + for i, atom in enumerate(STATIC_ATOMS): + if atom.atom_type == "PseudoElementAtom": + if pseudo_index is None: + pseudo_index = i + pseudo_count += 1 + elif ( + atom.atom_type == "NonInheritingAnonBoxAtom" + or atom.atom_type == "InheritingAnonBoxAtom" + ): + if anon_box_index is None: + anon_box_index = i + anon_box_count += 1 + output.write( + "/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n" + "#ifndef nsGkAtomConsts_h\n" + "#define nsGkAtomConsts_h\n\n" + "namespace mozilla {\n" + " constexpr uint32_t kAtomIndex_PseudoElements = %d;\n" + " constexpr uint32_t kAtomCount_PseudoElements = %d;\n" + " constexpr uint32_t kAtomIndex_AnonBoxes = %d;\n" + " constexpr uint32_t kAtomCount_AnonBoxes = %d;\n" + "}\n\n" + "#endif\n" % (pseudo_index, pseudo_count, anon_box_index, anon_box_count) + ) + + +if __name__ == "__main__": + generate_nsgkatomlist_h(sys.stdout) diff --git a/xpcom/ds/StickyTimeDuration.h b/xpcom/ds/StickyTimeDuration.h new file mode 100644 index 0000000000..ce4e8c1e69 --- /dev/null +++ b/xpcom/ds/StickyTimeDuration.h @@ -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/. */ + +#ifndef mozilla_StickyTimeDuration_h +#define mozilla_StickyTimeDuration_h + +#include <cmath> +#include "mozilla/TimeStamp.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { + +/** + * A ValueCalculator class that performs additional checks before performing + * arithmetic operations such that if either operand is Forever (or the + * negative equivalent) the result remains Forever (or the negative equivalent + * as appropriate). + * + * Currently this only checks if either argument to each operation is + * Forever/-Forever. However, it is possible that, for example, + * aA + aB > INT64_MAX (or < INT64_MIN). + * + * We currently don't check for that case since we don't expect that to + * happen often except under test conditions in which case the wrapping + * behavior is probably acceptable. + */ +class StickyTimeDurationValueCalculator { + public: + static int64_t Add(int64_t aA, int64_t aB) { + MOZ_ASSERT((aA != INT64_MAX || aB != INT64_MIN) && + (aA != INT64_MIN || aB != INT64_MAX), + "'Infinity + -Infinity' and '-Infinity + Infinity'" + " are undefined"); + + // Forever + x = Forever + // x + Forever = Forever + if (aA == INT64_MAX || aB == INT64_MAX) { + return INT64_MAX; + } + // -Forever + x = -Forever + // x + -Forever = -Forever + if (aA == INT64_MIN || aB == INT64_MIN) { + return INT64_MIN; + } + + return aA + aB; + } + + // Note that we can't just define Add and have BaseTimeDuration call Add with + // negative arguments since INT64_MAX != -INT64_MIN so the saturating logic + // won't work. + static int64_t Subtract(int64_t aA, int64_t aB) { + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || aA != aB, + "'Infinity - Infinity' and '-Infinity - -Infinity'" + " are undefined"); + + // Forever - x = Forever + // x - -Forever = Forever + if (aA == INT64_MAX || aB == INT64_MIN) { + return INT64_MAX; + } + // -Forever - x = -Forever + // x - Forever = -Forever + if (aA == INT64_MIN || aB == INT64_MAX) { + return INT64_MIN; + } + + return aA - aB; + } + + template <typename T> + static int64_t Multiply(int64_t aA, T aB) { + // Specializations for double, float, and int64_t are provided following. + return Multiply(aA, static_cast<int64_t>(aB)); + } + + static int64_t Divide(int64_t aA, int64_t aB) { + MOZ_ASSERT(aB != 0, "Division by zero"); + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || + (aB != INT64_MAX && aB != INT64_MIN), + "Dividing +/-Infinity by +/-Infinity is undefined"); + + // Forever / +x = Forever + // Forever / -x = -Forever + // -Forever / +x = -Forever + // -Forever / -x = Forever + if (aA == INT64_MAX || aA == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX; + } + // x / Forever = 0 + // x / -Forever = 0 + if (aB == INT64_MAX || aB == INT64_MIN) { + return 0; + } + + return aA / aB; + } + + static double DivideDouble(int64_t aA, int64_t aB) { + MOZ_ASSERT(aB != 0, "Division by zero"); + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || + (aB != INT64_MAX && aB != INT64_MIN), + "Dividing +/-Infinity by +/-Infinity is undefined"); + + // Forever / +x = Forever + // Forever / -x = -Forever + // -Forever / +x = -Forever + // -Forever / -x = Forever + if (aA == INT64_MAX || aA == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? NegativeInfinity<double>() + : PositiveInfinity<double>(); + } + // x / Forever = 0 + // x / -Forever = 0 + if (aB == INT64_MAX || aB == INT64_MIN) { + return 0.0; + } + + return static_cast<double>(aA) / aB; + } + + static int64_t Modulo(int64_t aA, int64_t aB) { + MOZ_ASSERT(aA != INT64_MAX && aA != INT64_MIN, + "Infinity modulo x is undefined"); + + return aA % aB; + } +}; + +template <> +inline int64_t StickyTimeDurationValueCalculator::Multiply<int64_t>( + int64_t aA, int64_t aB) { + MOZ_ASSERT((aA != 0 || (aB != INT64_MIN && aB != INT64_MAX)) && + ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0), + "Multiplication of infinity by zero"); + + // Forever * +x = Forever + // Forever * -x = -Forever + // -Forever * +x = -Forever + // -Forever * -x = Forever + // + // i.e. If one or more of the arguments is +/-Forever, then + // return -Forever if the signs differ, or +Forever otherwise. + if (aA == INT64_MAX || aA == INT64_MIN || aB == INT64_MAX || + aB == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX; + } + + return aA * aB; +} + +template <> +inline int64_t StickyTimeDurationValueCalculator::Multiply<double>(int64_t aA, + double aB) { + MOZ_ASSERT((aA != 0 || (!std::isinf(aB))) && + ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0.0), + "Multiplication of infinity by zero"); + + // As with Multiply<int64_t>, if one or more of the arguments is + // +/-Forever or +/-Infinity, then return -Forever if the signs differ, + // or +Forever otherwise. + if (aA == INT64_MAX || aA == INT64_MIN || std::isinf(aB)) { + return (aA >= 0) ^ (aB >= 0.0) ? INT64_MIN : INT64_MAX; + } + + return aA * aB; +} + +template <> +inline int64_t StickyTimeDurationValueCalculator::Multiply<float>(int64_t aA, + float aB) { + MOZ_ASSERT(std::isinf(aB) == std::isinf(static_cast<double>(aB)), + "Casting to float loses infinite-ness"); + + return Multiply(aA, static_cast<double>(aB)); +} + +/** + * Specialization of BaseTimeDuration that uses + * StickyTimeDurationValueCalculator for arithmetic on the mValue member. + * + * Use this class when you need a time duration that is expected to hold values + * of Forever (or the negative equivalent) *and* when you expect that + * time duration to be used in arithmetic operations (and not just value + * comparisons). + */ +typedef BaseTimeDuration<StickyTimeDurationValueCalculator> StickyTimeDuration; + +// Template specializations to allow arithmetic between StickyTimeDuration +// and TimeDuration objects by falling back to the safe behavior. +inline StickyTimeDuration operator+(const TimeDuration& aA, + const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) + aB; +} +inline StickyTimeDuration operator+(const StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA + StickyTimeDuration(aB); +} + +inline StickyTimeDuration operator-(const TimeDuration& aA, + const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) - aB; +} +inline StickyTimeDuration operator-(const StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA - StickyTimeDuration(aB); +} + +inline StickyTimeDuration& operator+=(StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA += StickyTimeDuration(aB); +} +inline StickyTimeDuration& operator-=(StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA -= StickyTimeDuration(aB); +} + +inline double operator/(const TimeDuration& aA, const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) / aB; +} +inline double operator/(const StickyTimeDuration& aA, const TimeDuration& aB) { + return aA / StickyTimeDuration(aB); +} + +inline StickyTimeDuration operator%(const TimeDuration& aA, + const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) % aB; +} +inline StickyTimeDuration operator%(const StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA % StickyTimeDuration(aB); +} + +} // namespace mozilla + +#endif /* mozilla_StickyTimeDuration_h */ diff --git a/xpcom/ds/Tokenizer.cpp b/xpcom/ds/Tokenizer.cpp new file mode 100644 index 0000000000..3b0f6b02dd --- /dev/null +++ b/xpcom/ds/Tokenizer.cpp @@ -0,0 +1,805 @@ +/* -*- 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 "Tokenizer.h" + +#include "nsUnicharUtils.h" +#include <algorithm> + +namespace mozilla { + +template <> +char const TokenizerBase<char>::sWhitespaces[] = {' ', '\t', 0}; +template <> +char16_t const TokenizerBase<char16_t>::sWhitespaces[3] = {' ', '\t', 0}; + +template <typename TChar> +static bool contains(TChar const* const list, TChar const needle) { + for (TChar const* c = list; *c; ++c) { + if (needle == *c) { + return true; + } + } + return false; +} + +template <typename TChar> +TTokenizer<TChar>::TTokenizer(const typename base::TAString& aSource, + const TChar* aWhitespaces, + const TChar* aAdditionalWordChars) + : TokenizerBase<TChar>(aWhitespaces, aAdditionalWordChars) { + base::mInputFinished = true; + aSource.BeginReading(base::mCursor); + mRecord = mRollback = base::mCursor; + aSource.EndReading(base::mEnd); +} + +template <typename TChar> +TTokenizer<TChar>::TTokenizer(const TChar* aSource, const TChar* aWhitespaces, + const TChar* aAdditionalWordChars) + : TTokenizer(typename base::TDependentString(aSource), aWhitespaces, + aAdditionalWordChars) {} + +template <typename TChar> +bool TTokenizer<TChar>::Next(typename base::Token& aToken) { + if (!base::HasInput()) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + base::mCursor = base::Parse(aToken); + + base::AssignFragment(aToken, mRollback, base::mCursor); + + base::mPastEof = aToken.Type() == base::TOKEN_EOF; + base::mHasFailed = false; + return true; +} + +template <typename TChar> +bool TTokenizer<TChar>::Check(const typename base::TokenType aTokenType, + typename base::Token& aResult) { + if (!base::HasInput()) { + base::mHasFailed = true; + return false; + } + + typename base::TAString::const_char_iterator next = base::Parse(aResult); + if (aTokenType != aResult.Type()) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + base::mCursor = next; + + base::AssignFragment(aResult, mRollback, base::mCursor); + + base::mPastEof = aResult.Type() == base::TOKEN_EOF; + base::mHasFailed = false; + return true; +} + +template <typename TChar> +bool TTokenizer<TChar>::Check(const typename base::Token& aToken) { +#ifdef DEBUG + base::Validate(aToken); +#endif + + if (!base::HasInput()) { + base::mHasFailed = true; + return false; + } + + typename base::Token parsed; + typename base::TAString::const_char_iterator next = base::Parse(parsed); + if (!aToken.Equals(parsed)) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + base::mCursor = next; + base::mPastEof = parsed.Type() == base::TOKEN_EOF; + base::mHasFailed = false; + return true; +} + +template <typename TChar> +void TTokenizer<TChar>::SkipWhites(WhiteSkipping aIncludeNewLines) { + if (!CheckWhite() && + (aIncludeNewLines == DONT_INCLUDE_NEW_LINE || !CheckEOL())) { + return; + } + + typename base::TAString::const_char_iterator rollback = mRollback; + while (CheckWhite() || (aIncludeNewLines == INCLUDE_NEW_LINE && CheckEOL())) { + } + + base::mHasFailed = false; + mRollback = rollback; +} + +template <typename TChar> +void TTokenizer<TChar>::SkipUntil(typename base::Token const& aToken) { + typename base::TAString::const_char_iterator rollback = base::mCursor; + const typename base::Token eof = base::Token::EndOfFile(); + + typename base::Token t; + while (Next(t)) { + if (aToken.Equals(t) || eof.Equals(t)) { + Rollback(); + break; + } + } + + mRollback = rollback; +} + +template <typename TChar> +bool TTokenizer<TChar>::CheckChar(bool (*aClassifier)(const TChar aChar)) { + if (!aClassifier) { + MOZ_ASSERT(false); + return false; + } + + if (!base::HasInput() || base::mCursor == base::mEnd) { + base::mHasFailed = true; + return false; + } + + if (!aClassifier(*base::mCursor)) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + ++base::mCursor; + base::mHasFailed = false; + return true; +} + +template <typename TChar> +bool TTokenizer<TChar>::CheckPhrase(const typename base::TAString& aPhrase) { + if (!base::HasInput()) { + return false; + } + + typedef typename base::TAString::const_char_iterator Cursor; + + TTokenizer<TChar> pattern(aPhrase); + MOZ_ASSERT(!pattern.CheckEOF(), + "This will return true but won't shift the Tokenizer's cursor"); + + return [&](Cursor cursor, Cursor rollback) mutable { + while (true) { + if (pattern.CheckEOF()) { + base::mHasFailed = false; + mRollback = cursor; + return true; + } + + typename base::Token t1, t2; + Unused << Next(t1); + Unused << pattern.Next(t2); + if (t1.Type() == t2.Type() && t1.Fragment().Equals(t2.Fragment())) { + continue; + } + + break; + } + + base::mHasFailed = true; + base::mPastEof = false; + base::mCursor = cursor; + mRollback = rollback; + return false; + }(base::mCursor, mRollback); +} + +template <typename TChar> +bool TTokenizer<TChar>::ReadChar(TChar* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + typename base::Token t; + if (!Check(base::TOKEN_CHAR, t)) { + return false; + } + + *aValue = t.AsChar(); + return true; +} + +template <typename TChar> +bool TTokenizer<TChar>::ReadChar(bool (*aClassifier)(const TChar aChar), + TChar* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + if (!CheckChar(aClassifier)) { + return false; + } + + *aValue = *mRollback; + return true; +} + +template <typename TChar> +bool TTokenizer<TChar>::ReadWord(typename base::TAString& aValue) { + typename base::Token t; + if (!Check(base::TOKEN_WORD, t)) { + return false; + } + + aValue.Assign(t.AsString()); + return true; +} + +template <typename TChar> +bool TTokenizer<TChar>::ReadWord(typename base::TDependentSubstring& aValue) { + typename base::Token t; + if (!Check(base::TOKEN_WORD, t)) { + return false; + } + + aValue.Rebind(t.AsString().BeginReading(), t.AsString().Length()); + return true; +} + +template <typename TChar> +bool TTokenizer<TChar>::ReadUntil(typename base::Token const& aToken, + typename base::TAString& aResult, + ClaimInclusion aInclude) { + typename base::TDependentSubstring substring; + bool rv = ReadUntil(aToken, substring, aInclude); + aResult.Assign(substring); + return rv; +} + +template <typename TChar> +bool TTokenizer<TChar>::ReadUntil(typename base::Token const& aToken, + typename base::TDependentSubstring& aResult, + ClaimInclusion aInclude) { + typename base::TAString::const_char_iterator record = mRecord; + Record(); + typename base::TAString::const_char_iterator rollback = mRollback = + base::mCursor; + + bool found = false; + typename base::Token t; + while (Next(t)) { + if (aToken.Equals(t)) { + found = true; + break; + } + if (t.Equals(base::Token::EndOfFile())) { + // We don't want to eat it. + Rollback(); + break; + } + } + + Claim(aResult, aInclude); + mRollback = rollback; + mRecord = record; + return found; +} + +template <typename TChar> +void TTokenizer<TChar>::Rollback() { + MOZ_ASSERT(base::mCursor > mRollback || base::mPastEof, "TODO!!!"); + + base::mPastEof = false; + base::mHasFailed = false; + base::mCursor = mRollback; +} + +template <typename TChar> +void TTokenizer<TChar>::Record(ClaimInclusion aInclude) { + mRecord = aInclude == INCLUDE_LAST ? mRollback : base::mCursor; +} + +template <typename TChar> +void TTokenizer<TChar>::Claim(typename base::TAString& aResult, + ClaimInclusion aInclusion) { + typename base::TAString::const_char_iterator close = + aInclusion == EXCLUDE_LAST ? mRollback : base::mCursor; + aResult.Assign(Substring(mRecord, close)); +} + +template <typename TChar> +void TTokenizer<TChar>::Claim(typename base::TDependentSubstring& aResult, + ClaimInclusion aInclusion) { + typename base::TAString::const_char_iterator close = + aInclusion == EXCLUDE_LAST ? mRollback : base::mCursor; + + MOZ_RELEASE_ASSERT(close >= mRecord, "Overflow!"); + aResult.Rebind(mRecord, close - mRecord); +} + +// TokenizerBase + +template <typename TChar> +TokenizerBase<TChar>::TokenizerBase(const TChar* aWhitespaces, + const TChar* aAdditionalWordChars) + : mPastEof(false), + mHasFailed(false), + mInputFinished(true), + mMode(Mode::FULL), + mMinRawDelivery(1024), + mWhitespaces(aWhitespaces ? aWhitespaces : sWhitespaces), + mAdditionalWordChars(aAdditionalWordChars), + mCursor(nullptr), + mEnd(nullptr), + mNextCustomTokenID(TOKEN_CUSTOM0) {} + +template <typename TChar> +auto TokenizerBase<TChar>::AddCustomToken(const TAString& aValue, + ECaseSensitivity aCaseInsensitivity, + bool aEnabled) -> Token { + MOZ_ASSERT(!aValue.IsEmpty()); + + UniquePtr<Token>& t = *mCustomTokens.AppendElement(); + t = MakeUnique<Token>(); + + t->mType = static_cast<TokenType>(++mNextCustomTokenID); + t->mCustomCaseInsensitivity = aCaseInsensitivity; + t->mCustomEnabled = aEnabled; + t->mCustom.Assign(aValue); + return *t; +} + +template <typename TChar> +void TokenizerBase<TChar>::RemoveCustomToken(Token& aToken) { + if (aToken.mType == TOKEN_UNKNOWN) { + // Already removed + return; + } + + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (custom->mType == aToken.mType) { + mCustomTokens.RemoveElement(custom); + aToken.mType = TOKEN_UNKNOWN; + return; + } + } + + MOZ_ASSERT(false, "Token to remove not found"); +} + +template <typename TChar> +void TokenizerBase<TChar>::EnableCustomToken(Token const& aToken, + bool aEnabled) { + if (aToken.mType == TOKEN_UNKNOWN) { + // Already removed + return; + } + + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (custom->Type() == aToken.Type()) { + // This effectively destroys the token instance. + custom->mCustomEnabled = aEnabled; + return; + } + } + + MOZ_ASSERT(false, "Token to change not found"); +} + +template <typename TChar> +void TokenizerBase<TChar>::SetTokenizingMode(Mode aMode) { + mMode = aMode; +} + +template <typename TChar> +bool TokenizerBase<TChar>::HasFailed() const { + return mHasFailed; +} + +template <typename TChar> +bool TokenizerBase<TChar>::HasInput() const { + return !mPastEof; +} + +template <typename TChar> +auto TokenizerBase<TChar>::Parse(Token& aToken) const -> + typename TAString::const_char_iterator { + if (mCursor == mEnd) { + if (!mInputFinished) { + return mCursor; + } + + aToken = Token::EndOfFile(); + return mEnd; + } + + MOZ_RELEASE_ASSERT(mEnd >= mCursor, "Overflow!"); + typename TAString::size_type available = mEnd - mCursor; + + uint32_t longestCustom = 0; + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (IsCustom(mCursor, *custom, &longestCustom)) { + aToken = *custom; + return mCursor + custom->mCustom.Length(); + } + } + + if (!mInputFinished && available < longestCustom) { + // Not enough data to deterministically decide. + return mCursor; + } + + typename TAString::const_char_iterator next = mCursor; + + if (mMode == Mode::CUSTOM_ONLY) { + // We have to do a brute-force search for all of the enabled custom + // tokens. + while (next < mEnd) { + ++next; + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (IsCustom(next, *custom)) { + aToken = Token::Raw(); + return next; + } + } + } + + if (mInputFinished) { + // End of the data reached. + aToken = Token::Raw(); + return next; + } + + if (longestCustom < available && available > mMinRawDelivery) { + // We can return some data w/o waiting for either a custom token + // or call to FinishData() when we leave the tail where all the + // custom tokens potentially fit, so we can't lose only partially + // delivered tokens. This preserves reasonable granularity. + aToken = Token::Raw(); + return mEnd - longestCustom + 1; + } + + // Not enough data to deterministically decide. + return mCursor; + } + + enum State { + PARSE_INTEGER, + PARSE_WORD, + PARSE_CRLF, + PARSE_LF, + PARSE_WS, + PARSE_CHAR, + } state; + + if (IsWordFirst(*next)) { + state = PARSE_WORD; + } else if (IsNumber(*next)) { + state = PARSE_INTEGER; + } else if (contains(mWhitespaces, *next)) { // not UTF-8 friendly? + state = PARSE_WS; + } else if (*next == '\r') { + state = PARSE_CRLF; + } else if (*next == '\n') { + state = PARSE_LF; + } else { + state = PARSE_CHAR; + } + + mozilla::CheckedUint64 resultingNumber = 0; + + while (next < mEnd) { + switch (state) { + case PARSE_INTEGER: + // Keep it simple for now + resultingNumber *= 10; + resultingNumber += static_cast<uint64_t>(*next - '0'); + + ++next; + if (IsPending(next)) { + break; + } + if (IsEnd(next) || !IsNumber(*next)) { + if (!resultingNumber.isValid()) { + aToken = Token::Error(); + } else { + aToken = Token::Number(resultingNumber.value()); + } + return next; + } + break; + + case PARSE_WORD: + ++next; + if (IsPending(next)) { + break; + } + if (IsEnd(next) || !IsWord(*next)) { + aToken = Token::Word(Substring(mCursor, next)); + return next; + } + break; + + case PARSE_CRLF: + ++next; + if (IsPending(next)) { + break; + } + if (!IsEnd(next) && *next == '\n') { // LF is optional + ++next; + } + aToken = Token::NewLine(); + return next; + + case PARSE_LF: + ++next; + aToken = Token::NewLine(); + return next; + + case PARSE_WS: + ++next; + aToken = Token::Whitespace(); + return next; + + case PARSE_CHAR: + ++next; + aToken = Token::Char(*mCursor); + return next; + } // switch (state) + } // while (next < end) + + MOZ_ASSERT(!mInputFinished); + return mCursor; +} + +template <typename TChar> +bool TokenizerBase<TChar>::IsEnd( + const typename TAString::const_char_iterator& caret) const { + return caret == mEnd; +} + +template <typename TChar> +bool TokenizerBase<TChar>::IsPending( + const typename TAString::const_char_iterator& caret) const { + return IsEnd(caret) && !mInputFinished; +} + +template <typename TChar> +bool TokenizerBase<TChar>::IsWordFirst(const TChar aInput) const { + // TODO: make this fully work with unicode + return (ToLowerCase(static_cast<uint32_t>(aInput)) != + ToUpperCase(static_cast<uint32_t>(aInput))) || + '_' == aInput || + (mAdditionalWordChars ? contains(mAdditionalWordChars, aInput) + : false); +} + +template <typename TChar> +bool TokenizerBase<TChar>::IsWord(const TChar aInput) const { + return IsWordFirst(aInput) || IsNumber(aInput); +} + +template <typename TChar> +bool TokenizerBase<TChar>::IsNumber(const TChar aInput) const { + // TODO: are there unicode numbers? + return aInput >= '0' && aInput <= '9'; +} + +template <typename TChar> +bool TokenizerBase<TChar>::IsCustom( + const typename TAString::const_char_iterator& caret, + const Token& aCustomToken, uint32_t* aLongest) const { + MOZ_ASSERT(aCustomToken.mType > TOKEN_CUSTOM0); + if (!aCustomToken.mCustomEnabled) { + return false; + } + + if (aLongest) { + *aLongest = std::max<uint32_t>(*aLongest, aCustomToken.mCustom.Length()); + } + + // This is not very likely to happen according to how we call this method + // and since it's on a hot path, it's just a diagnostic assert, + // not a release assert. + MOZ_DIAGNOSTIC_ASSERT(mEnd >= caret, "Overflow?"); + uint32_t inputLength = mEnd - caret; + if (aCustomToken.mCustom.Length() > inputLength) { + return false; + } + + TDependentSubstring inputFragment(caret, aCustomToken.mCustom.Length()); + if (aCustomToken.mCustomCaseInsensitivity == CASE_INSENSITIVE) { + if constexpr (std::is_same_v<TChar, char>) { + return inputFragment.Equals(aCustomToken.mCustom, + nsCaseInsensitiveUTF8StringComparator); + } else { + return inputFragment.Equals(aCustomToken.mCustom, + nsCaseInsensitiveStringComparator); + } + } + return inputFragment.Equals(aCustomToken.mCustom); +} + +template <typename TChar> +void TokenizerBase<TChar>::AssignFragment( + Token& aToken, typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end) { + aToken.AssignFragment(begin, end); +} + +#ifdef DEBUG + +template <typename TChar> +void TokenizerBase<TChar>::Validate(Token const& aToken) { + if (aToken.Type() == TOKEN_WORD) { + typename TAString::const_char_iterator c = aToken.AsString().BeginReading(); + typename TAString::const_char_iterator e = aToken.AsString().EndReading(); + + if (c < e) { + MOZ_ASSERT(IsWordFirst(*c)); + while (++c < e) { + MOZ_ASSERT(IsWord(*c)); + } + } + } +} + +#endif + +// TokenizerBase::Token + +template <typename TChar> +TokenizerBase<TChar>::Token::Token() + : mType(TOKEN_UNKNOWN), + mChar(0), + mInteger(0), + mCustomCaseInsensitivity(CASE_SENSITIVE), + mCustomEnabled(false) {} + +template <typename TChar> +TokenizerBase<TChar>::Token::Token(const Token& aOther) + : mType(aOther.mType), + mCustom(aOther.mCustom), + mChar(aOther.mChar), + mInteger(aOther.mInteger), + mCustomCaseInsensitivity(aOther.mCustomCaseInsensitivity), + mCustomEnabled(aOther.mCustomEnabled) { + if (mType == TOKEN_WORD || mType > TOKEN_CUSTOM0) { + mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length()); + } +} + +template <typename TChar> +auto TokenizerBase<TChar>::Token::operator=(const Token& aOther) -> Token& { + mType = aOther.mType; + mCustom = aOther.mCustom; + mChar = aOther.mChar; + mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length()); + mInteger = aOther.mInteger; + mCustomCaseInsensitivity = aOther.mCustomCaseInsensitivity; + mCustomEnabled = aOther.mCustomEnabled; + return *this; +} + +template <typename TChar> +void TokenizerBase<TChar>::Token::AssignFragment( + typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end) { + MOZ_RELEASE_ASSERT(end >= begin, "Overflow!"); + mFragment.Rebind(begin, end - begin); +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::Raw() -> Token { + Token t; + t.mType = TOKEN_RAW; + return t; +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::Word(TAString const& aValue) -> Token { + Token t; + t.mType = TOKEN_WORD; + t.mWord.Rebind(aValue.BeginReading(), aValue.Length()); + return t; +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::Char(TChar const aValue) -> Token { + Token t; + t.mType = TOKEN_CHAR; + t.mChar = aValue; + return t; +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::Number(uint64_t const aValue) -> Token { + Token t; + t.mType = TOKEN_INTEGER; + t.mInteger = aValue; + return t; +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::Whitespace() -> Token { + Token t; + t.mType = TOKEN_WS; + t.mChar = '\0'; + return t; +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::NewLine() -> Token { + Token t; + t.mType = TOKEN_EOL; + return t; +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::EndOfFile() -> Token { + Token t; + t.mType = TOKEN_EOF; + return t; +} + +// static +template <typename TChar> +auto TokenizerBase<TChar>::Token::Error() -> Token { + Token t; + t.mType = TOKEN_ERROR; + return t; +} + +template <typename TChar> +bool TokenizerBase<TChar>::Token::Equals(const Token& aOther) const { + if (mType != aOther.mType) { + return false; + } + + switch (mType) { + case TOKEN_INTEGER: + return AsInteger() == aOther.AsInteger(); + case TOKEN_WORD: + return AsString() == aOther.AsString(); + case TOKEN_CHAR: + return AsChar() == aOther.AsChar(); + default: + return true; + } +} + +template <typename TChar> +TChar TokenizerBase<TChar>::Token::AsChar() const { + MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS); + return mChar; +} + +template <typename TChar> +auto TokenizerBase<TChar>::Token::AsString() const -> TDependentSubstring { + MOZ_ASSERT(mType == TOKEN_WORD); + return mWord; +} + +template <typename TChar> +uint64_t TokenizerBase<TChar>::Token::AsInteger() const { + MOZ_ASSERT(mType == TOKEN_INTEGER); + return mInteger; +} + +template class TokenizerBase<char>; +template class TokenizerBase<char16_t>; + +template class TTokenizer<char>; +template class TTokenizer<char16_t>; + +} // namespace mozilla diff --git a/xpcom/ds/Tokenizer.h b/xpcom/ds/Tokenizer.h new file mode 100644 index 0000000000..713b63f269 --- /dev/null +++ b/xpcom/ds/Tokenizer.h @@ -0,0 +1,524 @@ +/* -*- 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 Tokenizer_h__ +#define Tokenizer_h__ + +#include <type_traits> + +#include "nsString.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +namespace mozilla { + +template <typename TChar> +class TokenizerBase { + public: + typedef nsTSubstring<TChar> TAString; + typedef nsTString<TChar> TString; + typedef nsTDependentString<TChar> TDependentString; + typedef nsTDependentSubstring<TChar> TDependentSubstring; + + static TChar const sWhitespaces[]; + + /** + * The analyzer works with elements in the input cut to a sequence of token + * where each token has an elementary type + */ + enum TokenType : uint32_t { + TOKEN_UNKNOWN, + TOKEN_RAW, + TOKEN_ERROR, + TOKEN_INTEGER, + TOKEN_WORD, + TOKEN_CHAR, + TOKEN_WS, + TOKEN_EOL, + TOKEN_EOF, + TOKEN_CUSTOM0 = 1000 + }; + + enum ECaseSensitivity { CASE_SENSITIVE, CASE_INSENSITIVE }; + + /** + * Class holding the type and the value of a token. It can be manually + * created to allow checks against it via methods of TTokenizer or are results + * of some of the TTokenizer's methods. + */ + class Token { + TokenType mType; + TDependentSubstring mWord; + TString mCustom; + TChar mChar; + uint64_t mInteger; + ECaseSensitivity mCustomCaseInsensitivity; + bool mCustomEnabled; + + // If this token is a result of the parsing process, this member is + // referencing a sub-string in the input buffer. If this is externally + // created Token this member is left an empty string. + TDependentSubstring mFragment; + + friend class TokenizerBase<TChar>; + void AssignFragment(typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end); + + static Token Raw(); + + public: + Token(); + Token(const Token& aOther); + Token& operator=(const Token& aOther); + + // Static constructors of tokens by type and value + static Token Word(TAString const& aWord); + static Token Char(TChar const aChar); + static Token Number(uint64_t const aNumber); + static Token Whitespace(); + static Token NewLine(); + static Token EndOfFile(); + static Token Error(); + + // Compares the two tokens, type must be identical and value + // of one of the tokens must be 'any' or equal. + bool Equals(const Token& aOther) const; + + TokenType Type() const { return mType; } + TChar AsChar() const; + TDependentSubstring AsString() const; + uint64_t AsInteger() const; + + TDependentSubstring Fragment() const { return mFragment; } + }; + + /** + * Consumers may register a custom string that, when found in the input, is + * considered a token and returned by Next*() and accepted by Check*() + * methods. AddCustomToken() returns a reference to a token that can then be + * comapred using Token::Equals() againts the output from Next*() or be passed + * to Check*(). + */ + Token AddCustomToken(const TAString& aValue, + ECaseSensitivity aCaseInsensitivity, + bool aEnabled = true); + template <uint32_t N> + Token AddCustomToken(const TChar (&aValue)[N], + ECaseSensitivity aCaseInsensitivity, + bool aEnabled = true) { + return AddCustomToken(TDependentSubstring(aValue, N - 1), + aCaseInsensitivity, aEnabled); + } + void RemoveCustomToken(Token& aToken); + /** + * Only applies to a custom type of a Token (see AddCustomToken above.) + * This turns on and off token recognition. When a custom token is disabled, + * it's ignored as never added as a custom token. + */ + void EnableCustomToken(Token const& aToken, bool aEnable); + + /** + * Mode of tokenization. + * FULL tokenization, the default, recognizes built-in tokens and any custom + * tokens, if added. CUSTOM_ONLY will only recognize custom tokens, the rest + * is seen as 'raw'. This mode can be understood as a 'binary' mode. + */ + enum class Mode { FULL, CUSTOM_ONLY }; + void SetTokenizingMode(Mode aMode); + + /** + * Return false iff the last Check*() call has returned false or when we've + * read past the end of the input string. + */ + [[nodiscard]] bool HasFailed() const; + + protected: + explicit TokenizerBase(const TChar* aWhitespaces = nullptr, + const TChar* aAdditionalWordChars = nullptr); + + // false if we have already read the EOF token. + bool HasInput() const; + // Main parsing function, it doesn't shift the read cursor, just returns the + // next token position. + typename TAString::const_char_iterator Parse(Token& aToken) const; + // Is read cursor at the end? + bool IsEnd(const typename TAString::const_char_iterator& caret) const; + // True, when we are at the end of the input data, but it has not been marked + // as complete yet. In that case we cannot proceed with providing a + // multi-TChar token. + bool IsPending(const typename TAString::const_char_iterator& caret) const; + // Is read cursor on a character that is a word start? + bool IsWordFirst(const TChar aInput) const; + // Is read cursor on a character that is an in-word letter? + bool IsWord(const TChar aInput) const; + // Is read cursor on a character that is a valid number? + // TODO - support multiple radix + bool IsNumber(const TChar aInput) const; + // Is equal to the given custom token? + bool IsCustom(const typename TAString::const_char_iterator& caret, + const Token& aCustomToken, uint32_t* aLongest = nullptr) const; + + // Friendly helper to assign a fragment on a Token + static void AssignFragment(Token& aToken, + typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end); + +#ifdef DEBUG + // This is called from inside Tokenizer methods to make sure the token is + // valid. + void Validate(Token const& aToken); +#endif + + // true iff we have already read the EOF token + bool mPastEof; + // true iff the last Check*() call has returned false, reverts to true on + // Rollback() call + bool mHasFailed; + // true if the input string is final (finished), false when we expect more + // data yet to be fed to the tokenizer (see IncrementalTokenizer derived + // class). + bool mInputFinished; + // custom only vs full tokenizing mode, see the Parse() method + Mode mMode; + // minimal raw data chunked delivery during incremental feed + uint32_t mMinRawDelivery; + + // Customizable list of whitespaces + const TChar* mWhitespaces; + // Additinal custom word characters + const TChar* mAdditionalWordChars; + + // All these point to the original buffer passed to the constructor or to the + // incremental buffer after FeedInput. + typename TAString::const_char_iterator + mCursor; // Position of the current (actually next to read) token start + typename TAString::const_char_iterator mEnd; // End of the input position + + // This is the list of tokens user has registered with AddCustomToken() + nsTArray<UniquePtr<Token>> mCustomTokens; + uint32_t mNextCustomTokenID; + + private: + TokenizerBase() = delete; + TokenizerBase(const TokenizerBase&) = delete; + TokenizerBase(TokenizerBase&&) = delete; + TokenizerBase(const TokenizerBase&&) = delete; + TokenizerBase& operator=(const TokenizerBase&) = delete; +}; + +/** + * This is a simple implementation of a lexical analyzer or maybe better + * called a tokenizer. + * + * Please use Tokenizer or Tokenizer16 classes, that are specializations + * of this template class. Tokenizer is for ASCII input, Tokenizer16 may + * handle char16_t input, but doesn't recognize whitespaces or numbers + * other than standard `char` specialized Tokenizer class. + */ +template <typename TChar> +class TTokenizer : public TokenizerBase<TChar> { + public: + typedef TokenizerBase<TChar> base; + + /** + * @param aSource + * The string to parse. + * IMPORTANT NOTE: TTokenizer doesn't ensure the input string buffer + * lifetime. It's up to the consumer to make sure the string's buffer outlives + * the TTokenizer! + * @param aWhitespaces + * If non-null TTokenizer will use this custom set of whitespaces for + * CheckWhite() and SkipWhites() calls. By default the list consists of space + * and tab. + * @param aAdditionalWordChars + * If non-null it will be added to the list of characters that consist a + * word. This is useful when you want to accept e.g. '-' in HTTP headers. By + * default a word character is consider any character for which upper case + * is different from lower case. + * + * If there is an overlap between aWhitespaces and aAdditionalWordChars, the + * check for word characters is made first. + */ + explicit TTokenizer(const typename base::TAString& aSource, + const TChar* aWhitespaces = nullptr, + const TChar* aAdditionalWordChars = nullptr); + explicit TTokenizer(const TChar* aSource, const TChar* aWhitespaces = nullptr, + const TChar* aAdditionalWordChars = nullptr); + + /** + * When there is still anything to read from the input, tokenize it, store the + * token type and value to aToken result and shift the cursor past this just + * parsed token. Each call to Next() reads another token from the input and + * shifts the cursor. Returns false if we have passed the end of the input. + */ + [[nodiscard]] bool Next(typename base::Token& aToken); + + /** + * Parse the token on the input read cursor position, check its type is equal + * to aTokenType and if so, put it into aResult, shift the cursor and return + * true. Otherwise, leave the input read cursor position intact and return + * false. + */ + [[nodiscard]] bool Check(const typename base::TokenType aTokenType, + typename base::Token& aResult); + /** + * Same as above method, just compares both token type and token value passed + * in aToken. When both the type and the value equals, shift the cursor and + * return true. Otherwise return false. + */ + [[nodiscard]] bool Check(const typename base::Token& aToken); + + /** + * SkipWhites method (below) may also skip new line characters automatically. + */ + enum WhiteSkipping { + /** + * SkipWhites will only skip what is defined as a white space (default). + */ + DONT_INCLUDE_NEW_LINE = 0, + /** + * SkipWhites will skip definited white spaces as well as new lines + * automatically. + */ + INCLUDE_NEW_LINE = 1 + }; + + /** + * Skips any occurence of whitespaces specified in mWhitespaces member, + * optionally skip also new lines. + */ + void SkipWhites(WhiteSkipping aIncludeNewLines = DONT_INCLUDE_NEW_LINE); + + /** + * Skips all tokens until the given one is found or EOF is hit. The token + * or EOF are next to read. + */ + void SkipUntil(typename base::Token const& aToken); + + // These are mostly shortcuts for the Check() methods above. + + /** + * Check whitespace character is present. + */ + [[nodiscard]] bool CheckWhite() { return Check(base::Token::Whitespace()); } + /** + * Check there is a single character on the read cursor position. If so, + * shift the read cursor position and return true. Otherwise false. + */ + [[nodiscard]] bool CheckChar(const TChar aChar) { + return Check(base::Token::Char(aChar)); + } + /** + * This is a customizable version of CheckChar. aClassifier is a function + * called with value of the character on the current input read position. If + * this user function returns true, read cursor is shifted and true returned. + * Otherwise false. The user classifiction function is not called when we are + * at or past the end and false is immediately returned. + */ + [[nodiscard]] bool CheckChar(bool (*aClassifier)(const TChar aChar)); + /** + * Check for a whole expected word. + */ + [[nodiscard]] bool CheckWord(const typename base::TAString& aWord) { + return Check(base::Token::Word(aWord)); + } + /** + * Shortcut for literal const word check with compile time length calculation. + */ + template <uint32_t N> + [[nodiscard]] bool CheckWord(const TChar (&aWord)[N]) { + return Check( + base::Token::Word(typename base::TDependentString(aWord, N - 1))); + } + /** + * Helper to check for a string compound of multiple tokens like "foo bar". + * The match is binary-exact, a white space or a delimiter character in the + * phrase must match exactly the characters in the input. + */ + [[nodiscard]] bool CheckPhrase(const typename base::TAString& aPhrase); + template <uint32_t N> + [[nodiscard]] bool CheckPhrase(const TChar (&aPhrase)[N]) { + return CheckPhrase(typename base::TDependentString(aPhrase, N - 1)); + } + /** + * Checks \r, \n or \r\n. + */ + [[nodiscard]] bool CheckEOL() { return Check(base::Token::NewLine()); } + /** + * Checks we are at the end of the input string reading. If so, shift past + * the end and returns true. Otherwise does nothing and returns false. + */ + [[nodiscard]] bool CheckEOF() { return Check(base::Token::EndOfFile()); } + + /** + * These are shortcuts to obtain the value immediately when the token type + * matches. + */ + [[nodiscard]] bool ReadChar(TChar* aValue); + [[nodiscard]] bool ReadChar(bool (*aClassifier)(const TChar aChar), + TChar* aValue); + [[nodiscard]] bool ReadWord(typename base::TAString& aValue); + [[nodiscard]] bool ReadWord(typename base::TDependentSubstring& aValue); + + /** + * This is an integer read helper. It returns false and doesn't move the read + * cursor when any of the following happens: + * - the token at the read cursor is not an integer + * - the final number doesn't fit the T type + * Otherwise true is returned, aValue is filled with the integral number + * and the cursor is moved forward. + */ + template <typename T> + [[nodiscard]] bool ReadInteger(T* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + typename base::TAString::const_char_iterator rollback = mRollback; + typename base::TAString::const_char_iterator cursor = base::mCursor; + typename base::Token t; + if (!Check(base::TOKEN_INTEGER, t)) { + return false; + } + + mozilla::CheckedInt<T> checked(t.AsInteger()); + if (!checked.isValid()) { + // Move to a state as if Check() call has failed + mRollback = rollback; + base::mCursor = cursor; + base::mHasFailed = true; + return false; + } + + *aValue = checked.value(); + return true; + } + + /** + * Same as above, but accepts an integer with an optional minus sign. + */ + template <typename T, typename V = std::enable_if_t< + std::is_signed_v<std::remove_pointer_t<T>>, + std::remove_pointer_t<T>>> + [[nodiscard]] bool ReadSignedInteger(T* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + typename base::TAString::const_char_iterator rollback = mRollback; + typename base::TAString::const_char_iterator cursor = base::mCursor; + auto revert = MakeScopeExit([&] { + // Move to a state as if Check() call has failed + mRollback = rollback; + base::mCursor = cursor; + base::mHasFailed = true; + }); + + // Using functional raw access because '-' could be part of the word set + // making CheckChar('-') not work. + bool minus = CheckChar([](const TChar aChar) { return aChar == '-'; }); + + typename base::Token t; + if (!Check(base::TOKEN_INTEGER, t)) { + return false; + } + + mozilla::CheckedInt<T> checked(t.AsInteger()); + if (minus) { + checked *= -1; + } + + if (!checked.isValid()) { + return false; + } + + *aValue = checked.value(); + revert.release(); + return true; + } + + /** + * Returns the read cursor position back as it was before the last call of any + * parsing method of TTokenizer (Next, Check*, Skip*, Read*) so that the last + * operation can be repeated. Rollback cannot be used multiple times, it only + * reverts the last successfull parse operation. It also cannot be used + * before any parsing operation has been called on the TTokenizer. + */ + void Rollback(); + + /** + * Record() and Claim() are collecting the input as it is being parsed to + * obtain a substring between particular syntax bounderies defined by any + * recursive descent parser or simple parser the TTokenizer is used to read + * the input for. Inlucsion of a token that has just been parsed can be + * controlled using an arguemnt. + */ + enum ClaimInclusion { + /** + * Include resulting (or passed) token of the last lexical analyzer + * operation in the result. + */ + INCLUDE_LAST, + /** + * Do not include it. + */ + EXCLUDE_LAST + }; + + /** + * Start the process of recording. Based on aInclude value the begining of + * the recorded sub-string is at the current position (EXCLUDE_LAST) or at the + * position before the last parsed token (INCLUDE_LAST). + */ + void Record(ClaimInclusion aInclude = EXCLUDE_LAST); + /** + * Claim result of the record started with Record() call before. Depending on + * aInclude the ending of the sub-string result includes or excludes the last + * parsed or checked token. + */ + void Claim(typename base::TAString& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + void Claim(typename base::TDependentSubstring& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + + /** + * If aToken is found, aResult is set to the substring between the current + * position and the position of aToken, potentially including aToken depending + * on aInclude. + * If aToken isn't found aResult is set to the substring between the current + * position and the end of the string. + * If aToken is found, the method returns true. Otherwise it returns false. + * + * Calling Rollback() after ReadUntil() will return the read cursor to the + * position it had before ReadUntil was called. + */ + [[nodiscard]] bool ReadUntil(typename base::Token const& aToken, + typename base::TDependentSubstring& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + [[nodiscard]] bool ReadUntil(typename base::Token const& aToken, + typename base::TAString& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + + protected: + // All these point to the original buffer passed to the TTokenizer's + // constructor + typename base::TAString::const_char_iterator + mRecord; // Position where the recorded sub-string for Claim() is + typename base::TAString::const_char_iterator + mRollback; // Position of the previous token start + + private: + TTokenizer() = delete; + TTokenizer(const TTokenizer&) = delete; + TTokenizer(TTokenizer&&) = delete; + TTokenizer(const TTokenizer&&) = delete; + TTokenizer& operator=(const TTokenizer&) = delete; +}; + +typedef TTokenizer<char> Tokenizer; +typedef TTokenizer<char16_t> Tokenizer16; + +} // namespace mozilla + +#endif // Tokenizer_h__ diff --git a/xpcom/ds/components.conf b/xpcom/ds/components.conf new file mode 100644 index 0000000000..c2cf35e787 --- /dev/null +++ b/xpcom/ds/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': '{35c66fd1-95e9-4e0a-80c5-c3bd2b375481}', + 'contract_ids': ['@mozilla.org/array;1'], + 'legacy_constructor': 'nsArrayBase::XPCOMConstructor', + 'headers': ['nsArray.h'], + }, + { + 'name': 'Observer', + 'js_name': 'obs', + 'cid': '{d07f5195-e3d1-11d2-8acd-00105a1b8860}', + 'contract_ids': ['@mozilla.org/observer-service;1'], + 'interfaces': ['nsIObserverService'], + 'legacy_constructor': 'nsObserverService::Create', + 'headers': ['/xpcom/ds/nsObserverService.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS, + }, +] diff --git a/xpcom/ds/moz.build b/xpcom/ds/moz.build new file mode 100644 index 0000000000..91833b5558 --- /dev/null +++ b/xpcom/ds/moz.build @@ -0,0 +1,157 @@ +# -*- 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 += [ + "nsIArray.idl", + "nsIArrayExtensions.idl", + "nsIINIParser.idl", + "nsIMutableArray.idl", + "nsIObserver.idl", + "nsIObserverService.idl", + "nsIPersistentProperties2.idl", + "nsIProperties.idl", + "nsIProperty.idl", + "nsIPropertyBag.idl", + "nsIPropertyBag2.idl", + "nsISerializable.idl", + "nsISimpleEnumerator.idl", + "nsIStringEnumerator.idl", + "nsISupportsIterators.idl", + "nsISupportsPrimitives.idl", + "nsIVariant.idl", + "nsIWritablePropertyBag.idl", + "nsIWritablePropertyBag2.idl", +] + +if CONFIG["OS_ARCH"] == "WINNT": + XPIDL_SOURCES += [ + "nsIWindowsRegKey.idl", + ] + EXPORTS += ["nsWindowsRegKey.h"] + SOURCES += ["nsWindowsRegKey.cpp"] + +XPIDL_MODULE = "xpcom_ds" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "!nsGkAtomConsts.h", + "!nsGkAtomList.h", + "nsArray.h", + "nsArrayEnumerator.h", + "nsArrayUtils.h", + "nsAtom.h", + "nsAtomHashKeys.h", + "nsBaseHashtable.h", + "nsCharSeparatedTokenizer.h", + "nsCheapSets.h", + "nsClassHashtable.h", + "nsCOMArray.h", + "nsCRT.h", + "nsDeque.h", + "nsEnumeratorUtils.h", + "nsExpirationTracker.h", + "nsGkAtoms.h", + "nsHashKeys.h", + "nsHashPropertyBag.h", + "nsHashtablesFwd.h", + "nsInterfaceHashtable.h", + "nsMathUtils.h", + "nsPersistentProperties.h", + "nsPointerHashKeys.h", + "nsProperties.h", + "nsQuickSort.h", + "nsRefCountedHashtable.h", + "nsRefPtrHashtable.h", + "nsSimpleEnumerator.h", + "nsStaticAtomUtils.h", + "nsStaticNameTable.h", + "nsStringEnumerator.h", + "nsSupportsPrimitives.h", + "nsTArray-inl.h", + "nsTArray.h", + "nsTArrayForwardDeclare.h", + "nsTHashMap.h", + "nsTHashSet.h", + "nsTHashtable.h", + "nsTObserverArray.h", + "nsTPriorityQueue.h", + "nsVariant.h", + "nsWhitespaceTokenizer.h", + "PLDHashTable.h", +] + +EXPORTS.mozilla += [ + "ArenaAllocator.h", + "ArenaAllocatorExtensions.h", + "ArrayAlgorithm.h", + "ArrayIterator.h", + "AtomArray.h", + "Dafsa.h", + "IncrementalTokenizer.h", + "Observer.h", + "PerfectHash.h", + "SimpleEnumerator.h", + "StickyTimeDuration.h", + "Tokenizer.h", +] + +UNIFIED_SOURCES += [ + "Dafsa.cpp", + "IncrementalTokenizer.cpp", + "nsArray.cpp", + "nsArrayEnumerator.cpp", + "nsArrayUtils.cpp", + "nsAtomTable.cpp", + "nsCharSeparatedTokenizer.cpp", + "nsCOMArray.cpp", + "nsCRT.cpp", + "nsDeque.cpp", + "nsEnumeratorUtils.cpp", + "nsGkAtoms.cpp", + "nsHashPropertyBag.cpp", + "nsINIParserImpl.cpp", + "nsObserverList.cpp", + "nsObserverService.cpp", + "nsPersistentProperties.cpp", + "nsProperties.cpp", + "nsQuickSort.cpp", + "nsSimpleEnumerator.cpp", + "nsStaticNameTable.cpp", + "nsStringEnumerator.cpp", + "nsSupportsPrimitives.cpp", + "nsTArray.cpp", + "nsTObserverArray.cpp", + "nsVariant.cpp", + "PLDHashTable.cpp", + "Tokenizer.cpp", +] + +LOCAL_INCLUDES += [ + "../io", +] + +GeneratedFile( + "nsGkAtomList.h", + script="StaticAtoms.py", + entry_point="generate_nsgkatomlist_h", + inputs=["Atom.py", "HTMLAtoms.py"], +) + +GeneratedFile( + "nsGkAtomConsts.h", + script="StaticAtoms.py", + entry_point="generate_nsgkatomconsts_h", + inputs=["Atom.py", "HTMLAtoms.py"], +) + +FINAL_LIBRARY = "xul" + +PYTHON_UNITTEST_MANIFESTS += [ + "test/python.toml", +] diff --git a/xpcom/ds/nsArray.cpp b/xpcom/ds/nsArray.cpp new file mode 100644 index 0000000000..60758133d9 --- /dev/null +++ b/xpcom/ds/nsArray.cpp @@ -0,0 +1,146 @@ +/* -*- 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 "nsArray.h" +#include "nsArrayEnumerator.h" +#include "nsThreadUtils.h" + +NS_INTERFACE_MAP_BEGIN(nsArray) + NS_INTERFACE_MAP_ENTRY(nsIArray) + NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions) + NS_INTERFACE_MAP_ENTRY(nsIMutableArray) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray) +NS_INTERFACE_MAP_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsArrayCC) + NS_INTERFACE_MAP_ENTRY(nsIArray) + NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions) + NS_INTERFACE_MAP_ENTRY(nsIMutableArray) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray) +NS_INTERFACE_MAP_END + +nsArrayBase::~nsArrayBase() { Clear(); } + +NS_IMPL_ADDREF(nsArray) +NS_IMPL_RELEASE(nsArray) + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsArrayCC) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsArrayCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsArrayCC) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsArrayCC) + tmp->Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsArrayCC) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArray) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMETHODIMP +nsArrayBase::GetLength(uint32_t* aLength) { + *aLength = mArray.Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::QueryElementAt(uint32_t aIndex, const nsIID& aIID, + void** aResult) { + nsISupports* obj = mArray.SafeObjectAt(aIndex); + if (!obj) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // no need to worry about a leak here, because SafeObjectAt() + // doesn't addref its result + return obj->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsArrayBase::IndexOf(uint32_t aStartIndex, nsISupports* aElement, + uint32_t* aResult) { + int32_t idx = mArray.IndexOf(aElement, aStartIndex); + if (idx == -1) { + return NS_ERROR_FAILURE; + } + + *aResult = static_cast<uint32_t>(idx); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::ScriptedEnumerate(const nsIID& aElemIID, uint8_t aArgc, + nsISimpleEnumerator** aResult) { + if (aArgc > 0) { + return NS_NewArrayEnumerator(aResult, static_cast<nsIArray*>(this), + aElemIID); + } + return NS_NewArrayEnumerator(aResult, static_cast<nsIArray*>(this)); +} + +NS_IMETHODIMP +nsArrayBase::EnumerateImpl(const nsID& aElemIID, + nsISimpleEnumerator** aResult) { + return NS_NewArrayEnumerator(aResult, static_cast<nsIArray*>(this), aElemIID); +} + +// nsIMutableArray implementation + +NS_IMETHODIMP +nsArrayBase::AppendElement(nsISupports* aElement) { + bool result = mArray.AppendObject(aElement); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::RemoveElementAt(uint32_t aIndex) { + bool result = mArray.RemoveObjectAt(aIndex); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::InsertElementAt(nsISupports* aElement, uint32_t aIndex) { + bool result = mArray.InsertObjectAt(aElement, aIndex); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex) { + mArray.ReplaceObjectAt(aElement, aIndex); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::Clear() { + mArray.Clear(); + return NS_OK; +} + +// nsIArrayExtensions implementation. + +NS_IMETHODIMP +nsArrayBase::Count(uint32_t* aResult) { return GetLength(aResult); } + +NS_IMETHODIMP +nsArrayBase::GetElementAt(uint32_t aIndex, nsISupports** aResult) { + nsCOMPtr<nsISupports> obj = mArray.SafeObjectAt(aIndex); + obj.forget(aResult); + return NS_OK; +} + +nsresult nsArrayBase::XPCOMConstructor(const nsIID& aIID, void** aResult) { + nsCOMPtr<nsIMutableArray> inst = Create(); + return inst->QueryInterface(aIID, aResult); +} + +already_AddRefed<nsIMutableArray> nsArrayBase::Create() { + nsCOMPtr<nsIMutableArray> inst; + if (NS_IsMainThread()) { + inst = new nsArrayCC; + } else { + inst = new nsArray; + } + return inst.forget(); +} diff --git a/xpcom/ds/nsArray.h b/xpcom/ds/nsArray.h new file mode 100644 index 0000000000..adff3b2980 --- /dev/null +++ b/xpcom/ds/nsArray.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 nsArray_h__ +#define nsArray_h__ + +#include "nsIMutableArray.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +// {35C66FD1-95E9-4e0a-80C5-C3BD2B375481} +#define NS_ARRAY_CID \ + { \ + 0x35c66fd1, 0x95e9, 0x4e0a, { \ + 0x80, 0xc5, 0xc3, 0xbd, 0x2b, 0x37, 0x54, 0x81 \ + } \ + } + +// nsArray without any refcounting declarations +class nsArrayBase : public nsIMutableArray { + public: + NS_DECL_NSIARRAY + NS_DECL_NSIARRAYEXTENSIONS + NS_DECL_NSIMUTABLEARRAY + + /* Both of these factory functions create a cycle-collectable array + on the main thread and a non-cycle-collectable array on other + threads. */ + static already_AddRefed<nsIMutableArray> Create(); + /* Only for the benefit of the XPCOM module system, use Create() + instead. */ + static nsresult XPCOMConstructor(const nsIID& aIID, void** aResult); + + protected: + nsArrayBase() = default; + nsArrayBase(const nsArrayBase& aOther); + explicit nsArrayBase(const nsCOMArray_base& aBaseArray) + : mArray(aBaseArray) {} + virtual ~nsArrayBase(); + + nsCOMArray_base mArray; +}; + +class nsArray final : public nsArrayBase { + friend class nsArrayBase; + + public: + NS_DECL_ISUPPORTS + + private: + nsArray() {} + nsArray(const nsArray& aOther); + explicit nsArray(const nsCOMArray_base& aBaseArray) + : nsArrayBase(aBaseArray) {} + ~nsArray() = default; +}; + +class nsArrayCC final : public nsArrayBase { + friend class nsArrayBase; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsArrayCC, nsIMutableArray) + + private: + nsArrayCC() {} + nsArrayCC(const nsArrayCC& aOther); + explicit nsArrayCC(const nsCOMArray_base& aBaseArray) + : nsArrayBase(aBaseArray) {} + ~nsArrayCC() = default; +}; + +#endif diff --git a/xpcom/ds/nsArrayEnumerator.cpp b/xpcom/ds/nsArrayEnumerator.cpp new file mode 100644 index 0000000000..ec475f9e5b --- /dev/null +++ b/xpcom/ds/nsArrayEnumerator.cpp @@ -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/. */ + +#include "mozilla/Attributes.h" + +#include "nsArrayEnumerator.h" + +#include "nsIArray.h" +#include "nsSimpleEnumerator.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/RefPtr.h" + +class nsSimpleArrayEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsSimpleArrayEnumerator methods + explicit nsSimpleArrayEnumerator(nsIArray* aValueArray, const nsID& aEntryIID) + : mValueArray(aValueArray), mEntryIID(aEntryIID), mIndex(0) {} + + const nsID& DefaultInterface() override { return mEntryIID; } + + private: + ~nsSimpleArrayEnumerator() override = default; + + protected: + nsCOMPtr<nsIArray> mValueArray; + const nsID mEntryIID; + uint32_t mIndex; +}; + +NS_IMETHODIMP +nsSimpleArrayEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (!mValueArray) { + *aResult = false; + return NS_OK; + } + + uint32_t cnt; + nsresult rv = mValueArray->GetLength(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + *aResult = (mIndex < cnt); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleArrayEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (!mValueArray) { + *aResult = nullptr; + return NS_OK; + } + + uint32_t cnt; + nsresult rv = mValueArray->GetLength(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + if (mIndex >= cnt) { + return NS_ERROR_UNEXPECTED; + } + + return mValueArray->QueryElementAt(mIndex++, NS_GET_IID(nsISupports), + (void**)aResult); +} + +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, nsIArray* aArray, + const nsID& aEntryIID) { + RefPtr<nsSimpleArrayEnumerator> enumer = + new nsSimpleArrayEnumerator(aArray, aEntryIID); + enumer.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// enumerator implementation for nsCOMArray +// creates a snapshot of the array in question +// you MUST use NS_NewArrayEnumerator to create this, so that +// allocation is done correctly +class nsCOMArrayEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // Use this instead of `new`. + static nsCOMArrayEnumerator* Allocate(const nsCOMArray_base& aArray, + const nsID& aEntryIID); + + // specialized operator to make sure we make room for mValues + void operator delete(void* aPtr) { free(aPtr); } + + const nsID& DefaultInterface() override { return mEntryIID; } + + private: + // nsSimpleArrayEnumerator methods + explicit nsCOMArrayEnumerator(const nsID& aEntryIID) + : mIndex(0), mArraySize(0), mEntryIID(aEntryIID) { + mValueArray[0] = nullptr; + } + + ~nsCOMArrayEnumerator(void) override; + + protected: + uint32_t mIndex; // current position + uint32_t mArraySize; // size of the array + + const nsID& mEntryIID; + + // this is actually bigger + nsISupports* mValueArray[1]; +}; + +nsCOMArrayEnumerator::~nsCOMArrayEnumerator() { + // only release the entries that we haven't visited yet + for (; mIndex < mArraySize; ++mIndex) { + NS_IF_RELEASE(mValueArray[mIndex]); + } +} + +NS_IMETHODIMP +nsCOMArrayEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = (mIndex < mArraySize); + return NS_OK; +} + +NS_IMETHODIMP +nsCOMArrayEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mIndex >= mArraySize) { + return NS_ERROR_UNEXPECTED; + } + + // pass the ownership of the reference to the caller. Since + // we AddRef'ed during creation of |this|, there is no need + // to AddRef here + *aResult = mValueArray[mIndex++]; + + // this really isn't necessary. just pretend this happens, since + // we'll never visit this value again! + // mValueArray[(mIndex-1)] = nullptr; + + return NS_OK; +} + +nsCOMArrayEnumerator* nsCOMArrayEnumerator::Allocate( + const nsCOMArray_base& aArray, const nsID& aEntryIID) { + // create enough space such that mValueArray points to a large + // enough value. Note that the initial value of aSize gives us + // space for mValueArray[0], so we must subtract + size_t size = sizeof(nsCOMArrayEnumerator); + uint32_t count; + if (aArray.Count() > 0) { + count = static_cast<uint32_t>(aArray.Count()); + size += (count - 1) * sizeof(aArray[0]); + } else { + count = 0; + } + + // Allocate a buffer large enough to contain our object and its array. + void* mem = moz_xmalloc(size); + auto result = + new (mozilla::KnownNotNull, mem) nsCOMArrayEnumerator(aEntryIID); + + result->mArraySize = count; + + // now need to copy over the values, and addref each one + // now this might seem like a lot of work, but we're actually just + // doing all our AddRef's ahead of time since GetNext() doesn't + // need to AddRef() on the way out + for (uint32_t i = 0; i < count; ++i) { + result->mValueArray[i] = aArray[i]; + NS_IF_ADDREF(result->mValueArray[i]); + } + + return result; +} + +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + const nsCOMArray_base& aArray, + const nsID& aEntryIID) { + RefPtr<nsCOMArrayEnumerator> enumerator = + nsCOMArrayEnumerator::Allocate(aArray, aEntryIID); + enumerator.forget(aResult); + return NS_OK; +} diff --git a/xpcom/ds/nsArrayEnumerator.h b/xpcom/ds/nsArrayEnumerator.h new file mode 100644 index 0000000000..a406a9ea66 --- /dev/null +++ b/xpcom/ds/nsArrayEnumerator.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 nsArrayEnumerator_h__ +#define nsArrayEnumerator_h__ + +// enumerator implementation for nsIArray + +#include "nsISupports.h" + +class nsISimpleEnumerator; +class nsIArray; +class nsCOMArray_base; + +// Create an enumerator for an existing nsIArray implementation +// The enumerator holds an owning reference to the array. +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, nsIArray* aArray, + const nsID& aEntryIID = NS_GET_IID(nsISupports)); + +// create an enumerator for an existing nsCOMArray<T> implementation +// The enumerator will hold an owning reference to each ELEMENT in +// the array. This means that the nsCOMArray<T> can safely go away +// without its objects going away. +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + const nsCOMArray_base& aArray, + const nsID& aEntryIID = NS_GET_IID(nsISupports)); + +#endif diff --git a/xpcom/ds/nsArrayUtils.cpp b/xpcom/ds/nsArrayUtils.cpp new file mode 100644 index 0000000000..e8ea8bd0f2 --- /dev/null +++ b/xpcom/ds/nsArrayUtils.cpp @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsArrayUtils.h" + +// +// do_QueryElementAt helper stuff +// +nsresult nsQueryArrayElementAt::operator()(const nsIID& aIID, + void** aResult) const { + nsresult status = mArray ? mArray->QueryElementAt(mIndex, aIID, aResult) + : NS_ERROR_NULL_POINTER; + + if (mErrorPtr) { + *mErrorPtr = status; + } + + return status; +} diff --git a/xpcom/ds/nsArrayUtils.h b/xpcom/ds/nsArrayUtils.h new file mode 100644 index 0000000000..6adad119c7 --- /dev/null +++ b/xpcom/ds/nsArrayUtils.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsArrayUtils_h__ +#define nsArrayUtils_h__ + +#include "nsCOMPtr.h" +#include "nsIArray.h" + +// helper class for do_QueryElementAt +class MOZ_STACK_CLASS nsQueryArrayElementAt final : public nsCOMPtr_helper { + public: + nsQueryArrayElementAt(nsIArray* aArray, uint32_t aIndex, nsresult* aErrorPtr) + : mArray(aArray), mIndex(aIndex), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, + void**) const override; + + private: + nsIArray* MOZ_NON_OWNING_REF mArray; + uint32_t mIndex; + nsresult* mErrorPtr; +}; + +inline const nsQueryArrayElementAt do_QueryElementAt(nsIArray* aArray, + uint32_t aIndex, + nsresult* aErrorPtr = 0) { + return nsQueryArrayElementAt(aArray, aIndex, aErrorPtr); +} + +#endif // nsArrayUtils_h__ diff --git a/xpcom/ds/nsAtom.h b/xpcom/ds/nsAtom.h new file mode 100644 index 0000000000..aa416e81ef --- /dev/null +++ b/xpcom/ds/nsAtom.h @@ -0,0 +1,299 @@ +/* -*- 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 nsAtom_h +#define nsAtom_h + +#include <type_traits> + +#include "mozilla/Atomics.h" +#include "mozilla/Char16.h" +#include "mozilla/MemoryReporting.h" +#include "nsISupports.h" +#include "nsString.h" + +namespace mozilla { +struct AtomsSizes; +} // namespace mozilla + +class nsStaticAtom; +class nsDynamicAtom; + +// This class encompasses both static and dynamic atoms. +// +// - In places where static and dynamic atoms can be used, use RefPtr<nsAtom>. +// This is by far the most common case. +// +// - In places where only static atoms can appear, use nsStaticAtom* to avoid +// unnecessary refcounting. This is a moderately common case. +// +// - In places where only dynamic atoms can appear, it doesn't matter much +// whether you use RefPtr<nsAtom> or RefPtr<nsDynamicAtom>. This is an +// extremely rare case. +// +class nsAtom { + public: + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + mozilla::AtomsSizes& aSizes) const; + + bool Equals(char16ptr_t aString, uint32_t aLength) const { + return mLength == aLength && + memcmp(GetUTF16String(), aString, mLength * sizeof(char16_t)) == 0; + } + + bool Equals(const nsAString& aString) const { + return Equals(aString.BeginReading(), aString.Length()); + } + + bool IsStatic() const { return mIsStatic; } + bool IsDynamic() const { return !IsStatic(); } + + inline const nsStaticAtom* AsStatic() const; + inline const nsDynamicAtom* AsDynamic() const; + inline nsDynamicAtom* AsDynamic(); + + char16ptr_t GetUTF16String() const; + + uint32_t GetLength() const { return mLength; } + + operator mozilla::Span<const char16_t>() const { + // Explicitly specify template argument here to avoid instantiating + // Span<char16_t> first and then implicitly converting to Span<const + // char16_t> + return mozilla::Span<const char16_t>{GetUTF16String(), GetLength()}; + } + + void ToString(nsAString& aString) const; + void ToUTF8String(nsACString& aString) const; + + // A hashcode that is better distributed than the actual atom pointer, for + // use in situations that need a well-distributed hashcode. It's called hash() + // rather than Hash() so we can use mozilla::BloomFilter<N, nsAtom>, because + // BloomFilter requires elements to implement a function called hash(). + // + uint32_t hash() const { return mHash; } + + // This function returns true if ToLowercaseASCII would return the string + // unchanged. + bool IsAsciiLowercase() const { return mIsAsciiLowercase; } + + // This function returns true if this is the empty atom. This is exactly + // equivalent to `this == nsGkAtoms::_empty`, but it's a bit less foot-gunny, + // since we also have `nsGkAtoms::empty`. + // + // Defined in nsGkAtoms.h + inline bool IsEmpty() const; + + // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting + // of this type is special. + inline MozExternalRefCountType AddRef(); + inline MozExternalRefCountType Release(); + + using HasThreadSafeRefCnt = std::true_type; + + protected: + constexpr nsAtom(uint32_t aLength, bool aIsStatic, uint32_t aHash, + bool aIsAsciiLowercase) + : mLength(aLength), + mIsStatic(aIsStatic), + mIsAsciiLowercase(aIsAsciiLowercase), + mHash(aHash) {} + + ~nsAtom() = default; + + const uint32_t mLength : 30; + const uint32_t mIsStatic : 1; + const uint32_t mIsAsciiLowercase : 1; + const uint32_t mHash; +}; + +// This class would be |final| if it wasn't for nsCSSAnonBoxPseudoStaticAtom +// and nsCSSPseudoElementStaticAtom, which are trivial subclasses used to +// ensure only certain static atoms are passed to certain functions. +class nsStaticAtom : public nsAtom { + public: + // These are deleted so it's impossible to RefPtr<nsStaticAtom>. Raw + // nsStaticAtom pointers should be used instead. + MozExternalRefCountType AddRef() = delete; + MozExternalRefCountType Release() = delete; + + // The static atom's precomputed hash value is an argument here, but it + // must be the same as would be computed by mozilla::HashString(aStr), + // which is what we use when atomizing strings. We compute this hash in + // Atom.py and assert in nsAtomTable::RegisterStaticAtoms that the two + // hashes match. + constexpr nsStaticAtom(uint32_t aLength, uint32_t aHash, + uint32_t aStringOffset, bool aIsAsciiLowercase) + : nsAtom(aLength, /* aIsStatic = */ true, aHash, aIsAsciiLowercase), + mStringOffset(aStringOffset) {} + + const char16_t* String() const { + return reinterpret_cast<const char16_t*>(uintptr_t(this) - mStringOffset); + } + + already_AddRefed<nsAtom> ToAddRefed() { + return already_AddRefed<nsAtom>(static_cast<nsAtom*>(this)); + } + + private: + // This is an offset to the string chars, which must be at a lower address in + // memory. + uint32_t mStringOffset; +}; + +class nsDynamicAtom : public nsAtom { + public: + // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting + // of this type is special. + MozExternalRefCountType AddRef() { + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count = ++mRefCnt; + if (count == 1) { + gUnusedAtomCount--; + } + return count; + } + + MozExternalRefCountType Release() { +#ifdef DEBUG + // We set a lower GC threshold for atoms in debug builds so that we exercise + // the GC machinery more often. + static const int32_t kAtomGCThreshold = 20; +#else + static const int32_t kAtomGCThreshold = 10000; +#endif + + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + nsrefcnt count = --mRefCnt; + if (count == 0) { + if (++gUnusedAtomCount >= kAtomGCThreshold) { + GCAtomTable(); + } + } + + return count; + } + + nsStringBuffer* StringBuffer() const { return mStringBuffer; } + + const char16_t* String() const { + return reinterpret_cast<const char16_t*>(mStringBuffer->Data()); + } + + private: + friend class nsAtomTable; + friend class nsAtomSubTable; + friend int32_t NS_GetUnusedAtomCount(); + + static mozilla::Atomic<int32_t, mozilla::ReleaseAcquire> gUnusedAtomCount; + static void GCAtomTable(); + + // These shouldn't be used directly, even by friend classes. The + // Create()/Destroy() methods use them. + nsDynamicAtom(already_AddRefed<nsStringBuffer>, uint32_t aLength, + uint32_t aHash, bool aIsAsciiLowercase); + ~nsDynamicAtom() = default; + + static nsDynamicAtom* Create(const nsAString& aString, uint32_t aHash); + static void Destroy(nsDynamicAtom* aAtom); + + mozilla::ThreadSafeAutoRefCnt mRefCnt; + RefPtr<nsStringBuffer> mStringBuffer; +}; + +const nsStaticAtom* nsAtom::AsStatic() const { + MOZ_ASSERT(IsStatic()); + return static_cast<const nsStaticAtom*>(this); +} + +const nsDynamicAtom* nsAtom::AsDynamic() const { + MOZ_ASSERT(IsDynamic()); + return static_cast<const nsDynamicAtom*>(this); +} + +nsDynamicAtom* nsAtom::AsDynamic() { + MOZ_ASSERT(IsDynamic()); + return static_cast<nsDynamicAtom*>(this); +} + +MozExternalRefCountType nsAtom::AddRef() { + return IsStatic() ? 2 : AsDynamic()->AddRef(); +} + +MozExternalRefCountType nsAtom::Release() { + return IsStatic() ? 1 : AsDynamic()->Release(); +} + +// The four forms of NS_Atomize (for use with |RefPtr<nsAtom>|) return the +// atom for the string given. At any given time there will always be one atom +// representing a given string. Atoms are intended to make string comparison +// cheaper by simplifying it to pointer equality. A pointer to the atom that +// does not own a reference is not guaranteed to be valid. + +// Find an atom that matches the given UTF-8 string. The string is assumed to +// be zero terminated. Never returns null. +already_AddRefed<nsAtom> NS_Atomize(const char* aUTF8String); + +// Find an atom that matches the given UTF-8 string. Never returns null. +already_AddRefed<nsAtom> NS_Atomize(const nsACString& aUTF8String); + +// Find an atom that matches the given UTF-16 string. The string is assumed to +// be zero terminated. Never returns null. +already_AddRefed<nsAtom> NS_Atomize(const char16_t* aUTF16String); + +// Find an atom that matches the given UTF-16 string. Never returns null. +already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String); + +// Find an atom that matches the given UTF-16 string, with a known +// already-computed hash via mozilla::HashString. Never returns null. +already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String, + uint32_t aKnownHash); + +// An optimized version of the method above for the main thread. +already_AddRefed<nsAtom> NS_AtomizeMainThread(const nsAString& aUTF16String); + +// Return a count of the total number of atoms currently alive in the system. +// +// Note that the result is imprecise and racy if other threads are currently +// operating on atoms. It's also slow, since it triggers a GC before counting. +// Currently this function is only used in tests, which should probably remain +// the case. +nsrefcnt NS_GetNumberOfAtoms(); + +// Return a pointer for a static atom for the string or null if there's no +// static atom for this string. +nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String); + +class nsAtomString : public nsString { + public: + explicit nsAtomString(const nsAtom* aAtom) { aAtom->ToString(*this); } +}; + +class nsAtomCString : public nsCString { + public: + explicit nsAtomCString(const nsAtom* aAtom) { aAtom->ToUTF8String(*this); } +}; + +class nsAutoAtomCString : public nsAutoCString { + public: + explicit nsAutoAtomCString(const nsAtom* aAtom) { + aAtom->ToUTF8String(*this); + } +}; + +class nsDependentAtomString : public nsDependentString { + public: + explicit nsDependentAtomString(const nsAtom* aAtom) + : nsDependentString(aAtom->GetUTF16String(), aAtom->GetLength()) {} +}; + +// Checks if the ascii chars in a given atom are already lowercase. +// If they are, no-op. Otherwise, converts all the ascii uppercase +// chars to lowercase and atomizes, storing the result in the inout +// param. +void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom); + +#endif // nsAtom_h diff --git a/xpcom/ds/nsAtomHashKeys.h b/xpcom/ds/nsAtomHashKeys.h new file mode 100644 index 0000000000..85aed03bd0 --- /dev/null +++ b/xpcom/ds/nsAtomHashKeys.h @@ -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/. */ + +/* Definitions for nsAtom-related hash keys */ + +#ifndef nsAtomHashKeys_h +#define nsAtomHashKeys_h + +#include "nsAtom.h" +#include "nsHashKeys.h" + +// TODO(emilio): Consider removing this and specializing AddToHash instead +// once bug 1849386 is fixed. + +// Hash keys suitable for use in nsTHashTable. +struct nsAtomHashKey : public nsRefPtrHashKey<nsAtom> { + using nsRefPtrHashKey::nsRefPtrHashKey; + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return MOZ_LIKELY(aKey) ? aKey->hash() : 0; + } +}; + +struct nsWeakAtomHashKey : public nsPtrHashKey<nsAtom> { + using nsPtrHashKey::nsPtrHashKey; + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return nsAtomHashKey::HashKey(aKey); + } +}; + +// Hash keys suitable for use in mozilla::HashMap. +namespace mozilla { + +struct AtomHashKey { + RefPtr<nsAtom> mKey; + + explicit AtomHashKey(nsAtom* aAtom) : mKey(aAtom) {} + + using Lookup = nsAtom*; + + static HashNumber hash(const Lookup& aKey) { return aKey->hash(); } + static bool match(const AtomHashKey& aFirst, const Lookup& aSecond) { + return aFirst.mKey == aSecond; + } +}; + +} // namespace mozilla + +#endif // nsAtomHashKeys_h diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp new file mode 100644 index 0000000000..a03fdcce53 --- /dev/null +++ b/xpcom/ds/nsAtomTable.cpp @@ -0,0 +1,706 @@ +/* -*- 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/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/MruCache.h" +#include "mozilla/RWLock.h" +#include "mozilla/TextUtils.h" +#include "nsHashKeys.h" +#include "nsThreadUtils.h" + +#include "nsAtom.h" +#include "nsAtomTable.h" +#include "nsGkAtoms.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "PLDHashTable.h" +#include "prenv.h" + +// There are two kinds of atoms handled by this module. +// +// - Dynamic: the atom itself is heap allocated, as is the char buffer it +// points to. |gAtomTable| holds weak references to dynamic atoms. When the +// refcount of a dynamic atom drops to zero, we increment a static counter. +// When that counter reaches a certain threshold, we iterate over the atom +// table, removing and deleting dynamic atoms with refcount zero. This allows +// us to avoid acquiring the atom table lock during normal refcounting. +// +// - Static: both the atom and its chars are statically allocated and +// immutable, so it ignores all AddRef/Release calls. +// +// Note that gAtomTable is used on multiple threads, and has internal +// synchronization. + +using namespace mozilla; + +//---------------------------------------------------------------------- + +enum class GCKind { + RegularOperation, + Shutdown, +}; + +//---------------------------------------------------------------------- + +// gUnusedAtomCount is incremented when an atom loses its last reference +// (and thus turned into unused state), and decremented when an unused +// atom gets a reference again. The atom table relies on this value to +// schedule GC. This value can temporarily go below zero when multiple +// threads are operating the same atom, so it has to be signed so that +// we wouldn't use overflow value for comparison. +// See nsAtom::AddRef() and nsAtom::Release(). +// This atomic can be accessed during the GC and other places where recorded +// events are not allowed, so its value is not preserved when recording or +// replaying. +Atomic<int32_t, ReleaseAcquire> nsDynamicAtom::gUnusedAtomCount; + +nsDynamicAtom::nsDynamicAtom(already_AddRefed<nsStringBuffer> aBuffer, + uint32_t aLength, uint32_t aHash, + bool aIsAsciiLowercase) + : nsAtom(aLength, /* aIsStatic = */ false, aHash, aIsAsciiLowercase), + mRefCnt(1), + mStringBuffer(aBuffer) {} + +// Returns true if ToLowercaseASCII would return the string unchanged. +static bool IsAsciiLowercase(const char16_t* aString, const uint32_t aLength) { + for (uint32_t i = 0; i < aLength; ++i) { + if (IS_ASCII_UPPER(aString[i])) { + return false; + } + } + return true; +} + +nsDynamicAtom* nsDynamicAtom::Create(const nsAString& aString, uint32_t aHash) { + // We tack the chars onto the end of the nsDynamicAtom object. + const bool isAsciiLower = + ::IsAsciiLowercase(aString.Data(), aString.Length()); + RefPtr<nsStringBuffer> buffer = nsStringBuffer::FromString(aString); + if (!buffer) { + buffer = nsStringBuffer::Create(aString.Data(), aString.Length()); + if (MOZ_UNLIKELY(!buffer)) { + MOZ_CRASH("Out of memory atomizing"); + } + } else { + MOZ_ASSERT(aString.IsTerminated(), + "String buffers are always null-terminated"); + } + auto* atom = + new nsDynamicAtom(buffer.forget(), aString.Length(), aHash, isAsciiLower); + MOZ_ASSERT(atom->String()[atom->GetLength()] == char16_t(0)); + MOZ_ASSERT(atom->Equals(aString)); + MOZ_ASSERT(atom->mHash == HashString(atom->String(), atom->GetLength())); + MOZ_ASSERT(atom->mIsAsciiLowercase == isAsciiLower); + return atom; +} + +void nsDynamicAtom::Destroy(nsDynamicAtom* aAtom) { delete aAtom; } + +void nsAtom::ToString(nsAString& aString) const { + // See the comment on |mString|'s declaration. + if (IsStatic()) { + // AssignLiteral() lets us assign without copying. This isn't a string + // literal, but it's a static atom and thus has an unbounded lifetime, + // which is what's important. + aString.AssignLiteral(AsStatic()->String(), mLength); + } else { + AsDynamic()->StringBuffer()->ToString(mLength, aString); + } +} + +void nsAtom::ToUTF8String(nsACString& aBuf) const { + CopyUTF16toUTF8(nsDependentString(GetUTF16String(), mLength), aBuf); +} + +void nsAtom::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) const { + // Static atoms are in static memory, and so are not measured here. + if (IsDynamic()) { + aSizes.mDynamicAtoms += aMallocSizeOf(this); + } +} + +char16ptr_t nsAtom::GetUTF16String() const { + return IsStatic() ? AsStatic()->String() : AsDynamic()->String(); +} + +//---------------------------------------------------------------------- + +struct AtomTableKey { + explicit AtomTableKey(const nsStaticAtom* aAtom) + : mUTF16String(aAtom->String()), + mUTF8String(nullptr), + mLength(aAtom->GetLength()), + mHash(aAtom->hash()) { + MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash); + } + + AtomTableKey(const char16_t* aUTF16String, uint32_t aLength, uint32_t aHash) + : mUTF16String(aUTF16String), + mUTF8String(nullptr), + mLength(aLength), + mHash(aHash) { + MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash); + } + + AtomTableKey(const char16_t* aUTF16String, uint32_t aLength) + : AtomTableKey(aUTF16String, aLength, HashString(aUTF16String, aLength)) { + } + + AtomTableKey(const char* aUTF8String, uint32_t aLength, bool* aErr) + : mUTF16String(nullptr), mUTF8String(aUTF8String), mLength(aLength) { + mHash = HashUTF8AsUTF16(mUTF8String, mLength, aErr); + } + + const char16_t* mUTF16String; + const char* mUTF8String; + uint32_t mLength; + uint32_t mHash; +}; + +struct AtomTableEntry : public PLDHashEntryHdr { + // These references are either to dynamic atoms, in which case they are + // non-owning, or they are to static atoms, which aren't really refcounted. + // See the comment at the top of this file for more details. + nsAtom* MOZ_NON_OWNING_REF mAtom; +}; + +struct AtomCache : public MruCache<AtomTableKey, nsAtom*, AtomCache> { + static HashNumber Hash(const AtomTableKey& aKey) { return aKey.mHash; } + static bool Match(const AtomTableKey& aKey, const nsAtom* aVal) { + MOZ_ASSERT(aKey.mUTF16String); + return aVal->Equals(aKey.mUTF16String, aKey.mLength); + } +}; + +static AtomCache sRecentlyUsedSmallMainThreadAtoms; +static AtomCache sRecentlyUsedLargeMainThreadAtoms; + +// In order to reduce locking contention for concurrent atomization, we segment +// the atom table into N subtables, each with a separate lock. If the hash +// values we use to select the subtable are evenly distributed, this reduces the +// probability of contention by a factor of N. See bug 1440824. +// +// NB: This is somewhat similar to the technique used by Java's +// ConcurrentHashTable. +class nsAtomSubTable { + friend class nsAtomTable; + mozilla::RWLock mLock; + PLDHashTable mTable; + nsAtomSubTable(); + void GCLocked(GCKind aKind) MOZ_REQUIRES(mLock); + void AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) + MOZ_REQUIRES_SHARED(mLock); + + AtomTableEntry* Search(AtomTableKey& aKey) const MOZ_REQUIRES_SHARED(mLock) { + // XXX There's no LockedForReadingByCurrentThread(); + return static_cast<AtomTableEntry*>(mTable.Search(&aKey)); + } + + AtomTableEntry* Add(AtomTableKey& aKey) MOZ_REQUIRES(mLock) { + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + return static_cast<AtomTableEntry*>(mTable.Add(&aKey)); // Infallible + } +}; + +// The outer atom table, which coordinates access to the inner array of +// subtables. +class nsAtomTable { + public: + nsAtomSubTable& SelectSubTable(AtomTableKey& aKey); + void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes); + void GC(GCKind aKind); + already_AddRefed<nsAtom> Atomize(const nsAString& aUTF16String, + uint32_t aHash); + already_AddRefed<nsAtom> Atomize(const nsACString& aUTF8String); + already_AddRefed<nsAtom> AtomizeMainThread(const nsAString& aUTF16String); + nsStaticAtom* GetStaticAtom(const nsAString& aUTF16String); + void RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen); + + // The result of this function may be imprecise if other threads are operating + // on atoms concurrently. It's also slow, since it triggers a GC before + // counting. + size_t RacySlowCount(); + + // This hash table op is a static member of this class so that it can take + // advantage of |friend| declarations. + static void AtomTableClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry); + + // We achieve measurable reduction in locking contention in parallel CSS + // parsing by increasing the number of subtables up to 128. This has been + // measured to have neglible impact on the performance of initialization, GC, + // and shutdown. + // + // Another important consideration is memory, since we're adding fixed + // overhead per content process, which we try to avoid. Measuring a + // mostly-empty page [1] with various numbers of subtables, we get the + // following deep sizes for the atom table: + // 1 subtable: 278K + // 8 subtables: 279K + // 16 subtables: 282K + // 64 subtables: 286K + // 128 subtables: 290K + // + // So 128 subtables costs us 12K relative to a single table, and 4K relative + // to 64 subtables. Conversely, measuring parallel (6 thread) CSS parsing on + // tp6-facebook, a single table provides ~150ms of locking overhead per + // thread, 64 subtables provides ~2-3ms of overhead, and 128 subtables + // provides <1ms. And so while either 64 or 128 subtables would probably be + // acceptable, achieving a measurable reduction in contention for 4k of fixed + // memory overhead is probably worth it. + // + // [1] The numbers will look different for content processes with complex + // pages loaded, but in those cases the actual atoms will dominate memory + // usage and the overhead of extra tables will be negligible. We're mostly + // interested in the fixed cost for nearly-empty content processes. + constexpr static size_t kNumSubTables = 512; // Must be power of two. + + // The atom table very quickly gets 10,000+ entries in it (or even 100,000+). + // But choosing the best initial subtable length has some subtleties: we add + // ~2700 static atoms at start-up, and then we start adding and removing + // dynamic atoms. If we make the tables too big to start with, when the first + // dynamic atom gets removed from a given table the load factor will be < 25% + // and we will shrink it. + // + // So we first make the simplifying assumption that the atoms are more or less + // evenly-distributed across the subtables (which is the case empirically). + // Then, we take the total atom count when the first dynamic atom is removed + // (~2700), divide that across the N subtables, and the largest capacity that + // will allow each subtable to be > 25% full with that count. + // + // So want an initial subtable capacity less than (2700 / N) * 4 = 10800 / N. + // Rounding down to the nearest power of two gives us 8192 / N. Since the + // capacity is double the initial length, we end up with (4096 / N) per + // subtable. + constexpr static size_t kInitialSubTableSize = 4096 / kNumSubTables; + + private: + nsAtomSubTable mSubTables[kNumSubTables]; +}; + +// Static singleton instance for the atom table. +static nsAtomTable* gAtomTable; + +static PLDHashNumber AtomTableGetHash(const void* aKey) { + const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey); + return k->mHash; +} + +static bool AtomTableMatchKey(const PLDHashEntryHdr* aEntry, const void* aKey) { + const AtomTableEntry* he = static_cast<const AtomTableEntry*>(aEntry); + const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey); + + if (k->mUTF8String) { + bool err = false; + return (CompareUTF8toUTF16(nsDependentCSubstring( + k->mUTF8String, k->mUTF8String + k->mLength), + nsDependentAtomString(he->mAtom), &err) == 0) && + !err; + } + + return he->mAtom->Equals(k->mUTF16String, k->mLength); +} + +void nsAtomTable::AtomTableClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) { + auto* entry = static_cast<AtomTableEntry*>(aEntry); + entry->mAtom = nullptr; +} + +static void AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) { + static_cast<AtomTableEntry*>(aEntry)->mAtom = nullptr; +} + +static const PLDHashTableOps AtomTableOps = { + AtomTableGetHash, AtomTableMatchKey, PLDHashTable::MoveEntryStub, + nsAtomTable::AtomTableClearEntry, AtomTableInitEntry}; + +nsAtomSubTable& nsAtomTable::SelectSubTable(AtomTableKey& aKey) { + // There are a few considerations around how we select subtables. + // + // First, we want entries to be evenly distributed across the subtables. This + // can be achieved by using any bits in the hash key, assuming the key itself + // is evenly-distributed. Empirical measurements indicate that this method + // produces a roughly-even distribution across subtables. + // + // Second, we want to use the hash bits that are least likely to influence an + // entry's position within the subtable. If we used the exact same bits used + // by the subtables, then each subtable would compute the same position for + // every entry it observes, leading to pessimal performance. In this case, + // we're using PLDHashTable, whose primary hash function uses the N leftmost + // bits of the hash value (where N is the log2 capacity of the table). This + // means we should prefer the rightmost bits here. + // + // Note that the below is equivalent to mHash % kNumSubTables, a replacement + // which an optimizing compiler should make, but let's avoid any doubt. + static_assert((kNumSubTables & (kNumSubTables - 1)) == 0, + "must be power of two"); + return mSubTables[aKey.mHash & (kNumSubTables - 1)]; +} + +void nsAtomTable::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) { + MOZ_ASSERT(NS_IsMainThread()); + aSizes.mTable += aMallocSizeOf(this); + for (auto& table : mSubTables) { + AutoReadLock lock(table.mLock); + table.AddSizeOfExcludingThisLocked(aMallocSizeOf, aSizes); + } +} + +void nsAtomTable::GC(GCKind aKind) { + MOZ_ASSERT(NS_IsMainThread()); + sRecentlyUsedSmallMainThreadAtoms.Clear(); + sRecentlyUsedLargeMainThreadAtoms.Clear(); + + // Note that this is effectively an incremental GC, since only one subtable + // is locked at a time. + for (auto& table : mSubTables) { + AutoWriteLock lock(table.mLock); + table.GCLocked(aKind); + } + + // We would like to assert that gUnusedAtomCount matches the number of atoms + // we found in the table which we removed. However, there are two problems + // with this: + // * We have multiple subtables, each with their own lock. For optimal + // performance we only want to hold one lock at a time, but this means + // that atoms can be added and removed between GC slices. + // * Even if we held all the locks and performed all GC slices atomically, + // the locks are not acquired for AddRef() and Release() calls. This means + // we might see a gUnusedAtomCount value in between, say, AddRef() + // incrementing mRefCnt and it decrementing gUnusedAtomCount. + // + // So, we don't bother asserting that there are no unused atoms at the end of + // a regular GC. But we can (and do) assert this just after the last GC at + // shutdown. + // + // Note that, barring refcounting bugs, an atom can only go from a zero + // refcount to a non-zero refcount while the atom table lock is held, so + // so we won't try to resurrect a zero refcount atom while trying to delete + // it. + + MOZ_ASSERT_IF(aKind == GCKind::Shutdown, + nsDynamicAtom::gUnusedAtomCount == 0); +} + +size_t nsAtomTable::RacySlowCount() { + // Trigger a GC so that the result is deterministic modulo other threads. + GC(GCKind::RegularOperation); + size_t count = 0; + for (auto& table : mSubTables) { + AutoReadLock lock(table.mLock); + count += table.mTable.EntryCount(); + } + + return count; +} + +nsAtomSubTable::nsAtomSubTable() + : mLock("Atom Sub-Table Lock"), + mTable(&AtomTableOps, sizeof(AtomTableEntry), + nsAtomTable::kInitialSubTableSize) {} + +void nsAtomSubTable::GCLocked(GCKind aKind) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + + int32_t removedCount = 0; // A non-atomic temporary for cheaper increments. + nsAutoCString nonZeroRefcountAtoms; + uint32_t nonZeroRefcountAtomsCount = 0; + for (auto i = mTable.Iter(); !i.Done(); i.Next()) { + auto* entry = static_cast<AtomTableEntry*>(i.Get()); + if (entry->mAtom->IsStatic()) { + continue; + } + + nsAtom* atom = entry->mAtom; + if (atom->IsDynamic() && atom->AsDynamic()->mRefCnt == 0) { + i.Remove(); + nsDynamicAtom::Destroy(atom->AsDynamic()); + ++removedCount; + } +#ifdef NS_FREE_PERMANENT_DATA + else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) { + // Only report leaking atoms in leak-checking builds in a run where we + // are checking for leaks, during shutdown. If something is anomalous, + // then we'll assert later in this function. + nsAutoCString name; + atom->ToUTF8String(name); + if (nonZeroRefcountAtomsCount == 0) { + nonZeroRefcountAtoms = name; + } else if (nonZeroRefcountAtomsCount < 20) { + nonZeroRefcountAtoms += ","_ns + name; + } else if (nonZeroRefcountAtomsCount == 20) { + nonZeroRefcountAtoms += ",..."_ns; + } + nonZeroRefcountAtomsCount++; + } +#endif + } + if (nonZeroRefcountAtomsCount) { + nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s", + nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get()); + NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get()); + } + + nsDynamicAtom::gUnusedAtomCount -= removedCount; +} + +void nsDynamicAtom::GCAtomTable() { + MOZ_ASSERT(gAtomTable); + if (NS_IsMainThread()) { + gAtomTable->GC(GCKind::RegularOperation); + } +} + +//---------------------------------------------------------------------- + +// Have the static atoms been inserted into the table? +static bool gStaticAtomsDone = false; + +void NS_InitAtomTable() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!gAtomTable); + + // We register static atoms immediately so they're available for use as early + // as possible. + gAtomTable = new nsAtomTable(); + gAtomTable->RegisterStaticAtoms(nsGkAtoms::sAtoms, nsGkAtoms::sAtomsLen); + gStaticAtomsDone = true; +} + +void NS_ShutdownAtomTable() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gAtomTable); + +#ifdef NS_FREE_PERMANENT_DATA + // Do a final GC to satisfy leak checking. We skip this step in release + // builds. + gAtomTable->GC(GCKind::Shutdown); +#endif + + delete gAtomTable; + gAtomTable = nullptr; +} + +void NS_AddSizeOfAtoms(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gAtomTable); + return gAtomTable->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); +} + +void nsAtomSubTable::AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) { + aSizes.mTable += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) { + auto* entry = static_cast<AtomTableEntry*>(iter.Get()); + entry->mAtom->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); + } +} + +void nsAtomTable::RegisterStaticAtoms(const nsStaticAtom* aAtoms, + size_t aAtomsLen) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(!gStaticAtomsDone, "Static atom insertion is finished!"); + + for (uint32_t i = 0; i < aAtomsLen; ++i) { + const nsStaticAtom* atom = &aAtoms[i]; + MOZ_ASSERT(IsAsciiNullTerminated(atom->String())); + MOZ_ASSERT(NS_strlen(atom->String()) == atom->GetLength()); + MOZ_ASSERT(atom->IsAsciiLowercase() == + ::IsAsciiLowercase(atom->String(), atom->GetLength())); + + // This assertion ensures the static atom's precomputed hash value matches + // what would be computed by mozilla::HashString(aStr), which is what we use + // when atomizing strings. We compute this hash in Atom.py. + MOZ_ASSERT(HashString(atom->String()) == atom->hash()); + + AtomTableKey key(atom); + nsAtomSubTable& table = SelectSubTable(key); + AutoWriteLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + if (he->mAtom) { + // There are two ways we could get here. + // - Register two static atoms with the same string. + // - Create a dynamic atom and then register a static atom with the same + // string while the dynamic atom is alive. + // Both cases can cause subtle bugs, and are disallowed. We're + // programming in C++ here, not Smalltalk. + nsAutoCString name; + he->mAtom->ToUTF8String(name); + MOZ_CRASH_UNSAFE_PRINTF("Atom for '%s' already exists", name.get()); + } + he->mAtom = const_cast<nsStaticAtom*>(atom); + } +} + +already_AddRefed<nsAtom> NS_Atomize(const char* aUTF8String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->Atomize(nsDependentCString(aUTF8String)); +} + +already_AddRefed<nsAtom> nsAtomTable::Atomize(const nsACString& aUTF8String) { + bool err; + AtomTableKey key(aUTF8String.Data(), aUTF8String.Length(), &err); + if (MOZ_UNLIKELY(err)) { + MOZ_ASSERT_UNREACHABLE("Tried to atomize invalid UTF-8."); + // The input was invalid UTF-8. Let's replace the errors with U+FFFD + // and atomize the result. + nsString str; + CopyUTF8toUTF16(aUTF8String, str); + return Atomize(str, HashString(str)); + } + nsAtomSubTable& table = SelectSubTable(key); + { + AutoReadLock lock(table.mLock); + if (AtomTableEntry* he = table.Search(key)) { + return do_AddRef(he->mAtom); + } + } + + AutoWriteLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + + if (he->mAtom) { + return do_AddRef(he->mAtom); + } + + nsString str; + CopyUTF8toUTF16(aUTF8String, str); + MOZ_ASSERT(nsStringBuffer::FromString(str), "Should create a string buffer"); + RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(str, key.mHash)); + + he->mAtom = atom; + + return atom.forget(); +} + +already_AddRefed<nsAtom> NS_Atomize(const nsACString& aUTF8String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->Atomize(aUTF8String); +} + +already_AddRefed<nsAtom> NS_Atomize(const char16_t* aUTF16String) { + return NS_Atomize(nsDependentString(aUTF16String)); +} + +already_AddRefed<nsAtom> nsAtomTable::Atomize(const nsAString& aUTF16String, + uint32_t aHash) { + AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), aHash); + nsAtomSubTable& table = SelectSubTable(key); + { + AutoReadLock lock(table.mLock); + if (AtomTableEntry* he = table.Search(key)) { + return do_AddRef(he->mAtom); + } + } + AutoWriteLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + + if (he->mAtom) { + RefPtr<nsAtom> atom = he->mAtom; + return atom.forget(); + } + + RefPtr<nsAtom> atom = + dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash)); + he->mAtom = atom; + + return atom.forget(); +} + +already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String, + uint32_t aKnownHash) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->Atomize(aUTF16String, aKnownHash); +} + +already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String) { + return NS_Atomize(aUTF16String, HashString(aUTF16String)); +} + +already_AddRefed<nsAtom> nsAtomTable::AtomizeMainThread( + const nsAString& aUTF16String) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<nsAtom> retVal; + size_t length = aUTF16String.Length(); + AtomTableKey key(aUTF16String.Data(), length); + + auto p = (length < 5) ? sRecentlyUsedSmallMainThreadAtoms.Lookup(key) + : sRecentlyUsedLargeMainThreadAtoms.Lookup(key); + if (p) { + retVal = p.Data(); + return retVal.forget(); + } + + nsAtomSubTable& table = SelectSubTable(key); + { + AutoReadLock lock(table.mLock); + if (AtomTableEntry* he = table.Search(key)) { + p.Set(he->mAtom); + return do_AddRef(he->mAtom); + } + } + + AutoWriteLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + if (he->mAtom) { + retVal = he->mAtom; + } else { + RefPtr<nsAtom> newAtom = + dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash)); + he->mAtom = newAtom; + retVal = std::move(newAtom); + } + + p.Set(retVal); + return retVal.forget(); +} + +already_AddRefed<nsAtom> NS_AtomizeMainThread(const nsAString& aUTF16String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->AtomizeMainThread(aUTF16String); +} + +nsrefcnt NS_GetNumberOfAtoms(void) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->RacySlowCount(); +} + +int32_t NS_GetUnusedAtomCount(void) { return nsDynamicAtom::gUnusedAtomCount; } + +nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String) { + MOZ_ASSERT(gStaticAtomsDone, "Static atom setup not yet done."); + MOZ_ASSERT(gAtomTable); + return gAtomTable->GetStaticAtom(aUTF16String); +} + +nsStaticAtom* nsAtomTable::GetStaticAtom(const nsAString& aUTF16String) { + AtomTableKey key(aUTF16String.Data(), aUTF16String.Length()); + nsAtomSubTable& table = SelectSubTable(key); + AutoReadLock lock(table.mLock); + AtomTableEntry* he = table.Search(key); + return he && he->mAtom->IsStatic() ? static_cast<nsStaticAtom*>(he->mAtom) + : nullptr; +} + +void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom) { + // Assume the common case is that the atom is already ASCII lowercase. + if (aAtom->IsAsciiLowercase()) { + return; + } + + nsAutoString lowercased; + ToLowerCaseASCII(nsDependentAtomString(aAtom), lowercased); + aAtom = NS_Atomize(lowercased); +} diff --git a/xpcom/ds/nsAtomTable.h b/xpcom/ds/nsAtomTable.h new file mode 100644 index 0000000000..ac69daea9f --- /dev/null +++ b/xpcom/ds/nsAtomTable.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 nsAtomTable_h__ +#define nsAtomTable_h__ + +#include "mozilla/MemoryReporting.h" +#include "nsAtom.h" + +#include <stddef.h> + +void NS_InitAtomTable(); +void NS_ShutdownAtomTable(); + +namespace mozilla { +struct AtomsSizes { + size_t mTable; + size_t mDynamicAtoms; + + AtomsSizes() : mTable(0), mDynamicAtoms(0) {} +}; +} // namespace mozilla + +void NS_AddSizeOfAtoms(mozilla::MallocSizeOf aMallocSizeOf, + mozilla::AtomsSizes& aSizes); + +#endif // nsAtomTable_h__ diff --git a/xpcom/ds/nsBaseHashtable.h b/xpcom/ds/nsBaseHashtable.h new file mode 100644 index 0000000000..7b57ef4666 --- /dev/null +++ b/xpcom/ds/nsBaseHashtable.h @@ -0,0 +1,1029 @@ +/* -*- 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 nsBaseHashtable_h__ +#define nsBaseHashtable_h__ + +#include <functional> +#include <utility> + +#include "mozilla/dom/SafeRefPtr.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Result.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsHashtablesFwd.h" +#include "nsTHashtable.h" + +namespace mozilla::detail { + +template <typename SmartPtr> +struct SmartPtrTraits { + static constexpr bool IsSmartPointer = false; + static constexpr bool IsRefCounted = false; +}; + +template <typename Pointee> +struct SmartPtrTraits<UniquePtr<Pointee>> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = false; + using SmartPointerType = UniquePtr<Pointee>; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template <typename U> + using OtherSmartPtrType = UniquePtr<U>; + + template <typename U, typename... Args> + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return mozilla::MakeUnique<U>(std::forward<Args>(aConstructionArgs)...); + } +}; + +template <typename Pointee> +struct SmartPtrTraits<RefPtr<Pointee>> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = true; + using SmartPointerType = RefPtr<Pointee>; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template <typename U> + using OtherSmartPtrType = RefPtr<U>; + + template <typename U, typename... Args> + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return MakeRefPtr<U>(std::forward<Args>(aConstructionArgs)...); + } +}; + +template <typename Pointee> +struct SmartPtrTraits<SafeRefPtr<Pointee>> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = true; + using SmartPointerType = SafeRefPtr<Pointee>; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template <typename U> + using OtherSmartPtrType = SafeRefPtr<U>; + + template <typename U, typename... Args> + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return MakeSafeRefPtr<U>(std::forward<Args>(aConstructionArgs)...); + } +}; + +template <typename Pointee> +struct SmartPtrTraits<nsCOMPtr<Pointee>> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = true; + using SmartPointerType = nsCOMPtr<Pointee>; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template <typename U> + using OtherSmartPtrType = nsCOMPtr<U>; + + template <typename U, typename... Args> + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return MakeRefPtr<U>(std::forward<Args>(aConstructionArgs)...); + } +}; + +template <class T> +T* PtrGetWeak(T* aPtr) { + return aPtr; +} + +template <class T> +T* PtrGetWeak(const RefPtr<T>& aPtr) { + return aPtr.get(); +} + +template <class T> +T* PtrGetWeak(const SafeRefPtr<T>& aPtr) { + return aPtr.unsafeGetRawPtr(); +} + +template <class T> +T* PtrGetWeak(const nsCOMPtr<T>& aPtr) { + return aPtr.get(); +} + +template <class T> +T* PtrGetWeak(const UniquePtr<T>& aPtr) { + return aPtr.get(); +} + +template <typename EntryType> +class nsBaseHashtableValueIterator : public ::detail::nsTHashtableIteratorBase { + // friend class nsTHashtable<EntryType>; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const std::decay_t<typename EntryType::DataType>; + using difference_type = int32_t; + using pointer = value_type*; + using reference = value_type&; + + using iterator_type = nsBaseHashtableValueIterator; + using const_iterator_type = nsBaseHashtableValueIterator; + + using nsTHashtableIteratorBase::nsTHashtableIteratorBase; + + value_type* operator->() const { + return &static_cast<const EntryType*>(mIterator.Get())->GetData(); + } + decltype(auto) operator*() const { + return static_cast<const EntryType*>(mIterator.Get())->GetData(); + } + + iterator_type& operator++() { + mIterator.Next(); + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } +}; + +template <typename EntryType> +class nsBaseHashtableValueRange { + public: + using IteratorType = nsBaseHashtableValueIterator<EntryType>; + using iterator = IteratorType; + + explicit nsBaseHashtableValueRange(const PLDHashTable& aHashtable) + : mHashtable{aHashtable} {} + + auto begin() const { return IteratorType{mHashtable}; } + auto end() const { + return IteratorType{mHashtable, typename IteratorType::EndIteratorTag{}}; + } + auto cbegin() const { return begin(); } + auto cend() const { return end(); } + + uint32_t Count() const { return mHashtable.EntryCount(); } + + private: + const PLDHashTable& mHashtable; +}; + +template <typename EntryType> +auto RangeSize(const detail::nsBaseHashtableValueRange<EntryType>& aRange) { + return aRange.Count(); +} + +} // namespace mozilla::detail + +/** + * Data type conversion helper that is used to wrap and unwrap the specified + * DataType. + */ +template <class DataType, class UserDataType> +class nsDefaultConverter { + public: + /** + * Maps the storage DataType to the exposed UserDataType. + */ + static UserDataType Unwrap(DataType& src) { return UserDataType(src); } + + /** + * Const ref variant used for example with nsCOMPtr wrappers. + */ + static DataType Wrap(const UserDataType& src) { return DataType(src); } + + /** + * Generic conversion, this is useful for things like already_AddRefed. + */ + template <typename U> + static DataType Wrap(U&& src) { + return std::forward<U>(src); + } + + template <typename U> + static UserDataType Unwrap(U&& src) { + return std::forward<U>(src); + } +}; + +/** + * the private nsTHashtable::EntryType class used by nsBaseHashtable + * @see nsTHashtable for the specification of this class + * @see nsBaseHashtable for template parameters + */ +template <class KeyClass, class TDataType> +class nsBaseHashtableET : public KeyClass { + public: + using DataType = TDataType; + + const DataType& GetData() const { return mData; } + DataType* GetModifiableData() { return &mData; } + template <typename U> + void SetData(U&& aData) { + mData = std::forward<U>(aData); + } + + decltype(auto) GetWeak() const { + return mozilla::detail::PtrGetWeak(GetData()); + } + + private: + DataType mData; + friend class nsTHashtable<nsBaseHashtableET<KeyClass, DataType>>; + template <typename KeyClassX, typename DataTypeX, typename UserDataTypeX, + typename ConverterX> + friend class nsBaseHashtable; + friend class ::detail::nsTHashtableKeyIterator< + nsBaseHashtableET<KeyClass, DataType>>; + + typedef typename KeyClass::KeyType KeyType; + typedef typename KeyClass::KeyTypePointer KeyTypePointer; + + template <typename... Args> + explicit nsBaseHashtableET(KeyTypePointer aKey, Args&&... aArgs); + nsBaseHashtableET(nsBaseHashtableET<KeyClass, DataType>&& aToMove) = default; + ~nsBaseHashtableET() = default; +}; + +/** + * Templated hashtable. Usually, this isn't instantiated directly but through + * its sub-class templates nsInterfaceHashtable, nsClassHashtable, + * nsRefPtrHashtable and nsTHashMap. + * + * Originally, UserDataType used to be the only type exposed to the user in the + * public member function signatures (hence its name), but this has proven to + * inadequate over time. Now, UserDataType is only exposed in by-value + * getter member functions that are called *Get*. Member functions that provide + * access to the DataType are called Lookup rather than Get. Note that this rule + * does not apply to nsRefPtrHashtable and nsInterfaceHashtable, as they are + * provide a similar interface, but are no genuine sub-classes of + * nsBaseHashtable. + * + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param DataType the datatype stored in the hashtable, + * for example, uint32_t or nsCOMPtr. + * @param UserDataType the datatype returned from the by-value getter member + * functions (named *Get*), for example uint32_t or nsISupports* + * @param Converter that is used to map from DataType to UserDataType. A + * default converter is provided that assumes implicit conversion is an + * option. + */ +template <class KeyClass, class DataType, class UserDataType, class Converter> +class nsBaseHashtable + : protected nsTHashtable<nsBaseHashtableET<KeyClass, DataType>> { + using Base = nsTHashtable<nsBaseHashtableET<KeyClass, DataType>>; + typedef mozilla::fallible_t fallible_t; + + public: + typedef typename KeyClass::KeyType KeyType; + typedef nsBaseHashtableET<KeyClass, DataType> EntryType; + + using nsTHashtable<EntryType>::Contains; + using nsTHashtable<EntryType>::GetGeneration; + using nsTHashtable<EntryType>::SizeOfExcludingThis; + using nsTHashtable<EntryType>::SizeOfIncludingThis; + + nsBaseHashtable() = default; + explicit nsBaseHashtable(uint32_t aInitLength) + : nsTHashtable<EntryType>(aInitLength) {} + + /** + * Return the number of entries in the table. + * @return number of entries + */ + [[nodiscard]] uint32_t Count() const { + return nsTHashtable<EntryType>::Count(); + } + + /** + * Return whether the table is empty. + * @return whether empty + */ + [[nodiscard]] bool IsEmpty() const { + return nsTHashtable<EntryType>::IsEmpty(); + } + + /** + * Get the value, returning a flag indicating the presence of the entry in + * the table. + * + * @param aKey the key to retrieve + * @param aData data associated with this key will be placed at this pointer. + * If you only need to check if the key exists, aData may be null. + * @return true if the key exists. If key does not exist, aData is not + * modified. + * + * @attention As opposed to Remove, this does not assign a value to *aData if + * no entry is present! (And also as opposed to the member function Get with + * the same signature that nsClassHashtable defines and hides this one.) + */ + [[nodiscard]] bool Get(KeyType aKey, UserDataType* aData) const { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return false; + } + + if (aData) { + *aData = Converter::Unwrap(ent->mData); + } + + return true; + } + + /** + * Get the value, returning a zero-initialized POD or a default-initialized + * object if the entry is not present in the table. + * + * This overload can only be used if UserDataType is default-constructible. + * Use the double-argument Get or MaybeGet with non-default-constructible + * UserDataType. + * + * @param aKey the key to retrieve + * @return The found value, or UserDataType{} if no entry was found with the + * given key. + * @note If zero/default-initialized values are stored in the table, it is + * not possible to distinguish between such a value and a missing entry. + */ + [[nodiscard]] UserDataType Get(KeyType aKey) const { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return UserDataType{}; + } + + return Converter::Unwrap(ent->mData); + } + + /** + * Get the value, returning Nothing if the entry is not present in the table. + * + * @param aKey the key to retrieve + * @return The found value wrapped in a Maybe, or Nothing if no entry was + * found with the given key. + */ + [[nodiscard]] mozilla::Maybe<UserDataType> MaybeGet(KeyType aKey) const { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return mozilla::Nothing(); + } + + return mozilla::Some(Converter::Unwrap(ent->mData)); + } + + using SmartPtrTraits = mozilla::detail::SmartPtrTraits<DataType>; + + /** + * Looks up aKey in the hash table. If it doesn't exist a new object of + * SmartPtrTraits::PointeeType will be created (using the arguments provided) + * and then returned. + * + * \note This can only be instantiated if DataType is a smart pointer. + */ + template <typename... Args> + auto GetOrInsertNew(KeyType aKey, Args&&... aConstructionArgs) { + static_assert( + SmartPtrTraits::IsSmartPointer, + "GetOrInsertNew can only be used with smart pointer data types"); + return mozilla::detail::PtrGetWeak(LookupOrInsertWith(std::move(aKey), [&] { + return SmartPtrTraits::template NewObject< + typename SmartPtrTraits::PointeeType>( + std::forward<Args>(aConstructionArgs)...); + })); + } + + /** + * Add aKey to the table if not already present, and return a reference to its + * value. If aKey is not already in the table then the a default-constructed + * or the provided value aData is used. + * + * If the arguments are non-trivial to provide, consider using + * LookupOrInsertWith instead. + */ + template <typename... Args> + DataType& LookupOrInsert(const KeyType& aKey, Args&&... aArgs) { + return WithEntryHandle(aKey, [&](auto entryHandle) -> DataType& { + return entryHandle.OrInsert(std::forward<Args>(aArgs)...); + }); + } + + /** + * Add aKey to the table if not already present, and return a reference to its + * value. If aKey is not already in the table then the value is + * constructed using the given factory. + */ + template <typename F> + DataType& LookupOrInsertWith(const KeyType& aKey, F&& aFunc) { + return WithEntryHandle(aKey, [&aFunc](auto entryHandle) -> DataType& { + return entryHandle.OrInsertWith(std::forward<F>(aFunc)); + }); + } + + /** + * Add aKey to the table if not already present, and return a reference to its + * value. If aKey is not already in the table then the value is + * constructed using the given factory. + */ + template <typename F> + [[nodiscard]] auto TryLookupOrInsertWith(const KeyType& aKey, F&& aFunc) { + return WithEntryHandle( + aKey, + [&aFunc](auto entryHandle) + -> mozilla::Result<std::reference_wrapper<DataType>, + typename std::invoke_result_t<F>::err_type> { + if (entryHandle) { + return std::ref(entryHandle.Data()); + } + + // XXX Use MOZ_TRY after generalizing QM_TRY to mfbt. + auto res = std::forward<F>(aFunc)(); + if (res.isErr()) { + return res.propagateErr(); + } + return std::ref(entryHandle.Insert(res.unwrap())); + }); + } + + /** + * If it does not yet, inserts a new entry with the handle's key and the + * value passed to this function. Otherwise, it updates the entry by the + * value passed to this function. + * + * \tparam U DataType must be implicitly convertible (and assignable) from U + * \post HasEntry() + * \param aKey the key to put + * \param aData the new data + */ + template <typename U> + DataType& InsertOrUpdate(KeyType aKey, U&& aData) { + return WithEntryHandle(aKey, [&aData](auto entryHandle) -> DataType& { + return entryHandle.InsertOrUpdate(std::forward<U>(aData)); + }); + } + + template <typename U> + [[nodiscard]] bool InsertOrUpdate(KeyType aKey, U&& aData, + const fallible_t& aFallible) { + return WithEntryHandle(aKey, aFallible, [&aData](auto maybeEntryHandle) { + if (!maybeEntryHandle) { + return false; + } + maybeEntryHandle->InsertOrUpdate(std::forward<U>(aData)); + return true; + }); + } + + /** + * Remove the entry associated with aKey (if any), _moving_ its current value + * into *aData. Return true if found. + * + * This overload can only be used if DataType is default-constructible. Use + * the single-argument Remove or Extract with non-default-constructible + * DataType. + * + * @param aKey the key to remove from the hashtable + * @param aData where to move the value. If an entry is not found, *aData + * will be assigned a default-constructed value (i.e. reset to + * zero or nullptr for primitive types). + * @return true if an entry for aKey was found (and removed) + */ + // XXX This should also better be marked nodiscard, but due to + // nsClassHashtable not guaranteeing non-nullness of entries, it is usually + // only checked if aData is nullptr in such cases. + // [[nodiscard]] + bool Remove(KeyType aKey, DataType* aData) { + if (auto* ent = this->GetEntry(aKey)) { + if (aData) { + *aData = std::move(ent->mData); + } + this->RemoveEntry(ent); + return true; + } + if (aData) { + *aData = std::move(DataType()); + } + return false; + } + + /** + * Remove the entry associated with aKey (if any). Return true if found. + * + * @param aKey the key to remove from the hashtable + * @return true if an entry for aKey was found (and removed) + */ + bool Remove(KeyType aKey) { + if (auto* ent = this->GetEntry(aKey)) { + this->RemoveEntry(ent); + return true; + } + + return false; + } + + /** + * Retrieve the value for a key and remove the corresponding entry at + * the same time. + * + * @param aKey the key to retrieve and remove + * @return the found value, or Nothing if no entry was found with the + * given key. + */ + [[nodiscard]] mozilla::Maybe<DataType> Extract(KeyType aKey) { + mozilla::Maybe<DataType> value; + if (EntryType* ent = this->GetEntry(aKey)) { + value.emplace(std::move(ent->mData)); + this->RemoveEntry(ent); + } + return value; + } + + template <typename HashtableRef> + struct LookupResult { + private: + EntryType* mEntry; + HashtableRef mTable; +#ifdef DEBUG + uint32_t mTableGeneration; +#endif + + public: + LookupResult(EntryType* aEntry, HashtableRef aTable) + : mEntry(aEntry), + mTable(aTable) +#ifdef DEBUG + , + mTableGeneration(aTable.GetGeneration()) +#endif + { + } + + // Is there something stored in the table? + explicit operator bool() const { + MOZ_ASSERT(mTableGeneration == mTable.GetGeneration()); + return mEntry; + } + + void Remove() { + if (!*this) { + return; + } + mTable.RemoveEntry(mEntry); + mEntry = nullptr; + } + + [[nodiscard]] DataType& Data() { + MOZ_ASSERT(!!*this, "must have an entry to access its value"); + return mEntry->mData; + } + + [[nodiscard]] const DataType& Data() const { + MOZ_ASSERT(!!*this, "must have an entry to access its value"); + return mEntry->mData; + } + + [[nodiscard]] DataType* DataPtrOrNull() { + return static_cast<bool>(*this) ? &mEntry->mData : nullptr; + } + + [[nodiscard]] const DataType* DataPtrOrNull() const { + return static_cast<bool>(*this) ? &mEntry->mData : nullptr; + } + + [[nodiscard]] DataType* operator->() { return &Data(); } + [[nodiscard]] const DataType* operator->() const { return &Data(); } + + [[nodiscard]] DataType& operator*() { return Data(); } + [[nodiscard]] const DataType& operator*() const { return Data(); } + }; + + /** + * Removes all entries matching a predicate. + * + * The predicate must be compatible with signature bool (const Iterator &). + */ + template <typename Pred> + void RemoveIf(Pred&& aPred) { + for (auto iter = Iter(); !iter.Done(); iter.Next()) { + if (aPred(const_cast<std::add_const_t<decltype(iter)>&>(iter))) { + iter.Remove(); + } + } + } + + /** + * Looks up aKey in the hashtable and returns an object that allows you to + * read/modify the value of the entry, or remove the entry (if found). + * + * A typical usage of this API looks like this: + * + * if (auto entry = hashtable.Lookup(key)) { + * DoSomething(entry.Data()); + * if (entry.Data() > 42) { + * entry.Remove(); + * } + * } // else - an entry with the given key doesn't exist + * + * This is useful for cases where you want to read/write the value of an entry + * and (optionally) remove the entry without having to do multiple hashtable + * lookups. If you want to insert a new entry if one does not exist, then use + * WithEntryHandle instead, see below. + */ + [[nodiscard]] auto Lookup(KeyType aKey) { + return LookupResult<nsBaseHashtable&>(this->GetEntry(aKey), *this); + } + + [[nodiscard]] auto Lookup(KeyType aKey) const { + return LookupResult<const nsBaseHashtable&>(this->GetEntry(aKey), *this); + } + + /** + * Used by WithEntryHandle as the argument type to its functor. It is + * associated with the Key passed to WithEntryHandle and manages only the + * potential entry with that key. Note that in case no modifying operations + * are called on the handle, the state of the hashtable remains unchanged, + * i.e. WithEntryHandle does not modify the hashtable itself. + * + * Provides query functions (Key, HasEntry/operator bool, Data) and + * modifying operations for inserting new entries (Insert), updating existing + * entries (Update) and removing existing entries (Remove). They have + * debug-only assertion that fail when the state of the entry doesn't match + * the expectation. There are variants prefixed with "Or" (OrInsert, OrUpdate, + * OrRemove) that are a no-op in case the entry does already exist resp. does + * not exist. There are also variants OrInsertWith and OrUpdateWith that don't + * accept a value, but a functor, which is only called if the operation takes + * place, which should be used if the provision of the value is not trivial + * (e.g. allocates a heap object). Finally, there's InsertOrUpdate that + * handles both existing and non-existing entries. + * + * Note that all functions of EntryHandle only deal with DataType, not with + * UserDataType. + */ + class EntryHandle : protected nsTHashtable<EntryType>::EntryHandle { + public: + using Base = typename nsTHashtable<EntryType>::EntryHandle; + + EntryHandle(EntryHandle&& aOther) = default; + ~EntryHandle() = default; + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&&) = delete; + + using Base::Key; + + using Base::HasEntry; + + using Base::operator bool; + + using Base::Entry; + + /** + * Inserts a new entry with the handle's key and the value passed to this + * function. + * + * \tparam Args DataType must be constructible from Args + * \pre !HasEntry() + * \post HasEntry() + */ + template <typename... Args> + DataType& Insert(Args&&... aArgs) { + Base::InsertInternal(std::forward<Args>(aArgs)...); + return Data(); + } + + /** + * If it doesn't yet exist, inserts a new entry with the handle's key and + * the value passed to this function. The value is not consumed if no insert + * takes place. + * + * \tparam Args DataType must be constructible from Args + * \post HasEntry() + */ + template <typename... Args> + DataType& OrInsert(Args&&... aArgs) { + if (!HasEntry()) { + return Insert(std::forward<Args>(aArgs)...); + } + return Data(); + } + + /** + * If it doesn't yet exist, inserts a new entry with the handle's key and + * the result of the functor passed to this function. The functor is not + * called if no insert takes place. + * + * \tparam F must return a value that is implicitly convertible to DataType + * \post HasEntry() + */ + template <typename F> + DataType& OrInsertWith(F&& aFunc) { + if (!HasEntry()) { + return Insert(std::forward<F>(aFunc)()); + } + return Data(); + } + + /** + * Updates the entry with the handle's key by the value passed to this + * function. + * + * \tparam U DataType must be assignable from U + * \pre HasEntry() + */ + template <typename U> + DataType& Update(U&& aData) { + MOZ_RELEASE_ASSERT(HasEntry()); + Data() = std::forward<U>(aData); + return Data(); + } + + /** + * If an entry with the handle's key already exists, updates its value by + * the value passed to this function. The value is not consumed if no update + * takes place. + * + * \tparam U DataType must be assignable from U + */ + template <typename U> + void OrUpdate(U&& aData) { + if (HasEntry()) { + Update(std::forward<U>(aData)); + } + } + + /** + * If an entry with the handle's key already exists, updates its value by + * the the result of the functor passed to this function. The functor is not + * called if no update takes place. + * + * \tparam F must return a value that DataType is assignable from + */ + template <typename F> + void OrUpdateWith(F&& aFunc) { + if (HasEntry()) { + Update(std::forward<F>(aFunc)()); + } + } + + /** + * If it does not yet, inserts a new entry with the handle's key and the + * value passed to this function. Otherwise, it updates the entry by the + * value passed to this function. + * + * \tparam U DataType must be implicitly convertible (and assignable) from U + * \post HasEntry() + */ + template <typename U> + DataType& InsertOrUpdate(U&& aData) { + if (!HasEntry()) { + Insert(std::forward<U>(aData)); + } else { + Update(std::forward<U>(aData)); + } + return Data(); + } + + using Base::Remove; + + using Base::OrRemove; + + /** + * Returns a reference to the value of the entry. + * + * \pre HasEntry() + */ + [[nodiscard]] DataType& Data() { return Entry()->mData; } + + [[nodiscard]] DataType* DataPtrOrNull() { + return static_cast<bool>(*this) ? &Data() : nullptr; + } + + [[nodiscard]] DataType* operator->() { return &Data(); } + + [[nodiscard]] DataType& operator*() { return Data(); } + + private: + friend class nsBaseHashtable; + + explicit EntryHandle(Base&& aBase) : Base(std::move(aBase)) {} + }; + + /** + * Performs a scoped operation on the entry for aKey, which may or may not + * exist when the function is called. It calls aFunc with an EntryHandle. The + * result of aFunc is returned as the result of this function. Its return type + * may be void. See the documentation of EntryHandle for the query and + * modifying operations it offers. + * + * A simple use of this function is, e.g., + * + * hashtable.WithEntryHandle(key, [](auto&& entry) { entry.OrInsert(42); }); + * + * \attention It is not safe to perform modifying operations on the hashtable + * other than through the EntryHandle within aFunc, and trying to do so will + * trigger debug assertions, and result in undefined behaviour otherwise. + */ + template <class F> + [[nodiscard]] auto WithEntryHandle(KeyType aKey, F&& aFunc) + -> std::invoke_result_t<F, EntryHandle&&> { + return Base::WithEntryHandle( + aKey, [&aFunc](auto entryHandle) -> decltype(auto) { + return std::forward<F>(aFunc)(EntryHandle{std::move(entryHandle)}); + }); + } + + /** + * Fallible variant of WithEntryHandle, with the following differences: + * - The functor aFunc must accept a Maybe<EntryHandle> (instead of an + * EntryHandle). + * - In case allocation of the slot for the entry fails, Nothing is passed to + * the functor. + * + * For more details, see the explanation on the non-fallible overload above. + */ + template <class F> + [[nodiscard]] auto WithEntryHandle(KeyType aKey, const fallible_t& aFallible, + F&& aFunc) + -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> { + return Base::WithEntryHandle( + aKey, aFallible, [&aFunc](auto maybeEntryHandle) { + return std::forward<F>(aFunc)( + maybeEntryHandle + ? mozilla::Some(EntryHandle{maybeEntryHandle.extract()}) + : mozilla::Nothing()); + }); + } + + public: + class ConstIterator { + public: + explicit ConstIterator(nsBaseHashtable* aTable) + : mBaseIterator(&aTable->mTable) {} + ~ConstIterator() = default; + + KeyType Key() const { + return static_cast<EntryType*>(mBaseIterator.Get())->GetKey(); + } + UserDataType UserData() const { + return Converter::Unwrap( + static_cast<EntryType*>(mBaseIterator.Get())->mData); + } + const DataType& Data() const { + return static_cast<EntryType*>(mBaseIterator.Get())->mData; + } + + bool Done() const { return mBaseIterator.Done(); } + void Next() { mBaseIterator.Next(); } + + ConstIterator() = delete; + ConstIterator(const ConstIterator&) = delete; + ConstIterator(ConstIterator&& aOther) = delete; + ConstIterator& operator=(const ConstIterator&) = delete; + ConstIterator& operator=(ConstIterator&&) = delete; + + protected: + PLDHashTable::Iterator mBaseIterator; + }; + + // This is an iterator that also allows entry removal. Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // const KeyType key = iter.Key(); + // const UserDataType data = iter.UserData(); + // // or + // const DataType& data = iter.Data(); + // // ... do stuff with |key| and/or |data| ... + // // ... possibly call iter.Remove() once ... + // } + // + class Iterator final : public ConstIterator { + public: + using ConstIterator::ConstIterator; + + using ConstIterator::Data; + DataType& Data() { + return static_cast<EntryType*>(this->mBaseIterator.Get())->mData; + } + + void Remove() { this->mBaseIterator.Remove(); } + }; + + Iterator Iter() { return Iterator(this); } + + ConstIterator ConstIter() const { + return ConstIterator(const_cast<nsBaseHashtable*>(this)); + } + + using nsTHashtable<EntryType>::Remove; + + /** + * Remove the entry associated with aIter. + * + * @param aIter the iterator pointing to the entry + * @pre !aIter.Done() + */ + void Remove(ConstIterator& aIter) { aIter.mBaseIterator.Remove(); } + + using typename nsTHashtable<EntryType>::iterator; + using typename nsTHashtable<EntryType>::const_iterator; + + using nsTHashtable<EntryType>::begin; + using nsTHashtable<EntryType>::end; + using nsTHashtable<EntryType>::cbegin; + using nsTHashtable<EntryType>::cend; + + using nsTHashtable<EntryType>::Keys; + + /** + * Return a range of the values (of DataType). Note this range iterates over + * the values in place, so modifications to the nsTHashtable invalidate the + * range while it's iterated, except when calling Remove() with a value + * iterator derived from that range. + */ + auto Values() const { + return mozilla::detail::nsBaseHashtableValueRange<EntryType>{this->mTable}; + } + + /** + * Remove an entry from a value range, specified via a value iterator, e.g. + * + * for (auto it = hash.Values().begin(), end = hash.Values().end(); + * it != end; * ++it) { + * if (*it > 42) { hash.Remove(it); } + * } + * + * You might also consider using RemoveIf though. + */ + void Remove(mozilla::detail::nsBaseHashtableValueIterator<EntryType>& aIter) { + aIter.mIterator.Remove(); + } + + /** + * reset the hashtable, removing all entries + */ + void Clear() { nsTHashtable<EntryType>::Clear(); } + + /** + * Measure the size of the table's entry storage. The size of things pointed + * to by entries must be measured separately; hence the "Shallow" prefix. + * + * @param aMallocSizeOf the function used to measure heap-allocated blocks + * @return the summed size of the table's storage + */ + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return this->mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Like ShallowSizeOfExcludingThis, but includes sizeof(*this). + */ + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Swap the elements in this hashtable with the elements in aOther. + */ + void SwapElements(nsBaseHashtable& aOther) { + nsTHashtable<EntryType>::SwapElements(aOther); + } + + using nsTHashtable<EntryType>::MarkImmutable; + + /** + * Makes a clone of this hashtable by copying all entries. This requires + * KeyType and DataType to be copy-constructible. + */ + nsBaseHashtable Clone() const { return CloneAs<nsBaseHashtable>(); } + + protected: + template <typename T> + T CloneAs() const { + static_assert(std::is_base_of_v<nsBaseHashtable, T>); + // XXX This can probably be optimized, see Bug 1694368. + T result(Count()); + for (const auto& srcEntry : *this) { + result.WithEntryHandle(srcEntry.GetKey(), [&](auto&& dstEntry) { + dstEntry.Insert(srcEntry.GetData()); + }); + } + return result; + } +}; + +// +// nsBaseHashtableET definitions +// + +template <class KeyClass, class DataType> +template <typename... Args> +nsBaseHashtableET<KeyClass, DataType>::nsBaseHashtableET(KeyTypePointer aKey, + Args&&... aArgs) + : KeyClass(aKey), mData(std::forward<Args>(aArgs)...) {} + +#endif // nsBaseHashtable_h__ diff --git a/xpcom/ds/nsCOMArray.cpp b/xpcom/ds/nsCOMArray.cpp new file mode 100644 index 0000000000..a7ce778e5c --- /dev/null +++ b/xpcom/ds/nsCOMArray.cpp @@ -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/. */ + +#include "nsCOMArray.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/OperatorNewExtensions.h" + +#include "nsCOMPtr.h" + +// This specialization is private to nsCOMArray. +// It exists solely to automatically zero-out newly created array elements. +template <> +class nsTArrayElementTraits<nsISupports*> { + typedef nsISupports* E; + + public: + // Zero out the value + static inline void Construct(E* aE) { + new (mozilla::KnownNotNull, static_cast<void*>(aE)) E(); + } + // Invoke the copy-constructor in place. + template <class A> + static inline void Construct(E* aE, const A& aArg) { + new (mozilla::KnownNotNull, static_cast<void*>(aE)) E(aArg); + } + // Construct in place. + template <class... Args> + static inline void Emplace(E* aE, Args&&... aArgs) { + new (mozilla::KnownNotNull, static_cast<void*>(aE)) + E(std::forward<Args>(aArgs)...); + } + // Invoke the destructor in place. + static inline void Destruct(E* aE) { aE->~E(); } +}; + +static void ReleaseObjects(nsTArray<nsISupports*>& aArray); + +// implementations of non-trivial methods in nsCOMArray_base + +nsCOMArray_base::nsCOMArray_base(const nsCOMArray_base& aOther) { + // make sure we do only one allocation + mArray.SetCapacity(aOther.Count()); + AppendObjects(aOther); +} + +nsCOMArray_base::~nsCOMArray_base() { Clear(); } + +int32_t nsCOMArray_base::IndexOf(nsISupports* aObject, + uint32_t aStartIndex) const { + return mArray.IndexOf(aObject, aStartIndex); +} + +int32_t nsCOMArray_base::IndexOfObject(nsISupports* aObject) const { + nsCOMPtr<nsISupports> supports = do_QueryInterface(aObject); + if (NS_WARN_IF(!supports)) { + return -1; + } + + uint32_t i, count; + int32_t retval = -1; + count = mArray.Length(); + for (i = 0; i < count; ++i) { + nsCOMPtr<nsISupports> arrayItem = do_QueryInterface(mArray[i]); + if (arrayItem == supports) { + retval = i; + break; + } + } + return retval; +} + +bool nsCOMArray_base::EnumerateForwards(nsBaseArrayEnumFunc aFunc, + void* aData) const { + for (uint32_t index = 0; index < mArray.Length(); ++index) { + if (!(*aFunc)(mArray[index], aData)) { + return false; + } + } + + return true; +} + +bool nsCOMArray_base::EnumerateBackwards(nsBaseArrayEnumFunc aFunc, + void* aData) const { + for (uint32_t index = mArray.Length(); index--;) { + if (!(*aFunc)(mArray[index], aData)) { + return false; + } + } + + return true; +} + +bool nsCOMArray_base::InsertObjectAt(nsISupports* aObject, int32_t aIndex) { + if ((uint32_t)aIndex > mArray.Length()) { + return false; + } + + mArray.InsertElementAt(aIndex, aObject); + + NS_IF_ADDREF(aObject); + return true; +} + +void nsCOMArray_base::InsertElementAt(uint32_t aIndex, nsISupports* aElement) { + mArray.InsertElementAt(aIndex, aElement); + NS_IF_ADDREF(aElement); +} + +void nsCOMArray_base::InsertElementAt(uint32_t aIndex, + already_AddRefed<nsISupports> aElement) { + mArray.InsertElementAt(aIndex, aElement.take()); +} + +bool nsCOMArray_base::InsertObjectsAt(const nsCOMArray_base& aObjects, + int32_t aIndex) { + if ((uint32_t)aIndex > mArray.Length()) { + return false; + } + + mArray.InsertElementsAt(aIndex, aObjects.mArray); + + // need to addref all these + uint32_t count = aObjects.Length(); + for (uint32_t i = 0; i < count; ++i) { + NS_IF_ADDREF(aObjects[i]); + } + + return true; +} + +void nsCOMArray_base::InsertElementsAt(uint32_t aIndex, + const nsCOMArray_base& aElements) { + mArray.InsertElementsAt(aIndex, aElements.mArray); + + // need to addref all these + uint32_t count = aElements.Length(); + for (uint32_t i = 0; i < count; ++i) { + NS_IF_ADDREF(aElements[i]); + } +} + +void nsCOMArray_base::InsertElementsAt(uint32_t aIndex, + nsISupports* const* aElements, + uint32_t aCount) { + mArray.InsertElementsAt(aIndex, aElements, aCount); + + // need to addref all these + for (uint32_t i = 0; i < aCount; ++i) { + NS_IF_ADDREF(aElements[i]); + } +} + +void nsCOMArray_base::ReplaceObjectAt(nsISupports* aObject, int32_t aIndex) { + mArray.EnsureLengthAtLeast(aIndex + 1); + nsISupports* oldObject = mArray[aIndex]; + // Make sure to addref first, in case aObject == oldObject + NS_IF_ADDREF(mArray[aIndex] = aObject); + NS_IF_RELEASE(oldObject); +} + +bool nsCOMArray_base::RemoveObject(nsISupports* aObject) { + bool result = mArray.RemoveElement(aObject); + if (result) { + NS_IF_RELEASE(aObject); + } + return result; +} + +bool nsCOMArray_base::RemoveObjectAt(int32_t aIndex) { + if (uint32_t(aIndex) < mArray.Length()) { + nsISupports* element = mArray[aIndex]; + + mArray.RemoveElementAt(aIndex); + NS_IF_RELEASE(element); + return true; + } + + return false; +} + +void nsCOMArray_base::RemoveElementAt(uint32_t aIndex) { + nsISupports* element = mArray[aIndex]; + mArray.RemoveElementAt(aIndex); + NS_IF_RELEASE(element); +} + +bool nsCOMArray_base::RemoveObjectsAt(int32_t aIndex, int32_t aCount) { + if (uint32_t(aIndex) + uint32_t(aCount) <= mArray.Length()) { + nsTArray<nsISupports*> elementsToDestroy(aCount); + elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount); + mArray.RemoveElementsAt(aIndex, aCount); + ReleaseObjects(elementsToDestroy); + return true; + } + + return false; +} + +void nsCOMArray_base::RemoveElementsAt(uint32_t aIndex, uint32_t aCount) { + nsTArray<nsISupports*> elementsToDestroy(aCount); + elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount); + mArray.RemoveElementsAt(aIndex, aCount); + ReleaseObjects(elementsToDestroy); +} + +// useful for destructors +void ReleaseObjects(nsTArray<nsISupports*>& aArray) { + for (uint32_t i = 0; i < aArray.Length(); ++i) { + NS_IF_RELEASE(aArray[i]); + } +} + +void nsCOMArray_base::Clear() { + nsTArray<nsISupports*> objects = std::move(mArray); + ReleaseObjects(objects); +} + +bool nsCOMArray_base::SetCount(int32_t aNewCount) { + NS_ASSERTION(aNewCount >= 0, "SetCount(negative index)"); + if (aNewCount < 0) { + return false; + } + + int32_t count = mArray.Length(); + if (count > aNewCount) { + RemoveObjectsAt(aNewCount, mArray.Length() - aNewCount); + } + mArray.SetLength(aNewCount); + return true; +} diff --git a/xpcom/ds/nsCOMArray.h b/xpcom/ds/nsCOMArray.h new file mode 100644 index 0000000000..9b2abb3cbf --- /dev/null +++ b/xpcom/ds/nsCOMArray.h @@ -0,0 +1,387 @@ +/* -*- 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 nsCOMArray_h__ +#define nsCOMArray_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/MemoryReporting.h" + +#include "nsCycleCollectionNoteChild.h" +#include "nsTArray.h" +#include "nsISupports.h" + +#include <iterator> + +// See below for the definition of nsCOMArray<T> + +// a class that's nsISupports-specific, so that we can contain the +// work of this class in the XPCOM dll +class nsCOMArray_base { + friend class nsArrayBase; + + protected: + nsCOMArray_base() = default; + explicit nsCOMArray_base(int32_t aCount) : mArray(aCount) {} + nsCOMArray_base(const nsCOMArray_base& aOther); + nsCOMArray_base(nsCOMArray_base&& aOther) = default; + nsCOMArray_base& operator=(nsCOMArray_base&& aOther) = default; + ~nsCOMArray_base(); + + int32_t IndexOf(nsISupports* aObject, uint32_t aStartIndex = 0) const; + bool Contains(nsISupports* aObject) const { return IndexOf(aObject) != -1; } + + int32_t IndexOfObject(nsISupports* aObject) const; + bool ContainsObject(nsISupports* aObject) const { + return IndexOfObject(aObject) != -1; + } + + typedef bool (*nsBaseArrayEnumFunc)(void* aElement, void* aData); + + // enumerate through the array with a callback. + bool EnumerateForwards(nsBaseArrayEnumFunc aFunc, void* aData) const; + + bool EnumerateBackwards(nsBaseArrayEnumFunc aFunc, void* aData) const; + + bool InsertObjectAt(nsISupports* aObject, int32_t aIndex); + void InsertElementAt(uint32_t aIndex, nsISupports* aElement); + void InsertElementAt(uint32_t aIndex, already_AddRefed<nsISupports> aElement); + bool InsertObjectsAt(const nsCOMArray_base& aObjects, int32_t aIndex); + void InsertElementsAt(uint32_t aIndex, const nsCOMArray_base& aElements); + void InsertElementsAt(uint32_t aIndex, nsISupports* const* aElements, + uint32_t aCount); + void ReplaceObjectAt(nsISupports* aObject, int32_t aIndex); + void ReplaceElementAt(uint32_t aIndex, nsISupports* aElement) { + nsISupports* oldElement = mArray[aIndex]; + NS_IF_ADDREF(mArray[aIndex] = aElement); + NS_IF_RELEASE(oldElement); + } + bool AppendObject(nsISupports* aObject) { + return InsertObjectAt(aObject, Count()); + } + void AppendElement(nsISupports* aElement) { + InsertElementAt(Length(), aElement); + } + void AppendElement(already_AddRefed<nsISupports> aElement) { + InsertElementAt(Length(), std::move(aElement)); + } + + bool AppendObjects(const nsCOMArray_base& aObjects) { + return InsertObjectsAt(aObjects, Count()); + } + void AppendElements(const nsCOMArray_base& aElements) { + return InsertElementsAt(Length(), aElements); + } + void AppendElements(nsISupports* const* aElements, uint32_t aCount) { + return InsertElementsAt(Length(), aElements, aCount); + } + bool RemoveObject(nsISupports* aObject); + nsISupports** Elements() { return mArray.Elements(); } + void SwapElements(nsCOMArray_base& aOther) { + mArray.SwapElements(aOther.mArray); + } + + public: + // elements in the array (including null elements!) + int32_t Count() const { return mArray.Length(); } + // nsTArray-compatible version + uint32_t Length() const { return mArray.Length(); } + bool IsEmpty() const { return mArray.IsEmpty(); } + + // If the array grows, the newly created entries will all be null; + // if the array shrinks, the excess entries will all be released. + bool SetCount(int32_t aNewCount); + // nsTArray-compatible version + void TruncateLength(uint32_t aNewLength) { + if (mArray.Length() > aNewLength) { + RemoveElementsAt(aNewLength, mArray.Length() - aNewLength); + } + } + + // remove all elements in the array, and call NS_RELEASE on each one + void Clear(); + + nsISupports* ObjectAt(int32_t aIndex) const { return mArray[aIndex]; } + // nsTArray-compatible version + nsISupports* ElementAt(uint32_t aIndex) const { return mArray[aIndex]; } + + nsISupports* SafeObjectAt(int32_t aIndex) const { + return mArray.SafeElementAt(aIndex, nullptr); + } + // nsTArray-compatible version + nsISupports* SafeElementAt(uint32_t aIndex) const { + return mArray.SafeElementAt(aIndex, nullptr); + } + + nsISupports* operator[](int32_t aIndex) const { return mArray[aIndex]; } + + // remove an element at a specific position, shrinking the array + // as necessary + bool RemoveObjectAt(int32_t aIndex); + // nsTArray-compatible version + void RemoveElementAt(uint32_t aIndex); + + // remove a range of elements at a specific position, shrinking the array + // as necessary + bool RemoveObjectsAt(int32_t aIndex, int32_t aCount); + // nsTArray-compatible version + void RemoveElementsAt(uint32_t aIndex, uint32_t aCount); + + void SwapElementsAt(uint32_t aIndex1, uint32_t aIndex2) { + nsISupports* tmp = mArray[aIndex1]; + mArray[aIndex1] = mArray[aIndex2]; + mArray[aIndex2] = tmp; + } + + // Ensures there is enough space to store a total of aCapacity objects. + // This method never deletes any objects. + void SetCapacity(uint32_t aCapacity) { mArray.SetCapacity(aCapacity); } + uint32_t Capacity() { return mArray.Capacity(); } + + // Measures the size of the array's element storage. If you want to measure + // anything hanging off the array, you must iterate over the elements and + // measure them individually; hence the "Shallow" prefix. Note that because + // each element in an nsCOMArray<T> is actually a T* any such iteration + // should use a SizeOfIncludingThis() function on each element rather than a + // SizeOfExcludingThis() function, so that the memory taken by the T itself + // is included as well as anything it points to. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + protected: + // the actual storage + nsTArray<nsISupports*> mArray; + + private: + // don't implement these, defaults will muck with refcounts! + nsCOMArray_base& operator=(const nsCOMArray_base& aOther) = delete; +}; + +inline void ImplCycleCollectionUnlink(nsCOMArray_base& aField) { + aField.Clear(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsCOMArray_base& aField, + const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + int32_t length = aField.Count(); + for (int32_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags); + } +} + +// a non-XPCOM, refcounting array of XPCOM objects +// used as a member variable or stack variable - this object is NOT +// refcounted, but the objects that it holds are +// +// most of the read-only accessors like ObjectAt()/etc do NOT refcount +// on the way out. This means that you can do one of two things: +// +// * does an addref, but holds onto a reference +// nsCOMPtr<T> foo = array[i]; +// +// * avoids the refcount, but foo might go stale if array[i] is ever +// * modified/removed. Be careful not to NS_RELEASE(foo)! +// T* foo = array[i]; +// +// This array will accept null as an argument for any object, and will store +// null in the array. But that also means that methods like ObjectAt() may +// return null when referring to an existing, but null entry in the array. +template <class T> +class nsCOMArray : public nsCOMArray_base { + public: + typedef int32_t index_type; + typedef mozilla::ArrayIterator<T*, nsCOMArray> iterator; + typedef mozilla::ArrayIterator<const T*, nsCOMArray> const_iterator; + typedef std::reverse_iterator<iterator> reverse_iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + + nsCOMArray() = default; + explicit nsCOMArray(int32_t aCount) : nsCOMArray_base(aCount) {} + explicit nsCOMArray(const nsCOMArray<T>& aOther) : nsCOMArray_base(aOther) {} + nsCOMArray(nsCOMArray<T>&& aOther) = default; + ~nsCOMArray() = default; + + // We have a move assignment operator, but no copy assignment operator. + nsCOMArray<T>& operator=(nsCOMArray<T>&& aOther) = default; + + // these do NOT refcount on the way out, for speed + T* ObjectAt(int32_t aIndex) const { + return static_cast<T*>(nsCOMArray_base::ObjectAt(aIndex)); + } + // nsTArray-compatible version + T* ElementAt(uint32_t aIndex) const { + return static_cast<T*>(nsCOMArray_base::ElementAt(aIndex)); + } + + // these do NOT refcount on the way out, for speed + T* SafeObjectAt(int32_t aIndex) const { + return static_cast<T*>(nsCOMArray_base::SafeObjectAt(aIndex)); + } + // nsTArray-compatible version + T* SafeElementAt(uint32_t aIndex) const { + return static_cast<T*>(nsCOMArray_base::SafeElementAt(aIndex)); + } + + // indexing operator for syntactic sugar + T* operator[](int32_t aIndex) const { return ObjectAt(aIndex); } + + // index of the element in question.. does NOT refcount + // note: this does not check COM object identity. Use + // IndexOfObject() for that purpose + int32_t IndexOf(T* aObject, uint32_t aStartIndex = 0) const { + return nsCOMArray_base::IndexOf(aObject, aStartIndex); + } + bool Contains(T* aObject) const { return nsCOMArray_base::Contains(aObject); } + + // index of the element in question.. be careful! + // this is much slower than IndexOf() because it uses + // QueryInterface to determine actual COM identity of the object + // if you need to do this frequently then consider enforcing + // COM object identity before adding/comparing elements + int32_t IndexOfObject(T* aObject) const { + return nsCOMArray_base::IndexOfObject(aObject); + } + bool ContainsObject(nsISupports* aObject) const { + return nsCOMArray_base::ContainsObject(aObject); + } + + // inserts aObject at aIndex, shifting the objects at aIndex and + // later to make space + bool InsertObjectAt(T* aObject, int32_t aIndex) { + return nsCOMArray_base::InsertObjectAt(aObject, aIndex); + } + // nsTArray-compatible version + void InsertElementAt(uint32_t aIndex, T* aElement) { + nsCOMArray_base::InsertElementAt(aIndex, aElement); + } + + // inserts the objects from aObject at aIndex, shifting the + // objects at aIndex and later to make space + bool InsertObjectsAt(const nsCOMArray<T>& aObjects, int32_t aIndex) { + return nsCOMArray_base::InsertObjectsAt(aObjects, aIndex); + } + // nsTArray-compatible version + void InsertElementsAt(uint32_t aIndex, const nsCOMArray<T>& aElements) { + nsCOMArray_base::InsertElementsAt(aIndex, aElements); + } + void InsertElementsAt(uint32_t aIndex, T* const* aElements, uint32_t aCount) { + nsCOMArray_base::InsertElementsAt( + aIndex, reinterpret_cast<nsISupports* const*>(aElements), aCount); + } + + // replaces an existing element. Warning: if the array grows, + // the newly created entries will all be null + void ReplaceObjectAt(T* aObject, int32_t aIndex) { + nsCOMArray_base::ReplaceObjectAt(aObject, aIndex); + } + // nsTArray-compatible version + void ReplaceElementAt(uint32_t aIndex, T* aElement) { + nsCOMArray_base::ReplaceElementAt(aIndex, aElement); + } + + using TComparatorFunc = int (*)(T*, T*); + + // The default sort function uses nsTArray::Sort. + // Note that the order of equal items is unstable with this. + void Sort(TComparatorFunc aFunc) { + mArray.Sort( + [aFunc](nsISupports* const& aLeft, nsISupports* const& aRight) -> int { + return aFunc(static_cast<T*>(aLeft), static_cast<T*>(aRight)); + }); + } + + // Sort with a stable algorithm, uses nsTArray::StableSort. + void StableSort(TComparatorFunc aFunc) { + mArray.StableSort( + [aFunc](nsISupports* const& aLeft, nsISupports* const& aRight) -> int { + return aFunc(static_cast<T*>(aLeft), static_cast<T*>(aRight)); + }); + } + + // append an object, growing the array as necessary + bool AppendObject(T* aObject) { + return nsCOMArray_base::AppendObject(aObject); + } + // nsTArray-compatible version + void AppendElement(T* aElement) { nsCOMArray_base::AppendElement(aElement); } + void AppendElement(already_AddRefed<T> aElement) { + nsCOMArray_base::AppendElement(std::move(aElement)); + } + + // append objects, growing the array as necessary + bool AppendObjects(const nsCOMArray<T>& aObjects) { + return nsCOMArray_base::AppendObjects(aObjects); + } + // nsTArray-compatible version + void AppendElements(const nsCOMArray<T>& aElements) { + return nsCOMArray_base::AppendElements(aElements); + } + void AppendElements(T* const* aElements, uint32_t aCount) { + InsertElementsAt(Length(), aElements, aCount); + } + + // remove the first instance of the given object and shrink the + // array as necessary + // Warning: if you pass null here, it will remove the first null element + bool RemoveObject(T* aObject) { + return nsCOMArray_base::RemoveObject(aObject); + } + // nsTArray-compatible version + bool RemoveElement(T* aElement) { + return nsCOMArray_base::RemoveObject(aElement); + } + + T** Elements() { return reinterpret_cast<T**>(nsCOMArray_base::Elements()); } + void SwapElements(nsCOMArray<T>& aOther) { + nsCOMArray_base::SwapElements(aOther); + } + + // Methods for range-based for loops. + iterator begin() { return iterator(*this, 0); } + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator cbegin() const { return begin(); } + iterator end() { return iterator(*this, Length()); } + const_iterator end() const { return const_iterator(*this, Length()); } + const_iterator cend() const { return end(); } + + // Methods for reverse iterating. + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + const_reverse_iterator crbegin() const { return rbegin(); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + const_reverse_iterator crend() const { return rend(); } + + private: + // don't implement these! + nsCOMArray<T>& operator=(const nsCOMArray<T>& aOther) = delete; +}; + +template <typename T> +inline void ImplCycleCollectionUnlink(nsCOMArray<T>& aField) { + aField.Clear(); +} + +template <typename E> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsCOMArray<E>& aField, + const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + int32_t length = aField.Count(); + for (int32_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags); + } +} + +#endif diff --git a/xpcom/ds/nsCRT.cpp b/xpcom/ds/nsCRT.cpp new file mode 100644 index 0000000000..cccdf5d134 --- /dev/null +++ b/xpcom/ds/nsCRT.cpp @@ -0,0 +1,123 @@ +/* -*- 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/. */ + +/** + * MODULE NOTES: + * @update gess7/30/98 + * + * Much as I hate to do it, we were using string compares wrong. + * Often, programmers call functions like strcmp(s1,s2), and pass + * one or more null strings. Rather than blow up on these, I've + * added quick checks to ensure that cases like this don't cause + * us to fail. + * + * In general, if you pass a null into any of these string compare + * routines, we simply return 0. + */ + +#include "nsCRT.h" +#include "nsDebug.h" + +//---------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +// My lovely strtok routine + +#define IS_DELIM(m, c) ((m)[(c) >> 3] & (1 << ((c) & 7))) +#define SET_DELIM(m, c) ((m)[(c) >> 3] |= (1 << ((c) & 7))) +#define DELIM_TABLE_SIZE 32 + +char* nsCRT::strtok(char* aString, const char* aDelims, char** aNewStr) { + NS_ASSERTION(aString, + "Unlike regular strtok, the first argument cannot be null."); + + char delimTable[DELIM_TABLE_SIZE]; + uint32_t i; + char* result; + char* str = aString; + + for (i = 0; i < DELIM_TABLE_SIZE; ++i) { + delimTable[i] = '\0'; + } + + for (i = 0; aDelims[i]; i++) { + SET_DELIM(delimTable, static_cast<uint8_t>(aDelims[i])); + } + NS_ASSERTION(aDelims[i] == '\0', "too many delimiters"); + + // skip to beginning + while (*str && IS_DELIM(delimTable, static_cast<uint8_t>(*str))) { + str++; + } + result = str; + + // fix up the end of the token + while (*str) { + if (IS_DELIM(delimTable, static_cast<uint8_t>(*str))) { + *str++ = '\0'; + break; + } + str++; + } + *aNewStr = str; + + return str == result ? nullptr : result; +} + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Compare unichar string ptrs, stopping at the 1st null + * NOTE: If both are null, we return 0. + * NOTE: We terminate the search upon encountering a nullptr + * + * @update gess 11/10/99 + * @param s1 and s2 both point to unichar strings + * @return 0 if they match, -1 if s1<s2; 1 if s1>s2 + */ +int32_t nsCRT::strcmp(const char16_t* aStr1, const char16_t* aStr2) { + if (aStr1 && aStr2) { + for (;;) { + char16_t c1 = *aStr1++; + char16_t c2 = *aStr2++; + if (c1 != c2) { + if (c1 < c2) { + return -1; + } + return 1; + } + if (c1 == 0 || c2 == 0) { + break; + } + } + } else { + if (aStr1) { // aStr2 must have been null + return -1; + } + if (aStr2) { // aStr1 must have been null + return 1; + } + } + return 0; +} + +// This should use NSPR but NSPR isn't exporting its PR_strtoll function +// Until then... +int64_t nsCRT::atoll(const char* aStr) { + if (!aStr) { + return 0; + } + + int64_t ll = 0; + + while (*aStr && *aStr >= '0' && *aStr <= '9') { + ll *= 10; + ll += *aStr - '0'; + aStr++; + } + + return ll; +} diff --git a/xpcom/ds/nsCRT.h b/xpcom/ds/nsCRT.h new file mode 100644 index 0000000000..14b46c5059 --- /dev/null +++ b/xpcom/ds/nsCRT.h @@ -0,0 +1,119 @@ +/* -*- 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 nsCRT_h___ +#define nsCRT_h___ + +#include <stdlib.h> +#include <ctype.h> +#include "plstr.h" +#include "nscore.h" +#include "nsCRTGlue.h" + +#if defined(XP_WIN) +# define NS_LINEBREAK "\015\012" +# define NS_ULINEBREAK u"\015\012" +# define NS_LINEBREAK_LEN 2 +#else +# ifdef XP_UNIX +# define NS_LINEBREAK "\012" +# define NS_ULINEBREAK u"\012" +# define NS_LINEBREAK_LEN 1 +# endif /* XP_UNIX */ +#endif /* XP_WIN */ + +/// This is a wrapper class around all the C runtime functions. + +class nsCRT { + public: + enum { + LF = '\n' /* Line Feed */, + VTAB = '\v' /* Vertical Tab */, + CR = '\r' /* Carriage Return */ + }; + + /// String comparison. + static int32_t strcmp(const char* aStr1, const char* aStr2) { + return int32_t(PL_strcmp(aStr1, aStr2)); + } + + /// Case-insensitive string comparison. + static int32_t strcasecmp(const char* aStr1, const char* aStr2) { + /* Some functions like `PL_strcasecmp` are reimplementations + * of the their native POSIX counterparts, which breaks libFuzzer. + * For this purpose, we use the natives instead when fuzzing. + */ +#if defined(LIBFUZZER) && defined(LINUX) + return int32_t(::strcasecmp(aStr1, aStr2)); +#else + return int32_t(PL_strcasecmp(aStr1, aStr2)); +#endif + } + + /// Case-insensitive string comparison with length + static int32_t strncasecmp(const char* aStr1, const char* aStr2, + uint32_t aMaxLen) { +#if defined(LIBFUZZER) && defined(LINUX) + int32_t result = int32_t(::strncasecmp(aStr1, aStr2, aMaxLen)); +#else + int32_t result = int32_t(PL_strncasecmp(aStr1, aStr2, aMaxLen)); +#endif + // Egads. PL_strncasecmp is returning *very* negative numbers. + // Some folks expect -1,0,1, so let's temper its enthusiasm. + if (result < 0) { + result = -1; + } + return result; + } + + /// Case-insensitive substring search. + static char* strcasestr(const char* aStr1, const char* aStr2) { +#if defined(LIBFUZZER) && defined(LINUX) + return const_cast<char*>(::strcasestr(aStr1, aStr2)); +#else + return PL_strcasestr(aStr1, aStr2); +#endif + } + + /** + + How to use this fancy (thread-safe) version of strtok: + + void main(void) { + printf("%s\n\nTokens:\n", string); + // Establish string and get the first token: + char* newStr; + token = nsCRT::strtok(string, seps, &newStr); + while (token != nullptr) { + // While there are tokens in "string" + printf(" %s\n", token); + // Get next token: + token = nsCRT::strtok(newStr, seps, &newStr); + } + } + * WARNING - STRTOK WHACKS str THE FIRST TIME IT IS CALLED * + * MAKE A COPY OF str IF YOU NEED TO USE IT AFTER strtok() * + */ + static char* strtok(char* aStr, const char* aDelims, char** aNewStr); + + /// Like strcmp except for ucs2 strings + static int32_t strcmp(const char16_t* aStr1, const char16_t* aStr2); + + // String to longlong + static int64_t atoll(const char* aStr); + + static char ToUpper(char aChar) { return NS_ToUpper(aChar); } + static char ToLower(char aChar) { return NS_ToLower(aChar); } + + static bool IsAsciiSpace(char16_t aChar) { + return NS_IsAsciiWhitespace(aChar); + } +}; + +inline bool NS_IS_SPACE(char16_t aChar) { + return ((int(aChar) & 0x7f) == int(aChar)) && isspace(int(aChar)); +} + +#endif /* nsCRT_h___ */ diff --git a/xpcom/ds/nsCharSeparatedTokenizer.cpp b/xpcom/ds/nsCharSeparatedTokenizer.cpp new file mode 100644 index 0000000000..7288ed070f --- /dev/null +++ b/xpcom/ds/nsCharSeparatedTokenizer.cpp @@ -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/. */ + +#include "nsCharSeparatedTokenizer.h" + +template class nsTSubstringSplitter<char>; +template class nsTSubstringSplitter<char16_t>; diff --git a/xpcom/ds/nsCharSeparatedTokenizer.h b/xpcom/ds/nsCharSeparatedTokenizer.h new file mode 100644 index 0000000000..5cf6992e3e --- /dev/null +++ b/xpcom/ds/nsCharSeparatedTokenizer.h @@ -0,0 +1,274 @@ +/* -*- 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 __nsCharSeparatedTokenizer_h +#define __nsCharSeparatedTokenizer_h + +#include "mozilla/Maybe.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/TypedEnumBits.h" + +#include "nsCRTGlue.h" +#include "nsTDependentSubstring.h" + +// Flags -- only one for now. If we need more, they should be defined to +// be 1 << 1, 1 << 2, etc. (They're masks, and aFlags is a bitfield.) +enum class nsTokenizerFlags { + Default = 0, + SeparatorOptional = 1 << 0, + IncludeEmptyTokenAtEnd = 1 << 1 +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTokenizerFlags) + +/** + * This parses a SeparatorChar-separated string into tokens. + * Whitespace surrounding tokens is not treated as part of tokens, however + * whitespace inside a token is. If the final token is the empty string, it is + * not returned by default. + * + * Some examples, with SeparatorChar = ',': + * + * "foo, bar, baz" -> "foo" "bar" "baz" + * "foo,bar,baz" -> "foo" "bar" "baz" + * "foo , bar hi , baz" -> "foo" "bar hi" "baz" + * "foo, ,bar,baz" -> "foo" "" "bar" "baz" + * "foo,,bar,baz" -> "foo" "" "bar" "baz" + * "foo,bar,baz," -> "foo" "bar" "baz" + * + * The function used for whitespace detection is a template argument. + * By default, it is NS_IsAsciiWhitespace. + */ +template <typename TDependentSubstringType, bool IsWhitespace(char16_t), + nsTokenizerFlags Flags = nsTokenizerFlags::Default> +class nsTCharSeparatedTokenizer { + using CharType = typename TDependentSubstringType::char_type; + using SubstringType = typename TDependentSubstringType::substring_type; + + public: + using DependentSubstringType = TDependentSubstringType; + + nsTCharSeparatedTokenizer(const SubstringType& aSource, + CharType aSeparatorChar) + : mIter(aSource.Data(), aSource.Length()), + mEnd(aSource.Data() + aSource.Length(), aSource.Data(), + aSource.Length()), + mSeparatorChar(aSeparatorChar), + mWhitespaceBeforeFirstToken(false), + mWhitespaceAfterCurrentToken(false), + mSeparatorAfterCurrentToken(false) { + // Skip initial whitespace + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceBeforeFirstToken = true; + ++mIter; + } + } + + /** + * Checks if any more tokens are available. + */ + bool hasMoreTokens() const { + MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter), + "Should be at beginning of token if there is one"); + + if constexpr (Flags & nsTokenizerFlags::IncludeEmptyTokenAtEnd) { + return mIter < mEnd || (mIter == mEnd && mSeparatorAfterCurrentToken); + } else { + return mIter < mEnd; + } + } + + /* + * Returns true if there is whitespace prior to the first token. + */ + bool whitespaceBeforeFirstToken() const { + return mWhitespaceBeforeFirstToken; + } + + /* + * Returns true if there is a separator after the current token. + * Useful if you want to check whether the last token has a separator + * after it which may not be valid. + */ + bool separatorAfterCurrentToken() const { + return mSeparatorAfterCurrentToken; + } + + /* + * Returns true if there is any whitespace after the current token. + */ + bool whitespaceAfterCurrentToken() const { + return mWhitespaceAfterCurrentToken; + } + + /** + * Returns the next token. + */ + const DependentSubstringType nextToken() { + mozilla::RangedPtr<const CharType> tokenStart = mIter; + mozilla::RangedPtr<const CharType> tokenEnd = mIter; + + MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter), + "Should be at beginning of token if there is one"); + + // Search until we hit separator or end (or whitespace, if a separator + // isn't required -- see clause with 'break' below). + while (mIter < mEnd && *mIter != mSeparatorChar) { + // Skip to end of the current word. + while (mIter < mEnd && !IsWhitespace(*mIter) && + *mIter != mSeparatorChar) { + ++mIter; + } + tokenEnd = mIter; + + // Skip whitespace after the current word. + mWhitespaceAfterCurrentToken = false; + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + if constexpr (Flags & nsTokenizerFlags::SeparatorOptional) { + // We've hit (and skipped) whitespace, and that's sufficient to end + // our token, regardless of whether we've reached a SeparatorChar. + break; + } // (else, we'll keep looping until we hit mEnd or SeparatorChar) + } + + mSeparatorAfterCurrentToken = (mIter != mEnd && *mIter == mSeparatorChar); + MOZ_ASSERT((Flags & nsTokenizerFlags::SeparatorOptional) || + (mSeparatorAfterCurrentToken == (mIter < mEnd)), + "If we require a separator and haven't hit the end of " + "our string, then we shouldn't have left the loop " + "unless we hit a separator"); + + // Skip separator (and any whitespace after it), if we're at one. + if (mSeparatorAfterCurrentToken) { + ++mIter; + + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + } + + return Substring(tokenStart.get(), tokenEnd.get()); + } + + auto ToRange() const; + + private: + mozilla::RangedPtr<const CharType> mIter; + const mozilla::RangedPtr<const CharType> mEnd; + const CharType mSeparatorChar; + bool mWhitespaceBeforeFirstToken; + bool mWhitespaceAfterCurrentToken; + bool mSeparatorAfterCurrentToken; +}; + +constexpr bool NS_TokenizerIgnoreNothing(char16_t) { return false; } + +template <bool IsWhitespace(char16_t), typename CharType, + nsTokenizerFlags Flags = nsTokenizerFlags::Default> +using nsTCharSeparatedTokenizerTemplate = + nsTCharSeparatedTokenizer<nsTDependentSubstring<CharType>, IsWhitespace, + Flags>; + +template <bool IsWhitespace(char16_t), + nsTokenizerFlags Flags = nsTokenizerFlags::Default> +using nsCharSeparatedTokenizerTemplate = + nsTCharSeparatedTokenizerTemplate<IsWhitespace, char16_t, Flags>; + +using nsCharSeparatedTokenizer = + nsCharSeparatedTokenizerTemplate<NS_IsAsciiWhitespace>; + +template <bool IsWhitespace(char16_t), + nsTokenizerFlags Flags = nsTokenizerFlags::Default> +using nsCCharSeparatedTokenizerTemplate = + nsTCharSeparatedTokenizerTemplate<IsWhitespace, char, Flags>; + +using nsCCharSeparatedTokenizer = + nsCCharSeparatedTokenizerTemplate<NS_IsAsciiWhitespace>; + +/** + * Adapts a char separated tokenizer for use in a range-based for loop. + * + * Use this typically only indirectly, e.g. like + * + * for (const auto& token : nsCharSeparatedTokenizer(aText, ' ').ToRange()) { + * // ... + * } + */ +template <typename Tokenizer> +class nsTokenizedRange { + public: + using DependentSubstringType = typename Tokenizer::DependentSubstringType; + + explicit nsTokenizedRange(Tokenizer&& aTokenizer) + : mTokenizer(std::move(aTokenizer)) {} + + struct EndSentinel {}; + struct Iterator { + explicit Iterator(const Tokenizer& aTokenizer) : mTokenizer(aTokenizer) { + Next(); + } + + const DependentSubstringType& operator*() const { return *mCurrentToken; } + + Iterator& operator++() { + Next(); + return *this; + } + + bool operator==(const EndSentinel&) const { + return mCurrentToken.isNothing(); + } + + bool operator!=(const EndSentinel&) const { return mCurrentToken.isSome(); } + + private: + void Next() { + mCurrentToken.reset(); + + if (mTokenizer.hasMoreTokens()) { + mCurrentToken.emplace(mTokenizer.nextToken()); + } + } + + Tokenizer mTokenizer; + mozilla::Maybe<DependentSubstringType> mCurrentToken; + }; + + auto begin() const { return Iterator{mTokenizer}; } + auto end() const { return EndSentinel{}; } + + private: + const Tokenizer mTokenizer; +}; + +template <typename TDependentSubstringType, bool IsWhitespace(char16_t), + nsTokenizerFlags Flags> +auto nsTCharSeparatedTokenizer<TDependentSubstringType, IsWhitespace, + Flags>::ToRange() const { + return nsTokenizedRange{nsTCharSeparatedTokenizer{*this}}; +} + +// You should not need to instantiate this class directly. +// Use nsTSubstring::Split instead. +template <typename T> +class nsTSubstringSplitter + : public nsTokenizedRange<nsTCharSeparatedTokenizerTemplate< + NS_TokenizerIgnoreNothing, T, + nsTokenizerFlags::IncludeEmptyTokenAtEnd>> { + public: + using nsTokenizedRange<nsTCharSeparatedTokenizerTemplate< + NS_TokenizerIgnoreNothing, T, + nsTokenizerFlags::IncludeEmptyTokenAtEnd>>::nsTokenizedRange; +}; + +extern template class nsTSubstringSplitter<char>; +extern template class nsTSubstringSplitter<char16_t>; + +#endif /* __nsCharSeparatedTokenizer_h */ diff --git a/xpcom/ds/nsCheapSets.h b/xpcom/ds/nsCheapSets.h new file mode 100644 index 0000000000..3d09ccfdb7 --- /dev/null +++ b/xpcom/ds/nsCheapSets.h @@ -0,0 +1,155 @@ +/* -*- 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 __nsCheapSets_h__ +#define __nsCheapSets_h__ + +#include "nsTHashtable.h" +#include <stdint.h> + +enum nsCheapSetOperator { + OpNext = 0, // enumerator says continue + OpRemove = 1, // enumerator says remove and continue +}; + +/** + * A set that takes up minimal size when there are 0 or 1 entries in the set. + * Use for cases where sizes of 0 and 1 are even slightly common. + */ +template <typename EntryType> +class nsCheapSet { + public: + typedef typename EntryType::KeyType KeyType; + typedef nsCheapSetOperator (*Enumerator)(EntryType* aEntry, void* userArg); + + nsCheapSet() : mState(ZERO) { mUnion.table = nullptr; } + ~nsCheapSet() { Clear(); } + + /** + * Remove all entries. + */ + void Clear() { + switch (mState) { + case ZERO: + break; + case ONE: + GetSingleEntry()->~EntryType(); + break; + case MANY: + delete mUnion.table; + break; + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + break; + } + mState = ZERO; + } + + void Put(const KeyType aVal); + + void Remove(const KeyType aVal); + + bool Contains(const KeyType aVal) { + switch (mState) { + case ZERO: + return false; + case ONE: + return GetSingleEntry()->KeyEquals(EntryType::KeyToPointer(aVal)); + case MANY: + return !!mUnion.table->GetEntry(aVal); + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + return false; + } + } + + uint32_t EnumerateEntries(Enumerator aEnumFunc, void* aUserArg) { + switch (mState) { + case ZERO: + return 0; + case ONE: + if (aEnumFunc(GetSingleEntry(), aUserArg) == OpRemove) { + GetSingleEntry()->~EntryType(); + mState = ZERO; + } + return 1; + case MANY: { + uint32_t n = mUnion.table->Count(); + for (auto iter = mUnion.table->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<EntryType*>(iter.Get()); + if (aEnumFunc(entry, aUserArg) == OpRemove) { + iter.Remove(); + } + } + return n; + } + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + return 0; + } + } + + private: + EntryType* GetSingleEntry() { + return reinterpret_cast<EntryType*>(&mUnion.singleEntry[0]); + } + + enum SetState { ZERO, ONE, MANY }; + + union { + nsTHashtable<EntryType>* table; + char singleEntry[sizeof(EntryType)]; + } mUnion; + enum SetState mState; +}; + +template <typename EntryType> +void nsCheapSet<EntryType>::Put(const KeyType aVal) { + switch (mState) { + case ZERO: + new (GetSingleEntry()) EntryType(EntryType::KeyToPointer(aVal)); + mState = ONE; + return; + case ONE: { + nsTHashtable<EntryType>* table = new nsTHashtable<EntryType>(); + EntryType* entry = GetSingleEntry(); + table->PutEntry(entry->GetKey()); + entry->~EntryType(); + mUnion.table = table; + mState = MANY; + } + [[fallthrough]]; + + case MANY: + mUnion.table->PutEntry(aVal); + return; + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + return; + } +} + +template <typename EntryType> +void nsCheapSet<EntryType>::Remove(const KeyType aVal) { + switch (mState) { + case ZERO: + break; + case ONE: + if (Contains(aVal)) { + GetSingleEntry()->~EntryType(); + mState = ZERO; + } + break; + case MANY: + mUnion.table->RemoveEntry(aVal); + break; + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + break; + } +} + +#endif diff --git a/xpcom/ds/nsClassHashtable.h b/xpcom/ds/nsClassHashtable.h new file mode 100644 index 0000000000..a1a2384f7e --- /dev/null +++ b/xpcom/ds/nsClassHashtable.h @@ -0,0 +1,115 @@ +/* -*- 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 nsClassHashtable_h__ +#define nsClassHashtable_h__ + +#include <utility> + +#include "mozilla/UniquePtr.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" + +/** + * Helper class that provides methods to wrap and unwrap the UserDataType. + */ +template <class T> +class nsUniquePtrConverter { + public: + using UserDataType = T*; + using DataType = mozilla::UniquePtr<T>; + + static UserDataType Unwrap(DataType& src) { return src.get(); } + static DataType Wrap(UserDataType&& src) { return DataType(std::move(src)); } + static DataType Wrap(const UserDataType& src) { return DataType(src); } +}; + +/** + * templated hashtable class maps keys to C++ object pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param Class the class-type being wrapped + * @see nsInterfaceHashtable, nsClassHashtable + */ +template <class KeyClass, class T> +class nsClassHashtable : public nsBaseHashtable<KeyClass, mozilla::UniquePtr<T>, + T*, nsUniquePtrConverter<T>> { + public: + typedef typename KeyClass::KeyType KeyType; + typedef T* UserDataType; + typedef nsBaseHashtable<KeyClass, mozilla::UniquePtr<T>, T*, + nsUniquePtrConverter<T>> + base_type; + + using base_type::IsEmpty; + using base_type::Remove; + + nsClassHashtable() = default; + explicit nsClassHashtable(uint32_t aInitLength) : base_type(aInitLength) {} + + /** + * @copydoc nsBaseHashtable::Get + * @param aData if the key doesn't exist, pData will be set to nullptr. + */ + bool Get(KeyType aKey, UserDataType* aData) const; + + /** + * @copydoc nsBaseHashtable::Get + * @returns nullptr if the key is not present. + */ + [[nodiscard]] UserDataType Get(KeyType aKey) const; +}; + +template <typename K, typename T> +inline void ImplCycleCollectionUnlink(nsClassHashtable<K, T>& aField) { + aField.Clear(); +} + +template <typename K, typename T> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + const nsClassHashtable<K, T>& aField, const char* aName, + uint32_t aFlags = 0) { + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + ImplCycleCollectionTraverse(aCallback, *iter.UserData(), aName, aFlags); + } +} + +// +// nsClassHashtable definitions +// + +template <class KeyClass, class T> +bool nsClassHashtable<KeyClass, T>::Get(KeyType aKey, T** aRetVal) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRetVal) { + *aRetVal = ent->GetData().get(); + } + + return true; + } + + if (aRetVal) { + *aRetVal = nullptr; + } + + return false; +} + +template <class KeyClass, class T> +T* nsClassHashtable<KeyClass, T>::Get(KeyType aKey) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + + return ent->GetData().get(); +} + +#endif // nsClassHashtable_h__ diff --git a/xpcom/ds/nsDeque.cpp b/xpcom/ds/nsDeque.cpp new file mode 100644 index 0000000000..07f61fa471 --- /dev/null +++ b/xpcom/ds/nsDeque.cpp @@ -0,0 +1,265 @@ +/* -*- 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 "nsDeque.h" +#include "nsISupportsImpl.h" +#include <string.h> + +#include "mozilla/CheckedInt.h" + +#define modulus(x, y) ((x) % (y)) + +namespace mozilla { +namespace detail { + +/** + * Standard constructor + * @param deallocator, called by Erase and ~nsDequeBase + */ +nsDequeBase::nsDequeBase() { + MOZ_COUNT_CTOR(nsDequeBase); + mOrigin = mSize = 0; + mData = mBuffer; // don't allocate space until you must + mCapacity = sizeof(mBuffer) / sizeof(mBuffer[0]); + memset(mData, 0, mCapacity * sizeof(mBuffer[0])); +} + +/** + * Destructor + */ +nsDequeBase::~nsDequeBase() { + MOZ_COUNT_DTOR(nsDequeBase); + + if (mData && mData != mBuffer) { + free(mData); + } + mData = nullptr; +} + +size_t nsDequeBase::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t size = 0; + if (mData != mBuffer) { + size += aMallocSizeOf(mData); + } + + return size; +} + +/** + * Remove all items from container without destroying them. + */ +void nsDequeBase::Empty() { + if (mSize && mData) { + memset(mData, 0, mCapacity * sizeof(*mData)); + } + mSize = 0; + mOrigin = 0; +} + +/** + * This method quadruples the size of the deque + * Elements in the deque are resequenced so that elements + * in the deque are stored sequentially + * + * @return whether growing succeeded + */ +bool nsDequeBase::GrowCapacity() { + mozilla::CheckedInt<size_t> newCapacity = mCapacity; + newCapacity *= 4; + + NS_ASSERTION(newCapacity.isValid(), "Overflow"); + if (!newCapacity.isValid()) { + return false; + } + + // Sanity check the new byte size. + mozilla::CheckedInt<size_t> newByteSize = newCapacity; + newByteSize *= sizeof(void*); + + NS_ASSERTION(newByteSize.isValid(), "Overflow"); + if (!newByteSize.isValid()) { + return false; + } + + void** temp = (void**)malloc(newByteSize.value()); + if (!temp) { + return false; + } + + // Here's the interesting part: You can't just move the elements + // directly (in situ) from the old buffer to the new one. + // Since capacity has changed, the old origin doesn't make + // sense anymore. It's better to resequence the elements now. + + memcpy(temp, mData + mOrigin, sizeof(void*) * (mCapacity - mOrigin)); + memcpy(temp + (mCapacity - mOrigin), mData, sizeof(void*) * mOrigin); + + if (mData != mBuffer) { + free(mData); + } + + mCapacity = newCapacity.value(); + mOrigin = 0; // now realign the origin... + mData = temp; + + return true; +} + +/** + * This method adds an item to the end of the deque. + * This operation has the potential to cause the + * underlying buffer to resize. + * + * @param aItem: new item to be added to deque + */ +bool nsDequeBase::Push(void* aItem, const fallible_t&) { + if (mSize == mCapacity && !GrowCapacity()) { + return false; + } + mData[modulus(mOrigin + mSize, mCapacity)] = aItem; + mSize++; + return true; +} + +/** + * This method adds an item to the front of the deque. + * This operation has the potential to cause the + * underlying buffer to resize. + * + * --Commments for GrowCapacity() case + * We've grown and shifted which means that the old + * final element in the deque is now the first element + * in the deque. This is temporary. + * We haven't inserted the new element at the front. + * + * To continue with the idea of having the front at zero + * after a grow, we move the old final item (which through + * the voodoo of mOrigin-- is now the first) to its final + * position which is conveniently the old length. + * + * Note that this case only happens when the deque is full. + * [And that pieces of this magic only work if the deque is full.] + * picture: + * [ABCDEFGH] @[mOrigin:3]:D. + * Task: PushFront("Z") + * shift mOrigin so, @[mOrigin:2]:C + * stretch and rearrange: (mOrigin:0) + * [CDEFGHAB ________ ________ ________] + * copy: (The second C is currently out of bounds) + * [CDEFGHAB C_______ ________ ________] + * later we will insert Z: + * [ZDEFGHAB C_______ ________ ________] + * and increment size: 9. (C is no longer out of bounds) + * -- + * @param aItem: new item to be added to deque + */ +bool nsDequeBase::PushFront(void* aItem, const fallible_t&) { + if (mOrigin == 0) { + mOrigin = mCapacity - 1; + } else { + mOrigin--; + } + + if (mSize == mCapacity) { + if (!GrowCapacity()) { + return false; + } + /* Comments explaining this are above*/ + mData[mSize] = mData[mOrigin]; + } + mData[mOrigin] = aItem; + mSize++; + return true; +} + +/** + * Remove and return the last item in the container. + * + * @return ptr to last item in container + */ +void* nsDequeBase::Pop() { + void* result = nullptr; + if (mSize > 0) { + --mSize; + size_t offset = modulus(mSize + mOrigin, mCapacity); + result = mData[offset]; + mData[offset] = nullptr; + if (!mSize) { + mOrigin = 0; + } + } + return result; +} + +/** + * This method gets called you want to remove and return + * the first member in the container. + * + * @return last item in container + */ +void* nsDequeBase::PopFront() { + void* result = nullptr; + if (mSize > 0) { + NS_ASSERTION(mOrigin < mCapacity, "Error: Bad origin"); + result = mData[mOrigin]; + mData[mOrigin++] = nullptr; // zero it out for debugging purposes. + mSize--; + // Cycle around if we pop off the end + // and reset origin if when we pop the last element + if (mCapacity == mOrigin || !mSize) { + mOrigin = 0; + } + } + return result; +} + +/** + * This method gets called you want to peek at the bottom + * member without removing it. + * + * @return last item in container + */ +void* nsDequeBase::Peek() const { + void* result = nullptr; + if (mSize > 0) { + result = mData[modulus(mSize - 1 + mOrigin, mCapacity)]; + } + return result; +} + +/** + * This method gets called you want to peek at the topmost + * member without removing it. + * + * @return last item in container + */ +void* nsDequeBase::PeekFront() const { + void* result = nullptr; + if (mSize > 0) { + result = mData[mOrigin]; + } + return result; +} + +/** + * Call this to retrieve the ith element from this container. + * Keep in mind that accessing the underlying elements is + * done in a relative fashion. Object 0 is not necessarily + * the first element (the first element is at mOrigin). + * + * @param aIndex : 0 relative offset of item you want + * @return void* or null + */ +void* nsDequeBase::ObjectAt(size_t aIndex) const { + void* result = nullptr; + if (aIndex < mSize) { + result = mData[modulus(mOrigin + aIndex, mCapacity)]; + } + return result; +} +} // namespace detail +} // namespace mozilla diff --git a/xpcom/ds/nsDeque.h b/xpcom/ds/nsDeque.h new file mode 100644 index 0000000000..4f0bf27037 --- /dev/null +++ b/xpcom/ds/nsDeque.h @@ -0,0 +1,538 @@ +/* -*- 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/. */ + +/** + * MODULE NOTES: + * + * The Deque is a very small, very efficient container object + * than can hold items of type T*, offering the following features: + * - Its interface supports pushing, popping, and peeking of items at the back + * or front, and retrieval from any position. + * - It can iterate over items via a ForEach method, range-for, or an iterator + * class. + * - When full, it can efficiently resize dynamically. + * + * NOTE: The only bit of trickery here is that this deque is + * built upon a ring-buffer. Like all ring buffers, the first + * item may not be at index[0]. The mOrigin member determines + * where the first child is. This point is quietly hidden from + * customers of this class. + */ + +#ifndef _NSDEQUE +#define _NSDEQUE +#include <cstddef> + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsISupports.h" + +namespace mozilla { + +namespace detail { +/** + * The deque (double-ended queue) class is a common container type, + * whose behavior mimics a line in your favorite checkout stand. + * Classic CS describes the common behavior of a queue as FIFO. + * A deque allows insertion and removal at both ends of + * the container. + * + * nsDequeBase implements the untyped deque data structure while + * nsDeque provides the typed implementation and iterators. + */ +class nsDequeBase { + public: + /** + * Returns the number of items currently stored in + * this deque. + * + * @return number of items currently in the deque + */ + inline size_t GetSize() const { return mSize; } + + protected: + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + /** + * Constructs an empty deque. + * + * @param aDeallocator Optional deallocator functor that will be called from + * Erase() and the destructor on any remaining item. + * The deallocator is owned by the deque and will be + * deleted at destruction time. + */ + explicit nsDequeBase(); + + /** + * Deque destructor. Erases all items, deletes the deallocator. + */ + ~nsDequeBase(); + + /** + * Appends new member at the end of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] bool Push(void* aItem, const fallible_t&); + + /** + * Inserts new member at the front of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] bool PushFront(void* aItem, const fallible_t&); + + /** + * Remove and return the last item in the container. + * + * @return the item that was the last item in container + */ + void* Pop(); + + /** + * Remove and return the first item in the container. + * + * @return the item that was first item in container + */ + void* PopFront(); + + /** + * Retrieve the last item without removing it. + * + * @return the last item in container + */ + void* Peek() const; + + /** + * Retrieve the first item without removing it. + * + * @return the first item in container + */ + void* PeekFront() const; + + /** + * Retrieve a member from the deque without removing it. + * + * @param index of desired item + * @return item in list, or nullptr if index is outside the deque + */ + void* ObjectAt(size_t aIndex) const; + + bool GrowCapacity(); + + /** + * Remove all items from container without destroying them. + */ + void Empty(); + + size_t mSize; + size_t mCapacity; + size_t mOrigin; + void* mBuffer[8]; + void** mData; + + nsDequeBase& operator=(const nsDequeBase& aOther) = delete; + nsDequeBase(const nsDequeBase& aOther) = delete; +}; + +// This iterator assumes that the deque itself is const, i.e., it cannot be +// modified while the iterator is used. +// Also it is a 'const' iterator in that it provides copies of the deque's +// elements, and therefore it is not possible to modify the deque's contents +// by assigning to a dereference of this iterator. +template <typename Deque> +class ConstDequeIterator { + public: + ConstDequeIterator(const Deque& aDeque, size_t aIndex) + : mDeque(aDeque), mIndex(aIndex) {} + ConstDequeIterator& operator++() { + ++mIndex; + return *this; + } + bool operator==(const ConstDequeIterator& aOther) const { + return mIndex == aOther.mIndex; + } + bool operator!=(const ConstDequeIterator& aOther) const { + return mIndex != aOther.mIndex; + } + typename Deque::PointerType operator*() const { + // Don't allow out-of-deque dereferences. + MOZ_RELEASE_ASSERT(mIndex < mDeque.GetSize()); + return mDeque.ObjectAt(mIndex); + } + + private: + const Deque& mDeque; + size_t mIndex; +}; + +// It is a 'const' iterator in that it provides copies of the deque's +// elements, and therefore it is not possible to modify the deque's contents +// by assigning to a dereference of this iterator. +// If the deque is modified in other ways, this iterator will stay at the same +// index, and will handle past-the-end comparisons, but not dereferencing. +template <typename Deque> +class ConstIterator { + public: + // Special index for the end iterator, to track the possibly-shifting + // deque size. + static const size_t EndIteratorIndex = size_t(-1); + + ConstIterator(const Deque& aDeque, size_t aIndex) + : mDeque(aDeque), mIndex(aIndex) {} + ConstIterator& operator++() { + // End-iterator shouldn't be modified. + MOZ_ASSERT(mIndex != EndIteratorIndex); + ++mIndex; + return *this; + } + bool operator==(const ConstIterator& aOther) const { + return EffectiveIndex() == aOther.EffectiveIndex(); + } + bool operator!=(const ConstIterator& aOther) const { + return EffectiveIndex() != aOther.EffectiveIndex(); + } + typename Deque::PointerType operator*() const { + // Don't allow out-of-deque dereferences. + MOZ_RELEASE_ASSERT(mIndex < mDeque.GetSize()); + return mDeque.ObjectAt(mIndex); + } + + private: + // 0 <= index < deque.GetSize() inside the deque, deque.GetSize() otherwise. + // Only used when comparing indices, not to actually access items. + size_t EffectiveIndex() const { + return (mIndex < mDeque.GetSize()) ? mIndex : mDeque.GetSize(); + } + + const Deque& mDeque; + size_t mIndex; // May point outside the deque! +}; + +} // namespace detail +} // namespace mozilla + +/** + * The nsDequeFunctor class is used when you want to create + * callbacks between the deque and your generic code. + * Use these objects in a call to ForEach(), and as custom deallocators. + */ +template <typename T> +class nsDequeFunctor { + public: + virtual void operator()(T* aObject) = 0; + virtual ~nsDequeFunctor() = default; +}; + +/****************************************************** + * Here comes the nsDeque class itself... + ******************************************************/ + +/** + * The deque (double-ended queue) class is a common container type, + * whose behavior mimics a line in your favorite checkout stand. + * Classic CS describes the common behavior of a queue as FIFO. + * A deque allows insertion and removal at both ends of + * the container. + * + * The deque stores pointers to items. + */ +template <typename T> +class nsDeque : public mozilla::detail::nsDequeBase { + typedef mozilla::fallible_t fallible_t; + + public: + using PointerType = T*; + using ConstDequeIterator = mozilla::detail::ConstDequeIterator<nsDeque<T>>; + using ConstIterator = mozilla::detail::ConstIterator<nsDeque<T>>; + + /** + * Constructs an empty deque. + * + * @param aDeallocator Optional deallocator functor that will be called from + * Erase() and the destructor on any remaining item. + * The deallocator is owned by the deque and will be + * deleted at destruction time. + */ + explicit nsDeque(nsDequeFunctor<T>* aDeallocator = nullptr) { + MOZ_COUNT_CTOR(nsDeque); + mDeallocator = aDeallocator; + } + + /** + * Deque destructor. Erases all items, deletes the deallocator. + */ + ~nsDeque() { + MOZ_COUNT_DTOR(nsDeque); + + Erase(); + SetDeallocator(nullptr); + } + + /** + * Appends new member at the end of the deque. + * + * @param aItem item to store in deque + */ + inline void Push(T* aItem) { + if (!nsDequeBase::Push(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(T*)); + } + } + + /** + * Appends new member at the end of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] inline bool Push(T* aItem, const fallible_t& aFaillible) { + return nsDequeBase::Push(aItem, aFaillible); + } + + /** + * Inserts new member at the front of the deque. + * + * @param aItem item to store in deque + */ + inline void PushFront(T* aItem) { + if (!nsDequeBase::PushFront(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(T*)); + } + } + + /** + * Inserts new member at the front of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] bool PushFront(T* aItem, const fallible_t& aFallible) { + return nsDequeBase::PushFront(aItem, aFallible); + } + + /** + * Remove and return the last item in the container. + * + * @return the item that was the last item in container + */ + inline T* Pop() { return static_cast<T*>(nsDequeBase::Pop()); } + + /** + * Remove and return the first item in the container. + * + * @return the item that was first item in container + */ + inline T* PopFront() { return static_cast<T*>(nsDequeBase::PopFront()); } + + /** + * Retrieve the last item without removing it. + * + * @return the last item in container + */ + inline T* Peek() const { return static_cast<T*>(nsDequeBase::Peek()); } + + /** + * Retrieve the first item without removing it. + * + * @return the first item in container + */ + inline T* PeekFront() const { + return static_cast<T*>(nsDequeBase::PeekFront()); + } + + /** + * Retrieve a member from the deque without removing it. + * + * @param index of desired item + * @return item in list, or nullptr if index is outside the deque + */ + inline T* ObjectAt(size_t aIndex) const { + if (NS_WARN_IF(aIndex >= GetSize())) { + return nullptr; + } + return static_cast<T*>(nsDequeBase::ObjectAt(aIndex)); + } + + /** + * Remove and delete all items from container. + * Deletes are handled by the deallocator nsDequeFunctor + * which is specified at deque construction. + */ + void Erase() { + if (mDeallocator && mSize) { + ForEach(*mDeallocator); + } + Empty(); + } + + /** + * Call this method when you want to iterate through all + * items in the container, passing a functor along + * to call your code. + * If the deque is modified during ForEach, iteration will continue based on + * item indices; meaning that front operations may effectively skip over + * items or visit some items multiple times. + * + * @param aFunctor object to call for each member + */ + void ForEach(nsDequeFunctor<T>& aFunctor) const { + for (size_t i = 0; i < mSize; ++i) { + aFunctor(ObjectAt(i)); + } + } + + // If this deque is const, we can provide ConstDequeIterator's. + ConstDequeIterator begin() const { return ConstDequeIterator(*this, 0); } + ConstDequeIterator end() const { return ConstDequeIterator(*this, mSize); } + + // If this deque is *not* const, we provide ConstIterator's that can handle + // deque size changes. + ConstIterator begin() { return ConstIterator(*this, 0); } + ConstIterator end() { + return ConstIterator(*this, ConstIterator::EndIteratorIndex); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t size = nsDequeBase::SizeOfExcludingThis(aMallocSizeOf); + if (mDeallocator) { + size += aMallocSizeOf(mDeallocator); + } + return size; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + protected: + nsDequeFunctor<T>* mDeallocator; + + private: + /** + * Copy constructor (deleted) + * + * @param aOther another deque + */ + nsDeque(const nsDeque& aOther) = delete; + + /** + * Deque assignment operator (deleted) + * + * @param aOther another deque + * @return *this + */ + nsDeque& operator=(const nsDeque& aOther) = delete; + + void SetDeallocator(nsDequeFunctor<T>* aDeallocator) { + delete mDeallocator; + mDeallocator = aDeallocator; + } +}; + +/** + * Specialization of nsDeque for reference counted pointers. + * + * Provides builtin reference handling as part of Push/Peek/Pop + */ +template <typename T> +class nsRefPtrDeque : private nsDeque<T> { + typedef mozilla::fallible_t fallible_t; + + class RefPtrDeallocator : public nsDequeFunctor<T> { + public: + virtual void operator()(T* aObject) override { + RefPtr<T> releaseMe = dont_AddRef(aObject); + } + }; + + public: + using PointerType = RefPtr<T>; + using ConstDequeIterator = + mozilla::detail::ConstDequeIterator<nsRefPtrDeque<T>>; + using ConstIterator = mozilla::detail::ConstIterator<nsRefPtrDeque<T>>; + + explicit nsRefPtrDeque() : nsDeque<T>(new RefPtrDeallocator()) {} + + inline void PushFront(already_AddRefed<T> aItem) { + T* item = aItem.take(); + nsDeque<T>::PushFront(item); + } + + inline void PushFront(T* aItem) { PushFront(do_AddRef(aItem)); } + + inline void Push(T* aItem) { Push(do_AddRef(aItem)); } + + inline void Push(already_AddRefed<T> aItem) { + T* item = aItem.take(); + nsDeque<T>::Push(item); + } + + inline already_AddRefed<T> PopFront() { + return dont_AddRef(nsDeque<T>::PopFront()); + } + + inline already_AddRefed<T> Pop() { return dont_AddRef(nsDeque<T>::Pop()); } + + inline T* PeekFront() const { return nsDeque<T>::PeekFront(); } + + inline T* Peek() const { return nsDeque<T>::Peek(); } + + inline T* ObjectAt(size_t aIndex) const { + return nsDeque<T>::ObjectAt(aIndex); + } + + inline void Erase() { nsDeque<T>::Erase(); } + + // If this deque is const, we can provide ConstDequeIterator's. + ConstDequeIterator begin() const { return ConstDequeIterator(*this, 0); } + ConstDequeIterator end() const { + return ConstDequeIterator(*this, GetSize()); + } + + // If this deque is *not* const, we provide ConstIterator's that can handle + // deque size changes. + ConstIterator begin() { return ConstIterator(*this, 0); } + ConstIterator end() { + return ConstIterator(*this, ConstIterator::EndIteratorIndex); + } + + inline size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return nsDeque<T>::SizeOfExcludingThis(aMallocSizeOf); + } + + inline size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return nsDeque<T>::SizeOfIncludingThis(aMallocSizeOf); + } + + inline size_t GetSize() const { return nsDeque<T>::GetSize(); } + + /** + * Call this method when you want to iterate through all + * items in the container, passing a functor along + * to call your code. + * If the deque is modified during ForEach, iteration will continue based on + * item indices; meaning that front operations may effectively skip over + * items or visit some items multiple times. + * + * @param aFunctor object to call for each member + */ + void ForEach(nsDequeFunctor<T>& aFunctor) const { + size_t size = GetSize(); + for (size_t i = 0; i < size; ++i) { + aFunctor(ObjectAt(i)); + } + } +}; + +#endif diff --git a/xpcom/ds/nsEnumeratorUtils.cpp b/xpcom/ds/nsEnumeratorUtils.cpp new file mode 100644 index 0000000000..69631818a3 --- /dev/null +++ b/xpcom/ds/nsEnumeratorUtils.cpp @@ -0,0 +1,248 @@ +/* -*- 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 "nsEnumeratorUtils.h" + +#include "nsIStringEnumerator.h" +#include "nsSimpleEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +class EmptyEnumeratorImpl : public nsSimpleEnumerator, + public nsIUTF8StringEnumerator, + public nsIStringEnumerator { + public: + EmptyEnumeratorImpl() = default; + + // nsISupports interface. Not really inherited, but no mRefCnt. + NS_DECL_ISUPPORTS_INHERITED + + // nsISimpleEnumerator + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + NS_DECL_NSISTRINGENUMERATORBASE + // can't use NS_DECL_NSISTRINGENUMERATOR because they share the + // HasMore() signature + + NS_IMETHOD GetNext(nsAString& aResult) override; + + static EmptyEnumeratorImpl* GetInstance() { + static const EmptyEnumeratorImpl kInstance; + return const_cast<EmptyEnumeratorImpl*>(&kInstance); + } +}; + +// nsISupports interface +NS_IMETHODIMP_(MozExternalRefCountType) +EmptyEnumeratorImpl::AddRef(void) { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +EmptyEnumeratorImpl::Release(void) { return 1; } + +NS_IMPL_QUERY_INTERFACE_INHERITED(EmptyEnumeratorImpl, nsSimpleEnumerator, + nsIUTF8StringEnumerator, nsIStringEnumerator) + +// nsISimpleEnumerator interface +NS_IMETHODIMP +EmptyEnumeratorImpl::HasMoreElements(bool* aResult) { + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::HasMore(bool* aResult) { + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsISupports** aResult) { return NS_ERROR_FAILURE; } + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsACString& aResult) { + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsAString& aResult) { return NS_ERROR_UNEXPECTED; } + +NS_IMETHODIMP +EmptyEnumeratorImpl::StringIterator(nsIJSEnumerator** aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult) { + *aResult = EmptyEnumeratorImpl::GetInstance(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsSingletonEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + explicit nsSingletonEnumerator(nsISupports* aValue); + + private: + ~nsSingletonEnumerator() override; + + protected: + nsCOMPtr<nsISupports> mValue; + bool mConsumed; +}; + +nsSingletonEnumerator::nsSingletonEnumerator(nsISupports* aValue) + : mValue(aValue) { + mConsumed = (mValue ? false : true); +} + +nsSingletonEnumerator::~nsSingletonEnumerator() = default; + +NS_IMETHODIMP +nsSingletonEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = !mConsumed; + return NS_OK; +} + +NS_IMETHODIMP +nsSingletonEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_FAILURE; + } + + mConsumed = true; + + *aResult = mValue; + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult, + nsISupports* aSingleton) { + RefPtr<nsSingletonEnumerator> enumer = new nsSingletonEnumerator(aSingleton); + enumer.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsUnionEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator); + + private: + ~nsUnionEnumerator() override; + + protected: + nsCOMPtr<nsISimpleEnumerator> mFirstEnumerator, mSecondEnumerator; + bool mConsumed; + bool mAtSecond; +}; + +nsUnionEnumerator::nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator) + : mFirstEnumerator(aFirstEnumerator), + mSecondEnumerator(aSecondEnumerator), + mConsumed(false), + mAtSecond(false) {} + +nsUnionEnumerator::~nsUnionEnumerator() = default; + +NS_IMETHODIMP +nsUnionEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsresult rv; + + if (mConsumed) { + *aResult = false; + return NS_OK; + } + + if (!mAtSecond) { + rv = mFirstEnumerator->HasMoreElements(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (*aResult) { + return NS_OK; + } + + mAtSecond = true; + } + + rv = mSecondEnumerator->HasMoreElements(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (*aResult) { + return NS_OK; + } + + *aResult = false; + mConsumed = true; + return NS_OK; +} + +NS_IMETHODIMP +nsUnionEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_FAILURE; + } + + if (!mAtSecond) { + return mFirstEnumerator->GetNext(aResult); + } + + return mSecondEnumerator->GetNext(aResult); +} + +nsresult NS_NewUnionEnumerator(nsISimpleEnumerator** aResult, + nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator) { + *aResult = nullptr; + if (!aFirstEnumerator) { + *aResult = aSecondEnumerator; + } else if (!aSecondEnumerator) { + *aResult = aFirstEnumerator; + } else { + auto* enumer = new nsUnionEnumerator(aFirstEnumerator, aSecondEnumerator); + *aResult = enumer; + } + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/ds/nsEnumeratorUtils.h b/xpcom/ds/nsEnumeratorUtils.h new file mode 100644 index 0000000000..f7a0db099e --- /dev/null +++ b/xpcom/ds/nsEnumeratorUtils.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/. */ + +#ifndef nsEnumeratorUtils_h__ +#define nsEnumeratorUtils_h__ + +#include "nscore.h" + +class nsISupports; +class nsISimpleEnumerator; + +nsresult NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult); + +nsresult NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult, + nsISupports* aSingleton); + +nsresult NS_NewUnionEnumerator(nsISimpleEnumerator** aResult, + nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator); + +#endif /* nsEnumeratorUtils_h__ */ diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h new file mode 100644 index 0000000000..cc35017b2a --- /dev/null +++ b/xpcom/ds/nsExpirationTracker.h @@ -0,0 +1,609 @@ +/* -*- 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 NSEXPIRATIONTRACKER_H_ +#define NSEXPIRATIONTRACKER_H_ + +#include <cstring> +#include "MainThreadUtils.h" +#include "nsAlgorithm.h" +#include "nsDebug.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISupports.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nscore.h" +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefCountType.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" + +/** + * Data used to track the expiration state of an object. We promise that this + * is 32 bits so that objects that includes this as a field can pad and align + * efficiently. + */ +struct nsExpirationState { + enum { + NOT_TRACKED = (1U << 4) - 1, + MAX_INDEX_IN_GENERATION = (1U << 28) - 1 + }; + + nsExpirationState() : mGeneration(NOT_TRACKED), mIndexInGeneration(0) {} + bool IsTracked() { return mGeneration != NOT_TRACKED; } + + /** + * The generation that this object belongs to, or NOT_TRACKED. + */ + uint32_t mGeneration : 4; + uint32_t mIndexInGeneration : 28; +}; + +/** + * ExpirationTracker classes: + * - ExpirationTrackerImpl (Thread-safe class) + * - nsExpirationTracker (Main-thread only class) + * + * These classes can track the lifetimes and usage of a large number of + * objects, and send a notification some window of time after a live object was + * last used. This is very useful when you manage a large number of objects + * and want to flush some after they haven't been used for a while. + * nsExpirationTracker is designed to be very space and time efficient. + * + * The type parameter T is the object type that we will track pointers to. T + * must include an accessible method GetExpirationState() that returns a + * pointer to an nsExpirationState associated with the object (preferably, + * stored in a field of the object). + * + * The parameter K is the number of generations that will be used. Increasing + * the number of generations narrows the window within which we promise + * to fire notifications, at a slight increase in space cost for the tracker. + * We require 2 <= K <= nsExpirationState::NOT_TRACKED (currently 15). + * + * To use this class, you need to inherit from it and override the + * NotifyExpired() method. + * + * The approach is to track objects in K generations. When an object is accessed + * it moves from its current generation to the newest generation. Generations + * are stored in a cyclic array; when a timer interrupt fires, we advance + * the current generation pointer to effectively age all objects very + * efficiently. By storing information in each object about its generation and + * index within its generation array, we make removal of objects from a + * generation very cheap. + * + * Future work: + * -- Add a method to change the timer period? + */ + +/** + * Base class for ExiprationTracker implementations. + * + * nsExpirationTracker class below is a specialized class to be inherited by the + * instances to be accessed only on main-thread. + * + * For creating a thread-safe tracker, you can define a subclass inheriting this + * base class and specialize the Mutex and AutoLock to be used. + * + * For an example of using ExpirationTrackerImpl with a DataMutex + * @see mozilla::gfx::GradientCache. + * + */ +template <typename T, uint32_t K, typename Mutex, typename AutoLock> +class ExpirationTrackerImpl { + public: + /** + * Initialize the tracker. + * @param aTimerPeriod the timer period in milliseconds. The guarantees + * provided by the tracker are defined in terms of this period. If the + * period is zero, then we don't use a timer and rely on someone calling + * AgeOneGenerationLocked explicitly. + * @param aName the name of the subclass for telemetry. + * @param aEventTarget the optional event target on main thread to label the + * runnable of the asynchronous invocation to NotifyExpired(). + + */ + ExpirationTrackerImpl(uint32_t aTimerPeriod, const char* aName, + nsIEventTarget* aEventTarget = nullptr) + : mTimerPeriod(aTimerPeriod), + mNewestGeneration(0), + mInAgeOneGeneration(false), + mName(aName), + mEventTarget(aEventTarget) { + static_assert(K >= 2 && K <= nsExpirationState::NOT_TRACKED, + "Unsupported number of generations (must be 2 <= K <= 15)"); + // If we are not initialized on the main thread, the owner is responsible + // for dealing with memory pressure events. + if (NS_IsMainThread()) { + mObserver = new ExpirationTrackerObserver(); + mObserver->Init(this); + } + } + + virtual ~ExpirationTrackerImpl() { + if (mTimer) { + mTimer->Cancel(); + } + if (mObserver) { + MOZ_ASSERT(NS_IsMainThread()); + mObserver->Destroy(); + } + } + + /** + * Add an object to be tracked. It must not already be tracked. It will + * be added to the newest generation, i.e., as if it was just used. + * @return an error on out-of-memory + */ + nsresult AddObjectLocked(T* aObj, const AutoLock& aAutoLock) { + if (NS_WARN_IF(!aObj)) { + MOZ_DIAGNOSTIC_ASSERT(false, "Invalid object to add"); + return NS_ERROR_UNEXPECTED; + } + nsExpirationState* state = aObj->GetExpirationState(); + if (NS_WARN_IF(state->IsTracked())) { + MOZ_DIAGNOSTIC_ASSERT(false, + "Tried to add an object that's already tracked"); + return NS_ERROR_UNEXPECTED; + } + nsTArray<T*>& generation = mGenerations[mNewestGeneration]; + uint32_t index = generation.Length(); + if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) { + NS_WARNING("More than 256M elements tracked, this is probably a problem"); + return NS_ERROR_OUT_OF_MEMORY; + } + if (index == 0) { + // We might need to start the timer + nsresult rv = CheckStartTimerLocked(aAutoLock); + if (NS_FAILED(rv)) { + return rv; + } + } + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + generation.AppendElement(aObj); + state->mGeneration = mNewestGeneration; + state->mIndexInGeneration = index; + return NS_OK; + } + + /** + * Remove an object from the tracker. It must currently be tracked. + */ + void RemoveObjectLocked(T* aObj, const AutoLock& aAutoLock) { + if (NS_WARN_IF(!aObj)) { + MOZ_DIAGNOSTIC_ASSERT(false, "Invalid object to remove"); + return; + } + nsExpirationState* state = aObj->GetExpirationState(); + if (NS_WARN_IF(!state->IsTracked())) { + MOZ_DIAGNOSTIC_ASSERT(false, + "Tried to remove an object that's not tracked"); + return; + } + nsTArray<T*>& generation = mGenerations[state->mGeneration]; + uint32_t index = state->mIndexInGeneration; + MOZ_ASSERT(generation.Length() > index && generation[index] == aObj, + "Object is lying about its index"); + // Move the last object to fill the hole created by removing aObj + T* lastObj = generation.PopLastElement(); + // XXX It looks weird that index might point to the element that was just + // removed. Is that really correct? + if (index < generation.Length()) { + generation[index] = lastObj; + } + lastObj->GetExpirationState()->mIndexInGeneration = index; + state->mGeneration = nsExpirationState::NOT_TRACKED; + // We do not check whether we need to stop the timer here. The timer + // will check that itself next time it fires. Checking here would not + // be efficient since we'd need to track all generations. Also we could + // thrash by incessantly creating and destroying timers if someone + // kept adding and removing an object from the tracker. + } + + /** + * Notify that an object has been used. + * @return an error if we lost the object from the tracker... + */ + nsresult MarkUsedLocked(T* aObj, const AutoLock& aAutoLock) { + nsExpirationState* state = aObj->GetExpirationState(); + if (mNewestGeneration == state->mGeneration) { + return NS_OK; + } + RemoveObjectLocked(aObj, aAutoLock); + return AddObjectLocked(aObj, aAutoLock); + } + + /** + * The timer calls this, but it can also be manually called if you want + * to age objects "artifically". This can result in calls to + * NotifyExpiredLocked. + */ + void AgeOneGenerationLocked(const AutoLock& aAutoLock) { + if (mInAgeOneGeneration) { + NS_WARNING("Can't reenter AgeOneGeneration from NotifyExpired"); + return; + } + + mInAgeOneGeneration = true; + uint32_t reapGeneration = + mNewestGeneration > 0 ? mNewestGeneration - 1 : K - 1; + nsTArray<T*>& generation = mGenerations[reapGeneration]; + // The following is rather tricky. We have to cope with objects being + // removed from this generation either because of a call to RemoveObject + // (or indirectly via MarkUsedLocked) inside NotifyExpiredLocked. + // Fortunately no objects can be added to this generation because it's not + // the newest generation. We depend on the fact that RemoveObject can only + // cause the indexes of objects in this generation to *decrease*, not + // increase. So if we start from the end and work our way backwards we are + // guaranteed to see each object at least once. + size_t index = generation.Length(); + for (;;) { + // Objects could have been removed so index could be outside + // the array + index = XPCOM_MIN(index, generation.Length()); + if (index == 0) { + break; + } + --index; + NotifyExpiredLocked(generation[index], aAutoLock); + } + // Any leftover objects from reapGeneration just end up in the new + // newest-generation. This is bad form, though, so warn if there are any. + if (!generation.IsEmpty()) { + NS_WARNING("Expired objects were not removed or marked used"); + } + // Free excess memory used by the generation array, since we probably + // just removed most or all of its elements. + generation.Compact(); + mNewestGeneration = reapGeneration; + mInAgeOneGeneration = false; + } + + /** + * This just calls AgeOneGenerationLocked K times. Under normal circumstances + * this will result in all objects getting NotifyExpiredLocked called on them, + * but if NotifyExpiredLocked itself marks some objects as used, then those + * objects might not expire. This would be a good thing to call if we get into + * a critically-low memory situation. + */ + void AgeAllGenerationsLocked(const AutoLock& aAutoLock) { + uint32_t i; + for (i = 0; i < K; ++i) { + AgeOneGenerationLocked(aAutoLock); + } + } + + class Iterator { + private: + ExpirationTrackerImpl<T, K, Mutex, AutoLock>* mTracker; + uint32_t mGeneration; + uint32_t mIndex; + + public: + Iterator(ExpirationTrackerImpl<T, K, Mutex, AutoLock>* aTracker, + AutoLock& aAutoLock) + : mTracker(aTracker), mGeneration(0), mIndex(0) {} + + T* Next() { + while (mGeneration < K) { + nsTArray<T*>* generation = &mTracker->mGenerations[mGeneration]; + if (mIndex < generation->Length()) { + ++mIndex; + return (*generation)[mIndex - 1]; + } + ++mGeneration; + mIndex = 0; + } + return nullptr; + } + }; + + friend class Iterator; + + bool IsEmptyLocked(const AutoLock& aAutoLock) const { + for (uint32_t i = 0; i < K; ++i) { + if (!mGenerations[i].IsEmpty()) { + return false; + } + } + return true; + } + + size_t Length(const AutoLock& aAutoLock) const { + size_t len = 0; + for (uint32_t i = 0; i < K; ++i) { + len += mGenerations[i].Length(); + } + return len; + } + + // @return The amount of memory used by this ExpirationTrackerImpl, excluding + // sizeof(*this). If you want to measure anything hanging off the mGenerations + // array, you must iterate over the elements and measure them individually; + // hence the "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t bytes = 0; + for (uint32_t i = 0; i < K; ++i) { + bytes += mGenerations[i].ShallowSizeOfExcludingThis(aMallocSizeOf); + } + return bytes; + } + + protected: + /** + * This must be overridden to catch notifications. It is called whenever + * we detect that an object has not been used for at least (K-1)*mTimerPeriod + * milliseconds. If timer events are not delayed, it will be called within + * roughly K*mTimerPeriod milliseconds after the last use. + * (Unless AgeOneGenerationLocked or AgeAllGenerationsLocked have been called + * to accelerate the aging process.) + * + * NOTE: These bounds ignore delays in timer firings due to actual work being + * performed by the browser. We use a slack timer so there is always at least + * mTimerPeriod milliseconds between firings, which gives us + * (K-1)*mTimerPeriod as a pretty solid lower bound. The upper bound is rather + * loose, however. If the maximum amount by which any given timer firing is + * delayed is D, then the upper bound before NotifyExpiredLocked is called is + * K*(mTimerPeriod + D). + * + * The NotifyExpiredLocked call is expected to remove the object from the + * tracker, but it need not. The object (or other objects) could be + * "resurrected" by calling MarkUsedLocked() on them, or they might just not + * be removed. Any objects left over that have not been resurrected or removed + * are placed in the new newest-generation, but this is considered "bad form" + * and should be avoided (we'll issue a warning). (This recycling counts + * as "a use" for the purposes of the expiry guarantee above...) + * + * For robustness and simplicity, we allow objects to be notified more than + * once here in the same timer tick. + */ + virtual void NotifyExpiredLocked(T*, const AutoLock&) = 0; + + /** + * This may be overridden to perform any post-aging work that needs to be + * done while still holding the lock. It will be called once after each timer + * event, and each low memory event has been handled. + */ + virtual void NotifyHandlerEndLocked(const AutoLock&){}; + + /** + * This may be overridden to perform any post-aging work that needs to be + * done outside the lock. It will be called once after each + * NotifyEndTransactionLocked call. + */ + virtual void NotifyHandlerEnd(){}; + + virtual Mutex& GetMutex() = 0; + + private: + class ExpirationTrackerObserver; + RefPtr<ExpirationTrackerObserver> mObserver; + nsTArray<T*> mGenerations[K]; + nsCOMPtr<nsITimer> mTimer; + uint32_t mTimerPeriod; + uint32_t mNewestGeneration; + bool mInAgeOneGeneration; + const char* const mName; // Used for timer firing profiling. + const nsCOMPtr<nsIEventTarget> mEventTarget; + + /** + * Whenever "memory-pressure" is observed, it calls AgeAllGenerationsLocked() + * to minimize memory usage. + */ + class ExpirationTrackerObserver final : public nsIObserver { + public: + void Init(ExpirationTrackerImpl<T, K, Mutex, AutoLock>* aObj) { + mOwner = aObj; + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "memory-pressure", false); + } + } + void Destroy() { + mOwner = nullptr; + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "memory-pressure"); + } + } + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + private: + ExpirationTrackerImpl<T, K, Mutex, AutoLock>* mOwner; + }; + + void HandleLowMemory() { + { + AutoLock lock(GetMutex()); + AgeAllGenerationsLocked(lock); + NotifyHandlerEndLocked(lock); + } + NotifyHandlerEnd(); + } + + void HandleTimeout() { + { + AutoLock lock(GetMutex()); + AgeOneGenerationLocked(lock); + // Cancel the timer if we have no objects to track + if (IsEmptyLocked(lock)) { + mTimer->Cancel(); + mTimer = nullptr; + } + NotifyHandlerEndLocked(lock); + } + NotifyHandlerEnd(); + } + + static void TimerCallback(nsITimer* aTimer, void* aThis) { + ExpirationTrackerImpl* tracker = static_cast<ExpirationTrackerImpl*>(aThis); + tracker->HandleTimeout(); + } + + nsresult CheckStartTimerLocked(const AutoLock& aAutoLock) { + if (mTimer || !mTimerPeriod) { + return NS_OK; + } + + return NS_NewTimerWithFuncCallback( + getter_AddRefs(mTimer), TimerCallback, this, mTimerPeriod, + nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY, mName, mEventTarget); + } +}; + +namespace detail { + +class PlaceholderLock { + public: + void Lock() {} + void Unlock() {} +}; + +class PlaceholderAutoLock { + public: + explicit PlaceholderAutoLock(PlaceholderLock&) {} + ~PlaceholderAutoLock() = default; +}; + +template <typename T, uint32_t K> +using SingleThreadedExpirationTracker = + ExpirationTrackerImpl<T, K, PlaceholderLock, PlaceholderAutoLock>; + +} // namespace detail + +template <typename T, uint32_t K> +class nsExpirationTracker + : protected ::detail::SingleThreadedExpirationTracker<T, K> { + typedef ::detail::PlaceholderLock Lock; + typedef ::detail::PlaceholderAutoLock AutoLock; + + Lock mLock; + + AutoLock FakeLock() { + NS_ASSERT_OWNINGTHREAD(nsExpirationTracker); + return AutoLock(mLock); + } + + Lock& GetMutex() override { + NS_ASSERT_OWNINGTHREAD(nsExpirationTracker); + return mLock; + } + + void NotifyExpiredLocked(T* aObject, const AutoLock&) override { + NotifyExpired(aObject); + } + + /** + * Since there are no users of these callbacks in the single threaded case, + * we mark them as final with the hope that the compiler can optimize the + * method calls out entirely. + */ + void NotifyHandlerEndLocked(const AutoLock&) final {} + void NotifyHandlerEnd() final {} + + protected: + NS_DECL_OWNINGTHREAD + + virtual void NotifyExpired(T* aObj) = 0; + + public: + nsExpirationTracker(uint32_t aTimerPeriod, const char* aName, + nsIEventTarget* aEventTarget = nullptr) + : ::detail::SingleThreadedExpirationTracker<T, K>(aTimerPeriod, aName, + aEventTarget) {} + + virtual ~nsExpirationTracker() { + NS_ASSERT_OWNINGTHREAD(nsExpirationTracker); + } + + nsresult AddObject(T* aObj) { + return this->AddObjectLocked(aObj, FakeLock()); + } + + void RemoveObject(T* aObj) { this->RemoveObjectLocked(aObj, FakeLock()); } + + nsresult MarkUsed(T* aObj) { return this->MarkUsedLocked(aObj, FakeLock()); } + + void AgeOneGeneration() { this->AgeOneGenerationLocked(FakeLock()); } + + void AgeAllGenerations() { this->AgeAllGenerationsLocked(FakeLock()); } + + class Iterator { + private: + AutoLock mAutoLock; + typename ExpirationTrackerImpl<T, K, Lock, AutoLock>::Iterator mIterator; + + public: + explicit Iterator(nsExpirationTracker<T, K>* aTracker) + : mAutoLock(aTracker->GetMutex()), mIterator(aTracker, mAutoLock) {} + + T* Next() { return mIterator.Next(); } + }; + + friend class Iterator; + + bool IsEmpty() { return this->IsEmptyLocked(FakeLock()); } +}; + +template <typename T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP ExpirationTrackerImpl<T, K, Mutex, AutoLock>:: + ExpirationTrackerObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "memory-pressure") && mOwner) { + mOwner->HandleLowMemory(); + } + return NS_OK; +} + +template <class T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP_(MozExternalRefCountType) +ExpirationTrackerImpl<T, K, Mutex, AutoLock>::ExpirationTrackerObserver::AddRef( + void) { + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "ExpirationTrackerObserver", sizeof(*this)); + return mRefCnt; +} + +template <class T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP_(MozExternalRefCountType) +ExpirationTrackerImpl<T, K, Mutex, + AutoLock>::ExpirationTrackerObserver::Release(void) { + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "ExpirationTrackerObserver"); + if (mRefCnt == 0) { + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + mRefCnt = 1; /* stabilize */ + delete (this); + return 0; + } + return mRefCnt; +} + +template <class T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP ExpirationTrackerImpl<T, K, Mutex, AutoLock>:: + ExpirationTrackerObserver::QueryInterface(REFNSIID aIID, + void** aInstancePtr) { + NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!"); + nsresult rv = NS_ERROR_FAILURE; + NS_INTERFACE_TABLE(ExpirationTrackerObserver, nsIObserver) + return rv; +} + +#endif /*NSEXPIRATIONTRACKER_H_*/ diff --git a/xpcom/ds/nsGkAtoms.cpp b/xpcom/ds/nsGkAtoms.cpp new file mode 100644 index 0000000000..402e0eb779 --- /dev/null +++ b/xpcom/ds/nsGkAtoms.cpp @@ -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/. */ + +#include "nsGkAtoms.h" + +namespace mozilla { +namespace detail { + +// Because this is `constexpr` it ends up in read-only memory where it can be +// shared between processes. +extern constexpr GkAtoms gGkAtoms = { +// The initialization of each atom's string. +// +// Expansion of the example GK_ATOM entries in nsGkAtoms.h: +// +// u"a", +// u"bb", +// u"Ccc", +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + u"" value_, +#include "nsGkAtomList.h" +#undef GK_ATOM + { +// The initialization of the atoms themselves. +// +// Note that |value_| is an 8-bit string, and so |sizeof(value_)| is equal +// to the number of chars (including the terminating '\0'). The |u""| prefix +// converts |value_| to a 16-bit string. +// +// Expansion of the example GK_ATOM entries in nsGkAtoms.h: +// +// nsStaticAtom( +// 1, +// 0x01234567, +// offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::a)]) - +// offsetof(GkAtoms, a_string), +// true), +// +// nsStaticAtom( +// 2, +// 0x12345678, +// offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::bb)]) - +// offsetof(GkAtoms, bb_string), +// false), +// +// nsStaticAtom( +// 3, +// 0x23456789, +// offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::Ccc)]) - +// offsetof(GkAtoms, Ccc_string), +// false), +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + nsStaticAtom( \ + sizeof(value_) - 1, hash_, \ + offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::name_)]) - \ + offsetof(GkAtoms, name_##_string), \ + is_ascii_lower_), +#include "nsGkAtomList.h" +#undef GK_ATOM + }}; + +} // namespace detail +} // namespace mozilla + +const nsStaticAtom* const nsGkAtoms::sAtoms = mozilla::detail::gGkAtoms.mAtoms; diff --git a/xpcom/ds/nsGkAtoms.h b/xpcom/ds/nsGkAtoms.h new file mode 100644 index 0000000000..9babf10502 --- /dev/null +++ b/xpcom/ds/nsGkAtoms.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 nsGkAtoms_h___ +#define nsGkAtoms_h___ + +#include "nsAtom.h" + +// Static atoms are structured carefully to satisfy a lot of constraints. +// +// - We have ~2300 static atoms. +// +// - We want them to be constexpr so they end up in .rodata, and thus shared +// between processes, minimizing memory usage. +// +// - We need them to be in an array, so we can iterate over them (for +// registration and lookups). +// +// - Each static atom has a string literal associated with it. We can't use a +// pointer to the string literal because then the atoms won't end up in +// .rodata. Therefore the string literals and the atoms must be arranged in a +// way such that a numeric index can be used instead. This numeric index +// (nsStaticAtom::mStringOffset) must be computable at compile-time to keep +// the static atom constexpr. It should also not be too large (a uint32_t is +// reasonable). +// +// - Each static atom stores the hash value of its associated string literal; +// it's used in various ways. The hash value must be specified at +// compile-time, to keep the static atom constexpr. +// +// - As well as accessing each static atom via array indexing, we need an +// individual pointer, e.g. nsGkAtoms::foo. We want this to be constexpr so +// it doesn't take up any space in memory. +// +// - The array of static atoms can't be in a .h file, because it's a huge +// constexpr expression, which would blow out compile times. But the +// individual pointers for the static atoms must be in a .h file so they are +// public. +// +// nsGkAtoms below defines static atoms in a way that satisfies these +// constraints. It uses nsGkAtomList.h, which defines the names and values of +// the atoms. nsGkAtomList.h is generated by StaticAtoms.py and has entries +// that look like this: +// +// GK_ATOM(a, "a", 0x01234567, nsStaticAtom, Atom) +// GK_ATOM(bb, "bb", 0x12345678, nsCSSPseudoElementStaticAtom, +// PseudoElementAtom) +// GK_ATOM(Ccc, "Ccc", 0x23456789, nsCSSAnonBoxPseudoStaticAtom, +// InheritingAnonBoxAtom) +// +// Comments throughout this file and nsGkAtoms.cpp show how these entries get +// expanded by macros. + +// Trivial subclasses of nsStaticAtom so that function signatures can require +// an atom from a specific atom list. +#define DEFINE_STATIC_ATOM_SUBCLASS(name_) \ + class name_ : public nsStaticAtom { \ + public: \ + constexpr name_(uint32_t aLength, uint32_t aHash, uint32_t aOffset, \ + bool aIsAsciiLowercase) \ + : nsStaticAtom(aLength, aHash, aOffset, aIsAsciiLowercase) {} \ + }; + +DEFINE_STATIC_ATOM_SUBCLASS(nsCSSAnonBoxPseudoStaticAtom) +DEFINE_STATIC_ATOM_SUBCLASS(nsCSSPseudoElementStaticAtom) + +#undef DEFINE_STATIC_ATOM_SUBCLASS + +namespace mozilla { +namespace detail { + +// This `detail` class contains the atom strings and the atom objects. Because +// they are together in a class, the `mStringOffset` field of the atoms will be +// small and can be initialized at compile time. +// +// A `detail` namespace is used because the things within it aren't directly +// referenced by external users of these static atoms. +struct GkAtoms { +// The declaration of each atom's string. +// +// Expansion of the example GK_ATOM entries from above: +// +// const char16_t a_string[sizeof("a")]; +// const char16_t bb_string[sizeof("bb")]; +// const char16_t Ccc_string[sizeof("Ccc")]; +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + const char16_t name_##_string[sizeof(value_)]; +#include "nsGkAtomList.h" +#undef GK_ATOM + + // The enum value for each atom. + enum class Atoms { +// Expansion of the example GK_ATOM entries above: +// +// a, +// bb, +// Ccc, +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) name_, +#include "nsGkAtomList.h" +#undef GK_ATOM + AtomsCount + }; + + const nsStaticAtom mAtoms[static_cast<size_t>(Atoms::AtomsCount)]; +}; + +// The GkAtoms instance is `extern const` so it can be defined in a .cpp file. +// +// XXX: The NS_EXTERNAL_VIS is necessary to work around an apparent GCC bug: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87494 +#if defined(__GNUC__) && !defined(__clang__) +extern NS_EXTERNAL_VIS const GkAtoms gGkAtoms; +#else +extern const GkAtoms gGkAtoms; +#endif + +} // namespace detail +} // namespace mozilla + +// This class holds the pointers to the individual atoms. +class nsGkAtoms { + private: + friend void NS_InitAtomTable(); + + // This is a useful handle to the array of atoms, used below and also + // possibly by Rust code. + static const nsStaticAtom* const sAtoms; + + // The number of atoms, used below. + static constexpr size_t sAtomsLen = + static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::AtomsCount); + + public: + static nsStaticAtom* GetAtomByIndex(size_t aIndex) { + MOZ_ASSERT(aIndex < sAtomsLen); + return const_cast<nsStaticAtom*>(&sAtoms[aIndex]); + } + + static size_t IndexOf(const nsStaticAtom* atom) { + nsStaticAtom* firstAtom = GetAtomByIndex(0); + size_t ret = atom - firstAtom; + MOZ_ASSERT(ret < sAtomsLen); + return ret; + } + +// The definition of the pointer to each static atom. +// +// These types are not `static constexpr <type>* const` -- even though these +// atoms are immutable -- because they are often passed to functions with +// `nsAtom*` parameters that can be passed both dynamic and static atoms. +// +// Expansion of the example GK_ATOM entries above: +// +// static constexpr nsStaticAtom* a = +// const_cast<nsStaticAtom*>( +// &mozilla::detail::gGkAtoms.mAtoms[ +// static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::a)]); +// +// static constexpr nsStaticAtom* bb = +// const_cast<nsStaticAtom*>( +// &mozilla::detail::gGkAtoms.mAtoms[ +// static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::bb)]); +// +// static constexpr nsStaticAtom* Ccc = +// const_cast<nsStaticAtom*>( +// &mozilla::detail::gGkAtoms.mAtoms[ +// static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::Ccc)]); +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + static constexpr nsStaticAtom* name_ = const_cast<nsStaticAtom*>( \ + &mozilla::detail::gGkAtoms.mAtoms[static_cast<size_t>( \ + mozilla::detail::GkAtoms::Atoms::name_)]); +#include "nsGkAtomList.h" +#undef GK_ATOM +}; + +inline bool nsAtom::IsEmpty() const { return this == nsGkAtoms::_empty; } + +#endif /* nsGkAtoms_h___ */ diff --git a/xpcom/ds/nsHashKeys.h b/xpcom/ds/nsHashKeys.h new file mode 100644 index 0000000000..2231293f66 --- /dev/null +++ b/xpcom/ds/nsHashKeys.h @@ -0,0 +1,612 @@ +/* -*- 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 nsTHashKeys_h__ +#define nsTHashKeys_h__ + +#include "nsID.h" +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" +#include <new> + +#include "nsString.h" +#include "nsCRTGlue.h" +#include "nsUnicharUtils.h" +#include "nsPointerHashKeys.h" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <type_traits> +#include <utility> + +#include "mozilla/HashFunctions.h" + +namespace mozilla { + +// These are defined analogously to the HashString overloads in mfbt. + +inline uint32_t HashString(const nsAString& aStr) { + return HashString(aStr.BeginReading(), aStr.Length()); +} + +inline uint32_t HashString(const nsACString& aStr) { + return HashString(aStr.BeginReading(), aStr.Length()); +} + +} // namespace mozilla + +/** @file nsHashKeys.h + * standard HashKey classes for nsBaseHashtable and relatives. Each of these + * classes follows the nsTHashtable::EntryType specification + * + * Lightweight keytypes provided here: + * nsStringHashKey + * nsCStringHashKey + * nsUint32HashKey + * nsUint64HashKey + * nsFloatHashKey + * IntPtrHashKey + * nsPtrHashKey + * nsVoidPtrHashKey + * nsISupportsHashKey + * nsIDHashKey + * nsDepCharHashKey + * nsCharPtrHashKey + * nsUnicharPtrHashKey + * nsGenericHashKey + */ + +/** + * hashkey wrapper using nsAString KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsStringHashKey : public PLDHashEntryHdr { + public: + typedef const nsAString& KeyType; + typedef const nsAString* KeyTypePointer; + + explicit nsStringHashKey(KeyTypePointer aStr) : mStr(*aStr) {} + nsStringHashKey(const nsStringHashKey&) = delete; + nsStringHashKey(nsStringHashKey&& aToMove) + : PLDHashEntryHdr(std::move(aToMove)), mStr(std::move(aToMove.mStr)) {} + ~nsStringHashKey() = default; + + KeyType GetKey() const { return mStr; } + bool KeyEquals(const KeyTypePointer aKey) const { return mStr.Equals(*aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + return mozilla::HashString(*aKey); + } + +#ifdef MOZILLA_INTERNAL_API + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +#endif + + enum { ALLOW_MEMMOVE = true }; + + private: + nsString mStr; +}; + +#ifdef MOZILLA_INTERNAL_API + +namespace mozilla::detail { + +template <class CharT, bool Unicode = true> +struct comparatorTraits {}; + +template <> +struct comparatorTraits<char, false> { + static int caseInsensitiveCompare(const char* aLhs, const char* aRhs, + size_t aLhsLength, size_t aRhsLength) { + return nsCaseInsensitiveCStringComparator(aLhs, aRhs, aLhsLength, + aRhsLength); + }; +}; + +template <> +struct comparatorTraits<char, true> { + static int caseInsensitiveCompare(const char* aLhs, const char* aRhs, + size_t aLhsLength, size_t aRhsLength) { + return nsCaseInsensitiveUTF8StringComparator(aLhs, aRhs, aLhsLength, + aRhsLength); + }; +}; + +template <> +struct comparatorTraits<char16_t, true> { + static int caseInsensitiveCompare(const char16_t* aLhs, const char16_t* aRhs, + size_t aLhsLength, size_t aRhsLength) { + return nsCaseInsensitiveStringComparator(aLhs, aRhs, aLhsLength, + aRhsLength); + }; +}; + +} // namespace mozilla::detail + +/** + * This is internal-API only because nsCaseInsensitive{C}StringComparator is + * internal-only. + * + * @see nsTHashtable::EntryType for specification + */ + +template <typename T, bool Unicode> +class nsTStringCaseInsensitiveHashKey : public PLDHashEntryHdr { + public: + typedef const nsTSubstring<T>& KeyType; + typedef const nsTSubstring<T>* KeyTypePointer; + + explicit nsTStringCaseInsensitiveHashKey(KeyTypePointer aStr) : mStr(*aStr) { + // take it easy just deal HashKey + } + + nsTStringCaseInsensitiveHashKey(const nsTStringCaseInsensitiveHashKey&) = + delete; + nsTStringCaseInsensitiveHashKey(nsTStringCaseInsensitiveHashKey&& aToMove) + : PLDHashEntryHdr(std::move(aToMove)), mStr(std::move(aToMove.mStr)) {} + ~nsTStringCaseInsensitiveHashKey() = default; + + KeyType GetKey() const { return mStr; } + bool KeyEquals(const KeyTypePointer aKey) const { + using comparator = typename mozilla::detail::comparatorTraits<T, Unicode>; + return mStr.Equals(*aKey, comparator::caseInsensitiveCompare); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + nsTAutoString<T> tmKey(*aKey); + ToLowerCase(tmKey); + return mozilla::HashString(tmKey); + } + enum { ALLOW_MEMMOVE = true }; + + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + private: + const nsTString<T> mStr; +}; + +using nsStringCaseInsensitiveHashKey = + nsTStringCaseInsensitiveHashKey<char16_t, true>; +using nsCStringASCIICaseInsensitiveHashKey = + nsTStringCaseInsensitiveHashKey<char, false>; +using nsCStringUTF8CaseInsensitiveHashKey = + nsTStringCaseInsensitiveHashKey<char, true>; + +#endif // MOZILLA_INTERNAL_API + +/** + * hashkey wrapper using nsACString KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsCStringHashKey : public PLDHashEntryHdr { + public: + typedef const nsACString& KeyType; + typedef const nsACString* KeyTypePointer; + + explicit nsCStringHashKey(const nsACString* aStr) : mStr(*aStr) {} + nsCStringHashKey(nsCStringHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mStr(std::move(aOther.mStr)) {} + ~nsCStringHashKey() = default; + + KeyType GetKey() const { return mStr; } + bool KeyEquals(KeyTypePointer aKey) const { return mStr.Equals(*aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashString(*aKey); + } + +#ifdef MOZILLA_INTERNAL_API + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +#endif + + enum { ALLOW_MEMMOVE = true }; + + private: + const nsCString mStr; +}; + +/** + * hashkey wrapper using integral or enum KeyTypes + * + * @see nsTHashtable::EntryType for specification + */ +template <typename T, + std::enable_if_t<std::is_integral_v<T> || std::is_enum_v<T>, int> = 0> +class nsIntegralHashKey : public PLDHashEntryHdr { + public: + using KeyType = const T&; + using KeyTypePointer = const T*; + + explicit nsIntegralHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsIntegralHashKey(nsIntegralHashKey&& aOther) noexcept + : PLDHashEntryHdr(std::move(aOther)), mValue(aOther.mValue) {} + ~nsIntegralHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashGeneric(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const T mValue; +}; + +/** + * hashkey wrapper using uint32_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +using nsUint32HashKey = nsIntegralHashKey<uint32_t>; + +/** + * hashkey wrapper using uint64_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +using nsUint64HashKey = nsIntegralHashKey<uint64_t>; + +/** + * hashkey wrapper using float KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsFloatHashKey : public PLDHashEntryHdr { + public: + typedef const float& KeyType; + typedef const float* KeyTypePointer; + + explicit nsFloatHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsFloatHashKey(nsFloatHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {} + ~nsFloatHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return *reinterpret_cast<const uint32_t*>(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const float mValue; +}; + +/** + * hashkey wrapper using intptr_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +using IntPtrHashKey = nsIntegralHashKey<intptr_t>; + +/** + * hashkey wrapper using nsISupports* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsISupportsHashKey : public PLDHashEntryHdr { + public: + using KeyType = nsISupports*; + using KeyTypePointer = const nsISupports*; + + explicit nsISupportsHashKey(const nsISupports* aKey) + : mSupports(const_cast<nsISupports*>(aKey)) {} + nsISupportsHashKey(nsISupportsHashKey&& aOther) = default; + ~nsISupportsHashKey() = default; + + KeyType GetKey() const { return mSupports; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mSupports; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashGeneric(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + nsCOMPtr<nsISupports> mSupports; +}; + +/** + * hashkey wrapper using refcounted * KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template <class T> +class nsRefPtrHashKey : public PLDHashEntryHdr { + public: + using KeyType = T*; + using KeyTypePointer = const T*; + + explicit nsRefPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {} + nsRefPtrHashKey(nsRefPtrHashKey&& aOther) = default; + ~nsRefPtrHashKey() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashGeneric(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + RefPtr<T> mKey; +}; + +template <class T> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsRefPtrHashKey<T>& aField, + const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.GetKey(), aName, aFlags); +} + +/** + * hashkey wrapper using a function pointer KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template <class T> +class nsFuncPtrHashKey : public PLDHashEntryHdr { + public: + typedef T& KeyType; + typedef const T* KeyTypePointer; + + explicit nsFuncPtrHashKey(const T* aKey) : mKey(*const_cast<T*>(aKey)) {} + nsFuncPtrHashKey(const nsFuncPtrHashKey<T>& aToCopy) : mKey(aToCopy.mKey) {} + ~nsFuncPtrHashKey() = default; + + KeyType GetKey() const { return const_cast<T&>(mKey); } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashGeneric(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + + protected: + T mKey; +}; + +/** + * hashkey wrapper using nsID KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsIDHashKey : public PLDHashEntryHdr { + public: + typedef const nsID& KeyType; + typedef const nsID* KeyTypePointer; + + explicit nsIDHashKey(const nsID* aInID) : mID(*aInID) {} + nsIDHashKey(nsIDHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mID(std::move(aOther.mID)) {} + ~nsIDHashKey() = default; + + KeyType GetKey() const { return mID; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey->Equals(mID); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + // Hash the nsID object's raw bytes. + return mozilla::HashBytes(aKey, sizeof(KeyType)); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + nsID mID; +}; + +/** + * hashkey wrapper using nsID* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsIDPointerHashKey : public PLDHashEntryHdr { + public: + typedef const nsID* KeyType; + typedef const nsID* KeyTypePointer; + + explicit nsIDPointerHashKey(const nsID* aInID) : mID(aInID) {} + nsIDPointerHashKey(nsIDPointerHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mID(aOther.mID) {} + ~nsIDPointerHashKey() = default; + + KeyType GetKey() const { return mID; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey->Equals(*mID); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + // Hash the nsID object's raw bytes. + return mozilla::HashBytes(aKey, sizeof(*aKey)); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + const nsID* mID; +}; + +/** + * hashkey wrapper for "dependent" const char*; this class does not "own" + * its string pointer. + * + * This class must only be used if the strings have a lifetime longer than + * the hashtable they occupy. This normally occurs only for static + * strings or strings that have been arena-allocated. + * + * @see nsTHashtable::EntryType for specification + */ +class nsDepCharHashKey : public PLDHashEntryHdr { + public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit nsDepCharHashKey(const char* aKey) : mKey(aKey) {} + nsDepCharHashKey(nsDepCharHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {} + ~nsDepCharHashKey() = default; + + const char* GetKey() const { return mKey; } + bool KeyEquals(const char* aKey) const { return !strcmp(mKey, aKey); } + + static const char* KeyToPointer(const char* aKey) { return aKey; } + static PLDHashNumber HashKey(const char* aKey) { + return mozilla::HashString(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const char* mKey; +}; + +/** + * hashkey wrapper for const char*; at construction, this class duplicates + * a string pointed to by the pointer so that it doesn't matter whether or not + * the string lives longer than the hash table. + */ +class nsCharPtrHashKey : public PLDHashEntryHdr { + public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit nsCharPtrHashKey(const char* aKey) : mKey(strdup(aKey)) {} + + nsCharPtrHashKey(const nsCharPtrHashKey&) = delete; + nsCharPtrHashKey(nsCharPtrHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(aOther.mKey) { + aOther.mKey = nullptr; + } + + ~nsCharPtrHashKey() { + if (mKey) { + free(const_cast<char*>(mKey)); + } + } + + const char* GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return !strcmp(mKey, aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashString(aKey); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(mKey); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + const char* mKey; +}; + +/** + * hashkey wrapper for const char16_t*; at construction, this class duplicates + * a string pointed to by the pointer so that it doesn't matter whether or not + * the string lives longer than the hash table. + */ +class nsUnicharPtrHashKey : public PLDHashEntryHdr { + public: + typedef const char16_t* KeyType; + typedef const char16_t* KeyTypePointer; + + explicit nsUnicharPtrHashKey(const char16_t* aKey) : mKey(NS_xstrdup(aKey)) {} + nsUnicharPtrHashKey(const nsUnicharPtrHashKey& aToCopy) = delete; + nsUnicharPtrHashKey(nsUnicharPtrHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(aOther.mKey) { + aOther.mKey = nullptr; + } + + ~nsUnicharPtrHashKey() { + if (mKey) { + free(const_cast<char16_t*>(mKey)); + } + } + + const char16_t* GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return !NS_strcmp(mKey, aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashString(aKey); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(mKey); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + const char16_t* mKey; +}; + +namespace mozilla { + +template <typename T> +PLDHashNumber Hash(const T& aValue) { + return aValue.Hash(); +} + +} // namespace mozilla + +/** + * Hashtable key class to use with objects for which Hash() and operator==() + * are defined. + */ +template <typename T> +class nsGenericHashKey : public PLDHashEntryHdr { + public: + typedef const T& KeyType; + typedef const T* KeyTypePointer; + + explicit nsGenericHashKey(KeyTypePointer aKey) : mKey(*aKey) {} + nsGenericHashKey(const nsGenericHashKey&) = delete; + nsGenericHashKey(nsGenericHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {} + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return ::mozilla::Hash(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + T mKey; +}; + +#endif // nsTHashKeys_h__ diff --git a/xpcom/ds/nsHashPropertyBag.cpp b/xpcom/ds/nsHashPropertyBag.cpp new file mode 100644 index 0000000000..68dd612c57 --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.cpp @@ -0,0 +1,366 @@ +/* -*- 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 "nsHashPropertyBag.h" + +#include <utility> + +#include "mozilla/Attributes.h" +#include "mozilla/SimpleEnumerator.h" +#include "nsArray.h" +#include "nsArrayEnumerator.h" +#include "nsIProperty.h" +#include "nsIVariant.h" +#include "nsThreadUtils.h" +#include "nsVariant.h" + +using mozilla::MakeRefPtr; +using mozilla::SimpleEnumerator; +using mozilla::Unused; + +extern "C" { + +// This function uses C linkage because it's exposed to Rust to support the +// `HashPropertyBag` wrapper in the `storage_variant` crate. +void NS_NewHashPropertyBag(nsIWritablePropertyBag** aBag) { + MakeRefPtr<nsHashPropertyBag>().forget(aBag); +} + +} // extern "C" + +/* + * nsHashPropertyBagBase implementation. + */ + +NS_IMETHODIMP +nsHashPropertyBagBase::HasKey(const nsAString& aName, bool* aResult) { + *aResult = mPropertyHash.Get(aName, nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::Get(const nsAString& aName, nsIVariant** aResult) { + if (!mPropertyHash.Get(aName, aResult)) { + *aResult = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetProperty(const nsAString& aName, + nsIVariant** aResult) { + bool isFound = mPropertyHash.Get(aName, aResult); + if (!isFound) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetProperty(const nsAString& aName, nsIVariant* aValue) { + if (NS_WARN_IF(!aValue)) { + return NS_ERROR_INVALID_ARG; + } + + mPropertyHash.InsertOrUpdate(aName, aValue); + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::DeleteProperty(const nsAString& aName) { + return mPropertyHash.Remove(aName) ? NS_OK : NS_ERROR_FAILURE; +} + +// +// nsSimpleProperty class and impl; used for GetEnumerator +// + +class nsSimpleProperty final : public nsIProperty { + ~nsSimpleProperty() = default; + + public: + nsSimpleProperty(const nsAString& aName, nsIVariant* aValue) + : mName(aName), mValue(aValue) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTY + protected: + nsString mName; + nsCOMPtr<nsIVariant> mValue; +}; + +NS_IMPL_ISUPPORTS(nsSimpleProperty, nsIProperty) + +NS_IMETHODIMP +nsSimpleProperty::GetName(nsAString& aName) { + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleProperty::GetValue(nsIVariant** aValue) { + NS_IF_ADDREF(*aValue = mValue); + return NS_OK; +} + +// end nsSimpleProperty + +NS_IMETHODIMP +nsHashPropertyBagBase::GetEnumerator(nsISimpleEnumerator** aResult) { + nsCOMPtr<nsIMutableArray> propertyArray = nsArray::Create(); + if (!propertyArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + const nsAString& key = iter.Key(); + nsIVariant* data = iter.UserData(); + nsSimpleProperty* sprop = new nsSimpleProperty(key, data); + propertyArray->AppendElement(sprop); + } + + return NS_NewArrayEnumerator(aResult, propertyArray, NS_GET_IID(nsIProperty)); +} + +#define IMPL_GETSETPROPERTY_AS(Name, Type) \ + NS_IMETHODIMP \ + nsHashPropertyBagBase::GetPropertyAs##Name(const nsAString& prop, \ + Type* _retval) { \ + nsIVariant* v = mPropertyHash.GetWeak(prop); \ + if (!v) return NS_ERROR_NOT_AVAILABLE; \ + return v->GetAs##Name(_retval); \ + } \ + \ + NS_IMETHODIMP \ + nsHashPropertyBagBase::SetPropertyAs##Name(const nsAString& prop, \ + Type value) { \ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); \ + var->SetAs##Name(value); \ + return SetProperty(prop, var); \ + } + +IMPL_GETSETPROPERTY_AS(Int32, int32_t) +IMPL_GETSETPROPERTY_AS(Uint32, uint32_t) +IMPL_GETSETPROPERTY_AS(Int64, int64_t) +IMPL_GETSETPROPERTY_AS(Uint64, uint64_t) +IMPL_GETSETPROPERTY_AS(Double, double) +IMPL_GETSETPROPERTY_AS(Bool, bool) + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsAString(const nsAString& aProp, + nsAString& aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsAString(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsACString(const nsAString& aProp, + nsACString& aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsACString(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsAUTF8String(const nsAString& aProp, + nsACString& aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsAUTF8String(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsInterface(const nsAString& aProp, + const nsIID& aIID, + void** aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + nsCOMPtr<nsISupports> val; + nsresult rv = v->GetAsISupports(getter_AddRefs(val)); + if (NS_FAILED(rv)) { + return rv; + } + if (!val) { + // We have a value, but it's null + *aResult = nullptr; + return NS_OK; + } + return val->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsAString(const nsAString& aProp, + const nsAString& aValue) { + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsAString(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsACString(const nsAString& aProp, + const nsACString& aValue) { + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsACString(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsAUTF8String(const nsAString& aProp, + const nsACString& aValue) { + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsAUTF8String(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsInterface(const nsAString& aProp, + nsISupports* aValue) { + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsISupports(aValue); + return SetProperty(aProp, var); +} + +void nsHashPropertyBagBase::CopyFrom(const nsHashPropertyBagBase* aOther) { + for (const auto& entry : aOther->mPropertyHash) { + SetProperty(entry.GetKey(), entry.GetWeak()); + } +} + +void nsHashPropertyBagBase::CopyFrom(nsIPropertyBag* aOther) { + CopyFrom(this, aOther); +} + +/* static */ void nsHashPropertyBagBase::CopyFrom(nsIWritablePropertyBag* aTo, + nsIPropertyBag* aFrom) { + if (aTo && aFrom) { + nsCOMPtr<nsISimpleEnumerator> enumerator; + if (NS_SUCCEEDED(aFrom->GetEnumerator(getter_AddRefs(enumerator)))) { + for (auto& property : SimpleEnumerator<nsIProperty>(enumerator)) { + nsString name; + nsCOMPtr<nsIVariant> value; + Unused << NS_WARN_IF(NS_FAILED(property->GetName(name))); + Unused << NS_WARN_IF( + NS_FAILED(property->GetValue(getter_AddRefs(value)))); + Unused << NS_WARN_IF( + NS_FAILED(aTo->SetProperty(std::move(name), value))); + } + } else { + NS_WARNING("Unable to copy nsIPropertyBag"); + } + } +} + +nsresult nsGetProperty::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult rv; + + if (mPropBag) { + rv = mPropBag->GetPropertyAsInterface(mPropName, aIID, aInstancePtr); + } else { + rv = NS_ERROR_NULL_POINTER; + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = rv; + } + return rv; +} + +/* + * nsHashPropertyBag implementation. + */ + +NS_IMPL_ADDREF(nsHashPropertyBag) +NS_IMPL_RELEASE(nsHashPropertyBag) + +NS_INTERFACE_MAP_BEGIN(nsHashPropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END + +/* + * We need to ensure that the hashtable is destroyed on the main thread, as + * the nsIVariant values are main-thread only objects. + */ +class ProxyHashtableDestructor final : public mozilla::Runnable { + public: + using HashtableType = nsInterfaceHashtable<nsStringHashKey, nsIVariant>; + explicit ProxyHashtableDestructor(HashtableType&& aTable) + : mozilla::Runnable("ProxyHashtableDestructor"), + mPropertyHash(std::move(aTable)) {} + + NS_IMETHODIMP + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + HashtableType table(std::move(mPropertyHash)); + return NS_OK; + } + + private: + HashtableType mPropertyHash; +}; + +nsHashPropertyBag::~nsHashPropertyBag() { + if (!NS_IsMainThread()) { + RefPtr<ProxyHashtableDestructor> runnable = + new ProxyHashtableDestructor(std::move(mPropertyHash)); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + } +} + +/* + * nsHashPropertyBagOMT implementation + */ +NS_IMPL_ADDREF(nsHashPropertyBagOMT) +NS_IMPL_RELEASE(nsHashPropertyBagOMT) + +NS_INTERFACE_MAP_BEGIN(nsHashPropertyBagOMT) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END + +nsHashPropertyBagOMT::nsHashPropertyBagOMT() { + // nsHashPropertyBagOMT is supposed to be used off-main thread. If you need a + // single threaded property bag on the main thread, you should consider using + // nsHashPropertyBagCC instead, to prevent leaks. + MOZ_ASSERT(!NS_IsMainThread()); +} + +/* + * nsHashPropertyBagCC implementation. + */ + +NS_IMPL_CYCLE_COLLECTION(nsHashPropertyBagCC, mPropertyHash) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHashPropertyBagCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHashPropertyBagCC) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHashPropertyBagCC) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END diff --git a/xpcom/ds/nsHashPropertyBag.h b/xpcom/ds/nsHashPropertyBag.h new file mode 100644 index 0000000000..7a2e6b0ac8 --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.h @@ -0,0 +1,80 @@ +/* -*- 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 nsHashPropertyBag_h___ +#define nsHashPropertyBag_h___ + +#include "nsIVariant.h" +#include "nsIWritablePropertyBag.h" +#include "nsIWritablePropertyBag2.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsInterfaceHashtable.h" + +class nsHashPropertyBagBase : public nsIWritablePropertyBag, + public nsIWritablePropertyBag2 { + public: + nsHashPropertyBagBase() = default; + + void CopyFrom(const nsHashPropertyBagBase* aOther); + void CopyFrom(nsIPropertyBag* aOther); + static void CopyFrom(nsIWritablePropertyBag* aTo, nsIPropertyBag* aFrom); + + NS_DECL_NSIPROPERTYBAG + NS_DECL_NSIPROPERTYBAG2 + + NS_DECL_NSIWRITABLEPROPERTYBAG + NS_DECL_NSIWRITABLEPROPERTYBAG2 + + protected: + // a hash table of string -> nsIVariant + nsInterfaceHashtable<nsStringHashKey, nsIVariant> mPropertyHash; +}; + +class nsHashPropertyBag : public nsHashPropertyBagBase { + public: + nsHashPropertyBag() = default; + NS_DECL_THREADSAFE_ISUPPORTS + + protected: + virtual ~nsHashPropertyBag(); +}; + +/* + * Off Main Thread variant of nsHashPropertyBag. Instances of this class + * should not be created on a main thread, nor should it contain main thread + * only objects, such as XPCVariants. The purpose of this class is to provide a + * way to use the property bag off main thread. + * Note: this class needs to be created and destroyed on the same thread and + * should be used single threaded. + */ +class nsHashPropertyBagOMT final : public nsHashPropertyBagBase { + public: + nsHashPropertyBagOMT(); + NS_DECL_ISUPPORTS + + protected: + // Doesn't need to dispatch to main thread because it cannot contain + // XPCVariants + virtual ~nsHashPropertyBagOMT() = default; +}; + +/* A cycle collected nsHashPropertyBag for main-thread-only use. */ +class nsHashPropertyBagCC final : public nsHashPropertyBagBase { + public: + nsHashPropertyBagCC() = default; + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHashPropertyBagCC, + nsIWritablePropertyBag) + protected: + virtual ~nsHashPropertyBagCC() = default; +}; + +inline nsISupports* ToSupports(nsHashPropertyBagBase* aPropertyBag) { + return static_cast<nsIWritablePropertyBag*>(aPropertyBag); +} + +#endif /* nsHashPropertyBag_h___ */ diff --git a/xpcom/ds/nsHashtablesFwd.h b/xpcom/ds/nsHashtablesFwd.h new file mode 100644 index 0000000000..080f79887d --- /dev/null +++ b/xpcom/ds/nsHashtablesFwd.h @@ -0,0 +1,94 @@ +/* -*- 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_DS_NSHASHTABLESFWD_H_ +#define XPCOM_DS_NSHASHTABLESFWD_H_ + +#include "mozilla/Attributes.h" + +struct PLDHashEntryHdr; + +template <class T> +class MOZ_IS_REFPTR nsCOMPtr; + +template <class T> +class MOZ_IS_REFPTR RefPtr; + +template <class EntryType> +class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable; + +template <class DataType, class UserDataType> +class nsDefaultConverter; + +template <class KeyClass, class DataType, class UserDataType, + class Converter = nsDefaultConverter<DataType, UserDataType>> +class nsBaseHashtable; + +template <class KeyClass, class T> +class nsClassHashtable; + +template <class KeyClass, class PtrType> +class nsRefCountedHashtable; + +/** + * templated hashtable class maps keys to interface pointers. + * See nsBaseHashtable for complete declaration. + * @deprecated This is going to be removed. Use nsTHashMap instead. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param Interface the interface-type being wrapped + * @see nsClassHashtable, nsTHashMap + */ +template <class KeyClass, class Interface> +using nsInterfaceHashtable = + nsRefCountedHashtable<KeyClass, nsCOMPtr<Interface>>; + +/** + * templated hashtable class maps keys to reference pointers. + * See nsBaseHashtable for complete declaration. + * @deprecated This is going to be removed. Use nsTHashMap instead. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param PtrType the reference-type being wrapped + * @see nsClassHashtable, nsTHashMap + */ +template <class KeyClass, class ClassType> +using nsRefPtrHashtable = nsRefCountedHashtable<KeyClass, RefPtr<ClassType>>; + +namespace mozilla::detail { +template <class KeyType, class = void> +struct nsKeyClass; +} // namespace mozilla::detail + +/** + * A universal hash map that maps some KeyType to some DataType. It can be used + * for any DataType, including RefPtr<T>, nsCOMPtr<T> and UniquePtr<T>. + * + * For the default hash keys types, the appropriate hash key class is determined + * automatically, so you can just specify `nsTHashMap<uint32_t, + * RefPtr<Foo>>`, for example. + * + * If you require custom hash behaviour (e.g. case insensitive string handling), + * you can still specify a hash key class derived from PLDHashEntryHdr + * explicitly. + * + * If you need to use a custom UserDataType, use nsBaseHashtable (or + * nsTHashtable) directly. However, you should double-check if that's really + * necessary. + */ +template <class KeyType, class DataType> +using nsTHashMap = + nsBaseHashtable<typename mozilla::detail::nsKeyClass<KeyType>::type, + DataType, DataType>; + +template <class KeyClass> +class nsTBaseHashSet; + +template <class KeyType> +using nsTHashSet = + nsTBaseHashSet<typename mozilla::detail::nsKeyClass<KeyType>::type>; + +#endif // XPCOM_DS_NSHASHTABLESFWD_H_ diff --git a/xpcom/ds/nsIArray.idl b/xpcom/ds/nsIArray.idl new file mode 100644 index 0000000000..7e514649cb --- /dev/null +++ b/xpcom/ds/nsIArray.idl @@ -0,0 +1,103 @@ +/* -*- 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 nsISimpleEnumerator; + +/** + * nsIArray + * + * An indexed collection of elements. Provides basic functionality for + * retrieving elements at a specific position, searching for + * elements. Indexes are zero-based, such that the last element in the + * array is stored at the index length-1. + * + * For an array which can be modified, see nsIMutableArray below. + * + * Neither interface makes any attempt to protect the individual + * elements from modification. The convention is that the elements of + * the array should not be modified. Documentation within a specific + * interface should describe variations from this convention. + * + * It is also convention that if an interface provides access to an + * nsIArray, that the array should not be QueryInterfaced to an + * nsIMutableArray for modification. If the interface in question had + * intended the array to be modified, it would have returned an + * nsIMutableArray! + * + * null is a valid entry in the array, and as such any nsISupports + * parameters may be null, except where noted. + */ +[scriptable, builtinclass, uuid(114744d9-c369-456e-b55a-52fe52880d2d)] +interface nsIArray : nsISupports +{ + /** + * length + * + * number of elements in the array. + */ + readonly attribute unsigned long length; + + /** + * queryElementAt() + * + * Retrieve a specific element of the array, and QueryInterface it + * to the specified interface. null is a valid result for + * this method, but exceptions are thrown in other circumstances + * + * @param index position of element + * @param uuid the IID of the requested interface + * @param result the object, QI'd to the requested interface + * + * @throws NS_ERROR_NO_INTERFACE when an entry exists at the + * specified index, but the requested interface is not + * available. + * @throws NS_ERROR_ILLEGAL_VALUE when index > length-1 + * + */ + void queryElementAt(in unsigned long index, + in nsIIDRef uuid, + [iid_is(uuid), retval] out nsQIResult result); + + /** + * indexOf() + * + * Get the position of a specific element. Note that since null is + * a valid input, exceptions are used to indicate that an element + * is not found. + * + * @param startIndex The initial element to search in the array + * To start at the beginning, use 0 as the + * startIndex + * @param element The element you are looking for + * @returns a number >= startIndex which is the position of the + * element in the array. + * @throws NS_ERROR_FAILURE if the element was not in the array. + */ + unsigned long indexOf(in unsigned long startIndex, + in nsISupports element); + + /** + * enumerate the array + * + * @returns a new enumerator positioned at the start of the array + * @throws NS_ERROR_FAILURE if the array is empty (to make it easy + * to detect errors), or NS_ERROR_OUT_OF_MEMORY if out of memory. + */ + [binaryname(ScriptedEnumerate), optional_argc] + nsISimpleEnumerator enumerate([optional] in nsIIDRef aElemIID); + + [noscript] + nsISimpleEnumerator enumerateImpl(in nsIDRef aElemIID); + + %{C++ + nsresult + Enumerate(nsISimpleEnumerator** aRetVal, const nsID& aElemIID = NS_GET_IID(nsISupports)) + { + return EnumerateImpl(aElemIID, aRetVal); + } + %} +}; diff --git a/xpcom/ds/nsIArrayExtensions.idl b/xpcom/ds/nsIArrayExtensions.idl new file mode 100644 index 0000000000..872a5c018d --- /dev/null +++ b/xpcom/ds/nsIArrayExtensions.idl @@ -0,0 +1,51 @@ +/* -*- 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 "nsIArray.idl" + +/** + * Helper interface for allowing scripts to treat nsIArray instances as if + * they were nsISupportsArray instances while iterating. + * + * nsISupportsArray is convenient to iterate over in JavaScript: + * + * for (let i = 0; i < array.Count(); ++i) { + * let elem = array.GetElementAt(i); + * ... + * } + * + * but doing the same with nsIArray is somewhat less convenient, since + * queryElementAt is not nearly so nice to use from JavaScript. So we provide + * this extension interface so interfaces that currently return + * nsISupportsArray can start returning nsIArrayExtensions and all JavaScript + * should Just Work. Eventually we'll roll this interface into nsIArray + * itself, possibly getting rid of the Count() method, as it duplicates + * nsIArray functionality. + */ +[scriptable, builtinclass, uuid(261d442e-050c-453d-8aaa-b3f23bcc528b)] +interface nsIArrayExtensions : nsIArray +{ + /** + * Count() + * + * Retrieves the length of the array. This is an alias for the + * |nsIArray.length| attribute. + */ + uint32_t Count(); + + /** + * GetElementAt() + * + * Retrieve a specific element of the array. null is a valid result for + * this method. + * + * Note: If the index is out of bounds null will be returned. + * This differs from the behavior of nsIArray.queryElementAt() which + * will throw if an invalid index is specified. + * + * @param index position of element + */ + nsISupports GetElementAt(in uint32_t index); +}; diff --git a/xpcom/ds/nsIINIParser.idl b/xpcom/ds/nsIINIParser.idl new file mode 100644 index 0000000000..434490bf42 --- /dev/null +++ b/xpcom/ds/nsIINIParser.idl @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIUTF8StringEnumerator; +interface nsIFile; + +[scriptable, uuid(7eb955f6-3e78-4d39-b72f-c1bf12a94bce)] +interface nsIINIParser : nsISupports +{ + /** + * Initializes an INI file from string data + */ + void initFromString(in AUTF8String aData); + + /** + * Enumerates the [section]s available in the INI file. + */ + nsIUTF8StringEnumerator getSections(); + + /** + * Enumerates the keys available within a section. + */ + nsIUTF8StringEnumerator getKeys(in AUTF8String aSection); + + /** + * Get the value of a string for a particular section and key. + */ + AUTF8String getString(in AUTF8String aSection, in AUTF8String aKey); +}; + +[scriptable, uuid(b67bb24b-31a3-4a6a-a5d9-0485c9af5a04)] +interface nsIINIParserWriter : nsISupports +{ + + /** + * Set the value of a string for a particular section and key. + */ + void setString(in AUTF8String aSection, in AUTF8String aKey, in AUTF8String aValue); + + /** + * Write to the INI file. + */ + void writeFile(in nsIFile aINIFile); + + /** + * Return the formatted INI file contents + */ + AUTF8String writeToString(); +}; + +[scriptable, uuid(ccae7ea5-1218-4b51-aecb-c2d8ecd46af9)] +interface nsIINIParserFactory : nsISupports +{ + /** + * Create an iniparser instance from a local file. + */ + nsIINIParser createINIParser([optional] in nsIFile aINIFile); +}; diff --git a/xpcom/ds/nsIMutableArray.idl b/xpcom/ds/nsIMutableArray.idl new file mode 100644 index 0000000000..3f06ecbeb1 --- /dev/null +++ b/xpcom/ds/nsIMutableArray.idl @@ -0,0 +1,92 @@ +/* -*- 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 "nsIArrayExtensions.idl" + +/** + * nsIMutableArray + * A separate set of methods that will act on the array. Consumers of + * nsIArray should not QueryInterface to nsIMutableArray unless they + * own the array. + * + * As above, it is legal to add null elements to the array. Note also + * that null elements can be created as a side effect of + * insertElementAt(). Conversely, if insertElementAt() is never used, + * and null elements are never explicitly added to the array, then it + * is guaranteed that queryElementAt() will never return a null value. + * + * Any of these methods may throw NS_ERROR_OUT_OF_MEMORY when the + * array must grow to complete the call, but the allocation fails. + */ +[scriptable, builtinclass, uuid(af059da0-c85b-40ec-af07-ae4bfdc192cc)] +interface nsIMutableArray : nsIArrayExtensions +{ + /** + * appendElement() + * + * Append an element at the end of the array. + * + * @param element The element to append. + */ + void appendElement(in nsISupports element); + + /** + * removeElementAt() + * + * Remove an element at a specific position, moving all elements + * stored at a higher position down one. + * To remove a specific element, use indexOf() to find the index + * first, then call removeElementAt(). + * + * @param index the position of the item + * + */ + void removeElementAt(in unsigned long index); + + /** + * insertElementAt() + * + * Insert an element at the given position, moving the element + * currently located in that position, and all elements in higher + * position, up by one. + * + * @param element The element to insert + * @param index The position in the array: + * If the position is lower than the current length + * of the array, the elements at that position and + * onwards are bumped one position up. + * If the position is equal to the current length + * of the array, the new element is appended. + * An index lower than 0 or higher than the current + * length of the array is invalid and will be ignored. + */ + void insertElementAt(in nsISupports element, in unsigned long index); + + /** + * replaceElementAt() + * + * Replace the element at the given position. + * + * @param element The new element to insert + * @param index The position in the array + * If the position is lower than the current length + * of the array, an existing element will be replaced. + * If the position is equal to the current length + * of the array, the new element is appended. + * If the position is higher than the current length + * of the array, empty elements are appended followed + * by the new element at the specified position. + * An index lower than 0 is invalid and will be ignored. + */ + void replaceElementAt(in nsISupports element, in unsigned long index); + + + /** + * clear() + * + * clear the entire array, releasing all stored objects + */ + void clear(); +}; diff --git a/xpcom/ds/nsINIParserImpl.cpp b/xpcom/ds/nsINIParserImpl.cpp new file mode 100644 index 0000000000..6ed52947d8 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.cpp @@ -0,0 +1,143 @@ +/* -*- 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 "nsINIParserImpl.h" + +#include "nsINIParser.h" +#include "nsStringEnumerator.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +class nsINIParserImpl final : public nsIINIParser, public nsIINIParserWriter { + ~nsINIParserImpl() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSER + NS_DECL_NSIINIPARSERWRITER + + nsresult Init(nsIFile* aINIFile) { return mParser.Init(aINIFile); } + + private: + nsINIParser mParser; + bool ContainsNull(const nsACString& aStr); +}; + +NS_IMPL_ISUPPORTS(nsINIParserFactory, nsIINIParserFactory) + +NS_IMETHODIMP +nsINIParserFactory::CreateINIParser(nsIFile* aINIFile, nsIINIParser** aResult) { + *aResult = nullptr; + + RefPtr<nsINIParserImpl> p(new nsINIParserImpl()); + + if (aINIFile) { + nsresult rv = p->Init(aINIFile); + if (NS_FAILED(rv)) { + return rv; + } + } + + p.forget(aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsINIParserImpl, nsIINIParser, nsIINIParserWriter) + +bool nsINIParserImpl::ContainsNull(const nsACString& aStr) { + return aStr.CountChar('\0') > 0; +} + +static bool SectionCB(const char* aSection, void* aClosure) { + nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure); + strings->AppendElement()->Assign(aSection); + return true; +} + +NS_IMETHODIMP +nsINIParserImpl::GetSections(nsIUTF8StringEnumerator** aResult) { + nsTArray<nsCString>* strings = new nsTArray<nsCString>; + + nsresult rv = mParser.GetSections(SectionCB, strings); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings); + } + + if (NS_FAILED(rv)) { + delete strings; + } + + return rv; +} + +static bool KeyCB(const char* aKey, const char* aValue, void* aClosure) { + nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure); + strings->AppendElement()->Assign(aKey); + return true; +} + +NS_IMETHODIMP +nsINIParserImpl::GetKeys(const nsACString& aSection, + nsIUTF8StringEnumerator** aResult) { + if (ContainsNull(aSection)) { + return NS_ERROR_INVALID_ARG; + } + + nsTArray<nsCString>* strings = new nsTArray<nsCString>; + + nsresult rv = + mParser.GetStrings(PromiseFlatCString(aSection).get(), KeyCB, strings); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings); + } + + if (NS_FAILED(rv)) { + delete strings; + } + + return rv; +} + +NS_IMETHODIMP +nsINIParserImpl::GetString(const nsACString& aSection, const nsACString& aKey, + nsACString& aResult) { + if (ContainsNull(aSection) || ContainsNull(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + return mParser.GetString(PromiseFlatCString(aSection).get(), + PromiseFlatCString(aKey).get(), aResult); +} + +NS_IMETHODIMP +nsINIParserImpl::InitFromString(const nsACString& aData) { + return mParser.InitFromString(nsCString(aData)); +} + +NS_IMETHODIMP +nsINIParserImpl::SetString(const nsACString& aSection, const nsACString& aKey, + const nsACString& aValue) { + if (ContainsNull(aSection) || ContainsNull(aKey) || ContainsNull(aValue)) { + return NS_ERROR_INVALID_ARG; + } + + return mParser.SetString(PromiseFlatCString(aSection).get(), + PromiseFlatCString(aKey).get(), + PromiseFlatCString(aValue).get()); +} + +NS_IMETHODIMP +nsINIParserImpl::WriteFile(nsIFile* aINIFile) { + return mParser.WriteToFile(aINIFile); +} + +NS_IMETHODIMP +nsINIParserImpl::WriteToString(nsACString& aOutput) { + aOutput.Truncate(); + mParser.WriteToString(aOutput); + + return NS_OK; +} diff --git a/xpcom/ds/nsINIParserImpl.h b/xpcom/ds/nsINIParserImpl.h new file mode 100644 index 0000000000..1a60fd1071 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.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 nsINIParserImpl_h__ +#define nsINIParserImpl_h__ + +#include "nsIINIParser.h" +#include "mozilla/Attributes.h" + +#define NS_INIPARSERFACTORY_CONTRACTID "@mozilla.org/xpcom/ini-parser-factory;1" + +class nsINIParserFactory final : public nsIINIParserFactory { + ~nsINIParserFactory() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSERFACTORY +}; + +#endif // nsINIParserImpl_h__ diff --git a/xpcom/ds/nsIObserver.idl b/xpcom/ds/nsIObserver.idl new file mode 100644 index 0000000000..773424d0a7 --- /dev/null +++ b/xpcom/ds/nsIObserver.idl @@ -0,0 +1,37 @@ +/* -*- 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" + +/** + * This interface is implemented by an object that wants + * to observe an event corresponding to a topic. + */ + +[scriptable, function, uuid(DB242E01-E4D9-11d2-9DDE-000064657374)] +interface nsIObserver : nsISupports { + + /** + * Observe will be called when there is a notification for the + * topic |aTopic|. This assumes that the object implementing + * this interface has been registered with an observer service + * such as the nsIObserverService. + * + * If you expect multiple topics/subjects, the impl is + * responsible for filtering. + * + * You should not modify, add, remove, or enumerate + * notifications in the implemention of observe. + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param aData : Notification specific wide string. + * subject event. + */ + void observe( in nsISupports aSubject, + in string aTopic, + in wstring aData ); + +}; diff --git a/xpcom/ds/nsIObserverService.idl b/xpcom/ds/nsIObserverService.idl new file mode 100644 index 0000000000..5ca51d5d44 --- /dev/null +++ b/xpcom/ds/nsIObserverService.idl @@ -0,0 +1,109 @@ +/* -*- 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 nsIObserver; +interface nsISimpleEnumerator; + +/** + * nsIObserverService + * + * Service allows a client listener (nsIObserver) to register and unregister for + * notifications of specific string referenced topic. Service also provides a + * way to notify registered listeners and a way to enumerate registered client + * listeners. + */ + +[scriptable, builtinclass, uuid(D07F5192-E3D1-11d2-8ACD-00105A1B8860)] +interface nsIObserverService : nsISupports +{ + + /** + * AddObserver + * + * Registers a given listener for a notifications regarding the specified + * topic. + * + * @param anObserve : The interface pointer which will receive notifications. + * @param aTopic : The notification topic or subject. + * @param ownsWeak : If set to false, the nsIObserverService will hold a + * strong reference to |anObserver|. If set to true and + * |anObserver| supports the nsIWeakReference interface, + * a weak reference will be held. Otherwise an error will be + * returned. + */ + void addObserver( in nsIObserver anObserver, in string aTopic, + [optional] in boolean ownsWeak); + + /** + * removeObserver + * + * Unregisters a given listener from notifications regarding the specified + * topic. + * + * @param anObserver : The interface pointer which will stop recieving + * notifications. + * @param aTopic : The notification topic or subject. + */ + void removeObserver( in nsIObserver anObserver, in string aTopic ); + + /** + * notifyObservers + * + * Notifies all registered listeners of the given topic. + * Must not be used with shutdown topics (will assert + * on the parent process). + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param someData : Notification specific wide string. + */ + void notifyObservers( in nsISupports aSubject, + in string aTopic, + [optional] in wstring someData ); + + /** + * hasObservers + * + * Checks to see if there are registered listeners for the given topic. + * + * Implemented in "nsObserverService.cpp". + * + * @param aTopic : The notification topic or subject. + * @param aFound : An out parameter; True if there are registered observers, + * False otherwise. + */ + [noscript, notxpcom, nostdcall] boolean hasObservers(in string aTopic); + + %{C++ + /** + * notifyWhenScriptSafe + * + * Notifies all registered listeners of the given topic once it is safe to + * run script. + * + * Implemented in "nsObserverService.cpp". + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param someData : Notification specific wide string. + */ + nsresult NotifyWhenScriptSafe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData = nullptr); + %} + + /** + * enumerateObservers + * + * Returns an enumeration of all registered listeners. + * + * @param aTopic : The notification topic or subject. + */ + nsISimpleEnumerator enumerateObservers( in string aTopic ); + + +}; diff --git a/xpcom/ds/nsIPersistentProperties.h b/xpcom/ds/nsIPersistentProperties.h new file mode 100644 index 0000000000..eef30c2ff2 --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties.h @@ -0,0 +1,13 @@ +/* -*- 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 __gen_nsIPersistentProperties_h__ +#define __gen_nsIPersistentProperties_h__ + +// "soft" switch over to an IDL generated header file +#include "nsIPersistentProperties2.h" + +#endif /* __gen_nsIPersistentProperties_h__ */ diff --git a/xpcom/ds/nsIPersistentProperties2.idl b/xpcom/ds/nsIPersistentProperties2.idl new file mode 100644 index 0000000000..7b07ba33ab --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties2.idl @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIProperties.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsISimpleEnumerator; + +%{C++ +#include "mozilla/MemoryReporting.h" +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +[scriptable, uuid(283EE646-1AEF-11D4-98B3-00C04fA0CE9A)] +interface nsIPropertyElement : nsISupports { + attribute AUTF8String key; + attribute AString value; +}; + +[scriptable, builtinclass, uuid(706867af-0400-4faa-beb1-0dae87308784)] +interface nsIPersistentProperties : nsIProperties +{ + /** + * load a set of name/value pairs from the input stream + * names and values should be in UTF8 + */ + void load(in nsIInputStream input); + + /** + * output the values to the stream - results will be in UTF8 + */ + void save(in nsIOutputStream output, in AUTF8String header); + + /** + * get an enumeration of nsIPropertyElement objects, + * which are read-only (i.e. setting properties on the element will + * not make changes back into the source nsIPersistentProperties + */ + nsISimpleEnumerator enumerate(); + + /** + * shortcut to nsIProperty's get() which retrieves a string value + * directly (and thus faster) + */ + AString getStringProperty(in AUTF8String key); + + /** + * shortcut to nsIProperty's set() which sets a string value + * directly (and thus faster). If the given property already exists, + * then the old value will be returned + */ + AString setStringProperty(in AUTF8String key, in AString value); + + [notxpcom, nostdcall] size_t sizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); +}; diff --git a/xpcom/ds/nsIProperties.idl b/xpcom/ds/nsIProperties.idl new file mode 100644 index 0000000000..50d833e231 --- /dev/null +++ b/xpcom/ds/nsIProperties.idl @@ -0,0 +1,46 @@ +/* -*- 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" + +/* + * Simple mapping service interface. + */ + +[scriptable, uuid(78650582-4e93-4b60-8e85-26ebd3eb14ca)] +interface nsIProperties : nsISupports +{ + /** + * Gets a property with a given name. + * + * @throws NS_ERROR_FAILURE if a property with that name doesn't exist. + * @throws NS_ERROR_NO_INTERFACE if the found property fails to QI to the + * given iid. + */ + void get(in string prop, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); + + /** + * Sets a property with a given name to a given value. + */ + void set(in string prop, in nsISupports value); + + /** + * Returns true if the property with the given name exists. + */ + boolean has(in string prop); + + /** + * Undefines a property. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * already exist. + */ + void undefine(in string prop); + + /** + * Returns an array of the keys. + */ + Array<ACString> getKeys(); +}; diff --git a/xpcom/ds/nsIProperty.idl b/xpcom/ds/nsIProperty.idl new file mode 100644 index 0000000000..9e6e1e487e --- /dev/null +++ b/xpcom/ds/nsIProperty.idl @@ -0,0 +1,25 @@ +/* -*- 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/. */ + +/* nsIVariant based Property support. */ + +#include "nsISupports.idl" + +interface nsIVariant; + +[scriptable, uuid(6dcf9030-a49f-11d5-910d-0010a4e73d9a)] +interface nsIProperty : nsISupports +{ + /** + * Get the name of the property. + */ + readonly attribute AString name; + + /** + * Get the value of the property. + */ + readonly attribute nsIVariant value; +}; diff --git a/xpcom/ds/nsIPropertyBag.idl b/xpcom/ds/nsIPropertyBag.idl new file mode 100644 index 0000000000..57bd263451 --- /dev/null +++ b/xpcom/ds/nsIPropertyBag.idl @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsISupports.idl" + +interface nsIVariant; +interface nsISimpleEnumerator; + +[scriptable, uuid(bfcd37b0-a49f-11d5-910d-0010a4e73d9a)] +interface nsIPropertyBag : nsISupports +{ + /** + * Get a nsISimpleEnumerator whose elements are nsIProperty objects. + */ + readonly attribute nsISimpleEnumerator enumerator; + + /** + * Get a property value for the given name. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * exist. + */ + nsIVariant getProperty(in AString name); +}; diff --git a/xpcom/ds/nsIPropertyBag2.idl b/xpcom/ds/nsIPropertyBag2.idl new file mode 100644 index 0000000000..3232dce0ce --- /dev/null +++ b/xpcom/ds/nsIPropertyBag2.idl @@ -0,0 +1,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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsIPropertyBag.idl" + +[scriptable, uuid(625cfd1e-da1e-4417-9ee9-dbc8e0b3fd79)] +interface nsIPropertyBag2 : nsIPropertyBag +{ + // Accessing a property as a different type may attempt conversion to the + // requested value + + int32_t getPropertyAsInt32 (in AString prop); + uint32_t getPropertyAsUint32 (in AString prop); + int64_t getPropertyAsInt64 (in AString prop); + uint64_t getPropertyAsUint64 (in AString prop); + double getPropertyAsDouble (in AString prop); + AString getPropertyAsAString (in AString prop); + ACString getPropertyAsACString (in AString prop); + AUTF8String getPropertyAsAUTF8String (in AString prop); + boolean getPropertyAsBool (in AString prop); + + /** + * This method returns null if the value exists, but is null. + * + * Note: C++ callers should not use this method. They should use the + * typesafe `do_GetProperty` wrapper instead. + */ + void getPropertyAsInterface (in AString prop, + in nsIIDRef iid, + [iid_is(iid), retval] out nsQIResult result); + + /** + * This method returns null if the value does not exist, + * or exists but is null. + */ + nsIVariant get (in AString prop); + + /** + * Check for the existence of a key. + */ + boolean hasKey (in AString prop); +}; + + +%{C++ +#include "nsCOMPtr.h" +#include "nsAString.h" + +class MOZ_STACK_CLASS nsGetProperty final : public nsCOMPtr_helper { + public: + nsGetProperty(nsIPropertyBag2* aPropBag, const nsAString& aPropName, nsresult* aError) + : mPropBag(aPropBag), mPropName(aPropName), mErrorPtr(aError) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + nsIPropertyBag2* MOZ_NON_OWNING_REF mPropBag; + const nsAString& mPropName; + nsresult* mErrorPtr; +}; + +/** + * A typesafe wrapper around nsIPropertyBag2::GetPropertyAsInterface. Similar + * to the `do_QueryInterface` family of functions, when assigned to a + * `nsCOMPtr` of a given type, attempts to query the given property to that + * type. + * + * If `aError` is passed, the return value of `GetPropertyAsInterface` is + * stored in it. + */ +inline const nsGetProperty do_GetProperty(nsIPropertyBag2* aPropBag, + const nsAString& aPropName, + nsresult* aError = 0) { + return nsGetProperty(aPropBag, aPropName, aError); +} + +%} diff --git a/xpcom/ds/nsISerializable.idl b/xpcom/ds/nsISerializable.idl new file mode 100644 index 0000000000..1920fa1b16 --- /dev/null +++ b/xpcom/ds/nsISerializable.idl @@ -0,0 +1,32 @@ +/* -*- 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 nsIObjectInputStream; +interface nsIObjectOutputStream; + +[scriptable, uuid(91cca981-c26d-44a8-bebe-d9ed4891503a)] +interface nsISerializable : nsISupports +{ + /** + * Initialize the object implementing nsISerializable, which must have + * been freshly constructed via CreateInstance. All data members that + * can't be set to default values must have been serialized by write, + * and should be read from aInputStream in the same order by this method. + */ + [must_use] void read(in nsIObjectInputStream aInputStream); + + /** + * Serialize the object implementing nsISerializable to aOutputStream, by + * writing each data member that must be recovered later to reconstitute + * a working replica of this object, in a canonical member and byte order, + * to aOutputStream. + * + * NB: a class that implements nsISerializable *must* also implement + * nsIClassInfo, in particular nsIClassInfo::GetClassID. + */ + void write(in nsIObjectOutputStream aOutputStream); +}; diff --git a/xpcom/ds/nsISimpleEnumerator.idl b/xpcom/ds/nsISimpleEnumerator.idl new file mode 100644 index 0000000000..1a2b47705d --- /dev/null +++ b/xpcom/ds/nsISimpleEnumerator.idl @@ -0,0 +1,77 @@ +/* -*- 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" + +/** + * Used to enumerate over elements defined by its implementor. + * Although hasMoreElements() can be called independently of getNext(), + * getNext() must be pre-ceeded by a call to hasMoreElements(). There is + * no way to "reset" an enumerator, once you obtain one. + * + * @version 1.0 + */ + +/** + * A wrapper for an nsISimpleEnumerator instance which implements the + * JavaScript iteration protocol. + */ +[scriptable, uuid(4432e8ae-d4d3-42a6-a4d1-829f1c29512b)] +interface nsIJSEnumerator : nsISupports { + [symbol] + nsIJSEnumerator iterator(); + + [implicit_jscontext] + jsval next(); +}; + +[scriptable, uuid(796f340d-0a2a-490b-9c60-640765e99782)] +interface nsISimpleEnumeratorBase : nsISupports { + /** + * Returns a JavaScript iterator for all remaining entries in the enumerator. + * Each entry is typically queried to the appropriate interface for the + * enumerator. + */ + [symbol] + nsIJSEnumerator iterator(); + + /** + * Returns JavaScript iterator for all remaining entries in the enumerator. + * Each entry is queried only to the supplied interface. If any element + * fails to query to that interface, the error is propagated to the caller. + */ + nsIJSEnumerator entries(in nsIIDRef aIface); +}; + +[scriptable, uuid(D1899240-F9D2-11D2-BDD6-000064657374)] +interface nsISimpleEnumerator : nsISimpleEnumeratorBase { + /** + * Called to determine whether or not the enumerator has + * any elements that can be returned via getNext(). This method + * is generally used to determine whether or not to initiate or + * continue iteration over the enumerator, though it can be + * called without subsequent getNext() calls. Does not affect + * internal state of enumerator. + * + * @see getNext() + * @return true if there are remaining elements in the enumerator. + * false if there are no more elements in the enumerator. + */ + boolean hasMoreElements(); + + /** + * Called to retrieve the next element in the enumerator. The "next" + * element is the first element upon the first call. Must be + * pre-ceeded by a call to hasMoreElements() which returns PR_TRUE. + * This method is generally called within a loop to iterate over + * the elements in the enumerator. + * + * @see hasMoreElements() + * @throws NS_ERROR_FAILURE if there are no more elements + * to enumerate. + * @return the next element in the enumeration. + */ + nsISupports getNext(); +}; diff --git a/xpcom/ds/nsIStringEnumerator.idl b/xpcom/ds/nsIStringEnumerator.idl new file mode 100644 index 0000000000..96ab966bff --- /dev/null +++ b/xpcom/ds/nsIStringEnumerator.idl @@ -0,0 +1,37 @@ +/* -*- 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/. */ + +#include "nsISupports.idl" + +interface nsIJSEnumerator; + +/** + * Used to enumerate over an ordered list of strings. + */ + +/** + * Base class for C++-implemented string iterators. JS implementors need not + * be queryable to it. + */ +[scriptable, uuid(f5213d15-a4d1-4fb7-8a48-d69ccb7fb0eb)] +interface nsIStringEnumeratorBase : nsISupports +{ + [symbol, binaryname(StringIterator)] + nsIJSEnumerator iterator(); +}; + +[scriptable, uuid(50d3ef6c-9380-4f06-9fb2-95488f7d141c)] +interface nsIStringEnumerator : nsIStringEnumeratorBase +{ + boolean hasMore(); + AString getNext(); +}; + +[scriptable, uuid(9bdf1010-3695-4907-95ed-83d0410ec307)] +interface nsIUTF8StringEnumerator : nsIStringEnumeratorBase +{ + boolean hasMore(); + AUTF8String getNext(); +}; diff --git a/xpcom/ds/nsISupportsIterators.idl b/xpcom/ds/nsISupportsIterators.idl new file mode 100644 index 0000000000..8d47375d57 --- /dev/null +++ b/xpcom/ds/nsISupportsIterators.idl @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* nsISupportsIterators.idl --- IDL defining general purpose iterators */ + + +#include "nsISupports.idl" + + + /* + ... + */ + + + /** + * ... + */ +[scriptable, uuid(7330650e-1dd2-11b2-a0c2-9ff86ee97bed)] +interface nsIOutputIterator : nsISupports + { + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + }; + + /** + * ... + */ +[scriptable, uuid(85585e12-1dd2-11b2-a930-f6929058269a)] +interface nsIInputIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(8da01646-1dd2-11b2-98a7-c7009045be7e)] +interface nsIForwardIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(948defaa-1dd1-11b2-89f6-8ce81f5ebda9)] +interface nsIBidirectionalIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Move this iterator to the previous position in the underlying container or sequence. + */ + void stepBackward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(9bd6fdb0-1dd1-11b2-9101-d15375968230)] +interface nsIRandomAccessIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Retrieve (and |AddRef()|) an element at some offset from where this iterator currently points. + * The offset may be negative. |getElementAt(0)| is equivalent to |getElement()|. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + * @result a new reference to the indicated element (if any) + */ + nsISupports getElementAt( in int32_t anOffset ); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position |anOffset| away from that currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * |putElementAt(0, obj)| is equivalent to |putElement(obj)|. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElementAt( in int32_t anOffset, in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Move this iterator by |anOffset| positions in the underlying container or sequence. + * |anOffset| may be negative. |stepForwardBy(1)| is equivalent to |stepForward()|. + * |stepForwardBy(0)| is a no-op. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + */ + void stepForwardBy( in int32_t anOffset ); + + /** + * Move this iterator to the previous position in the underlying container or sequence. + */ + void stepBackward(); + + /** + * Move this iterator backwards by |anOffset| positions in the underlying container or sequence. + * |anOffset| may be negative. |stepBackwardBy(1)| is equivalent to |stepBackward()|. + * |stepBackwardBy(n)| is equivalent to |stepForwardBy(-n)|. |stepBackwardBy(0)| is a no-op. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + */ + void stepBackwardBy( in int32_t anOffset ); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + +%{C++ +%} diff --git a/xpcom/ds/nsISupportsPrimitives.idl b/xpcom/ds/nsISupportsPrimitives.idl new file mode 100644 index 0000000000..d661ce3b21 --- /dev/null +++ b/xpcom/ds/nsISupportsPrimitives.idl @@ -0,0 +1,222 @@ +/* -*- 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/. */ + +/* nsISupports wrappers for single primitive pieces of data. */ + +#include "nsISupports.idl" + +/** + * Primitive base interface. + * + * These first three are pointer types and do data copying + * using the nsIMemory. Be careful! + */ + +[scriptable, builtinclass, uuid(d0d4b136-1dd1-11b2-9371-f0727ef827c0)] +interface nsISupportsPrimitive : nsISupports +{ + const unsigned short TYPE_ID = 1; + const unsigned short TYPE_CSTRING = 2; + const unsigned short TYPE_STRING = 3; + const unsigned short TYPE_PRBOOL = 4; + const unsigned short TYPE_PRUINT8 = 5; + const unsigned short TYPE_PRUINT16 = 6; + const unsigned short TYPE_PRUINT32 = 7; + const unsigned short TYPE_PRUINT64 = 8; + const unsigned short TYPE_PRTIME = 9; + const unsigned short TYPE_CHAR = 10; + const unsigned short TYPE_PRINT16 = 11; + const unsigned short TYPE_PRINT32 = 12; + const unsigned short TYPE_PRINT64 = 13; + const unsigned short TYPE_FLOAT = 14; + const unsigned short TYPE_DOUBLE = 15; + // 16 was for TYPE_VOID + const unsigned short TYPE_INTERFACE_POINTER = 17; + + readonly attribute unsigned short type; +}; + +/** + * Scriptable storage for nsID structures + */ + +[scriptable, builtinclass, uuid(d18290a0-4a1c-11d3-9890-006008962422)] +interface nsISupportsID : nsISupportsPrimitive +{ + attribute nsIDPtr data; + string toString(); +}; + +/** + * Scriptable storage for ASCII strings + */ + +[scriptable, builtinclass, uuid(d65ff270-4a1c-11d3-9890-006008962422)] +interface nsISupportsCString : nsISupportsPrimitive +{ + attribute ACString data; + string toString(); +}; + +/** + * Scriptable storage for Unicode strings + */ + +[scriptable, builtinclass, uuid(d79dc970-4a1c-11d3-9890-006008962422)] +interface nsISupportsString : nsISupportsPrimitive +{ + attribute AString data; + wstring toString(); +}; + +/** + * The rest are truly primitive and are passed by value + */ + +/** + * Scriptable storage for booleans + */ + +[scriptable, builtinclass, uuid(ddc3b490-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRBool : nsISupportsPrimitive +{ + attribute boolean data; + string toString(); +}; + +/** + * Scriptable storage for 8-bit integers + */ + +[scriptable, builtinclass, uuid(dec2e4e0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint8 : nsISupportsPrimitive +{ + attribute uint8_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 16-bit integers + */ + +[scriptable, builtinclass, uuid(dfacb090-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint16 : nsISupportsPrimitive +{ + attribute uint16_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 32-bit integers + */ + +[scriptable, builtinclass, uuid(e01dc470-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint32 : nsISupportsPrimitive +{ + attribute uint32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, builtinclass, uuid(e13567c0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint64 : nsISupportsPrimitive +{ + attribute uint64_t data; + string toString(); +}; + +/** + * Scriptable storage for NSPR date/time values + */ + +[scriptable, builtinclass, uuid(e2563630-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRTime : nsISupportsPrimitive +{ + attribute PRTime data; + string toString(); +}; + +/** + * Scriptable storage for single character values + * (often used to store an ASCII character) + */ + +[scriptable, builtinclass, uuid(e2b05e40-4a1c-11d3-9890-006008962422)] +interface nsISupportsChar : nsISupportsPrimitive +{ + attribute char data; + string toString(); +}; + +/** + * Scriptable storage for 16-bit integers + */ + +[scriptable, builtinclass, uuid(e30d94b0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt16 : nsISupportsPrimitive +{ + attribute int16_t data; + string toString(); +}; + +/** + * Scriptable storage for 32-bit integers + */ + +[scriptable, builtinclass, uuid(e36c5250-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt32 : nsISupportsPrimitive +{ + attribute int32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, builtinclass, uuid(e3cb0ff0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt64 : nsISupportsPrimitive +{ + attribute int64_t data; + string toString(); +}; + +/** + * Scriptable storage for floating point numbers + */ + +[scriptable, builtinclass, uuid(abeaa390-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsFloat : nsISupportsPrimitive +{ + attribute float data; + string toString(); +}; + +/** + * Scriptable storage for doubles + */ + +[scriptable, builtinclass, uuid(b32523a0-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsDouble : nsISupportsPrimitive +{ + attribute double data; + string toString(); +}; + +/** + * Scriptable storage for other XPCOM objects + */ + +[scriptable, builtinclass, uuid(995ea724-1dd1-11b2-9211-c21bdd3e7ed0)] +interface nsISupportsInterfacePointer : nsISupportsPrimitive +{ + attribute nsISupports data; + attribute nsIDPtr dataIID; + + string toString(); +}; diff --git a/xpcom/ds/nsIVariant.idl b/xpcom/ds/nsIVariant.idl new file mode 100644 index 0000000000..0d7f7e70c3 --- /dev/null +++ b/xpcom/ds/nsIVariant.idl @@ -0,0 +1,162 @@ +/* -*- 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 long avoided variant support for xpcom. */ + +#include "nsISupports.idl" + +%{C++ +#include "xptinfo.h" + +// This enum class used to be a const-only XPIDL interface, containing literal +// integer descriptions of the different fields. Instead, it now directly +// references the nsXPTTypeTag variants VTYPE_ are intended to match. +struct nsIDataType +{ + enum { + // These MUST match the declarations in xptinfo.h. + // Otherwise the world is likely to explode. + VTYPE_INT8 = TD_INT8 , + VTYPE_INT16 = TD_INT16 , + VTYPE_INT32 = TD_INT32 , + VTYPE_INT64 = TD_INT64 , + VTYPE_UINT8 = TD_UINT8 , + VTYPE_UINT16 = TD_UINT16 , + VTYPE_UINT32 = TD_UINT32 , + VTYPE_UINT64 = TD_UINT64 , + VTYPE_FLOAT = TD_FLOAT , + VTYPE_DOUBLE = TD_DOUBLE , + VTYPE_BOOL = TD_BOOL , + VTYPE_CHAR = TD_CHAR , + VTYPE_WCHAR = TD_WCHAR , + VTYPE_VOID = TD_VOID , + VTYPE_ID = TD_NSIDPTR , + VTYPE_CHAR_STR = TD_PSTRING , + VTYPE_WCHAR_STR = TD_PWSTRING , + VTYPE_INTERFACE = TD_INTERFACE_TYPE , + VTYPE_INTERFACE_IS = TD_INTERFACE_IS_TYPE, + VTYPE_ARRAY = TD_LEGACY_ARRAY , + VTYPE_STRING_SIZE_IS = TD_PSTRING_SIZE_IS , + VTYPE_WSTRING_SIZE_IS = TD_PWSTRING_SIZE_IS , + VTYPE_UTF8STRING = TD_UTF8STRING , + VTYPE_CSTRING = TD_CSTRING , + VTYPE_ASTRING = TD_ASTRING , + + // Non-xpt variant types + VTYPE_EMPTY_ARRAY = 254 , + VTYPE_EMPTY = 255 + }; +}; +%} + + +/** + * XPConnect has magic to transparently convert between nsIVariant and JS types. + * We mark the interface [scriptable] so that JS can use methods + * that refer to this interface. But we mark all the methods and attributes + * [noscript] since any nsIVariant object will be automatically converted to a + * JS type anyway. + */ + +[scriptable, builtinclass, uuid(81e4c2de-acac-4ad6-901a-b5fb1b851a0d)] +interface nsIVariant : nsISupports +{ + [notxpcom,nostdcall] readonly attribute uint16_t dataType; + + [noscript] uint8_t getAsInt8(); + [noscript] int16_t getAsInt16(); + [noscript] int32_t getAsInt32(); + [noscript] int64_t getAsInt64(); + [noscript] uint8_t getAsUint8(); + [noscript] uint16_t getAsUint16(); + [noscript] uint32_t getAsUint32(); + [noscript] uint64_t getAsUint64(); + [noscript] float getAsFloat(); + [noscript] double getAsDouble(); + [noscript] boolean getAsBool(); + [noscript] char getAsChar(); + [noscript] wchar getAsWChar(); + [notxpcom] nsresult getAsID(out nsID retval); + [noscript] AString getAsAString(); + [noscript] ACString getAsACString(); + [noscript] AUTF8String getAsAUTF8String(); + [noscript] string getAsString(); + [noscript] wstring getAsWString(); + [noscript] nsISupports getAsISupports(); + [noscript] jsval getAsJSVal(); + + [noscript] void getAsInterface(out nsIIDPtr iid, + [iid_is(iid), retval] out nsQIResult iface); + + [notxpcom] nsresult getAsArray(out uint16_t type, out nsIID iid, + out uint32_t count, out voidPtr ptr); + + [noscript] void getAsStringWithSize(out uint32_t size, + [size_is(size), retval] out string str); + + [noscript] void getAsWStringWithSize(out uint32_t size, + [size_is(size), retval] out wstring str); +}; + +/** + * An object that implements nsIVariant may or may NOT also implement this + * nsIWritableVariant. + * + * If the 'writable' attribute is false then attempts to call any of the 'set' + * methods can be expected to fail. Setting the 'writable' attribute may or + * may not succeed. + * + */ + +[scriptable, builtinclass, uuid(5586a590-8c82-11d5-90f3-0010a4e73d9a)] +interface nsIWritableVariant : nsIVariant +{ + attribute boolean writable; + + void setAsInt8(in uint8_t aValue); + void setAsInt16(in int16_t aValue); + void setAsInt32(in int32_t aValue); + void setAsInt64(in int64_t aValue); + void setAsUint8(in uint8_t aValue); + void setAsUint16(in uint16_t aValue); + void setAsUint32(in uint32_t aValue); + void setAsUint64(in uint64_t aValue); + void setAsFloat(in float aValue); + void setAsDouble(in double aValue); + void setAsBool(in boolean aValue); + void setAsChar(in char aValue); + void setAsWChar(in wchar aValue); + void setAsID(in nsIDRef aValue); + void setAsAString(in AString aValue); + void setAsACString(in ACString aValue); + void setAsAUTF8String(in AUTF8String aValue); + void setAsString(in string aValue); + void setAsWString(in wstring aValue); + void setAsISupports(in nsISupports aValue); + + void setAsInterface(in nsIIDRef iid, + [iid_is(iid)] in nsQIResult iface); + + [noscript] void setAsArray(in uint16_t type, in nsIIDPtr iid, + in uint32_t count, in voidPtr ptr); + + void setAsStringWithSize(in uint32_t size, + [size_is(size)] in string str); + + void setAsWStringWithSize(in uint32_t size, + [size_is(size)] in wstring str); + + void setAsVoid(); + void setAsEmpty(); + void setAsEmptyArray(); + + void setFromVariant(in nsIVariant aValue); +}; + +%{C++ +// The contractID for the generic implementation built in to xpcom. +#define NS_VARIANT_CONTRACTID "@mozilla.org/variant;1" +%} diff --git a/xpcom/ds/nsIWindowsRegKey.idl b/xpcom/ds/nsIWindowsRegKey.idl new file mode 100644 index 0000000000..c56fe62544 --- /dev/null +++ b/xpcom/ds/nsIWindowsRegKey.idl @@ -0,0 +1,294 @@ +/* -*- 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 "nsISupports.idl" + +%{C++ +#include <windows.h> +%} + +native HKEY(HKEY); + +/** + * This interface is designed to provide scriptable access to the Windows + * registry system ("With Great Power Comes Great Responsibility"). The + * interface represents a single key in the registry. + * + * This interface is highly Win32 specific. + */ +[scriptable, uuid(2555b930-d64f-437e-9be7-0a2cb252c1f4)] +interface nsIWindowsRegKey : nsISupports +{ + /** + * Root keys. The values for these keys correspond to the values from + * WinReg.h in the MS Platform SDK. The ROOT_KEY_ prefix corresponds to the + * HKEY_ prefix in the MS Platform SDK. + * + * This interface is not restricted to using only these root keys. + */ + const unsigned long ROOT_KEY_CLASSES_ROOT = 0x80000000; + const unsigned long ROOT_KEY_CURRENT_USER = 0x80000001; + const unsigned long ROOT_KEY_LOCAL_MACHINE = 0x80000002; + + /** + * Values for the mode parameter passed to the open and create methods. + * The values defined here correspond to the REGSAM values defined in + * WinNT.h in the MS Platform SDK, where ACCESS_ is replaced with KEY_. + * + * This interface is not restricted to using only these access types. + */ + const unsigned long ACCESS_BASIC = 0x00020000; + const unsigned long ACCESS_QUERY_VALUE = 0x00000001; + const unsigned long ACCESS_SET_VALUE = 0x00000002; + const unsigned long ACCESS_CREATE_SUB_KEY = 0x00000004; + const unsigned long ACCESS_ENUMERATE_SUB_KEYS = 0x00000008; + const unsigned long ACCESS_NOTIFY = 0x00000010; + const unsigned long ACCESS_READ = ACCESS_BASIC | + ACCESS_QUERY_VALUE | + ACCESS_ENUMERATE_SUB_KEYS | + ACCESS_NOTIFY; + const unsigned long ACCESS_WRITE = ACCESS_BASIC | + ACCESS_SET_VALUE | + ACCESS_CREATE_SUB_KEY; + const unsigned long ACCESS_ALL = ACCESS_READ | + ACCESS_WRITE; + const unsigned long WOW64_32 = 0x00000200; + const unsigned long WOW64_64 = 0x00000100; + + + /** + * Values for the type of a registry value. The numeric values of these + * constants are taken directly from WinNT.h in the MS Platform SDK. + * The Microsoft documentation should be consulted for the exact meaning of + * these value types. + * + * This interface is somewhat restricted to using only these value types. + * There is no method that is directly equivalent to RegQueryValueEx or + * RegSetValueEx. In particular, this interface does not support the + * REG_MULTI_SZ and REG_EXPAND_SZ value types. It is still possible to + * enumerate values that have other types (i.e., getValueType may return a + * type not defined below). + */ + const unsigned long TYPE_NONE = 0; // REG_NONE + const unsigned long TYPE_STRING = 1; // REG_SZ + const unsigned long TYPE_BINARY = 3; // REG_BINARY + const unsigned long TYPE_INT = 4; // REG_DWORD + const unsigned long TYPE_INT64 = 11; // REG_QWORD + + /** + * This method closes the key. If the key is already closed, then this + * method does nothing. + */ + void close(); + + /** + * This method opens an existing key. This method fails if the key + * does not exist. + * + * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey + * parameter of this function. However, for compatibility with 64-bit + * Windows, that usage should probably be avoided in favor of openChild. + * + * @param rootKey + * A root key defined above or any valid HKEY on 32-bit Windows. + * @param relPath + * A relative path from the given root key. + * @param mode + * Access mode, which is a bit-wise OR of the ACCESS_ values defined + * above. + */ + void open(in unsigned long rootKey, in AString relPath, in unsigned long mode); + + /** + * This method opens an existing key or creates a new key. + * + * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey + * parameter of this function. However, for compatibility with 64-bit + * Windows, that usage should probably be avoided in favor of createChild. + * + * @param rootKey + * A root key defined above or any valid HKEY on 32-bit Windows. + * @param relPath + * A relative path from the given root key. + * @param mode + * Access mode, which is a bit-wise OR of the ACCESS_ values defined + * above. + */ + void create(in unsigned long rootKey, in AString relPath, in unsigned long mode); + + /** + * This method opens a subkey relative to this key. This method fails if the + * key does not exist. + * + * @return nsIWindowsRegKey for the newly opened subkey. + */ + nsIWindowsRegKey openChild(in AString relPath, in unsigned long mode); + + /** + * This method opens or creates a subkey relative to this key. + * + * @return nsIWindowsRegKey for the newly opened or created subkey. + */ + nsIWindowsRegKey createChild(in AString relPath, in unsigned long mode); + + /** + * This attribute returns the number of child keys. + */ + readonly attribute unsigned long childCount; + + /** + * This method returns the name of the n'th child key. + * + * @param index + * The index of the requested child key. + */ + AString getChildName(in unsigned long index); + + /** + * This method checks to see if the key has a child by the given name. + * + * @param name + * The name of the requested child key. + */ + boolean hasChild(in AString name); + + /** + * This attribute returns the number of values under this key. + */ + readonly attribute unsigned long valueCount; + + /** + * This method returns the name of the n'th value under this key. + * + * @param index + * The index of the requested value. + */ + AString getValueName(in unsigned long index); + + /** + * This method checks to see if the key has a value by the given name. + * + * @param name + * The name of the requested value. + */ + boolean hasValue(in AString name); + + /** + * This method removes a child key and all of its values. This method will + * fail if the key has any children of its own. + * + * @param relPath + * The relative path from this key to the key to be removed. + */ + void removeChild(in AString relPath); + + /** + * This method removes the value with the given name. + * + * @param name + * The name of the value to be removed. + */ + void removeValue(in AString name); + + /** + * This method returns the type of the value with the given name. The return + * value is one of the "TYPE_" constants defined above. + * + * @param name + * The name of the value to query. + */ + unsigned long getValueType(in AString name); + + /** + * This method reads the string contents of the named value as a Unicode + * string. + * + * @param name + * The name of the value to query. This parameter can be the empty + * string to request the key's default value. + */ + AString readStringValue(in AString name); + + /** + * This method reads the integer contents of the named value. + * + * @param name + * The name of the value to query. + */ + unsigned long readIntValue(in AString name); + + /** + * This method reads the 64-bit integer contents of the named value. + * + * @param name + * The name of the value to query. + */ + unsigned long long readInt64Value(in AString name); + + /** + * This method reads the binary contents of the named value under this key. + * + * JavaScript callers should take care with the result of this method since + * it will be byte-expanded to form a JS string. (The binary data will be + * treated as an ISO-Latin-1 character string, which it is not). + * + * @param name + * The name of the value to query. + */ + ACString readBinaryValue(in AString name); + + /** + * This method writes the unicode string contents of the named value. The + * value will be created if it does not already exist. + * + * @param name + * The name of the value to modify. This parameter can be the empty + * string to modify the key's default value. + * @param data + * The data for the value to modify. + */ + void writeStringValue(in AString name, in AString data); + + /** + * This method writes the integer contents of the named value. The value + * will be created if it does not already exist. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeIntValue(in AString name, in unsigned long data); + + /** + * This method writes the 64-bit integer contents of the named value. The + * value will be created if it does not already exist. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeInt64Value(in AString name, in unsigned long long data); + + /** + * This method writes the binary contents of the named value. The value will + * be created if it does not already exist. + * + * JavaScript callers should take care with the value passed to this method + * since it will be truncated from a JS string (unicode) to a ISO-Latin-1 + * string. (The binary data will be treated as an ISO-Latin-1 character + * string, which it is not). So, JavaScript callers should only pass + * character values in the range \u0000 to \u00FF, or else data loss will + * occur. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeBinaryValue(in AString name, in ACString data); +}; diff --git a/xpcom/ds/nsIWritablePropertyBag.idl b/xpcom/ds/nsIWritablePropertyBag.idl new file mode 100644 index 0000000000..e916b7ccd6 --- /dev/null +++ b/xpcom/ds/nsIWritablePropertyBag.idl @@ -0,0 +1,27 @@ +/* -*- 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/. */ + +/* nsIVariant based writable Property Bag support. */ + +#include "nsIPropertyBag.idl" + +[scriptable, uuid(96fc4671-eeb4-4823-9421-e50fb70ad353)] +interface nsIWritablePropertyBag : nsIPropertyBag +{ + /** + * Set a property with the given name to the given value. If + * a property already exists with the given name, it is + * overwritten. + */ + void setProperty(in AString name, in nsIVariant value); + + /** + * Delete a property with the given name. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * exist. + */ + void deleteProperty(in AString name); +}; diff --git a/xpcom/ds/nsIWritablePropertyBag2.idl b/xpcom/ds/nsIWritablePropertyBag2.idl new file mode 100644 index 0000000000..50f093905d --- /dev/null +++ b/xpcom/ds/nsIWritablePropertyBag2.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsIPropertyBag2.idl" + +[scriptable, uuid(9cfd1587-360e-4957-a58f-4c2b1c5e7ed9)] +interface nsIWritablePropertyBag2 : nsIPropertyBag2 +{ + void setPropertyAsInt32 (in AString prop, in int32_t value); + void setPropertyAsUint32 (in AString prop, in uint32_t value); + void setPropertyAsInt64 (in AString prop, in int64_t value); + void setPropertyAsUint64 (in AString prop, in uint64_t value); + void setPropertyAsDouble (in AString prop, in double value); + void setPropertyAsAString (in AString prop, in AString value); + void setPropertyAsACString (in AString prop, in ACString value); + void setPropertyAsAUTF8String (in AString prop, in AUTF8String value); + void setPropertyAsBool (in AString prop, in boolean value); + void setPropertyAsInterface (in AString prop, in nsISupports value); +}; diff --git a/xpcom/ds/nsInterfaceHashtable.h b/xpcom/ds/nsInterfaceHashtable.h new file mode 100644 index 0000000000..644864cf8f --- /dev/null +++ b/xpcom/ds/nsInterfaceHashtable.h @@ -0,0 +1,14 @@ +/* -*- 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 nsInterfaceHashtable_h__ +#define nsInterfaceHashtable_h__ + +#include "nsRefCountedHashtable.h" +#include "nsHashKeys.h" +#include "nsCOMPtr.h" + +#endif // nsInterfaceHashtable_h__ diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h new file mode 100644 index 0000000000..527e0c3eb2 --- /dev/null +++ b/xpcom/ds/nsMathUtils.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMathUtils_h__ +#define nsMathUtils_h__ + +#include "nscore.h" +#include <cmath> +#include <float.h> + +#if defined(XP_SOLARIS) +# include <ieeefp.h> +#endif + +/* + * round + */ +inline double NS_round(double aNum) { + return aNum >= 0.0 ? floor(aNum + 0.5) : ceil(aNum - 0.5); +} +inline float NS_roundf(float aNum) { + return aNum >= 0.0f ? floorf(aNum + 0.5f) : ceilf(aNum - 0.5f); +} +inline int32_t NS_lround(double aNum) { + return aNum >= 0.0 ? int32_t(aNum + 0.5) : int32_t(aNum - 0.5); +} + +/* NS_roundup30 rounds towards infinity for positive and */ +/* negative numbers. */ + +#if defined(XP_WIN) && defined(_M_IX86) && !defined(__GNUC__) && \ + !defined(__clang__) +inline int32_t NS_lroundup30(float x) { + /* Code derived from Laurent de Soras' paper at */ + /* http://ldesoras.free.fr/doc/articles/rounding_en.pdf */ + + /* Rounding up on Windows is expensive using the float to */ + /* int conversion and the floor function. A faster */ + /* approach is to use f87 rounding while assuming the */ + /* default rounding mode of rounding to the nearest */ + /* integer. This rounding mode, however, actually rounds */ + /* to the nearest integer so we add the floating point */ + /* number to itself and add our rounding factor before */ + /* doing the conversion to an integer. We then do a right */ + /* shift of one bit on the integer to divide by two. */ + + /* This routine doesn't handle numbers larger in magnitude */ + /* than 2^30 but this is fine for NSToCoordRound because */ + /* Coords are limited to 2^30 in magnitude. */ + + static const double round_to_nearest = 0.5f; + int i; + + __asm { + fld x ; load fp argument + fadd st, st(0) ; double it + fadd round_to_nearest ; add the rounding factor + fistp dword ptr i ; convert the result to int + } + return i >> 1; /* divide by 2 */ +} +#endif /* XP_WIN && _M_IX86 && !__GNUC__ */ + +inline int32_t NS_lroundf(float aNum) { + return aNum >= 0.0f ? int32_t(aNum + 0.5f) : int32_t(aNum - 0.5f); +} + +/* + * hypot. We don't need a super accurate version of this, if a platform + * turns up with none of the possibilities below it would be okay to fall + * back to sqrt(x*x + y*y). + */ +inline double NS_hypot(double aNum1, double aNum2) { +#ifdef __GNUC__ + return __builtin_hypot(aNum1, aNum2); +#elif defined _WIN32 + return _hypot(aNum1, aNum2); +#else + return hypot(aNum1, aNum2); +#endif +} + +/** + * Check whether a floating point number is finite (not +/-infinity and not a + * NaN value). + */ +inline bool NS_finite(double aNum) { +#ifdef WIN32 + // NOTE: '!!' casts an int to bool without spamming MSVC warning C4800. + return !!_finite(aNum); +#else + return std::isfinite(aNum); +#endif +} + +/** + * Returns the result of the modulo of x by y using a floored division. + * fmod(x, y) is using a truncated division. + * The main difference is that the result of this method will have the sign of + * y while the result of fmod(x, y) will have the sign of x. + */ +inline double NS_floorModulo(double aNum1, double aNum2) { + return (aNum1 - aNum2 * floor(aNum1 / aNum2)); +} + +#endif diff --git a/xpcom/ds/nsObserverList.cpp b/xpcom/ds/nsObserverList.cpp new file mode 100644 index 0000000000..4f21c4fcc9 --- /dev/null +++ b/xpcom/ds/nsObserverList.cpp @@ -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/. */ + +#include "nsObserverList.h" + +#include "mozilla/ResultExtensions.h" +#include "mozilla/Try.h" +#include "nsCOMArray.h" +#include "xpcpublic.h" + +nsresult nsObserverList::AddObserver(nsIObserver* anObserver, bool ownsWeak) { + NS_ASSERTION(anObserver, "Null input"); + + MOZ_TRY(mObservers.AppendWeakElement(anObserver, ownsWeak)); + return NS_OK; +} + +nsresult nsObserverList::RemoveObserver(nsIObserver* anObserver) { + NS_ASSERTION(anObserver, "Null input"); + + MOZ_TRY(mObservers.RemoveWeakElement(anObserver)); + return NS_OK; +} + +void nsObserverList::GetObserverList(nsISimpleEnumerator** anEnumerator) { + RefPtr<nsObserverEnumerator> e(new nsObserverEnumerator(this)); + e.forget(anEnumerator); +} + +nsCOMArray<nsIObserver> nsObserverList::ReverseCloneObserverArray() { + nsCOMArray<nsIObserver> array; + array.SetCapacity(mObservers.Length()); + + // XXX This could also use RemoveElementsBy if we lifted the promise to fill + // aArray in reverse order. Although there shouldn't be anyone explicitly + // relying on the order, changing this might cause subtle problems, so we + // better leave it as it is. + + for (int32_t i = mObservers.Length() - 1; i >= 0; --i) { + nsCOMPtr<nsIObserver> observer = mObservers[i].GetValue(); + if (observer) { + array.AppendElement(observer.forget()); + } else { + // the object has gone away, remove the weakref + mObservers.RemoveElementAt(i); + } + } + + return array; +} + +void nsObserverList::AppendStrongObservers(nsCOMArray<nsIObserver>& aArray) { + aArray.SetCapacity(aArray.Length() + mObservers.Length()); + + for (int32_t i = mObservers.Length() - 1; i >= 0; --i) { + if (!mObservers[i].IsWeak()) { + nsCOMPtr<nsIObserver> observer = mObservers[i].GetValue(); + aArray.AppendObject(observer); + } + } +} + +void nsObserverList::NotifyObservers(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + const nsCOMArray<nsIObserver> observers = ReverseCloneObserverArray(); + + for (int32_t i = 0; i < observers.Count(); ++i) { + observers[i]->Observe(aSubject, aTopic, someData); + } +} + +nsObserverEnumerator::nsObserverEnumerator(nsObserverList* aObserverList) + : mIndex(0), mObservers(aObserverList->ReverseCloneObserverArray()) {} + +NS_IMETHODIMP +nsObserverEnumerator::HasMoreElements(bool* aResult) { + *aResult = (mIndex < mObservers.Count()); + return NS_OK; +} + +NS_IMETHODIMP +nsObserverEnumerator::GetNext(nsISupports** aResult) { + if (mIndex == mObservers.Count()) { + return NS_ERROR_FAILURE; + } + + NS_ADDREF(*aResult = mObservers[mIndex]); + ++mIndex; + return NS_OK; +} diff --git a/xpcom/ds/nsObserverList.h b/xpcom/ds/nsObserverList.h new file mode 100644 index 0000000000..9f209e40d6 --- /dev/null +++ b/xpcom/ds/nsObserverList.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 nsObserverList_h___ +#define nsObserverList_h___ + +#include "nsISupports.h" +#include "nsCOMArray.h" +#include "nsIObserver.h" +#include "nsHashKeys.h" +#include "nsMaybeWeakPtr.h" +#include "nsSimpleEnumerator.h" +#include "mozilla/Attributes.h" + +class nsObserverList : public nsCharPtrHashKey { + friend class nsObserverService; + + public: + explicit nsObserverList(const char* aKey) : nsCharPtrHashKey(aKey) { + MOZ_COUNT_CTOR(nsObserverList); + } + + nsObserverList(nsObserverList&& aOther) + : nsCharPtrHashKey(std::move(aOther)), + mObservers(std::move(aOther.mObservers)) { + MOZ_COUNT_CTOR(nsObserverList); + } + + MOZ_COUNTED_DTOR(nsObserverList) + + [[nodiscard]] nsresult AddObserver(nsIObserver* aObserver, bool aOwnsWeak); + [[nodiscard]] nsresult RemoveObserver(nsIObserver* aObserver); + + void NotifyObservers(nsISupports* aSubject, const char* aTopic, + const char16_t* aSomeData); + void GetObserverList(nsISimpleEnumerator** aEnumerator); + + // Clone an array with the observers of this category. + // The array is filled in last-added-first order. + nsCOMArray<nsIObserver> ReverseCloneObserverArray(); + + // Like FillObserverArray(), but only for strongly held observers. + void AppendStrongObservers(nsCOMArray<nsIObserver>& aArray); + + private: + nsMaybeWeakPtrArray<nsIObserver> mObservers; +}; + +class nsObserverEnumerator final : public nsSimpleEnumerator { + public: + NS_DECL_NSISIMPLEENUMERATOR + + explicit nsObserverEnumerator(nsObserverList* aObserverList); + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIObserver); } + + private: + ~nsObserverEnumerator() override = default; + + int32_t mIndex; // Counts up from 0 + nsCOMArray<nsIObserver> mObservers; +}; + +#endif /* nsObserverList_h___ */ diff --git a/xpcom/ds/nsObserverService.cpp b/xpcom/ds/nsObserverService.cpp new file mode 100644 index 0000000000..e0fd430cd4 --- /dev/null +++ b/xpcom/ds/nsObserverService.cpp @@ -0,0 +1,358 @@ +/* -*- 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 "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsIConsoleService.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIScriptError.h" +#include "nsObserverService.h" +#include "nsObserverList.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsEnumeratorUtils.h" +#include "xpcpublic.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Try.h" +#include "nsString.h" + +// Log module for nsObserverService logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=ObserverService:5 +// set MOZ_LOG_FILE=service.log +// +// This enables LogLevel::Debug level information and places all output in +// the file service.log. +static mozilla::LazyLogModule sObserverServiceLog("ObserverService"); +#define LOG(x) MOZ_LOG(sObserverServiceLog, mozilla::LogLevel::Debug, x) + +using namespace mozilla; + +NS_IMETHODIMP +nsObserverService::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + struct SuspectObserver { + SuspectObserver(const char* aTopic, size_t aReferentCount) + : mTopic(aTopic), mReferentCount(aReferentCount) {} + const char* mTopic; + size_t mReferentCount; + }; + + size_t totalNumStrong = 0; + size_t totalNumWeakAlive = 0; + size_t totalNumWeakDead = 0; + nsTArray<SuspectObserver> suspectObservers; + + for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) { + nsObserverList* observerList = iter.Get(); + if (!observerList) { + continue; + } + + size_t topicNumStrong = 0; + size_t topicNumWeakAlive = 0; + size_t topicNumWeakDead = 0; + + nsMaybeWeakPtrArray<nsIObserver>& observers = observerList->mObservers; + for (uint32_t i = 0; i < observers.Length(); i++) { + if (observers[i].IsWeak()) { + nsCOMPtr<nsIObserver> ref = observers[i].GetValue(); + if (ref) { + topicNumWeakAlive++; + } else { + topicNumWeakDead++; + } + } else { + topicNumStrong++; + } + } + + totalNumStrong += topicNumStrong; + totalNumWeakAlive += topicNumWeakAlive; + totalNumWeakDead += topicNumWeakDead; + + // Keep track of topics that have a suspiciously large number + // of referents (symptom of leaks). + size_t topicTotal = topicNumStrong + topicNumWeakAlive + topicNumWeakDead; + if (topicTotal > kSuspectReferentCount) { + SuspectObserver suspect(observerList->GetKey(), topicTotal); + suspectObservers.AppendElement(suspect); + } + } + + // These aren't privacy-sensitive and so don't need anonymizing. + for (uint32_t i = 0; i < suspectObservers.Length(); i++) { + SuspectObserver& suspect = suspectObservers[i]; + nsPrintfCString suspectPath("observer-service-suspect/referent(topic=%s)", + suspect.mTopic); + aHandleReport->Callback( + /* process */ ""_ns, suspectPath, KIND_OTHER, UNITS_COUNT, + suspect.mReferentCount, + nsLiteralCString("A topic with a suspiciously large number of " + "referents. This may be symptomatic of a leak " + "if the number of referents is high with " + "respect to the number of windows."), + aData); + } + + MOZ_COLLECT_REPORT( + "observer-service/referent/strong", KIND_OTHER, UNITS_COUNT, + totalNumStrong, + "The number of strong references held by the observer service."); + + MOZ_COLLECT_REPORT( + "observer-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT, + totalNumWeakAlive, + "The number of weak references held by the observer service that are " + "still alive."); + + MOZ_COLLECT_REPORT( + "observer-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT, + totalNumWeakDead, + "The number of weak references held by the observer service that are " + "dead."); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsObserverService Implementation + +NS_IMPL_ISUPPORTS(nsObserverService, nsIObserverService, nsObserverService, + nsIMemoryReporter) + +nsObserverService::nsObserverService() : mShuttingDown(false) {} + +nsObserverService::~nsObserverService(void) { Shutdown(); } + +void nsObserverService::RegisterReporter() { RegisterWeakMemoryReporter(this); } + +void nsObserverService::Shutdown() { + if (mShuttingDown) { + return; + } + + mShuttingDown = true; + UnregisterWeakMemoryReporter(this); + mObserverTopicTable.Clear(); +} + +nsresult nsObserverService::Create(const nsIID& aIID, void** aInstancePtr) { + LOG(("nsObserverService::Create()")); + + RefPtr<nsObserverService> os = new nsObserverService(); + + // The memory reporter can not be immediately registered here because + // the nsMemoryReporterManager may attempt to get the nsObserverService + // during initialization, causing a recursive GetService. + NS_DispatchToCurrentThread( + NewRunnableMethod("nsObserverService::RegisterReporter", os, + &nsObserverService::RegisterReporter)); + + return os->QueryInterface(aIID, aInstancePtr); +} + +nsresult nsObserverService::EnsureValidCall() const { + if (!NS_IsMainThread()) { + MOZ_CRASH("Using observer service off the main thread!"); + return NS_ERROR_UNEXPECTED; + } + + if (mShuttingDown) { + NS_ERROR("Using observer service after XPCOM shutdown!"); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + return NS_OK; +} + +nsresult nsObserverService::FilterHttpOnTopics(const char* aTopic) { + // Specifically allow http-on-opening-request and http-on-stop-request in the + // child process; see bug 1269765. + if (mozilla::net::IsNeckoChild() && !strncmp(aTopic, "http-on-", 8) && + strcmp(aTopic, "http-on-failed-opening-request") && + strcmp(aTopic, "http-on-opening-request") && + strcmp(aTopic, "http-on-stop-request") && + strcmp(aTopic, "http-on-image-cache-response")) { + nsCOMPtr<nsIConsoleService> console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + nsCOMPtr<nsIScriptError> error( + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(u"http-on-* observers only work in the parent process"_ns, + u""_ns, u""_ns, 0, 0, nsIScriptError::warningFlag, + "chrome javascript"_ns, false /* from private window */, + true /* from chrome context */); + console->LogMessage(error); + + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic, + bool aOwnsWeak) { + LOG(("nsObserverService::AddObserver(%p: %s, %s)", (void*)aObserver, aTopic, + aOwnsWeak ? "weak" : "strong")); + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_TRY(FilterHttpOnTopics(aTopic)); + + nsObserverList* observerList = mObserverTopicTable.PutEntry(aTopic); + if (!observerList) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return observerList->AddObserver(aObserver, aOwnsWeak); +} + +NS_IMETHODIMP +nsObserverService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) { + LOG(("nsObserverService::RemoveObserver(%p: %s)", (void*)aObserver, aTopic)); + + if (mShuttingDown) { + // The service is shutting down. Let's ignore this call. + return NS_OK; + } + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (!observerList) { + return NS_ERROR_FAILURE; + } + + return observerList->RemoveObserver(aObserver); +} + +NS_IMETHODIMP +nsObserverService::EnumerateObservers(const char* aTopic, + nsISimpleEnumerator** anEnumerator) { + LOG(("nsObserverService::EnumerateObservers(%s)", aTopic)); + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!anEnumerator) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (!observerList) { + return NS_NewEmptyEnumerator(anEnumerator); + } + + observerList->GetObserverList(anEnumerator); + return NS_OK; +} + +// Enumerate observers of aTopic and call Observe on each. +NS_IMETHODIMP nsObserverService::NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) { + LOG(("nsObserverService::NotifyObservers(%s)", aTopic)); + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(AppShutdown::IsNoOrLegalShutdownTopic(aTopic)); + + AUTO_PROFILER_MARKER_TEXT("NotifyObservers", OTHER, MarkerStack::Capture(), + nsDependentCString(aTopic)); + AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE( + "nsObserverService::NotifyObservers", OTHER, aTopic); + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (observerList) { + observerList->NotifyObservers(aSubject, aTopic, aSomeData); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsObserverService::UnmarkGrayStrongObservers() { + MOZ_TRY(EnsureValidCall()); + + nsCOMArray<nsIObserver> strongObservers; + for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) { + nsObserverList* aObserverList = iter.Get(); + if (aObserverList) { + aObserverList->AppendStrongObservers(strongObservers); + } + } + + for (uint32_t i = 0; i < strongObservers.Length(); ++i) { + xpc_TryUnmarkWrappedGrayObject(strongObservers[i]); + } + + return NS_OK; +} + +bool nsObserverService::HasObservers(const char* aTopic) { + return mObserverTopicTable.Contains(aTopic); +} + +namespace { + +class NotifyWhenScriptSafeRunnable : public mozilla::Runnable { + public: + NotifyWhenScriptSafeRunnable(nsIObserverService* aObs, nsISupports* aSubject, + const char* aTopic, const char16_t* aData) + : mozilla::Runnable("NotifyWhenScriptSafeRunnable"), + mObs(aObs), + mSubject(aSubject), + mTopic(aTopic) { + if (aData) { + mData.Assign(aData); + } else { + mData.SetIsVoid(true); + } + } + + NS_IMETHOD Run() override { + const char16_t* data = mData.IsVoid() ? nullptr : mData.get(); + return mObs->NotifyObservers(mSubject, mTopic.get(), data); + } + + private: + nsCOMPtr<nsIObserverService> mObs; + nsCOMPtr<nsISupports> mSubject; + nsCString mTopic; + nsString mData; +}; + +} // namespace + +nsresult nsIObserverService::NotifyWhenScriptSafe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (nsContentUtils::IsSafeToRunScript()) { + return NotifyObservers(aSubject, aTopic, aData); + } + + nsContentUtils::AddScriptRunner(MakeAndAddRef<NotifyWhenScriptSafeRunnable>( + this, aSubject, aTopic, aData)); + return NS_OK; +} diff --git a/xpcom/ds/nsObserverService.h b/xpcom/ds/nsObserverService.h new file mode 100644 index 0000000000..f4e445995b --- /dev/null +++ b/xpcom/ds/nsObserverService.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 nsObserverService_h___ +#define nsObserverService_h___ + +#include "nsIObserverService.h" +#include "nsObserverList.h" +#include "nsIMemoryReporter.h" +#include "nsTHashtable.h" +#include "mozilla/Attributes.h" + +// {D07F5195-E3D1-11d2-8ACD-00105A1B8860} +#define NS_OBSERVERSERVICE_CID \ + { \ + 0xd07f5195, 0xe3d1, 0x11d2, { \ + 0x8a, 0xcd, 0x0, 0x10, 0x5a, 0x1b, 0x88, 0x60 \ + } \ + } + +class nsObserverService final : public nsIObserverService, + public nsIMemoryReporter { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_OBSERVERSERVICE_CID) + + nsObserverService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVERSERVICE + NS_DECL_NSIMEMORYREPORTER + + void Shutdown(); + + [[nodiscard]] static nsresult Create(const nsIID& aIID, void** aInstancePtr); + + // Unmark any strongly held observers implemented in JS so the cycle + // collector will not traverse them. + NS_IMETHOD UnmarkGrayStrongObservers(); + + private: + ~nsObserverService(void); + void RegisterReporter(); + nsresult EnsureValidCall() const; + nsresult FilterHttpOnTopics(const char* aTopic); + + static const size_t kSuspectReferentCount = 100; + bool mShuttingDown; + nsTHashtable<nsObserverList> mObserverTopicTable; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsObserverService, NS_OBSERVERSERVICE_CID) + +#endif /* nsObserverService_h___ */ diff --git a/xpcom/ds/nsPersistentProperties.cpp b/xpcom/ds/nsPersistentProperties.cpp new file mode 100644 index 0000000000..51a4891e19 --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.cpp @@ -0,0 +1,583 @@ +/* -*- 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 "nsArrayEnumerator.h" +#include "nsID.h" +#include "nsCOMArray.h" +#include "nsUnicharInputStream.h" +#include "nsPrintfCString.h" + +#include "nsPersistentProperties.h" +#include "nsIProperties.h" + +#include "mozilla/ArenaAllocatorExtensions.h" + +using mozilla::ArenaStrdup; + +struct PropertyTableEntry : public PLDHashEntryHdr { + // both of these are arena-allocated + const char* mKey; + const char16_t* mValue; +}; + +static const struct PLDHashTableOps property_HashTableOps = { + PLDHashTable::HashStringKey, + PLDHashTable::MatchStringKey, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr, +}; + +// +// parser stuff +// +enum EParserState { + eParserState_AwaitingKey, + eParserState_Key, + eParserState_AwaitingValue, + eParserState_Value, + eParserState_Comment +}; + +enum EParserSpecial { + eParserSpecial_None, // not parsing a special character + eParserSpecial_Escaped, // awaiting a special character + eParserSpecial_Unicode // parsing a \Uxxx value +}; + +class MOZ_STACK_CLASS nsPropertiesParser { + public: + explicit nsPropertiesParser(nsIPersistentProperties* aProps) + : mUnicodeValuesRead(0), + mUnicodeValue(u'\0'), + mHaveMultiLine(false), + mMultiLineCanSkipN(false), + mMinLength(0), + mState(eParserState_AwaitingKey), + mSpecialState(eParserSpecial_None), + mProps(aProps) {} + + void FinishValueState(nsAString& aOldValue) { + static const char trimThese[] = " \t"; + mKey.Trim(trimThese, false, true); + + // This is really ugly hack but it should be fast + char16_t backup_char; + uint32_t minLength = mMinLength; + if (minLength) { + backup_char = mValue[minLength - 1]; + mValue.SetCharAt('x', minLength - 1); + } + mValue.Trim(trimThese, false, true); + if (minLength) { + mValue.SetCharAt(backup_char, minLength - 1); + } + + mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue); + mSpecialState = eParserSpecial_None; + WaitForKey(); + } + + EParserState GetState() { return mState; } + + static nsresult SegmentWriter(nsIUnicharInputStream* aStream, void* aClosure, + const char16_t* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount); + + nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength); + + private: + bool ParseValueCharacter( + char16_t aChar, // character that is just being parsed + const char16_t* aCur, // pointer to character aChar in the buffer + const char16_t*& aTokenStart, // string copying is done in blocks as big + // as possible, aTokenStart points to the + // beginning of this block + nsAString& aOldValue); // when duplicate property is found, new value + // is stored into hashtable and the old one is + // placed in this variable + + void WaitForKey() { mState = eParserState_AwaitingKey; } + + void EnterKeyState() { + mKey.Truncate(); + mState = eParserState_Key; + } + + void WaitForValue() { mState = eParserState_AwaitingValue; } + + void EnterValueState() { + mValue.Truncate(); + mMinLength = 0; + mState = eParserState_Value; + mSpecialState = eParserSpecial_None; + } + + void EnterCommentState() { mState = eParserState_Comment; } + + nsAutoString mKey; + nsAutoString mValue; + + uint32_t mUnicodeValuesRead; // should be 4! + char16_t mUnicodeValue; // currently parsed unicode value + bool mHaveMultiLine; // is TRUE when last processed characters form + // any of following sequences: + // - "\\\r" + // - "\\\n" + // - "\\\r\n" + // - any sequence above followed by any + // combination of ' ' and '\t' + bool mMultiLineCanSkipN; // TRUE if "\\\r" was detected + uint32_t mMinLength; // limit right trimming at the end to not trim + // escaped whitespaces + EParserState mState; + // if we see a '\' then we enter this special state + EParserSpecial mSpecialState; + nsCOMPtr<nsIPersistentProperties> mProps; +}; + +inline bool IsWhiteSpace(char16_t aChar) { + return (aChar == ' ') || (aChar == '\t') || (aChar == '\r') || + (aChar == '\n'); +} + +inline bool IsEOL(char16_t aChar) { return (aChar == '\r') || (aChar == '\n'); } + +bool nsPropertiesParser::ParseValueCharacter(char16_t aChar, + const char16_t* aCur, + const char16_t*& aTokenStart, + nsAString& aOldValue) { + switch (mSpecialState) { + // the normal state - look for special characters + case eParserSpecial_None: + switch (aChar) { + case '\\': + if (mHaveMultiLine) { + // there is nothing to append to mValue yet + mHaveMultiLine = false; + } else { + mValue += Substring(aTokenStart, aCur); + } + + mSpecialState = eParserSpecial_Escaped; + break; + + case '\n': + // if we detected multiline and got only "\\\r" ignore next "\n" if + // any + if (mHaveMultiLine && mMultiLineCanSkipN) { + // but don't allow another '\n' to be skipped + mMultiLineCanSkipN = false; + // Now there is nothing to append to the mValue since we are + // skipping whitespaces at the beginning of the new line of the + // multiline property. Set aTokenStart properly to ensure that + // nothing is appended if we find regular line-end or the end of the + // buffer. + aTokenStart = aCur + 1; + break; + } + [[fallthrough]]; + + case '\r': + // we're done! We have a key and value + mValue += Substring(aTokenStart, aCur); + FinishValueState(aOldValue); + mHaveMultiLine = false; + break; + + default: + // there is nothing to do with normal characters, + // but handle multilines correctly + if (mHaveMultiLine) { + if (aChar == ' ' || aChar == '\t') { + // don't allow another '\n' to be skipped + mMultiLineCanSkipN = false; + // Now there is nothing to append to the mValue since we are + // skipping whitespaces at the beginning of the new line of the + // multiline property. Set aTokenStart properly to ensure that + // nothing is appended if we find regular line-end or the end of + // the buffer. + aTokenStart = aCur + 1; + break; + } + mHaveMultiLine = false; + aTokenStart = aCur; + } + break; // from switch on (aChar) + } + break; // from switch on (mSpecialState) + + // saw a \ character, so parse the character after that + case eParserSpecial_Escaped: + // probably want to start parsing at the next token + // other characters, like 'u' might override this + aTokenStart = aCur + 1; + mSpecialState = eParserSpecial_None; + + switch (aChar) { + // the easy characters - \t, \n, and so forth + case 't': + mValue += char16_t('\t'); + mMinLength = mValue.Length(); + break; + case 'n': + mValue += char16_t('\n'); + mMinLength = mValue.Length(); + break; + case 'r': + mValue += char16_t('\r'); + mMinLength = mValue.Length(); + break; + case '\\': + mValue += char16_t('\\'); + break; + + // switch to unicode mode! + case 'u': + case 'U': + mSpecialState = eParserSpecial_Unicode; + mUnicodeValuesRead = 0; + mUnicodeValue = 0; + break; + + // a \ immediately followed by a newline means we're going multiline + case '\r': + case '\n': + mHaveMultiLine = true; + mMultiLineCanSkipN = (aChar == '\r'); + mSpecialState = eParserSpecial_None; + break; + + default: + // don't recognize the character, so just append it + mValue += aChar; + break; + } + break; + + // we're in the middle of parsing a 4-character unicode value + // like \u5f39 + case eParserSpecial_Unicode: + if ('0' <= aChar && aChar <= '9') { + mUnicodeValue = (mUnicodeValue << 4) | (aChar - '0'); + } else if ('a' <= aChar && aChar <= 'f') { + mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'a' + 0x0a); + } else if ('A' <= aChar && aChar <= 'F') { + mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'A' + 0x0a); + } else { + // non-hex character. Append what we have, and move on. + mValue += mUnicodeValue; + mMinLength = mValue.Length(); + mSpecialState = eParserSpecial_None; + + // leave aTokenStart at this unknown character, so it gets appended + aTokenStart = aCur; + + // ensure parsing this non-hex character again + return false; + } + + if (++mUnicodeValuesRead >= 4) { + aTokenStart = aCur + 1; + mSpecialState = eParserSpecial_None; + mValue += mUnicodeValue; + mMinLength = mValue.Length(); + } + + break; + } + + return true; +} + +nsresult nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream, + void* aClosure, + const char16_t* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + nsPropertiesParser* parser = static_cast<nsPropertiesParser*>(aClosure); + parser->ParseBuffer(aFromSegment, aCount); + + *aWriteCount = aCount; + return NS_OK; +} + +nsresult nsPropertiesParser::ParseBuffer(const char16_t* aBuffer, + uint32_t aBufferLength) { + const char16_t* cur = aBuffer; + const char16_t* end = aBuffer + aBufferLength; + + // points to the start/end of the current key or value + const char16_t* tokenStart = nullptr; + + // if we're in the middle of parsing a key or value, make sure + // the current token points to the beginning of the current buffer + if (mState == eParserState_Key || mState == eParserState_Value) { + tokenStart = aBuffer; + } + + nsAutoString oldValue; + + while (cur != end) { + char16_t c = *cur; + + switch (mState) { + case eParserState_AwaitingKey: + if (c == '#' || c == '!') { + EnterCommentState(); + } + + else if (!IsWhiteSpace(c)) { + // not a comment, not whitespace, we must have found a key! + EnterKeyState(); + tokenStart = cur; + } + break; + + case eParserState_Key: + if (c == '=' || c == ':') { + mKey += Substring(tokenStart, cur); + WaitForValue(); + } + break; + + case eParserState_AwaitingValue: + if (IsEOL(c)) { + // no value at all! mimic the normal value-ending + EnterValueState(); + FinishValueState(oldValue); + } + + // ignore white space leading up to the value + else if (!IsWhiteSpace(c)) { + tokenStart = cur; + EnterValueState(); + + // make sure to handle this first character + if (ParseValueCharacter(c, cur, tokenStart, oldValue)) { + cur++; + } + // If the character isn't consumed, don't do cur++ and parse + // the character again. This can happen f.e. for char 'X' in sequence + // "\u00X". This character can be control character and must be + // processed again. + continue; + } + break; + + case eParserState_Value: + if (ParseValueCharacter(c, cur, tokenStart, oldValue)) { + cur++; + } + // See few lines above for reason of doing this + continue; + + case eParserState_Comment: + // stay in this state till we hit EOL + if (c == '\r' || c == '\n') { + WaitForKey(); + } + break; + } + + // finally, advance to the next character + cur++; + } + + // if we're still parsing the value and are in eParserSpecial_None, then + // append whatever we have.. + if (mState == eParserState_Value && tokenStart && + mSpecialState == eParserSpecial_None) { + mValue += Substring(tokenStart, cur); + } + // if we're still parsing the key, then append whatever we have.. + else if (mState == eParserState_Key && tokenStart) { + mKey += Substring(tokenStart, cur); + } + + return NS_OK; +} + +nsPersistentProperties::nsPersistentProperties() + : mIn(nullptr), + mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16) {} + +nsPersistentProperties::~nsPersistentProperties() = default; + +size_t nsPersistentProperties::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + // The memory used by mTable is accounted for in mArena. + size_t n = 0; + n += mArena.SizeOfExcludingThis(aMallocSizeOf); + n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + return aMallocSizeOf(this) + n; +} + +NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties, + nsIProperties) + +NS_IMETHODIMP +nsPersistentProperties::Load(nsIInputStream* aIn) { + nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn)); + + if (rv != NS_OK) { + NS_WARNING("Error creating UnicharInputStream"); + return NS_ERROR_FAILURE; + } + + nsPropertiesParser parser(this); + + uint32_t nProcessed; + // If this 4096 is changed to some other value, make sure to adjust + // the bug121341.properties test file accordingly. + while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter, + &parser, 4096, &nProcessed)) && + nProcessed != 0) + ; + mIn = nullptr; + if (NS_FAILED(rv)) { + return rv; + } + + // We may have an unprocessed value at this point + // if the last line did not have a proper line ending. + if (parser.GetState() == eParserState_Value) { + nsAutoString oldValue; + parser.FinishValueState(oldValue); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::SetStringProperty(const nsACString& aKey, + const nsAString& aNewValue, + nsAString& aOldValue) { + const nsCString& flatKey = PromiseFlatCString(aKey); + auto entry = static_cast<PropertyTableEntry*>(mTable.Add(flatKey.get())); + + if (entry->mKey) { + aOldValue = entry->mValue; + NS_WARNING( + nsPrintfCString("the property %s already exists", flatKey.get()).get()); + } else { + aOldValue.Truncate(); + } + + entry->mKey = ArenaStrdup(flatKey, mArena); + entry->mValue = ArenaStrdup(aNewValue, mArena); + + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::GetStringProperty(const nsACString& aKey, + nsAString& aValue) { + const nsCString& flatKey = PromiseFlatCString(aKey); + + auto entry = static_cast<PropertyTableEntry*>(mTable.Search(flatKey.get())); + if (!entry) { + return NS_ERROR_FAILURE; + } + + aValue = entry->mValue; + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult) { + nsCOMArray<nsIPropertyElement> props; + + // We know the necessary size; we can avoid growing it while adding elements + props.SetCapacity(mTable.EntryCount()); + + // Step through hash entries populating a transient array + for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PropertyTableEntry*>(iter.Get()); + + RefPtr<nsPropertyElement> element = new nsPropertyElement( + nsDependentCString(entry->mKey), nsDependentString(entry->mValue)); + + if (!props.AppendObject(element)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_NewArrayEnumerator(aResult, props, NS_GET_IID(nsIPropertyElement)); +} + +//////////////////////////////////////////////////////////////////////////////// +// XXX Some day we'll unify the nsIPersistentProperties interface with +// nsIProperties, but until now... + +NS_IMETHODIMP +nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID, + void** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::Set(const char* aProp, nsISupports* value) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +nsPersistentProperties::Undefine(const char* aProp) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::Has(const char* aProp, bool* aResult) { + *aResult = !!mTable.Search(aProp); + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::GetKeys(nsTArray<nsCString>& aKeys) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyElement +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsPropertyElement::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsPropertyElement> propElem = new nsPropertyElement(); + return propElem->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement) + +NS_IMETHODIMP +nsPropertyElement::GetKey(nsACString& aReturnKey) { + aReturnKey = mKey; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::GetValue(nsAString& aReturnValue) { + aReturnValue = mValue; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::SetKey(const nsACString& aKey) { + mKey = aKey; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::SetValue(const nsAString& aValue) { + mValue = aValue; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/ds/nsPersistentProperties.h b/xpcom/ds/nsPersistentProperties.h new file mode 100644 index 0000000000..e35f938371 --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.h @@ -0,0 +1,57 @@ +/* -*- 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 nsPersistentProperties_h___ +#define nsPersistentProperties_h___ + +#include "nsIPersistentProperties2.h" +#include "PLDHashTable.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/Attributes.h" + +class nsIUnicharInputStream; + +class nsPersistentProperties final : public nsIPersistentProperties { + public: + nsPersistentProperties(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROPERTIES + NS_DECL_NSIPERSISTENTPROPERTIES + + private: + ~nsPersistentProperties(); + + protected: + nsCOMPtr<nsIUnicharInputStream> mIn; + + PLDHashTable mTable; + mozilla::ArenaAllocator<2048, 4> mArena; +}; + +class nsPropertyElement final : public nsIPropertyElement { + public: + nsPropertyElement() = default; + + nsPropertyElement(const nsACString& aKey, const nsAString& aValue) + : mKey(aKey), mValue(aValue) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTYELEMENT + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + ~nsPropertyElement() = default; + + protected: + nsCString mKey; + nsString mValue; +}; + +#endif /* nsPersistentProperties_h___ */ diff --git a/xpcom/ds/nsPointerHashKeys.h b/xpcom/ds/nsPointerHashKeys.h new file mode 100644 index 0000000000..0a4af3b2f8 --- /dev/null +++ b/xpcom/ds/nsPointerHashKeys.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/. */ + +/* Definitions for nsPtrHashKey<T> and nsVoidPtrHashKey. */ + +#ifndef nsPointerHashKeys_h +#define nsPointerHashKeys_h + +#include "nscore.h" + +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" + +#include "PLDHashTable.h" + +/** + * hashkey wrapper using T* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template <class T> +class nsPtrHashKey : public PLDHashEntryHdr { + public: + using KeyType = T*; + using KeyTypePointer = const T*; + + explicit nsPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {} + nsPtrHashKey(nsPtrHashKey&& aToMove) = default; + ~nsPtrHashKey() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashGeneric(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + protected: + T* MOZ_NON_OWNING_REF mKey; +}; + +using nsVoidPtrHashKey = nsPtrHashKey<const void>; + +#endif // nsPointerHashKeys_h diff --git a/xpcom/ds/nsProperties.cpp b/xpcom/ds/nsProperties.cpp new file mode 100644 index 0000000000..2094981634 --- /dev/null +++ b/xpcom/ds/nsProperties.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 "nsProperties.h" + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsProperties, nsIProperties) + +NS_IMETHODIMP +nsProperties::Get(const char* prop, const nsIID& uuid, void** result) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsISupports> value; + if (!nsProperties_HashBase::Get(prop, getter_AddRefs(value))) { + return NS_ERROR_FAILURE; + } + return (value) ? value->QueryInterface(uuid, result) : NS_ERROR_NO_INTERFACE; +} + +NS_IMETHODIMP +nsProperties::Set(const char* prop, nsISupports* value) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + InsertOrUpdate(prop, value); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::Undefine(const char* prop) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + return nsProperties_HashBase::Remove(prop) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsProperties::Has(const char* prop, bool* result) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + *result = nsProperties_HashBase::Contains(prop); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::GetKeys(nsTArray<nsCString>& aKeys) { + mozilla::AppendToArray(aKeys, this->Keys()); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/ds/nsProperties.h b/xpcom/ds/nsProperties.h new file mode 100644 index 0000000000..c45c7361ea --- /dev/null +++ b/xpcom/ds/nsProperties.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 nsProperties_h___ +#define nsProperties_h___ + +#include "nsIProperties.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" +#include "mozilla/Attributes.h" + +typedef nsInterfaceHashtable<nsCharPtrHashKey, nsISupports> + nsProperties_HashBase; + +class nsProperties final : public nsIProperties, public nsProperties_HashBase { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTIES + + nsProperties() = default; + + private: + ~nsProperties() = default; +}; + +#endif /* nsProperties_h___ */ diff --git a/xpcom/ds/nsQuickSort.cpp b/xpcom/ds/nsQuickSort.cpp new file mode 100644 index 0000000000..1fb3dede88 --- /dev/null +++ b/xpcom/ds/nsQuickSort.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* We need this because Solaris' version of qsort is broken and + * causes array bounds reads. + */ + +#include <stdlib.h> +#include "nsAlgorithm.h" +#include "nsQuickSort.h" + +extern "C" { + +#if !defined(DEBUG) && (defined(__cplusplus) || defined(__gcc)) +# ifndef INLINE +# define INLINE inline +# endif +#else +# define INLINE +#endif + +typedef int cmp_t(const void*, const void*, void*); +static INLINE char* med3(char*, char*, char*, cmp_t*, void*); +static INLINE void swapfunc(char*, char*, int, int); + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) \ + { \ + long i = (n) / sizeof(TYPE); \ + TYPE* pi = (TYPE*)(parmi); \ + TYPE* pj = (TYPE*)(parmj); \ + do { \ + TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ + } + +#define SWAPINIT(a, es) \ + swaptype = ((char*)a - (char*)0) % sizeof(long) || es % sizeof(long) ? 2 \ + : es == sizeof(long) ? 0 \ + : 1; + +static INLINE void swapfunc(char* a, char* b, int n, int swaptype) { + if (swaptype <= 1) swapcode(long, a, b, n) else swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long*)(a); \ + *(long*)(a) = *(long*)(b); \ + *(long*)(b) = t; \ + } else \ + swapfunc((char*)a, (char*)b, (int)es, swaptype) + +#define vecswap(a, b, n) \ + if ((n) > 0) swapfunc((char*)a, (char*)b, (int)n, swaptype) + +static INLINE char* med3(char* a, char* b, char* c, cmp_t* cmp, void* data) { + return cmp(a, b, data) < 0 + ? (cmp(b, c, data) < 0 ? b : (cmp(a, c, data) < 0 ? c : a)) + : (cmp(b, c, data) > 0 ? b : (cmp(a, c, data) < 0 ? a : c)); +} + +void NS_QuickSort(void* a, unsigned int n, unsigned int es, cmp_t* cmp, + void* data) { + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype; + +loop: + SWAPINIT(a, es); + /* Use insertion sort when input is small */ + if (n < 7) { + for (pm = (char*)a + es; pm < (char*)a + n * es; pm += es) + for (pl = pm; pl > (char*)a && cmp(pl - es, pl, data) > 0; pl -= es) + swap(pl, pl - es); + return; + } + /* Choose pivot */ + pm = (char*)a + (n / 2) * es; + if (n > 7) { + pl = (char*)a; + pn = (char*)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp, data); + pm = med3(pm - d, pm, pm + d, cmp, data); + pn = med3(pn - 2 * d, pn - d, pn, cmp, data); + } + pm = med3(pl, pm, pn, cmp, data); + } + swap(a, pm); + pa = pb = (char*)a + es; + + pc = pd = (char*)a + (n - 1) * es; + /* loop invariants: + * [a, pa) = pivot + * [pa, pb) < pivot + * [pb, pc + es) unprocessed + * [pc + es, pd + es) > pivot + * [pd + es, pn) = pivot + */ + for (;;) { + while (pb <= pc && (r = cmp(pb, a, data)) <= 0) { + if (r == 0) { + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a, data)) >= 0) { + if (r == 0) { + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) break; + swap(pb, pc); + pb += es; + pc -= es; + } + /* Move pivot values */ + pn = (char*)a + n * es; + r = XPCOM_MIN(pa - (char*)a, pb - pa); + vecswap(a, pb - r, r); + r = XPCOM_MIN<size_t>(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + /* Recursively process partitioned items */ + if ((r = pb - pa) > (int)es) NS_QuickSort(a, r / es, es, cmp, data); + if ((r = pd - pc) > (int)es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } + /* NS_QuickSort(pn - r, r / es, es, cmp, data);*/ +} +} + +#undef INLINE +#undef swapcode +#undef SWAPINIT +#undef swap +#undef vecswap diff --git a/xpcom/ds/nsQuickSort.h b/xpcom/ds/nsQuickSort.h new file mode 100644 index 0000000000..6526c3a205 --- /dev/null +++ b/xpcom/ds/nsQuickSort.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* We need this because Solaris' version of qsort is broken and + * causes array bounds reads. + */ + +#ifndef nsQuickSort_h___ +#define nsQuickSort_h___ + +#include "nscore.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * You most probably want to not use this but std::sort or std::stable_sort + * It will be removed eventually from the tree, see bug 1839052. + * + * Quicksort is recursive without limits and thus may make overflow the stack. + * And while being close to optimal for randomized data for some edge cases + * it can reach quadratic performance O(n*n) instead of O(n*log(n)). + * + * Parameters: + * 1. the array to sort + * 2. the number of elements in the array + * 3. the size of each array element + * 4. comparison function taking two elements and parameter #5 and + * returning an integer: + * + less than zero if the first element should be before the second + * + 0 if the order of the elements does not matter + * + greater than zero if the second element should be before the first + * 5. extra data to pass to comparison function + */ +[[deprecated("Use std::sort or std::make/sort_heap instead.")]] void +NS_QuickSort(void*, unsigned int, unsigned int, + int (*)(const void*, const void*, void*), void*); + +#ifdef __cplusplus +} +#endif + +#endif /* nsQuickSort_h___ */ diff --git a/xpcom/ds/nsRefCountedHashtable.h b/xpcom/ds/nsRefCountedHashtable.h new file mode 100644 index 0000000000..e3a0456a09 --- /dev/null +++ b/xpcom/ds/nsRefCountedHashtable.h @@ -0,0 +1,245 @@ +/* -*- 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_DS_NSREFCOUNTEDHASHTABLE_H_ +#define XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_ + +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" + +/** + * templated hashtable class maps keys to reference pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param PtrType the reference-type being wrapped + * @see nsClassHashtable, nsTHashMap + */ +template <class KeyClass, class PtrType> +class nsRefCountedHashtable + : public nsBaseHashtable< + KeyClass, PtrType, + typename mozilla::detail::SmartPtrTraits<PtrType>::RawPointerType> { + public: + using KeyType = typename KeyClass::KeyType; + using SmartPtrTraits = mozilla::detail::SmartPtrTraits<PtrType>; + using PointeeType = typename SmartPtrTraits::PointeeType; + using RawPointerType = typename SmartPtrTraits::RawPointerType; + using base_type = nsBaseHashtable<KeyClass, PtrType, RawPointerType>; + + using base_type::base_type; + + static_assert(SmartPtrTraits::IsRefCounted); + + /** + * @copydoc nsBaseHashtable::Get + * @param aData This is an XPCOM getter, so aData is already_addrefed. + * If the key doesn't exist, *aData will be set to nullptr. + */ + bool Get(KeyType aKey, RawPointerType* aData) const; + + /** + * @copydoc nsBaseHashtable::Get + */ + [[nodiscard]] already_AddRefed<PointeeType> Get(KeyType aKey) const; + + /** + * Gets a weak reference to the hashtable entry. + * @param aFound If not nullptr, will be set to true if the entry is found, + * to false otherwise. + * @return The entry, or nullptr if not found. Do not release this pointer! + */ + [[nodiscard]] RawPointerType GetWeak(KeyType aKey, + bool* aFound = nullptr) const; + + using base_type::InsertOrUpdate; + + template <typename U, + typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>> + void InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData); + + template <typename U, + typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>> + [[nodiscard]] bool InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData, + const mozilla::fallible_t&); + + template <typename U, + typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>> + void InsertOrUpdate(KeyType aKey, already_AddRefed<U>&& aData); + + template <typename U, + typename = std::enable_if_t<std::is_base_of_v<PointeeType, U>>> + [[nodiscard]] bool InsertOrUpdate(KeyType aKey, already_AddRefed<U>&& aData, + const mozilla::fallible_t&); + + /** + * Remove the entry associated with aKey (if any), optionally _moving_ its + * current value into *aData, thereby avoiding calls to AddRef and Release. + * Return true if found. + * @param aKey the key to remove from the hashtable + * @param aData where to move the value (if non-null). If an entry is not + * found it will be set to nullptr. + * @return true if an entry for aKey was found (and removed) + */ + inline bool Remove(KeyType aKey, RawPointerType* aData = nullptr); + + nsRefCountedHashtable Clone() const { + return this->template CloneAs<nsRefCountedHashtable>(); + } +}; + +template <typename K, typename T> +inline void ImplCycleCollectionUnlink(nsRefCountedHashtable<K, T>& aField) { + aField.Clear(); +} + +template <typename K, typename T> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsRefCountedHashtable<K, T>& aField, const char* aName, + uint32_t aFlags = 0) { + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(aCallback, iter.UserData(), aName, aFlags); + } +} + +// +// nsRefCountedHashtable definitions +// + +template <class KeyClass, class PtrType> +bool nsRefCountedHashtable<KeyClass, PtrType>::Get( + KeyType aKey, RawPointerType* aRefPtr) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRefPtr) { + *aRefPtr = ent->GetData(); + + NS_IF_ADDREF(*aRefPtr); + } + + return true; + } + + // if the key doesn't exist, set *aRefPtr to null + // so that it is a valid XPCOM getter + if (aRefPtr) { + *aRefPtr = nullptr; + } + + return false; +} + +template <class KeyClass, class PtrType> +already_AddRefed<typename nsRefCountedHashtable<KeyClass, PtrType>::PointeeType> +nsRefCountedHashtable<KeyClass, PtrType>::Get(KeyType aKey) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + + PtrType copy = ent->GetData(); + return copy.forget(); +} + +template <class KeyClass, class PtrType> +typename nsRefCountedHashtable<KeyClass, PtrType>::RawPointerType +nsRefCountedHashtable<KeyClass, PtrType>::GetWeak(KeyType aKey, + bool* aFound) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aFound) { + *aFound = true; + } + + return ent->GetData(); + } + + // Key does not exist, return nullptr and set aFound to false + if (aFound) { + *aFound = false; + } + + return nullptr; +} + +template <class KeyClass, class PtrType> +template <typename U, typename> +void nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData) { + if (!InsertOrUpdate(aKey, std::move(aData), mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } +} + +template <class KeyClass, class PtrType> +template <typename U, typename> +bool nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType<U>&& aData, + const mozilla::fallible_t&) { + typename base_type::EntryType* ent = this->PutEntry(aKey, mozilla::fallible); + + if (!ent) { + return false; + } + + ent->SetData(std::move(aData)); + + return true; +} + +template <class KeyClass, class PtrType> +template <typename U, typename> +void nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate( + KeyType aKey, already_AddRefed<U>&& aData) { + if (!InsertOrUpdate(aKey, std::move(aData), mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } +} + +template <class KeyClass, class PtrType> +template <typename U, typename> +bool nsRefCountedHashtable<KeyClass, PtrType>::InsertOrUpdate( + KeyType aKey, already_AddRefed<U>&& aData, const mozilla::fallible_t&) { + typename base_type::EntryType* ent = this->PutEntry(aKey, mozilla::fallible); + + if (!ent) { + return false; + } + + ent->SetData(std::move(aData)); + + return true; +} + +template <class KeyClass, class PtrType> +bool nsRefCountedHashtable<KeyClass, PtrType>::Remove(KeyType aKey, + RawPointerType* aRefPtr) { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRefPtr) { + ent->GetModifiableData()->forget(aRefPtr); + } + this->RemoveEntry(ent); + return true; + } + + if (aRefPtr) { + *aRefPtr = nullptr; + } + return false; +} + +#endif // XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_ diff --git a/xpcom/ds/nsRefPtrHashtable.h b/xpcom/ds/nsRefPtrHashtable.h new file mode 100644 index 0000000000..55b946bf7b --- /dev/null +++ b/xpcom/ds/nsRefPtrHashtable.h @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRefPtrHashtable_h__ +#define nsRefPtrHashtable_h__ + +#include "nsRefCountedHashtable.h" + +#endif // nsRefPtrHashtable_h__ diff --git a/xpcom/ds/nsSimpleEnumerator.cpp b/xpcom/ds/nsSimpleEnumerator.cpp new file mode 100644 index 0000000000..0be7b684b7 --- /dev/null +++ b/xpcom/ds/nsSimpleEnumerator.cpp @@ -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/. */ + +#include "nsSimpleEnumerator.h" + +#include "mozilla/dom/IteratorResultBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Try.h" +#include "nsContentUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +class JSEnumerator final : public nsIJSEnumerator { + NS_DECL_ISUPPORTS + NS_DECL_NSIJSENUMERATOR + + explicit JSEnumerator(nsISimpleEnumerator* aEnumerator, const nsID& aIID) + : mEnumerator(aEnumerator), mIID(aIID) {} + + private: + ~JSEnumerator() = default; + + nsCOMPtr<nsISimpleEnumerator> mEnumerator; + const nsID mIID; +}; + +} // anonymous namespace + +nsresult JSEnumerator::Iterator(nsIJSEnumerator** aResult) { + RefPtr<JSEnumerator> result(this); + result.forget(aResult); + return NS_OK; +} + +nsresult JSEnumerator::Next(JSContext* aCx, JS::MutableHandleValue aResult) { + RootedDictionary<IteratorResult> result(aCx); + + nsCOMPtr<nsISupports> elem; + if (NS_FAILED(mEnumerator->GetNext(getter_AddRefs(elem)))) { + result.mDone = true; + } else { + result.mDone = false; + + JS::RootedValue value(aCx); + MOZ_TRY(nsContentUtils::WrapNative(aCx, elem, &mIID, &value)); + result.mValue = value; + } + + if (!ToJSValue(aCx, result, aResult)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(JSEnumerator, nsIJSEnumerator) + +nsresult nsSimpleEnumerator::Iterator(nsIJSEnumerator** aResult) { + auto result = MakeRefPtr<JSEnumerator>(this, DefaultInterface()); + result.forget(aResult); + return NS_OK; +} + +nsresult nsSimpleEnumerator::Entries(const nsIID& aIface, + nsIJSEnumerator** aResult) { + auto result = MakeRefPtr<JSEnumerator>(this, aIface); + result.forget(aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsSimpleEnumerator, nsISimpleEnumerator, + nsISimpleEnumeratorBase) diff --git a/xpcom/ds/nsSimpleEnumerator.h b/xpcom/ds/nsSimpleEnumerator.h new file mode 100644 index 0000000000..60ab6419bc --- /dev/null +++ b/xpcom/ds/nsSimpleEnumerator.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSimpleEnumerator_h +#define nsSimpleEnumerator_h + +#include "nsISimpleEnumerator.h" + +class nsSimpleEnumerator : public nsISimpleEnumerator { + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATORBASE + + virtual const nsID& DefaultInterface() { return NS_GET_IID(nsISupports); } + + protected: + virtual ~nsSimpleEnumerator() = default; +}; + +#endif diff --git a/xpcom/ds/nsStaticAtomUtils.h b/xpcom/ds/nsStaticAtomUtils.h new file mode 100644 index 0000000000..8c766f63e1 --- /dev/null +++ b/xpcom/ds/nsStaticAtomUtils.h @@ -0,0 +1,36 @@ +/* -*- 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 nsStaticAtomUtils_h +#define nsStaticAtomUtils_h + +#include <stdint.h> +#include "nsAtom.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" + +// This class holds basic operations on arrays of static atoms. +class nsStaticAtomUtils { + public: + static mozilla::Maybe<uint32_t> Lookup(nsAtom* aAtom, + const nsStaticAtom* aAtoms, + uint32_t aCount) { + if (aAtom->IsStatic()) { + ptrdiff_t index = aAtom->AsStatic() - aAtoms; + if (index >= 0 && index < static_cast<ptrdiff_t>(aCount)) { + return mozilla::Some(static_cast<uint32_t>(index)); + } + } + return mozilla::Nothing(); + } + + static bool IsMember(nsAtom* aAtom, const nsStaticAtom* aAtoms, + uint32_t aCount) { + return Lookup(aAtom, aAtoms, aCount).isSome(); + } +}; + +#endif // nsStaticAtomUtils_h diff --git a/xpcom/ds/nsStaticNameTable.cpp b/xpcom/ds/nsStaticNameTable.cpp new file mode 100644 index 0000000000..2ee731c153 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.cpp @@ -0,0 +1,180 @@ +/* -*- 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/. */ + +/* Class to manage lookup of static names in a table. */ + +#include "mozilla/HashFunctions.h" +#include "mozilla/TextUtils.h" + +#include "nsCRT.h" + +#include "nscore.h" +#include "nsISupportsImpl.h" + +#include "nsStaticNameTable.h" + +using namespace mozilla; + +struct NameTableKey { + NameTableKey(const nsDependentCString aNameArray[], const nsCString* aKeyStr) + : mNameArray(aNameArray), mIsUnichar(false) { + mKeyStr.m1b = aKeyStr; + } + + NameTableKey(const nsDependentCString aNameArray[], const nsString* aKeyStr) + : mNameArray(aNameArray), mIsUnichar(true) { + mKeyStr.m2b = aKeyStr; + } + + const nsDependentCString* mNameArray; + union { + const nsCString* m1b; + const nsString* m2b; + } mKeyStr; + bool mIsUnichar; +}; + +struct NameTableEntry : public PLDHashEntryHdr { + int32_t mIndex; +}; + +static bool matchNameKeysCaseInsensitive(const PLDHashEntryHdr* aHdr, + const void* aVoidKey) { + auto entry = static_cast<const NameTableEntry*>(aHdr); + auto key = static_cast<const NameTableKey*>(aVoidKey); + const nsDependentCString* name = &key->mNameArray[entry->mIndex]; + + return key->mIsUnichar ? key->mKeyStr.m2b->LowerCaseEqualsASCII( + name->get(), name->Length()) + : key->mKeyStr.m1b->LowerCaseEqualsASCII( + name->get(), name->Length()); +} + +/* + * caseInsensitiveHashKey is just like PLDHashTable::HashStringKey except it + * uses (*s & ~0x20) instead of simply *s. This means that "aFOO" and + * "afoo" and "aFoo" will all hash to the same thing. It also means + * that some strings that aren't case-insensensitively equal will hash + * to the same value, but it's just a hash function so it doesn't + * matter. + */ +static PLDHashNumber caseInsensitiveStringHashKey(const void* aKey) { + PLDHashNumber h = 0; + const NameTableKey* tableKey = static_cast<const NameTableKey*>(aKey); + if (tableKey->mIsUnichar) { + for (const char16_t* s = tableKey->mKeyStr.m2b->get(); *s != '\0'; s++) { + h = AddToHash(h, *s & ~0x20); + } + } else { + for (const unsigned char* s = reinterpret_cast<const unsigned char*>( + tableKey->mKeyStr.m1b->get()); + *s != '\0'; s++) { + h = AddToHash(h, *s & ~0x20); + } + } + return h; +} + +static const struct PLDHashTableOps nametable_CaseInsensitiveHashTableOps = { + caseInsensitiveStringHashKey, + matchNameKeysCaseInsensitive, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr, +}; + +nsStaticCaseInsensitiveNameTable::nsStaticCaseInsensitiveNameTable( + const char* const aNames[], int32_t aLength) + : mNameArray(nullptr), + mNameTable(&nametable_CaseInsensitiveHashTableOps, sizeof(NameTableEntry), + aLength), + mNullStr("") { + MOZ_COUNT_CTOR(nsStaticCaseInsensitiveNameTable); + + MOZ_ASSERT(aNames, "null name table"); + MOZ_ASSERT(aLength, "0 length"); + + mNameArray = + (nsDependentCString*)moz_xmalloc(aLength * sizeof(nsDependentCString)); + + for (int32_t index = 0; index < aLength; ++index) { + const char* raw = aNames[index]; +#ifdef DEBUG + { + // verify invariants of contents + nsAutoCString temp1(raw); + nsDependentCString temp2(raw); + ToLowerCase(temp1); + MOZ_ASSERT(temp1.Equals(temp2), "upper case char in table"); + MOZ_ASSERT(IsAsciiNullTerminated(raw), + "non-ascii string in table -- " + "case-insensitive matching won't work right"); + } +#endif + // use placement-new to initialize the string object + nsDependentCString* strPtr = &mNameArray[index]; + new (strPtr) nsDependentCString(raw); + + NameTableKey key(mNameArray, strPtr); + + auto entry = static_cast<NameTableEntry*>(mNameTable.Add(&key, fallible)); + if (!entry) { + continue; + } + + // If the entry already exists it's unlikely but possible that its index is + // zero, in which case this assertion won't fail. But if the index is + // non-zero (highly likely) then it will fail. In other words, this + // assertion is likely but not guaranteed to detect if an entry is already + // used. + MOZ_ASSERT(entry->mIndex == 0, "Entry already exists!"); + + entry->mIndex = index; + } + mNameTable.MarkImmutable(); +} + +nsStaticCaseInsensitiveNameTable::~nsStaticCaseInsensitiveNameTable() { + // manually call the destructor on placement-new'ed objects + for (uint32_t index = 0; index < mNameTable.EntryCount(); index++) { + mNameArray[index].~nsDependentCString(); + } + free((void*)mNameArray); + MOZ_COUNT_DTOR(nsStaticCaseInsensitiveNameTable); +} + +int32_t nsStaticCaseInsensitiveNameTable::Lookup( + const nsACString& aName) const { + NS_ASSERTION(mNameArray, "not inited"); + + const nsCString& str = PromiseFlatCString(aName); + + NameTableKey key(mNameArray, &str); + auto entry = static_cast<NameTableEntry*>(mNameTable.Search(&key)); + + return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND; +} + +int32_t nsStaticCaseInsensitiveNameTable::Lookup(const nsAString& aName) const { + NS_ASSERTION(mNameArray, "not inited"); + + const nsString& str = PromiseFlatString(aName); + + NameTableKey key(mNameArray, &str); + auto entry = static_cast<NameTableEntry*>(mNameTable.Search(&key)); + + return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND; +} + +const nsCString& nsStaticCaseInsensitiveNameTable::GetStringValue( + int32_t aIndex) { + NS_ASSERTION(mNameArray, "not inited"); + + if ((NOT_FOUND < aIndex) && ((uint32_t)aIndex < mNameTable.EntryCount())) { + return mNameArray[aIndex]; + } + return mNullStr; +} diff --git a/xpcom/ds/nsStaticNameTable.h b/xpcom/ds/nsStaticNameTable.h new file mode 100644 index 0000000000..302d82c1e4 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.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/. */ + +/* Classes to manage lookup of static names in a table. */ + +#ifndef nsStaticNameTable_h___ +#define nsStaticNameTable_h___ + +#include "PLDHashTable.h" +#include "nsString.h" + +/* This class supports case insensitive lookup. + * + * It differs from atom tables: + * - It supports case insensitive lookup. + * - It has minimal footprint by not copying the string table. + * - It does no locking. + * - It returns zero based indexes and const nsCString& as required by its + * callers in the parser. + * - It is not an xpcom interface - meant for fast lookup in static tables. + * + * ***REQUIREMENTS*** + * - It *requires* that all entries in the table be lowercase only. + * - It *requires* that the table of strings be in memory that lives at least + * as long as this table object - typically a static string array. + */ + +class nsStaticCaseInsensitiveNameTable { + public: + enum { NOT_FOUND = -1 }; + + int32_t Lookup(const nsACString& aName) const; + int32_t Lookup(const nsAString& aName) const; + const nsCString& GetStringValue(int32_t aIndex); + + nsStaticCaseInsensitiveNameTable(const char* const aNames[], int32_t aLength); + ~nsStaticCaseInsensitiveNameTable(); + + private: + nsDependentCString* mNameArray; + PLDHashTable mNameTable; + nsDependentCString mNullStr; +}; + +#endif /* nsStaticNameTable_h___ */ diff --git a/xpcom/ds/nsStringEnumerator.cpp b/xpcom/ds/nsStringEnumerator.cpp new file mode 100644 index 0000000000..34576c7f50 --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.cpp @@ -0,0 +1,312 @@ +/* -*- 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 "nsStringEnumerator.h" +#include "nsSimpleEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "mozilla/Attributes.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Try.h" +#include "mozilla/dom/IteratorResultBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsTArray.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +class JSStringEnumerator final : public nsIJSEnumerator { + NS_DECL_ISUPPORTS + NS_DECL_NSIJSENUMERATOR + + explicit JSStringEnumerator(nsIStringEnumerator* aEnumerator) + : mEnumerator(aEnumerator) { + MOZ_ASSERT(mEnumerator); + } + + private: + ~JSStringEnumerator() = default; + + nsCOMPtr<nsIStringEnumerator> mEnumerator; +}; + +} // anonymous namespace + +nsresult JSStringEnumerator::Iterator(nsIJSEnumerator** aResult) { + RefPtr<JSStringEnumerator> result(this); + result.forget(aResult); + return NS_OK; +} + +nsresult JSStringEnumerator::Next(JSContext* aCx, + JS::MutableHandleValue aResult) { + RootedDictionary<IteratorResult> result(aCx); + + nsAutoString elem; + if (NS_FAILED(mEnumerator->GetNext(elem))) { + result.mDone = true; + } else { + result.mDone = false; + + if (!ToJSValue( + aCx, elem, + JS::MutableHandleValue::fromMarkedLocation(&result.mValue))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (!ToJSValue(aCx, result, aResult)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(JSStringEnumerator, nsIJSEnumerator) + +// +// nsStringEnumeratorBase +// + +nsresult nsStringEnumeratorBase::GetNext(nsAString& aResult) { + nsAutoCString str; + MOZ_TRY(GetNext(str)); + + CopyUTF8toUTF16(str, aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumeratorBase::StringIterator(nsIJSEnumerator** aRetVal) { + auto result = MakeRefPtr<JSStringEnumerator>(this); + result.forget(aRetVal); + return NS_OK; +} + +// +// nsStringEnumerator +// + +class nsStringEnumerator final : public nsSimpleEnumerator, + public nsIStringEnumerator, + public nsIUTF8StringEnumerator { + public: + nsStringEnumerator(const nsTArray<nsString>* aArray, bool aOwnsArray) + : mArray(aArray), mIndex(0), mOwnsArray(aOwnsArray), mIsUnicode(true) {} + + nsStringEnumerator(const nsTArray<nsCString>* aArray, bool aOwnsArray) + : mCArray(aArray), mIndex(0), mOwnsArray(aOwnsArray), mIsUnicode(false) {} + + nsStringEnumerator(const nsTArray<nsString>* aArray, nsISupports* aOwner) + : mArray(aArray), + mIndex(0), + mOwner(aOwner), + mOwnsArray(false), + mIsUnicode(true) {} + + nsStringEnumerator(const nsTArray<nsCString>* aArray, nsISupports* aOwner) + : mCArray(aArray), + mIndex(0), + mOwner(aOwner), + mOwnsArray(false), + mIsUnicode(false) {} + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIUTF8STRINGENUMERATOR + NS_DECL_NSISTRINGENUMERATORBASE + + // have to declare nsIStringEnumerator manually, because of + // overlapping method names + NS_IMETHOD GetNext(nsAString& aResult) override; + NS_DECL_NSISIMPLEENUMERATOR + + const nsID& DefaultInterface() override { + if (mIsUnicode) { + return NS_GET_IID(nsISupportsString); + } + return NS_GET_IID(nsISupportsCString); + } + + private: + ~nsStringEnumerator() { + if (mOwnsArray) { + // const-casting is safe here, because the NS_New* + // constructors make sure mOwnsArray is consistent with + // the constness of the objects + if (mIsUnicode) { + delete const_cast<nsTArray<nsString>*>(mArray); + } else { + delete const_cast<nsTArray<nsCString>*>(mCArray); + } + } + } + + union { + const nsTArray<nsString>* mArray; + const nsTArray<nsCString>* mCArray; + }; + + inline uint32_t Count() { + return mIsUnicode ? mArray->Length() : mCArray->Length(); + } + + uint32_t mIndex; + + // the owner allows us to hold a strong reference to the object + // that owns the array. Having a non-null value in mOwner implies + // that mOwnsArray is false, because we rely on the real owner + // to release the array + nsCOMPtr<nsISupports> mOwner; + bool mOwnsArray; + bool mIsUnicode; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsStringEnumerator, nsSimpleEnumerator, + nsIStringEnumerator, nsIUTF8StringEnumerator) + +NS_IMETHODIMP +nsStringEnumerator::HasMore(bool* aResult) { + *aResult = mIndex < Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::HasMoreElements(bool* aResult) { return HasMore(aResult); } + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsISupports** aResult) { + if (mIndex >= mArray->Length()) { + return NS_ERROR_FAILURE; + } + + if (mIsUnicode) { + nsSupportsString* stringImpl = new nsSupportsString(); + + stringImpl->SetData(mArray->ElementAt(mIndex++)); + *aResult = stringImpl; + } else { + nsSupportsCString* cstringImpl = new nsSupportsCString(); + + cstringImpl->SetData(mCArray->ElementAt(mIndex++)); + *aResult = cstringImpl; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsAString& aResult) { + if (NS_WARN_IF(mIndex >= Count())) { + return NS_ERROR_UNEXPECTED; + } + + if (mIsUnicode) { + aResult = mArray->ElementAt(mIndex++); + } else { + CopyUTF8toUTF16(mCArray->ElementAt(mIndex++), aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsACString& aResult) { + if (NS_WARN_IF(mIndex >= Count())) { + return NS_ERROR_UNEXPECTED; + } + + if (mIsUnicode) { + CopyUTF16toUTF8(mArray->ElementAt(mIndex++), aResult); + } else { + aResult = mCArray->ElementAt(mIndex++); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::StringIterator(nsIJSEnumerator** aRetVal) { + auto result = MakeRefPtr<JSStringEnumerator>(this); + result.forget(aRetVal); + return NS_OK; +} + +template <class T> +static inline nsresult StringEnumeratorTail(T** aResult) { + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +// +// constructors +// + +nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray, + nsISupports* aOwner) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, aOwner); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray<nsCString>* aArray, + nsISupports* aOwner) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, aOwner); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewAdoptingStringEnumerator(nsIStringEnumerator** aResult, + nsTArray<nsString>* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, true); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewAdoptingUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + nsTArray<nsCString>* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, true); + return StringEnumeratorTail(aResult); +} + +// const ones internally just forward to the non-const equivalents +nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, false); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray<nsCString>* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, false); + return StringEnumeratorTail(aResult); +} diff --git a/xpcom/ds/nsStringEnumerator.h b/xpcom/ds/nsStringEnumerator.h new file mode 100644 index 0000000000..0061227c34 --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.h @@ -0,0 +1,102 @@ +/* -*- 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 nsStringEnumerator_h +#define nsStringEnumerator_h + +#include "nsIStringEnumerator.h" +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" + +class nsStringEnumeratorBase : public nsIStringEnumerator, + public nsIUTF8StringEnumerator { + public: + NS_DECL_NSISTRINGENUMERATORBASE + + NS_IMETHOD GetNext(nsAString&) override; + + using nsIUTF8StringEnumerator::GetNext; + + protected: + virtual ~nsStringEnumeratorBase() = default; +}; + +// nsIStringEnumerator/nsIUTF8StringEnumerator implementations +// +// Currently all implementations support both interfaces. The +// constructors below provide the most common interface for the given +// type (i.e. nsIStringEnumerator for char16_t* strings, and so +// forth) but any resulting enumerators can be queried to the other +// type. Internally, the enumerators will hold onto the type that was +// passed in and do conversion if GetNext() for the other type of +// string is called. + +// There are a few different types of enumerators: + +// +// These enumerators hold a pointer to the array. Be careful +// because modifying the array may confuse the iterator, especially if +// you insert or remove elements in the middle of the array. +// + +// The non-adopting enumerator requires that the array sticks around +// at least as long as the enumerator does. These are for constant +// string arrays that the enumerator does not own, this could be used +// in VERY specialized cases such as when the provider KNOWS that the +// string enumerator will be consumed immediately, or will at least +// outlast the array. +// For example: +// +// nsTArray<nsCString> array; +// array.AppendCString("abc"); +// array.AppendCString("def"); +// NS_NewStringEnumerator(&enumerator, &array, true); +// +// // call some internal method which iterates the enumerator +// InternalMethod(enumerator); +// NS_RELEASE(enumerator); +// +[[nodiscard]] nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray, + nsISupports* aOwner); +[[nodiscard]] nsresult NS_NewUTF8StringEnumerator( + nsIUTF8StringEnumerator** aResult, const nsTArray<nsCString>* aArray); + +[[nodiscard]] nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray); + +// Adopting string enumerators assume ownership of the array and will +// call |operator delete| on the array when the enumerator is destroyed +// this is useful when the provider creates an array solely for the +// purpose of creating the enumerator. +// For example: +// +// nsTArray<nsCString>* array = new nsTArray<nsCString>; +// array->AppendString("abcd"); +// NS_NewAdoptingStringEnumerator(&result, array); +[[nodiscard]] nsresult NS_NewAdoptingStringEnumerator( + nsIStringEnumerator** aResult, nsTArray<nsString>* aArray); + +[[nodiscard]] nsresult NS_NewAdoptingUTF8StringEnumerator( + nsIUTF8StringEnumerator** aResult, nsTArray<nsCString>* aArray); + +// these versions take a refcounted "owner" which will be addreffed +// when the enumerator is created, and destroyed when the enumerator +// is released. This allows providers to give non-owning pointers to +// ns*StringArray member variables without worrying about lifetime +// issues +// For example: +// +// nsresult MyClass::Enumerate(nsIUTF8StringEnumerator** aResult) { +// mCategoryList->AppendString("abcd"); +// return NS_NewStringEnumerator(aResult, mCategoryList, this); +// } +// +[[nodiscard]] nsresult NS_NewUTF8StringEnumerator( + nsIUTF8StringEnumerator** aResult, const nsTArray<nsCString>* aArray, + nsISupports* aOwner); + +#endif // defined nsStringEnumerator_h diff --git a/xpcom/ds/nsSupportsPrimitives.cpp b/xpcom/ds/nsSupportsPrimitives.cpp new file mode 100644 index 0000000000..1d0db73187 --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.cpp @@ -0,0 +1,603 @@ +/* -*- 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 "nsSupportsPrimitives.h" +#include "mozilla/Assertions.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.h" +#include <algorithm> + +template <typename T> +static char* DataToString(const char* aFormat, T aData) { + static const int size = 32; + char buf[size]; + + SprintfLiteral(buf, aFormat, aData); + + return moz_xstrdup(buf); +} + +/***************************************************************************** + * nsSupportsCString + *****************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsCString, nsISupportsCString, nsISupportsPrimitive) + +NS_IMETHODIMP +nsSupportsCString::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::GetData(nsACString& aData) { + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::ToString(char** aResult) { + *aResult = ToNewCString(mData, mozilla::fallible); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::SetData(const nsACString& aData) { + bool ok = mData.Assign(aData, mozilla::fallible); + if (!ok) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/***************************************************************************** + * nsSupportsString + *****************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsString, nsISupportsString, nsISupportsPrimitive) + +NS_IMETHODIMP +nsSupportsString::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_STRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::GetData(nsAString& aData) { + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::ToString(char16_t** aResult) { + *aResult = ToNewUnicode(mData, mozilla::fallible); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::SetData(const nsAString& aData) { + bool ok = mData.Assign(aData, mozilla::fallible); + if (!ok) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRBool, nsISupportsPRBool, nsISupportsPrimitive) + +nsSupportsPRBool::nsSupportsPRBool() : mData(false) {} + +NS_IMETHODIMP +nsSupportsPRBool::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRBOOL; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::GetData(bool* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::SetData(bool aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = moz_xstrdup(mData ? "true" : "false"); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint8, nsISupportsPRUint8, nsISupportsPrimitive) + +nsSupportsPRUint8::nsSupportsPRUint8() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint8::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT8; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::GetData(uint8_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::SetData(uint8_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", static_cast<unsigned int>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint16, nsISupportsPRUint16, nsISupportsPrimitive) + +nsSupportsPRUint16::nsSupportsPRUint16() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint16::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT16; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::GetData(uint16_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::SetData(uint16_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", static_cast<unsigned int>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint32, nsISupportsPRUint32, nsISupportsPrimitive) + +nsSupportsPRUint32::nsSupportsPRUint32() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint32::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT32; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::GetData(uint32_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::SetData(uint32_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint64, nsISupportsPRUint64, nsISupportsPrimitive) + +nsSupportsPRUint64::nsSupportsPRUint64() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint64::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT64; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::GetData(uint64_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::SetData(uint64_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%llu", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRTime, nsISupportsPRTime, nsISupportsPrimitive) + +nsSupportsPRTime::nsSupportsPRTime() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRTime::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRTIME; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::GetData(PRTime* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::SetData(PRTime aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%" PRIu64, mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsChar, nsISupportsChar, nsISupportsPrimitive) + +nsSupportsChar::nsSupportsChar() : mData(0) {} + +NS_IMETHODIMP +nsSupportsChar::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_CHAR; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::GetData(char* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::SetData(char aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = static_cast<char*>(moz_xmalloc(2 * sizeof(char))); + *aResult[0] = mData; + *aResult[1] = '\0'; + + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt16, nsISupportsPRInt16, nsISupportsPrimitive) + +nsSupportsPRInt16::nsSupportsPRInt16() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRInt16::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT16; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::GetData(int16_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::SetData(int16_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%d", static_cast<int>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt32, nsISupportsPRInt32, nsISupportsPrimitive) + +nsSupportsPRInt32::nsSupportsPRInt32() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRInt32::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT32; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::GetData(int32_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::SetData(int32_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%d", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt64, nsISupportsPRInt64, nsISupportsPrimitive) + +nsSupportsPRInt64::nsSupportsPRInt64() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRInt64::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT64; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::GetData(int64_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::SetData(int64_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%" PRId64, mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsFloat, nsISupportsFloat, nsISupportsPrimitive) + +nsSupportsFloat::nsSupportsFloat() : mData(float(0.0)) {} + +NS_IMETHODIMP +nsSupportsFloat::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_FLOAT; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::GetData(float* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::SetData(float aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%f", static_cast<double>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsDouble, nsISupportsDouble, nsISupportsPrimitive) + +nsSupportsDouble::nsSupportsDouble() : mData(double(0.0)) {} + +NS_IMETHODIMP +nsSupportsDouble::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_DOUBLE; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::GetData(double* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::SetData(double aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%f", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsInterfacePointer, nsISupportsInterfacePointer, + nsISupportsPrimitive) + +nsSupportsInterfacePointer::nsSupportsInterfacePointer() : mIID(nullptr) {} + +nsSupportsInterfacePointer::~nsSupportsInterfacePointer() { + if (mIID) { + free(mIID); + } +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_INTERFACE_POINTER; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetData(nsISupports** aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + NS_IF_ADDREF(*aData); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::SetData(nsISupports* aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetDataIID(nsID** aIID) { + NS_ASSERTION(aIID, "Bad pointer"); + + *aIID = mIID ? mIID->Clone() : nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::SetDataIID(const nsID* aIID) { + if (mIID) { + free(mIID); + } + + mIID = aIID ? aIID->Clone() : nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + + // jband sez: think about asking nsIInterfaceInfoManager whether + // the interface has a known human-readable name + *aResult = moz_xstrdup("[interface pointer]"); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsDependentCString, nsISupportsCString, + nsISupportsPrimitive) + +nsSupportsDependentCString::nsSupportsDependentCString(const char* aStr) + : mData(aStr) {} + +NS_IMETHODIMP +nsSupportsDependentCString::GetType(uint16_t* aType) { + if (NS_WARN_IF(!aType)) { + return NS_ERROR_INVALID_ARG; + } + + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::GetData(nsACString& aData) { + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::ToString(char** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = ToNewCString(mData, mozilla::fallible); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::SetData(const nsACString& aData) { + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/xpcom/ds/nsSupportsPrimitives.h b/xpcom/ds/nsSupportsPrimitives.h new file mode 100644 index 0000000000..28d768a135 --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.h @@ -0,0 +1,279 @@ +/* -*- 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 nsSupportsPrimitives_h__ +#define nsSupportsPrimitives_h__ + +#include "mozilla/Attributes.h" + +#include "nsISupportsPrimitives.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +/***************************************************************************/ + +class nsSupportsCString final : public nsISupportsCString { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + + nsSupportsCString() = default; + + private: + ~nsSupportsCString() = default; + + nsCString mData; +}; + +/***************************************************************************/ + +class nsSupportsString final : public nsISupportsString { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSSTRING + + nsSupportsString() = default; + + private: + ~nsSupportsString() = default; + + nsString mData; +}; + +/***************************************************************************/ + +class nsSupportsPRBool final : public nsISupportsPRBool { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRBOOL + + nsSupportsPRBool(); + + private: + ~nsSupportsPRBool() = default; + + bool mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint8 final : public nsISupportsPRUint8 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT8 + + nsSupportsPRUint8(); + + private: + ~nsSupportsPRUint8() = default; + + uint8_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint16 final : public nsISupportsPRUint16 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT16 + + nsSupportsPRUint16(); + + private: + ~nsSupportsPRUint16() = default; + + uint16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint32 final : public nsISupportsPRUint32 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT32 + + nsSupportsPRUint32(); + + private: + ~nsSupportsPRUint32() = default; + + uint32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint64 final : public nsISupportsPRUint64 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT64 + + nsSupportsPRUint64(); + + private: + ~nsSupportsPRUint64() = default; + + uint64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRTime final : public nsISupportsPRTime { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRTIME + + nsSupportsPRTime(); + + private: + ~nsSupportsPRTime() = default; + + PRTime mData; +}; + +/***************************************************************************/ + +class nsSupportsChar final : public nsISupportsChar { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCHAR + + nsSupportsChar(); + + private: + ~nsSupportsChar() = default; + + char mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt16 final : public nsISupportsPRInt16 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT16 + + nsSupportsPRInt16(); + + private: + ~nsSupportsPRInt16() = default; + + int16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt32 final : public nsISupportsPRInt32 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT32 + + nsSupportsPRInt32(); + + private: + ~nsSupportsPRInt32() = default; + + int32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt64 final : public nsISupportsPRInt64 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT64 + + nsSupportsPRInt64(); + + private: + ~nsSupportsPRInt64() = default; + + int64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsFloat final : public nsISupportsFloat { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSFLOAT + + nsSupportsFloat(); + + private: + ~nsSupportsFloat() = default; + + float mData; +}; + +/***************************************************************************/ + +class nsSupportsDouble final : public nsISupportsDouble { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSDOUBLE + + nsSupportsDouble(); + + private: + ~nsSupportsDouble() = default; + + double mData; +}; + +/***************************************************************************/ + +class nsSupportsInterfacePointer final : public nsISupportsInterfacePointer { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSINTERFACEPOINTER + + nsSupportsInterfacePointer(); + + private: + ~nsSupportsInterfacePointer(); + + nsCOMPtr<nsISupports> mData; + nsID* mIID; +}; + +/***************************************************************************/ + +/** + * Wraps a static const char* buffer for use with nsISupportsCString + * + * Only use this class with static buffers, or arena-allocated buffers of + * permanent lifetime! + */ +class nsSupportsDependentCString final : public nsISupportsCString { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + + explicit nsSupportsDependentCString(const char* aStr); + + private: + ~nsSupportsDependentCString() = default; + + nsDependentCString mData; +}; + +#endif /* nsSupportsPrimitives_h__ */ diff --git a/xpcom/ds/nsTArray-inl.h b/xpcom/ds/nsTArray-inl.h new file mode 100644 index 0000000000..3d9ff1d410 --- /dev/null +++ b/xpcom/ds/nsTArray-inl.h @@ -0,0 +1,689 @@ +/* -*- 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 nsTArray_h__ +# error "Don't include this file directly" +#endif + +// NOTE: We don't use MOZ_COUNT_CTOR/MOZ_COUNT_DTOR to perform leak checking of +// nsTArray_base objects intentionally for the following reasons: +// * The leak logging isn't as useful as other types of logging, as +// nsTArray_base is frequently relocated without invoking a constructor, such +// as when stored within another nsTArray. This means that +// XPCOM_MEM_LOG_CLASSES cannot be used to identify specific leaks of nsTArray +// objects. +// * The nsTArray type is layout compatible with the ThinVec crate with the +// correct flags, and ThinVec does not currently perform leak logging. +// This means that if a large number of arrays are transferred between Rust +// and C++ code using ThinVec, for example within another ThinVec, they +// will not be logged correctly and might appear as e.g. negative leaks. +// * Leaks which have been found thanks to the leak logging added by this +// type have often not been significant, and/or have needed to be +// circumvented using some other mechanism. Most leaks found with this type +// in them also include other types which will continue to be tracked. + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::nsTArray_base() : mHdr(EmptyHdr()) {} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::~nsTArray_base() { + if (!HasEmptyHeader() && !UsesAutoArrayBuffer()) { + Alloc::Free(mHdr); + } +} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::nsTArray_base(const nsTArray_base&) + : mHdr(EmptyHdr()) { + // Actual copying happens through nsTArray_CopyEnabler, we just need to do the + // initialization of mHdr. +} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>& +nsTArray_base<Alloc, RelocationStrategy>::operator=(const nsTArray_base&) { + // Actual copying happens through nsTArray_CopyEnabler, so do nothing here (do + // not copy mHdr). + return *this; +} + +template <class Alloc, class RelocationStrategy> +const nsTArrayHeader* +nsTArray_base<Alloc, RelocationStrategy>::GetAutoArrayBufferUnsafe( + size_t aElemAlign) const { + // Assuming |this| points to an nsAutoArray, we want to get a pointer to + // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf! + + const void* autoBuf = + &reinterpret_cast<const AutoTArray<nsTArray<uint32_t>, 1>*>(this) + ->mAutoBuf; + + // If we're on a 32-bit system and aElemAlign is 8, we need to adjust our + // pointer to take into account the extra alignment in the auto array. + + static_assert( + sizeof(void*) != 4 || (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 && + sizeof(AutoTArray<mozilla::AlignedElem<8>, 1>) == + sizeof(void*) + sizeof(nsTArrayHeader) + 4 + + sizeof(mozilla::AlignedElem<8>)), + "auto array padding wasn't what we expected"); + + // We don't support alignments greater than 8 bytes. + MOZ_ASSERT(aElemAlign <= 4 || aElemAlign == 8, "unsupported alignment."); + if (sizeof(void*) == 4 && aElemAlign == 8) { + autoBuf = reinterpret_cast<const char*>(autoBuf) + 4; + } + + return reinterpret_cast<const Header*>(autoBuf); +} + +template <class Alloc, class RelocationStrategy> +bool nsTArray_base<Alloc, RelocationStrategy>::UsesAutoArrayBuffer() const { + if (!mHdr->mIsAutoArray) { + return false; + } + + // This is nuts. If we were sane, we'd pass aElemAlign as a parameter to + // this function. Unfortunately this function is called in nsTArray_base's + // destructor, at which point we don't know value_type's alignment. + // + // We'll fall on our face and return true when we should say false if + // + // * we're not using our auto buffer, + // * aElemAlign == 4, and + // * mHdr == GetAutoArrayBuffer(8). + // + // This could happen if |*this| lives on the heap and malloc allocated our + // buffer on the heap adjacent to |*this|. + // + // However, we can show that this can't happen. If |this| is an auto array + // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8) + // always points to memory owned by |*this|, because (as we assert below) + // + // * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), + // and + // * sizeof(nsTArrayHeader) > 4. + // + // Since AutoTArray always contains an nsTArrayHeader, + // GetAutoArrayBuffer(8) will always point inside the auto array object, + // even if it doesn't point at the beginning of the header. + // + // Note that this means that we can't store elements with alignment 16 in an + // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory + // owned by this AutoTArray. We statically assert that value_type's + // alignment is 8 bytes or less in AutoTArray. + + static_assert(sizeof(nsTArrayHeader) > 4, "see comment above"); + +#ifdef DEBUG + ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) - + reinterpret_cast<const char*>(GetAutoArrayBuffer(4)); + MOZ_ASSERT(diff >= 0 && diff <= 4, + "GetAutoArrayBuffer doesn't do what we expect."); +#endif + + return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8); +} + +// defined in nsTArray.cpp +bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, + size_t aElemSize); + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::ExtendCapacity(size_type aLength, + size_type aCount, + size_type aElemSize) { + mozilla::CheckedInt<size_type> newLength = aLength; + newLength += aCount; + + if (!newLength.isValid()) { + return ActualAlloc::FailureResult(); + } + + return this->EnsureCapacity<ActualAlloc>(newLength.value(), aElemSize); +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::EnsureCapacityImpl( + size_type aCapacity, size_type aElemSize) { + MOZ_ASSERT(aCapacity > mHdr->mCapacity, + "Should have been checked by caller (EnsureCapacity)"); + + // If the requested memory allocation exceeds size_type(-1)/2, then + // our doubling algorithm may not be able to allocate it. + // Additionally, if it exceeds uint32_t(-1) then we couldn't fit in the + // Header::mCapacity member. Just bail out in cases like that. We don't want + // to be allocating 2 GB+ arrays anyway. + if (!IsTwiceTheRequiredBytesRepresentableAsUint32(aCapacity, aElemSize)) { + ActualAlloc::SizeTooBig((size_t)aCapacity * aElemSize); + return ActualAlloc::FailureResult(); + } + + size_t reqSize = sizeof(Header) + aCapacity * aElemSize; + + if (HasEmptyHeader()) { + // Malloc() new data + Header* header = static_cast<Header*>(ActualAlloc::Malloc(reqSize)); + if (!header) { + return ActualAlloc::FailureResult(); + } + header->mLength = 0; + header->mCapacity = aCapacity; + header->mIsAutoArray = 0; + mHdr = header; + + return ActualAlloc::SuccessResult(); + } + + // We increase our capacity so that the allocated buffer grows exponentially, + // which gives us amortized O(1) appending. Below the threshold, we use + // powers-of-two. Above the threshold, we grow by at least 1.125, rounding up + // to the nearest MiB. + const size_t slowGrowthThreshold = 8 * 1024 * 1024; + + size_t bytesToAlloc; + if (reqSize >= slowGrowthThreshold) { + size_t currSize = sizeof(Header) + Capacity() * aElemSize; + size_t minNewSize = currSize + (currSize >> 3); // multiply by 1.125 + bytesToAlloc = reqSize > minNewSize ? reqSize : minNewSize; + + // Round up to the next multiple of MiB. + const size_t MiB = 1 << 20; + bytesToAlloc = MiB * ((bytesToAlloc + MiB - 1) / MiB); + } else { + // Round up to the next power of two. + bytesToAlloc = mozilla::RoundUpPow2(reqSize); + } + + Header* header; + if (UsesAutoArrayBuffer() || !RelocationStrategy::allowRealloc) { + // Malloc() and copy + header = static_cast<Header*>(ActualAlloc::Malloc(bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + header, mHdr, Length(), aElemSize); + + if (!UsesAutoArrayBuffer()) { + ActualAlloc::Free(mHdr); + } + } else { + // Realloc() existing data + header = static_cast<Header*>(ActualAlloc::Realloc(mHdr, bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + } + + // How many elements can we fit in bytesToAlloc? + size_t newCapacity = (bytesToAlloc - sizeof(Header)) / aElemSize; + MOZ_ASSERT(newCapacity >= aCapacity, "Didn't enlarge the array enough!"); + header->mCapacity = newCapacity; + + mHdr = header; + + return ActualAlloc::SuccessResult(); +} + +// We don't need use Alloc template parameter specified here because failure to +// shrink the capacity will leave the array unchanged. +template <class Alloc, class RelocationStrategy> +void nsTArray_base<Alloc, RelocationStrategy>::ShrinkCapacity( + size_type aElemSize, size_t aElemAlign) { + if (HasEmptyHeader() || UsesAutoArrayBuffer()) { + return; + } + + if (mHdr->mLength >= mHdr->mCapacity) { // should never be greater than... + return; + } + + size_type length = Length(); + + if (IsAutoArray() && GetAutoArrayBuffer(aElemAlign)->mCapacity >= length) { + Header* header = GetAutoArrayBuffer(aElemAlign); + + // Move the data, but don't copy the header to avoid overwriting mCapacity. + header->mLength = length; + RelocationStrategy::RelocateNonOverlappingRegion(header + 1, mHdr + 1, + length, aElemSize); + + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = header; + return; + } + + if (length == 0) { + MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements"); + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = EmptyHdr(); + return; + } + + size_type newSize = sizeof(Header) + length * aElemSize; + + Header* newHeader; + if (!RelocationStrategy::allowRealloc) { + // Malloc() and copy. + newHeader = + static_cast<Header*>(nsTArrayFallibleAllocator::Malloc(newSize)); + if (!newHeader) { + return; + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + newHeader, mHdr, Length(), aElemSize); + + nsTArrayFallibleAllocator::Free(mHdr); + } else { + // Realloc() existing data. + newHeader = + static_cast<Header*>(nsTArrayFallibleAllocator::Realloc(mHdr, newSize)); + if (!newHeader) { + return; + } + } + + mHdr = newHeader; + mHdr->mCapacity = length; +} + +template <class Alloc, class RelocationStrategy> +void nsTArray_base<Alloc, RelocationStrategy>::ShrinkCapacityToZero( + size_type aElemSize, size_t aElemAlign) { + MOZ_ASSERT(mHdr->mLength == 0); + + if (HasEmptyHeader() || UsesAutoArrayBuffer()) { + return; + } + + const bool isAutoArray = IsAutoArray(); + + nsTArrayFallibleAllocator::Free(mHdr); + + if (isAutoArray) { + mHdr = GetAutoArrayBufferUnsafe(aElemAlign); + mHdr->mLength = 0; + } else { + mHdr = EmptyHdr(); + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +void nsTArray_base<Alloc, RelocationStrategy>::ShiftData(index_type aStart, + size_type aOldLen, + size_type aNewLen, + size_type aElemSize, + size_t aElemAlign) { + if (aOldLen == aNewLen) { + return; + } + + // Determine how many elements need to be shifted + size_type num = mHdr->mLength - (aStart + aOldLen); + + // Compute the resulting length of the array + mHdr->mLength += aNewLen - aOldLen; + if (mHdr->mLength == 0) { + ShrinkCapacityToZero(aElemSize, aElemAlign); + } else { + // Maybe nothing needs to be shifted + if (num == 0) { + return; + } + // Perform shift (change units to bytes first) + aStart *= aElemSize; + aNewLen *= aElemSize; + aOldLen *= aElemSize; + char* baseAddr = reinterpret_cast<char*>(mHdr + 1) + aStart; + RelocationStrategy::RelocateOverlappingRegion( + baseAddr + aNewLen, baseAddr + aOldLen, num, aElemSize); + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +void nsTArray_base<Alloc, RelocationStrategy>::SwapFromEnd(index_type aStart, + size_type aCount, + size_type aElemSize, + size_t aElemAlign) { + // This method is part of the implementation of + // nsTArray::SwapRemoveElement{s,}At. For more information, read the + // documentation on that method. + if (aCount == 0) { + return; + } + + // We are going to be removing aCount elements. Update our length to point to + // the new end of the array. + size_type oldLength = mHdr->mLength; + mHdr->mLength -= aCount; + + if (mHdr->mLength == 0) { + // If we have no elements remaining in the array, we can free our buffer. + ShrinkCapacityToZero(aElemSize, aElemAlign); + return; + } + + // Determine how many elements we need to move from the end of the array into + // the now-removed section. This will either be the number of elements which + // were removed (if there are more elements in the tail of the array), or the + // entire tail of the array, whichever is smaller. + size_type relocCount = std::min(aCount, mHdr->mLength - aStart); + if (relocCount == 0) { + return; + } + + // Move the elements which are now stranded after the end of the array back + // into the now-vacated memory. + index_type sourceBytes = (oldLength - relocCount) * aElemSize; + index_type destBytes = aStart * aElemSize; + + // Perform the final copy. This is guaranteed to be a non-overlapping copy + // as our source contains only still-valid entries, and the destination + // contains only invalid entries which need to be overwritten. + MOZ_ASSERT(sourceBytes >= destBytes, + "The source should be after the destination."); + MOZ_ASSERT(sourceBytes - destBytes >= relocCount * aElemSize, + "The range should be nonoverlapping"); + + char* baseAddr = reinterpret_cast<char*>(mHdr + 1); + RelocationStrategy::RelocateNonOverlappingRegion( + baseAddr + destBytes, baseAddr + sourceBytes, relocCount, aElemSize); +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::InsertSlotsAt(index_type aIndex, + size_type aCount, + size_type aElemSize, + size_t aElemAlign) { + if (MOZ_UNLIKELY(aIndex > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + + if (!ActualAlloc::Successful( + this->ExtendCapacity<ActualAlloc>(Length(), aCount, aElemSize))) { + return ActualAlloc::FailureResult(); + } + + // Move the existing elements as needed. Note that this will + // change our mLength, so no need to call IncrementLength. + ShiftData<ActualAlloc>(aIndex, 0, aCount, aElemSize, aElemAlign); + + return ActualAlloc::SuccessResult(); +} + +// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes +// |nsTArray_base &array| in its constructor. When it's destructed, it ensures +// that +// +// * array.mIsAutoArray has the same value as it did when we started, and +// * if array has an auto buffer and mHdr would otherwise point to +// sEmptyTArrayHeader, array.mHdr points to array's auto buffer. + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::IsAutoArrayRestorer:: + IsAutoArrayRestorer(nsTArray_base<Alloc, RelocationStrategy>& aArray, + size_t aElemAlign) + : mArray(aArray), mElemAlign(aElemAlign), mIsAuto(aArray.IsAutoArray()) {} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, + RelocationStrategy>::IsAutoArrayRestorer::~IsAutoArrayRestorer() { + // Careful: We don't want to set mIsAutoArray = 1 on sEmptyTArrayHeader. + if (mIsAuto && mArray.HasEmptyHeader()) { + // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts + // that mHdr->mIsAutoArray is true, which surely isn't the case here. + mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign); + mArray.mHdr->mLength = 0; + } else if (!mArray.HasEmptyHeader()) { + mArray.mHdr->mIsAutoArray = mIsAuto; + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc, class Allocator> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::SwapArrayElements( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign) { + // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we + // have an auto buffer. We need to point mHdr back to our auto buffer before + // we return, otherwise we'll forget that we have an auto buffer at all! + // IsAutoArrayRestorer takes care of this for us. + + IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign); + typename nsTArray_base<Allocator, RelocationStrategy>::IsAutoArrayRestorer + otherAutoRestorer(aOther, aElemAlign); + + // If neither array uses an auto buffer which is big enough to store the + // other array's elements, then ensure that both arrays use malloc'ed storage + // and swap their mHdr pointers. + if ((!UsesAutoArrayBuffer() || Capacity() < aOther.Length()) && + (!aOther.UsesAutoArrayBuffer() || aOther.Capacity() < Length())) { + if (!EnsureNotUsingAutoArrayBuffer<ActualAlloc>(aElemSize) || + !aOther.template EnsureNotUsingAutoArrayBuffer<ActualAlloc>( + aElemSize)) { + return ActualAlloc::FailureResult(); + } + + Header* temp = mHdr; + mHdr = aOther.mHdr; + aOther.mHdr = temp; + + return ActualAlloc::SuccessResult(); + } + + // Swap the two arrays by copying, since at least one is using an auto + // buffer which is large enough to hold all of the aOther's elements. We'll + // copy the shorter array into temporary storage. + // + // (We could do better than this in some circumstances. Suppose we're + // swapping arrays X and Y. X has space for 2 elements in its auto buffer, + // but currently has length 4, so it's using malloc'ed storage. Y has length + // 2. When we swap X and Y, we don't need to use a temporary buffer; we can + // write Y straight into X's auto buffer, write X's malloc'ed buffer on top + // of Y, and then switch X to using its auto buffer.) + + if (!ActualAlloc::Successful( + EnsureCapacity<ActualAlloc>(aOther.Length(), aElemSize)) || + !Allocator::Successful( + aOther.template EnsureCapacity<Allocator>(Length(), aElemSize))) { + return ActualAlloc::FailureResult(); + } + + // The EnsureCapacity calls above shouldn't have caused *both* arrays to + // switch from their auto buffers to malloc'ed space. + MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(), + "One of the arrays should be using its auto buffer."); + + size_type smallerLength = XPCOM_MIN(Length(), aOther.Length()); + size_type largerLength = XPCOM_MAX(Length(), aOther.Length()); + void* smallerElements; + void* largerElements; + if (Length() <= aOther.Length()) { + smallerElements = Hdr() + 1; + largerElements = aOther.Hdr() + 1; + } else { + smallerElements = aOther.Hdr() + 1; + largerElements = Hdr() + 1; + } + + // Allocate temporary storage for the smaller of the two arrays. We want to + // allocate this space on the stack, if it's not too large. Sounds like a + // job for AutoTArray! (One of the two arrays we're swapping is using an + // auto buffer, so we're likely not allocating a lot of space here. But one + // could, in theory, allocate a huge AutoTArray on the heap.) + AutoTArray<uint8_t, 64 * sizeof(void*)> temp; + if (!ActualAlloc::Successful(temp.template EnsureCapacity<ActualAlloc>( + smallerLength * aElemSize, sizeof(uint8_t)))) { + return ActualAlloc::FailureResult(); + } + + RelocationStrategy::RelocateNonOverlappingRegion( + temp.Elements(), smallerElements, smallerLength, aElemSize); + RelocationStrategy::RelocateNonOverlappingRegion( + smallerElements, largerElements, largerLength, aElemSize); + RelocationStrategy::RelocateNonOverlappingRegion( + largerElements, temp.Elements(), smallerLength, aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) && + (Length() == 0 || !aOther.HasEmptyHeader()), + "Don't set sEmptyTArrayHeader's length."); + size_type tempLength = Length(); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (!HasEmptyHeader()) { + mHdr->mLength = aOther.Length(); + } + if (!aOther.HasEmptyHeader()) { + aOther.mHdr->mLength = tempLength; + } + + return ActualAlloc::SuccessResult(); +} + +template <class Alloc, class RelocationStrategy> +template <class Allocator> +void nsTArray_base<Alloc, RelocationStrategy>::MoveInit( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign) { + // This method is similar to SwapArrayElements, but specialized for the case + // where the target array is empty with no allocated heap storage. It is + // provided and used to simplify template instantiation and enable better code + // generation. + + MOZ_ASSERT(Length() == 0); + MOZ_ASSERT(Capacity() == 0 || (IsAutoArray() && UsesAutoArrayBuffer())); + + // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we + // have an auto buffer. We need to point mHdr back to our auto buffer before + // we return, otherwise we'll forget that we have an auto buffer at all! + // IsAutoArrayRestorer takes care of this for us. + + IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign); + typename nsTArray_base<Allocator, RelocationStrategy>::IsAutoArrayRestorer + otherAutoRestorer(aOther, aElemAlign); + + // If neither array uses an auto buffer which is big enough to store the + // other array's elements, then ensure that both arrays use malloc'ed storage + // and swap their mHdr pointers. + if ((!IsAutoArray() || Capacity() < aOther.Length()) && + !aOther.UsesAutoArrayBuffer()) { + mHdr = aOther.mHdr; + + aOther.mHdr = EmptyHdr(); + + return; + } + + // Move the data by copying, since at least one has an auto + // buffer which is large enough to hold all of the aOther's elements. + + EnsureCapacity<nsTArrayInfallibleAllocator>(aOther.Length(), aElemSize); + + // The EnsureCapacity calls above shouldn't have caused *both* arrays to + // switch from their auto buffers to malloc'ed space. + MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(), + "One of the arrays should be using its auto buffer."); + + RelocationStrategy::RelocateNonOverlappingRegion(Hdr() + 1, aOther.Hdr() + 1, + aOther.Length(), aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) && + (Length() == 0 || !aOther.HasEmptyHeader()), + "Don't set sEmptyTArrayHeader's length."); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (!HasEmptyHeader()) { + mHdr->mLength = aOther.Length(); + } + if (!aOther.HasEmptyHeader()) { + aOther.mHdr->mLength = 0; + } +} + +template <class Alloc, class RelocationStrategy> +template <class Allocator> +void nsTArray_base<Alloc, RelocationStrategy>::MoveConstructNonAutoArray( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign) { + // We know that we are not an (Copyable)AutoTArray and we know that we are + // empty, so don't use SwapArrayElements which doesn't know either of these + // facts and is very complex. + + if (aOther.IsEmpty()) { + return; + } + + // aOther might be an (Copyable)AutoTArray though, and it might use its inline + // buffer. + const bool otherUsesAutoArrayBuffer = aOther.UsesAutoArrayBuffer(); + if (otherUsesAutoArrayBuffer) { + // Use nsTArrayInfallibleAllocator regardless of Alloc because this is + // called from a move constructor, which cannot report an error to the + // caller. + aOther.template EnsureNotUsingAutoArrayBuffer<nsTArrayInfallibleAllocator>( + aElemSize); + } + + const bool otherIsAuto = otherUsesAutoArrayBuffer || aOther.IsAutoArray(); + mHdr = aOther.mHdr; + // We might write to mHdr, so ensure it's not the static empty header. aOther + // shouldn't have been empty if we get here anyway. + MOZ_ASSERT(!HasEmptyHeader()); + + if (otherIsAuto) { + mHdr->mIsAutoArray = false; + aOther.mHdr = aOther.GetAutoArrayBufferUnsafe(aElemAlign); + aOther.mHdr->mLength = 0; + } else { + aOther.mHdr = aOther.EmptyHdr(); + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +bool nsTArray_base<Alloc, RelocationStrategy>::EnsureNotUsingAutoArrayBuffer( + size_type aElemSize) { + if (UsesAutoArrayBuffer()) { + // If you call this on a 0-length array, we'll set that array's mHdr to + // sEmptyTArrayHeader, in flagrant violation of the AutoTArray invariants. + // It's up to you to set it back! (If you don't, the AutoTArray will + // forget that it has an auto buffer.) + if (Length() == 0) { + mHdr = EmptyHdr(); + return true; + } + + size_type size = sizeof(Header) + Length() * aElemSize; + + Header* header = static_cast<Header*>(ActualAlloc::Malloc(size)); + if (!header) { + return false; + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + header, mHdr, Length(), aElemSize); + header->mCapacity = Length(); + mHdr = header; + } + + return true; +} diff --git a/xpcom/ds/nsTArray.cpp b/xpcom/ds/nsTArray.cpp new file mode 100644 index 0000000000..3c193ece91 --- /dev/null +++ b/xpcom/ds/nsTArray.cpp @@ -0,0 +1,27 @@ +/* -*- 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 "nsTArray.h" +#include "nsXPCOM.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsDebug.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/IntegerPrintfMacros.h" + +// Ensure this is sufficiently aligned so that Elements() and co don't create +// unaligned pointers, or slices with unaligned pointers for empty arrays, see +// https://github.com/servo/servo/issues/22613. +alignas(8) const nsTArrayHeader sEmptyTArrayHeader = {0, 0, 0}; + +bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, + size_t aElemSize) { + using mozilla::CheckedUint32; + return ((CheckedUint32(aCapacity) * aElemSize) * 2).isValid(); +} + +void ::detail::SetCycleCollectionArrayFlag(uint32_t& aFlags) { + aFlags |= CycleCollectionEdgeNameArrayFlag; +} diff --git a/xpcom/ds/nsTArray.h b/xpcom/ds/nsTArray.h new file mode 100644 index 0000000000..f17624b721 --- /dev/null +++ b/xpcom/ds/nsTArray.h @@ -0,0 +1,3376 @@ +/* -*- 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 nsTArray_h__ +#define nsTArray_h__ + +#include <string.h> + +#include <algorithm> +#include <functional> +#include <initializer_list> +#include <iterator> +#include <new> +#include <ostream> +#include <type_traits> +#include <utility> + +#include "mozilla/Alignment.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/FunctionTypeTraits.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/NotNull.h" +#include "mozilla/Span.h" +#include "mozilla/fallible.h" +#include "mozilla/mozalloc.h" +#include "nsAlgorithm.h" +#include "nsDebug.h" +#include "nsISupports.h" +#include "nsRegionFwd.h" +#include "nsTArrayForwardDeclare.h" + +namespace JS { +template <class T> +class Heap; +} /* namespace JS */ + +class nsCycleCollectionTraversalCallback; +class nsRegion; + +namespace mozilla::a11y { +class BatchData; +} + +namespace mozilla { +namespace layers { +class Animation; +class FrameStats; +struct PropertyAnimationGroup; +struct TileClient; +} // namespace layers +} // namespace mozilla + +namespace mozilla { +struct SerializedStructuredCloneBuffer; +class SourceBufferTask; +} // namespace mozilla + +namespace mozilla::dom::binding_detail { +template <typename, typename> +class RecordEntry; +} + +namespace mozilla::dom::ipc { +class StructuredCloneData; +} // namespace mozilla::dom::ipc + +namespace mozilla::dom { +class ClonedMessageData; +class MessageData; +class MessagePortIdentifier; +struct MozPluginParameter; +template <typename T> +struct Nullable; +class OwningFileOrDirectory; +class OwningStringOrBooleanOrObject; +class OwningUTF8StringOrDouble; +class Pref; +class RefMessageData; +class ResponsiveImageCandidate; +class ServiceWorkerRegistrationData; +namespace indexedDB { +class SerializedStructuredCloneReadInfo; +class ObjectStoreCursorResponse; +class IndexCursorResponse; +} // namespace indexedDB +} // namespace mozilla::dom + +namespace mozilla::ipc { +class ContentSecurityPolicy; +template <class T> +class Endpoint; +} // namespace mozilla::ipc + +class JSStructuredCloneData; + +template <class T> +class RefPtr; + +// +// nsTArray<E> is a resizable array class, like std::vector. +// +// Unlike std::vector, which follows C++'s construction/destruction rules, +// By default, nsTArray assumes that instances of E can be relocated safely +// using memory utils (memcpy/memmove). +// +// The public classes defined in this header are +// +// nsTArray<E>, +// CopyableTArray<E>, +// FallibleTArray<E>, +// AutoTArray<E, N>, +// CopyableAutoTArray<E, N> +// +// nsTArray, CopyableTArray, AutoTArray and CopyableAutoTArray are infallible by +// default. To opt-in to fallible behaviour, use the `mozilla::fallible` +// parameter and check the return value. +// +// CopyableTArray and CopyableAutoTArray< are copy-constructible and +// copy-assignable. Use these only when syntactically necessary to avoid implcit +// unintentional copies. nsTArray/AutoTArray can be conveniently copied using +// the Clone() member function. Consider using std::move where possible. +// +// If you just want to declare the nsTArray types (e.g., if you're in a header +// file and don't need the full nsTArray definitions) consider including +// nsTArrayForwardDeclare.h instead of nsTArray.h. +// +// The template parameter E specifies the type of the elements and has the +// following requirements: +// +// E MUST be safely memmove()'able. +// E MUST define a copy-constructor. +// E MAY define operator< for sorting. +// E MAY define operator== for searching. +// +// (Note that the memmove requirement may be relaxed for certain types - see +// nsTArray_RelocationStrategy below.) +// +// There is a public type value_type defined as E within each array class, and +// we reference the type under this name below. +// +// For member functions taking a Comparator instance, Comparator must be either +// a functor with a tri-state comparison function with a signature compatible to +// +// /** @return negative iff a < b, 0 iff a == b, positive iff a > b */ +// int (const value_type& a, const value_type& b); +// +// or a class defining member functions with signatures compatible to: +// +// class Comparator { +// public: +// /** @return True if the elements are equals; false otherwise. */ +// bool Equals(const value_type& a, const value_type& b) const; +// +// /** @return True if (a < b); false otherwise. */ +// bool LessThan(const value_type& a, const value_type& b) const; +// }; +// +// The Equals member function is used for searching, and the LessThan member +// function is used for searching and sorting. Note that some member functions, +// e.g. Compare, are templates where a different type Item can be used for the +// element to compare to. In that case, the signatures must be compatible to +// allow those comparisons, but the details are not documented here. +// + +// +// nsTArrayFallibleResult and nsTArrayInfallibleResult types are proxy types +// which are used because you cannot use a templated type which is bound to +// void as an argument to a void function. In order to work around that, we +// encode either a void or a boolean inside these proxy objects, and pass them +// to the aforementioned function instead, and then use the type information to +// decide what to do in the function. +// +// Note that public nsTArray methods should never return a proxy type. Such +// types are only meant to be used in the internal nsTArray helper methods. +// Public methods returning non-proxy types cannot be called from other +// nsTArray members. +// +struct nsTArrayFallibleResult { + // Note: allows implicit conversions from and to bool + MOZ_IMPLICIT constexpr nsTArrayFallibleResult(bool aResult) + : mResult(aResult) {} + + MOZ_IMPLICIT constexpr operator bool() { return mResult; } + + private: + bool mResult; +}; + +struct nsTArrayInfallibleResult {}; + +// +// nsTArray*Allocators must all use the same |free()|, to allow swap()'ing +// between fallible and infallible variants. +// + +struct nsTArrayFallibleAllocatorBase { + typedef bool ResultType; + typedef nsTArrayFallibleResult ResultTypeProxy; + + static constexpr ResultType Result(ResultTypeProxy aResult) { + return aResult; + } + static constexpr bool Successful(ResultTypeProxy aResult) { return aResult; } + static constexpr ResultTypeProxy SuccessResult() { return true; } + static constexpr ResultTypeProxy FailureResult() { return false; } + static constexpr ResultType ConvertBoolToResultType(bool aValue) { + return aValue; + } +}; + +struct nsTArrayInfallibleAllocatorBase { + typedef void ResultType; + typedef nsTArrayInfallibleResult ResultTypeProxy; + + static constexpr ResultType Result(ResultTypeProxy aResult) {} + static constexpr bool Successful(ResultTypeProxy) { return true; } + static constexpr ResultTypeProxy SuccessResult() { return ResultTypeProxy(); } + + [[noreturn]] static ResultTypeProxy FailureResult() { + MOZ_CRASH("Infallible nsTArray should never fail"); + } + + template <typename T> + static constexpr ResultType ConvertBoolToResultType(T aValue) { + if (!aValue) { + MOZ_CRASH("infallible nsTArray should never convert false to ResultType"); + } + } + + template <typename T> + static constexpr ResultType ConvertBoolToResultType( + const mozilla::NotNull<T>& aValue) {} +}; + +struct nsTArrayFallibleAllocator : nsTArrayFallibleAllocatorBase { + static void* Malloc(size_t aSize) { return malloc(aSize); } + static void* Realloc(void* aPtr, size_t aSize) { + return realloc(aPtr, aSize); + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t) {} +}; + +struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase { + static void* Malloc(size_t aSize) MOZ_NONNULL_RETURN { + return moz_xmalloc(aSize); + } + static void* Realloc(void* aPtr, size_t aSize) MOZ_NONNULL_RETURN { + return moz_xrealloc(aPtr, aSize); + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); } +}; + +// nsTArray_base stores elements into the space allocated beyond +// sizeof(*this). This is done to minimize the size of the nsTArray +// object when it is empty. +struct nsTArrayHeader { + uint32_t mLength; + uint32_t mCapacity : 31; + uint32_t mIsAutoArray : 1; +}; + +extern "C" { +extern const nsTArrayHeader sEmptyTArrayHeader; +} + +namespace detail { +// nsTArray_CopyDisabler disables copy operations. +class nsTArray_CopyDisabler { + public: + nsTArray_CopyDisabler() = default; + + nsTArray_CopyDisabler(const nsTArray_CopyDisabler&) = delete; + nsTArray_CopyDisabler& operator=(const nsTArray_CopyDisabler&) = delete; +}; + +} // namespace detail + +// This class provides a SafeElementAt method to nsTArray<E*> which does +// not take a second default value parameter. +template <class E, class Derived> +struct nsTArray_SafeElementAtHelper : public ::detail::nsTArray_CopyDisabler { + typedef E* elem_type; + typedef size_t index_type; + + // No implementation is provided for these two methods, and that is on + // purpose, since we don't support these functions on non-pointer type + // instantiations. + elem_type& SafeElementAt(index_type aIndex); + const elem_type& SafeElementAt(index_type aIndex) const; +}; + +template <class E, class Derived> +struct nsTArray_SafeElementAtHelper<E*, Derived> + : public ::detail::nsTArray_CopyDisabler { + typedef E* elem_type; + // typedef const E* const_elem_type; XXX: see below + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) { + return static_cast<Derived*>(this)->SafeElementAt(aIndex, nullptr); + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + // Also, the use of const_elem_type for nsTArray<xpcGCCallback> in + // xpcprivate.h causes build failures on Windows because xpcGCCallback is a + // function pointer and MSVC doesn't like qualifying it with |const|. + elem_type SafeElementAt(index_type aIndex) const { + return static_cast<const Derived*>(this)->SafeElementAt(aIndex, nullptr); + } +}; + +// E is a smart pointer type; the +// smart pointer can act as its element_type*. +template <class E, class Derived> +struct nsTArray_SafeElementAtSmartPtrHelper + : public ::detail::nsTArray_CopyDisabler { + typedef typename E::element_type* elem_type; + typedef const typename E::element_type* const_elem_type; + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) { + auto* derived = static_cast<Derived*>(this); + if (aIndex < derived->Length()) { + return derived->Elements()[aIndex]; + } + return nullptr; + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + elem_type SafeElementAt(index_type aIndex) const { + auto* derived = static_cast<const Derived*>(this); + if (aIndex < derived->Length()) { + return derived->Elements()[aIndex]; + } + return nullptr; + } +}; + +template <class T> +class nsCOMPtr; + +template <class E, class Derived> +struct nsTArray_SafeElementAtHelper<nsCOMPtr<E>, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper<nsCOMPtr<E>, Derived> {}; + +template <class E, class Derived> +struct nsTArray_SafeElementAtHelper<RefPtr<E>, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper<RefPtr<E>, Derived> {}; + +namespace mozilla { +template <class T> +class OwningNonNull; +} // namespace mozilla + +template <class E, class Derived> +struct nsTArray_SafeElementAtHelper<mozilla::OwningNonNull<E>, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper<mozilla::OwningNonNull<E>, + Derived> {}; + +// Servo bindings. +extern "C" void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, + size_t aElementSize); +extern "C" void Gecko_ClearPODTArray(void* aArray, size_t aElementSize, + size_t aElementAlign); + +// +// This class serves as a base class for nsTArray. It shouldn't be used +// directly. It holds common implementation code that does not depend on the +// element type of the nsTArray. +// +template <class Alloc, class RelocationStrategy> +class nsTArray_base { + // Allow swapping elements with |nsTArray_base|s created using a + // different allocator. This is kosher because all allocators use + // the same free(). + template <class XAlloc, class XRelocationStrategy> + friend class nsTArray_base; + + // Needed for AppendElements from an array with a different allocator, which + // calls ShiftData. + template <class E, class XAlloc> + friend class nsTArray_Impl; + + friend void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, + size_t aElemSize); + friend void Gecko_ClearPODTArray(void* aTArray, size_t aElementSize, + size_t aElementAlign); + + protected: + typedef nsTArrayHeader Header; + + public: + typedef size_t size_type; + typedef size_t index_type; + + // @return The number of elements in the array. + size_type Length() const { return mHdr->mLength; } + + // @return True if the array is empty or false otherwise. + bool IsEmpty() const { return Length() == 0; } + + // @return The number of elements that can fit in the array without forcing + // the array to be re-allocated. The length of an array is always less + // than or equal to its capacity. + size_type Capacity() const { return mHdr->mCapacity; } + +#ifdef DEBUG + void* DebugGetHeader() const { return mHdr; } +#endif + + protected: + nsTArray_base(); + + ~nsTArray_base(); + + nsTArray_base(const nsTArray_base&); + nsTArray_base& operator=(const nsTArray_base&); + + // Resize the storage if necessary to achieve the requested capacity. + // @param aCapacity The requested number of array elements. + // @param aElemSize The size of an array element. + // @return False if insufficient memory is available; true otherwise. + template <typename ActualAlloc> + typename ActualAlloc::ResultTypeProxy EnsureCapacity(size_type aCapacity, + size_type aElemSize) { + // Do this check here so that our callers can inline it. + if (aCapacity <= mHdr->mCapacity) { + return ActualAlloc::SuccessResult(); + } + return EnsureCapacityImpl<ActualAlloc>(aCapacity, aElemSize); + } + + // The rest of EnsureCapacity. Should only be called if aCapacity > + // mHdr->mCapacity. + template <typename ActualAlloc> + typename ActualAlloc::ResultTypeProxy EnsureCapacityImpl(size_type aCapacity, + size_type aElemSize); + + // Extend the storage to accommodate aCount extra elements. + // @param aLength The current size of the array. + // @param aCount The number of elements to add. + // @param aElemSize The size of an array element. + // @return False if insufficient memory is available or the new length + // would overflow; true otherwise. + template <typename ActualAlloc> + typename ActualAlloc::ResultTypeProxy ExtendCapacity(size_type aLength, + size_type aCount, + size_type aElemSize); + + // Tries to resize the storage to the minimum required amount. If this fails, + // the array is left as-is. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + void ShrinkCapacity(size_type aElemSize, size_t aElemAlign); + + // Resizes the storage to 0. This may only be called when Length() is already + // 0. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + void ShrinkCapacityToZero(size_type aElemSize, size_t aElemAlign); + + // This method may be called to resize a "gap" in the array by shifting + // elements around. It updates mLength appropriately. If the resulting + // array has zero elements, then the array's memory is free'd. + // @param aStart The starting index of the gap. + // @param aOldLen The current length of the gap. + // @param aNewLen The desired length of the gap. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + template <typename ActualAlloc> + void ShiftData(index_type aStart, size_type aOldLen, size_type aNewLen, + size_type aElemSize, size_t aElemAlign); + + // This method may be called to swap elements from the end of the array to + // fill a "gap" in the array. If the resulting array has zero elements, then + // the array's memory is free'd. + // @param aStart The starting index of the gap. + // @param aCount The length of the gap. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + template <typename ActualAlloc> + void SwapFromEnd(index_type aStart, size_type aCount, size_type aElemSize, + size_t aElemAlign); + + // This method increments the length member of the array's header. + // Note that mHdr may actually be sEmptyTArrayHeader in the case where a + // zero-length array is inserted into our array. But then aNum should + // always be 0. + void IncrementLength(size_t aNum) { + if (HasEmptyHeader()) { + if (MOZ_UNLIKELY(aNum != 0)) { + // Writing a non-zero length to the empty header would be extremely bad. + MOZ_CRASH(); + } + } else { + mHdr->mLength += aNum; + } + } + + // This method inserts blank slots into the array. + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of slots to insert + // @param aElementSize the size of an array element. + // @param aElemAlign the alignment in bytes of an array element. + template <typename ActualAlloc> + typename ActualAlloc::ResultTypeProxy InsertSlotsAt(index_type aIndex, + size_type aCount, + size_type aElementSize, + size_t aElemAlign); + + template <typename ActualAlloc, class Allocator> + typename ActualAlloc::ResultTypeProxy SwapArrayElements( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign); + + template <class Allocator> + void MoveConstructNonAutoArray( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign); + + template <class Allocator> + void MoveInit(nsTArray_base<Allocator, RelocationStrategy>& aOther, + size_type aElemSize, size_t aElemAlign); + + // This is an RAII class used in SwapArrayElements. + class IsAutoArrayRestorer { + public: + IsAutoArrayRestorer(nsTArray_base<Alloc, RelocationStrategy>& aArray, + size_t aElemAlign); + ~IsAutoArrayRestorer(); + + private: + nsTArray_base<Alloc, RelocationStrategy>& mArray; + size_t mElemAlign; + bool mIsAuto; + }; + + // Helper function for SwapArrayElements. Ensures that if the array + // is an AutoTArray that it doesn't use the built-in buffer. + template <typename ActualAlloc> + bool EnsureNotUsingAutoArrayBuffer(size_type aElemSize); + + // Returns true if this nsTArray is an AutoTArray with a built-in buffer. + bool IsAutoArray() const { return mHdr->mIsAutoArray; } + + // Returns a Header for the built-in buffer of this AutoTArray. + Header* GetAutoArrayBuffer(size_t aElemAlign) { + MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this"); + return GetAutoArrayBufferUnsafe(aElemAlign); + } + const Header* GetAutoArrayBuffer(size_t aElemAlign) const { + MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this"); + return GetAutoArrayBufferUnsafe(aElemAlign); + } + + // Returns a Header for the built-in buffer of this AutoTArray, but doesn't + // assert that we are an AutoTArray. + Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) { + return const_cast<Header*>( + static_cast<const nsTArray_base<Alloc, RelocationStrategy>*>(this) + ->GetAutoArrayBufferUnsafe(aElemAlign)); + } + const Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) const; + + // Returns true if this is an AutoTArray and it currently uses the + // built-in buffer to store its elements. + bool UsesAutoArrayBuffer() const; + + // The array's elements (prefixed with a Header). This pointer is never + // null. If the array is empty, then this will point to sEmptyTArrayHeader. + Header* mHdr; + + Header* Hdr() const MOZ_NONNULL_RETURN { return mHdr; } + Header** PtrToHdr() MOZ_NONNULL_RETURN { return &mHdr; } + static Header* EmptyHdr() MOZ_NONNULL_RETURN { + return const_cast<Header*>(&sEmptyTArrayHeader); + } + + [[nodiscard]] bool HasEmptyHeader() const { return mHdr == EmptyHdr(); } +}; + +namespace detail { + +// Used for argument checking in nsTArrayElementTraits::Emplace. +template <typename... T> +struct ChooseFirst; + +template <> +struct ChooseFirst<> { + // Choose a default type that is guaranteed to not match E* for any + // nsTArray<E>. + typedef void Type; +}; + +template <typename A, typename... Args> +struct ChooseFirst<A, Args...> { + typedef A Type; +}; + +} // namespace detail + +// +// This class defines convenience functions for element specific operations. +// Specialize this template if necessary. +// +template <class E> +class nsTArrayElementTraits { + public: + // Invoke the default constructor in place. + static inline void Construct(E* aE) { + // Do NOT call "E()"! That triggers C++ "default initialization" + // which zeroes out POD ("plain old data") types such as regular + // ints. We don't want that because it can be a performance issue + // and people don't expect it; nsTArray should work like a regular + // C/C++ array in this respect. + new (static_cast<void*>(aE)) E; + } + // Invoke the copy-constructor in place. + template <class A> + static inline void Construct(E* aE, A&& aArg) { + using E_NoCV = std::remove_cv_t<E>; + using A_NoCV = std::remove_cv_t<A>; + static_assert(!std::is_same_v<E_NoCV*, A_NoCV>, + "For safety, we disallow constructing nsTArray<E> elements " + "from E* pointers. See bug 960591."); + new (static_cast<void*>(aE)) E(std::forward<A>(aArg)); + } + // Construct in place. + template <class... Args> + static inline void Emplace(E* aE, Args&&... aArgs) { + using E_NoCV = std::remove_cv_t<E>; + using A_NoCV = + std::remove_cv_t<typename ::detail::ChooseFirst<Args...>::Type>; + static_assert(!std::is_same_v<E_NoCV*, A_NoCV>, + "For safety, we disallow constructing nsTArray<E> elements " + "from E* pointers. See bug 960591."); + new (static_cast<void*>(aE)) E(std::forward<Args>(aArgs)...); + } + // Invoke the destructor in place. + static inline void Destruct(E* aE) { aE->~E(); } +}; + +// The default comparator used by nsTArray +template <class A, class B> +class nsDefaultComparator { + public: + bool Equals(const A& aA, const B& aB) const { return aA == aB; } + bool LessThan(const A& aA, const B& aB) const { return aA < aB; } +}; + +template <bool IsTriviallyCopyConstructible, bool IsSameType> +struct AssignRangeAlgorithm { + template <class Item, class ElemType, class IndexType, class SizeType> + static void implementation(ElemType* aElements, IndexType aStart, + SizeType aCount, const Item* aValues) { + ElemType* iter = aElements + aStart; + ElemType* end = iter + aCount; + for (; iter != end; ++iter, ++aValues) { + nsTArrayElementTraits<ElemType>::Construct(iter, *aValues); + } + } +}; + +template <> +struct AssignRangeAlgorithm<true, true> { + template <class Item, class ElemType, class IndexType, class SizeType> + static void implementation(ElemType* aElements, IndexType aStart, + SizeType aCount, const Item* aValues) { + if (aValues) { + memcpy(aElements + aStart, aValues, aCount * sizeof(ElemType)); + } + } +}; + +// +// Normally elements are copied with memcpy and memmove, but for some element +// types that is problematic. The nsTArray_RelocationStrategy template class +// can be specialized to ensure that copying calls constructors and destructors +// instead, as is done below for JS::Heap<E> elements. +// + +// +// A class that defines how to copy elements using memcpy/memmove. +// +struct nsTArray_RelocateUsingMemutils { + const static bool allowRealloc = true; + + static void RelocateNonOverlappingRegionWithHeader(void* aDest, + const void* aSrc, + size_t aCount, + size_t aElemSize) { + memcpy(aDest, aSrc, sizeof(nsTArrayHeader) + aCount * aElemSize); + } + + static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) { + memmove(aDest, aSrc, aCount * aElemSize); + } + + static void RelocateNonOverlappingRegion(void* aDest, void* aSrc, + size_t aCount, size_t aElemSize) { + memcpy(aDest, aSrc, aCount * aElemSize); + } +}; + +// +// A template class that defines how to relocate elements using the type's move +// constructor and destructor appropriately. +// +template <class ElemType> +struct nsTArray_RelocateUsingMoveConstructor { + typedef nsTArrayElementTraits<ElemType> traits; + + const static bool allowRealloc = false; + + static void RelocateNonOverlappingRegionWithHeader(void* aDest, void* aSrc, + size_t aCount, + size_t aElemSize) { + nsTArrayHeader* destHeader = static_cast<nsTArrayHeader*>(aDest); + nsTArrayHeader* srcHeader = static_cast<nsTArrayHeader*>(aSrc); + *destHeader = *srcHeader; + RelocateNonOverlappingRegion( + static_cast<uint8_t*>(aDest) + sizeof(nsTArrayHeader), + static_cast<uint8_t*>(aSrc) + sizeof(nsTArrayHeader), aCount, + aElemSize); + } + + // RelocateNonOverlappingRegion and RelocateOverlappingRegion are defined by + // analogy with memmove and memcpy that are used for relocation of + // trivially-relocatable types through nsTArray_RelocateUsingMemutils. What + // they actually do is slightly different: RelocateOverlappingRegion checks to + // see which direction the movement needs to take place, whether from + // back-to-front of the range to be moved or from front-to-back. + // RelocateNonOverlappingRegion assumes that relocating front-to-back is + // always valid. They use RelocateRegionForward and RelocateRegionBackward, + // which are analogous to std::move and std::move_backward respectively, + // except they don't move-assign the destination from the source but + // move-construct the destination from the source and destroy the source. + static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) { + ElemType* destBegin = static_cast<ElemType*>(aDest); + ElemType* srcBegin = static_cast<ElemType*>(aSrc); + + // If destination and source are the same, this is a no-op. + // In practice, we don't do this. + if (destBegin == srcBegin) { + return; + } + + ElemType* srcEnd = srcBegin + aCount; + ElemType* destEnd = destBegin + aCount; + + // Figure out whether to relocate back-to-front or front-to-back. + if (srcEnd > destBegin && srcEnd < destEnd) { + RelocateRegionBackward(srcBegin, srcEnd, destEnd); + } else { + RelocateRegionForward(srcBegin, srcEnd, destBegin); + } + } + + static void RelocateNonOverlappingRegion(void* aDest, void* aSrc, + size_t aCount, size_t aElemSize) { + ElemType* destBegin = static_cast<ElemType*>(aDest); + ElemType* srcBegin = static_cast<ElemType*>(aSrc); + ElemType* srcEnd = srcBegin + aCount; +#ifdef DEBUG + ElemType* destEnd = destBegin + aCount; + MOZ_ASSERT(srcEnd <= destBegin || srcBegin >= destEnd); +#endif + RelocateRegionForward(srcBegin, srcEnd, destBegin); + } + + private: + static void RelocateRegionForward(ElemType* srcBegin, ElemType* srcEnd, + ElemType* destBegin) { + ElemType* srcElem = srcBegin; + ElemType* destElem = destBegin; + + while (srcElem != srcEnd) { + RelocateElement(srcElem, destElem); + ++destElem; + ++srcElem; + } + } + + static void RelocateRegionBackward(ElemType* srcBegin, ElemType* srcEnd, + ElemType* destEnd) { + ElemType* srcElem = srcEnd; + ElemType* destElem = destEnd; + while (srcElem != srcBegin) { + --destElem; + --srcElem; + RelocateElement(srcElem, destElem); + } + } + + static void RelocateElement(ElemType* srcElem, ElemType* destElem) { + traits::Construct(destElem, std::move(*srcElem)); + traits::Destruct(srcElem); + } +}; + +// +// The default behaviour is to use memcpy/memmove for everything. +// +template <class E> +struct MOZ_NEEDS_MEMMOVABLE_TYPE nsTArray_RelocationStrategy { + using Type = nsTArray_RelocateUsingMemutils; +}; + +// +// Some classes require constructors/destructors to be called, so they are +// specialized here. +// +#define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(E) \ + template <> \ + struct nsTArray_RelocationStrategy<E> { \ + using Type = nsTArray_RelocateUsingMoveConstructor<E>; \ + }; + +#define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(T) \ + template <typename S> \ + struct nsTArray_RelocationStrategy<T<S>> { \ + using Type = nsTArray_RelocateUsingMoveConstructor<T<S>>; \ + }; + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(JS::Heap) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(std::function) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(mozilla::ipc::Endpoint) + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsRegion) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsIntRegion) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::layers::TileClient) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::SerializedStructuredCloneBuffer) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::ipc::StructuredCloneData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::ClonedMessageData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::indexedDB::ObjectStoreCursorResponse) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::indexedDB::IndexCursorResponse) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo); +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(JSStructuredCloneData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::MessageData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::RefMessageData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::SourceBufferTask) + +// +// Base class for nsTArray_Impl that is templated on element type and derived +// nsTArray_Impl class, to allow extra conversions to be added for specific +// types. +// +template <class E, class Derived> +struct nsTArray_TypedBase : public nsTArray_SafeElementAtHelper<E, Derived> {}; + +// +// Specialization of nsTArray_TypedBase for arrays containing JS::Heap<E> +// elements. +// +// These conversions are safe because JS::Heap<E> and E share the same +// representation, and since the result of the conversions are const references +// we won't miss any barriers. +// +// The static_cast is necessary to obtain the correct address for the derived +// class since we are a base class used in multiple inheritance. +// +template <class E, class Derived> +struct nsTArray_TypedBase<JS::Heap<E>, Derived> + : public nsTArray_SafeElementAtHelper<JS::Heap<E>, Derived> { + operator const nsTArray<E>&() { + static_assert(sizeof(E) == sizeof(JS::Heap<E>), + "JS::Heap<E> must be binary compatible with E."); + Derived* self = static_cast<Derived*>(this); + return *reinterpret_cast<nsTArray<E>*>(self); + } + + operator const FallibleTArray<E>&() { + Derived* self = static_cast<Derived*>(this); + return *reinterpret_cast<FallibleTArray<E>*>(self); + } +}; + +namespace detail { + +// These helpers allow us to differentiate between tri-state comparator +// functions and classes with LessThan() and Equal() methods. If an object, when +// called as a function with two instances of our element type, returns an int, +// we treat it as a tri-state comparator. +// +// T is the type of the comparator object we want to check. U is the array +// element type that we'll be comparing. +// +// V is never passed, and is only used to allow us to specialize on the return +// value of the comparator function. +template <typename T, typename U, typename V = int> +struct IsCompareMethod : std::false_type {}; + +template <typename T, typename U> +struct IsCompareMethod< + T, U, decltype(std::declval<T>()(std::declval<U>(), std::declval<U>()))> + : std::true_type {}; + +// These two wrappers allow us to use either a tri-state comparator, or an +// object with Equals() and LessThan() methods interchangeably. They provide a +// tri-state Compare() method, and Equals() method, and a LessThan() method. +// +// Depending on the type of the underlying comparator, they either pass these +// through directly, or synthesize them from the methods available on the +// comparator. +// +// Callers should always use the most-specific of these methods that match their +// purpose. + +// Comparator wrapper for a tri-state comparator function +template <typename T, typename U, bool IsCompare = IsCompareMethod<T, U>::value> +struct CompareWrapper { +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4180) /* Silence "qualifier applied to function \ + type has no meaning" warning */ +#endif + MOZ_IMPLICIT CompareWrapper(const T& aComparator) + : mComparator(aComparator) {} + + template <typename A, typename B> + int Compare(A& aLeft, B& aRight) const { + return mComparator(aLeft, aRight); + } + + template <typename A, typename B> + bool Equals(A& aLeft, B& aRight) const { + return Compare(aLeft, aRight) == 0; + } + + template <typename A, typename B> + bool LessThan(A& aLeft, B& aRight) const { + return Compare(aLeft, aRight) < 0; + } + + const T& mComparator; +#ifdef _MSC_VER +# pragma warning(pop) +#endif +}; + +// Comparator wrapper for a class with Equals() and LessThan() methods. +template <typename T, typename U> +struct CompareWrapper<T, U, false> { + MOZ_IMPLICIT CompareWrapper(const T& aComparator) + : mComparator(aComparator) {} + + template <typename A, typename B> + int Compare(A& aLeft, B& aRight) const { + if (LessThan(aLeft, aRight)) { + return -1; + } + if (Equals(aLeft, aRight)) { + return 0; + } + return 1; + } + + template <typename A, typename B> + bool Equals(A& aLeft, B& aRight) const { + return mComparator.Equals(aLeft, aRight); + } + + template <typename A, typename B> + bool LessThan(A& aLeft, B& aRight) const { + return mComparator.LessThan(aLeft, aRight); + } + + const T& mComparator; +}; + +} // namespace detail + +// +// nsTArray_Impl contains most of the guts supporting nsTArray, FallibleTArray, +// AutoTArray. +// +// The only situation in which you might need to use nsTArray_Impl in your code +// is if you're writing code which mutates a TArray which may or may not be +// infallible. +// +// Code which merely reads from a TArray which may or may not be infallible can +// simply cast the TArray to |const nsTArray&|; both fallible and infallible +// TArrays can be cast to |const nsTArray&|. +// +template <class E, class Alloc> +class nsTArray_Impl + : public nsTArray_base<Alloc, + typename nsTArray_RelocationStrategy<E>::Type>, + public nsTArray_TypedBase<E, nsTArray_Impl<E, Alloc>> { + private: + friend class nsTArray<E>; + + typedef nsTArrayFallibleAllocator FallibleAlloc; + typedef nsTArrayInfallibleAllocator InfallibleAlloc; + + public: + typedef typename nsTArray_RelocationStrategy<E>::Type relocation_type; + typedef nsTArray_base<Alloc, relocation_type> base_type; + typedef typename base_type::size_type size_type; + typedef typename base_type::index_type index_type; + typedef E value_type; + typedef nsTArray_Impl<E, Alloc> self_type; + typedef nsTArrayElementTraits<E> elem_traits; + typedef nsTArray_SafeElementAtHelper<E, self_type> safeelementat_helper_type; + typedef mozilla::ArrayIterator<value_type&, self_type> iterator; + typedef mozilla::ArrayIterator<const value_type&, self_type> const_iterator; + typedef std::reverse_iterator<iterator> reverse_iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + + using base_type::EmptyHdr; + using safeelementat_helper_type::SafeElementAt; + + // A special value that is used to indicate an invalid or unknown index + // into the array. + static const index_type NoIndex = index_type(-1); + + using base_type::Length; + + // + // Finalization method + // + + ~nsTArray_Impl() { + if (!base_type::IsEmpty()) { + ClearAndRetainStorage(); + } + // mHdr cleanup will be handled by base destructor + } + + // + // Initialization methods + // + + nsTArray_Impl() = default; + + // Initialize this array and pre-allocate some number of elements. + explicit nsTArray_Impl(size_type aCapacity) { SetCapacity(aCapacity); } + + // Initialize this array with an r-value. + // Allow different types of allocators, since the allocator doesn't matter. + template <typename Allocator> + explicit nsTArray_Impl(nsTArray_Impl<E, Allocator>&& aOther) noexcept { + // We cannot be a (Copyable)AutoTArray because that overrides this ctor. + MOZ_ASSERT(!this->IsAutoArray()); + + // This does not use SwapArrayElements because that's unnecessarily complex. + this->MoveConstructNonAutoArray(aOther, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + } + + // The array's copy-constructor performs a 'deep' copy of the given array. + // @param aOther The array object to copy. + // + // It's very important that we declare this method as taking |const + // self_type&| as opposed to taking |const nsTArray_Impl<E, OtherAlloc>| for + // an arbitrary OtherAlloc. + // + // If we don't declare a constructor taking |const self_type&|, C++ generates + // a copy-constructor for this class which merely copies the object's + // members, which is obviously wrong. + // + // You can pass an nsTArray_Impl<E, OtherAlloc> to this method because + // nsTArray_Impl<E, X> can be cast to const nsTArray_Impl<E, Y>&. So the + // effect on the API is the same as if we'd declared this method as taking + // |const nsTArray_Impl<E, OtherAlloc>&|. + nsTArray_Impl(const nsTArray_Impl&) = default; + + // Allow converting to a const array with a different kind of allocator, + // Since the allocator doesn't matter for const arrays + template <typename Allocator> + [[nodiscard]] operator const nsTArray_Impl<E, Allocator>&() const& { + return *reinterpret_cast<const nsTArray_Impl<E, Allocator>*>(this); + } + // And we have to do this for our subclasses too + [[nodiscard]] operator const nsTArray<E>&() const& { + return *reinterpret_cast<const nsTArray<E>*>(this); + } + [[nodiscard]] operator const FallibleTArray<E>&() const& { + return *reinterpret_cast<const FallibleTArray<E>*>(this); + } + + // The array's assignment operator performs a 'deep' copy of the given + // array. It is optimized to reuse existing storage if possible. + // @param aOther The array object to copy. + nsTArray_Impl& operator=(const nsTArray_Impl&) = default; + + // The array's move assignment operator steals the underlying data from + // the other array. + // @param other The array object to move from. + self_type& operator=(self_type&& aOther) { + if (this != &aOther) { + Clear(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + return *this; + } + + // Return true if this array has the same length and the same + // elements as |aOther|. + template <typename Allocator> + [[nodiscard]] bool operator==( + const nsTArray_Impl<E, Allocator>& aOther) const { + size_type len = Length(); + if (len != aOther.Length()) { + return false; + } + + // XXX std::equal would be as fast or faster here + for (index_type i = 0; i < len; ++i) { + if (!(operator[](i) == aOther[i])) { + return false; + } + } + + return true; + } + + // Return true if this array does not have the same length and the same + // elements as |aOther|. + [[nodiscard]] bool operator!=(const self_type& aOther) const { + return !operator==(aOther); + } + + // If Alloc == FallibleAlloc, ReplaceElementsAt might fail, without a way to + // signal this to the caller, so we disallow copying via operator=. Callers + // should use ReplaceElementsAt with a fallible argument instead, and check + // the result. + template <typename Allocator, + typename = std::enable_if_t<std::is_same_v<Alloc, InfallibleAlloc>, + Allocator>> + self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) { + AssignInternal<InfallibleAlloc>(aOther.Elements(), aOther.Length()); + return *this; + } + + template <typename Allocator> + self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) { + Clear(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + return *this; + } + + // @return The amount of memory used by this nsTArray_Impl, excluding + // sizeof(*this). If you want to measure anything hanging off the array, you + // must iterate over the elements and measure them individually; hence the + // "Shallow" prefix. + [[nodiscard]] size_t ShallowSizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + if (this->UsesAutoArrayBuffer() || this->HasEmptyHeader()) { + return 0; + } + return aMallocSizeOf(this->Hdr()); + } + + // @return The amount of memory used by this nsTArray_Impl, including + // sizeof(*this). If you want to measure anything hanging off the array, you + // must iterate over the elements and measure them individually; hence the + // "Shallow" prefix. + [[nodiscard]] size_t ShallowSizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + // + // Accessor methods + // + + // This method provides direct access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + [[nodiscard]] value_type* Elements() MOZ_NONNULL_RETURN { + return reinterpret_cast<value_type*>(Hdr() + 1); + } + + // This method provides direct, readonly access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + [[nodiscard]] const value_type* Elements() const MOZ_NONNULL_RETURN { + return reinterpret_cast<const value_type*>(Hdr() + 1); + } + + // This method provides direct access to an element of the array. The given + // index must be within the array bounds. + // @param aIndex The index of an element in the array. + // @return A reference to the i'th element of the array. + [[nodiscard]] value_type& ElementAt(index_type aIndex) { + if (MOZ_UNLIKELY(aIndex >= Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + return Elements()[aIndex]; + } + + // This method provides direct, readonly access to an element of the array + // The given index must be within the array bounds. + // @param aIndex The index of an element in the array. + // @return A const reference to the i'th element of the array. + [[nodiscard]] const value_type& ElementAt(index_type aIndex) const { + if (MOZ_UNLIKELY(aIndex >= Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + return Elements()[aIndex]; + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + [[nodiscard]] value_type& SafeElementAt(index_type aIndex, value_type& aDef) { + return aIndex < Length() ? Elements()[aIndex] : aDef; + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + [[nodiscard]] const value_type& SafeElementAt(index_type aIndex, + const value_type& aDef) const { + return aIndex < Length() ? Elements()[aIndex] : aDef; + } + + // Shorthand for ElementAt(aIndex) + [[nodiscard]] value_type& operator[](index_type aIndex) { + return ElementAt(aIndex); + } + + // Shorthand for ElementAt(aIndex) + [[nodiscard]] const value_type& operator[](index_type aIndex) const { + return ElementAt(aIndex); + } + + // Shorthand for ElementAt(length - 1) + [[nodiscard]] value_type& LastElement() { return ElementAt(Length() - 1); } + + // Shorthand for ElementAt(length - 1) + [[nodiscard]] const value_type& LastElement() const { + return ElementAt(Length() - 1); + } + + // Shorthand for SafeElementAt(length - 1, def) + [[nodiscard]] value_type& SafeLastElement(value_type& aDef) { + return SafeElementAt(Length() - 1, aDef); + } + + // Shorthand for SafeElementAt(length - 1, def) + [[nodiscard]] const value_type& SafeLastElement( + const value_type& aDef) const { + return SafeElementAt(Length() - 1, aDef); + } + + // Methods for range-based for loops. + [[nodiscard]] iterator begin() { return iterator(*this, 0); } + [[nodiscard]] const_iterator begin() const { + return const_iterator(*this, 0); + } + [[nodiscard]] const_iterator cbegin() const { return begin(); } + [[nodiscard]] iterator end() { return iterator(*this, Length()); } + [[nodiscard]] const_iterator end() const { + return const_iterator(*this, Length()); + } + [[nodiscard]] const_iterator cend() const { return end(); } + + // Methods for reverse iterating. + [[nodiscard]] reverse_iterator rbegin() { return reverse_iterator(end()); } + [[nodiscard]] const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + [[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); } + [[nodiscard]] reverse_iterator rend() { return reverse_iterator(begin()); } + [[nodiscard]] const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + [[nodiscard]] const_reverse_iterator crend() const { return rend(); } + + // Span integration + + [[nodiscard]] operator mozilla::Span<value_type>() { + return mozilla::Span<value_type>(Elements(), Length()); + } + + [[nodiscard]] operator mozilla::Span<const value_type>() const { + return mozilla::Span<const value_type>(Elements(), Length()); + } + + // + // Search methods + // + + // This method searches for the first element in this array that is equal + // to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found. + template <class Item, class Comparator> + [[nodiscard]] bool Contains(const Item& aItem, + const Comparator& aComp) const { + return ApplyIf( + aItem, 0, aComp, []() { return true; }, []() { return false; }); + } + + // Like Contains(), but assumes a sorted array. + template <class Item, class Comparator> + [[nodiscard]] bool ContainsSorted(const Item& aItem, + const Comparator& aComp) const { + return BinaryIndexOf(aItem, aComp) != NoIndex; + } + + // This method searches for the first element in this array that is equal + // to the given element. This method assumes that 'operator==' is defined + // for value_type. + // @param aItem The item to search for. + // @return true if the element was found. + template <class Item> + [[nodiscard]] bool Contains(const Item& aItem) const { + return Contains(aItem, nsDefaultComparator<value_type, Item>()); + } + + // Like Contains(), but assumes a sorted array. + template <class Item> + [[nodiscard]] bool ContainsSorted(const Item& aItem) const { + return BinaryIndexOf(aItem) != NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @param aComp The Comparator used to determine element equality. + // @return The index of the found element or NoIndex if not found. + template <class Item, class Comparator> + [[nodiscard]] index_type IndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const { + ::detail::CompareWrapper<Comparator, Item> comp(aComp); + + const value_type* iter = Elements() + aStart; + const value_type* iend = Elements() + Length(); + for (; iter != iend; ++iter) { + if (comp.Equals(*iter, aItem)) { + return index_type(iter - Elements()); + } + } + return NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. This method assumes + // that 'operator==' is defined for value_type. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @return The index of the found element or NoIndex if not found. + template <class Item> + [[nodiscard]] index_type IndexOf(const Item& aItem, + index_type aStart = 0) const { + return IndexOf(aItem, aStart, nsDefaultComparator<value_type, Item>()); + } + + // This method searches for the offset of the last element in this + // array that is equal to the given element. + // @param aItem The item to search for. + // @param aStart The index to start from. If greater than or equal to the + // length of the array, then the entire array is searched. + // @param aComp The Comparator used to determine element equality. + // @return The index of the found element or NoIndex if not found. + template <class Item, class Comparator> + [[nodiscard]] index_type LastIndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const { + ::detail::CompareWrapper<Comparator, Item> comp(aComp); + + size_type endOffset = aStart >= Length() ? Length() : aStart + 1; + const value_type* iend = Elements() - 1; + const value_type* iter = iend + endOffset; + for (; iter != iend; --iter) { + if (comp.Equals(*iter, aItem)) { + return index_type(iter - Elements()); + } + } + return NoIndex; + } + + // This method searches for the offset of the last element in this + // array that is equal to the given element. This method assumes + // that 'operator==' is defined for value_type. + // @param aItem The item to search for. + // @param aStart The index to start from. If greater than or equal to the + // length of the array, then the entire array is searched. + // @return The index of the found element or NoIndex if not found. + template <class Item> + [[nodiscard]] index_type LastIndexOf(const Item& aItem, + index_type aStart = NoIndex) const { + return LastIndexOf(aItem, aStart, nsDefaultComparator<value_type, Item>()); + } + + // This method searches for the offset for the element in this array + // that is equal to the given element. The array is assumed to be sorted. + // If there is more than one equivalent element, there is no guarantee + // on which one will be returned. + // @param aItem The item to search for. + // @param aComp The Comparator used. + // @return The index of the found element or NoIndex if not found. + template <class Item, class Comparator> + [[nodiscard]] index_type BinaryIndexOf(const Item& aItem, + const Comparator& aComp) const { + using mozilla::BinarySearchIf; + ::detail::CompareWrapper<Comparator, Item> comp(aComp); + + size_t index; + bool found = BinarySearchIf( + Elements(), 0, Length(), + // Note: We pass the Compare() args here in reverse order and negate the + // results for compatibility reasons. Some existing callers use Equals() + // functions with first arguments which match aElement but not aItem, or + // second arguments that match aItem but not aElement. To accommodate + // those callers, we preserve the argument order of the older version of + // this API. These callers, however, should be fixed, and this special + // case removed. + [&](const value_type& aElement) { + return -comp.Compare(aElement, aItem); + }, + &index); + return found ? index : NoIndex; + } + + // This method searches for the offset for the element in this array + // that is equal to the given element. The array is assumed to be sorted. + // This method assumes that 'operator==' and 'operator<' are defined. + // @param aItem The item to search for. + // @return The index of the found element or NoIndex if not found. + template <class Item> + [[nodiscard]] index_type BinaryIndexOf(const Item& aItem) const { + return BinaryIndexOf(aItem, nsDefaultComparator<value_type, Item>()); + } + + // + // Mutation methods + // + private: + template <typename ActualAlloc, class Item> + typename ActualAlloc::ResultType AssignInternal(const Item* aArray, + size_type aArrayLen); + + public: + template <class Allocator, typename ActualAlloc = Alloc> + [[nodiscard]] typename ActualAlloc::ResultType Assign( + const nsTArray_Impl<E, Allocator>& aOther) { + return AssignInternal<ActualAlloc>(aOther.Elements(), aOther.Length()); + } + + template <class Allocator> + [[nodiscard]] bool Assign(const nsTArray_Impl<E, Allocator>& aOther, + const mozilla::fallible_t&) { + return Assign<Allocator, FallibleAlloc>(aOther); + } + + template <class Allocator> + void Assign(nsTArray_Impl<E, Allocator>&& aOther) { + Clear(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + // This method call the destructor on each element of the array, empties it, + // but does not shrink the array's capacity. + // See also SetLengthAndRetainStorage. + // Make sure to call Compact() if needed to avoid keeping a huge array + // around. + void ClearAndRetainStorage() { + if (this->HasEmptyHeader()) { + return; + } + + DestructRange(0, Length()); + base_type::mHdr->mLength = 0; + } + + // This method modifies the length of the array, but unlike SetLength + // it doesn't deallocate/reallocate the current internal storage. + // The new length MUST be shorter than or equal to the current capacity. + // If the new length is larger than the existing length of the array, + // then new elements will be constructed using value_type's default + // constructor. If shorter, elements will be destructed and removed. + // See also ClearAndRetainStorage. + // @param aNewLen The desired length of this array. + void SetLengthAndRetainStorage(size_type aNewLen) { + MOZ_ASSERT(aNewLen <= base_type::Capacity()); + size_type oldLen = Length(); + if (aNewLen > oldLen) { + /// XXX(Bug 1631367) SetLengthAndRetainStorage should be disabled for + /// FallibleTArray. + InsertElementsAtInternal<InfallibleAlloc>(oldLen, aNewLen - oldLen); + return; + } + if (aNewLen < oldLen) { + DestructRange(aNewLen, oldLen - aNewLen); + base_type::mHdr->mLength = aNewLen; + } + } + + // This method replaces a range of elements in this array. + // @param aStart The starting index of the elements to replace. + // @param aCount The number of elements to replace. This may be zero to + // insert elements without removing any existing elements. + // @param aArray The values to copy into this array. Must be non-null, + // and these elements must not already exist in the array + // being modified. + // @param aArrayLen The number of values to copy into this array. + // @return A pointer to the new elements in the array, or null if + // the operation failed due to insufficient memory. + private: + template <typename ActualAlloc, class Item> + value_type* ReplaceElementsAtInternal(index_type aStart, size_type aCount, + const Item* aArray, + size_type aArrayLen); + + public: + template <class Item> + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item* aArray, + size_type aArrayLen, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aArray, + aArrayLen); + } + + // A variation on the ReplaceElementsAt method defined above. + template <class Item> + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + const nsTArray<Item>& aArray, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aArray); + } + + template <class Item> + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + mozilla::Span<Item> aSpan, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aSpan); + } + + // A variation on the ReplaceElementsAt method defined above. + template <class Item> + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item& aItem, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal<FallibleAlloc>(aStart, aCount, aItem); + } + + // A variation on the ReplaceElementsAt method defined above. + template <class Item> + mozilla::NotNull<value_type*> ReplaceElementAt(index_type aIndex, + Item&& aItem) { + value_type* const elem = &ElementAt(aIndex); + elem_traits::Destruct(elem); + elem_traits::Construct(elem, std::forward<Item>(aItem)); + return mozilla::WrapNotNullUnchecked(elem); + } + + // InsertElementsAt is ReplaceElementsAt with 0 elements to replace. + // XXX Provide a proper documentation of InsertElementsAt. + template <class Item> + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + const Item* aArray, + size_type aArrayLen, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal<FallibleAlloc>(aIndex, 0, aArray, + aArrayLen); + } + + template <class Item, class Allocator> + [[nodiscard]] value_type* InsertElementsAt( + index_type aIndex, const nsTArray_Impl<Item, Allocator>& aArray, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal<FallibleAlloc>( + aIndex, 0, aArray.Elements(), aArray.Length()); + } + + template <class Item> + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + mozilla::Span<Item> aSpan, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal<FallibleAlloc>(aIndex, 0, aSpan.Elements(), + aSpan.Length()); + } + + private: + template <typename ActualAlloc> + value_type* InsertElementAtInternal(index_type aIndex); + + // Insert a new element without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly inserted element, or null on OOM. + public: + [[nodiscard]] value_type* InsertElementAt(index_type aIndex, + const mozilla::fallible_t&) { + return InsertElementAtInternal<FallibleAlloc>(aIndex); + } + + private: + template <typename ActualAlloc, class Item> + value_type* InsertElementAtInternal(index_type aIndex, Item&& aItem); + + // Insert a new element, move constructing if possible. + public: + template <class Item> + [[nodiscard]] value_type* InsertElementAt(index_type aIndex, Item&& aItem, + const mozilla::fallible_t&) { + return InsertElementAtInternal<FallibleAlloc>(aIndex, + std::forward<Item>(aItem)); + } + + // Reconstruct the element at the given index, and return a pointer to the + // reconstructed element. This will destroy the existing element and + // default-construct a new one, giving you a state much like what single-arg + // InsertElementAt(), or no-arg AppendElement() does, but without changing the + // length of the array. + // + // array[idx] = value_type() + // + // would accomplish the same thing as long as value_type has the appropriate + // moving operator=, but some types don't for various reasons. + mozilla::NotNull<value_type*> ReconstructElementAt(index_type aIndex) { + value_type* elem = &ElementAt(aIndex); + elem_traits::Destruct(elem); + elem_traits::Construct(elem); + return mozilla::WrapNotNullUnchecked(elem); + } + + // This method searches for the smallest index of an element that is strictly + // greater than |aItem|. If |aItem| is inserted at this index, the array will + // remain sorted and |aItem| would come after all elements that are equal to + // it. If |aItem| is greater than or equal to all elements in the array, the + // array length is returned. + // + // Note that consumers who want to know whether there are existing items equal + // to |aItem| in the array can just check that the return value here is > 0 + // and indexing into the previous slot gives something equal to |aItem|. + // + // + // @param aItem The item to search for. + // @param aComp The Comparator used. + // @return The index of greatest element <= to |aItem| + // @precondition The array is sorted + template <class Item, class Comparator> + [[nodiscard]] index_type IndexOfFirstElementGt( + const Item& aItem, const Comparator& aComp) const { + using mozilla::BinarySearchIf; + ::detail::CompareWrapper<Comparator, Item> comp(aComp); + + size_t index; + BinarySearchIf( + Elements(), 0, Length(), + [&](const value_type& aElement) { + return comp.Compare(aElement, aItem) <= 0 ? 1 : -1; + }, + &index); + return index; + } + + // A variation on the IndexOfFirstElementGt method defined above. + template <class Item> + [[nodiscard]] index_type IndexOfFirstElementGt(const Item& aItem) const { + return IndexOfFirstElementGt(aItem, + nsDefaultComparator<value_type, Item>()); + } + + private: + template <typename ActualAlloc, class Item, class Comparator> + value_type* InsertElementSortedInternal(Item&& aItem, + const Comparator& aComp) { + index_type index = IndexOfFirstElementGt<Item, Comparator>(aItem, aComp); + return InsertElementAtInternal<ActualAlloc>(index, + std::forward<Item>(aItem)); + } + + // Inserts |aItem| at such an index to guarantee that if the array + // was previously sorted, it will remain sorted after this + // insertion. + public: + template <class Item, class Comparator> + [[nodiscard]] value_type* InsertElementSorted(Item&& aItem, + const Comparator& aComp, + const mozilla::fallible_t&) { + return InsertElementSortedInternal<FallibleAlloc>(std::forward<Item>(aItem), + aComp); + } + + // A variation on the InsertElementSorted method defined above. + public: + template <class Item> + [[nodiscard]] value_type* InsertElementSorted(Item&& aItem, + const mozilla::fallible_t&) { + return InsertElementSortedInternal<FallibleAlloc>( + std::forward<Item>(aItem), nsDefaultComparator<value_type, Item>{}); + } + + private: + template <typename ActualAlloc, class Item> + value_type* AppendElementsInternal(const Item* aArray, size_type aArrayLen); + + // This method appends elements to the end of this array. + // @param aArray The elements to append to this array. + // @param aArrayLen The number of elements to append to this array. + // @return A pointer to the new elements in the array, or null if + // the operation failed due to insufficient memory. + public: + template <class Item> + [[nodiscard]] value_type* AppendElements(const Item* aArray, + size_type aArrayLen, + const mozilla::fallible_t&) { + return AppendElementsInternal<FallibleAlloc>(aArray, aArrayLen); + } + + template <class Item> + [[nodiscard]] value_type* AppendElements(mozilla::Span<Item> aSpan, + const mozilla::fallible_t&) { + return AppendElementsInternal<FallibleAlloc>(aSpan.Elements(), + aSpan.Length()); + } + + // A variation on the AppendElements method defined above. + template <class Item, class Allocator> + [[nodiscard]] value_type* AppendElements( + const nsTArray_Impl<Item, Allocator>& aArray, + const mozilla::fallible_t&) { + return AppendElementsInternal<FallibleAlloc>(aArray.Elements(), + aArray.Length()); + } + + private: + template <typename ActualAlloc, class Item, class Allocator> + value_type* AppendElementsInternal(nsTArray_Impl<Item, Allocator>&& aArray); + + // Move all elements from another array to the end of this array. + // @return A pointer to the newly appended elements, or null on OOM. + public: + template <class Item, class Allocator> + [[nodiscard]] value_type* AppendElements( + nsTArray_Impl<Item, Allocator>&& aArray, const mozilla::fallible_t&) { + return AppendElementsInternal<FallibleAlloc>(std::move(aArray)); + } + + // Append a new element, constructed in place from the provided arguments. + protected: + template <typename ActualAlloc, class... Args> + value_type* EmplaceBackInternal(Args&&... aItem); + + public: + template <class... Args> + [[nodiscard]] value_type* EmplaceBack(const mozilla::fallible_t&, + Args&&... aArgs) { + return EmplaceBackInternal<FallibleAlloc, Args...>( + std::forward<Args>(aArgs)...); + } + + private: + template <typename ActualAlloc, class Item> + value_type* AppendElementInternal(Item&& aItem); + + // Append a new element, move constructing if possible. + public: + template <class Item> + [[nodiscard]] value_type* AppendElement(Item&& aItem, + const mozilla::fallible_t&) { + return AppendElementInternal<FallibleAlloc>(std::forward<Item>(aItem)); + } + + private: + template <typename ActualAlloc> + value_type* AppendElementsInternal(size_type aCount) { + if (!ActualAlloc::Successful(this->template ExtendCapacity<ActualAlloc>( + Length(), aCount, sizeof(value_type)))) { + return nullptr; + } + value_type* elems = Elements() + Length(); + size_type i; + for (i = 0; i < aCount; ++i) { + elem_traits::Construct(elems + i); + } + this->IncrementLength(aCount); + return elems; + } + + // Append new elements without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly appended elements, or null on OOM. + public: + [[nodiscard]] value_type* AppendElements(size_type aCount, + const mozilla::fallible_t&) { + return AppendElementsInternal<FallibleAlloc>(aCount); + } + + private: + // Append a new element without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly appended element, or null on OOM. + public: + [[nodiscard]] value_type* AppendElement(const mozilla::fallible_t&) { + return AppendElements(1, mozilla::fallible); + } + + // This method removes a single element from this array, like + // std::vector::erase. + // @param pos to the element to remove + const_iterator RemoveElementAt(const_iterator pos) { + MOZ_ASSERT(pos.GetArray() == this); + + RemoveElementAt(pos.GetIndex()); + return pos; + } + + // This method removes a range of elements from this array, like + // std::vector::erase. + // @param first iterator to the first of elements to remove + // @param last iterator to the last of elements to remove + const_iterator RemoveElementsRange(const_iterator first, + const_iterator last) { + MOZ_ASSERT(first.GetArray() == this); + MOZ_ASSERT(last.GetArray() == this); + MOZ_ASSERT(last.GetIndex() >= first.GetIndex()); + + RemoveElementsAt(first.GetIndex(), last.GetIndex() - first.GetIndex()); + return first; + } + + // This method removes a range of elements from this array. + // @param aStart The starting index of the elements to remove. + // @param aCount The number of elements to remove. + void RemoveElementsAt(index_type aStart, size_type aCount); + + private: + // Remove a range of elements from this array, but do not check that + // the range is in bounds. + // @param aStart The starting index of the elements to remove. + // @param aCount The number of elements to remove. + void RemoveElementsAtUnsafe(index_type aStart, size_type aCount); + + public: + // Similar to the above, but it removes just one element. This does bounds + // checking only in debug builds. + void RemoveElementAtUnsafe(index_type aIndex) { + MOZ_ASSERT(aIndex < Length(), "Trying to remove an invalid element"); + RemoveElementsAtUnsafe(aIndex, 1); + } + + // A variation on the RemoveElementsAt method defined above. + void RemoveElementAt(index_type aIndex) { RemoveElementsAt(aIndex, 1); } + + // A variation on RemoveElementAt that removes the last element. + void RemoveLastElement() { RemoveLastElements(1); } + + // A variation on RemoveElementsAt that removes the last 'aCount' elements. + void RemoveLastElements(const size_type aCount) { + // This assertion is redundant, but produces a better error message than the + // release assertion within TruncateLength. + MOZ_ASSERT(aCount <= Length()); + TruncateLength(Length() - aCount); + } + + // Removes the last element of the array and returns a copy of it. + [[nodiscard]] value_type PopLastElement() { + // This function intentionally does not call ElementsAt and calls + // TruncateLengthUnsafe directly to avoid multiple release checks for + // non-emptiness. + // This debug assertion is redundant, but produces a better error message + // than the release assertion below. + MOZ_ASSERT(!base_type::IsEmpty()); + const size_type oldLen = Length(); + if (MOZ_UNLIKELY(0 == oldLen)) { + mozilla::detail::InvalidArrayIndex_CRASH(1, 0); + } + value_type elem = std::move(Elements()[oldLen - 1]); + TruncateLengthUnsafe(oldLen - 1); + return elem; + } + + // This method performs index-based removals from an array without preserving + // the order of the array. This is useful if you are using the array as a + // set-like data structure. + // + // These removals are efficient, as they move as few elements as possible. At + // most N elements, where N is the number of removed elements, will have to + // be relocated. + // + // ## Examples + // + // When removing an element from the end of the array, it can be removed in + // place, by destroying it and decrementing the length. + // + // [ 1, 2, 3 ] => [ 1, 2 ] + // ^ + // + // When removing any other single element, it is removed by swapping it with + // the last element, and then decrementing the length as before. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ] + // ^ + // + // This method also supports efficiently removing a range of elements. If they + // are at the end, then they can all be removed like in the one element case. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ] + // ^--------^ + // + // If more elements are removed than exist after the removed section, the + // remaining elements will be shifted down like in a normal removal. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ] + // ^--------^ + // + // And if fewer elements are removed than exist after the removed section, + // elements will be moved from the end of the array to fill the vacated space. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ] + // ^--^ + // + // @param aStart The starting index of the elements to remove. @param aCount + // The number of elements to remove. + void UnorderedRemoveElementsAt(index_type aStart, size_type aCount); + + // A variation on the UnorderedRemoveElementsAt method defined above to remove + // a single element. This operation is sometimes called `SwapRemove`. + // + // This method is O(1), but does not preserve the order of the elements. + void UnorderedRemoveElementAt(index_type aIndex) { + UnorderedRemoveElementsAt(aIndex, 1); + } + + void Clear() { + ClearAndRetainStorage(); + base_type::ShrinkCapacityToZero(sizeof(value_type), + MOZ_ALIGNOF(value_type)); + } + + // This method removes elements based on the return value of the + // callback function aPredicate. If the function returns true for + // an element, the element is removed. aPredicate will be called + // for each element in order. It is not safe to access the array + // inside aPredicate. + // + // Returns the number of elements removed. + template <typename Predicate> + size_type RemoveElementsBy(Predicate aPredicate); + + // This helper function combines IndexOf with RemoveElementAt to "search + // and destroy" the first element that is equal to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found + template <class Item, class Comparator> + bool RemoveElement(const Item& aItem, const Comparator& aComp) { + index_type i = IndexOf(aItem, 0, aComp); + if (i == NoIndex) { + return false; + } + + RemoveElementsAtUnsafe(i, 1); + return true; + } + + // A variation on the RemoveElement method defined above that assumes + // that 'operator==' is defined for value_type. + template <class Item> + bool RemoveElement(const Item& aItem) { + return RemoveElement(aItem, nsDefaultComparator<value_type, Item>()); + } + + // This helper function combines IndexOfFirstElementGt with + // RemoveElementAt to "search and destroy" the last element that + // is equal to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found + template <class Item, class Comparator> + bool RemoveElementSorted(const Item& aItem, const Comparator& aComp) { + index_type index = IndexOfFirstElementGt(aItem, aComp); + if (index > 0 && aComp.Equals(ElementAt(index - 1), aItem)) { + RemoveElementsAtUnsafe(index - 1, 1); + return true; + } + return false; + } + + // A variation on the RemoveElementSorted method defined above. + template <class Item> + bool RemoveElementSorted(const Item& aItem) { + return RemoveElementSorted(aItem, nsDefaultComparator<value_type, Item>()); + } + + // This method causes the elements contained in this array and the given + // array to be swapped. + template <class Allocator> + void SwapElements(nsTArray_Impl<E, Allocator>& aOther) { + // The only case this might fail were if someone called this with a + // AutoTArray upcast to nsTArray_Impl, under the conditions mentioned in the + // overload for AutoTArray below. + this->template SwapArrayElements<InfallibleAlloc>( + aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + template <size_t N> + void SwapElements(AutoTArray<E, N>& aOther) { + // Allocation might fail if Alloc==FallibleAlloc and + // Allocator==InfallibleAlloc and aOther uses auto storage. Allow this for + // small inline sizes, and crash in the rare case of a small OOM error. + static_assert(!std::is_same_v<Alloc, FallibleAlloc> || + sizeof(E) * N <= 1024); + this->template SwapArrayElements<InfallibleAlloc>( + aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + template <class Allocator> + [[nodiscard]] auto SwapElements(nsTArray_Impl<E, Allocator>& aOther, + const mozilla::fallible_t&) { + // Allocation might fail if Alloc==FallibleAlloc and + // Allocator==InfallibleAlloc and aOther uses auto storage. + return FallibleAlloc::Result( + this->template SwapArrayElements<FallibleAlloc>( + aOther, sizeof(value_type), MOZ_ALIGNOF(value_type))); + } + + private: + // Used by ApplyIf functions to invoke a callable that takes either: + // - Nothing: F(void) + // - Index only: F(size_t) + // - Reference to element only: F(maybe-const value_type&) + // - Both index and reference: F(size_t, maybe-const value_type&) + // `value_type` must be const when called from const method. + template <typename T, typename Param0, typename Param1> + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = false; + }; + template <typename T> + struct InvokeWithIndexAndOrReferenceHelper<T, void, void> { + static constexpr bool valid = true; + template <typename F> + static auto Invoke(F&& f, size_t, T&) { + return f(); + } + }; + template <typename T> + struct InvokeWithIndexAndOrReferenceHelper<T, size_t, void> { + static constexpr bool valid = true; + template <typename F> + static auto Invoke(F&& f, size_t i, T&) { + return f(i); + } + }; + template <typename T> + struct InvokeWithIndexAndOrReferenceHelper<T, T&, void> { + static constexpr bool valid = true; + template <typename F> + static auto Invoke(F&& f, size_t, T& e) { + return f(e); + } + }; + template <typename T> + struct InvokeWithIndexAndOrReferenceHelper<T, const T&, void> { + static constexpr bool valid = true; + template <typename F> + static auto Invoke(F&& f, size_t, T& e) { + return f(e); + } + }; + template <typename T> + struct InvokeWithIndexAndOrReferenceHelper<T, size_t, T&> { + static constexpr bool valid = true; + template <typename F> + static auto Invoke(F&& f, size_t i, T& e) { + return f(i, e); + } + }; + template <typename T> + struct InvokeWithIndexAndOrReferenceHelper<T, size_t, const T&> { + static constexpr bool valid = true; + template <typename F> + static auto Invoke(F&& f, size_t i, T& e) { + return f(i, e); + } + }; + template <typename T, typename F> + static auto InvokeWithIndexAndOrReference(F&& f, size_t i, T& e) { + using Invoker = InvokeWithIndexAndOrReferenceHelper< + T, typename mozilla::FunctionTypeTraits<F>::template ParameterType<0>, + typename mozilla::FunctionTypeTraits<F>::template ParameterType<1>>; + static_assert(Invoker::valid, + "ApplyIf's Function parameters must match either: (void), " + "(size_t), (maybe-const value_type&), or " + "(size_t, maybe-const value_type&)"); + return Invoker::Invoke(std::forward<F>(f), i, e); + } + + public: + // 'Apply' family of methods. + // + // The advantages of using Apply methods with lambdas include: + // - Safety of accessing elements from within the call, when the array cannot + // have been modified between the iteration and the subsequent access. + // - Avoiding moot conversions: pointer->index during a search, followed by + // index->pointer after the search when accessing the element. + // - Embedding your code into the algorithm, giving the compiler more chances + // to optimize. + + // Search for the first element comparing equal to aItem with the given + // comparator (`==` by default). + // If such an element exists, return the result of evaluating either: + // - `aFunction()` + // - `aFunction(index_type)` + // - `aFunction(maybe-const? value_type&)` + // - `aFunction(index_type, maybe-const? value_type&)` + // (`aFunction` must have one of the above signatures with these exact types, + // including references; implicit conversions or generic types not allowed. + // If `this` array is const, the referenced `value_type` must be const too; + // otherwise it may be either const or non-const.) + // But if the element is not found, return the result of evaluating + // `aFunctionElse()`. + template <class Item, class Comparator, class Function, class FunctionElse> + auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp, + Function&& aFunction, FunctionElse&& aFunctionElse) const { + static_assert( + std::is_same_v< + typename mozilla::FunctionTypeTraits<Function>::ReturnType, + typename mozilla::FunctionTypeTraits<FunctionElse>::ReturnType>, + "ApplyIf's `Function` and `FunctionElse` must return the same type."); + + ::detail::CompareWrapper<Comparator, Item> comp(aComp); + + const value_type* const elements = Elements(); + const value_type* const iend = elements + Length(); + for (const value_type* iter = elements + aStart; iter != iend; ++iter) { + if (comp.Equals(*iter, aItem)) { + return InvokeWithIndexAndOrReference<const value_type>( + std::forward<Function>(aFunction), iter - elements, *iter); + } + } + return aFunctionElse(); + } + template <class Item, class Comparator, class Function, class FunctionElse> + auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp, + Function&& aFunction, FunctionElse&& aFunctionElse) { + static_assert( + std::is_same_v< + typename mozilla::FunctionTypeTraits<Function>::ReturnType, + typename mozilla::FunctionTypeTraits<FunctionElse>::ReturnType>, + "ApplyIf's `Function` and `FunctionElse` must return the same type."); + + ::detail::CompareWrapper<Comparator, Item> comp(aComp); + + value_type* const elements = Elements(); + value_type* const iend = elements + Length(); + for (value_type* iter = elements + aStart; iter != iend; ++iter) { + if (comp.Equals(*iter, aItem)) { + return InvokeWithIndexAndOrReference<value_type>( + std::forward<Function>(aFunction), iter - elements, *iter); + } + } + return aFunctionElse(); + } + template <class Item, class Function, class FunctionElse> + auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction, + FunctionElse&& aFunctionElse) const { + return ApplyIf(aItem, aStart, nsDefaultComparator<value_type, Item>(), + std::forward<Function>(aFunction), + std::forward<FunctionElse>(aFunctionElse)); + } + template <class Item, class Function, class FunctionElse> + auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction, + FunctionElse&& aFunctionElse) { + return ApplyIf(aItem, aStart, nsDefaultComparator<value_type, Item>(), + std::forward<Function>(aFunction), + std::forward<FunctionElse>(aFunctionElse)); + } + template <class Item, class Function, class FunctionElse> + auto ApplyIf(const Item& aItem, Function&& aFunction, + FunctionElse&& aFunctionElse) const { + return ApplyIf(aItem, 0, std::forward<Function>(aFunction), + std::forward<FunctionElse>(aFunctionElse)); + } + template <class Item, class Function, class FunctionElse> + auto ApplyIf(const Item& aItem, Function&& aFunction, + FunctionElse&& aFunctionElse) { + return ApplyIf(aItem, 0, std::forward<Function>(aFunction), + std::forward<FunctionElse>(aFunctionElse)); + } + + // + // Allocation + // + + // This method may increase the capacity of this array object to the + // specified amount. This method may be called in advance of several + // AppendElement operations to minimize heap re-allocations. This method + // will not reduce the number of elements in this array. + // @param aCapacity The desired capacity of this array. + // @return True if the operation succeeded; false if we ran out of memory + protected: + template <typename ActualAlloc = Alloc> + typename ActualAlloc::ResultType SetCapacity(size_type aCapacity) { + return ActualAlloc::Result(this->template EnsureCapacity<ActualAlloc>( + aCapacity, sizeof(value_type))); + } + + public: + [[nodiscard]] bool SetCapacity(size_type aCapacity, + const mozilla::fallible_t&) { + return SetCapacity<FallibleAlloc>(aCapacity); + } + + // This method modifies the length of the array. If the new length is + // larger than the existing length of the array, then new elements will be + // constructed using value_type's default constructor. Otherwise, this call + // removes elements from the array (see also RemoveElementsAt). + // @param aNewLen The desired length of this array. + // @return True if the operation succeeded; false otherwise. + // See also TruncateLength for a more efficient variant if the new length is + // guaranteed to be smaller than the old. + protected: + template <typename ActualAlloc = Alloc> + typename ActualAlloc::ResultType SetLength(size_type aNewLen) { + const size_type oldLen = Length(); + if (aNewLen > oldLen) { + return ActualAlloc::ConvertBoolToResultType( + InsertElementsAtInternal<ActualAlloc>(oldLen, aNewLen - oldLen) != + nullptr); + } + + TruncateLengthUnsafe(aNewLen); + return ActualAlloc::ConvertBoolToResultType(true); + } + + public: + [[nodiscard]] bool SetLength(size_type aNewLen, const mozilla::fallible_t&) { + return SetLength<FallibleAlloc>(aNewLen); + } + + // This method modifies the length of the array, but may only be + // called when the new length is shorter than the old. It can + // therefore be called when value_type has no default constructor, + // unlike SetLength. It removes elements from the array (see also + // RemoveElementsAt). + // @param aNewLen The desired length of this array. + void TruncateLength(size_type aNewLen) { + // This assertion is redundant, but produces a better error message than the + // release assertion below. + MOZ_ASSERT(aNewLen <= Length(), "caller should use SetLength instead"); + + if (MOZ_UNLIKELY(aNewLen > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aNewLen, Length()); + } + + TruncateLengthUnsafe(aNewLen); + } + + private: + void TruncateLengthUnsafe(size_type aNewLen) { + const size_type oldLen = Length(); + if (oldLen) { + DestructRange(aNewLen, oldLen - aNewLen); + base_type::mHdr->mLength = aNewLen; + } + } + + // This method ensures that the array has length at least the given + // length. If the current length is shorter than the given length, + // then new elements will be constructed using value_type's default + // constructor. + // @param aMinLen The desired minimum length of this array. + // @return True if the operation succeeded; false otherwise. + protected: + template <typename ActualAlloc = Alloc> + typename ActualAlloc::ResultType EnsureLengthAtLeast(size_type aMinLen) { + size_type oldLen = Length(); + if (aMinLen > oldLen) { + return ActualAlloc::ConvertBoolToResultType( + !!InsertElementsAtInternal<ActualAlloc>(oldLen, aMinLen - oldLen)); + } + return ActualAlloc::ConvertBoolToResultType(true); + } + + public: + [[nodiscard]] bool EnsureLengthAtLeast(size_type aMinLen, + const mozilla::fallible_t&) { + return EnsureLengthAtLeast<FallibleAlloc>(aMinLen); + } + + // This method inserts elements into the array, constructing + // them using value_type's default constructor. + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of elements to insert + private: + template <typename ActualAlloc> + value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount) { + if (!ActualAlloc::Successful(this->template InsertSlotsAt<ActualAlloc>( + aIndex, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)))) { + return nullptr; + } + + // Initialize the extra array elements + value_type* iter = Elements() + aIndex; + value_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter); + } + + return Elements() + aIndex; + } + + public: + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + size_type aCount, + const mozilla::fallible_t&) { + return InsertElementsAtInternal<FallibleAlloc>(aIndex, aCount); + } + + // This method inserts elements into the array, constructing them + // value_type's copy constructor (or whatever one-arg constructor + // happens to match the Item type). + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of elements to insert. + // @param aItem the value to use when constructing the new elements. + private: + template <typename ActualAlloc, class Item> + value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount, + const Item& aItem); + + public: + template <class Item> + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + size_type aCount, + const Item& aItem, + const mozilla::fallible_t&) { + return InsertElementsAt<Item, FallibleAlloc>(aIndex, aCount, aItem); + } + + // This method may be called to minimize the memory used by this array. + void Compact() { + ShrinkCapacity(sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + // + // Sorting + // + + // This method sorts the elements of the array. It uses the LessThan + // method defined on the given Comparator object to collate elements or + // it wraps a tri-state comparison lambda into such a comparator. + // It uses std::sort. It expects value_type to be move assignable and + // constructible and uses those always, regardless of the chosen + // nsTArray_RelocationStrategy. + // + // @param aComp The Comparator used to collate elements. + template <class Comparator> + void Sort(const Comparator& aComp) { + static_assert(std::is_move_assignable_v<value_type>); + static_assert(std::is_move_constructible_v<value_type>); + + ::detail::CompareWrapper<Comparator, value_type> comp(aComp); + std::sort(begin(), end(), [&comp](const auto& left, const auto& right) { + return comp.LessThan(left, right); + }); + } + + // A variation on the Sort method defined above that assumes that + // 'operator<' is defined for 'value_type'. + void Sort() { Sort(nsDefaultComparator<value_type, value_type>()); } + + // This method sorts the elements of the array in a stable way (i.e. not + // changing the relative order of elements considered equal by the + // Comparator). It uses the LessThan method defined on the given Comparator + // object to collate elements. + // It uses std::stable_sort. It expects value_type to be move assignable and + // constructible and uses those always, regardless of the chosen + // nsTArray_RelocationStrategy. + // + // @param aComp The Comparator used to collate elements. + template <class Comparator> + void StableSort(const Comparator& aComp) { + static_assert(std::is_move_assignable_v<value_type>); + static_assert(std::is_move_constructible_v<value_type>); + + const ::detail::CompareWrapper<Comparator, value_type> comp(aComp); + std::stable_sort(Elements(), Elements() + Length(), + [&comp](const auto& lhs, const auto& rhs) { + return comp.LessThan(lhs, rhs); + }); + } + + // A variation on the StableSort method defined above that assumes that + // 'operator<' is defined for 'value_type'. + void StableSort() { + StableSort(nsDefaultComparator<value_type, value_type>()); + } + + // This method reverses the array in place. + void Reverse() { + value_type* elements = Elements(); + const size_type len = Length(); + for (index_type i = 0, iend = len / 2; i < iend; ++i) { + std::swap(elements[i], elements[len - i - 1]); + } + } + + protected: + using base_type::Hdr; + using base_type::ShrinkCapacity; + + // This method invokes value_type's destructor on a range of elements. + // @param aStart The index of the first element to destroy. + // @param aCount The number of elements to destroy. + void DestructRange(index_type aStart, size_type aCount) { + value_type* iter = Elements() + aStart; + value_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Destruct(iter); + } + } + + // This method invokes value_type's copy-constructor on a range of elements. + // @param aStart The index of the first element to construct. + // @param aCount The number of elements to construct. + // @param aValues The array of elements to copy. + template <class Item> + void AssignRange(index_type aStart, size_type aCount, const Item* aValues) { + AssignRangeAlgorithm< + std::is_trivially_copy_constructible_v<Item>, + std::is_same_v<Item, value_type>>::implementation(Elements(), aStart, + aCount, aValues); + } +}; + +template <typename E, class Alloc> +template <typename ActualAlloc, class Item> +auto nsTArray_Impl<E, Alloc>::AssignInternal(const Item* aArray, + size_type aArrayLen) -> + typename ActualAlloc::ResultType { + static_assert(std::is_same_v<ActualAlloc, InfallibleAlloc> || + std::is_same_v<ActualAlloc, FallibleAlloc>); + + if constexpr (std::is_same_v<ActualAlloc, InfallibleAlloc>) { + ClearAndRetainStorage(); + } + // Adjust memory allocation up-front to catch errors in the fallible case. + // We might relocate the elements to be destroyed unnecessarily. This could be + // optimized, but would make things more complicated. + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + aArrayLen, sizeof(value_type)))) { + return ActualAlloc::ConvertBoolToResultType(false); + } + + MOZ_ASSERT_IF(this->HasEmptyHeader(), aArrayLen == 0); + if (!this->HasEmptyHeader()) { + if constexpr (std::is_same_v<ActualAlloc, FallibleAlloc>) { + ClearAndRetainStorage(); + } + AssignRange(0, aArrayLen, aArray); + base_type::mHdr->mLength = aArrayLen; + } + + return ActualAlloc::ConvertBoolToResultType(true); +} + +template <typename E, class Alloc> +template <typename ActualAlloc, class Item> +auto nsTArray_Impl<E, Alloc>::ReplaceElementsAtInternal(index_type aStart, + size_type aCount, + const Item* aArray, + size_type aArrayLen) + -> value_type* { + if (MOZ_UNLIKELY(aStart > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); + } + if (MOZ_UNLIKELY(aCount > Length() - aStart)) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart + aCount, Length()); + } + + // Adjust memory allocation up-front to catch errors. + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + aArrayLen - aCount, sizeof(value_type)))) { + return nullptr; + } + DestructRange(aStart, aCount); + this->template ShiftData<ActualAlloc>( + aStart, aCount, aArrayLen, sizeof(value_type), MOZ_ALIGNOF(value_type)); + AssignRange(aStart, aArrayLen, aArray); + return Elements() + aStart; +} + +template <typename E, class Alloc> +void nsTArray_Impl<E, Alloc>::RemoveElementsAt(index_type aStart, + size_type aCount) { + MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index"); + + mozilla::CheckedInt<index_type> rangeEnd = aStart; + rangeEnd += aCount; + + if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); + } + + RemoveElementsAtUnsafe(aStart, aCount); +} + +template <typename E, class Alloc> +void nsTArray_Impl<E, Alloc>::RemoveElementsAtUnsafe(index_type aStart, + size_type aCount) { + DestructRange(aStart, aCount); + this->template ShiftData<InfallibleAlloc>( + aStart, aCount, 0, sizeof(value_type), MOZ_ALIGNOF(value_type)); +} + +template <typename E, class Alloc> +void nsTArray_Impl<E, Alloc>::UnorderedRemoveElementsAt(index_type aStart, + size_type aCount) { + MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index"); + + mozilla::CheckedInt<index_type> rangeEnd = aStart; + rangeEnd += aCount; + + if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); + } + + // Destroy the elements which are being removed, and then swap elements in to + // replace them from the end. See the docs on the declaration of this + // function. + DestructRange(aStart, aCount); + this->template SwapFromEnd<InfallibleAlloc>( + aStart, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)); +} + +template <typename E, class Alloc> +template <typename Predicate> +auto nsTArray_Impl<E, Alloc>::RemoveElementsBy(Predicate aPredicate) + -> size_type { + if (this->HasEmptyHeader()) { + return 0; + } + + index_type j = 0; + const index_type len = Length(); + value_type* const elements = Elements(); + for (index_type i = 0; i < len; ++i) { + const bool result = aPredicate(elements[i]); + + // Check that the array has not been modified by the predicate. + MOZ_DIAGNOSTIC_ASSERT(len == base_type::mHdr->mLength && + elements == Elements()); + + if (result) { + elem_traits::Destruct(elements + i); + } else { + if (j < i) { + relocation_type::RelocateNonOverlappingRegion( + elements + j, elements + i, 1, sizeof(value_type)); + } + ++j; + } + } + + base_type::mHdr->mLength = j; + return len - j; +} + +template <typename E, class Alloc> +template <typename ActualAlloc, class Item> +auto nsTArray_Impl<E, Alloc>::InsertElementsAtInternal(index_type aIndex, + size_type aCount, + const Item& aItem) + -> value_type* { + if (!ActualAlloc::Successful(this->template InsertSlotsAt<ActualAlloc>( + aIndex, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)))) { + return nullptr; + } + + // Initialize the extra array elements + value_type* iter = Elements() + aIndex; + value_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter, aItem); + } + + return Elements() + aIndex; +} + +template <typename E, class Alloc> +template <typename ActualAlloc> +auto nsTArray_Impl<E, Alloc>::InsertElementAtInternal(index_type aIndex) + -> value_type* { + if (MOZ_UNLIKELY(aIndex > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + this->template ShiftData<ActualAlloc>(aIndex, 0, 1, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + value_type* elem = Elements() + aIndex; + elem_traits::Construct(elem); + return elem; +} + +template <typename E, class Alloc> +template <typename ActualAlloc, class Item> +auto nsTArray_Impl<E, Alloc>::InsertElementAtInternal(index_type aIndex, + Item&& aItem) + -> value_type* { + if (MOZ_UNLIKELY(aIndex > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + this->template ShiftData<ActualAlloc>(aIndex, 0, 1, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + value_type* elem = Elements() + aIndex; + elem_traits::Construct(elem, std::forward<Item>(aItem)); + return elem; +} + +template <typename E, class Alloc> +template <typename ActualAlloc, class Item> +auto nsTArray_Impl<E, Alloc>::AppendElementsInternal(const Item* aArray, + size_type aArrayLen) + -> value_type* { + if (!ActualAlloc::Successful(this->template ExtendCapacity<ActualAlloc>( + Length(), aArrayLen, sizeof(value_type)))) { + return nullptr; + } + index_type len = Length(); + AssignRange(len, aArrayLen, aArray); + this->IncrementLength(aArrayLen); + return Elements() + len; +} + +template <typename E, class Alloc> +template <typename ActualAlloc, class Item, class Allocator> +auto nsTArray_Impl<E, Alloc>::AppendElementsInternal( + nsTArray_Impl<Item, Allocator>&& aArray) -> value_type* { + if constexpr (std::is_same_v<Alloc, Allocator>) { + MOZ_ASSERT(&aArray != this, "argument must be different aArray"); + } + if (Length() == 0) { + // XXX This might still be optimized. If aArray uses auto-storage but we + // won't, we might better retain our storage if it's sufficiently large. + this->ShrinkCapacityToZero(sizeof(value_type), MOZ_ALIGNOF(value_type)); + this->MoveInit(aArray, sizeof(value_type), MOZ_ALIGNOF(value_type)); + return Elements(); + } + + index_type len = Length(); + index_type otherLen = aArray.Length(); + if (!ActualAlloc::Successful(this->template ExtendCapacity<ActualAlloc>( + len, otherLen, sizeof(value_type)))) { + return nullptr; + } + relocation_type::RelocateNonOverlappingRegion( + Elements() + len, aArray.Elements(), otherLen, sizeof(value_type)); + this->IncrementLength(otherLen); + aArray.template ShiftData<ActualAlloc>(0, otherLen, 0, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + return Elements() + len; +} + +template <typename E, class Alloc> +template <typename ActualAlloc, class Item> +auto nsTArray_Impl<E, Alloc>::AppendElementInternal(Item&& aItem) + -> value_type* { + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + value_type* elem = Elements() + Length(); + elem_traits::Construct(elem, std::forward<Item>(aItem)); + this->mHdr->mLength += 1; + return elem; +} + +template <typename E, class Alloc> +template <typename ActualAlloc, class... Args> +auto nsTArray_Impl<E, Alloc>::EmplaceBackInternal(Args&&... aArgs) + -> value_type* { + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + value_type* elem = Elements() + Length(); + elem_traits::Emplace(elem, std::forward<Args>(aArgs)...); + this->mHdr->mLength += 1; + return elem; +} + +template <typename E, typename Alloc> +inline void ImplCycleCollectionUnlink(nsTArray_Impl<E, Alloc>& aField) { + aField.Clear(); +} + +namespace detail { +// This is defined in the cpp file to avoid including +// nsCycleCollectionNoteChild.h in this header file. +void SetCycleCollectionArrayFlag(uint32_t& aFlags); +} // namespace detail + +template <typename E, typename Alloc> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsTArray_Impl<E, Alloc>& aField, const char* aName, uint32_t aFlags = 0) { + ::detail::SetCycleCollectionArrayFlag(aFlags); + size_t length = aField.Length(); + E* elements = aField.Elements(); + for (size_t i = 0; i < length; ++i) { + ImplCycleCollectionTraverse(aCallback, elements[i], aName, aFlags); + } +} + +// +// nsTArray is an infallible vector class. See the comment at the top of this +// file for more details. +// +template <class E> +class nsTArray : public nsTArray_Impl<E, nsTArrayInfallibleAllocator> { + public: + using InfallibleAlloc = nsTArrayInfallibleAllocator; + using base_type = nsTArray_Impl<E, InfallibleAlloc>; + using self_type = nsTArray<E>; + using typename base_type::index_type; + using typename base_type::size_type; + using typename base_type::value_type; + + nsTArray() {} + explicit nsTArray(size_type aCapacity) : base_type(aCapacity) {} + MOZ_IMPLICIT nsTArray(std::initializer_list<E> aIL) { + AppendElements(aIL.begin(), aIL.size()); + } + + template <class Item> + nsTArray(const Item* aArray, size_type aArrayLen) { + AppendElements(aArray, aArrayLen); + } + + template <class Item> + explicit nsTArray(mozilla::Span<Item> aSpan) { + AppendElements(aSpan); + } + + template <class Allocator> + explicit nsTArray(const nsTArray_Impl<E, Allocator>& aOther) + : base_type(aOther) {} + template <class Allocator> + MOZ_IMPLICIT nsTArray(nsTArray_Impl<E, Allocator>&& aOther) + : base_type(std::move(aOther)) {} + + template <class Allocator> + self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) { + base_type::operator=(aOther); + return *this; + } + template <class Allocator> + self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) { + // This is quite complex, since we don't know if we are an AutoTArray. While + // AutoTArray overrides this operator=, this might be called on a nsTArray& + // bound to an AutoTArray. + base_type::operator=(std::move(aOther)); + return *this; + } + + using base_type::AppendElement; + using base_type::AppendElements; + using base_type::EmplaceBack; + using base_type::EnsureLengthAtLeast; + using base_type::InsertElementAt; + using base_type::InsertElementsAt; + using base_type::InsertElementSorted; + using base_type::ReplaceElementsAt; + using base_type::SetCapacity; + using base_type::SetLength; + + template <class Item> + mozilla::NotNull<value_type*> AppendElements(const Item* aArray, + size_type aArrayLen) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal<InfallibleAlloc>(aArray, + aArrayLen)); + } + + template <class Item> + mozilla::NotNull<value_type*> AppendElements(mozilla::Span<Item> aSpan) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal<InfallibleAlloc>(aSpan.Elements(), + aSpan.Length())); + } + + template <class Item, class Allocator> + mozilla::NotNull<value_type*> AppendElements( + const nsTArray_Impl<Item, Allocator>& aArray) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal<InfallibleAlloc>( + aArray.Elements(), aArray.Length())); + } + + template <class Item, class Allocator> + mozilla::NotNull<value_type*> AppendElements( + nsTArray_Impl<Item, Allocator>&& aArray) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal<InfallibleAlloc>( + std::move(aArray))); + } + + template <class Item> + mozilla::NotNull<value_type*> AppendElement(Item&& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementInternal<InfallibleAlloc>( + std::forward<Item>(aItem))); + } + + mozilla::NotNull<value_type*> AppendElements(size_type aCount) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal<InfallibleAlloc>(aCount)); + } + + mozilla::NotNull<value_type*> AppendElement() { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal<InfallibleAlloc>(1)); + } + + self_type Clone() const { + self_type result; + result.Assign(*this); + return result; + } + + mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex, + size_type aCount) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementsAtInternal<InfallibleAlloc>(aIndex, + aCount)); + } + + template <class Item> + mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex, + size_type aCount, + const Item& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementsAtInternal<InfallibleAlloc>(aIndex, aCount, + aItem)); + } + + template <class Item> + mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex, + const Item* aArray, + size_type aArrayLen) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal<InfallibleAlloc>( + aIndex, 0, aArray, aArrayLen)); + } + + template <class Item, class Allocator> + mozilla::NotNull<value_type*> InsertElementsAt( + index_type aIndex, const nsTArray_Impl<Item, Allocator>& aArray) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal<InfallibleAlloc>( + aIndex, 0, aArray.Elements(), aArray.Length())); + } + + template <class Item> + mozilla::NotNull<value_type*> InsertElementsAt(index_type aIndex, + mozilla::Span<Item> aSpan) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal<InfallibleAlloc>( + aIndex, 0, aSpan.Elements(), aSpan.Length())); + } + + mozilla::NotNull<value_type*> InsertElementAt(index_type aIndex) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementAtInternal<InfallibleAlloc>(aIndex)); + } + + template <class Item> + mozilla::NotNull<value_type*> InsertElementAt(index_type aIndex, + Item&& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementAtInternal<InfallibleAlloc>( + aIndex, std::forward<Item>(aItem))); + } + + template <class Item> + mozilla::NotNull<value_type*> ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item* aArray, + size_type aArrayLen) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal<InfallibleAlloc>( + aStart, aCount, aArray, aArrayLen)); + } + + template <class Item> + mozilla::NotNull<value_type*> ReplaceElementsAt( + index_type aStart, size_type aCount, const nsTArray<Item>& aArray) { + return ReplaceElementsAt(aStart, aCount, aArray.Elements(), + aArray.Length()); + } + + template <class Item> + mozilla::NotNull<value_type*> ReplaceElementsAt(index_type aStart, + size_type aCount, + mozilla::Span<Item> aSpan) { + return ReplaceElementsAt(aStart, aCount, aSpan.Elements(), aSpan.Length()); + } + + template <class Item> + mozilla::NotNull<value_type*> ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item& aItem) { + return ReplaceElementsAt(aStart, aCount, &aItem, 1); + } + + template <class Item, class Comparator> + mozilla::NotNull<value_type*> InsertElementSorted(Item&& aItem, + const Comparator& aComp) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementSortedInternal<InfallibleAlloc>( + std::forward<Item>(aItem), aComp)); + } + + template <class Item> + mozilla::NotNull<value_type*> InsertElementSorted(Item&& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementSortedInternal<InfallibleAlloc>( + std::forward<Item>(aItem), + nsDefaultComparator<value_type, Item>{})); + } + + template <class... Args> + mozilla::NotNull<value_type*> EmplaceBack(Args&&... aArgs) { + return mozilla::WrapNotNullUnchecked( + this->template EmplaceBackInternal<InfallibleAlloc, Args...>( + std::forward<Args>(aArgs)...)); + } +}; + +template <class E> +class CopyableTArray : public nsTArray<E> { + public: + using nsTArray<E>::nsTArray; + + CopyableTArray(const CopyableTArray& aOther) : nsTArray<E>() { + this->Assign(aOther); + } + CopyableTArray& operator=(const CopyableTArray& aOther) { + if (this != &aOther) { + this->Assign(aOther); + } + return *this; + } + template <typename Allocator> + MOZ_IMPLICIT CopyableTArray(const nsTArray_Impl<E, Allocator>& aOther) { + this->Assign(aOther); + } + template <typename Allocator> + CopyableTArray& operator=(const nsTArray_Impl<E, Allocator>& aOther) { + if constexpr (std::is_same_v<Allocator, nsTArrayInfallibleAllocator>) { + if (this == &aOther) { + return *this; + } + } + this->Assign(aOther); + return *this; + } + template <typename Allocator> + MOZ_IMPLICIT CopyableTArray(nsTArray_Impl<E, Allocator>&& aOther) + : nsTArray<E>{std::move(aOther)} {} + template <typename Allocator> + CopyableTArray& operator=(nsTArray_Impl<E, Allocator>&& aOther) { + static_cast<nsTArray<E>&>(*this) = std::move(aOther); + return *this; + } + + CopyableTArray(CopyableTArray&&) = default; + CopyableTArray& operator=(CopyableTArray&&) = default; +}; + +// +// FallibleTArray is a fallible vector class. +// +template <class E> +class FallibleTArray : public nsTArray_Impl<E, nsTArrayFallibleAllocator> { + public: + typedef nsTArray_Impl<E, nsTArrayFallibleAllocator> base_type; + typedef FallibleTArray<E> self_type; + typedef typename base_type::size_type size_type; + + FallibleTArray() = default; + explicit FallibleTArray(size_type aCapacity) : base_type(aCapacity) {} + + template <class Allocator> + explicit FallibleTArray(const nsTArray_Impl<E, Allocator>& aOther) + : base_type(aOther) {} + template <class Allocator> + explicit FallibleTArray(nsTArray_Impl<E, Allocator>&& aOther) + : base_type(std::move(aOther)) {} + + template <class Allocator> + self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) { + base_type::operator=(aOther); + return *this; + } + template <class Allocator> + self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) { + base_type::operator=(std::move(aOther)); + return *this; + } +}; + +// +// AutoTArray<E, N> is like nsTArray<E>, but with N elements of inline storage. +// Storing more than N elements is fine, but it will cause a heap allocation. +// +template <class E, size_t N> +class MOZ_NON_MEMMOVABLE AutoTArray : public nsTArray<E> { + static_assert(N != 0, "AutoTArray<E, 0> should be specialized"); + + public: + typedef AutoTArray<E, N> self_type; + typedef nsTArray<E> base_type; + typedef typename base_type::Header Header; + typedef typename base_type::value_type value_type; + + AutoTArray() : mAlign() { Init(); } + + AutoTArray(self_type&& aOther) : nsTArray<E>() { + Init(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + explicit AutoTArray(base_type&& aOther) : mAlign() { + Init(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + template <typename Allocator> + explicit AutoTArray(nsTArray_Impl<value_type, Allocator>&& aOther) { + Init(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + MOZ_IMPLICIT AutoTArray(std::initializer_list<E> aIL) : mAlign() { + Init(); + this->AppendElements(aIL.begin(), aIL.size()); + } + + self_type& operator=(self_type&& aOther) { + base_type::operator=(std::move(aOther)); + return *this; + } + + template <typename Allocator> + self_type& operator=(nsTArray_Impl<value_type, Allocator>&& aOther) { + base_type::operator=(std::move(aOther)); + return *this; + } + + // Intentionally hides nsTArray_Impl::Clone to make clones usually be + // AutoTArray as well. + self_type Clone() const { + self_type result; + result.Assign(*this); + return result; + } + + private: + // nsTArray_base casts itself as an nsAutoArrayBase in order to get a pointer + // to mAutoBuf. + template <class Allocator, class RelocationStrategy> + friend class nsTArray_base; + + void Init() { + static_assert(MOZ_ALIGNOF(value_type) <= 8, + "can't handle alignments greater than 8, " + "see nsTArray_base::UsesAutoArrayBuffer()"); + // Temporary work around for VS2012 RC compiler crash + Header** phdr = base_type::PtrToHdr(); + *phdr = reinterpret_cast<Header*>(&mAutoBuf); + (*phdr)->mLength = 0; + (*phdr)->mCapacity = N; + (*phdr)->mIsAutoArray = 1; + + MOZ_ASSERT(base_type::GetAutoArrayBuffer(MOZ_ALIGNOF(value_type)) == + reinterpret_cast<Header*>(&mAutoBuf), + "GetAutoArrayBuffer needs to be fixed"); + } + + // Declare mAutoBuf aligned to the maximum of the header's alignment and + // value_type's alignment. We need to use a union rather than + // MOZ_ALIGNED_DECL because GCC is picky about what goes into + // __attribute__((aligned(foo))). + union { + char mAutoBuf[sizeof(nsTArrayHeader) + N * sizeof(value_type)]; + // Do the max operation inline to ensure that it is a compile-time constant. + mozilla::AlignedElem<(MOZ_ALIGNOF(Header) > MOZ_ALIGNOF(value_type)) + ? MOZ_ALIGNOF(Header) + : MOZ_ALIGNOF(value_type)> + mAlign; + }; +}; + +// +// Specialization of AutoTArray<E, N> for the case where N == 0. +// AutoTArray<E, 0> behaves exactly like nsTArray<E>, but without this +// specialization, it stores a useless inline header. +// +// We do have many AutoTArray<E, 0> objects in memory: about 2,000 per tab as +// of May 2014. These are typically not explicitly AutoTArray<E, 0> but rather +// AutoTArray<E, N> for some value N depending on template parameters, in +// generic code. +// +// For that reason, we optimize this case with the below partial specialization, +// which ensures that AutoTArray<E, 0> is just like nsTArray<E>, without any +// inline header overhead. +// +template <class E> +class AutoTArray<E, 0> : public nsTArray<E> { + using nsTArray<E>::nsTArray; +}; + +template <class E, size_t N> +struct nsTArray_RelocationStrategy<AutoTArray<E, N>> { + using Type = nsTArray_RelocateUsingMoveConstructor<AutoTArray<E, N>>; +}; + +template <class E, size_t N> +class CopyableAutoTArray : public AutoTArray<E, N> { + public: + typedef CopyableAutoTArray<E, N> self_type; + using AutoTArray<E, N>::AutoTArray; + + CopyableAutoTArray(const CopyableAutoTArray& aOther) : AutoTArray<E, N>() { + this->Assign(aOther); + } + CopyableAutoTArray& operator=(const CopyableAutoTArray& aOther) { + if (this != &aOther) { + this->Assign(aOther); + } + return *this; + } + template <typename Allocator> + MOZ_IMPLICIT CopyableAutoTArray(const nsTArray_Impl<E, Allocator>& aOther) { + this->Assign(aOther); + } + template <typename Allocator> + CopyableAutoTArray& operator=(const nsTArray_Impl<E, Allocator>& aOther) { + if constexpr (std::is_same_v<Allocator, nsTArrayInfallibleAllocator>) { + if (this == &aOther) { + return *this; + } + } + this->Assign(aOther); + return *this; + } + template <typename Allocator> + MOZ_IMPLICIT CopyableAutoTArray(nsTArray_Impl<E, Allocator>&& aOther) + : AutoTArray<E, N>{std::move(aOther)} {} + template <typename Allocator> + CopyableAutoTArray& operator=(nsTArray_Impl<E, Allocator>&& aOther) { + static_cast<AutoTArray<E, N>&>(*this) = std::move(aOther); + return *this; + } + + // CopyableTArray exists for cases where an explicit Clone is not possible. + // These uses should not be mixed, so we delete Clone() here. + self_type Clone() const = delete; + + CopyableAutoTArray(CopyableAutoTArray&&) = default; + CopyableAutoTArray& operator=(CopyableAutoTArray&&) = default; +}; + +namespace mozilla { +template <typename E, typename ArrayT> +class nsTArrayBackInserter { + ArrayT* mArray; + + class Proxy { + ArrayT& mArray; + + public: + explicit Proxy(ArrayT& aArray) : mArray{aArray} {} + + template <typename E2> + void operator=(E2&& aValue) { + mArray.AppendElement(std::forward<E2>(aValue)); + } + }; + + public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + explicit nsTArrayBackInserter(ArrayT& aArray) : mArray{&aArray} {} + + // Return a proxy so that nsTArrayBackInserter has the default special member + // functions, and the operator= template is defined in Proxy rather than this + // class (which otherwise breaks with recent MS STL versions). + // See also Bug 1331137, comment 11. + Proxy operator*() { return Proxy(*mArray); } + + nsTArrayBackInserter& operator++() { return *this; } + nsTArrayBackInserter& operator++(int) { return *this; } +}; +} // namespace mozilla + +template <typename E> +auto MakeBackInserter(nsTArray<E>& aArray) { + return mozilla::nsTArrayBackInserter<E, nsTArray<E>>{aArray}; +} + +// Span integration +namespace mozilla { +template <typename E, class Alloc> +Span(nsTArray_Impl<E, Alloc>&) -> Span<E>; + +template <typename E, class Alloc> +Span(const nsTArray_Impl<E, Alloc>&) -> Span<const E>; + +// Provides a view on a nsTArray through which the existing array elements can +// be accessed in a non-const way, but the array itself cannot be modified, so +// that references to elements are guaranteed to be stable. +template <typename E> +class nsTArrayView { + public: + using element_type = E; + using pointer = element_type*; + using reference = element_type&; + using index_type = typename Span<element_type>::index_type; + using size_type = typename Span<element_type>::index_type; + + explicit nsTArrayView(nsTArray<element_type> aArray) + : mArray(std::move(aArray)), mSpan(mArray) {} + + element_type& operator[](index_type aIndex) { return mSpan[aIndex]; } + + const element_type& operator[](index_type aIndex) const { + return mSpan[aIndex]; + } + + size_type Length() const { return mSpan.Length(); } + + auto begin() { return mSpan.begin(); } + auto end() { return mSpan.end(); } + auto begin() const { return mSpan.begin(); } + auto end() const { return mSpan.end(); } + auto cbegin() const { return mSpan.cbegin(); } + auto cend() const { return mSpan.cend(); } + + Span<element_type> AsSpan() { return mSpan; } + Span<const element_type> AsSpan() const { return mSpan; } + + private: + nsTArray<element_type> mArray; + const Span<element_type> mSpan; +}; + +template <typename Range, typename = std::enable_if_t<std::is_same_v< + typename std::iterator_traits< + typename Range::iterator>::iterator_category, + std::random_access_iterator_tag>>> +auto RangeSize(const Range& aRange) { + // See https://en.cppreference.com/w/cpp/iterator/begin, section 'User-defined + // overloads'. + using std::begin; + using std::end; + + return std::distance(begin(aRange), end(aRange)); +} + +/** + * Materialize a range as a nsTArray (or a compatible variant, like AutoTArray) + * of an explicitly specified type. The array value type must be implicitly + * convertible from the range's value type. + */ +template <typename Array, typename Range> +auto ToTArray(const Range& aRange) { + using std::begin; + using std::end; + + Array res; + res.SetCapacity(RangeSize(aRange)); + std::copy(begin(aRange), end(aRange), MakeBackInserter(res)); + return res; +} + +/** + * Materialize a range as a nsTArray of its (decayed) value type. + */ +template <typename Range> +auto ToArray(const Range& aRange) { + return ToTArray<nsTArray<std::decay_t< + typename std::iterator_traits<typename Range::iterator>::value_type>>>( + aRange); +} + +/** + * Appends all elements from a range to an array. + */ +template <typename Array, typename Range> +void AppendToArray(Array& aArray, const Range& aRange) { + using std::begin; + using std::end; + + aArray.SetCapacity(aArray.Length() + RangeSize(aRange)); + std::copy(begin(aRange), end(aRange), MakeBackInserter(aArray)); +} + +} // namespace mozilla + +// MOZ_DBG support + +template <class E, class Alloc> +std::ostream& operator<<(std::ostream& aOut, + const nsTArray_Impl<E, Alloc>& aTArray) { + return aOut << mozilla::Span(aTArray); +} + +// Assert that AutoTArray doesn't have any extra padding inside. +// +// It's important that the data stored in this auto array takes up a multiple of +// 8 bytes; e.g. AutoTArray<uint32_t, 1> wouldn't work. Since AutoTArray +// contains a pointer, its size must be a multiple of alignof(void*). (This is +// because any type may be placed into an array, and there's no padding between +// elements of an array.) The compiler pads the end of the structure to +// enforce this rule. +// +// If we used AutoTArray<uint32_t, 1> below, this assertion would fail on a +// 64-bit system, where the compiler inserts 4 bytes of padding at the end of +// the auto array to make its size a multiple of alignof(void*) == 8 bytes. + +static_assert(sizeof(AutoTArray<uint32_t, 2>) == + sizeof(void*) + sizeof(nsTArrayHeader) + sizeof(uint32_t) * 2, + "AutoTArray shouldn't contain any extra padding, " + "see the comment"); + +// Definitions of nsTArray_Impl methods +#include "nsTArray-inl.h" + +#endif // nsTArray_h__ diff --git a/xpcom/ds/nsTArrayForwardDeclare.h b/xpcom/ds/nsTArrayForwardDeclare.h new file mode 100644 index 0000000000..f888550803 --- /dev/null +++ b/xpcom/ds/nsTArrayForwardDeclare.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 nsTArrayForwardDeclare_h__ +#define nsTArrayForwardDeclare_h__ + +// +// This simple header file contains forward declarations for the TArray family +// of classes. +// +// Including this header is preferable to writing +// +// template<class E> class nsTArray; +// +// yourself, since this header gives us flexibility to e.g. change the default +// template parameters. +// + +#include <stddef.h> + +template <class E> +class nsTArray; + +template <class E> +class FallibleTArray; + +template <class E> +class CopyableTArray; + +template <class E, size_t N> +class AutoTArray; + +template <class E, size_t N> +class CopyableAutoTArray; + +#endif diff --git a/xpcom/ds/nsTHashMap.h b/xpcom/ds/nsTHashMap.h new file mode 100644 index 0000000000..37aefbba87 --- /dev/null +++ b/xpcom/ds/nsTHashMap.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XPCOM_DS_NSTHASHMAP_H_ +#define XPCOM_DS_NSTHASHMAP_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsBaseHashtable.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsAtomHashKeys.h" +#include "nsHashtablesFwd.h" +#include <type_traits> + +namespace mozilla::detail { +template <class KeyType> +struct nsKeyClass< + KeyType, std::enable_if_t<sizeof(KeyType) && + std::is_base_of_v<PLDHashEntryHdr, KeyType>>> { + using type = KeyType; +}; + +template <typename KeyType> +struct nsKeyClass<KeyType*> { + using type = nsPtrHashKey<KeyType>; +}; + +template <> +struct nsKeyClass<nsAtom*> { + using type = nsWeakAtomHashKey; +}; + +template <typename Ret, typename... Args> +struct nsKeyClass<Ret (*)(Args...)> { + using type = nsFuncPtrHashKey<Ret (*)(Args...)>; +}; + +template <> +struct nsKeyClass<nsCString> { + using type = nsCStringHashKey; +}; + +// This uses the case-sensitive hash key class, if you want the +// case-insensitive hash key, use nsStringCaseInsensitiveHashKey explicitly. +template <> +struct nsKeyClass<nsString> { + using type = nsStringHashKey; +}; + +template <typename KeyType> +struct nsKeyClass<KeyType, std::enable_if_t<std::is_integral_v<KeyType> || + std::is_enum_v<KeyType>>> { + using type = nsIntegralHashKey<KeyType>; +}; + +template <> +struct nsKeyClass<nsCOMPtr<nsISupports>> { + using type = nsISupportsHashKey; +}; + +template <typename T> +struct nsKeyClass<RefPtr<T>> { + using type = nsRefPtrHashKey<T>; +}; + +template <> +struct nsKeyClass<RefPtr<nsAtom>> { + using type = nsAtomHashKey; +}; + +template <> +struct nsKeyClass<nsID> { + using type = nsIDHashKey; +}; + +} // namespace mozilla::detail + +// The actual definition of nsTHashMap is in nsHashtablesFwd.h, since it is a +// type alias. + +#endif // XPCOM_DS_NSTHASHMAP_H_ diff --git a/xpcom/ds/nsTHashSet.h b/xpcom/ds/nsTHashSet.h new file mode 100644 index 0000000000..4d905299db --- /dev/null +++ b/xpcom/ds/nsTHashSet.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 XPCOM_DS_NSTHASHSET_H_ +#define XPCOM_DS_NSTHASHSET_H_ + +#include "nsHashtablesFwd.h" +#include "nsTHashMap.h" // for nsKeyClass + +/** + * Templated hash set. Don't use this directly, but use nsTHashSet instead + * (defined as a type alias in nsHashtablesFwd.h). + * + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + */ +template <class KeyClass> +class nsTBaseHashSet : protected nsTHashtable<KeyClass> { + using Base = nsTHashtable<KeyClass>; + typedef mozilla::fallible_t fallible_t; + + public: + // XXX We have a similar situation here like with DataType/UserDataType in + // nsBaseHashtable. It's less problematic here due to the more constrained + // interface, but it may still be confusing. KeyType is not the stored key + // type, but the one exposed to the user, i.e. as a parameter type, and as the + // value type when iterating. It is currently impossible to move-insert a + // RefPtr<T>, e.g., since the KeyType is T* in that case. + using ValueType = typename KeyClass::KeyType; + + // KeyType is defined just for compatibility with nsTHashMap. For a set, the + // key type is conceptually equivalent to the value type. + using KeyType = typename KeyClass::KeyType; + + using Base::Base; + + // Query operations. + + using Base::Contains; + using Base::GetGeneration; + using Base::ShallowSizeOfExcludingThis; + using Base::ShallowSizeOfIncludingThis; + using Base::SizeOfExcludingThis; + using Base::SizeOfIncludingThis; + + /** + * Return the number of entries in the table. + * @return number of entries + */ + [[nodiscard]] uint32_t Count() const { return Base::Count(); } + + /** + * Return whether the table is empty. + * @return whether empty + */ + [[nodiscard]] bool IsEmpty() const { return Base::IsEmpty(); } + + using iterator = ::detail::nsTHashtableKeyIterator<KeyClass>; + using const_iterator = iterator; + + [[nodiscard]] auto begin() const { return Base::Keys().begin(); } + + [[nodiscard]] auto end() const { return Base::Keys().end(); } + + [[nodiscard]] auto cbegin() const { return Base::Keys().cbegin(); } + + [[nodiscard]] auto cend() const { return Base::Keys().cend(); } + + // Mutating operations. + + using Base::Clear; + using Base::MarkImmutable; + + /** + * Inserts a value into the set. Has no effect if the value is already in the + * set. This overload is infallible and crashes if memory is exhausted. + * + * \note For strict consistency with nsTHashtable::EntryHandle method naming, + * this should rather be called OrInsert, as it is legal to call it when the + * value is already in the set. For simplicity, as we don't have two methods, + * we still use "Insert" instead. + */ + void Insert(ValueType aValue) { Base::PutEntry(aValue); } + + /** + * Inserts a value into the set. Has no effect if the value is already in the + * set. This overload is fallible and returns false if memory is exhausted. + * + * \note See note on infallible overload. + */ + [[nodiscard]] bool Insert(ValueType aValue, const mozilla::fallible_t&) { + return Base::PutEntry(aValue, mozilla::fallible); + } + + /** + * Inserts a value into the set. Has no effect if the value is already in the + * set. This member function is infallible and crashes if memory is exhausted. + * + * \return true if the value was actually inserted, false if it was already in + * the set. + */ + bool EnsureInserted(ValueType aValue) { return Base::EnsureInserted(aValue); } + + using Base::Remove; + + /** + * Removes a value from the set. Has no effect if the value is not in the set. + * + * \note For strict consistency with nsTHashtable::EntryHandle method naming, + * this should rather be called OrRemove, as it is legal to call it when the + * value is not in the set. For simplicity, as we don't have two methods, + * we still use "Remove" instead. + */ + void Remove(ValueType aValue) { Base::RemoveEntry(aValue); } + + using Base::EnsureRemoved; + + /** + * Removes all elements matching a predicate. + * + * The predicate must be compatible with signature bool (ValueType). + */ + template <typename Pred> + void RemoveIf(Pred&& aPred) { + for (auto it = cbegin(), end = cend(); it != end; ++it) { + if (aPred(static_cast<ValueType>(*it))) { + Remove(it); + } + } + } +}; + +template <typename KeyClass> +auto RangeSize(const nsTBaseHashSet<KeyClass>& aRange) { + return aRange.Count(); +} + +class nsCycleCollectionTraversalCallback; + +template <class KeyClass> +inline void ImplCycleCollectionUnlink(nsTBaseHashSet<KeyClass>& aField) { + aField.Clear(); +} + +template <class KeyClass> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + const nsTBaseHashSet<KeyClass>& aField, const char* aName, + uint32_t aFlags = 0) { + for (const auto& entry : aField) { + CycleCollectionNoteChild(aCallback, mozilla::detail::PtrGetWeak(entry), + aName, aFlags); + } +} + +namespace mozilla { +template <typename SetT> +class nsTSetInserter { + SetT* mSet; + + class Proxy { + SetT& mSet; + + public: + explicit Proxy(SetT& aSet) : mSet{aSet} {} + + template <typename E2> + void operator=(E2&& aValue) { + mSet.Insert(std::forward<E2>(aValue)); + } + }; + + public: + using iterator_category = std::output_iterator_tag; + + explicit nsTSetInserter(SetT& aSet) : mSet{&aSet} {} + + Proxy operator*() { return Proxy(*mSet); } + + nsTSetInserter& operator++() { return *this; } + nsTSetInserter& operator++(int) { return *this; } +}; +} // namespace mozilla + +template <typename E> +auto MakeInserter(nsTBaseHashSet<E>& aSet) { + return mozilla::nsTSetInserter<nsTBaseHashSet<E>>{aSet}; +} + +#endif // XPCOM_DS_NSTHASHSET_H_ diff --git a/xpcom/ds/nsTHashtable.h b/xpcom/ds/nsTHashtable.h new file mode 100644 index 0000000000..d4a385551f --- /dev/null +++ b/xpcom/ds/nsTHashtable.h @@ -0,0 +1,966 @@ +/* -*- 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/. */ + +// See the comment at the top of mfbt/HashTable.h for a comparison between +// PLDHashTable and mozilla::HashTable. + +#ifndef nsTHashtable_h__ +#define nsTHashtable_h__ + +#include <iterator> +#include <new> +#include <type_traits> +#include <utility> + +#include "PLDHashTable.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/PodOperations.h" +#include "mozilla/fallible.h" +#include "nsPointerHashKeys.h" +#include "nsTArrayForwardDeclare.h" + +template <class EntryType> +class nsTHashtable; + +namespace detail { +class nsTHashtableIteratorBase { + public: + using EndIteratorTag = PLDHashTable::Iterator::EndIteratorTag; + + nsTHashtableIteratorBase(nsTHashtableIteratorBase&& aOther) = default; + + nsTHashtableIteratorBase& operator=(nsTHashtableIteratorBase&& aOther) { + // User-defined because the move assignment operator is deleted in + // PLDHashtable::Iterator. + return operator=(static_cast<const nsTHashtableIteratorBase&>(aOther)); + } + + nsTHashtableIteratorBase(const nsTHashtableIteratorBase& aOther) + : mIterator{aOther.mIterator.Clone()} {} + nsTHashtableIteratorBase& operator=(const nsTHashtableIteratorBase& aOther) { + // Since PLDHashTable::Iterator has no assignment operator, we destroy and + // recreate mIterator. + mIterator.~Iterator(); + new (&mIterator) PLDHashTable::Iterator(aOther.mIterator.Clone()); + return *this; + } + + explicit nsTHashtableIteratorBase(PLDHashTable::Iterator aFrom) + : mIterator{std::move(aFrom)} {} + + explicit nsTHashtableIteratorBase(const PLDHashTable& aTable) + : mIterator{&const_cast<PLDHashTable&>(aTable)} {} + + nsTHashtableIteratorBase(const PLDHashTable& aTable, EndIteratorTag aTag) + : mIterator{&const_cast<PLDHashTable&>(aTable), aTag} {} + + bool operator==(const nsTHashtableIteratorBase& aRhs) const { + return mIterator == aRhs.mIterator; + } + bool operator!=(const nsTHashtableIteratorBase& aRhs) const { + return !(*this == aRhs); + } + + protected: + PLDHashTable::Iterator mIterator; +}; + +// STL-style iterators to allow the use in range-based for loops, e.g. +template <typename T> +class nsTHashtableEntryIterator : public nsTHashtableIteratorBase { + friend class nsTHashtable<std::remove_const_t<T>>; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = int32_t; + using pointer = value_type*; + using reference = value_type&; + + using iterator_type = nsTHashtableEntryIterator; + using const_iterator_type = nsTHashtableEntryIterator<const T>; + + using nsTHashtableIteratorBase::nsTHashtableIteratorBase; + + value_type* operator->() const { + return static_cast<value_type*>(mIterator.Get()); + } + value_type& operator*() const { + return *static_cast<value_type*>(mIterator.Get()); + } + + iterator_type& operator++() { + mIterator.Next(); + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } + + operator const_iterator_type() const { + return const_iterator_type{mIterator.Clone()}; + } +}; + +template <typename EntryType> +class nsTHashtableKeyIterator : public nsTHashtableIteratorBase { + friend class nsTHashtable<EntryType>; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const std::decay_t<typename EntryType::KeyType>; + using difference_type = int32_t; + using pointer = value_type*; + using reference = value_type&; + + using iterator_type = nsTHashtableKeyIterator; + using const_iterator_type = nsTHashtableKeyIterator; + + using nsTHashtableIteratorBase::nsTHashtableIteratorBase; + + value_type* operator->() const { + return &static_cast<const EntryType*>(mIterator.Get())->GetKey(); + } + decltype(auto) operator*() const { + return static_cast<const EntryType*>(mIterator.Get())->GetKey(); + } + + iterator_type& operator++() { + mIterator.Next(); + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } +}; + +template <typename EntryType> +class nsTHashtableKeyRange { + public: + using IteratorType = nsTHashtableKeyIterator<EntryType>; + using iterator = IteratorType; + + explicit nsTHashtableKeyRange(const PLDHashTable& aHashtable) + : mHashtable{aHashtable} {} + + auto begin() const { return IteratorType{mHashtable}; } + auto end() const { + return IteratorType{mHashtable, typename IteratorType::EndIteratorTag{}}; + } + auto cbegin() const { return begin(); } + auto cend() const { return end(); } + + uint32_t Count() const { return mHashtable.EntryCount(); } + + private: + const PLDHashTable& mHashtable; +}; + +template <typename EntryType> +auto RangeSize(const ::detail::nsTHashtableKeyRange<EntryType>& aRange) { + return aRange.Count(); +} + +} // namespace detail + +/** + * a base class for templated hashtables. + * + * Clients will rarely need to use this class directly. Check the derived + * classes first, to see if they will meet your needs. + * + * @param EntryType the templated entry-type class that is managed by the + * hashtable. <code>EntryType</code> must extend the following declaration, + * and <strong>must not declare any virtual functions or derive from classes + * with virtual functions.</strong> Any vtable pointer would break the + * PLDHashTable code. + *<pre> class EntryType : public PLDHashEntryHdr + * { + * public: or friend nsTHashtable<EntryType>; + * // KeyType is what we use when Get()ing or Put()ing this entry + * // this should either be a simple datatype (uint32_t, nsISupports*) or + * // a const reference (const nsAString&) + * typedef something KeyType; + * // KeyTypePointer is the pointer-version of KeyType, because + * // PLDHashTable.h requires keys to cast to <code>const void*</code> + * typedef const something* KeyTypePointer; + * + * EntryType(KeyTypePointer aKey); + * + * // A copy or C++11 Move constructor must be defined, even if + * // AllowMemMove() == true, otherwise you will cause link errors. + * EntryType(const EntryType& aEnt); // Either this... + * EntryType(EntryType&& aEnt); // ...or this + * + * // the destructor must be defined... or you will cause link errors! + * ~EntryType(); + * + * // KeyEquals(): does this entry match this key? + * bool KeyEquals(KeyTypePointer aKey) const; + * + * // KeyToPointer(): Convert KeyType to KeyTypePointer + * static KeyTypePointer KeyToPointer(KeyType aKey); + * + * // HashKey(): calculate the hash number + * static PLDHashNumber HashKey(KeyTypePointer aKey); + * + * // ALLOW_MEMMOVE can we move this class with memmove(), or do we have + * // to use the copy constructor? + * enum { ALLOW_MEMMOVE = true/false }; + * }</pre> + * + * @see nsInterfaceHashtable + * @see nsClassHashtable + * @see nsTHashMap + * @author "Benjamin Smedberg <bsmedberg@covad.net>" + */ + +template <class EntryType> +class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable { + typedef mozilla::fallible_t fallible_t; + static_assert(std::is_pointer_v<typename EntryType::KeyTypePointer>, + "KeyTypePointer should be a pointer"); + + public: + // Separate constructors instead of default aInitLength parameter since + // otherwise the default no-arg constructor isn't found. + nsTHashtable() + : mTable(Ops(), sizeof(EntryType), PLDHashTable::kDefaultInitialLength) {} + explicit nsTHashtable(uint32_t aInitLength) + : mTable(Ops(), sizeof(EntryType), aInitLength) {} + + /** + * destructor, cleans up and deallocates + */ + ~nsTHashtable() = default; + + nsTHashtable(nsTHashtable<EntryType>&& aOther); + nsTHashtable<EntryType>& operator=(nsTHashtable<EntryType>&& aOther); + + nsTHashtable(const nsTHashtable<EntryType>&) = delete; + nsTHashtable& operator=(const nsTHashtable<EntryType>&) = delete; + + /** + * Return the generation number for the table. This increments whenever + * the table data items are moved. + */ + uint32_t GetGeneration() const { return mTable.Generation(); } + + /** + * KeyType is typedef'ed for ease of use. + */ + typedef typename EntryType::KeyType KeyType; + + /** + * KeyTypePointer is typedef'ed for ease of use. + */ + typedef typename EntryType::KeyTypePointer KeyTypePointer; + + /** + * Return the number of entries in the table. + * @return number of entries + */ + uint32_t Count() const { return mTable.EntryCount(); } + + /** + * Return true if the hashtable is empty. + */ + bool IsEmpty() const { return Count() == 0; } + + /** + * Get the entry associated with a key. + * @param aKey the key to retrieve + * @return pointer to the entry class, if the key exists; nullptr if the + * key doesn't exist + */ + EntryType* GetEntry(KeyType aKey) const { + return static_cast<EntryType*>( + mTable.Search(EntryType::KeyToPointer(aKey))); + } + + /** + * Return true if an entry for the given key exists, false otherwise. + * @param aKey the key to retrieve + * @return true if the key exists, false if the key doesn't exist + */ + bool Contains(KeyType aKey) const { return !!GetEntry(aKey); } + + /** + * Infallibly get the entry associated with a key, or create a new entry, + * @param aKey the key to retrieve + * @return pointer to the entry retrieved; never nullptr + */ + EntryType* PutEntry(KeyType aKey) { + // Infallible WithEntryHandle. + return WithEntryHandle( + aKey, [](auto entryHandle) { return entryHandle.OrInsert(); }); + } + + /** + * Fallibly get the entry associated with a key, or create a new entry, + * @param aKey the key to retrieve + * @return pointer to the entry retrieved; nullptr only if memory can't + * be allocated + */ + [[nodiscard]] EntryType* PutEntry(KeyType aKey, const fallible_t& aFallible) { + return WithEntryHandle(aKey, aFallible, [](auto maybeEntryHandle) { + return maybeEntryHandle ? maybeEntryHandle->OrInsert() : nullptr; + }); + } + + /** + * Get the entry associated with a key, or create a new entry using infallible + * allocation and insert that. + * @param aKey the key to retrieve + * @param aEntry will be assigned (if non-null) to the entry that was + * found or created + * @return true if a new entry was created, or false if an existing entry + * was found + */ + [[nodiscard]] bool EnsureInserted(KeyType aKey, + EntryType** aEntry = nullptr) { + auto oldCount = Count(); + EntryType* entry = PutEntry(aKey); + if (aEntry) { + *aEntry = entry; + } + return oldCount != Count(); + } + + /** + * Remove the entry associated with a key. + * @param aKey of the entry to remove + */ + void RemoveEntry(KeyType aKey) { + mTable.Remove(EntryType::KeyToPointer(aKey)); + } + + /** + * Lookup the entry associated with aKey and remove it if found, otherwise + * do nothing. + * @param aKey of the entry to remove + * @return true if an entry was found and removed, or false if no entry + * was found for aKey + */ + bool EnsureRemoved(KeyType aKey) { + auto* entry = GetEntry(aKey); + if (entry) { + RemoveEntry(entry); + return true; + } + return false; + } + + /** + * Remove the entry associated with a key. + * @param aEntry the entry-pointer to remove (obtained from GetEntry) + */ + void RemoveEntry(EntryType* aEntry) { mTable.RemoveEntry(aEntry); } + + /** + * Remove the entry associated with a key, but don't resize the hashtable. + * This is a low-level method, and is not recommended unless you know what + * you're doing. If you use it, please add a comment explaining why you + * didn't use RemoveEntry(). + * @param aEntry the entry-pointer to remove (obtained from GetEntry) + */ + void RawRemoveEntry(EntryType* aEntry) { mTable.RawRemove(aEntry); } + + protected: + class EntryHandle { + public: + EntryHandle(EntryHandle&& aOther) = default; + ~EntryHandle() = default; + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&&) = delete; + + KeyType Key() const { return mKey; } + + bool HasEntry() const { return mEntryHandle.HasEntry(); } + + explicit operator bool() const { return mEntryHandle.operator bool(); } + + EntryType* Entry() { return static_cast<EntryType*>(mEntryHandle.Entry()); } + + void Insert() { InsertInternal(); } + + EntryType* OrInsert() { + if (!HasEntry()) { + Insert(); + } + return Entry(); + } + + void Remove() { mEntryHandle.Remove(); } + + void OrRemove() { mEntryHandle.OrRemove(); } + + protected: + template <typename... Args> + void InsertInternal(Args&&... aArgs) { + MOZ_RELEASE_ASSERT(!HasEntry()); + mEntryHandle.Insert([&](PLDHashEntryHdr* entry) { + new (mozilla::KnownNotNull, entry) EntryType( + EntryType::KeyToPointer(mKey), std::forward<Args>(aArgs)...); + }); + } + + private: + friend class nsTHashtable; + + EntryHandle(KeyType aKey, PLDHashTable::EntryHandle&& aEntryHandle) + : mKey(aKey), mEntryHandle(std::move(aEntryHandle)) {} + + KeyType mKey; + PLDHashTable::EntryHandle mEntryHandle; + }; + + template <class F> + auto WithEntryHandle(KeyType aKey, F&& aFunc) + -> std::invoke_result_t<F, EntryHandle&&> { + return this->mTable.WithEntryHandle( + EntryType::KeyToPointer(aKey), + [&aKey, &aFunc](auto entryHandle) -> decltype(auto) { + return std::forward<F>(aFunc)( + EntryHandle{aKey, std::move(entryHandle)}); + }); + } + + template <class F> + auto WithEntryHandle(KeyType aKey, const mozilla::fallible_t& aFallible, + F&& aFunc) + -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> { + return this->mTable.WithEntryHandle( + EntryType::KeyToPointer(aKey), aFallible, + [&aKey, &aFunc](auto maybeEntryHandle) { + return std::forward<F>(aFunc)( + maybeEntryHandle + ? mozilla::Some(EntryHandle{aKey, maybeEntryHandle.extract()}) + : mozilla::Nothing()); + }); + } + + public: + class ConstIterator { + public: + explicit ConstIterator(nsTHashtable* aTable) + : mBaseIterator(&aTable->mTable) {} + ~ConstIterator() = default; + + KeyType Key() const { return Get()->GetKey(); } + + const EntryType* Get() const { + return static_cast<const EntryType*>(mBaseIterator.Get()); + } + + bool Done() const { return mBaseIterator.Done(); } + void Next() { mBaseIterator.Next(); } + + ConstIterator() = delete; + ConstIterator(const ConstIterator&) = delete; + ConstIterator(ConstIterator&& aOther) = delete; + ConstIterator& operator=(const ConstIterator&) = delete; + ConstIterator& operator=(ConstIterator&&) = delete; + + protected: + PLDHashTable::Iterator mBaseIterator; + }; + + // This is an iterator that also allows entry removal. Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // Entry* entry = iter.Get(); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + class Iterator final : public ConstIterator { + public: + using ConstIterator::ConstIterator; + + using ConstIterator::Get; + + EntryType* Get() const { + return static_cast<EntryType*>(this->mBaseIterator.Get()); + } + + void Remove() { this->mBaseIterator.Remove(); } + }; + + Iterator Iter() { return Iterator(this); } + + ConstIterator ConstIter() const { + return ConstIterator(const_cast<nsTHashtable*>(this)); + } + + using const_iterator = ::detail::nsTHashtableEntryIterator<const EntryType>; + using iterator = ::detail::nsTHashtableEntryIterator<EntryType>; + + iterator begin() { return iterator{mTable}; } + const_iterator begin() const { return const_iterator{mTable}; } + const_iterator cbegin() const { return begin(); } + iterator end() { + return iterator{mTable, typename iterator::EndIteratorTag{}}; + } + const_iterator end() const { + return const_iterator{mTable, typename const_iterator::EndIteratorTag{}}; + } + const_iterator cend() const { return end(); } + + void Remove(const_iterator& aIter) { aIter.mIterator.Remove(); } + + /** + * Return a range of the keys (of KeyType). Note this range iterates over the + * keys in place, so modifications to the nsTHashtable invalidate the range + * while it's iterated, except when calling Remove() with a key iterator + * derived from that range. + */ + auto Keys() const { + return ::detail::nsTHashtableKeyRange<EntryType>{mTable}; + } + + /** + * Remove an entry from a key range, specified via a key iterator, e.g. + * + * for (auto it = hash.Keys().begin(), end = hash.Keys().end(); + * it != end; * ++it) { + * if (*it > 42) { hash.Remove(it); } + * } + */ + void Remove(::detail::nsTHashtableKeyIterator<EntryType>& aIter) { + aIter.mIterator.Remove(); + } + + /** + * Remove all entries, return hashtable to "pristine" state. It's + * conceptually the same as calling the destructor and then re-calling the + * constructor. + */ + void Clear() { mTable.Clear(); } + + /** + * Measure the size of the table's entry storage. Does *not* measure anything + * hanging off table entries; hence the "Shallow" prefix. To measure that, + * either use SizeOfExcludingThis() or iterate manually over the entries, + * calling SizeOfExcludingThis() on each one. + * + * @param aMallocSizeOf the function used to measure heap-allocated blocks + * @return the measured shallow size of the table + */ + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Like ShallowSizeOfExcludingThis, but includes sizeof(*this). + */ + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * This is a "deep" measurement of the table. To use it, |EntryType| must + * define SizeOfExcludingThis, and that method will be called on all live + * entries. + */ + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = ConstIter(); !iter.Done(); iter.Next()) { + n += (*iter.Get()).SizeOfExcludingThis(aMallocSizeOf); + } + return n; + } + + /** + * Like SizeOfExcludingThis, but includes sizeof(*this). + */ + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Swap the elements in this hashtable with the elements in aOther. + */ + void SwapElements(nsTHashtable<EntryType>& aOther) { + MOZ_ASSERT_IF(this->mTable.Ops() && aOther.mTable.Ops(), + this->mTable.Ops() == aOther.mTable.Ops()); + std::swap(this->mTable, aOther.mTable); + } + + /** + * Mark the table as constant after initialization. + * + * This will prevent assertions when a read-only hash is accessed on multiple + * threads without synchronization. + */ + void MarkImmutable() { mTable.MarkImmutable(); } + + protected: + PLDHashTable mTable; + + static PLDHashNumber s_HashKey(const void* aKey); + + static bool s_MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey); + + static void s_CopyEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + + static void s_ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + private: + // copy constructor, not implemented + nsTHashtable(nsTHashtable<EntryType>& aToCopy) = delete; + + /** + * Gets the table's ops. + */ + static const PLDHashTableOps* Ops(); + + // assignment operator, not implemented + nsTHashtable<EntryType>& operator=(nsTHashtable<EntryType>& aToEqual) = + delete; +}; + +namespace mozilla { +namespace detail { + +// Like PLDHashTable::MoveEntryStub, but specialized for fixed N (i.e. the size +// of the entries in the hashtable). Saves a memory read to figure out the size +// from the table and gives the compiler the opportunity to inline the memcpy. +// +// We define this outside of nsTHashtable so only one copy exists for every N, +// rather than separate copies for every EntryType used with nsTHashtable. +template <size_t N> +static void FixedSizeEntryMover(PLDHashTable*, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + memcpy(aTo, aFrom, N); +} + +} // namespace detail +} // namespace mozilla + +// +// template definitions +// + +template <class EntryType> +nsTHashtable<EntryType>::nsTHashtable(nsTHashtable<EntryType>&& aOther) + : mTable(std::move(aOther.mTable)) {} + +template <class EntryType> +nsTHashtable<EntryType>& nsTHashtable<EntryType>::operator=( + nsTHashtable<EntryType>&& aOther) { + mTable = std::move(aOther.mTable); + return *this; +} + +template <class EntryType> +/* static */ const PLDHashTableOps* nsTHashtable<EntryType>::Ops() { + // If this variable is a global variable, we get strange start-up failures on + // WindowsCrtPatch.h (see bug 1166598 comment 20). But putting it inside a + // function avoids that problem. + static const PLDHashTableOps sOps = { + s_HashKey, s_MatchEntry, + EntryType::ALLOW_MEMMOVE + ? mozilla::detail::FixedSizeEntryMover<sizeof(EntryType)> + : s_CopyEntry, + // Simplify hashtable clearing in case our entries are trivially + // destructible. + std::is_trivially_destructible_v<EntryType> ? nullptr : s_ClearEntry, + // We don't use a generic initEntry hook because we want to allow + // initialization of data members defined in derived classes directly + // in the entry constructor (for example when a member can't be default + // constructed). + nullptr}; + return &sOps; +} + +// static definitions + +template <class EntryType> +PLDHashNumber nsTHashtable<EntryType>::s_HashKey(const void* aKey) { + return EntryType::HashKey(static_cast<KeyTypePointer>(aKey)); +} + +template <class EntryType> +bool nsTHashtable<EntryType>::s_MatchEntry(const PLDHashEntryHdr* aEntry, + const void* aKey) { + return (static_cast<const EntryType*>(aEntry)) + ->KeyEquals(static_cast<KeyTypePointer>(aKey)); +} + +template <class EntryType> +void nsTHashtable<EntryType>::s_CopyEntry(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + auto* fromEntry = const_cast<std::remove_const_t<EntryType>*>( + static_cast<const EntryType*>(aFrom)); + + new (mozilla::KnownNotNull, aTo) EntryType(std::move(*fromEntry)); + + fromEntry->~EntryType(); +} + +template <class EntryType> +void nsTHashtable<EntryType>::s_ClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) { + static_cast<EntryType*>(aEntry)->~EntryType(); +} + +class nsCycleCollectionTraversalCallback; + +template <class EntryType> +inline void ImplCycleCollectionUnlink(nsTHashtable<EntryType>& aField) { + aField.Clear(); +} + +template <class EntryType> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsTHashtable<EntryType>& aField, const char* aName, uint32_t aFlags = 0) { + for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) { + EntryType* entry = iter.Get(); + ImplCycleCollectionTraverse(aCallback, *entry, aName, aFlags); + } +} + +/** + * For nsTHashtable with pointer entries, we can have a template specialization + * that layers a typed T* interface on top of a common implementation that + * works internally with void pointers. This arrangement saves code size and + * might slightly improve performance as well. + */ + +/** + * We need a separate entry type class for the inheritance structure of the + * nsTHashtable specialization below; nsVoidPtrHashKey is simply typedefed to a + * specialization of nsPtrHashKey, and the formulation: + * + * class nsTHashtable<nsPtrHashKey<T>> : + * protected nsTHashtable<nsPtrHashKey<const void> + * + * is not going to turn out very well, since we'd wind up with an nsTHashtable + * instantiation that is its own base class. + */ +namespace detail { + +class VoidPtrHashKey : public nsPtrHashKey<const void> { + typedef nsPtrHashKey<const void> Base; + + public: + explicit VoidPtrHashKey(const void* aKey) : Base(aKey) {} +}; + +} // namespace detail + +/** + * See the main nsTHashtable documentation for descriptions of this class's + * methods. + */ +template <typename T> +class nsTHashtable<nsPtrHashKey<T>> + : protected nsTHashtable<::detail::VoidPtrHashKey> { + typedef nsTHashtable<::detail::VoidPtrHashKey> Base; + typedef nsPtrHashKey<T> EntryType; + + // We play games with reinterpret_cast'ing between these two classes, so + // try to ensure that playing said games is reasonable. + static_assert(sizeof(nsPtrHashKey<T>) == sizeof(::detail::VoidPtrHashKey), + "hash keys must be the same size"); + + nsTHashtable(const nsTHashtable& aOther) = delete; + nsTHashtable& operator=(const nsTHashtable& aOther) = delete; + + public: + nsTHashtable() = default; + explicit nsTHashtable(uint32_t aInitLength) : Base(aInitLength) {} + + ~nsTHashtable() = default; + + nsTHashtable(nsTHashtable&&) = default; + + using Base::Clear; + using Base::Count; + using Base::GetGeneration; + using Base::IsEmpty; + + using Base::MarkImmutable; + using Base::ShallowSizeOfExcludingThis; + using Base::ShallowSizeOfIncludingThis; + + /* Wrapper functions */ + EntryType* GetEntry(T* aKey) const { + return reinterpret_cast<EntryType*>(Base::GetEntry(aKey)); + } + + bool Contains(T* aKey) const { return Base::Contains(aKey); } + + EntryType* PutEntry(T* aKey) { + return reinterpret_cast<EntryType*>(Base::PutEntry(aKey)); + } + + [[nodiscard]] EntryType* PutEntry(T* aKey, + const mozilla::fallible_t& aFallible) { + return reinterpret_cast<EntryType*>(Base::PutEntry(aKey, aFallible)); + } + + [[nodiscard]] bool EnsureInserted(T* aKey, EntryType** aEntry = nullptr) { + return Base::EnsureInserted( + aKey, reinterpret_cast<::detail::VoidPtrHashKey**>(aEntry)); + } + + void RemoveEntry(T* aKey) { Base::RemoveEntry(aKey); } + + bool EnsureRemoved(T* aKey) { return Base::EnsureRemoved(aKey); } + + void RemoveEntry(EntryType* aEntry) { + Base::RemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + void RawRemoveEntry(EntryType* aEntry) { + Base::RawRemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + protected: + class EntryHandle : protected Base::EntryHandle { + public: + using Base = nsTHashtable::Base::EntryHandle; + + EntryHandle(EntryHandle&& aOther) = default; + ~EntryHandle() = default; + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&&) = delete; + + using Base::Key; + + using Base::HasEntry; + + using Base::operator bool; + + EntryType* Entry() { return reinterpret_cast<EntryType*>(Base::Entry()); } + + using Base::Insert; + + EntryType* OrInsert() { + if (!HasEntry()) { + Insert(); + } + return Entry(); + } + + using Base::Remove; + + using Base::OrRemove; + + private: + friend class nsTHashtable; + + explicit EntryHandle(Base&& aBase) : Base(std::move(aBase)) {} + }; + + template <class F> + auto WithEntryHandle(KeyType aKey, F aFunc) + -> std::invoke_result_t<F, EntryHandle&&> { + return Base::WithEntryHandle(aKey, [&aFunc](auto entryHandle) { + return aFunc(EntryHandle{std::move(entryHandle)}); + }); + } + + template <class F> + auto WithEntryHandle(KeyType aKey, const mozilla::fallible_t& aFallible, + F aFunc) + -> std::invoke_result_t<F, mozilla::Maybe<EntryHandle>&&> { + return Base::WithEntryHandle( + aKey, aFallible, [&aFunc](auto maybeEntryHandle) { + return aFunc(maybeEntryHandle ? mozilla::Some(EntryHandle{ + maybeEntryHandle.extract()}) + : mozilla::Nothing()); + }); + } + + public: + class ConstIterator { + public: + explicit ConstIterator(nsTHashtable* aTable) + : mBaseIterator(&aTable->mTable) {} + ~ConstIterator() = default; + + KeyType Key() const { return Get()->GetKey(); } + + const EntryType* Get() const { + return static_cast<const EntryType*>(mBaseIterator.Get()); + } + + bool Done() const { return mBaseIterator.Done(); } + void Next() { mBaseIterator.Next(); } + + ConstIterator() = delete; + ConstIterator(const ConstIterator&) = delete; + ConstIterator(ConstIterator&& aOther) = delete; + ConstIterator& operator=(const ConstIterator&) = delete; + ConstIterator& operator=(ConstIterator&&) = delete; + + protected: + PLDHashTable::Iterator mBaseIterator; + }; + + class Iterator final : public ConstIterator { + public: + using ConstIterator::ConstIterator; + + using ConstIterator::Get; + + EntryType* Get() const { + return static_cast<EntryType*>(this->mBaseIterator.Get()); + } + + void Remove() { this->mBaseIterator.Remove(); } + }; + + Iterator Iter() { return Iterator(this); } + + ConstIterator ConstIter() const { + return ConstIterator(const_cast<nsTHashtable*>(this)); + } + + using const_iterator = ::detail::nsTHashtableEntryIterator<const EntryType>; + using iterator = ::detail::nsTHashtableEntryIterator<EntryType>; + + iterator begin() { return iterator{mTable}; } + const_iterator begin() const { return const_iterator{mTable}; } + const_iterator cbegin() const { return begin(); } + iterator end() { + return iterator{mTable, typename iterator::EndIteratorTag{}}; + } + const_iterator end() const { + return const_iterator{mTable, typename const_iterator::EndIteratorTag{}}; + } + const_iterator cend() const { return end(); } + + auto Keys() const { + return ::detail::nsTHashtableKeyRange<nsPtrHashKey<T>>{mTable}; + } + + void Remove(::detail::nsTHashtableKeyIterator<EntryType>& aIter) { + aIter.mIterator.Remove(); + } + + void SwapElements(nsTHashtable& aOther) { Base::SwapElements(aOther); } +}; + +#endif // nsTHashtable_h__ diff --git a/xpcom/ds/nsTObserverArray.cpp b/xpcom/ds/nsTObserverArray.cpp new file mode 100644 index 0000000000..68211873dd --- /dev/null +++ b/xpcom/ds/nsTObserverArray.cpp @@ -0,0 +1,27 @@ +/* -*- 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 "nsTObserverArray.h" + +void nsTObserverArray_base::AdjustIterators(index_type aModPos, + diff_type aAdjustment) { + MOZ_ASSERT(aAdjustment == -1 || aAdjustment == 1, "invalid adjustment"); + Iterator_base* iter = mIterators; + while (iter) { + if (iter->mPosition > aModPos) { + iter->mPosition += aAdjustment; + } + iter = iter->mNext; + } +} + +void nsTObserverArray_base::ClearIterators() { + Iterator_base* iter = mIterators; + while (iter) { + iter->mPosition = 0; + iter = iter->mNext; + } +} diff --git a/xpcom/ds/nsTObserverArray.h b/xpcom/ds/nsTObserverArray.h new file mode 100644 index 0000000000..4d3e4087e0 --- /dev/null +++ b/xpcom/ds/nsTObserverArray.h @@ -0,0 +1,583 @@ +/* -*- 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 nsTObserverArray_h___ +#define nsTObserverArray_h___ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/ReverseIterator.h" +#include "nsTArray.h" +#include "nsCycleCollectionNoteChild.h" + +/** + * An array of observers. Like a normal array, but supports iterators that are + * stable even if the array is modified during iteration. + * The template parameter T is the observer type the array will hold; + * N is the number of built-in storage slots that come with the array. + * NOTE: You probably want to use nsTObserverArray, unless you specifically + * want built-in storage. See below. + * @see nsTObserverArray, nsTArray + */ + +class nsTObserverArray_base { + public: + typedef size_t index_type; + typedef size_t size_type; + typedef ptrdiff_t diff_type; + + protected: + class Iterator_base { + public: + Iterator_base(const Iterator_base&) = delete; + + protected: + friend class nsTObserverArray_base; + + Iterator_base(index_type aPosition, Iterator_base* aNext) + : mPosition(aPosition), mNext(aNext) {} + + // The current position of the iterator. Its exact meaning differs + // depending on iterator. See nsTObserverArray<T>::ForwardIterator. + index_type mPosition; + + // The next iterator currently iterating the same array + Iterator_base* mNext; + }; + + nsTObserverArray_base() : mIterators(nullptr) {} + + ~nsTObserverArray_base() { + NS_ASSERTION(mIterators == nullptr, "iterators outlasting array"); + } + + /** + * Adjusts iterators after an element has been inserted or removed + * from the array. + * @param aModPos Position where elements were added or removed. + * @param aAdjustment -1 if an element was removed, 1 if an element was + * added. + */ + void AdjustIterators(index_type aModPos, diff_type aAdjustment); + + /** + * Clears iterators when the array is destroyed. + */ + void ClearIterators(); + + mutable Iterator_base* mIterators; +}; + +template <class T, size_t N> +class nsAutoTObserverArray : protected nsTObserverArray_base { + public: + typedef T value_type; + typedef nsTArray<T> array_type; + + nsAutoTObserverArray() = default; + + // + // Accessor methods + // + + // @return The number of elements in the array. + size_type Length() const { return mArray.Length(); } + + // @return True if the array is empty or false otherwise. + bool IsEmpty() const { return mArray.IsEmpty(); } + + // This method provides direct, readonly access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + const value_type* Elements() const { return mArray.Elements(); } + value_type* Elements() { return mArray.Elements(); } + + // This method provides direct access to an element of the array. The given + // index must be within the array bounds. If the underlying array may change + // during iteration, use an iterator instead of this function. + // @param aIndex The index of an element in the array. + // @return A reference to the i'th element of the array. + value_type& ElementAt(index_type aIndex) { return mArray.ElementAt(aIndex); } + + // Same as above, but readonly. + const value_type& ElementAt(index_type aIndex) const { + return mArray.ElementAt(aIndex); + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + value_type& SafeElementAt(index_type aIndex, value_type& aDef) { + return mArray.SafeElementAt(aIndex, aDef); + } + + // Same as above, but readonly. + const value_type& SafeElementAt(index_type aIndex, + const value_type& aDef) const { + return mArray.SafeElementAt(aIndex, aDef); + } + + // No operator[] is provided because the point of this class is to support + // allow modifying the array during iteration, and ElementAt() is not safe + // in those conditions. + + // + // Search methods + // + + // This method searches, starting from the beginning of the array, + // for the first element in this array that is equal to the given element. + // 'operator==' must be defined for value_type. + // @param aItem The item to search for. + // @return true if the element was found. + template <class Item> + bool Contains(const Item& aItem) const { + return IndexOf(aItem) != array_type::NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. + // 'operator==' must be defined for value_type. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @return The index of the found element or NoIndex if not found. + template <class Item> + index_type IndexOf(const Item& aItem, index_type aStart = 0) const { + return mArray.IndexOf(aItem, aStart); + } + + // + // Mutation methods + // + + // Insert a given element at the given index. + // @param aIndex The index at which to insert item. + // @param aItem The item to insert, + template <class Item> + void InsertElementAt(index_type aIndex, const Item& aItem) { + mArray.InsertElementAt(aIndex, aItem); + AdjustIterators(aIndex, 1); + } + + // Same as above but without copy constructing. + // This is useful to avoid temporaries. + value_type* InsertElementAt(index_type aIndex) { + value_type* item = mArray.InsertElementAt(aIndex); + AdjustIterators(aIndex, 1); + return item; + } + + // Prepend an element to the array unless it already exists in the array. + // 'operator==' must be defined for value_type. + // @param aItem The item to prepend. + template <class Item> + void PrependElementUnlessExists(const Item& aItem) { + if (!Contains(aItem)) { + mArray.InsertElementAt(0, aItem); + AdjustIterators(0, 1); + } + } + + // Append an element to the array. + // @param aItem The item to append. + template <class Item> + void AppendElement(Item&& aItem) { + mArray.AppendElement(std::forward<Item>(aItem)); + } + + // Same as above, but without copy-constructing. This is useful to avoid + // temporaries. + value_type* AppendElement() { return mArray.AppendElement(); } + + // Append an element to the array unless it already exists in the array. + // 'operator==' must be defined for value_type. + // @param aItem The item to append. + template <class Item> + void AppendElementUnlessExists(const Item& aItem) { + if (!Contains(aItem)) { + mArray.AppendElement(aItem); + } + } + + // Remove an element from the array. + // @param aIndex The index of the item to remove. + void RemoveElementAt(index_type aIndex) { + NS_ASSERTION(aIndex < mArray.Length(), "invalid index"); + mArray.RemoveElementAt(aIndex); + AdjustIterators(aIndex, -1); + } + + // This helper function combines IndexOf with RemoveElementAt to "search + // and destroy" the first element that is equal to the given element. + // 'operator==' must be defined for value_type. + // @param aItem The item to search for. + // @return true if the element was found and removed. + template <class Item> + bool RemoveElement(const Item& aItem) { + index_type index = mArray.IndexOf(aItem, 0); + if (index == array_type::NoIndex) { + return false; + } + + mArray.RemoveElementAt(index); + AdjustIterators(index, -1); + return true; + } + + // See nsTArray::RemoveElementsBy. Neither the predicate nor the removal of + // elements from the array must have any side effects that modify the array. + template <typename Predicate> + void NonObservingRemoveElementsBy(Predicate aPredicate) { + index_type i = 0; + mArray.RemoveElementsBy([&](const value_type& aItem) { + if (aPredicate(aItem)) { + // This element is going to be removed. + AdjustIterators(i, -1); + return true; + } + ++i; + return false; + }); + } + + // Removes all observers and collapses all iterators to the beginning of + // the array. The result is that forward iterators will see all elements + // in the array. + void Clear() { + mArray.Clear(); + ClearIterators(); + } + + // Compact the array to minimize the memory it uses + void Compact() { mArray.Compact(); } + + // Returns the number of bytes on the heap taken up by this object, not + // including sizeof(*this). If you want to measure anything hanging off the + // array, you must iterate over the elements and measure them individually; + // hence the "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + // + // Iterators + // + + // Base class for iterators. Do not use this directly. + class Iterator : public Iterator_base { + protected: + friend class nsAutoTObserverArray; + typedef nsAutoTObserverArray<T, N> array_type; + + Iterator(const Iterator& aOther) + : Iterator(aOther.mPosition, aOther.mArray) {} + + Iterator(index_type aPosition, const array_type& aArray) + : Iterator_base(aPosition, aArray.mIterators), + mArray(const_cast<array_type&>(aArray)) { + aArray.mIterators = this; + } + + ~Iterator() { + NS_ASSERTION(mArray.mIterators == this, + "Iterators must currently be destroyed in opposite order " + "from the construction order. It is suggested that you " + "simply put them on the stack"); + mArray.mIterators = mNext; + } + + // The array we're iterating + array_type& mArray; + }; + + // Iterates the array forward from beginning to end. mPosition points + // to the element that will be returned on next call to GetNext. + // Elements: + // - prepended to the array during iteration *will not* be traversed + // - appended during iteration *will* be traversed + // - removed during iteration *will not* be traversed. + // @see EndLimitedIterator + class ForwardIterator : protected Iterator { + public: + typedef nsAutoTObserverArray<T, N> array_type; + typedef Iterator base_type; + + explicit ForwardIterator(const array_type& aArray) : Iterator(0, aArray) {} + + ForwardIterator(const array_type& aArray, index_type aPos) + : Iterator(aPos, aArray) {} + + bool operator<(const ForwardIterator& aOther) const { + NS_ASSERTION(&this->mArray == &aOther.mArray, + "not iterating the same array"); + return base_type::mPosition < aOther.mPosition; + } + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { + return base_type::mPosition < base_type::mArray.Length(); + } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + value_type& GetNext() { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.Elements()[base_type::mPosition++]; + } + + // Removes the element at the current iterator position. + // (the last element returned from |GetNext()|) + // This will not affect the next call to |GetNext()| + void Remove() { + return base_type::mArray.RemoveElementAt(base_type::mPosition - 1); + } + }; + + // EndLimitedIterator works like ForwardIterator, but will not iterate new + // observers appended to the array after the iterator was created. + class EndLimitedIterator : protected ForwardIterator { + public: + typedef nsAutoTObserverArray<T, N> array_type; + typedef Iterator base_type; + + explicit EndLimitedIterator(const array_type& aArray) + : ForwardIterator(aArray), mEnd(aArray, aArray.Length()) {} + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { return *this < mEnd; } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + value_type& GetNext() { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.Elements()[base_type::mPosition++]; + } + + // Removes the element at the current iterator position. + // (the last element returned from |GetNext()|) + // This will not affect the next call to |GetNext()| + void Remove() { + return base_type::mArray.RemoveElementAt(base_type::mPosition - 1); + } + + private: + ForwardIterator mEnd; + }; + + // Iterates the array backward from end to start. mPosition points + // to the element that was returned last. + // Elements: + // - prepended to the array during iteration *will* be traversed, + // unless the iteration already arrived at the first element + // - appended during iteration *will not* be traversed + // - removed during iteration *will not* be traversed. + class BackwardIterator : protected Iterator { + public: + typedef nsAutoTObserverArray<T, N> array_type; + typedef Iterator base_type; + + explicit BackwardIterator(const array_type& aArray) + : Iterator(aArray.Length(), aArray) {} + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { return base_type::mPosition > 0; } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + value_type& GetNext() { + NS_ASSERTION(HasMore(), "iterating beyond start of array"); + return base_type::mArray.Elements()[--base_type::mPosition]; + } + + // Removes the element at the current iterator position. + // (the last element returned from |GetNext()|) + // This will not affect the next call to |GetNext()| + void Remove() { + return base_type::mArray.RemoveElementAt(base_type::mPosition); + } + }; + + struct EndSentinel {}; + + // Internal type, do not use directly, see + // ForwardRange()/EndLimitedRange()/BackwardRange(). + template <typename Iterator, typename U> + struct STLIterator { + using value_type = std::remove_reference_t<U>; + + explicit STLIterator(const nsAutoTObserverArray<T, N>& aArray) + : mIterator{aArray} { + operator++(); + } + + bool operator!=(const EndSentinel&) const { + // We are a non-end-sentinel and the other is an end-sentinel, so we are + // still valid if mCurrent is valid. + return mCurrent; + } + + STLIterator& operator++() { + mCurrent = mIterator.HasMore() ? &mIterator.GetNext() : nullptr; + return *this; + } + + value_type* operator->() { return mCurrent; } + U& operator*() { return *mCurrent; } + + private: + Iterator mIterator; + value_type* mCurrent; + }; + + // Internal type, do not use directly, see + // ForwardRange()/EndLimitedRange()/BackwardRange(). + template <typename Iterator, typename U> + class STLIteratorRange { + public: + using iterator = STLIterator<Iterator, U>; + + explicit STLIteratorRange(const nsAutoTObserverArray<T, N>& aArray) + : mArray{aArray} {} + + STLIteratorRange(const STLIteratorRange& aOther) = delete; + + iterator begin() const { return iterator{mArray}; } + EndSentinel end() const { return {}; } + + private: + const nsAutoTObserverArray<T, N>& mArray; + }; + + template <typename U> + using STLForwardIteratorRange = STLIteratorRange<ForwardIterator, U>; + + template <typename U> + using STLEndLimitedIteratorRange = STLIteratorRange<EndLimitedIterator, U>; + + template <typename U> + using STLBackwardIteratorRange = STLIteratorRange<BackwardIterator, U>; + + // Constructs a range (usable with range-based for) based on the + // ForwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto ForwardRange() { return STLForwardIteratorRange<T>{*this}; } + + // Constructs a const range (usable with range-based for) based on the + // ForwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto ForwardRange() const { return STLForwardIteratorRange<const T>{*this}; } + + // Constructs a range (usable with range-based for) based on the + // EndLimitedIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto EndLimitedRange() { return STLEndLimitedIteratorRange<T>{*this}; } + + // Constructs a const range (usable with range-based for) based on the + // EndLimitedIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto EndLimitedRange() const { + return STLEndLimitedIteratorRange<const T>{*this}; + } + + // Constructs a range (usable with range-based for) based on the + // BackwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto BackwardRange() { return STLBackwardIteratorRange<T>{*this}; } + + // Constructs a const range (usable with range-based for) based on the + // BackwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto BackwardRange() const { + return STLBackwardIteratorRange<const T>{*this}; + } + + // Constructs a const range (usable with range-based for and STL-style + // algorithms) based on a non-observing iterator. The array must not be + // modified during iteration. + auto NonObservingRange() const { + return mozilla::detail::IteratorRange< + typename AutoTArray<T, N>::const_iterator, + typename AutoTArray<T, N>::const_reverse_iterator>{mArray.cbegin(), + mArray.cend()}; + } + + protected: + AutoTArray<T, N> mArray; +}; + +template <class T> +class nsTObserverArray : public nsAutoTObserverArray<T, 0> { + public: + typedef nsAutoTObserverArray<T, 0> base_type; + typedef nsTObserverArray_base::size_type size_type; + + // + // Initialization methods + // + + nsTObserverArray() = default; + + // Initialize this array and pre-allocate some number of elements. + explicit nsTObserverArray(size_type aCapacity) { + base_type::mArray.SetCapacity(aCapacity); + } + + nsTObserverArray Clone() const { + auto result = nsTObserverArray{}; + result.mArray.Assign(this->mArray); + return result; + } +}; + +template <typename T, size_t N> +auto MakeBackInserter(nsAutoTObserverArray<T, N>& aArray) { + return mozilla::nsTArrayBackInserter<T, nsAutoTObserverArray<T, N>>{aArray}; +} + +template <typename T, size_t N> +inline void ImplCycleCollectionUnlink(nsAutoTObserverArray<T, N>& aField) { + aField.Clear(); +} + +template <typename T, size_t N> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsAutoTObserverArray<T, N>& aField, const char* aName, + uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + size_t length = aField.Length(); + for (size_t i = 0; i < length; ++i) { + ImplCycleCollectionTraverse(aCallback, aField.ElementAt(i), aName, aFlags); + } +} + +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(array_, func_, params_) \ + do { \ + for (RefPtr obs_ : (array_).ForwardRange()) { \ + obs_->func_ params_; \ + } \ + } while (0) + +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(array_, func_, params_) \ + do { \ + for (auto* obs_ : (array_).ForwardRange()) { \ + obs_->func_ params_; \ + } \ + } while (0) + +#endif // nsTObserverArray_h___ diff --git a/xpcom/ds/nsTPriorityQueue.h b/xpcom/ds/nsTPriorityQueue.h new file mode 100644 index 0000000000..1fa1cc1e50 --- /dev/null +++ b/xpcom/ds/nsTPriorityQueue.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 NS_TPRIORITY_QUEUE_H_ +#define NS_TPRIORITY_QUEUE_H_ + +#include "mozilla/Assertions.h" +#include "nsTArray.h" + +/** + * A templatized priority queue data structure that uses an nsTArray to serve as + * a binary heap. The default comparator causes this to act like a min-heap. + * Only the LessThan method of the comparator is used. + */ +template <class T, class Compare = nsDefaultComparator<T, T>> +class nsTPriorityQueue { + public: + typedef typename nsTArray<T>::size_type size_type; + + /** + * Default constructor also creates a comparator object using the default + * constructor for type Compare. + */ + nsTPriorityQueue() : mCompare(Compare()) {} + + /** + * Constructor to allow a specific instance of a comparator object to be + * used. + */ + explicit nsTPriorityQueue(const Compare& aComp) : mCompare(aComp) {} + + nsTPriorityQueue(nsTPriorityQueue&&) = default; + nsTPriorityQueue& operator=(nsTPriorityQueue&&) = default; + + /** + * @return True if the queue is empty or false otherwise. + */ + bool IsEmpty() const { return mElements.IsEmpty(); } + + /** + * @return The number of elements in the queue. + */ + size_type Length() const { return mElements.Length(); } + + /** + * @return The topmost element in the queue without changing the queue. This + * is the element 'a' such that there is no other element 'b' in the queue for + * which Compare(b, a) returns true. (Since this container does not check + * for duplicate entries there may exist 'b' for which Compare(a, b) returns + * false.) + */ + const T& Top() const { + MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue"); + return mElements[0]; + } + + /** + * Adds an element to the queue + * @param aElement The element to add + * @return true on success, false on out of memory. + */ + void Push(T&& aElement) { + mElements.AppendElement(std::move(aElement)); + + // Sift up + size_type i = mElements.Length() - 1; + while (i) { + size_type parent = (size_type)((i - 1) / 2); + if (mCompare.LessThan(mElements[parent], mElements[i])) { + break; + } + std::swap(mElements[i], mElements[parent]); + i = parent; + } + } + + /** + * Removes and returns the top-most element from the queue. + * @return The topmost element, that is, the element 'a' such that there is no + * other element 'b' in the queue for which Compare(b, a) returns true. + * @see Top() + */ + T Pop() { + MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue"); + T pop = std::move(mElements[0]); + + const size_type newLength = mElements.Length() - 1; + if (newLength == 0) { + mElements.Clear(); + return pop; + } + + // Move last to front + mElements[0] = mElements.PopLastElement(); + + // Sift down + size_type i = 0; + while (2 * i + 1 < newLength) { + size_type swap = i; + size_type l_child = 2 * i + 1; + if (mCompare.LessThan(mElements[l_child], mElements[i])) { + swap = l_child; + } + size_type r_child = l_child + 1; + if (r_child < newLength && + mCompare.LessThan(mElements[r_child], mElements[swap])) { + swap = r_child; + } + if (swap == i) { + break; + } + std::swap(mElements[i], mElements[swap]); + i = swap; + } + + return pop; + } + + /** + * Removes all elements from the queue. + */ + void Clear() { mElements.Clear(); } + + /** + * Provides readonly access to the queue elements as an array. Generally this + * should be avoided but may be needed in some situations such as when the + * elements contained in the queue need to be enumerated for cycle-collection. + * @return A pointer to the first element of the array. If the array is + * empty, then this pointer must not be dereferenced. + */ + const T* Elements() const { return mElements.Elements(); } + + nsTPriorityQueue Clone() const { + auto res = nsTPriorityQueue{mCompare}; + res.mElements = mElements.Clone(); + return res; + } + + protected: + nsTArray<T> mElements; + Compare mCompare; // Comparator object +}; + +#endif // NS_TPRIORITY_QUEUE_H_ diff --git a/xpcom/ds/nsVariant.cpp b/xpcom/ds/nsVariant.cpp new file mode 100644 index 0000000000..f8a40431c7 --- /dev/null +++ b/xpcom/ds/nsVariant.cpp @@ -0,0 +1,1876 @@ +/* -*- 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 "nsVariant.h" +#include "prprf.h" +#include "prdtoa.h" +#include <math.h> +#include "nsCycleCollectionParticipant.h" +#include "xptinfo.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsCRTGlue.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Printf.h" + +/***************************************************************************/ +// Helpers for static convert functions... + +static nsresult String2Double(const char* aString, double* aResult) { + char* next; + double value = PR_strtod(aString, &next); + if (next == aString) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + *aResult = value; + return NS_OK; +} + +static nsresult AString2Double(const nsAString& aString, double* aResult) { + char* pChars = ToNewCString(aString, mozilla::fallible); + if (!pChars) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsresult rv = String2Double(pChars, aResult); + free(pChars); + return rv; +} + +static nsresult AUTF8String2Double(const nsAUTF8String& aString, + double* aResult) { + return String2Double(PromiseFlatUTF8String(aString).get(), aResult); +} + +static nsresult ACString2Double(const nsACString& aString, double* aResult) { + return String2Double(PromiseFlatCString(aString).get(), aResult); +} + +// Fills aOutData with double, uint32_t, or int32_t. +// Returns NS_OK, an error code, or a non-NS_OK success code +nsresult nsDiscriminatedUnion::ToManageableNumber( + nsDiscriminatedUnion* aOutData) const { + nsresult rv; + + switch (mType) { + // This group results in a int32_t... + +#define CASE__NUMBER_INT32(type_, member_) \ + case nsIDataType::type_: \ + aOutData->u.mInt32Value = u.member_; \ + aOutData->mType = nsIDataType::VTYPE_INT32; \ + return NS_OK; + + CASE__NUMBER_INT32(VTYPE_INT8, mInt8Value) + CASE__NUMBER_INT32(VTYPE_INT16, mInt16Value) + CASE__NUMBER_INT32(VTYPE_INT32, mInt32Value) + CASE__NUMBER_INT32(VTYPE_UINT8, mUint8Value) + CASE__NUMBER_INT32(VTYPE_UINT16, mUint16Value) + CASE__NUMBER_INT32(VTYPE_BOOL, mBoolValue) + CASE__NUMBER_INT32(VTYPE_CHAR, mCharValue) + CASE__NUMBER_INT32(VTYPE_WCHAR, mWCharValue) + +#undef CASE__NUMBER_INT32 + + // This group results in a uint32_t... + + case nsIDataType::VTYPE_UINT32: + aOutData->u.mUint32Value = u.mUint32Value; + aOutData->mType = nsIDataType::VTYPE_UINT32; + return NS_OK; + + // This group results in a double... + + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT64: + // XXX Need boundary checking here. + // We may need to return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA + aOutData->u.mDoubleValue = double(u.mInt64Value); + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_FLOAT: + aOutData->u.mDoubleValue = u.mFloatValue; + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_DOUBLE: + aOutData->u.mDoubleValue = u.mDoubleValue; + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + rv = String2Double(u.str.mStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_ASTRING: + rv = AString2Double(*u.mAStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + rv = AUTF8String2Double(*u.mUTF8StringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + rv = ACString2Double(*u.mCStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + rv = AString2Double(nsDependentString(u.wstr.mWStringValue), + &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + + // This group fails... + + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +/***************************************************************************/ +// Array helpers... + +void nsDiscriminatedUnion::FreeArray() { + NS_ASSERTION(mType == nsIDataType::VTYPE_ARRAY, "bad FreeArray call"); + NS_ASSERTION(u.array.mArrayValue, "bad array"); + NS_ASSERTION(u.array.mArrayCount, "bad array count"); + +#define CASE__FREE_ARRAY_PTR(type_, ctype_) \ + case nsIDataType::type_: { \ + ctype_** p = (ctype_**)u.array.mArrayValue; \ + for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \ + if (*p) free((char*)*p); \ + break; \ + } + +#define CASE__FREE_ARRAY_IFACE(type_, ctype_) \ + case nsIDataType::type_: { \ + ctype_** p = (ctype_**)u.array.mArrayValue; \ + for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \ + if (*p) (*p)->Release(); \ + break; \ + } + + switch (u.array.mArrayType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + break; + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + CASE__FREE_ARRAY_PTR(VTYPE_ID, nsID) + CASE__FREE_ARRAY_PTR(VTYPE_CHAR_STR, char) + CASE__FREE_ARRAY_PTR(VTYPE_WCHAR_STR, char16_t) + CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE, nsISupports) + CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE_IS, nsISupports) + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + break; + } + + // Free the array memory. + free((char*)u.array.mArrayValue); + +#undef CASE__FREE_ARRAY_PTR +#undef CASE__FREE_ARRAY_IFACE +} + +static nsresult CloneArray(uint16_t aInType, const nsIID* aInIID, + uint32_t aInCount, void* aInValue, + uint16_t* aOutType, nsIID* aOutIID, + uint32_t* aOutCount, void** aOutValue) { + NS_ASSERTION(aInCount, "bad param"); + NS_ASSERTION(aInValue, "bad param"); + NS_ASSERTION(aOutType, "bad param"); + NS_ASSERTION(aOutCount, "bad param"); + NS_ASSERTION(aOutValue, "bad param"); + + uint32_t i; + + // First we figure out the size of the elements for the new u.array. + + size_t elementSize; + size_t allocSize; + + switch (aInType) { + case nsIDataType::VTYPE_INT8: + elementSize = sizeof(int8_t); + break; + case nsIDataType::VTYPE_INT16: + elementSize = sizeof(int16_t); + break; + case nsIDataType::VTYPE_INT32: + elementSize = sizeof(int32_t); + break; + case nsIDataType::VTYPE_INT64: + elementSize = sizeof(int64_t); + break; + case nsIDataType::VTYPE_UINT8: + elementSize = sizeof(uint8_t); + break; + case nsIDataType::VTYPE_UINT16: + elementSize = sizeof(uint16_t); + break; + case nsIDataType::VTYPE_UINT32: + elementSize = sizeof(uint32_t); + break; + case nsIDataType::VTYPE_UINT64: + elementSize = sizeof(uint64_t); + break; + case nsIDataType::VTYPE_FLOAT: + elementSize = sizeof(float); + break; + case nsIDataType::VTYPE_DOUBLE: + elementSize = sizeof(double); + break; + case nsIDataType::VTYPE_BOOL: + elementSize = sizeof(bool); + break; + case nsIDataType::VTYPE_CHAR: + elementSize = sizeof(char); + break; + case nsIDataType::VTYPE_WCHAR: + elementSize = sizeof(char16_t); + break; + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + elementSize = sizeof(void*); + break; + + // The rest are illegal. + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + // Alloc the u.array. + + allocSize = aInCount * elementSize; + *aOutValue = moz_xmalloc(allocSize); + + // Clone the elements. + + switch (aInType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + memcpy(*aOutValue, aInValue, allocSize); + break; + + case nsIDataType::VTYPE_INTERFACE_IS: + if (aOutIID) { + *aOutIID = *aInIID; + } + [[fallthrough]]; + + case nsIDataType::VTYPE_INTERFACE: { + memcpy(*aOutValue, aInValue, allocSize); + + nsISupports** p = (nsISupports**)*aOutValue; + for (i = aInCount; i > 0; ++p, --i) + if (*p) { + (*p)->AddRef(); + } + break; + } + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + case nsIDataType::VTYPE_ID: { + nsID** inp = (nsID**)aInValue; + nsID** outp = (nsID**)*aOutValue; + for (i = aInCount; i > 0; --i) { + nsID* idp = *(inp++); + if (idp) { + *(outp++) = idp->Clone(); + } else { + *(outp++) = nullptr; + } + } + break; + } + + case nsIDataType::VTYPE_CHAR_STR: { + char** inp = (char**)aInValue; + char** outp = (char**)*aOutValue; + for (i = aInCount; i > 0; i--) { + char* str = *(inp++); + if (str) { + *(outp++) = moz_xstrdup(str); + } else { + *(outp++) = nullptr; + } + } + break; + } + + case nsIDataType::VTYPE_WCHAR_STR: { + char16_t** inp = (char16_t**)aInValue; + char16_t** outp = (char16_t**)*aOutValue; + for (i = aInCount; i > 0; i--) { + char16_t* str = *(inp++); + if (str) { + *(outp++) = NS_xstrdup(str); + } else { + *(outp++) = nullptr; + } + } + break; + } + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + default: + NS_ERROR("bad type in array!"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + *aOutType = aInType; + *aOutCount = aInCount; + return NS_OK; +} + +/***************************************************************************/ + +#define TRIVIAL_DATA_CONVERTER(type_, member_, retval_) \ + if (mType == nsIDataType::type_) { \ + *retval_ = u.member_; \ + return NS_OK; \ + } + +#define NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \ + nsresult nsDiscriminatedUnion::ConvertTo##name_(Ctype_* aResult) const { \ + TRIVIAL_DATA_CONVERTER(type_, m##name_##Value, aResult) \ + nsDiscriminatedUnion tempData; \ + nsresult rv = ToManageableNumber(&tempData); \ + /* */ \ + /* NOTE: rv may indicate a success code that we want to preserve */ \ + /* For the final return. So all the return cases below should return */ \ + /* this rv when indicating success. */ \ + /* */ \ + if (NS_FAILED(rv)) return rv; \ + switch (tempData.mType) { +#define CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_INT32: \ + *aResult = (Ctype_)tempData.u.mInt32Value; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_INT32: { \ + int32_t value = tempData.u.mInt32Value; \ + if (value < (min_) || value > (max_)) { \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + } \ + *aResult = (Ctype_)value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_UINT32: \ + *aResult = (Ctype_)tempData.u.mUint32Value; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \ + case nsIDataType::VTYPE_UINT32: { \ + uint32_t value = tempData.u.mUint32Value; \ + if (value > (max_)) return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + *aResult = (Ctype_)value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_DOUBLE: \ + *aResult = (Ctype_)tempData.u.mDoubleValue; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_DOUBLE: { \ + double value = tempData.u.mDoubleValue; \ + if (std::isnan(value) || value < (min_) || value > (max_)) { \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + } \ + *aResult = (Ctype_)value; \ + return (0.0 == fmod(value, 1.0)) ? rv \ + : NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA; \ + } + +#define CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \ + CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \ + CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \ + CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) + +#define NUMERIC_CONVERSION_METHOD_END \ + default: \ + NS_ERROR("bad type returned from ToManageableNumber"); \ + return NS_ERROR_CANNOT_CONVERT_DATA; \ + } \ + } + +#define NUMERIC_CONVERSION_METHOD_NORMAL(type_, Ctype_, name_, min_, max_) \ + NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \ + CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \ + NUMERIC_CONVERSION_METHOD_END + +/***************************************************************************/ +// These expand into full public methods... + +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT8, uint8_t, Int8, (-127 - 1), 127) +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT16, int16_t, Int16, (-32767 - 1), + 32767) + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_INT32, int32_t, Int32) +CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(int32_t) +CASE__NUMERIC_CONVERSION_UINT32_MAX(int32_t, 2147483647) +CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(int32_t, (-2147483647 - 1), + 2147483647) +NUMERIC_CONVERSION_METHOD_END + +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT8, uint8_t, Uint8, 0, 255) +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT16, uint16_t, Uint16, 0, 65535) + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_UINT32, uint32_t, Uint32) +CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(uint32_t, 0, 2147483647) +CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(uint32_t) +CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(uint32_t, 0, 4294967295U) +NUMERIC_CONVERSION_METHOD_END + +// XXX toFloat conversions need to be fixed! +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_FLOAT, float, Float) +CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(float) +CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(float) +CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(float) +NUMERIC_CONVERSION_METHOD_END + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_DOUBLE, double, Double) +CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(double) +CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(double) +CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(double) +NUMERIC_CONVERSION_METHOD_END + +// XXX toChar conversions need to be fixed! +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_CHAR, char, Char, CHAR_MIN, CHAR_MAX) + +// XXX toWChar conversions need to be fixed! +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_WCHAR, char16_t, WChar, 0, + std::numeric_limits<char16_t>::max()) + +#undef NUMERIC_CONVERSION_METHOD_BEGIN +#undef CASE__NUMERIC_CONVERSION_INT32_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_INT32_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_UINT32_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT +#undef CASES__NUMERIC_CONVERSION_NORMAL +#undef NUMERIC_CONVERSION_METHOD_END +#undef NUMERIC_CONVERSION_METHOD_NORMAL + +/***************************************************************************/ + +// Just leverage a numeric converter for bool (but restrict the values). +// XXX Is this really what we want to do? + +nsresult nsDiscriminatedUnion::ConvertToBool(bool* aResult) const { + TRIVIAL_DATA_CONVERTER(VTYPE_BOOL, mBoolValue, aResult) + + double val; + nsresult rv = ConvertToDouble(&val); + if (NS_FAILED(rv)) { + return rv; + } + // NaN is falsy in JS, so we might as well make it false here. + if (std::isnan(val)) { + *aResult = false; + } else { + *aResult = 0.0 != val; + } + return rv; +} + +/***************************************************************************/ + +nsresult nsDiscriminatedUnion::ConvertToInt64(int64_t* aResult) const { + TRIVIAL_DATA_CONVERTER(VTYPE_INT64, mInt64Value, aResult) + TRIVIAL_DATA_CONVERTER(VTYPE_UINT64, mUint64Value, aResult) + + nsDiscriminatedUnion tempData; + nsresult rv = ToManageableNumber(&tempData); + if (NS_FAILED(rv)) { + return rv; + } + switch (tempData.mType) { + case nsIDataType::VTYPE_INT32: + *aResult = tempData.u.mInt32Value; + return rv; + case nsIDataType::VTYPE_UINT32: + *aResult = tempData.u.mUint32Value; + return rv; + case nsIDataType::VTYPE_DOUBLE: { + double value = tempData.u.mDoubleValue; + if (std::isnan(value)) { + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + // XXX should check for data loss here! + *aResult = value; + return rv; + } + default: + NS_ERROR("bad type returned from ToManageableNumber"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +nsresult nsDiscriminatedUnion::ConvertToUint64(uint64_t* aResult) const { + return ConvertToInt64((int64_t*)aResult); +} + +/***************************************************************************/ + +bool nsDiscriminatedUnion::String2ID(nsID* aPid) const { + nsAutoString tempString; + nsAString* pString; + + switch (mType) { + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + return aPid->Parse(u.str.mStringValue); + case nsIDataType::VTYPE_CSTRING: + return aPid->Parse(PromiseFlatCString(*u.mCStringValue).get()); + case nsIDataType::VTYPE_UTF8STRING: + return aPid->Parse(PromiseFlatUTF8String(*u.mUTF8StringValue).get()); + case nsIDataType::VTYPE_ASTRING: + pString = u.mAStringValue; + break; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + tempString.Assign(u.wstr.mWStringValue); + pString = &tempString; + break; + default: + NS_ERROR("bad type in call to String2ID"); + return false; + } + + char* pChars = ToNewCString(*pString, mozilla::fallible); + if (!pChars) { + return false; + } + bool result = aPid->Parse(pChars); + free(pChars); + return result; +} + +nsresult nsDiscriminatedUnion::ConvertToID(nsID* aResult) const { + nsID id; + + switch (mType) { + case nsIDataType::VTYPE_ID: + *aResult = u.mIDValue; + return NS_OK; + case nsIDataType::VTYPE_INTERFACE: + *aResult = NS_GET_IID(nsISupports); + return NS_OK; + case nsIDataType::VTYPE_INTERFACE_IS: + *aResult = u.iface.mInterfaceID; + return NS_OK; + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + if (!String2ID(&id)) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + *aResult = id; + return NS_OK; + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +/***************************************************************************/ + +nsresult nsDiscriminatedUnion::ToString(nsACString& aOutString) const { + mozilla::SmprintfPointer pptr; + + switch (mType) { + // all the stuff we don't handle... + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_WCHAR: + NS_ERROR("ToString being called for a string type - screwy logic!"); + [[fallthrough]]; + + // XXX We might want stringified versions of these... ??? + + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_EMPTY: + aOutString.SetIsVoid(true); + return NS_OK; + + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + + // nsID has its own text formatter. + + case nsIDataType::VTYPE_ID: { + aOutString.Assign(u.mIDValue.ToString().get()); + return NS_OK; + } + + // Can't use Smprintf for floats, since it's locale-dependent +#define CASE__APPENDFLOAT_NUMBER(type_, member_) \ + case nsIDataType::type_: { \ + nsAutoCString str; \ + str.AppendFloat(u.member_); \ + aOutString.Assign(str); \ + return NS_OK; \ + } + + CASE__APPENDFLOAT_NUMBER(VTYPE_FLOAT, mFloatValue) + CASE__APPENDFLOAT_NUMBER(VTYPE_DOUBLE, mDoubleValue) + +#undef CASE__APPENDFLOAT_NUMBER + + // the rest can be Smprintf'd and use common code. + +#define CASE__SMPRINTF_NUMBER(type_, format_, cast_, member_) \ + case nsIDataType::type_: \ + static_assert(sizeof(cast_) >= sizeof(u.member_), \ + "size of type should be at least as big as member"); \ + pptr = mozilla::Smprintf(format_, (cast_)u.member_); \ + break; + + CASE__SMPRINTF_NUMBER(VTYPE_INT8, "%d", int, mInt8Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT16, "%d", int, mInt16Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT32, "%d", int, mInt32Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT64, "%" PRId64, int64_t, mInt64Value) + + CASE__SMPRINTF_NUMBER(VTYPE_UINT8, "%u", unsigned, mUint8Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT16, "%u", unsigned, mUint16Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT32, "%u", unsigned, mUint32Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT64, "%" PRIu64, int64_t, mUint64Value) + + // XXX Would we rather print "true" / "false" ? + CASE__SMPRINTF_NUMBER(VTYPE_BOOL, "%d", int, mBoolValue) + + CASE__SMPRINTF_NUMBER(VTYPE_CHAR, "%c", char, mCharValue) + +#undef CASE__SMPRINTF_NUMBER + } + + if (!pptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + aOutString.Assign(pptr.get()); + return NS_OK; +} + +nsresult nsDiscriminatedUnion::ConvertToAString(nsAString& aResult) const { + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + aResult.Assign(*u.mAStringValue); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + CopyASCIItoUTF16(*u.mCStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + CopyUTF8toUTF16(*u.mUTF8StringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + CopyASCIItoUTF16(mozilla::MakeStringSpan(u.str.mStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + aResult.Assign(u.wstr.mWStringValue); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + CopyASCIItoUTF16( + nsDependentCString(u.str.mStringValue, u.str.mStringLength), aResult); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + aResult.Assign(u.wstr.mWStringValue, u.wstr.mWStringLength); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: + aResult.Assign(u.mWCharValue); + return NS_OK; + default: { + nsAutoCString tempCString; + nsresult rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + CopyASCIItoUTF16(tempCString, aResult); + return NS_OK; + } + } +} + +nsresult nsDiscriminatedUnion::ConvertToACString(nsACString& aResult) const { + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + LossyCopyUTF16toASCII(*u.mAStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + aResult.Assign(*u.mCStringValue); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + // XXX This is an extra copy that should be avoided + // once Jag lands support for UTF8String and associated + // conversion methods. + LossyCopyUTF16toASCII(NS_ConvertUTF8toUTF16(*u.mUTF8StringValue), + aResult); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + aResult.Assign(*u.str.mStringValue); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + LossyCopyUTF16toASCII(nsDependentString(u.wstr.mWStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + aResult.Assign(u.str.mStringValue, u.str.mStringLength); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + LossyCopyUTF16toASCII( + nsDependentString(u.wstr.mWStringValue, u.wstr.mWStringLength), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: { + const char16_t* str = &u.mWCharValue; + LossyCopyUTF16toASCII(Substring(str, 1), aResult); + return NS_OK; + } + default: + return ToString(aResult); + } +} + +nsresult nsDiscriminatedUnion::ConvertToAUTF8String( + nsAUTF8String& aResult) const { + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + CopyUTF16toUTF8(*u.mAStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + // XXX Extra copy, can be removed if we're sure CSTRING can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(*u.mCStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + aResult.Assign(*u.mUTF8StringValue); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + // XXX Extra copy, can be removed if we're sure CHAR_STR can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(u.str.mStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + CopyUTF16toUTF8(mozilla::MakeStringSpan(u.wstr.mWStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + // XXX Extra copy, can be removed if we're sure CHAR_STR can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(nsDependentCString( + u.str.mStringValue, u.str.mStringLength)), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + CopyUTF16toUTF8( + nsDependentString(u.wstr.mWStringValue, u.wstr.mWStringLength), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: { + const char16_t* str = &u.mWCharValue; + CopyUTF16toUTF8(Substring(str, 1), aResult); + return NS_OK; + } + default: { + nsAutoCString tempCString; + nsresult rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + // XXX Extra copy, can be removed if we're sure tempCString can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(tempCString), aResult); + return NS_OK; + } + } +} + +nsresult nsDiscriminatedUnion::ConvertToString(char** aResult) const { + uint32_t ignored; + return ConvertToStringWithSize(&ignored, aResult); +} + +nsresult nsDiscriminatedUnion::ConvertToWString(char16_t** aResult) const { + uint32_t ignored; + return ConvertToWStringWithSize(&ignored, aResult); +} + +nsresult nsDiscriminatedUnion::ConvertToStringWithSize(uint32_t* aSize, + char** aStr) const { + nsAutoString tempString; + nsAutoCString tempCString; + nsresult rv; + + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + *aSize = u.mAStringValue->Length(); + *aStr = ToNewCString(*u.mAStringValue); + break; + case nsIDataType::VTYPE_CSTRING: + *aSize = u.mCStringValue->Length(); + *aStr = ToNewCString(*u.mCStringValue); + break; + case nsIDataType::VTYPE_UTF8STRING: { + // XXX This is doing 1 extra copy. Need to fix this + // when Jag lands UTF8String + // we want: + // *aSize = *mUTF8StringValue->Length(); + // *aStr = ToNewCString(*mUTF8StringValue); + // But this will have to do for now. + const NS_ConvertUTF8toUTF16 tempString16(*u.mUTF8StringValue); + *aSize = tempString16.Length(); + *aStr = ToNewCString(tempString16); + break; + } + case nsIDataType::VTYPE_CHAR_STR: { + nsDependentCString cString(u.str.mStringValue); + *aSize = cString.Length(); + *aStr = ToNewCString(cString); + break; + } + case nsIDataType::VTYPE_WCHAR_STR: { + nsDependentString string(u.wstr.mWStringValue); + *aSize = string.Length(); + *aStr = ToNewCString(string); + break; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: { + nsDependentCString cString(u.str.mStringValue, u.str.mStringLength); + *aSize = cString.Length(); + *aStr = ToNewCString(cString); + break; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + nsDependentString string(u.wstr.mWStringValue, u.wstr.mWStringLength); + *aSize = string.Length(); + *aStr = ToNewCString(string); + break; + } + case nsIDataType::VTYPE_WCHAR: + tempString.Assign(u.mWCharValue); + *aSize = tempString.Length(); + *aStr = ToNewCString(tempString); + break; + default: + rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + *aSize = tempCString.Length(); + *aStr = ToNewCString(tempCString); + break; + } + + return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} +nsresult nsDiscriminatedUnion::ConvertToWStringWithSize(uint32_t* aSize, + char16_t** aStr) const { + nsAutoString tempString; + nsAutoCString tempCString; + nsresult rv; + + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + *aSize = u.mAStringValue->Length(); + *aStr = ToNewUnicode(*u.mAStringValue); + break; + case nsIDataType::VTYPE_CSTRING: + *aSize = u.mCStringValue->Length(); + *aStr = ToNewUnicode(*u.mCStringValue); + break; + case nsIDataType::VTYPE_UTF8STRING: { + *aStr = UTF8ToNewUnicode(*u.mUTF8StringValue, aSize); + break; + } + case nsIDataType::VTYPE_CHAR_STR: { + nsDependentCString cString(u.str.mStringValue); + *aSize = cString.Length(); + *aStr = ToNewUnicode(cString); + break; + } + case nsIDataType::VTYPE_WCHAR_STR: { + nsDependentString string(u.wstr.mWStringValue); + *aSize = string.Length(); + *aStr = ToNewUnicode(string); + break; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: { + nsDependentCString cString(u.str.mStringValue, u.str.mStringLength); + *aSize = cString.Length(); + *aStr = ToNewUnicode(cString); + break; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + nsDependentString string(u.wstr.mWStringValue, u.wstr.mWStringLength); + *aSize = string.Length(); + *aStr = ToNewUnicode(string); + break; + } + case nsIDataType::VTYPE_WCHAR: + tempString.Assign(u.mWCharValue); + *aSize = tempString.Length(); + *aStr = ToNewUnicode(tempString); + break; + default: + rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + *aSize = tempCString.Length(); + *aStr = ToNewUnicode(tempCString); + break; + } + + return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult nsDiscriminatedUnion::ConvertToISupports(nsISupports** aResult) const { + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + if (u.iface.mInterfaceValue) { + return u.iface.mInterfaceValue->QueryInterface(NS_GET_IID(nsISupports), + (void**)aResult); + } else { + *aResult = nullptr; + return NS_OK; + } + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +nsresult nsDiscriminatedUnion::ConvertToInterface(nsIID** aIID, + void** aInterface) const { + const nsIID* piid; + + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + piid = &NS_GET_IID(nsISupports); + break; + case nsIDataType::VTYPE_INTERFACE_IS: + piid = &u.iface.mInterfaceID; + break; + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + *aIID = piid->Clone(); + + if (u.iface.mInterfaceValue) { + return u.iface.mInterfaceValue->QueryInterface(*piid, aInterface); + } + + *aInterface = nullptr; + return NS_OK; +} + +nsresult nsDiscriminatedUnion::ConvertToArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, + void** aPtr) const { + // XXX perhaps we'd like to add support for converting each of the various + // types into an array containing one element of that type. We can leverage + // CloneArray to do this if we want to support this. + + if (mType == nsIDataType::VTYPE_ARRAY) { + return CloneArray(u.array.mArrayType, &u.array.mArrayInterfaceID, + u.array.mArrayCount, u.array.mArrayValue, aType, aIID, + aCount, aPtr); + } + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +/***************************************************************************/ +// static setter functions... + +#define DATA_SETTER_PROLOGUE Cleanup() + +#define DATA_SETTER_EPILOGUE(type_) mType = nsIDataType::type_; + +#define DATA_SETTER(type_, member_, value_) \ + DATA_SETTER_PROLOGUE; \ + u.member_ = value_; \ + DATA_SETTER_EPILOGUE(type_) + +#define DATA_SETTER_WITH_CAST(type_, member_, cast_, value_) \ + DATA_SETTER_PROLOGUE; \ + u.member_ = cast_ value_; \ + DATA_SETTER_EPILOGUE(type_) + +/********************************************/ + +#define CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) { +#define CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \ + rv = aValue->GetAs##name_(&(u.member_)); + +#define CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \ + rv = aValue->GetAs##name_(cast_ & (u.member_)); + +#define CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) \ + if (NS_SUCCEEDED(rv)) { \ + mType = nsIDataType::type_; \ + } \ + break; \ + } + +#define CASE__SET_FROM_VARIANT_TYPE(type_, member_, name_) \ + case nsIDataType::type_: \ + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \ + CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \ + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) + +#define CASE__SET_FROM_VARIANT_VTYPE_CAST(type_, cast_, member_, name_) \ + case nsIDataType::type_: \ + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \ + CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \ + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) + +nsresult nsDiscriminatedUnion::SetFromVariant(nsIVariant* aValue) { + nsresult rv = NS_OK; + + Cleanup(); + + uint16_t type = aValue->GetDataType(); + + switch (type) { + CASE__SET_FROM_VARIANT_VTYPE_CAST(VTYPE_INT8, (uint8_t*), mInt8Value, Int8) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT16, mInt16Value, Int16) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT32, mInt32Value, Int32) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT8, mUint8Value, Uint8) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT16, mUint16Value, Uint16) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT32, mUint32Value, Uint32) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_FLOAT, mFloatValue, Float) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_DOUBLE, mDoubleValue, Double) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_BOOL, mBoolValue, Bool) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_CHAR, mCharValue, Char) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_WCHAR, mWCharValue, WChar) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_ID, mIDValue, ID) + + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ASTRING); + u.mAStringValue = new nsString(); + + rv = aValue->GetAsAString(*u.mAStringValue); + if (NS_FAILED(rv)) { + delete u.mAStringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ASTRING) + + case nsIDataType::VTYPE_CSTRING: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_CSTRING); + u.mCStringValue = new nsCString(); + + rv = aValue->GetAsACString(*u.mCStringValue); + if (NS_FAILED(rv)) { + delete u.mCStringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_CSTRING) + + case nsIDataType::VTYPE_UTF8STRING: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_UTF8STRING); + u.mUTF8StringValue = new nsUTF8String(); + + rv = aValue->GetAsAUTF8String(*u.mUTF8StringValue); + if (NS_FAILED(rv)) { + delete u.mUTF8StringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_UTF8STRING) + + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_STRING_SIZE_IS); + rv = aValue->GetAsStringWithSize(&u.str.mStringLength, + &u.str.mStringValue); + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_STRING_SIZE_IS) + + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_INTERFACE_IS); + // XXX This iid handling is ugly! + nsIID* iid; + rv = aValue->GetAsInterface(&iid, (void**)&u.iface.mInterfaceValue); + if (NS_SUCCEEDED(rv)) { + u.iface.mInterfaceID = *iid; + free((char*)iid); + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_INTERFACE_IS) + + case nsIDataType::VTYPE_ARRAY: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ARRAY); + rv = aValue->GetAsArray(&u.array.mArrayType, &u.array.mArrayInterfaceID, + &u.array.mArrayCount, &u.array.mArrayValue); + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ARRAY) + + case nsIDataType::VTYPE_VOID: + SetToVoid(); + rv = NS_OK; + break; + case nsIDataType::VTYPE_EMPTY_ARRAY: + SetToEmptyArray(); + rv = NS_OK; + break; + case nsIDataType::VTYPE_EMPTY: + SetToEmpty(); + rv = NS_OK; + break; + default: + NS_ERROR("bad type in variant!"); + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +void nsDiscriminatedUnion::SetFromInt8(uint8_t aValue) { + DATA_SETTER_WITH_CAST(VTYPE_INT8, mInt8Value, (uint8_t), aValue); +} +void nsDiscriminatedUnion::SetFromInt16(int16_t aValue) { + DATA_SETTER(VTYPE_INT16, mInt16Value, aValue); +} +void nsDiscriminatedUnion::SetFromInt32(int32_t aValue) { + DATA_SETTER(VTYPE_INT32, mInt32Value, aValue); +} +void nsDiscriminatedUnion::SetFromInt64(int64_t aValue) { + DATA_SETTER(VTYPE_INT64, mInt64Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint8(uint8_t aValue) { + DATA_SETTER(VTYPE_UINT8, mUint8Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint16(uint16_t aValue) { + DATA_SETTER(VTYPE_UINT16, mUint16Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint32(uint32_t aValue) { + DATA_SETTER(VTYPE_UINT32, mUint32Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint64(uint64_t aValue) { + DATA_SETTER(VTYPE_UINT64, mUint64Value, aValue); +} +void nsDiscriminatedUnion::SetFromFloat(float aValue) { + DATA_SETTER(VTYPE_FLOAT, mFloatValue, aValue); +} +void nsDiscriminatedUnion::SetFromDouble(double aValue) { + DATA_SETTER(VTYPE_DOUBLE, mDoubleValue, aValue); +} +void nsDiscriminatedUnion::SetFromBool(bool aValue) { + DATA_SETTER(VTYPE_BOOL, mBoolValue, aValue); +} +void nsDiscriminatedUnion::SetFromChar(char aValue) { + DATA_SETTER(VTYPE_CHAR, mCharValue, aValue); +} +void nsDiscriminatedUnion::SetFromWChar(char16_t aValue) { + DATA_SETTER(VTYPE_WCHAR, mWCharValue, aValue); +} +void nsDiscriminatedUnion::SetFromID(const nsID& aValue) { + DATA_SETTER(VTYPE_ID, mIDValue, aValue); +} +void nsDiscriminatedUnion::SetFromAString(const nsAString& aValue) { + DATA_SETTER_PROLOGUE; + u.mAStringValue = new nsString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_ASTRING); +} + +void nsDiscriminatedUnion::SetFromACString(const nsACString& aValue) { + DATA_SETTER_PROLOGUE; + u.mCStringValue = new nsCString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_CSTRING); +} + +void nsDiscriminatedUnion::SetFromAUTF8String(const nsAUTF8String& aValue) { + DATA_SETTER_PROLOGUE; + u.mUTF8StringValue = new nsUTF8String(aValue); + DATA_SETTER_EPILOGUE(VTYPE_UTF8STRING); +} + +nsresult nsDiscriminatedUnion::SetFromString(const char* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + return SetFromStringWithSize(strlen(aValue), aValue); +} +nsresult nsDiscriminatedUnion::SetFromWString(const char16_t* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + return SetFromWStringWithSize(NS_strlen(aValue), aValue); +} +void nsDiscriminatedUnion::SetFromISupports(nsISupports* aValue) { + return SetFromInterface(NS_GET_IID(nsISupports), aValue); +} +void nsDiscriminatedUnion::SetFromInterface(const nsIID& aIID, + nsISupports* aValue) { + DATA_SETTER_PROLOGUE; + NS_IF_ADDREF(aValue); + u.iface.mInterfaceValue = aValue; + u.iface.mInterfaceID = aIID; + DATA_SETTER_EPILOGUE(VTYPE_INTERFACE_IS); +} +nsresult nsDiscriminatedUnion::SetFromArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue || !aCount) { + return NS_ERROR_NULL_POINTER; + } + + nsresult rv = CloneArray(aType, aIID, aCount, aValue, &u.array.mArrayType, + &u.array.mArrayInterfaceID, &u.array.mArrayCount, + &u.array.mArrayValue); + if (NS_FAILED(rv)) { + return rv; + } + DATA_SETTER_EPILOGUE(VTYPE_ARRAY); + return NS_OK; +} +nsresult nsDiscriminatedUnion::SetFromStringWithSize(uint32_t aSize, + const char* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + u.str.mStringValue = (char*)moz_xmemdup(aValue, (aSize + 1) * sizeof(char)); + u.str.mStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_STRING_SIZE_IS); + return NS_OK; +} +nsresult nsDiscriminatedUnion::SetFromWStringWithSize(uint32_t aSize, + const char16_t* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + u.wstr.mWStringValue = + (char16_t*)moz_xmemdup(aValue, (aSize + 1) * sizeof(char16_t)); + u.wstr.mWStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS); + return NS_OK; +} +void nsDiscriminatedUnion::AllocateWStringWithSize(uint32_t aSize) { + DATA_SETTER_PROLOGUE; + u.wstr.mWStringValue = (char16_t*)moz_xmalloc((aSize + 1) * sizeof(char16_t)); + u.wstr.mWStringValue[aSize] = '\0'; + u.wstr.mWStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS); +} +void nsDiscriminatedUnion::SetToVoid() { + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_VOID); +} +void nsDiscriminatedUnion::SetToEmpty() { + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_EMPTY); +} +void nsDiscriminatedUnion::SetToEmptyArray() { + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_EMPTY_ARRAY); +} + +/***************************************************************************/ + +void nsDiscriminatedUnion::Cleanup() { + switch (mType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ID: + break; + case nsIDataType::VTYPE_ASTRING: + delete u.mAStringValue; + break; + case nsIDataType::VTYPE_CSTRING: + delete u.mCStringValue; + break; + case nsIDataType::VTYPE_UTF8STRING: + delete u.mUTF8StringValue; + break; + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + free((char*)u.str.mStringValue); + break; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + free((char*)u.wstr.mWStringValue); + break; + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + NS_IF_RELEASE(u.iface.mInterfaceValue); + break; + case nsIDataType::VTYPE_ARRAY: + FreeArray(); + break; + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + break; + default: + NS_ERROR("bad type in variant!"); + break; + } + + mType = nsIDataType::VTYPE_EMPTY; +} + +void nsDiscriminatedUnion::Traverse( + nsCycleCollectionTraversalCallback& aCb) const { + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData"); + aCb.NoteXPCOMChild(u.iface.mInterfaceValue); + break; + case nsIDataType::VTYPE_ARRAY: + switch (u.array.mArrayType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: { + nsISupports** p = (nsISupports**)u.array.mArrayValue; + for (uint32_t i = u.array.mArrayCount; i > 0; ++p, --i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData[i]"); + aCb.NoteXPCOMChild(*p); + } + break; + } + default: + break; + } + break; + default: + break; + } +} + +/***************************************************************************/ +/***************************************************************************/ +// members... + +nsVariantBase::nsVariantBase() : mWritable(true) {} + +// For all the data getters we just forward to the static (and sharable) +// 'ConvertTo' functions. + +uint16_t nsVariantBase::GetDataType() { return mData.GetType(); } + +NS_IMETHODIMP +nsVariantBase::GetAsInt8(uint8_t* aResult) { + return mData.ConvertToInt8(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt16(int16_t* aResult) { + return mData.ConvertToInt16(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt32(int32_t* aResult) { + return mData.ConvertToInt32(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt64(int64_t* aResult) { + return mData.ConvertToInt64(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint8(uint8_t* aResult) { + return mData.ConvertToUint8(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint16(uint16_t* aResult) { + return mData.ConvertToUint16(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint32(uint32_t* aResult) { + return mData.ConvertToUint32(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint64(uint64_t* aResult) { + return mData.ConvertToUint64(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsFloat(float* aResult) { + return mData.ConvertToFloat(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsDouble(double* aResult) { + return mData.ConvertToDouble(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsBool(bool* aResult) { return mData.ConvertToBool(aResult); } + +NS_IMETHODIMP +nsVariantBase::GetAsChar(char* aResult) { return mData.ConvertToChar(aResult); } + +NS_IMETHODIMP +nsVariantBase::GetAsWChar(char16_t* aResult) { + return mData.ConvertToWChar(aResult); +} + +NS_IMETHODIMP_(nsresult) +nsVariantBase::GetAsID(nsID* aResult) { return mData.ConvertToID(aResult); } + +NS_IMETHODIMP +nsVariantBase::GetAsAString(nsAString& aResult) { + return mData.ConvertToAString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsACString(nsACString& aResult) { + return mData.ConvertToACString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsAUTF8String(nsAUTF8String& aResult) { + return mData.ConvertToAUTF8String(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsString(char** aResult) { + return mData.ConvertToString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsWString(char16_t** aResult) { + return mData.ConvertToWString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsISupports(nsISupports** aResult) { + return mData.ConvertToISupports(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsJSVal(JS::MutableHandleValue) { + // Can only get the jsval from an XPCVariant. + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +NS_IMETHODIMP +nsVariantBase::GetAsInterface(nsIID** aIID, void** aInterface) { + return mData.ConvertToInterface(aIID, aInterface); +} + +NS_IMETHODIMP_(nsresult) +nsVariantBase::GetAsArray(uint16_t* aType, nsIID* aIID, uint32_t* aCount, + void** aPtr) { + return mData.ConvertToArray(aType, aIID, aCount, aPtr); +} + +NS_IMETHODIMP +nsVariantBase::GetAsStringWithSize(uint32_t* aSize, char** aStr) { + return mData.ConvertToStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::GetAsWStringWithSize(uint32_t* aSize, char16_t** aStr) { + return mData.ConvertToWStringWithSize(aSize, aStr); +} + +/***************************************************************************/ + +NS_IMETHODIMP +nsVariantBase::GetWritable(bool* aWritable) { + *aWritable = mWritable; + return NS_OK; +} +NS_IMETHODIMP +nsVariantBase::SetWritable(bool aWritable) { + if (!mWritable && aWritable) { + return NS_ERROR_FAILURE; + } + mWritable = aWritable; + return NS_OK; +} + +/***************************************************************************/ + +// For all the data setters we just forward to the static (and sharable) +// 'SetFrom' functions. + +NS_IMETHODIMP +nsVariantBase::SetAsInt8(uint8_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt8(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt16(int16_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt16(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt32(int32_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt32(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt64(int64_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt64(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint8(uint8_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint8(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint16(uint16_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint16(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint32(uint32_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint32(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint64(uint64_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint64(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsFloat(float aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromFloat(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsDouble(double aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromDouble(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsBool(bool aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromBool(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsChar(char aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromChar(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsWChar(char16_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromWChar(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsID(const nsID& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromID(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsAString(const nsAString& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromAString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsACString(const nsACString& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromACString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsAUTF8String(const nsAUTF8String& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromAUTF8String(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsString(const char* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromString(aValue); +} + +NS_IMETHODIMP +nsVariantBase::SetAsWString(const char16_t* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromWString(aValue); +} + +NS_IMETHODIMP +nsVariantBase::SetAsISupports(nsISupports* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromISupports(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInterface(const nsIID& aIID, void* aInterface) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInterface(aIID, (nsISupports*)aInterface); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsArray(uint16_t aType, const nsIID* aIID, uint32_t aCount, + void* aPtr) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromArray(aType, aIID, aCount, aPtr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsStringWithSize(uint32_t aSize, const char* aStr) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsWStringWithSize(uint32_t aSize, const char16_t* aStr) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromWStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsVoid() { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToVoid(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsEmpty() { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToEmpty(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsEmptyArray() { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToEmptyArray(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetFromVariant(nsIVariant* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromVariant(aValue); +} + +/* nsVariant implementation */ + +NS_IMPL_ISUPPORTS(nsVariant, nsIVariant, nsIWritableVariant) + +/* nsVariantCC implementation */ +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsVariantCC) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIVariant) + NS_INTERFACE_MAP_ENTRY(nsIWritableVariant) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsVariantCC) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsVariantCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsVariantCC) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsVariantCC) + tmp->mData.Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsVariantCC) + tmp->mData.Cleanup(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END diff --git a/xpcom/ds/nsVariant.h b/xpcom/ds/nsVariant.h new file mode 100644 index 0000000000..8de1fe4618 --- /dev/null +++ b/xpcom/ds/nsVariant.h @@ -0,0 +1,223 @@ +/* -*- 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 nsVariant_h +#define nsVariant_h + +#include "nsIVariant.h" +#include "nsStringFwd.h" +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" + +/** + * Map the nsAUTF8String, nsUTF8String classes to the nsACString and + * nsCString classes respectively for now. These defines need to be removed + * once Jag lands his nsUTF8String implementation. + */ +#define nsAUTF8String nsACString +#define nsUTF8String nsCString +#define PromiseFlatUTF8String PromiseFlatCString + +/** + * nsDiscriminatedUnion is a class that nsIVariant implementors can use + * to hold the underlying data. + */ + +class nsDiscriminatedUnion { + public: + nsDiscriminatedUnion() : mType(nsIDataType::VTYPE_EMPTY) { + u.mInt8Value = '\0'; + } + nsDiscriminatedUnion(const nsDiscriminatedUnion&) = delete; + nsDiscriminatedUnion(nsDiscriminatedUnion&&) = delete; + + ~nsDiscriminatedUnion() { Cleanup(); } + + nsDiscriminatedUnion& operator=(const nsDiscriminatedUnion&) = delete; + nsDiscriminatedUnion& operator=(nsDiscriminatedUnion&&) = delete; + + void Cleanup(); + + uint16_t GetType() const { return mType; } + + [[nodiscard]] nsresult ConvertToInt8(uint8_t* aResult) const; + [[nodiscard]] nsresult ConvertToInt16(int16_t* aResult) const; + [[nodiscard]] nsresult ConvertToInt32(int32_t* aResult) const; + [[nodiscard]] nsresult ConvertToInt64(int64_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint8(uint8_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint16(uint16_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint32(uint32_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint64(uint64_t* aResult) const; + [[nodiscard]] nsresult ConvertToFloat(float* aResult) const; + [[nodiscard]] nsresult ConvertToDouble(double* aResult) const; + [[nodiscard]] nsresult ConvertToBool(bool* aResult) const; + [[nodiscard]] nsresult ConvertToChar(char* aResult) const; + [[nodiscard]] nsresult ConvertToWChar(char16_t* aResult) const; + + [[nodiscard]] nsresult ConvertToID(nsID* aResult) const; + + [[nodiscard]] nsresult ConvertToAString(nsAString& aResult) const; + [[nodiscard]] nsresult ConvertToAUTF8String(nsAUTF8String& aResult) const; + [[nodiscard]] nsresult ConvertToACString(nsACString& aResult) const; + [[nodiscard]] nsresult ConvertToString(char** aResult) const; + [[nodiscard]] nsresult ConvertToWString(char16_t** aResult) const; + [[nodiscard]] nsresult ConvertToStringWithSize(uint32_t* aSize, + char** aStr) const; + [[nodiscard]] nsresult ConvertToWStringWithSize(uint32_t* aSize, + char16_t** aStr) const; + + [[nodiscard]] nsresult ConvertToISupports(nsISupports** aResult) const; + [[nodiscard]] nsresult ConvertToInterface(nsIID** aIID, + void** aInterface) const; + [[nodiscard]] nsresult ConvertToArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, void** aPtr) const; + + [[nodiscard]] nsresult SetFromVariant(nsIVariant* aValue); + + void SetFromInt8(uint8_t aValue); + void SetFromInt16(int16_t aValue); + void SetFromInt32(int32_t aValue); + void SetFromInt64(int64_t aValue); + void SetFromUint8(uint8_t aValue); + void SetFromUint16(uint16_t aValue); + void SetFromUint32(uint32_t aValue); + void SetFromUint64(uint64_t aValue); + void SetFromFloat(float aValue); + void SetFromDouble(double aValue); + void SetFromBool(bool aValue); + void SetFromChar(char aValue); + void SetFromWChar(char16_t aValue); + void SetFromID(const nsID& aValue); + void SetFromAString(const nsAString& aValue); + void SetFromAUTF8String(const nsAUTF8String& aValue); + void SetFromACString(const nsACString& aValue); + [[nodiscard]] nsresult SetFromString(const char* aValue); + [[nodiscard]] nsresult SetFromWString(const char16_t* aValue); + void SetFromISupports(nsISupports* aValue); + void SetFromInterface(const nsIID& aIID, nsISupports* aValue); + [[nodiscard]] nsresult SetFromArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aValue); + [[nodiscard]] nsresult SetFromStringWithSize(uint32_t aSize, + const char* aValue); + [[nodiscard]] nsresult SetFromWStringWithSize(uint32_t aSize, + const char16_t* aValue); + + // Like SetFromWStringWithSize, but leaves the string uninitialized. It does + // does write the null-terminator. + void AllocateWStringWithSize(uint32_t aSize); + + void SetToVoid(); + void SetToEmpty(); + void SetToEmptyArray(); + + void Traverse(nsCycleCollectionTraversalCallback& aCb) const; + + private: + [[nodiscard]] nsresult ToManageableNumber( + nsDiscriminatedUnion* aOutData) const; + void FreeArray(); + [[nodiscard]] bool String2ID(nsID* aPid) const; + [[nodiscard]] nsresult ToString(nsACString& aOutString) const; + + public: + union { + int8_t mInt8Value; + int16_t mInt16Value; + int32_t mInt32Value; + int64_t mInt64Value; + uint8_t mUint8Value; + uint16_t mUint16Value; + uint32_t mUint32Value; + uint64_t mUint64Value; + float mFloatValue; + double mDoubleValue; + bool mBoolValue; + char mCharValue; + char16_t mWCharValue; + nsIID mIDValue; + nsAString* mAStringValue; + nsAUTF8String* mUTF8StringValue; + nsACString* mCStringValue; + struct { + // This is an owning reference that cannot be an nsCOMPtr because + // nsDiscriminatedUnion needs to be POD. AddRef/Release are manually + // called on this. + nsISupports* MOZ_OWNING_REF mInterfaceValue; + nsIID mInterfaceID; + } iface; + struct { + nsIID mArrayInterfaceID; + void* mArrayValue; + uint32_t mArrayCount; + uint16_t mArrayType; + } array; + struct { + char* mStringValue; + uint32_t mStringLength; + } str; + struct { + char16_t* mWStringValue; + uint32_t mWStringLength; + } wstr; + } u; + uint16_t mType; +}; + +/** + * nsVariant implements the generic variant support. The xpcom module registers + * a factory (see NS_VARIANT_CONTRACTID in nsIVariant.idl) that will create + * these objects. They are created 'empty' and 'writable'. + * + * nsIVariant users won't usually need to see this class. + */ +class nsVariantBase : public nsIWritableVariant { + public: + NS_DECL_NSIVARIANT + NS_DECL_NSIWRITABLEVARIANT + + nsVariantBase(); + + protected: + ~nsVariantBase() = default; + + nsDiscriminatedUnion mData; + bool mWritable; +}; + +class nsVariant final : public nsVariantBase { + public: + NS_DECL_ISUPPORTS + + nsVariant() = default; + + private: + ~nsVariant() = default; +}; + +class nsVariantCC final : public nsVariantBase { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsVariantCC) + + nsVariantCC() = default; + + private: + ~nsVariantCC() = default; +}; + +/** + * Users of nsIVariant should be using the contractID and not this CID. + * - see NS_VARIANT_CONTRACTID in nsIVariant.idl. + */ + +#define NS_VARIANT_CID \ + { /* 0D6EA1D0-879C-11d5-90EF-0010A4E73D9A */ \ + 0xd6ea1d0, 0x879c, 0x11d5, { \ + 0x90, 0xef, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a \ + } \ + } + +#endif // nsVariant_h diff --git a/xpcom/ds/nsWhitespaceTokenizer.h b/xpcom/ds/nsWhitespaceTokenizer.h new file mode 100644 index 0000000000..77fea70850 --- /dev/null +++ b/xpcom/ds/nsWhitespaceTokenizer.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsWhitespaceTokenizer_h +#define __nsWhitespaceTokenizer_h + +#include "mozilla/RangedPtr.h" +#include "nsDependentSubstring.h" +#include "nsCRTGlue.h" + +template <typename DependentSubstringType, bool IsWhitespace(char16_t)> +class nsTWhitespaceTokenizer { + typedef typename DependentSubstringType::char_type CharType; + typedef typename DependentSubstringType::substring_type SubstringType; + + public: + explicit nsTWhitespaceTokenizer(const SubstringType& aSource) + : mIter(aSource.Data(), aSource.Length()), + mEnd(aSource.Data() + aSource.Length(), aSource.Data(), + aSource.Length()), + mWhitespaceBeforeFirstToken(false), + mWhitespaceAfterCurrentToken(false) { + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceBeforeFirstToken = true; + ++mIter; + } + } + + /** + * Checks if any more tokens are available. + */ + bool hasMoreTokens() const { return mIter < mEnd; } + + /* + * Returns true if there is whitespace prior to the first token. + */ + bool whitespaceBeforeFirstToken() const { + return mWhitespaceBeforeFirstToken; + } + + /* + * Returns true if there is any whitespace after the current token. + * This is always true unless we're reading the last token. + */ + bool whitespaceAfterCurrentToken() const { + return mWhitespaceAfterCurrentToken; + } + + /** + * Returns the next token. + */ + const DependentSubstringType nextToken() { + const mozilla::RangedPtr<const CharType> tokenStart = mIter; + while (mIter < mEnd && !IsWhitespace(*mIter)) { + ++mIter; + } + const mozilla::RangedPtr<const CharType> tokenEnd = mIter; + mWhitespaceAfterCurrentToken = false; + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + return Substring(tokenStart.get(), tokenEnd.get()); + } + + private: + mozilla::RangedPtr<const CharType> mIter; + const mozilla::RangedPtr<const CharType> mEnd; + bool mWhitespaceBeforeFirstToken; + bool mWhitespaceAfterCurrentToken; +}; + +template <bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace> +class nsWhitespaceTokenizerTemplate + : public nsTWhitespaceTokenizer<nsDependentSubstring, IsWhitespace> { + public: + explicit nsWhitespaceTokenizerTemplate(const nsAString& aSource) + : nsTWhitespaceTokenizer<nsDependentSubstring, IsWhitespace>(aSource) {} +}; + +typedef nsWhitespaceTokenizerTemplate<> nsWhitespaceTokenizer; + +template <bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace> +class nsCWhitespaceTokenizerTemplate + : public nsTWhitespaceTokenizer<nsDependentCSubstring, IsWhitespace> { + public: + explicit nsCWhitespaceTokenizerTemplate(const nsACString& aSource) + : nsTWhitespaceTokenizer<nsDependentCSubstring, IsWhitespace>(aSource) {} +}; + +typedef nsCWhitespaceTokenizerTemplate<> nsCWhitespaceTokenizer; + +#endif /* __nsWhitespaceTokenizer_h */ diff --git a/xpcom/ds/nsWindowsRegKey.cpp b/xpcom/ds/nsWindowsRegKey.cpp new file mode 100644 index 0000000000..85520a8af9 --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include "nsCOMPtr.h" +#include "nsWindowsRegKey.h" +#include "mozilla/RefPtr.h" +#include "mozilla/widget/WinRegistry.h" + +//----------------------------------------------------------------------------- + +using namespace mozilla::widget; + +class nsWindowsRegKey final : public nsIWindowsRegKey { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWSREGKEY + + nsWindowsRegKey() = default; + + private: + ~nsWindowsRegKey() = default; + + WinRegistry::Key mKey; +}; + +NS_IMPL_ISUPPORTS(nsWindowsRegKey, nsIWindowsRegKey) + +NS_IMETHODIMP +nsWindowsRegKey::Close() { + mKey = WinRegistry::Key(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::Open(uint32_t aRootKey, const nsAString& aPath, + uint32_t aMode) { + mKey = WinRegistry::Key((HKEY)(uintptr_t)(aRootKey), PromiseFlatString(aPath), + WinRegistry::KeyMode(aMode)); + return mKey ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::Create(uint32_t aRootKey, const nsAString& aPath, + uint32_t aMode) { + mKey = + WinRegistry::Key((HKEY)(uintptr_t)(aRootKey), PromiseFlatString(aPath), + WinRegistry::KeyMode(aMode), WinRegistry::Key::Create); + return mKey ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::OpenChild(const nsAString& aPath, uint32_t aMode, + nsIWindowsRegKey** aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr<nsWindowsRegKey> child = new nsWindowsRegKey(); + child->mKey = WinRegistry::Key(mKey, PromiseFlatString(aPath), + WinRegistry::KeyMode(aMode)); + if (!child->mKey) { + return NS_ERROR_FAILURE; + } + child.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::CreateChild(const nsAString& aPath, uint32_t aMode, + nsIWindowsRegKey** aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr<nsWindowsRegKey> child = new nsWindowsRegKey(); + child->mKey = + WinRegistry::Key(mKey, PromiseFlatString(aPath), + WinRegistry::KeyMode(aMode), WinRegistry::Key::Create); + if (!child->mKey) { + return NS_ERROR_FAILURE; + } + child.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetChildCount(uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = mKey.GetChildCount(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetChildName(uint32_t aIndex, nsAString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.GetChildName(aIndex, aResult) ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasChild(const nsAString& aName, bool* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + // Check for the existence of a child key by opening the key with minimal + // rights. Perhaps there is a more efficient way to do this? + *aResult = !!WinRegistry::Key(mKey, PromiseFlatString(aName), + WinRegistry::KeyMode::Read); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueCount(uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = mKey.GetValueCount(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueName(uint32_t aIndex, nsAString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.GetValueName(aIndex, aResult) ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasValue(const nsAString& aName, bool* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = mKey.GetValueType(PromiseFlatString(aName)) != + WinRegistry::ValueType::None; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::RemoveChild(const nsAString& aName) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.RemoveChildKey(PromiseFlatString(aName)) ? NS_OK + : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::RemoveValue(const nsAString& aName) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.RemoveValue(PromiseFlatString(aName)) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueType(const nsAString& aName, uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = uint32_t(mKey.GetValueType(PromiseFlatString(aName))); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadStringValue(const nsAString& aName, nsAString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + constexpr auto kFlags = WinRegistry::StringFlags::Sz | + WinRegistry::StringFlags::ExpandSz | + WinRegistry::StringFlags::LegacyMultiSz | + WinRegistry::StringFlags::ExpandEnvironment; + auto value = mKey.GetValueAsString(PromiseFlatString(aName), kFlags); + if (!value) { + return NS_ERROR_FAILURE; + } + aResult = value.extract(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadIntValue(const nsAString& aName, uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + auto value = mKey.GetValueAsDword(PromiseFlatString(aName)); + if (!value) { + return NS_ERROR_FAILURE; + } + *aResult = value.extract(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadInt64Value(const nsAString& aName, uint64_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + auto value = mKey.GetValueAsQword(PromiseFlatString(aName)); + if (!value) { + return NS_ERROR_FAILURE; + } + *aResult = value.extract(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadBinaryValue(const nsAString& aName, nsACString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + auto value = mKey.GetValueAsBinary(PromiseFlatString(aName)); + if (!value) { + return NS_ERROR_FAILURE; + } + nsTArray<uint8_t> data = value.extract(); + if (!aResult.Assign((const char*)data.Elements(), data.Length(), + mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteStringValue(const nsAString& aName, + const nsAString& aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.WriteValueAsString(PromiseFlatString(aName), + PromiseFlatString(aValue)) + ? NS_OK + : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteIntValue(const nsAString& aName, uint32_t aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.WriteValueAsDword(PromiseFlatString(aName), aValue) + ? NS_OK + : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteInt64Value(const nsAString& aName, uint64_t aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.WriteValueAsQword(PromiseFlatString(aName), aValue) + ? NS_OK + : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteBinaryValue(const nsAString& aName, + const nsACString& aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mKey.WriteValueAsBinary(PromiseFlatString(aName), aValue) + ? NS_OK + : NS_ERROR_FAILURE; +} + +//----------------------------------------------------------------------------- + +void NS_NewWindowsRegKey(nsIWindowsRegKey** aResult) { + RefPtr<nsWindowsRegKey> key = new nsWindowsRegKey(); + key.forget(aResult); +} + +//----------------------------------------------------------------------------- + +nsresult nsWindowsRegKeyConstructor(const nsIID& aIID, void** aResult) { + nsCOMPtr<nsIWindowsRegKey> key; + NS_NewWindowsRegKey(getter_AddRefs(key)); + return key->QueryInterface(aIID, aResult); +} diff --git a/xpcom/ds/nsWindowsRegKey.h b/xpcom/ds/nsWindowsRegKey.h new file mode 100644 index 0000000000..c8032540ab --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.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 nsWindowsRegKey_h__ +#define nsWindowsRegKey_h__ + +//----------------------------------------------------------------------------- + +#include "nsIWindowsRegKey.h" + +/** + * This ContractID may be used to instantiate a windows registry key object + * via the XPCOM component manager. + */ +#define NS_WINDOWSREGKEY_CONTRACTID "@mozilla.org/windows-registry-key;1" + +/** + * This function may be used to instantiate a windows registry key object prior + * to XPCOM being initialized. + */ +extern "C" void NS_NewWindowsRegKey(nsIWindowsRegKey** aResult); + +//----------------------------------------------------------------------------- + +#ifdef IMPL_LIBXUL + +// a53bc624-d577-4839-b8ec-bb5040a52ff4 +# define NS_WINDOWSREGKEY_CID \ + { \ + 0xa53bc624, 0xd577, 0x4839, { \ + 0xb8, 0xec, 0xbb, 0x50, 0x40, 0xa5, 0x2f, 0xf4 \ + } \ + } + +[[nodiscard]] extern nsresult nsWindowsRegKeyConstructor(const nsIID& aIID, + void** aResult); + +#endif // IMPL_LIBXUL + +//----------------------------------------------------------------------------- + +#endif // nsWindowsRegKey_h__ diff --git a/xpcom/ds/test/python.toml b/xpcom/ds/test/python.toml new file mode 100644 index 0000000000..41c5e33164 --- /dev/null +++ b/xpcom/ds/test/python.toml @@ -0,0 +1,4 @@ +[DEFAULT] +subsuite = "xpcom" + +["test_dafsa.py"] diff --git a/xpcom/ds/test/test_dafsa.py b/xpcom/ds/test/test_dafsa.py new file mode 100644 index 0000000000..9becdd6c06 --- /dev/null +++ b/xpcom/ds/test/test_dafsa.py @@ -0,0 +1,546 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import unittest +from io import StringIO + +import mozunit +from incremental_dafsa import Dafsa, Node + + +def _node_to_string(node: Node, prefix, buffer, cache): + if not node.is_end_node: + prefix += ( + str(ord(node.character)) if ord(node.character) < 10 else node.character + ) + else: + prefix += "$" + cached = cache.get(id(node)) + buffer.write("{}{}".format(prefix, "=" if cached else "").strip() + "\n") + + if not cached: + cache[id(node)] = node + if node: + for node in sorted(node.children.values(), key=lambda n: n.character): + _node_to_string(node, prefix, buffer, cache) + + +def _dafsa_to_string(dafsa: Dafsa): + """Encodes the dafsa into a string notation. + + Each node is printed on its own line with all the nodes that precede it. + The end node is designated with the "$" character. + If it joins into an existing node, the end of the line is adorned with a "=". + Though this doesn't carry information about which other prefix it has joined with, + it has seemed to be precise enough for testing. + + For example, with the input data of: + * a1 + * ac1 + * bc1 + + [root] --- a ---- 1 --- [end] + | | / + -- b -- c--- + + The output will be: + a + a1 + a1$ <- end node was found + ac + ac1= <- joins with the "a1" prefix + b + bc= <- joins with the "ac" prefix + """ + buffer = StringIO() + cache = {} + + for node in sorted(dafsa.root_node.children.values(), key=lambda n: n.character): + _node_to_string(node, "", buffer, cache) + return buffer.getvalue().strip() + + +def _to_words(data): + return [line.strip() for line in data.strip().split("\n")] + + +def _assert_dafsa(data, expected): + words = _to_words(data) + dafsa = Dafsa.from_tld_data(words) + + expected = expected.strip() + expected = "\n".join([line.strip() for line in expected.split("\n")]) + as_string = _dafsa_to_string(dafsa) + assert as_string == expected + + +class TestDafsa(unittest.TestCase): + def test_1(self): + _assert_dafsa( + """ + a1 + ac1 + acc1 + bd1 + bc1 + bcc1 + """, + """ + a + a1 + a1$ + ac + ac1= + acc + acc1= + b + bc= + bd + bd1= + """, + ) + + def test_2(self): + _assert_dafsa( + """ + ab1 + b1 + bb1 + bbb1 + """, + """ + a + ab + ab1 + ab1$ + b + b1= + bb + bb1= + bbb= + """, + ) + + def test_3(self): + _assert_dafsa( + """ + a.ca1 + a.com1 + c.corg1 + b.ca1 + b.com1 + b.corg1 + """, + """ + a + a. + a.c + a.ca + a.ca1 + a.ca1$ + a.co + a.com + a.com1= + b + b. + b.c + b.ca= + b.co + b.com= + b.cor + b.corg + b.corg1= + c + c. + c.c + c.co + c.cor= + """, + ) + + def test_4(self): + _assert_dafsa( + """ + acom1 + bcomcom1 + acomcom1 + """, + """ + a + ac + aco + acom + acom1 + acom1$ + acomc + acomco + acomcom + acomcom1= + b + bc + bco + bcom + bcomc= + """, + ) + + def test_5(self): + _assert_dafsa( + """ + a.d1 + a.c.d1 + b.d1 + b.c.d1 + """, + """ + a + a. + a.c + a.c. + a.c.d + a.c.d1 + a.c.d1$ + a.d= + b + b.= + """, + ) + + def test_6(self): + _assert_dafsa( + """ + a61 + a661 + b61 + b661 + """, + """ + a + a6 + a61 + a61$ + a66 + a661= + b + b6= + """, + ) + + def test_7(self): + _assert_dafsa( + """ + a61 + a6661 + b61 + b6661 + """, + """ + a + a6 + a61 + a61$ + a66 + a666 + a6661= + b + b6= + """, + ) + + def test_8(self): + _assert_dafsa( + """ + acc1 + bc1 + bccc1 + """, + """ + a + ac + acc + acc1 + acc1$ + b + bc + bc1= + bcc= + """, + ) + + def test_9(self): + _assert_dafsa( + """ + acc1 + bc1 + bcc1 + """, + """ + a + ac + acc + acc1 + acc1$ + b + bc + bc1= + bcc= + """, + ) + + def test_10(self): + _assert_dafsa( + """ + acc1 + cc1 + cccc1 + """, + """ + a + ac + acc + acc1 + acc1$ + c + cc + cc1= + ccc= + """, + ) + + def test_11(self): + _assert_dafsa( + """ + ac1 + acc1 + bc1 + bcc1 + """, + """ + a + ac + ac1 + ac1$ + acc + acc1= + b + bc= + """, + ) + + def test_12(self): + _assert_dafsa( + """ + acd1 + bcd1 + bcdd1 + """, + """ + a + ac + acd + acd1 + acd1$ + b + bc + bcd + bcd1= + bcdd= + """, + ) + + def test_13(self): + _assert_dafsa( + """ + ac1 + acc1 + bc1 + bcc1 + bccc1 + """, + """ + a + ac + ac1 + ac1$ + acc + acc1= + b + bc + bc1= + bcc= + """, + ) + + def test_14(self): + _assert_dafsa( + """ + acc1 + acccc1 + bcc1 + bcccc1 + bcccccc1 + """, + """ + a + ac + acc + acc1 + acc1$ + accc + acccc + acccc1= + b + bc + bcc + bcc1= + bccc= + """, + ) + + def test_15(self): + _assert_dafsa( + """ + ac1 + bc1 + acac1 + """, + """ + a + ac + ac1 + ac1$ + aca + acac + acac1= + b + bc= + """, + ) + + def test_16(self): + _assert_dafsa( + """ + bat1 + t1 + tbat1 + """, + """ + b + ba + bat + bat1 + bat1$ + t + t1= + tb= + """, + ) + + def test_17(self): + _assert_dafsa( + """ + acow1 + acat1 + t1 + tcat1 + acatcat1 + """, + """ + a + ac + aca + acat + acat1 + acat1$ + acatc + acatca + acatcat + acatcat1= + aco + acow + acow1= + t= + """, + ) + + def test_18(self): + _assert_dafsa( + """ + bc1 + abc1 + abcxyzc1 + """, + """ + a + ab + abc + abc1 + abc1$ + abcx + abcxy + abcxyz + abcxyzc + abcxyzc1= + b + bc= + """, + ) + + def test_19(self): + _assert_dafsa( + """ + a.z1 + a.y1 + c.z1 + d.z1 + d.y1 + """, + """ + a + a. + a.y + a.y1 + a.y1$ + a.z + a.z1= + c + c. + c.z= + d + d.= + """, + ) + + def test_20(self): + _assert_dafsa( + """ + acz1 + acy1 + accz1 + acccz1 + bcz1 + bcy1 + bccz1 + bcccz1 + """, + """ + a + ac + acc + accc + acccz + acccz1 + acccz1$ + accz= + acy + acy1= + acz= + b + bc= + """, + ) + + +if __name__ == "__main__": + mozunit.main() diff --git a/xpcom/ds/tools/incremental_dafsa.py b/xpcom/ds/tools/incremental_dafsa.py new file mode 100644 index 0000000000..1157c26850 --- /dev/null +++ b/xpcom/ds/tools/incremental_dafsa.py @@ -0,0 +1,509 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Incremental algorithm for creating a "deterministic acyclic finite state +automaton" (DAFSA). At the time of writing this algorithm, there was existing logic +that depended on a different format for the DAFSA, so this contains convenience +functions for converting to a compatible structure. This legacy format is defined +in make_dafsa.py. +""" + +from typing import Callable, Dict, List, Optional + + +class Node: + children: Dict[str, "Node"] + parents: Dict[str, List["Node"]] + character: str + is_root_node: bool + is_end_node: bool + + def __init__(self, character, is_root_node=False, is_end_node=False): + self.children = {} + self.parents = {} + self.character = character + self.is_root_node = is_root_node + self.is_end_node = is_end_node + + def __str__(self): + """Produce a helpful string representation of this node. + + This is expected to only be used for debugging. + The produced output is: + + "c[def.] <123>" + ^ ^ ^ + | | Internal python ID of the node (used for de-duping) + | | + | One possible path through the tree to the end + | + Current node character + """ + + if self.is_root_node: + return "<root>" + elif self.is_end_node: + return "<end>" + + first_potential_match = "" + node = self + while node.children: + first_character = next(iter(node.children)) + if first_character: + first_potential_match += first_character + node = node.children[first_character] + + return "%s[%s] <%d>" % (self.character, first_potential_match, id(self)) + + def add_child(self, child): + self.children[child.character] = child + child.parents.setdefault(self.character, []) + child.parents[self.character].append(self) + + def remove(self): + # remove() must only be called when this node has only a single parent, and that + # parent doesn't need this child anymore. + # The caller is expected to have performed this validation. + # (placing asserts here add a non-trivial performance hit) + + # There's only a single parent, so only one list should be in the "parents" map + parent_list = next(iter(self.parents.values())) + self.remove_parent(parent_list[0]) + for child in list(self.children.values()): + child.remove_parent(self) + + def remove_parent(self, parent_node: "Node"): + parent_node.children.pop(self.character) + parents_for_character = self.parents[parent_node.character] + parents_for_character.remove(parent_node) + if not parents_for_character: + self.parents.pop(parent_node.character) + + def copy_fork_node(self, fork_node: "Node", child_to_avoid: Optional["Node"]): + """Shallow-copy a node's children. + + When adding a new word, sometimes previously-joined suffixes aren't perfect + matches any more. When this happens, some nodes need to be "copied" out. + For all non-end nodes, there's a child to avoid in the shallow-copy. + """ + + for child in fork_node.children.values(): + if child is not child_to_avoid: + self.add_child(child) + + def is_fork(self): + """Check if this node has multiple parents""" + + if len(self.parents) == 0: + return False + + if len(self.parents) > 1: + return True + + return len(next(iter(self.parents.values()))) > 1 + + def is_replacement_for_prefix_end_node(self, old: "Node"): + """Check if this node is a valid replacement for an old end node. + + A node is a valid replacement if it maintains all existing child paths while + adding the new child path needed for the new word. + + Args: + old: node being replaced + + Returns: True if this node is a valid replacement node. + """ + + if len(self.children) != len(old.children) + 1: + return False + + for character, other_node in old.children.items(): + this_node = self.children.get(character) + if other_node is not this_node: + return False + + return True + + def is_replacement_for_prefix_node(self, old: "Node"): + """Check if this node is a valid replacement for a non-end node. + + A node is a valid replacement if it: + * Has one new child that the old node doesn't + * Is missing a child that the old node has + * Shares all other children + + Returns: True if this node is a valid replacement node. + """ + + if len(self.children) != len(old.children): + return False + + found_extra_child = False + + for character, other_node in old.children.items(): + this_node = self.children.get(character) + if other_node is not this_node: + if found_extra_child: + # Found two children in the old node that aren't in the new one, + # this isn't a valid replacement + return False + else: + found_extra_child = True + + return found_extra_child + + +class SuffixCursor: + index: int # Current position of the cursor within the DAFSA. + node: Node + + def __init__(self, index, node): + self.index = index + self.node = node + + def _query(self, character: str, check: Callable[[Node], bool]): + for node in self.node.parents.get(character, []): + if check(node): + self.index -= 1 + self.node = node + return True + return False + + def find_single_child(self, character): + """Find the next matching suffix node that has a single child. + + Return True if such a node is found.""" + return self._query(character, lambda node: len(node.children) == 1) + + def find_end_of_prefix_replacement(self, end_of_prefix: Node): + """Find the next matching suffix node that replaces the old prefix-end node. + + Return True if such a node is found.""" + return self._query( + end_of_prefix.character, + lambda node: node.is_replacement_for_prefix_end_node(end_of_prefix), + ) + + def find_inside_of_prefix_replacement(self, prefix_node: Node): + """Find the next matching suffix node that replaces a node within the prefix. + + Return True if such a node is found.""" + return self._query( + prefix_node.character, + lambda node: node.is_replacement_for_prefix_node(prefix_node), + ) + + +class DafsaAppendStateMachine: + """State machine for adding a word to a Dafsa. + + Each state returns a function reference to the "next state". States should be + invoked until "None" is returned, in which case the new word has been appended. + + The prefix and suffix indexes are placed according to the currently-known valid + value (not the next value being investigated). Additionally, they are 0-indexed + against the root node (which sits behind the beginning of the string). + + Let's imagine we're at the following state when adding, for example, the + word "mozilla.org": + + mozilla.org + ^ ^ ^ ^ + | | | | + / | | \ + [root] | | [end] node + node | \ + | suffix + \ + prefix + + In this state, the furthest prefix match we could find was: + [root] - m - o - z - i - l + The index of the prefix match is "5". + + Additionally, we've been looking for suffix nodes, and we've already found: + r - g - [end] + The current suffix index is "10". + The next suffix node we'll attempt to find is at index "9". + """ + + root_node: Node + prefix_index: int + suffix_cursor: SuffixCursor + stack: List[Node] + word: str + suffix_overlaps_prefix: bool + first_fork_index: Optional[int] + _state: Callable + + def __init__(self, word, root_node, end_node): + self.root_node = root_node + self.prefix_index = 0 + self.suffix_cursor = SuffixCursor(len(word) + 1, end_node) + self.stack = [root_node] + self.word = word + self.suffix_overlaps_prefix = False + self.first_fork_index = None + self._state = self._find_prefix + + def run(self): + """Run this state machine to completion, adding the new word.""" + while self._state is not None: + self._state = self._state() + + def _find_prefix(self): + """Find the longest existing prefix that matches the current word.""" + prefix_node = self.root_node + while self.prefix_index < len(self.word): + next_character = self.word[self.prefix_index] + next_node = prefix_node.children.get(next_character) + if not next_node: + # We're finished finding the prefix, let's find the longest suffix + # match now. + return self._find_suffix_nodes_after_prefix + + self.prefix_index += 1 + prefix_node = next_node + self.stack.append(next_node) + + if not self.first_fork_index and next_node.is_fork(): + self.first_fork_index = self.prefix_index + + # Deja vu, we've appended this string before. Since this string has + # already been appended, we don't have to do anything. + return None + + def _find_suffix_nodes_after_prefix(self): + """Find the chain of suffix nodes for characters after the prefix.""" + while self.suffix_cursor.index - 1 > self.prefix_index: + # To fetch the next character, we need to subtract two from the current + # suffix index. This is because: + # * The next suffix node is 1 node before our current node (subtract 1) + # * The suffix index includes the root node before the beginning of the + # string - it's like the string is 1-indexed (subtract 1 again). + next_character = self.word[self.suffix_cursor.index - 2] + if not self.suffix_cursor.find_single_child(next_character): + return self._add_new_nodes + + if self.suffix_cursor.node is self.stack[-1]: + # The suffix match is overlapping with the prefix! This can happen in + # cases like: + # * "ab" + # * "abb" + # The suffix cursor is at the same node as the prefix match, but they're + # at different positions in the word. + # + # [root] - a - b - [end] + # ^ + # / \ + # / \ + # prefix suffix + # \ / + # \ / + # VV + # "abb" + if not self.first_fork_index: + # There hasn't been a fork, so our prefix isn't shared. So, we + # can mark this node as a fork, since the repetition means + # that there's two paths that are now using this node + self.first_fork_index = self.prefix_index + return self._add_new_nodes + + # Removes the link between the unique part of the prefix and the + # shared part of the prefix. + self.stack[self.first_fork_index].remove_parent( + self.stack[self.first_fork_index - 1] + ) + self.suffix_overlaps_prefix = True + + if self.first_fork_index is None: + return self._find_next_suffix_nodes + elif self.suffix_cursor.index - 1 == self.first_fork_index: + return self._find_next_suffix_node_at_prefix_end_at_fork + else: + return self._find_next_suffix_node_at_prefix_end_after_fork + + def _find_next_suffix_node_at_prefix_end_at_fork(self): + """Find the next suffix node that replaces the end of the prefix. + + In this state, the prefix_end node is the same as the first fork node. + Therefore, if a match can be found, the old prefix node can't be entirely + deleted since it's used elsewhere. Instead, just the link between our + unique prefix and the end of the fork is removed. + """ + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + self.first_fork_index = None + + if not self.suffix_overlaps_prefix: + existing_node.remove_parent(self.stack[self.prefix_index]) + else: + # When the suffix overlaps the prefix, the old "parent link" was removed + # earlier in the "find_suffix_nodes_after_prefix" step. + self.suffix_overlaps_prefix = False + + return self._find_next_suffix_nodes + + def _find_next_suffix_node_at_prefix_end_after_fork(self): + """Find the next suffix node that replaces the end of the prefix. + + In this state, the prefix_end node is after the first fork node. + Therefore, even if a match is found, we don't want to modify the replaced + prefix node since an unrelated word chain uses it. + """ + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + if self.prefix_index == self.first_fork_index: + return self._find_next_suffix_node_within_prefix_at_fork + else: + return self._find_next_suffix_nodes_within_prefix_after_fork + + def _find_next_suffix_node_within_prefix_at_fork(self): + """Find the next suffix node within a prefix. + + In this state, we've already worked our way back and found nodes in the suffix + to replace prefix nodes after the fork node. We have now reached the fork node, + and if we find a replacement for it, then we can remove the link between it + and our then-unique prefix and clear the fork status. + """ + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_inside_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + self.first_fork_index = None + + if not self.suffix_overlaps_prefix: + existing_node.remove_parent(self.stack[self.prefix_index]) + else: + # When the suffix overlaps the prefix, the old "parent link" was removed + # earlier in the "find_suffix_nodes_after_prefix" step. + self.suffix_overlaps_prefix = False + + return self._find_next_suffix_nodes + + def _find_next_suffix_nodes_within_prefix_after_fork(self): + """Find the next suffix nodes within a prefix. + + Finds suffix nodes to replace prefix nodes, but doesn't modify the prefix + nodes since they're after a fork (so, we're sharing prefix nodes with + other words and can't modify them). + """ + while True: + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_inside_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + if self.prefix_index == self.first_fork_index: + return self._find_next_suffix_node_within_prefix_at_fork + + def _find_next_suffix_nodes(self): + """Find all remaining suffix nodes in the chain. + + In this state, there's no (longer) any fork, so there's no other words + using our current prefix. Therefore, as we find replacement nodes as we + work our way backwards, we can remove the now-unused prefix nodes. + """ + while True: + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node): + return self._add_new_nodes + + # This prefix node is wholly replaced by the new suffix node, so it can + # be deleted. + existing_node.remove() + self.prefix_index -= 1 + + def _add_new_nodes(self): + """Adds new nodes to support the new word. + + Duplicates forked nodes to make room for new links, adds new nodes for new + characters, and splices the prefix to the suffix to finish embedding the new + word into the DAFSA. + """ + if self.first_fork_index is not None: + front_node = _duplicate_fork_nodes( + self.stack, + self.first_fork_index, + self.prefix_index, + # if suffix_overlaps_parent, the parent link was removed + # earlier in the word-adding process. + remove_parent_link=not self.suffix_overlaps_prefix, + ) + else: + front_node = self.stack[self.prefix_index] + + new_text = self.word[self.prefix_index : self.suffix_cursor.index - 1] + for character in new_text: + new_node = Node(character) + front_node.add_child(new_node) + front_node = new_node + + front_node.add_child(self.suffix_cursor.node) + return None # Done! + + +def _duplicate_fork_nodes(stack, fork_index, prefix_index, remove_parent_link=True): + parent_node = stack[fork_index - 1] + if remove_parent_link: + # remove link to old chain that we're going to be copying + stack[fork_index].remove_parent(parent_node) + + for index in range(fork_index, prefix_index + 1): + fork_node = stack[index] + replacement_node = Node(fork_node.character) + child_to_avoid = None + if index < len(stack) - 1: + # We're going to be manually replacing the next node in the stack, + # so don't connect it as a child. + child_to_avoid = stack[index + 1] + + replacement_node.copy_fork_node(fork_node, child_to_avoid) + parent_node.add_child(replacement_node) + parent_node = replacement_node + + return parent_node + + +class Dafsa: + root_node: Node + end_node: Node + + def __init__(self): + self.root_node = Node(None, is_root_node=True) + self.end_node = Node(None, is_end_node=True) + + @classmethod + def from_tld_data(cls, lines): + """Create a dafsa for TLD data. + + TLD data has a domain and a "type" enum. The source data encodes the type as a + text number, but the dafsa-consuming code assumes that the type is a raw byte + number (e.g.: "1" => 0x01). + + This function acts as a helper, processing this TLD detail before creating a + standard dafsa. + """ + + dafsa = cls() + for i, word in enumerate(lines): + domain_number = word[-1] + # Convert type from string to byte representation + raw_domain_number = chr(ord(domain_number) & 0x0F) + + word = "%s%s" % (word[:-1], raw_domain_number) + dafsa.append(word) + return dafsa + + def append(self, word): + state_machine = DafsaAppendStateMachine(word, self.root_node, self.end_node) + state_machine.run() diff --git a/xpcom/ds/tools/make_dafsa.py b/xpcom/ds/tools/make_dafsa.py new file mode 100644 index 0000000000..a9cedf78a8 --- /dev/null +++ b/xpcom/ds/tools/make_dafsa.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +A Deterministic acyclic finite state automaton (DAFSA) is a compact +representation of an unordered word list (dictionary). + +http://en.wikipedia.org/wiki/Deterministic_acyclic_finite_state_automaton + +This python program converts a list of strings to a byte array in C++. +This python program fetches strings and return values from a gperf file +and generates a C++ file with a byte array representing graph that can be +used as a memory efficient replacement for the perfect hash table. + +The input strings are assumed to consist of printable 7-bit ASCII characters +and the return values are assumed to be one digit integers. + +In this program a DAFSA is a diamond shaped graph starting at a common +root node and ending at a common end node. All internal nodes contain +a character and each word is represented by the characters in one path from +the root node to the end node. + +The order of the operations is crucial since lookups will be performed +starting from the source with no backtracking. Thus a node must have at +most one child with a label starting by the same character. The output +is also arranged so that all jumps are to increasing addresses, thus forward +in memory. + +The generated output has suffix free decoding so that the sign of leading +bits in a link (a reference to a child node) indicate if it has a size of one, +two or three bytes and if it is the last outgoing link from the actual node. +A node label is terminated by a byte with the leading bit set. + +The generated byte array can described by the following BNF: + +<byte> ::= < 8-bit value in range [0x00-0xFF] > + +<char> ::= < printable 7-bit ASCII character, byte in range [0x20-0x7F] > +<end_char> ::= < char + 0x80, byte in range [0xA0-0xFF] > +<return value> ::= < value + 0x80, byte in range [0x80-0x8F] > + +<offset1> ::= < byte in range [0x00-0x3F] > +<offset2> ::= < byte in range [0x40-0x5F] > +<offset3> ::= < byte in range [0x60-0x7F] > + +<end_offset1> ::= < byte in range [0x80-0xBF] > +<end_offset2> ::= < byte in range [0xC0-0xDF] > +<end_offset3> ::= < byte in range [0xE0-0xFF] > + +<prefix> ::= <char> + +<label> ::= <end_char> + | <char> <label> + +<end_label> ::= <return_value> + | <char> <end_label> + +<offset> ::= <offset1> + | <offset2> <byte> + | <offset3> <byte> <byte> + +<end_offset> ::= <end_offset1> + | <end_offset2> <byte> + | <end_offset3> <byte> <byte> + +<offsets> ::= <end_offset> + | <offset> <offsets> + +<source> ::= <offsets> + +<node> ::= <label> <offsets> + | <prefix> <node> + | <end_label> + +<dafsa> ::= <source> + | <dafsa> <node> + +Decoding: + +<char> -> printable 7-bit ASCII character +<end_char> & 0x7F -> printable 7-bit ASCII character +<return value> & 0x0F -> integer +<offset1 & 0x3F> -> integer +((<offset2> & 0x1F>) << 8) + <byte> -> integer +((<offset3> & 0x1F>) << 16) + (<byte> << 8) + <byte> -> integer + +end_offset1, end_offset2 and and_offset3 are decoded same as offset1, +offset2 and offset3 respectively. + +The first offset in a list of offsets is the distance in bytes between the +offset itself and the first child node. Subsequent offsets are the distance +between previous child node and next child node. Thus each offset links a node +to a child node. The distance is always counted between start addresses, i.e. +first byte in decoded offset or first byte in child node. + +Example 1: + +%% +aa, 1 +a, 2 +%% + +The input is first parsed to a list of words: +["aa1", "a2"] + +This produces the following graph: +[root] --- a --- 0x02 --- [end] + | / + | / + - a --- 0x01 + +A C++ representation of the compressed graph is generated: + +const unsigned char dafsa[7] = { + 0x81, 0xE1, 0x02, 0x81, 0x82, 0x61, 0x81, +}; + +The bytes in the generated array has the following meaning: + + 0: 0x81 <end_offset1> child at position 0 + (0x81 & 0x3F) -> jump to 1 + + 1: 0xE1 <end_char> label character (0xE1 & 0x7F) -> match "a" + 2: 0x02 <offset1> child at position 2 + (0x02 & 0x3F) -> jump to 4 + + 3: 0x81 <end_offset1> child at position 4 + (0x81 & 0x3F) -> jump to 5 + 4: 0x82 <return_value> 0x82 & 0x0F -> return 2 + + 5: 0x61 <char> label character 0x61 -> match "a" + 6: 0x81 <return_value> 0x81 & 0x0F -> return 1 + +Example 2: + +%% +aa, 1 +bbb, 2 +baa, 1 +%% + +The input is first parsed to a list of words: +["aa1", "bbb2", "baa1"] + +This produces the following graph: +[root] --- a --- a --- 0x01 --- [end] + | / / / + | / / / + - b --- b --- b --- 0x02 + +A C++ representation of the compressed graph is generated: + +const unsigned char dafsa[11] = { + 0x02, 0x83, 0xE2, 0x02, 0x83, 0x61, 0x61, 0x81, 0x62, 0x62, 0x82, +}; + +The bytes in the generated array has the following meaning: + + 0: 0x02 <offset1> child at position 0 + (0x02 & 0x3F) -> jump to 2 + 1: 0x83 <end_offset1> child at position 2 + (0x83 & 0x3F) -> jump to 5 + + 2: 0xE2 <end_char> label character (0xE2 & 0x7F) -> match "b" + 3: 0x02 <offset1> child at position 3 + (0x02 & 0x3F) -> jump to 5 + 4: 0x83 <end_offset1> child at position 5 + (0x83 & 0x3F) -> jump to 8 + + 5: 0x61 <char> label character 0x61 -> match "a" + 6: 0x61 <char> label character 0x61 -> match "a" + 7: 0x81 <return_value> 0x81 & 0x0F -> return 1 + + 8: 0x62 <char> label character 0x62 -> match "b" + 9: 0x62 <char> label character 0x62 -> match "b" +10: 0x82 <return_value> 0x82 & 0x0F -> return 2 +""" +import struct +import sys + +from incremental_dafsa import Dafsa, Node + + +class InputError(Exception): + """Exception raised for errors in the input file.""" + + +def top_sort(dafsa: Dafsa): + """Generates list of nodes in topological sort order.""" + incoming = {} + + def count_incoming(node: Node): + """Counts incoming references.""" + if not node.is_end_node: + if id(node) not in incoming: + incoming[id(node)] = 1 + for child in node.children.values(): + count_incoming(child) + else: + incoming[id(node)] += 1 + + for node in dafsa.root_node.children.values(): + count_incoming(node) + + for node in dafsa.root_node.children.values(): + incoming[id(node)] -= 1 + + waiting = [ + node for node in dafsa.root_node.children.values() if incoming[id(node)] == 0 + ] + nodes = [] + + while waiting: + node = waiting.pop() + assert incoming[id(node)] == 0 + nodes.append(node) + for child in node.children.values(): + if not child.is_end_node: + incoming[id(child)] -= 1 + if incoming[id(child)] == 0: + waiting.append(child) + return nodes + + +def encode_links(node: Node, offsets, current): + """Encodes a list of children as one, two or three byte offsets.""" + if next(iter(node.children.values())).is_end_node: + # This is an <end_label> node and no links follow such nodes + return [] + guess = 3 * len(node.children) + assert node.children + + children = sorted(node.children.values(), key=lambda x: -offsets[id(x)]) + while True: + offset = current + guess + buf = [] + for child in children: + last = len(buf) + distance = offset - offsets[id(child)] + assert distance > 0 and distance < (1 << 21) + + if distance < (1 << 6): + # A 6-bit offset: "s0xxxxxx" + buf.append(distance) + elif distance < (1 << 13): + # A 13-bit offset: "s10xxxxxxxxxxxxx" + buf.append(0x40 | (distance >> 8)) + buf.append(distance & 0xFF) + else: + # A 21-bit offset: "s11xxxxxxxxxxxxxxxxxxxxx" + buf.append(0x60 | (distance >> 16)) + buf.append((distance >> 8) & 0xFF) + buf.append(distance & 0xFF) + # Distance in first link is relative to following record. + # Distance in other links are relative to previous link. + offset -= distance + if len(buf) == guess: + break + guess = len(buf) + # Set most significant bit to mark end of links in this node. + buf[last] |= 1 << 7 + buf.reverse() + return buf + + +def encode_prefix(label): + """Encodes a node label as a list of bytes without a trailing high byte. + + This method encodes a node if there is exactly one child and the + child follows immediately after so that no jump is needed. This label + will then be a prefix to the label in the child node. + """ + assert label + return [ord(c) for c in reversed(label)] + + +def encode_label(label): + """Encodes a node label as a list of bytes with a trailing high byte >0x80.""" + buf = encode_prefix(label) + # Set most significant bit to mark end of label in this node. + buf[0] |= 1 << 7 + return buf + + +def encode(dafsa: Dafsa): + """Encodes a DAFSA to a list of bytes""" + output = [] + offsets = {} + + for node in reversed(top_sort(dafsa)): + if ( + len(node.children) == 1 + and not next(iter(node.children.values())).is_end_node + and (offsets[id(next(iter(node.children.values())))] == len(output)) + ): + output.extend(encode_prefix(node.character)) + else: + output.extend(encode_links(node, offsets, len(output))) + output.extend(encode_label(node.character)) + offsets[id(node)] = len(output) + + output.extend(encode_links(dafsa.root_node, offsets, len(output))) + output.reverse() + return output + + +def to_cxx(data, preamble=None): + """Generates C++ code from a list of encoded bytes.""" + text = "/* This file is generated. DO NOT EDIT!\n\n" + text += "The byte array encodes a dictionary of strings and values. See " + text += "make_dafsa.py for documentation." + text += "*/\n\n" + + if preamble: + text += preamble + text += "\n\n" + + text += "const unsigned char kDafsa[%s] = {\n" % len(data) + for i in range(0, len(data), 12): + text += " " + text += ", ".join("0x%02x" % byte for byte in data[i : i + 12]) + text += ",\n" + text += "};\n" + return text + + +def words_to_cxx(words, preamble=None): + """Generates C++ code from a word list""" + dafsa = Dafsa.from_tld_data(words) + return to_cxx(encode(dafsa), preamble) + + +def words_to_bin(words): + """Generates bytes from a word list""" + dafsa = Dafsa.from_tld_data(words) + data = encode(dafsa) + return struct.pack("%dB" % len(data), *data) + + +def parse_gperf(infile): + """Parses gperf file and extract strings and return code""" + lines = [line.strip() for line in infile] + + # Extract the preamble. + first_delimeter = lines.index("%%") + preamble = "\n".join(lines[0:first_delimeter]) + + # Extract strings after the first '%%' and before the second '%%'. + begin = first_delimeter + 1 + end = lines.index("%%", begin) + lines = lines[begin:end] + for line in lines: + if line[-3:-1] != ", ": + raise InputError('Expected "domainname, <digit>", found "%s"' % line) + # Technically the DAFSA format could support return values in range [0-31], + # but the values below are the only with a defined meaning. + if line[-1] not in "0124": + raise InputError( + 'Expected value to be one of {0,1,2,4}, found "%s"' % line[-1] + ) + return (preamble, [line[:-3] + line[-1] for line in lines]) + + +def main(outfile, infile): + with open(infile, "r") as infile: + preamble, words = parse_gperf(infile) + outfile.write(words_to_cxx(words, preamble)) + return 0 + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("usage: %s infile outfile" % sys.argv[0]) + sys.exit(1) + + with open(sys.argv[2], "w") as outfile: + sys.exit(main(outfile, sys.argv[1])) diff --git a/xpcom/ds/tools/perfecthash.py b/xpcom/ds/tools/perfecthash.py new file mode 100644 index 0000000000..929b643a30 --- /dev/null +++ b/xpcom/ds/tools/perfecthash.py @@ -0,0 +1,377 @@ +# perfecthash.py - Helper for generating perfect hash functions +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 perfect hash function (PHF) is a function which maps distinct elements from a +source set to a sequence of integers with no collisions. + +PHFs generated by this module use a 32-bit FNV hash to index an intermediate +table. The value from that table is then used to run a second 32-bit FNV hash to +get a final index. + +The algorithm works by starting with the largest set of conflicts, and testing +intermediate table values until no conflicts are generated, allowing efficient +index lookup at runtime. (see also: http://stevehanov.ca/blog/index.php?id=119) + +NOTE: This perfect hash generator was designed to be simple, easy to follow, and +maintainable, rather than being as optimized as possible, due to the relatively +small dataset it was designed for. In the future we may want to optimize further. +""" + +import textwrap +from collections import namedtuple + +import six +from mozbuild.util import ensure_bytes + + +# Iteration over bytestrings works differently in Python 2 and 3; this function +# captures the two possibilities. Returns an 'int' given the output of iterating +# through a bytestring regardless of the input. +def _ord(c): + if six.PY3: + return c + return ord(c) + + +class PerfectHash(object): + """PerfectHash objects represent a computed perfect hash function, which + can be generated at compile time to provide highly efficient and compact + static HashTables. + + Consumers must provide an intermediate table size to store the generated + hash function. Larger tables will generate more quickly, while smaller + tables will consume less space on disk.""" + + # 32-bit FNV offset basis and prime value. + # NOTE: Must match values in |PerfectHash.h| + FNV_OFFSET_BASIS = 0x811C9DC5 + FNV_PRIME = 16777619 + U32_MAX = 0xFFFFFFFF + + # Bucket of entries which map into a given intermediate index. + Bucket = namedtuple("Bucket", "index entries") + + def __init__(self, entries, size, validate=True, key=lambda e: e[0]): + """Create a new PerfectHash + + @param entries set of entries to generate a PHF for + @param size size of the PHF intermediate table + @param validate test the generated hash function after generation + @param key function to get 'memoryview'-compatible key for an + entry. defaults to extracting the first element of an + entry 2-tuple. + """ + + assert 0 < len(entries) < self.U32_MAX, "bad # of entries!" + self._key = key + + # Allocate the intermediate table and keys. + self.table = [0] * size + self.entries = [None] * len(entries) + + # A set of Bucket values. Each bucket contains the index into the + # intermediate table, and a list of Entries which map into that bucket. + buckets = [self.Bucket(idx, []) for idx in range(size)] + + # Determine which input strings map to which buckets in the table. + for entry in entries: + assert entry is not None, "None entry in entries" + assert self.key(entry).itemsize == 1, "non-byte key elements" + buckets[self._hash(self.key(entry)) % size].entries.append(entry) + + # Sort the buckets such that the largest one comes first. + buckets.sort(key=lambda b: len(b.entries), reverse=True) + + for bucket in buckets: + # Once we've reached an empty bucket, we're done. + if len(bucket.entries) == 0: + break + + # Try values for the basis until we find one with no conflicts. + idx = 0 + basis = 1 + slots = [] + while idx < len(bucket.entries): + slot = self._hash(self.key(bucket.entries[idx]), basis) % len( + self.entries + ) + if self.entries[slot] is not None or slot in slots: + # There was a conflict, try the next basis. + basis += 1 + idx = 0 + del slots[:] + assert basis < self.U32_MAX, "table too small" + else: + slots.append(slot) + idx += 1 + + assert basis < self.U32_MAX + + # We've found a basis which doesn't conflict + self.table[bucket.index] = basis + for slot, entry in zip(slots, bucket.entries): + self.entries[slot] = entry + + # Validate that looking up each key succeeds + if validate: + for entry in entries: + assert self.get_entry(self.key(entry)), "get_entry(%s)" % repr(entry) + + @classmethod + def _hash(cls, key, basis=FNV_OFFSET_BASIS): + """A basic FNV-based hash function. key is the memoryview to hash. + 32-bit FNV is used for indexing into the first table, and the value + stored in that table is used as the offset basis for indexing into the + values table.""" + for byte in memoryview(ensure_bytes(key)): + obyte = _ord(byte) + basis ^= obyte # xor-in the byte + basis *= cls.FNV_PRIME # Multiply by the FNV prime + basis &= cls.U32_MAX # clamp to 32-bits + return basis + + def key(self, entry): + return memoryview(ensure_bytes(self._key(entry))) + + def get_raw_index(self, key): + """Determine the index in self.entries without validating""" + mid = self.table[self._hash(key) % len(self.table)] + return self._hash(key, mid) % len(self.entries) + + def get_index(self, key): + """Given a key, determine the index in self.entries""" + idx = self.get_raw_index(key) + if memoryview(ensure_bytes(key)) != self.key(self.entries[idx]): + return None + return idx + + def get_entry(self, key): + """Given a key, get the corresponding entry""" + idx = self.get_index(key) + if idx is None: + return None + return self.entries[idx] + + @staticmethod + def _indent(text, amount=1): + return text.replace("\n", "\n" + (" " * amount)) + + def codegen(self, name, entry_type): + """Create a helper for codegening PHF logic""" + return CGHelper(self, name, entry_type) + + def cxx_codegen( + self, + name, + entry_type, + lower_entry, + entries_name=None, + return_type=None, + return_entry="return entry;", + key_type="const char*", + key_bytes="aKey", + key_length="strlen(aKey)", + ): + """Generate complete C++ code for a get_entry-style method. + + @param name Name for the entry getter function. + @param entry_type C++ type of each entry in static memory. + @param lower_entry Function called with each entry to convert it to a + C++ literal of type 'entry_type'. + + # Optional Keyword Parameters: + @param entries_name Name for the generated entry table. + + @param return_type C++ return type, default: 'const entry_type&' + @param return_entry Override the default behaviour for returning the + found entry. 'entry' is a reference to the found + entry, and 'aKey' is the lookup key. 'return_entry' + can be used for additional checks, e.g. for keys + not in the table. + + @param key_type C++ key type, default: 'const char*' + @param key_bytes 'const char*' expression to get bytes for 'aKey' + @param key_length 'size_t' expression to get length of 'aKey'""" + + if entries_name is None: + entries_name = "s%sEntries" % name + + cg = self.codegen(entries_name, entry_type) + entries = cg.gen_entries(lower_entry) + getter = cg.gen_getter( + name, return_type, return_entry, key_type, key_bytes, key_length + ) + + return "%s\n\n%s" % (entries, getter) + + +class CGHelper(object): + """Helper object for generating C++ code for a PerfectHash. + Created using PerfectHash.codegen().""" + + def __init__(self, phf, entries_name, entry_type): + self.phf = phf + self.entries_name = entries_name + self.entry_type = entry_type + + @staticmethod + def _indent(text, amount=1): + return text.replace("\n", "\n" + (" " * amount)) + + def basis_ty(self): + """Determine how big of an integer is needed for bases table.""" + max_base = max(self.phf.table) + if max_base <= 0xFF: + return "uint8_t" + elif max_base <= 0xFFFF: + return "uint16_t" + return "uint32_t" + + def basis_table(self, name="BASES"): + """Generate code for a static basis table""" + bases = "" + for idx, base in enumerate(self.phf.table): + if idx and idx % 16 == 0: # 16 bases per line + bases += "\n " + bases += "%4d," % base + return ( + textwrap.dedent( + """\ + static const %s %s[] = { + %s + }; + """ + ) + % (self.basis_ty(), name, bases) + ) + + def gen_entries(self, lower_entry): + """Generate code for an entries table""" + entries = self._indent( + ",\n".join(lower_entry(entry).rstrip() for entry in self.phf.entries) + ) + return ( + textwrap.dedent( + """\ + const %s %s[] = { + %s + }; + """ + ) + % (self.entry_type, self.entries_name, entries) + ) + + def gen_getter( + self, + name, + return_type=None, + return_entry="return entry;", + key_type="const char*", + key_bytes="aKey", + key_length="strlen(aKey)", + ): + """Generate the code for a C++ getter. + + @param name Name for the entry getter function. + @param return_type C++ return type, default: 'const entry_type&' + @param return_entry Override the default behaviour for returning the + found entry. 'entry' is a reference to the found + entry, and 'aKey' is the lookup key. 'return_entry' + can be used for additional checks, e.g. for keys + not in the table. + + @param key_type C++ key type, default: 'const char*' + @param key_bytes 'const char*' expression to get bytes for 'aKey' + @param key_length 'size_t' expression to get length of 'aKey'""" + + if return_type is None: + return_type = "const %s&" % self.entry_type + + return ( + textwrap.dedent( + """ + %(return_type)s + %(name)s(%(key_type)s aKey) + { + %(basis_table)s + + const char* bytes = %(key_bytes)s; + size_t length = %(key_length)s; + auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES, + %(entries_name)s); + %(return_entry)s + } + """ + ) + % { + "name": name, + "basis_table": self._indent(self.basis_table()), + "entries_name": self.entries_name, + "return_type": return_type, + "return_entry": self._indent(return_entry), + "key_type": key_type, + "key_bytes": key_bytes, + "key_length": key_length, + } + ) + + def gen_jslinearstr_getter( + self, name, return_type=None, return_entry="return entry;" + ): + """Generate code for a specialized getter taking JSLinearStrings. + This getter avoids copying the JS string, but only supports ASCII keys. + + @param name Name for the entry getter function. + @param return_type C++ return type, default: 'const entry_type&' + @param return_entry Override the default behaviour for returning the + found entry. 'entry' is a reference to the found + entry, and 'aKey' is the lookup key. 'return_entry' + can be used for additional checks, e.g. for keys + not in the table.""" + + assert all( + _ord(b) <= 0x7F for e in self.phf.entries for b in self.phf.key(e) + ), "non-ASCII key" + + if return_type is None: + return_type = "const %s&" % self.entry_type + + return ( + textwrap.dedent( + """ + %(return_type)s + %(name)s(JSLinearString* aKey) + { + %(basis_table)s + + size_t length = JS::GetLinearStringLength(aKey); + + JS::AutoCheckCannotGC nogc; + if (JS::LinearStringHasLatin1Chars(aKey)) { + auto& entry = mozilla::perfecthash::Lookup( + JS::GetLatin1LinearStringChars(nogc, aKey), + length, BASES, %(entries_name)s); + + %(return_entry)s + } else { + auto& entry = mozilla::perfecthash::Lookup( + JS::GetTwoByteLinearStringChars(nogc, aKey), + length, BASES, %(entries_name)s); + + %(return_entry)s + } + } + """ + ) + % { + "name": name, + "basis_table": self._indent(self.basis_table()), + "entries_name": self.entries_name, + "return_type": return_type, + "return_entry": self._indent(return_entry, 2), + } + ) diff --git a/xpcom/geckoprocesstypes_generator/geckoprocesstypes/__init__.py b/xpcom/geckoprocesstypes_generator/geckoprocesstypes/__init__.py new file mode 100644 index 0000000000..9b5b3cb8a1 --- /dev/null +++ b/xpcom/geckoprocesstypes_generator/geckoprocesstypes/__init__.py @@ -0,0 +1,203 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from collections import namedtuple + +# Please make sure you follow ipc/docs/processes.rst and keep updating all +# places that might depend on a new process type and that are not covered (yet) +# by that code. + +# The entries in this file define support functions for each of the process +# types present in Gecko. +# +# GECKO_PROCESS_TYPE( +# enum-value, +# enum-name, +# string-name, +# proc-typename, +# process-bin-type, +# procinfo-typename, +# webidl-typename, +# allcaps-name, +# crash-ping, +# ) +# +# enum-value: +# Unsigned int value the enum will use to identify the process type. This +# value must not be shared by different process types and should never be +# changed since it is used e.g. in telemetry reporting. +# +# ***These values should be mirrored in nsIXULRuntime.idl.*** +# +# enum-name: +# Used to name the GeckoChildProcess enum. E.g. `Foo` will become +# `GeckoChildProcess_Foo`. The enum's value will be the enum-value above. +# +# string-name: +# Human-readable name. It is exposed to things like telemetry and the crash +# reporter, so it should not be changed casually. +# +# proc-typename: +# Used as NAME in the `XRE_Is${NAME}Process` function. Ideally, this should +# match the enum-name. This is included since there are legacy exceptions to +# that rule. +# +# process-bin-type: +# Either Self or PluginContainer. Determines whether the child process may +# be started using the same binary as the parent process, or whether to use +# plugin-container (Note that whether or not this value is actually obeyed +# depends on platform and build configuration. Do not use this value +# directly, but rather use XRE_GetChildProcBinPathType to resolve this). +# +# procinfo-typename: +# Used as NAME in the ProcType enum defined by ProcInfo.h. +# +# webidl-typename: +# Used as NAME in the ChromeUtils.cpp code +# +# allcaps-name: +# Used as NAME for checking SYNC_ENUM in nsXULAppAPI.h +# +# crash-ping: +# Boolean reflecting if the process is allowed to send crash ping. + + +GeckoProcessType = namedtuple( + "GeckoProcessType", + [ + "enum_value", + "enum_name", + "string_name", + "proc_typename", + "process_bin_type", + "procinfo_typename", + "webidl_typename", + "allcaps_name", + "crash_ping", + ], +) + +process_types = [ + GeckoProcessType( + 0, + "Default", + "default", + "Parent", + "Self", + "Browser", + "Browser", + "DEFAULT", + True, + ), + GeckoProcessType( + 2, + "Content", + "tab", + "Content", + "Self", + "Content", + "Content", + "CONTENT", + True, + ), + GeckoProcessType( + 3, + "IPDLUnitTest", + "ipdlunittest", + "IPDLUnitTest", + "Self", + "IPDLUnitTest", + "IpdlUnitTest", + "IPDLUNITTEST", + False, + ), + GeckoProcessType( + 4, + "GMPlugin", + "gmplugin", + "GMPlugin", + "PluginContainer", + "GMPlugin", + "GmpPlugin", + "GMPLUGIN", + True, + ), + GeckoProcessType( + 5, + "GPU", + "gpu", + "GPU", + "Self", + "GPU", + "Gpu", + "GPU", + True, + ), + GeckoProcessType( + 6, + "VR", + "vr", + "VR", + "Self", + "VR", + "Vr", + "VR", + True, + ), + GeckoProcessType( + 7, + "RDD", + "rdd", + "RDD", + "Self", + "RDD", + "Rdd", + "RDD", + True, + ), + GeckoProcessType( + 8, + "Socket", + "socket", + "Socket", + "Self", + "Socket", + "Socket", + "SOCKET", + True, + ), + GeckoProcessType( + 9, + "RemoteSandboxBroker", + "sandboxbroker", + "RemoteSandboxBroker", + "PluginContainer", + "RemoteSandboxBroker", + "RemoteSandboxBroker", + "REMOTESANDBOXBROKER", + True, + ), + GeckoProcessType( + 10, + "ForkServer", + "forkserver", + "ForkServer", + "Self", + "ForkServer", + "ForkServer", + "FORKSERVER", + True, + ), + GeckoProcessType( + 11, + "Utility", + "utility", + "Utility", + "Self", + "Utility", + "Utility", + "UTILITY", + True, + ), +] diff --git a/xpcom/geckoprocesstypes_generator/geckoprocesstypes/moz.build b/xpcom/geckoprocesstypes_generator/geckoprocesstypes/moz.build new file mode 100644 index 0000000000..568f361a54 --- /dev/null +++ b/xpcom/geckoprocesstypes_generator/geckoprocesstypes/moz.build @@ -0,0 +1,5 @@ +# -*- 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/. diff --git a/xpcom/geckoprocesstypes_generator/setup.py b/xpcom/geckoprocesstypes_generator/setup.py new file mode 100644 index 0000000000..9fd8dff82d --- /dev/null +++ b/xpcom/geckoprocesstypes_generator/setup.py @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from setuptools import find_packages, setup + +setup( + name="geckoprocesstypes", + version="1.0", + description="Generator for GeckoProcessTypes related resources.", + author="Mozilla Foundation", + license="MPL 2.0", + packages=find_packages(), + install_requires=[], + entry_points={"console_scripts": ["GeckoProcessTypes.py = geckoprocesstypes:main"]}, + keywords=["geckoprocesstypes"], +) diff --git a/xpcom/glue/FileUtils.cpp b/xpcom/glue/FileUtils.cpp new file mode 100644 index 0000000000..537c060bf7 --- /dev/null +++ b/xpcom/glue/FileUtils.cpp @@ -0,0 +1,579 @@ +/* -*- 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/FileUtils.h" + +#include "nscore.h" +#include "private/pprio.h" +#include "prmem.h" +#include "mozilla/BaseProfilerMarkers.h" +#include "mozilla/MemUtils.h" + +#if defined(XP_MACOSX) +# include <fcntl.h> +# include <unistd.h> +# include <mach/machine.h> +# include <mach-o/fat.h> +# include <mach-o/loader.h> +# include <sys/mman.h> +# include <sys/stat.h> +# include <limits.h> +#elif defined(XP_UNIX) +# include <fcntl.h> +# include <unistd.h> +# if defined(LINUX) +# include <elf.h> +# endif +# include <sys/types.h> +# include <sys/stat.h> +#elif defined(XP_WIN) +# include <nsWindowsHelpers.h> +# include <mozilla/NativeNt.h> +# include <mozilla/ScopeExit.h> +#endif + +// Functions that are not to be used in standalone glue must be implemented +// within this #if block +#if defined(MOZILLA_INTERNAL_API) + +# include "nsString.h" + +bool mozilla::fallocate(PRFileDesc* aFD, int64_t aLength) { +# if defined(HAVE_POSIX_FALLOCATE) + return posix_fallocate(PR_FileDesc2NativeHandle(aFD), 0, aLength) == 0; +# elif defined(XP_WIN) + int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (oldpos == -1) { + return false; + } + + if (PR_Seek64(aFD, aLength, PR_SEEK_SET) != aLength) { + return false; + } + + bool retval = (0 != SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))); + + PR_Seek64(aFD, oldpos, PR_SEEK_SET); + return retval; +# elif defined(XP_MACOSX) + int fd = PR_FileDesc2NativeHandle(aFD); + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength}; + // Try to get a continous chunk of disk space + int ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + return false; + } + } + return ftruncate(fd, aLength) == 0; +# elif defined(XP_UNIX) + // The following is copied from fcntlSizeHint in sqlite + /* If the OS does not have posix_fallocate(), fake it. First use + ** ftruncate() to set the file size, then write a single byte to + ** the last byte in each block within the extended region. This + ** is the same technique used by glibc to implement posix_fallocate() + ** on systems that do not have a real fallocate() system call. + */ + int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (oldpos == -1) { + return false; + } + + struct stat buf; + int fd = PR_FileDesc2NativeHandle(aFD); + if (fstat(fd, &buf)) { + return false; + } + + if (buf.st_size >= aLength) { + return false; + } + + const int nBlk = buf.st_blksize; + + if (!nBlk) { + return false; + } + + if (ftruncate(fd, aLength)) { + return false; + } + + int nWrite; // Return value from write() + int64_t iWrite = ((buf.st_size + 2 * nBlk - 1) / nBlk) * nBlk - + 1; // Next offset to write to + while (iWrite < aLength) { + nWrite = 0; + if (PR_Seek64(aFD, iWrite, PR_SEEK_SET) == iWrite) { + nWrite = PR_Write(aFD, "", 1); + } + if (nWrite != 1) { + break; + } + iWrite += nBlk; + } + + PR_Seek64(aFD, oldpos, PR_SEEK_SET); + return nWrite == 1; +# else + return false; +# endif +} + +void mozilla::ReadAheadLib(nsIFile* aFile) { +# if defined(XP_WIN) + nsAutoString path; + if (!aFile || NS_FAILED(aFile->GetPath(path))) { + return; + } + ReadAheadLib(path.get()); +# elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + nsAutoCString nativePath; + if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { + return; + } + ReadAheadLib(nativePath.get()); +# endif +} + +void mozilla::ReadAheadFile(nsIFile* aFile, const size_t aOffset, + const size_t aCount, mozilla::filedesc_t* aOutFd) { +# if defined(XP_WIN) + nsAutoString path; + if (!aFile || NS_FAILED(aFile->GetPath(path))) { + return; + } + ReadAheadFile(path.get(), aOffset, aCount, aOutFd); +# elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + nsAutoCString nativePath; + if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { + return; + } + ReadAheadFile(nativePath.get(), aOffset, aCount, aOutFd); +# endif +} + +mozilla::PathString mozilla::GetLibraryName(mozilla::pathstr_t aDirectory, + const char* aLib) { +# ifdef XP_WIN + nsAutoString fullName; + if (aDirectory) { + fullName.Assign(aDirectory); + fullName.Append('\\'); + } + AppendUTF8toUTF16(MakeStringSpan(aLib), fullName); + if (!strstr(aLib, ".dll")) { + fullName.AppendLiteral(".dll"); + } + return std::move(fullName); +# else + char* temp = PR_GetLibraryName(aDirectory, aLib); + if (!temp) { + return ""_ns; + } + nsAutoCString libname(temp); + PR_FreeLibraryName(temp); + return std::move(libname); +# endif +} + +mozilla::PathString mozilla::GetLibraryFilePathname(mozilla::pathstr_t aName, + PRFuncPtr aAddr) { +# ifdef XP_WIN + HMODULE handle = GetModuleHandleW(char16ptr_t(aName)); + if (!handle) { + return u""_ns; + } + + nsAutoString path; + path.SetLength(MAX_PATH); + DWORD len = GetModuleFileNameW(handle, char16ptr_t(path.BeginWriting()), + path.Length()); + if (!len) { + return u""_ns; + } + + path.SetLength(len); + return std::move(path); +# else + char* temp = PR_GetLibraryFilePathname(aName, aAddr); + if (!temp) { + return ""_ns; + } + nsAutoCString path(temp); + PR_Free(temp); // PR_GetLibraryFilePathname() uses PR_Malloc(). + return std::move(path); +# endif +} + +#endif // defined(MOZILLA_INTERNAL_API) + +#if defined(LINUX) && !defined(ANDROID) + +static const unsigned int bufsize = 4096; + +# ifdef __LP64__ +typedef Elf64_Ehdr Elf_Ehdr; +typedef Elf64_Phdr Elf_Phdr; +static const unsigned char ELFCLASS = ELFCLASS64; +typedef Elf64_Off Elf_Off; +# else +typedef Elf32_Ehdr Elf_Ehdr; +typedef Elf32_Phdr Elf_Phdr; +static const unsigned char ELFCLASS = ELFCLASS32; +typedef Elf32_Off Elf_Off; +# endif + +#elif defined(XP_MACOSX) + +# if defined(__i386__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86; +# elif defined(__x86_64__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86_64; +# elif defined(__ppc__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC; +# elif defined(__ppc64__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC64; +# elif defined(__aarch64__) +static const uint32_t CPU_TYPE = CPU_TYPE_ARM64; +# else +# error Unsupported CPU type +# endif + +# ifdef __LP64__ +# undef LC_SEGMENT +# define LC_SEGMENT LC_SEGMENT_64 +# undef MH_MAGIC +# define MH_MAGIC MH_MAGIC_64 +# define cpu_mach_header mach_header_64 +# define segment_command segment_command_64 +# else +# define cpu_mach_header mach_header +# endif + +class ScopedMMap { + public: + explicit ScopedMMap(const char* aFilePath) : buf(nullptr) { + fd = open(aFilePath, O_RDONLY); + if (fd < 0) { + return; + } + struct stat st; + if (fstat(fd, &st) < 0) { + return; + } + size = st.st_size; + buf = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buf == MAP_FAILED) { + buf = nullptr; + } + } + ~ScopedMMap() { + if (buf) { + munmap(buf, size); + } + if (fd >= 0) { + close(fd); + } + } + operator char*() { return buf; } + int getFd() { return fd; } + + private: + int fd; + char* buf; + size_t size; +}; +#endif + +void mozilla::ReadAhead(mozilla::filedesc_t aFd, const size_t aOffset, + const size_t aCount) { +#if defined(XP_WIN) + + LARGE_INTEGER fpOriginal; + LARGE_INTEGER fpOffset; +# if defined(HAVE_LONG_LONG) + fpOffset.QuadPart = 0; +# else + fpOffset.u.LowPart = 0; + fpOffset.u.HighPart = 0; +# endif + + // Get the current file pointer so that we can restore it. This isn't + // really necessary other than to provide the same semantics regarding the + // file pointer that other platforms do + if (!SetFilePointerEx(aFd, fpOffset, &fpOriginal, FILE_CURRENT)) { + return; + } + + if (aOffset) { +# if defined(HAVE_LONG_LONG) + fpOffset.QuadPart = static_cast<LONGLONG>(aOffset); +# else + fpOffset.u.LowPart = aOffset; + fpOffset.u.HighPart = 0; +# endif + + if (!SetFilePointerEx(aFd, fpOffset, nullptr, FILE_BEGIN)) { + return; + } + } + + char buf[64 * 1024]; + size_t totalBytesRead = 0; + DWORD dwBytesRead; + // Do dummy reads to trigger kernel-side readhead via + // FILE_FLAG_SEQUENTIAL_SCAN. Abort when underfilling because during testing + // the buffers are read fully A buffer that's not keeping up would imply that + // readahead isn't working right + while (totalBytesRead < aCount && + ReadFile(aFd, buf, sizeof(buf), &dwBytesRead, nullptr) && + dwBytesRead == sizeof(buf)) { + totalBytesRead += dwBytesRead; + } + + // Restore the file pointer + SetFilePointerEx(aFd, fpOriginal, nullptr, FILE_BEGIN); + +#elif defined(LINUX) && !defined(ANDROID) + + readahead(aFd, aOffset, aCount); + +#elif defined(XP_MACOSX) + + struct radvisory ra; + ra.ra_offset = aOffset; + ra.ra_count = aCount; + // The F_RDADVISE fcntl is equivalent to Linux' readahead() system call. + fcntl(aFd, F_RDADVISE, &ra); + +#endif +} + +void mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath) { + if (!aFilePath) { + return; + } + +#ifdef XP_WIN + auto WideToUTF8 = [](const wchar_t* aStr) -> std::string { + std::string s; + // Determine the number of output bytes (including null terminator). + const int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, -1, nullptr, 0, + nullptr, nullptr); + if (numConv == 0) { + return s; + } + s.resize(numConv); + const int numConvd = ::WideCharToMultiByte(CP_UTF8, 0, aStr, -1, s.data(), + numConv, nullptr, nullptr); + if (numConvd != numConv) { + // Error during conversion, remove any temporary data. + s.clear(); + } + return s; + }; +#endif + + AUTO_BASE_PROFILER_MARKER_TEXT("ReadAheadLib", OTHER, {}, +#ifdef XP_WIN + WideToUTF8(aFilePath) +#else + aFilePath +#endif + ); + +#if defined(XP_WIN) + if (!CanPrefetchMemory()) { + ReadAheadFile(aFilePath); + return; + } + nsAutoHandle fd(CreateFileW(aFilePath, GENERIC_READ | GENERIC_EXECUTE, + FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, nullptr)); + if (!fd) { + return; + } + + nsAutoHandle mapping(CreateFileMapping( + fd, nullptr, SEC_IMAGE | PAGE_EXECUTE_READ, 0, 0, nullptr)); + if (!mapping) { + return; + } + + PVOID data = MapViewOfFile( + mapping, FILE_MAP_READ | FILE_MAP_EXECUTE | SEC_IMAGE, 0, 0, 0); + if (!data) { + return; + } + auto guard = MakeScopeExit([=]() { UnmapViewOfFile(data); }); + mozilla::nt::PEHeaders headers(data); + Maybe<Span<const uint8_t>> bounds = headers.GetBounds(); + if (!bounds) { + return; + } + + PrefetchMemory((uint8_t*)data, bounds->Length()); + +#elif defined(LINUX) && !defined(ANDROID) + int fd = open(aFilePath, O_RDONLY); + if (fd < 0) { + return; + } + + union { + char buf[bufsize]; + Elf_Ehdr ehdr; + } elf; + // Read ELF header (ehdr) and program header table (phdr). + // We check that the ELF magic is found, that the ELF class matches + // our own, and that the program header table as defined in the ELF + // headers fits in the buffer we read. + if ((read(fd, elf.buf, bufsize) <= 0) || (memcmp(elf.buf, ELFMAG, 4)) || + (elf.ehdr.e_ident[EI_CLASS] != ELFCLASS) || + // Upcast e_phentsize so the multiplication is done in the same precision + // as the subsequent addition, to satisfy static analyzers and avoid + // issues with abnormally large program header tables. + (elf.ehdr.e_phoff + + (static_cast<Elf_Off>(elf.ehdr.e_phentsize) * elf.ehdr.e_phnum) >= + bufsize)) { + close(fd); + return; + } + // The program header table contains segment definitions. One such + // segment type is PT_LOAD, which describes how the dynamic loader + // is going to map the file in memory. We use that information to + // find the biggest offset from the library that will be mapped in + // memory. + Elf_Phdr* phdr = (Elf_Phdr*)&elf.buf[elf.ehdr.e_phoff]; + Elf_Off end = 0; + for (int phnum = elf.ehdr.e_phnum; phnum; phdr++, phnum--) { + if ((phdr->p_type == PT_LOAD) && (end < phdr->p_offset + phdr->p_filesz)) { + end = phdr->p_offset + phdr->p_filesz; + } + } + // Let the kernel read ahead what the dynamic loader is going to + // map in memory soon after. + if (end > 0) { + ReadAhead(fd, 0, end); + } + close(fd); +#elif defined(XP_MACOSX) + ScopedMMap buf(aFilePath); + char* base = buf; + if (!base) { + return; + } + + // An OSX binary might either be a fat (universal) binary or a + // Mach-O binary. A fat binary actually embeds several Mach-O + // binaries. If we have a fat binary, find the offset where the + // Mach-O binary for our CPU type can be found. + struct fat_header* fh = (struct fat_header*)base; + + if (OSSwapBigToHostInt32(fh->magic) == FAT_MAGIC) { + uint32_t nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); + struct fat_arch* arch = (struct fat_arch*)&buf[sizeof(struct fat_header)]; + for (; nfat_arch; arch++, nfat_arch--) { + if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE) { + base += OSSwapBigToHostInt32(arch->offset); + break; + } + } + if (base == buf) { + return; + } + } + + // Check Mach-O magic in the Mach header + struct cpu_mach_header* mh = (struct cpu_mach_header*)base; + if (mh->magic != MH_MAGIC) { + return; + } + + // The Mach header is followed by a sequence of load commands. + // Each command has a header containing the command type and the + // command size. LD_SEGMENT commands describes how the dynamic + // loader is going to map the file in memory. We use that + // information to find the biggest offset from the library that + // will be mapped in memory. + char* cmd = &base[sizeof(struct cpu_mach_header)]; + uint32_t end = 0; + for (uint32_t ncmds = mh->ncmds; ncmds; ncmds--) { + struct segment_command* sh = (struct segment_command*)cmd; + if (sh->cmd != LC_SEGMENT) { + continue; + } + if (end < sh->fileoff + sh->filesize) { + end = sh->fileoff + sh->filesize; + } + cmd += sh->cmdsize; + } + // Let the kernel read ahead what the dynamic loader is going to + // map in memory soon after. + if (end > 0) { + ReadAhead(buf.getFd(), base - buf, end); + } +#endif +} + +void mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset, + const size_t aCount, mozilla::filedesc_t* aOutFd) { +#if defined(XP_WIN) + if (!aFilePath) { + if (aOutFd) { + *aOutFd = INVALID_HANDLE_VALUE; + } + return; + } + HANDLE fd = CreateFileW(aFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + if (aOutFd) { + *aOutFd = fd; + } + if (fd == INVALID_HANDLE_VALUE) { + return; + } + ReadAhead(fd, aOffset, aCount); + if (!aOutFd) { + CloseHandle(fd); + } +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + if (!aFilePath) { + if (aOutFd) { + *aOutFd = -1; + } + return; + } + int fd = open(aFilePath, O_RDONLY); + if (aOutFd) { + *aOutFd = fd; + } + if (fd < 0) { + return; + } + size_t count; + if (aCount == SIZE_MAX) { + struct stat st; + if (fstat(fd, &st) < 0) { + if (!aOutFd) { + close(fd); + } + return; + } + count = st.st_size; + } else { + count = aCount; + } + ReadAhead(fd, aOffset, count); + if (!aOutFd) { + close(fd); + } +#endif +} diff --git a/xpcom/glue/FileUtils.h b/xpcom/glue/FileUtils.h new file mode 100644 index 0000000000..8e66ee5860 --- /dev/null +++ b/xpcom/glue/FileUtils.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_FileUtils_h +#define mozilla_FileUtils_h + +#include "nscore.h" // nullptr + +#if defined(XP_UNIX) +# include <unistd.h> +#elif defined(XP_WIN) +# include <io.h> +#endif +#include "prio.h" +#include "prlink.h" + +#include <memory> // unique_ptr +#include "nsIFile.h" +#include <errno.h> +#include <limits.h> + +namespace mozilla { + +#if defined(XP_WIN) +typedef void* filedesc_t; +typedef const wchar_t* pathstr_t; +#else +typedef int filedesc_t; +typedef const char* pathstr_t; +#endif + +#if defined(MOZILLA_INTERNAL_API) + +struct PRCloseDeleter { + void operator()(PRFileDesc* aFd) { + if (aFd) { + PR_Close(aFd); + } + } +}; +using AutoFDClose = UniquePtr<PRFileDesc, PRCloseDeleter>; + +/* RAII wrapper for FILE descriptors */ +struct FCloseDeleter { + void operator()(FILE* p) { + if (p) { + fclose(p); + } + } +}; +using ScopedCloseFile = UniquePtr<FILE, FCloseDeleter>; + +/** + * Fallocate efficiently and continuously allocates files via fallocate-type + * APIs. This is useful for avoiding fragmentation. On sucess the file be padded + * with zeros to grow to aLength. + * + * @param aFD file descriptor. + * @param aLength length of file to grow to. + * @return true on success. + */ +bool fallocate(PRFileDesc* aFD, int64_t aLength); + +/** + * Use readahead to preload shared libraries into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFile nsIFile representing path to shared library + */ +void ReadAheadLib(nsIFile* aFile); + +/** + * Use readahead to preload a file into the file cache before reading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFile nsIFile representing path to shared library + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + * @param aOutFd Pointer to file descriptor. If specified, ReadAheadFile will + * return its internal, opened file descriptor instead of closing it. + */ +void ReadAheadFile(nsIFile* aFile, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX, + filedesc_t* aOutFd = nullptr); + +/* + * Wrappers for PR_GetLibraryName and PR_GetLibraryFilePathname. + */ +PathString GetLibraryName(pathstr_t aDirectory, const char* aLib); +PathString GetLibraryFilePathname(pathstr_t aName, PRFuncPtr aAddr); + +#endif // MOZILLA_INTERNAL_API + +/** + * Use readahead to preload shared libraries into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFilePath path to shared library + */ +void ReadAheadLib(pathstr_t aFilePath); + +/** + * Use readahead to preload a file into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFilePath path to shared library + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + * @param aOutFd Pointer to file descriptor. If specified, ReadAheadFile will + * return its internal, opened file descriptor instead of closing it. + */ +void ReadAheadFile(pathstr_t aFilePath, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX, + filedesc_t* aOutFd = nullptr); + +/** + * Use readahead to preload a file into the file cache before reading. + * When this function exits, the file pointer is guaranteed to be in the same + * position it was in before this function was called. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFd file descriptor opened for read access + * (on Windows, file must be opened with FILE_FLAG_SEQUENTIAL_SCAN) + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + */ +void ReadAhead(filedesc_t aFd, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX); + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/MemUtils.cpp b/xpcom/glue/MemUtils.cpp new file mode 100644 index 0000000000..402568383c --- /dev/null +++ b/xpcom/glue/MemUtils.cpp @@ -0,0 +1,60 @@ +/* -*- 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/MemUtils.h" + +#if defined(XP_WIN) +# include <windows.h> +# include "mozilla/Maybe.h" +#else +# include <sys/mman.h> +#endif + +#if defined(XP_WIN) +typedef BOOL(WINAPI* PrefetchVirtualMemoryFn)(HANDLE, ULONG_PTR, PVOID, ULONG); + +static mozilla::Maybe<PrefetchVirtualMemoryFn> sPrefetchVirtualMemory; + +void MaybeInitPrefetchVirtualMemory() { + if (sPrefetchVirtualMemory.isNothing()) { + sPrefetchVirtualMemory.emplace( + reinterpret_cast<PrefetchVirtualMemoryFn>(GetProcAddress( + GetModuleHandleW(L"kernel32.dll"), "PrefetchVirtualMemory"))); + } +} +#endif + +bool mozilla::CanPrefetchMemory() { +#if defined(XP_SOLARIS) || defined(XP_UNIX) + return true; +#elif defined(XP_WIN) + MaybeInitPrefetchVirtualMemory(); + return *sPrefetchVirtualMemory; +#else + return false; +#endif +} + +void mozilla::PrefetchMemory(uint8_t* aStart, size_t aNumBytes) { + if (aNumBytes == 0) { + return; + } + +#if defined(XP_SOLARIS) + posix_madvise(aStart, aNumBytes, POSIX_MADV_WILLNEED); +#elif defined(XP_UNIX) + madvise(aStart, aNumBytes, MADV_WILLNEED); +#elif defined(XP_WIN) + MaybeInitPrefetchVirtualMemory(); + if (*sPrefetchVirtualMemory) { + WIN32_MEMORY_RANGE_ENTRY entry; + entry.VirtualAddress = aStart; + entry.NumberOfBytes = aNumBytes; + (*sPrefetchVirtualMemory)(GetCurrentProcess(), 1, &entry, 0); + return; + } +#endif +} diff --git a/xpcom/glue/MemUtils.h b/xpcom/glue/MemUtils.h new file mode 100644 index 0000000000..606df35d09 --- /dev/null +++ b/xpcom/glue/MemUtils.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_MemUtils_h +#define mozilla_MemUtils_h + +#include <stddef.h> +#include <stdint.h> + +namespace mozilla { + +bool CanPrefetchMemory(); +void PrefetchMemory(uint8_t* aStart, size_t aNumBytes); + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/XREAppData.cpp b/xpcom/glue/XREAppData.cpp new file mode 100644 index 0000000000..82084d3b5f --- /dev/null +++ b/xpcom/glue/XREAppData.cpp @@ -0,0 +1,60 @@ +/* -*- 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/XREAppData.h" +#include "nsCRTGlue.h" + +namespace mozilla { + +XREAppData& XREAppData::operator=(const StaticXREAppData& aOther) { + vendor = aOther.vendor; + name = aOther.name; + remotingName = aOther.remotingName; + version = aOther.version; + buildID = aOther.buildID; + ID = aOther.ID; + copyright = aOther.copyright; + flags = aOther.flags; + minVersion = aOther.minVersion; + maxVersion = aOther.maxVersion; + crashReporterURL = aOther.crashReporterURL; + profile = aOther.profile; + UAName = aOther.UAName; + sourceURL = aOther.sourceURL; + updateURL = aOther.updateURL; + + return *this; +} + +XREAppData& XREAppData::operator=(const XREAppData& aOther) = default; + +void XREAppData::SanitizeNameForDBus(nsACString& aName) { + auto IsValidDBusNameChar = [](char aChar) { + return IsAsciiAlpha(aChar) || IsAsciiDigit(aChar) || aChar == '_'; + }; + + // D-Bus names can contain only [a-z][A-Z][0-9]_, so we replace all characters + // that aren't in that range with underscores. + char* cur = aName.BeginWriting(); + char* end = aName.EndWriting(); + for (; cur != end; cur++) { + if (!IsValidDBusNameChar(*cur)) { + *cur = '_'; + } + } +} + +void XREAppData::GetDBusAppName(nsACString& aName) const { + const char* env = getenv("MOZ_DBUS_APP_NAME"); + if (env) { + aName.Assign(env); + } else { + aName.Assign(remotingName); + SanitizeNameForDBus(aName); + } +} + +} // namespace mozilla diff --git a/xpcom/glue/moz.build b/xpcom/glue/moz.build new file mode 100644 index 0000000000..29c75e7241 --- /dev/null +++ b/xpcom/glue/moz.build @@ -0,0 +1,12 @@ +# -*- 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/. + +DIRS += ["standalone"] + +EXPORTS.mozilla += [ + "FileUtils.h", + "MemUtils.h", +] diff --git a/xpcom/glue/objs.mozbuild b/xpcom/glue/objs.mozbuild new file mode 100644 index 0000000000..709fd0c121 --- /dev/null +++ b/xpcom/glue/objs.mozbuild @@ -0,0 +1,17 @@ +# -*- 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/. + +xpcom_glue_src_lcppsrcs = [ + "FileUtils.cpp", + "MemUtils.cpp", + "XREAppData.cpp", +] + +xpcom_glue_src_cppsrcs = ["/xpcom/glue/%s" % s for s in xpcom_glue_src_lcppsrcs] + +xpcom_gluens_src_lcppsrcs = [] + +xpcom_gluens_src_cppsrcs = ["/xpcom/glue/%s" % s for s in xpcom_gluens_src_lcppsrcs] diff --git a/xpcom/glue/standalone/moz.build b/xpcom/glue/standalone/moz.build new file mode 100644 index 0000000000..bf4cce5daf --- /dev/null +++ b/xpcom/glue/standalone/moz.build @@ -0,0 +1,36 @@ +# -*- 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/. + +SOURCES += [ + "../FileUtils.cpp", + "../MemUtils.cpp", + "nsXPCOMGlue.cpp", +] + +Library("xpcomglue") + +FORCE_STATIC_LIB = True + +if CONFIG["CC_TYPE"] == "clang-cl": + DEFINES["_USE_ANSI_CPP"] = True + # Don't include directives about which CRT to use + CFLAGS += ["-Zl"] + CXXFLAGS += ["-Zl"] + +DEFINES["XPCOM_GLUE"] = True + +LOCAL_INCLUDES += [ + "../../build", + "../../threads", +] + +# Don't use STL wrappers here (i.e. wrapped <new>); they require mozalloc +DisableStlWrapping() + +DIST_INSTALL = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["GLIB_CFLAGS"] diff --git a/xpcom/glue/standalone/nsXPCOMGlue.cpp b/xpcom/glue/standalone/nsXPCOMGlue.cpp new file mode 100644 index 0000000000..e8d48193e2 --- /dev/null +++ b/xpcom/glue/standalone/nsXPCOMGlue.cpp @@ -0,0 +1,407 @@ +/* -*- 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/Bootstrap.h" + +#include "nsXPCOMPrivate.h" +#include <stdlib.h> +#include <stdio.h> + +#include "mozilla/FileUtils.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Try.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" + +using namespace mozilla; + +#define XPCOM_DEPENDENT_LIBS_LIST "dependentlibs.list" + +#if defined(XP_WIN) +# define READ_TEXTMODE L"rt" +#else +# define READ_TEXTMODE "r" +#endif + +typedef void (*NSFuncPtr)(); + +#if defined(XP_WIN) +# include <windows.h> +using LibHandleType = HMODULE; +#else +using LibHandleType = void*; +#endif + +using LibHandleResult = ::mozilla::Result<LibHandleType, DLErrorType>; + +#if defined(XP_WIN) +# include <mbstring.h> +# include "mozilla/PreXULSkeletonUI.h" + +static LibHandleResult GetLibHandle(pathstr_t aDependentLib) { + LibHandleType libHandle = + LoadLibraryExW(aDependentLib, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + + if (!libHandle) { + DWORD err = GetLastError(); +# if defined(DEBUG) + LPWSTR lpMsgBuf; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&lpMsgBuf, 0, nullptr); + wprintf(L"Error loading %ls: %s\n", aDependentLib, lpMsgBuf); + LocalFree(lpMsgBuf); +# endif // defined(DEBUG) + return Err(err); + } + + return libHandle; +} + +static NSFuncPtr GetSymbol(LibHandleType aLibHandle, const char* aSymbol) { + return (NSFuncPtr)GetProcAddress(aLibHandle, aSymbol); +} + +static void CloseLibHandle(LibHandleType aLibHandle) { + FreeLibrary(aLibHandle); +} + +#else +# include <dlfcn.h> + +# if defined(MOZ_LINKER) +extern "C" { +NS_HIDDEN __typeof(dlopen) __wrap_dlopen; +NS_HIDDEN __typeof(dlsym) __wrap_dlsym; +NS_HIDDEN __typeof(dlclose) __wrap_dlclose; +} + +# define dlopen __wrap_dlopen +# define dlsym __wrap_dlsym +# define dlclose __wrap_dlclose +# endif + +static LibHandleResult GetLibHandle(pathstr_t aDependentLib) { + LibHandleType libHandle = dlopen(aDependentLib, RTLD_GLOBAL | RTLD_LAZY +# ifdef XP_MACOSX + | RTLD_FIRST +# endif + ); + if (!libHandle) { + UniqueFreePtr<char> errMsg(strdup(dlerror())); + fprintf(stderr, "XPCOMGlueLoad error for file %s:\n%s\n", aDependentLib, + errMsg.get()); + return Err(std::move(errMsg)); + } + return libHandle; +} + +static NSFuncPtr GetSymbol(LibHandleType aLibHandle, const char* aSymbol) { + return (NSFuncPtr)dlsym(aLibHandle, aSymbol); +} + +# if !defined(MOZ_LINKER) && !defined(__ANDROID__) +static void CloseLibHandle(LibHandleType aLibHandle) { dlclose(aLibHandle); } +# endif +#endif + +struct DependentLib { + LibHandleType libHandle; + DependentLib* next; +}; + +static DependentLib* sTop; + +static void AppendDependentLib(LibHandleType aLibHandle) { + auto* d = new DependentLib; + if (!d) { + return; + } + + d->next = sTop; + d->libHandle = aLibHandle; + + sTop = d; +} + +using ReadDependentCBResult = ::mozilla::Result<::mozilla::Ok, DLErrorType>; + +static ReadDependentCBResult ReadDependentCB( + pathstr_t aDependentLib, LibLoadingStrategy aLibLoadingStrategy) { +#if !defined(MOZ_LINKER) && !defined(__ANDROID__) + // Don't bother doing a ReadAhead if we're not in the parent process. + // What we need from the library should already be in the system file + // cache. + if (aLibLoadingStrategy == LibLoadingStrategy::ReadAhead) { + ReadAheadLib(aDependentLib); + } +#endif + LibHandleType libHandle; + MOZ_TRY_VAR(libHandle, GetLibHandle(aDependentLib)); + + AppendDependentLib(libHandle); + return Ok(); +} + +#ifdef XP_WIN +static ReadDependentCBResult ReadDependentCB( + const char* aDependentLib, LibLoadingStrategy aLibLoadingStrategy) { + wchar_t wideDependentLib[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, aDependentLib, -1, wideDependentLib, + MAX_PATH); + return ReadDependentCB(wideDependentLib, aLibLoadingStrategy); +} + +inline FILE* TS_tfopen(const char* path, const wchar_t* mode) { + wchar_t wPath[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wPath, MAX_PATH); + return _wfopen(wPath, mode); +} +#else +inline FILE* TS_tfopen(const char* aPath, const char* aMode) { + return fopen(aPath, aMode); +} +#endif + +#if !defined(MOZ_LINKER) && !defined(__ANDROID__) +static void XPCOMGlueUnload() { + while (sTop) { + CloseLibHandle(sTop->libHandle); + + DependentLib* temp = sTop; + sTop = sTop->next; + + delete temp; + } +} +#endif + +#if defined(XP_WIN) +// like strpbrk but finds the *last* char, not the first +static const char* ns_strrpbrk(const char* string, const char* strCharSet) { + const char* found = nullptr; + for (; *string; ++string) { + for (const char* search = strCharSet; *search; ++search) { + if (*search == *string) { + found = string; + // Since we're looking for the last char, we save "found" + // until we're at the end of the string. + } + } + } + + return found; +} +#endif + +using XPCOMGlueLoadError = BootstrapError; +using XPCOMGlueLoadResult = + ::mozilla::Result<::mozilla::Ok, XPCOMGlueLoadError>; + +static XPCOMGlueLoadResult XPCOMGlueLoad( + const char* aXPCOMFile, LibLoadingStrategy aLibLoadingStrategy) { +#if defined(MOZ_LINKER) || defined(__ANDROID__) + ReadDependentCBResult readDependentCBResult = + ReadDependentCB(aXPCOMFile, aLibLoadingStrategy); + if (readDependentCBResult.isErr()) { + return Err(AsVariant(readDependentCBResult.unwrapErr())); + } +#else + char xpcomDir[MAXPATHLEN]; +# ifdef XP_WIN + const char* lastSlash = ns_strrpbrk(aXPCOMFile, "/\\"); +# elif XP_MACOSX + // On OSX, the dependentlibs.list file lives under Contents/Resources. + // However, the actual libraries listed in dependentlibs.list live under + // Contents/MacOS. We want to read the list from Contents/Resources, then + // load the libraries from Contents/MacOS. + const char* tempSlash = strrchr(aXPCOMFile, '/'); + size_t tempLen = size_t(tempSlash - aXPCOMFile); + if (tempLen > MAXPATHLEN) { + return Err(AsVariant(NS_ERROR_FAILURE)); + } + char tempBuffer[MAXPATHLEN]; + memcpy(tempBuffer, aXPCOMFile, tempLen); + tempBuffer[tempLen] = '\0'; + const char* slash = strrchr(tempBuffer, '/'); + tempLen = size_t(slash - tempBuffer); + const char* lastSlash = aXPCOMFile + tempLen; +# else + const char* lastSlash = strrchr(aXPCOMFile, '/'); +# endif + char* cursor; + if (lastSlash) { + size_t len = size_t(lastSlash - aXPCOMFile); + + if (len > MAXPATHLEN - sizeof(XPCOM_FILE_PATH_SEPARATOR +# ifdef XP_MACOSX + "Resources" XPCOM_FILE_PATH_SEPARATOR +# endif + XPCOM_DEPENDENT_LIBS_LIST)) { + return Err(AsVariant(NS_ERROR_FAILURE)); + } + memcpy(xpcomDir, aXPCOMFile, len); + strcpy(xpcomDir + len, XPCOM_FILE_PATH_SEPARATOR +# ifdef XP_MACOSX + "Resources" XPCOM_FILE_PATH_SEPARATOR +# endif + XPCOM_DEPENDENT_LIBS_LIST); + cursor = xpcomDir + len + 1; + } else { + strcpy(xpcomDir, XPCOM_DEPENDENT_LIBS_LIST); + cursor = xpcomDir; + } + + if (getenv("MOZ_RUN_GTEST") +# ifdef FUZZING + || getenv("FUZZER") +# endif + ) { + strcat(xpcomDir, ".gtest"); + } + + const auto flist = TS_tfopen(xpcomDir, READ_TEXTMODE); + const auto cleanup = MakeScopeExit([&]() { + if (flist) { + fclose(flist); + } + }); + if (!flist) { + return Err(AsVariant(NS_ERROR_FAILURE)); + } + +# ifdef XP_MACOSX + tempLen = size_t(cursor - xpcomDir); + if (tempLen > MAXPATHLEN - sizeof("MacOS" XPCOM_FILE_PATH_SEPARATOR) - 1) { + return Err(AsVariant(NS_ERROR_FAILURE)); + } + strcpy(cursor, "MacOS" XPCOM_FILE_PATH_SEPARATOR); + cursor += strlen(cursor); +# endif + *cursor = '\0'; + + char buffer[MAXPATHLEN]; + + while (fgets(buffer, sizeof(buffer), flist)) { + int l = strlen(buffer); + + // ignore empty lines and comments + if (l == 0 || *buffer == '#') { + continue; + } + + // cut the trailing newline, if present + if (buffer[l - 1] == '\n') { + buffer[l - 1] = '\0'; + } + + if (l + size_t(cursor - xpcomDir) > MAXPATHLEN) { + return Err(AsVariant(NS_ERROR_FAILURE)); + } + + strcpy(cursor, buffer); + ReadDependentCBResult readDependentCBResult = + ReadDependentCB(xpcomDir, aLibLoadingStrategy); + if (readDependentCBResult.isErr()) { + XPCOMGlueUnload(); + return Err(AsVariant(readDependentCBResult.unwrapErr())); + } + +# ifdef XP_WIN + // We call PollPreXULSkeletonUIEvents here in order to not get flagged by + // Windows as nonresponsive. In order to not be flagged as such, we seem to + // simply need to respond to *a* message every few seconds. The halfway + // point on slow systems between process start and nsWindow taking over the + // skeleton UI window seems to be XUL being loaded. Accordingly, placing + // this call here covers the most ground (as we will call this after + // prefetching and loading all of the dlls in dependentlibs.list, which + // includes xul.dll.) + PollPreXULSkeletonUIEvents(); +# endif + } +#endif + return Ok(); +} + +#if defined(MOZ_WIDGET_GTK) && \ + (defined(MOZ_MEMORY) || defined(__FreeBSD__) || defined(__NetBSD__)) +# define MOZ_GSLICE_INIT +#endif + +#ifdef MOZ_GSLICE_INIT +# include <glib.h> + +class GSliceInit { + public: + GSliceInit() { + mHadGSlice = bool(getenv("G_SLICE")); + if (!mHadGSlice) { + // Disable the slice allocator, since jemalloc already uses similar layout + // algorithms, and using a sub-allocator tends to increase fragmentation. + // This must be done before g_thread_init() is called. + // glib >= 2.36 initializes g_slice as a side effect of its various static + // initializers, so this needs to happen before glib is loaded, which is + // this is hooked in XPCOMGlueStartup before libxul is loaded. This + // relies on the main executable not depending on glib. + setenv("G_SLICE", "always-malloc", 1); + } + } + + ~GSliceInit() { + if (!mHadGSlice) { + unsetenv("G_SLICE"); + } + } + + private: + bool mHadGSlice; +}; +#endif + +namespace mozilla { + +BootstrapResult GetBootstrap(const char* aXPCOMFile, + LibLoadingStrategy aLibLoadingStrategy) { +#ifdef MOZ_GSLICE_INIT + GSliceInit gSliceInit; +#endif + + if (!aXPCOMFile) { + return Err(AsVariant(NS_ERROR_INVALID_ARG)); + } + + char* lastSlash = + strrchr(const_cast<char*>(aXPCOMFile), XPCOM_FILE_PATH_SEPARATOR[0]); + if (!lastSlash) { + return Err(AsVariant(NS_ERROR_FILE_INVALID_PATH)); + } + + size_t base_len = size_t(lastSlash - aXPCOMFile) + 1; + + UniqueFreePtr<char> file( + reinterpret_cast<char*>(malloc(base_len + sizeof(XPCOM_DLL)))); + memcpy(file.get(), aXPCOMFile, base_len); + memcpy(file.get() + base_len, XPCOM_DLL, sizeof(XPCOM_DLL)); + + MOZ_TRY(XPCOMGlueLoad(file.get(), aLibLoadingStrategy)); + + if (!sTop) { + return Err(AsVariant(NS_ERROR_NOT_AVAILABLE)); + } + GetBootstrapType func = + (GetBootstrapType)GetSymbol(sTop->libHandle, "XRE_GetBootstrap"); + if (!func) { + return Err(AsVariant(NS_ERROR_NOT_AVAILABLE)); + } + + Bootstrap::UniquePtr b; + (*func)(b); + + return b; +} + +} // namespace mozilla diff --git a/xpcom/idl-parser/setup.py b/xpcom/idl-parser/setup.py new file mode 100644 index 0000000000..af6b417cb7 --- /dev/null +++ b/xpcom/idl-parser/setup.py @@ -0,0 +1,18 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from setuptools import find_packages, setup + +setup( + name="xpidl", + version="1.0", + description="Parser and header generator for xpidl files.", + author="Mozilla Foundation", + license="MPL 2.0", + packages=find_packages(), + install_requires=["ply>=3.6,<4.0"], + url="https://github.com/pelmers/xpidl", + entry_points={"console_scripts": ["header.py = xpidl.header:main"]}, + keywords=["xpidl", "parser"], +) diff --git a/xpcom/idl-parser/xpidl/__init__.py b/xpcom/idl-parser/xpidl/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/xpcom/idl-parser/xpidl/__init__.py diff --git a/xpcom/idl-parser/xpidl/header.py b/xpcom/idl-parser/xpidl/header.py new file mode 100644 index 0000000000..ed179b1bad --- /dev/null +++ b/xpcom/idl-parser/xpidl/header.py @@ -0,0 +1,723 @@ +#!/usr/bin/env python +# header.py - Generate C++ header files from IDL. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Print a C++ header file for the IDL files specified on the command line""" + +import itertools +import os.path +import re + +from xpidl import xpidl + +printdoccomments = False + +if printdoccomments: + + def printComments(fd, clist, indent): + for c in clist: + fd.write("%s%s\n" % (indent, c)) + +else: + + def printComments(fd, clist, indent): + pass + + +def firstCap(str): + return str[0].upper() + str[1:] + + +def attributeParamName(a): + return "a" + firstCap(a.name) + + +def attributeParamNames(a, getter, return_param=True): + if getter and (a.notxpcom or not return_param): + l = [] + else: + l = [attributeParamName(a)] + if a.implicit_jscontext: + l.insert(0, "cx") + return ", ".join(l) + + +def attributeNativeName(a, getter): + binaryname = a.binaryname is not None and a.binaryname or firstCap(a.name) + return "%s%s" % (getter and "Get" or "Set", binaryname) + + +def attributeAttributes(a, getter): + ret = "" + + if a.must_use: + ret = "[[nodiscard]] " + ret + + # Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not + # builtinclass" case too, so we'd just have memberCanRunScript() check + # explicit_setter_can_run_script/explicit_setter_can_run_script and call it + # here. But that would likely require a fair amount of Gecko-side + # annotation work. See bug 1534292. + if (a.explicit_getter_can_run_script and getter) or ( + a.explicit_setter_can_run_script and not getter + ): + ret = "MOZ_CAN_RUN_SCRIPT " + ret + + return ret + + +def attributeReturnType(a, getter, macro): + """macro should be NS_IMETHOD or NS_IMETHODIMP""" + # Pick the type to be returned from the getter/setter. + if a.notxpcom: + ret = a.realtype.nativeType("in").strip() if getter else "void" + else: + ret = "nsresult" + + # Set calling convention and virtual-ness + if a.nostdcall: + if macro == "NS_IMETHOD": + # This is the declaration. + ret = "virtual %s" % ret + else: + if ret == "nsresult": + ret = macro + else: + ret = "%s_(%s)" % (macro, ret) + + return attributeAttributes(a, getter) + ret + + +def attributeParamlist(a, getter, return_param=True): + if getter and (a.notxpcom or not return_param): + l = [] + else: + l = [ + "%s%s" + % (a.realtype.nativeType(getter and "out" or "in"), attributeParamName(a)) + ] + if a.implicit_jscontext: + l.insert(0, "JSContext* cx") + + return ", ".join(l) + + +def attributeAsNative(a, getter, declType="NS_IMETHOD"): + params = { + "returntype": attributeReturnType(a, getter, declType), + "binaryname": attributeNativeName(a, getter), + "paramlist": attributeParamlist(a, getter), + } + return "%(returntype)s %(binaryname)s(%(paramlist)s)" % params + + +def methodNativeName(m): + return m.binaryname is not None and m.binaryname or firstCap(m.name) + + +def methodAttributes(m): + ret = "" + + if m.must_use: + ret = "[[nodiscard]] " + ret + + # Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not + # builtinclass" case too, so we'd just have memberCanRunScript() check + # explicit_can_run_script and call it here. But that would likely require + # a fair amount of Gecko-side annotation work. See bug 1534292. + if m.explicit_can_run_script: + ret = "MOZ_CAN_RUN_SCRIPT " + ret + + return ret + + +def methodReturnType(m, macro): + """macro should be NS_IMETHOD or NS_IMETHODIMP""" + if m.notxpcom: + ret = m.realtype.nativeType("in").strip() + else: + ret = "nsresult" + + # Set calling convention and virtual-ness + if m.nostdcall: + if macro == "NS_IMETHOD": + # This is the declaration + ret = "virtual %s" % ret + else: + if ret == "nsresult": + ret = macro + else: + ret = "%s_(%s)" % (macro, ret) + + return methodAttributes(m) + ret + + +def methodAsNative(m, declType="NS_IMETHOD"): + return "%s %s(%s)" % ( + methodReturnType(m, declType), + methodNativeName(m), + paramlistAsNative(m), + ) + + +def paramlistAsNative(m, empty="void", return_param=True): + l = [paramAsNative(p) for p in m.params] + + if m.implicit_jscontext: + l.append("JSContext* cx") + + if m.optional_argc: + l.append("uint8_t _argc") + + if not m.notxpcom and m.realtype.name != "void" and return_param: + l.append( + paramAsNative( + xpidl.Param( + paramtype="out", + type=None, + name="_retval", + attlist=[], + location=None, + realtype=m.realtype, + ) + ) + ) + + # Set any optional out params to default to nullptr. Skip if we just added + # extra non-optional args to l. + if len(l) == len(m.params): + paramIter = len(m.params) - 1 + while ( + paramIter >= 0 + and m.params[paramIter].optional + and "out" in m.params[paramIter].paramtype + ): + t = m.params[paramIter].type + # Strings can't be optional, so this shouldn't happen, but let's make sure: + if t == "AString" or t == "ACString" or t == "AUTF8String": + break + l[paramIter] += " = nullptr" + paramIter -= 1 + + if len(l) == 0: + return empty + + return ", ".join(l) + + +def memberCanRunScript(member): + # This can only happen if the member is scriptable and its interface is not builtinclass. + return member.isScriptable() and not member.iface.attributes.builtinclass + + +def runScriptAnnotation(member): + return "JS_HAZ_CAN_RUN_SCRIPT " if memberCanRunScript(member) else "" + + +def paramAsNative(p): + default_spec = "" + if p.default_value: + default_spec = " = " + p.default_value + return "%s%s%s" % (p.nativeType(), p.name, default_spec) + + +def paramlistNames(m, return_param=True): + names = [p.name for p in m.params] + + if m.implicit_jscontext: + names.append("cx") + + if m.optional_argc: + names.append("_argc") + + if not m.notxpcom and m.realtype.name != "void" and return_param: + names.append("_retval") + + if len(names) == 0: + return "" + return ", ".join(names) + + +header = """/* + * DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s + */ + +#ifndef __gen_%(basename)s_h__ +#define __gen_%(basename)s_h__ +""" + +include = """ +#include "%(basename)s.h" +""" + +jsvalue_include = """ +#include "js/Value.h" +""" + +infallible_includes = """ +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +""" + +can_run_script_includes = """ +#include "js/GCAnnotations.h" +""" + +header_end = """/* For IDL files that don't want to include root IDL files. */ +#ifndef NS_NO_VTABLE +#define NS_NO_VTABLE +#endif +""" + +footer = """ +#endif /* __gen_%(basename)s_h__ */ +""" + +forward_decl = """class %(name)s; /* forward declaration */ + +""" + + +def idl_basename(f): + """returns the base name of a file with the last extension stripped""" + return os.path.basename(f).rpartition(".")[0] + + +def print_header(idl, fd, filename, relpath): + fd.write(header % {"relpath": relpath, "basename": idl_basename(filename)}) + + foundinc = False + for inc in idl.includes(): + if not foundinc: + foundinc = True + fd.write("\n") + fd.write(include % {"basename": idl_basename(inc.filename)}) + + if idl.needsJSTypes(): + fd.write(jsvalue_include) + + # Include some extra files if any attributes are infallible. + interfaces = [p for p in idl.productions if p.kind == "interface"] + wroteRunScriptIncludes = False + wroteInfallibleIncludes = False + for iface in interfaces: + for member in iface.members: + if not isinstance(member, xpidl.Attribute) and not isinstance( + member, xpidl.Method + ): + continue + if not wroteInfallibleIncludes and member.infallible: + fd.write(infallible_includes) + wroteInfallibleIncludes = True + if not wroteRunScriptIncludes and memberCanRunScript(member): + fd.write(can_run_script_includes) + wroteRunScriptIncludes = True + if wroteRunScriptIncludes and wroteInfallibleIncludes: + break + + fd.write("\n") + fd.write(header_end) + + for p in idl.productions: + if p.kind == "include": + continue + if p.kind == "cdata": + fd.write(p.data) + continue + + if p.kind == "webidl": + write_webidl(p, fd) + continue + if p.kind == "forward": + fd.write(forward_decl % {"name": p.name}) + continue + if p.kind == "interface": + write_interface(p, fd) + continue + if p.kind == "typedef": + printComments(fd, p.doccomments, "") + fd.write("typedef %s %s;\n\n" % (p.realtype.nativeType("in"), p.name)) + + fd.write(footer % {"basename": idl_basename(filename)}) + + +def write_webidl(p, fd): + path = p.native.split("::") + for seg in path[:-1]: + fd.write("namespace %s {\n" % seg) + fd.write("class %s; /* webidl %s */\n" % (path[-1], p.name)) + for seg in reversed(path[:-1]): + fd.write("} // namespace %s\n" % seg) + fd.write("\n") + + +iface_header = r""" +/* starting interface: %(name)s */ +#define %(defname)s_IID_STR "%(iid)s" + +#define %(defname)s_IID \ + {0x%(m0)s, 0x%(m1)s, 0x%(m2)s, \ + { %(m3joined)s }} + +""" + +uuid_decoder = re.compile( + r"""(?P<m0>[a-f0-9]{8})- + (?P<m1>[a-f0-9]{4})- + (?P<m2>[a-f0-9]{4})- + (?P<m3>[a-f0-9]{4})- + (?P<m4>[a-f0-9]{12})$""", + re.X, +) + +iface_prolog = """ { + public: + + NS_DECLARE_STATIC_IID_ACCESSOR(%(defname)s_IID) + +""" + +iface_scriptable = """\ + /* Used by ToJSValue to check which scriptable interface is implemented. */ + using ScriptableInterfaceType = %(name)s; + +""" + +iface_epilog = """}; + + NS_DEFINE_STATIC_IID_ACCESSOR(%(name)s, %(defname)s_IID)""" + +iface_decl = """ + +/* Use this macro when declaring classes that implement this interface. */ +#define NS_DECL_%(macroname)s """ + +iface_nonvirtual = """ + +/* Use this macro when declaring the members of this interface when the + class doesn't implement the interface. This is useful for forwarding. */ +#define NS_DECL_NON_VIRTUAL_%(macroname)s """ + +iface_forward = """ + +/* Use this macro to declare functions that forward the behavior of this interface to another object. */ +#define NS_FORWARD_%(macroname)s(_to) """ # NOQA: E501 + +iface_forward_safe = """ + +/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */ +#define NS_FORWARD_SAFE_%(macroname)s(_to) """ # NOQA: E501 + +builtin_infallible_tmpl = """\ + %(attributes)sinline %(realtype)s %(nativename)s(%(args)s) + { + %(realtype)sresult; + mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result; + } +""" + +# NOTE: We don't use RefPtr::forget here because we don't want to need the +# definition of %(realtype)s in scope, which we would need for the +# AddRef/Release calls. +refcnt_infallible_tmpl = """\ + %(attributes)s inline already_AddRefed<%(realtype)s> %(nativename)s(%(args)s) + { + %(realtype)s* result = nullptr; + mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return already_AddRefed<%(realtype)s>(result); + } +""" + +iface_threadsafe_tmpl = """\ +namespace mozilla::detail { +template <> +class InterfaceNeedsThreadSafeRefCnt<%(name)s> : public std::true_type {}; +} +""" + + +def infallibleDecl(member): + isattr = isinstance(member, xpidl.Attribute) + ismethod = isinstance(member, xpidl.Method) + assert isattr or ismethod + + realtype = member.realtype.nativeType("in") + tmpl = builtin_infallible_tmpl + + if member.realtype.kind != "builtin" and member.realtype.kind != "cenum": + assert realtype.endswith(" *"), "bad infallible type" + tmpl = refcnt_infallible_tmpl + realtype = realtype[:-2] # strip trailing pointer + + if isattr: + nativename = attributeNativeName(member, getter=True) + args = attributeParamlist(member, getter=True, return_param=False) + argnames = attributeParamNames(member, getter=True, return_param=False) + attributes = attributeAttributes(member, getter=True) + else: + nativename = methodNativeName(member) + args = paramlistAsNative(member, return_param=False) + argnames = paramlistNames(member, return_param=False) + attributes = methodAttributes(member) + + return tmpl % { + "attributes": attributes, + "realtype": realtype, + "nativename": nativename, + "args": args, + "argnames": argnames + ", " if argnames else "", + } + + +def write_interface(iface, fd): + if iface.namemap is None: + raise Exception("Interface was not resolved.") + + # Confirm that no names of methods will overload in this interface + names = set() + + def record_name(name): + if name in names: + raise Exception( + "Unexpected overloaded virtual method %s in interface %s" + % (name, iface.name) + ) + names.add(name) + + for m in iface.members: + if type(m) == xpidl.Attribute: + record_name(attributeNativeName(m, getter=True)) + if not m.readonly: + record_name(attributeNativeName(m, getter=False)) + elif type(m) == xpidl.Method: + record_name(methodNativeName(m)) + + def write_const_decls(g): + fd.write(" enum {\n") + enums = [] + for c in g: + printComments(fd, c.doccomments, " ") + basetype = c.basetype + value = c.getValue() + enums.append( + " %(name)s = %(value)s%(signed)s" + % { + "name": c.name, + "value": value, + "signed": (not basetype.signed) and "U" or "", + } + ) + fd.write(",\n".join(enums)) + fd.write("\n };\n\n") + + def write_cenum_decl(b): + fd.write(" enum %s : uint%d_t {\n" % (b.basename, b.width)) + for var in b.variants: + fd.write(" %s = %s,\n" % (var.name, var.value)) + fd.write(" };\n\n") + + def write_method_decl(m): + printComments(fd, m.doccomments, " ") + + fd.write(" /* %s */\n" % m.toIDL()) + fd.write(" %s%s = 0;\n\n" % (runScriptAnnotation(m), methodAsNative(m))) + + if m.infallible: + fd.write(infallibleDecl(m)) + + def write_attr_decl(a): + printComments(fd, a.doccomments, " ") + + fd.write(" /* %s */\n" % a.toIDL()) + + fd.write(" %s%s = 0;\n" % (runScriptAnnotation(a), attributeAsNative(a, True))) + if a.infallible: + fd.write(infallibleDecl(a)) + + if not a.readonly: + fd.write( + " %s%s = 0;\n" % (runScriptAnnotation(a), attributeAsNative(a, False)) + ) + fd.write("\n") + + defname = iface.name.upper() + if iface.name[0:2] == "ns": + defname = "NS_" + defname[2:] + + names = uuid_decoder.match(iface.attributes.uuid).groupdict() + m3str = names["m3"] + names["m4"] + names["m3joined"] = ", ".join(["0x%s" % m3str[i : i + 2] for i in range(0, 16, 2)]) + + if iface.name[2] == "I": + implclass = iface.name[:2] + iface.name[3:] + else: + implclass = "_MYCLASS_" + + names.update( + { + "defname": defname, + "macroname": iface.name.upper(), + "name": iface.name, + "iid": iface.attributes.uuid, + "implclass": implclass, + } + ) + + fd.write(iface_header % names) + + printComments(fd, iface.doccomments, "") + + fd.write("class ") + foundcdata = False + for m in iface.members: + if isinstance(m, xpidl.CDATA): + foundcdata = True + + if not foundcdata: + fd.write("NS_NO_VTABLE ") + + fd.write(iface.name) + if iface.base: + fd.write(" : public %s" % iface.base) + fd.write(iface_prolog % names) + + if iface.attributes.scriptable: + fd.write(iface_scriptable % names) + + for key, group in itertools.groupby(iface.members, key=type): + if key == xpidl.ConstMember: + write_const_decls(group) # iterator of all the consts + else: + for member in group: + if key == xpidl.Attribute: + write_attr_decl(member) + elif key == xpidl.Method: + write_method_decl(member) + elif key == xpidl.CDATA: + fd.write(" %s" % member.data) + elif key == xpidl.CEnum: + write_cenum_decl(member) + else: + raise Exception("Unexpected interface member: %s" % member) + + fd.write(iface_epilog % names) + + if iface.attributes.rust_sync: + fd.write(iface_threadsafe_tmpl % names) + + fd.write(iface_decl % names) + + def writeDeclaration(fd, iface, virtual): + declType = "NS_IMETHOD" if virtual else "nsresult" + suffix = " override" if virtual else "" + for member in iface.members: + if isinstance(member, xpidl.Attribute): + if member.infallible: + fd.write( + "\\\n using %s::%s; " + % (iface.name, attributeNativeName(member, True)) + ) + fd.write( + "\\\n %s%s; " % (attributeAsNative(member, True, declType), suffix) + ) + if not member.readonly: + fd.write( + "\\\n %s%s; " + % (attributeAsNative(member, False, declType), suffix) + ) + elif isinstance(member, xpidl.Method): + fd.write("\\\n %s%s; " % (methodAsNative(member, declType), suffix)) + if len(iface.members) == 0: + fd.write("\\\n /* no methods! */") + elif member.kind not in ("attribute", "method"): + fd.write("\\") + + writeDeclaration(fd, iface, True) + fd.write(iface_nonvirtual % names) + writeDeclaration(fd, iface, False) + fd.write(iface_forward % names) + + def emitTemplate(forward_infallible, tmpl, tmpl_notxpcom=None): + if tmpl_notxpcom is None: + tmpl_notxpcom = tmpl + for member in iface.members: + if isinstance(member, xpidl.Attribute): + if forward_infallible and member.infallible: + fd.write( + "\\\n using %s::%s; " + % (iface.name, attributeNativeName(member, True)) + ) + attr_tmpl = tmpl_notxpcom if member.notxpcom else tmpl + fd.write( + attr_tmpl + % { + "asNative": attributeAsNative(member, True), + "nativeName": attributeNativeName(member, True), + "paramList": attributeParamNames(member, True), + } + ) + if not member.readonly: + fd.write( + attr_tmpl + % { + "asNative": attributeAsNative(member, False), + "nativeName": attributeNativeName(member, False), + "paramList": attributeParamNames(member, False), + } + ) + elif isinstance(member, xpidl.Method): + if member.notxpcom: + fd.write( + tmpl_notxpcom + % { + "asNative": methodAsNative(member), + "nativeName": methodNativeName(member), + "paramList": paramlistNames(member), + } + ) + else: + fd.write( + tmpl + % { + "asNative": methodAsNative(member), + "nativeName": methodNativeName(member), + "paramList": paramlistNames(member), + } + ) + if len(iface.members) == 0: + fd.write("\\\n /* no methods! */") + elif member.kind not in ("attribute", "method"): + fd.write("\\") + + emitTemplate( + True, + "\\\n %(asNative)s override { return _to %(nativeName)s(%(paramList)s); } ", + ) + + fd.write(iface_forward_safe % names) + + # Don't try to safely forward notxpcom functions, because we have no + # sensible default error return. Instead, the caller will have to + # implement them. + emitTemplate( + False, + "\\\n %(asNative)s override { return !_to ? NS_ERROR_NULL_POINTER : _to->%(nativeName)s(%(paramList)s); } ", # NOQA: E501 + "\\\n %(asNative)s override; ", + ) + + fd.write("\n\n") + + +def main(outputfile): + xpidl.IDLParser() + + +if __name__ == "__main__": + main(None) diff --git a/xpcom/idl-parser/xpidl/jsonxpt.py b/xpcom/idl-parser/xpidl/jsonxpt.py new file mode 100644 index 0000000000..342188f645 --- /dev/null +++ b/xpcom/idl-parser/xpidl/jsonxpt.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python +# jsonxpt.py - Generate json XPT typelib files from IDL. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Generate a json XPT typelib for an IDL file""" + +import itertools +import json + +from xpidl import xpidl + +# A map of xpidl.py types to xpt enum variants +TypeMap = { + # builtins + "boolean": "TD_BOOL", + "void": "TD_VOID", + "int16_t": "TD_INT16", + "int32_t": "TD_INT32", + "int64_t": "TD_INT64", + "uint8_t": "TD_UINT8", + "uint16_t": "TD_UINT16", + "uint32_t": "TD_UINT32", + "uint64_t": "TD_UINT64", + "octet": "TD_UINT8", + "short": "TD_INT16", + "long": "TD_INT32", + "long long": "TD_INT64", + "unsigned short": "TD_UINT16", + "unsigned long": "TD_UINT32", + "unsigned long long": "TD_UINT64", + "float": "TD_FLOAT", + "double": "TD_DOUBLE", + "char": "TD_CHAR", + "string": "TD_PSTRING", + "wchar": "TD_WCHAR", + "wstring": "TD_PWSTRING", + # special types + "nsid": "TD_NSID", + "astring": "TD_ASTRING", + "utf8string": "TD_UTF8STRING", + "cstring": "TD_CSTRING", + "jsval": "TD_JSVAL", + "promise": "TD_PROMISE", +} + + +def flags(*flags): + return [flag for flag, cond in flags if cond] + + +def get_type(type, calltype, iid_is=None, size_is=None): + while isinstance(type, xpidl.Typedef): + type = type.realtype + + if isinstance(type, xpidl.Builtin): + ret = {"tag": TypeMap[type.name]} + if type.name in ["string", "wstring"] and size_is is not None: + ret["tag"] += "_SIZE_IS" + ret["size_is"] = size_is + return ret + + if isinstance(type, xpidl.Array): + # NB: For a Array<T> we pass down the iid_is to get the type of T. + # This allows Arrays of InterfaceIs types to work. + return { + "tag": "TD_ARRAY", + "element": get_type(type.type, calltype, iid_is), + } + + if isinstance(type, xpidl.LegacyArray): + # NB: For a Legacy [array] T we pass down iid_is to get the type of T. + # This allows [array] of InterfaceIs types to work. + return { + "tag": "TD_LEGACY_ARRAY", + "size_is": size_is, + "element": get_type(type.type, calltype, iid_is), + } + + if isinstance(type, xpidl.Interface) or isinstance(type, xpidl.Forward): + return { + "tag": "TD_INTERFACE_TYPE", + "name": type.name, + } + + if isinstance(type, xpidl.WebIDL): + return { + "tag": "TD_DOMOBJECT", + "name": type.name, + "native": type.native, + "headerFile": type.headerFile, + } + + if isinstance(type, xpidl.Native): + if type.specialtype == "nsid" and type.isPtr(calltype): + return {"tag": "TD_NSIDPTR"} + elif type.specialtype: + return {"tag": TypeMap[type.specialtype]} + elif iid_is is not None: + return { + "tag": "TD_INTERFACE_IS_TYPE", + "iid_is": iid_is, + } + else: + return {"tag": "TD_VOID"} + + if isinstance(type, xpidl.CEnum): + # As far as XPConnect is concerned, cenums are just unsigned integers. + return {"tag": "TD_UINT%d" % type.width} + + raise Exception("Unknown type!") + + +def mk_param(type, in_=0, out=0, optional=0): + return { + "type": type, + "flags": flags( + ("in", in_), + ("out", out), + ("optional", optional), + ), + } + + +def mk_method(method, params, getter=0, setter=0, optargc=0, hasretval=0, symbol=0): + return { + "name": method.name, + # NOTE: We don't include any return value information here, as we'll + # never call the methods if they're marked notxpcom, and all xpcom + # methods return the same type (nsresult). + # XXX: If we ever use these files for other purposes than xptcodegen we + # may want to write that info. + "params": params, + "flags": flags( + ("getter", getter), + ("setter", setter), + ("hidden", method.noscript or method.notxpcom), + ("optargc", optargc), + ("jscontext", method.implicit_jscontext), + ("hasretval", hasretval), + ("symbol", method.symbol), + ), + } + + +def attr_param_idx(p, m, attr): + attr_val = getattr(p, attr, None) + if not attr_val: + return None + for i, param in enumerate(m.params): + if param.name == attr_val: + return i + raise Exception(f"Need parameter named '{attr_val}' for attribute '{attr}'") + + +def build_interface(iface): + if iface.namemap is None: + raise Exception("Interface was not resolved.") + + assert ( + iface.attributes.scriptable + ), "Don't generate XPT info for non-scriptable interfaces" + + # State used while building an interface + consts = [] + methods = [] + + def build_const(c): + consts.append( + { + "name": c.name, + "type": get_type(c.basetype, ""), + "value": c.getValue(), # All of our consts are numbers + } + ) + + def build_cenum(b): + for var in b.variants: + consts.append( + { + "name": var.name, + "type": get_type(b, "in"), + "value": var.value, + } + ) + + def build_method(m): + params = [] + for p in m.params: + params.append( + mk_param( + get_type( + p.realtype, + p.paramtype, + iid_is=attr_param_idx(p, m, "iid_is"), + size_is=attr_param_idx(p, m, "size_is"), + ), + in_=p.paramtype.count("in"), + out=p.paramtype.count("out"), + optional=p.optional, + ) + ) + + hasretval = len(m.params) > 0 and m.params[-1].retval + if not m.notxpcom and m.realtype.name != "void": + hasretval = True + params.append(mk_param(get_type(m.realtype, "out"), out=1)) + + methods.append( + mk_method(m, params, optargc=m.optional_argc, hasretval=hasretval) + ) + + def build_attr(a): + assert a.realtype.name != "void" + # Write the getter + getter_params = [] + if not a.notxpcom: + getter_params.append(mk_param(get_type(a.realtype, "out"), out=1)) + + methods.append(mk_method(a, getter_params, getter=1, hasretval=1)) + + # And maybe the setter + if not a.readonly: + param = mk_param(get_type(a.realtype, "in"), in_=1) + methods.append(mk_method(a, [param], setter=1)) + + for member in iface.members: + if isinstance(member, xpidl.ConstMember): + build_const(member) + elif isinstance(member, xpidl.Attribute): + build_attr(member) + elif isinstance(member, xpidl.Method): + build_method(member) + elif isinstance(member, xpidl.CEnum): + build_cenum(member) + elif isinstance(member, xpidl.CDATA): + pass + else: + raise Exception("Unexpected interface member: %s" % member) + + return { + "name": iface.name, + "uuid": iface.attributes.uuid, + "methods": methods, + "consts": consts, + "parent": iface.base, + "flags": flags( + ("function", iface.attributes.function), + ("builtinclass", iface.attributes.builtinclass), + ("main_process_only", iface.attributes.main_process_scriptable_only), + ), + } + + +# These functions are the public interface of this module. They are very simple +# functions, but are exported so that if we need to do something more +# complex in them in the future we can. + + +def build_typelib(idl): + """Given a parsed IDL file, generate and return the typelib""" + return [ + build_interface(p) + for p in idl.productions + if p.kind == "interface" and p.attributes.scriptable + ] + + +def link(typelibs): + """Link a list of typelibs together into a single typelib""" + linked = list(itertools.chain.from_iterable(typelibs)) + assert len(set(iface["name"] for iface in linked)) == len( + linked + ), "Multiple typelibs containing the same interface were linked together" + return linked + + +def write(typelib, fd): + """Write typelib into fd""" + json.dump(typelib, fd, indent=2, sort_keys=True) diff --git a/xpcom/idl-parser/xpidl/moz.build b/xpcom/idl-parser/xpidl/moz.build new file mode 100644 index 0000000000..60252a0b81 --- /dev/null +++ b/xpcom/idl-parser/xpidl/moz.build @@ -0,0 +1,11 @@ +# -*- 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/. + +PYTHON_UNITTEST_MANIFESTS += [ + "python.toml", +] + +GeneratedFile("xpidl.stub", script="header.py", entry_point="main") diff --git a/xpcom/idl-parser/xpidl/python.toml b/xpcom/idl-parser/xpidl/python.toml new file mode 100644 index 0000000000..577127329e --- /dev/null +++ b/xpcom/idl-parser/xpidl/python.toml @@ -0,0 +1,4 @@ +[DEFAULT] +subsuite = "xpcom" + +["runtests.py"] diff --git a/xpcom/idl-parser/xpidl/runtests.py b/xpcom/idl-parser/xpidl/runtests.py new file mode 100755 index 0000000000..2dd269dfd9 --- /dev/null +++ b/xpcom/idl-parser/xpidl/runtests.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python +# +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ +# +# Unit tests for xpidl.py + +import sys + +# Hack: the first entry in sys.path is the directory containing the script. +# This messes things up because that directory is the xpidl module, and that +# which conflicts with the xpidl submodule in the imports further below. +sys.path.pop(0) + +import unittest + +import mozunit + +from xpidl import header, xpidl + + +class TestParser(unittest.TestCase): + def setUp(self): + self.p = xpidl.IDLParser() + + def testEmpty(self): + i = self.p.parse("", filename="f") + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertEqual([], i.productions) + + def testForwardInterface(self): + i = self.p.parse("interface foo;", filename="f") + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Forward)) + self.assertEqual("foo", i.productions[0].name) + + def testInterface(self): + i = self.p.parse("[uuid(abc)] interface foo {};", filename="f") + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + self.assertEqual("foo", i.productions[0].name) + + def testAttributes(self): + i = self.p.parse( + "[scriptable, builtinclass, function, uuid(abc)] interface foo {};", + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + self.assertEqual("foo", iface.name) + self.assertTrue(iface.attributes.scriptable) + self.assertTrue(iface.attributes.builtinclass) + self.assertTrue(iface.attributes.function) + + i = self.p.parse("[uuid(abc)] interface foo {};", filename="f") + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + self.assertEqual("foo", iface.name) + self.assertFalse(iface.attributes.scriptable) + + def testMethod(self): + i = self.p.parse( + """[uuid(abc)] interface foo { +void bar(); +};""", + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + m = iface.members[0] + self.assertTrue(isinstance(m, xpidl.Method)) + self.assertEqual("bar", m.name) + self.assertEqual(xpidl.TypeId("void"), m.type) + + def testMethodParams(self): + i = self.p.parse( + """ + [scriptable, uuid(aaa)] interface nsISupports {}; + [uuid(abc)] interface foo : nsISupports { + long bar(in long a, in float b, [array] in long c); + };""", + filename="f", + ) + i.resolve([], self.p, {}) + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[1], xpidl.Interface)) + iface = i.productions[1] + m = iface.members[0] + self.assertTrue(isinstance(m, xpidl.Method)) + self.assertEqual("bar", m.name) + self.assertEqual(xpidl.TypeId("long"), m.type) + self.assertEqual(3, len(m.params)) + self.assertEqual(xpidl.TypeId("long"), m.params[0].type) + self.assertEqual("in", m.params[0].paramtype) + self.assertEqual(xpidl.TypeId("float"), m.params[1].type) + self.assertEqual("in", m.params[1].paramtype) + self.assertEqual(xpidl.TypeId("long"), m.params[2].type) + self.assertEqual("in", m.params[2].paramtype) + self.assertTrue(isinstance(m.params[2].realtype, xpidl.LegacyArray)) + self.assertEqual("long", m.params[2].realtype.type.name) + + def testAttribute(self): + i = self.p.parse( + """[uuid(abc)] interface foo { +attribute long bar; +};""", + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + a = iface.members[0] + self.assertTrue(isinstance(a, xpidl.Attribute)) + self.assertEqual("bar", a.name) + self.assertEqual(xpidl.TypeId("long"), a.type) + + def testOverloadedVirtual(self): + i = self.p.parse( + """ + [scriptable, uuid(00000000-0000-0000-0000-000000000000)] interface nsISupports {}; + [uuid(abc)] interface foo : nsISupports { + attribute long bar; + void getBar(); + };""", + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + i.resolve([], self.p, {}) + + class FdMock: + def write(self, s): + pass + + try: + header.print_header(i, FdMock(), filename="f", relpath="f") + self.assertTrue(False, "Header printing failed to fail") + except Exception as e: + self.assertEqual( + e.args[0], + "Unexpected overloaded virtual method GetBar in interface foo", + ) + + def testNotISupports(self): + i = self.p.parse( + """ + [uuid(abc)] interface foo {}; + """, + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + try: + i.resolve([], self.p, {}) + self.assertTrue( + False, "Must check that interfaces inherit from nsISupports" + ) + except xpidl.IDLError as e: + self.assertEqual(e.args[0], "Interface 'foo' must inherit from nsISupports") + + def testBuiltinClassParent(self): + i = self.p.parse( + """ + [scriptable, uuid(aaa)] interface nsISupports {}; + [scriptable, builtinclass, uuid(abc)] interface foo : nsISupports {}; + [scriptable, uuid(def)] interface bar : foo {}; + """, + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + try: + i.resolve([], self.p, {}) + self.assertTrue( + False, "non-builtinclasses can't inherit from builtinclasses" + ) + except xpidl.IDLError as e: + self.assertEqual( + e.args[0], + "interface 'bar' is not builtinclass but derives from builtinclass 'foo'", + ) + + def testScriptableNotXPCOM(self): + # notxpcom method requires builtinclass on the interface + i = self.p.parse( + """ + [scriptable, uuid(aaa)] interface nsISupports {}; + [scriptable, uuid(abc)] interface nsIScriptableWithNotXPCOM : nsISupports { + [notxpcom] void method2(); + }; + """, + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + try: + i.resolve([], self.p, {}) + self.assertTrue( + False, + "Resolve should fail for non-builtinclasses with notxpcom methods", + ) + except xpidl.IDLError as e: + self.assertEqual( + e.args[0], + ( + "scriptable interface 'nsIScriptableWithNotXPCOM' " + "must be marked [builtinclass] because it contains a [notxpcom] " + "method 'method2'" + ), + ) + + # notxpcom attribute requires builtinclass on the interface + i = self.p.parse( + """ + interface nsISomeInterface; + [scriptable, uuid(aaa)] interface nsISupports {}; + [scriptable, uuid(abc)] interface nsIScriptableWithNotXPCOM : nsISupports { + [notxpcom] attribute nsISomeInterface attrib; + }; + """, + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + try: + i.resolve([], self.p, {}) + self.assertTrue( + False, + "Resolve should fail for non-builtinclasses with notxpcom attributes", + ) + except xpidl.IDLError as e: + self.assertEqual( + e.args[0], + ( + "scriptable interface 'nsIScriptableWithNotXPCOM' must be marked " + "[builtinclass] because it contains a [notxpcom] attribute 'attrib'" + ), + ) + + def testUndefinedConst(self): + i = self.p.parse( + """ + [scriptable, uuid(aaa)] interface nsISupports {}; + [scriptable, uuid(abc)] interface foo : nsISupports { + const unsigned long X = Y + 1; + }; + """, + filename="f", + ) + self.assertTrue(isinstance(i, xpidl.IDL)) + try: + i.resolve([], self.p, {}) + self.assertTrue(False, "Must detect undefined symbols") + except xpidl.IDLError as e: + self.assertEqual(e.args[0], ("cannot find symbol 'Y'")) + + +if __name__ == "__main__": + mozunit.main(runwith="unittest") diff --git a/xpcom/idl-parser/xpidl/rust.py b/xpcom/idl-parser/xpidl/rust.py new file mode 100644 index 0000000000..b9fc6627fb --- /dev/null +++ b/xpcom/idl-parser/xpidl/rust.py @@ -0,0 +1,693 @@ +# rust.py - Generate rust bindings from IDL. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Print a runtime Rust bindings file for the IDL file specified""" + +# --- Safety Hazards --- + +# We currently don't generate some bindings for some IDL methods in rust code, +# due to there being ABI safety hazards if we were to do so. This is the +# documentation for the reasons why we don't generate certain types of bindings, +# so that we don't accidentally start generating them in the future. + +# notxpcom methods and attributes return their results directly by value. The x86 +# windows stdcall ABI returns aggregates by value differently for methods than +# functions, and rust only exposes the function ABI, so that's the one we're +# using. The correct ABI can be emulated for notxpcom methods returning aggregates +# by passing an &mut ReturnType parameter as the second parameter. This strategy +# is used by the winapi-rs crate. +# https://github.com/retep998/winapi-rs/blob/7338a5216a6a7abeefcc6bb1bc34381c81d3e247/src/macros.rs#L220-L231 +# +# Right now we can generate code for notxpcom methods and attributes, as we don't +# support passing aggregates by value over these APIs ever (the types which are +# allowed in xpidl.py shouldn't include any aggregates), so the code is +# correct. In the future if we want to start supporting returning aggregates by +# value, we will need to use a workaround such as the one used by winapi.rs. + +# nostdcall methods on x86 windows will use the thiscall ABI, which is not +# stable in rust right now, so we cannot generate bindings to them. + +# In general, passing C++ objects by value over the C ABI is not a good idea, +# and when possible we should avoid doing so. We don't generate bindings for +# these methods here currently. + +import os.path +import re + +from xpidl import xpidl + + +class AutoIndent(object): + """A small autoindenting wrapper around a fd. + Used to make the code output more readable.""" + + def __init__(self, fd): + self.fd = fd + self.indent = 0 + + def write(self, string): + """A smart write function which automatically adjusts the + indentation of each line as it is written by counting braces""" + for s in string.split("\n"): + s = s.strip() + indent = self.indent + if len(s) == 0: + indent = 0 + elif s[0] == "}": + indent -= 1 + + self.fd.write(" " * indent + s + "\n") + for c in s: + if c == "(" or c == "{" or c == "[": + self.indent += 1 + elif c == ")" or c == "}" or c == "]": + self.indent -= 1 + + +def rustSanitize(s): + keywords = [ + "abstract", + "alignof", + "as", + "become", + "box", + "break", + "const", + "continue", + "crate", + "do", + "else", + "enum", + "extern", + "false", + "final", + "fn", + "for", + "if", + "impl", + "in", + "let", + "loop", + "macro", + "match", + "mod", + "move", + "mut", + "offsetof", + "override", + "priv", + "proc", + "pub", + "pure", + "ref", + "return", + "Self", + "self", + "sizeof", + "static", + "struct", + "super", + "trait", + "true", + "type", + "typeof", + "unsafe", + "unsized", + "use", + "virtual", + "where", + "while", + "yield", + ] + if s in keywords: + return s + "_" + return s + + +# printdoccomments = False +printdoccomments = True + +if printdoccomments: + + def printComments(fd, clist, indent): + fd.write("%s%s" % (indent, doccomments(clist))) + + def doccomments(clist): + if len(clist) == 0: + return "" + s = "/// ```text" + for c in clist: + for cc in c.splitlines(): + s += "\n/// " + cc + s += "\n/// ```\n///\n" + return s + +else: + + def printComments(fd, clist, indent): + pass + + def doccomments(clist): + return "" + + +def firstCap(str): + return str[0].upper() + str[1:] + + +# Attribute VTable Methods +def attributeNativeName(a, getter): + binaryname = rustSanitize(a.binaryname if a.binaryname else firstCap(a.name)) + return "%s%s" % ("Get" if getter else "Set", binaryname) + + +def attributeReturnType(a, getter): + if a.notxpcom: + if getter: + return a.realtype.rustType("in").strip() + return "::libc::c_void" + return "::nserror::nsresult" + + +def attributeParamName(a): + return "a" + firstCap(a.name) + + +def attributeRawParamList(iface, a, getter): + if getter and a.notxpcom: + l = [] + else: + l = [(attributeParamName(a), a.realtype.rustType("out" if getter else "in"))] + if a.implicit_jscontext: + raise xpidl.RustNoncompat("jscontext is unsupported") + if a.nostdcall: + raise xpidl.RustNoncompat("nostdcall is unsupported") + return l + + +def attributeParamList(iface, a, getter): + l = ["this: *const " + iface.name] + l += ["%s: %s" % x for x in attributeRawParamList(iface, a, getter)] + return ", ".join(l) + + +def attrAsVTableEntry(iface, m, getter): + try: + return 'pub %s: unsafe extern "system" fn (%s) -> %s' % ( + attributeNativeName(m, getter), + attributeParamList(iface, m, getter), + attributeReturnType(m, getter), + ) + except xpidl.RustNoncompat as reason: + return """\ +/// Unable to generate binding because `%s` +pub %s: *const ::libc::c_void""" % ( + reason, + attributeNativeName(m, getter), + ) + + +# Method VTable generation functions +def methodNativeName(m): + binaryname = m.binaryname is not None and m.binaryname or firstCap(m.name) + return rustSanitize(binaryname) + + +def methodReturnType(m): + if m.notxpcom: + return m.realtype.rustType("in").strip() + return "::nserror::nsresult" + + +def methodRawParamList(iface, m): + l = [(rustSanitize(p.name), p.rustType()) for p in m.params] + + if m.implicit_jscontext: + raise xpidl.RustNoncompat("jscontext is unsupported") + + if m.optional_argc: + raise xpidl.RustNoncompat("optional_argc is unsupported") + + if m.nostdcall: + raise xpidl.RustNoncompat("nostdcall is unsupported") + + if not m.notxpcom and m.realtype.name != "void": + l.append(("_retval", m.realtype.rustType("out"))) + + return l + + +def methodParamList(iface, m): + l = ["this: *const %s" % iface.name] + l += ["%s: %s" % x for x in methodRawParamList(iface, m)] + return ", ".join(l) + + +def methodAsVTableEntry(iface, m): + try: + return 'pub %s: unsafe extern "system" fn (%s) -> %s' % ( + methodNativeName(m), + methodParamList(iface, m), + methodReturnType(m), + ) + except xpidl.RustNoncompat as reason: + return """\ +/// Unable to generate binding because `%s` +pub %s: *const ::libc::c_void""" % ( + reason, + methodNativeName(m), + ) + + +method_impl_tmpl = """\ +#[inline] +pub unsafe fn %(name)s(&self, %(params)s) -> %(ret_ty)s { + ((*self.vtable).%(name)s)(self, %(args)s) +} +""" + + +def methodAsWrapper(iface, m): + try: + param_list = methodRawParamList(iface, m) + params = ["%s: %s" % x for x in param_list] + args = [x[0] for x in param_list] + + return method_impl_tmpl % { + "name": methodNativeName(m), + "params": ", ".join(params), + "ret_ty": methodReturnType(m), + "args": ", ".join(args), + } + except xpidl.RustNoncompat: + # Dummy field for the doc comments to attach to. + # Private so that it's not shown in rustdoc. + return "const _%s: () = ();" % methodNativeName(m) + + +infallible_impl_tmpl = """\ +#[inline] +pub unsafe fn %(name)s(&self) -> %(realtype)s { + let mut result = <%(realtype)s as ::std::default::Default>::default(); + let _rv = ((*self.vtable).%(name)s)(self, &mut result); + debug_assert!(_rv.succeeded()); + result +} +""" + + +def attrAsWrapper(iface, m, getter): + try: + if m.implicit_jscontext: + raise xpidl.RustNoncompat("jscontext is unsupported") + + if m.nostdcall: + raise xpidl.RustNoncompat("nostdcall is unsupported") + + name = attributeParamName(m) + + if getter and m.infallible and m.realtype.kind == "builtin": + # NOTE: We don't support non-builtin infallible getters in Rust code. + return infallible_impl_tmpl % { + "name": attributeNativeName(m, getter), + "realtype": m.realtype.rustType("in"), + } + + param_list = attributeRawParamList(iface, m, getter) + params = ["%s: %s" % x for x in param_list] + return method_impl_tmpl % { + "name": attributeNativeName(m, getter), + "params": ", ".join(params), + "ret_ty": attributeReturnType(m, getter), + "args": "" if getter and m.notxpcom else name, + } + + except xpidl.RustNoncompat: + # Dummy field for the doc comments to attach to. + # Private so that it's not shown in rustdoc. + return "const _%s: () = ();" % attributeNativeName(m, getter) + + +header = """\ +// +// DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s +// + +""" + + +def idl_basename(f): + """returns the base name of a file with the last extension stripped""" + return os.path.splitext(os.path.basename(f))[0] + + +def print_rust_bindings(idl, fd, relpath): + fd = AutoIndent(fd) + + fd.write(header % {"relpath": relpath}) + + # All of the idl files will be included into the same rust module, as we + # can't do forward declarations. Because of this, we want to ignore all + # import statements + + for p in idl.productions: + if p.kind == "include" or p.kind == "cdata" or p.kind == "forward": + continue + + if p.kind == "interface": + write_interface(p, fd) + continue + + if p.kind == "typedef": + try: + # We have to skip the typedef of bool to bool (it doesn't make any sense anyways) + if p.name == "bool": + continue + + if printdoccomments: + fd.write( + "/// `typedef %s %s;`\n///\n" + % (p.realtype.nativeType("in"), p.name) + ) + fd.write(doccomments(p.doccomments)) + fd.write("pub type %s = %s;\n\n" % (p.name, p.realtype.rustType("in"))) + except xpidl.RustNoncompat as reason: + fd.write( + "/* unable to generate %s typedef because `%s` */\n\n" + % (p.name, reason) + ) + + +base_vtable_tmpl = """ +/// We need to include the members from the base interface's vtable at the start +/// of the VTable definition. +pub __base: %sVTable, + +""" + + +vtable_tmpl = """\ +// This struct represents the interface's VTable. A pointer to a statically +// allocated version of this struct is at the beginning of every %(name)s +// object. It contains one pointer field for each method in the interface. In +// the case where we can't generate a binding for a method, we include a void +// pointer. +#[doc(hidden)] +#[repr(C)] +pub struct %(name)sVTable {%(base)s%(entries)s} + +""" + + +# NOTE: This template is not generated for nsISupports, as it has no base interfaces. +deref_tmpl = """\ +// Every interface struct type implements `Deref` to its base interface. This +// causes methods on the base interfaces to be directly avaliable on the +// object. For example, you can call `.AddRef` or `.QueryInterface` directly +// on any interface which inherits from `nsISupports`. +impl ::std::ops::Deref for %(name)s { + type Target = %(base)s; + #[inline] + fn deref(&self) -> &%(base)s { + unsafe { + ::std::mem::transmute(self) + } + } +} + +// Ensure we can use .coerce() to cast to our base types as well. Any type which +// our base interface can coerce from should be coercable from us as well. +impl<T: %(base)sCoerce> %(name)sCoerce for T { + #[inline] + fn coerce_from(v: &%(name)s) -> &Self { + T::coerce_from(v) + } +} +""" + + +struct_tmpl = """\ +// The actual type definition for the interface. This struct has methods +// declared on it which will call through its vtable. You never want to pass +// this type around by value, always pass it behind a reference. + +#[repr(C)] +pub struct %(name)s { + vtable: &'static %(name)sVTable, + + /// This field is a phantomdata to ensure that the VTable type and any + /// struct containing it is not safe to send across threads by default, as + /// XPCOM is generally not threadsafe. + /// + /// If this type is marked as [rust_sync], there will be explicit `Send` and + /// `Sync` implementations on this type, which will override the inherited + /// negative impls from `Rc`. + __nosync: ::std::marker::PhantomData<::std::rc::Rc<u8>>, + + // Make the rust compiler aware that there might be interior mutability + // in what actually implements the interface. This works around UB + // introduced by https://github.com/llvm/llvm-project/commit/01859da84bad95fd51d6a03b08b60c660e642a4f + // that a rust lint would make blatantly obvious, but doesn't exist. + // (See https://github.com/rust-lang/rust/issues/111229). + // This prevents optimizations, but those optimizations weren't available + // before rustc switched to LLVM 16, and they now cause problems because + // of the UB. + // Until there's a lint available to find all our UB, it's simpler to + // avoid the UB in the first place, at the cost of preventing optimizations + // in places that don't cause UB. But again, those optimizations weren't + // available before. + __maybe_interior_mutability: ::std::cell::UnsafeCell<[u8; 0]>, +} + +// Implementing XpCom for an interface exposes its IID, which allows for easy +// use of the `.query_interface<T>` helper method. This also defines that +// method for %(name)s. +unsafe impl XpCom for %(name)s { + const IID: nsIID = nsID(0x%(m0)s, 0x%(m1)s, 0x%(m2)s, + [%(m3joined)s]); +} + +// We need to implement the RefCounted trait so we can be used with `RefPtr`. +// This trait teaches `RefPtr` how to manage our memory. +unsafe impl RefCounted for %(name)s { + #[inline] + unsafe fn addref(&self) { + self.AddRef(); + } + #[inline] + unsafe fn release(&self) { + self.Release(); + } +} + +// This trait is implemented on all types which can be coerced to from %(name)s. +// It is used in the implementation of `fn coerce<T>`. We hide it from the +// documentation, because it clutters it up a lot. +#[doc(hidden)] +pub trait %(name)sCoerce { + /// Cheaply cast a value of this type from a `%(name)s`. + fn coerce_from(v: &%(name)s) -> &Self; +} + +// The trivial implementation: We can obviously coerce ourselves to ourselves. +impl %(name)sCoerce for %(name)s { + #[inline] + fn coerce_from(v: &%(name)s) -> &Self { + v + } +} + +impl %(name)s { + /// Cast this `%(name)s` to one of its base interfaces. + #[inline] + pub fn coerce<T: %(name)sCoerce>(&self) -> &T { + T::coerce_from(self) + } +} +""" + + +sendsync_tmpl = """\ +// This interface is marked as [rust_sync], meaning it is safe to be transferred +// and used from multiple threads silmultaneously. These override the default +// from the __nosync marker type allowng the type to be sent between threads. +unsafe impl Send for %(name)s {} +unsafe impl Sync for %(name)s {} +""" + + +wrapper_tmpl = """\ +// The implementations of the function wrappers which are exposed to rust code. +// Call these methods rather than manually calling through the VTable struct. +impl %(name)s { +%(consts)s +%(methods)s +} + +""" + +vtable_entry_tmpl = """\ +/* %(idl)s */ +%(entry)s, +""" + + +const_wrapper_tmpl = """\ +%(docs)s +pub const %(name)s: %(type)s = %(val)s; +""" + + +method_wrapper_tmpl = """\ +%(docs)s +/// `%(idl)s` +%(wrapper)s +""" + + +uuid_decoder = re.compile( + r"""(?P<m0>[a-f0-9]{8})- + (?P<m1>[a-f0-9]{4})- + (?P<m2>[a-f0-9]{4})- + (?P<m3>[a-f0-9]{4})- + (?P<m4>[a-f0-9]{12})$""", + re.X, +) + + +def write_interface(iface, fd): + if iface.namemap is None: + raise Exception("Interface was not resolved.") + + assert iface.base or (iface.name == "nsISupports") + + # Extract the UUID's information so that it can be written into the struct definition + names = uuid_decoder.match(iface.attributes.uuid).groupdict() + m3str = names["m3"] + names["m4"] + names["m3joined"] = ", ".join(["0x%s" % m3str[i : i + 2] for i in range(0, 16, 2)]) + names["name"] = iface.name + + if printdoccomments: + if iface.base is not None: + fd.write("/// `interface %s : %s`\n///\n" % (iface.name, iface.base)) + else: + fd.write("/// `interface %s`\n///\n" % iface.name) + printComments(fd, iface.doccomments, "") + fd.write(struct_tmpl % names) + + if iface.attributes.rust_sync: + fd.write(sendsync_tmpl % {"name": iface.name}) + + if iface.base is not None: + fd.write( + deref_tmpl + % { + "name": iface.name, + "base": iface.base, + } + ) + + entries = [] + for member in iface.members: + if type(member) == xpidl.Attribute: + entries.append( + vtable_entry_tmpl + % { + "idl": member.toIDL(), + "entry": attrAsVTableEntry(iface, member, True), + } + ) + if not member.readonly: + entries.append( + vtable_entry_tmpl + % { + "idl": member.toIDL(), + "entry": attrAsVTableEntry(iface, member, False), + } + ) + + elif type(member) == xpidl.Method: + entries.append( + vtable_entry_tmpl + % { + "idl": member.toIDL(), + "entry": methodAsVTableEntry(iface, member), + } + ) + + fd.write( + vtable_tmpl + % { + "name": iface.name, + "base": base_vtable_tmpl % iface.base if iface.base is not None else "", + "entries": "\n".join(entries), + } + ) + + # Get all of the constants + consts = [] + for member in iface.members: + if type(member) == xpidl.ConstMember: + consts.append( + const_wrapper_tmpl + % { + "docs": doccomments(member.doccomments), + "type": member.realtype.rustType("in"), + "name": member.name, + "val": member.getValue(), + } + ) + if type(member) == xpidl.CEnum: + for var in member.variants: + consts.append( + const_wrapper_tmpl + % { + "docs": "", + "type": member.rustType("in"), + "name": var.name, + "val": var.getValue(), + } + ) + + methods = [] + for member in iface.members: + if type(member) == xpidl.Attribute: + methods.append( + method_wrapper_tmpl + % { + "docs": doccomments(member.doccomments), + "idl": member.toIDL(), + "wrapper": attrAsWrapper(iface, member, True), + } + ) + if not member.readonly: + methods.append( + method_wrapper_tmpl + % { + "docs": doccomments(member.doccomments), + "idl": member.toIDL(), + "wrapper": attrAsWrapper(iface, member, False), + } + ) + + elif type(member) == xpidl.Method: + methods.append( + method_wrapper_tmpl + % { + "docs": doccomments(member.doccomments), + "idl": member.toIDL(), + "wrapper": methodAsWrapper(iface, member), + } + ) + + fd.write( + wrapper_tmpl + % { + "name": iface.name, + "consts": "\n".join(consts), + "methods": "\n".join(methods), + } + ) diff --git a/xpcom/idl-parser/xpidl/rust_macros.py b/xpcom/idl-parser/xpidl/rust_macros.py new file mode 100644 index 0000000000..bc1788b96c --- /dev/null +++ b/xpcom/idl-parser/xpidl/rust_macros.py @@ -0,0 +1,111 @@ +# rust_macros.py - Generate rust_macros bindings from IDL. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Generate rust bindings information for the IDL file specified""" + +from xpidl import rust, xpidl + +derive_method_tmpl = """\ +Method { + name: "%(name)s", + params: &[%(params)s], + ret: "%(ret)s", +}""" + + +def attrAsMethodStruct(iface, m, getter): + params = [ + 'Param { name: "%s", ty: "%s" }' % x + for x in rust.attributeRawParamList(iface, m, getter) + ] + return derive_method_tmpl % { + "name": rust.attributeNativeName(m, getter), + "params": ", ".join(params), + "ret": "::nserror::nsresult", + } + + +def methodAsMethodStruct(iface, m): + params = [ + 'Param { name: "%s", ty: "%s" }' % x for x in rust.methodRawParamList(iface, m) + ] + return derive_method_tmpl % { + "name": rust.methodNativeName(m), + "params": ", ".join(params), + "ret": rust.methodReturnType(m), + } + + +derive_iface_tmpl = """\ +Interface { + name: "%(name)s", + base: %(base)s, + sync: %(sync)s, + methods: %(methods)s, +}, +""" + + +def write_interface(iface, fd): + if iface.namemap is None: + raise Exception("Interface was not resolved.") + + assert iface.base or (iface.name == "nsISupports") + + base = 'Some("%s")' % iface.base if iface.base is not None else "None" + try: + methods = "" + for member in iface.members: + if type(member) == xpidl.Attribute: + methods += "/* %s */\n" % member.toIDL() + methods += "%s,\n" % attrAsMethodStruct(iface, member, True) + if not member.readonly: + methods += "%s,\n" % attrAsMethodStruct(iface, member, False) + methods += "\n" + + elif type(member) == xpidl.Method: + methods += "/* %s */\n" % member.toIDL() + methods += "%s,\n\n" % methodAsMethodStruct(iface, member) + fd.write( + derive_iface_tmpl + % { + "name": iface.name, + "base": base, + "sync": "true" if iface.attributes.rust_sync else "false", + "methods": "Ok(&[\n%s])" % methods, + } + ) + except xpidl.RustNoncompat as reason: + fd.write( + derive_iface_tmpl + % { + "name": iface.name, + "base": base, + "sync": "false", + "methods": 'Err("%s")' % reason, + } + ) + + +header = """\ +// +// DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s +// + +""" + + +def print_rust_macros_bindings(idl, fd, relpath): + fd = rust.AutoIndent(fd) + + fd.write(header % {"relpath": relpath}) + fd.write("{static D: &[Interface] = &[\n") + + for p in idl.productions: + if p.kind == "interface": + write_interface(p, fd) + + fd.write("]; D}\n") diff --git a/xpcom/idl-parser/xpidl/xpidl.py b/xpcom/idl-parser/xpidl/xpidl.py new file mode 100755 index 0000000000..b95fd14bc5 --- /dev/null +++ b/xpcom/idl-parser/xpidl/xpidl.py @@ -0,0 +1,2124 @@ +#!/usr/bin/env python +# xpidl.py - A parser for cross-platform IDL (XPIDL) files. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 parser for cross-platform IDL (XPIDL) files.""" + +import os.path +import re +import sys +import textwrap +from collections import namedtuple + +import six +from ply import lex, yacc + +"""A type conforms to the following pattern: + + def nativeType(self, calltype): + 'returns a string representation of the native type + calltype must be 'in', 'out', 'inout', or 'element' + +Interface members const/method/attribute conform to the following pattern: + + name = 'string' + + def toIDL(self): + 'returns the member signature as IDL' +""" + + +# XXX(nika): Fix the IDL files which do this so we can remove this list? +def rustPreventForward(s): + """These types are foward declared as interfaces, but never actually defined + in IDL files. We don't want to generate references to them in rust for that + reason.""" + return s in ( + "nsIFrame", + "nsSubDocumentFrame", + ) + + +def attlistToIDL(attlist): + if len(attlist) == 0: + return "" + + attlist = list(attlist) + attlist.sort(key=lambda a: a[0]) + + return "[%s] " % ",".join( + [ + "%s%s" % (name, value is not None and "(%s)" % value or "") + for name, value, aloc in attlist + ] + ) + + +_paramsHardcode = { + 2: ("array", "shared", "iid_is", "size_is", "retval"), + 3: ("array", "size_is", "const"), +} + + +def paramAttlistToIDL(attlist): + if len(attlist) == 0: + return "" + + # Hack alert: g_hash_table_foreach is pretty much unimitatable... hardcode + # quirk + attlist = list(attlist) + sorted = [] + if len(attlist) in _paramsHardcode: + for p in _paramsHardcode[len(attlist)]: + i = 0 + while i < len(attlist): + if attlist[i][0] == p: + sorted.append(attlist[i]) + del attlist[i] + continue + + i += 1 + + sorted.extend(attlist) + + return "[%s] " % ", ".join( + [ + "%s%s" % (name, value is not None and " (%s)" % value or "") + for name, value, aloc in sorted + ] + ) + + +def unaliasType(t): + while t.kind == "typedef": + t = t.realtype + assert t is not None + return t + + +def getBuiltinOrNativeTypeName(t): + t = unaliasType(t) + if t.kind == "builtin": + return t.name + elif t.kind == "native": + assert t.specialtype is not None + return "[%s]" % t.specialtype + else: + return None + + +class BuiltinLocation(object): + def get(self): + return "<builtin type>" + + def __str__(self): + return self.get() + + +class Builtin(object): + kind = "builtin" + location = BuiltinLocation + + def __init__(self, name, nativename, rustname, signed=False, maybeConst=False): + self.name = name + self.nativename = nativename + self.rustname = rustname + self.signed = signed + self.maybeConst = maybeConst + + def isPointer(self): + """Check if this type is a pointer type - this will control how pointers act""" + return self.nativename.endswith("*") + + def nativeType(self, calltype, shared=False, const=False): + if self.name in ["string", "wstring"] and calltype == "element": + raise IDLError( + "Use string class types for string Array elements", self.location + ) + + if const: + print( + IDLError( + "[const] doesn't make sense on builtin types.", + self.location, + warning=True, + ), + file=sys.stderr, + ) + const = "const " + elif calltype == "in" and self.isPointer(): + const = "const " + elif shared: + if not self.isPointer(): + raise IDLError( + "[shared] not applicable to non-pointer types.", self.location + ) + const = "const " + else: + const = "" + return "%s%s %s" % (const, self.nativename, "*" if "out" in calltype else "") + + def rustType(self, calltype, shared=False, const=False): + # We want to rewrite any *mut pointers to *const pointers if constness + # was requested. + const = const or ("out" not in calltype and self.isPointer()) or shared + rustname = self.rustname + if const and self.isPointer(): + rustname = self.rustname.replace("*mut", "*const") + + return "%s%s" % ("*mut " if "out" in calltype else "", rustname) + + +builtinNames = [ + Builtin("boolean", "bool", "bool"), + Builtin("void", "void", "libc::c_void"), + Builtin("octet", "uint8_t", "u8", False, True), + Builtin("short", "int16_t", "i16", True, True), + Builtin("long", "int32_t", "i32", True, True), + Builtin("long long", "int64_t", "i64", True, True), + Builtin("unsigned short", "uint16_t", "u16", False, True), + Builtin("unsigned long", "uint32_t", "u32", False, True), + Builtin("unsigned long long", "uint64_t", "u64", False, True), + Builtin("float", "float", "libc::c_float", True, False), + Builtin("double", "double", "libc::c_double", True, False), + Builtin("char", "char", "libc::c_char", True, False), + Builtin("string", "char *", "*const libc::c_char", False, False), + Builtin("wchar", "char16_t", "u16", False, False), + Builtin("wstring", "char16_t *", "*const u16", False, False), + # As seen in mfbt/RefCountType.h, this type has special handling to + # maintain binary compatibility with MSCOM's IUnknown that cannot be + # expressed in XPIDL. + Builtin( + "MozExternalRefCountType", "MozExternalRefCountType", "MozExternalRefCountType" + ), +] + +builtinMap = {} +for b in builtinNames: + builtinMap[b.name] = b + + +class Location(object): + _line = None + + def __init__(self, lexer, lineno, lexpos): + self._lineno = lineno + self._lexpos = lexpos + self._lexdata = lexer.lexdata + self._file = getattr(lexer, "filename", "<unknown>") + + def __eq__(self, other): + return self._lexpos == other._lexpos and self._file == other._file + + def resolve(self): + if self._line: + return + + startofline = self._lexdata.rfind("\n", 0, self._lexpos) + 1 + endofline = self._lexdata.find("\n", self._lexpos, self._lexpos + 80) + self._line = self._lexdata[startofline:endofline] + self._colno = self._lexpos - startofline + + def pointerline(self): + def i(): + for i in range(0, self._colno): + yield " " + yield "^" + + return "".join(i()) + + def get(self): + self.resolve() + return "%s line %s:%s" % (self._file, self._lineno, self._colno) + + def __str__(self): + self.resolve() + return "%s line %s:%s\n%s\n%s" % ( + self._file, + self._lineno, + self._colno, + self._line, + self.pointerline(), + ) + + +class NameMap(object): + """Map of name -> object. Each object must have a .name and .location property. + Setting the same name twice throws an error.""" + + def __init__(self): + self._d = {} + + def __getitem__(self, key): + if key in builtinMap: + return builtinMap[key] + return self._d[key] + + def __iter__(self): + return six.itervalues(self._d) + + def __contains__(self, key): + return key in builtinMap or key in self._d + + def set(self, object): + if object.name in builtinMap: + raise IDLError( + "name '%s' is a builtin and cannot be redeclared" % (object.name), + object.location, + ) + if object.name.startswith("_"): + object.name = object.name[1:] + if object.name in self._d: + old = self._d[object.name] + if old == object: + return + if isinstance(old, Forward) and isinstance(object, Interface): + self._d[object.name] = object + elif isinstance(old, Interface) and isinstance(object, Forward): + pass + else: + raise IDLError( + "name '%s' specified twice. Previous location: %s" + % (object.name, self._d[object.name].location), + object.location, + ) + else: + self._d[object.name] = object + + def get(self, id, location): + try: + return self[id] + except KeyError: + raise IDLError(f"Name '{id}' not found", location) + + +class RustNoncompat(Exception): + """ + This exception is raised when a particular type or function cannot be safely exposed to + rust code + """ + + def __init__(self, reason): + self.reason = reason + + def __str__(self): + return self.reason + + +class IDLError(Exception): + def __init__(self, message, location, warning=False, notes=None): + self.message = message + self.location = location + self.warning = warning + self.notes = notes + + def __str__(self): + error = "%s: %s, %s" % ( + self.warning and "warning" or "error", + self.message, + self.location, + ) + if self.notes is not None: + error += "\nnote: %s" % self.notes + return error + + +class Include(object): + kind = "include" + + def __init__(self, filename, location): + self.filename = filename + self.location = location + + def __str__(self): + return "".join(["include '%s'\n" % self.filename]) + + def resolve(self, parent): + def incfiles(): + yield self.filename + for dir in parent.incdirs: + yield os.path.join(dir, self.filename) + + for file in incfiles(): + if not os.path.exists(file): + continue + + if file in parent.includeCache: + self.IDL = parent.includeCache[file] + else: + self.IDL = parent.parser.parse( + open(file, encoding="utf-8").read(), filename=file + ) + self.IDL.resolve( + parent.incdirs, + parent.parser, + parent.webidlconfig, + parent.includeCache, + ) + parent.includeCache[file] = self.IDL + + for type in self.IDL.getNames(): + parent.setName(type) + parent.deps.extend(self.IDL.deps) + return + + raise IDLError("File '%s' not found" % self.filename, self.location) + + +class IDL(object): + def __init__(self, productions): + self.hasSequence = False + self.productions = productions + self.deps = [] + + def setName(self, object): + self.namemap.set(object) + + def getName(self, id, location): + if id.name == "Array": + if id.params is None or len(id.params) != 1: + raise IDLError("Array takes exactly 1 parameter", location) + self.hasSequence = True + return Array(self.getName(id.params[0], location), location) + + if id.params is not None: + raise IDLError("Generic type '%s' unrecognized" % id.name, location) + + try: + return self.namemap[id.name] + except KeyError: + raise IDLError("type '%s' not found" % id.name, location) + + def hasName(self, id): + return id in self.namemap + + def getNames(self): + return iter(self.namemap) + + def __str__(self): + return "".join([str(p) for p in self.productions]) + + def resolve(self, incdirs, parser, webidlconfig, includeCache=None): + self.namemap = NameMap() + self.incdirs = incdirs + self.parser = parser + self.webidlconfig = webidlconfig + self.includeCache = {} if includeCache is None else includeCache + for p in self.productions: + p.resolve(self) + + def includes(self): + for p in self.productions: + if p.kind == "include": + yield p + if self.hasSequence: + yield Include("nsTArray.h", BuiltinLocation) + + def needsJSTypes(self): + for p in self.productions: + if p.kind == "interface" and p.needsJSTypes(): + return True + return False + + +class CDATA(object): + kind = "cdata" + _re = re.compile(r"\n+") + + def __init__(self, data, location): + self.data = self._re.sub("\n", data) + self.location = location + + def resolve(self, parent): + # This can be a false-positive if the word `virtual` is included in a + # comment, however this doesn't seem to happen very often. + if isinstance(parent, Interface) and re.search(r"\bvirtual\b", self.data): + raise IDLError( + "cannot declare a C++ `virtual` member in XPIDL interface", + self.location, + notes=textwrap.fill( + """All virtual members must be declared directly using XPIDL. + Both the Rust bindings and XPConnect rely on the per-platform + vtable layouts generated by the XPIDL compiler to allow + cross-language XPCOM method calls between JS and C++. + Consider using a `[notxpcom, nostdcall]` method instead.""" + ), + ) + + def __str__(self): + return "cdata: %s\n\t%r\n" % (self.location.get(), self.data) + + def count(self): + return 0 + + +class Typedef(object): + kind = "typedef" + + def __init__(self, type, name, location, doccomments): + self.type = type + self.name = name + self.location = location + self.doccomments = doccomments + + def __eq__(self, other): + return self.name == other.name and self.type == other.type + + def resolve(self, parent): + parent.setName(self) + self.realtype = parent.getName(self.type, self.location) + + if not isinstance(self.realtype, (Builtin, CEnum, Native, Typedef)): + raise IDLError("Unsupported typedef target type", self.location) + + def nativeType(self, calltype): + return "%s %s" % (self.name, "*" if "out" in calltype else "") + + def rustType(self, calltype): + if self.name == "nsresult": + return "%s::nserror::nsresult" % ("*mut " if "out" in calltype else "") + + return "%s%s" % ("*mut " if "out" in calltype else "", self.name) + + def __str__(self): + return "typedef %s %s\n" % (self.type, self.name) + + +class Forward(object): + kind = "forward" + + def __init__(self, name, location, doccomments): + self.name = name + self.location = location + self.doccomments = doccomments + + def __eq__(self, other): + return other.kind == "forward" and other.name == self.name + + def resolve(self, parent): + # Hack alert: if an identifier is already present, move the doccomments + # forward. + if parent.hasName(self.name): + for i in range(0, len(parent.productions)): + if parent.productions[i] is self: + break + for i in range(i + 1, len(parent.productions)): + if hasattr(parent.productions[i], "doccomments"): + parent.productions[i].doccomments[0:0] = self.doccomments + break + + parent.setName(self) + + def nativeType(self, calltype): + if calltype == "element": + return "RefPtr<%s>" % self.name + return "%s *%s" % (self.name, "*" if "out" in calltype else "") + + def rustType(self, calltype): + if rustPreventForward(self.name): + raise RustNoncompat("forward declaration %s is unsupported" % self.name) + if calltype == "element": + return "Option<RefPtr<%s>>" % self.name + return "%s*const %s" % ("*mut" if "out" in calltype else "", self.name) + + def __str__(self): + return "forward-declared %s\n" % self.name + + +class Native(object): + kind = "native" + + modifier = None + specialtype = None + + # A tuple type here means that a custom value is used for each calltype: + # (in, out/inout, array element) respectively. + # A `None` here means that the written type should be used as-is. + specialtypes = { + "nsid": None, + "utf8string": ("const nsACString&", "nsACString&", "nsCString"), + "cstring": ("const nsACString&", "nsACString&", "nsCString"), + "astring": ("const nsAString&", "nsAString&", "nsString"), + "jsval": ("JS::Handle<JS::Value>", "JS::MutableHandle<JS::Value>", "JS::Value"), + "promise": "::mozilla::dom::Promise", + } + + def __init__(self, name, nativename, attlist, location): + self.name = name + self.nativename = nativename + self.location = location + + for name, value, aloc in attlist: + if value is not None: + raise IDLError("Unexpected attribute value", aloc) + if name in ("ptr", "ref"): + if self.modifier is not None: + raise IDLError("More than one ptr/ref modifier", aloc) + self.modifier = name + elif name in self.specialtypes.keys(): + if self.specialtype is not None: + raise IDLError("More than one special type", aloc) + self.specialtype = name + if self.specialtypes[name] is not None: + self.nativename = self.specialtypes[name] + else: + raise IDLError("Unexpected attribute", aloc) + + def __eq__(self, other): + return ( + self.name == other.name + and self.nativename == other.nativename + and self.modifier == other.modifier + and self.specialtype == other.specialtype + ) + + def resolve(self, parent): + parent.setName(self) + + def isPtr(self, calltype): + return self.modifier == "ptr" + + def isRef(self, calltype): + return self.modifier == "ref" + + def nativeType(self, calltype, const=False, shared=False): + if shared: + if calltype != "out": + raise IDLError( + "[shared] only applies to out parameters.", self.location + ) + const = True + + if isinstance(self.nativename, tuple): + if calltype == "in": + return self.nativename[0] + " " + elif "out" in calltype: + return self.nativename[1] + " " + else: + return self.nativename[2] + " " + + # 'in' nsid parameters should be made 'const' + if self.specialtype == "nsid" and calltype == "in": + const = True + + if calltype == "element": + if self.specialtype == "nsid": + if self.isPtr(calltype): + raise IDLError( + "Array<nsIDPtr> not yet supported. " + "File an XPConnect bug if you need it.", + self.location, + ) + + # ns[CI]?IDs should be held directly in Array<T>s + return self.nativename + + if self.isRef(calltype): + raise IDLError( + "[ref] qualified type unsupported in Array<T>", self.location + ) + + # Promises should be held in RefPtr<T> in Array<T>s + if self.specialtype == "promise": + return "RefPtr<mozilla::dom::Promise>" + + if self.isRef(calltype): + m = "& " # [ref] is always passed with a single indirection + else: + m = "* " if "out" in calltype else "" + if self.isPtr(calltype): + m += "* " + return "%s%s %s" % (const and "const " or "", self.nativename, m) + + def rustType(self, calltype, const=False, shared=False): + # For the most part, 'native' types don't make sense in rust, as they + # are native C++ types. However, we can support a few types here, as + # they're important and can easily be translated. + # + # NOTE: This code doesn't try to perfectly match C++ constness, as + # constness doesn't affect ABI, and raw pointers are already unsafe. + + if self.modifier not in ["ptr", "ref"]: + raise RustNoncompat("Rust only supports [ref] / [ptr] native types") + + if shared: + if calltype != "out": + raise IDLError( + "[shared] only applies to out parameters.", self.location + ) + const = True + + # 'in' nsid parameters should be made 'const' + if self.specialtype == "nsid" and calltype == "in": + const = True + + prefix = "*const " if const or shared else "*mut " + if "out" in calltype and self.isPtr(calltype): + prefix = "*mut " + prefix + + if self.specialtype: + # The string types are very special, and need to be handled seperately. + if self.specialtype in ["cstring", "utf8string"]: + if calltype == "in": + return "*const ::nsstring::nsACString" + elif "out" in calltype: + return "*mut ::nsstring::nsACString" + else: + return "::nsstring::nsCString" + if self.specialtype == "astring": + if calltype == "in": + return "*const ::nsstring::nsAString" + elif "out" in calltype: + return "*mut ::nsstring::nsAString" + else: + return "::nsstring::nsString" + # nsid has some special handling, but generally re-uses the generic + # prefix handling above. + if self.specialtype == "nsid": + if "element" in calltype: + if self.isPtr(calltype): + raise IDLError( + "Array<nsIDPtr> not yet supported. " + "File an XPConnect bug if you need it.", + self.location, + ) + return self.nativename + return prefix + self.nativename + raise RustNoncompat("special type %s unsupported" % self.specialtype) + + # These 3 special types correspond to native pointer types which can + # generally be supported behind pointers. Other types are not supported + # for now. + if self.nativename == "void": + return prefix + "libc::c_void" + if self.nativename == "char": + return prefix + "libc::c_char" + if self.nativename == "char16_t": + return prefix + "u16" + + raise RustNoncompat("native type %s unsupported" % self.nativename) + + def __str__(self): + return "native %s(%s)\n" % (self.name, self.nativename) + + +class WebIDL(object): + kind = "webidl" + + def __init__(self, name, location): + self.name = name + self.location = location + + def __eq__(self, other): + return other.kind == "webidl" and self.name == other.name + + def resolve(self, parent): + # XXX(nika): We don't handle _every_ kind of webidl object here (as that + # would be hard). For example, we don't support nsIDOM*-defaulting + # interfaces. + # TODO: More explicit compile-time checks? + + assert ( + parent.webidlconfig is not None + ), "WebIDL declarations require passing webidlconfig to resolve." + + # Resolve our native name according to the WebIDL configs. + config = parent.webidlconfig.get(self.name, {}) + self.native = config.get("nativeType") + if self.native is None: + self.native = "mozilla::dom::%s" % self.name + self.headerFile = config.get("headerFile") + if self.headerFile is None: + self.headerFile = self.native.replace("::", "/") + ".h" + + parent.setName(self) + + def nativeType(self, calltype, const=False): + if calltype == "element": + return "RefPtr<%s%s>" % ("const " if const else "", self.native) + return "%s%s *%s" % ( + "const " if const else "", + self.native, + "*" if "out" in calltype else "", + ) + + def rustType(self, calltype, const=False): + # Just expose the type as a void* - we can't do any better. + return "%s*const libc::c_void" % ("*mut " if "out" in calltype else "") + + def __str__(self): + return "webidl %s\n" % self.name + + +class Interface(object): + kind = "interface" + + def __init__(self, name, attlist, base, members, location, doccomments): + self.name = name + self.attributes = InterfaceAttributes(attlist, location) + self.base = base + self.members = members + self.location = location + self.namemap = NameMap() + self.doccomments = doccomments + self.nativename = name + + for m in members: + if not isinstance(m, CDATA): + self.namemap.set(m) + + def __eq__(self, other): + return self.name == other.name and self.location == other.location + + def resolve(self, parent): + self.idl = parent + + if not self.attributes.scriptable and self.attributes.builtinclass: + raise IDLError( + "Non-scriptable interface '%s' doesn't need to be marked builtinclass" + % self.name, + self.location, + ) + + # Hack alert: if an identifier is already present, libIDL assigns + # doc comments incorrectly. This is quirks-mode extraordinaire! + if parent.hasName(self.name): + for member in self.members: + if hasattr(member, "doccomments"): + member.doccomments[0:0] = self.doccomments + break + self.doccomments = parent.getName(TypeId(self.name), None).doccomments + + if self.attributes.function: + has_method = False + for member in self.members: + if member.kind == "method": + if has_method: + raise IDLError( + "interface '%s' has multiple methods, but marked 'function'" + % self.name, + self.location, + ) + else: + has_method = True + + parent.setName(self) + if self.base is not None: + realbase = parent.getName(TypeId(self.base), self.location) + if realbase.kind != "interface": + raise IDLError( + "interface '%s' inherits from non-interface type '%s'" + % (self.name, self.base), + self.location, + ) + + if self.attributes.scriptable and not realbase.attributes.scriptable: + raise IDLError( + "interface '%s' is scriptable but derives from " + "non-scriptable '%s'" % (self.name, self.base), + self.location, + warning=True, + ) + + if ( + self.attributes.scriptable + and realbase.attributes.builtinclass + and not self.attributes.builtinclass + ): + raise IDLError( + "interface '%s' is not builtinclass but derives from " + "builtinclass '%s'" % (self.name, self.base), + self.location, + ) + + if realbase.attributes.rust_sync and not self.attributes.rust_sync: + raise IDLError( + "interface '%s' is not rust_sync but derives from rust_sync '%s'" + % (self.name, self.base), + self.location, + ) + + if ( + self.attributes.rust_sync + and self.attributes.scriptable + and not self.attributes.builtinclass + ): + raise IDLError( + "interface '%s' is rust_sync but is not builtinclass" % self.name, + self.location, + ) + elif self.name != "nsISupports": + raise IDLError( + "Interface '%s' must inherit from nsISupports" % self.name, + self.location, + ) + + for member in self.members: + member.resolve(self) + + # The number 250 is NOT arbitrary; this number is the maximum number of + # stub entries defined in xpcom/reflect/xptcall/genstubs.pl + # Do not increase this value without increasing the number in that + # location, or you WILL cause otherwise unknown problems! + if self.countEntries() > 250 and not self.attributes.builtinclass: + raise IDLError( + "interface '%s' has too many entries" % self.name, self.location + ) + + def nativeType(self, calltype, const=False): + if calltype == "element": + return "RefPtr<%s>" % self.name + return "%s%s *%s" % ( + "const " if const else "", + self.name, + "*" if "out" in calltype else "", + ) + + def rustType(self, calltype, const=False): + if calltype == "element": + return "Option<RefPtr<%s>>" % self.name + return "%s*const %s" % ("*mut " if "out" in calltype else "", self.name) + + def __str__(self): + l = ["interface %s\n" % self.name] + if self.base is not None: + l.append("\tbase %s\n" % self.base) + l.append(str(self.attributes)) + if self.members is None: + l.append("\tincomplete type\n") + else: + for m in self.members: + l.append(str(m)) + return "".join(l) + + def getConst(self, name, location): + # The constant may be in a base class + iface = self + while name not in iface.namemap and iface.base is not None: + iface = self.idl.getName(TypeId(iface.base), self.location) + if name not in iface.namemap: + raise IDLError("cannot find symbol '%s'" % name, location) + c = iface.namemap.get(name, location) + if c.kind != "const": + raise IDLError("symbol '%s' is not a constant" % name, location) + + return c.getValue() + + def needsJSTypes(self): + for m in self.members: + if m.kind == "attribute" and m.type == TypeId("jsval"): + return True + if m.kind == "method" and m.needsJSTypes(): + return True + return False + + def countEntries(self): + """Returns the number of entries in the vtable for this interface.""" + total = sum(member.count() for member in self.members) + if self.base is not None: + realbase = self.idl.getName(TypeId(self.base), self.location) + total += realbase.countEntries() + return total + + +class InterfaceAttributes(object): + uuid = None + scriptable = False + builtinclass = False + function = False + main_process_scriptable_only = False + rust_sync = False + + def setuuid(self, value): + self.uuid = value.lower() + + def setscriptable(self): + self.scriptable = True + + def setfunction(self): + self.function = True + + def setbuiltinclass(self): + self.builtinclass = True + + def setmain_process_scriptable_only(self): + self.main_process_scriptable_only = True + + def setrust_sync(self): + self.rust_sync = True + + actions = { + "uuid": (True, setuuid), + "scriptable": (False, setscriptable), + "builtinclass": (False, setbuiltinclass), + "function": (False, setfunction), + "object": (False, lambda self: True), + "main_process_scriptable_only": (False, setmain_process_scriptable_only), + "rust_sync": (False, setrust_sync), + } + + def __init__(self, attlist, location): + def badattribute(self): + raise IDLError("Unexpected interface attribute '%s'" % name, location) + + for name, val, aloc in attlist: + hasval, action = self.actions.get(name, (False, badattribute)) + if hasval: + if val is None: + raise IDLError("Expected value for attribute '%s'" % name, aloc) + + action(self, val) + else: + if val is not None: + raise IDLError("Unexpected value for attribute '%s'" % name, aloc) + + action(self) + + if self.uuid is None: + raise IDLError("interface has no uuid", location) + + def __str__(self): + l = [] + if self.uuid: + l.append("\tuuid: %s\n" % self.uuid) + if self.scriptable: + l.append("\tscriptable\n") + if self.builtinclass: + l.append("\tbuiltinclass\n") + if self.function: + l.append("\tfunction\n") + if self.main_process_scriptable_only: + l.append("\tmain_process_scriptable_only\n") + if self.rust_sync: + l.append("\trust_sync\n") + return "".join(l) + + +class ConstMember(object): + kind = "const" + + def __init__(self, type, name, value, location, doccomments): + self.type = type + self.name = name + self.valueFn = value + self.location = location + self.doccomments = doccomments + + def resolve(self, parent): + self.realtype = parent.idl.getName(self.type, self.location) + self.iface = parent + basetype = self.realtype + while isinstance(basetype, Typedef): + basetype = basetype.realtype + if not isinstance(basetype, Builtin) or not basetype.maybeConst: + raise IDLError( + "const may only be a short or long type, not %s" % self.type, + self.location, + ) + + self.basetype = basetype + # Value is a lambda. Resolve it. + self.value = self.valueFn(self.iface) + + min_val = -(2**31) if basetype.signed else 0 + max_val = 2**31 - 1 if basetype.signed else 2**32 - 1 + if self.value < min_val or self.value > max_val: + raise IDLError( + "xpidl constants must fit within %s" + % ("int32_t" if basetype.signed else "uint32_t"), + self.location, + ) + + def getValue(self): + return self.value + + def __str__(self): + return "\tconst %s %s = %s\n" % (self.type, self.name, self.getValue()) + + def count(self): + return 0 + + +# Represents a single name/value pair in a CEnum +class CEnumVariant(object): + # Treat CEnumVariants as consts in terms of value resolution, so we can + # do things like binary operation values for enum members. + kind = "const" + + def __init__(self, name, value, location): + self.name = name + self.valueFn = value + self.location = location + + def getValue(self): + return self.value + + +class CEnum(object): + kind = "cenum" + + def __init__(self, width, name, variants, location, doccomments): + # We have to set a name here, otherwise we won't pass namemap checks on + # the interface. This name will change it in resolve(), in order to + # namespace the enum within the interface. + self.name = name + self.basename = name + self.width = width + self.location = location + self.namemap = NameMap() + self.doccomments = doccomments + self.variants = variants + if self.width not in (8, 16, 32): + raise IDLError("Width must be one of {8, 16, 32}", self.location) + + def resolve(self, iface): + self.iface = iface + # Renaming enum to faux-namespace the enum type to the interface in JS + # so we don't collide in the global namespace. Hacky/ugly but it does + # the job well enough, and the name will still be interface::variant in + # C++. + self.name = "%s_%s" % (self.iface.name, self.basename) + self.iface.idl.setName(self) + + # Compute the value for each enum variant that doesn't set its own + # value + next_value = 0 + for variant in self.variants: + # CEnum variants resolve to interface level consts in javascript, + # meaning their names could collide with other interface members. + # Iterate through all CEnum variants to make sure there are no + # collisions. + self.iface.namemap.set(variant) + # Value may be a lambda. If it is, resolve it. + if variant.valueFn: + next_value = variant.value = variant.valueFn(self.iface) + else: + variant.value = next_value + next_value += 1 + + def count(self): + return 0 + + def nativeType(self, calltype): + if "out" in calltype: + return "%s::%s *" % (self.iface.name, self.basename) + return "%s::%s " % (self.iface.name, self.basename) + + def rustType(self, calltype): + return "%s u%d" % ("*mut" if "out" in calltype else "", self.width) + + def __str__(self): + body = ", ".join("%s = %s" % v for v in self.variants) + return "\tcenum %s : %d { %s };\n" % (self.name, self.width, body) + + +# Infallible doesn't work for all return types. +# +# It also must be implemented on a builtinclass (otherwise it'd be unsound as +# it could be implemented by JS). +def ensureInfallibleIsSound(methodOrAttribute): + if not methodOrAttribute.infallible: + return + if methodOrAttribute.realtype.kind not in [ + "builtin", + "interface", + "forward", + "webidl", + "cenum", + ]: + raise IDLError( + "[infallible] only works on interfaces, domobjects, and builtin types " + "(numbers, booleans, cenum, and raw char types)", + methodOrAttribute.location, + ) + ifaceAttributes = methodOrAttribute.iface.attributes + if ifaceAttributes.scriptable and not ifaceAttributes.builtinclass: + raise IDLError( + "[infallible] attributes and methods are only allowed on " + "non-[scriptable] or [builtinclass] interfaces", + methodOrAttribute.location, + ) + + if methodOrAttribute.notxpcom: + raise IDLError( + "[infallible] does not make sense for a [notxpcom] " "method or attribute", + methodOrAttribute.location, + ) + + +# An interface cannot be implemented by JS if it has a notxpcom or nostdcall +# method or attribute, so it must be marked as builtinclass. +def ensureBuiltinClassIfNeeded(methodOrAttribute): + iface = methodOrAttribute.iface + if not iface.attributes.scriptable or iface.attributes.builtinclass: + return + if iface.name == "nsISupports": + return + if methodOrAttribute.notxpcom: + raise IDLError( + ( + "scriptable interface '%s' must be marked [builtinclass] because it " + "contains a [notxpcom] %s '%s'" + ) + % (iface.name, methodOrAttribute.kind, methodOrAttribute.name), + methodOrAttribute.location, + ) + if methodOrAttribute.nostdcall: + raise IDLError( + ( + "scriptable interface '%s' must be marked [builtinclass] because it " + "contains a [nostdcall] %s '%s'" + ) + % (iface.name, methodOrAttribute.kind, methodOrAttribute.name), + methodOrAttribute.location, + ) + + +class Attribute(object): + kind = "attribute" + noscript = False + notxpcom = False + readonly = False + symbol = False + implicit_jscontext = False + nostdcall = False + must_use = False + binaryname = None + infallible = False + # explicit_setter_can_run_script is true if the attribute is explicitly + # annotated as having a setter that can cause script to run. + explicit_setter_can_run_script = False + # explicit_getter_can_run_script is true if the attribute is explicitly + # annotated as having a getter that can cause script to run. + explicit_getter_can_run_script = False + + def __init__(self, type, name, attlist, readonly, location, doccomments): + self.type = type + self.name = name + self.attlist = attlist + self.readonly = readonly + self.location = location + self.doccomments = doccomments + + for name, value, aloc in attlist: + if name == "binaryname": + if value is None: + raise IDLError("binaryname attribute requires a value", aloc) + + self.binaryname = value + continue + + if value is not None: + raise IDLError("Unexpected attribute value", aloc) + + if name == "noscript": + self.noscript = True + elif name == "notxpcom": + self.notxpcom = True + elif name == "symbol": + self.symbol = True + elif name == "implicit_jscontext": + self.implicit_jscontext = True + elif name == "nostdcall": + self.nostdcall = True + elif name == "must_use": + self.must_use = True + elif name == "infallible": + self.infallible = True + elif name == "can_run_script": + if ( + self.explicit_setter_can_run_script + or self.explicit_getter_can_run_script + ): + raise IDLError( + "Redundant getter_can_run_script or " + "setter_can_run_script annotation on " + "attribute", + aloc, + ) + self.explicit_setter_can_run_script = True + self.explicit_getter_can_run_script = True + elif name == "setter_can_run_script": + if self.explicit_setter_can_run_script: + raise IDLError( + "Redundant setter_can_run_script annotation " "on attribute", + aloc, + ) + self.explicit_setter_can_run_script = True + elif name == "getter_can_run_script": + if self.explicit_getter_can_run_script: + raise IDLError( + "Redundant getter_can_run_script annotation " "on attribute", + aloc, + ) + self.explicit_getter_can_run_script = True + else: + raise IDLError("Unexpected attribute '%s'" % name, aloc) + + def resolve(self, iface): + self.iface = iface + self.realtype = iface.idl.getName(self.type, self.location) + + ensureInfallibleIsSound(self) + ensureBuiltinClassIfNeeded(self) + + def toIDL(self): + attribs = attlistToIDL(self.attlist) + readonly = self.readonly and "readonly " or "" + return "%s%sattribute %s %s;" % (attribs, readonly, self.type, self.name) + + def isScriptable(self): + if not self.iface.attributes.scriptable: + return False + return not (self.noscript or self.notxpcom or self.nostdcall) + + def __str__(self): + return "\t%sattribute %s %s\n" % ( + self.readonly and "readonly " or "", + self.type, + self.name, + ) + + def count(self): + return self.readonly and 1 or 2 + + +class Method(object): + kind = "method" + noscript = False + notxpcom = False + symbol = False + binaryname = None + implicit_jscontext = False + nostdcall = False + must_use = False + optional_argc = False + # explicit_can_run_script is true if the method is explicitly annotated + # as being able to cause script to run. + explicit_can_run_script = False + infallible = False + + def __init__(self, type, name, attlist, paramlist, location, doccomments, raises): + self.type = type + self.name = name + self.attlist = attlist + self.params = paramlist + self.location = location + self.doccomments = doccomments + self.raises = raises + + for name, value, aloc in attlist: + if name == "binaryname": + if value is None: + raise IDLError("binaryname attribute requires a value", aloc) + + self.binaryname = value + continue + + if value is not None: + raise IDLError("Unexpected attribute value", aloc) + + if name == "noscript": + self.noscript = True + elif name == "notxpcom": + self.notxpcom = True + elif name == "symbol": + self.symbol = True + elif name == "implicit_jscontext": + self.implicit_jscontext = True + elif name == "optional_argc": + self.optional_argc = True + elif name == "nostdcall": + self.nostdcall = True + elif name == "must_use": + self.must_use = True + elif name == "can_run_script": + self.explicit_can_run_script = True + elif name == "infallible": + self.infallible = True + else: + raise IDLError("Unexpected attribute '%s'" % name, aloc) + + self.namemap = NameMap() + for p in paramlist: + self.namemap.set(p) + + def resolve(self, iface): + self.iface = iface + self.realtype = self.iface.idl.getName(self.type, self.location) + + ensureInfallibleIsSound(self) + ensureBuiltinClassIfNeeded(self) + + for p in self.params: + p.resolve(self) + for p in self.params: + if p.retval and p != self.params[-1]: + raise IDLError( + "'retval' parameter '%s' is not the last parameter" % p.name, + self.location, + ) + if p.size_is: + size_param = self.namemap.get(p.size_is, p.location) + if ( + p.paramtype.count("in") == 1 + and size_param.paramtype.count("in") == 0 + ): + raise IDLError( + "size_is parameter of an input must also be an input", + p.location, + ) + if getBuiltinOrNativeTypeName(size_param.realtype) != "unsigned long": + raise IDLError( + "size_is parameter must have type 'unsigned long'", + p.location, + ) + if p.iid_is: + iid_param = self.namemap.get(p.iid_is, p.location) + if ( + p.paramtype.count("in") == 1 + and iid_param.paramtype.count("in") == 0 + ): + raise IDLError( + "iid_is parameter of an input must also be an input", + p.location, + ) + if getBuiltinOrNativeTypeName(iid_param.realtype) != "[nsid]": + raise IDLError( + "iid_is parameter must be an nsIID", + self.location, + ) + + def isScriptable(self): + if not self.iface.attributes.scriptable: + return False + return not (self.noscript or self.notxpcom or self.nostdcall) + + def __str__(self): + return "\t%s %s(%s)\n" % ( + self.type, + self.name, + ", ".join([p.name for p in self.params]), + ) + + def toIDL(self): + if len(self.raises): + raises = " raises (%s)" % ",".join(self.raises) + else: + raises = "" + + return "%s%s %s (%s)%s;" % ( + attlistToIDL(self.attlist), + self.type, + self.name, + ", ".join([p.toIDL() for p in self.params]), + raises, + ) + + def needsJSTypes(self): + if self.implicit_jscontext: + return True + if self.type == TypeId("jsval"): + return True + for p in self.params: + t = p.realtype + if isinstance(t, Native) and t.specialtype == "jsval": + return True + return False + + def count(self): + return 1 + + +class Param(object): + size_is = None + iid_is = None + const = False + array = False + retval = False + shared = False + optional = False + default_value = None + + def __init__(self, paramtype, type, name, attlist, location, realtype=None): + self.paramtype = paramtype + self.type = type + self.name = name + self.attlist = attlist + self.location = location + self.realtype = realtype + + for name, value, aloc in attlist: + # Put the value-taking attributes first! + if name == "size_is": + if value is None: + raise IDLError("'size_is' must specify a parameter", aloc) + self.size_is = value + elif name == "iid_is": + if value is None: + raise IDLError("'iid_is' must specify a parameter", aloc) + self.iid_is = value + elif name == "default": + if value is None: + raise IDLError("'default' must specify a default value", aloc) + self.default_value = value + else: + if value is not None: + raise IDLError("Unexpected value for attribute '%s'" % name, aloc) + + if name == "const": + self.const = True + elif name == "array": + self.array = True + elif name == "retval": + self.retval = True + elif name == "shared": + self.shared = True + elif name == "optional": + self.optional = True + else: + raise IDLError("Unexpected attribute '%s'" % name, aloc) + + def resolve(self, method): + self.realtype = method.iface.idl.getName(self.type, self.location) + if self.array: + self.realtype = LegacyArray(self.realtype) + + def nativeType(self): + kwargs = {} + if self.shared: + kwargs["shared"] = True + if self.const: + kwargs["const"] = True + + try: + return self.realtype.nativeType(self.paramtype, **kwargs) + except IDLError as e: + raise IDLError(str(e), self.location) + except TypeError: + raise IDLError("Unexpected parameter attribute", self.location) + + def rustType(self): + kwargs = {} + if self.shared: + kwargs["shared"] = True + if self.const: + kwargs["const"] = True + + try: + return self.realtype.rustType(self.paramtype, **kwargs) + except IDLError as e: + raise IDLError(str(e), self.location) + except TypeError: + raise IDLError("Unexpected parameter attribute", self.location) + + def toIDL(self): + return "%s%s %s %s" % ( + paramAttlistToIDL(self.attlist), + self.paramtype, + self.type, + self.name, + ) + + +class LegacyArray(object): + def __init__(self, basetype): + self.type = basetype + self.location = self.type.location + + def nativeType(self, calltype, const=False): + if "element" in calltype: + raise IDLError("nested [array] unsupported", self.location) + + # For legacy reasons, we have to add a 'const ' to builtin pointer array + # types. (`[array] in string` and `[array] in wstring` parameters) + if ( + calltype == "in" + and isinstance(self.type, Builtin) + and self.type.isPointer() + ): + const = True + + return "%s%s*%s" % ( + "const " if const else "", + self.type.nativeType("legacyelement"), + "*" if "out" in calltype else "", + ) + + def rustType(self, calltype, const=False): + return "%s%s%s" % ( + "*mut " if "out" in calltype else "", + "*const " if const else "*mut ", + self.type.rustType("legacyelement"), + ) + + +class Array(object): + kind = "array" + + def __init__(self, type, location): + self.type = type + self.location = location + + @property + def name(self): + return "Array<%s>" % self.type.name + + def resolve(self, idl): + idl.getName(self.type, self.location) + + def nativeType(self, calltype): + if calltype == "legacyelement": + raise IDLError("[array] Array<T> is unsupported", self.location) + + base = "nsTArray<%s>" % self.type.nativeType("element") + if "out" in calltype: + return "%s& " % base + elif "in" == calltype: + return "const %s& " % base + else: + return base + + def rustType(self, calltype): + if calltype == "legacyelement": + raise IDLError("[array] Array<T> is unsupported", self.location) + + base = "thin_vec::ThinVec<%s>" % self.type.rustType("element") + if "out" in calltype: + return "*mut %s" % base + elif "in" == calltype: + return "*const %s" % base + else: + return base + + +TypeId = namedtuple("TypeId", "name params") + + +# Make str(TypeId) produce a nicer value +TypeId.__str__ = ( + lambda self: "%s<%s>" % (self.name, ", ".join(str(p) for p in self.params)) + if self.params is not None + else self.name +) + + +# Allow skipping 'params' in TypeId(..) +TypeId.__new__.__defaults__ = (None,) + + +class IDLParser(object): + keywords = { + "cenum": "CENUM", + "const": "CONST", + "interface": "INTERFACE", + "in": "IN", + "inout": "INOUT", + "out": "OUT", + "attribute": "ATTRIBUTE", + "raises": "RAISES", + "readonly": "READONLY", + "native": "NATIVE", + "typedef": "TYPEDEF", + "webidl": "WEBIDL", + } + + tokens = [ + "IDENTIFIER", + "CDATA", + "INCLUDE", + "IID", + "NUMBER", + "HEXNUM", + "LSHIFT", + "RSHIFT", + "NATIVEID", + ] + + tokens.extend(keywords.values()) + + states = (("nativeid", "exclusive"),) + + hexchar = r"[a-fA-F0-9]" + + t_NUMBER = r"-?\d+" + t_HEXNUM = r"0x%s+" % hexchar + t_LSHIFT = r"<<" + t_RSHIFT = r">>" + + literals = '"(){}[]<>,;:=|+-*' + + t_ignore = " \t" + + def t_multilinecomment(self, t): + r"/\*(\n|.)*?\*/" + t.lexer.lineno += t.value.count("\n") + if t.value.startswith("/**"): + self._doccomments.append(t.value) + + def t_singlelinecomment(self, t): + r"//[^\n]*" + + def t_IID(self, t): + return t + + t_IID.__doc__ = r"%(c)s{8}-%(c)s{4}-%(c)s{4}-%(c)s{4}-%(c)s{12}" % {"c": hexchar} + + def t_IDENTIFIER(self, t): + r"(unsigned\ long\ long|unsigned\ short|unsigned\ long|long\ long)(?!_?[A-Za-z][A-Za-z_0-9])|_?[A-Za-z][A-Za-z_0-9]*" # NOQA: E501 + t.type = self.keywords.get(t.value, "IDENTIFIER") + return t + + def t_LCDATA(self, t): + r"%\{[ ]*C\+\+[ ]*\n(?P<cdata>(\n|.)*?\n?)%\}[ ]*(C\+\+)?" + t.type = "CDATA" + t.value = t.lexer.lexmatch.group("cdata") + t.lexer.lineno += t.value.count("\n") + return t + + def t_INCLUDE(self, t): + r'\#include[ \t]+"[^"\n]+"' + inc, value, end = t.value.split('"') + t.value = value + return t + + def t_directive(self, t): + r"\#(?P<directive>[a-zA-Z]+)[^\n]+" + raise IDLError( + "Unrecognized directive %s" % t.lexer.lexmatch.group("directive"), + Location( + lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos + ), + ) + + def t_newline(self, t): + r"\n+" + t.lexer.lineno += len(t.value) + + def t_nativeid_NATIVEID(self, t): + # Matches non-parenthesis characters, or a single open and closing + # parenthesis with at least one non-parenthesis character before, + # between and after them (for compatibility with std::function). + r"[^()\n]+(?:\([^()\n]+\)[^()\n]+)?(?=\))" + t.lexer.begin("INITIAL") + return t + + t_nativeid_ignore = "" + + def t_ANY_error(self, t): + raise IDLError( + "unrecognized input", + Location( + lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos + ), + ) + + precedence = ( + ("left", "|"), + ("left", "LSHIFT", "RSHIFT"), + ("left", "+", "-"), + ("left", "*"), + ("left", "UMINUS"), + ) + + def p_idlfile(self, p): + """idlfile : productions""" + p[0] = IDL(p[1]) + + def p_productions_start(self, p): + """productions :""" + p[0] = [] + + def p_productions_cdata(self, p): + """productions : CDATA productions""" + p[0] = list(p[2]) + p[0].insert(0, CDATA(p[1], self.getLocation(p, 1))) + + def p_productions_include(self, p): + """productions : INCLUDE productions""" + p[0] = list(p[2]) + p[0].insert(0, Include(p[1], self.getLocation(p, 1))) + + def p_productions_interface(self, p): + """productions : interface productions + | typedef productions + | native productions + | webidl productions""" + p[0] = list(p[2]) + p[0].insert(0, p[1]) + + def p_typedef(self, p): + """typedef : TYPEDEF type IDENTIFIER ';'""" + p[0] = Typedef( + type=p[2], + name=p[3], + location=self.getLocation(p, 1), + doccomments=p.slice[1].doccomments, + ) + + def p_native(self, p): + """native : attributes NATIVE IDENTIFIER afternativeid '(' NATIVEID ')' ';'""" + p[0] = Native( + name=p[3], + nativename=p[6], + attlist=p[1]["attlist"], + location=self.getLocation(p, 2), + ) + + def p_afternativeid(self, p): + """afternativeid :""" + # this is a place marker: we switch the lexer into literal identifier + # mode here, to slurp up everything until the closeparen + self.lexer.begin("nativeid") + + def p_webidl(self, p): + """webidl : WEBIDL IDENTIFIER ';'""" + p[0] = WebIDL(name=p[2], location=self.getLocation(p, 2)) + + def p_anyident(self, p): + """anyident : IDENTIFIER + | CONST""" + p[0] = {"value": p[1], "location": self.getLocation(p, 1)} + + def p_attributes(self, p): + """attributes : '[' attlist ']' + |""" + if len(p) == 1: + p[0] = {"attlist": []} + else: + p[0] = {"attlist": p[2], "doccomments": p.slice[1].doccomments} + + def p_attlist_start(self, p): + """attlist : attribute""" + p[0] = [p[1]] + + def p_attlist_continue(self, p): + """attlist : attribute ',' attlist""" + p[0] = list(p[3]) + p[0].insert(0, p[1]) + + def p_attribute(self, p): + """attribute : anyident attributeval""" + p[0] = (p[1]["value"], p[2], p[1]["location"]) + + def p_attributeval(self, p): + """attributeval : '(' IDENTIFIER ')' + | '(' IID ')' + |""" + if len(p) > 1: + p[0] = p[2] + + def p_interface(self, p): + """interface : attributes INTERFACE IDENTIFIER ifacebase ifacebody ';'""" + atts, INTERFACE, name, base, body, SEMI = p[1:] + attlist = atts["attlist"] + doccomments = [] + if "doccomments" in atts: + doccomments.extend(atts["doccomments"]) + doccomments.extend(p.slice[2].doccomments) + + def loc(): + return self.getLocation(p, 2) + + if body is None: + # forward-declared interface... must not have attributes! + if len(attlist) != 0: + raise IDLError( + "Forward-declared interface must not have attributes", loc() + ) + + if base is not None: + raise IDLError("Forward-declared interface must not have a base", loc()) + p[0] = Forward(name=name, location=loc(), doccomments=doccomments) + else: + p[0] = Interface( + name=name, + attlist=attlist, + base=base, + members=body, + location=loc(), + doccomments=doccomments, + ) + + def p_ifacebody(self, p): + """ifacebody : '{' members '}' + |""" + if len(p) > 1: + p[0] = p[2] + + def p_ifacebase(self, p): + """ifacebase : ':' IDENTIFIER + |""" + if len(p) == 3: + p[0] = p[2] + + def p_members_start(self, p): + """members :""" + p[0] = [] + + def p_members_continue(self, p): + """members : member members""" + p[0] = list(p[2]) + p[0].insert(0, p[1]) + + def p_member_cdata(self, p): + """member : CDATA""" + p[0] = CDATA(p[1], self.getLocation(p, 1)) + + def p_member_const(self, p): + """member : CONST type IDENTIFIER '=' number ';'""" + p[0] = ConstMember( + type=p[2], + name=p[3], + value=p[5], + location=self.getLocation(p, 1), + doccomments=p.slice[1].doccomments, + ) + + # All "number" products return a function(interface) + + def p_number_decimal(self, p): + """number : NUMBER""" + n = int(p[1]) + p[0] = lambda i: n + + def p_number_hex(self, p): + """number : HEXNUM""" + n = int(p[1], 16) + p[0] = lambda i: n + + def p_number_identifier(self, p): + """number : IDENTIFIER""" + id = p[1] + loc = self.getLocation(p, 1) + p[0] = lambda i: i.getConst(id, loc) + + def p_number_paren(self, p): + """number : '(' number ')'""" + p[0] = p[2] + + def p_number_neg(self, p): + """number : '-' number %prec UMINUS""" + n = p[2] + p[0] = lambda i: -n(i) + + def p_number_add(self, p): + """number : number '+' number + | number '-' number + | number '*' number""" + n1 = p[1] + n2 = p[3] + if p[2] == "+": + p[0] = lambda i: n1(i) + n2(i) + elif p[2] == "-": + p[0] = lambda i: n1(i) - n2(i) + else: + p[0] = lambda i: n1(i) * n2(i) + + def p_number_shift(self, p): + """number : number LSHIFT number + | number RSHIFT number""" + n1 = p[1] + n2 = p[3] + if p[2] == "<<": + p[0] = lambda i: n1(i) << n2(i) + else: + p[0] = lambda i: n1(i) >> n2(i) + + def p_number_bitor(self, p): + """number : number '|' number""" + n1 = p[1] + n2 = p[3] + p[0] = lambda i: n1(i) | n2(i) + + def p_member_cenum(self, p): + """member : CENUM IDENTIFIER ':' NUMBER '{' variants '}' ';'""" + p[0] = CEnum( + name=p[2], + width=int(p[4]), + variants=p[6], + location=self.getLocation(p, 1), + doccomments=p.slice[1].doccomments, + ) + + def p_variants_start(self, p): + """variants :""" + p[0] = [] + + def p_variants_single(self, p): + """variants : variant""" + p[0] = [p[1]] + + def p_variants_continue(self, p): + """variants : variant ',' variants""" + p[0] = [p[1]] + p[3] + + def p_variant_implicit(self, p): + """variant : IDENTIFIER""" + p[0] = CEnumVariant(p[1], None, self.getLocation(p, 1)) + + def p_variant_explicit(self, p): + """variant : IDENTIFIER '=' number""" + p[0] = CEnumVariant(p[1], p[3], self.getLocation(p, 1)) + + def p_member_att(self, p): + """member : attributes optreadonly ATTRIBUTE type IDENTIFIER ';'""" + if "doccomments" in p[1]: + doccomments = p[1]["doccomments"] + elif p[2] is not None: + doccomments = p[2] + else: + doccomments = p.slice[3].doccomments + + p[0] = Attribute( + type=p[4], + name=p[5], + attlist=p[1]["attlist"], + readonly=p[2] is not None, + location=self.getLocation(p, 3), + doccomments=doccomments, + ) + + def p_member_method(self, p): + """member : attributes type IDENTIFIER '(' paramlist ')' raises ';'""" + if "doccomments" in p[1]: + doccomments = p[1]["doccomments"] + else: + doccomments = p.slice[2].doccomments + + p[0] = Method( + type=p[2], + name=p[3], + attlist=p[1]["attlist"], + paramlist=p[5], + location=self.getLocation(p, 3), + doccomments=doccomments, + raises=p[7], + ) + + def p_paramlist(self, p): + """paramlist : param moreparams + |""" + if len(p) == 1: + p[0] = [] + else: + p[0] = list(p[2]) + p[0].insert(0, p[1]) + + def p_moreparams_start(self, p): + """moreparams :""" + p[0] = [] + + def p_moreparams_continue(self, p): + """moreparams : ',' param moreparams""" + p[0] = list(p[3]) + p[0].insert(0, p[2]) + + def p_param(self, p): + """param : attributes paramtype type IDENTIFIER""" + p[0] = Param( + paramtype=p[2], + type=p[3], + name=p[4], + attlist=p[1]["attlist"], + location=self.getLocation(p, 4), + ) + + def p_paramtype(self, p): + """paramtype : IN + | INOUT + | OUT""" + p[0] = p[1] + + def p_optreadonly(self, p): + """optreadonly : READONLY + |""" + if len(p) > 1: + p[0] = p.slice[1].doccomments + else: + p[0] = None + + def p_raises(self, p): + """raises : RAISES '(' idlist ')' + |""" + if len(p) == 1: + p[0] = [] + else: + p[0] = p[3] + + def p_idlist(self, p): + """idlist : IDENTIFIER""" + p[0] = [p[1]] + + def p_idlist_continue(self, p): + """idlist : IDENTIFIER ',' idlist""" + p[0] = list(p[3]) + p[0].insert(0, p[1]) + + def p_type_id(self, p): + """type : IDENTIFIER""" + p[0] = TypeId(name=p[1]) + p.slice[0].doccomments = p.slice[1].doccomments + + def p_type_generic(self, p): + """type : IDENTIFIER '<' typelist '>'""" + p[0] = TypeId(name=p[1], params=p[3]) + p.slice[0].doccomments = p.slice[1].doccomments + + def p_typelist(self, p): + """typelist : type""" + p[0] = [p[1]] + + def p_typelist_continue(self, p): + """typelist : type ',' typelist""" + p[0] = list(p[3]) + p[0].insert(0, p[1]) + + def p_error(self, t): + if not t: + raise IDLError( + "Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) " + "or both", + None, + ) + else: + location = Location(self.lexer, t.lineno, t.lexpos) + raise IDLError("invalid syntax", location) + + def __init__(self): + self._doccomments = [] + self.lexer = lex.lex(object=self, debug=False) + self.parser = yacc.yacc(module=self, write_tables=False, debug=False) + + def clearComments(self): + self._doccomments = [] + + def token(self): + t = self.lexer.token() + if t is not None and t.type != "CDATA": + t.doccomments = self._doccomments + self._doccomments = [] + return t + + def parse(self, data, filename=None): + if filename is not None: + self.lexer.filename = filename + self.lexer.lineno = 1 + self.lexer.input(data) + idl = self.parser.parse(lexer=self) + if filename is not None: + idl.deps.append(filename) + return idl + + def getLocation(self, p, i): + return Location(self.lexer, p.lineno(i), p.lexpos(i)) + + +if __name__ == "__main__": + p = IDLParser() + for f in sys.argv[1:]: + print("Parsing %s" % f) + p.parse(open(f, encoding="utf-8").read(), filename=f) diff --git a/xpcom/io/Base64.cpp b/xpcom/io/Base64.cpp new file mode 100644 index 0000000000..4bd0394ecb --- /dev/null +++ b/xpcom/io/Base64.cpp @@ -0,0 +1,780 @@ +/* -*- 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 "Base64.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsIInputStream.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "plbase64.h" + +namespace { + +// BEGIN base64 encode code copied and modified from NSPR +const unsigned char* const base = + (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +// The Base64 encoder assumes all characters are less than 256; for 16-bit +// strings, that means assuming that all characters are within range, and +// masking off high bits if necessary. +template <typename T> +uint8_t CharTo8Bit(T aChar) { + return uint8_t(aChar); +} + +template <typename SrcT, typename DestT> +static void Encode3to4(const SrcT* aSrc, DestT* aDest) { + uint32_t b32 = (uint32_t)0; + int i, j = 18; + + for (i = 0; i < 3; ++i) { + b32 <<= 8; + b32 |= CharTo8Bit(aSrc[i]); + } + + for (i = 0; i < 4; ++i) { + aDest[i] = base[(uint32_t)((b32 >> j) & 0x3F)]; + j -= 6; + } +} + +template <typename SrcT, typename DestT> +static void Encode2to4(const SrcT* aSrc, DestT* aDest) { + uint8_t src0 = CharTo8Bit(aSrc[0]); + uint8_t src1 = CharTo8Bit(aSrc[1]); + aDest[0] = base[(uint32_t)((src0 >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)(((src0 & 0x03) << 4) | ((src1 >> 4) & 0x0F))]; + aDest[2] = base[(uint32_t)((src1 & 0x0F) << 2)]; + aDest[3] = DestT('='); +} + +template <typename SrcT, typename DestT> +static void Encode1to4(const SrcT* aSrc, DestT* aDest) { + uint8_t src0 = CharTo8Bit(aSrc[0]); + aDest[0] = base[(uint32_t)((src0 >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)((src0 & 0x03) << 4)]; + aDest[2] = DestT('='); + aDest[3] = DestT('='); +} + +template <typename SrcT, typename DestT> +static void Encode(const SrcT* aSrc, uint32_t aSrcLen, DestT* aDest) { + while (aSrcLen >= 3) { + Encode3to4(aSrc, aDest); + aSrc += 3; + aDest += 4; + aSrcLen -= 3; + } + + switch (aSrcLen) { + case 2: + Encode2to4(aSrc, aDest); + break; + case 1: + Encode1to4(aSrc, aDest); + break; + case 0: + break; + default: + MOZ_ASSERT_UNREACHABLE("coding error"); + } +} + +// END base64 encode code copied and modified from NSPR. + +template <typename T> +struct EncodeInputStream_State { + unsigned char c[3]; + uint8_t charsOnStack; + typename T::char_type* buffer; +}; + +template <typename T> +nsresult EncodeInputStream_Encoder(nsIInputStream* aStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount) { + MOZ_ASSERT(aCount > 0, "Er, what?"); + + EncodeInputStream_State<T>* state = + static_cast<EncodeInputStream_State<T>*>(aClosure); + + // We consume the whole data always. + *aWriteCount = aCount; + + // If we have any data left from last time, encode it now. + uint32_t countRemaining = aCount; + const unsigned char* src = (const unsigned char*)aFromSegment; + if (state->charsOnStack) { + MOZ_ASSERT(state->charsOnStack == 1 || state->charsOnStack == 2); + + // Not enough data to compose a triple. + if (state->charsOnStack == 1 && countRemaining == 1) { + state->charsOnStack = 2; + state->c[1] = src[0]; + return NS_OK; + } + + uint32_t consumed = 0; + unsigned char firstSet[4]; + if (state->charsOnStack == 1) { + firstSet[0] = state->c[0]; + firstSet[1] = src[0]; + firstSet[2] = src[1]; + firstSet[3] = '\0'; + consumed = 2; + } else /* state->charsOnStack == 2 */ { + firstSet[0] = state->c[0]; + firstSet[1] = state->c[1]; + firstSet[2] = src[0]; + firstSet[3] = '\0'; + consumed = 1; + } + + Encode(firstSet, 3, state->buffer); + state->buffer += 4; + countRemaining -= consumed; + src += consumed; + state->charsOnStack = 0; + + // Nothing is left. + if (!countRemaining) { + return NS_OK; + } + } + + // Encode as many full triplets as possible. + uint32_t encodeLength = countRemaining - countRemaining % 3; + MOZ_ASSERT(encodeLength % 3 == 0, "Should have an exact number of triplets!"); + Encode(src, encodeLength, state->buffer); + state->buffer += (encodeLength / 3) * 4; + src += encodeLength; + countRemaining -= encodeLength; + + if (countRemaining) { + // We should never have a full triplet left at this point. + MOZ_ASSERT(countRemaining < 3, "We should have encoded more!"); + state->c[0] = src[0]; + state->c[1] = (countRemaining == 2) ? src[1] : '\0'; + state->charsOnStack = countRemaining; + } + + return NS_OK; +} + +mozilla::Result<uint32_t, nsresult> CalculateBase64EncodedLength( + const size_t aBinaryLen, const uint32_t aPrefixLen = 0) { + mozilla::CheckedUint32 res = aBinaryLen; + // base 64 encoded length is 4/3rds the length of the input data, rounded up + res += 2; + res /= 3; + res *= 4; + res += aPrefixLen; + if (!res.isValid()) { + return mozilla::Err(NS_ERROR_FAILURE); + } + return res.value(); +} + +template <typename T> +nsresult EncodeInputStream(nsIInputStream* aInputStream, T& aDest, + uint32_t aCount, uint32_t aOffset) { + nsresult rv; + uint64_t count64 = aCount; + + if (!aCount) { + rv = aInputStream->Available(&count64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // if count64 is over 4GB, it will be failed at the below condition, + // then will return NS_ERROR_OUT_OF_MEMORY + aCount = (uint32_t)count64; + } + + const auto base64LenOrErr = CalculateBase64EncodedLength(count64, aOffset); + if (base64LenOrErr.isErr()) { + // XXX For some reason, it was NS_ERROR_OUT_OF_MEMORY here instead of + // NS_ERROR_FAILURE, so we keep that. + return NS_ERROR_OUT_OF_MEMORY; + } + + auto handleOrErr = aDest.BulkWrite(base64LenOrErr.inspect(), aOffset, false); + if (handleOrErr.isErr()) { + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + EncodeInputStream_State<T> state{ + .c = {'\0', '\0', '\0'}, + .charsOnStack = 0, + .buffer = handle.Elements() + aOffset, + }; + + while (aCount > 0) { + uint32_t read = 0; + + rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>, + (void*)&state, aCount, &read); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + MOZ_CRASH("Not implemented for async streams!"); + } + if (rv == NS_ERROR_NOT_IMPLEMENTED) { + MOZ_CRASH("Requires a stream that implements ReadSegments!"); + } + return rv; + } + + if (!read) { + break; + } + + aCount -= read; + } + + // Finish encoding if anything is left + if (state.charsOnStack) { + Encode(state.c, state.charsOnStack, state.buffer); + state.buffer += 4; + } + + // If we encountered EOF before reading aCount bytes, the resulting string + // could be shorter than predicted, so determine the length from the state. + size_t trueLength = state.buffer - handle.Elements(); + handle.Finish(trueLength, false); + + return NS_OK; +} + +// Maps an encoded character to a value in the Base64 alphabet, per +// RFC 4648, Table 1. Invalid input characters map to UINT8_MAX. + +static const uint8_t kBase64DecodeTable[] = { + // clang-format off + /* 0 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 8 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 16 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 24 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 32 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 40 */ 255, 255, 255, + 62 /* + */, + 255, 255, 255, + 63 /* / */, + + /* 48 */ /* 0 - 9 */ 52, 53, 54, 55, 56, 57, 58, 59, + /* 56 */ 60, 61, 255, 255, 255, 255, 255, 255, + + /* 64 */ 255, /* A - Z */ 0, 1, 2, 3, 4, 5, 6, + /* 72 */ 7, 8, 9, 10, 11, 12, 13, 14, + /* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, + /* 88 */ 23, 24, 25, 255, 255, 255, 255, 255, + /* 96 */ 255, /* a - z */ 26, 27, 28, 29, 30, 31, 32, + /* 104 */ 33, 34, 35, 36, 37, 38, 39, 40, + /* 112 */ 41, 42, 43, 44, 45, 46, 47, 48, + /* 120 */ 49, 50, 51, 255, 255, 255, 255, 255, +}; +static_assert(mozilla::ArrayLength(kBase64DecodeTable) == 0x80); +// clang-format on + +template <typename T> +[[nodiscard]] bool Base64CharToValue(T aChar, uint8_t* aValue) { + size_t index = static_cast<uint8_t>(aChar); + if (index >= mozilla::ArrayLength(kBase64DecodeTable)) { + *aValue = 255; + return false; + } + *aValue = kBase64DecodeTable[index]; + return *aValue != 255; +} + +static const char kBase64URLAlphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +static_assert(mozilla::ArrayLength(kBase64URLAlphabet) == 0x41); + +// Maps an encoded character to a value in the Base64 URL alphabet, per +// RFC 4648, Table 2. Invalid input characters map to UINT8_MAX. +static const uint8_t kBase64URLDecodeTable[] = { + // clang-format off + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, + 62 /* - */, + 255, 255, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */ + 255, 255, 255, 255, 255, 255, 255, + 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, /* A - Z */ + 255, 255, 255, 255, + 63 /* _ */, + 255, + 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, /* a - z */ + 255, 255, 255, 255, 255, +}; +static_assert(mozilla::ArrayLength(kBase64URLDecodeTable) == 0x80); +// clang-format on + +bool Base64URLCharToValue(char aChar, uint8_t* aValue) { + uint8_t index = static_cast<uint8_t>(aChar); + if (index >= mozilla::ArrayLength(kBase64URLDecodeTable)) { + *aValue = 255; + return false; + } + *aValue = kBase64URLDecodeTable[index]; + return *aValue != 255; +} + +} // namespace + +namespace mozilla { + +nsresult Base64EncodeInputStream(nsIInputStream* aInputStream, + nsACString& aDest, uint32_t aCount, + uint32_t aOffset) { + return EncodeInputStream<nsACString>(aInputStream, aDest, aCount, aOffset); +} + +nsresult Base64EncodeInputStream(nsIInputStream* aInputStream, nsAString& aDest, + uint32_t aCount, uint32_t aOffset) { + return EncodeInputStream<nsAString>(aInputStream, aDest, aCount, aOffset); +} + +nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + char** aBase64) { + if (aBinaryLen == 0) { + *aBase64 = (char*)moz_xmalloc(1); + (*aBase64)[0] = '\0'; + return NS_OK; + } + + const auto base64LenOrErr = CalculateBase64EncodedLength(aBinaryLen); + if (base64LenOrErr.isErr()) { + return base64LenOrErr.inspectErr(); + } + const uint32_t base64Len = base64LenOrErr.inspect(); + + *aBase64 = nullptr; + + // Add one byte for null termination. + UniqueFreePtr<char[]> base64((char*)malloc(base64Len + 1)); + if (!base64) { + return NS_ERROR_OUT_OF_MEMORY; + } + + Encode(aBinary, aBinaryLen, base64.get()); + base64[base64Len] = '\0'; + + *aBase64 = base64.release(); + return NS_OK; +} + +template <bool Append = false, typename T, typename U> +static nsresult Base64EncodeHelper(const T* const aBinary, + const size_t aBinaryLen, U& aBase64) { + if (aBinaryLen == 0) { + if (!Append) { + aBase64.Truncate(); + } + return NS_OK; + } + + const uint32_t prefixLen = Append ? aBase64.Length() : 0; + const auto base64LenOrErr = + CalculateBase64EncodedLength(aBinaryLen, prefixLen); + if (base64LenOrErr.isErr()) { + return base64LenOrErr.inspectErr(); + } + const uint32_t base64Len = base64LenOrErr.inspect(); + + auto handleOrErr = aBase64.BulkWrite(base64Len, prefixLen, false); + if (handleOrErr.isErr()) { + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + Encode(aBinary, aBinaryLen, handle.Elements() + prefixLen); + handle.Finish(base64Len, false); + return NS_OK; +} + +nsresult Base64EncodeAppend(const char* aBinary, uint32_t aBinaryLen, + nsAString& aBase64) { + return Base64EncodeHelper<true>(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64EncodeAppend(const char* aBinary, uint32_t aBinaryLen, + nsACString& aBase64) { + return Base64EncodeHelper<true>(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64EncodeAppend(const nsACString& aBinary, nsACString& aBase64) { + return Base64EncodeHelper<true>(aBinary.BeginReading(), aBinary.Length(), + aBase64); +} + +nsresult Base64EncodeAppend(const nsACString& aBinary, nsAString& aBase64) { + return Base64EncodeHelper<true>(aBinary.BeginReading(), aBinary.Length(), + aBase64); +} + +nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsACString& aBase64) { + return Base64EncodeHelper(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsAString& aBase64) { + return Base64EncodeHelper(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64Encode(const nsACString& aBinary, nsACString& aBase64) { + return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64); +} + +nsresult Base64Encode(const nsACString& aBinary, nsAString& aBase64) { + return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64); +} + +nsresult Base64Encode(const nsAString& aBinary, nsAString& aBase64) { + return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64); +} + +template <typename T, typename U, typename Decoder> +static bool Decode4to3(const T* aSrc, U* aDest, Decoder aToVal) { + uint8_t w, x, y, z; + if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x) || !aToVal(aSrc[2], &y) || + !aToVal(aSrc[3], &z)) { + return false; + } + aDest[0] = U(uint8_t(w << 2 | x >> 4)); + aDest[1] = U(uint8_t(x << 4 | y >> 2)); + aDest[2] = U(uint8_t(y << 6 | z)); + return true; +} + +template <typename T, typename U, typename Decoder> +static bool Decode3to2(const T* aSrc, U* aDest, Decoder aToVal) { + uint8_t w, x, y; + if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x) || !aToVal(aSrc[2], &y)) { + return false; + } + aDest[0] = U(uint8_t(w << 2 | x >> 4)); + aDest[1] = U(uint8_t(x << 4 | y >> 2)); + return true; +} + +template <typename T, typename U, typename Decoder> +static bool Decode2to1(const T* aSrc, U* aDest, Decoder aToVal) { + uint8_t w, x; + if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x)) { + return false; + } + aDest[0] = U(uint8_t(w << 2 | x >> 4)); + return true; +} + +template <typename SrcT, typename DestT> +static nsresult Base64DecodeHelper(const SrcT* aBase64, uint32_t aBase64Len, + DestT* aBinary, uint32_t* aBinaryLen) { + MOZ_ASSERT(aBinary); + + const SrcT* input = aBase64; + uint32_t inputLength = aBase64Len; + DestT* binary = aBinary; + uint32_t binaryLength = 0; + + // Handle trailing '=' characters. + if (inputLength && (inputLength % 4 == 0)) { + if (aBase64[inputLength - 1] == SrcT('=')) { + if (aBase64[inputLength - 2] == SrcT('=')) { + inputLength -= 2; + } else { + inputLength -= 1; + } + } + } + + while (inputLength >= 4) { + if (!Decode4to3(input, binary, Base64CharToValue<SrcT>)) { + return NS_ERROR_INVALID_ARG; + } + + input += 4; + inputLength -= 4; + binary += 3; + binaryLength += 3; + } + + switch (inputLength) { + case 3: + if (!Decode3to2(input, binary, Base64CharToValue<SrcT>)) { + return NS_ERROR_INVALID_ARG; + } + binaryLength += 2; + break; + case 2: + if (!Decode2to1(input, binary, Base64CharToValue<SrcT>)) { + return NS_ERROR_INVALID_ARG; + } + binaryLength += 1; + break; + case 1: + return NS_ERROR_INVALID_ARG; + case 0: + break; + default: + MOZ_CRASH("Too many characters leftover"); + } + + aBinary[binaryLength] = DestT('\0'); + *aBinaryLen = binaryLength; + + return NS_OK; +} + +nsresult Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary, + uint32_t* aBinaryLen) { + // Check for overflow. + if (aBase64Len > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Decode to decode the empty string. + if (aBase64Len == 0) { + *aBinary = (char*)moz_xmalloc(1); + (*aBinary)[0] = '\0'; + *aBinaryLen = 0; + return NS_OK; + } + + *aBinary = nullptr; + *aBinaryLen = (aBase64Len * 3) / 4; + + // Add one byte for null termination. + UniqueFreePtr<char[]> binary((char*)malloc(*aBinaryLen + 1)); + if (!binary) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = + Base64DecodeHelper(aBase64, aBase64Len, binary.get(), aBinaryLen); + if (NS_FAILED(rv)) { + return rv; + } + + *aBinary = binary.release(); + return NS_OK; +} + +template <typename T, typename U> +static nsresult Base64DecodeString(const T& aBase64, U& aBinary) { + aBinary.Truncate(); + + // Check for overflow. + if (aBase64.Length() > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't decode the empty string + if (aBase64.IsEmpty()) { + return NS_OK; + } + + uint32_t binaryLen = ((aBase64.Length() * 3) / 4); + + auto handleOrErr = aBinary.BulkWrite(binaryLen, 0, false); + if (handleOrErr.isErr()) { + // Must not touch the handle if failing here, but we + // already truncated the string at the top, so it's + // unchanged. + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + nsresult rv = Base64DecodeHelper(aBase64.BeginReading(), aBase64.Length(), + handle.Elements(), &binaryLen); + if (NS_FAILED(rv)) { + // Retruncate to match old semantics of this method. + handle.Finish(0, true); + return rv; + } + + handle.Finish(binaryLen, true); + return NS_OK; +} + +nsresult Base64Decode(const nsACString& aBase64, nsACString& aBinary) { + return Base64DecodeString(aBase64, aBinary); +} + +nsresult Base64Decode(const nsAString& aBase64, nsAString& aBinary) { + return Base64DecodeString(aBase64, aBinary); +} + +nsresult Base64Decode(const nsAString& aBase64, nsACString& aBinary) { + return Base64DecodeString(aBase64, aBinary); +} + +nsresult Base64URLDecode(const nsACString& aBase64, + Base64URLDecodePaddingPolicy aPaddingPolicy, + FallibleTArray<uint8_t>& aBinary) { + // Don't decode empty strings. + if (aBase64.IsEmpty()) { + aBinary.Clear(); + return NS_OK; + } + + // Check for overflow. + uint32_t base64Len = aBase64.Length(); + if (base64Len > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + const char* base64 = aBase64.BeginReading(); + + // The decoded length may be 1-2 bytes over, depending on the final quantum. + uint32_t binaryLen = (base64Len * 3) / 4; + + // Determine whether to check for and ignore trailing padding. + bool maybePadded = false; + switch (aPaddingPolicy) { + case Base64URLDecodePaddingPolicy::Require: + if (base64Len % 4) { + // Padded input length must be a multiple of 4. + return NS_ERROR_INVALID_ARG; + } + maybePadded = true; + break; + + case Base64URLDecodePaddingPolicy::Ignore: + // Check for padding only if the length is a multiple of 4. + maybePadded = !(base64Len % 4); + break; + + // If we're expecting unpadded input, no need for additional checks. + // `=` isn't in the decode table, so padded strings will fail to decode. + default: + MOZ_FALLTHROUGH_ASSERT("Invalid decode padding policy"); + case Base64URLDecodePaddingPolicy::Reject: + break; + } + if (maybePadded && base64[base64Len - 1] == '=') { + if (base64[base64Len - 2] == '=') { + base64Len -= 2; + } else { + base64Len -= 1; + } + } + + if (NS_WARN_IF(!aBinary.SetCapacity(binaryLen, mozilla::fallible))) { + return NS_ERROR_OUT_OF_MEMORY; + } + aBinary.SetLengthAndRetainStorage(binaryLen); + uint8_t* binary = aBinary.Elements(); + + for (; base64Len >= 4; base64Len -= 4) { + if (!Decode4to3(base64, binary, Base64URLCharToValue)) { + return NS_ERROR_INVALID_ARG; + } + base64 += 4; + binary += 3; + } + + if (base64Len == 3) { + if (!Decode3to2(base64, binary, Base64URLCharToValue)) { + return NS_ERROR_INVALID_ARG; + } + binary += 2; + } else if (base64Len == 2) { + if (!Decode2to1(base64, binary, Base64URLCharToValue)) { + return NS_ERROR_INVALID_ARG; + } + binary += 1; + } else if (base64Len) { + return NS_ERROR_INVALID_ARG; + } + + // Set the length to the actual number of decoded bytes. + aBinary.TruncateLength(binary - aBinary.Elements()); + return NS_OK; +} + +nsresult Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary, + Base64URLEncodePaddingPolicy aPaddingPolicy, + nsACString& aBase64) { + aBase64.Truncate(); + // Don't encode empty strings. + if (aBinaryLen == 0) { + return NS_OK; + } + + // Allocate a buffer large enough to hold the encoded string with padding. + const auto base64LenOrErr = CalculateBase64EncodedLength(aBinaryLen); + if (base64LenOrErr.isErr()) { + return base64LenOrErr.inspectErr(); + } + const uint32_t base64Len = base64LenOrErr.inspect(); + + auto handleOrErr = aBase64.BulkWrite(base64Len, 0, false); + if (handleOrErr.isErr()) { + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + char* base64 = handle.Elements(); + + uint32_t index = 0; + for (; index + 3 <= aBinaryLen; index += 3) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) | + (aBinary[index + 1] >> 4)]; + *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2) | + (aBinary[index + 2] >> 6)]; + *base64++ = kBase64URLAlphabet[aBinary[index + 2] & 0x3f]; + } + + uint32_t remaining = aBinaryLen - index; + if (remaining == 1) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4)]; + } else if (remaining == 2) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) | + (aBinary[index + 1] >> 4)]; + *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2)]; + } + + uint32_t length = base64 - handle.Elements(); + if (aPaddingPolicy == Base64URLEncodePaddingPolicy::Include) { + if (length % 4 == 2) { + *base64++ = '='; + *base64++ = '='; + length += 2; + } else if (length % 4 == 3) { + *base64++ = '='; + length += 1; + } + } else { + MOZ_ASSERT(aPaddingPolicy == Base64URLEncodePaddingPolicy::Omit, + "Invalid encode padding policy"); + } + + handle.Finish(length, false); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/Base64.h b/xpcom/io/Base64.h new file mode 100644 index 0000000000..0a9b3a0305 --- /dev/null +++ b/xpcom/io/Base64.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 mozilla_Base64_h__ +#define mozilla_Base64_h__ + +#include "nsString.h" + +class nsIInputStream; + +namespace mozilla { + +[[nodiscard]] nsresult Base64EncodeInputStream(nsIInputStream* aInputStream, + nsACString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); +[[nodiscard]] nsresult Base64EncodeInputStream(nsIInputStream* aInputStream, + nsAString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); + +// Encode 8-bit data of a given length and append the Base64 encoded data to +// aBase64. +[[nodiscard]] nsresult Base64EncodeAppend(const char* aBinary, + uint32_t aBinaryLen, + nsAString& aBase64); +[[nodiscard]] nsresult Base64EncodeAppend(const char* aBinary, + uint32_t aBinaryLen, + nsACString& aBase64); +[[nodiscard]] nsresult Base64EncodeAppend(const nsACString& aBinary, + nsACString& aBase64); +[[nodiscard]] nsresult Base64EncodeAppend(const nsACString& aBinary, + nsAString& aBase64); + +[[nodiscard]] nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + char** aBase64); +[[nodiscard]] nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsACString& aBase64); +[[nodiscard]] nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsAString& aBase64); +[[nodiscard]] nsresult Base64Encode(const nsACString& aBinary, + nsACString& aBase64); +[[nodiscard]] nsresult Base64Encode(const nsACString& aBinary, + nsAString& aBase64); + +// The high bits of any characters in aBinary are dropped. +[[nodiscard]] nsresult Base64Encode(const nsAString& aBinary, + nsAString& aBase64); + +[[nodiscard]] nsresult Base64Decode(const char* aBase64, uint32_t aBase64Len, + char** aBinary, uint32_t* aBinaryLen); +[[nodiscard]] nsresult Base64Decode(const nsACString& aBase64, + nsACString& aBinary); + +// The high bits of any characters in aBase64 are dropped. +[[nodiscard]] nsresult Base64Decode(const nsAString& aBase64, + nsAString& aBinary); +[[nodiscard]] nsresult Base64Decode(const nsAString& aBase64, + nsACString& aBinary); + +enum class Base64URLEncodePaddingPolicy { + Include, + Omit, +}; + +/** + * Converts |aBinary| to an unpadded, Base64 URL-encoded string per RFC 4648. + * Aims to encode the data in constant time. The caller retains ownership + * of |aBinary|. + */ +[[nodiscard]] nsresult Base64URLEncode( + uint32_t aBinaryLen, const uint8_t* aBinary, + Base64URLEncodePaddingPolicy aPaddingPolicy, nsACString& aBase64); + +enum class Base64URLDecodePaddingPolicy { + Require, + Ignore, + Reject, +}; + +/** + * Decodes a Base64 URL-encoded |aBase64| into |aBinary|. + */ +[[nodiscard]] nsresult Base64URLDecode( + const nsACString& aBase64, Base64URLDecodePaddingPolicy aPaddingPolicy, + FallibleTArray<uint8_t>& aBinary); + +} // namespace mozilla + +#endif diff --git a/xpcom/io/CocoaFileUtils.h b/xpcom/io/CocoaFileUtils.h new file mode 100644 index 0000000000..7438acd392 --- /dev/null +++ b/xpcom/io/CocoaFileUtils.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This namespace contains methods with Obj-C/Cocoa implementations. The header +// is C/C++ for inclusion in C/C++-only files. + +#ifndef CocoaFileUtils_h_ +#define CocoaFileUtils_h_ + +#include "CFTypeRefPtr.h" +#include "nscore.h" +#include "nsString.h" +#include <CoreFoundation/CoreFoundation.h> + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef aUrl); +nsresult OpenURL(CFURLRef aUrl); +nsresult GetFileCreatorCode(CFURLRef aUrl, OSType* aCreatorCode); +nsresult SetFileCreatorCode(CFURLRef aUrl, OSType aCreatorCode); +nsresult GetFileTypeCode(CFURLRef aUrl, OSType* aTypeCode); +nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode); + +// Can be called off of the main thread. +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL); +// Can be called off of the main thread. +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb, + const bool createProps = false); +// Can be called off of the main thread. +void CopyQuarantineReferrerUrl(const CFStringRef aFilePath, + nsAString& aReferrer); + +CFTypeRefPtr<CFURLRef> GetTemporaryFolder(); + +CFTypeRefPtr<CFURLRef> GetProductDirectory(bool aLocal); + +} // namespace CocoaFileUtils + +#endif diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm new file mode 100644 index 0000000000..3710be864c --- /dev/null +++ b/xpcom/io/CocoaFileUtils.mm @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "CocoaFileUtils.h" +#include "nsCocoaUtils.h" +#include <Cocoa/Cocoa.h> +#include "nsObjCExceptions.h" +#include "nsDebug.h" +#include "nsString.h" +#include "mozilla/MacStringHelpers.h" + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef url) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (NS_WARN_IF(!url)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoreleasePool localPool; + + BOOL success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)url path] + inFileViewerRootedAtPath:@""]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult OpenURL(CFURLRef url) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (NS_WARN_IF(!url)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoreleasePool localPool; + + [[NSWorkspace sharedWorkspace] openURL:(NSURL*)url]; + + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult GetFileCreatorCode(CFURLRef url, OSType* creatorCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!creatorCode)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoreleasePool localPool; + + NSString* resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = + [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath + error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode]; + if (!creatorNum) { + return NS_ERROR_FAILURE; + } + + *creatorCode = [creatorNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (NS_WARN_IF(!url)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoreleasePool localPool; + + NSDictionary* dict = [NSDictionary + dictionaryWithObject:[NSNumber numberWithUnsignedLong:creatorCode] + forKey:NSFileHFSCreatorCode]; + BOOL success = + [[NSFileManager defaultManager] setAttributes:dict + ofItemAtPath:[(NSURL*)url path] + error:nil]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult GetFileTypeCode(CFURLRef url, OSType* typeCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!typeCode)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoreleasePool localPool; + + NSString* resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = + [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath + error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode]; + if (!typeNum) { + return NS_ERROR_FAILURE; + } + + *typeCode = [typeNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult SetFileTypeCode(CFURLRef url, OSType typeCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (NS_WARN_IF(!url)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoreleasePool localPool; + + NSDictionary* dict = [NSDictionary + dictionaryWithObject:[NSNumber numberWithUnsignedLong:typeCode] + forKey:NSFileHFSTypeCode]; + BOOL success = + [[NSFileManager defaultManager] setAttributes:dict + ofItemAtPath:[(NSURL*)url path] + error:nil]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +// Can be called off of the main thread. +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL) { + nsAutoreleasePool localPool; + + typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, + CFTypeRef); + static MDItemSetAttribute_type mdItemSetAttributeFunc = NULL; + + static bool did_symbol_lookup = false; + if (!did_symbol_lookup) { + did_symbol_lookup = true; + + CFBundleRef metadata_bundle = + ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata")); + if (!metadata_bundle) { + return; + } + + mdItemSetAttributeFunc = + (MDItemSetAttribute_type)::CFBundleGetFunctionPointerForName( + metadata_bundle, CFSTR("MDItemSetAttribute")); + } + if (!mdItemSetAttributeFunc) { + return; + } + + MDItemRef mdItem = ::MDItemCreate(NULL, filePath); + if (!mdItem) { + return; + } + + CFMutableArrayRef list = ::CFArrayCreateMutable(kCFAllocatorDefault, 2, NULL); + if (!list) { + ::CFRelease(mdItem); + return; + } + + // The first item in the list is the source URL of the downloaded file. + if (sourceURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(sourceURL)); + } + + // If the referrer is known, store that in the second position. + if (referrerURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(referrerURL)); + } + + mdItemSetAttributeFunc(mdItem, kMDItemWhereFroms, list); + + ::CFRelease(list); + ::CFRelease(mdItem); +} + +// Can be called off of the main thread. +static CFMutableDictionaryRef CreateQuarantineDictionary( + const CFURLRef aFileURL, const bool aCreateProps) { + nsAutoreleasePool localPool; + + CFDictionaryRef quarantineProps = NULL; + if (aCreateProps) { + quarantineProps = ::CFDictionaryCreate(NULL, NULL, NULL, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + } else { + Boolean success = ::CFURLCopyResourcePropertyForKey( + aFileURL, kCFURLQuarantinePropertiesKey, &quarantineProps, NULL); + // If there aren't any quarantine properties then the user probably + // set up an exclusion and we don't need to add metadata. + if (!success || !quarantineProps) { + return NULL; + } + } + + // We don't know what to do if the props aren't a dictionary. + if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) { + ::CFRelease(quarantineProps); + return NULL; + } + + // Make a mutable copy of the properties. + CFMutableDictionaryRef mutQuarantineProps = ::CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps); + ::CFRelease(quarantineProps); + + return mutQuarantineProps; +} + +// Can be called off of the main thread. +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb, + const bool createProps /* = false */) { + nsAutoreleasePool localPool; + + CFURLRef fileURL = ::CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, filePath, kCFURLPOSIXPathStyle, false); + + CFMutableDictionaryRef mutQuarantineProps = + CreateQuarantineDictionary(fileURL, createProps); + if (!mutQuarantineProps) { + ::CFRelease(fileURL); + return; + } + + // Add metadata that the OS couldn't infer. + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineTypeKey)) { + CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload + : kLSQuarantineTypeOtherDownload; + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineTypeKey, type); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) && + referrerURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineOriginURLKey, + referrerURL); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) && + sourceURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineDataURLKey, + sourceURL); + } + + // Set quarantine properties on file. + ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey, + mutQuarantineProps, NULL); + + ::CFRelease(fileURL); + ::CFRelease(mutQuarantineProps); +} + +// Can be called off of the main thread. +void CopyQuarantineReferrerUrl(const CFStringRef aFilePath, + nsAString& aReferrer) { + nsAutoreleasePool localPool; + + CFURLRef fileURL = ::CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, aFilePath, kCFURLPOSIXPathStyle, false); + + CFMutableDictionaryRef mutQuarantineProps = + CreateQuarantineDictionary(fileURL, false); + ::CFRelease(fileURL); + if (!mutQuarantineProps) { + return; + } + + CFTypeRef referrerRef = + ::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey); + if (referrerRef && ::CFGetTypeID(referrerRef) == ::CFURLGetTypeID()) { + // URL string must be copied prior to releasing the dictionary. + mozilla::CopyCocoaStringToXPCOMString( + (NSString*)::CFURLGetString(static_cast<CFURLRef>(referrerRef)), + aReferrer); + } + + ::CFRelease(mutQuarantineProps); +} + +CFTypeRefPtr<CFURLRef> GetTemporaryFolder() { + nsAutoreleasePool localPool; + + NSString* tempDir = ::NSTemporaryDirectory(); + return tempDir == nil ? NULL + : CFTypeRefPtr<CFURLRef>::WrapUnderGetRule( + (__bridge CFURLRef)[NSURL fileURLWithPath:tempDir + isDirectory:YES]); +} + +CFTypeRefPtr<CFURLRef> GetProductDirectory(bool aLocal) { + nsAutoreleasePool localPool; + + NSSearchPathDirectory folderType = + aLocal ? NSCachesDirectory : NSLibraryDirectory; + NSFileManager* manager = [NSFileManager defaultManager]; + return CFTypeRefPtr<CFURLRef>::WrapUnderGetRule((__bridge CFURLRef)[[manager + URLsForDirectory:folderType + inDomains:NSUserDomainMask] firstObject]); +} + +} // namespace CocoaFileUtils diff --git a/xpcom/io/FileDescriptorFile.cpp b/xpcom/io/FileDescriptorFile.cpp new file mode 100644 index 0000000000..6398a89760 --- /dev/null +++ b/xpcom/io/FileDescriptorFile.cpp @@ -0,0 +1,442 @@ +/* -*- 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 "FileDescriptorFile.h" + +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/NeckoChild.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "private/pprio.h" +#include "SerializedLoadContext.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(FileDescriptorFile, nsIFile) + +LazyLogModule gFDFileLog("FDFile"); +#undef DBG +#define DBG(...) MOZ_LOG(gFDFileLog, LogLevel::Debug, (__VA_ARGS__)) + +FileDescriptorFile::FileDescriptorFile(const FileDescriptor& aFD, + nsIFile* aFile) { + MOZ_ASSERT(aFD.IsValid()); + auto platformHandle = aFD.ClonePlatformHandle(); + mFD = FileDescriptor(platformHandle.get()); + mFile = aFile; +} + +FileDescriptorFile::FileDescriptorFile(const FileDescriptorFile& aOther) { + auto platformHandle = aOther.mFD.ClonePlatformHandle(); + mFD = FileDescriptor(platformHandle.get()); + aOther.mFile->Clone(getter_AddRefs(mFile)); +} + +//----------------------------------------------------------------------------- +// FileDescriptorFile::nsIFile functions that we override logic for +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FileDescriptorFile::Clone(nsIFile** aFileOut) { + RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(*this); + fdFile.forget(aFileOut); + return NS_OK; +} + +NS_IMETHODIMP +FileDescriptorFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aRetval) { + // Remove optional OS_READAHEAD flag so we test against PR_RDONLY + aFlags &= ~nsIFile::OS_READAHEAD; + + // Remove optional/deprecated DELETE_ON_CLOSE flag + aFlags &= ~nsIFile::DELETE_ON_CLOSE; + + // All other flags require write access to the file and + // this implementation only provides read access. + if (aFlags != PR_RDONLY) { + DBG("OpenNSPRFileDesc flags error (%" PRIu32 ")\n", aFlags); + return NS_ERROR_NOT_AVAILABLE; + } + + if (!mFD.IsValid()) { + DBG("OpenNSPRFileDesc error: no file descriptor\n"); + return NS_ERROR_NOT_AVAILABLE; + } + + auto platformHandle = mFD.ClonePlatformHandle(); + *aRetval = PR_ImportFile(PROsfd(platformHandle.release())); + + if (!*aRetval) { + DBG("OpenNSPRFileDesc Clone failure\n"); + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FileDescriptorFile::nsIFile functions that we delegate to underlying nsIFile +//----------------------------------------------------------------------------- + +nsresult FileDescriptorFile::GetLeafName(nsAString& aLeafName) { + return mFile->GetLeafName(aLeafName); +} + +NS_IMETHODIMP +FileDescriptorFile::GetNativeLeafName(nsACString& aLeafName) { + return mFile->GetNativeLeafName(aLeafName); +} + +NS_IMETHODIMP FileDescriptorFile::GetDisplayName(nsAString& aLeafName) { + return mFile->GetDisplayName(aLeafName); +} + +nsresult FileDescriptorFile::GetTarget(nsAString& aRetVal) { + return mFile->GetTarget(aRetVal); +} + +NS_IMETHODIMP +FileDescriptorFile::GetNativeTarget(nsACString& aRetVal) { + return mFile->GetNativeTarget(aRetVal); +} + +nsresult FileDescriptorFile::GetPath(nsAString& aRetVal) { + return mFile->GetPath(aRetVal); +} + +PathString FileDescriptorFile::NativePath() { return mFile->NativePath(); } + +NS_IMETHODIMP +FileDescriptorFile::Equals(nsIFile* aOther, bool* aRetVal) { + return mFile->Equals(aOther, aRetVal); +} + +NS_IMETHODIMP +FileDescriptorFile::Contains(nsIFile* aOther, bool* aRetVal) { + return mFile->Contains(aOther, aRetVal); +} + +NS_IMETHODIMP +FileDescriptorFile::GetParent(nsIFile** aParent) { + return mFile->GetParent(aParent); +} + +NS_IMETHODIMP +FileDescriptorFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) { + return mFile->GetPersistentDescriptor(aPersistentDescriptor); +} + +//----------------------------------------------------------------------------- +// FileDescriptorFile::nsIFile functions that are not currently supported +//----------------------------------------------------------------------------- + +nsresult FileDescriptorFile::Append(const nsAString& aNode) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::AppendNative(const nsACString& aFragment) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Normalize() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::Create(uint32_t aType, uint32_t aPermissions, + bool aSkipAncestors) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::SetLeafName(const nsAString& aLeafName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetNativeLeafName(const nsACString& aLeafName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::InitWithPath(const nsAString& aPath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::InitWithNativePath(const nsACString& aPath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::InitWithFile(nsIFile* aFile) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::AppendRelativePath(const nsAString& aNode) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::AppendRelativeNativePath(const nsACString& aFragment) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetPersistentDescriptor( + const nsACString& aPersistentDescriptor) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetRelativeDescriptor(nsIFile* aFromFile, + nsACString& aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetRelativeDescriptor(nsIFile* aFromFile, + const nsACString& aRelativeDesc) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetRelativePath(nsIFile* aFromFile, nsACString& aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetRelativePath(nsIFile* aFromFile, + const nsACString& aRelativePath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::CopyTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::CopyToNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::CopyToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::MoveTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::MoveToNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::MoveToFollowingLinks(nsIFile* aNewParent, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::MoveToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::RenameTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::RenameToNative(nsIFile* aNewParentDir, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Remove(bool aRecursive, uint32_t* aRemoveCount) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetPermissions(uint32_t* aPermissions) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetPermissions(uint32_t aPermissions) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetPermissionsOfLink(uint32_t aPermissions) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastAccessedTime(PRTime* aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastAccessedTime(PRTime aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastModifiedTime(PRTime* aLastModTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastModifiedTime(PRTime aLastModTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetCreationTime(PRTime* aCreationTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetCreationTimeOfLink(PRTime* aCreationTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetFileSize(int64_t* aFileSize) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetFileSize(int64_t aFileSize) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetFileSizeOfLink(int64_t* aFileSize) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Exists(bool* aRetVal) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::IsWritable(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsReadable(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsExecutable(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsHidden(bool* aRetVal) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::IsDirectory(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsFile(bool* aRetVal) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::IsSymlink(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsSpecial(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::CreateUnique(uint32_t aType, uint32_t aAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::OpenANSIFileDesc(const char* aMode, FILE** aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Load(PRLibrary** aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetDiskCapacity(int64_t* aDiskCapacity) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Reveal() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::Launch() { return NS_ERROR_NOT_IMPLEMENTED; } + +} // namespace net +} // namespace mozilla diff --git a/xpcom/io/FileDescriptorFile.h b/xpcom/io/FileDescriptorFile.h new file mode 100644 index 0000000000..1d0fbad2d8 --- /dev/null +++ b/xpcom/io/FileDescriptorFile.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 _FileDescriptorFile_h +#define _FileDescriptorFile_h + +#include "mozilla/Attributes.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "nsIFile.h" +#include "private/pprio.h" + +namespace mozilla { +namespace net { + +/** + * A limited implementation of nsIFile that wraps a FileDescriptor object + * allowing the file to be read from. Added to allow a child process to use + * an nsIFile object for a file it does not have access to on the filesystem + * but has been provided a FileDescriptor for from the parent. Many nsIFile + * methods are not implemented and this is not intended to be a general + * purpose file implementation. + */ +class FileDescriptorFile final : public nsIFile { + typedef mozilla::ipc::FileDescriptor FileDescriptor; + + public: + FileDescriptorFile(const FileDescriptor& aFD, nsIFile* aFile); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFILE + + private: + ~FileDescriptorFile() = default; + + FileDescriptorFile(const FileDescriptorFile& other); + + // regular nsIFile object, that we forward most calls to. + nsCOMPtr<nsIFile> mFile; + FileDescriptor mFD; +}; + +} // namespace net +} // namespace mozilla + +#endif // _FileDescriptorFile_h diff --git a/xpcom/io/FilePreferences.cpp b/xpcom/io/FilePreferences.cpp new file mode 100644 index 0000000000..1d96c72810 --- /dev/null +++ b/xpcom/io/FilePreferences.cpp @@ -0,0 +1,373 @@ +/* -*- 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 "FilePreferences.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Tokenizer.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsString.h" + +namespace mozilla { +namespace FilePreferences { + +static StaticMutex sMutex; + +static bool sBlockUNCPaths = false; +typedef nsTArray<nsString> WinPaths; + +static WinPaths& PathAllowlist() MOZ_REQUIRES(sMutex) { + sMutex.AssertCurrentThreadOwns(); + + static WinPaths sPaths MOZ_GUARDED_BY(sMutex); + return sPaths; +} + +#ifdef XP_WIN +const auto kDevicePathSpecifier = u"\\\\?\\"_ns; + +typedef char16_t char_path_t; +#else +typedef char char_path_t; +#endif + +// Initially false to make concurrent consumers acquire the lock and sync. +// The plain bool is synchronized with sMutex, the atomic one is for a quick +// check w/o the need to acquire the lock on the hot path. +static bool sForbiddenPathsEmpty = false; +static Atomic<bool, Relaxed> sForbiddenPathsEmptyQuickCheck{false}; + +typedef nsTArray<nsTString<char_path_t>> Paths; +static StaticAutoPtr<Paths> sForbiddenPaths; + +static Paths& ForbiddenPaths() { + sMutex.AssertCurrentThreadOwns(); + if (!sForbiddenPaths) { + sForbiddenPaths = new nsTArray<nsTString<char_path_t>>(); + ClearOnShutdown(&sForbiddenPaths); + } + return *sForbiddenPaths; +} + +static void AllowUNCDirectory(char const* directory) { + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(directory, getter_AddRefs(file)); + if (!file) { + return; + } + + nsString path; + if (NS_FAILED(file->GetTarget(path))) { + return; + } + + // The allowlist makes sense only for UNC paths, because this code is used + // to block only UNC paths, hence, no need to add non-UNC directories here + // as those would never pass the check. + if (!StringBeginsWith(path, u"\\\\"_ns)) { + return; + } + + StaticMutexAutoLock lock(sMutex); + + if (!PathAllowlist().Contains(path)) { + PathAllowlist().AppendElement(path); + } +} + +void InitPrefs() { + sBlockUNCPaths = + Preferences::GetBool("network.file.disable_unc_paths", false); + + nsTAutoString<char_path_t> forbidden; +#ifdef XP_WIN + Preferences::GetString("network.file.path_blacklist", forbidden); +#else + Preferences::GetCString("network.file.path_blacklist", forbidden); +#endif + + StaticMutexAutoLock lock(sMutex); + + if (forbidden.IsEmpty()) { + sForbiddenPathsEmptyQuickCheck = (sForbiddenPathsEmpty = true); + return; + } + + ForbiddenPaths().Clear(); + TTokenizer<char_path_t> p(forbidden); + while (!p.CheckEOF()) { + nsTString<char_path_t> path; + Unused << p.ReadUntil(TTokenizer<char_path_t>::Token::Char(','), path); + path.Trim(" "); + if (!path.IsEmpty()) { + ForbiddenPaths().AppendElement(path); + } + Unused << p.CheckChar(','); + } + + sForbiddenPathsEmptyQuickCheck = + (sForbiddenPathsEmpty = ForbiddenPaths().Length() == 0); +} + +void InitDirectoriesAllowlist() { + // NS_GRE_DIR is the installation path where the binary resides. + AllowUNCDirectory(NS_GRE_DIR); + // NS_APP_USER_PROFILE_50_DIR and NS_APP_USER_PROFILE_LOCAL_50_DIR are the two + // parts of the profile we store permanent and local-specific data. + AllowUNCDirectory(NS_APP_USER_PROFILE_50_DIR); + AllowUNCDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR); +} + +namespace { // anon + +template <typename TChar> +class TNormalizer : public TTokenizer<TChar> { + typedef TTokenizer<TChar> base; + + public: + typedef typename base::Token Token; + + TNormalizer(const nsTSubstring<TChar>& aFilePath, const Token& aSeparator) + : TTokenizer<TChar>(aFilePath), mSeparator(aSeparator) {} + + bool Get(nsTSubstring<TChar>& aNormalizedFilePath) { + aNormalizedFilePath.Truncate(); + + // Windows UNC paths begin with double separator (\\) + // Linux paths begin with just one separator (/) + // If we want to use the normalizer for regular windows paths this code + // will need to be updated. +#ifdef XP_WIN + if (base::Check(mSeparator)) { + aNormalizedFilePath.Append(mSeparator.AsChar()); + } +#endif + + if (base::Check(mSeparator)) { + aNormalizedFilePath.Append(mSeparator.AsChar()); + } + + while (base::HasInput()) { + if (!ConsumeName()) { + return false; + } + } + + for (auto const& name : mStack) { + aNormalizedFilePath.Append(name); + } + + return true; + } + + private: + bool ConsumeName() { + if (base::CheckEOF()) { + return true; + } + + if (CheckCurrentDir()) { + return true; + } + + if (CheckParentDir()) { + if (!mStack.Length()) { + // This means there are more \.. than valid names + return false; + } + + mStack.RemoveLastElement(); + return true; + } + + nsTDependentSubstring<TChar> name; + if (base::ReadUntil(mSeparator, name, base::INCLUDE_LAST) && + name.Length() == 1) { + // this means an empty name (a lone slash), which is illegal + return false; + } + mStack.AppendElement(name); + + return true; + } + + bool CheckParentDir() { + typename nsTString<TChar>::const_char_iterator cursor = base::mCursor; + if (base::CheckChar('.') && base::CheckChar('.') && CheckSeparator()) { + return true; + } + + base::mCursor = cursor; + return false; + } + + bool CheckCurrentDir() { + typename nsTString<TChar>::const_char_iterator cursor = base::mCursor; + if (base::CheckChar('.') && CheckSeparator()) { + return true; + } + + base::mCursor = cursor; + return false; + } + + bool CheckSeparator() { return base::Check(mSeparator) || base::CheckEOF(); } + + Token const mSeparator; + nsTArray<nsTDependentSubstring<TChar>> mStack; +}; + +#ifdef XP_WIN +bool IsDOSDevicePathWithDrive(const nsAString& aFilePath) { + if (!StringBeginsWith(aFilePath, kDevicePathSpecifier)) { + return false; + } + + const auto pathNoPrefix = + nsDependentSubstring(aFilePath, kDevicePathSpecifier.Length()); + + // After the device path specifier, the rest of file path can be: + // - starts with the volume or drive. e.g. \\?\C:\... + // - UNCs. e.g. \\?\UNC\Server\Share\Test\Foo.txt + // - device UNCs. e.g. \\?\server1\e:\utilities\\filecomparer\... + // The first case should not be blocked by IsBlockedUNCPath. + if (!StartsWithDiskDesignatorAndBackslash(pathNoPrefix)) { + return false; + } + + return true; +} +#endif + +} // namespace + +bool IsBlockedUNCPath(const nsAString& aFilePath) { + typedef TNormalizer<char16_t> Normalizer; + if (!sBlockUNCPaths) { + return false; + } + + if (!StringBeginsWith(aFilePath, u"\\\\"_ns)) { + return false; + } + +#ifdef XP_WIN + // ToDo: We don't need to check this once we can check if there is a valid + // server or host name that is prefaced by "\\". + // https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats + if (IsDOSDevicePathWithDrive(aFilePath)) { + return false; + } +#endif + + nsAutoString normalized; + if (!Normalizer(aFilePath, Normalizer::Token::Char('\\')).Get(normalized)) { + // Broken paths are considered invalid and thus inaccessible + return true; + } + + StaticMutexAutoLock lock(sMutex); + + for (const auto& allowedPrefix : PathAllowlist()) { + if (StringBeginsWith(normalized, allowedPrefix)) { + if (normalized.Length() == allowedPrefix.Length()) { + return false; + } + if (normalized[allowedPrefix.Length()] == L'\\') { + return false; + } + + // When we are here, the path has a form "\\path\prefixevil" + // while we have an allowed prefix of "\\path\prefix". + // Note that we don't want to add a slash to the end of a prefix + // so that opening the directory (no slash at the end) still works. + break; + } + } + + return true; +} + +#ifdef XP_WIN +const char kPathSeparator = '\\'; +#else +const char kPathSeparator = '/'; +#endif + +bool IsAllowedPath(const nsTSubstring<char_path_t>& aFilePath) { + typedef TNormalizer<char_path_t> Normalizer; + + // An atomic quick check out of the lock, because this is mostly `true`. + if (sForbiddenPathsEmptyQuickCheck) { + return true; + } + + StaticMutexAutoLock lock(sMutex); + + if (sForbiddenPathsEmpty) { + return true; + } + + // If sForbidden has been cleared at shutdown, we must avoid calling + // ForbiddenPaths() again, as that will recreate the array and we will leak. + if (!sForbiddenPaths) { + return true; + } + + nsTAutoString<char_path_t> normalized; + if (!Normalizer(aFilePath, Normalizer::Token::Char(kPathSeparator)) + .Get(normalized)) { + // Broken paths are considered invalid and thus inaccessible + return false; + } + + for (const auto& prefix : ForbiddenPaths()) { + if (StringBeginsWith(normalized, prefix)) { + if (normalized.Length() > prefix.Length() && + normalized[prefix.Length()] != kPathSeparator) { + continue; + } + return false; + } + } + + return true; +} + +#ifdef XP_WIN +bool StartsWithDiskDesignatorAndBackslash(const nsAString& aAbsolutePath) { + // aAbsolutePath can only be (in regular expression): + // UNC path: ^\\\\.* + // A single backslash: ^\\.* + // A disk designator with a backslash: ^[A-Za-z]:\\.* + return aAbsolutePath.Length() >= 3 && IsAsciiAlpha(aAbsolutePath.CharAt(0)) && + aAbsolutePath.CharAt(1) == L':' && + aAbsolutePath.CharAt(2) == kPathSeparator; +} +#endif + +void testing::SetBlockUNCPaths(bool aBlock) { sBlockUNCPaths = aBlock; } + +void testing::AddDirectoryToAllowlist(nsAString const& aPath) { + StaticMutexAutoLock lock(sMutex); + PathAllowlist().AppendElement(aPath); +} + +bool testing::NormalizePath(nsAString const& aPath, nsAString& aNormalized) { + typedef TNormalizer<char16_t> Normalizer; + Normalizer normalizer(aPath, Normalizer::Token::Char('\\')); + return normalizer.Get(aNormalized); +} + +} // namespace FilePreferences +} // namespace mozilla diff --git a/xpcom/io/FilePreferences.h b/xpcom/io/FilePreferences.h new file mode 100644 index 0000000000..ee80429047 --- /dev/null +++ b/xpcom/io/FilePreferences.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/. */ + +#include "nsAString.h" + +namespace mozilla { +namespace FilePreferences { + +void InitPrefs(); +void InitDirectoriesAllowlist(); +bool IsBlockedUNCPath(const nsAString& aFilePath); + +#ifdef XP_WIN +bool IsAllowedPath(const nsAString& aFilePath); +#else +bool IsAllowedPath(const nsACString& aFilePath); +#endif + +#ifdef XP_WIN +bool StartsWithDiskDesignatorAndBackslash(const nsAString& aAbsolutePath); +#endif + +extern const char kPathSeparator; +#ifdef XP_WIN +extern const nsLiteralString kDevicePathSpecifier; +#endif + +namespace testing { + +void SetBlockUNCPaths(bool aBlock); +void AddDirectoryToAllowlist(nsAString const& aPath); +bool NormalizePath(nsAString const& aPath, nsAString& aNormalized); + +} // namespace testing + +} // namespace FilePreferences +} // namespace mozilla diff --git a/xpcom/io/FileUtilsWin.cpp b/xpcom/io/FileUtilsWin.cpp new file mode 100644 index 0000000000..5bf7e4c968 --- /dev/null +++ b/xpcom/io/FileUtilsWin.cpp @@ -0,0 +1,127 @@ +/* -*- 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 "FileUtilsWin.h" + +#include <windows.h> +#include <psapi.h> + +#include "base/process_util.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" +#include "nsWindowsHelpers.h" + +namespace mozilla { + +bool HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename) { + AUTO_PROFILER_LABEL("HandletoFilename", OTHER); + + aFilename.Truncate(); + // This implementation is nice because it uses fully documented APIs that + // are available on all Windows versions that we support. + nsAutoHandle fileMapping( + CreateFileMapping(aHandle, nullptr, PAGE_READONLY, 0, 1, nullptr)); + if (!fileMapping) { + return false; + } + const auto view = MapViewOfFile(fileMapping, FILE_MAP_READ, aOffset.HighPart, + aOffset.LowPart, 1); + if (!view) { + return false; + } + const auto cleanup = + MakeScopeExit([&]() { mozilla::Unused << UnmapViewOfFile(view); }); + + nsAutoString mappedFilename; + DWORD len = 0; + SetLastError(ERROR_SUCCESS); + do { + mappedFilename.SetLength(mappedFilename.Length() + MAX_PATH); + len = GetMappedFileNameW(GetCurrentProcess(), view, mappedFilename.get(), + mappedFilename.Length()); + } while (!len && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + if (!len) { + return false; + } + mappedFilename.Truncate(len); + return NtPathToDosPath(mappedFilename, aFilename); +} + +template <class T> +struct RVAMap { + RVAMap(HANDLE map, DWORD offset) { + SYSTEM_INFO info; + GetSystemInfo(&info); + + DWORD alignedOffset = + (offset / info.dwAllocationGranularity) * info.dwAllocationGranularity; + + MOZ_ASSERT(offset - alignedOffset < info.dwAllocationGranularity, "Wtf"); + + mRealView = ::MapViewOfFile(map, FILE_MAP_READ, 0, alignedOffset, + sizeof(T) + (offset - alignedOffset)); + + mMappedView = + mRealView + ? reinterpret_cast<T*>((char*)mRealView + (offset - alignedOffset)) + : nullptr; + } + ~RVAMap() { + if (mRealView) { + ::UnmapViewOfFile(mRealView); + } + } + operator const T*() const { return mMappedView; } + const T* operator->() const { return mMappedView; } + + private: + const T* mMappedView; + void* mRealView; +}; + +uint32_t GetExecutableArchitecture(const wchar_t* aPath) { + nsAutoHandle file(::CreateFileW(aPath, GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + nullptr)); + if (!file) { + return base::PROCESS_ARCH_INVALID; + } + + nsAutoHandle map( + ::CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr)); + if (!map) { + return base::PROCESS_ARCH_INVALID; + } + + RVAMap<IMAGE_DOS_HEADER> peHeader(map, 0); + if (!peHeader) { + return base::PROCESS_ARCH_INVALID; + } + + RVAMap<IMAGE_NT_HEADERS> ntHeader(map, peHeader->e_lfanew); + if (!ntHeader) { + return base::PROCESS_ARCH_INVALID; + } + + switch (ntHeader->FileHeader.Machine) { + case IMAGE_FILE_MACHINE_I386: + return base::PROCESS_ARCH_I386; + case IMAGE_FILE_MACHINE_AMD64: + return base::PROCESS_ARCH_X86_64; + case IMAGE_FILE_MACHINE_ARM64: + return base::PROCESS_ARCH_ARM_64; + case IMAGE_FILE_MACHINE_ARM: + case IMAGE_FILE_MACHINE_ARMNT: + case IMAGE_FILE_MACHINE_THUMB: + return base::PROCESS_ARCH_ARM; + default: + return base::PROCESS_ARCH_INVALID; + } +} + +} // namespace mozilla diff --git a/xpcom/io/FileUtilsWin.h b/xpcom/io/FileUtilsWin.h new file mode 100644 index 0000000000..548aed6dd7 --- /dev/null +++ b/xpcom/io/FileUtilsWin.h @@ -0,0 +1,146 @@ +/* -*- 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_FileUtilsWin_h +#define mozilla_FileUtilsWin_h + +#include <windows.h> + +#include "nsString.h" + +namespace mozilla { + +inline bool EnsureLongPath(nsAString& aDosPath) { + nsAutoString inputPath(aDosPath); + while (true) { + DWORD requiredLength = GetLongPathNameW( + inputPath.get(), reinterpret_cast<wchar_t*>(aDosPath.BeginWriting()), + aDosPath.Length()); + if (!requiredLength) { + return false; + } + if (requiredLength < aDosPath.Length()) { + // When GetLongPathNameW deems the last argument too small, + // it returns a value, but when you pass that value back, it's + // satisfied and returns a number that's one smaller. If the above + // check was == instead of <, the loop would go on forever with + // GetLongPathNameW returning oscillating values! + aDosPath.Truncate(requiredLength); + return true; + } + aDosPath.SetLength(requiredLength); + } +} + +inline bool NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath) { + aDosPath.Truncate(); + if (aNtPath.IsEmpty()) { + return true; + } + constexpr auto symLinkPrefix = u"\\??\\"_ns; + uint32_t ntPathLen = aNtPath.Length(); + uint32_t symLinkPrefixLen = symLinkPrefix.Length(); + if (ntPathLen >= 6 && aNtPath.CharAt(5) == L':' && + ntPathLen >= symLinkPrefixLen && + Substring(aNtPath, 0, symLinkPrefixLen).Equals(symLinkPrefix)) { + // Symbolic link for DOS device. Just strip off the prefix. + aDosPath = aNtPath; + aDosPath.Cut(0, 4); + return true; + } + nsAutoString logicalDrives; + while (true) { + DWORD requiredLength = GetLogicalDriveStringsW( + logicalDrives.Length(), + reinterpret_cast<wchar_t*>(logicalDrives.BeginWriting())); + if (!requiredLength) { + return false; + } + if (requiredLength < logicalDrives.Length()) { + // When GetLogicalDriveStringsW deems the first argument too small, + // it returns a value, but when you pass that value back, it's + // satisfied and returns a number that's one smaller. If the above + // check was == instead of <, the loop would go on forever with + // GetLogicalDriveStringsW returning oscillating values! + logicalDrives.Truncate(requiredLength); + // logicalDrives now has the format "C:\\\0D:\\\0Z:\\\0". That is, + // the sequence drive letter, colon, backslash, U+0000 repeats. + break; + } + logicalDrives.SetLength(requiredLength); + } + + const char16_t* cur = logicalDrives.BeginReading(); + const char16_t* end = logicalDrives.EndReading(); + nsString targetPath; + targetPath.SetLength(MAX_PATH); + wchar_t driveTemplate[] = L" :"; + while (cur < end) { + // Unfortunately QueryDosDevice doesn't support the idiom for querying the + // output buffer size, so it may require retries. + driveTemplate[0] = *cur; + DWORD targetPathLen = 0; + SetLastError(ERROR_SUCCESS); + while (true) { + targetPathLen = QueryDosDeviceW( + driveTemplate, reinterpret_cast<wchar_t*>(targetPath.BeginWriting()), + targetPath.Length()); + if (targetPathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + targetPath.SetLength(targetPath.Length() * 2); + } + if (targetPathLen) { + // Need to use wcslen here because targetPath contains embedded NULL chars + size_t firstTargetPathLen = wcslen(targetPath.get()); + const char16_t* pathComponent = + aNtPath.BeginReading() + firstTargetPathLen; + bool found = _wcsnicmp(char16ptr_t(aNtPath.BeginReading()), + targetPath.get(), firstTargetPathLen) == 0 && + *pathComponent == L'\\'; + if (found) { + aDosPath = driveTemplate; + aDosPath += pathComponent; + return EnsureLongPath(aDosPath); + } + } + // Find the next U+0000 within the logical string + while (*cur) { + // This loop skips the drive letter, the colon + // and the backslash. + cur++; + } + // Skip over the U+0000 that ends a drive entry + // within the logical string + cur++; + } + // Try to handle UNC paths. NB: This must happen after we've checked drive + // mappings in case a UNC path is mapped to a drive! + constexpr auto uncPrefix = u"\\\\"_ns; + constexpr auto deviceMupPrefix = u"\\Device\\Mup\\"_ns; + if (StringBeginsWith(aNtPath, deviceMupPrefix)) { + aDosPath = uncPrefix; + aDosPath += Substring(aNtPath, deviceMupPrefix.Length()); + return true; + } + constexpr auto deviceLanmanRedirectorPrefix = + u"\\Device\\LanmanRedirector\\"_ns; + if (StringBeginsWith(aNtPath, deviceLanmanRedirectorPrefix)) { + aDosPath = uncPrefix; + aDosPath += Substring(aNtPath, deviceLanmanRedirectorPrefix.Length()); + return true; + } + return false; +} + +bool HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename); + +uint32_t GetExecutableArchitecture(const wchar_t* aPath); + +} // namespace mozilla + +#endif // mozilla_FileUtilsWin_h diff --git a/xpcom/io/FixedBufferOutputStream.cpp b/xpcom/io/FixedBufferOutputStream.cpp new file mode 100644 index 0000000000..1c841bb91f --- /dev/null +++ b/xpcom/io/FixedBufferOutputStream.cpp @@ -0,0 +1,161 @@ +/* -*- 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 "FixedBufferOutputStream.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/StreamBufferSinkImpl.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +namespace mozilla { + +FixedBufferOutputStream::FixedBufferOutputStream( + UniquePtr<StreamBufferSink>&& aSink) + : mSink(std::move(aSink)), + mMutex("FixedBufferOutputStream::mMutex"), + mOffset(0), + mWriting(false), + mClosed(false) {} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + size_t aLength) { + MOZ_ASSERT(aLength); + + return MakeRefPtr<FixedBufferOutputStream>(MakeUnique<BufferSink>(aLength)); +} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + size_t aLength, const mozilla::fallible_t&) { + MOZ_ASSERT(aLength); + + auto sink = BufferSink::Alloc(aLength); + + if (NS_WARN_IF(!sink)) { + return nullptr; + } + + return MakeRefPtr<FixedBufferOutputStream>(std::move(sink)); +} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + mozilla::Span<char> aBuffer) { + return MakeRefPtr<FixedBufferOutputStream>( + MakeUnique<nsBorrowedSink>(aBuffer)); +} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + UniquePtr<StreamBufferSink>&& aSink) { + return MakeRefPtr<FixedBufferOutputStream>(std::move(aSink)); +} + +nsDependentCSubstring FixedBufferOutputStream::WrittenData() { + MutexAutoLock autoLock(mMutex); + + return mSink->Slice(mOffset); +} + +NS_IMPL_ISUPPORTS(FixedBufferOutputStream, nsIOutputStream) + +NS_IMETHODIMP +FixedBufferOutputStream::Close() { + MutexAutoLock autoLock(mMutex); + + if (mWriting) { + return NS_ERROR_NOT_AVAILABLE; + } + + mClosed = true; + + return NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::Flush() { return NS_OK; } + +NS_IMETHODIMP +FixedBufferOutputStream::StreamStatus() { + MutexAutoLock autoLock(mMutex); + return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) { + return WriteSegments(NS_CopyBufferToSegment, const_cast<char*>(aBuf), aCount, + _retval); +} + +NS_IMETHODIMP +FixedBufferOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval) { + return WriteSegments(NS_CopyStreamToSegment, aFromStream, aCount, _retval); +} + +NS_IMETHODIMP +FixedBufferOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) { + MOZ_ASSERT(_retval); + + MutexAutoLock autoLock(mMutex); + + if (mWriting) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + size_t length = mSink->Data().Length(); + size_t offset = mOffset; + + MOZ_ASSERT(length >= offset, "Bad stream state!"); + + size_t maxCount = length - offset; + if (maxCount == 0) { + *_retval = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + mWriting = true; + + nsresult rv; + + { + MutexAutoUnlock autoUnlock(mMutex); + + rv = aReader(this, aClosure, mSink->Data().Elements() + offset, 0, aCount, + _retval); + } + + mWriting = false; + + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(*_retval <= aCount, + "Reader should not read more than we asked it to read!"); + mOffset += *_retval; + } + + // As defined in nsIOutputStream.idl, do not pass reader func errors. + return NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::IsNonBlocking(bool* _retval) { + *_retval = true; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/FixedBufferOutputStream.h b/xpcom/io/FixedBufferOutputStream.h new file mode 100644 index 0000000000..db7fba3c3a --- /dev/null +++ b/xpcom/io/FixedBufferOutputStream.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_FixedBufferOutputStream_h +#define mozilla_FixedBufferOutputStream_h + +#include <cstddef> +#include "mozilla/fallible.h" +#include "mozilla/Mutex.h" +#include "mozilla/Span.h" +#include "mozilla/UniquePtr.h" +#include "nsIOutputStream.h" +#include "nsStringFwd.h" + +template <class T> +class RefPtr; + +namespace mozilla { + +class StreamBufferSink; + +// An output stream so you can read your potentially-async input stream into +// a contiguous buffer using NS_AsyncCopy. Back when streams were more +// synchronous and people didn't know blocking I/O was bad, if you wanted to +// read a stream into a flat buffer, you could use NS_ReadInputStreamToString +// or NS_ReadInputStreamToBuffer. But those don't work with async streams. +// This can be used to replace hand-rolled Read/AsyncWait() loops. Because you +// specify the expected size up front, the buffer is pre-allocated so wasteful +// reallocations can be avoided. +class FixedBufferOutputStream final : public nsIOutputStream { + template <typename T, typename... Args> + friend RefPtr<T> MakeRefPtr(Args&&... aArgs); + + public: + // Factory method to get a FixedBufferOutputStream by allocating a char buffer + // with the given length. + static RefPtr<FixedBufferOutputStream> Create(size_t aLength); + + // Factory method to get a FixedBufferOutputStream by allocating a char buffer + // with the given length, fallible version. + static RefPtr<FixedBufferOutputStream> Create(size_t aLength, + const mozilla::fallible_t&); + + // Factory method to get a FixedBufferOutputStream from a preallocated char + // buffer. The output stream doesn't take ownership of the char buffer, so the + // char buffer must outlive the output stream (to avoid a use-after-free). + static RefPtr<FixedBufferOutputStream> Create(mozilla::Span<char> aBuffer); + + // Factory method to get a FixedBufferOutputStream from an arbitrary + // StreamBufferSink. + static RefPtr<FixedBufferOutputStream> Create( + UniquePtr<StreamBufferSink>&& aSink); + + nsDependentCSubstring WrittenData(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + private: + explicit FixedBufferOutputStream(UniquePtr<StreamBufferSink>&& aSink); + + ~FixedBufferOutputStream() = default; + + const UniquePtr<StreamBufferSink> mSink; + + Mutex mMutex; + + size_t mOffset MOZ_GUARDED_BY(mMutex); + bool mWriting MOZ_GUARDED_BY(mMutex); + bool mClosed MOZ_GUARDED_BY(mMutex); +}; + +} // namespace mozilla + +#endif // mozilla_FixedBufferOutputStream_h diff --git a/xpcom/io/InputStreamLengthHelper.cpp b/xpcom/io/InputStreamLengthHelper.cpp new file mode 100644 index 0000000000..9a767f9229 --- /dev/null +++ b/xpcom/io/InputStreamLengthHelper.cpp @@ -0,0 +1,258 @@ +/* -*- 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 "InputStreamLengthHelper.h" +#include "mozilla/dom/WorkerCommon.h" +#include "nsIAsyncInputStream.h" +#include "nsIInputStream.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { + +namespace { + +class AvailableEvent final : public Runnable { + public: + AvailableEvent(nsIInputStream* stream, + const std::function<void(int64_t aLength)>& aCallback) + : Runnable("mozilla::AvailableEvent"), + mStream(stream), + mCallback(aCallback), + mSize(-1) { + mCallbackTarget = GetCurrentSerialEventTarget(); + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_IMETHOD + Run() override { + // ping + if (!NS_IsMainThread()) { + uint64_t size = 0; + if (NS_WARN_IF(NS_FAILED(mStream->Available(&size)))) { + mSize = -1; + } else { + mSize = (int64_t)size; + } + + mStream = nullptr; + + nsCOMPtr<nsIRunnable> self(this); // overly cute + mCallbackTarget->Dispatch(self.forget(), NS_DISPATCH_NORMAL); + mCallbackTarget = nullptr; + return NS_OK; + } + + // pong + std::function<void(int64_t aLength)> callback; + callback.swap(mCallback); + callback(mSize); + return NS_OK; + } + + private: + nsCOMPtr<nsIInputStream> mStream; + std::function<void(int64_t aLength)> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + + int64_t mSize; +}; + +} // namespace + +/* static */ +bool InputStreamLengthHelper::GetSyncLength(nsIInputStream* aStream, + int64_t* aLength) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aLength); + + *aLength = -1; + + // Sync length access. + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(aStream); + if (streamLength) { + int64_t length = -1; + nsresult rv = streamLength->Length(&length); + + // All good! + if (NS_SUCCEEDED(rv)) { + *aLength = length; + return true; + } + + // Already closed stream or an error occurred. + if (rv == NS_BASE_STREAM_CLOSED || + NS_WARN_IF(rv == NS_ERROR_NOT_AVAILABLE) || + NS_WARN_IF(rv != NS_BASE_STREAM_WOULD_BLOCK)) { + return true; + } + } + + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(aStream); + if (asyncStreamLength) { + // GetAsyncLength should be used. + return false; + } + + // We cannot calculate the length of an async stream. + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream); + if (asyncStream) { + return false; + } + + // For main-thread only, we want to avoid calling ::Available() for blocking + // streams. + if (NS_IsMainThread()) { + bool nonBlocking = false; + if (NS_WARN_IF(NS_FAILED(aStream->IsNonBlocking(&nonBlocking)))) { + // Let's return -1. There is nothing else we can do here. + return true; + } + + if (!nonBlocking) { + return false; + } + } + + // Fallback using available(). + uint64_t available = 0; + nsresult rv = aStream->Available(&available); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Let's return -1. There is nothing else we can do here. + return true; + } + + *aLength = (int64_t)available; + return true; +} + +/* static */ +void InputStreamLengthHelper::GetAsyncLength( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aCallback); + + // We don't want to allow this class to be used on workers because we are not + // using the correct Runnable types. + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread() || + !dom::IsCurrentThreadRunningWorker()); + + RefPtr<InputStreamLengthHelper> helper = + new InputStreamLengthHelper(aStream, aCallback); + + // Let's be sure that we don't call ::Available() on main-thread. + if (NS_IsMainThread()) { + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(aStream); + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(aStream); + if (!streamLength && !asyncStreamLength) { + // We cannot calculate the length of an async stream. We must fix the + // caller if this happens. +#ifdef DEBUG + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream); + MOZ_DIAGNOSTIC_ASSERT(!asyncStream); +#endif + + bool nonBlocking = false; + if (NS_SUCCEEDED(aStream->IsNonBlocking(&nonBlocking)) && !nonBlocking) { + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + RefPtr<AvailableEvent> event = new AvailableEvent(aStream, aCallback); + target->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + return; + } + } + } + + // Let's go async in order to have similar behaviors for sync and async + // nsIInputStreamLength implementations. + GetCurrentSerialEventTarget()->Dispatch(helper, NS_DISPATCH_NORMAL); +} + +InputStreamLengthHelper::InputStreamLengthHelper( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback) + : Runnable("InputStreamLengthHelper"), + mStream(aStream), + mCallback(aCallback) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aCallback); +} + +InputStreamLengthHelper::~InputStreamLengthHelper() = default; + +NS_IMETHODIMP +InputStreamLengthHelper::Run() { + // Sync length access. + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(mStream); + if (streamLength) { + int64_t length = -1; + nsresult rv = streamLength->Length(&length); + + // All good! + if (NS_SUCCEEDED(rv)) { + ExecCallback(length); + return NS_OK; + } + + // Already closed stream or an error occurred. + if (rv == NS_BASE_STREAM_CLOSED || + NS_WARN_IF(rv == NS_ERROR_NOT_AVAILABLE) || + NS_WARN_IF(rv != NS_BASE_STREAM_WOULD_BLOCK)) { + ExecCallback(-1); + return NS_OK; + } + } + + // Async length access. + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(mStream); + if (asyncStreamLength) { + nsresult rv = + asyncStreamLength->AsyncLengthWait(this, GetCurrentSerialEventTarget()); + if (NS_WARN_IF(NS_FAILED(rv))) { + ExecCallback(-1); + } + + return NS_OK; + } + + // Fallback using available(). + uint64_t available = 0; + nsresult rv = mStream->Available(&available); + if (NS_WARN_IF(NS_FAILED(rv))) { + ExecCallback(-1); + return NS_OK; + } + + ExecCallback((int64_t)available); + return NS_OK; +} + +NS_IMETHODIMP +InputStreamLengthHelper::OnInputStreamLengthReady( + nsIAsyncInputStreamLength* aStream, int64_t aLength) { + ExecCallback(aLength); + return NS_OK; +} + +void InputStreamLengthHelper::ExecCallback(int64_t aLength) { + MOZ_ASSERT(mCallback); + + std::function<void(int64_t aLength)> callback; + callback.swap(mCallback); + + callback(aLength); +} + +NS_IMPL_ISUPPORTS_INHERITED(InputStreamLengthHelper, Runnable, + nsIInputStreamLengthCallback) + +} // namespace mozilla diff --git a/xpcom/io/InputStreamLengthHelper.h b/xpcom/io/InputStreamLengthHelper.h new file mode 100644 index 0000000000..1f44c70fe3 --- /dev/null +++ b/xpcom/io/InputStreamLengthHelper.h @@ -0,0 +1,57 @@ +/* -*- 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_InputStreamLengthHelper_h +#define mozilla_InputStreamLengthHelper_h + +#include <functional> + +#include "nsISupportsImpl.h" +#include "nsIInputStreamLength.h" +#include "nsThreadUtils.h" + +class nsIInputStream; + +namespace mozilla { + +// This class helps to retrieve the stream's length. + +class InputStreamLengthHelper final : public Runnable, + public nsIInputStreamLengthCallback { + public: + NS_DECL_ISUPPORTS_INHERITED + + // This is one of the 2 entry points of this class. It returns false if the + // length cannot be taken synchronously. + static bool GetSyncLength(nsIInputStream* aStream, int64_t* aLength); + + // This is one of the 2 entry points of this class. The callback is executed + // asynchronously when the length is known. + static void GetAsyncLength( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback); + + private: + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + + InputStreamLengthHelper( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback); + + ~InputStreamLengthHelper(); + + NS_IMETHOD + Run() override; + + void ExecCallback(int64_t aLength); + + nsCOMPtr<nsIInputStream> mStream; + std::function<void(int64_t aLength)> mCallback; +}; + +} // namespace mozilla + +#endif // mozilla_InputStreamLengthHelper_h diff --git a/xpcom/io/InputStreamLengthWrapper.cpp b/xpcom/io/InputStreamLengthWrapper.cpp new file mode 100644 index 0000000000..9ba4968ad2 --- /dev/null +++ b/xpcom/io/InputStreamLengthWrapper.cpp @@ -0,0 +1,345 @@ +/* -*- 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 "InputStreamLengthWrapper.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +namespace mozilla { + +using namespace ipc; + +NS_IMPL_ADDREF(InputStreamLengthWrapper); +NS_IMPL_RELEASE(InputStreamLengthWrapper); + +NS_INTERFACE_MAP_BEGIN(InputStreamLengthWrapper) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mWeakCloneableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIIPCSerializableInputStream, + mWeakIPCSerializableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, + mWeakSeekableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, + mWeakTellableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +/* static */ +already_AddRefed<nsIInputStream> InputStreamLengthWrapper::MaybeWrap( + already_AddRefed<nsIInputStream> aInputStream, int64_t aLength) { + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + MOZ_ASSERT(inputStream); + + nsCOMPtr<nsIInputStreamLength> length = do_QueryInterface(inputStream); + if (length) { + return inputStream.forget(); + } + + nsCOMPtr<nsIAsyncInputStreamLength> asyncLength = + do_QueryInterface(inputStream); + if (asyncLength) { + return inputStream.forget(); + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(inputStream); + if (!asyncStream) { + return inputStream.forget(); + } + + inputStream = new InputStreamLengthWrapper(inputStream.forget(), aLength); + return inputStream.forget(); +} + +InputStreamLengthWrapper::InputStreamLengthWrapper( + already_AddRefed<nsIInputStream> aInputStream, int64_t aLength) + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mLength(aLength), + mConsumed(false), + mMutex("InputStreamLengthWrapper::mMutex") { + MOZ_ASSERT(mLength >= 0); + + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + SetSourceStream(inputStream.forget()); +} + +InputStreamLengthWrapper::InputStreamLengthWrapper() + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mLength(-1), + mConsumed(false), + mMutex("InputStreamLengthWrapper::mMutex") {} + +InputStreamLengthWrapper::~InputStreamLengthWrapper() = default; + +void InputStreamLengthWrapper::SetSourceStream( + already_AddRefed<nsIInputStream> aInputStream) { + MOZ_ASSERT(!mInputStream); + + mInputStream = std::move(aInputStream); + + nsCOMPtr<nsICloneableInputStream> cloneableStream = + do_QueryInterface(mInputStream); + if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) { + mWeakCloneableInputStream = cloneableStream; + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream = + do_QueryInterface(mInputStream); + if (serializableStream && SameCOMIdentity(mInputStream, serializableStream)) { + mWeakIPCSerializableInputStream = serializableStream; + } + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mInputStream); + if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) { + mWeakSeekableInputStream = seekableStream; + } + + nsCOMPtr<nsITellableStream> tellableStream = do_QueryInterface(mInputStream); + if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) { + mWeakTellableInputStream = tellableStream; + } + + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(mInputStream); + if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) { + mWeakAsyncInputStream = asyncInputStream; + } +} + +// nsIInputStream interface + +NS_IMETHODIMP +InputStreamLengthWrapper::Close() { + NS_ENSURE_STATE(mInputStream); + return mInputStream->Close(); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::Available(uint64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + return mInputStream->Available(aLength); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::StreamStatus() { + NS_ENSURE_STATE(mInputStream); + return mInputStream->StreamStatus(); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) { + NS_ENSURE_STATE(mInputStream); + mConsumed = true; + return mInputStream->Read(aBuffer, aCount, aReadCount); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +InputStreamLengthWrapper::IsNonBlocking(bool* aNonBlocking) { + NS_ENSURE_STATE(mInputStream); + return mInputStream->IsNonBlocking(aNonBlocking); +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +InputStreamLengthWrapper::GetCloneable(bool* aCloneable) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + mWeakCloneableInputStream->GetCloneable(aCloneable); + return NS_OK; +} + +NS_IMETHODIMP +InputStreamLengthWrapper::Clone(nsIInputStream** aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> stream = + new InputStreamLengthWrapper(clonedStream.forget(), mLength); + + stream.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +InputStreamLengthWrapper::CloseWithStatus(nsresult aStatus) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + mConsumed = true; + return mWeakAsyncInputStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + } + + return mWeakAsyncInputStream->AsyncWait(callback, aFlags, aRequestedCount, + aEventTarget); +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +InputStreamLengthWrapper::OnInputStreamReady(nsIAsyncInputStream* aStream) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakAsyncInputStream); + MOZ_ASSERT(mWeakAsyncInputStream == aStream); + + nsCOMPtr<nsIInputStreamCallback> callback; + { + MutexAutoLock lock(mMutex); + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamReady(this); +} + +// nsIIPCSerializableInputStream + +void InputStreamLengthWrapper::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + InputStreamHelper::SerializedComplexity(mInputStream, aMaxSize, aSizeUsed, + aPipes, aTransferables); +} + +void InputStreamLengthWrapper::Serialize( + mozilla::ipc::InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakIPCSerializableInputStream); + + InputStreamLengthWrapperParams params; + InputStreamHelper::SerializeInputStream(mInputStream, params.stream(), + aMaxSize, aSizeUsed); + params.length() = mLength; + params.consumed() = mConsumed; + + aParams = params; +} + +bool InputStreamLengthWrapper::Deserialize( + const mozilla::ipc::InputStreamParams& aParams) { + MOZ_ASSERT(!mInputStream); + MOZ_ASSERT(!mWeakIPCSerializableInputStream); + + if (aParams.type() != InputStreamParams::TInputStreamLengthWrapperParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const InputStreamLengthWrapperParams& params = + aParams.get_InputStreamLengthWrapperParams(); + + nsCOMPtr<nsIInputStream> stream = + InputStreamHelper::DeserializeInputStream(params.stream()); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + SetSourceStream(stream.forget()); + + mLength = params.length(); + mConsumed = params.consumed(); + + return true; +} + +// nsISeekableStream + +NS_IMETHODIMP +InputStreamLengthWrapper::Seek(int32_t aWhence, int64_t aOffset) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + mConsumed = true; + return mWeakSeekableInputStream->Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::SetEOF() { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + mConsumed = true; + return mWeakSeekableInputStream->SetEOF(); +} + +// nsITellableStream + +NS_IMETHODIMP +InputStreamLengthWrapper::Tell(int64_t* aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakTellableInputStream); + + return mWeakTellableInputStream->Tell(aResult); +} + +// nsIInputStreamLength + +NS_IMETHODIMP +InputStreamLengthWrapper::Length(int64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + *aLength = mLength; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/InputStreamLengthWrapper.h b/xpcom/io/InputStreamLengthWrapper.h new file mode 100644 index 0000000000..a11a808fa7 --- /dev/null +++ b/xpcom/io/InputStreamLengthWrapper.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 InputStreamLengthWrapper_h +#define InputStreamLengthWrapper_h + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "nsIInputStreamLength.h" + +namespace mozilla { + +// A wrapper keeps an inputStream together with its length. +// This class can be used for nsIInputStreams that do not implement +// nsIInputStreamLength. + +class InputStreamLengthWrapper final : public nsIAsyncInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream, + public nsIInputStreamCallback, + public nsIInputStreamLength { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + + // This method creates a InputStreamLengthWrapper around aInputStream if + // this doesn't implement nsIInputStreamLength or + // nsIInputStreamAsyncLength interface, but it implements + // nsIAsyncInputStream. For this kind of streams, + // InputStreamLengthHelper is not able to retrieve the length. This + // method will make such streams ready to be used with + // InputStreamLengthHelper. + static already_AddRefed<nsIInputStream> MaybeWrap( + already_AddRefed<nsIInputStream> aInputStream, int64_t aLength); + + // The length here will be used when nsIInputStreamLength::Length() is called. + InputStreamLengthWrapper(already_AddRefed<nsIInputStream> aInputStream, + int64_t aLength); + + // This CTOR is meant to be used just for IPC. + InputStreamLengthWrapper(); + + private: + ~InputStreamLengthWrapper(); + + void SetSourceStream(already_AddRefed<nsIInputStream> aInputStream); + + nsCOMPtr<nsIInputStream> mInputStream; + + // Raw pointers because these are just QI of mInputStream. + nsICloneableInputStream* mWeakCloneableInputStream; + nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream; + nsISeekableStream* mWeakSeekableInputStream; + nsITellableStream* mWeakTellableInputStream; + nsIAsyncInputStream* mWeakAsyncInputStream; + + int64_t mLength; + bool mConsumed; + + mozilla::Mutex mMutex; + + // This is used for AsyncWait and it's protected by mutex. + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mMutex); +}; + +} // namespace mozilla + +#endif // InputStreamLengthWrapper_h diff --git a/xpcom/io/NonBlockingAsyncInputStream.cpp b/xpcom/io/NonBlockingAsyncInputStream.cpp new file mode 100644 index 0000000000..00e8598d86 --- /dev/null +++ b/xpcom/io/NonBlockingAsyncInputStream.cpp @@ -0,0 +1,388 @@ +/* -*- 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 "NonBlockingAsyncInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +namespace mozilla { + +using namespace ipc; + +class NonBlockingAsyncInputStream::AsyncWaitRunnable final + : public CancelableRunnable { + RefPtr<NonBlockingAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + + public: + AsyncWaitRunnable(NonBlockingAsyncInputStream* aStream, + nsIInputStreamCallback* aCallback) + : CancelableRunnable("AsyncWaitRunnable"), + mStream(aStream), + mCallback(aCallback) {} + + NS_IMETHOD + Run() override { + mStream->RunAsyncWaitCallback(this, mCallback.forget()); + return NS_OK; + } + + nsresult Cancel() override { + mStream = nullptr; + return NS_OK; + } +}; + +NS_IMPL_ADDREF(NonBlockingAsyncInputStream); +NS_IMPL_RELEASE(NonBlockingAsyncInputStream); + +NonBlockingAsyncInputStream::WaitClosureOnly::WaitClosureOnly( + AsyncWaitRunnable* aRunnable, nsIEventTarget* aEventTarget) + : mRunnable(aRunnable), mEventTarget(aEventTarget) {} + +NS_INTERFACE_MAP_BEGIN(NonBlockingAsyncInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mWeakCloneableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mWeakIPCSerializableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, + mWeakSeekableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, + mWeakTellableInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +/* static */ +nsresult NonBlockingAsyncInputStream::Create( + already_AddRefed<nsIInputStream> aInputStream, + nsIAsyncInputStream** aResult) { + MOZ_DIAGNOSTIC_ASSERT(aResult); + + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + + bool nonBlocking = false; + nsresult rv = inputStream->IsNonBlocking(&nonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_DIAGNOSTIC_ASSERT(nonBlocking); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(inputStream); + MOZ_DIAGNOSTIC_ASSERT(!asyncInputStream); +#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED + + RefPtr<NonBlockingAsyncInputStream> stream = + new NonBlockingAsyncInputStream(inputStream.forget()); + + stream.forget(aResult); + return NS_OK; +} + +NonBlockingAsyncInputStream::NonBlockingAsyncInputStream( + already_AddRefed<nsIInputStream> aInputStream) + : mInputStream(std::move(aInputStream)), + mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mLock("NonBlockingAsyncInputStream::mLock"), + mClosed(false) { + MOZ_ASSERT(mInputStream); + + nsCOMPtr<nsICloneableInputStream> cloneableStream = + do_QueryInterface(mInputStream); + if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) { + mWeakCloneableInputStream = cloneableStream; + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream = + do_QueryInterface(mInputStream); + if (serializableStream && SameCOMIdentity(mInputStream, serializableStream)) { + mWeakIPCSerializableInputStream = serializableStream; + } + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mInputStream); + if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) { + mWeakSeekableInputStream = seekableStream; + } + + nsCOMPtr<nsITellableStream> tellableStream = do_QueryInterface(mInputStream); + if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) { + mWeakTellableInputStream = tellableStream; + } +} + +NonBlockingAsyncInputStream::~NonBlockingAsyncInputStream() = default; + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Close() { + RefPtr<AsyncWaitRunnable> waitClosureOnlyRunnable; + nsCOMPtr<nsIEventTarget> waitClosureOnlyEventTarget; + + { + MutexAutoLock lock(mLock); + + if (mClosed) { + // Here we could return NS_BASE_STREAM_CLOSED as well, but just to avoid + // warning messages, let's make everybody happy with a NS_OK. + return NS_OK; + } + + mClosed = true; + + NS_ENSURE_STATE(mInputStream); + nsresult rv = mInputStream->Close(); + if (NS_WARN_IF(NS_FAILED(rv))) { + mWaitClosureOnly.reset(); + return rv; + } + + // If we have a WaitClosureOnly runnable, it's time to use it. + if (mWaitClosureOnly.isSome()) { + waitClosureOnlyRunnable = std::move(mWaitClosureOnly->mRunnable); + waitClosureOnlyEventTarget = std::move(mWaitClosureOnly->mEventTarget); + + mWaitClosureOnly.reset(); + + // Now we want to dispatch the asyncWaitCallback. + mAsyncWaitCallback = waitClosureOnlyRunnable; + } + } + + if (waitClosureOnlyRunnable) { + if (waitClosureOnlyEventTarget) { + waitClosureOnlyEventTarget->Dispatch(waitClosureOnlyRunnable, + NS_DISPATCH_NORMAL); + } else { + waitClosureOnlyRunnable->Run(); + } + } + + return NS_OK; +} + +// nsIInputStream interface + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Available(uint64_t* aLength) { + nsresult rv = mInputStream->Available(aLength); + // Don't issue warnings for legal condition NS_BASE_STREAM_CLOSED. + if (rv == NS_BASE_STREAM_CLOSED || NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Nothing more to read. Let's close the stream now. + if (*aLength == 0) { + MutexAutoLock lock(mLock); + mInputStream->Close(); + mClosed = true; + return NS_BASE_STREAM_CLOSED; + } + + return NS_OK; +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::StreamStatus() { + return mInputStream->StreamStatus(); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) { + return mInputStream->Read(aBuffer, aCount, aReadCount); +} + +namespace { + +class MOZ_RAII ReadSegmentsData { + public: + ReadSegmentsData(NonBlockingAsyncInputStream* aStream, + nsWriteSegmentFun aFunc, void* aClosure) + : mStream(aStream), mFunc(aFunc), mClosure(aClosure) {} + + NonBlockingAsyncInputStream* mStream; + nsWriteSegmentFun mFunc; + void* mClosure; +}; + +nsresult ReadSegmentsWriter(nsIInputStream* aInStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount) { + ReadSegmentsData* data = static_cast<ReadSegmentsData*>(aClosure); + return data->mFunc(data->mStream, data->mClosure, aFromSegment, aToOffset, + aCount, aWriteCount); +} + +} // namespace + +NS_IMETHODIMP +NonBlockingAsyncInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aResult) { + ReadSegmentsData data(this, aWriter, aClosure); + return mInputStream->ReadSegments(ReadSegmentsWriter, &data, aCount, aResult); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +NonBlockingAsyncInputStream::GetCloneable(bool* aCloneable) { + NS_ENSURE_STATE(mWeakCloneableInputStream); + return mWeakCloneableInputStream->GetCloneable(aCloneable); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Clone(nsIInputStream** aResult) { + NS_ENSURE_STATE(mWeakCloneableInputStream); + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream; + rv = Create(clonedStream.forget(), getter_AddRefs(asyncStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncStream.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +NonBlockingAsyncInputStream::CloseWithStatus(nsresult aStatus) { + return Close(); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + RefPtr<AsyncWaitRunnable> runnable; + { + MutexAutoLock lock(mLock); + + mWaitClosureOnly.reset(); + mAsyncWaitCallback = nullptr; + + if (!aCallback) { + // Canceling previous callbacks, which is done above. + return NS_OK; + } + + // Maybe the stream is already closed. + if (!mClosed) { + uint64_t length; + nsresult rv = mInputStream->Available(&length); + if (NS_SUCCEEDED(rv) && length == 0) { + mInputStream->Close(); + mClosed = true; + } + } + + runnable = new AsyncWaitRunnable(this, aCallback); + if ((aFlags & nsIAsyncInputStream::WAIT_CLOSURE_ONLY) && !mClosed) { + mWaitClosureOnly.emplace(runnable, aEventTarget); + return NS_OK; + } + + mAsyncWaitCallback = runnable; + } + + MOZ_ASSERT(runnable); + + if (aEventTarget) { + return aEventTarget->Dispatch(runnable.forget()); + } + + return runnable->Run(); +} + +// nsIIPCSerializableInputStream + +void NonBlockingAsyncInputStream::SerializedComplexity( + uint32_t aMaxSize, uint32_t* aSizeUsed, uint32_t* aPipes, + uint32_t* aTransferables) { + InputStreamHelper::SerializedComplexity(mInputStream, aMaxSize, aSizeUsed, + aPipes, aTransferables); +} + +void NonBlockingAsyncInputStream::Serialize( + mozilla::ipc::InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(mWeakIPCSerializableInputStream); + InputStreamHelper::SerializeInputStream(mInputStream, aParams, aMaxSize, + aSizeUsed); +} + +bool NonBlockingAsyncInputStream::Deserialize( + const mozilla::ipc::InputStreamParams& aParams) { + MOZ_CRASH("NonBlockingAsyncInputStream cannot be deserialized!"); + return true; +} + +// nsISeekableStream + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Seek(int32_t aWhence, int64_t aOffset) { + NS_ENSURE_STATE(mWeakSeekableInputStream); + return mWeakSeekableInputStream->Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::SetEOF() { + NS_ENSURE_STATE(mWeakSeekableInputStream); + return NS_ERROR_NOT_IMPLEMENTED; +} + +// nsITellableStream + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Tell(int64_t* aResult) { + NS_ENSURE_STATE(mWeakTellableInputStream); + return mWeakTellableInputStream->Tell(aResult); +} + +void NonBlockingAsyncInputStream::RunAsyncWaitCallback( + NonBlockingAsyncInputStream::AsyncWaitRunnable* aRunnable, + already_AddRefed<nsIInputStreamCallback> aCallback) { + nsCOMPtr<nsIInputStreamCallback> callback = std::move(aCallback); + + { + MutexAutoLock lock(mLock); + if (mAsyncWaitCallback != aRunnable) { + // The callback has been canceled in the meantime. + return; + } + + mAsyncWaitCallback = nullptr; + } + + callback->OnInputStreamReady(this); +} + +} // namespace mozilla diff --git a/xpcom/io/NonBlockingAsyncInputStream.h b/xpcom/io/NonBlockingAsyncInputStream.h new file mode 100644 index 0000000000..453ac2eafc --- /dev/null +++ b/xpcom/io/NonBlockingAsyncInputStream.h @@ -0,0 +1,85 @@ +/* -*- 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/. */ + +#ifndef NonBlockingAsyncInputStream_h +#define NonBlockingAsyncInputStream_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" + +// This class aims to wrap a non-blocking and non-async inputStream and expose +// it as nsIAsyncInputStream. +// Probably you don't want to use this class directly. Instead use +// NS_MakeAsyncNonBlockingInputStream() as it will handle different stream +// variants without requiring you to special-case them yourself. + +namespace mozilla { + +class NonBlockingAsyncInputStream final : public nsIAsyncInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + + // |aInputStream| must be a non-blocking, non-async inputSteam. + static nsresult Create(already_AddRefed<nsIInputStream> aInputStream, + nsIAsyncInputStream** aAsyncInputStream); + + private: + explicit NonBlockingAsyncInputStream( + already_AddRefed<nsIInputStream> aInputStream); + ~NonBlockingAsyncInputStream(); + + class AsyncWaitRunnable; + + void RunAsyncWaitCallback(AsyncWaitRunnable* aRunnable, + already_AddRefed<nsIInputStreamCallback> aCallback); + + nsCOMPtr<nsIInputStream> mInputStream; + + // Raw pointers because these are just QI of mInputStream. + nsICloneableInputStream* MOZ_NON_OWNING_REF mWeakCloneableInputStream; + nsIIPCSerializableInputStream* MOZ_NON_OWNING_REF + mWeakIPCSerializableInputStream; + nsISeekableStream* MOZ_NON_OWNING_REF mWeakSeekableInputStream; + nsITellableStream* MOZ_NON_OWNING_REF mWeakTellableInputStream; + + Mutex mLock; + + struct WaitClosureOnly { + WaitClosureOnly(AsyncWaitRunnable* aRunnable, nsIEventTarget* aEventTarget); + + RefPtr<AsyncWaitRunnable> mRunnable; + nsCOMPtr<nsIEventTarget> mEventTarget; + }; + + // This is set when AsyncWait is called with a callback and with + // WAIT_CLOSURE_ONLY as flag. + // This is protected by mLock. + Maybe<WaitClosureOnly> mWaitClosureOnly MOZ_GUARDED_BY(mLock); + + // This is protected by mLock. + RefPtr<AsyncWaitRunnable> mAsyncWaitCallback MOZ_GUARDED_BY(mLock); + + // This is protected by mLock. + bool mClosed MOZ_GUARDED_BY(mLock); +}; + +} // namespace mozilla + +#endif // NonBlockingAsyncInputStream_h diff --git a/xpcom/io/SlicedInputStream.cpp b/xpcom/io/SlicedInputStream.cpp new file mode 100644 index 0000000000..c64af8e9dd --- /dev/null +++ b/xpcom/io/SlicedInputStream.cpp @@ -0,0 +1,668 @@ +/* -*- 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 "SlicedInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ScopeExit.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +namespace mozilla { + +using namespace ipc; + +NS_IMPL_ADDREF(SlicedInputStream); +NS_IMPL_RELEASE(SlicedInputStream); + +NS_INTERFACE_MAP_BEGIN(SlicedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mWeakCloneableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIIPCSerializableInputStream, + mWeakIPCSerializableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, + mWeakSeekableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, + mWeakTellableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, + mWeakInputStreamLength || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIAsyncInputStreamLength, mWeakAsyncInputStreamLength || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIInputStreamLengthCallback, + mWeakAsyncInputStreamLength || !mInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +SlicedInputStream::SlicedInputStream( + already_AddRefed<nsIInputStream> aInputStream, uint64_t aStart, + uint64_t aLength) + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mWeakInputStreamLength(nullptr), + mWeakAsyncInputStreamLength(nullptr), + mStart(aStart), + mLength(aLength), + mCurPos(0), + mClosed(false), + mAsyncWaitFlags(0), + mAsyncWaitRequestedCount(0), + mMutex("SlicedInputStream::mMutex") { + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + SetSourceStream(inputStream.forget()); +} + +SlicedInputStream::SlicedInputStream() + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mWeakInputStreamLength(nullptr), + mWeakAsyncInputStreamLength(nullptr), + mStart(0), + mLength(0), + mCurPos(0), + mClosed(false), + mAsyncWaitFlags(0), + mAsyncWaitRequestedCount(0), + mMutex("SlicedInputStream::mMutex") {} + +SlicedInputStream::~SlicedInputStream() = default; + +void SlicedInputStream::SetSourceStream( + already_AddRefed<nsIInputStream> aInputStream) { + MOZ_ASSERT(!mInputStream); + + mInputStream = std::move(aInputStream); + + nsCOMPtr<nsICloneableInputStream> cloneableStream = + do_QueryInterface(mInputStream); + if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) { + mWeakCloneableInputStream = cloneableStream; + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream = + do_QueryInterface(mInputStream); + if (serializableStream && SameCOMIdentity(mInputStream, serializableStream)) { + mWeakIPCSerializableInputStream = serializableStream; + } + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mInputStream); + if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) { + mWeakSeekableInputStream = seekableStream; + } + + nsCOMPtr<nsITellableStream> tellableStream = do_QueryInterface(mInputStream); + if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) { + mWeakTellableInputStream = tellableStream; + } + + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(mInputStream); + if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) { + mWeakAsyncInputStream = asyncInputStream; + } + + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(mInputStream); + if (streamLength && SameCOMIdentity(mInputStream, streamLength)) { + mWeakInputStreamLength = streamLength; + } + + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(mInputStream); + if (asyncStreamLength && SameCOMIdentity(mInputStream, asyncStreamLength)) { + mWeakAsyncInputStreamLength = asyncStreamLength; + } +} + +uint64_t SlicedInputStream::AdjustRange(uint64_t aRange) { + CheckedUint64 range(aRange); + range += mCurPos; + + // Let's remove extra length from the end. + if (range.isValid() && range.value() > mStart + mLength) { + aRange -= XPCOM_MIN((uint64_t)aRange, range.value() - (mStart + mLength)); + } + + // Let's remove extra length from the begin. + if (mCurPos < mStart) { + aRange -= XPCOM_MIN((uint64_t)aRange, mStart - mCurPos); + } + + return aRange; +} + +// nsIInputStream interface + +NS_IMETHODIMP +SlicedInputStream::Close() { + NS_ENSURE_STATE(mInputStream); + + mClosed = true; + return mInputStream->Close(); +} + +NS_IMETHODIMP +SlicedInputStream::Available(uint64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = mInputStream->Available(aLength); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aLength = AdjustRange(*aLength); + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::StreamStatus() { + NS_ENSURE_STATE(mInputStream); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = mInputStream->StreamStatus(); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + } + return rv; +} + +NS_IMETHODIMP +SlicedInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) { + *aReadCount = 0; + + if (mClosed) { + return NS_OK; + } + + if (mCurPos < mStart) { + nsCOMPtr<nsISeekableStream> seekableStream = + do_QueryInterface(mInputStream); + if (seekableStream) { + nsresult rv = + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, mStart); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos = mStart; + } else { + char buf[4096]; + while (mCurPos < mStart) { + uint32_t bytesRead; + uint64_t bufCount = XPCOM_MIN(mStart - mCurPos, (uint64_t)sizeof(buf)); + nsresult rv = mInputStream->Read(buf, bufCount, &bytesRead); + if (NS_SUCCEEDED(rv) && bytesRead == 0) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos += bytesRead; + } + } + } + + // Let's reduce aCount in case it's too big. + if (mCurPos + aCount > mStart + mLength) { + aCount = mStart + mLength - mCurPos; + } + + // Nothing else to read. + if (!aCount) { + return NS_OK; + } + + nsresult rv = mInputStream->Read(aBuffer, aCount, aReadCount); + if (NS_SUCCEEDED(rv) && *aReadCount == 0) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos += *aReadCount; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SlicedInputStream::IsNonBlocking(bool* aNonBlocking) { + NS_ENSURE_STATE(mInputStream); + return mInputStream->IsNonBlocking(aNonBlocking); +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +SlicedInputStream::GetCloneable(bool* aCloneable) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::Clone(nsIInputStream** aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> sis = + new SlicedInputStream(clonedStream.forget(), mStart, mLength); + + sis.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +SlicedInputStream::CloseWithStatus(nsresult aStatus) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + mClosed = true; + return mWeakAsyncInputStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +SlicedInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + + uint32_t flags = aFlags; + uint32_t requestedCount = aRequestedCount; + + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + + // If we haven't started retrieving data, let's see if we can seek. + // If we cannot seek, we will do consecutive reads. + if (mCurPos < mStart && mWeakSeekableInputStream) { + nsresult rv = mWeakSeekableInputStream->Seek( + nsISeekableStream::NS_SEEK_SET, mStart); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos = mStart; + } + + mAsyncWaitFlags = aFlags; + mAsyncWaitRequestedCount = aRequestedCount; + mAsyncWaitEventTarget = aEventTarget; + + // If we are not at the right position, let's do an asyncWait just internal. + if (mCurPos < mStart) { + flags = 0; + requestedCount = mStart - mCurPos; + } + } + + return mWeakAsyncInputStream->AsyncWait(callback, flags, requestedCount, + aEventTarget); +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +SlicedInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakAsyncInputStream); + MOZ_ASSERT(mWeakAsyncInputStream == aStream); + + nsCOMPtr<nsIInputStreamCallback> callback; + uint32_t asyncWaitFlags = 0; + uint32_t asyncWaitRequestedCount = 0; + nsCOMPtr<nsIEventTarget> asyncWaitEventTarget; + + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + auto raii = MakeScopeExit([&] { + mMutex.AssertCurrentThreadOwns(); + mAsyncWaitCallback = nullptr; + mAsyncWaitEventTarget = nullptr; + }); + + asyncWaitFlags = mAsyncWaitFlags; + asyncWaitRequestedCount = mAsyncWaitRequestedCount; + asyncWaitEventTarget = mAsyncWaitEventTarget; + + // If at the end of this locked block, the callback is not null, it will be + // executed, otherwise, we are going to exec another AsyncWait(). + callback = mAsyncWaitCallback; + + if (mCurPos < mStart) { + char buf[4096]; + nsresult rv = NS_OK; + while (mCurPos < mStart) { + uint32_t bytesRead; + uint64_t bufCount = XPCOM_MIN(mStart - mCurPos, (uint64_t)sizeof(buf)); + rv = mInputStream->Read(buf, bufCount, &bytesRead); + if (NS_SUCCEEDED(rv) && bytesRead == 0) { + mClosed = true; + break; + } + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + asyncWaitFlags = 0; + asyncWaitRequestedCount = mStart - mCurPos; + // Here we want to exec another AsyncWait(). + callback = nullptr; + break; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + mCurPos += bytesRead; + } + + // Now we are ready to do the 'real' asyncWait. + if (mCurPos >= mStart) { + // We don't want to nullify the callback now, because it will be needed + // at the next ::OnInputStreamReady. + raii.release(); + callback = nullptr; + } + } + } + + if (callback) { + return callback->OnInputStreamReady(this); + } + + return mWeakAsyncInputStream->AsyncWait( + this, asyncWaitFlags, asyncWaitRequestedCount, asyncWaitEventTarget); +} + +// nsIIPCSerializableInputStream + +void SlicedInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + InputStreamHelper::SerializedComplexity(mInputStream, aMaxSize, aSizeUsed, + aPipes, aTransferables); + + // If we're going to be serializing a pipe to transfer the sliced data, and we + // are getting no efficiency improvements from transferables, stream this + // sliced input stream directly as a pipe to avoid streaming data which will + // be sliced off anyway. + if (*aPipes > 0 && *aTransferables == 0) { + *aSizeUsed = 0; + *aPipes = 1; + *aTransferables = 0; + } +} + +void SlicedInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakIPCSerializableInputStream); + + uint32_t sizeUsed = 0, pipes = 0, transferables = 0; + SerializedComplexity(aMaxSize, &sizeUsed, &pipes, &transferables); + if (pipes > 0 && transferables == 0) { + InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + SlicedInputStreamParams params; + InputStreamHelper::SerializeInputStream(mInputStream, params.stream(), + aMaxSize, aSizeUsed); + params.start() = mStart; + params.length() = mLength; + params.curPos() = mCurPos; + params.closed() = mClosed; + + aParams = params; +} + +bool SlicedInputStream::Deserialize( + const mozilla::ipc::InputStreamParams& aParams) { + MOZ_ASSERT(!mInputStream); + MOZ_ASSERT(!mWeakIPCSerializableInputStream); + + if (aParams.type() != InputStreamParams::TSlicedInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const SlicedInputStreamParams& params = aParams.get_SlicedInputStreamParams(); + + nsCOMPtr<nsIInputStream> stream = + InputStreamHelper::DeserializeInputStream(params.stream()); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + SetSourceStream(stream.forget()); + + mStart = params.start(); + mLength = params.length(); + mCurPos = params.curPos(); + mClosed = params.closed(); + + return true; +} + +// nsISeekableStream + +NS_IMETHODIMP +SlicedInputStream::Seek(int32_t aWhence, int64_t aOffset) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + int64_t offset; + nsresult rv; + + switch (aWhence) { + case NS_SEEK_SET: + offset = mStart + aOffset; + break; + case NS_SEEK_CUR: + // mCurPos could be lower than mStart if the reading has not started yet. + offset = XPCOM_MAX(mStart, mCurPos) + aOffset; + break; + case NS_SEEK_END: { + uint64_t available; + rv = mInputStream->Available(&available); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + offset = XPCOM_MIN(mStart + mLength, available) + aOffset; + break; + } + default: + return NS_ERROR_ILLEGAL_VALUE; + } + + if (offset < (int64_t)mStart || offset > (int64_t)(mStart + mLength)) { + return NS_ERROR_INVALID_ARG; + } + + rv = mWeakSeekableInputStream->Seek(NS_SEEK_SET, offset); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos = offset; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::SetEOF() { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + mClosed = true; + return mWeakSeekableInputStream->SetEOF(); +} + +// nsITellableStream + +NS_IMETHODIMP +SlicedInputStream::Tell(int64_t* aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakTellableInputStream); + + int64_t tell = 0; + + nsresult rv = mWeakTellableInputStream->Tell(&tell); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (tell < (int64_t)mStart) { + *aResult = 0; + return NS_OK; + } + + *aResult = tell - mStart; + if (*aResult > (int64_t)mLength) { + *aResult = mLength; + } + + return NS_OK; +} + +// nsIInputStreamLength + +NS_IMETHODIMP +SlicedInputStream::Length(int64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakInputStreamLength); + + nsresult rv = mWeakInputStreamLength->Length(aLength); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (*aLength == -1) { + return NS_OK; + } + + *aLength = (int64_t)AdjustRange((uint64_t)*aLength); + return NS_OK; +} + +// nsIAsyncInputStreamLength + +NS_IMETHODIMP +SlicedInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStreamLength); + + nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + mAsyncWaitLengthCallback = aCallback; + } + + return mWeakAsyncInputStreamLength->AsyncLengthWait(callback, aEventTarget); +} + +// nsIInputStreamLengthCallback + +NS_IMETHODIMP +SlicedInputStream::OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakAsyncInputStreamLength); + MOZ_ASSERT(mWeakAsyncInputStreamLength == aStream); + + nsCOMPtr<nsIInputStreamLengthCallback> callback; + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitLengthCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitLengthCallback); + } + + if (aLength != -1) { + aLength = (int64_t)AdjustRange((uint64_t)aLength); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamLengthReady(this, aLength); +} + +} // namespace mozilla diff --git a/xpcom/io/SlicedInputStream.h b/xpcom/io/SlicedInputStream.h new file mode 100644 index 0000000000..30b6c118ab --- /dev/null +++ b/xpcom/io/SlicedInputStream.h @@ -0,0 +1,102 @@ +/* -*- 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 SlicedInputStream_h +#define SlicedInputStream_h + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "nsIInputStreamLength.h" + +namespace mozilla { + +// A wrapper for a slice of an underlying input stream. + +class SlicedInputStream final : public nsIAsyncInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream, + public nsIInputStreamCallback, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStreamLengthCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + + // Create an input stream whose data comes from a slice of aInputStream. The + // slice begins at aStart bytes beyond aInputStream's current position, and + // extends for a maximum of aLength bytes. If aInputStream contains fewer + // than aStart bytes, reading from SlicedInputStream returns no data. If + // aInputStream contains more than aStart bytes, but fewer than aStart + + // aLength bytes, reading from SlicedInputStream returns as many bytes as can + // be consumed from aInputStream after reading aLength bytes. + // + // aInputStream should not be read from after constructing a + // SlicedInputStream wrapper around it. + + SlicedInputStream(already_AddRefed<nsIInputStream> aInputStream, + uint64_t aStart, uint64_t aLength); + + // This CTOR is meant to be used just for IPC. + SlicedInputStream(); + + private: + ~SlicedInputStream(); + + void SetSourceStream(already_AddRefed<nsIInputStream> aInputStream); + + uint64_t AdjustRange(uint64_t aRange); + + nsCOMPtr<nsIInputStream> mInputStream; + + // Raw pointers because these are just QI of mInputStream. + nsICloneableInputStream* mWeakCloneableInputStream; + nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream; + nsISeekableStream* mWeakSeekableInputStream; + nsITellableStream* mWeakTellableInputStream; + nsIAsyncInputStream* mWeakAsyncInputStream; + nsIInputStreamLength* mWeakInputStreamLength; + nsIAsyncInputStreamLength* mWeakAsyncInputStreamLength; + + uint64_t mStart; + uint64_t mLength; + uint64_t mCurPos; + + bool mClosed; + + // These four are used for AsyncWait. They are protected by mutex because + // touched on multiple threads. + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsIEventTarget> mAsyncWaitEventTarget MOZ_GUARDED_BY(mMutex); + uint32_t mAsyncWaitFlags MOZ_GUARDED_BY(mMutex); + uint32_t mAsyncWaitRequestedCount MOZ_GUARDED_BY(mMutex); + + // This is use for nsIAsyncInputStreamLength::AsyncWait. + // This is protected by mutex. + nsCOMPtr<nsIInputStreamLengthCallback> mAsyncWaitLengthCallback + MOZ_GUARDED_BY(mMutex); + + Mutex mMutex; +}; + +} // namespace mozilla + +#endif // SlicedInputStream_h diff --git a/xpcom/io/SnappyCompressOutputStream.cpp b/xpcom/io/SnappyCompressOutputStream.cpp new file mode 100644 index 0000000000..ebe9e1073c --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.cpp @@ -0,0 +1,259 @@ +/* -*- 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/SnappyCompressOutputStream.h" + +#include <algorithm> +#include "nsStreamUtils.h" +#include "snappy/snappy.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(SnappyCompressOutputStream, nsIOutputStream); + +// static +const size_t SnappyCompressOutputStream::kMaxBlockSize = snappy::kBlockSize; + +SnappyCompressOutputStream::SnappyCompressOutputStream( + nsIOutputStream* aBaseStream, size_t aBlockSize) + : mBaseStream(aBaseStream), + mBlockSize(std::min(aBlockSize, kMaxBlockSize)), + mNextByte(0), + mCompressedBufferLength(0), + mStreamIdentifierWritten(false) { + MOZ_ASSERT(mBlockSize > 0); + + // This implementation only supports sync base streams. Verify this in debug + // builds. Note, this can be simpler than the check in + // SnappyUncompressInputStream because we don't have to deal with the + // nsStringInputStream oddness of being non-blocking and sync. +#ifdef DEBUG + bool baseNonBlocking; + nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(!baseNonBlocking); +#endif +} + +size_t SnappyCompressOutputStream::BlockSize() const { return mBlockSize; } + +NS_IMETHODIMP +SnappyCompressOutputStream::Close() { + if (!mBaseStream) { + return NS_OK; + } + + nsresult rv = Flush(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mBaseStream->Close(); + mBaseStream = nullptr; + + mBuffer = nullptr; + mCompressedBuffer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Flush() { + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = FlushToBaseStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mBaseStream->Flush(); + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::StreamStatus() { + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + return mBaseStream->StreamStatus(); +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* aResultOut) { + return WriteSegments(NS_CopyBufferToSegment, const_cast<char*>(aBuf), aCount, + aResultOut); +} + +NS_IMETHODIMP +SnappyCompressOutputStream::WriteFrom(nsIInputStream*, uint32_t, uint32_t*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, uint32_t aCount, + uint32_t* aBytesWrittenOut) { + *aBytesWrittenOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + if (!mBuffer) { + mBuffer.reset(new (fallible) char[mBlockSize]); + if (NS_WARN_IF(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + while (aCount > 0) { + // Determine how much space is left in our flat, uncompressed buffer. + MOZ_ASSERT(mNextByte <= mBlockSize); + uint32_t remaining = mBlockSize - mNextByte; + + // If it is full, then compress and flush the data to the base stream. + if (remaining == 0) { + nsresult rv = FlushToBaseStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Now the entire buffer should be available for copying. + MOZ_ASSERT(!mNextByte); + remaining = mBlockSize; + } + + uint32_t numToRead = std::min(remaining, aCount); + uint32_t numRead = 0; + + nsresult rv = aReader(this, aClosure, &mBuffer[mNextByte], + *aBytesWrittenOut, numToRead, &numRead); + + // As defined in nsIOutputStream.idl, do not pass reader func errors. + if (NS_FAILED(rv)) { + return NS_OK; + } + + // End-of-file + if (numRead == 0) { + return NS_OK; + } + + mNextByte += numRead; + *aBytesWrittenOut += numRead; + aCount -= numRead; + } + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::IsNonBlocking(bool* aNonBlockingOut) { + *aNonBlockingOut = false; + return NS_OK; +} + +SnappyCompressOutputStream::~SnappyCompressOutputStream() { Close(); } + +nsresult SnappyCompressOutputStream::FlushToBaseStream() { + MOZ_ASSERT(mBaseStream); + + // Lazily create the compressed buffer on our first flush. This + // allows us to report OOM during stream operation. This buffer + // will then get re-used until the stream is closed. + if (!mCompressedBuffer) { + mCompressedBufferLength = MaxCompressedBufferLength(mBlockSize); + mCompressedBuffer.reset(new (fallible) char[mCompressedBufferLength]); + if (NS_WARN_IF(!mCompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // The first chunk must be a StreamIdentifier chunk. Write it out + // if we have not done so already. + nsresult rv = MaybeFlushStreamIdentifier(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Compress the data to our internal compressed buffer. + size_t compressedLength; + rv = WriteCompressedData(mCompressedBuffer.get(), mCompressedBufferLength, + mBuffer.get(), mNextByte, &compressedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(compressedLength > 0); + + mNextByte = 0; + + // Write the compressed buffer out to the base stream. + uint32_t numWritten = 0; + rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(compressedLength == numWritten); + + return NS_OK; +} + +nsresult SnappyCompressOutputStream::MaybeFlushStreamIdentifier() { + MOZ_ASSERT(mCompressedBuffer); + + if (mStreamIdentifierWritten) { + return NS_OK; + } + + // Build the StreamIdentifier in our compressed buffer. + size_t compressedLength; + nsresult rv = WriteStreamIdentifier( + mCompressedBuffer.get(), mCompressedBufferLength, &compressedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Write the compressed buffer out to the base stream. + uint32_t numWritten = 0; + rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(compressedLength == numWritten); + + mStreamIdentifierWritten = true; + + return NS_OK; +} + +nsresult SnappyCompressOutputStream::WriteAll(const char* aBuf, uint32_t aCount, + uint32_t* aBytesWrittenOut) { + *aBytesWrittenOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + uint32_t offset = 0; + while (aCount > 0) { + uint32_t numWritten = 0; + nsresult rv = mBaseStream->Write(aBuf + offset, aCount, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + offset += numWritten; + aCount -= numWritten; + *aBytesWrittenOut += numWritten; + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/SnappyCompressOutputStream.h b/xpcom/io/SnappyCompressOutputStream.h new file mode 100644 index 0000000000..895691034b --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.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 mozilla_SnappyCompressOutputStream_h__ +#define mozilla_SnappyCompressOutputStream_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "nsISupportsImpl.h" +#include "SnappyFrameUtils.h" + +namespace mozilla { + +class SnappyCompressOutputStream final : public nsIOutputStream, + protected detail::SnappyFrameUtils { + public: + // Maximum compression block size. + static const size_t kMaxBlockSize; + + // Construct a new blocking output stream to compress data to + // the given base stream. The base stream must also be blocking. + // The compression block size may optionally be set to a value + // up to kMaxBlockSize. + explicit SnappyCompressOutputStream(nsIOutputStream* aBaseStream, + size_t aBlockSize = kMaxBlockSize); + + // The compression block size. To optimize stream performance + // try to write to the stream in segments at least this size. + size_t BlockSize() const; + + private: + virtual ~SnappyCompressOutputStream(); + + nsresult FlushToBaseStream(); + nsresult MaybeFlushStreamIdentifier(); + nsresult WriteAll(const char* aBuf, uint32_t aCount, + uint32_t* aBytesWrittenOut); + + nsCOMPtr<nsIOutputStream> mBaseStream; + const size_t mBlockSize; + + // Buffer holding copied uncompressed data. This must be copied here + // so that the compression can be performed on a single flat buffer. + mozilla::UniquePtr<char[]> mBuffer; + + // The next byte in the uncompressed data to copy incoming data to. + size_t mNextByte; + + // Buffer holding the resulting compressed data. + mozilla::UniquePtr<char[]> mCompressedBuffer; + size_t mCompressedBufferLength; + + // The first thing written to the stream must be a stream identifier. + bool mStreamIdentifierWritten; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM +}; + +} // namespace mozilla + +#endif // mozilla_SnappyCompressOutputStream_h__ diff --git a/xpcom/io/SnappyFrameUtils.cpp b/xpcom/io/SnappyFrameUtils.cpp new file mode 100644 index 0000000000..c606794fd9 --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.cpp @@ -0,0 +1,241 @@ +/* -*- 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/SnappyFrameUtils.h" + +#include "crc32c.h" +#include "mozilla/EndianUtils.h" +#include "nsDebug.h" +#include "snappy/snappy.h" + +namespace { + +using mozilla::NativeEndian; +using mozilla::detail::SnappyFrameUtils; + +SnappyFrameUtils::ChunkType ReadChunkType(uint8_t aByte) { + if (aByte == 0xff) { + return SnappyFrameUtils::StreamIdentifier; + } else if (aByte == 0x00) { + return SnappyFrameUtils::CompressedData; + } else if (aByte == 0x01) { + return SnappyFrameUtils::UncompressedData; + } else if (aByte == 0xfe) { + return SnappyFrameUtils::Padding; + } + + return SnappyFrameUtils::Reserved; +} + +void WriteChunkType(char* aDest, SnappyFrameUtils::ChunkType aType) { + unsigned char* dest = reinterpret_cast<unsigned char*>(aDest); + if (aType == SnappyFrameUtils::StreamIdentifier) { + *dest = 0xff; + } else if (aType == SnappyFrameUtils::CompressedData) { + *dest = 0x00; + } else if (aType == SnappyFrameUtils::UncompressedData) { + *dest = 0x01; + } else if (aType == SnappyFrameUtils::Padding) { + *dest = 0xfe; + } else { + *dest = 0x02; + } +} + +void WriteUInt24(char* aBuf, uint32_t aVal) { + MOZ_ASSERT(!(aVal & 0xff000000)); + uint32_t tmp = NativeEndian::swapToLittleEndian(aVal); + memcpy(aBuf, &tmp, 3); +} + +uint32_t ReadUInt24(const char* aBuf) { + uint32_t val = 0; + memcpy(&val, aBuf, 3); + return NativeEndian::swapFromLittleEndian(val); +} + +// This mask is explicitly defined in the snappy framing_format.txt file. +uint32_t MaskChecksum(uint32_t aValue) { + return ((aValue >> 15) | (aValue << 17)) + 0xa282ead8; +} + +} // namespace + +namespace mozilla { +namespace detail { + +using mozilla::LittleEndian; + +// static +nsresult SnappyFrameUtils::WriteStreamIdentifier(char* aDest, + size_t aDestLength, + size_t* aBytesWrittenOut) { + if (NS_WARN_IF(aDestLength < (kHeaderLength + kStreamIdentifierDataLength))) { + return NS_ERROR_NOT_AVAILABLE; + } + + WriteChunkType(aDest, StreamIdentifier); + aDest[1] = 0x06; // Data length + aDest[2] = 0x00; + aDest[3] = 0x00; + aDest[4] = 0x73; // "sNaPpY" + aDest[5] = 0x4e; + aDest[6] = 0x61; + aDest[7] = 0x50; + aDest[8] = 0x70; + aDest[9] = 0x59; + + static_assert(kHeaderLength + kStreamIdentifierDataLength == 10, + "StreamIdentifier chunk should be exactly 10 bytes long"); + *aBytesWrittenOut = kHeaderLength + kStreamIdentifierDataLength; + + return NS_OK; +} + +// static +nsresult SnappyFrameUtils::WriteCompressedData(char* aDest, size_t aDestLength, + const char* aData, + size_t aDataLength, + size_t* aBytesWrittenOut) { + *aBytesWrittenOut = 0; + + size_t neededLength = MaxCompressedBufferLength(aDataLength); + if (NS_WARN_IF(aDestLength < neededLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + size_t offset = 0; + + WriteChunkType(aDest, CompressedData); + offset += kChunkTypeLength; + + // Skip length for now and write it out after we know the compressed length. + size_t lengthOffset = offset; + offset += kChunkLengthLength; + + uint32_t crc = ComputeCrc32c( + ~0, reinterpret_cast<const unsigned char*>(aData), aDataLength); + uint32_t maskedCrc = MaskChecksum(crc); + LittleEndian::writeUint32(aDest + offset, maskedCrc); + offset += kCRCLength; + + size_t compressedLength; + snappy::RawCompress(aData, aDataLength, aDest + offset, &compressedLength); + + // Go back and write the data length. + size_t dataLength = compressedLength + kCRCLength; + WriteUInt24(aDest + lengthOffset, dataLength); + + *aBytesWrittenOut = kHeaderLength + dataLength; + + return NS_OK; +} + +// static +nsresult SnappyFrameUtils::ParseHeader(const char* aSource, + size_t aSourceLength, + ChunkType* aTypeOut, + size_t* aDataLengthOut) { + if (NS_WARN_IF(aSourceLength < kHeaderLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aTypeOut = ReadChunkType(aSource[0]); + *aDataLengthOut = ReadUInt24(aSource + kChunkTypeLength); + + return NS_OK; +} + +// static +nsresult SnappyFrameUtils::ParseData(char* aDest, size_t aDestLength, + ChunkType aType, const char* aData, + size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut) { + switch (aType) { + case StreamIdentifier: + return ParseStreamIdentifier(aDest, aDestLength, aData, aDataLength, + aBytesWrittenOut, aBytesReadOut); + + case CompressedData: + return ParseCompressedData(aDest, aDestLength, aData, aDataLength, + aBytesWrittenOut, aBytesReadOut); + + // TODO: support other snappy chunk types + default: + MOZ_ASSERT_UNREACHABLE("Unsupported snappy framing chunk type."); + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +// static +nsresult SnappyFrameUtils::ParseStreamIdentifier(char*, size_t, + const char* aData, + size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut) { + *aBytesWrittenOut = 0; + *aBytesReadOut = 0; + if (NS_WARN_IF(aDataLength != kStreamIdentifierDataLength || + aData[0] != 0x73 || aData[1] != 0x4e || aData[2] != 0x61 || + aData[3] != 0x50 || aData[4] != 0x70 || aData[5] != 0x59)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + *aBytesReadOut = aDataLength; + return NS_OK; +} + +// static +nsresult SnappyFrameUtils::ParseCompressedData(char* aDest, size_t aDestLength, + const char* aData, + size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut) { + *aBytesWrittenOut = 0; + *aBytesReadOut = 0; + size_t offset = 0; + + uint32_t readCrc = LittleEndian::readUint32(aData + offset); + offset += kCRCLength; + + size_t uncompressedLength; + if (NS_WARN_IF(!snappy::GetUncompressedLength( + aData + offset, aDataLength - offset, &uncompressedLength))) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + if (NS_WARN_IF(aDestLength < uncompressedLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (NS_WARN_IF(!snappy::RawUncompress(aData + offset, aDataLength - offset, + aDest))) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + uint32_t crc = ComputeCrc32c( + ~0, reinterpret_cast<const unsigned char*>(aDest), uncompressedLength); + uint32_t maskedCrc = MaskChecksum(crc); + if (NS_WARN_IF(readCrc != maskedCrc)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + *aBytesWrittenOut = uncompressedLength; + *aBytesReadOut = aDataLength; + + return NS_OK; +} + +// static +size_t SnappyFrameUtils::MaxCompressedBufferLength(size_t aSourceLength) { + size_t neededLength = kHeaderLength; + neededLength += kCRCLength; + neededLength += snappy::MaxCompressedLength(aSourceLength); + return neededLength; +} + +} // namespace detail +} // namespace mozilla diff --git a/xpcom/io/SnappyFrameUtils.h b/xpcom/io/SnappyFrameUtils.h new file mode 100644 index 0000000000..923994c661 --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.h @@ -0,0 +1,80 @@ +/* -*- 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_SnappyFrameUtils_h__ +#define mozilla_SnappyFrameUtils_h__ + +#include <cstddef> + +#include "mozilla/Attributes.h" +#include "nsError.h" + +namespace mozilla { +namespace detail { + +// +// Utility class providing primitives necessary to build streams based +// on the snappy compressor. This essentially abstracts the framing format +// defined in: +// +// other-licences/snappy/src/framing_format.txt +// +// NOTE: Currently only the StreamIdentifier and CompressedData chunks are +// supported. +// +class SnappyFrameUtils { + public: + enum ChunkType { + Unknown, + StreamIdentifier, + CompressedData, + UncompressedData, + Padding, + Reserved, + ChunkTypeCount + }; + + static const size_t kChunkTypeLength = 1; + static const size_t kChunkLengthLength = 3; + static const size_t kHeaderLength = kChunkTypeLength + kChunkLengthLength; + static const size_t kStreamIdentifierDataLength = 6; + static const size_t kCRCLength = 4; + + static nsresult WriteStreamIdentifier(char* aDest, size_t aDestLength, + size_t* aBytesWrittenOut); + + static nsresult WriteCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut); + + static nsresult ParseHeader(const char* aSource, size_t aSourceLength, + ChunkType* aTypeOut, size_t* aDataLengthOut); + + static nsresult ParseData(char* aDest, size_t aDestLength, ChunkType aType, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut); + + static nsresult ParseStreamIdentifier(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut); + + static nsresult ParseCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut); + + static size_t MaxCompressedBufferLength(size_t aSourceLength); + + protected: + SnappyFrameUtils() = default; + virtual ~SnappyFrameUtils() = default; +}; + +} // namespace detail +} // namespace mozilla + +#endif // mozilla_SnappyFrameUtils_h__ diff --git a/xpcom/io/SnappyUncompressInputStream.cpp b/xpcom/io/SnappyUncompressInputStream.cpp new file mode 100644 index 0000000000..2872c8c7a2 --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.cpp @@ -0,0 +1,386 @@ +/* -*- 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/SnappyUncompressInputStream.h" + +#include <algorithm> +#include "nsIAsyncInputStream.h" +#include "nsStreamUtils.h" +#include "snappy/snappy.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(SnappyUncompressInputStream, nsIInputStream); + +// Putting kCompressedBufferLength inside a function avoids a static +// constructor. +static size_t CompressedBufferLength() { + static size_t kCompressedBufferLength = + detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize); + + MOZ_ASSERT(kCompressedBufferLength > 0); + return kCompressedBufferLength; +} + +SnappyUncompressInputStream::SnappyUncompressInputStream( + nsIInputStream* aBaseStream) + : mBaseStream(aBaseStream), + mUncompressedBytes(0), + mNextByte(0), + mNextChunkType(Unknown), + mNextChunkDataLength(0), + mNeedFirstStreamIdentifier(true) { + // This implementation only supports sync base streams. Verify this in debug + // builds. Note, this is a bit complicated because the streams we support + // advertise different capabilities: + // - nsFileInputStream - blocking and sync + // - nsStringInputStream - non-blocking and sync + // - nsPipeInputStream - can be blocking, but provides async interface +#ifdef DEBUG + bool baseNonBlocking; + nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (baseNonBlocking) { + nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mBaseStream); + MOZ_ASSERT(!async); + } +#endif +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Close() { + if (!mBaseStream) { + return NS_OK; + } + + mBaseStream->Close(); + mBaseStream = nullptr; + + mUncompressedBuffer = nullptr; + mCompressedBuffer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Available(uint64_t* aLengthOut) { + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + // If we have uncompressed bytes, then we are done. + *aLengthOut = UncompressedLength(); + if (*aLengthOut > 0) { + return NS_OK; + } + + // Otherwise, attempt to uncompress bytes until we get something or the + // underlying stream is drained. We loop here because some chunks can + // be StreamIdentifiers, padding, etc with no data. + uint32_t bytesRead; + do { + nsresult rv = ParseNextChunk(&bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + *aLengthOut = UncompressedLength(); + } while (*aLengthOut == 0 && bytesRead); + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::StreamStatus() { + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + // If we have uncompressed bytes, then we're still open. + if (UncompressedLength() > 0) { + return NS_OK; + } + + // Otherwise we'll need to read from the underlying stream, so check it + return mBaseStream->StreamStatus(); +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount, + uint32_t* aBytesReadOut) { + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut); +} + +NS_IMETHODIMP +SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aBytesReadOut) { + *aBytesReadOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv; + + // Do not try to use the base stream's ReadSegements here. Its very + // unlikely we will get a single buffer that contains all of the compressed + // data and therefore would have to copy into our own buffer anyways. + // Instead, focus on making efficient use of the Read() interface. + + while (aCount > 0) { + // We have some decompressed data in our buffer. Provide it to the + // callers writer function. + if (mUncompressedBytes > 0) { + MOZ_ASSERT(mUncompressedBuffer); + uint32_t remaining = UncompressedLength(); + uint32_t numToWrite = std::min(aCount, remaining); + uint32_t numWritten; + rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte], + *aBytesReadOut, numToWrite, &numWritten); + + // As defined in nsIInputputStream.idl, do not pass writer func errors. + if (NS_FAILED(rv)) { + return NS_OK; + } + + // End-of-file + if (numWritten == 0) { + return NS_OK; + } + + *aBytesReadOut += numWritten; + mNextByte += numWritten; + MOZ_ASSERT(mNextByte <= mUncompressedBytes); + + if (mNextByte == mUncompressedBytes) { + mNextByte = 0; + mUncompressedBytes = 0; + } + + aCount -= numWritten; + + continue; + } + + // Otherwise uncompress the next chunk and loop. Any resulting data + // will set mUncompressedBytes which we check at the top of the loop. + uint32_t bytesRead; + rv = ParseNextChunk(&bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + // If we couldn't read anything and there is no more data to provide + // to the caller, then this is eof. + if (bytesRead == 0 && mUncompressedBytes == 0) { + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut) { + *aNonBlockingOut = false; + return NS_OK; +} + +SnappyUncompressInputStream::~SnappyUncompressInputStream() { Close(); } + +nsresult SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut) { + // There must not be any uncompressed data already in mUncompressedBuffer. + MOZ_ASSERT(mUncompressedBytes == 0); + MOZ_ASSERT(mNextByte == 0); + + nsresult rv; + *aBytesReadOut = 0; + + // Lazily create our two buffers so we can report OOM during stream + // operation. These allocations only happens once. The buffers are reused + // until the stream is closed. + if (!mUncompressedBuffer) { + mUncompressedBuffer.reset(new (fallible) char[snappy::kBlockSize]); + if (NS_WARN_IF(!mUncompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (!mCompressedBuffer) { + mCompressedBuffer.reset(new (fallible) char[CompressedBufferLength()]); + if (NS_WARN_IF(!mCompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // We have no decompressed data and we also have not seen the start of stream + // yet. Read and validate the StreamIdentifier chunk. Also read the next + // header to determine the size of the first real data chunk. + if (mNeedFirstStreamIdentifier) { + const uint32_t firstReadLength = + kHeaderLength + kStreamIdentifierDataLength + kHeaderLength; + MOZ_ASSERT(firstReadLength <= CompressedBufferLength()); + + rv = ReadAll(mCompressedBuffer.get(), firstReadLength, firstReadLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { + return rv; + } + + rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, &mNextChunkType, + &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (NS_WARN_IF(mNextChunkType != StreamIdentifier || + mNextChunkDataLength != kStreamIdentifierDataLength)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + size_t offset = kHeaderLength; + + mNeedFirstStreamIdentifier = false; + + size_t numRead; + size_t numWritten; + rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, + mNextChunkType, &mCompressedBuffer[offset], + mNextChunkDataLength, &numWritten, &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(numWritten == 0); + MOZ_ASSERT(numRead == mNextChunkDataLength); + offset += numRead; + + rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + // We have no compressed data and we don't know how big the next chunk is. + // This happens when we get an EOF pause in the middle of a stream and also + // at the end of the stream. Simply read the next header and return. The + // chunk body will be read on the next entry into this method. + if (mNextChunkType == Unknown) { + rv = ReadAll(mCompressedBuffer.get(), kHeaderLength, kHeaderLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { + return rv; + } + + rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, &mNextChunkType, + &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + // We have no decompressed data, but we do know the size of the next chunk. + // Read at least that much from the base stream. + uint32_t readLength = mNextChunkDataLength; + MOZ_ASSERT(readLength <= CompressedBufferLength()); + + // However, if there is enough data in the base stream, also read the next + // chunk header. This helps optimize the stream by avoiding many small reads. + uint64_t avail; + rv = mBaseStream->Available(&avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (avail >= (readLength + kHeaderLength)) { + readLength += kHeaderLength; + MOZ_ASSERT(readLength <= CompressedBufferLength()); + } + + rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { + return rv; + } + + size_t numRead; + size_t numWritten; + rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType, + mCompressedBuffer.get(), mNextChunkDataLength, &numWritten, + &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(numRead == mNextChunkDataLength); + + mUncompressedBytes = numWritten; + + // If we were unable to directly read the next chunk header, then clear + // our internal state. We will have to perform a small read to get the + // header the next time we enter this method. + if (*aBytesReadOut <= mNextChunkDataLength) { + mNextChunkType = Unknown; + mNextChunkDataLength = 0; + return NS_OK; + } + + // We got the next chunk header. Parse it so that we are ready to for the + // next call into this method. + rv = ParseHeader(&mCompressedBuffer[numRead], *aBytesReadOut - numRead, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount, + uint32_t aMinValidCount, + uint32_t* aBytesReadOut) { + MOZ_ASSERT(aCount >= aMinValidCount); + + *aBytesReadOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + uint32_t offset = 0; + while (aCount > 0) { + uint32_t bytesRead = 0; + nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // EOF, but don't immediately return. We need to validate min read bytes + // below. + if (bytesRead == 0) { + break; + } + + *aBytesReadOut += bytesRead; + offset += bytesRead; + aCount -= bytesRead; + } + + // Reading zero bytes is not an error. Its the expected EOF condition. + // Only compare to the minimum valid count if we read at least one byte. + if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + return NS_OK; +} + +size_t SnappyUncompressInputStream::UncompressedLength() const { + MOZ_ASSERT(mNextByte <= mUncompressedBytes); + return mUncompressedBytes - mNextByte; +} + +} // namespace mozilla diff --git a/xpcom/io/SnappyUncompressInputStream.h b/xpcom/io/SnappyUncompressInputStream.h new file mode 100644 index 0000000000..c61ec321a9 --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.h @@ -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/. */ + +#ifndef mozilla_SnappyUncompressInputStream_h__ +#define mozilla_SnappyUncompressInputStream_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsISupportsImpl.h" +#include "SnappyFrameUtils.h" + +namespace mozilla { + +class SnappyUncompressInputStream final : public nsIInputStream, + protected detail::SnappyFrameUtils { + public: + // Construct a new blocking stream to uncompress the given base stream. The + // base stream must also be blocking. The base stream does not have to be + // buffered. + explicit SnappyUncompressInputStream(nsIInputStream* aBaseStream); + + private: + virtual ~SnappyUncompressInputStream(); + + // Parse the next chunk of data. This may populate mBuffer and set + // mBufferFillSize. This should not be called when mBuffer already + // contains data. + nsresult ParseNextChunk(uint32_t* aBytesReadOut); + + // Convenience routine to Read() from the base stream until we get + // the given number of bytes or reach EOF. + // + // aBuf - The buffer to write the bytes into. + // aCount - Max number of bytes to read. If the stream closes + // fewer bytes my be read. + // aMinValidCount - A minimum expected number of bytes. If we find + // fewer than this many bytes, then return + // NS_ERROR_CORRUPTED_CONTENT. If nothing was read due + // due to EOF (aBytesReadOut == 0), then NS_OK is returned. + // aBytesReadOut - An out parameter indicating how many bytes were read. + nsresult ReadAll(char* aBuf, uint32_t aCount, uint32_t aMinValidCount, + uint32_t* aBytesReadOut); + + // Convenience routine to determine how many bytes of uncompressed data + // we currently have in our buffer. + size_t UncompressedLength() const; + + nsCOMPtr<nsIInputStream> mBaseStream; + + // Buffer to hold compressed data. Must copy here since we need a large + // flat buffer to run the uncompress process on. Always the same length + // of SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize) + // bytes long. + mozilla::UniquePtr<char[]> mCompressedBuffer; + + // Buffer storing the resulting uncompressed data. Exactly snappy::kBlockSize + // bytes long. + mozilla::UniquePtr<char[]> mUncompressedBuffer; + + // Number of bytes of uncompressed data in mBuffer. + size_t mUncompressedBytes; + + // Next byte of mBuffer to return in ReadSegments(). Must be less than + // mBufferFillSize + size_t mNextByte; + + // Next chunk in the stream that has been parsed during read-ahead. + ChunkType mNextChunkType; + + // Length of next chunk's length that has been determined during read-ahead. + size_t mNextChunkDataLength; + + // The stream must begin with a StreamIdentifier chunk. Are we still + // expecting it? + bool mNeedFirstStreamIdentifier; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM +}; + +} // namespace mozilla + +#endif // mozilla_SnappyUncompressInputStream_h__ diff --git a/xpcom/io/SpecialSystemDirectory.cpp b/xpcom/io/SpecialSystemDirectory.cpp new file mode 100644 index 0000000000..4b1055f3fe --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.cpp @@ -0,0 +1,730 @@ +/* -*- 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 "SpecialSystemDirectory.h" +#include "mozilla/Try.h" +#include "nsString.h" +#include "nsDependentString.h" +#include "nsIXULAppInfo.h" + +#if defined(XP_WIN) + +# include <windows.h> +# include <stdlib.h> +# include <stdio.h> +# include <string.h> +# include <direct.h> +# include <shlobj.h> +# include <knownfolders.h> +# include <guiddef.h> + +#elif defined(XP_UNIX) + +# include <limits.h> +# include <unistd.h> +# include <stdlib.h> +# include <sys/param.h> +# include "prenv.h" +# if defined(MOZ_WIDGET_COCOA) +# include "CFTypeRefPtr.h" +# include "CocoaFileUtils.h" +# endif +# if defined(MOZ_WIDGET_GTK) +# include "mozilla/WidgetUtilsGtk.h" +# endif + +#endif + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(MAX_PATH) +# define MAXPATHLEN MAX_PATH +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +#if defined(XP_WIN) + +static nsresult GetKnownFolder(GUID* aGuid, nsIFile** aFile) { + if (!aGuid) { + return NS_ERROR_FAILURE; + } + + PWSTR path = nullptr; + SHGetKnownFolderPath(*aGuid, 0, nullptr, &path); + + if (!path) { + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_NewLocalFile(nsDependentString(path), true, aFile); + + CoTaskMemFree(path); + return rv; +} + +static nsresult GetWindowsFolder(int aFolder, nsIFile** aFile) { + WCHAR path_orig[MAX_PATH + 3]; + WCHAR* path = path_orig + 1; + BOOL result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true); + + if (!result) { + return NS_ERROR_FAILURE; + } + + // Append the trailing slash + int len = wcslen(path); + if (len == 0) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + if (len > 1 && path[len - 1] != L'\\') { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +# if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) +/* + * Return the default save-to location for the Windows Library passed in + * through aFolderId. + */ +static nsresult GetLibrarySaveToPath(int aFallbackFolderId, + REFKNOWNFOLDERID aFolderId, + nsIFile** aFile) { + RefPtr<IShellLibrary> shellLib; + RefPtr<IShellItem> savePath; + SHLoadLibraryFromKnownFolder(aFolderId, STGM_READ, IID_IShellLibrary, + getter_AddRefs(shellLib)); + + if (shellLib && SUCCEEDED(shellLib->GetDefaultSaveFolder( + DSFT_DETECT, IID_IShellItem, getter_AddRefs(savePath)))) { + wchar_t* str = nullptr; + if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) { + nsAutoString path; + path.Assign(str); + path.Append('\\'); + nsresult rv = NS_NewLocalFile(path, false, aFile); + CoTaskMemFree(str); + return rv; + } + } + + return GetWindowsFolder(aFallbackFolderId, aFile); +} +# endif + +/** + * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by + * querying the registry when the call to SHGetSpecialFolderPathW is unable to + * provide these paths (Bug 513958). + */ +static nsresult GetRegWindowsAppDataFolder(bool aLocal, nsIFile** aFile) { + HKEY key; + LPCWSTR keyName = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; + DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key); + if (res != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + WCHAR path[MAX_PATH + 2]; + DWORD type, size; + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), nullptr, + &type, (LPBYTE)&path, &size); + ::RegCloseKey(key); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) { + return NS_ERROR_FAILURE; + } + + // Append the trailing slash + int len = wcslen(path); + if (len > 1 && path[len - 1] != L'\\') { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +#endif // XP_WIN + +#if defined(XP_UNIX) +static nsresult GetUnixHomeDir(nsIFile** aFile) { +# if defined(ANDROID) + // XXX no home dir on android; maybe we should return the sdcard if present? + return NS_ERROR_FAILURE; +# else + return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true, + aFile); +# endif +} + +static nsresult GetUnixSystemConfigDir(nsIFile** aFile) { +# if defined(ANDROID) + return NS_ERROR_FAILURE; +# else + nsAutoCString appName; + if (nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1")) { + MOZ_TRY(appInfo->GetName(appName)); + } else { + appName.AssignLiteral(MOZ_APP_BASENAME); + } + + ToLowerCase(appName); + + nsDependentCString sysConfigDir; + if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { + const char* mozSystemConfigDir = PR_GetEnv("MOZ_SYSTEM_CONFIG_DIR"); + if (mozSystemConfigDir) { + sysConfigDir.Assign(nsDependentCString(mozSystemConfigDir)); + } + } +# if defined(MOZ_WIDGET_GTK) + if (sysConfigDir.IsEmpty() && mozilla::widget::IsRunningUnderFlatpak()) { + sysConfigDir.Assign(nsLiteralCString("/app/etc")); + } +# endif + if (sysConfigDir.IsEmpty()) { + sysConfigDir.Assign(nsLiteralCString("/etc")); + } + MOZ_TRY(NS_NewNativeLocalFile(sysConfigDir, true, aFile)); + MOZ_TRY((*aFile)->AppendNative(appName)); + return NS_OK; +# endif +} + +/* + The following license applies to the xdg_user_dir_lookup function: + + Copyright (c) 2007 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +static char* xdg_user_dir_lookup(const char* aType) { + FILE* file; + char* home_dir; + char* config_home; + char* config_file; + char buffer[512]; + char* user_dir; + char* p; + char* d; + int len; + int relative; + + home_dir = getenv("HOME"); + + if (!home_dir) { + goto error; + } + + config_home = getenv("XDG_CONFIG_HOME"); + if (!config_home || config_home[0] == 0) { + config_file = + (char*)malloc(strlen(home_dir) + strlen("/.config/user-dirs.dirs") + 1); + if (!config_file) { + goto error; + } + + strcpy(config_file, home_dir); + strcat(config_file, "/.config/user-dirs.dirs"); + } else { + config_file = + (char*)malloc(strlen(config_home) + strlen("/user-dirs.dirs") + 1); + if (!config_file) { + goto error; + } + + strcpy(config_file, config_home); + strcat(config_file, "/user-dirs.dirs"); + } + + file = fopen(config_file, "r"); + free(config_file); + if (!file) { + goto error; + } + + user_dir = nullptr; + while (fgets(buffer, sizeof(buffer), file)) { + /* Remove newline at end */ + len = strlen(buffer); + if (len > 0 && buffer[len - 1] == '\n') { + buffer[len - 1] = 0; + } + + p = buffer; + while (*p == ' ' || *p == '\t') { + p++; + } + + if (strncmp(p, "XDG_", 4) != 0) { + continue; + } + p += 4; + if (strncmp(p, aType, strlen(aType)) != 0) { + continue; + } + p += strlen(aType); + if (strncmp(p, "_DIR", 4) != 0) { + continue; + } + p += 4; + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p != '=') { + continue; + } + p++; + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p != '"') { + continue; + } + p++; + + relative = 0; + if (strncmp(p, "$HOME/", 6) == 0) { + p += 6; + relative = 1; + } else if (*p != '/') { + continue; + } + + if (relative) { + user_dir = (char*)malloc(strlen(home_dir) + 1 + strlen(p) + 1); + if (!user_dir) { + goto error2; + } + + strcpy(user_dir, home_dir); + strcat(user_dir, "/"); + } else { + user_dir = (char*)malloc(strlen(p) + 1); + if (!user_dir) { + goto error2; + } + + *user_dir = 0; + } + + d = user_dir + strlen(user_dir); + while (*p && *p != '"') { + if ((*p == '\\') && (*(p + 1) != 0)) { + p++; + } + *d++ = *p++; + } + *d = 0; + } +error2: + fclose(file); + + if (user_dir) { + return user_dir; + } + +error: + return nullptr; +} + +static const char xdg_user_dirs[] = + "DESKTOP\0" + "DOCUMENTS\0" + "DOWNLOAD\0" + "MUSIC\0" + "PICTURES\0" + "PUBLICSHARE\0" + "TEMPLATES\0" + "VIDEOS"; + +static const uint8_t xdg_user_dir_offsets[] = {0, 8, 18, 27, 33, 42, 54, 64}; + +static nsresult GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory, + nsIFile** aFile) { + char* dir = xdg_user_dir_lookup( + xdg_user_dirs + + xdg_user_dir_offsets[aSystemDirectory - Unix_XDG_Desktop]); + + nsresult rv; + nsCOMPtr<nsIFile> file; + bool exists; + if (dir) { + rv = NS_NewNativeLocalFile(nsDependentCString(dir), true, + getter_AddRefs(file)); + free(dir); + + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + if (!exists) { + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + return rv; + } + } + } else if (Unix_XDG_Desktop == aSystemDirectory) { + // for the XDG desktop dir, fall back to HOME/Desktop + // (for historical compatibility) + nsCOMPtr<nsIFile> home; + rv = GetUnixHomeDir(getter_AddRefs(home)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = home->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->AppendNative("Desktop"_ns); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + // fallback to HOME only if HOME/Desktop doesn't exist + if (!exists) { + file = home; + } + } else { + // no fallback for the other XDG dirs + return NS_ERROR_FAILURE; + } + + *aFile = nullptr; + file.swap(*aFile); + + return NS_OK; +} +#endif + +nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile) { +#if defined(XP_WIN) + WCHAR path[MAX_PATH]; +#else + char path[MAXPATHLEN]; +#endif + + switch (aSystemSystemDirectory) { + case OS_CurrentWorkingDirectory: +#if defined(XP_WIN) + if (!_wgetcwd(path, MAX_PATH)) { + return NS_ERROR_FAILURE; + } + return NS_NewLocalFile(nsDependentString(path), true, aFile); +#else + if (!getcwd(path, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } +#endif + +#if !defined(XP_WIN) + return NS_NewNativeLocalFile(nsDependentCString(path), true, aFile); +#endif + + case OS_TemporaryDirectory: +#if defined(XP_WIN) + { + DWORD len = ::GetTempPathW(MAX_PATH, path); + if (len == 0) { + break; + } + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); + } +#elif defined(MOZ_WIDGET_COCOA) + { + return GetOSXFolderType(kUserDomain, kTemporaryFolderType, aFile); + } + +#elif defined(XP_UNIX) + { + static const char* tPath = nullptr; + if (!tPath) { + tPath = PR_GetEnv("TMPDIR"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TMP"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TEMP"); + if (!tPath || !*tPath) { + tPath = "/tmp/"; + } + } + } + } + return NS_NewNativeLocalFile(nsDependentCString(tPath), true, aFile); + } +#else + break; +#endif +#if defined(MOZ_WIDGET_COCOA) + case Mac_SystemDirectory: { + return GetOSXFolderType(kClassicDomain, kSystemFolderType, aFile); + } + case Mac_UserLibDirectory: { + return GetOSXFolderType(kUserDomain, kDomainLibraryFolderType, aFile); + } + case Mac_HomeDirectory: { + return GetOSXFolderType(kUserDomain, kDomainTopLevelFolderType, aFile); + } + case Mac_DefaultDownloadDirectory: { + nsresult rv = GetOSXFolderType(kUserDomain, kDownloadsFolderType, aFile); + if (NS_FAILED(rv)) { + return GetOSXFolderType(kUserDomain, kDesktopFolderType, aFile); + } + return NS_OK; + } + case Mac_UserDesktopDirectory: { + return GetOSXFolderType(kUserDomain, kDesktopFolderType, aFile); + } + case Mac_LocalApplicationsDirectory: { + return GetOSXFolderType(kLocalDomain, kApplicationsFolderType, aFile); + } + case Mac_UserPreferencesDirectory: { + return GetOSXFolderType(kUserDomain, kPreferencesFolderType, aFile); + } + case Mac_PictureDocumentsDirectory: { + return GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, aFile); + } + case Mac_DefaultScreenshotDirectory: { + auto prefValue = CFTypeRefPtr<CFPropertyListRef>::WrapUnderCreateRule( + CFPreferencesCopyAppValue(CFSTR("location"), + CFSTR("com.apple.screencapture"))); + + if (!prefValue || CFGetTypeID(prefValue.get()) != CFStringGetTypeID()) { + return GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, + aFile); + } + + nsAutoString path; + mozilla::Span<char16_t> data = + path.GetMutableData(CFStringGetLength((CFStringRef)prefValue.get())); + CFStringGetCharacters((CFStringRef)prefValue.get(), + CFRangeMake(0, data.Length()), + reinterpret_cast<UniChar*>(data.Elements())); + + return NS_NewLocalFile(path, true, aFile); + } +#elif defined(XP_WIN) + case Win_SystemDirectory: { + int32_t len = ::GetSystemDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) { + break; + } + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); + } + + case Win_WindowsDirectory: { + int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); + } + + case Win_ProgramFiles: { + return GetWindowsFolder(CSIDL_PROGRAM_FILES, aFile); + } + + case Win_HomeDirectory: { + nsresult rv = GetWindowsFolder(CSIDL_PROFILE, aFile); + if (NS_SUCCEEDED(rv)) { + return rv; + } + + int32_t len; + if ((len = ::GetEnvironmentVariableW(L"HOME", path, MAX_PATH)) > 0) { + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + rv = NS_NewLocalFile(nsDependentString(path, len), true, aFile); + if (NS_SUCCEEDED(rv)) { + return rv; + } + } + + len = ::GetEnvironmentVariableW(L"HOMEDRIVE", path, MAX_PATH); + if (0 < len && len < MAX_PATH) { + WCHAR temp[MAX_PATH]; + DWORD len2 = ::GetEnvironmentVariableW(L"HOMEPATH", temp, MAX_PATH); + if (0 < len2 && len + len2 < MAX_PATH) { + wcsncat(path, temp, len2); + } + + len = wcslen(path); + + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); + } + break; + } + case Win_Programs: { + return GetWindowsFolder(CSIDL_PROGRAMS, aFile); + } + + case Win_Downloads: { + // Defined in KnownFolders.h. + GUID folderid_downloads = { + 0x374de290, + 0x123f, + 0x4565, + {0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}}; + nsresult rv = GetKnownFolder(&folderid_downloads, aFile); + // On WinXP, there is no downloads folder, default + // to 'Desktop'. + if (NS_ERROR_FAILURE == rv) { + rv = GetWindowsFolder(CSIDL_DESKTOP, aFile); + } + return rv; + } + + case Win_Favorites: { + return GetWindowsFolder(CSIDL_FAVORITES, aFile); + } + case Win_Desktopdirectory: { + return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, aFile); + } + case Win_Cookies: { + return GetWindowsFolder(CSIDL_COOKIES, aFile); + } + case Win_Appdata: { + nsresult rv = GetWindowsFolder(CSIDL_APPDATA, aFile); + if (NS_FAILED(rv)) { + rv = GetRegWindowsAppDataFolder(false, aFile); + } + return rv; + } + case Win_LocalAppdata: { + nsresult rv = GetWindowsFolder(CSIDL_LOCAL_APPDATA, aFile); + if (NS_FAILED(rv)) { + rv = GetRegWindowsAppDataFolder(true, aFile); + } + return rv; + } +# if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + case Win_Documents: { + return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS, FOLDERID_DocumentsLibrary, + aFile); + } +# endif +#endif // XP_WIN + +#if defined(XP_UNIX) + case Unix_HomeDirectory: + return GetUnixHomeDir(aFile); + + case Unix_XDG_Desktop: + case Unix_XDG_Download: + return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile); + + case Unix_SystemConfigDirectory: + return GetUnixSystemConfigDir(aFile); +#endif + + default: + break; + } + return NS_ERROR_NOT_AVAILABLE; +} + +#if defined(MOZ_WIDGET_COCOA) +nsresult GetOSXFolderType(short aDomain, OSType aFolderType, + nsIFile** aLocalFile) { + nsresult rv = NS_ERROR_FAILURE; + + if (aFolderType == kTemporaryFolderType) { + NS_NewLocalFile(u""_ns, true, aLocalFile); + nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile)); + if (localMacFile) { + rv = localMacFile->InitWithCFURL( + CocoaFileUtils::GetTemporaryFolder().get()); + } + return rv; + } + + OSErr err; + FSRef fsRef; + err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef); + if (err == noErr) { + NS_NewLocalFile(u""_ns, true, aLocalFile); + nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile)); + if (localMacFile) { + rv = localMacFile->InitWithFSRef(&fsRef); + } + } + return rv; +} +#endif diff --git a/xpcom/io/SpecialSystemDirectory.h b/xpcom/io/SpecialSystemDirectory.h new file mode 100644 index 0000000000..e760b0ae26 --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.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 _SPECIALSYSTEMDIRECTORY_H_ +#define _SPECIALSYSTEMDIRECTORY_H_ + +#include "nscore.h" +#include "nsIFile.h" + +#ifdef MOZ_WIDGET_COCOA +# include "nsILocalFileMac.h" +# include "prenv.h" +#endif + +enum SystemDirectories { + OS_TemporaryDirectory = 2, + // 3 Used to be OS_CurrentProcessDirectory, which we never actually + // supported getting... + OS_CurrentWorkingDirectory = 4, + + Mac_SystemDirectory = 101, + Mac_UserLibDirectory = 102, + Mac_HomeDirectory = 103, + Mac_DefaultDownloadDirectory = 104, + Mac_UserDesktopDirectory = 105, + Mac_LocalApplicationsDirectory = 106, + Mac_UserPreferencesDirectory = 107, + Mac_PictureDocumentsDirectory = 108, + Mac_DefaultScreenshotDirectory = 109, + + Win_SystemDirectory = 201, + Win_WindowsDirectory = 202, + Win_HomeDirectory = 203, + Win_Programs = 205, + Win_Favorites = 209, + Win_Desktopdirectory = 213, + Win_Appdata = 221, + Win_Cookies = 223, + Win_LocalAppdata = 224, + Win_ProgramFiles = 225, + Win_Downloads = 226, +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + Win_Documents = 228, +#endif + + Unix_HomeDirectory = 303, + Unix_XDG_Desktop = 304, + Unix_XDG_Download = 306, + Unix_SystemConfigDirectory = 307 +}; + +nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile); +#ifdef MOZ_WIDGET_COCOA +nsresult GetOSXFolderType(short aDomain, OSType aFolderType, + nsIFile** aLocalFile); +#endif + +#endif diff --git a/xpcom/io/StreamBufferSink.h b/xpcom/io/StreamBufferSink.h new file mode 100644 index 0000000000..bf00527aea --- /dev/null +++ b/xpcom/io/StreamBufferSink.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_StreamBufferSink_h +#define mozilla_StreamBufferSink_h + +#include <cstddef> +#include "mozilla/Span.h" +#include "nsString.h" + +namespace mozilla { + +class StreamBufferSink { + public: + virtual mozilla::Span<char> Data() = 0; + + nsDependentCSubstring Slice(size_t aOffset) { + return nsDependentCSubstring(Data().First(aOffset)); + } + + virtual ~StreamBufferSink() = default; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSink_h diff --git a/xpcom/io/StreamBufferSinkImpl.h b/xpcom/io/StreamBufferSinkImpl.h new file mode 100644 index 0000000000..9fba413ef2 --- /dev/null +++ b/xpcom/io/StreamBufferSinkImpl.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 mozilla_StreamBufferSinkImpl_h +#define mozilla_StreamBufferSinkImpl_h + +#include "mozilla/Buffer.h" +#include "mozilla/StreamBufferSink.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class BufferSink final : public StreamBufferSink { + public: + explicit BufferSink(Buffer<char>&& aBuffer) : mBuffer(std::move(aBuffer)) {} + + explicit BufferSink(size_t aLength) : mBuffer(aLength) {} + + static UniquePtr<BufferSink> Alloc(size_t aLength) { + auto maybeBuffer = Buffer<char>::Alloc(aLength); + if (!maybeBuffer) { + return nullptr; + } + + return MakeUnique<BufferSink>(maybeBuffer.extract()); + } + + mozilla::Span<char> Data() override { return mBuffer.AsWritableSpan(); } + + private: + Buffer<char> mBuffer; +}; + +class nsBorrowedSink final : public StreamBufferSink { + public: + explicit nsBorrowedSink(mozilla::Span<char> aBuffer) : mBuffer(aBuffer) {} + + mozilla::Span<char> Data() override { return mBuffer; } + + private: + mozilla::Span<char> mBuffer; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSinkImpl_h diff --git a/xpcom/io/StreamBufferSource.h b/xpcom/io/StreamBufferSource.h new file mode 100644 index 0000000000..f0a3d30eea --- /dev/null +++ b/xpcom/io/StreamBufferSource.h @@ -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/. */ + +#ifndef mozilla_StreamBufferSource_h +#define mozilla_StreamBufferSource_h + +#include <cstddef> +#include "ErrorList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Span.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +namespace mozilla { + +class StreamBufferSource { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StreamBufferSource) + + virtual Span<const char> Data() = 0; + + virtual nsresult GetData(nsACString& aString) { + Span<const char> data = Data(); + if (!aString.Assign(data.Elements(), data.Length(), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + + virtual bool Owning() = 0; + + virtual size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) { + return SizeOfExcludingThisEvenIfShared(aMallocSizeOf); + } + size_t SizeOfIncludingThisIfUnshared(MallocSizeOf aMallocSizeOf) { + if (mRefCnt > 1) { + return 0; + } + size_t n = aMallocSizeOf(this); + n += SizeOfExcludingThisIfUnshared(aMallocSizeOf); + return n; + } + + virtual size_t SizeOfExcludingThisEvenIfShared( + MallocSizeOf aMallocSizeOf) = 0; + size_t SizeOfIncludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + n += SizeOfExcludingThisEvenIfShared(aMallocSizeOf); + return n; + } + + protected: + virtual ~StreamBufferSource() = default; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSource_h diff --git a/xpcom/io/StreamBufferSourceImpl.h b/xpcom/io/StreamBufferSourceImpl.h new file mode 100644 index 0000000000..0d04adcc24 --- /dev/null +++ b/xpcom/io/StreamBufferSourceImpl.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_StreamBufferSourceImpl_h +#define mozilla_StreamBufferSourceImpl_h + +#include "mozilla/StreamBufferSource.h" + +#include "nsTArray.h" + +namespace mozilla { + +class nsTArraySource final : public StreamBufferSource { + public: + explicit nsTArraySource(nsTArray<uint8_t>&& aArray) + : mArray(std::move(aArray)) {} + + Span<const char> Data() override { + return Span{reinterpret_cast<const char*>(mArray.Elements()), + mArray.Length()}; + } + + bool Owning() override { return true; } + + size_t SizeOfExcludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) override { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + const nsTArray<uint8_t> mArray; +}; + +class nsCStringSource final : public StreamBufferSource { + public: + explicit nsCStringSource(nsACString&& aString) + : mString(std::move(aString)) {} + + Span<const char> Data() override { return mString; } + + nsresult GetData(nsACString& aString) override { + if (!aString.Assign(mString, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + + bool Owning() override { return true; } + + size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) override { + return mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + size_t SizeOfExcludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) override { + return mString.SizeOfExcludingThisEvenIfShared(aMallocSizeOf); + } + + private: + const nsCString mString; +}; + +class nsBorrowedSource final : public StreamBufferSource { + public: + explicit nsBorrowedSource(Span<const char> aBuffer) : mBuffer(aBuffer) {} + + Span<const char> Data() override { return mBuffer; } + + bool Owning() override { return false; } + + size_t SizeOfExcludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) override { + return 0; + } + + private: + const Span<const char> mBuffer; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSourceImpl_h diff --git a/xpcom/io/components.conf b/xpcom/io/components.conf new file mode 100644 index 0000000000..0c4f44b712 --- /dev/null +++ b/xpcom/io/components.conf @@ -0,0 +1,42 @@ +# -*- 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 = [ + { + 'name': 'Directory', + 'js_name': 'dirsvc', + 'cid': '{f00152d0-b40b-11d3-8c9c-000064657374}', + 'contract_ids': ['@mozilla.org/file/directory_service;1'], + 'interfaces': ['nsIDirectoryService', 'nsIProperties'], + 'legacy_constructor': 'nsDirectoryService::Create', + 'headers': ['nsDirectoryService.h'], + }, + { + 'cid': '{565e3a2c-1dd2-11b2-8da1-b4cef17e568d}', + 'contract_ids': ['@mozilla.org/io/multiplex-input-stream;1'], + 'legacy_constructor': 'nsMultiplexInputStreamConstructor', + 'headers': ['nsMultiplexInputStream.h'], + }, + { + 'cid': '{e4a0ee4e-0775-457b-9118-b3ae97a7c758}', + 'contract_ids': ['@mozilla.org/pipe;1'], + 'legacy_constructor': 'nsPipeConstructor', + 'headers': ['/xpcom/io/nsPipe.h'], + }, + { + 'cid': '{7225c040-a9bf-11d3-a197-0050041caf44}', + 'contract_ids': ['@mozilla.org/scriptableinputstream;1'], + 'legacy_constructor': 'nsScriptableInputStream::Create', + 'headers': ['nsScriptableInputStream.h'], + }, + { + 'cid': '{0abb0835-5000-4790-af28-61b3ba17c295}', + 'contract_ids': ['@mozilla.org/io/string-input-stream;1'], + 'legacy_constructor': 'nsStringInputStreamConstructor', + 'headers': ['/xpcom/build/XPCOMModule.h'], + 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS, + }, +] diff --git a/xpcom/io/crc32c.c b/xpcom/io/crc32c.c new file mode 100644 index 0000000000..3494945c0f --- /dev/null +++ b/xpcom/io/crc32c.c @@ -0,0 +1,154 @@ +/* + * Based on file found here: + * + * https://svnweb.freebsd.org/base/stable/10/sys/libkern/crc32.c?revision=256281 + */ + +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +/* + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1 + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to hight-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera + * tions for all combinations of data and CRC register values + * + * The values must be right-shifted by eight bits by the "updcrc + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions + * polynomial $edb88320 + * + * + * CRC32 code derived from work by Gary S. Brown. + */ + +#include "crc32c.h" + +/* CRC32C routines, these use a different polynomial */ +/*****************************************************************/ +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +/*****************************************************************/ + +static const uint32_t crc32Table[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L +}; + +// NOTE: See source URL at top of this file for multitable implementation which +// offers a performance boost at the cost of ~8KB of static tables. + +uint32_t +ComputeCrc32c(uint32_t crc, const void *buf, size_t size) +{ + const uint8_t *p = buf; + + + while (size--) + crc = crc32Table[(crc ^ *p++) & 0xff] ^ (crc >> 8); + + return crc; +} diff --git a/xpcom/io/crc32c.h b/xpcom/io/crc32c.h new file mode 100644 index 0000000000..2830c28659 --- /dev/null +++ b/xpcom/io/crc32c.h @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 crc32c_h +#define crc32c_h + +#include <stdint.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Compute a CRC32c as defined in RFC3720. This is a different polynomial than +// what is used in the crc for zlib, etc. Typical usage to calculate a new CRC: +// +// ComputeCrc32c(~0, buffer, bufferLength); +// +uint32_t ComputeCrc32c(uint32_t aCrc, const void* aBuf, size_t aSize); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // crc32c_h diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build new file mode 100644 index 0000000000..91dca0cdd1 --- /dev/null +++ b/xpcom/io/moz.build @@ -0,0 +1,162 @@ +# -*- 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 += [ + "nsIAsyncInputStream.idl", + "nsIAsyncOutputStream.idl", + "nsIBinaryInputStream.idl", + "nsIBinaryOutputStream.idl", + "nsICloneableInputStream.idl", + "nsIConverterInputStream.idl", + "nsIConverterOutputStream.idl", + "nsIDirectoryEnumerator.idl", + "nsIDirectoryService.idl", + "nsIFile.idl", + "nsIInputStream.idl", + "nsIInputStreamLength.idl", + "nsIInputStreamPriority.idl", + "nsIInputStreamTee.idl", + "nsIIOUtil.idl", + "nsILineInputStream.idl", + "nsILocalFileWin.idl", + "nsIMultiplexInputStream.idl", + "nsIObjectInputStream.idl", + "nsIObjectOutputStream.idl", + "nsIOutputStream.idl", + "nsIPipe.idl", + "nsIRandomAccessStream.idl", + "nsISafeOutputStream.idl", + "nsIScriptableBase64Encoder.idl", + "nsIScriptableInputStream.idl", + "nsISeekableStream.idl", + "nsIStorageStream.idl", + "nsIStreamBufferAccess.idl", + "nsIStringStream.idl", + "nsITellableStream.idl", + "nsIUnicharInputStream.idl", + "nsIUnicharLineInputStream.idl", + "nsIUnicharOutputStream.idl", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + XPIDL_SOURCES += [ + "nsILocalFileMac.idl", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + EXPORTS += ["nsLocalFileWin.h"] + EXPORTS.mozilla += [ + "FileUtilsWin.h", + ] + UNIFIED_SOURCES += [ + "FileUtilsWin.cpp", + "nsLocalFileWin.cpp", + ] +else: + EXPORTS += ["nsLocalFileUnix.h"] + UNIFIED_SOURCES += [ + "nsLocalFileUnix.cpp", + ] + +XPIDL_MODULE = "xpcom_io" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "FileDescriptorFile.h", + "nsAnonymousTemporaryFile.h", + "nsAppDirectoryServiceDefs.h", + "nsDirectoryService.h", + "nsDirectoryServiceDefs.h", + "nsDirectoryServiceUtils.h", + "nsEscape.h", + "nsLinebreakConverter.h", + "nsLocalFile.h", + "nsLocalFileCommon.h", + "nsMultiplexInputStream.h", + "nsNativeCharsetUtils.h", + "nsScriptableInputStream.h", + "nsStorageStream.h", + "nsStreamUtils.h", + "nsStringStream.h", + "nsUnicharInputStream.h", + "nsWildCard.h", + "SpecialSystemDirectory.h", +] + +EXPORTS.mozilla += [ + "Base64.h", + "FilePreferences.h", + "FixedBufferOutputStream.h", + "InputStreamLengthHelper.h", + "InputStreamLengthWrapper.h", + "NonBlockingAsyncInputStream.h", + "SlicedInputStream.h", + "SnappyCompressOutputStream.h", + "SnappyFrameUtils.h", + "SnappyUncompressInputStream.h", + "StreamBufferSink.h", + "StreamBufferSinkImpl.h", + "StreamBufferSource.h", + "StreamBufferSourceImpl.h", +] + +UNIFIED_SOURCES += [ + "Base64.cpp", + "crc32c.c", + "FileDescriptorFile.cpp", + "FilePreferences.cpp", + "FixedBufferOutputStream.cpp", + "InputStreamLengthHelper.cpp", + "InputStreamLengthWrapper.cpp", + "NonBlockingAsyncInputStream.cpp", + "nsAnonymousTemporaryFile.cpp", + "nsAppFileLocationProvider.cpp", + "nsBinaryStream.cpp", + "nsDirectoryService.cpp", + "nsEscape.cpp", + "nsInputStreamTee.cpp", + "nsIOUtil.cpp", + "nsLinebreakConverter.cpp", + "nsLocalFileCommon.cpp", + "nsMultiplexInputStream.cpp", + "nsNativeCharsetUtils.cpp", + "nsPipe3.cpp", + "nsScriptableBase64Encoder.cpp", + "nsScriptableInputStream.cpp", + "nsSegmentedBuffer.cpp", + "nsStorageStream.cpp", + "nsStreamUtils.cpp", + "nsStringStream.cpp", + "nsUnicharInputStream.cpp", + "nsWildCard.cpp", + "SlicedInputStream.cpp", + "SnappyCompressOutputStream.cpp", + "SnappyFrameUtils.cpp", + "SnappyUncompressInputStream.cpp", + "SpecialSystemDirectory.cpp", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "CocoaFileUtils.mm", + ] + +DEFINES["MOZ_APP_BASENAME"] = '"%s"' % CONFIG["MOZ_APP_BASENAME"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +if CONFIG["OS_ARCH"] == "Linux" and "lib64" in CONFIG["libdir"]: + DEFINES["HAVE_USR_LIB64_DIR"] = True + +LOCAL_INCLUDES += [ + "!..", + "../build", +] diff --git a/xpcom/io/nsAnonymousTemporaryFile.cpp b/xpcom/io/nsAnonymousTemporaryFile.cpp new file mode 100644 index 0000000000..1c9dce57d5 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.cpp @@ -0,0 +1,264 @@ +/* -*- 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 "nsAnonymousTemporaryFile.h" +#include "nsXULAppAPI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "prio.h" +#include "SpecialSystemDirectory.h" + +#ifdef XP_WIN +# include "nsIObserver.h" +# include "nsIObserverService.h" +# include "mozilla/ResultExtensions.h" +# include "mozilla/Services.h" +# include "nsIUserIdleService.h" +# include "nsISimpleEnumerator.h" +# include "nsIFile.h" +# include "nsITimer.h" +# include "nsCRT.h" + +#endif + +using namespace mozilla; + +// We store the temp files in the system temp dir. +// +// On Windows systems in particular we use a sub-directory of the temp +// directory, because: +// 1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power +// cycle (and perhaps if we crash) the files are not deleted. We store +// the temporary files in a known sub-dir so that we can find and delete +// them easily and quickly. +// 2. On Windows NT the system temp dir is in the user's $HomeDir/AppData, +// so we can be sure the user always has write privileges to that +// directory; if the sub-dir for our temp files was in some shared location +// and was created by a privileged user, it's possible that other users +// wouldn't have write access to that sub-dir. (Non-Windows systems +// don't store their temp files in a sub-dir, so this isn't an issue on +// those platforms). +// 3. Content processes can access the system temp dir +// (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR +// for content process for example, which is where we previously stored +// temp files on Windows). This argument applies to all platforms, not +// just Windows. +static nsresult GetTempDir(nsIFile** aTempDir) { + if (NS_WARN_IF(!aTempDir)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = + GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef XP_WIN + // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files + // in a subdir of the temp dir and delete that in an idle service observer + // to ensure it's been cleared. + rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + tmpFile.forget(aTempDir); + + return NS_OK; +} + +nsresult NS_OpenAnonymousTemporaryNsIFile(nsIFile** aFile) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + nsCOMPtr<nsIFile> tmpFile; + rv = GetTempDir(getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Give the temp file a name with a random element. CreateUnique will also + // append a counter to the name if it encounters a name collision. Adding + // a random element to the name reduces the likelihood of a name collision, + // so that CreateUnique() doesn't end up trying a lot of name variants in + // its "try appending an incrementing counter" loop, as file IO can be + // expensive on some mobile flash drives. + nsAutoCString name("mozilla-temp-"); + name.AppendInt(rand()); + + rv = tmpFile->AppendNative(name); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + tmpFile.forget(aFile); + return NS_OK; +} + +nsresult NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc) { + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE, PR_IRWXU, + aOutFileDesc); + + return rv; +} + +#ifdef XP_WIN + +// On Windows we have an idle service observer that runs some time after +// startup and deletes any stray anonymous temporary files... + +// Duration of idle time before we'll get a callback whereupon we attempt to +// remove any stray and unused anonymous temp files. +# define TEMP_FILE_IDLE_TIME_S 30 + +// The nsAnonTempFileRemover is created in a timer, which sets an idle observer. +// This is expiration time (in ms) which initial timer is set for (3 minutes). +# define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000 + +# define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown" + +// This class adds itself as an idle observer. When the application has +// been idle for about 30 seconds we'll get a notification, whereupon we'll +// attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all +// temp files that were supposed to be deleted on application exit were actually +// deleted, as they may not be if we previously crashed. See bugs 572579 and +// 785662. This is only needed on some versions of Windows, +// nsIFile::DELETE_ON_CLOSE works on other platforms. +// This class adds itself as a shutdown observer so that it can cancel the +// idle observer and its timer on shutdown. Note: the observer and idle +// services hold references to instances of this object, and those references +// are what keep this object alive. +class nsAnonTempFileRemover final : public nsIObserver, public nsINamed { + public: + NS_DECL_ISUPPORTS + + nsAnonTempFileRemover() {} + + nsresult Init() { + // We add the idle observer in a timer, so that the app has enough + // time to start up before we add the idle observer. If we register the + // idle observer too early, it will be registered before the fake idle + // service is installed when running in xpcshell, and this interferes with + // the fake idle service, causing xpcshell-test failures. + MOZ_TRY_VAR(mTimer, NS_NewTimerWithObserver(this, SCHEDULE_TIMEOUT_MS, + nsITimer::TYPE_ONE_SHOT)); + + // Register shutdown observer so we can cancel the timer if we shutdown + // before the timer runs. + nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService(); + if (NS_WARN_IF(!obsSrv)) { + return NS_ERROR_FAILURE; + } + return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false); + } + + void Cleanup() { + // Cancel timer. + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + // Remove idle service observer. + nsCOMPtr<nsIUserIdleService> idleSvc = + do_GetService("@mozilla.org/widget/useridleservice;1"); + if (idleSvc) { + idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + // Remove shutdown observer. + nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService(); + if (obsSrv) { + obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC); + } + } + + NS_IMETHODIMP Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 && + NS_FAILED(RegisterIdleObserver())) { + Cleanup(); + } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) { + // The user has been idle for a while, clean up the temp files. + // The idle service will drop its reference to this object after + // we exit, destroying this object. + RemoveAnonTempFileFiles(); + Cleanup(); + } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) { + Cleanup(); + } + return NS_OK; + } + + NS_IMETHODIMP GetName(nsACString& aName) { + aName.AssignLiteral("nsAnonTempFileRemover"); + return NS_OK; + } + + nsresult RegisterIdleObserver() { + // Add this as an idle observer. When we've been idle for + // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then + // try to delete any stray temp files. + nsCOMPtr<nsIUserIdleService> idleSvc = + do_GetService("@mozilla.org/widget/useridleservice;1"); + if (!idleSvc) { + return NS_ERROR_FAILURE; + } + return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + + void RemoveAnonTempFileFiles() { + nsCOMPtr<nsIFile> tmpDir; + nsresult rv = GetTempDir(getter_AddRefs(tmpDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Remove the directory recursively. + tmpDir->Remove(true); + } + + private: + ~nsAnonTempFileRemover() {} + + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS(nsAnonTempFileRemover, nsIObserver, nsINamed) + +nsresult CreateAnonTempFileRemover() { + // Create a temp file remover. If Init() succeeds, the temp file remover is + // kept alive by a reference held by the observer service, since the temp file + // remover is a shutdown observer. We only create the temp file remover if + // we're running in the main process; there's no point in doing the temp file + // removal multiple times per startup. + if (!XRE_IsParentProcess()) { + return NS_OK; + } + RefPtr<nsAnonTempFileRemover> tempRemover = new nsAnonTempFileRemover(); + return tempRemover->Init(); +} + +#endif diff --git a/xpcom/io/nsAnonymousTemporaryFile.h b/xpcom/io/nsAnonymousTemporaryFile.h new file mode 100644 index 0000000000..8e900dda4c --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.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/. */ + +#pragma once + +#include "prio.h" +#include "nscore.h" +#include "nsIFile.h" + +/** + * OpenAnonymousTemporaryFile + * + * Creates and opens a temporary file which has a random name. Callers have no + * control over the file name, and the file is opened in a temporary location + * which is appropriate for the platform. + * + * Upon success, aOutFileDesc contains an opened handle to the temporary file. + * The caller is responsible for closing the file when they're finished with it. + * + * The file will be deleted when the file handle is closed. On non-Windows + * platforms the file will be unlinked before this function returns. On Windows + * the OS supplied delete-on-close mechanism is unreliable if the application + * crashes or the computer power cycles unexpectedly, so unopened temporary + * files are purged at some time after application startup. + * + */ +nsresult NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc); + +/** + * OpenAnonymousTemporaryNsIFile + * + * Similar to the previous function, it returns a nsIFile. Note that the nsIFile + * will not be deleted automagically. The callee has to call aFile->Remove() in + * order to remove the temporary file. + */ +nsresult NS_OpenAnonymousTemporaryNsIFile(nsIFile** aFile); diff --git a/xpcom/io/nsAppDirectoryServiceDefs.h b/xpcom/io/nsAppDirectoryServiceDefs.h new file mode 100644 index 0000000000..521dbe0d50 --- /dev/null +++ b/xpcom/io/nsAppDirectoryServiceDefs.h @@ -0,0 +1,102 @@ +/* -*- 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 nsAppDirectoryServiceDefs_h___ +#define nsAppDirectoryServiceDefs_h___ + +//======================================================================================== +// +// Defines property names for directories available from standard +// nsIDirectoryServiceProviders. These keys are not guaranteed to exist because +// the nsIDirectoryServiceProviders which provide them are optional. +// +// Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or +// subclass). Keys whose definition ends in "LIST" return an nsISimpleEnumerator +// which enumerates a list of file objects. +// +// System and XPCOM level properties are defined in nsDirectoryServiceDefs.h. +// +//======================================================================================== + +// -------------------------------------------------------------------------------------- +// Files and directories which exist on a per-product basis +// -------------------------------------------------------------------------------------- + +#define NS_APP_APPLICATION_REGISTRY_FILE "AppRegF" +#define NS_APP_APPLICATION_REGISTRY_DIR "AppRegD" + +#define NS_APP_DEFAULTS_50_DIR "DefRt" // The root dir of all defaults dirs +#define NS_APP_PREF_DEFAULTS_50_DIR "PrfDef" + +#define NS_APP_USER_PROFILES_ROOT_DIR \ + "DefProfRt" // The dir where user profile dirs live. +#define NS_APP_USER_PROFILES_LOCAL_ROOT_DIR \ + "DefProfLRt" // The dir where user profile temp dirs live. + +#define NS_APP_RES_DIR "ARes" +#define NS_APP_CHROME_DIR "AChrom" + +#define NS_APP_CHROME_DIR_LIST "AChromDL" + +// -------------------------------------------------------------------------------------- +// Files and directories which exist on a per-profile basis +// These locations are typically provided by the profile mgr +// -------------------------------------------------------------------------------------- + +// In a shared profile environment, prefixing a profile-relative +// key with NS_SHARED returns a location that is shared by +// other users of the profile. Without this prefix, the consumer +// has exclusive access to this location. + +#define NS_SHARED "SHARED" + +#define NS_APP_PREFS_50_DIR "PrefD" // Directory which contains user prefs +#define NS_APP_PREFS_50_FILE "PrefF" +#define NS_APP_PREFS_DEFAULTS_DIR_LIST "PrefDL" +#define NS_APP_PREFS_OVERRIDE_DIR \ + "PrefDOverride" // Directory for per-profile defaults + +#define NS_APP_USER_PROFILE_50_DIR "ProfD" +#define NS_APP_USER_PROFILE_LOCAL_50_DIR "ProfLD" + +#define NS_APP_USER_CHROME_DIR "UChrm" + +#define NS_APP_USER_PANELS_50_FILE "UPnls" +#define NS_APP_CACHE_PARENT_DIR "cachePDir" + +#define NS_APP_INSTALL_CLEANUP_DIR \ + "XPIClnupD" // location of xpicleanup.dat xpicleanup.exe + +#define NS_APP_INDEXEDDB_PARENT_DIR "indexedDBPDir" + +#define NS_APP_PERMISSION_PARENT_DIR "permissionDBPDir" + +#if defined(MOZ_CONTENT_TEMP_DIR) +// +// NS_APP_CONTENT_PROCESS_TEMP_DIR refers to a directory that is read and +// write accessible from a sandboxed content process. The key may be used in +// either process, but the directory is intended to be used for short-lived +// files that need to be saved to the filesystem by the content process and +// don't need to survive browser restarts. The directory is reset on startup. +// +// When MOZ_CONTENT_TEMP_DIR is defined and sandboxing is enabled (versus +// manually disabled via prefs), the content process replaces NS_OS_TEMP_DIR +// with NS_APP_CONTENT_PROCESS_TEMP_DIR so that legacy code in content +// attempting to write to NS_OS_TEMP_DIR will write to +// NS_APP_CONTENT_PROCESS_TEMP_DIR instead. When MOZ_SANDBOX is +// defined but sandboxing is disabled, NS_APP_CONTENT_PROCESS_TEMP_DIR +// falls back to NS_OS_TEMP_DIR in both content and chrome processes. +// +// New code should avoid writing to the filesystem from the content process +// and should instead proxy through the parent process whenever possible. +// +// At present, all sandboxed content processes use the same directory for +// NS_APP_CONTENT_PROCESS_TEMP_DIR, but that should not be relied upon. +// +# define NS_APP_CONTENT_PROCESS_TEMP_DIR "ContentTmpD" +#endif // defined(MOZ_SANDBOX) + +#endif // nsAppDirectoryServiceDefs_h___ diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp new file mode 100644 index 0000000000..189f1efe13 --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.cpp @@ -0,0 +1,333 @@ +/* -*- 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 "nsAppFileLocationProvider.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsEnumeratorUtils.h" +#include "nsAtom.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsSimpleEnumerator.h" +#include "prenv.h" +#include "nsCRT.h" +#if defined(MOZ_WIDGET_COCOA) +# include <Carbon/Carbon.h> +# include "CocoaFileUtils.h" +# include "nsILocalFileMac.h" +#elif defined(XP_WIN) +# include <windows.h> +# include <shlobj.h> +#elif defined(XP_UNIX) +# include <unistd.h> +# include <stdlib.h> +# include <sys/param.h> +#endif + +// WARNING: These hard coded names need to go away. They need to +// come from localizable resources + +#if defined(MOZ_WIDGET_COCOA) +# define APP_REGISTRY_NAME "Application Registry"_ns +# define ESSENTIAL_FILES "Essential Files"_ns +#elif defined(XP_WIN) +# define APP_REGISTRY_NAME "registry.dat"_ns +#else +# define APP_REGISTRY_NAME "appreg"_ns +#endif + +// define default product directory +#define DEFAULT_PRODUCT_DIR nsLiteralCString(MOZ_USER_DIR) + +#define DEFAULTS_DIR_NAME "defaults"_ns +#define DEFAULTS_PREF_DIR_NAME "pref"_ns +#define RES_DIR_NAME "res"_ns +#define CHROME_DIR_NAME "chrome"_ns + +//***************************************************************************** +// nsAppFileLocationProvider::Constructor/Destructor +//***************************************************************************** + +nsAppFileLocationProvider::nsAppFileLocationProvider() = default; + +//***************************************************************************** +// nsAppFileLocationProvider::nsISupports +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsAppFileLocationProvider, nsIDirectoryServiceProvider) + +//***************************************************************************** +// nsAppFileLocationProvider::nsIDirectoryServiceProvider +//***************************************************************************** + +NS_IMETHODIMP +nsAppFileLocationProvider::GetFile(const char* aProp, bool* aPersistent, + nsIFile** aResult) { + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aResult = nullptr; + *aPersistent = true; + + if (nsCRT::strcmp(aProp, NS_APP_APPLICATION_REGISTRY_DIR) == 0) { + rv = GetProductDirectory(getter_AddRefs(localFile)); + } else if (nsCRT::strcmp(aProp, NS_APP_APPLICATION_REGISTRY_FILE) == 0) { + rv = GetProductDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendNative(APP_REGISTRY_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_DEFAULTS_50_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_PREF_DEFAULTS_50_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_PREF_DIR_NAME); + } + } + } else if (nsCRT::strcmp(aProp, NS_APP_USER_PROFILES_ROOT_DIR) == 0) { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile)); + } else if (nsCRT::strcmp(aProp, NS_APP_USER_PROFILES_LOCAL_ROOT_DIR) == 0) { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile), true); + } else if (nsCRT::strcmp(aProp, NS_APP_RES_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(RES_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_CHROME_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(CHROME_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_INSTALL_CLEANUP_DIR) == 0) { + // This is cloned so that embeddors will have a hook to override + // with their own cleanup dir. See bugzilla bug #105087 + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + } + + if (localFile && NS_SUCCEEDED(rv)) { + localFile.forget(aResult); + return NS_OK; + } + + return rv; +} + +nsresult nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile) { + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + nsresult rv; + + if (!mMozBinDirectory) { + // Get the mozilla bin directory + // 1. Check the directory service first for NS_XPCOM_CURRENT_PROCESS_DIR + // This will be set if a directory was passed to NS_InitXPCOM + // 2. If that doesn't work, set it to be the current process directory + nsCOMPtr<nsIProperties> directoryService( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = + directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mMozBinDirectory)); + if (NS_FAILED(rv)) { + rv = directoryService->Get(NS_OS_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mMozBinDirectory)); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + nsCOMPtr<nsIFile> aFile; + rv = mMozBinDirectory->Clone(getter_AddRefs(aFile)); + if (NS_FAILED(rv)) { + return rv; + } + + NS_IF_ADDREF(*aLocalFile = aFile); + return NS_OK; +} + +//---------------------------------------------------------------------------------------- +// GetProductDirectory - Gets the directory which contains the application data +// folder +// +// UNIX : ~/.mozilla/ +// WIN : <Application Data folder on user's machine>\Mozilla +// Mac : :Documents:Mozilla: +//---------------------------------------------------------------------------------------- +nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, + bool aLocal) { + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + bool exists; + nsCOMPtr<nsIFile> localDir; + +#if defined(MOZ_WIDGET_COCOA) + NS_NewLocalFile(u""_ns, true, getter_AddRefs(localDir)); + if (!localDir) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsILocalFileMac> localDirMac(do_QueryInterface(localDir)); + + rv = localDirMac->InitWithCFURL( + CocoaFileUtils::GetProductDirectory(aLocal).get()); + if (NS_FAILED(rv)) { + return rv; + } +#elif defined(XP_WIN) + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + const char* prop = aLocal ? NS_WIN_LOCAL_APPDATA_DIR : NS_WIN_APPDATA_DIR; + rv = directoryService->Get(prop, NS_GET_IID(nsIFile), + getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return rv; + } +#elif defined(XP_UNIX) + rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true, + getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return rv; + } +#else +# error dont_know_how_to_get_product_dir_on_your_platform +#endif + + rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR); + if (NS_FAILED(rv)) { + return rv; + } + rv = localDir->Exists(&exists); + + if (NS_SUCCEEDED(rv) && !exists) { + rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + + if (NS_FAILED(rv)) { + return rv; + } + + localDir.forget(aLocalFile); + + return rv; +} + +//---------------------------------------------------------------------------------------- +// GetDefaultUserProfileRoot - Gets the directory which contains each user +// profile dir +// +// UNIX : ~/.mozilla/ +// WIN : <Application Data folder on user's machine>\Mozilla\Profiles +// Mac : :Documents:Mozilla:Profiles: +//---------------------------------------------------------------------------------------- +nsresult nsAppFileLocationProvider::GetDefaultUserProfileRoot( + nsIFile** aLocalFile, bool aLocal) { + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + nsCOMPtr<nsIFile> localDir; + + rv = GetProductDirectory(getter_AddRefs(localDir), aLocal); + if (NS_FAILED(rv)) { + return rv; + } + +#if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN) + // These 3 platforms share this part of the path - do them as one + rv = localDir->AppendRelativeNativePath("Profiles"_ns); + if (NS_FAILED(rv)) { + return rv; + } + + bool exists; + rv = localDir->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) { + rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0775); + } + if (NS_FAILED(rv)) { + return rv; + } +#endif + + localDir.forget(aLocalFile); + + return rv; +} + +//***************************************************************************** +// nsAppFileLocationProvider::nsIDirectoryServiceProvider +//***************************************************************************** + +class nsAppDirectoryEnumerator : public nsSimpleEnumerator { + public: + /** + * aKeyList is a null-terminated list of properties which are provided by + * aProvider They do not need to be publicly defined keys. + */ + nsAppDirectoryEnumerator(nsIDirectoryServiceProvider* aProvider, + const char* aKeyList[]) + : mProvider(aProvider), mCurrentKey(aKeyList) {} + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + NS_IMETHOD HasMoreElements(bool* aResult) override { + while (!mNext && *mCurrentKey) { + bool dontCare; + nsCOMPtr<nsIFile> testFile; + (void)mProvider->GetFile(*mCurrentKey++, &dontCare, + getter_AddRefs(testFile)); + mNext = testFile; + } + *aResult = mNext != nullptr; + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports** aResult) override { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + + bool hasMore; + HasMoreElements(&hasMore); + if (!hasMore) { + return NS_ERROR_FAILURE; + } + + *aResult = mNext; + NS_IF_ADDREF(*aResult); + mNext = nullptr; + + return *aResult ? NS_OK : NS_ERROR_FAILURE; + } + + protected: + nsCOMPtr<nsIDirectoryServiceProvider> mProvider; + const char** mCurrentKey; + nsCOMPtr<nsIFile> mNext; +}; diff --git a/xpcom/io/nsAppFileLocationProvider.h b/xpcom/io/nsAppFileLocationProvider.h new file mode 100644 index 0000000000..d2fd9cf651 --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.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 nsAppFileLocationProvider_h +#define nsAppFileLocationProvider_h + +#include "nsIDirectoryService.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +class nsIFile; + +//***************************************************************************** +// class nsAppFileLocationProvider +//***************************************************************************** + +class nsAppFileLocationProvider final : public nsIDirectoryServiceProvider { + public: + nsAppFileLocationProvider(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + + private: + ~nsAppFileLocationProvider() = default; + + protected: + nsresult CloneMozBinDirectory(nsIFile** aLocalFile); + /** + * Get the product directory. This is a user-specific directory for storing + * application settings (e.g. the Application Data directory on windows + * systems). + * @param aLocal If true, should try to get a directory that is only stored + * locally (ie not transferred with roaming profiles) + */ + nsresult GetProductDirectory(nsIFile** aLocalFile, bool aLocal = false); + nsresult GetDefaultUserProfileRoot(nsIFile** aLocalFile, bool aLocal = false); + + nsCOMPtr<nsIFile> mMozBinDirectory; +}; + +#endif diff --git a/xpcom/io/nsBinaryStream.cpp b/xpcom/io/nsBinaryStream.cpp new file mode 100644 index 0000000000..555edf5345 --- /dev/null +++ b/xpcom/io/nsBinaryStream.cpp @@ -0,0 +1,1007 @@ +/* -*- 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 contains implementations of the nsIBinaryInputStream and + * nsIBinaryOutputStream interfaces. Together, these interfaces allows reading + * and writing of primitive data types (integers, floating-point values, + * booleans, etc.) to a stream in a binary, untagged, fixed-endianness format. + * This might be used, for example, to implement network protocols or to + * produce architecture-neutral binary disk files, i.e. ones that can be read + * and written by both big-endian and little-endian platforms. Output is + * written in big-endian order (high-order byte first), as this is traditional + * network order. + * + * @See nsIBinaryInputStream + * @See nsIBinaryOutputStream + */ +#include <algorithm> +#include <string.h> + +#include "nsBinaryStream.h" + +#include "mozilla/EndianUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Span.h" +#include "mozilla/UniquePtr.h" + +#include "nsCRT.h" +#include "nsString.h" +#include "nsISerializable.h" +#include "nsIClassInfo.h" +#include "nsComponentManagerUtils.h" +#include "nsIURI.h" // for NS_IURI_IID +#include "nsIX509Cert.h" // for NS_IX509CERT_IID + +#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{,ByteLength},IsArrayBufferObject} +#include "js/GCAPI.h" // JS::AutoCheckCannotGC +#include "js/RootingAPI.h" // JS::{Handle,Rooted} +#include "js/Value.h" // JS::Value + +using mozilla::AsBytes; +using mozilla::MakeUnique; +using mozilla::PodCopy; +using mozilla::Span; +using mozilla::UniquePtr; + +already_AddRefed<nsIObjectOutputStream> NS_NewObjectOutputStream( + nsIOutputStream* aOutputStream) { + MOZ_ASSERT(aOutputStream); + auto stream = mozilla::MakeRefPtr<nsBinaryOutputStream>(); + + MOZ_ALWAYS_SUCCEEDS(stream->SetOutputStream(aOutputStream)); + return stream.forget(); +} + +already_AddRefed<nsIObjectInputStream> NS_NewObjectInputStream( + nsIInputStream* aInputStream) { + MOZ_ASSERT(aInputStream); + auto stream = mozilla::MakeRefPtr<nsBinaryInputStream>(); + + MOZ_ALWAYS_SUCCEEDS(stream->SetInputStream(aInputStream)); + return stream.forget(); +} + +NS_IMPL_ISUPPORTS(nsBinaryOutputStream, nsIObjectOutputStream, + nsIBinaryOutputStream, nsIOutputStream) + +NS_IMETHODIMP +nsBinaryOutputStream::Flush() { + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Flush(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Close() { + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::StreamStatus() { + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->StreamStatus(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* aActualBytes) { + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Write(aBuf, aCount, aActualBytes); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount, + uint32_t* aResult) { + MOZ_ASSERT_UNREACHABLE("WriteFrom"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + MOZ_ASSERT_UNREACHABLE("WriteSegments"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::IsNonBlocking(bool* aNonBlocking) { + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->IsNonBlocking(aNonBlocking); +} + +nsresult nsBinaryOutputStream::WriteFully(const char* aBuf, uint32_t aCount) { + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv; + uint32_t bytesWritten; + + rv = mOutputStream->Write(aBuf, aCount, &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != aCount) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryOutputStream::SetOutputStream(nsIOutputStream* aOutputStream) { + if (NS_WARN_IF(!aOutputStream)) { + return NS_ERROR_INVALID_ARG; + } + mOutputStream = aOutputStream; + mBufferAccess = do_QueryInterface(aOutputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteBoolean(bool aBoolean) { return Write8(aBoolean); } + +NS_IMETHODIMP +nsBinaryOutputStream::Write8(uint8_t aByte) { + return WriteFully((const char*)&aByte, sizeof(aByte)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write16(uint16_t aNum) { + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + return WriteFully((const char*)&aNum, sizeof(aNum)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write32(uint32_t aNum) { + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + return WriteFully((const char*)&aNum, sizeof(aNum)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write64(uint64_t aNum) { + nsresult rv; + uint32_t bytesWritten; + + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + rv = Write(reinterpret_cast<char*>(&aNum), sizeof(aNum), &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != sizeof(aNum)) { + return NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteFloat(float aFloat) { + static_assert(sizeof(float) == sizeof(uint32_t), + "False assumption about sizeof(float)"); + return Write32(*reinterpret_cast<uint32_t*>(&aFloat)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteDouble(double aDouble) { + static_assert(sizeof(double) == sizeof(uint64_t), + "False assumption about sizeof(double)"); + return Write64(*reinterpret_cast<uint64_t*>(&aDouble)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteStringZ(const char* aString) { + uint32_t length; + nsresult rv; + + length = strlen(aString); + rv = Write32(length); + if (NS_FAILED(rv)) { + return rv; + } + return WriteFully(aString, length); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteWStringZ(const char16_t* aString) { + uint32_t length = NS_strlen(aString); + nsresult rv = Write32(length); + if (NS_FAILED(rv)) { + return rv; + } + + if (length == 0) { + return NS_OK; + } + +#ifdef IS_BIG_ENDIAN + rv = WriteBytes(AsBytes(Span(aString, length))); +#else + // XXX use WriteSegments here to avoid copy! + char16_t* copy; + char16_t temp[64]; + if (length <= 64) { + copy = temp; + } else { + copy = static_cast<char16_t*>(malloc(length * sizeof(char16_t))); + if (!copy) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + NS_ASSERTION((uintptr_t(aString) & 0x1) == 0, "aString not properly aligned"); + mozilla::NativeEndian::copyAndSwapToBigEndian(copy, aString, length); + rv = WriteBytes(AsBytes(Span(copy, length))); + if (copy != temp) { + free(copy); + } +#endif + + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteUtf8Z(const char16_t* aString) { + return WriteStringZ(NS_ConvertUTF16toUTF8(aString).get()); +} + +nsresult nsBinaryOutputStream::WriteBytes(Span<const uint8_t> aBytes) { + nsresult rv; + uint32_t bytesWritten; + + rv = Write(reinterpret_cast<const char*>(aBytes.Elements()), aBytes.Length(), + &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != aBytes.Length()) { + return NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteBytesFromJS(const char* aString, uint32_t aLength) { + return WriteBytes(AsBytes(Span(aString, aLength))); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteByteArray(const nsTArray<uint8_t>& aByteArray) { + return WriteBytes(aByteArray); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef) { + return WriteCompoundObject(aObject, NS_GET_IID(nsISupports), aIsStrongRef); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSingleRefObject(nsISupports* aObject) { + return WriteCompoundObject(aObject, NS_GET_IID(nsISupports), true); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteCompoundObject(nsISupports* aObject, + const nsIID& aIID, + bool aIsStrongRef) { + nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject); + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aObject); + + // Can't deal with weak refs + if (NS_WARN_IF(!aIsStrongRef)) { + return NS_ERROR_UNEXPECTED; + } + if (NS_WARN_IF(!classInfo) || NS_WARN_IF(!serializable)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCID cid; + nsresult rv = classInfo->GetClassIDNoAlloc(&cid); + if (NS_SUCCEEDED(rv)) { + rv = WriteID(cid); + } else { + nsCID* cidptr = nullptr; + rv = classInfo->GetClassID(&cidptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = WriteID(*cidptr); + + free(cidptr); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = WriteID(aIID); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return serializable->Write(this); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteID(const nsIID& aIID) { + nsresult rv = Write32(aIID.m0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Write16(aIID.m1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Write16(aIID.m2); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = WriteBytes(aIID.m3); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBinaryOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) { + if (mBufferAccess) { + return mBufferAccess->GetBuffer(aLength, aAlignMask); + } + return nullptr; +} + +NS_IMETHODIMP_(void) +nsBinaryOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) { + if (mBufferAccess) { + mBufferAccess->PutBuffer(aBuffer, aLength); + } +} + +NS_IMPL_ISUPPORTS(nsBinaryInputStream, nsIObjectInputStream, + nsIBinaryInputStream, nsIInputStream) + +NS_IMETHODIMP +nsBinaryInputStream::Available(uint64_t* aResult) { + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->Available(aResult); +} + +NS_IMETHODIMP +nsBinaryInputStream::StreamStatus() { + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->StreamStatus(); +} + +NS_IMETHODIMP +nsBinaryInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead) { + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + + // mInputStream might give us short reads, so deal with that. + uint32_t totalRead = 0; + + uint32_t bytesRead; + do { + nsresult rv = mInputStream->Read(aBuffer, aCount, &bytesRead); + if (rv == NS_BASE_STREAM_WOULD_BLOCK && totalRead != 0) { + // We already read some data. Return it. + break; + } + + if (NS_FAILED(rv)) { + return rv; + } + + totalRead += bytesRead; + aBuffer += bytesRead; + aCount -= bytesRead; + } while (aCount != 0 && bytesRead != 0); + + *aNumRead = totalRead; + + return NS_OK; +} + +// when forwarding ReadSegments to mInputStream, we need to make sure +// 'this' is being passed to the writer each time. To do this, we need +// a thunking function which keeps the real input stream around. + +// the closure wrapper +struct MOZ_STACK_CLASS ReadSegmentsClosure { + nsCOMPtr<nsIInputStream> mRealInputStream; + void* mRealClosure; + nsWriteSegmentFun mRealWriter; + nsresult mRealResult; + uint32_t mBytesRead; // to properly implement aToOffset +}; + +// the thunking function +static nsresult ReadSegmentForwardingThunk(nsIInputStream* aStream, + void* aClosure, + const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + ReadSegmentsClosure* thunkClosure = + reinterpret_cast<ReadSegmentsClosure*>(aClosure); + + NS_ASSERTION(NS_SUCCEEDED(thunkClosure->mRealResult), + "How did this get to be a failure status?"); + + thunkClosure->mRealResult = thunkClosure->mRealWriter( + thunkClosure->mRealInputStream, thunkClosure->mRealClosure, aFromSegment, + thunkClosure->mBytesRead + aToOffset, aCount, aWriteCount); + + return thunkClosure->mRealResult; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + + ReadSegmentsClosure thunkClosure = {this, aClosure, aWriter, NS_OK, 0}; + + // mInputStream might give us short reads, so deal with that. + uint32_t bytesRead; + do { + nsresult rv = mInputStream->ReadSegments(ReadSegmentForwardingThunk, + &thunkClosure, aCount, &bytesRead); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK && thunkClosure.mBytesRead != 0) { + // We already read some data. Return it. + break; + } + + if (NS_FAILED(rv)) { + return rv; + } + + thunkClosure.mBytesRead += bytesRead; + aCount -= bytesRead; + } while (aCount != 0 && bytesRead != 0 && + NS_SUCCEEDED(thunkClosure.mRealResult)); + + *aResult = thunkClosure.mBytesRead; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::IsNonBlocking(bool* aNonBlocking) { + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->IsNonBlocking(aNonBlocking); +} + +NS_IMETHODIMP +nsBinaryInputStream::Close() { + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryInputStream::SetInputStream(nsIInputStream* aInputStream) { + if (NS_WARN_IF(!aInputStream)) { + return NS_ERROR_INVALID_ARG; + } + mInputStream = aInputStream; + mBufferAccess = do_QueryInterface(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBoolean(bool* aBoolean) { + uint8_t byteResult; + nsresult rv = Read8(&byteResult); + if (NS_FAILED(rv)) { + return rv; + } + *aBoolean = !!byteResult; + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read8(uint8_t* aByte) { + nsresult rv; + uint32_t bytesRead; + + rv = Read(reinterpret_cast<char*>(aByte), sizeof(*aByte), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != 1) { + return NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read16(uint16_t* aNum) { + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read32(uint32_t* aNum) { + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read64(uint64_t* aNum) { + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadFloat(float* aFloat) { + static_assert(sizeof(float) == sizeof(uint32_t), + "False assumption about sizeof(float)"); + return Read32(reinterpret_cast<uint32_t*>(aFloat)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadDouble(double* aDouble) { + static_assert(sizeof(double) == sizeof(uint64_t), + "False assumption about sizeof(double)"); + return Read64(reinterpret_cast<uint64_t*>(aDouble)); +} + +static nsresult WriteSegmentToCString(nsIInputStream* aStream, void* aClosure, + const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + nsACString* outString = static_cast<nsACString*>(aClosure); + + outString->Append(aFromSegment, aCount); + + *aWriteCount = aCount; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadCString(nsACString& aString) { + nsresult rv; + uint32_t length, bytesRead; + + rv = Read32(&length); + if (NS_FAILED(rv)) { + return rv; + } + + aString.Truncate(); + rv = ReadSegments(WriteSegmentToCString, &aString, length, &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + if (bytesRead != length) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +// sometimes, WriteSegmentToString will be handed an odd-number of +// bytes, which means we only have half of the last char16_t +struct WriteStringClosure { + char16_t* mWriteCursor; + bool mHasCarryoverByte; + char mCarryoverByte; +}; + +// there are a few cases we have to account for here: +// * even length buffer, no carryover - easy, just append +// * odd length buffer, no carryover - the last byte needs to be saved +// for carryover +// * odd length buffer, with carryover - first byte needs to be used +// with the carryover byte, and +// the rest of the even length +// buffer is appended as normal +// * even length buffer, with carryover - the first byte needs to be +// used with the previous carryover byte. +// this gives you an odd length buffer, +// so you have to save the last byte for +// the next carryover + +// same version of the above, but with correct casting and endian swapping +static nsresult WriteSegmentToString(nsIInputStream* aStream, void* aClosure, + const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + MOZ_ASSERT(aCount > 0, "Why are we being told to write 0 bytes?"); + static_assert(sizeof(char16_t) == 2, "We can't handle other sizes!"); + + WriteStringClosure* closure = static_cast<WriteStringClosure*>(aClosure); + char16_t* cursor = closure->mWriteCursor; + + // we're always going to consume the whole buffer no matter what + // happens, so take care of that right now.. that allows us to + // tweak aCount later. Do NOT move this! + *aWriteCount = aCount; + + // if the last Write had an odd-number of bytes read, then + if (closure->mHasCarryoverByte) { + // re-create the two-byte sequence we want to work with + char bytes[2] = {closure->mCarryoverByte, *aFromSegment}; + *cursor = *(char16_t*)bytes; + // Now the little endianness dance + mozilla::NativeEndian::swapToBigEndianInPlace(cursor, 1); + ++cursor; + + // now skip past the first byte of the buffer.. code from here + // can assume normal operations, but should not assume aCount + // is relative to the ORIGINAL buffer + ++aFromSegment; + --aCount; + + closure->mHasCarryoverByte = false; + } + + // this array is possibly unaligned... be careful how we access it! + const char16_t* unicodeSegment = + reinterpret_cast<const char16_t*>(aFromSegment); + + // calculate number of full characters in segment (aCount could be odd!) + uint32_t segmentLength = aCount / sizeof(char16_t); + + // copy all data into our aligned buffer. byte swap if necessary. + // cursor may be unaligned, so we cannot use copyAndSwapToBigEndian directly + memcpy(cursor, unicodeSegment, segmentLength * sizeof(char16_t)); + char16_t* end = cursor + segmentLength; + mozilla::NativeEndian::swapToBigEndianInPlace(cursor, segmentLength); + closure->mWriteCursor = end; + + // remember this is the modifed aCount and aFromSegment, + // so that will take into account the fact that we might have + // skipped the first byte in the buffer + if (aCount % sizeof(char16_t) != 0) { + // we must have had a carryover byte, that we'll need the next + // time around + closure->mCarryoverByte = aFromSegment[aCount - 1]; + closure->mHasCarryoverByte = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadString(nsAString& aString) { + nsresult rv; + uint32_t length, bytesRead; + + rv = Read32(&length); + if (NS_FAILED(rv)) { + return rv; + } + + if (length == 0) { + aString.Truncate(); + return NS_OK; + } + + // pre-allocate output buffer, and get direct access to buffer... + if (!aString.SetLength(length, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + WriteStringClosure closure; + closure.mWriteCursor = aString.BeginWriting(); + closure.mHasCarryoverByte = false; + + rv = ReadSegments(WriteSegmentToString, &closure, length * sizeof(char16_t), + &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!closure.mHasCarryoverByte, "some strange stream corruption!"); + + if (bytesRead != length * sizeof(char16_t)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsBinaryInputStream::ReadBytesToBuffer(uint32_t aLength, + uint8_t* aBuffer) { + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aBuffer), aLength, &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != aLength) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBytes(uint32_t aLength, char** aResult) { + char* s = static_cast<char*>(malloc(aLength)); + if (!s) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = ReadBytesToBuffer(aLength, reinterpret_cast<uint8_t*>(s)); + if (NS_FAILED(rv)) { + free(s); + return rv; + } + + *aResult = s; + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadByteArray(uint32_t aLength, + nsTArray<uint8_t>& aResult) { + if (!aResult.SetLength(aLength, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsresult rv = ReadBytesToBuffer(aLength, aResult.Elements()); + if (NS_FAILED(rv)) { + aResult.Clear(); + } + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadArrayBuffer(uint64_t aLength, + JS::Handle<JS::Value> aBuffer, + JSContext* aCx, uint64_t* aReadLength) { + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::Rooted<JSObject*> buffer(aCx, &aBuffer.toObject()); + if (!JS::IsArrayBufferObject(buffer)) { + return NS_ERROR_FAILURE; + } + + size_t bufferLength = JS::GetArrayBufferByteLength(buffer); + if (bufferLength < aLength) { + return NS_ERROR_FAILURE; + } + + uint32_t bufSize = std::min<uint64_t>(aLength, 4096); + UniquePtr<char[]> buf = MakeUnique<char[]>(bufSize); + + uint64_t pos = 0; + *aReadLength = 0; + do { + // Read data into temporary buffer. + uint32_t bytesRead; + uint32_t amount = std::min<uint64_t>(aLength - pos, bufSize); + nsresult rv = Read(buf.get(), amount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(bytesRead <= amount); + + if (bytesRead == 0) { + break; + } + + // Copy data into actual buffer. + + JS::AutoCheckCannotGC nogc; + bool isShared; + if (bufferLength != JS::GetArrayBufferByteLength(buffer)) { + return NS_ERROR_FAILURE; + } + + char* data = reinterpret_cast<char*>( + JS::GetArrayBufferData(buffer, &isShared, nogc)); + MOZ_ASSERT(!isShared); // Implied by JS::GetArrayBufferData() + if (!data) { + return NS_ERROR_FAILURE; + } + + *aReadLength += bytesRead; + PodCopy(data + pos, buf.get(), bytesRead); + + pos += bytesRead; + } while (pos < aLength); + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadObject(bool aIsStrongRef, nsISupports** aObject) { + nsCID cid; + nsIID iid; + nsresult rv = ReadID(&cid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = ReadID(&iid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // HACK: Intercept old (pre-gecko6) nsIURI IID, and replace with + // the updated IID, so that we're QI'ing to an actual interface. + // (As soon as we drop support for upgrading from pre-gecko6, we can + // remove this chunk.) + static const nsIID oldURIiid = { + 0x7a22cc0, + 0xce5, + 0x11d3, + {0x93, 0x31, 0x0, 0x10, 0x4b, 0xa0, 0xfd, 0x40}}; + + // hackaround for bug 670542 + static const nsIID oldURIiid2 = { + 0xd6d04c36, + 0x0fa4, + 0x4db3, + {0xbe, 0x05, 0x4a, 0x18, 0x39, 0x71, 0x03, 0xe2}}; + + // hackaround for bug 682031 + static const nsIID oldURIiid3 = { + 0x12120b20, + 0x0929, + 0x40e9, + {0x88, 0xcf, 0x6e, 0x08, 0x76, 0x6e, 0x8b, 0x23}}; + + // hackaround for bug 1195415 + static const nsIID oldURIiid4 = { + 0x395fe045, + 0x7d18, + 0x4adb, + {0xa3, 0xfd, 0xaf, 0x98, 0xc8, 0xa1, 0xaf, 0x11}}; + + if (iid.Equals(oldURIiid) || iid.Equals(oldURIiid2) || + iid.Equals(oldURIiid3) || iid.Equals(oldURIiid4)) { + const nsIID newURIiid = NS_IURI_IID; + iid = newURIiid; + } + + // Hack around bug 1508939 + // The old CSP serialization can't be handled cleanly when + // it's embedded in an old style principal + static const nsIID oldCSPiid = { + 0xb3c4c0ae, + 0xbd5e, + 0x4cad, + {0x87, 0xe0, 0x8d, 0x21, 0x0d, 0xbb, 0x3f, 0x9f}}; + if (iid.Equals(oldCSPiid)) { + return NS_ERROR_FAILURE; + } + // END HACK + + // HACK: Service workers store resource security info on disk in the dom + // Cache API. When the uuid of the nsIX509Cert interface changes + // these serialized objects cannot be loaded any more. This hack + // works around this issue. + + // hackaround for bug 1247580 (FF45 to FF46 transition) + static const nsIID oldCertIID = { + 0xf8ed8364, + 0xced9, + 0x4c6e, + {0x86, 0xba, 0x48, 0xaf, 0x53, 0xc3, 0x93, 0xe6}}; + + if (iid.Equals(oldCertIID)) { + const nsIID newCertIID = NS_IX509CERT_IID; + iid = newCertIID; + } + // END HACK + + nsCOMPtr<nsISupports> object = do_CreateInstance(cid, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(object); + if (NS_WARN_IF(!serializable)) { + return NS_ERROR_UNEXPECTED; + } + + rv = serializable->Read(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return object->QueryInterface(iid, reinterpret_cast<void**>(aObject)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadID(nsID* aResult) { + nsresult rv = Read32(&aResult->m0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Read16(&aResult->m1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Read16(&aResult->m2); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + const uint32_t toRead = sizeof(aResult->m3); + uint32_t bytesRead = 0; + rv = Read(reinterpret_cast<char*>(&aResult->m3[0]), toRead, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (bytesRead != toRead) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBinaryInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) { + if (mBufferAccess) { + return mBufferAccess->GetBuffer(aLength, aAlignMask); + } + return nullptr; +} + +NS_IMETHODIMP_(void) +nsBinaryInputStream::PutBuffer(char* aBuffer, uint32_t aLength) { + if (mBufferAccess) { + mBufferAccess->PutBuffer(aBuffer, aLength); + } +} diff --git a/xpcom/io/nsBinaryStream.h b/xpcom/io/nsBinaryStream.h new file mode 100644 index 0000000000..94b92bb746 --- /dev/null +++ b/xpcom/io/nsBinaryStream.h @@ -0,0 +1,99 @@ +/* -*- 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 nsBinaryStream_h___ +#define nsBinaryStream_h___ + +#include "nsCOMPtr.h" +#include "nsAString.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIStreamBufferAccess.h" + +#define NS_BINARYOUTPUTSTREAM_CID \ + { /* 86c37b9a-74e7-4672-844e-6e7dd83ba484 */ \ + 0x86c37b9a, 0x74e7, 0x4672, { \ + 0x84, 0x4e, 0x6e, 0x7d, 0xd8, 0x3b, 0xa4, 0x84 \ + } \ + } + +#define NS_BINARYOUTPUTSTREAM_CONTRACTID "@mozilla.org/binaryoutputstream;1" + +// Derive from nsIObjectOutputStream so this class can be used as a superclass +// by nsObjectOutputStream. +class nsBinaryOutputStream final : public nsIObjectOutputStream { + public: + nsBinaryOutputStream() = default; + + protected: + friend already_AddRefed<nsIObjectOutputStream> NS_NewObjectOutputStream( + nsIOutputStream*); + + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIOutputStream methods + NS_DECL_NSIOUTPUTSTREAM + + // nsIBinaryOutputStream methods + NS_DECL_NSIBINARYOUTPUTSTREAM + + // nsIObjectOutputStream methods + NS_DECL_NSIOBJECTOUTPUTSTREAM + + // Call Write(), ensuring that all proffered data is written + nsresult WriteFully(const char* aBuf, uint32_t aCount); + + nsCOMPtr<nsIOutputStream> mOutputStream; + nsCOMPtr<nsIStreamBufferAccess> mBufferAccess; + + private: + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryOutputStream() = default; +}; + +#define NS_BINARYINPUTSTREAM_CID \ + { /* c521a612-2aad-46db-b6ab-3b821fb150b1 */ \ + 0xc521a612, 0x2aad, 0x46db, { \ + 0xb6, 0xab, 0x3b, 0x82, 0x1f, 0xb1, 0x50, 0xb1 \ + } \ + } + +#define NS_BINARYINPUTSTREAM_CONTRACTID "@mozilla.org/binaryinputstream;1" + +class nsBinaryInputStream final : public nsIObjectInputStream { + public: + nsBinaryInputStream() = default; + + protected: + friend already_AddRefed<nsIObjectInputStream> NS_NewObjectInputStream( + nsIInputStream*); + + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIInputStream methods + NS_DECL_NSIINPUTSTREAM + + // nsIBinaryInputStream methods + NS_DECL_NSIBINARYINPUTSTREAM + + // nsIObjectInputStream methods + NS_DECL_NSIOBJECTINPUTSTREAM + + nsCOMPtr<nsIInputStream> mInputStream; + nsCOMPtr<nsIStreamBufferAccess> mBufferAccess; + + private: + // Shared infrastructure for ReadBytes and ReadByteArray. Callers + // are expected to provide a buffer that can contain aLength bytes. + nsresult ReadBytesToBuffer(uint32_t aLength, uint8_t* aBuffer); + + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryInputStream() = default; +}; + +#endif // nsBinaryStream_h___ diff --git a/xpcom/io/nsDirectoryService.cpp b/xpcom/io/nsDirectoryService.cpp new file mode 100644 index 0000000000..f5e841c6ea --- /dev/null +++ b/xpcom/io/nsDirectoryService.cpp @@ -0,0 +1,448 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsDirectoryService.h" +#include "nsLocalFile.h" +#include "nsDebug.h" +#include "nsGkAtoms.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" + +#include "mozilla/SimpleEnumerator.h" +#include "nsICategoryManager.h" +#include "nsISimpleEnumerator.h" + +#if defined(XP_WIN) +# include <windows.h> +# include <shlobj.h> +# include <stdlib.h> +# include <stdio.h> +#elif defined(XP_UNIX) +# include <unistd.h> +# include <stdlib.h> +# include <sys/param.h> +# include "prenv.h" +# ifdef MOZ_WIDGET_COCOA +# include <CoreServices/CoreServices.h> +# include <Carbon/Carbon.h> +# endif +#endif + +#include "SpecialSystemDirectory.h" +#include "nsAppFileLocationProvider.h" +#include "BinaryPath.h" + +using namespace mozilla; + +//---------------------------------------------------------------------------------------- +nsresult nsDirectoryService::GetCurrentProcessDirectory(nsIFile** aFile) +//---------------------------------------------------------------------------------------- +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + *aFile = nullptr; + + // Set the component registry location: + if (!gService) { + return NS_ERROR_FAILURE; + } + + if (!mXCurProcD) { +#if defined(ANDROID) + // Some callers relying on this fallback make assumptions that don't + // hold on Android for BinaryPath::GetFile, so use GRE_HOME instead. + const char* greHome = getenv("GRE_HOME"); + if (!greHome) { + return NS_ERROR_FAILURE; + } + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(greHome), true, + getter_AddRefs(mXCurProcD)); + if (NS_FAILED(rv)) { + return rv; + } +#else + nsCOMPtr<nsIFile> file; + if (NS_SUCCEEDED(BinaryPath::GetFile(getter_AddRefs(file)))) { + nsresult rv = file->GetParent(getter_AddRefs(mXCurProcD)); + if (NS_FAILED(rv)) { + return rv; + } + } +#endif + } + return mXCurProcD->Clone(aFile); +} // GetCurrentProcessDirectory() + +StaticRefPtr<nsDirectoryService> nsDirectoryService::gService; + +nsDirectoryService::nsDirectoryService() : mHashtable(128) {} + +nsresult nsDirectoryService::Create(REFNSIID aIID, void** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + if (!gService) { + return NS_ERROR_NOT_INITIALIZED; + } + + return gService->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsDirectoryService::Init() { + MOZ_ASSERT_UNREACHABLE("nsDirectoryService::Init() for internal use only!"); + return NS_OK; +} + +void nsDirectoryService::RealInit() { + NS_ASSERTION(!gService, + "nsDirectoryService::RealInit Mustn't initialize twice!"); + + gService = new nsDirectoryService(); + + // Let the list hold the only reference to the provider. + nsAppFileLocationProvider* defaultProvider = new nsAppFileLocationProvider; + gService->mProviders.AppendElement(defaultProvider); +} + +nsDirectoryService::~nsDirectoryService() = default; + +NS_IMPL_ISUPPORTS(nsDirectoryService, nsIProperties, nsIDirectoryService, + nsIDirectoryServiceProvider, nsIDirectoryServiceProvider2) + +NS_IMETHODIMP +nsDirectoryService::Undefine(const char* aProp) { + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString key(aProp); + return mHashtable.Remove(key) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::GetKeys(nsTArray<nsCString>& aKeys) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +struct MOZ_STACK_CLASS FileData { + FileData(const char* aProperty, const nsIID& aUUID) + : property(aProperty), data(nullptr), persistent(true), uuid(aUUID) {} + + const char* property; + nsCOMPtr<nsISupports> data; + bool persistent; + const nsIID& uuid; +}; + +static bool FindProviderFile(nsIDirectoryServiceProvider* aElement, + FileData* aData) { + nsresult rv; + if (aData->uuid.Equals(NS_GET_IID(nsISimpleEnumerator))) { + // Not all providers implement this iface + nsCOMPtr<nsIDirectoryServiceProvider2> prov2 = do_QueryInterface(aElement); + if (prov2) { + nsCOMPtr<nsISimpleEnumerator> newFiles; + rv = prov2->GetFiles(aData->property, getter_AddRefs(newFiles)); + if (NS_SUCCEEDED(rv) && newFiles) { + if (aData->data) { + nsCOMPtr<nsISimpleEnumerator> unionFiles; + + NS_NewUnionEnumerator(getter_AddRefs(unionFiles), + (nsISimpleEnumerator*)aData->data.get(), + newFiles); + + if (unionFiles) { + unionFiles.swap(*(nsISimpleEnumerator**)&aData->data); + } + } else { + aData->data = newFiles; + } + + aData->persistent = false; // Enumerators can never be persistent + return rv == NS_SUCCESS_AGGREGATE_RESULT; + } + } + } else { + rv = aElement->GetFile(aData->property, &aData->persistent, + (nsIFile**)&aData->data); + if (NS_SUCCEEDED(rv) && aData->data) { + return false; + } + } + + return true; +} + +NS_IMETHODIMP +nsDirectoryService::Get(const char* aProp, const nsIID& aUuid, void** aResult) { + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(NS_IsMainThread(), "Do not call dirsvc::get on non-main threads!"); + + nsDependentCString key(aProp); + + nsCOMPtr<nsIFile> cachedFile = mHashtable.Get(key); + + if (cachedFile) { + nsCOMPtr<nsIFile> cloneFile; + cachedFile->Clone(getter_AddRefs(cloneFile)); + return cloneFile->QueryInterface(aUuid, aResult); + } + + // it is not one of our defaults, lets check any providers + FileData fileData(aProp, aUuid); + + for (int32_t i = mProviders.Length() - 1; i >= 0; i--) { + if (!FindProviderFile(mProviders[i], &fileData)) { + break; + } + } + if (fileData.data) { + if (fileData.persistent) { + Set(aProp, static_cast<nsIFile*>(fileData.data.get())); + } + nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult); + fileData.data = nullptr; // AddRef occurs in FindProviderFile() + return rv; + } + + FindProviderFile(static_cast<nsIDirectoryServiceProvider*>(this), &fileData); + if (fileData.data) { + if (fileData.persistent) { + Set(aProp, static_cast<nsIFile*>(fileData.data.get())); + } + nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult); + fileData.data = nullptr; // AddRef occurs in FindProviderFile() + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::Set(const char* aProp, nsISupports* aValue) { + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + if (!aValue) { + return NS_ERROR_FAILURE; + } + + const nsDependentCString key(aProp); + return mHashtable.WithEntryHandle(key, [&](auto&& entry) { + if (!entry) { + nsCOMPtr<nsIFile> ourFile = do_QueryInterface(aValue); + if (ourFile) { + nsCOMPtr<nsIFile> cloneFile; + ourFile->Clone(getter_AddRefs(cloneFile)); + entry.Insert(std::move(cloneFile)); + return NS_OK; + } + } + return NS_ERROR_FAILURE; + }); +} + +NS_IMETHODIMP +nsDirectoryService::Has(const char* aProp, bool* aResult) { + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = false; + nsCOMPtr<nsIFile> value; + nsresult rv = Get(aProp, NS_GET_IID(nsIFile), getter_AddRefs(value)); + if (NS_FAILED(rv)) { + return NS_OK; + } + + if (value) { + *aResult = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDirectoryService::RegisterProvider(nsIDirectoryServiceProvider* aProv) { + if (!aProv) { + return NS_ERROR_FAILURE; + } + + mProviders.AppendElement(aProv); + return NS_OK; +} + +void nsDirectoryService::RegisterCategoryProviders() { + nsCOMPtr<nsICategoryManager> catman( + do_GetService(NS_CATEGORYMANAGER_CONTRACTID)); + if (!catman) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + catman->EnumerateCategory(XPCOM_DIRECTORY_PROVIDER_CATEGORY, + getter_AddRefs(entries)); + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entries)) { + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + if (nsCOMPtr<nsIDirectoryServiceProvider> provider = + do_GetService(contractID.get())) { + RegisterProvider(provider); + } + } +} + +NS_IMETHODIMP +nsDirectoryService::UnregisterProvider(nsIDirectoryServiceProvider* aProv) { + if (!aProv) { + return NS_ERROR_FAILURE; + } + + mProviders.RemoveElement(aProv); + return NS_OK; +} + +// DO NOT ADD ANY LOCATIONS TO THIS FUNCTION UNTIL YOU TALK TO: +// dougt@netscape.com. This is meant to be a place of xpcom or system specific +// file locations, not application specific locations. If you need the later, +// register a callback for your application. + +NS_IMETHODIMP +nsDirectoryService::GetFile(const char* aProp, bool* aPersistent, + nsIFile** aResult) { + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aResult = nullptr; + *aPersistent = true; + + RefPtr<nsAtom> inAtom = NS_Atomize(aProp); + + // check to see if it is one of our defaults + + if (inAtom == nsGkAtoms::DirectoryService_CurrentProcess || + inAtom == nsGkAtoms::DirectoryService_OS_CurrentProcessDirectory) { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } + + // Unless otherwise set, the core pieces of the GRE exist + // in the current process directory. + else if (inAtom == nsGkAtoms::DirectoryService_GRE_Directory || + inAtom == nsGkAtoms::DirectoryService_GRE_BinDirectory) { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_TemporaryDirectory) { + rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_CurrentWorkingDirectory) { + rv = GetSpecialSystemDirectory(OS_CurrentWorkingDirectory, + getter_AddRefs(localFile)); + } +#if defined(MOZ_WIDGET_COCOA) + else if (inAtom == nsGkAtoms::DirectoryService_SystemDirectory) { + rv = GetSpecialSystemDirectory(Mac_SystemDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_UserLibDirectory) { + rv = GetSpecialSystemDirectory(Mac_UserLibDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::Home) { + rv = + GetSpecialSystemDirectory(Mac_HomeDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultDownloadDirectory) { + rv = GetSpecialSystemDirectory(Mac_DefaultDownloadDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Mac_UserDesktopDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_LocalApplicationsDirectory) { + rv = GetSpecialSystemDirectory(Mac_LocalApplicationsDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_UserPreferencesDirectory) { + rv = GetSpecialSystemDirectory(Mac_UserPreferencesDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_PictureDocumentsDirectory) { + rv = GetSpecialSystemDirectory(Mac_PictureDocumentsDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultScreenshotDirectory) { + rv = GetSpecialSystemDirectory(Mac_DefaultScreenshotDirectory, + getter_AddRefs(localFile)); + } +#elif defined(XP_WIN) + else if (inAtom == nsGkAtoms::DirectoryService_SystemDirectory) { + rv = GetSpecialSystemDirectory(Win_SystemDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_WindowsDirectory) { + rv = GetSpecialSystemDirectory(Win_WindowsDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_WindowsProgramFiles) { + rv = GetSpecialSystemDirectory(Win_ProgramFiles, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::Home) { + rv = + GetSpecialSystemDirectory(Win_HomeDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_Programs) { + rv = GetSpecialSystemDirectory(Win_Programs, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_Favorites) { + rv = GetSpecialSystemDirectory(Win_Favorites, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Win_Desktopdirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_Appdata) { + rv = GetSpecialSystemDirectory(Win_Appdata, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_LocalAppdata) { + rv = GetSpecialSystemDirectory(Win_LocalAppdata, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_WinCookiesDirectory) { + rv = GetSpecialSystemDirectory(Win_Cookies, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultDownloadDirectory) { + rv = GetSpecialSystemDirectory(Win_Downloads, getter_AddRefs(localFile)); + } +#elif defined(XP_UNIX) + else if (inAtom == nsGkAtoms::Home) { + rv = GetSpecialSystemDirectory(Unix_HomeDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Unix_XDG_Desktop, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultDownloadDirectory) { + rv = + GetSpecialSystemDirectory(Unix_XDG_Download, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsGkAtoms::DirectoryService_OS_SystemConfigDir) { + rv = GetSpecialSystemDirectory(Unix_SystemConfigDirectory, + getter_AddRefs(localFile)); + } +#endif + + if (NS_FAILED(rv)) { + return rv; + } + + if (!localFile) { + return NS_ERROR_FAILURE; + } + + localFile.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryService::GetFiles(const char* aProp, nsISimpleEnumerator** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + + return NS_ERROR_FAILURE; +} diff --git a/xpcom/io/nsDirectoryService.h b/xpcom/io/nsDirectoryService.h new file mode 100644 index 0000000000..49f3c85f51 --- /dev/null +++ b/xpcom/io/nsDirectoryService.h @@ -0,0 +1,59 @@ +/* -*- 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 nsDirectoryService_h___ +#define nsDirectoryService_h___ + +#include "nsIDirectoryService.h" +#include "nsInterfaceHashtable.h" +#include "nsIFile.h" +#include "nsDirectoryServiceDefs.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" + +#define NS_DIRECTORY_SERVICE_CID \ + { \ + 0xf00152d0, 0xb40b, 0x11d3, { \ + 0x8c, 0x9c, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74 \ + } \ + } + +class nsDirectoryService final : public nsIDirectoryService, + public nsIProperties, + public nsIDirectoryServiceProvider2 { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIPROPERTIES + + NS_DECL_NSIDIRECTORYSERVICE + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + + nsDirectoryService(); + + static void RealInit(); + void RegisterCategoryProviders(); + + static nsresult Create(REFNSIID aIID, void** aResult); + + static mozilla::StaticRefPtr<nsDirectoryService> gService; + + void SetCurrentProcessDirectory(nsIFile* aFile) { mXCurProcD = aFile; } + nsresult GetCurrentProcessDirectory(nsIFile**); + + private: + ~nsDirectoryService(); + nsCOMPtr<nsIFile> mXCurProcD; + + nsInterfaceHashtable<nsCStringHashKey, nsIFile> mHashtable; + nsTArray<nsCOMPtr<nsIDirectoryServiceProvider>> mProviders; +}; + +#endif diff --git a/xpcom/io/nsDirectoryServiceDefs.h b/xpcom/io/nsDirectoryServiceDefs.h new file mode 100644 index 0000000000..9f0368ff06 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceDefs.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Defines the property names for directories available from + * nsIDirectoryService. These dirs are always available even if no + * nsIDirectoryServiceProviders have been registered with the service. + * Application level keys are defined in nsAppDirectoryServiceDefs.h. + * + * Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or + * subclass). Keys whose definition ends in "LIST" return an nsISimpleEnumerator + * which enumerates a list of file objects. + * + * Defines listed in this file are FROZEN. This list may grow. Each unique + * string in this file should have a corresponding atom defined in + * StaticAtoms.py (search for "DirectoryService"), regardless of whether it + * is defined here due to conditional compilation. + */ + +#ifndef nsDirectoryServiceDefs_h___ +#define nsDirectoryServiceDefs_h___ + +/* General OS specific locations */ + +#define NS_OS_HOME_DIR "Home" + +#define NS_OS_TEMP_DIR "TmpD" +#define NS_OS_CURRENT_WORKING_DIR "CurWorkD" +/* Files stored in this directory will appear on the user's desktop, + * if there is one, otherwise it's just the same as "Home" + */ +#define NS_OS_DESKTOP_DIR "Desk" + +#define NS_OS_DEFAULT_DOWNLOAD_DIR "DfltDwnld" + +/* Property returns the directory in which the procces was started from. + */ +#define NS_OS_CURRENT_PROCESS_DIR "CurProcD" + +/* This location is similar to NS_OS_CURRENT_PROCESS_DIR, however, + * NS_XPCOM_CURRENT_PROCESS_DIR can be overriden by passing a "bin + * directory" to NS_InitXPCOM(). + */ +#define NS_XPCOM_CURRENT_PROCESS_DIR "XCurProcD" + +/* Property will return the location of the the XPCOM Shared Library. + */ +#define NS_XPCOM_LIBRARY_FILE "XpcomLib" + +/* Property will return the current location of the GRE directory. + * On OSX, this typically points to Contents/Resources in the app bundle. + * If no GRE is used, this propery will behave like + * NS_XPCOM_CURRENT_PROCESS_DIR. + */ +#define NS_GRE_DIR "GreD" + +/* Property will return the current location of the GRE-binaries directory. + * On OSX, this typically points to Contents/MacOS in the app bundle. On + * all other platforms, this will be identical to NS_GRE_DIR. + * Since this property is based on the NS_GRE_DIR, if no GRE is used, this + * propery will behave like NS_XPCOM_CURRENT_PROCESS_DIR. + */ +#define NS_GRE_BIN_DIR "GreBinD" + +/* Platform Specific Locations */ + +#if !defined(XP_UNIX) || defined(MOZ_WIDGET_COCOA) +# define NS_OS_SYSTEM_DIR "SysD" +#endif + +#if defined(MOZ_WIDGET_COCOA) +# define NS_MAC_USER_LIB_DIR "ULibDir" +# define NS_OSX_LOCAL_APPLICATIONS_DIR "LocApp" +# define NS_OSX_USER_PREFERENCES_DIR "UsrPrfs" +# define NS_OSX_PICTURE_DOCUMENTS_DIR "Pct" +#elif defined(XP_WIN) +# define NS_WIN_WINDOWS_DIR "WinD" +# define NS_WIN_PROGRAM_FILES_DIR "ProgF" +# define NS_WIN_HOME_DIR NS_OS_HOME_DIR +# define NS_WIN_PROGRAMS_DIR "Progs" // User start menu programs directory! +# define NS_WIN_FAVORITES_DIR "Favs" +# define NS_WIN_APPDATA_DIR "AppData" +# define NS_WIN_LOCAL_APPDATA_DIR "LocalAppData" +# define NS_WIN_COOKIES_DIR "CookD" +#elif defined(XP_UNIX) +# define NS_UNIX_HOME_DIR NS_OS_HOME_DIR +#endif + +#if defined(MOZ_WIDGET_GTK) +# define NS_OS_SYSTEM_CONFIG_DIR "SysConfD" +#endif // defined(MOZ_WIDGET_GTK) + +#endif diff --git a/xpcom/io/nsDirectoryServiceUtils.h b/xpcom/io/nsDirectoryServiceUtils.h new file mode 100644 index 0000000000..c48bf4e2fe --- /dev/null +++ b/xpcom/io/nsDirectoryServiceUtils.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 nsDirectoryServiceUtils_h___ +#define nsDirectoryServiceUtils_h___ + +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsXPCOMCID.h" +#include "nsIFile.h" + +inline nsresult NS_GetSpecialDirectory(const char* aSpecialDirName, + nsIFile** aResult) { + nsresult rv; + nsCOMPtr<nsIProperties> serv( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + + return serv->Get(aSpecialDirName, NS_GET_IID(nsIFile), + reinterpret_cast<void**>(aResult)); +} + +#endif diff --git a/xpcom/io/nsEscape.cpp b/xpcom/io/nsEscape.cpp new file mode 100644 index 0000000000..f211ea2809 --- /dev/null +++ b/xpcom/io/nsEscape.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 "nsEscape.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/TextUtils.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "nsASCIIMask.h" + +static const char hexCharsUpper[] = "0123456789ABCDEF"; +static const char hexCharsUpperLower[] = "0123456789ABCDEFabcdef"; + +static const unsigned char netCharType[256] = + // clang-format off +/* Bit 0 xalpha -- the alphas +** Bit 1 xpalpha -- as xalpha but +** converts spaces to plus and plus to %2B +** Bit 3 ... path -- as xalphas but doesn't escape '/' +** Bit 4 ... NSURL-ref -- extra encoding for Apple NSURL compatibility. +** This encoding set is used on encoded URL ref +** components before converting a URL to an NSURL +** so we don't include '%' to avoid double encoding. +*/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, /* 0x */ + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, /* 1x */ + /* ! " # $ % & ' ( ) * + , - . / */ + 0x0,0x8,0x0,0x0,0x8,0x8,0x8,0x8,0x8,0x8,0xf,0xc,0x8,0xf,0xf,0xc, /* 2x */ + /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + 0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x8,0x8,0x0,0x8,0x0,0x8, /* 3x */ + /* @ A B C D E F G H I J K L M N O */ + 0x8,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf, /* 4x */ + /* bits for '@' changed from 7 to 0 so '@' can be escaped */ + /* in usernames and passwords in publishing. */ + /* P Q R S T U V W X Y Z [ \ ] ^ _ */ + 0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x0,0x0,0x0,0x0,0xf, /* 5x */ + /* ` a b c d e f g h i j k l m n o */ + 0x0,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf, /* 6x */ + /* p q r s t u v w x y z { | } ~ DEL */ + 0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x0,0x0,0x0,0x8,0x0, /* 7x */ + 0x0, + }; + +/* decode % escaped hex codes into character values + */ +#define UNHEX(C) \ + ((C >= '0' && C <= '9') ? C - '0' : \ + ((C >= 'A' && C <= 'F') ? C - 'A' + 10 : \ + ((C >= 'a' && C <= 'f') ? C - 'a' + 10 : 0))) +// clang-format on + +#define IS_OK(C) (netCharType[((unsigned char)(C))] & (aFlags)) +#define HEX_ESCAPE '%' + +static const uint32_t ENCODE_MAX_LEN = 6; // %uABCD + +static uint32_t AppendPercentHex(char* aBuffer, unsigned char aChar) { + uint32_t i = 0; + aBuffer[i++] = '%'; + aBuffer[i++] = hexCharsUpper[aChar >> 4]; // high nibble + aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low nibble + return i; +} + +static uint32_t AppendPercentHex(char16_t* aBuffer, char16_t aChar) { + uint32_t i = 0; + aBuffer[i++] = '%'; + if (aChar & 0xff00) { + aBuffer[i++] = 'u'; + aBuffer[i++] = hexCharsUpper[aChar >> 12]; // high-byte high nibble + aBuffer[i++] = hexCharsUpper[(aChar >> 8) & 0xF]; // high-byte low nibble + } + aBuffer[i++] = hexCharsUpper[(aChar >> 4) & 0xF]; // low-byte high nibble + aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low-byte low nibble + return i; +} + +//---------------------------------------------------------------------------------------- +char* nsEscape(const char* aStr, size_t aLength, size_t* aOutputLength, + nsEscapeMask aFlags) +//---------------------------------------------------------------------------------------- +{ + if (!aStr) { + return nullptr; + } + + size_t charsToEscape = 0; + + const unsigned char* src = (const unsigned char*)aStr; + for (size_t i = 0; i < aLength; ++i) { + if (!IS_OK(src[i])) { + charsToEscape++; + } + } + + // calculate how much memory should be allocated + // original length + 2 bytes for each escaped character + terminating '\0' + // do the sum in steps to check for overflow + size_t dstSize = aLength + 1 + charsToEscape; + if (dstSize <= aLength) { + return nullptr; + } + dstSize += charsToEscape; + if (dstSize < aLength) { + return nullptr; + } + + // fail if we need more than 4GB + if (dstSize > UINT32_MAX) { + return nullptr; + } + + char* result = (char*)moz_xmalloc(dstSize); + + unsigned char* dst = (unsigned char*)result; + if (aFlags == url_XPAlphas) { + for (size_t i = 0; i < aLength; ++i) { + unsigned char c = *src++; + if (IS_OK(c)) { + *dst++ = c; + } else if (c == ' ') { + *dst++ = '+'; /* convert spaces to pluses */ + } else { + *dst++ = HEX_ESCAPE; + *dst++ = hexCharsUpper[c >> 4]; /* high nibble */ + *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */ + } + } + } else { + for (size_t i = 0; i < aLength; ++i) { + unsigned char c = *src++; + if (IS_OK(c)) { + *dst++ = c; + } else { + *dst++ = HEX_ESCAPE; + *dst++ = hexCharsUpper[c >> 4]; /* high nibble */ + *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */ + } + } + } + + *dst = '\0'; /* tack on eos */ + if (aOutputLength) { + *aOutputLength = dst - (unsigned char*)result; + } + + return result; +} + +//---------------------------------------------------------------------------------------- +char* nsUnescape(char* aStr) +//---------------------------------------------------------------------------------------- +{ + nsUnescapeCount(aStr); + return aStr; +} + +//---------------------------------------------------------------------------------------- +int32_t nsUnescapeCount(char* aStr) +//---------------------------------------------------------------------------------------- +{ + char* src = aStr; + char* dst = aStr; + + char c1[] = " "; + char c2[] = " "; + char* const pc1 = c1; + char* const pc2 = c2; + + if (!*src) { + // A null string was passed in. Nothing to escape. + // Returns early as the string might not actually be mutable with + // length 0. + return 0; + } + + while (*src) { + c1[0] = *(src + 1); + if (*(src + 1) == '\0') { + c2[0] = '\0'; + } else { + c2[0] = *(src + 2); + } + + if (*src != HEX_ESCAPE || strpbrk(pc1, hexCharsUpperLower) == nullptr || + strpbrk(pc2, hexCharsUpperLower) == nullptr) { + *dst++ = *src++; + } else { + src++; /* walk over escape */ + if (*src) { + *dst = UNHEX(*src) << 4; + src++; + } + if (*src) { + *dst = (*dst + UNHEX(*src)); + src++; + } + dst++; + } + } + + *dst = 0; + return (int)(dst - aStr); + +} /* NET_UnEscapeCnt */ + +void nsAppendEscapedHTML(const nsACString& aSrc, nsACString& aDst) { + // Preparation: aDst's length will increase by at least aSrc's length. If the + // addition overflows, we skip this, which is fine, and we'll likely abort + // while (infallibly) appending due to aDst becoming too large. + mozilla::CheckedInt<nsACString::size_type> newCapacity = aDst.Length(); + newCapacity += aSrc.Length(); + if (newCapacity.isValid()) { + aDst.SetCapacity(newCapacity.value()); + } + + for (auto cur = aSrc.BeginReading(); cur != aSrc.EndReading(); cur++) { + if (*cur == '<') { + aDst.AppendLiteral("<"); + } else if (*cur == '>') { + aDst.AppendLiteral(">"); + } else if (*cur == '&') { + aDst.AppendLiteral("&"); + } else if (*cur == '"') { + aDst.AppendLiteral("""); + } else if (*cur == '\'') { + aDst.AppendLiteral("'"); + } else { + aDst.Append(*cur); + } + } +} + +//---------------------------------------------------------------------------------------- +// +// The following table encodes which characters needs to be escaped for which +// parts of an URL. The bits are the "url components" in the enum EscapeMask, +// see nsEscape.h. + +template <size_t N> +static constexpr void AddUnescapedChars(const char (&aChars)[N], + uint32_t aFlags, + std::array<uint32_t, 256>& aTable) { + for (size_t i = 0; i < N - 1; ++i) { + aTable[static_cast<unsigned char>(aChars[i])] |= aFlags; + } +} + +static constexpr std::array<uint32_t, 256> BuildEscapeChars() { + constexpr uint32_t kAllModes = esc_Scheme | esc_Username | esc_Password | + esc_Host | esc_Directory | esc_FileBaseName | + esc_FileExtension | esc_Param | esc_Query | + esc_Ref | esc_ExtHandler; + + std::array<uint32_t, 256> table{0}; + + // Alphanumerics shouldn't be escaped in all escape modes. + AddUnescapedChars("0123456789", kAllModes, table); + AddUnescapedChars("ABCDEFGHIJKLMNOPQRSTUVWXYZ", kAllModes, table); + AddUnescapedChars("abcdefghijklmnopqrstuvwxyz", kAllModes, table); + AddUnescapedChars("!$&()*+,-_~", kAllModes, table); + + // Extra characters which aren't escaped in particular escape modes. + AddUnescapedChars(".", esc_Scheme, table); + // Note that behavior of esc_Username and esc_Password is the same, so these + // could be merged (in the URL spec, both reference the "userinfo encode set" + // https://url.spec.whatwg.org/#userinfo-percent-encode-set, so the same + // behavior is expected.) + // Leaving separate for now to minimize risk, as these are also IDL-exposed + // as separate constants. + AddUnescapedChars("'.", esc_Username, table); + AddUnescapedChars("'.", esc_Password, table); + AddUnescapedChars(".", esc_Host, table); // Same as esc_Scheme + AddUnescapedChars("'./:;=@[]|", esc_Directory, table); + AddUnescapedChars("'.:;=@[]|", esc_FileBaseName, table); + AddUnescapedChars("':;=@[]|", esc_FileExtension, table); + AddUnescapedChars(".:;=@[\\]^`{|}", esc_Param, table); + AddUnescapedChars("./:;=?@[\\]^`{|}", esc_Query, table); + AddUnescapedChars("#'./:;=?@[\\]^{|}", esc_Ref, table); + AddUnescapedChars("#'./:;=?@[]", esc_ExtHandler, table); + + return table; +} + +static constexpr std::array<uint32_t, 256> EscapeChars = BuildEscapeChars(); + +static bool dontNeedEscape(unsigned char aChar, uint32_t aFlags) { + return EscapeChars[(size_t)aChar] & aFlags; +} +static bool dontNeedEscape(uint16_t aChar, uint32_t aFlags) { + return aChar < EscapeChars.size() ? (EscapeChars[(size_t)aChar] & aFlags) + : false; +} + +//---------------------------------------------------------------------------------------- + +/** + * Templated helper for URL escaping a portion of a string. + * + * @param aPart The pointer to the beginning of the portion of the string to + * escape. + * @param aPartLen The length of the string to escape. + * @param aFlags Flags used to configure escaping. @see EscapeMask + * @param aResult String that has the URL escaped portion appended to. Only + * altered if the string is URL escaped or |esc_AlwaysCopy| is specified. + * @param aDidAppend Indicates whether or not data was appended to |aResult|. + * @return NS_ERROR_INVALID_ARG, NS_ERROR_OUT_OF_MEMORY on failure. + */ +template <class T> +static nsresult T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, + uint32_t aFlags, const ASCIIMaskArray* aFilterMask, + T& aResult, bool& aDidAppend) { + typedef nsCharTraits<typename T::char_type> traits; + typedef typename traits::unsigned_char_type unsigned_char_type; + static_assert(sizeof(*aPart) == 1 || sizeof(*aPart) == 2, + "unexpected char type"); + + if (!aPart) { + MOZ_ASSERT_UNREACHABLE("null pointer"); + return NS_ERROR_INVALID_ARG; + } + + bool forced = !!(aFlags & esc_Forced); + bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII); + bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII); + bool writing = !!(aFlags & esc_AlwaysCopy); + bool colon = !!(aFlags & esc_Colon); + bool spaces = !!(aFlags & esc_Spaces); + + auto src = reinterpret_cast<const unsigned_char_type*>(aPart); + + typename T::char_type tempBuffer[100]; + unsigned int tempBufferPos = 0; + + for (size_t i = 0; i < aPartLen; ++i) { + unsigned_char_type c = *src++; + + // If there is a filter, we wish to skip any characters which match it. + // This is needed so we don't perform an extra pass just to extract the + // filtered characters. + if (aFilterMask && mozilla::ASCIIMask::IsMasked(*aFilterMask, c)) { + if (!writing) { + if (!aResult.Append(aPart, i, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + writing = true; + } + continue; + } + + // if the char has not to be escaped or whatever follows % is + // a valid escaped string, just copy the char. + // + // Also the % will not be escaped until forced + // See bugzilla bug 61269 for details why we changed this + // + // And, we will not escape non-ascii characters if requested. + // On special request we will also escape the colon even when + // not covered by the matrix. + // ignoreAscii is not honored for control characters (C0 and DEL) + // + // 0x20..0x7e are the valid ASCII characters. + if ((dontNeedEscape(c, aFlags) || (c == HEX_ESCAPE && !forced) || + (c > 0x7f && ignoreNonAscii) || + (c >= 0x20 && c < 0x7f && ignoreAscii)) && + !(c == ':' && colon) && !(c == ' ' && spaces)) { + if (writing) { + tempBuffer[tempBufferPos++] = c; + } + } else { /* do the escape magic */ + if (!writing) { + if (!aResult.Append(aPart, i, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + writing = true; + } + uint32_t len = ::AppendPercentHex(tempBuffer + tempBufferPos, c); + tempBufferPos += len; + MOZ_ASSERT(len <= ENCODE_MAX_LEN, "potential buffer overflow"); + } + + // Flush the temp buffer if it doesnt't have room for another encoded char. + if (tempBufferPos >= mozilla::ArrayLength(tempBuffer) - ENCODE_MAX_LEN) { + NS_ASSERTION(writing, "should be writing"); + if (!aResult.Append(tempBuffer, tempBufferPos, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + tempBufferPos = 0; + } + } + if (writing) { + if (!aResult.Append(tempBuffer, tempBufferPos, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + aDidAppend = writing; + return NS_OK; +} + +bool NS_EscapeURL(const char* aPart, int32_t aPartLen, uint32_t aFlags, + nsACString& aResult) { + size_t partLen; + if (aPartLen < 0) { + partLen = strlen(aPart); + } else { + partLen = aPartLen; + } + + return NS_EscapeURLSpan(mozilla::Span(aPart, partLen), aFlags, aResult); +} + +bool NS_EscapeURLSpan(mozilla::Span<const char> aStr, uint32_t aFlags, + nsACString& aResult) { + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Elements(), aStr.Length(), aFlags, nullptr, + aResult, appended); + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsACString::char_type)); + } + + return appended; +} + +nsresult NS_EscapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult, const mozilla::fallible_t&) { + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, nullptr, + aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + aResult = aStr; + } + + return rv; +} + +nsresult NS_EscapeAndFilterURL(const nsACString& aStr, uint32_t aFlags, + const ASCIIMaskArray* aFilterMask, + nsACString& aResult, + const mozilla::fallible_t&) { + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aFilterMask, + aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + if (!aResult.Assign(aStr, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return rv; +} + +const nsAString& NS_EscapeURL(const nsAString& aStr, uint32_t aFlags, + nsAString& aResult) { + bool result = false; + nsresult rv = T_EscapeURL<nsAString>(aStr.Data(), aStr.Length(), aFlags, + nullptr, aResult, result); + + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsAString::char_type)); + } + + if (result) { + return aResult; + } + return aStr; +} + +// Starting at aStr[aStart] find the first index in aStr that matches any +// character that is forbidden by aFunction. Return false if not found. +static bool FindFirstMatchFrom(const nsString& aStr, size_t aStart, + const std::function<bool(char16_t)>& aFunction, + size_t* aIndex) { + for (size_t j = aStart, l = aStr.Length(); j < l; ++j) { + if (aFunction(aStr[j])) { + *aIndex = j; + return true; + } + } + return false; +} + +const nsAString& NS_EscapeURL(const nsString& aStr, + const std::function<bool(char16_t)>& aFunction, + nsAString& aResult) { + bool didEscape = false; + for (size_t i = 0, strLen = aStr.Length(); i < strLen;) { + size_t j; + if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aFunction, &j))) { + if (i == 0) { + didEscape = true; + aResult.Truncate(); + aResult.SetCapacity(aStr.Length()); + } + if (j != i) { + // The substring from 'i' up to 'j' that needs no escaping. + aResult.Append(nsDependentSubstring(aStr, i, j - i)); + } + char16_t buffer[ENCODE_MAX_LEN]; + uint32_t bufferLen = ::AppendPercentHex(buffer, aStr[j]); + MOZ_ASSERT(bufferLen <= ENCODE_MAX_LEN, "buffer overflow"); + aResult.Append(buffer, bufferLen); + i = j + 1; + } else { + if (MOZ_UNLIKELY(didEscape)) { + // The tail of the string that needs no escaping. + aResult.Append(nsDependentSubstring(aStr, i, strLen - i)); + } + break; + } + } + if (MOZ_UNLIKELY(didEscape)) { + return aResult; + } + return aStr; +} + +bool NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult) { + bool didAppend = false; + nsresult rv = + NS_UnescapeURL(aStr, aLen, aFlags, aResult, didAppend, mozilla::fallible); + if (rv == NS_ERROR_OUT_OF_MEMORY) { + ::NS_ABORT_OOM(aLen * sizeof(nsACString::char_type)); + } + + return didAppend; +} + +nsresult NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult, bool& aDidAppend, + const mozilla::fallible_t&) { + if (!aStr) { + MOZ_ASSERT_UNREACHABLE("null pointer"); + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(aResult.IsEmpty(), + "Passing a non-empty string as an out parameter!"); + + uint32_t len; + if (aLen < 0) { + size_t stringLength = strlen(aStr); + if (stringLength >= UINT32_MAX) { + return NS_ERROR_OUT_OF_MEMORY; + } + len = stringLength; + } else { + len = aLen; + } + + bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII); + bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII); + bool writing = !!(aFlags & esc_AlwaysCopy); + bool skipControl = !!(aFlags & esc_SkipControl); + bool skipInvalidHostChar = !!(aFlags & esc_Host); + + unsigned char* destPtr; + uint32_t destPos; + + if (writing) { + if (!aResult.SetLength(len, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + destPos = 0; + destPtr = reinterpret_cast<unsigned char*>(aResult.BeginWriting()); + } + + const char* last = aStr; + const char* end = aStr + len; + + for (const char* p = aStr; p < end; ++p) { + if (*p == HEX_ESCAPE && p + 2 < end) { + unsigned char c1 = *((unsigned char*)p + 1); + unsigned char c2 = *((unsigned char*)p + 2); + unsigned char u = (UNHEX(c1) << 4) + UNHEX(c2); + if (mozilla::IsAsciiHexDigit(c1) && mozilla::IsAsciiHexDigit(c2) && + (!skipInvalidHostChar || dontNeedEscape(u, aFlags) || c1 >= '8') && + ((c1 < '8' && !ignoreAscii) || (c1 >= '8' && !ignoreNonAscii)) && + !(skipControl && + (c1 < '2' || (c1 == '7' && (c2 == 'f' || c2 == 'F'))))) { + if (MOZ_UNLIKELY(!writing)) { + writing = true; + if (!aResult.SetLength(len, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + destPos = 0; + destPtr = reinterpret_cast<unsigned char*>(aResult.BeginWriting()); + } + if (p > last) { + auto toCopy = p - last; + memcpy(destPtr + destPos, last, toCopy); + destPos += toCopy; + MOZ_ASSERT(destPos <= len); + last = p; + } + destPtr[destPos] = u; + destPos += 1; + MOZ_ASSERT(destPos <= len); + p += 2; + last += 3; + } + } + } + if (writing && last < end) { + auto toCopy = end - last; + memcpy(destPtr + destPos, last, toCopy); + destPos += toCopy; + MOZ_ASSERT(destPos <= len); + } + + if (writing) { + aResult.Truncate(destPos); + } + + aDidAppend = writing; + return NS_OK; +} diff --git a/xpcom/io/nsEscape.h b/xpcom/io/nsEscape.h new file mode 100644 index 0000000000..0e86fe8487 --- /dev/null +++ b/xpcom/io/nsEscape.h @@ -0,0 +1,243 @@ +/* -*- 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/. */ + +/* First checked in on 98/12/03 by John R. McMullen, derived from + * net.h/mkparse.c. */ + +#ifndef _ESCAPE_H_ +#define _ESCAPE_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" +#include <functional> + +/** + * Valid mask values for nsEscape + * Note: these values are copied in nsINetUtil.idl. Any changes should be kept + * in sync. + */ +typedef enum { + url_All = 0, // %-escape every byte unconditionally + url_XAlphas = + 1u << 0, // Normal escape - leave alphas intact, escape the rest + url_XPAlphas = + 1u + << 1, // As url_XAlphas, but convert spaces (0x20) to '+' and plus to %2B + url_Path = 1u << 2, // As url_XAlphas, but don't escape slash ('/') + url_NSURLRef = 1u << 3 // Extra URL ref encoding required for Apple's + // NSURL compatibility +} nsEscapeMask; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Escape the given string according to mask + * @param aSstr The string to escape + * @param aLength The length of the string to escape + * @param aOutputLen A pointer that will be used to store the length of the + * output string, if not null + * @param aMask How to escape the string + * @return A newly allocated escaped string that must be free'd with + * nsCRT::free, or null on failure + * @note: Please, don't use this function. Use NS_Escape instead! + */ +char* nsEscape(const char* aStr, size_t aLength, size_t* aOutputLen, + nsEscapeMask aMask); + +/** + * Decodes '%'-escaped hex codes into character values, modifies the parameter, + * returns the same buffer + */ +char* nsUnescape(char* aStr); + +/** + * Decodes '%'-escaped hex codes into character values, modifies the parameter + * buffer, returns the length of the result (result may contain \0's). + */ +int32_t nsUnescapeCount(char* aStr); + +#ifdef __cplusplus +} +#endif + +/** + * Infallibly append aSrc to aDst, escaping chars that are problematic for HTML + * display. + */ +void nsAppendEscapedHTML(const nsACString& aSrc, nsACString& aDst); + +/** + * NS_EscapeURL/NS_UnescapeURL constants for |flags| parameter: + * + * Note: These values are copied to nsINetUtil.idl + * Any changes should be kept in sync + */ +enum EscapeMask { + /** url components **/ + esc_Scheme = 1u << 0, + esc_Username = 1u << 1, + esc_Password = 1u << 2, + esc_Host = 1u << 3, + esc_Directory = 1u << 4, + esc_FileBaseName = 1u << 5, + esc_FileExtension = 1u << 6, + esc_FilePath = esc_Directory | esc_FileBaseName | esc_FileExtension, + esc_Param = 1u << 7, + esc_Query = 1u << 8, + esc_Ref = 1u << 9, + /** special flags **/ + esc_Minimal = esc_Scheme | esc_Username | esc_Password | esc_Host | + esc_FilePath | esc_Param | esc_Query | esc_Ref, + esc_Forced = 1u << 10, /* forces escaping of existing escape sequences */ + esc_OnlyASCII = 1u << 11, /* causes non-ascii octets to be skipped */ + esc_OnlyNonASCII = + 1u << 12, /* causes _graphic_ ascii octets (0x20-0x7E) + * to be skipped when escaping. causes all + * ascii octets (<= 0x7F) to be skipped when unescaping */ + esc_AlwaysCopy = + 1u << 13, /* copy input to result buf even if escaping is unnecessary */ + esc_Colon = 1u << 14, /* forces escape of colon */ + esc_SkipControl = 1u << 15, /* skips C0 and DEL from unescaping */ + esc_Spaces = 1u << 16, /* forces escape of spaces */ + esc_ExtHandler = 1u << 17 /* For escaping external protocol handler urls. + * Escapes everything except: + * a-z, 0-9 and !#$&'()*+,-./:;=?@[]_~ */ +}; + +/** + * NS_EscapeURL + * + * Escapes invalid char's in an URL segment. Has no side-effect if the URL + * segment is already escaped, unless aFlags has the esc_Forced bit in which + * case % will also be escaped. Iff some part of aStr is escaped is the + * final result appended to aResult. You can also request that aStr is + * always appended to aResult with esc_AlwaysCopy. + * + * @param aStr url segment string + * @param aLen url segment string length (-1 if unknown) + * @param aFlags url segment type flag (see EscapeMask above) + * @param aResult result buffer, untouched if aStr is already escaped unless + * aFlags has esc_AlwaysCopy + * + * @return true if aResult was written to (i.e. at least one character was + * escaped or esc_AlwaysCopy was requested), false otherwise. + */ +bool NS_EscapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult); + +bool NS_EscapeURLSpan(mozilla::Span<const char> aStr, uint32_t aFlags, + nsACString& aResult); + +/** + * Expands URL escape sequences... beware embedded null bytes! + * + * @param aStr url string to unescape + * @param aLen length of aStr + * @param aFlags only esc_OnlyNonASCII, esc_SkipControl and esc_AlwaysCopy + * are recognized + * @param aResult result buffer, untouched if aStr is already unescaped unless + * aFlags has esc_AlwaysCopy + * + * @return true if aResult was written to (i.e. at least one character was + * unescaped or esc_AlwaysCopy was requested), false otherwise. + */ +bool NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult); + +/** + * Fallible version of |NS_UnescapeURL|. See above for details. + * + * @param aAppended Out param: true if aResult was written to (i.e. at least + * one character was unescaped or esc_AlwaysCopy was + * requested), false otherwise. + */ +nsresult NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult, bool& aAppended, + const mozilla::fallible_t&); + +/** returns resultant string length **/ +inline int32_t NS_UnescapeURL(char* aStr) { return nsUnescapeCount(aStr); } + +/** + * String friendly versions... + */ +inline const nsACString& NS_EscapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + if (NS_EscapeURLSpan(aStr, aFlags, aResult)) { + return aResult; + } + return aStr; +} + +/** + * Fallible version of NS_EscapeURL. On success aResult will point to either + * the original string or an escaped copy. + */ +nsresult NS_EscapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult, const mozilla::fallible_t&); + +// Forward declaration for nsASCIIMask.h +typedef std::array<bool, 128> ASCIIMaskArray; + +/** + * The same as NS_EscapeURL, except it also filters out characters that match + * aFilterMask. + */ +nsresult NS_EscapeAndFilterURL(const nsACString& aStr, uint32_t aFlags, + const ASCIIMaskArray* aFilterMask, + nsACString& aResult, const mozilla::fallible_t&); + +inline const nsACString& NS_UnescapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + if (NS_UnescapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) { + return aResult; + } + return aStr; +} + +const nsAString& NS_EscapeURL(const nsAString& aStr, uint32_t aFlags, + nsAString& aResult); + +/** + * Percent-escapes all characters in aStr that occurs in aForbidden. + * @param aStr the input URL string + * @param aFunction returns true for characters that should be escaped + * @param aResult the result if some characters were escaped + * @return aResult if some characters were escaped, or aStr otherwise (aResult + * is unmodified in that case) + */ +const nsAString& NS_EscapeURL(const nsString& aStr, + const std::function<bool(char16_t)>& aFunction, + nsAString& aResult); + +/** + * CString version of nsEscape. Returns true on success, false + * on out of memory. To reverse this function, use NS_UnescapeURL. + */ +inline bool NS_Escape(const nsACString& aOriginal, nsACString& aEscaped, + nsEscapeMask aMask) { + size_t escLen = 0; + char* esc = + nsEscape(aOriginal.BeginReading(), aOriginal.Length(), &escLen, aMask); + if (!esc) { + return false; + } + aEscaped.Adopt(esc, escLen); + return true; +} + +/** + * Inline unescape of mutable string object. + */ +inline nsACString& NS_UnescapeURL(nsACString& aStr) { + aStr.SetLength(nsUnescapeCount(aStr.BeginWriting())); + return aStr; +} + +#endif // _ESCAPE_H_ diff --git a/xpcom/io/nsIAsyncInputStream.idl b/xpcom/io/nsIAsyncInputStream.idl new file mode 100644 index 0000000000..8dbe442663 --- /dev/null +++ b/xpcom/io/nsIAsyncInputStream.idl @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIInputStream.idl" + +interface nsIInputStreamCallback; +interface nsIEventTarget; + +/** + * If an input stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK + * when read. The caller must then wait for the stream to have some data to + * read. If the stream implements nsIAsyncInputStream, then the caller can use + * this interface to request an asynchronous notification when the stream + * becomes readable or closed (via the AsyncWait method). + * + * While this interface is almost exclusively used with non-blocking streams, it + * is not necessary that nsIInputStream::isNonBlocking return true. Nor is it + * necessary that a non-blocking nsIInputStream implementation also implement + * nsIAsyncInputStream. + */ +[scriptable, builtinclass, uuid(a5f255ab-4801-4161-8816-277ac92f6ad1)] +interface nsIAsyncInputStream : nsIInputStream +{ + /** + * This method closes the stream and sets its internal status. If the + * stream is already closed, then this method is ignored. Once the stream + * is closed, the stream's status cannot be changed. Any successful status + * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which + * has an effect equivalent to nsIInputStream::close. + * + * NOTE: this method exists in part to support pipes, which have both an + * input end and an output end. If the input end of a pipe is closed, then + * writes to the output end of the pipe will fail. The error code returned + * when an attempt is made to write to a "broken" pipe corresponds to the + * status code passed in when the input end of the pipe was closed, which + * greatly simplifies working with pipes in some cases. + * + * @param aStatus + * The error that will be reported if this stream is accessed after + * it has been closed. + */ + void closeWithStatus(in nsresult aStatus); + + /** + * Asynchronously wait for the stream to be readable or closed. The + * notification is one-shot, meaning that each asyncWait call will result + * in exactly one notification callback. After the OnInputStreamReady event + * is dispatched, the stream releases its reference to the + * nsIInputStreamCallback object. It is safe to call asyncWait again from the + * notification handler. + * + * This method may be called at any time (even if read has not been called). + * In other words, this method may be called when the stream already has + * data to read. It may also be called when the stream is closed and will NOT + * result in an error return, e.g., NS_BASE_STREAM_CLOSED. If the stream is + * already readable or closed when AsyncWait is called, then the + * OnInputStreamReady event will be dispatched immediately. Otherwise, the + * event will be dispatched when the stream becomes readable or closed. + * + * @param aCallback + * This object is notified when the stream becomes ready. This + * parameter may be null to clear an existing callback. + * @param aFlags + * This parameter specifies optional flags passed in to configure + * the behavior of this method. Pass zero to specify no flags. + * @param aRequestedCount + * Wait until at least this many bytes can be read. This is only + * a suggestion to the underlying stream; it may be ignored. The + * caller may pass zero to indicate no preference. + * @param aEventTarget + * Specify NULL to receive notification on ANY thread (possibly even + * recursively on the calling thread -- i.e., synchronously), or + * specify that the notification be delivered to a specific event + * target. + */ + void asyncWait(in nsIInputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); + + /** + * If passed to asyncWait, this flag overrides the default behavior, + * causing the OnInputStreamReady notification to be suppressed until the + * stream becomes closed (either as a result of closeWithStatus/close being + * called on the stream or possibly due to some error in the underlying + * stream). + */ + const unsigned long WAIT_CLOSURE_ONLY = (1<<0); +}; + +/** + * This is a companion interface for nsIAsyncInputStream::asyncWait. + */ +[function, scriptable, uuid(d1f28e94-3a6e-4050-a5f5-2e81b1fc2a43)] +interface nsIInputStreamCallback : nsISupports +{ + /** + * Called to indicate that the stream is either readable or closed. + * + * @param aStream + * The stream whose asyncWait method was called. + */ + void onInputStreamReady(in nsIAsyncInputStream aStream); +}; diff --git a/xpcom/io/nsIAsyncOutputStream.idl b/xpcom/io/nsIAsyncOutputStream.idl new file mode 100644 index 0000000000..88f9d107cf --- /dev/null +++ b/xpcom/io/nsIAsyncOutputStream.idl @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIOutputStream.idl" + +interface nsIOutputStreamCallback; +interface nsIEventTarget; + +/** + * If an output stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK + * when written to. The caller must then wait for the stream to become + * writable. If the stream implements nsIAsyncOutputStream, then the caller can + * use this interface to request an asynchronous notification when the stream + * becomes writable or closed (via the AsyncWait method). + * + * While this interface is almost exclusively used with non-blocking streams, it + * is not necessary that nsIOutputStream::isNonBlocking return true. Nor is it + * necessary that a non-blocking nsIOutputStream implementation also implement + * nsIAsyncOutputStream. + */ +[scriptable, builtinclass, uuid(beb632d3-d77a-4e90-9134-f9ece69e8200)] +interface nsIAsyncOutputStream : nsIOutputStream +{ + /** + * This method closes the stream and sets its internal status. If the + * stream is already closed, then this method is ignored. Once the stream + * is closed, the stream's status cannot be changed. Any successful status + * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which + * is equivalent to nsIInputStream::close. + * + * NOTE: this method exists in part to support pipes, which have both an + * input end and an output end. If the output end of a pipe is closed, then + * reads from the input end of the pipe will fail. The error code returned + * when an attempt is made to read from a "closed" pipe corresponds to the + * status code passed in when the output end of the pipe is closed, which + * greatly simplifies working with pipes in some cases. + * + * @param aStatus + * The error that will be reported if this stream is accessed after + * it has been closed. + */ + void closeWithStatus(in nsresult reason); + + /** + * Asynchronously wait for the stream to be writable or closed. The + * notification is one-shot, meaning that each asyncWait call will result + * in exactly one notification callback. After the OnOutputStreamReady event + * is dispatched, the stream releases its reference to the + * nsIOutputStreamCallback object. It is safe to call asyncWait again from the + * notification handler. + * + * This method may be called at any time (even if write has not been called). + * In other words, this method may be called when the stream already has + * room for more data. It may also be called when the stream is closed. If + * the stream is already writable or closed when AsyncWait is called, then the + * OnOutputStreamReady event will be dispatched immediately. Otherwise, the + * event will be dispatched when the stream becomes writable or closed. + * + * @param aCallback + * This object is notified when the stream becomes ready. This + * parameter may be null to clear an existing callback. + * @param aFlags + * This parameter specifies optional flags passed in to configure + * the behavior of this method. Pass zero to specify no flags. + * @param aRequestedCount + * Wait until at least this many bytes can be written. This is only + * a suggestion to the underlying stream; it may be ignored. The + * caller may pass zero to indicate no preference. + * @param aEventTarget + * Specify NULL to receive notification on ANY thread (possibly even + * recursively on the calling thread -- i.e., synchronously), or + * specify that the notification be delivered to a specific event + * target. + */ + void asyncWait(in nsIOutputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); + + /** + * If passed to asyncWait, this flag overrides the default behavior, + * causing the OnOutputStreamReady notification to be suppressed until the + * stream becomes closed (either as a result of closeWithStatus/close being + * called on the stream or possibly due to some error in the underlying + * stream). + */ + const unsigned long WAIT_CLOSURE_ONLY = (1<<0); +}; + +/** + * This is a companion interface for nsIAsyncOutputStream::asyncWait. + */ +[function, scriptable, uuid(40dbcdff-9053-42c5-a57c-3ec910d0f148)] +interface nsIOutputStreamCallback : nsISupports +{ + /** + * Called to indicate that the stream is either writable or closed. + * + * @param aStream + * The stream whose asyncWait method was called. + */ + void onOutputStreamReady(in nsIAsyncOutputStream aStream); +}; diff --git a/xpcom/io/nsIBinaryInputStream.idl b/xpcom/io/nsIBinaryInputStream.idl new file mode 100644 index 0000000000..698dbae4f8 --- /dev/null +++ b/xpcom/io/nsIBinaryInputStream.idl @@ -0,0 +1,118 @@ +/* -*- 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 "nsIInputStream.idl" + +/** + * This interface allows consumption of primitive data types from a "binary + * stream" containing untagged, big-endian binary data, i.e. as produced by an + * implementation of nsIBinaryOutputStream. This might be used, for example, + * to implement network protocols or to read from architecture-neutral disk + * files, i.e. ones that can be read and written by both big-endian and + * little-endian platforms. + * + * @See nsIBinaryOutputStream + */ + +[scriptable, builtinclass, uuid(899b826b-2eb3-469c-8b31-4c29f5d341a6)] +interface nsIBinaryInputStream : nsIInputStream { + void setInputStream(in nsIInputStream aInputStream); + + /** + * Read 8-bits from the stream. + * + * @return that byte to be treated as a boolean. + */ + boolean readBoolean(); + + uint8_t read8(); + uint16_t read16(); + uint32_t read32(); + uint64_t read64(); + + float readFloat(); + double readDouble(); + + /** + * Read an 8-bit pascal style string from the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + ACString readCString(); + + /** + * Read an 16-bit pascal style string from the stream. + * 32-bit length field, followed by length PRUnichars. + */ + AString readString(); + + /** + * Read an opaque byte array from the stream. + * + * @param aLength the number of bytes that must be read. + * + * @throws NS_ERROR_FAILURE if it can't read aLength bytes + */ + void readBytes(in uint32_t aLength, + [size_is(aLength), retval] out string aString); + + /** + * Read an opaque byte array from the stream, storing the results + * as an array of PRUint8s. + * + * @param aLength the number of bytes that must be read. + * + * @throws NS_ERROR_FAILURE if it can't read aLength bytes + */ + Array<uint8_t> readByteArray(in uint32_t aLength); + + /** + * Read opaque bytes from the stream, storing the results in an ArrayBuffer. + * + * @param aLength the number of bytes that must be read + * @param aArrayBuffer the arraybuffer in which to store the results + * Note: passing view.buffer, where view is an ArrayBufferView of an + * ArrayBuffer, is not valid unless view.byteOffset == 0. + * + * @return The number of bytes actually read into aArrayBuffer. + */ + [implicit_jscontext] + uint64_t readArrayBuffer(in uint64_t aLength, in jsval aArrayBuffer); +}; + +%{C++ + +#ifdef MOZILLA_INTERNAL_API +#include "nsString.h" + +inline nsresult +NS_ReadOptionalCString(nsIBinaryInputStream* aStream, nsACString& aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadCString(aResult); + else + aResult.Truncate(); + } + return rv; +} + +inline nsresult +NS_ReadOptionalString(nsIBinaryInputStream* aStream, nsAString& aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadString(aResult); + else + aResult.Truncate(); + } + return rv; +} +#endif + +%} diff --git a/xpcom/io/nsIBinaryOutputStream.idl b/xpcom/io/nsIBinaryOutputStream.idl new file mode 100644 index 0000000000..caa96eb699 --- /dev/null +++ b/xpcom/io/nsIBinaryOutputStream.idl @@ -0,0 +1,103 @@ +/* -*- 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 "nsIOutputStream.idl" + +%{C++ +namespace mozilla { +template<class ElementType, size_t Extent> class Span; +} +%} +native Bytes(mozilla::Span<const uint8_t>); + +/** + * This interface allows writing of primitive data types (integers, + * floating-point values, booleans, etc.) to a stream in a binary, untagged, + * fixed-endianness format. This might be used, for example, to implement + * network protocols or to produce architecture-neutral binary disk files, + * i.e. ones that can be read and written by both big-endian and little-endian + * platforms. Output is written in big-endian order (high-order byte first), + * as this is traditional network order. + * + * @See nsIBinaryInputStream + */ + +[scriptable, builtinclass, uuid(204ee610-8765-11d3-90cf-0040056a906e)] +interface nsIBinaryOutputStream : nsIOutputStream { + void setOutputStream(in nsIOutputStream aOutputStream); + + /** + * Write a boolean as an 8-bit char to the stream. + */ + void writeBoolean(in boolean aBoolean); + + void write8(in uint8_t aByte); + void write16(in uint16_t a16); + void write32(in uint32_t a32); + void write64(in uint64_t a64); + + void writeFloat(in float aFloat); + void writeDouble(in double aDouble); + + /** + * Write an 8-bit pascal style string to the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + void writeStringZ(in string aString); + + /** + * Write a 16-bit pascal style string to the stream. + * 32-bit length field, followed by length PRUnichars. + */ + void writeWStringZ(in wstring aString); + + /** + * Write an 8-bit pascal style string (UTF8-encoded) to the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + void writeUtf8Z(in wstring aString); + + /** + * Write an opaque byte array to the stream. + */ + [binaryname(WriteBytesFromJS)] + void writeBytes([size_is(aLength)] in string aString, + [optional] in uint32_t aLength); + + /** + * Non-scriptable and saner-signature version of the same. + */ + [noscript, nostdcall, binaryname(WriteBytes)] + void writeBytesNative(in Bytes aBytes); + + /** + * Write an opaque byte array to the stream. + */ + void writeByteArray(in Array<uint8_t> aBytes); +}; + +%{C++ + +inline nsresult +NS_WriteOptionalStringZ(nsIBinaryOutputStream* aStream, const char* aString) +{ + bool nonnull = (aString != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteStringZ(aString); + return rv; +} + +inline nsresult +NS_WriteOptionalWStringZ(nsIBinaryOutputStream* aStream, const char16_t* aString) +{ + bool nonnull = (aString != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteWStringZ(aString); + return rv; +} + +%} diff --git a/xpcom/io/nsICloneableInputStream.idl b/xpcom/io/nsICloneableInputStream.idl new file mode 100644 index 0000000000..adefd0f428 --- /dev/null +++ b/xpcom/io/nsICloneableInputStream.idl @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIInputStream.idl" + +[scriptable, builtinclass, uuid(8149be1f-44d3-4f14-8b65-a57a5fbbeb97)] +interface nsICloneableInputStream : nsISupports +{ + // Allow streams that implement the interface to determine if cloning + // possible at runtime. For example, this allows wrappers to check if + // their base stream supports cloning. + [infallible] readonly attribute boolean cloneable; + + // Produce a copy of the current stream in the most efficient way possible. + // In this case "copy" means that both the original and cloned streams + // should produce the same bytes for all future reads. Bytes that have + // already been consumed from the original stream are not copied to the + // clone. Operations on the two streams should be completely independent + // after the clone() occurs. + nsIInputStream clone(); +}; + +// This interface implements cloneWithRange() because for some streams +// (RemoteLazyInputStream only, so far) are more efficient to produce a sub +// stream with range than doing clone + SlicedInputStream(). +[scriptable, builtinclass, uuid(ece853c3-aded-4cef-8f51-0d1493d60bd5)] +interface nsICloneableInputStreamWithRange : nsICloneableInputStream +{ + nsIInputStream cloneWithRange(in uint64_t start, in uint64_t length); +}; diff --git a/xpcom/io/nsIConverterInputStream.idl b/xpcom/io/nsIConverterInputStream.idl new file mode 100644 index 0000000000..ad1f9bfbc4 --- /dev/null +++ b/xpcom/io/nsIConverterInputStream.idl @@ -0,0 +1,45 @@ +/* -*- 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 "nsIUnicharInputStream.idl" + +interface nsIInputStream; + +/** + * A unichar input stream that wraps an input stream. + * This allows reading unicode strings from a stream, automatically converting + * the bytes from a selected character encoding. + */ +[scriptable, uuid(FC66FFB6-5404-4908-A4A3-27F92FA0579D)] +interface nsIConverterInputStream : nsIUnicharInputStream { + /** + * Default replacement char value, U+FFFD REPLACEMENT CHARACTER. + */ + const char16_t DEFAULT_REPLACEMENT_CHARACTER = 0xFFFD; + + /** + * Special replacement character value that requests errors to + * be treated as fatal. + */ + const char16_t ERRORS_ARE_FATAL = 0; + + /** + * Initialize this stream. + * @param aStream + * The underlying stream to read from. + * @param aCharset + * The character encoding to use for converting the bytes of the + * stream. A null charset will be interpreted as UTF-8. + * @param aBufferSize + * How many bytes to buffer. + * @param aReplacementChar + * The character to replace unknown byte sequences in the stream + * with. The standard replacement character is U+FFFD. + * A value of 0x0000 will cause an exception to be thrown if unknown + * byte sequences are encountered in the stream. + */ + void init (in nsIInputStream aStream, in string aCharset, + in long aBufferSize, in char16_t aReplacementChar); +}; diff --git a/xpcom/io/nsIConverterOutputStream.idl b/xpcom/io/nsIConverterOutputStream.idl new file mode 100644 index 0000000000..41faa5332d --- /dev/null +++ b/xpcom/io/nsIConverterOutputStream.idl @@ -0,0 +1,30 @@ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIUnicharOutputStream.idl" + +interface nsIOutputStream; + +/** + * This interface allows writing strings to a stream, doing automatic + * character encoding conversion. + */ +[scriptable, builtinclass, uuid(4b71113a-cb0d-479f-8ed5-01daeba2e8d4)] +interface nsIConverterOutputStream : nsIUnicharOutputStream +{ + /** + * Initialize this stream. Must be called before any other method on this + * interface, or you will crash. The output stream passed to this method + * must not be null, or you will crash. + * + * @param aOutStream + * The underlying output stream to which the converted strings will + * be written. + * @param aCharset + * The character set to use for encoding the characters. A null + * charset will be interpreted as UTF-8. + */ + void init(in nsIOutputStream aOutStream, in string aCharset); +}; diff --git a/xpcom/io/nsIDirectoryEnumerator.idl b/xpcom/io/nsIDirectoryEnumerator.idl new file mode 100644 index 0000000000..e8abb81acd --- /dev/null +++ b/xpcom/io/nsIDirectoryEnumerator.idl @@ -0,0 +1,34 @@ +/* -*- 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" +#include "nsISimpleEnumerator.idl" + +interface nsIFile; + +/** + * This interface provides a means for enumerating the contents of a directory. + * It is similar to nsISimpleEnumerator except the retrieved entries are QI'ed + * to nsIFile, and there is a mechanism for closing the directory when the + * enumeration is complete. + */ +[scriptable, uuid(31f7f4ae-6916-4f2d-a81e-926a4e3022ee)] +interface nsIDirectoryEnumerator : nsISimpleEnumerator +{ + /** + * Retrieves the next file in the sequence. The "nextFile" element is the + * first element upon the first call. This attribute is null if there is no + * next element. + */ + readonly attribute nsIFile nextFile; + + /** + * Closes the directory being enumerated, releasing the system resource. + * @throws NS_OK if the call succeeded and the directory was closed. + * NS_ERROR_FAILURE if the directory close failed. + * It is safe to call this function many times. + */ + void close(); +}; diff --git a/xpcom/io/nsIDirectoryService.idl b/xpcom/io/nsIDirectoryService.idl new file mode 100644 index 0000000000..99c4324782 --- /dev/null +++ b/xpcom/io/nsIDirectoryService.idl @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsISimpleEnumerator; + +/** + * nsIDirectoryServiceProvider + * + * Used by Directory Service to get file locations. + */ + +[scriptable, uuid(bbf8cab0-d43a-11d3-8cc2-00609792278c)] +interface nsIDirectoryServiceProvider: nsISupports +{ + /** + * getFile + * + * Directory Service calls this when it gets the first request for + * a prop or on every request if the prop is not persistent. + * + * @param prop The symbolic name of the file. + * @param persistent TRUE - The returned file will be cached by Directory + * Service. Subsequent requests for this prop will + * bypass the provider and use the cache. + * FALSE - The provider will be asked for this prop + * each time it is requested. + * + * @return The file represented by the property. + * + */ + nsIFile getFile(in string prop, out boolean persistent); +}; + +/** + * nsIDirectoryServiceProvider2 + * + * An extension of nsIDirectoryServiceProvider which allows + * multiple files to be returned for the given key. + */ + +[scriptable, uuid(2f977d4b-5485-11d4-87e2-0010a4e75ef2)] +interface nsIDirectoryServiceProvider2: nsIDirectoryServiceProvider +{ + /** + * getFiles + * + * Directory Service calls this when it gets a request for + * a prop and the requested type is nsISimpleEnumerator. + * + * @param prop The symbolic name of the file list. + * + * @return An enumerator for a list of file locations. + * The elements in the enumeration are nsIFile + * @returnCode NS_SUCCESS_AGGREGATE_RESULT if this result should be + * aggregated with other "lower" providers. + */ + nsISimpleEnumerator getFiles(in string prop); +}; + +/** + * nsIDirectoryService + */ + +[scriptable, uuid(57a66a60-d43a-11d3-8cc2-00609792278c)] +interface nsIDirectoryService: nsISupports +{ + /** + * init + * + * Must be called. Used internally by XPCOM initialization. + * + */ + void init(); + + /** + * registerProvider + * + * Register a provider with the service. + * + * @param prov The service will keep a strong reference + * to this object. It will be released when + * the service is released. + * + */ + void registerProvider(in nsIDirectoryServiceProvider prov); + + /** + * unregisterProvider + * + * Unregister a provider with the service. + * + * @param prov + * + */ + void unregisterProvider(in nsIDirectoryServiceProvider prov); +}; diff --git a/xpcom/io/nsIFile.idl b/xpcom/io/nsIFile.idl new file mode 100644 index 0000000000..535a7a88ee --- /dev/null +++ b/xpcom/io/nsIFile.idl @@ -0,0 +1,597 @@ +/* -*- 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" +#include "nsIDirectoryEnumerator.idl" + +%{C++ +struct PRFileDesc; +struct PRLibrary; +#include <stdio.h> +#include "mozilla/Path.h" +#include "nsCOMPtr.h" +#include "nsStringFwd.h" +namespace mozilla { +using PathString = nsTString<filesystem::Path::value_type>; +using PathSubstring = nsTSubstring<filesystem::Path::value_type>; +} // namespace mozilla +%} + +[ptr] native PRFileDescStar(PRFileDesc); +[ptr] native PRLibraryStar(PRLibrary); +[ptr] native FILE(FILE); +native PathString(mozilla::PathString); + +/** + * An nsIFile is an abstract representation of a filename. It manages + * filename encoding issues, pathname component separators ('/' vs. '\\' + * vs. ':') and weird stuff like differing volumes with identical names, as + * on pre-Darwin Macintoshes. + * + * This file has long introduced itself to new hackers with this opening + * paragraph: + * + * This is the only correct cross-platform way to specify a file. + * Strings are not such a way. If you grew up on windows or unix, you + * may think they are. Welcome to reality. + * + * While taking the pose struck here to heart would be uncalled for, one + * may safely conclude that writing cross-platform code is an embittering + * experience. + * + * All methods with string parameters have two forms. The preferred + * form operates on UCS-2 encoded characters strings. An alternate + * form operates on characters strings encoded in the "native" charset. + * + * A string containing characters encoded in the native charset cannot + * be safely passed to javascript via xpconnect. Therefore, the "native + * methods" are not scriptable. + */ +[scriptable, main_process_scriptable_only, uuid(2fa6884a-ae65-412a-9d4c-ce6e34544ba1), builtinclass] +interface nsIFile : nsISupports +{ + /** + * Create Types + * + * NORMAL_FILE_TYPE - A normal file. + * DIRECTORY_TYPE - A directory/folder. + */ + const unsigned long NORMAL_FILE_TYPE = 0; + const unsigned long DIRECTORY_TYPE = 1; + + /** + * append[Native] + * + * This function is used for constructing a descendent of the + * current nsIFile. + * + * @param node + * A string which is intended to be a child node of the nsIFile. + * For security reasons, this cannot contain .. and cannot start with + * a directory separator. For the |appendNative| method, the node must + * be in the native filesystem charset. + */ + void append(in AString node); + [noscript] void appendNative(in ACString node); + + /** + * Normalize the pathName (e.g. removing .. and . components on Unix). + */ + void normalize(); + + /** + * create + * + * This function will create a new file or directory in the + * file system. Any nodes that have not been created or + * resolved, will be. If the file or directory already + * exists create() will return NS_ERROR_FILE_ALREADY_EXISTS. + * + * @param type + * This specifies the type of file system object + * to be made. The only two types at this time + * are file and directory which are defined above. + * If the type is unrecongnized, we will return an + * error (NS_ERROR_FILE_UNKNOWN_TYPE). + * + * @param permissions + * The unix style octal permissions. This may + * be ignored on systems that do not need to do + * permissions. + * + * @param skipAncestors + * Optional; if set to true, we'll skip creating + * ancestor directories (and return an error instead). + */ + [must_use] void create(in unsigned long type, in unsigned long permissions, + [optional,default(false)] in bool skipAncestors); + + /** + * Accessor to the leaf name of the file itself. + * For the |nativeLeafName| method, the nativeLeafName must + * be in the native filesystem charset. + */ + attribute AString leafName; + [noscript] attribute ACString nativeLeafName; + + /** + * The leaf name as displayed in OS-provided file pickers and similar UI. + * On Windows and macOS, 'real' leaf names of some directories can be + * in English, but the OS will show a different, translated name to users + * using a different locale. So folders like "Downloads", "Desktop" and + * "Documents" might not normally appear to users with that (English) name, + * but with an OS-localized translation. This API will return such a + * translation if it exists, or the leafName if it doesn't. + * On Linux, this will always be the same as `leafName`. + */ + readonly attribute AString displayName; + + /** + * copyTo[Native] + * + * This will copy this file to the specified newParentDir. + * If a newName is specified, the file will be renamed. + * If 'this' is not created we will return an error + * (NS_ERROR_FILE_NOT_FOUND). + * + * copyTo may fail if the file already exists in the destination + * directory. + * + * copyTo will NOT resolve aliases/shortcuts during the copy. + * + * @param newParentDir + * This param is the destination directory. If the + * newParentDir is null, copyTo() will use the parent + * directory of this file. If the newParentDir is not + * empty and is not a directory, an error will be + * returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For the + * |CopyToNative| method, the newName must be in the + * native filesystem charset. + * + * @param newName + * This param allows you to specify a new name for + * the file to be copied. This param may be empty, in + * which case the current leaf name will be used. + */ + void copyTo(in nsIFile newParentDir, in AString newName); + [noscript] void CopyToNative(in nsIFile newParentDir, in ACString newName); + + /** + * copyToFollowingLinks[Native] + * + * This function is identical to copyTo with the exception that, + * as the name implies, it follows symbolic links. The XP_UNIX + * implementation always follow symbolic links when copying. For + * the |CopyToFollowingLinks| method, the newName must be in the + * native filesystem charset. + */ + void copyToFollowingLinks(in nsIFile newParentDir, in AString newName); + [noscript] void copyToFollowingLinksNative(in nsIFile newParentDir, in ACString newName); + + /** + * moveTo[Native] + * + * A method to move this file or directory to newParentDir. + * If a newName is specified, the file or directory will be renamed. + * If 'this' is not created we will return an error + * (NS_ERROR_FILE_NOT_FOUND). + * If 'this' is a file, and the destination file already exists, moveTo + * will replace the old file. + * This object is updated to refer to the new file. + * + * moveTo will NOT resolve aliases/shortcuts during the copy. + * moveTo will do the right thing and allow copies across volumes. + * moveTo will return an error (NS_ERROR_FILE_DIR_NOT_EMPTY) if 'this' is + * a directory and the destination directory is not empty. + * moveTo will return an error (NS_ERROR_FILE_ACCESS_DENIED) if 'this' is + * a directory and the destination directory is not writable. + * + * @param newParentDir + * This param is the destination directory. If the + * newParentDir is empty, moveTo() will rename the file + * within its current directory. If the newParentDir is + * not empty and does not name a directory, an error will + * be returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For + * the |moveToNative| method, the newName must be in the + * native filesystem charset. + * + * @param newName + * This param allows you to specify a new name for + * the file to be moved. This param may be empty, in + * which case the current leaf name will be used. + */ + void moveTo(in nsIFile newParentDir, in AString newName); + [noscript] void moveToNative(in nsIFile newParentDir, in ACString newName); + + /** + * moveToFollowingLinks[Native] + * + * This function is identical to moveTo with the exception that, + * as the name implies, it follows symbolic links. The XP_UNIX + * implementation always follows symbolic links when moving. For + * the |MoveToFollowingLinks| method, the newName ust be in the native + * filesystem charset. + */ + void moveToFollowingLinks(in nsIFile newParentDir, in AString newName); + [noscript] void moveToFollowingLinksNative(in nsIFile newParentDir, in ACString newName); + + /** + * renameTo + * + * This method is identical to moveTo except that if this file or directory + * is moved to a a different volume, it fails and returns an error + * (NS_ERROR_FILE_ACCESS_DENIED). + * This object will still point to the old location after renaming. + */ + void renameTo(in nsIFile newParentDir, in AString newName); + [noscript] void renameToNative(in nsIFile newParentDir, in ACString newName); + + /** + * This will try to delete this file. The 'recursive' flag + * must be PR_TRUE to delete directories which are not empty. + * + * If passed, 'removeCount' will be incremented by the total number of files + * and/or directories removed. Will be 1 unless the 'recursive' flag is + * set. The parameter must be initialized beforehand. + * + * This will not resolve any symlinks. + */ + void remove(in boolean recursive, [optional] inout uint32_t removeCount); + + /** + * Attributes of nsIFile. + */ + + attribute unsigned long permissions; + attribute unsigned long permissionsOfLink; + + /** + * The last accesss time of the file in milliseconds from midnight, January + * 1, 1970 GMT, if available. + */ + attribute PRTime lastAccessedTime; + attribute PRTime lastAccessedTimeOfLink; + + /** + * File Times are to be in milliseconds from + * midnight (00:00:00), January 1, 1970 Greenwich Mean + * Time (GMT). + */ + attribute PRTime lastModifiedTime; + attribute PRTime lastModifiedTimeOfLink; + + /** + * The creation time of file in milliseconds from midnight, January 1, 1970 + * GMT, if available. + * + * This attribute is only implemented on Windows and macOS. Accessing this + * on another platform will this will throw NS_ERROR_NOT_IMPLEMENTED. + */ + readonly attribute PRTime creationTime; + readonly attribute PRTime creationTimeOfLink; + + /** + * WARNING! On the Mac, getting/setting the file size with nsIFile + * only deals with the size of the data fork. If you need to + * know the size of the combined data and resource forks use the + * GetFileSizeWithResFork() method defined on nsILocalFileMac. + */ + attribute int64_t fileSize; + readonly attribute int64_t fileSizeOfLink; + + /** + * target & path + * + * Accessor to the string path. The native version of these + * strings are not guaranteed to be a usable path to pass to + * NSPR or the C stdlib. There are problems that affect + * platforms on which a path does not fully specify a file + * because two volumes can have the same name (e.g., mac). + * This is solved by holding "private", native data in the + * nsIFile implementation. This native data is lost when + * you convert to a string. + * + * DO NOT PASS TO USE WITH NSPR OR STDLIB! + * + * target + * Find out what the symlink points at. Will give error + * (NS_ERROR_FILE_INVALID_PATH) if not a symlink. + * + * path + * Find out what the nsIFile points at. + * + * Note that the ACString attributes are returned in the + * native filesystem charset. + * + */ + readonly attribute AString target; + [noscript] readonly attribute ACString nativeTarget; + readonly attribute AString path; + [notxpcom,nostdcall,must_use] PathString nativePath(); +%{C++ +#ifndef XP_WIN + nsresult GetNativePath(nsACString& aPath); +#endif + /* + * Returns a human-readable path string. + */ + nsCString HumanReadablePath(); +%} + + boolean exists(); + boolean isWritable(); + boolean isReadable(); + boolean isExecutable(); + boolean isHidden(); + boolean isDirectory(); + boolean isFile(); + boolean isSymlink(); + /** + * Not a regular file, not a directory, not a symlink. + */ + boolean isSpecial(); + + /** + * createUnique + * + * This function will create a new file or directory in the + * file system. Any nodes that have not been created or + * resolved, will be. If this file already exists, we try + * variations on the leaf name "suggestedName" until we find + * one that did not already exist. + * + * If the search for nonexistent files takes too long + * (thousands of the variants already exist), we give up and + * return NS_ERROR_FILE_TOO_BIG. + * + * @param type + * This specifies the type of file system object + * to be made. The only two types at this time + * are file and directory which are defined above. + * If the type is unrecongnized, we will return an + * error (NS_ERROR_FILE_UNKNOWN_TYPE). + * + * @param permissions + * The unix style octal permissions. This may + * be ignored on systems that do not need to do + * permissions. + */ + [must_use] + void createUnique(in unsigned long type, in unsigned long permissions); + + /** + * clone() + * + * This function will allocate and initialize a nsIFile object to the + * exact location of the |this| nsIFile. + * + * @param file + * A nsIFile which this object will be initialize + * with. + * + */ + nsIFile clone(); + + /** + * Will determine if the inFile equals this. + */ + boolean equals(in nsIFile inFile); + + /** + * Will determine if inFile is a descendant of this file. + * This routine looks in subdirectories too. + */ + boolean contains(in nsIFile inFile); + + /** + * Parent will be null when this is at the top of the volume. + */ + readonly attribute nsIFile parent; + + /** + * Returns an enumeration of the elements in a directory. Each + * element in the enumeration is an nsIFile. + * + * @throws NS_ERROR_FILE_NOT_DIRECTORY if the current nsIFile does + * not specify a directory. + */ + [binaryname(DirectoryEntriesImpl)] + readonly attribute nsIDirectoryEnumerator directoryEntries; + + %{C++ + nsresult GetDirectoryEntries(nsIDirectoryEnumerator** aOut) + { + return GetDirectoryEntriesImpl(aOut); + }; + %} + + /** + * initWith[Native]Path + * + * This function will initialize the nsIFile object. Any + * internal state information will be reset. + * + * @param filePath + * A string which specifies a full file path to a + * location. Relative paths will be treated as an + * error (NS_ERROR_FILE_UNRECOGNIZED_PATH). For + * initWithNativePath, the filePath must be in the native + * filesystem charset. + */ + void initWithPath(in AString filePath); + [noscript] void initWithNativePath(in ACString filePath); + + /** + * initWithFile + * + * Initialize this object with another file + * + * @param aFile + * the file this becomes equivalent to + */ + void initWithFile(in nsIFile aFile); + + /** + * Flag for openNSPRFileDesc(), to hint to the OS that the file will be + * read sequentially with agressive readahead. + */ + const unsigned long OS_READAHEAD = 0x40000000; + + /** + * Flag for openNSPRFileDesc(). Deprecated and unreliable! + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close! + */ + const unsigned long DELETE_ON_CLOSE = 0x80000000; + + /** + * Return the result of PR_Open on the file. The caller is + * responsible for calling PR_Close on the result. On success, the + * returned PRFileDescr must be non-null. + * + * @param flags the PR_Open flags from prio.h, plus optionally + * OS_READAHEAD or DELETE_ON_CLOSE. OS_READAHEAD is a hint to the + * OS that the file will be read sequentially with agressive + * readahead. DELETE_ON_CLOSE is unreliable on Windows and is deprecated. + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close. + */ + [noscript, must_use] PRFileDescStar openNSPRFileDesc(in long flags, + in long mode); + + /** + * Return the result of fopen on the file. The caller is + * responsible for calling fclose on the result. On success, the + * returned FILE pointer must be non-null. + */ + [noscript, must_use] FILE openANSIFileDesc(in string mode); + + /** + * Return the result of PR_LoadLibrary on the file. The caller is + * responsible for calling PR_UnloadLibrary on the result. + */ + [noscript, must_use] PRLibraryStar load(); + + // number of bytes available on disk to non-superuser + [must_use] readonly attribute int64_t diskSpaceAvailable; + + // disk capacity in bytes + [must_use] readonly attribute int64_t diskCapacity; + + /** + * appendRelative[Native]Path + * + * Append a relative path to the current path of the nsIFile object. + * + * @param relativeFilePath + * relativeFilePath is a native relative path. For security reasons, + * this cannot contain .. and cannot start with a directory separator. + * For the |appendRelativeNativePath| method, the relativeFilePath + * must be in the native filesystem charset. + */ + void appendRelativePath(in AString relativeFilePath); + [noscript] void appendRelativeNativePath(in ACString relativeFilePath); + + /** + * Accessor to a null terminated string which will specify + * the file in a persistent manner for disk storage. + * + * The character set of this attribute is undefined. DO NOT TRY TO + * INTERPRET IT AS HUMAN READABLE TEXT! + */ + [must_use] attribute ACString persistentDescriptor; + + /** + * reveal + * + * Ask the operating system to open the folder which contains + * this file or folder. This routine only works on platforms which + * support the ability to open a folder and is run async on Windows. + * This routine must be called on the main. + */ + [must_use] void reveal(); + + /** + * launch + * + * Ask the operating system to attempt to open the file. + * this really just simulates "double clicking" the file on your platform. + * This routine only works on platforms which support this functionality + * and is run async on Windows. This routine must be called on the + * main thread. + */ + [must_use] void launch(); + + /** + * getRelativeDescriptor + * + * Returns a relative file path in an opaque, XP format. It is therefore + * not a native path. + * + * The character set of the string returned from this function is + * undefined. DO NOT TRY TO INTERPRET IT AS HUMAN READABLE TEXT! + * + * @param fromFile + * the file from which the descriptor is relative. + * Throws if fromFile is null. + */ + [must_use] ACString getRelativeDescriptor(in nsIFile fromFile); + + /** + * setRelativeDescriptor + * + * Initializes the file to the location relative to fromFile using + * a string returned by getRelativeDescriptor. + * + * @param fromFile + * the file to which the descriptor is relative + * @param relative + * the relative descriptor obtained from getRelativeDescriptor + */ + [must_use] + void setRelativeDescriptor(in nsIFile fromFile, in ACString relativeDesc); + + /** + * getRelativePath + * + * Returns a relative file from 'fromFile' to this file as a UTF-8 string. + * Going up the directory tree is represented via "../". '/' is used as + * the path segment separator. This is not a native path, since it's UTF-8 + * encoded. + * + * @param fromFile + * the file from which the path is relative. + * Throws if fromFile is null. + */ + [must_use] AUTF8String getRelativePath(in nsIFile fromFile); + + /** + * setRelativePath + * + * Initializes the file to the location relative to fromFile using + * a string returned by getRelativePath. + * + * @param fromFile + * the file from which the path is relative + * @param relative + * the relative path obtained from getRelativePath + */ + [must_use] + void setRelativePath(in nsIFile fromFile, in AUTF8String relativeDesc); +}; + +%{C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsDirectoryServiceUtils.h" +#include "nsString.h" + +inline std::ostream& operator<<(std::ostream& aOut, const nsIFile& aFile) { + nsIFile* file = const_cast<nsIFile*>(&aFile); + nsAutoString path; + file->GetPath(path); + return aOut << "nsIFile { " << path << " }"; +} +#endif +%} diff --git a/xpcom/io/nsIIOUtil.idl b/xpcom/io/nsIIOUtil.idl new file mode 100644 index 0000000000..589556b367 --- /dev/null +++ b/xpcom/io/nsIIOUtil.idl @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIOutputStream; + +/** + * nsIIOUtil provdes various xpcom/io-related utility methods. + */ +[scriptable, builtinclass, uuid(e8152f7f-4209-4c63-ad23-c3d2aa0c5a49)] +interface nsIIOUtil : nsISupports +{ + /** + * Test whether an input stream is buffered. See nsStreamUtils.h + * documentation for NS_InputStreamIsBuffered for the definition of + * "buffered" used here and for edge-case behavior. + * + * @throws NS_ERROR_INVALID_POINTER if null is passed in. + */ + boolean inputStreamIsBuffered(in nsIInputStream aStream); + + /** + * Test whether an output stream is buffered. See nsStreamUtils.h + * documentation for NS_OutputStreamIsBuffered for the definition of + * "buffered" used here and for edge-case behavior. + * + * @throws NS_ERROR_INVALID_POINTER if null is passed in. + */ + boolean outputStreamIsBuffered(in nsIOutputStream aStream); +}; diff --git a/xpcom/io/nsIInputStream.idl b/xpcom/io/nsIInputStream.idl new file mode 100644 index 0000000000..9a72eaeae2 --- /dev/null +++ b/xpcom/io/nsIInputStream.idl @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; + +%{C++ +/** + * The signature of the writer function passed to ReadSegments. This + * is the "consumer" of data that gets read from the stream's buffer. + * + * @param aInStream stream being read + * @param aClosure opaque parameter passed to ReadSegments + * @param aFromSegment pointer to memory owned by the input stream. This is + * where the writer function should start consuming data. + * @param aToOffset amount of data already consumed by this writer during this + * ReadSegments call. This is also the sum of the aWriteCount + * returns from this writer over the previous invocations of + * the writer by this ReadSegments call. + * @param aCount Number of bytes available to be read starting at aFromSegment + * @param [out] aWriteCount number of bytes read by this writer function call + * + * Implementers should return the following: + * + * @return NS_OK and (*aWriteCount > 0) if consumed some data + * @return <any-error> if not interested in consuming any data + * + * Errors are never passed to the caller of ReadSegments. + * + * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior. + */ +typedef nsresult (*nsWriteSegmentFun)(nsIInputStream *aInStream, + void *aClosure, + const char *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount); +%} + +native nsWriteSegmentFun(nsWriteSegmentFun); + +/** + * nsIInputStream + * + * An interface describing a readable stream of data. An input stream may be + * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking + * input stream may suspend the calling thread in order to satisfy a call to + * Close, Available, Read, or ReadSegments. A non-blocking input stream, on + * the other hand, must not block the calling thread of execution. + * + * NOTE: blocking input streams are often read on a background thread to avoid + * locking up the main application thread. For this reason, it is generally + * the case that a blocking input stream should be implemented using thread- + * safe AddRef and Release. + */ +[scriptable, builtinclass, uuid(53cdbc97-c2d7-4e30-b2c3-45b2ee79db18)] +interface nsIInputStream : nsISupports +{ + /** + * Close the stream. This method causes subsequent calls to Read and + * ReadSegments to return 0 bytes read to indicate end-of-file. Any + * subsequent calls to Available or StreamStatus should throw + * NS_BASE_STREAM_CLOSED. + * + * Succeeds (without side effects) if already closed. + */ + void close(); + + /** + * Determine number of bytes available in the stream. A non-blocking + * stream that does not yet have any data to read should return 0 bytes + * from this method (i.e., it must not throw the NS_BASE_STREAM_WOULD_BLOCK + * exception). + * + * In addition to the number of bytes available in the stream, this method + * also informs the caller of the current status of the stream. A stream + * that is closed will throw an exception when this method is called. That + * enables the caller to know the condition of the stream before attempting + * to read from it. If a stream is at end-of-file, but not closed, then + * this method returns 0 bytes available. (Note: some nsIInputStream + * implementations automatically close when eof is reached; some do not). + * + * NOTE: Streams implementing nsIAsyncInputStream must automatically close + * when eof is reached, as otherwise it is impossible to distinguish between + * a stream waiting for more data and a stream at EOF using Available(). + * + * @return number of bytes currently available in the stream. + * + * @throws NS_BASE_STREAM_CLOSED if the stream is closed normally. + * @throws <other-error> if the stream is closed due to some error + * condition + */ + unsigned long long available(); + + /** + * Check the current status of the stream. A stream that is closed will + * throw an exception when this method is called. That enables the caller + * to know the condition of the stream before attempting to read from it. + * + * This method will not throw NS_BASE_STREAM_WOULD_BLOCK, even if the stream + * is an non-blocking stream with no data. A non-blocking stream that does + * not yet have any data to read should return NS_OK. + * + * NOTE: Unlike available, his method should not block the calling thread + * (e.g. to query the state of a file descriptor), even when called on a + * blocking stream. + * + * @throws NS_BASE_STREAM_CLOSED if the stream closed normally + * @throws <other-error> if the stream closed with a different status + */ + void streamStatus(); + + /** + * Read data from the stream. + * + * @param aBuf the buffer into which the data is to be read + * @param aCount the maximum number of bytes to be read + * + * @return number of bytes read (may be less than aCount). + * @return 0 if reached end-of-file + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws <other-error> on failure + * + * NOTE: this method should not throw NS_BASE_STREAM_CLOSED. + */ + [noscript] unsigned long read(in charPtr aBuf, in unsigned long aCount); + + /** + * Low-level read method that provides access to the stream's underlying + * buffer. The writer function may be called multiple times for segmented + * buffers. ReadSegments is expected to keep calling the writer until + * either there is nothing left to read or the writer returns an error. + * ReadSegments should not call the writer with zero bytes to consume. + * + * @param aWriter the "consumer" of the data to be read + * @param aClosure opaque parameter passed to writer + * @param aCount the maximum number of bytes to be read + * + * @return number of bytes read (may be less than aCount) + * @return 0 if reached end-of-file (or if aWriter refused to consume data) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer + * @throws <other-error> on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer (e.g., socket input stream). + * + * NOTE: this method should not throw NS_BASE_STREAM_CLOSED. + */ + [noscript] unsigned long readSegments(in nsWriteSegmentFun aWriter, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * @return true if stream is non-blocking + * + * NOTE: reading from a blocking input stream will block the calling thread + * until at least one byte of data can be extracted from the stream. + * + * NOTE: a non-blocking input stream may implement nsIAsyncInputStream to + * provide consumers with a way to wait for the stream to have more data + * once its read method is unable to return any data without blocking. + */ + boolean isNonBlocking(); +}; diff --git a/xpcom/io/nsIInputStreamLength.idl b/xpcom/io/nsIInputStreamLength.idl new file mode 100644 index 0000000000..f9f685704a --- /dev/null +++ b/xpcom/io/nsIInputStreamLength.idl @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIEventTarget; +interface nsIInputStreamLengthCallback; + +/** + * Note: Instead of using these interfaces directly, consider to use + * InputStreamLengthHelper class. + */ + +[uuid(452d059f-9a9c-4434-8839-e10d1405647c)] +interface nsIInputStreamLength : nsISupports +{ + /** + * Returns the total length of the stream if known. Otherwise it returns -1. + * This is different than calling available() which returns the number of + * bytes ready to be read from the stream. + * -1 is a valid value for a stream that doesn't know its length. For + * instance, a pipe stream could return such value. + * + * It could throw NS_BASE_STREAM_WOULD_BLOCK if the inputStream is + * non-blocking. If this happens, you should use + * nsIAsyncInputStreamLength::asyncLengthWait(). + * + * If the stream has already been read (read()/readSegments()/close()/seek() + * methods has been called), length() returns NS_ERROR_NOT_AVAILABLE. + * + * This is not an attribute because a stream can change its length. For + * instance, if the stream is a file inputStream and the underlying OS file + * changes, its length will change as well. + */ + long long length(); +}; + +[uuid(b63f9ecf-4668-44a3-93bd-72dbc65a6125)] +interface nsIAsyncInputStreamLength : nsISupports +{ + /** + * If the stream is non-blocking, nsIInputStreamLength::length() can return + * NS_BASE_STREAM_WOULD_BLOCK. The caller must then wait for the stream to + * know its length. + * + * If the stream implements nsIAsyncInputStreamLength, then the caller can + * use this interface to request an asynchronous notification when the + * stream's length becomes known (via the AsyncLengthWait method). + * If the length is already known, the aCallback will be still called + * asynchronously. + * + * If the stream has already been read (read()/readSegments()/close()/seek() + * methods has been called), length() returns NS_ERROR_NOT_AVAILABLE. + * + * @param aCallback + * This object is notified when the length becomes known. This + * parameter may be null to clear an existing callback. + * @param aEventTarget + * Specify that the notification must be delivered to a specific event + * target. + */ + void asyncLengthWait(in nsIInputStreamLengthCallback aCallback, + in nsIEventTarget aEventTarget); +}; + +/** + * This is a companion interface for + * nsIAsyncInputStreamLength::asyncLengthWait. + */ +[function, uuid(9c0c13b9-1b33-445d-8adb-a8a7866a6c06)] +interface nsIInputStreamLengthCallback : nsISupports +{ + /** + * Called to inform what the total length of the stream is. + * + * @param aStream + * The stream whose asyncLengthWait method was called. + * @param aLength + * The stream's length. It can be -1 if the stream doesn't know its + * length. For instance, this can happen for a pipe inputStream. + */ + void onInputStreamLengthReady(in nsIAsyncInputStreamLength aStream, + in long long aLength); +}; diff --git a/xpcom/io/nsIInputStreamPriority.idl b/xpcom/io/nsIInputStreamPriority.idl new file mode 100644 index 0000000000..d5938b7c27 --- /dev/null +++ b/xpcom/io/nsIInputStreamPriority.idl @@ -0,0 +1,17 @@ +/* -*- 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" +#include "nsIRunnable.idl" + +[scriptable, uuid(daa45b24-98ee-4eb2-9cec-aad0bc023e9d)] +interface nsIInputStreamPriority : nsISupports +{ + /** + * An input stream implementing this interface will dispatch runnable + * events with this priority. See nsIRunnablePriority. + */ + attribute unsigned long priority; +}; diff --git a/xpcom/io/nsIInputStreamTee.idl b/xpcom/io/nsIInputStreamTee.idl new file mode 100644 index 0000000000..60881e0fb8 --- /dev/null +++ b/xpcom/io/nsIInputStreamTee.idl @@ -0,0 +1,42 @@ +/* -*- 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 "nsIInputStream.idl" + +interface nsIOutputStream; +interface nsIEventTarget; + +/** + * A nsIInputStreamTee is a wrapper for an input stream, that when read + * reads the specified amount of data from its |source| and copies that + * data to its |sink|. |sink| must be a blocking output stream. + */ +[scriptable, builtinclass, uuid(90a9d790-3bca-421e-a73b-98f68e13c917)] +interface nsIInputStreamTee : nsIInputStream +{ + attribute nsIInputStream source; + attribute nsIOutputStream sink; + + /** + * If |eventTarget| is set, copying to sink is done asynchronously using + * the event-target (e.g. a thread). If |eventTarget| is not set, copying + * to sink happens synchronously while reading from the source. + */ + attribute nsIEventTarget eventTarget; +}; + +%{C++ +// factory methods +extern nsresult +NS_NewInputStreamTee(nsIInputStream **tee, // read from this input stream + nsIInputStream *source, + nsIOutputStream *sink); + +extern nsresult +NS_NewInputStreamTeeAsync(nsIInputStream **tee, // read from this input stream + nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *eventTarget); +%} diff --git a/xpcom/io/nsILineInputStream.idl b/xpcom/io/nsILineInputStream.idl new file mode 100644 index 0000000000..34ef1e7320 --- /dev/null +++ b/xpcom/io/nsILineInputStream.idl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(c97b466c-1e6e-4773-a4ab-2b2b3190a7a6)] +interface nsILineInputStream : nsISupports +{ + /** + * Read a single line from the stream, where a line is a + * possibly zero length sequence of 8bit chars terminated by a + * CR, LF, CRLF, LFCR, or eof. + * The line terminator is not returned. + * @retval false + * End of file. This line is the last line of the file + * (aLine is valid). + * @retval true + * The file contains further lines. + * @note Do not mix readLine with other read functions. + * Doing so can cause various problems and is not supported. + */ + boolean readLine(out ACString aLine); +}; diff --git a/xpcom/io/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl new file mode 100644 index 0000000000..38559517e7 --- /dev/null +++ b/xpcom/io/nsILocalFileMac.idl @@ -0,0 +1,212 @@ +/* -*- 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 "nsIFile.idl" + +%{C++ +#include <Carbon/Carbon.h> +#include <CoreFoundation/CoreFoundation.h> +%} + + native OSType(OSType); + native FSSpec(FSSpec); + native FSRef(FSRef); +[ptr] native FSRefPtr(FSRef); + native CFURLRef(CFURLRef); + +[scriptable, builtinclass, uuid(623eca5b-c25d-4e27-be5a-789a66c4b2f7)] +interface nsILocalFileMac : nsIFile +{ + /** + * initWithCFURL + * + * Init this object with a CFURLRef + * + * NOTE: Supported only for XP_MACOSX + * NOTE: If the path of the CFURL is /a/b/c, at least a/b must exist beforehand. + * + * @param aCFURL the CoreFoundation URL + * + */ + [noscript] void initWithCFURL(in CFURLRef aCFURL); + + /** + * initWithFSRef + * + * Init this object with an FSRef + * + * NOTE: Supported only for XP_MACOSX + * + * @param aFSRef the native FSRef + * + */ + [noscript] void initWithFSRef([const] in FSRefPtr aFSRef); + + /** + * getCFURL + * + * Returns the CFURLRef of the file object. The caller is + * responsible for calling CFRelease() on it. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] CFURLRef getCFURL(); + + /** + * getFSRef + * + * Returns the FSRef of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] FSRef getFSRef(); + + /** + * getFSSpec + * + * Returns the FSSpec of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * @return + * + */ + [noscript] FSSpec getFSSpec(); + + /** + * fileSizeWithResFork + * + * Returns the combined size of both the data fork and the resource + * fork (if present) rather than just the size of the data fork + * as returned by GetFileSize() + * + */ + readonly attribute int64_t fileSizeWithResFork; + + /** + * fileType, creator + * + * File type and creator attributes + * + */ + [noscript] attribute OSType fileType; + [noscript] attribute OSType fileCreator; + + /** + * launchWithDoc + * + * Launch the application that this file points to with a document. + * + * @param aDocToLoad Must not be NULL. If no document, use nsIFile::launch + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void launchWithDoc(in nsIFile aDocToLoad, in boolean aLaunchInBackground); + + /** + * openDocWithApp + * + * Open the document that this file points to with the given application. + * + * @param aAppToOpenWith The application with which to open the document. + * If NULL, the creator code of the document is used + * to determine the application. + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void openDocWithApp(in nsIFile aAppToOpenWith, in boolean aLaunchInBackground); + + /** + * isPackage + * + * returns true if a directory is determined to be a package under Mac OS 9/X + * + */ + boolean isPackage(); + + /** + * bundleDisplayName + * + * returns the display name of the application bundle (usually the human + * readable name of the application) + */ + readonly attribute AString bundleDisplayName; + + /** + * bundleIdentifier + * + * returns the identifier of the bundle + */ + readonly attribute AUTF8String bundleIdentifier; + + /** + * Last modified time of a bundle's contents (as opposed to its package + * directory). Our convention is to make the bundle's Info.plist file + * stand in for the rest of its contents -- since this file contains the + * bundle's version information and other identifiers. For non-bundles + * this is the same as lastModifiedTime. + */ + readonly attribute int64_t bundleContentsLastModifiedTime; + + /** + * Return whether or not the file has an extended attribute. + * + * @param aAttrName The attribute name to check for. + * + * @return Whether or not the extended attribute is present. + */ + bool hasXAttr(in ACString aAttrName); + + /** + * Get the value of the extended attribute. + * + * @param aAttrName The attribute name to read. + * + * @return The extended attribute value. + */ + Array<uint8_t> getXAttr(in ACString aAttrName); + + /** + * Set an extended attribute. + * + * @param aAttrName The attribute name to set a value for. + * @param aAttrValue The value to set for the attribute. + */ + void setXAttr(in ACString aAttrName, in Array<uint8_t> aAttrValue); + + /** + * Delete an extended attribute. + * + * @param aAttrName The extended attribute to delete. + */ + void delXAttr(in ACString aAttrName); +}; + +%{C++ +extern "C" +{ +NS_EXPORT nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowSymlinks, nsILocalFileMac** result); +NS_EXPORT nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowSymlinks, nsILocalFileMac** result); +} +%} diff --git a/xpcom/io/nsILocalFileWin.idl b/xpcom/io/nsILocalFileWin.idl new file mode 100644 index 0000000000..a3f80d391b --- /dev/null +++ b/xpcom/io/nsILocalFileWin.idl @@ -0,0 +1,98 @@ +/* -*- Mode: Java; 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 "nsIFile.idl" + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescStar(PRFileDesc); + +[scriptable, builtinclass, uuid(e7a3a954-384b-4aeb-a5f7-55626b0de9be)] +interface nsILocalFileWin : nsIFile +{ + /** + * initWithCommandLine + * + * Initialize this object based on the main app path of a commandline + * handler. + * + * @param aCommandLine + * the commandline to parse an app path out of. + */ + void initWithCommandLine(in AString aCommandLine); + /** + * getVersionInfoValue + * + * Retrieve a metadata field from the file's VERSIONINFO block. + * Throws NS_ERROR_FAILURE if no value is found, or the value is empty. + * + * @param aField The field to look up. + * + */ + AString getVersionInfoField(in string aField); + + /** + * The canonical path of the file, which avoids short/long + * pathname inconsistencies. The nsIFile persistent + * descriptor is not guaranteed to be canonicalized (it may + * persist either the long or the short path name). The format of + * the canonical path will vary with the underlying file system: + * it will typically be the short pathname on filesystems that + * support both short and long path forms. + */ + [noscript] readonly attribute AString canonicalPath; + + /** + * Get or set whether this file is marked read-only. + * + * Throws NS_ERROR_FILE_INVALID_PATH for an invalid file. + * Throws NS_ERROR_FAILURE if the set or get fails. + */ + attribute bool readOnly; + + /** + * Setting this to true will prepend the prefix "\\?\" to all parsed file + * paths which match ^[A-Za-z]:\\.* (regex) syntax. + * + * There are two known issues (and potentially more) which can be resolved + * by the prefix: + * - In the Windows API, the maximum length for a path is MAX_PATH in + * general. However, Windows API has many functions that also have Unicode + * versions to permit an extended-length path for a maximum total path + * length of 32,767 characters. + * + * - A path component which ends with a dot is not allowed for Windows + * API. + * + * If either of these issues are expected to be common in your code, you + * should set this flag to true. (You should probably not have to set this + * flag to true.) + */ + attribute boolean useDOSDevicePathSyntax; + + /** + * Identical to nsIFile::openNSPRFileDesc except it also uses the + * FILE_SHARE_DELETE flag. + */ + [noscript] PRFileDescStar openNSPRFileDescShareDelete(in long flags, + in long mode); + + /** + * Return the Windows-specific file attributes of this file. + */ + [noscript] unsigned long getWindowsFileAttributes(); + + /** + * Set or clear the Windows specific file attributes of this file. + * + * @param aSetAttrs Attribute to set on the file. + * @param aClearAttrs Attributes to clear on the file. + */ + [noscript] void setWindowsFileAttributes(in unsigned long aSetAttrs, + in unsigned long aClearAttrs); +}; diff --git a/xpcom/io/nsIMultiplexInputStream.idl b/xpcom/io/nsIMultiplexInputStream.idl new file mode 100644 index 0000000000..3729ccf753 --- /dev/null +++ b/xpcom/io/nsIMultiplexInputStream.idl @@ -0,0 +1,35 @@ +/* -*- 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 "nsIInputStream.idl" + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +[scriptable, builtinclass, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)] +interface nsIMultiplexInputStream : nsISupports +{ + /** + * Number of streams in this multiplex-stream + */ + [infallible] readonly attribute unsigned long count; + + /** + * Appends a stream to the end of the streams. The cursor of the stream + * should be located at the beginning of the stream if the implementation + * of this nsIMultiplexInputStream also is used as an nsISeekableStream. + * @param stream stream to append + */ + void appendStream(in nsIInputStream stream); + + /** + * Get stream at specified index. + * @param index return stream at this index, must be < count + * @return stream at specified index + */ + nsIInputStream getStream(in unsigned long index); +}; diff --git a/xpcom/io/nsIOUtil.cpp b/xpcom/io/nsIOUtil.cpp new file mode 100644 index 0000000000..b637e34a88 --- /dev/null +++ b/xpcom/io/nsIOUtil.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 "nsIOUtil.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsStreamUtils.h" + +NS_IMPL_ISUPPORTS(nsIOUtil, nsIIOUtil) + +NS_IMETHODIMP +nsIOUtil::InputStreamIsBuffered(nsIInputStream* aStream, bool* aResult) { + if (NS_WARN_IF(!aStream)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = NS_InputStreamIsBuffered(aStream); + return NS_OK; +} + +NS_IMETHODIMP +nsIOUtil::OutputStreamIsBuffered(nsIOutputStream* aStream, bool* aResult) { + if (NS_WARN_IF(!aStream)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = NS_OutputStreamIsBuffered(aStream); + return NS_OK; +} diff --git a/xpcom/io/nsIOUtil.h b/xpcom/io/nsIOUtil.h new file mode 100644 index 0000000000..32794433eb --- /dev/null +++ b/xpcom/io/nsIOUtil.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/. */ + +#ifndef nsIOUtil_h__ +#define nsIOUtil_h__ + +#define NS_IOUTIL_CID \ + { \ + 0xeb833911, 0x4f49, 0x4623, { \ + 0x84, 0x5f, 0xe5, 0x8a, 0x8e, 0x6d, 0xe4, 0xc2 \ + } \ + } + +#include "nsIIOUtil.h" +#include "mozilla/Attributes.h" + +class nsIOUtil final : public nsIIOUtil { + ~nsIOUtil() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIOUTIL +}; + +#endif /* nsIOUtil_h__ */ diff --git a/xpcom/io/nsIObjectInputStream.idl b/xpcom/io/nsIObjectInputStream.idl new file mode 100644 index 0000000000..c00b93a99a --- /dev/null +++ b/xpcom/io/nsIObjectInputStream.idl @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIBinaryInputStream.idl" + +/** + * @see nsIObjectOutputStream + * @see nsIBinaryInputStream + */ + +[scriptable, builtinclass, uuid(6c248606-4eae-46fa-9df0-ba58502368eb)] +interface nsIObjectInputStream : nsIBinaryInputStream +{ + /** + * Read an object from this stream to satisfy a strong or weak reference + * to one of its interfaces. If the interface was not along the primary + * inheritance chain ending in the "root" or XPCOM-identity nsISupports, + * readObject will QueryInterface from the deserialized object root to the + * correct interface, which was specified when the object was serialized. + * + * @see nsIObjectOutputStream + */ + nsISupports readObject(in boolean aIsStrongRef); + + [notxpcom] nsresult readID(out nsID aID); + + /** + * Optimized deserialization support -- see nsIStreamBufferAccess.idl. + */ + [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength); +}; + +%{C++ + +already_AddRefed<nsIObjectInputStream> +NS_NewObjectInputStream(nsIInputStream* aOutputStream); + +inline nsresult +NS_ReadOptionalObject(nsIObjectInputStream* aStream, bool aIsStrongRef, + nsISupports* *aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadObject(aIsStrongRef, aResult); + else + *aResult = nullptr; + } + return rv; +} + +%} diff --git a/xpcom/io/nsIObjectOutputStream.idl b/xpcom/io/nsIObjectOutputStream.idl new file mode 100644 index 0000000000..c5cdf27942 --- /dev/null +++ b/xpcom/io/nsIObjectOutputStream.idl @@ -0,0 +1,99 @@ +/* -*- 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 "nsIBinaryOutputStream.idl" + +/** + * @See nsIObjectInputStream + * @See nsIBinaryOutputStream + */ + +[scriptable, builtinclass, uuid(92c898ac-5fde-4b99-87b3-5d486422094b)] +interface nsIObjectOutputStream : nsIBinaryOutputStream +{ + /** + * Write the object whose "root" or XPCOM-identity nsISupports is aObject. + * The cause for writing this object is a strong or weak reference, so the + * aIsStrongRef argument must tell which kind of pointer is being followed + * here during serialization. + * + * If the object has only one strong reference in the serialization and no + * weak refs, use writeSingleRefObject. This is a valuable optimization: + * it saves space in the stream, and cycles on both ends of the process. + * + * If the reference being serialized is a pointer to an interface not on + * the primary inheritance chain ending in the root nsISupports, you must + * call writeCompoundObject instead of this method. + */ + void writeObject(in nsISupports aObject, in boolean aIsStrongRef); + + /** + * Write an object referenced singly and strongly via its root nsISupports + * or a subclass of its root nsISupports. There must not be other refs to + * aObject in memory, or in the serialization. + */ + void writeSingleRefObject(in nsISupports aObject); + + /** + * Write the object referenced by an interface pointer at aObject that + * inherits from a non-primary nsISupports, i.e., a reference to one of + * the multiply inherited interfaces derived from an nsISupports other + * than the root or XPCOM-identity nsISupports; or a reference to an + * inner object in the case of true XPCOM aggregation. aIID identifies + * this interface. + */ + void writeCompoundObject(in nsISupports aObject, + in nsIIDRef aIID, + in boolean aIsStrongRef); + + void writeID(in nsIDRef aID); + + /** + * Optimized serialization support -- see nsIStreamBufferAccess.idl. + */ + [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength); +}; + +%{C++ +already_AddRefed<nsIObjectOutputStream> +NS_NewObjectOutputStream(nsIOutputStream* aOutputStream); + +inline nsresult +NS_WriteOptionalObject(nsIObjectOutputStream* aStream, nsISupports* aObject, + bool aIsStrongRef) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteObject(aObject, aIsStrongRef); + return rv; +} + +inline nsresult +NS_WriteOptionalSingleRefObject(nsIObjectOutputStream* aStream, + nsISupports* aObject) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteSingleRefObject(aObject); + return rv; +} + +inline nsresult +NS_WriteOptionalCompoundObject(nsIObjectOutputStream* aStream, + nsISupports* aObject, + const nsIID& aIID, + bool aIsStrongRef) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteCompoundObject(aObject, aIID, aIsStrongRef); + return rv; +} + +%} diff --git a/xpcom/io/nsIOutputStream.idl b/xpcom/io/nsIOutputStream.idl new file mode 100644 index 0000000000..ffe9a5ffed --- /dev/null +++ b/xpcom/io/nsIOutputStream.idl @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIOutputStream; +interface nsIInputStream; + +%{C++ +/** + * The signature for the reader function passed to WriteSegments. This + * is the "provider" of data that gets written into the stream's buffer. + * + * @param aOutStream stream being written to + * @param aClosure opaque parameter passed to WriteSegments + * @param aToSegment pointer to memory owned by the output stream + * @param aFromOffset amount already written (since WriteSegments was called) + * @param aCount length of toSegment + * @param aReadCount number of bytes written + * + * Implementers should return the following: + * + * @throws <any-error> if not interested in providing any data + * + * Errors are never passed to the caller of WriteSegments. + */ +typedef nsresult (*nsReadSegmentFun)(nsIOutputStream *aOutStream, + void *aClosure, + char *aToSegment, + uint32_t aFromOffset, + uint32_t aCount, + uint32_t *aReadCount); +%} + +native nsReadSegmentFun(nsReadSegmentFun); + +/** + * nsIOutputStream + * + * An interface describing a writable stream of data. An output stream may be + * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking + * output stream may suspend the calling thread in order to satisfy a call to + * Close, Flush, Write, WriteFrom, or WriteSegments. A non-blocking output + * stream, on the other hand, must not block the calling thread of execution. + * + * NOTE: blocking output streams are often written to on a background thread to + * avoid locking up the main application thread. For this reason, it is + * generally the case that a blocking output stream should be implemented using + * thread- safe AddRef and Release. + */ +[scriptable, builtinclass, uuid(0d0acd2a-61b4-11d4-9877-00c04fa0cf4a)] +interface nsIOutputStream : nsISupports +{ + /** + * Close the stream. Forces the output stream to flush any buffered data. + * Any subsequent calls to StreamStatus should throw NS_BASE_STREAM_CLOSED. + * Succeeds without effect if already closed. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking + * the calling thread (non-blocking mode only) + */ + void close(); + + /** + * Flush the stream. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking + * the calling thread (non-blocking mode only) + */ + void flush(); + + /** + * Check the current status of the stream. A stream that is closed will + * throw an exception when this method is called. That enables the caller + * to know the condition of the stream before attempting to write into it. + * + * This method will not throw NS_BASE_STREAM_WOULD_BLOCK, even if the stream + * is a non-blocking stream with no available space. A non-blocking stream + * which has not been closed, but has no available room should return NS_OK. + * + * NOTE: This method should not block the calling thread (e.g. to query the + * state of a file descriptor), even when called on a blocking stream. + * + * @throws NS_BASE_STREAM_CLOSED if the stream closed normally + * @throws <other-error> if the stream closed with a different status + */ + void streamStatus(); + + /** + * Write data into the stream. + * + * @param aBuf the buffer containing the data to be written + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only) + * @throws <other-error> on failure + */ + unsigned long write(in string aBuf, in unsigned long aCount); + + /** + * Writes data into the stream from an input stream. + * + * @param aFromStream the stream containing the data to be written + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only). This failure + * means no bytes were transferred. + * @throws <other-error> on failure + * + * NOTE: This method is defined by this interface in order to allow the + * output stream to efficiently copy the data from the input stream into + * its internal buffer (if any). If this method was provided as an external + * facility, a separate char* buffer would need to be used in order to call + * the output stream's other Write method. + */ + unsigned long writeFrom(in nsIInputStream aFromStream, + in unsigned long aCount); + + /** + * Low-level write method that has access to the stream's underlying buffer. + * The reader function may be called multiple times for segmented buffers. + * WriteSegments is expected to keep calling the reader until either there + * is nothing left to write or the reader returns an error. WriteSegments + * should not call the reader with zero bytes to provide. + * + * @param aReader the "provider" of the data to be written + * @param aClosure opaque parameter passed to reader + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only). This failure + * means no bytes were transferred. + * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer + * @throws <other-error> on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer (e.g., socket output stream). + */ + [noscript] unsigned long writeSegments(in nsReadSegmentFun aReader, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * @return true if stream is non-blocking + * + * NOTE: writing to a blocking output stream will block the calling thread + * until all given data can be consumed by the stream. + * + * NOTE: a non-blocking output stream may implement nsIAsyncOutputStream to + * provide consumers with a way to wait for the stream to accept more data + * once its write method is unable to accept any data without blocking. + */ + boolean isNonBlocking(); +}; diff --git a/xpcom/io/nsIPipe.idl b/xpcom/io/nsIPipe.idl new file mode 100644 index 0000000000..cb5a035a32 --- /dev/null +++ b/xpcom/io/nsIPipe.idl @@ -0,0 +1,171 @@ +/* -*- 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 nsIAsyncInputStream; +interface nsIAsyncOutputStream; + +/** + * nsIPipe represents an in-process buffer that can be read using nsIInputStream + * and written using nsIOutputStream. The reader and writer of a pipe do not + * have to be on the same thread. As a result, the pipe is an ideal mechanism + * to bridge data exchange between two threads. For example, a worker thread + * might write data to a pipe from which the main thread will read. + * + * Each end of the pipe can be either blocking or non-blocking. Recall that a + * non-blocking stream will return NS_BASE_STREAM_WOULD_BLOCK if it cannot be + * read or written to without blocking the calling thread. For example, if you + * try to read from an empty pipe that has not yet been closed, then if that + * pipe's input end is non-blocking, then the read call will fail immediately + * with NS_BASE_STREAM_WOULD_BLOCK as the error condition. However, if that + * pipe's input end is blocking, then the read call will not return until the + * pipe has data or until the pipe is closed. This example presumes that the + * pipe is being filled asynchronously on some background thread. + * + * The pipe supports nsIAsyncInputStream and nsIAsyncOutputStream, which give + * the user of a non-blocking pipe the ability to wait for the pipe to become + * ready again. For example, in the case of an empty non-blocking pipe, the + * user can call AsyncWait on the input end of the pipe to be notified when + * the pipe has data to read (or when the pipe becomes closed). + * + * NS_NewPipe2 and NS_NewPipe provide convenient pipe constructors. In most + * cases nsIPipe is not actually used. It is usually enough to just get + * references to the pipe's input and output end. In which case, the pipe is + * automatically closed when the respective pipe ends are released. + */ +[scriptable, uuid(25d0de93-685e-4ea4-95d3-d884e31df63c)] +interface nsIPipe : nsISupports +{ + /** + * initialize this pipe + * + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param segmentCount + * specifies the max number of segments (pass 0 to use default + * value). Passing UINT32_MAX here causes the pipe to have + * "infinite" space. This mode can be useful in some cases, but + * should always be used with caution. The default value for this + * parameter is a finite value. + */ + [must_use] void init(in boolean nonBlockingInput, + in boolean nonBlockingOutput, + in unsigned long segmentSize, + in unsigned long segmentCount); + + /** + * The pipe's input end, which also implements nsISearchableInputStream. + * Getting fails if the pipe hasn't been initialized. + */ + [must_use] readonly attribute nsIAsyncInputStream inputStream; + + /** + * The pipe's output end. Getting fails if the pipe hasn't been + * initialized. + */ + [must_use] readonly attribute nsIAsyncOutputStream outputStream; +}; + +/** + * XXX this interface doesn't really belong in here. It is here because + * currently nsPipeInputStream is the only implementation of this interface. + */ +[scriptable, uuid(8C39EF62-F7C9-11d4-98F5-001083010E9B)] +interface nsISearchableInputStream : nsISupports +{ + /** + * Searches for a string in the input stream. Since the stream has a notion + * of EOF, it is possible that the string may at some time be in the + * buffer, but is is not currently found up to some offset. Consequently, + * both the found and not found cases return an offset: + * if found, return offset where it was found + * if not found, return offset of the first byte not searched + * In the case the stream is at EOF and the string is not found, the first + * byte not searched will correspond to the length of the buffer. + */ + void search(in string forString, + in boolean ignoreCase, + out boolean found, + out unsigned long offsetSearchedTo); +}; + +%{C++ + +class nsIInputStream; +class nsIOutputStream; + +/** + * NS_NewPipe2 + * + * This function supersedes NS_NewPipe. It differs from NS_NewPipe in two + * major ways: + * (1) returns nsIAsyncInputStream and nsIAsyncOutputStream, so it is + * not necessary to QI in order to access these interfaces. + * (2) the size of the pipe is determined by the number of segments + * times the size of each segment. + * + * @param pipeIn + * resulting input end of the pipe + * @param pipeOut + * resulting output end of the pipe + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param segmentCount + * specifies the max number of segments (pass 0 to use default value) + * passing UINT32_MAX here causes the pipe to have "infinite" space. + * this mode can be useful in some cases, but should always be used with + * caution. the default value for this parameter is a finite value. + */ +extern void +NS_NewPipe2(nsIAsyncInputStream **pipeIn, + nsIAsyncOutputStream **pipeOut, + bool nonBlockingInput = false, + bool nonBlockingOutput = false, + uint32_t segmentSize = 0, + uint32_t segmentCount = 0); + +/** + * NS_NewPipe + * + * Preserved for backwards compatibility. Plus, this interface is more + * amiable in certain contexts (e.g., when you don't need the pipe's async + * capabilities). + * + * @param pipeIn + * resulting input end of the pipe + * @param pipeOut + * resulting output end of the pipe + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param maxSize + * specifies the max size of the pipe (pass 0 to use default value) + * number of segments is maxSize / segmentSize, and maxSize must be a + * multiple of segmentSize. passing UINT32_MAX here causes the + * pipe to have "infinite" space. this mode can be useful in some + * cases, but should always be used with caution. the default value + * for this parameter is a finite value. + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + */ +extern void +NS_NewPipe(nsIInputStream **pipeIn, + nsIOutputStream **pipeOut, + uint32_t segmentSize = 0, + uint32_t maxSize = 0, + bool nonBlockingInput = false, + bool nonBlockingOutput = false); + +%} diff --git a/xpcom/io/nsIRandomAccessStream.idl b/xpcom/io/nsIRandomAccessStream.idl new file mode 100644 index 0000000000..20421def17 --- /dev/null +++ b/xpcom/io/nsIRandomAccessStream.idl @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsISeekableStream.idl" + +interface nsIInputStream; +interface nsIInterfaceRequestor; +interface nsIOutputStream; + +%{C++ +namespace mozilla::ipc { +class RandomAccessStreamParams; +} // namespace mozilla::ipc +%} + +native RandomAccessStreamParams(mozilla::ipc::RandomAccessStreamParams); +[ref] native RandomAccessStreamParamsRef(mozilla::ipc::RandomAccessStreamParams); + +/** + * nsIRandomAccessStream + * + * An interface which supports both reading and writing to a storage starting + * at the current offset. Both the input stream and the output stream share the + * offset in the stream. Read operations invoked on the input stream start at + * the offset and advance it past the bytes read. Write operations invoked on + * the output stream start the offset and advance it past the bytes written. + * The offset can be set to an arbitrary value prior reading or writting. Each + * call to getInputStream or getOutputStream always returns the same object, + * rather than creating a new stream. It's recommended for objects implementing + * this interface to also implement nsIInputStream and nsIOutputStream, so they + * can be easilly used with e.g. NS_AsyncCopy. + */ +[scriptable, builtinclass, uuid(9b5904a8-886a-420f-a1d8-847de8ffc133)] +interface nsIRandomAccessStream : nsISeekableStream +{ + /** + * This method always returns the same object. + */ + nsIInputStream getInputStream(); + + /** + * This method always returns the same object. + */ + nsIOutputStream getOutputStream(); + + /** + * Like getInputStream but infallible. + */ + [notxpcom, nostdcall] nsIInputStream inputStream(); + + /** + * Like getOutputStream but infallible. + */ + [notxpcom, nostdcall] nsIOutputStream outputStream(); + + [notxpcom, nostdcall] RandomAccessStreamParams serialize(in nsIInterfaceRequestor aCallbacks); + + [notxpcom, nostdcall] bool deserialize(inout RandomAccessStreamParamsRef params); +}; diff --git a/xpcom/io/nsISafeOutputStream.idl b/xpcom/io/nsISafeOutputStream.idl new file mode 100644 index 0000000000..dae78b554a --- /dev/null +++ b/xpcom/io/nsISafeOutputStream.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface provides a mechanism to control an output stream + * that takes care not to overwrite an existing target until it is known + * that all writes to the destination succeeded. + * + * An object that supports this interface is intended to also support + * nsIOutputStream. + * + * For example, a file output stream that supports this interface writes to + * a temporary file, and moves it over the original file when |finish| is + * called only if the stream can be successfully closed and all writes + * succeeded. If |finish| is called but something went wrong during + * writing, it will delete the temporary file and not touch the original. + * If the stream is closed by calling |close| directly, or the stream + * goes away, the original file will not be overwritten, and the temporary + * file will be deleted. + * + * Currently, this interface is implemented only for file output streams. + */ +[scriptable, uuid(5f914307-5c34-4e1f-8e32-ec749d25b27a)] +interface nsISafeOutputStream : nsISupports +{ + /** + * Call this method to close the stream and cause the original target + * to be overwritten. Note: if any call to |write| failed to write out + * all of the data given to it, then calling this method will |close| the + * stream and return failure. Further, if closing the stream fails, this + * method will return failure. The original target will be overwritten only + * if all calls to |write| succeeded and the stream was successfully closed. + */ + void finish(); +}; diff --git a/xpcom/io/nsIScriptableBase64Encoder.idl b/xpcom/io/nsIScriptableBase64Encoder.idl new file mode 100644 index 0000000000..6414518056 --- /dev/null +++ b/xpcom/io/nsIScriptableBase64Encoder.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; + +/** + * nsIScriptableBase64Encoder efficiently encodes the contents + * of a nsIInputStream to a Base64 string. This avoids the need + * to read the entire stream into a buffer, and only then do the + * Base64 encoding. + * + * If you already have a buffer full of data, you should use + * btoa instead! + */ +[scriptable, uuid(9479c864-d1f9-45ab-b7b9-28b907bd2ba9)] +interface nsIScriptableBase64Encoder : nsISupports +{ + /** + * These methods take an nsIInputStream and return a narrow or wide + * string with the contents of the nsIInputStream base64 encoded. + * + * The stream passed in must support ReadSegments and must not be + * a non-blocking stream that will return NS_BASE_STREAM_WOULD_BLOCK. + * If either of these restrictions are violated we will abort. + */ + ACString encodeToCString(in nsIInputStream stream, in unsigned long length); + AString encodeToString(in nsIInputStream stream, in unsigned long length); +}; diff --git a/xpcom/io/nsIScriptableInputStream.idl b/xpcom/io/nsIScriptableInputStream.idl new file mode 100644 index 0000000000..7c18274055 --- /dev/null +++ b/xpcom/io/nsIScriptableInputStream.idl @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; + +/** + * nsIScriptableInputStream provides scriptable access to an nsIInputStream + * instance. + */ +[scriptable, uuid(3fce9015-472a-4080-ac3e-cd875dbe361e)] +interface nsIScriptableInputStream : nsISupports +{ + /** + * Closes the stream. + */ + void close(); + + /** + * Wrap the given nsIInputStream with this nsIScriptableInputStream. + * + * @param aInputStream parameter providing the stream to wrap + */ + void init(in nsIInputStream aInputStream); + + /** + * Return the number of bytes currently available in the stream + * + * @return the number of bytes + * + * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed + */ + unsigned long long available(); + + /** + * Read data from the stream. + * + * WARNING: If the data contains a null byte, then this method will return + * a truncated string. + * + * @param aCount the maximum number of bytes to read + * + * @return the data, which will be an empty string if the stream is at EOF. + * + * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed + * @throws NS_ERROR_NOT_INITIALIZED if init was not called + */ + string read(in unsigned long aCount); + + /** + * Read data from the stream, including NULL bytes. + * + * @param aCount the maximum number of bytes to read. + * + * @return the data from the stream, which will be an empty string if EOF + * has been reached. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream + * would block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + */ + ACString readBytes(in unsigned long aCount); +}; diff --git a/xpcom/io/nsISeekableStream.idl b/xpcom/io/nsISeekableStream.idl new file mode 100644 index 0000000000..b12a7f9baf --- /dev/null +++ b/xpcom/io/nsISeekableStream.idl @@ -0,0 +1,65 @@ +/* -*- 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 "nsITellableStream.idl" + +/* + * nsISeekableStream + * + * Note that a stream might not implement all methods (e.g., a readonly stream + * won't implement setEOF) + */ + +#include "nsISupports.idl" + +[scriptable, uuid(8429d350-1040-4661-8b71-f2a6ba455980)] +interface nsISeekableStream : nsITellableStream +{ + /* + * Sets the stream pointer to the value of the 'offset' parameter + */ + const int32_t NS_SEEK_SET = 0; + + /* + * Sets the stream pointer to its current location plus the value + * of the offset parameter. + */ + const int32_t NS_SEEK_CUR = 1; + + /* + * Sets the stream pointer to the size of the stream plus the value + * of the offset parameter. + */ + const int32_t NS_SEEK_END = 2; + + /** + * seek + * + * This method moves the stream offset of the steam implementing this + * interface. + * + * @param whence specifies how to interpret the 'offset' parameter in + * setting the stream offset associated with the implementing + * stream. + * + * @param offset specifies a value, in bytes, that is used in conjunction + * with the 'whence' parameter to set the stream offset of the + * implementing stream. A negative value causes seeking in + * the reverse direction. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + void seek(in long whence, in long long offset); + + /** + * setEOF + * + * This method truncates the stream at the current offset. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + void setEOF(); +}; diff --git a/xpcom/io/nsIStorageStream.idl b/xpcom/io/nsIStorageStream.idl new file mode 100644 index 0000000000..c2792f5002 --- /dev/null +++ b/xpcom/io/nsIStorageStream.idl @@ -0,0 +1,69 @@ +/* -*- 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 nsIInputStream; +interface nsIOutputStream; + +/** + * The nsIStorageStream interface maintains an internal data buffer that can be + * filled using a single output stream. One or more independent input streams + * can be created to read the data from the buffer non-destructively. + */ + +[scriptable, uuid(44a200fe-6c2b-4b41-b4e3-63e8c14e7c0d)] +interface nsIStorageStream : nsISupports +{ + /** + * + * Initialize the stream, setting up the amount of space that will be + * allocated for the stream's backing-store. + * + * @param segmentSize + * Size of each segment. Must be a power of two. + * @param maxSize + * Maximum total size of this stream. length will always be less + * than or equal to this value. Passing UINT32_MAX is safe. + */ + void init(in uint32_t segmentSize, in uint32_t maxSize); + + /** + * Get a reference to the one and only output stream for this instance. + * The zero-based startPosition argument is used is used to set the initial + * write cursor position. The startPosition cannot be set larger than the + * current buffer length. Calling this method has the side-effect of + * truncating the internal buffer to startPosition bytes. + */ + nsIOutputStream getOutputStream(in int32_t startPosition); + + /** + * Create a new input stream to read data (written by the singleton output + * stream) from the internal buffer. Multiple, independent input streams + * can be created. + */ + nsIInputStream newInputStream(in int32_t startPosition); + + /** + * The length attribute indicates the total number of bytes stored in the + * nsIStorageStream internal buffer, regardless of any consumption by input + * streams. Assigning to the length field can be used to truncate the + * buffer data, but can not be used when either the instance's output + * stream is in use. + * + * @See #writeInProgress */ + attribute uint32_t length; + + /** + * True, when output stream has not yet been Close'ed + */ + readonly attribute boolean writeInProgress; +}; + +%{C++ +// Factory method +nsresult +NS_NewStorageStream(uint32_t segmentSize, uint32_t maxSize, nsIStorageStream **result); +%} diff --git a/xpcom/io/nsIStreamBufferAccess.idl b/xpcom/io/nsIStreamBufferAccess.idl new file mode 100644 index 0000000000..c0883a439b --- /dev/null +++ b/xpcom/io/nsIStreamBufferAccess.idl @@ -0,0 +1,88 @@ +/* -*- 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" + +/** + * An interface for access to a buffering stream implementation's underlying + * memory buffer. + * + * Stream implementations that QueryInterface to nsIStreamBufferAccess must + * ensure that all buffers are aligned on the most restrictive type size for + * the current architecture (e.g., sizeof(double) for RISCy CPUs). malloc(3) + * satisfies this requirement. + */ +[scriptable, builtinclass, uuid(ac923b72-ac87-4892-ac7a-ca385d429435)] +interface nsIStreamBufferAccess : nsISupports +{ + /** + * Get access to a contiguous, aligned run of bytes in the stream's buffer. + * Exactly one successful getBuffer call must occur before a putBuffer call + * taking the non-null pointer returned by the successful getBuffer. + * + * The run of bytes are the next bytes (modulo alignment padding) to read + * for an input stream, and the next bytes (modulo alignment padding) to + * store before (eventually) writing buffered data to an output stream. + * There can be space beyond this run of bytes in the buffer for further + * accesses before the fill or flush point is reached. + * + * @param aLength + * Count of contiguous bytes requested at the address A that satisfies + * (A & aAlignMask) == 0 in the buffer, starting from the current stream + * position, mapped to a buffer address B. The stream implementation + * must pad from B to A by skipping bytes (if input stream) or storing + * zero bytes (if output stream). + * + * @param aAlignMask + * Bit-mask computed by subtracting 1 from the power-of-two alignment + * modulus (e.g., 3 or sizeof(uint32_t)-1 for uint32_t alignment). + * + * @return + * The aligned pointer to aLength bytes in the buffer, or null if the + * buffer has no room for aLength bytes starting at the next address A + * after the current position that satisfies (A & aAlignMask) == 0. + */ + [notxpcom,noscript] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + + /** + * Relinquish access to the stream's buffer, filling if at end of an input + * buffer, flushing if completing an output buffer. After a getBuffer call + * that returns non-null, putBuffer must be called. + * + * @param aBuffer + * A non-null pointer returned by getBuffer on the same stream buffer + * access object. + * + * @param aLength + * The same count of contiguous bytes passed to the getBuffer call that + * returned aBuffer. + */ + [notxpcom,noscript] void putBuffer(in charPtr aBuffer, in uint32_t aLength); + + /** + * Disable and enable buffering on the stream implementing this interface. + * DisableBuffering flushes an output stream's buffer, and invalidates an + * input stream's buffer. + */ + void disableBuffering(); + void enableBuffering(); + + /** + * The underlying, unbuffered input or output stream. + */ + readonly attribute nsISupports unbufferedStream; +}; + +%{C++ + +/** + * These macros get and put a buffer given either an sba parameter that may + * point to an object implementing nsIStreamBufferAccess, nsIObjectInputStream, + * or nsIObjectOutputStream. + */ +#define NS_GET_BUFFER(sba,n,a) ((sba)->GetBuffer(n, a)) +#define NS_PUT_BUFFER(sba,p,n) ((sba)->PutBuffer(p, n)) + +%} diff --git a/xpcom/io/nsIStringStream.idl b/xpcom/io/nsIStringStream.idl new file mode 100644 index 0000000000..3e084cbe7f --- /dev/null +++ b/xpcom/io/nsIStringStream.idl @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" + +%{C++ +#include "mozilla/MemoryReporting.h" + +namespace mozilla { +class StreamBufferSource; +} // namespace mozilla +%} + +native MallocSizeOf(mozilla::MallocSizeOf); +[ptr] native StreamBufferSource(mozilla::StreamBufferSource); + +/** + * nsIStringInputStream + * + * Provides scriptable and specialized C++-only methods for initializing a + * nsIInputStream implementation with a simple character array. + */ +[scriptable, builtinclass, uuid(450cd2d4-f0fd-424d-b365-b1251f80fd53)] +interface nsIStringInputStream : nsIInputStream +{ + /** + * SetData - assign data to the input stream (copied on assignment). + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + * + * NOTE: C++ code should consider using AdoptData or ShareData to avoid + * making an extra copy of the stream data. + * + * NOTE: For JS callers, the given data must not contain null characters + * (other than a null terminator) because a null character in the middle of + * the data string will be seen as a terminator when the data is converted + * from a JS string to a C++ character array. + */ + void setData(in string data, in long dataLen); + + /** + * SetUTF8Data - encode input data to UTF-8 and assign it to the input + * stream. + * + * @param data - stream data + * + * NOTE: This method is meant to be used by JS callers, + */ + void setUTF8Data(in AUTF8String data); + + /** + * NOTE: the following methods are designed to give C++ code added control + * over the ownership and lifetime of the stream data. Use with care :-) + */ + + /** + * AdoptData - assign data to the input stream. the input stream takes + * ownership of the given data buffer and will free it when + * the input stream is destroyed. + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + */ + [noscript] void adoptData(in charPtr data, in long dataLen); + + /** + * ShareData - assign data to the input stream. the input stream references + * the given data buffer until the input stream is destroyed. the given + * data buffer must outlive the input stream. + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + */ + [noscript] void shareData(in string data, in long dataLen); + + /** + * SetDataSource - assign data to the input stream. the input stream holds + * a strong reference to the given data buffer until it is destroyed. + * + * @param source - stream data source + */ + [noscript] void setDataSource(in StreamBufferSource source); + + [noscript, notxpcom] + size_t SizeOfIncludingThisIfUnshared(in MallocSizeOf aMallocSizeOf); + + [noscript, notxpcom] + size_t SizeOfIncludingThisEvenIfShared(in MallocSizeOf aMallocSizeOf); +}; diff --git a/xpcom/io/nsITellableStream.idl b/xpcom/io/nsITellableStream.idl new file mode 100644 index 0000000000..c2cfaba0de --- /dev/null +++ b/xpcom/io/nsITellableStream.idl @@ -0,0 +1,34 @@ +/* -*- 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/. */ + + +/* + * nsITellableStream + * + * This class is separate from nsISeekableStream in order to let streams to + * implement ::Tell() without implementing the whole nsISeekableStream + * interface. Callers can QI the stream to know what is implemented. This is + * mainly done for nsPipeInputStream. + * + * + * Implementing this interface, streams are able to expose the current offset + * via ::tell(). + */ + +#include "nsISupports.idl" + +[scriptable, uuid(ee942946-4538-45d2-bf05-ffdbf5932621)] +interface nsITellableStream : nsISupports +{ + /** + * tell + * + * This method reports the current offset, in bytes, from the start of the + * stream. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + long long tell(); +}; diff --git a/xpcom/io/nsIUnicharInputStream.idl b/xpcom/io/nsIUnicharInputStream.idl new file mode 100644 index 0000000000..3ae467cc83 --- /dev/null +++ b/xpcom/io/nsIUnicharInputStream.idl @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIUnicharInputStream; +interface nsIInputStream; + +%{C++ +/** + * The signature of the writer function passed to ReadSegments. This + * is the "consumer" of data that gets read from the stream's buffer. + * + * @param aInStream stream being read + * @param aClosure opaque parameter passed to ReadSegments + * @param aFromSegment pointer to memory owned by the input stream + * @param aToOffset number of UTF-16 code units already read + * (since ReadSegments was called) + * @param aCount length of fromSegment + * @param aWriteCount number of UTF-16 code units read + * + * Implementers should return the following: + * + * @throws <any-error> if not interested in consuming any data + * + * Errors are never passed to the caller of ReadSegments. + * + * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior. + */ +typedef nsresult (*nsWriteUnicharSegmentFun)(nsIUnicharInputStream *aInStream, + void *aClosure, + const char16_t *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount); +%} +native nsWriteUnicharSegmentFun(nsWriteUnicharSegmentFun); + +/** + * Abstract UTF-16 input stream + * @see nsIInputStream + */ +[scriptable, uuid(d5e3bd80-6723-4b92-b0c9-22f6162fd94f)] +interface nsIUnicharInputStream : nsISupports { + /** + * Reads into a caller-provided array. + * + * @return The number of utf-16 code units that were successfully read. + * May be less than aCount, even if there is more data in the input + * stream. A return value of 0 means EOF. + * + * @note To read more than 2^32 code units, call this method multiple times. + */ + [noscript] unsigned long read([array, size_is(aCount)] in char16_t aBuf, + in unsigned long aCount); + + /** + * Low-level read method that has access to the stream's underlying buffer. + * The writer function may be called multiple times for segmented buffers. + * ReadSegments is expected to keep calling the writer until either there is + * nothing left to read or the writer returns an error. ReadSegments should + * not call the writer with zero UTF-16 code units to consume. + * + * @param aWriter the "consumer" of the data to be read + * @param aClosure opaque parameter passed to writer + * @param aCount the maximum number of UTF-16 code units to be read + * + * @return number of UTF-16 code units read (may be less than aCount) + * @return 0 if reached end of file (or if aWriter refused to consume data) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws <other-error> on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer + */ + [noscript] unsigned long readSegments(in nsWriteUnicharSegmentFun aWriter, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * Read into a string object. + * + * @param aCount The number of UTF-16 code units that should be read + * @return The number of UTF-16 code units that were read. + */ + unsigned long readString(in unsigned long aCount, out AString aString); + + /** + * Close the stream and free associated resources. This also closes the + * underlying stream, if any. + */ + void close(); +}; diff --git a/xpcom/io/nsIUnicharLineInputStream.idl b/xpcom/io/nsIUnicharLineInputStream.idl new file mode 100644 index 0000000000..0322c6a1eb --- /dev/null +++ b/xpcom/io/nsIUnicharLineInputStream.idl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(67f42475-ba80-40f8-ac0b-649c89230184)] +interface nsIUnicharLineInputStream : nsISupports +{ + /** + * Read a single line from the stream, where a line is a + * possibly zero length sequence of characters terminated by a + * CR, LF, CRLF, LFCR, or eof. + * The line terminator is not returned. + * @retval false + * End of file. This line is the last line of the file + * (aLine is valid). + * @retval true + * The file contains further lines. + * @note Do not mix readLine with other read functions. + * Doing so can cause various problems and is not supported. + */ + boolean readLine(out AString aLine); +}; diff --git a/xpcom/io/nsIUnicharOutputStream.idl b/xpcom/io/nsIUnicharOutputStream.idl new file mode 100644 index 0000000000..743f532e03 --- /dev/null +++ b/xpcom/io/nsIUnicharOutputStream.idl @@ -0,0 +1,45 @@ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +/** + * An interface that allows writing unicode data. + */ +[scriptable, uuid(2d00b1bb-8b21-4a63-bcc6-7213f513ac2e)] +interface nsIUnicharOutputStream : nsISupports +{ + /** + * Write a single character to the stream. When writing many characters, + * prefer the string-taking write method. + * + * @retval true The character was written successfully + * @retval false Not all bytes of the character could be written. + */ + boolean write(in unsigned long aCount, + [const, array, size_is(aCount)] in char16_t c); + + /** + * Write a string to the stream. + * + * @retval true The string was written successfully + * @retval false Not all bytes of the string could be written. + */ + boolean writeString(in AString str); + + /** + * Flush the stream. This finishes the conversion and writes any bytes that + * finish the current byte sequence. + * + * It does NOT flush the underlying stream. + */ + void flush(); + + /** + * Close the stream and free associated resources. This also closes the + * underlying stream. + */ + void close(); +}; diff --git a/xpcom/io/nsInputStreamTee.cpp b/xpcom/io/nsInputStreamTee.cpp new file mode 100644 index 0000000000..3c0d32e0cb --- /dev/null +++ b/xpcom/io/nsInputStreamTee.cpp @@ -0,0 +1,341 @@ +/* -*- 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 <stdlib.h> +#include "mozilla/Logging.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" +#include "nsIInputStreamTee.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +#ifdef LOG +# undef LOG +#endif + +static LazyLogModule sTeeLog("nsInputStreamTee"); +#define LOG(args) MOZ_LOG(sTeeLog, mozilla::LogLevel::Debug, args) + +class nsInputStreamTee final : public nsIInputStreamTee { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIINPUTSTREAMTEE + + nsInputStreamTee(); + bool SinkIsValid(); + void InvalidateSink(); + + private: + ~nsInputStreamTee() = default; + + nsresult TeeSegment(const char* aBuf, uint32_t aCount); + + static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t, + uint32_t, uint32_t*); + + private: + nsCOMPtr<nsIInputStream> mSource; + nsCOMPtr<nsIOutputStream> mSink; + nsCOMPtr<nsIEventTarget> mEventTarget; + nsWriteSegmentFun mWriter; // for implementing ReadSegments + void* mClosure; // for implementing ReadSegments + Maybe<Mutex> mLock; // synchronize access to mSinkIsValid + bool mSinkIsValid; // False if TeeWriteEvent fails +}; + +class nsInputStreamTeeWriteEvent : public Runnable { + public: + // aTee's lock is held across construction of this object + nsInputStreamTeeWriteEvent(const char* aBuf, uint32_t aCount, + nsIOutputStream* aSink, nsInputStreamTee* aTee) + : mozilla::Runnable("nsInputStreamTeeWriteEvent") { + // copy the buffer - will be free'd by dtor + mBuf = (char*)malloc(aCount); + if (mBuf) { + memcpy(mBuf, (char*)aBuf, aCount); + } + mCount = aCount; + mSink = aSink; + bool isNonBlocking; + mSink->IsNonBlocking(&isNonBlocking); + NS_ASSERTION(isNonBlocking == false, "mSink is nonblocking"); + mTee = aTee; + } + + NS_IMETHOD Run() override { + if (!mBuf) { + NS_WARNING( + "nsInputStreamTeeWriteEvent::Run() " + "memory not allocated\n"); + return NS_OK; + } + MOZ_ASSERT(mSink, "mSink is null!"); + + // The output stream could have been invalidated between when + // this event was dispatched and now, so check before writing. + if (!mTee->SinkIsValid()) { + return NS_OK; + } + + LOG( + ("nsInputStreamTeeWriteEvent::Run() [%p]" + "will write %u bytes to %p\n", + this, mCount, mSink.get())); + + uint32_t totalBytesWritten = 0; + while (mCount) { + nsresult rv; + uint32_t bytesWritten = 0; + rv = mSink->Write(mBuf + totalBytesWritten, mCount, &bytesWritten); + if (NS_FAILED(rv)) { + LOG(("nsInputStreamTeeWriteEvent::Run[%p] error %" PRIx32 " in writing", + this, static_cast<uint32_t>(rv))); + mTee->InvalidateSink(); + break; + } + totalBytesWritten += bytesWritten; + NS_ASSERTION(bytesWritten <= mCount, "wrote too much"); + mCount -= bytesWritten; + } + return NS_OK; + } + + protected: + virtual ~nsInputStreamTeeWriteEvent() { + if (mBuf) { + free(mBuf); + } + mBuf = nullptr; + } + + private: + char* mBuf; + uint32_t mCount; + nsCOMPtr<nsIOutputStream> mSink; + // back pointer to the tee that created this runnable + RefPtr<nsInputStreamTee> mTee; +}; + +nsInputStreamTee::nsInputStreamTee() + : mWriter(nullptr), mClosure(nullptr), mSinkIsValid(true) {} + +bool nsInputStreamTee::SinkIsValid() { + MutexAutoLock lock(*mLock); + return mSinkIsValid; +} + +void nsInputStreamTee::InvalidateSink() { + MutexAutoLock lock(*mLock); + mSinkIsValid = false; +} + +nsresult nsInputStreamTee::TeeSegment(const char* aBuf, uint32_t aCount) { + if (!mSink) { + return NS_OK; // nothing to do + } + if (mLock) { // asynchronous case + NS_ASSERTION(mEventTarget, "mEventTarget is null, mLock is not null."); + if (!SinkIsValid()) { + return NS_OK; // nothing to do + } + nsCOMPtr<nsIRunnable> event = + new nsInputStreamTeeWriteEvent(aBuf, aCount, mSink, this); + LOG(("nsInputStreamTee::TeeSegment [%p] dispatching write %u bytes\n", this, + aCount)); + return mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); + } else { // synchronous case + NS_ASSERTION(!mEventTarget, "mEventTarget is not null, mLock is null."); + nsresult rv; + uint32_t totalBytesWritten = 0; + while (aCount) { + uint32_t bytesWritten = 0; + rv = mSink->Write(aBuf + totalBytesWritten, aCount, &bytesWritten); + if (NS_FAILED(rv)) { + // ok, this is not a fatal error... just drop our reference to mSink + // and continue on as if nothing happened. + NS_WARNING("Write failed (non-fatal)"); + // catch possible misuse of the input stream tee + NS_ASSERTION(rv != NS_BASE_STREAM_WOULD_BLOCK, + "sink must be a blocking stream"); + mSink = nullptr; + break; + } + totalBytesWritten += bytesWritten; + NS_ASSERTION(bytesWritten <= aCount, "wrote too much"); + aCount -= bytesWritten; + } + return NS_OK; + } +} + +nsresult nsInputStreamTee::WriteSegmentFun(nsIInputStream* aIn, void* aClosure, + const char* aFromSegment, + uint32_t aOffset, uint32_t aCount, + uint32_t* aWriteCount) { + nsInputStreamTee* tee = reinterpret_cast<nsInputStreamTee*>(aClosure); + nsresult rv = tee->mWriter(aIn, tee->mClosure, aFromSegment, aOffset, aCount, + aWriteCount); + if (NS_FAILED(rv) || (*aWriteCount == 0)) { + NS_ASSERTION((NS_FAILED(rv) ? (*aWriteCount == 0) : true), + "writer returned an error with non-zero writeCount"); + return rv; + } + + return tee->TeeSegment(aFromSegment, *aWriteCount); +} + +NS_IMPL_ISUPPORTS(nsInputStreamTee, nsIInputStreamTee, nsIInputStream) +NS_IMETHODIMP +nsInputStreamTee::Close() { + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + nsresult rv = mSource->Close(); + mSource = nullptr; + mSink = nullptr; + return rv; +} + +NS_IMETHODIMP +nsInputStreamTee::Available(uint64_t* aAvail) { + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->Available(aAvail); +} + +NS_IMETHODIMP +nsInputStreamTee::StreamStatus() { + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->StreamStatus(); +} + +NS_IMETHODIMP +nsInputStreamTee::Read(char* aBuf, uint32_t aCount, uint32_t* aBytesRead) { + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = mSource->Read(aBuf, aCount, aBytesRead); + if (NS_FAILED(rv) || (*aBytesRead == 0)) { + return rv; + } + + return TeeSegment(aBuf, *aBytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aBytesRead) { + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + + mWriter = aWriter; + mClosure = aClosure; + + return mSource->ReadSegments(WriteSegmentFun, this, aCount, aBytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::IsNonBlocking(bool* aResult) { + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->IsNonBlocking(aResult); +} + +NS_IMETHODIMP +nsInputStreamTee::SetSource(nsIInputStream* aSource) { + mSource = aSource; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSource(nsIInputStream** aSource) { + NS_IF_ADDREF(*aSource = mSource); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetSink(nsIOutputStream* aSink) { +#ifdef DEBUG + if (aSink) { + bool nonBlocking; + nsresult rv = aSink->IsNonBlocking(&nonBlocking); + if (NS_FAILED(rv) || nonBlocking) { + NS_ERROR("aSink should be a blocking stream"); + } + } +#endif + mSink = aSink; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSink(nsIOutputStream** aSink) { + NS_IF_ADDREF(*aSink = mSink); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetEventTarget(nsIEventTarget* aEventTarget) { + mEventTarget = aEventTarget; + if (mEventTarget) { + // Only need synchronization if this is an async tee + mLock.emplace("nsInputStreamTee.mLock"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetEventTarget(nsIEventTarget** aEventTarget) { + NS_IF_ADDREF(*aEventTarget = mEventTarget); + return NS_OK; +} + +nsresult NS_NewInputStreamTeeAsync(nsIInputStream** aResult, + nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aEventTarget) { + nsresult rv; + + nsCOMPtr<nsIInputStreamTee> tee = new nsInputStreamTee(); + rv = tee->SetSource(aSource); + if (NS_FAILED(rv)) { + return rv; + } + + rv = tee->SetSink(aSink); + if (NS_FAILED(rv)) { + return rv; + } + + rv = tee->SetEventTarget(aEventTarget); + if (NS_FAILED(rv)) { + return rv; + } + + tee.forget(aResult); + return rv; +} + +nsresult NS_NewInputStreamTee(nsIInputStream** aResult, nsIInputStream* aSource, + nsIOutputStream* aSink) { + return NS_NewInputStreamTeeAsync(aResult, aSource, aSink, nullptr); +} + +#undef LOG diff --git a/xpcom/io/nsLinebreakConverter.cpp b/xpcom/io/nsLinebreakConverter.cpp new file mode 100644 index 0000000000..019fc0e4ae --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.cpp @@ -0,0 +1,452 @@ +/* -*- 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 "nsLinebreakConverter.h" + +#include "nsCRT.h" + +/*---------------------------------------------------------------------------- + GetLinebreakString + + Could make this inline +----------------------------------------------------------------------------*/ +static const char* GetLinebreakString( + nsLinebreakConverter::ELinebreakType aBreakType) { + static const char* const sLinebreaks[] = {"", // any + NS_LINEBREAK, // platform + LFSTR, // content + CRLF, // net + CRSTR, // Mac + LFSTR, // Unix + CRLF, // Windows + " ", // space + nullptr}; + + return sLinebreaks[aBreakType]; +} + +/*---------------------------------------------------------------------------- + AppendLinebreak + + Wee inline method to append a line break. Modifies ioDest. +----------------------------------------------------------------------------*/ +template <class T> +void AppendLinebreak(T*& aIoDest, const char* aLineBreakStr) { + *aIoDest++ = *aLineBreakStr; + + if (aLineBreakStr[1]) { + *aIoDest++ = aLineBreakStr[1]; + } +} + +/*---------------------------------------------------------------------------- + CountChars + + Counts occurrences of breakStr in aSrc +----------------------------------------------------------------------------*/ +template <class T> +int32_t CountLinebreaks(const T* aSrc, int32_t aInLen, const char* aBreakStr) { + const T* src = aSrc; + const T* srcEnd = aSrc + aInLen; + int32_t theCount = 0; + + while (src < srcEnd) { + if (*src == *aBreakStr) { + src++; + + if (aBreakStr[1]) { + if (src < srcEnd && *src == aBreakStr[1]) { + src++; + theCount++; + } + } else { + theCount++; + } + } else { + src++; + } + } + + return theCount; +} + +/*---------------------------------------------------------------------------- + ConvertBreaks + + ioLen *includes* a terminating null, if any +----------------------------------------------------------------------------*/ +template <class T> +static T* ConvertBreaks(const T* aInSrc, int32_t& aIoLen, const char* aSrcBreak, + const char* aDestBreak) { + NS_ASSERTION(aInSrc && aSrcBreak && aDestBreak, "Got a null string"); + + T* resultString = nullptr; + + // handle the no conversion case + if (nsCRT::strcmp(aSrcBreak, aDestBreak) == 0) { + resultString = (T*)malloc(sizeof(T) * aIoLen); + if (!resultString) { + return nullptr; + } + memcpy(resultString, aInSrc, + sizeof(T) * aIoLen); // includes the null, if any + return resultString; + } + + int32_t srcBreakLen = strlen(aSrcBreak); + int32_t destBreakLen = strlen(aDestBreak); + + // handle the easy case, where the string length does not change, and the + // breaks are only 1 char long, i.e. CR <-> LF + if (srcBreakLen == destBreakLen && srcBreakLen == 1) { + resultString = (T*)malloc(sizeof(T) * aIoLen); + if (!resultString) { + return nullptr; + } + + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + T* dst = resultString; + + char srcBreakChar = *aSrcBreak; // we know it's one char long already + char dstBreakChar = *aDestBreak; + + while (src < srcEnd) { + if (*src == srcBreakChar) { + *dst++ = dstBreakChar; + src++; + } else { + *dst++ = *src++; + } + } + + // aIoLen does not change + } else { + // src and dest termination is different length. Do it a slower way. + + // count linebreaks in src. Assumes that chars in 2-char linebreaks are + // unique. + int32_t numLinebreaks = CountLinebreaks(aInSrc, aIoLen, aSrcBreak); + + int32_t newBufLen = + aIoLen - (numLinebreaks * srcBreakLen) + (numLinebreaks * destBreakLen); + resultString = (T*)malloc(sizeof(T) * newBufLen); + if (!resultString) { + return nullptr; + } + + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + T* dst = resultString; + + while (src < srcEnd) { + if (*src == *aSrcBreak) { + *dst++ = *aDestBreak; + if (aDestBreak[1]) { + *dst++ = aDestBreak[1]; + } + + src++; + if (src < srcEnd && aSrcBreak[1] && *src == aSrcBreak[1]) { + src++; + } + } else { + *dst++ = *src++; + } + } + + aIoLen = newBufLen; + } + + return resultString; +} + +/*---------------------------------------------------------------------------- + ConvertBreaksInSitu + + Convert breaks in situ. Can only do this if the linebreak length + does not change. +----------------------------------------------------------------------------*/ +template <class T> +static void ConvertBreaksInSitu(T* aInSrc, int32_t aInLen, char aSrcBreak, + char aDestBreak) { + T* src = aInSrc; + T* srcEnd = aInSrc + aInLen; + + while (src < srcEnd) { + if (*src == aSrcBreak) { + *src = aDestBreak; + } + + src++; + } +} + +/*---------------------------------------------------------------------------- + ConvertUnknownBreaks + + Convert unknown line breaks to the specified break. + + This will convert CRLF pairs to one break, and single CR or LF to a break. +----------------------------------------------------------------------------*/ +template <class T> +static T* ConvertUnknownBreaks(const T* aInSrc, int32_t& aIoLen, + const char* aDestBreak) { + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + + int32_t destBreakLen = strlen(aDestBreak); + int32_t finalLen = 0; + + while (src < srcEnd) { + if (*src == nsCRT::CR) { + if (src + 1 < srcEnd && src[1] == nsCRT::LF) { + // CRLF + finalLen += destBreakLen; + src++; + } else { + // Lone CR + finalLen += destBreakLen; + } + } else if (*src == nsCRT::LF) { + // Lone LF + finalLen += destBreakLen; + } else { + finalLen++; + } + src++; + } + + T* resultString = (T*)malloc(sizeof(T) * finalLen); + if (!resultString) { + return nullptr; + } + + src = aInSrc; + srcEnd = aInSrc + aIoLen; // includes null, if any + + T* dst = resultString; + + while (src < srcEnd) { + if (*src == nsCRT::CR) { + if (src + 1 < srcEnd && src[1] == nsCRT::LF) { + // CRLF + AppendLinebreak(dst, aDestBreak); + src++; + } else { + // Lone CR + AppendLinebreak(dst, aDestBreak); + } + } else if (*src == nsCRT::LF) { + // Lone LF + AppendLinebreak(dst, aDestBreak); + } else { + *dst++ = *src; + } + src++; + } + + aIoLen = finalLen; + return resultString; +} + +/*---------------------------------------------------------------------------- + ConvertLineBreaks + +----------------------------------------------------------------------------*/ +char* nsLinebreakConverter::ConvertLineBreaks(const char* aSrc, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen, + int32_t* aOutLen) { + NS_ASSERTION(aDestBreaks != eLinebreakAny && aSrcBreaks != eLinebreakSpace, + "Invalid parameter"); + if (!aSrc) { + return nullptr; + } + + int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(aSrc) + 1 : aSrcLen; + + char* resultString; + if (aSrcBreaks == eLinebreakAny) { + resultString = + ConvertUnknownBreaks(aSrc, sourceLen, GetLinebreakString(aDestBreaks)); + } else + resultString = + ConvertBreaks(aSrc, sourceLen, GetLinebreakString(aSrcBreaks), + GetLinebreakString(aDestBreaks)); + + if (aOutLen) { + *aOutLen = sourceLen; + } + return resultString; +} + +/*---------------------------------------------------------------------------- + ConvertLineBreaksInSitu + +----------------------------------------------------------------------------*/ +nsresult nsLinebreakConverter::ConvertLineBreaksInSitu( + char** aIoBuffer, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) { + NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed"); + if (!aIoBuffer || !*aIoBuffer) { + return NS_ERROR_NULL_POINTER; + } + + NS_ASSERTION(aDestBreaks != eLinebreakAny && aSrcBreaks != eLinebreakSpace, + "Invalid parameter"); + + int32_t sourceLen = + (aSrcLen == kIgnoreLen) ? strlen(*aIoBuffer) + 1 : aSrcLen; + + // can we convert in-place? + const char* srcBreaks = GetLinebreakString(aSrcBreaks); + const char* dstBreaks = GetLinebreakString(aDestBreaks); + + if (aSrcBreaks != eLinebreakAny && strlen(srcBreaks) == 1 && + strlen(dstBreaks) == 1) { + ConvertBreaksInSitu(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (aOutLen) { + *aOutLen = sourceLen; + } + } else { + char* destBuffer; + + if (aSrcBreaks == eLinebreakAny) { + destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks); + } else { + destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks); + } + + if (!destBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aIoBuffer = destBuffer; + if (aOutLen) { + *aOutLen = sourceLen; + } + } + + return NS_OK; +} + +/*---------------------------------------------------------------------------- + ConvertUnicharLineBreaks + +----------------------------------------------------------------------------*/ +char16_t* nsLinebreakConverter::ConvertUnicharLineBreaks( + const char16_t* aSrc, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) { + NS_ASSERTION(aDestBreaks != eLinebreakAny && aSrcBreaks != eLinebreakSpace, + "Invalid parameter"); + if (!aSrc) { + return nullptr; + } + + int32_t bufLen = (aSrcLen == kIgnoreLen) ? NS_strlen(aSrc) + 1 : aSrcLen; + + char16_t* resultString; + if (aSrcBreaks == eLinebreakAny) { + resultString = + ConvertUnknownBreaks(aSrc, bufLen, GetLinebreakString(aDestBreaks)); + } else + resultString = ConvertBreaks(aSrc, bufLen, GetLinebreakString(aSrcBreaks), + GetLinebreakString(aDestBreaks)); + + if (aOutLen) { + *aOutLen = bufLen; + } + return resultString; +} + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult nsLinebreakConverter::ConvertUnicharLineBreaksInSitu( + char16_t** aIoBuffer, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) { + NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed"); + if (!aIoBuffer || !*aIoBuffer) { + return NS_ERROR_NULL_POINTER; + } + NS_ASSERTION(aDestBreaks != eLinebreakAny && aSrcBreaks != eLinebreakSpace, + "Invalid parameter"); + + int32_t sourceLen = + (aSrcLen == kIgnoreLen) ? NS_strlen(*aIoBuffer) + 1 : aSrcLen; + + // can we convert in-place? + const char* srcBreaks = GetLinebreakString(aSrcBreaks); + const char* dstBreaks = GetLinebreakString(aDestBreaks); + + if ((aSrcBreaks != eLinebreakAny) && (strlen(srcBreaks) == 1) && + (strlen(dstBreaks) == 1)) { + ConvertBreaksInSitu(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (aOutLen) { + *aOutLen = sourceLen; + } + } else { + char16_t* destBuffer; + + if (aSrcBreaks == eLinebreakAny) { + destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks); + } else { + destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks); + } + + if (!destBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aIoBuffer = destBuffer; + if (aOutLen) { + *aOutLen = sourceLen; + } + } + + return NS_OK; +} + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult nsLinebreakConverter::ConvertStringLineBreaks( + nsString& aIoString, ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks) { + NS_ASSERTION(aDestBreaks != eLinebreakAny && aSrcBreaks != eLinebreakSpace, + "Invalid parameter"); + + // nothing to do + if (aIoString.IsEmpty()) { + return NS_OK; + } + + nsresult rv; + + // remember the old buffer in case + // we blow it away later + char16_t* stringBuf = aIoString.BeginWriting(mozilla::fallible); + if (!stringBuf) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t newLen; + + rv = ConvertUnicharLineBreaksInSitu(&stringBuf, aSrcBreaks, aDestBreaks, + aIoString.Length() + 1, &newLen); + if (NS_FAILED(rv)) { + return rv; + } + + const char16_t* currentBuf = aIoString.get(); + if (currentBuf != stringBuf) { + aIoString.Adopt(stringBuf, newLen - 1); + } + + return NS_OK; +} diff --git a/xpcom/io/nsLinebreakConverter.h b/xpcom/io/nsLinebreakConverter.h new file mode 100644 index 0000000000..2ecde3beec --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.h @@ -0,0 +1,141 @@ +/* -*- 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 nsLinebreakConverter_h_ +#define nsLinebreakConverter_h_ + +#include "nscore.h" +#include "nsString.h" + +// utility class for converting between different line breaks. + +class nsLinebreakConverter { + public: + // Note: enum must match char* array in GetLinebreakString + typedef enum { + eLinebreakAny, // any kind of linebreak (i.e. "don't care" source) + + eLinebreakPlatform, // platform linebreak + eLinebreakContent, // Content model linebreak (LF) + eLinebreakNet, // Form submission linebreak (CRLF) + + eLinebreakMac, // CR + eLinebreakUnix, // LF + eLinebreakWindows, // CRLF + + eLinebreakSpace // space characters. Only valid as destination type + + } ELinebreakType; + + enum { kIgnoreLen = -1 }; + + /* ConvertLineBreaks + * Convert line breaks in the supplied string, allocating and returning + * a new buffer. Returns nullptr on failure. + * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is + * assumed to be null terminated, otherwise it must be at least + * aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass + * eLinebreakAny. If known, pass the known value, as this may + * be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source. If -1, the source is assumed to be a + * null-terminated string. + * @param aOutLen: used to return character length of returned buffer, if not + * null. + */ + static char* ConvertLineBreaks(const char* aSrc, ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, + int32_t* aOutLen = nullptr); + + /* ConvertUnicharLineBreaks + * Convert line breaks in the supplied string, allocating and returning + * a new buffer. Returns nullptr on failure. + * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is + * assumed to be null terminated, otherwise it must be at least + * aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass + * eLinebreakAny. If known, pass the known value, as this may + * be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source, in characters. If -1, the source is + * assumed to be a null-terminated string. + * @param aOutLen: used to return character length of returned buffer, if not + * null. + */ + static char16_t* ConvertUnicharLineBreaks(const char16_t* aSrc, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, + int32_t* aOutLen = nullptr); + + /* ConvertStringLineBreaks + * Convert line breaks in the supplied string, changing the string buffer + * (i.e. in-place conversion) + * @param ioString: the string to be converted. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass + * eLinebreakAny. If known, pass the known value, as this may + * be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source, in characters. If -1, the source is + * assumed to be a null-terminated string. + */ + static nsresult ConvertStringLineBreaks(nsString& aIoString, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks); + + /* ConvertLineBreaksInSitu + * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE + * BUFFER, BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So + * be prepared to keep a copy of the old pointer, and free it if this passes + * back a new pointer. ALSO NOTE: DON'T PASS A STATIC STRING POINTER TO THIS + * FUNCTION. + * + * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string + * is assumed to be null terminated, otherwise it must be at + * least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass + * eLinebreakAny. If known, pass the known value, as this may + * be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source. If -1, the source is assumed to be a + * null-terminated string. + * @param aOutLen: used to return character length of returned buffer, if not + * null. + */ + static nsresult ConvertLineBreaksInSitu(char** aIoBuffer, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, + int32_t* aOutLen = nullptr); + + /* ConvertUnicharLineBreaksInSitu + * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE + * BUFFER, BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So + * be prepared to keep a copy of the old pointer, and free it if this passes + * back a new pointer. + * + * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string + * is assumed to be null terminated, otherwise it must be at + * least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass + * eLinebreakAny. If known, pass the known value, as this may + * be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source in characters. If -1, the source is + * assumed to be a null-terminated string. + * @param aOutLen: used to return character length of returned buffer, if not + * null. + */ + static nsresult ConvertUnicharLineBreaksInSitu(char16_t** aIoBuffer, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, + int32_t* aOutLen = nullptr); +}; + +#endif // nsLinebreakConverter_h_ diff --git a/xpcom/io/nsLocalFile.h b/xpcom/io/nsLocalFile.h new file mode 100644 index 0000000000..779b2e6c95 --- /dev/null +++ b/xpcom/io/nsLocalFile.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NS_LOCAL_FILE_H_ +#define _NS_LOCAL_FILE_H_ + +#include "nscore.h" + +#define NS_LOCAL_FILE_CID \ + { \ + 0x2e23e220, 0x60be, 0x11d3, { \ + 0x8c, 0x4a, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74 \ + } \ + } + +#define NS_DECL_NSLOCALFILE_UNICODE_METHODS \ + nsresult AppendUnicode(const char16_t* aNode); \ + nsresult GetUnicodeLeafName(char16_t** aLeafName); \ + nsresult SetUnicodeLeafName(const char16_t* aLeafName); \ + nsresult CopyToUnicode(nsIFile* aNewParentDir, \ + const char16_t* aNewLeafName); \ + nsresult CopyToFollowingLinksUnicode(nsIFile* aNewParentDir, \ + const char16_t* aNewLeafName); \ + nsresult MoveToUnicode(nsIFile* aNewParentDir, \ + const char16_t* aNewLeafName); \ + nsresult GetUnicodeTarget(char16_t** aTarget); \ + nsresult GetUnicodePath(char16_t** aPath); \ + nsresult InitWithUnicodePath(const char16_t* aPath); \ + nsresult AppendRelativeUnicodePath(const char16_t* aRelativePath); + +// XPCOMInit needs to know about how we are implemented, +// so here we will export it. Other users should not depend +// on this. + +#include <errno.h> +#include "nsIFile.h" + +#ifdef XP_WIN +# include "nsLocalFileWin.h" +#elif defined(XP_UNIX) +# include "nsLocalFileUnix.h" +#else +# error NOT_IMPLEMENTED +#endif + +#define NSRESULT_FOR_RETURN(ret) (((ret) < 0) ? NSRESULT_FOR_ERRNO() : NS_OK) + +inline nsresult nsresultForErrno(int aErr) { + switch (aErr) { + case 0: + return NS_OK; +#ifdef EDQUOT + case EDQUOT: /* Quota exceeded */ + [[fallthrough]]; // to NS_ERROR_FILE_NO_DEVICE_SPACE +#endif + case ENOSPC: + return NS_ERROR_FILE_NO_DEVICE_SPACE; +#ifdef EISDIR + case EISDIR: /* Is a directory. */ + return NS_ERROR_FILE_IS_DIRECTORY; +#endif + case ENAMETOOLONG: + return NS_ERROR_FILE_NAME_TOO_LONG; + case ENOEXEC: /* Executable file format error. */ + return NS_ERROR_FILE_EXECUTION_FAILED; + case ENOENT: + return NS_ERROR_FILE_NOT_FOUND; + case ENOTDIR: + return NS_ERROR_FILE_DESTINATION_NOT_DIR; +#ifdef ELOOP + case ELOOP: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; +#endif /* ELOOP */ +#ifdef ENOLINK + case ENOLINK: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; +#endif /* ENOLINK */ + case EEXIST: + return NS_ERROR_FILE_ALREADY_EXISTS; +#ifdef EPERM + case EPERM: +#endif /* EPERM */ + case EACCES: + return NS_ERROR_FILE_ACCESS_DENIED; +#ifdef EROFS + case EROFS: /* Read-only file system. */ + return NS_ERROR_FILE_READ_ONLY; +#endif + /* + * On AIX 4.3, ENOTEMPTY is defined as EEXIST, + * so there can't be cases for both without + * preprocessing. + */ +#if ENOTEMPTY != EEXIST + case ENOTEMPTY: + return NS_ERROR_FILE_DIR_NOT_EMPTY; +#endif /* ENOTEMPTY != EEXIST */ + /* Note that nsIFile.createUnique() returns + NS_ERROR_FILE_TOO_BIG when it cannot create a temporary + file with a unique filename. + See https://developer.mozilla.org/en-US/docs/Table_Of_Errors + Other usages of NS_ERROR_FILE_TOO_BIG in the source tree + are in line with the POSIX semantics of EFBIG. + So this is a reasonably good approximation. + */ + case EFBIG: /* File too large. */ + return NS_ERROR_FILE_TOO_BIG; + +#ifdef ENOATTR + case ENOATTR: + return NS_ERROR_NOT_AVAILABLE; +#endif // ENOATTR + + default: + return NS_ERROR_FAILURE; + } +} + +#define NSRESULT_FOR_ERRNO() nsresultForErrno(errno) + +#endif diff --git a/xpcom/io/nsLocalFileCommon.cpp b/xpcom/io/nsLocalFileCommon.cpp new file mode 100644 index 0000000000..f6eabf2d0f --- /dev/null +++ b/xpcom/io/nsLocalFileCommon.cpp @@ -0,0 +1,438 @@ +/* -*- 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 "nsLocalFile.h" // includes platform-specific headers + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsReadableUtils.h" +#include "nsPrintfCString.h" +#include "nsCRT.h" +#include "nsNativeCharsetUtils.h" +#include "nsUTF8Utils.h" +#include "nsArray.h" +#include "nsLocalFileCommon.h" + +#ifdef XP_WIN +# include <string.h> +#endif + +// Extensions that should be considered 'executable', ie will not allow users +// to open immediately without first saving to disk, and potentially provoke +// other warnings. PLEASE read the longer comment in +// toolkit/components/reputationservice/ApplicationReputation.cpp +// before modifying this list! +// If you update this list, make sure to update the length of sExecutableExts +// in nsLocalFileCommmon.h. +/* static */ +const char* const sExecutableExts[] = { + // clang-format off + ".accda", // MS Access database + ".accdb", // MS Access database + ".accde", // MS Access database + ".accdr", // MS Access database + ".ad", + ".ade", // access project extension + ".adp", + ".afploc", // Apple Filing Protocol Location + ".air", // Adobe AIR installer + ".app", // executable application + ".application", // from bug 348763 + ".appref-ms", // ClickOnce link + ".appx", + ".appxbundle", + ".asp", + ".atloc", // Appletalk Location + ".bas", + ".bat", + ".cer", // Signed certificate file + ".chm", + ".cmd", + ".com", + ".cpl", + ".crt", + ".der", + ".diagcab", // Windows archive + ".exe", + ".fileloc", // Apple finder internet location data file + ".ftploc", // Apple FTP Location + ".fxp", // FoxPro compiled app + ".hlp", + ".hta", + ".inetloc", // Apple finder internet location data file + ".inf", + ".ins", + ".isp", + ".jar", // java application bundle +#ifndef MOZ_ESR + ".jnlp", +#endif + ".js", + ".jse", + ".lnk", + ".mad", // Access Module Shortcut + ".maf", // Access + ".mag", // Access Diagram Shortcut + ".mam", // Access Macro Shortcut + ".maq", // Access Query Shortcut + ".mar", // Access Report Shortcut + ".mas", // Access Stored Procedure + ".mat", // Access Table Shortcut + ".mau", // Media Attachment Unit + ".mav", // Access View Shortcut + ".maw", // Access Data Access Page + ".mda", // Access Add-in, MDA Access 2 Workgroup + ".mdb", + ".mde", + ".mdt", // Access Add-in Data + ".mdw", // Access Workgroup Information + ".mdz", // Access Wizard Template + ".msc", + ".msh", // Microsoft Shell + ".msh1", // Microsoft Shell + ".msh1xml", // Microsoft Shell + ".msh2", // Microsoft Shell + ".msh2xml", // Microsoft Shell + ".mshxml", // Microsoft Shell + ".msi", + ".msix", + ".msixbundle", + ".msp", + ".mst", + ".ops", // Office Profile Settings + ".pcd", + ".pif", + ".plg", // Developer Studio Build Log + ".prf", // windows system file + ".prg", + ".pst", + ".reg", + ".scf", // Windows explorer command + ".scr", + ".sct", + ".settingcontent-ms", + ".shb", + ".shs", + ".url", + ".vb", + ".vbe", + ".vbs", + ".vdx", + ".vsd", + ".vsdm", + ".vsdx", + ".vsmacros", // Visual Studio .NET Binary-based Macro Project + ".vss", + ".vssm", + ".vssx", + ".vst", + ".vstm", + ".vstx", + ".vsw", + ".vsx", + ".vtx", + ".webloc", // MacOS website location file + ".ws", + ".wsc", + ".wsf", + ".wsh", + ".xll" // MS Excel dynamic link library + // clang-format on +}; + +#if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN) +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile* aFile) { + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString path; + aFile->GetNativePath(path); + if (path.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + return InitWithNativePath(path); +} +#endif + +#define kMaxFilenameLength 255 +#define kMaxExtensionLength 100 +#define kMaxSequenceNumberLength 5 // "-9999" +// requirement: kMaxExtensionLength < +// kMaxFilenameLength - kMaxSequenceNumberLength + +NS_IMETHODIMP +nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes) { + nsresult rv; + bool longName; + +#ifdef XP_WIN + nsAutoString pathName, leafName, rootName, suffix; + rv = GetPath(pathName); +#else + nsAutoCString pathName, leafName, rootName, suffix; + rv = GetNativePath(pathName); +#endif + if (NS_FAILED(rv)) { + return rv; + } + + auto FailedBecauseExists = [&](nsresult aRv) { + if (aRv == NS_ERROR_FILE_ACCESS_DENIED) { + bool exists; + return NS_SUCCEEDED(Exists(&exists)) && exists; + } + return aRv == NS_ERROR_FILE_ALREADY_EXISTS; + }; + + longName = + (pathName.Length() + kMaxSequenceNumberLength > kMaxFilenameLength); + if (!longName) { + rv = Create(aType, aAttributes); + if (!FailedBecauseExists(rv)) { + return rv; + } + } + +#ifdef XP_WIN + rv = GetLeafName(leafName); + if (NS_FAILED(rv)) { + return rv; + } + + const int32_t lastDot = leafName.RFindChar(char16_t('.')); +#else + rv = GetNativeLeafName(leafName); + if (NS_FAILED(rv)) { + return rv; + } + + const int32_t lastDot = leafName.RFindChar('.'); +#endif + + if (lastDot == kNotFound) { + rootName = leafName; + } else { + suffix = Substring(leafName, lastDot); // include '.' + rootName = Substring(leafName, 0, lastDot); // strip suffix and dot + } + + if (longName) { + int32_t maxRootLength = + (kMaxFilenameLength - (pathName.Length() - leafName.Length()) - + suffix.Length() - kMaxSequenceNumberLength); + + // We cannot create an item inside a directory whose name is too long. + // Also, ensure that at least one character remains after we truncate + // the root name, as we don't want to end up with an empty leaf name. + if (maxRootLength < 2) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + +#ifdef XP_WIN + // ensure that we don't cut the name in mid-UTF16-character + rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength]) + ? maxRootLength - 1 + : maxRootLength); + SetLeafName(rootName + suffix); +#else + if (NS_IsNativeUTF8()) { + // ensure that we don't cut the name in mid-UTF8-character + // (assume the name is valid UTF8 to begin with) + while (UTF8traits::isInSeq(rootName[maxRootLength])) { + --maxRootLength; + } + + // Another check to avoid ending up with an empty leaf name. + if (maxRootLength == 0 && suffix.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + rootName.SetLength(maxRootLength); + SetNativeLeafName(rootName + suffix); +#endif + nsresult rvCreate = Create(aType, aAttributes); + if (!FailedBecauseExists(rvCreate)) { + return rvCreate; + } + } + + for (int indx = 1; indx < 10000; ++indx) { + // start with "Picture-1.jpg" after "Picture.jpg" exists +#ifdef XP_WIN + SetLeafName(rootName + + NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + suffix); +#else + SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix); +#endif + rv = Create(aType, aAttributes); + if (NS_SUCCEEDED(rv) || !FailedBecauseExists(rv)) { + return rv; + } + } + + // The disk is full, sort of + return NS_ERROR_FILE_TOO_BIG; +} + +#if defined(XP_WIN) +static const char16_t kPathSeparatorChar = '\\'; +#elif defined(XP_UNIX) +static const char16_t kPathSeparatorChar = '/'; +#else +# error Need to define file path separator for your platform +#endif + +static void SplitPath(char16_t* aPath, nsTArray<char16_t*>& aNodeArray) { + if (*aPath == 0) { + return; + } + + if (*aPath == kPathSeparatorChar) { + aPath++; + } + aNodeArray.AppendElement(aPath); + + for (char16_t* cp = aPath; *cp != 0; ++cp) { + if (*cp == kPathSeparatorChar) { + *cp++ = 0; + if (*cp == 0) { + break; + } + aNodeArray.AppendElement(cp); + } + } +} + +NS_IMETHODIMP +nsLocalFile::GetRelativeDescriptor(nsIFile* aFromFile, nsACString& aResult) { + if (NS_WARN_IF(!aFromFile)) { + return NS_ERROR_INVALID_ARG; + } + + // + // aResult will be UTF-8 encoded + // + + nsresult rv; + aResult.Truncate(0); + + nsAutoString thisPath, fromPath; + AutoTArray<char16_t*, 32> thisNodes; + AutoTArray<char16_t*, 32> fromNodes; + + rv = GetPath(thisPath); + if (NS_FAILED(rv)) { + return rv; + } + rv = aFromFile->GetPath(fromPath); + if (NS_FAILED(rv)) { + return rv; + } + + // get raw pointer to mutable string buffer + char16_t* thisPathPtr = thisPath.BeginWriting(); + char16_t* fromPathPtr = fromPath.BeginWriting(); + + SplitPath(thisPathPtr, thisNodes); + SplitPath(fromPathPtr, fromNodes); + + size_t nodeIndex; + for (nodeIndex = 0; + nodeIndex < thisNodes.Length() && nodeIndex < fromNodes.Length(); + ++nodeIndex) { +#ifdef XP_WIN + if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]), + char16ptr_t(fromNodes[nodeIndex]))) { + break; + } +#else + if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) { + break; + } +#endif + } + + size_t branchIndex = nodeIndex; + for (nodeIndex = branchIndex; nodeIndex < fromNodes.Length(); ++nodeIndex) { + aResult.AppendLiteral("../"); + } + StringJoinAppend(aResult, "/"_ns, mozilla::Span{thisNodes}.From(branchIndex), + [](nsACString& dest, const auto& thisNode) { + // XXX(Bug 1682869) We wouldn't need to reconstruct a + // nsDependentString here if SplitPath already returned + // nsDependentString. In fact, it seems SplitPath might be + // replaced by ParseString? + AppendUTF16toUTF8(nsDependentString{thisNode}, dest); + }); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile, + const nsACString& aRelativeDesc) { + constexpr auto kParentDirStr = "../"_ns; + + nsCOMPtr<nsIFile> targetFile; + nsresult rv = aFromFile->Clone(getter_AddRefs(targetFile)); + if (NS_FAILED(rv)) { + return rv; + } + + // + // aRelativeDesc is UTF-8 encoded + // + + nsCString::const_iterator strBegin, strEnd; + aRelativeDesc.BeginReading(strBegin); + aRelativeDesc.EndReading(strEnd); + + nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd); + nsCString::const_iterator pos(strBegin); + + nsCOMPtr<nsIFile> parentDir; + while (FindInReadable(kParentDirStr, nodeBegin, nodeEnd)) { + rv = targetFile->GetParent(getter_AddRefs(parentDir)); + if (NS_FAILED(rv)) { + return rv; + } + if (!parentDir) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + targetFile = parentDir; + + nodeBegin = nodeEnd; + pos = nodeEnd; + nodeEnd = strEnd; + } + + nodeBegin = nodeEnd = pos; + while (nodeEnd != strEnd) { + FindCharInReadable('/', nodeEnd, strEnd); + targetFile->Append(NS_ConvertUTF8toUTF16(Substring(nodeBegin, nodeEnd))); + if (nodeEnd != strEnd) { // If there's more left in the string, inc over + // the '/' nodeEnd is on. + ++nodeEnd; + } + nodeBegin = nodeEnd; + } + + return InitWithFile(targetFile); +} + +NS_IMETHODIMP +nsLocalFile::GetRelativePath(nsIFile* aFromFile, nsACString& aResult) { + return GetRelativeDescriptor(aFromFile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::SetRelativePath(nsIFile* aFromFile, + const nsACString& aRelativePath) { + return SetRelativeDescriptor(aFromFile, aRelativePath); +} diff --git a/xpcom/io/nsLocalFileCommon.h b/xpcom/io/nsLocalFileCommon.h new file mode 100644 index 0000000000..3db0ac9e89 --- /dev/null +++ b/xpcom/io/nsLocalFileCommon.h @@ -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/. */ + +#ifndef _NS_LOCAL_FILE_COMMON_H_ +#define _NS_LOCAL_FILE_COMMON_H_ + +#ifdef MOZ_ESR +extern const char* const sExecutableExts[107]; +#else +extern const char* const sExecutableExts[108]; +#endif + +#endif diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp new file mode 100644 index 0000000000..ae501f4041 --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -0,0 +1,2932 @@ +/* -*- 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/. */ + +/** + * Implementation of nsIFile for "unixy" systems. + */ + +#include "nsLocalFile.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/FilePreferences.h" +#include "prtime.h" + +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <dirent.h> + +#if defined(XP_MACOSX) +# include <sys/xattr.h> +#endif + +#if defined(USE_LINUX_QUOTACTL) +# include <sys/mount.h> +# include <sys/quota.h> +# include <sys/sysmacros.h> +# ifndef BLOCK_SIZE +# define BLOCK_SIZE 1024 /* kernel block size */ +# endif +#endif + +#include "nsDirectoryServiceDefs.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsIDirectoryEnumerator.h" +#include "nsSimpleEnumerator.h" +#include "private/pprio.h" +#include "prlink.h" + +#ifdef MOZ_WIDGET_GTK +# include "nsIGIOService.h" +#endif + +#ifdef MOZ_WIDGET_COCOA +# include <Carbon/Carbon.h> +# include "CocoaFileUtils.h" +# include "prmem.h" +# include "plbase64.h" + +static nsresult MacErrorMapper(OSErr inErr); +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/java/GeckoAppShellWrappers.h" +# include "nsIMIMEService.h" +# include <linux/magic.h> +#endif + +#include "nsNativeCharsetUtils.h" +#include "nsTraceRefcnt.h" + +/** + * we need these for statfs() + */ +#ifdef HAVE_SYS_STATVFS_H +# if defined(__osf__) && defined(__DECCXX) +extern "C" int statvfs(const char*, struct statvfs*); +# endif +# include <sys/statvfs.h> +#endif + +#ifdef HAVE_SYS_STATFS_H +# include <sys/statfs.h> +#endif + +#ifdef HAVE_SYS_VFS_H +# include <sys/vfs.h> +#endif + +#if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__)) +# define STATFS statvfs64 +# define F_BSIZE f_frsize +#elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__)) +# define STATFS statvfs +# define F_BSIZE f_frsize +#elif defined(HAVE_STATFS64) +# define STATFS statfs64 +# define F_BSIZE f_bsize +#elif defined(HAVE_STATFS) +# define STATFS statfs +# define F_BSIZE f_bsize +#endif + +using namespace mozilla; + +#define ENSURE_STAT_CACHE() \ + do { \ + if (!FillStatCache()) return NSRESULT_FOR_ERRNO(); \ + } while (0) + +#define CHECK_mPath() \ + do { \ + if (mPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \ + if (!FilePreferences::IsAllowedPath(mPath)) \ + return NS_ERROR_FILE_ACCESS_DENIED; \ + } while (0) + +static PRTime TimespecToMillis(const struct timespec& aTimeSpec) { + return PRTime(aTimeSpec.tv_sec) * PR_MSEC_PER_SEC + + PRTime(aTimeSpec.tv_nsec) / PR_NSEC_PER_MSEC; +} + +/* directory enumerator */ +class nsDirEnumeratorUnix final : public nsSimpleEnumerator, + public nsIDirectoryEnumerator { + public: + nsDirEnumeratorUnix(); + + // nsISupports interface + NS_DECL_ISUPPORTS_INHERITED + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsIDirectoryEnumerator interface + NS_DECL_NSIDIRECTORYENUMERATOR + + NS_IMETHOD Init(nsLocalFile* aParent, bool aIgnored); + + NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + private: + ~nsDirEnumeratorUnix() override; + + protected: + NS_IMETHOD GetNextEntry(); + + DIR* mDir; + struct dirent* mEntry; + nsCString mParentPath; +}; + +nsDirEnumeratorUnix::nsDirEnumeratorUnix() : mDir(nullptr), mEntry(nullptr) {} + +nsDirEnumeratorUnix::~nsDirEnumeratorUnix() { Close(); } + +NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix, nsSimpleEnumerator, + nsIDirectoryEnumerator) + +NS_IMETHODIMP +nsDirEnumeratorUnix::Init(nsLocalFile* aParent, + bool aResolveSymlinks /*ignored*/) { + nsAutoCString dirPath; + if (NS_FAILED(aParent->GetNativePath(dirPath)) || dirPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // When enumerating the directory, the paths must have a slash at the end. + nsAutoCString dirPathWithSlash(dirPath); + dirPathWithSlash.Append('/'); + if (!FilePreferences::IsAllowedPath(dirPathWithSlash)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + if (NS_FAILED(aParent->GetNativePath(mParentPath))) { + return NS_ERROR_FAILURE; + } + + mDir = opendir(dirPath.get()); + if (!mDir) { + return NSRESULT_FOR_ERRNO(); + } + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::HasMoreElements(bool* aResult) { + *aResult = mDir && mEntry; + if (!*aResult) { + Close(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNext(nsISupports** aResult) { + nsCOMPtr<nsIFile> file; + nsresult rv = GetNextFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + if (!file) { + return NS_ERROR_FAILURE; + } + file.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNextEntry() { + do { + errno = 0; + mEntry = readdir(mDir); + + // end of dir or error + if (!mEntry) { + return NSRESULT_FOR_ERRNO(); + } + + // keep going past "." and ".." + } while (mEntry->d_name[0] == '.' && + (mEntry->d_name[1] == '\0' || // .\0 + (mEntry->d_name[1] == '.' && mEntry->d_name[2] == '\0'))); // ..\0 + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNextFile(nsIFile** aResult) { + nsresult rv; + if (!mDir || !mEntry) { + *aResult = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIFile> file = new nsLocalFile(); + + if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) || + NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) { + return rv; + } + + file.forget(aResult); + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::Close() { + if (mDir) { + closedir(mDir); + mDir = nullptr; + } + return NS_OK; +} + +nsLocalFile::nsLocalFile() : mCachedStat() {} + +nsLocalFile::nsLocalFile(const nsACString& aFilePath) : mCachedStat() { + InitWithNativePath(aFilePath); +} + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) : mPath(aOther.mPath) {} + +#ifdef MOZ_WIDGET_COCOA +NS_IMPL_ISUPPORTS(nsLocalFile, nsILocalFileMac, nsIFile) +#else +NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile) +#endif + +nsresult nsLocalFile::nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr) { + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + + *aInstancePtr = nullptr; + + nsCOMPtr<nsIFile> inst = new nsLocalFile(); + return inst->QueryInterface(aIID, aInstancePtr); +} + +bool nsLocalFile::FillStatCache() { + if (!FilePreferences::IsAllowedPath(mPath)) { + errno = EACCES; + return false; + } + + if (STAT(mPath.get(), &mCachedStat) == -1) { + // try lstat it may be a symlink + if (LSTAT(mPath.get(), &mCachedStat) == -1) { + return false; + } + } + return true; +} + +NS_IMETHODIMP +nsLocalFile::Clone(nsIFile** aFile) { + // Just copy-construct ourselves + RefPtr<nsLocalFile> copy = new nsLocalFile(*this); + copy.forget(aFile); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString& aFilePath) { + if (!aFilePath.IsEmpty() && aFilePath.First() == '~') { + if (aFilePath.Length() == 1 || aFilePath.CharAt(1) == '/') { + // Home dir for the current user + + nsCOMPtr<nsIFile> homeDir; + nsAutoCString homePath; + if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_HOME_DIR, + getter_AddRefs(homeDir))) || + NS_FAILED(homeDir->GetNativePath(homePath))) { + return NS_ERROR_FAILURE; + } + + mPath = homePath; + if (aFilePath.Length() > 2) { + mPath.Append(Substring(aFilePath, 1)); + } + } else { + // Home dir for an arbitrary user e.g. `~foo/bar` -> `/home/foo/bar` + // (`/Users/foo/bar` on Mac). The accurate way to get this directory + // is with `getpwnam`, but we would like to avoid doing blocking + // filesystem I/O while creating an `nsIFile`. + + mPath = +#ifdef XP_MACOSX + "/Users/"_ns +#else + "/home/"_ns +#endif + + Substring(aFilePath, 1); + } + } else { + if (aFilePath.IsEmpty() || aFilePath.First() != '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + mPath = aFilePath; + } + + if (!FilePreferences::IsAllowedPath(mPath)) { + mPath.Truncate(); + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // trim off trailing slashes + ssize_t len = mPath.Length(); + while ((len > 1) && (mPath[len - 1] == '/')) { + --len; + } + mPath.SetLength(len); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CreateAllAncestors(uint32_t aPermissions) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // <jband> I promise to play nice + char* buffer = mPath.BeginWriting(); + char* slashp = buffer; + int mkdir_result = 0; + int mkdir_errno; + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: before: %s\n", buffer); +#endif + + while ((slashp = strchr(slashp + 1, '/'))) { + /* + * Sequences of '/' are equivalent to a single '/'. + */ + if (slashp[1] == '/') { + continue; + } + + /* + * If the path has a trailing slash, don't make the last component, + * because we'll get EEXIST in Create when we try to build the final + * component again, and it's easier to condition the logic here than + * there. + */ + if (slashp[1] == '\0') { + break; + } + + /* Temporarily NUL-terminate here */ + *slashp = '\0'; +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer); +#endif + mkdir_result = mkdir(buffer, aPermissions); + if (mkdir_result == -1) { + mkdir_errno = errno; + /* + * Always set |errno| to EEXIST if the dir already exists + * (we have to do this here since the errno value is not consistent + * in all cases - various reasons like different platform, + * automounter-controlled dir, etc. can affect it (see bug 125489 + * for details)). + */ + if (mkdir_errno != EEXIST && access(buffer, F_OK) == 0) { + mkdir_errno = EEXIST; + } +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: errno: %d\n", mkdir_errno); +#endif + } + + /* Put the / back */ + *slashp = '/'; + } + + /* + * We could get EEXIST for an existing file -- not directory -- + * but that's OK: we'll get ENOTDIR when we try to make the final + * component of the path back in Create and error out appropriately. + */ + if (mkdir_result == -1 && mkdir_errno != EEXIST) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aResult) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + *aResult = PR_Open(mPath.get(), aFlags, aMode); + if (!*aResult) { + return NS_ErrorAccordingToNSPR(); + } + + if (aFlags & DELETE_ON_CLOSE) { + PR_Delete(mPath.get()); + } + +#if defined(HAVE_POSIX_FADVISE) + if (aFlags & OS_READAHEAD) { + posix_fadvise(PR_FileDesc2NativeHandle(*aResult), 0, 0, + POSIX_FADV_SEQUENTIAL); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + *aResult = fopen(mPath.get(), aMode); + if (!*aResult) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static int do_create(const char* aPath, int aFlags, mode_t aMode, + PRFileDesc** aResult) { + *aResult = PR_Open(aPath, aFlags, aMode); + return *aResult ? 0 : -1; +} + +static int do_mkdir(const char* aPath, int aFlags, mode_t aMode, + PRFileDesc** aResult) { + *aResult = nullptr; + return mkdir(aPath, aMode); +} + +nsresult nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags, + uint32_t aPermissions, + bool aSkipAncestors, + PRFileDesc** aResult) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) { + return NS_ERROR_FILE_UNKNOWN_TYPE; + } + + int (*createFunc)(const char*, int, mode_t, PRFileDesc**) = + (aType == NORMAL_FILE_TYPE) ? do_create : do_mkdir; + + int result = createFunc(mPath.get(), aFlags, aPermissions, aResult); + if (result == -1 && errno == ENOENT && !aSkipAncestors) { + /* + * If we failed because of missing ancestor components, try to create + * them and then retry the original creation. + * + * Ancestor directories get the same permissions as the file we're + * creating, with the X bit set for each of (user,group,other) with + * an R bit in the original permissions. If you want to do anything + * fancy like setgid or sticky bits, do it by hand. + */ + int dirperm = aPermissions; + if (aPermissions & S_IRUSR) { + dirperm |= S_IXUSR; + } + if (aPermissions & S_IRGRP) { + dirperm |= S_IXGRP; + } + if (aPermissions & S_IROTH) { + dirperm |= S_IXOTH; + } + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions, + dirperm); +#endif + + if (NS_FAILED(CreateAllAncestors(dirperm))) { + return NS_ERROR_FAILURE; + } + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get()); +#endif + result = createFunc(mPath.get(), aFlags, aPermissions, aResult); + } + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t aType, uint32_t aPermissions, + bool aSkipAncestors) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + PRFileDesc* junk = nullptr; + nsresult rv = CreateAndKeepOpen( + aType, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | PR_EXCL, aPermissions, + aSkipAncestors, &junk); + if (junk) { + PR_Close(junk); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString& aFragment) { + if (aFragment.IsEmpty()) { + return NS_OK; + } + + // only one component of path can be appended and cannot append ".." + nsACString::const_iterator begin, end; + if (aFragment.EqualsASCII("..") || + FindCharInReadable('/', aFragment.BeginReading(begin), + aFragment.EndReading(end))) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + return AppendRelativeNativePath(aFragment); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment) { + if (aFragment.IsEmpty()) { + return NS_OK; + } + + // No leading '/' and cannot be ".." + if (aFragment.First() == '/' || aFragment.EqualsASCII("..")) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (aFragment.Contains('/')) { + // can't contain .. as a path component. Ensure that the valid components + // "foo..foo", "..foo", and "foo.." are not falsely detected, + // but the invalid paths "../", "foo/..", "foo/../foo", + // "../foo", etc are. + constexpr auto doubleDot = "/.."_ns; + nsACString::const_iterator start, end, offset; + aFragment.BeginReading(start); + aFragment.EndReading(end); + offset = end; + while (FindInReadable(doubleDot, start, offset)) { + if (offset == end || *offset == '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + start = offset; + offset = end; + } + + // catches the remaining cases of prefixes + if (StringBeginsWith(aFragment, "../"_ns)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + if (!mPath.EqualsLiteral("/")) { + mPath.Append('/'); + } + mPath.Append(aFragment); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Normalize() { + char resolved_path[PATH_MAX] = ""; + char* resolved_path_ptr = nullptr; + + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + resolved_path_ptr = realpath(mPath.get(), resolved_path); + + // if there is an error, the return is null. + if (!resolved_path_ptr) { + return NSRESULT_FOR_ERRNO(); + } + + mPath = resolved_path; + return NS_OK; +} + +void nsLocalFile::LocateNativeLeafName(nsACString::const_iterator& aBegin, + nsACString::const_iterator& aEnd) { + // XXX perhaps we should cache this?? + + mPath.BeginReading(aBegin); + mPath.EndReading(aEnd); + + nsACString::const_iterator it = aEnd; + nsACString::const_iterator stop = aBegin; + --stop; + while (--it != stop) { + if (*it == '/') { + aBegin = ++it; + return; + } + } + // else, the entire path is the leaf name (which means this + // isn't an absolute path... unexpected??) +} + +NS_IMETHODIMP +nsLocalFile::GetNativeLeafName(nsACString& aLeafName) { + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + aLeafName = Substring(begin, end); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) { + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDisplayName(nsAString& aLeafName) { + return GetLeafName(aLeafName); +} + +nsCString nsLocalFile::NativePath() { return mPath; } + +nsresult nsIFile::GetNativePath(nsACString& aResult) { + aResult = NativePath(); + return NS_OK; +} + +nsCString nsIFile::HumanReadablePath() { + nsCString path; + DebugOnly<nsresult> rv = GetNativePath(path); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return path; +} + +nsresult nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent, + const nsACString& aNewName, + nsACString& aResult) { + nsresult rv; + nsCOMPtr<nsIFile> oldParent; + + if (!aNewParent) { + if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) { + return rv; + } + aNewParent = oldParent.get(); + } else { + // check to see if our target directory exists + bool targetExists; + if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) { + return rv; + } + + if (!targetExists) { + // XXX create the new directory with some permissions + rv = aNewParent->Create(DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + return rv; + } + } else { + // make sure that the target is actually a directory + bool targetIsDirectory; + if (NS_FAILED(rv = aNewParent->IsDirectory(&targetIsDirectory))) { + return rv; + } + if (!targetIsDirectory) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + + nsACString::const_iterator nameBegin, nameEnd; + if (!aNewName.IsEmpty()) { + aNewName.BeginReading(nameBegin); + aNewName.EndReading(nameEnd); + } else { + LocateNativeLeafName(nameBegin, nameEnd); + } + + nsAutoCString dirName; + if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) { + return rv; + } + + aResult = dirName + "/"_ns + Substring(nameBegin, nameEnd); + return NS_OK; +} + +nsresult nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent) { + nsresult rv; + /* + * dirCheck is used for various boolean test results such as from Equals, + * Exists, isDir, etc. + */ + bool dirCheck, isSymlink; + uint32_t oldPerms; + + if (NS_FAILED(rv = IsDirectory(&dirCheck))) { + return rv; + } + if (!dirCheck) { + return CopyToNative(aNewParent, ""_ns); + } + + if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) { + return rv; + } + if (dirCheck) { + // can't copy dir to itself + return NS_ERROR_INVALID_ARG; + } + + if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) { + return rv; + } + // get the dirs old permissions + if (NS_FAILED(rv = GetPermissions(&oldPerms))) { + return rv; + } + if (!dirCheck) { + if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) { + return rv; + } + } else { // dir exists lets try to use leaf + nsAutoCString leafName; + if (NS_FAILED(rv = GetNativeLeafName(leafName))) { + return rv; + } + if (NS_FAILED(rv = aNewParent->AppendNative(leafName))) { + return rv; + } + if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) { + return rv; + } + if (dirCheck) { + return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists + } + if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) { + return rv; + } + } + + nsCOMPtr<nsIDirectoryEnumerator> dirIterator; + if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) { + return rv; + } + + nsCOMPtr<nsIFile> entry; + while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(entry))) && + entry) { + if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) { + return rv; + } + if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) { + return rv; + } + if (dirCheck && !isSymlink) { + nsCOMPtr<nsIFile> destClone; + rv = aNewParent->Clone(getter_AddRefs(destClone)); + if (NS_SUCCEEDED(rv)) { + if (NS_FAILED(rv = entry->CopyToNative(destClone, ""_ns))) { +#ifdef DEBUG + nsresult rv2; + nsAutoCString pathName; + if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) { + return rv2; + } + printf("Operation not supported: %s\n", pathName.get()); +#endif + if (rv == NS_ERROR_OUT_OF_MEMORY) { + return rv; + } + continue; + } + } + } else { + if (NS_FAILED(rv = entry->CopyToNative(aNewParent, ""_ns))) { +#ifdef DEBUG + nsresult rv2; + nsAutoCString pathName; + if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) { + return rv2; + } + printf("Operation not supported: %s\n", pathName.get()); +#endif + if (rv == NS_ERROR_OUT_OF_MEMORY) { + return rv; + } + continue; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) { + nsresult rv; + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // we copy the parent here so 'aNewParent' remains immutable + nsCOMPtr<nsIFile> workParent; + if (aNewParent) { + if (NS_FAILED(rv = aNewParent->Clone(getter_AddRefs(workParent)))) { + return rv; + } + } else { + if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) { + return rv; + } + } + + // check to see if we are a directory or if we are a file + bool isDirectory; + if (NS_FAILED(rv = IsDirectory(&isDirectory))) { + return rv; + } + + nsAutoCString newPathName; + if (isDirectory) { + if (!aNewName.IsEmpty()) { + if (NS_FAILED(rv = workParent->AppendNative(aNewName))) { + return rv; + } + } else { + if (NS_FAILED(rv = GetNativeLeafName(newPathName))) { + return rv; + } + if (NS_FAILED(rv = workParent->AppendNative(newPathName))) { + return rv; + } + } + if (NS_FAILED(rv = CopyDirectoryTo(workParent))) { + return rv; + } + } else { + rv = GetNativeTargetPathName(workParent, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + +#ifdef DEBUG_blizzard + printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get()); +#endif + + // actually create the file. + auto* newFile = new nsLocalFile(); + nsCOMPtr<nsIFile> fileRef(newFile); // release on exit + + rv = newFile->InitWithNativePath(newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + // get the old permissions + uint32_t myPerms = 0; + rv = GetPermissions(&myPerms); + if (NS_FAILED(rv)) { + return rv; + } + + // Create the new file with the old file's permissions, even if write + // permission is missing. We can't create with write permission and + // then change back to myPerm on all filesystems (FAT on Linux, e.g.). + // But we can write to a read-only file on all Unix filesystems if we + // open it successfully for writing. + + PRFileDesc* newFD; + rv = newFile->CreateAndKeepOpen( + NORMAL_FILE_TYPE, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, myPerms, + /* aSkipAncestors = */ false, &newFD); + if (NS_FAILED(rv)) { + return rv; + } + + // open the old file, too + bool specialFile; + if (NS_FAILED(rv = IsSpecial(&specialFile))) { + PR_Close(newFD); + return rv; + } + if (specialFile) { +#ifdef DEBUG + printf("Operation not supported: %s\n", mPath.get()); +#endif + // make sure to clean up properly + PR_Close(newFD); + return NS_OK; + } + +#if defined(XP_MACOSX) + bool quarantined = true; + (void)HasXAttr("com.apple.quarantine"_ns, &quarantined); +#endif + + PRFileDesc* oldFD; + rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD); + if (NS_FAILED(rv)) { + // make sure to clean up properly + PR_Close(newFD); + return rv; + } + +#ifdef DEBUG_blizzard + int32_t totalRead = 0; + int32_t totalWritten = 0; +#endif + char buf[BUFSIZ]; + int32_t bytesRead; + + // record PR_Write() error for better error message later. + nsresult saved_write_error = NS_OK; + nsresult saved_read_error = NS_OK; + nsresult saved_read_close_error = NS_OK; + nsresult saved_write_close_error = NS_OK; + + // DONE: Does PR_Read() return bytesRead < 0 for error? + // Yes., The errors from PR_Read are not so common and + // the value may not have correspondence in NS_ERROR_*, but + // we do catch it still, immediately after while() loop. + // We can differentiate errors pf PR_Read and PR_Write by + // looking at saved_write_error value. If PR_Write error occurs (and not + // PR_Read() error), save_write_error is not NS_OK. + + while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) { +#ifdef DEBUG_blizzard + totalRead += bytesRead; +#endif + + // PR_Write promises never to do a short write + int32_t bytesWritten = PR_Write(newFD, buf, bytesRead); + if (bytesWritten < 0) { + saved_write_error = NSRESULT_FOR_ERRNO(); + bytesRead = -1; + break; + } + NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?"); + +#ifdef DEBUG_blizzard + totalWritten += bytesWritten; +#endif + } + + // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR, + // we are better off to prepare for retrying. But we need confirmation if + // EINTR is returned. + + // Record error if PR_Read() failed. + // Must be done before any other I/O which may reset errno. + if (bytesRead < 0 && saved_write_error == NS_OK) { + saved_read_error = NSRESULT_FOR_ERRNO(); + } + +#ifdef DEBUG_blizzard + printf("read %d bytes, wrote %d bytes\n", totalRead, totalWritten); +#endif + + // DONE: Errors of close can occur. Read man page of + // close(2); + // This is likely to happen if the file system is remote file + // system (NFS, CIFS, etc.) and network outage occurs. + // At least, we should tell the user that filesystem/disk is + // hosed (possibly due to network error, hard disk failure, + // etc.) so that users can take remedial action. + + // close the files + if (PR_Close(newFD) < 0) { + saved_write_close_error = NSRESULT_FOR_ERRNO(); +#if DEBUG + // This error merits printing. + fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n", + errno); +#endif + } +#if defined(XP_MACOSX) + else if (!quarantined) { + // If the original file was not in quarantine, lift the quarantine that + // file creation added because of LSFileQuarantineEnabled. + (void)newFile->DelXAttr("com.apple.quarantine"_ns); + } +#endif // defined(XP_MACOSX) + + if (PR_Close(oldFD) < 0) { + saved_read_close_error = NSRESULT_FOR_ERRNO(); +#if DEBUG + fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n", + errno); +#endif + } + + // Let us report the failure to write and read. + // check for write/read error after cleaning up + if (bytesRead < 0) { + if (saved_write_error != NS_OK) { + return saved_write_error; + } + if (saved_read_error != NS_OK) { + return saved_read_error; + } +#if DEBUG + MOZ_ASSERT(0); +#endif + } + + if (saved_write_close_error != NS_OK) { + return saved_write_close_error; + } + if (saved_read_close_error != NS_OK) { + return saved_read_close_error; + } + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return CopyToNative(aNewParent, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName) { + nsresult rv; + + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // check to make sure that we have a new parent + nsAutoCString newPathName; + rv = GetNativeTargetPathName(aNewParent, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + if (!FilePreferences::IsAllowedPath(newPathName)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // try for atomic rename, falling back to copy/delete + if (rename(mPath.get(), newPathName.get()) < 0) { + if (errno == EXDEV) { + rv = CopyToNative(aNewParent, aNewName); + if (NS_SUCCEEDED(rv)) { + rv = Remove(true); + } + } else { + rv = NSRESULT_FOR_ERRNO(); + } + } + + if (NS_SUCCEEDED(rv)) { + // Adjust this + mPath = newPathName; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return MoveToNative(aNewParent, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool aRecursive, uint32_t* aRemoveCount) { + CHECK_mPath(); + ENSURE_STAT_CACHE(); + + bool isSymLink; + + nsresult rv = IsSymlink(&isSymLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) { + rv = NSRESULT_FOR_RETURN(unlink(mPath.get())); + if (NS_SUCCEEDED(rv) && aRemoveCount) { + *aRemoveCount += 1; + } + return rv; + } + + if (aRecursive) { + auto* dir = new nsDirEnumeratorUnix(); + + RefPtr<nsSimpleEnumerator> dirRef(dir); // release on exit + + rv = dir->Init(this, false); + if (NS_FAILED(rv)) { + return rv; + } + + bool more; + while (NS_SUCCEEDED(dir->HasMoreElements(&more)) && more) { + nsCOMPtr<nsISupports> item; + rv = dir->GetNext(getter_AddRefs(item)); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> file = do_QueryInterface(item, &rv); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + // XXX: We care the result of the removal here while + // nsLocalFileWin does not. We should align the behavior. (bug 1779696) + rv = file->Remove(aRecursive, aRemoveCount); + +#ifdef ANDROID + // See bug 580434 - Bionic gives us just deleted files + if (rv == NS_ERROR_FILE_NOT_FOUND) { + continue; + } +#endif + if (NS_FAILED(rv)) { + return rv; + } + } + } + + rv = NSRESULT_FOR_RETURN(rmdir(mPath.get())); + if (NS_SUCCEEDED(rv) && aRemoveCount) { + *aRemoveCount += 1; + } + return rv; +} + +nsresult nsLocalFile::GetTimeImpl(PRTime* aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + CHECK_mPath(); + if (NS_WARN_IF(!aTime)) { + return NS_ERROR_INVALID_ARG; + } + + using StatFn = int (*)(const char*, struct STAT*); + StatFn statFn = aFollowLinks ? &STAT : &LSTAT; + + struct STAT fileStats {}; + if (statFn(mPath.get(), &fileStats) < 0) { + return NSRESULT_FOR_ERRNO(); + } + + struct timespec* timespec; + switch (aTimeField) { + case TimeField::AccessedTime: +#if (defined(__APPLE__) && defined(__MACH__)) + timespec = &fileStats.st_atimespec; +#else + timespec = &fileStats.st_atim; +#endif + break; + + case TimeField::ModifiedTime: +#if (defined(__APPLE__) && defined(__MACH__)) + timespec = &fileStats.st_mtimespec; +#else + timespec = &fileStats.st_mtim; +#endif + break; + + default: + MOZ_CRASH("Unknown TimeField"); + } + + *aTime = TimespecToMillis(*timespec); + + return NS_OK; +} + +nsresult nsLocalFile::SetTimeImpl(PRTime aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + CHECK_mPath(); + + using UtimesFn = int (*)(const char*, const timeval*); + UtimesFn utimesFn = &utimes; + +#if HAVE_LUTIMES + if (!aFollowLinks) { + utimesFn = &lutimes; + } +#endif + + ENSURE_STAT_CACHE(); + + if (aTime == 0) { + aTime = PR_Now(); + } + + // We only want to write to a single field (accessed time or modified time), + // but utimes() doesn't let you omit one. If you do, it will set that field to + // the current time, which is not what we want. + // + // So what we do is write to both fields, but copy one of the fields from our + // cached stat structure. + // + // If we are writing to the accessed time field, then we want to copy the + // modified time and vice versa. + + timeval times[2]; + + const size_t writeIndex = aTimeField == TimeField::AccessedTime ? 0 : 1; + const size_t copyIndex = aTimeField == TimeField::AccessedTime ? 1 : 0; + +#if (defined(__APPLE__) && defined(__MACH__)) + auto* copyFrom = aTimeField == TimeField::AccessedTime + ? &mCachedStat.st_mtimespec + : &mCachedStat.st_atimespec; +#else + auto* copyFrom = aTimeField == TimeField::AccessedTime ? &mCachedStat.st_mtim + : &mCachedStat.st_atim; +#endif + + times[copyIndex].tv_sec = copyFrom->tv_sec; + times[copyIndex].tv_usec = copyFrom->tv_nsec / 1000; + + times[writeIndex].tv_sec = aTime / PR_MSEC_PER_SEC; + times[writeIndex].tv_usec = (aTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC; + + int result = utimesFn(mPath.get(), times); + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) { + return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) { + return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) { + return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) { + return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) { + return GetTimeImpl(aLastModTime, TimeField::ModifiedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) { + return SetTimeImpl(aLastModTime, TimeField::ModifiedTime, + /* follow links ? */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) { + return GetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime, + /* follow link? */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) { + return SetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTime(PRTime* aCreationTime) { + return GetCreationTimeImpl(aCreationTime, false); +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTimeOfLink) { + return GetCreationTimeImpl(aCreationTimeOfLink, /* aFollowLinks = */ true); +} + +nsresult nsLocalFile::GetCreationTimeImpl(PRTime* aCreationTime, + bool aFollowLinks) { + CHECK_mPath(); + if (NS_WARN_IF(!aCreationTime)) { + return NS_ERROR_INVALID_ARG; + } + +#if defined(_DARWIN_FEATURE_64_BIT_INODE) + using StatFn = int (*)(const char*, struct STAT*); + StatFn statFn = aFollowLinks ? &STAT : &LSTAT; + + struct STAT fileStats {}; + if (statFn(mPath.get(), &fileStats) < 0) { + return NSRESULT_FOR_ERRNO(); + } + + *aCreationTime = TimespecToMillis(fileStats.st_birthtimespec); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +/* + * Only send back permissions bits: maybe we want to send back the whole + * mode_t to permit checks against other file types? + */ + +#define NORMALIZE_PERMS(mode) ((mode) & (S_IRWXU | S_IRWXG | S_IRWXO)) + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t* aPermissions) { + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + ENSURE_STAT_CACHE(); + *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) { + CHECK_mPath(); + if (NS_WARN_IF(!aPermissionsOfLink)) { + return NS_ERROR_INVALID_ARG; + } + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) { + return NSRESULT_FOR_ERRNO(); + } + *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissions(uint32_t aPermissions) { + CHECK_mPath(); + + /* + * Race condition here: we should use fchmod instead, there's no way to + * guarantee the name still refers to the same file. + */ + if (chmod(mPath.get(), aPermissions) >= 0) { + return NS_OK; + } +#if defined(ANDROID) && defined(STATFS) + // For the time being, this is restricted for use by Android, but we + // will figure out what to do for all platforms in bug 638503 + struct STATFS sfs; + if (STATFS(mPath.get(), &sfs) < 0) { + return NSRESULT_FOR_ERRNO(); + } + + // if this is a FAT file system we can't set file permissions + if (sfs.f_type == MSDOS_SUPER_MAGIC) { + return NS_OK; + } +#endif + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) { + // There isn't a consistent mechanism for doing this on UNIX platforms. We + // might want to carefully implement this in the future though. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSize(int64_t* aFileSize) { + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + *aFileSize = 0; + ENSURE_STAT_CACHE(); + + if (!S_ISDIR(mCachedStat.st_mode)) { + *aFileSize = (int64_t)mCachedStat.st_size; + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileSize(int64_t aFileSize) { + CHECK_mPath(); + +#if defined(ANDROID) + /* no truncate on bionic */ + int fd = open(mPath.get(), O_WRONLY); + if (fd == -1) { + return NSRESULT_FOR_ERRNO(); + } + + int ret = ftruncate(fd, (off_t)aFileSize); + close(fd); + + if (ret == -1) { + return NSRESULT_FOR_ERRNO(); + } +#elif defined(HAVE_TRUNCATE64) + if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) { + return NSRESULT_FOR_ERRNO(); + } +#else + off_t size = (off_t)aFileSize; + if (truncate(mPath.get(), size) == -1) { + return NSRESULT_FOR_ERRNO(); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) { + CHECK_mPath(); + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + *aFileSize = (int64_t)sbuf.st_size; + return NS_OK; +} + +#if defined(USE_LINUX_QUOTACTL) +/* + * Searches /proc/self/mountinfo for given device (Major:Minor), + * returns exported name from /dev + * + * Fails when /proc/self/mountinfo or diven device don't exist. + */ +static bool GetDeviceName(unsigned int aDeviceMajor, unsigned int aDeviceMinor, + nsACString& aDeviceName) { + bool ret = false; + + const int kMountInfoLineLength = 200; + const int kMountInfoDevPosition = 6; + + char mountinfoLine[kMountInfoLineLength]; + char deviceNum[kMountInfoLineLength]; + + SprintfLiteral(deviceNum, "%u:%u", aDeviceMajor, aDeviceMinor); + + FILE* f = fopen("/proc/self/mountinfo", "rt"); + if (!f) { + return ret; + } + + // Expects /proc/self/mountinfo in format: + // 'ID ID major:minor root mountpoint flags - type devicename flags' + while (fgets(mountinfoLine, kMountInfoLineLength, f)) { + char* p_dev = strstr(mountinfoLine, deviceNum); + + for (int i = 0; i < kMountInfoDevPosition && p_dev; ++i) { + p_dev = strchr(p_dev, ' '); + if (p_dev) { + p_dev++; + } + } + + if (p_dev) { + char* p_dev_end = strchr(p_dev, ' '); + if (p_dev_end) { + *p_dev_end = '\0'; + aDeviceName.Assign(p_dev); + ret = true; + break; + } + } + } + + fclose(f); + return ret; +} +#endif + +#if defined(USE_LINUX_QUOTACTL) +template <typename StatInfoFunc, typename QuotaInfoFunc> +nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + QuotaInfoFunc&& aQuotaInfoFunc, + int64_t* aResult) +#else +template <typename StatInfoFunc> +nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + int64_t* aResult) +#endif +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + // These systems have the operations necessary to check disk space. + +#ifdef STATFS + + // check to make sure that mPath is properly initialized + CHECK_mPath(); + + struct STATFS fs_buf; + + /* + * Members of the STATFS struct that you should know about: + * F_BSIZE = block size on disk. + * f_bavail = number of free blocks available to a non-superuser. + * f_bfree = number of total free blocks in file system. + * f_blocks = number of total used or free blocks in file system. + */ + + if (STATFS(mPath.get(), &fs_buf) < 0) { + // The call to STATFS failed. +# ifdef DEBUG + printf("ERROR: GetDiskInfo: STATFS call FAILED. \n"); +# endif + return NS_ERROR_FAILURE; + } + + CheckedInt64 statfsResult = std::forward<StatInfoFunc>(aStatInfoFunc)(fs_buf); + if (!statfsResult.isValid()) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + // Assign statfsResult to *aResult in case one of the quota calls fails. + *aResult = statfsResult.value(); + +# if defined(USE_LINUX_QUOTACTL) + + if (!FillStatCache()) { + // Returns info from statfs + return NS_OK; + } + + nsAutoCString deviceName; + if (!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev), + deviceName)) { + // Returns info from statfs + return NS_OK; + } + + struct dqblk dq; + if (!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), getuid(), + (caddr_t)&dq) +# ifdef QIF_BLIMITS + && dq.dqb_valid & QIF_BLIMITS +# endif + && dq.dqb_bhardlimit) { + CheckedInt64 quotaResult = std::forward<QuotaInfoFunc>(aQuotaInfoFunc)(dq); + if (!quotaResult.isValid()) { + // Returns info from statfs + return NS_OK; + } + + if (quotaResult.value() < *aResult) { + *aResult = quotaResult.value(); + } + } +# endif // defined(USE_LINUX_QUOTACTL) + +# ifdef DEBUG_DISK_SPACE + printf("DiskInfo: %lu bytes\n", *aResult); +# endif + + return NS_OK; + +#else // STATFS + /* + * This platform doesn't have statfs or statvfs. I'm sure that there's + * a way to check for free disk space and disk capacity on platforms that + * don't have statfs (I'm SURE they have df, for example). + * + * Until we figure out how to do that, lets be honest and say that this + * command isn't implemented properly for these platforms yet. + */ +# ifdef DEBUG + printf("ERROR: GetDiskInfo: Not implemented for plaforms without statfs.\n"); +# endif + return NS_ERROR_NOT_IMPLEMENTED; + +#endif // STATFS +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) { + return GetDiskInfo( + [](const struct STATFS& aStatInfo) { + return aStatInfo.f_bavail * static_cast<uint64_t>(aStatInfo.F_BSIZE); + }, +#if defined(USE_LINUX_QUOTACTL) + [](const struct dqblk& aQuotaInfo) -> uint64_t { + // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes + const uint64_t hardlimit = aQuotaInfo.dqb_bhardlimit * BLOCK_SIZE; + if (hardlimit > aQuotaInfo.dqb_curspace) { + return hardlimit - aQuotaInfo.dqb_curspace; + } + return 0; + }, +#endif + aDiskSpaceAvailable); +} + +NS_IMETHODIMP +nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity) { + return GetDiskInfo( + [](const struct STATFS& aStatInfo) { + return aStatInfo.f_blocks * static_cast<uint64_t>(aStatInfo.F_BSIZE); + }, +#if defined(USE_LINUX_QUOTACTL) + [](const struct dqblk& aQuotaInfo) { + // dqb_bhardlimit is count of BLOCK_SIZE blocks + return aQuotaInfo.dqb_bhardlimit * BLOCK_SIZE; + }, +#endif + aDiskCapacity); +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile** aParent) { + CHECK_mPath(); + if (NS_WARN_IF(!aParent)) { + return NS_ERROR_INVALID_ARG; + } + *aParent = nullptr; + + // if '/' we are at the top of the volume, return null + if (mPath.EqualsLiteral("/")) { + return NS_OK; + } + + // <brendan, after jband> I promise to play nice + char* buffer = mPath.BeginWriting(); + // find the last significant slash in buffer + char* slashp = strrchr(buffer, '/'); + NS_ASSERTION(slashp, "non-canonical path?"); + if (!slashp) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // for the case where we are at '/' + if (slashp == buffer) { + slashp++; + } + + // temporarily terminate buffer at the last significant slash + char c = *slashp; + *slashp = '\0'; + + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true, + getter_AddRefs(localFile)); + + // make buffer whole again + *slashp = c; + + if (NS_FAILED(rv)) { + return rv; + } + + localFile.forget(aParent); + return NS_OK; +} + +/* + * The results of Exists, isWritable and isReadable are not cached. + */ + +NS_IMETHODIMP +nsLocalFile::Exists(bool* aResult) { + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), F_OK) == 0); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool* aResult) { + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), W_OK) == 0); + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool* aResult) { + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), R_OK) == 0); + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool* aResult) { + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + // Check extension (bug 663899). On certain platforms, the file + // extension may cause the OS to treat it as executable regardless of + // the execute bit, such as .jar on Mac OS X. We borrow the code from + // nsLocalFileWin, slightly modified. + + // Don't be fooled by symlinks. + bool symLink; + nsresult rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString path; + if (symLink) { + GetTarget(path); + } else { + GetPath(path); + } + + int32_t dotIdx = path.RFindChar(char16_t('.')); + if (dotIdx != kNotFound) { + // Convert extension to lower case. + char16_t* p = path.BeginWriting(); + for (p += dotIdx + 1; *p; ++p) { + *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; + } + + // Search for any of the set of executable extensions. + static const char* const executableExts[] = { +#ifdef MOZ_WIDGET_COCOA + "afploc", // Can point to other files. +#endif + "air", // Adobe AIR installer +#ifdef MOZ_WIDGET_COCOA + "atloc", // Can point to other files. + "fileloc", // File location files can be used to point to other + // files. + "ftploc", // Can point to other files. + "inetloc", // Shouldn't be able to do the same, but can, due to + // macOS vulnerabilities. +#endif + "jar" // java application bundle + }; + nsDependentSubstring ext = Substring(path, dotIdx + 1); + for (auto executableExt : executableExts) { + if (ext.EqualsASCII(executableExt)) { + // Found a match. Set result and quit. + *aResult = true; + return NS_OK; + } + } + } + + // On OS X, then query Launch Services. +#ifdef MOZ_WIDGET_COCOA + // Certain Mac applications, such as Classic applications, which + // run under Rosetta, might not have the +x mode bit but are still + // considered to be executable by Launch Services (bug 646748). + CFURLRef url; + if (NS_FAILED(GetCFURL(&url))) { + return NS_ERROR_FAILURE; + } + + LSRequestedInfo theInfoRequest = kLSRequestAllInfo; + LSItemInfoRecord theInfo; + OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo); + ::CFRelease(url); + if (result == noErr) { + if ((theInfo.flags & kLSItemInfoIsApplication) != 0) { + *aResult = true; + return NS_OK; + } + } +#endif + + // Then check the execute bit. + *aResult = (access(mPath.get(), X_OK) == 0); +#ifdef SOLARIS + // On Solaris, access will always return 0 for root user, however + // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set. + // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950 + if (*aResult) { + struct STAT buf; + + *aResult = (STAT(mPath.get(), &buf) == 0); + if (*aResult || errno == EACCES) { + *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)); + return NS_OK; + } + + return NSRESULT_FOR_ERRNO(); + } +#endif + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + ENSURE_STAT_CACHE(); + *aResult = S_ISDIR(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + ENSURE_STAT_CACHE(); + *aResult = S_ISREG(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + *aResult = (*begin == '.'); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + CHECK_mPath(); + + struct STAT symStat; + if (LSTAT(mPath.get(), &symStat) == -1) { + return NSRESULT_FOR_ERRNO(); + } + *aResult = S_ISLNK(symStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + ENSURE_STAT_CACHE(); + *aResult = S_ISCHR(mCachedStat.st_mode) || S_ISBLK(mCachedStat.st_mode) || +#ifdef S_ISSOCK + S_ISSOCK(mCachedStat.st_mode) || +#endif + S_ISFIFO(mCachedStat.st_mode); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) { + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsAutoCString inPath; + nsresult rv = aInFile->GetNativePath(inPath); + if (NS_FAILED(rv)) { + return rv; + } + + // We don't need to worry about "/foo/" vs. "/foo" here + // because trailing slashes are stripped on init. + *aResult = !strcmp(inPath.get(), mPath.get()); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) { + CHECK_mPath(); + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString inPath; + nsresult rv; + + if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) { + return rv; + } + + *aResult = false; + + ssize_t len = mPath.Length(); + if (strncmp(mPath.get(), inPath.get(), len) == 0) { + // Now make sure that the |aInFile|'s path has a separator at len, + // which implies that it has more components after len. + if (inPath[len] == '/') { + *aResult = true; + } + } + + return NS_OK; +} + +static nsresult ReadLinkSafe(const nsCString& aTarget, int32_t aExpectedSize, + nsACString& aOutBuffer) { + // If we call readlink with a buffer size S it returns S, then we cannot tell + // if the buffer was big enough to hold the entire path. We allocate an + // additional byte so we can check if the buffer was large enough. + const auto allocSize = CheckedInt<size_t>(aExpectedSize) + 1; + if (!allocSize.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + auto result = aOutBuffer.BulkWrite(allocSize.value(), 0, false); + if (result.isErr()) { + return result.unwrapErr(); + } + + auto handle = result.unwrap(); + + while (true) { + ssize_t bytesWritten = + readlink(aTarget.get(), handle.Elements(), handle.Length()); + if (bytesWritten < 0) { + return NSRESULT_FOR_ERRNO(); + } + + // written >= 0 so it is safe to cast to size_t. + if ((size_t)bytesWritten < handle.Length()) { + // Target might have changed since the lstat call, or lstat might lie, see + // bug 1791029. + handle.Finish(bytesWritten, false); + return NS_OK; + } + + // The buffer was not large enough, so double it and try again. + auto restartResult = handle.RestartBulkWrite(handle.Length() * 2, 0, false); + if (restartResult.isErr()) { + return restartResult.unwrapErr(); + } + } +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString& aResult) { + CHECK_mPath(); + aResult.Truncate(); + + struct STAT symStat; + if (LSTAT(mPath.get(), &symStat) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + if (!S_ISLNK(symStat.st_mode)) { + return NS_ERROR_FILE_INVALID_PATH; + } + + nsAutoCString target; + nsresult rv = ReadLinkSafe(mPath, symStat.st_size, target); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIFile> self(this); + int32_t maxLinks = 40; + while (true) { + if (maxLinks-- == 0) { + rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + break; + } + + if (target[0] != '/') { + nsCOMPtr<nsIFile> parent; + if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) { + break; + } + if (NS_FAILED(rv = parent->AppendRelativeNativePath(target))) { + break; + } + if (NS_FAILED(rv = parent->GetNativePath(aResult))) { + break; + } + self = parent; + } else { + aResult = target; + } + + const nsPromiseFlatCString& flatRetval = PromiseFlatCString(aResult); + + // Any failure in testing the current target we'll just interpret + // as having reached our destiny. + if (LSTAT(flatRetval.get(), &symStat) == -1) { + break; + } + + // And of course we're done if it isn't a symlink. + if (!S_ISLNK(symStat.st_mode)) { + break; + } + + nsAutoCString newTarget; + rv = ReadLinkSafe(flatRetval, symStat.st_size, newTarget); + if (NS_FAILED(rv)) { + break; + } + + target = newTarget; + } + + if (NS_FAILED(rv)) { + aResult.Truncate(); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { + RefPtr<nsDirEnumeratorUnix> dir = new nsDirEnumeratorUnix(); + + nsresult rv = dir->Init(this, false); + if (NS_FAILED(rv)) { + *aEntries = nullptr; + } else { + dir.forget(aEntries); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary** aResult) { + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + + *aResult = PR_LoadLibrary(mPath.get()); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(true); +#endif + + if (!*aResult) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) { + return GetNativePath(aPersistentDescriptor); +} + +NS_IMETHODIMP +nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) { +#ifdef MOZ_WIDGET_COCOA + if (aPersistentDescriptor.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + // Support pathnames as user-supplied descriptors if they begin with '/' + // or '~'. These characters do not collide with the base64 set used for + // encoding alias records. + char first = aPersistentDescriptor.First(); + if (first == '/' || first == '~') { + return InitWithNativePath(aPersistentDescriptor); + } + + uint32_t dataSize = aPersistentDescriptor.Length(); + char* decodedData = PL_Base64Decode( + PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr); + if (!decodedData) { + NS_ERROR("SetPersistentDescriptor was given bad data"); + return NS_ERROR_FAILURE; + } + + // Cast to an alias record and resolve. + AliasRecord aliasHeader = *(AliasPtr)decodedData; + int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader); + if (aliasSize > + ((int32_t)dataSize * 3) / 4) { // be paranoid about having too few data + PR_Free(decodedData); // PL_Base64Decode() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_OK; + + // Move the now-decoded data into the Handle. + // The size of the decoded data is 3/4 the size of the encoded data. See + // plbase64.h + Handle newHandle = nullptr; + if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + PR_Free(decodedData); // PL_Base64Decode() uses PR_Malloc(). + if (NS_FAILED(rv)) { + return rv; + } + + Boolean changed; + FSRef resolvedFSRef; + OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef, + &changed); + + rv = MacErrorMapper(err); + DisposeHandle(newHandle); + if (NS_FAILED(rv)) { + return rv; + } + + return InitWithFSRef(&resolvedFSRef); +#else + return InitWithNativePath(aPersistentDescriptor); +#endif +} + +NS_IMETHODIMP +nsLocalFile::Reveal() { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + +#ifdef MOZ_WIDGET_GTK + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + return giovfs->RevealFile(this); +#elif defined(MOZ_WIDGET_COCOA) + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::RevealFileInFinder(url); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +#else + return NS_ERROR_FAILURE; +#endif +} + +NS_IMETHODIMP +nsLocalFile::Launch() { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + +#ifdef MOZ_WIDGET_GTK + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + return giovfs->LaunchFile(mPath); +#elif defined(MOZ_WIDGET_ANDROID) + // Not supported on GeckoView + return NS_ERROR_NOT_IMPLEMENTED; +#elif defined(MOZ_WIDGET_COCOA) + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::OpenURL(url); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +#else + return NS_ERROR_FAILURE; +#endif +} + +nsresult NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowSymlinks, + nsIFile** aResult) { + RefPtr<nsLocalFile> file = new nsLocalFile(); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithNativePath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + file.forget(aResult); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// unicode support +//----------------------------------------------------------------------------- + +#define SET_UCS(func, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \ + if (NS_FAILED(rv)) return rv; \ + return (func)(buf); \ + } + +#define GET_UCS(func, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = (func)(buf); \ + if (NS_FAILED(rv)) return rv; \ + return NS_CopyNativeToUnicode(buf, ucsArg); \ + } + +#define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \ + if (NS_FAILED(rv)) return rv; \ + return (func)(opaqueArg, buf); \ + } + +// Unicode interface Wrapper +nsresult nsLocalFile::InitWithPath(const nsAString& aFilePath) { + SET_UCS(InitWithNativePath, aFilePath); +} +nsresult nsLocalFile::Append(const nsAString& aNode) { + SET_UCS(AppendNative, aNode); +} +nsresult nsLocalFile::AppendRelativePath(const nsAString& aNode) { + SET_UCS(AppendRelativeNativePath, aNode); +} +nsresult nsLocalFile::GetLeafName(nsAString& aLeafName) { + GET_UCS(GetNativeLeafName, aLeafName); +} +nsresult nsLocalFile::SetLeafName(const nsAString& aLeafName) { + SET_UCS(SetNativeLeafName, aLeafName); +} +nsresult nsLocalFile::GetPath(nsAString& aResult) { + return NS_CopyNativeToUnicode(mPath, aResult); +} +nsresult nsLocalFile::CopyTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + SET_UCS_2ARGS_2(CopyToNative, aNewParentDir, aNewName); +} +nsresult nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + SET_UCS_2ARGS_2(CopyToFollowingLinksNative, aNewParentDir, aNewName); +} +nsresult nsLocalFile::MoveTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + SET_UCS_2ARGS_2(MoveToNative, aNewParentDir, aNewName); +} +NS_IMETHODIMP +nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + SET_UCS_2ARGS_2(MoveToFollowingLinksNative, aNewParentDir, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) { + SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::RenameToNative(nsIFile* aNewParentDir, + const nsACString& aNewName) { + nsresult rv; + + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // check to make sure that we have a new parent + nsAutoCString newPathName; + rv = GetNativeTargetPathName(aNewParentDir, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + if (!FilePreferences::IsAllowedPath(newPathName)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // try for atomic rename + if (rename(mPath.get(), newPathName.get()) < 0) { + if (errno == EXDEV) { + rv = NS_ERROR_FILE_ACCESS_DENIED; + } else { + rv = NSRESULT_FOR_ERRNO(); + } + } + + return rv; +} + +nsresult nsLocalFile::GetTarget(nsAString& aResult) { + GET_UCS(GetNativeTarget, aResult); +} + +nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, + nsIFile** aResult) { + nsAutoCString buf; + nsresult rv = NS_CopyUnicodeToNative(aPath, buf); + if (NS_FAILED(rv)) { + return rv; + } + return NS_NewNativeLocalFile(buf, aFollowLinks, aResult); +} + +// nsILocalFileMac + +#ifdef MOZ_WIDGET_COCOA + +NS_IMETHODIMP +nsLocalFile::HasXAttr(const nsACString& aAttrName, bool* aHasAttr) { + NS_ENSURE_ARG_POINTER(aHasAttr); + + nsAutoCString attrName{aAttrName}; + + ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0); + if (size == -1) { + if (errno == ENOATTR) { + *aHasAttr = false; + } else { + return NSRESULT_FOR_ERRNO(); + } + } else { + *aHasAttr = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetXAttr(const nsACString& aAttrName, + nsTArray<uint8_t>& aAttrValue) { + aAttrValue.Clear(); + + nsAutoCString attrName{aAttrName}; + + ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0); + + if (size == -1) { + return NSRESULT_FOR_ERRNO(); + } + + for (;;) { + aAttrValue.SetCapacity(size); + + // The attribute can change between our first call and this call, so we need + // to re-check the size and possibly call with a larger buffer. + ssize_t newSize = getxattr(mPath.get(), attrName.get(), + aAttrValue.Elements(), size, 0, 0); + if (newSize == -1) { + return NSRESULT_FOR_ERRNO(); + } + + if (newSize <= size) { + aAttrValue.SetLength(newSize); + break; + } else { + size = newSize; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetXAttr(const nsACString& aAttrName, + const nsTArray<uint8_t>& aAttrValue) { + nsAutoCString attrName{aAttrName}; + + if (setxattr(mPath.get(), attrName.get(), aAttrValue.Elements(), + aAttrValue.Length(), 0, 0) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::DelXAttr(const nsACString& aAttrName) { + nsAutoCString attrName{aAttrName}; + + // Ignore removing an attribute that does not exist. + if (removexattr(mPath.get(), attrName.get(), 0) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + return NS_OK; +} + +static nsresult MacErrorMapper(OSErr inErr) { + nsresult outErr; + + switch (inErr) { + case noErr: + outErr = NS_OK; + break; + + case fnfErr: + case afpObjectNotFound: + case afpDirNotFound: + outErr = NS_ERROR_FILE_NOT_FOUND; + break; + + case dupFNErr: + case afpObjectExists: + outErr = NS_ERROR_FILE_ALREADY_EXISTS; + break; + + case dskFulErr: + case afpDiskFull: + outErr = NS_ERROR_FILE_NO_DEVICE_SPACE; + break; + + case fLckdErr: + case afpVolLocked: + outErr = NS_ERROR_FILE_IS_LOCKED; + break; + + case afpAccessDenied: + outErr = NS_ERROR_FILE_ACCESS_DENIED; + break; + + case afpDirNotEmpty: + outErr = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + + // Can't find good map for some + case bdNamErr: + outErr = NS_ERROR_FAILURE; + break; + + default: + outErr = NS_ERROR_FAILURE; + break; + } + + return outErr; +} + +static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) { + // first see if the conversion would succeed and find the length of the + // result + CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef); + CFIndex charsConverted = ::CFStringGetBytes( + aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false, + nullptr, 0, &usedBufLen); + if (charsConverted == inStrLen) { + // all characters converted, do the actual conversion + aOutStr.SetLength(usedBufLen); + if (aOutStr.Length() != (unsigned int)usedBufLen) { + return NS_ERROR_OUT_OF_MEMORY; + } + UInt8* buffer = (UInt8*)aOutStr.BeginWriting(); + ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), + kCFStringEncodingUTF8, 0, false, buffer, usedBufLen, + &usedBufLen); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::InitWithCFURL(CFURLRef aCFURL) { + UInt8 path[PATH_MAX]; + if (::CFURLGetFileSystemRepresentation(aCFURL, true, path, PATH_MAX)) { + nsDependentCString nativePath((char*)path); + return InitWithNativePath(nativePath); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFSRef(const FSRef* aFSRef) { + if (NS_WARN_IF(!aFSRef)) { + return NS_ERROR_INVALID_ARG; + } + + CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef); + if (newURLRef) { + nsresult rv = InitWithCFURL(newURLRef); + ::CFRelease(newURLRef); + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetCFURL(CFURLRef* aResult) { + CHECK_mPath(); + + bool isDir; + IsDirectory(&isDir); + *aResult = ::CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (UInt8*)mPath.get(), mPath.Length(), isDir); + + return (*aResult ? NS_OK : NS_ERROR_FAILURE); +} + +NS_IMETHODIMP +nsLocalFile::GetFSRef(FSRef* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef url = nullptr; + if (NS_SUCCEEDED(GetCFURL(&url))) { + if (::CFURLGetFSRef(url, aResult)) { + rv = NS_OK; + } + ::CFRelease(url); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFSSpec(FSSpec* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + FSRef fsRef; + nsresult rv = GetFSRef(&fsRef); + if (NS_SUCCEEDED(rv)) { + OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr, + aResult, nullptr); + return MacErrorMapper(err); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork) { + if (NS_WARN_IF(!aFileSizeWithResFork)) { + return NS_ERROR_INVALID_ARG; + } + + FSRef fsRef; + nsresult rv = GetFSRef(&fsRef); + if (NS_FAILED(rv)) { + return rv; + } + + FSCatalogInfo catalogInfo; + OSErr err = + ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes, + &catalogInfo, nullptr, nullptr, nullptr); + if (err != noErr) { + return MacErrorMapper(err); + } + + *aFileSizeWithResFork = + catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileType(OSType* aFileType) { + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::SetFileType(OSType aFileType) { + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetFileCreator(OSType* aFileCreator) { + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::SetFileCreator(OSType aFileCreator) { + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::LaunchWithDoc(nsIFile* aDocToLoad, bool aLaunchInBackground) { + bool isExecutable; + nsresult rv = IsExecutable(&isExecutable); + if (NS_FAILED(rv)) { + return rv; + } + if (!isExecutable) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + FSRef appFSRef, docFSRef; + rv = GetFSRef(&appFSRef); + if (NS_FAILED(rv)) { + return rv; + } + + if (aDocToLoad) { + nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad); + rv = macDoc->GetFSRef(&docFSRef); + if (NS_FAILED(rv)) { + return rv; + } + } + + LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; + LSLaunchFSRefSpec thelaunchSpec; + + if (aLaunchInBackground) { + theLaunchFlags |= kLSLaunchDontSwitch; + } + memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); + + thelaunchSpec.appRef = &appFSRef; + if (aDocToLoad) { + thelaunchSpec.numDocs = 1; + thelaunchSpec.itemRefs = &docFSRef; + } + thelaunchSpec.launchFlags = theLaunchFlags; + + OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr); + if (err != noErr) { + return MacErrorMapper(err); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenDocWithApp(nsIFile* aAppToOpenWith, bool aLaunchInBackground) { + FSRef docFSRef; + nsresult rv = GetFSRef(&docFSRef); + if (NS_FAILED(rv)) { + return rv; + } + + if (!aAppToOpenWith) { + OSErr err = ::LSOpenFSRef(&docFSRef, nullptr); + return MacErrorMapper(err); + } + + nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv); + if (!appFileMac) { + return rv; + } + + bool isExecutable; + rv = appFileMac->IsExecutable(&isExecutable); + if (NS_FAILED(rv)) { + return rv; + } + if (!isExecutable) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + FSRef appFSRef; + rv = appFileMac->GetFSRef(&appFSRef); + if (NS_FAILED(rv)) { + return rv; + } + + LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; + LSLaunchFSRefSpec thelaunchSpec; + + if (aLaunchInBackground) { + theLaunchFlags |= kLSLaunchDontSwitch; + } + memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); + + thelaunchSpec.appRef = &appFSRef; + thelaunchSpec.numDocs = 1; + thelaunchSpec.itemRefs = &docFSRef; + thelaunchSpec.launchFlags = theLaunchFlags; + + OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr); + if (err != noErr) { + return MacErrorMapper(err); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsPackage(bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + CFURLRef url; + nsresult rv = GetCFURL(&url); + if (NS_FAILED(rv)) { + return rv; + } + + LSItemInfoRecord info; + OSStatus status = + ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info); + + ::CFRelease(url); + + if (status != noErr) { + return NS_ERROR_FAILURE; + } + + *aResult = !!(info.flags & kLSItemInfoIsPackage); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleDisplayName(nsAString& aOutBundleName) { + bool isPackage = false; + nsresult rv = IsPackage(&isPackage); + if (NS_FAILED(rv) || !isPackage) { + return NS_ERROR_FAILURE; + } + + nsAutoString name; + rv = GetLeafName(name); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t length = name.Length(); + if (Substring(name, length - 4, length).EqualsLiteral(".app")) { + // 4 characters in ".app" + aOutBundleName = Substring(name, 0, length - 4); + } else { + aOutBundleName = name; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier) { + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef urlRef; + if (NS_SUCCEEDED(GetCFURL(&urlRef))) { + CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef); + if (bundle) { + CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle); + if (bundleIdentifier) { + rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier); + } + ::CFRelease(bundle); + } + ::CFRelease(urlRef); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime) { + CHECK_mPath(); + if (NS_WARN_IF(!aLastModTime)) { + return NS_ERROR_INVALID_ARG; + } + + bool isPackage = false; + nsresult rv = IsPackage(&isPackage); + if (NS_FAILED(rv) || !isPackage) { + return GetLastModifiedTime(aLastModTime); + } + + nsAutoCString infoPlistPath(mPath); + infoPlistPath.AppendLiteral("/Contents/Info.plist"); + PRFileInfo64 info; + if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) { + return GetLastModifiedTime(aLastModTime); + } + int64_t modTime = int64_t(info.modifyTime); + if (modTime == 0) { + *aLastModTime = 0; + } else { + *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC); + } + + return NS_OK; +} + +NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile* aFile) { + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString nativePath; + nsresult rv = aFile->GetNativePath(nativePath); + if (NS_FAILED(rv)) { + return rv; + } + + return InitWithNativePath(nativePath); +} + +nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks, + nsILocalFileMac** aResult) { + RefPtr<nsLocalFile> file = new nsLocalFile(); + + nsresult rv = file->InitWithFSRef(aFSRef); + if (NS_FAILED(rv)) { + return rv; + } + file.forget(aResult); + return NS_OK; +} + +nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks, + nsILocalFileMac** aResult) { + RefPtr<nsLocalFile> file = new nsLocalFile(); + + nsresult rv = file->InitWithCFURL(aURL); + if (NS_FAILED(rv)) { + return rv; + } + file.forget(aResult); + return NS_OK; +} + +#endif diff --git a/xpcom/io/nsLocalFileUnix.h b/xpcom/io/nsLocalFileUnix.h new file mode 100644 index 0000000000..eb37e3effd --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.h @@ -0,0 +1,104 @@ +/* -*- 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/. */ + +/* + * Implementation of nsIFile for ``Unixy'' systems. + */ + +#ifndef _nsLocalFileUNIX_H_ +#define _nsLocalFileUNIX_H_ + +#include <sys/stat.h> + +#include "nscore.h" +#include "nsString.h" +#ifdef MOZ_WIDGET_COCOA +# include "nsILocalFileMac.h" +#endif + +// stat64 and lstat64 are deprecated on OS X. Normal stat and lstat are +// 64-bit by default on OS X 10.6+. +#if defined(HAVE_STAT64) && defined(HAVE_LSTAT64) && !defined(XP_DARWIN) +# if defined(AIX) +# if defined STAT +# undef STAT +# endif +# endif +# define STAT stat64 +# define LSTAT lstat64 +# define HAVE_STATS64 1 +#else +# define STAT stat +# define LSTAT lstat +#endif + +#if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H) +# define USE_LINUX_QUOTACTL +#endif + +class nsLocalFile final +#ifdef MOZ_WIDGET_COCOA + : public nsILocalFileMac +#else + : public nsIFile +#endif +{ + public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + explicit nsLocalFile(const nsACString& aFilePath); + + static nsresult nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFILE +#ifdef MOZ_WIDGET_COCOA + NS_DECL_NSILOCALFILEMAC +#endif + + private: + nsLocalFile(const nsLocalFile& aOther); + ~nsLocalFile() = default; + + protected: + // This stat cache holds the *last stat* - it does not invalidate. + // Call "FillStatCache" whenever you want to stat our file. + struct STAT mCachedStat; + nsCString mPath; + + void LocateNativeLeafName(nsACString::const_iterator&, + nsACString::const_iterator&); + + nsresult CopyDirectoryTo(nsIFile* aNewParent); + nsresult CreateAllAncestors(uint32_t aPermissions); + nsresult GetNativeTargetPathName(nsIFile* aNewParent, + const nsACString& aNewName, + nsACString& aResult); + + bool FillStatCache(); + + nsresult CreateAndKeepOpen(uint32_t aType, int aFlags, uint32_t aPermissions, + bool aSkipAncestors, PRFileDesc** aResult); + + enum class TimeField { AccessedTime, ModifiedTime }; + nsresult SetTimeImpl(PRTime aTime, TimeField aTimeField, bool aFollowLinks); + nsresult GetTimeImpl(PRTime* aTime, TimeField aTimeField, bool aFollowLinks); + + nsresult GetCreationTimeImpl(PRTime* aCreationTime, bool aFollowLinks); + +#if defined(USE_LINUX_QUOTACTL) + template <typename StatInfoFunc, typename QuotaInfoFunc> + nsresult GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + QuotaInfoFunc&& aQuotaInfoFunc, int64_t* aResult); +#else + template <typename StatInfoFunc> + nsresult GetDiskInfo(StatInfoFunc&& aStatInfoFunc, int64_t* aResult); +#endif +}; + +#endif /* _nsLocalFileUNIX_H_ */ diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp new file mode 100644 index 0000000000..13fea1d2ca --- /dev/null +++ b/xpcom/io/nsLocalFileWin.cpp @@ -0,0 +1,3697 @@ +/* -*- 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/DebugOnly.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/TextUtils.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Utf8.h" +#include "mozilla/WinHeaderOnlyUtils.h" + +#include "nsCOMPtr.h" + +#include "nsLocalFile.h" +#include "nsLocalFileCommon.h" +#include "nsIDirectoryEnumerator.h" +#include "nsNativeCharsetUtils.h" + +#include "nsSimpleEnumerator.h" +#include "prio.h" +#include "private/pprio.h" // To get PR_ImportFile +#include "nsHashKeys.h" + +#include "nsString.h" +#include "nsReadableUtils.h" + +#include <direct.h> +#include <fileapi.h> +#include <windows.h> +#include <shlwapi.h> +#include <aclapi.h> + +#include "shellapi.h" +#include "shlguid.h" + +#include <io.h> +#include <stdio.h> +#include <stdlib.h> +#include <mbstring.h> + +#include "prproces.h" +#include "prlink.h" + +#include "mozilla/FilePreferences.h" +#include "mozilla/Mutex.h" +#include "SpecialSystemDirectory.h" + +#include "nsTraceRefcnt.h" +#include "nsXPCOMCIDInternal.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "nsIWindowMediator.h" + +#include "mozIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIWidget.h" +#include "mozilla/ShellHeaderOnlyUtils.h" +#include "mozilla/WidgetUtils.h" +#include "WinUtils.h" + +using namespace mozilla; +using mozilla::FilePreferences::kDevicePathSpecifier; +using mozilla::FilePreferences::kPathSeparator; + +#define CHECK_mWorkingPath() \ + do { \ + if (mWorkingPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \ + } while (0) + +#ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED +# define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +#endif + +#ifndef DRIVE_REMOTE +# define DRIVE_REMOTE 4 +#endif + +namespace { + +nsresult NewLocalFile(const nsAString& aPath, bool aUseDOSDevicePathSyntax, + nsIFile** aResult) { + RefPtr<nsLocalFile> file = new nsLocalFile(); + + file->SetUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithPath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + + file.forget(aResult); + return NS_OK; +} + +} // anonymous namespace + +static HWND GetMostRecentNavigatorHWND() { + nsresult rv; + nsCOMPtr<nsIWindowMediator> winMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + return nullptr; + } + + nsCOMPtr<mozIDOMWindowProxy> navWin; + rv = winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + if (NS_FAILED(rv) || !navWin) { + return nullptr; + } + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); + nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return nullptr; + } + + return reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); +} + +nsresult nsLocalFile::RevealFile(const nsString& aResolvedPath) { + MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread"); + + DWORD attributes = GetFileAttributesW(aResolvedPath.get()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + return NS_ERROR_FILE_INVALID_PATH; + } + + HRESULT hr; + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + // We have a directory so we should open the directory itself. + LPITEMIDLIST dir = ILCreateFromPathW(aResolvedPath.get()); + if (!dir) { + return NS_ERROR_FAILURE; + } + + LPCITEMIDLIST selection[] = {dir}; + UINT count = ArrayLength(selection); + + // Perform the open of the directory. + hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); + CoTaskMemFree(dir); + } else { + int32_t len = aResolvedPath.Length(); + // We don't currently handle UNC long paths of the form \\?\ anywhere so + // this should be fine. + if (len > MAX_PATH) { + return NS_ERROR_FILE_INVALID_PATH; + } + WCHAR parentDirectoryPath[MAX_PATH + 1] = {0}; + wcsncpy(parentDirectoryPath, aResolvedPath.get(), MAX_PATH); + PathRemoveFileSpecW(parentDirectoryPath); + + // We have a file so we should open the parent directory. + LPITEMIDLIST dir = ILCreateFromPathW(parentDirectoryPath); + if (!dir) { + return NS_ERROR_FAILURE; + } + + // Set the item in the directory to select to the file we want to reveal. + LPITEMIDLIST item = ILCreateFromPathW(aResolvedPath.get()); + if (!item) { + CoTaskMemFree(dir); + return NS_ERROR_FAILURE; + } + + LPCITEMIDLIST selection[] = {item}; + UINT count = ArrayLength(selection); + + // Perform the selection of the file. + hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); + + CoTaskMemFree(dir); + CoTaskMemFree(item); + } + + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; +} + +// static +bool nsLocalFile::CheckForReservedFileName(const nsString& aFileName) { + static const nsLiteralString forbiddenNames[] = { + u"COM1"_ns, u"COM2"_ns, u"COM3"_ns, u"COM4"_ns, u"COM5"_ns, u"COM6"_ns, + u"COM7"_ns, u"COM8"_ns, u"COM9"_ns, u"LPT1"_ns, u"LPT2"_ns, u"LPT3"_ns, + u"LPT4"_ns, u"LPT5"_ns, u"LPT6"_ns, u"LPT7"_ns, u"LPT8"_ns, u"LPT9"_ns, + u"CON"_ns, u"PRN"_ns, u"AUX"_ns, u"NUL"_ns, u"CLOCK$"_ns}; + + for (const nsLiteralString& forbiddenName : forbiddenNames) { + if (StringBeginsWith(aFileName, forbiddenName, + nsASCIICaseInsensitiveStringComparator)) { + // invalid name is either the entire string, or a prefix with a period + if (aFileName.Length() == forbiddenName.Length() || + aFileName.CharAt(forbiddenName.Length()) == char16_t('.')) { + return true; + } + } + } + + return false; +} + +class nsDriveEnumerator : public nsSimpleEnumerator, + public nsIDirectoryEnumerator { + public: + explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISIMPLEENUMERATOR + NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) + nsresult Init(); + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + NS_IMETHOD GetNextFile(nsIFile** aResult) override { + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) { + return rv; + } + nsCOMPtr<nsISupports> next; + rv = GetNext(getter_AddRefs(next)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> result = do_QueryInterface(next); + result.forget(aResult); + return NS_OK; + } + + NS_IMETHOD Close() override { return NS_OK; } + + private: + virtual ~nsDriveEnumerator(); + + /* mDrives stores the null-separated drive names. + * Init sets them. + * HasMoreElements checks mStartOfCurrentDrive. + * GetNext advances mStartOfCurrentDrive. + */ + nsString mDrives; + nsAString::const_iterator mStartOfCurrentDrive; + nsAString::const_iterator mEndOfDrivesString; + const bool mUseDOSDevicePathSyntax; +}; + +//----------------------------------------------------------------------------- +// static helper functions +//----------------------------------------------------------------------------- + +/** + * While not comprehensive, this will map many common Windows error codes to a + * corresponding nsresult. If an unmapped error is encountered, the hex error + * code will be logged to stderr. Error codes, names, and descriptions can be + * found at the following MSDN page: + * https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes + * + * \note When adding more mappings here, it must be checked if there's code that + * depends on the current generic NS_ERROR_MODULE_WIN32 mapping for such error + * codes. + */ +static nsresult ConvertWinError(DWORD aWinErr) { + nsresult rv; + + switch (aWinErr) { + case ERROR_FILE_NOT_FOUND: + [[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND + case ERROR_PATH_NOT_FOUND: + [[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND + case ERROR_INVALID_DRIVE: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case ERROR_ACCESS_DENIED: + [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED + case ERROR_NOT_SAME_DEVICE: + [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED + case ERROR_CANNOT_MAKE: + [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED + case ERROR_CONTENT_BLOCKED: + rv = NS_ERROR_FILE_ACCESS_DENIED; + break; + case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags + [[fallthrough]]; // to NS_ERROR_FILE_IS_LOCKED + case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx + rv = NS_ERROR_FILE_IS_LOCKED; + break; + case ERROR_NOT_ENOUGH_MEMORY: + [[fallthrough]]; // to NS_ERROR_OUT_OF_MEMORY + case ERROR_NO_SYSTEM_RESOURCES: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case ERROR_DIR_NOT_EMPTY: + [[fallthrough]]; // to NS_ERROR_FILE_DIR_NOT_EMPTY + case ERROR_CURRENT_DIRECTORY: + rv = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + case ERROR_WRITE_PROTECT: + rv = NS_ERROR_FILE_READ_ONLY; + break; + case ERROR_HANDLE_DISK_FULL: + [[fallthrough]]; // to NS_ERROR_FILE_NO_DEVICE_SPACE + case ERROR_DISK_FULL: + rv = NS_ERROR_FILE_NO_DEVICE_SPACE; + break; + case ERROR_FILE_EXISTS: + [[fallthrough]]; // to NS_ERROR_FILE_ALREADY_EXISTS + case ERROR_ALREADY_EXISTS: + rv = NS_ERROR_FILE_ALREADY_EXISTS; + break; + case ERROR_FILENAME_EXCED_RANGE: + rv = NS_ERROR_FILE_NAME_TOO_LONG; + break; + case ERROR_DIRECTORY: + rv = NS_ERROR_FILE_NOT_DIRECTORY; + break; + case ERROR_FILE_CORRUPT: + [[fallthrough]]; // to NS_ERROR_FILE_FS_CORRUPTED + case ERROR_DISK_CORRUPT: + rv = NS_ERROR_FILE_FS_CORRUPTED; + break; + case ERROR_DEVICE_HARDWARE_ERROR: + [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE + case ERROR_DEVICE_NOT_CONNECTED: + [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE + case ERROR_DEV_NOT_EXIST: + [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE + case ERROR_IO_DEVICE: + rv = NS_ERROR_FILE_DEVICE_FAILURE; + break; + case ERROR_NOT_READY: + rv = NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE; + break; + case ERROR_INVALID_NAME: + rv = NS_ERROR_FILE_INVALID_PATH; + break; + case ERROR_INVALID_BLOCK: + [[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE + case ERROR_INVALID_HANDLE: + [[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE + case ERROR_ARENA_TRASHED: + rv = NS_ERROR_FILE_INVALID_HANDLE; + break; + case 0: + rv = NS_OK; + break; + default: + printf_stderr( + "ConvertWinError received an unrecognized WinError: 0x%" PRIx32 "\n", + static_cast<uint32_t>(aWinErr)); + MOZ_ASSERT((aWinErr & 0xFFFF) == aWinErr); + rv = NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_WIN32, aWinErr & 0xFFFF); + break; + } + return rv; +} + +// Check whether a path is a volume root. Expects paths to be \-terminated. +static bool IsRootPath(const nsAString& aPath) { + // Easy cases first: + if (aPath.Last() != L'\\') { + return false; + } + if (StringEndsWith(aPath, u":\\"_ns)) { + return true; + } + + nsAString::const_iterator begin, end; + aPath.BeginReading(begin); + aPath.EndReading(end); + // We know we've got a trailing slash, skip that: + end--; + // Find the next last slash: + if (RFindInReadable(u"\\"_ns, begin, end)) { + // Reset iterator: + aPath.EndReading(end); + end--; + auto lastSegment = Substring(++begin, end); + if (lastSegment.IsEmpty()) { + return false; + } + + // Check if we end with e.g. "c$", a drive letter in UNC or network shares + if (lastSegment.Last() == L'$' && lastSegment.Length() == 2 && + IsAsciiAlpha(lastSegment.First())) { + return true; + } + // Volume GUID paths: + if (StringBeginsWith(lastSegment, u"Volume{"_ns) && + lastSegment.Last() == L'}') { + return true; + } + } + return false; +} + +static auto kSpecialNTFSFilesInRoot = { + u"$MFT"_ns, u"$MFTMirr"_ns, u"$LogFile"_ns, u"$Volume"_ns, + u"$AttrDef"_ns, u"$Bitmap"_ns, u"$Boot"_ns, u"$BadClus"_ns, + u"$Secure"_ns, u"$UpCase"_ns, u"$Extend"_ns}; +static bool IsSpecialNTFSPath(const nsAString& aFilePath) { + nsAString::const_iterator begin, end; + aFilePath.BeginReading(begin); + aFilePath.EndReading(end); + auto iter = begin; + // Early exit if there's no '$' (common case) + if (!FindCharInReadable(L'$', iter, end)) { + return false; + } + + iter = begin; + // Any use of ':$' is illegal in filenames anyway; while we support some + // ADS stuff (ie ":Zone.Identifier"), none of them use the ':$' syntax: + if (FindInReadable(u":$"_ns, iter, end)) { + return true; + } + + auto normalized = mozilla::MakeUniqueFallible<wchar_t[]>(MAX_PATH); + if (!normalized) { + return true; + } + auto flatPath = PromiseFlatString(aFilePath); + auto fullPathRV = + GetFullPathNameW(flatPath.get(), MAX_PATH - 1, normalized.get(), nullptr); + if (fullPathRV == 0 || fullPathRV > MAX_PATH - 1) { + return false; + } + + nsString normalizedPath(normalized.get()); + normalizedPath.BeginReading(begin); + normalizedPath.EndReading(end); + iter = begin; + auto kDelimiters = u"\\:"_ns; + while (iter != end && FindCharInReadable(L'$', iter, end)) { + for (auto str : kSpecialNTFSFilesInRoot) { + if (StringBeginsWith(Substring(iter, end), str, + nsCaseInsensitiveStringComparator)) { + // If we're enclosed by separators or the beginning/end of the string, + // this is one of the special files. Check if we're on a volume root. + auto iterCopy = iter; + iterCopy.advance(str.Length()); + // We check for both \ and : here because the filename could be + // followd by a colon and a stream name/type, which shouldn't affect + // our check: + if (iterCopy == end || kDelimiters.Contains(*iterCopy)) { + iterCopy = iter; + // At the start of this path component, we don't need to care about + // colons: we would have caught those in the check for `:$` above. + if (iterCopy == begin || *(--iterCopy) == L'\\') { + return IsRootPath(Substring(begin, iter)); + } + } + } + } + iter++; + } + + return false; +} + +//----------------------------------------------------------------------------- +// We need the following three definitions to make |OpenFile| convert a file +// handle to an NSPR file descriptor correctly when |O_APPEND| flag is +// specified. It is defined in a private header of NSPR (primpl.h) we can't +// include. As a temporary workaround until we decide how to extend +// |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER| +// and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion +// of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied. +// Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h. +// In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary +// workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc| +// need to be changed to match the definitions for WinNT. +//----------------------------------------------------------------------------- +typedef enum { + _PR_TRI_TRUE = 1, + _PR_TRI_FALSE = 0, + _PR_TRI_UNKNOWN = -1 +} _PRTriStateBool; + +struct _MDFileDesc { + PROsfd osfd; +}; + +struct PRFilePrivate { + int32_t state; + bool nonblocking; + _PRTriStateBool inheritable; + PRFileDesc* next; + int lockCount; /* 0: not locked + * -1: a native lockfile call is in progress + * > 0: # times the file is locked */ + bool appendMode; + _MDFileDesc md; +}; + +//----------------------------------------------------------------------------- +// Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo, +// OpenDir, CloseDir, ReadDir) should go away once the corresponding +// UTF-16 APIs are implemented on all the supported platforms (or at least +// Windows 9x/ME) in NSPR. Currently, they're only implemented on +// Windows NT4 or later. (bug 330665) +//----------------------------------------------------------------------------- + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : +// PR_Open and _PR_MD_OPEN +nsresult OpenFile(const nsString& aName, int aOsflags, int aMode, + bool aShareDelete, PRFileDesc** aFd) { + int32_t access = 0; + + int32_t shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + int32_t disposition = 0; + int32_t attributes = 0; + + if (aShareDelete) { + shareMode |= FILE_SHARE_DELETE; + } + + if (aOsflags & PR_SYNC) { + attributes = FILE_FLAG_WRITE_THROUGH; + } + if (aOsflags & PR_RDONLY || aOsflags & PR_RDWR) { + access |= GENERIC_READ; + } + if (aOsflags & PR_WRONLY || aOsflags & PR_RDWR) { + access |= GENERIC_WRITE; + } + + if (aOsflags & PR_CREATE_FILE && aOsflags & PR_EXCL) { + disposition = CREATE_NEW; + } else if (aOsflags & PR_CREATE_FILE) { + if (aOsflags & PR_TRUNCATE) { + disposition = CREATE_ALWAYS; + } else { + disposition = OPEN_ALWAYS; + } + } else { + if (aOsflags & PR_TRUNCATE) { + disposition = TRUNCATE_EXISTING; + } else { + disposition = OPEN_EXISTING; + } + } + + if (aOsflags & nsIFile::DELETE_ON_CLOSE) { + attributes |= FILE_FLAG_DELETE_ON_CLOSE; + } + + if (aOsflags & nsIFile::OS_READAHEAD) { + attributes |= FILE_FLAG_SEQUENTIAL_SCAN; + } + + // If no write permissions are requested, and if we are possibly creating + // the file, then set the new file as read only. + // The flag has no effect if we happen to open the file. + if (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) && + disposition != OPEN_EXISTING) { + attributes |= FILE_ATTRIBUTE_READONLY; + } + + HANDLE file = ::CreateFileW(aName.get(), access, shareMode, nullptr, + disposition, attributes, nullptr); + + if (file == INVALID_HANDLE_VALUE) { + *aFd = nullptr; + return ConvertWinError(GetLastError()); + } + + *aFd = PR_ImportFile((PROsfd)file); + if (*aFd) { + // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to + // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c) + (*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? true : false; + return NS_OK; + } + + nsresult rv = NS_ErrorAccordingToNSPR(); + + CloseHandle(file); + + return rv; +} + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : +// PR_FileTimeToPRTime and _PR_FileTimeToPRTime +static void FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm) { +#ifdef __GNUC__ + const PRTime _pr_filetime_offset = 116444736000000000LL; +#else + const PRTime _pr_filetime_offset = 116444736000000000i64; +#endif + + MOZ_ASSERT(sizeof(FILETIME) == sizeof(PRTime)); + ::CopyMemory(aPrtm, aFiletime, sizeof(PRTime)); +#ifdef __GNUC__ + *aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL; +#else + *aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64; +#endif +} + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some +// changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64 +static nsresult GetFileInfo(const nsString& aName, + nsLocalFile::FileInfo* aInfo) { + if (aName.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + // Checking u"?*" for the file path excluding the kDevicePathSpecifier. + // ToDo: Check if checking "?" for the file path is still needed. + const int32_t offset = StringBeginsWith(aName, kDevicePathSpecifier) + ? kDevicePathSpecifier.Length() + : 0; + + if (aName.FindCharInSet(u"?*", offset) != kNotFound) { + return NS_ERROR_INVALID_ARG; + } + + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) { + return ConvertWinError(GetLastError()); + } + + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + aInfo->type = PR_FILE_OTHER; + } else if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + aInfo->type = PR_FILE_DIRECTORY; + } else { + aInfo->type = PR_FILE_FILE; + } + + aInfo->size = fileData.nFileSizeHigh; + aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow; + + if (0 == fileData.ftCreationTime.dwLowDateTime && + 0 == fileData.ftCreationTime.dwHighDateTime) { + aInfo->creationTime = aInfo->modifyTime; + } else { + FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime); + } + + FileTimeToPRTime(&fileData.ftLastAccessTime, &aInfo->accessTime); + FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime); + + return NS_OK; +} + +struct nsDir { + HANDLE handle; + WIN32_FIND_DATAW data; + bool firstEntry; +}; + +static nsresult OpenDir(const nsString& aName, nsDir** aDir) { + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + *aDir = nullptr; + + nsDir* d = new nsDir(); + nsAutoString filename(aName); + + // If |aName| ends in a slash or backslash, do not append another backslash. + if (filename.Last() == L'/' || filename.Last() == L'\\') { + filename.Append('*'); + } else { + filename.AppendLiteral("\\*"); + } + + filename.ReplaceChar(L'/', L'\\'); + + // FindFirstFileW Will have a last error of ERROR_DIRECTORY if + // <file_path>\* is passed in. If <unknown_path>\* is passed in then + // ERROR_PATH_NOT_FOUND will be the last error. + d->handle = ::FindFirstFileW(filename.get(), &(d->data)); + + if (d->handle == INVALID_HANDLE_VALUE) { + delete d; + return ConvertWinError(GetLastError()); + } + d->firstEntry = true; + + *aDir = d; + return NS_OK; +} + +static nsresult ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName) { + aName.Truncate(); + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + while (1) { + BOOL rv; + if (aDir->firstEntry) { + aDir->firstEntry = false; + rv = 1; + } else { + rv = ::FindNextFileW(aDir->handle, &(aDir->data)); + } + + if (rv == 0) { + break; + } + + const wchar_t* fileName; + fileName = (aDir)->data.cFileName; + + if ((aFlags & PR_SKIP_DOT) && (fileName[0] == L'.') && + (fileName[1] == L'\0')) { + continue; + } + if ((aFlags & PR_SKIP_DOT_DOT) && (fileName[0] == L'.') && + (fileName[1] == L'.') && (fileName[2] == L'\0')) { + continue; + } + + DWORD attrib = aDir->data.dwFileAttributes; + if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) { + continue; + } + + aName = fileName; + return NS_OK; + } + + DWORD err = GetLastError(); + return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err); +} + +static nsresult CloseDir(nsDir*& aDir) { + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + BOOL isOk = FindClose(aDir->handle); + delete aDir; + aDir = nullptr; + return isOk ? NS_OK : ConvertWinError(GetLastError()); +} + +//----------------------------------------------------------------------------- +// nsDirEnumerator +//----------------------------------------------------------------------------- + +class nsDirEnumerator final : public nsSimpleEnumerator, + public nsIDirectoryEnumerator { + private: + ~nsDirEnumerator() { Close(); } + + public: + NS_DECL_ISUPPORTS_INHERITED + + NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) + + nsDirEnumerator() : mDir(nullptr) {} + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + nsresult Init(nsIFile* aParent) { + nsAutoString filepath; + aParent->GetTarget(filepath); + + if (filepath.IsEmpty()) { + aParent->GetPath(filepath); + } + + if (filepath.IsEmpty()) { + return NS_ERROR_UNEXPECTED; + } + + // IsDirectory is not needed here because OpenDir will return + // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file. + nsresult rv = OpenDir(filepath, &mDir); + if (NS_FAILED(rv)) { + return rv; + } + + mParent = aParent; + return NS_OK; + } + + NS_IMETHOD HasMoreElements(bool* aResult) override { + nsresult rv; + if (!mNext && mDir) { + nsString name; + rv = ReadDir(mDir, PR_SKIP_BOTH, name); + if (NS_FAILED(rv)) { + return rv; + } + if (name.IsEmpty()) { + // end of dir entries + rv = CloseDir(mDir); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = false; + return NS_OK; + } + + nsCOMPtr<nsIFile> file; + rv = mParent->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->Append(name); + if (NS_FAILED(rv)) { + return rv; + } + + mNext = file.forget(); + } + *aResult = mNext != nullptr; + if (!*aResult) { + Close(); + } + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports** aResult) override { + nsresult rv; + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) { + return rv; + } + if (!hasMore) { + return NS_ERROR_FAILURE; + } + + mNext.forget(aResult); + return NS_OK; + } + + NS_IMETHOD GetNextFile(nsIFile** aResult) override { + *aResult = nullptr; + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) { + return rv; + } + mNext.forget(aResult); + return NS_OK; + } + + NS_IMETHOD Close() override { + if (mDir) { + nsresult rv = CloseDir(mDir); + NS_ASSERTION(NS_SUCCEEDED(rv), "close failed"); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + return NS_OK; + } + + protected: + nsDir* mDir; + nsCOMPtr<nsIFile> mParent; + nsCOMPtr<nsIFile> mNext; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumerator, nsSimpleEnumerator, + nsIDirectoryEnumerator) + +//----------------------------------------------------------------------------- +// nsLocalFile <public> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile() + : mDirty(true), mResolveDirty(true), mUseDOSDevicePathSyntax(false) {} + +nsLocalFile::nsLocalFile(const nsAString& aFilePath) + : mUseDOSDevicePathSyntax(false) { + InitWithPath(aFilePath); +} + +nsresult nsLocalFile::nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr) { + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + + nsLocalFile* inst = new nsLocalFile(); + nsresult rv = inst->QueryInterface(aIID, aInstancePtr); + if (NS_FAILED(rv)) { + delete inst; + return rv; + } + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsLocalFile::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile, nsILocalFileWin) + +//----------------------------------------------------------------------------- +// nsLocalFile <private> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) + : mDirty(true), + mResolveDirty(true), + mUseDOSDevicePathSyntax(aOther.mUseDOSDevicePathSyntax), + mWorkingPath(aOther.mWorkingPath) {} + +nsresult nsLocalFile::ResolveSymlink() { + std::wstring workingPath(mWorkingPath.Data()); + if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(workingPath)) { + return NS_ERROR_FAILURE; + } + mResolvedPath.Assign(workingPath.c_str(), workingPath.length()); + return NS_OK; +} + +// Resolve any shortcuts and stat the resolved path. After a successful return +// the path is guaranteed valid and the members of mFileInfo can be used. +nsresult nsLocalFile::ResolveAndStat() { + // if we aren't dirty then we are already done + if (!mDirty) { + return NS_OK; + } + + AUTO_PROFILER_LABEL("nsLocalFile::ResolveAndStat", OTHER); + // we can't resolve/stat anything that isn't a valid NSPR addressable path + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // this is usually correct + mResolvedPath.Assign(mWorkingPath); + + // Make sure root paths have a trailing slash. + nsAutoString nsprPath(mWorkingPath); + if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == u':') { + nsprPath.Append('\\'); + } + + // first we will see if the working path exists. If it doesn't then + // there is nothing more that can be done + nsresult rv = GetFileInfo(nsprPath, &mFileInfo); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFileInfo.type != PR_FILE_OTHER) { + mResolveDirty = false; + mDirty = false; + return NS_OK; + } + + // OTHER from GetFileInfo currently means a symlink + rv = ResolveSymlink(); + // Even if it fails we need to have the resolved path equal to working path + // for those functions that always use the resolved path. + if (NS_FAILED(rv)) { + mResolvedPath.Assign(mWorkingPath); + return rv; + } + + mResolveDirty = false; + // get the details of the resolved path + rv = GetFileInfo(mResolvedPath, &mFileInfo); + if (NS_FAILED(rv)) { + return rv; + } + + mDirty = false; + return NS_OK; +} + +/** + * Fills the mResolvedPath member variable with the file or symlink target + * if follow symlinks is on. This is a copy of the Resolve parts from + * ResolveAndStat. ResolveAndStat is much slower though because of the stat. + * + * @return NS_OK on success. + */ +nsresult nsLocalFile::Resolve() { + // if we aren't dirty then we are already done + if (!mResolveDirty) { + return NS_OK; + } + + // we can't resolve/stat anything that isn't a valid NSPR addressable path + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // this is usually correct + mResolvedPath.Assign(mWorkingPath); + + // TODO: Implement symlink support + + mResolveDirty = false; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsLocalFile::nsIFile +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::Clone(nsIFile** aFile) { + // Just copy-construct ourselves + RefPtr<nsLocalFile> file = new nsLocalFile(*this); + file.forget(aFile); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile* aFile) { + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoString path; + aFile->GetPath(path); + if (path.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + return InitWithPath(path); +} + +NS_IMETHODIMP +nsLocalFile::InitWithPath(const nsAString& aFilePath) { + MakeDirty(); + + nsAString::const_iterator begin, end; + aFilePath.BeginReading(begin); + aFilePath.EndReading(end); + + // input string must not be empty + if (begin == end) { + return NS_ERROR_FAILURE; + } + + char16_t firstChar = *begin; + char16_t secondChar = *(++begin); + + // just do a sanity check. if it has any forward slashes, it is not a Native + // path on windows. Also, it must have a colon at after the first char. + if (FindCharInReadable(L'/', begin, end)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (FilePreferences::IsBlockedUNCPath(aFilePath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (secondChar == L':') { + // Make sure we have a valid drive, later code assumes the drive letter + // is a single char a-z or A-Z. + if (MozPathGetDriveNumber<wchar_t>(aFilePath.Data()) == -1) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + if (IsSpecialNTFSPath(aFilePath)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + mWorkingPath = aFilePath; + // kill any trailing '\' + if (mWorkingPath.Last() == L'\\') { + mWorkingPath.Truncate(mWorkingPath.Length() - 1); + } + + // Bug 1626514: make sure that we don't end up with multiple prefixes. + + // Prepend the "\\?\" prefix if the useDOSDevicePathSyntax is set and the path + // starts with a disk designator and backslash. + if (mUseDOSDevicePathSyntax && + FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) { + mWorkingPath = kDevicePathSpecifier + mWorkingPath; + } + + return NS_OK; +} + +// Strip a handler command string of its quotes and parameters. +static void CleanupHandlerPath(nsString& aPath) { + // Example command strings passed into this routine: + + // 1) C:\Program Files\Company\some.exe -foo -bar + // 2) C:\Program Files\Company\some.dll + // 3) C:\Windows\some.dll,-foo -bar + // 4) C:\Windows\some.cpl,-foo -bar + + int32_t lastCommaPos = aPath.RFindChar(','); + if (lastCommaPos != kNotFound) aPath.Truncate(lastCommaPos); + + aPath.Append(' '); + + // case insensitive + int32_t index = aPath.LowerCaseFindASCII(".exe "); + if (index == kNotFound) index = aPath.LowerCaseFindASCII(".dll "); + if (index == kNotFound) index = aPath.LowerCaseFindASCII(".cpl "); + + if (index != kNotFound) aPath.Truncate(index + 4); + aPath.Trim(" ", true, true); +} + +// Strip the windows host process bootstrap executable rundll32.exe +// from a handler's command string if it exists. +static void StripRundll32(nsString& aCommandString) { + // Example rundll formats: + // C:\Windows\System32\rundll32.exe "path to dll" + // rundll32.exe "path to dll" + // C:\Windows\System32\rundll32.exe "path to dll", var var + // rundll32.exe "path to dll", var var + + constexpr auto rundllSegment = "rundll32.exe "_ns; + constexpr auto rundllSegmentShort = "rundll32 "_ns; + + // case insensitive + int32_t strLen = rundllSegment.Length(); + int32_t index = aCommandString.LowerCaseFindASCII(rundllSegment); + if (index == kNotFound) { + strLen = rundllSegmentShort.Length(); + index = aCommandString.LowerCaseFindASCII(rundllSegmentShort); + } + + if (index != kNotFound) { + uint32_t rundllSegmentLength = index + strLen; + aCommandString.Cut(0, rundllSegmentLength); + } +} + +// Returns the fully qualified path to an application handler based on +// a parameterized command string. Note this routine should not be used +// to launch the associated application as it strips parameters and +// rundll.exe from the string. Designed for retrieving display information +// on a particular handler. +/* static */ +bool nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler) { + nsAutoString handlerCommand(aCommandHandler); + + // Straight command path: + // + // %SystemRoot%\system32\NOTEPAD.EXE var + // "C:\Program Files\iTunes\iTunes.exe" var var + // C:\Program Files\iTunes\iTunes.exe var var + // + // Example rundll handlers: + // + // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var + // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll" + // C:\Windows\System32\rundll32.exe "path to dll", var var + // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo + // Viewer.dll", var var + + // Expand environment variables so we have full path strings. + uint32_t bufLength = + ::ExpandEnvironmentStringsW(handlerCommand.get(), nullptr, 0); + if (bufLength == 0) // Error + return false; + + auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength); + if (!destination) return false; + if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(), + bufLength)) + return false; + + handlerCommand.Assign(destination.get()); + + // Remove quotes around paths + handlerCommand.StripChars(u"\""); + + // Strip windows host process bootstrap so we can get to the actual + // handler. + StripRundll32(handlerCommand); + + // Trim any command parameters so that we have a native path we can + // initialize a local file with. + CleanupHandlerPath(handlerCommand); + + aCommandHandler.Assign(handlerCommand); + return true; +} + +NS_IMETHODIMP +nsLocalFile::InitWithCommandLine(const nsAString& aCommandLine) { + nsAutoString commandLine(aCommandLine); + if (!CleanupCmdHandlerPath(commandLine)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + return InitWithPath(commandLine); +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aResult) { + nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) { + *aResult = _wfopen(mWorkingPath.get(), NS_ConvertASCIItoUTF16(aMode).get()); + if (*aResult) { + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +static nsresult do_create(nsIFile* aFile, const nsString& aPath, + uint32_t aAttributes) { + PRFileDesc* file; + nsresult rv = + OpenFile(aPath, PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, + aAttributes, false, &file); + if (file) { + PR_Close(file); + } + + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + // need to return already-exists for directories (bug 452217) + bool isdir; + if (NS_SUCCEEDED(aFile->IsDirectory(&isdir)) && isdir) { + rv = NS_ERROR_FILE_ALREADY_EXISTS; + } + } + return rv; +} + +static nsresult do_mkdir(nsIFile*, const nsString& aPath, uint32_t) { + if (!::CreateDirectoryW(aPath.get(), nullptr)) { + return ConvertWinError(GetLastError()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t aType, uint32_t aAttributes, bool aSkipAncestors) { + if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) { + return NS_ERROR_FILE_UNKNOWN_TYPE; + } + + auto* createFunc = (aType == NORMAL_FILE_TYPE ? do_create : do_mkdir); + + nsresult rv = createFunc(this, mWorkingPath, aAttributes); + + if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv || + aSkipAncestors) { + return rv; + } + + // create directories to target + // + // A given local file can be either one of these forms: + // + // - normal: X:\some\path\on\this\drive + // ^--- start here + // + // - UNC path: \\machine\volume\some\path\on\this\drive + // ^--- start here + // + // Skip the first 'X:\' for the first form, and skip the first full + // '\\machine\volume\' segment for the second form. + + wchar_t* path = char16ptr_t(mWorkingPath.BeginWriting()); + + if (path[0] == L'\\' && path[1] == L'\\') { + // dealing with a UNC path here; skip past '\\machine\' + path = wcschr(path + 2, L'\\'); + if (!path) { + return NS_ERROR_FILE_INVALID_PATH; + } + ++path; + } + + // search for first slash after the drive (or volume) name + wchar_t* slash = wcschr(path, L'\\'); + + nsresult directoryCreateError = NS_OK; + if (slash) { + // skip the first '\\' + ++slash; + slash = wcschr(slash, L'\\'); + + while (slash) { + *slash = L'\0'; + + if (!::CreateDirectoryW(mWorkingPath.get(), nullptr)) { + rv = ConvertWinError(GetLastError()); + if (NS_ERROR_FILE_NOT_FOUND == rv && + NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + // If a previous CreateDirectory failed due to access, return that. + return NS_ERROR_FILE_ACCESS_DENIED; + } + // perhaps the base path already exists, or perhaps we don't have + // permissions to create the directory. NOTE: access denied could + // occur on a parent directory even though it exists. + else if (rv != NS_ERROR_FILE_ALREADY_EXISTS && + rv != NS_ERROR_FILE_ACCESS_DENIED) { + return rv; + } + + directoryCreateError = rv; + } + *slash = L'\\'; + ++slash; + slash = wcschr(slash, L'\\'); + } + } + + // If our last CreateDirectory failed due to access, return that. + if (NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + return directoryCreateError; + } + + return createFunc(this, mWorkingPath, aAttributes); +} + +NS_IMETHODIMP +nsLocalFile::Append(const nsAString& aNode) { + // append this path, multiple components are not permitted + return AppendInternal(PromiseFlatString(aNode), false); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativePath(const nsAString& aNode) { + // append this path, multiple components are permitted + return AppendInternal(PromiseFlatString(aNode), true); +} + +nsresult nsLocalFile::AppendInternal(const nsString& aNode, + bool aMultipleComponents) { + if (aNode.IsEmpty()) { + return NS_OK; + } + + // check the relative path for validity + if (aNode.First() == L'\\' || // can't start with an '\' + aNode.Contains(L'/') || // can't contain / + aNode.EqualsASCII("..")) { // can't be .. + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (aMultipleComponents) { + // can't contain .. as a path component. Ensure that the valid components + // "foo..foo", "..foo", and "foo.." are not falsely detected, + // but the invalid paths "..\", "foo\..", "foo\..\foo", + // "..\foo", etc are. + constexpr auto doubleDot = u"\\.."_ns; + nsAString::const_iterator start, end, offset; + aNode.BeginReading(start); + aNode.EndReading(end); + offset = end; + while (FindInReadable(doubleDot, start, offset)) { + if (offset == end || *offset == L'\\') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + start = offset; + offset = end; + } + + // catches the remaining cases of prefixes + if (StringBeginsWith(aNode, u"..\\"_ns)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + // single components can't contain '\' + else if (aNode.Contains(L'\\')) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + MakeDirty(); + + mWorkingPath.Append('\\'); + mWorkingPath.Append(aNode); + + if (IsSpecialNTFSPath(mWorkingPath)) { + // Revert changes to mWorkingPath: + mWorkingPath.SetLength(mWorkingPath.Length() - aNode.Length() - 1); + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + return NS_OK; +} + +nsresult nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, + int32_t aMode, + bool aShareDelete, + PRFileDesc** aResult) { + return OpenFile(mWorkingPath, aFlags, aMode, aShareDelete, aResult); +} + +#define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? (u) - (L'a' - L'A') : (u)) + +NS_IMETHODIMP +nsLocalFile::Normalize() { + // XXX See bug 187957 comment 18 for possible problems with this + // implementation. + + if (mWorkingPath.IsEmpty()) { + return NS_OK; + } + + nsAutoString path(mWorkingPath); + + // find the index of the root backslash for the path. Everything before + // this is considered fully normalized and cannot be ascended beyond + // using ".." For a local drive this is the first slash (e.g. "c:\"). + // For a UNC path it is the slash following the share name + // (e.g. "\\server\share\"). + int32_t rootIdx = 2; // default to local drive + if (path.First() == L'\\') { // if a share then calculate the rootIdx + rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server + if (rootIdx == kNotFound) { + return NS_OK; // already normalized + } + rootIdx = path.FindChar(L'\\', rootIdx + 1); + if (rootIdx == kNotFound) { + return NS_OK; // already normalized + } + } else if (path.CharAt(rootIdx) != L'\\') { + // The path has been specified relative to the current working directory + // for that drive. To normalize it, the current working directory for + // that drive needs to be inserted before the supplied relative path + // which will provide an absolute path (and the rootIdx will still be 2). + WCHAR cwd[MAX_PATH]; + WCHAR* pcwd = cwd; + int drive = TOUPPER(path.First()) - 'A' + 1; + /* We need to worry about IPH, for details read bug 419326. + * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx + * uses a bitmask, bit 0 is 'a:' + * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx + * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx + * take an int, 1 is 'a:'. + * + * Because of this, we need to do some math. Subtract 1 to convert from + * _chdrive/_getdcwd format to _getdrives drive numbering. + * Shift left x bits to convert from integer indexing to bitfield indexing. + * And of course, we need to find out if the drive is in the bitmask. + * + * If we're really unlucky, we can still lose, but only if the user + * manages to eject the drive between our call to _getdrives() and + * our *calls* to _wgetdcwd. + */ + if (!((1 << (drive - 1)) & _getdrives())) { + return NS_ERROR_FILE_INVALID_PATH; + } + if (!_wgetdcwd(drive, pcwd, MAX_PATH)) { + pcwd = _wgetdcwd(drive, 0, 0); + } + if (!pcwd) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsAutoString currentDir(pcwd); + if (pcwd != cwd) { + free(pcwd); + } + + if (currentDir.Last() == '\\') { + path.Replace(0, 2, currentDir); + } else { + path.Replace(0, 2, currentDir + u"\\"_ns); + } + } + + MOZ_ASSERT(0 < rootIdx && rootIdx < (int32_t)path.Length(), + "rootIdx is invalid"); + MOZ_ASSERT(path.CharAt(rootIdx) == '\\', "rootIdx is invalid"); + + // if there is nothing following the root path then it is already normalized + if (rootIdx + 1 == (int32_t)path.Length()) { + return NS_OK; + } + + // assign the root + const char16_t* pathBuffer = path.get(); // simplify access to the buffer + mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer + mWorkingPath.Assign(pathBuffer, rootIdx); + + // Normalize the path components. The actions taken are: + // + // "\\" condense to single backslash + // "." remove from path + // ".." up a directory + // "..." remove from path (any number of dots > 2) + // + // The last form is something that Windows 95 and 98 supported and + // is a shortcut for changing up multiple directories. Windows XP + // and ilk ignore it in a path, as is done here. + int32_t len, begin, end = rootIdx; + while (end < (int32_t)path.Length()) { + // find the current segment (text between the backslashes) to + // be examined, this will set the following variables: + // begin == index of first char in segment + // end == index 1 char after last char in segment + // len == length of segment + begin = end + 1; + end = path.FindChar('\\', begin); + if (end == kNotFound) { + end = path.Length(); + } + len = end - begin; + + // ignore double backslashes + if (len == 0) { + continue; + } + + // len != 0, and interesting paths always begin with a dot + if (pathBuffer[begin] == '.') { + // ignore single dots + if (len == 1) { + continue; + } + + // handle multiple dots + if (len >= 2 && pathBuffer[begin + 1] == L'.') { + // back up a path component on double dot + if (len == 2) { + int32_t prev = mWorkingPath.RFindChar('\\'); + if (prev >= rootIdx) { + mWorkingPath.Truncate(prev); + } + continue; + } + + // length is > 2 and the first two characters are dots. + // if the rest of the string is dots, then ignore it. + int idx = len - 1; + for (; idx >= 2; --idx) { + if (pathBuffer[begin + idx] != L'.') { + break; + } + } + + // this is true if the loop above didn't break + // and all characters in this segment are dots. + if (idx < 2) { + continue; + } + } + } + + // add the current component to the path, including the preceding backslash + mWorkingPath.Append(pathBuffer + begin - 1, len + 1); + } + + // kill trailing dots and spaces. + int32_t filePathLen = mWorkingPath.Length() - 1; + while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' || + mWorkingPath[filePathLen] == L'.')) { + mWorkingPath.Truncate(filePathLen--); + } + + MakeDirty(); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetLeafName(nsAString& aLeafName) { + aLeafName.Truncate(); + + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + int32_t offset = mWorkingPath.RFindChar(L'\\'); + + // if the working path is just a node without any lashes. + if (offset == kNotFound) { + aLeafName = mWorkingPath; + } else { + aLeafName = Substring(mWorkingPath, offset + 1); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLeafName(const nsAString& aLeafName) { + MakeDirty(); + + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + // cannot use nsCString::RFindChar() due to 0x5c problem + int32_t offset = mWorkingPath.RFindChar(L'\\'); + nsString newDir; + if (offset) { + newDir = Substring(mWorkingPath, 0, offset + 1) + aLeafName; + } else { + newDir = mWorkingPath + aLeafName; + } + if (IsSpecialNTFSPath(newDir)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + mWorkingPath.Assign(newDir); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDisplayName(nsAString& aLeafName) { + aLeafName.Truncate(); + + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + SHFILEINFOW sfi = {}; + DWORD_PTR result = ::SHGetFileInfoW(mWorkingPath.get(), 0, &sfi, sizeof(sfi), + SHGFI_DISPLAYNAME); + // If we found a display name, return that: + if (result) { + aLeafName.Assign(sfi.szDisplayName); + return NS_OK; + } + // Nope - fall back to the regular leaf name. + return GetLeafName(aLeafName); +} + +NS_IMETHODIMP +nsLocalFile::GetPath(nsAString& aResult) { + MOZ_ASSERT_IF( + mUseDOSDevicePathSyntax, + !FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)); + aResult = mWorkingPath; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetCanonicalPath(nsAString& aResult) { + EnsureShortPath(); + aResult.Assign(mShortWorkingPath); + return NS_OK; +} + +typedef struct { + WORD wLanguage; + WORD wCodePage; +} LANGANDCODEPAGE; + +NS_IMETHODIMP +nsLocalFile::GetVersionInfoField(const char* aField, nsAString& aResult) { + nsresult rv = NS_ERROR_FAILURE; + + const WCHAR* path = mWorkingPath.get(); + + DWORD dummy; + DWORD size = ::GetFileVersionInfoSizeW(path, &dummy); + if (!size) { + return rv; + } + + void* ver = moz_xcalloc(size, 1); + if (::GetFileVersionInfoW(path, 0, size, ver)) { + LANGANDCODEPAGE* translate = nullptr; + UINT pageCount; + BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation", + (void**)&translate, &pageCount); + if (queryResult && translate) { + for (int32_t i = 0; i < 2; ++i) { + wchar_t subBlock[MAX_PATH]; + _snwprintf(subBlock, MAX_PATH, L"\\StringFileInfo\\%04x%04x\\%S", + (i == 0 ? translate[0].wLanguage : ::GetUserDefaultLangID()), + translate[0].wCodePage, aField); + subBlock[MAX_PATH - 1] = 0; + LPVOID value = nullptr; + UINT size; + queryResult = ::VerQueryValueW(ver, subBlock, &value, &size); + if (queryResult && value) { + aResult.Assign(static_cast<char16_t*>(value)); + if (!aResult.IsEmpty()) { + rv = NS_OK; + break; + } + } + } + } + } + free(ver); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags, int32_t aMode, + PRFileDesc** aResult) { + nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, true, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +/** + * Determines if the drive type for the specified file is rmeote or local. + * + * @param path The path of the file to check + * @param remote Out parameter, on function success holds true if the specified + * file path is remote, or false if the file path is local. + * @return true on success. The return value implies absolutely nothing about + * wether the file is local or remote. + */ +static bool IsRemoteFilePath(LPCWSTR aPath, bool& aRemote) { + // Obtain the parent directory path and make sure it ends with + // a trailing backslash. + WCHAR dirPath[MAX_PATH + 1] = {0}; + wcsncpy(dirPath, aPath, MAX_PATH); + if (!PathRemoveFileSpecW(dirPath)) { + return false; + } + size_t len = wcslen(dirPath); + // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we + // recheck the required length here since we need to terminate it with + // a backslash. + if (len >= MAX_PATH) { + return false; + } + + dirPath[len] = L'\\'; + dirPath[len + 1] = L'\0'; + UINT driveType = GetDriveTypeW(dirPath); + aRemote = driveType == DRIVE_REMOTE; + return true; +} + +nsresult nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent, + const nsAString& aNewName, + uint32_t aOptions) { + nsresult rv = NS_OK; + nsAutoString filePath; + + bool move = aOptions & (Move | Rename); + + // get the path that we are going to copy to. + // Since windows does not know how to auto + // resolve shortcuts, we must work with the + // target. + nsAutoString destPath; + rv = aDestParent->GetTarget(destPath); + if (NS_FAILED(rv)) { + return rv; + } + + destPath.Append('\\'); + + if (aNewName.IsEmpty()) { + nsAutoString aFileName; + aSourceFile->GetLeafName(aFileName); + destPath.Append(aFileName); + } else { + destPath.Append(aNewName); + } + + if (aOptions & FollowSymlinks) { + rv = aSourceFile->GetTarget(filePath); + if (filePath.IsEmpty()) { + rv = aSourceFile->GetPath(filePath); + } + } else { + rv = aSourceFile->GetPath(filePath); + } + + if (NS_FAILED(rv)) { + return rv; + } + +#ifdef DEBUG + nsCOMPtr<nsILocalFileWin> srcWinFile = do_QueryInterface(aSourceFile); + MOZ_ASSERT(srcWinFile); + + bool srcUseDOSDevicePathSyntax; + srcWinFile->GetUseDOSDevicePathSyntax(&srcUseDOSDevicePathSyntax); + + nsCOMPtr<nsILocalFileWin> destWinFile = do_QueryInterface(aDestParent); + MOZ_ASSERT(destWinFile); + + bool destUseDOSDevicePathSyntax; + destWinFile->GetUseDOSDevicePathSyntax(&destUseDOSDevicePathSyntax); + + MOZ_ASSERT(srcUseDOSDevicePathSyntax == destUseDOSDevicePathSyntax, + "Copy or Move files with different values for " + "useDOSDevicePathSyntax would fail"); +#endif + + if (FilePreferences::IsBlockedUNCPath(destPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + int copyOK = 0; + if (move) { + copyOK = ::MoveFileExW(filePath.get(), destPath.get(), + MOVEFILE_REPLACE_EXISTING); + } + + // If we either failed to move the file, or this is a copy, try copying: + if (!copyOK && (!move || GetLastError() == ERROR_NOT_SAME_DEVICE)) { + // Failed renames here should just return access denied. + if (move && (aOptions & Rename)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying + // to a SMBV2 remote drive. Without this parameter subsequent append mode + // file writes can cause the resultant file to become corrupt. We only need + // to do this if the major version of Windows is > 5(Only Windows Vista and + // above can support SMBV2). With a 7200RPM hard drive: Copying a 1KB file + // with COPY_FILE_NO_BUFFERING takes about 30-60ms. Copying a 1KB file + // without COPY_FILE_NO_BUFFERING takes < 1ms. So we only use + // COPY_FILE_NO_BUFFERING when we have a remote drive. + DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; + bool path1Remote, path2Remote; + if (!IsRemoteFilePath(filePath.get(), path1Remote) || + !IsRemoteFilePath(destPath.get(), path2Remote) || path1Remote || + path2Remote) { + dwCopyFlags |= COPY_FILE_NO_BUFFERING; + } + + copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr, + nullptr, dwCopyFlags); + // On Windows 10, copying without buffering has started failing, so try + // with buffering... + if (!copyOK && (dwCopyFlags & COPY_FILE_NO_BUFFERING) && + GetLastError() == ERROR_INVALID_PARAMETER) { + dwCopyFlags &= ~COPY_FILE_NO_BUFFERING; + copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr, + nullptr, dwCopyFlags); + } + + if (move && copyOK) { + DeleteFileW(filePath.get()); + } + } + + if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure. + rv = ConvertWinError(GetLastError()); + } else if (move && !(aOptions & SkipNtfsAclReset)) { + // Set security permissions to inherit from parent. + // Note: propagates to all children: slow for big file trees + PACL pOldDACL = nullptr; + PSECURITY_DESCRIPTOR pSD = nullptr; + ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, nullptr, nullptr, + &pOldDACL, nullptr, &pSD); + UniquePtr<VOID, LocalFreeDeleter> autoFreeSecDesc(pSD); + if (pOldDACL) { + // Test the current DACL, if we find one that is inherited then we can + // skip the reset. This avoids a request for SeTcbPrivilege, which can + // cause a lot of audit events if enabled (Bug 1816694). + bool inherited = false; + for (DWORD i = 0; i < pOldDACL->AceCount; ++i) { + VOID* pAce = nullptr; + if (::GetAce(pOldDACL, i, &pAce) && + static_cast<PACE_HEADER>(pAce)->AceFlags & INHERITED_ACE) { + inherited = true; + break; + } + } + + if (!inherited) { + ::SetNamedSecurityInfoW( + (LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, + nullptr, nullptr, pOldDACL, nullptr); + } + } + } + + return rv; +} + +nsresult nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName, + uint32_t aOptions) { + bool move = aOptions & (Move | Rename); + bool followSymlinks = aOptions & FollowSymlinks; + // If we're not provided with a new parent, we're copying or moving to + // another file in the same directory and can safely skip checking if the + // destination directory exists: + bool targetInSameDirectory = !aParentDir; + + nsCOMPtr<nsIFile> newParentDir = aParentDir; + // check to see if this exists, otherwise return an error. + // we will check this by resolving. If the user wants us + // to follow links, then we are talking about the target, + // hence we can use the |FollowSymlinks| option. + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (!newParentDir) { + // no parent was specified. We must rename. + if (aNewName.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + rv = GetParent(getter_AddRefs(newParentDir)); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (!newParentDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + + if (!targetInSameDirectory) { + // make sure it exists and is a directory. Create it if not there. + bool exists = false; + rv = newParentDir->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + if (!exists) { + rv = newParentDir->Create(DIRECTORY_TYPE, + 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) { + return rv; + } + } else { + bool isDir = false; + rv = newParentDir->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + if (!isDir) { + if (followSymlinks) { + bool isLink = false; + rv = newParentDir->IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isLink) { + nsAutoString target; + rv = newParentDir->GetTarget(target); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIFile> realDest = new nsLocalFile(); + rv = realDest->InitWithPath(target); + if (NS_FAILED(rv)) { + return rv; + } + + return CopyMove(realDest, aNewName, aOptions); + } + } else { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + } + + // Try different ways to move/copy files/directories + bool done = false; + + bool isDir = false; + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + bool isSymlink = false; + rv = IsSymlink(&isSymlink); + if (NS_FAILED(rv)) { + return rv; + } + + // Try to move the file or directory, or try to copy a single file (or + // non-followed symlink) + if (move || !isDir || (isSymlink && !followSymlinks)) { + // Copy/Move single file, or move a directory + if (!aParentDir) { + aOptions |= SkipNtfsAclReset; + } + rv = CopySingleFile(this, newParentDir, aNewName, aOptions); + done = NS_SUCCEEDED(rv); + // If we are moving a directory and that fails, fallback on directory + // enumeration. See bug 231300 for details. + if (!done && !(move && isDir)) { + return rv; + } + } + + // Not able to copy or move directly, so enumerate it + if (!done) { + // create a new target destination in the new parentDir; + nsCOMPtr<nsIFile> target; + rv = newParentDir->Clone(getter_AddRefs(target)); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString allocatedNewName; + if (aNewName.IsEmpty()) { + bool isLink = false; + rv = IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isLink) { + nsAutoString temp; + rv = GetTarget(temp); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t offset = temp.RFindChar(L'\\'); + if (offset == kNotFound) { + allocatedNewName = temp; + } else { + allocatedNewName = Substring(temp, offset + 1); + } + } else { + GetLeafName(allocatedNewName); // this should be the leaf name of the + } + } else { + allocatedNewName = aNewName; + } + + rv = target->Append(allocatedNewName); + if (NS_FAILED(rv)) { + return rv; + } + + allocatedNewName.Truncate(); + + bool exists = false; + // check if the destination directory already exists + rv = target->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + if (!exists) { + // if the destination directory cannot be created, return an error + rv = target->Create(DIRECTORY_TYPE, + 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) { + return rv; + } + } else { + // check if the destination directory is writable and empty + bool isWritable = false; + rv = target->IsWritable(&isWritable); + if (NS_FAILED(rv)) { + return rv; + } + + if (!isWritable) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + nsCOMPtr<nsIDirectoryEnumerator> targetIterator; + rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator)); + if (NS_FAILED(rv)) { + return rv; + } + + bool more; + targetIterator->HasMoreElements(&more); + // return error if target directory is not empty + if (more) { + return NS_ERROR_FILE_DIR_NOT_EMPTY; + } + } + + RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); + + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + NS_WARNING("dirEnum initialization failed"); + return rv; + } + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(file))) && file) { + bool isDir = false; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + bool isLink = false; + rv = file->IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (move) { + if (followSymlinks) { + return NS_ERROR_FAILURE; + } + + rv = file->MoveTo(target, u""_ns); + if (NS_FAILED(rv)) { + return rv; + } + } else { + if (followSymlinks) { + rv = file->CopyToFollowingLinks(target, u""_ns); + } else { + rv = file->CopyTo(target, u""_ns); + } + if (NS_FAILED(rv)) { + return rv; + } + } + } + // we've finished moving all the children of this directory + // in the new directory. so now delete the directory + // note, we don't need to do a recursive delete. + // MoveTo() is recursive. At this point, + // we've already moved the children of the current folder + // to the new location. nothing should be left in the folder. + if (move) { + rv = Remove(false /* recursive */); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + // If we moved, we want to adjust this. + if (move) { + MakeDirty(); + + nsAutoString newParentPath; + newParentDir->GetPath(newParentPath); + + if (newParentPath.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + if (aNewName.IsEmpty()) { + nsAutoString aFileName; + GetLeafName(aFileName); + + InitWithPath(newParentPath); + Append(aFileName); + } else { + InitWithPath(newParentPath); + Append(aNewName); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) { + return CopyMove(aNewParentDir, aNewName, 0); +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return CopyMove(aNewParentDir, aNewName, FollowSymlinks); +} + +NS_IMETHODIMP +nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) { + return CopyMove(aNewParentDir, aNewName, Move); +} + +NS_IMETHODIMP +nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return CopyMove(aNewParentDir, aNewName, Move | FollowSymlinks); +} + +NS_IMETHODIMP +nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) { + // If we're not provided with a new parent, we're renaming inside one and + // the same directory and can safely skip checking if the destination + // directory exists: + bool targetInSameDirectory = !aNewParentDir; + + nsCOMPtr<nsIFile> targetParentDir = aNewParentDir; + // check to see if this exists, otherwise return an error. + // we will check this by resolving. If the user wants us + // to follow links, then we are talking about the target, + // hence we can use the |followSymlinks| parameter. + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (!targetParentDir) { + // no parent was specified. We must rename. + if (aNewName.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + rv = GetParent(getter_AddRefs(targetParentDir)); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (!targetParentDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + + if (!targetInSameDirectory) { + // make sure it exists and is a directory. Create it if not there. + bool exists = false; + rv = targetParentDir->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + if (!exists) { + rv = targetParentDir->Create(DIRECTORY_TYPE, 0644); + if (NS_FAILED(rv)) { + return rv; + } + } else { + bool isDir = false; + rv = targetParentDir->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + if (!isDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + + uint32_t options = Rename; + if (!aNewParentDir) { + options |= SkipNtfsAclReset; + } + // Move single file, or move a directory + return CopySingleFile(this, targetParentDir, aNewName, options); +} + +NS_IMETHODIMP +nsLocalFile::RenameToNative(nsIFile* aNewParentDir, + const nsACString& aNewName) { + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return RenameTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary** aResult) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + + PRLibSpec libSpec; + libSpec.value.pathname_u = mWorkingPath.get(); + libSpec.type = PR_LibSpec_PathnameU; + *aResult = PR_LoadLibraryWithFlags(libSpec, 0); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(true); +#endif + + if (*aResult) { + return NS_OK; + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool aRecursive, uint32_t* aRemoveCount) { + // NOTE: + // + // if the working path points to a shortcut, then we will only + // delete the shortcut itself. even if the shortcut points to + // a directory, we will not recurse into that directory or + // delete that directory itself. likewise, if the shortcut + // points to a normal file, we will not delete the real file. + // this is done to be consistent with the other platforms that + // behave this way. we do this even if the followLinks attribute + // is set to true. this helps protect against misuse that could + // lead to security bugs (e.g., bug 210588). + // + // Since shortcut files are no longer permitted to be used as unix-like + // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt") + // this processing is a lot simpler. Even if the shortcut file is + // pointing to a directory, only the mWorkingPath value is used and so + // only the shortcut file will be deleted. + + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + nsresult rv = NS_OK; + + bool isLink = false; + rv = IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + // only check to see if we have a directory if it isn't a link + bool isDir = false; + if (!isLink) { + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (isDir) { + if (aRecursive) { + // WARNING: neither the `SHFileOperation` nor `IFileOperation` APIs are + // appropriate here as neither handle long path names, i.e. paths prefixed + // with `\\?\` or longer than 260 characters on Windows 10+ system with + // long paths enabled. + + RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); + + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + return rv; + } + + // XXX: We are ignoring the result of the removal here while + // nsLocalFileUnix does not. We should align the behavior. (bug 1779696) + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(file))) && file) { + file->Remove(aRecursive, aRemoveCount); + } + } + if (RemoveDirectoryW(mWorkingPath.get()) == 0) { + return ConvertWinError(GetLastError()); + } + } else { + if (DeleteFileW(mWorkingPath.get()) == 0) { + return ConvertWinError(GetLastError()); + } + } + + if (aRemoveCount) { + *aRemoveCount += 1; + } + + MakeDirty(); + return rv; +} + +nsresult nsLocalFile::GetDateImpl(PRTime* aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aTime)) { + return NS_ERROR_INVALID_ARG; + } + + FileInfo symlinkInfo{}; + FileInfo* pInfo; + + if (aFollowLinks) { + if (nsresult rv = GetFileInfo(mWorkingPath, &symlinkInfo); NS_FAILED(rv)) { + return rv; + } + + pInfo = &symlinkInfo; + } else { + if (nsresult rv = ResolveAndStat(); NS_FAILED(rv)) { + return rv; + } + + pInfo = &mFileInfo; + } + + switch (aTimeField) { + case TimeField::AccessedTime: + *aTime = pInfo->accessTime / PR_USEC_PER_MSEC; + break; + + case TimeField::ModifiedTime: + *aTime = pInfo->modifyTime / PR_USEC_PER_MSEC; + break; + + default: + MOZ_CRASH("Unknown time field"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) { + return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime, + /* aFollowSymlinks = */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) { + return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime, + /* aFollowSymlinks = */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) { + return SetDateImpl(aLastAccessedTime, TimeField::AccessedTime); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) { + return SetLastAccessedTime(aLastAccessedTime); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) { + return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime, + /* aFollowSymlinks = */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime) { + return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime, + /* aFollowSymlinks = */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) { + return SetDateImpl(aLastModifiedTime, TimeField::ModifiedTime); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) { + return SetLastModifiedTime(aLastModifiedTime); +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTime(PRTime* aCreationTime) { + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aCreationTime)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = ResolveAndStat(); + NS_ENSURE_SUCCESS(rv, rv); + + *aCreationTime = mFileInfo.creationTime / PR_USEC_PER_MSEC; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTime) { + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aCreationTime)) { + return NS_ERROR_INVALID_ARG; + } + + FileInfo info; + nsresult rv = GetFileInfo(mWorkingPath, &info); + NS_ENSURE_SUCCESS(rv, rv); + + *aCreationTime = info.creationTime / PR_USEC_PER_MSEC; + + return NS_OK; +} + +nsresult nsLocalFile::SetDateImpl(PRTime aTime, + nsLocalFile::TimeField aTimeField) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the + // modification time for directories. + HANDLE file = + ::CreateFileW(mWorkingPath.get(), // pointer to name of the file + GENERIC_WRITE, // access (write) mode + 0, // share mode + nullptr, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS, // file attributes + nullptr); + + if (file == INVALID_HANDLE_VALUE) { + return ConvertWinError(GetLastError()); + } + + FILETIME ft; + SYSTEMTIME st; + PRExplodedTime pret; + + if (aTime == 0) { + aTime = PR_Now() / PR_USEC_PER_MSEC; + } + + // PR_ExplodeTime expects usecs... + PR_ExplodeTime(aTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret); + st.wYear = pret.tm_year; + st.wMonth = + pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0 + st.wDayOfWeek = pret.tm_wday; + st.wDay = pret.tm_mday; + st.wHour = pret.tm_hour; + st.wMinute = pret.tm_min; + st.wSecond = pret.tm_sec; + st.wMilliseconds = pret.tm_usec / 1000; + + const FILETIME* accessTime = nullptr; + const FILETIME* modifiedTime = nullptr; + + if (aTimeField == TimeField::AccessedTime) { + accessTime = &ft; + } else { + modifiedTime = &ft; + } + + nsresult rv = NS_OK; + + // if at least one of these fails... + if (!(SystemTimeToFileTime(&st, &ft) != 0 && + SetFileTime(file, nullptr, accessTime, modifiedTime) != 0)) { + rv = ConvertWinError(GetLastError()); + } + + CloseHandle(file); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t* aPermissions) { + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + + // get the permissions of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as GetPermissionsOfLink) + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + bool isWritable = false; + rv = IsWritable(&isWritable); + if (NS_FAILED(rv)) { + return rv; + } + + bool isExecutable = false; + rv = IsExecutable(&isExecutable); + if (NS_FAILED(rv)) { + return rv; + } + + *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read + if (isWritable) { + *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write + } + if (isExecutable) { + *aPermissions |= PR_IXUSR | PR_IXGRP | PR_IXOTH; // all execute + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissions) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. It is not + // possible for a link file to be executable. + + DWORD word = ::GetFileAttributesW(mWorkingPath.get()); + if (word == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + bool isWritable = !(word & FILE_ATTRIBUTE_READONLY); + *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read + if (isWritable) { + *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissions(uint32_t aPermissions) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // set the permissions of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as SetPermissionsOfLink) + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + // windows only knows about the following permissions + int mode = 0; + if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read + mode |= _S_IREAD; + } + if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write + mode |= _S_IWRITE; + } + + if (_wchmod(mResolvedPath.get(), mode) == -1) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) { + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + // windows only knows about the following permissions + int mode = 0; + if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read + mode |= _S_IREAD; + } + if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write + mode |= _S_IWRITE; + } + + if (_wchmod(mWorkingPath.get(), mode) == -1) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSize(int64_t* aFileSize) { + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + *aFileSize = mFileInfo.size; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + FileInfo info{}; + if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) { + return NS_ERROR_FILE_INVALID_PATH; + } + + *aFileSize = info.size; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileSize(int64_t aFileSize) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + HANDLE hFile = + ::CreateFileW(mWorkingPath.get(), // pointer to name of the file + GENERIC_WRITE, // access (write) mode + FILE_SHARE_READ, // share mode + nullptr, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_ATTRIBUTE_NORMAL, // file attributes + nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + return ConvertWinError(GetLastError()); + } + + // seek the file pointer to the new, desired end of file + // and then truncate the file at that position + nsresult rv = NS_ERROR_FAILURE; + LARGE_INTEGER distance; + distance.QuadPart = aFileSize; + if (SetFilePointerEx(hFile, distance, nullptr, FILE_BEGIN) && + SetEndOfFile(hFile)) { + MakeDirty(); + rv = NS_OK; + } + + CloseHandle(hFile); + return rv; +} + +static nsresult GetDiskSpaceAttributes(const nsString& aResolvedPath, + int64_t* aFreeBytesAvailable, + int64_t* aTotalBytes) { + ULARGE_INTEGER liFreeBytesAvailableToCaller; + ULARGE_INTEGER liTotalNumberOfBytes; + if (::GetDiskFreeSpaceExW(aResolvedPath.get(), &liFreeBytesAvailableToCaller, + &liTotalNumberOfBytes, nullptr)) { + *aFreeBytesAvailable = liFreeBytesAvailableToCaller.QuadPart; + *aTotalBytes = liTotalNumberOfBytes.QuadPart; + + return NS_OK; + } + + return ConvertWinError(::GetLastError()); +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aDiskSpaceAvailable)) { + return NS_ERROR_INVALID_ARG; + } + + *aDiskSpaceAvailable = 0; + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFileInfo.type == PR_FILE_FILE) { + // Since GetDiskFreeSpaceExW works only on directories, use the parent. + nsCOMPtr<nsIFile> parent; + if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) { + return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable); + } + } + + int64_t dummy = 0; + return GetDiskSpaceAttributes(mResolvedPath, aDiskSpaceAvailable, &dummy); +} + +NS_IMETHODIMP +nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aDiskCapacity)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFileInfo.type == PR_FILE_FILE) { + // Since GetDiskFreeSpaceExW works only on directories, use the parent. + nsCOMPtr<nsIFile> parent; + if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) { + return parent->GetDiskCapacity(aDiskCapacity); + } + } + + int64_t dummy = 0; + return GetDiskSpaceAttributes(mResolvedPath, &dummy, aDiskCapacity); +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile** aParent) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aParent)) { + return NS_ERROR_INVALID_ARG; + } + + // A two-character path must be a drive such as C:, so it has no parent + if (mWorkingPath.Length() == 2) { + *aParent = nullptr; + return NS_OK; + } + + int32_t offset = mWorkingPath.RFindChar(char16_t('\\')); + // adding this offset check that was removed in bug 241708 fixes mail + // directories that aren't relative to/underneath the profile dir. + // e.g., on a different drive. Before you remove them, please make + // sure local mail directories that aren't underneath the profile dir work. + if (offset == kNotFound) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + // A path of the form \\NAME is a top-level path and has no parent + if (offset == 1 && mWorkingPath[0] == L'\\') { + *aParent = nullptr; + return NS_OK; + } + + nsAutoString parentPath(mWorkingPath); + + if (offset > 0) { + parentPath.Truncate(offset); + } else { + parentPath.AssignLiteral("\\\\."); + } + + nsCOMPtr<nsIFile> localFile; + nsresult rv = NewLocalFile(parentPath, mUseDOSDevicePathSyntax, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) { + return rv; + } + + localFile.forget(aParent); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Exists(bool* aResult) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + MakeDirty(); + nsresult rv = ResolveAndStat(); + *aResult = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool* aIsWritable) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // The read-only attribute on a FAT directory only means that it can't + // be deleted. It is still possible to modify the contents of the directory. + nsresult rv = IsDirectory(aIsWritable); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = true; + return NS_OK; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If the file is normally allowed write access + // we should still return that the file is writable. + } else if (NS_FAILED(rv)) { + return rv; + } + if (*aIsWritable) { + return NS_OK; + } + + // writable if the file doesn't have the readonly attribute + rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = false; + return NS_OK; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If the file is normally allowed write access + // we should still return that the file is writable. + } else if (NS_FAILED(rv)) { + return rv; + } + *aIsWritable = !*aIsWritable; + + // If the read only attribute is not set, check to make sure + // we can open the file with write access. + if (*aIsWritable) { + PRFileDesc* file; + rv = OpenFile(mResolvedPath, PR_WRONLY, 0, false, &file); + if (NS_SUCCEEDED(rv)) { + PR_Close(file); + } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = false; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If it is locked and read only we would have + // gotten access denied + *aIsWritable = true; + } else { + return rv; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool* aResult) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = true; + return NS_OK; +} + +nsresult nsLocalFile::LookupExtensionIn(const char* const* aExtensionsArray, + size_t aArrayLength, bool* aResult) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsresult rv; + + // only files can be executables + bool isFile; + rv = IsFile(&isFile); + if (NS_FAILED(rv)) { + return rv; + } + if (!isFile) { + return NS_OK; + } + + // TODO: shouldn't we be checking mFollowSymlinks here? + bool symLink = false; + rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString path; + if (symLink) { + GetTarget(path); + } else { + GetPath(path); + } + + // kill trailing dots and spaces. + int32_t filePathLen = path.Length() - 1; + while (filePathLen > 0 && + (path[filePathLen] == L' ' || path[filePathLen] == L'.')) { + path.Truncate(filePathLen--); + } + + // Get extension. + int32_t dotIdx = path.RFindChar(char16_t('.')); + if (dotIdx != kNotFound) { + // Convert extension to lower case. + char16_t* p = path.BeginWriting(); + for (p += dotIdx + 1; *p; ++p) { + *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; + } + + nsDependentSubstring ext = Substring(path, dotIdx); + for (size_t i = 0; i < aArrayLength; ++i) { + if (ext.EqualsASCII(aExtensionsArray[i])) { + // Found a match. Set result and quit. + *aResult = true; + break; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool* aResult) { + return LookupExtensionIn(sExecutableExts, ArrayLength(sExecutableExts), + aResult); +} + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool* aResult) { + return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult); +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool* aResult) { + nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult); + if (NS_SUCCEEDED(rv)) { + *aResult = !*aResult; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool* aResult) { + return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, aResult); +} + +nsresult nsLocalFile::HasFileAttribute(DWORD aFileAttrib, bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + DWORD attributes = GetFileAttributesW(mResolvedPath.get()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + return ConvertWinError(GetLastError()); + } + + *aResult = ((attributes & aFileAttrib) != 0); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool* aResult) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + // TODO: Implement symlink support + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool* aResult) { + return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, aResult); +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) { + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(aInFile)); + if (!lf) { + *aResult = false; + return NS_OK; + } + + bool inUseDOSDevicePathSyntax; + lf->GetUseDOSDevicePathSyntax(&inUseDOSDevicePathSyntax); + + // If useDOSDevicePathSyntax are different remove the prefix from the one that + // might have it. This is added because of Omnijar. It compares files from + // different modules with itself. + bool removePathPrefix, removeInPathPrefix; + if (inUseDOSDevicePathSyntax != mUseDOSDevicePathSyntax) { + removeInPathPrefix = inUseDOSDevicePathSyntax; + removePathPrefix = mUseDOSDevicePathSyntax; + } else { + removePathPrefix = removeInPathPrefix = false; + } + + nsAutoString inFilePath, workingPath; + aInFile->GetPath(inFilePath); + workingPath = mWorkingPath; + + constexpr static auto equalPath = + [](nsAutoString& workingPath, nsAutoString& inFilePath, + bool removePathPrefix, bool removeInPathPrefix) { + if (removeInPathPrefix && + StringBeginsWith(inFilePath, kDevicePathSpecifier)) { + MOZ_ASSERT(!StringBeginsWith(workingPath, kDevicePathSpecifier)); + + inFilePath = Substring(inFilePath, kDevicePathSpecifier.Length()); + } else if (removePathPrefix && + StringBeginsWith(workingPath, kDevicePathSpecifier)) { + MOZ_ASSERT(!StringBeginsWith(inFilePath, kDevicePathSpecifier)); + + workingPath = Substring(workingPath, kDevicePathSpecifier.Length()); + } + + return _wcsicmp(workingPath.get(), inFilePath.get()) == 0; + }; + + if (equalPath(workingPath, inFilePath, removePathPrefix, + removeInPathPrefix)) { + *aResult = true; + return NS_OK; + } + + EnsureShortPath(); + lf->GetCanonicalPath(inFilePath); + workingPath = mShortWorkingPath; + *aResult = + equalPath(workingPath, inFilePath, removePathPrefix, removeInPathPrefix); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + *aResult = false; + + nsAutoString myFilePath; + if (NS_FAILED(GetTarget(myFilePath))) { + GetPath(myFilePath); + } + + uint32_t myFilePathLen = myFilePath.Length(); + + nsAutoString inFilePath; + if (NS_FAILED(aInFile->GetTarget(inFilePath))) { + aInFile->GetPath(inFilePath); + } + + // Make sure that the |aInFile|'s path has a trailing separator. + if (inFilePath.Length() > myFilePathLen && + inFilePath[myFilePathLen] == L'\\') { + if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) { + *aResult = true; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetTarget(nsAString& aResult) { + aResult.Truncate(); + Resolve(); + + MOZ_ASSERT_IF( + mUseDOSDevicePathSyntax, + !FilePreferences::StartsWithDiskDesignatorAndBackslash(mResolvedPath)); + + aResult = mResolvedPath; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { + nsresult rv; + + *aEntries = nullptr; + if (mWorkingPath.EqualsLiteral("\\\\.")) { + RefPtr<nsDriveEnumerator> drives = + new nsDriveEnumerator(mUseDOSDevicePathSyntax); + rv = drives->Init(); + if (NS_FAILED(rv)) { + return rv; + } + drives.forget(aEntries); + return NS_OK; + } + + RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + return rv; + } + + dirEnum.forget(aEntries); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) { + CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) { + if (IsUtf8(aPersistentDescriptor)) { + return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor)); + } else { + return InitWithNativePath(aPersistentDescriptor); + } +} + +NS_IMETHODIMP +nsLocalFile::GetReadOnly(bool* aReadOnly) { + NS_ENSURE_ARG_POINTER(aReadOnly); + + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + *aReadOnly = dwAttrs & FILE_ATTRIBUTE_READONLY; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetReadOnly(bool aReadOnly) { + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + if (aReadOnly) { + dwAttrs |= FILE_ATTRIBUTE_READONLY; + } else { + dwAttrs &= ~FILE_ATTRIBUTE_READONLY; + } + + if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetUseDOSDevicePathSyntax(bool* aUseDOSDevicePathSyntax) { + MOZ_ASSERT(aUseDOSDevicePathSyntax); + + *aUseDOSDevicePathSyntax = mUseDOSDevicePathSyntax; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax) { + if (mUseDOSDevicePathSyntax == aUseDOSDevicePathSyntax) { + return NS_OK; + } + + if (mUseDOSDevicePathSyntax) { + if (StringBeginsWith(mWorkingPath, kDevicePathSpecifier)) { + MakeDirty(); + // Remove the prefix + mWorkingPath = Substring(mWorkingPath, kDevicePathSpecifier.Length()); + } + } else { + if (FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) { + MakeDirty(); + // Prepend the prefix + mWorkingPath = kDevicePathSpecifier + mWorkingPath; + } + } + + mUseDOSDevicePathSyntax = aUseDOSDevicePathSyntax; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Reveal() { + // This API should be main thread only + MOZ_ASSERT(NS_IsMainThread()); + + // make sure mResolvedPath is set + nsresult rv = Resolve(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + return rv; + } + + nsCOMPtr<nsIRunnable> task = + NS_NewRunnableFunction("nsLocalFile::Reveal", [path = mResolvedPath]() { + MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread"); + + bool doCoUninitialize = SUCCEEDED(CoInitializeEx( + nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); + RevealFile(path); + if (doCoUninitialize) { + CoUninitialize(); + } + }); + + return NS_DispatchBackgroundTask(task, + nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK); +} + +NS_IMETHODIMP +nsLocalFile::GetWindowsFileAttributes(uint32_t* aAttrs) { + NS_ENSURE_ARG_POINTER(aAttrs); + + DWORD dwAttrs = ::GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return ConvertWinError(GetLastError()); + } + + *aAttrs = dwAttrs; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetWindowsFileAttributes(uint32_t aSetAttrs, + uint32_t aClearAttrs) { + DWORD dwAttrs = ::GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return ConvertWinError(GetLastError()); + } + + dwAttrs = (dwAttrs & ~aClearAttrs) | aSetAttrs; + + if (::SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) { + return ConvertWinError(GetLastError()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Launch() { + // This API should be main thread only + MOZ_ASSERT(NS_IsMainThread()); + + // use the app registry name to launch a shell execute.... + _bstr_t execPath(mWorkingPath.get()); + + _variant_t args; + // Pass VT_ERROR/DISP_E_PARAMNOTFOUND to omit an optional RPC parameter + // to execute a file with the default verb. + _variant_t verbDefault(DISP_E_PARAMNOTFOUND, VT_ERROR); + _variant_t showCmd(SW_SHOWNORMAL); + + // Use the directory of the file we're launching as the working + // directory. That way if we have a self extracting EXE it won't + // suggest to extract to the install directory. + wchar_t* workingDirectoryPtr = nullptr; + WCHAR workingDirectory[MAX_PATH + 1] = {L'\0'}; + wcsncpy(workingDirectory, mWorkingPath.get(), MAX_PATH); + if (PathRemoveFileSpecW(workingDirectory)) { + workingDirectoryPtr = workingDirectory; + } else { + NS_WARNING("Could not set working directory for launched file."); + } + + // We have two methods to launch a file: ShellExecuteExW and + // ShellExecuteByExplorer. ShellExecuteExW starts a new process as a child + // of the current process, while ShellExecuteByExplorer starts a new process + // as a child of explorer.exe. + // + // We prefer launching a process via ShellExecuteByExplorer because + // applications may not support the mitigation policies inherited from our + // process. For example, Skype for Business does not start correctly with + // the PreferSystem32Images policy which is one of the policies we use. + // + // If ShellExecuteByExplorer fails for some reason e.g. a system without + // running explorer.exe or VDI environment like Citrix, we fall back to + // ShellExecuteExW which still works in those special environments. + // + // There is an exception where we go straight to ShellExecuteExW without + // trying ShellExecuteByExplorer. When the extension of a downloaded file is + // "exe", we prefer security rather than compatibility. + // + // When a user launches a downloaded executable, the directory containing + // the downloaded file may contain a malicious DLL with a common name, which + // may have been downloaded before. If the downloaded executable is launched + // without the PreferSystem32Images policy, the process can be tricked into + // loading the malicious DLL in the same directory if its name is in the + // executable's dependent modules. Therefore, we always launch ".exe" + // executables via ShellExecuteExW so they inherit our process's mitigation + // policies including PreferSystem32Images. + // + // If the extension is not "exe", then we assume that we are launching an + // installed application, and therefore the security risk described above + // is lessened, as a malicious DLL is less likely to be installed in the + // application's directory. In that case, we attempt to preserve + // compatibility and try ShellExecuteByExplorer first. + + static const char* const onlyExeExt[] = {".exe"}; + bool isExecutable; + nsresult rv = + LookupExtensionIn(onlyExeExt, ArrayLength(onlyExeExt), &isExecutable); + if (NS_FAILED(rv)) { + isExecutable = false; + } + + // If the file is an executable, go straight to ShellExecuteExW. + // Otherwise try ShellExecuteByExplorer first, and if it fails, + // run ShellExecuteExW. + if (!isExecutable) { + mozilla::LauncherVoidResult shellExecuteOk = + mozilla::ShellExecuteByExplorer(execPath, args, verbDefault, + workingDirectoryPtr, showCmd); + if (shellExecuteOk.isOk()) { + return NS_OK; + } + } + + SHELLEXECUTEINFOW seinfo = {sizeof(SHELLEXECUTEINFOW)}; + seinfo.fMask = SEE_MASK_ASYNCOK; + seinfo.hwnd = GetMostRecentNavigatorHWND(); + seinfo.lpVerb = nullptr; + seinfo.lpFile = mWorkingPath.get(); + seinfo.lpParameters = nullptr; + seinfo.lpDirectory = workingDirectoryPtr; + seinfo.nShow = SW_SHOWNORMAL; + + if (!ShellExecuteExW(&seinfo)) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + return NS_OK; +} + +nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, + nsIFile** aResult) { + RefPtr<nsLocalFile> file = new nsLocalFile(); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithPath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + + file.forget(aResult); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Native (lossy) interface +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString& aFilePath) { + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aFilePath, tmp); + if (NS_SUCCEEDED(rv)) { + return InitWithPath(tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString& aNode) { + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNode, tmp); + if (NS_SUCCEEDED(rv)) { + return Append(tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString& aNode) { + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNode, tmp); + if (NS_SUCCEEDED(rv)) { + return AppendRelativePath(tmp); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeLeafName(nsACString& aLeafName) { + // NS_WARNING("This API is lossy. Use GetLeafName !"); + nsAutoString tmp; + nsresult rv = GetLeafName(tmp); + if (NS_SUCCEEDED(rv)) { + rv = NS_CopyUnicodeToNative(tmp, aLeafName); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) { + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp); + if (NS_SUCCEEDED(rv)) { + return SetLeafName(tmp); + } + + return rv; +} + +nsString nsLocalFile::NativePath() { return mWorkingPath; } + +nsCString nsIFile::HumanReadablePath() { + nsString path; + DebugOnly<nsresult> rv = GetPath(path); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return NS_ConvertUTF16toUTF8(path); +} + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile* aNewParentDir, const nsACString& aNewName) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return CopyTo(aNewParentDir, u""_ns); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return CopyTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParentDir, + const nsACString& aNewName) { + if (aNewName.IsEmpty()) { + return CopyToFollowingLinks(aNewParentDir, u""_ns); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return CopyToFollowingLinks(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile* aNewParentDir, const nsACString& aNewName) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return MoveTo(aNewParentDir, u""_ns); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return MoveTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParentDir, + const nsACString& aNewName) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return MoveToFollowingLinks(aNewParentDir, u""_ns); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return MoveToFollowingLinks(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString& aResult) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_WARNING("This API is lossy. Use GetTarget !"); + nsAutoString tmp; + nsresult rv = GetTarget(tmp); + if (NS_SUCCEEDED(rv)) { + rv = NS_CopyUnicodeToNative(tmp, aResult); + } + + return rv; +} + +nsresult NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult) { + nsAutoString buf; + nsresult rv = NS_CopyNativeToUnicode(aPath, buf); + if (NS_FAILED(rv)) { + *aResult = nullptr; + return rv; + } + return NS_NewLocalFile(buf, aFollowLinks, aResult); +} + +void nsLocalFile::EnsureShortPath() { + if (!mShortWorkingPath.IsEmpty()) { + return; + } + + WCHAR shortPath[MAX_PATH + 1]; + DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath, + ArrayLength(shortPath)); + // If an error occurred then lengthNeeded is set to 0 or the length of the + // needed buffer including null termination. If it succeeds the number of + // wide characters not including null termination is returned. + if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath)) { + mShortWorkingPath.Assign(shortPath); + } else { + mShortWorkingPath.Assign(mWorkingPath); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsDriveEnumerator, nsSimpleEnumerator, + nsIDirectoryEnumerator) + +nsDriveEnumerator::nsDriveEnumerator(bool aUseDOSDevicePathSyntax) + : mUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax) {} + +nsDriveEnumerator::~nsDriveEnumerator() {} + +nsresult nsDriveEnumerator::Init() { + /* If the length passed to GetLogicalDriveStrings is smaller + * than the length of the string it would return, it returns + * the length required for the string. */ + DWORD length = GetLogicalDriveStringsW(0, 0); + /* The string is null terminated */ + if (!mDrives.SetLength(length + 1, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!GetLogicalDriveStringsW(length, mDrives.get())) { + return NS_ERROR_FAILURE; + } + mDrives.BeginReading(mStartOfCurrentDrive); + mDrives.EndReading(mEndOfDrivesString); + return NS_OK; +} + +NS_IMETHODIMP +nsDriveEnumerator::HasMoreElements(bool* aHasMore) { + *aHasMore = *mStartOfCurrentDrive != L'\0'; + return NS_OK; +} + +NS_IMETHODIMP +nsDriveEnumerator::GetNext(nsISupports** aNext) { + /* GetLogicalDrives stored in mDrives is a concatenation + * of null terminated strings, followed by a null terminator. + * mStartOfCurrentDrive is an iterator pointing at the first + * character of the current drive. */ + if (*mStartOfCurrentDrive == L'\0') { + *aNext = nullptr; + return NS_ERROR_FAILURE; + } + + nsAString::const_iterator driveEnd = mStartOfCurrentDrive; + FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString); + nsString drive(Substring(mStartOfCurrentDrive, driveEnd)); + mStartOfCurrentDrive = ++driveEnd; + + nsIFile* file; + nsresult rv = NewLocalFile(drive, mUseDOSDevicePathSyntax, &file); + + *aNext = file; + return rv; +} diff --git a/xpcom/io/nsLocalFileWin.h b/xpcom/io/nsLocalFileWin.h new file mode 100644 index 0000000000..a3ea03a666 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.h @@ -0,0 +1,127 @@ +/* -*- 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 _nsLocalFileWIN_H_ +#define _nsLocalFileWIN_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsILocalFileWin.h" +#include "nsIClassInfoImpl.h" +#include "prio.h" + +#include "mozilla/Attributes.h" + +#include <windows.h> +#include <shlobj.h> + +#include <sys/stat.h> + +class nsLocalFile final : public nsILocalFileWin { + public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + explicit nsLocalFile(const nsAString& aFilePath); + + static nsresult nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIFile interface + NS_DECL_NSIFILE + + // nsILocalFileWin interface + NS_DECL_NSILOCALFILEWIN + + public: + // Removes registry command handler parameters, quotes, and expands + // environment strings. + static bool CleanupCmdHandlerPath(nsAString& aCommandHandler); + // Called off the main thread to open the window revealing the file + static nsresult RevealFile(const nsString& aResolvedPath); + + // Checks if the filename is one of the windows reserved filenames + // (com1, com2, etc...) and returns true if so. + static bool CheckForReservedFileName(const nsString& aFileName); + + // PRFileInfo64 does not hvae an accessTime field; + struct FileInfo { + PRFileType type; + PROffset64 size; + PRTime creationTime; + PRTime accessTime; + PRTime modifyTime; + }; + + private: + // CopyMove and CopySingleFile constants for |options| parameter: + enum CopyFileOption { + FollowSymlinks = 1u << 0, + Move = 1u << 1, + SkipNtfsAclReset = 1u << 2, + Rename = 1u << 3 + }; + + nsLocalFile(const nsLocalFile& aOther); + ~nsLocalFile() {} + + bool mDirty; // cached information can only be used when this is false + bool mResolveDirty; + + bool mUseDOSDevicePathSyntax; + + // this string will always be in native format! + nsString mWorkingPath; + + // this will be the resolved path of shortcuts, it will *NEVER* + // be returned to the user + nsString mResolvedPath; + + // this string, if not empty, is the *short* pathname that represents + // mWorkingPath + nsString mShortWorkingPath; + + FileInfo mFileInfo; + + void MakeDirty() { + mDirty = true; + mResolveDirty = true; + mShortWorkingPath.Truncate(); + } + + nsresult LookupExtensionIn(const char* const* aExtensionsArray, + size_t aArrayLength, bool* aResult); + + nsresult ResolveAndStat(); + nsresult Resolve(); + nsresult ResolveSymlink(); + + void EnsureShortPath(); + + nsresult CopyMove(nsIFile* aNewParentDir, const nsAString& aNewName, + uint32_t aOptions); + nsresult CopySingleFile(nsIFile* aSource, nsIFile* aDest, + const nsAString& aNewName, uint32_t aOptions); + + enum class TimeField { AccessedTime, ModifiedTime }; + + nsresult SetDateImpl(int64_t aTime, TimeField aTimeField); + nsresult GetDateImpl(PRTime* aTime, TimeField aTimeField, bool aFollowLinks); + nsresult HasFileAttribute(DWORD aFileAttrib, bool* aResult); + nsresult AppendInternal(const nsString& aNode, bool aMultipleComponents); + + nsresult OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, int32_t aMode, + bool aShareDelete, + PRFileDesc** aResult); +}; + +#endif diff --git a/xpcom/io/nsMultiplexInputStream.cpp b/xpcom/io/nsMultiplexInputStream.cpp new file mode 100644 index 0000000000..bc8a67ed23 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.cpp @@ -0,0 +1,1557 @@ +/* -*- 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/. */ + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#include "mozilla/Attributes.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Mutex.h" + +#include "base/basictypes.h" + +#include "nsMultiplexInputStream.h" +#include "nsIBufferedStreams.h" +#include "nsICloneableInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsISeekableStream.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIClassInfoImpl.h" +#include "nsIIPCSerializableInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsIInputStreamLength.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +using mozilla::DeprecatedAbs; + +class nsMultiplexInputStream final : public nsIMultiplexInputStream, + public nsISeekableStream, + public nsIIPCSerializableInputStream, + public nsICloneableInputStream, + public nsIAsyncInputStream, + public nsIInputStreamCallback, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength { + public: + nsMultiplexInputStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIMULTIPLEXINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + + // This is used for nsIAsyncInputStream::AsyncWait + void AsyncWaitCompleted(); + + // This is used for nsIAsyncInputStreamLength::AsyncLengthWait + void AsyncWaitCompleted(int64_t aLength, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mLock); + + struct StreamData { + nsresult Initialize(nsIInputStream* aOriginalStream) { + mCurrentPos = 0; + + mOriginalStream = aOriginalStream; + + mBufferedStream = aOriginalStream; + if (!NS_InputStreamIsBuffered(mBufferedStream)) { + nsCOMPtr<nsIInputStream> bufferedStream; + nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), + mBufferedStream.forget(), 4096); + NS_ENSURE_SUCCESS(rv, rv); + mBufferedStream = bufferedStream; + } + + mAsyncStream = do_QueryInterface(mBufferedStream); + mSeekableStream = do_QueryInterface(mBufferedStream); + + return NS_OK; + } + + nsCOMPtr<nsIInputStream> mOriginalStream; + + // Equal to mOriginalStream or a wrap around the original stream to make it + // buffered. + nsCOMPtr<nsIInputStream> mBufferedStream; + + // This can be null. + nsCOMPtr<nsIAsyncInputStream> mAsyncStream; + // This can be null. + nsCOMPtr<nsISeekableStream> mSeekableStream; + + uint64_t mCurrentPos; + }; + + Mutex& GetLock() MOZ_RETURN_CAPABILITY(mLock) { return mLock; } + + private: + ~nsMultiplexInputStream() = default; + + void NextStream() MOZ_REQUIRES(mLock) { + ++mCurrentStream; + mStartedReadingCurrent = false; + } + + nsresult AsyncWaitInternal(); + + // This method updates mSeekableStreams, mTellableStreams, + // mIPCSerializableStreams and mCloneableStreams values. + void UpdateQIMap(StreamData& aStream) MOZ_REQUIRES(mLock); + + struct MOZ_STACK_CLASS ReadSegmentsState { + nsCOMPtr<nsIInputStream> mThisStream; + uint32_t mOffset; + nsWriteSegmentFun mWriter; + void* mClosure; + bool mDone; + }; + + void SerializedComplexityInternal(uint32_t aMaxSize, uint32_t* aSizeUsed, + uint32_t* aPipes, uint32_t* aTransferables, + bool* aSerializeAsPipe); + + static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + + bool IsSeekable() const; + bool IsIPCSerializable() const; + bool IsCloneable() const; + bool IsAsyncInputStream() const; + bool IsInputStreamLength() const; + bool IsAsyncInputStreamLength() const; + + Mutex mLock; // Protects access to all data members. + + nsTArray<StreamData> mStreams MOZ_GUARDED_BY(mLock); + + uint32_t mCurrentStream MOZ_GUARDED_BY(mLock); + bool mStartedReadingCurrent MOZ_GUARDED_BY(mLock); + nsresult mStatus MOZ_GUARDED_BY(mLock); + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mLock); + uint32_t mAsyncWaitFlags MOZ_GUARDED_BY(mLock); + uint32_t mAsyncWaitRequestedCount MOZ_GUARDED_BY(mLock); + nsCOMPtr<nsIEventTarget> mAsyncWaitEventTarget MOZ_GUARDED_BY(mLock); + nsCOMPtr<nsIInputStreamLengthCallback> mAsyncWaitLengthCallback + MOZ_GUARDED_BY(mLock); + + class AsyncWaitLengthHelper; + RefPtr<AsyncWaitLengthHelper> mAsyncWaitLengthHelper MOZ_GUARDED_BY(mLock); + + uint32_t mSeekableStreams MOZ_GUARDED_BY(mLock); + uint32_t mIPCSerializableStreams MOZ_GUARDED_BY(mLock); + uint32_t mCloneableStreams MOZ_GUARDED_BY(mLock); + + // These are Atomics so that we can check them in QueryInterface without + // taking a lock (to look at mStreams.Length() and the numbers above) + // With no streams added yet, all of these are possible + Atomic<bool, Relaxed> mIsSeekableStream{true}; + Atomic<bool, Relaxed> mIsIPCSerializableStream{true}; + Atomic<bool, Relaxed> mIsCloneableStream{true}; + + Atomic<bool, Relaxed> mIsAsyncInputStream{false}; + Atomic<bool, Relaxed> mIsInputStreamLength{false}; + Atomic<bool, Relaxed> mIsAsyncInputStreamLength{false}; +}; + +NS_IMPL_ADDREF(nsMultiplexInputStream) +NS_IMPL_RELEASE(nsMultiplexInputStream) + +NS_IMPL_CLASSINFO(nsMultiplexInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_MULTIPLEXINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsMultiplexInputStream) + NS_INTERFACE_MAP_ENTRY(nsIMultiplexInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, IsSeekable()) + NS_INTERFACE_MAP_ENTRY(nsITellableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + IsIPCSerializable()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, IsAsyncInputStream()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + IsAsyncInputStream()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, + IsInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + IsAsyncInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMultiplexInputStream) + NS_IMPL_QUERY_CLASSINFO(nsMultiplexInputStream) +NS_INTERFACE_MAP_END + +NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream, nsIMultiplexInputStream, + nsIInputStream, nsISeekableStream, + nsITellableStream) + +static nsresult AvailableMaybeSeek(nsMultiplexInputStream::StreamData& aStream, + uint64_t* aResult) { + nsresult rv = aStream.mBufferedStream->Available(aResult); + if (rv == NS_BASE_STREAM_CLOSED) { + // Blindly seek to the current position if Available() returns + // NS_BASE_STREAM_CLOSED. + // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag, + // Seek() could reopen the file if REOPEN_ON_REWIND flag is set. + if (aStream.mSeekableStream) { + nsresult rvSeek = + aStream.mSeekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + if (NS_SUCCEEDED(rvSeek)) { + rv = aStream.mBufferedStream->Available(aResult); + } + } + } + return rv; +} + +nsMultiplexInputStream::nsMultiplexInputStream() + : mLock("nsMultiplexInputStream lock"), + mCurrentStream(0), + mStartedReadingCurrent(false), + mStatus(NS_OK), + mAsyncWaitFlags(0), + mAsyncWaitRequestedCount(0), + mSeekableStreams(0), + mIPCSerializableStreams(0), + mCloneableStreams(0) {} + +NS_IMETHODIMP +nsMultiplexInputStream::GetCount(uint32_t* aCount) { + MutexAutoLock lock(mLock); + *aCount = mStreams.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::AppendStream(nsIInputStream* aStream) { + MutexAutoLock lock(mLock); + + StreamData* streamData = mStreams.AppendElement(fallible); + if (NS_WARN_IF(!streamData)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = streamData->Initialize(aStream); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateQIMap(*streamData); + + if (mStatus == NS_BASE_STREAM_CLOSED) { + // We were closed, but now we have more data to read. + mStatus = NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult) { + MutexAutoLock lock(mLock); + + if (aIndex >= mStreams.Length()) { + return NS_ERROR_NOT_AVAILABLE; + } + + StreamData& streamData = mStreams.ElementAt(aIndex); + nsCOMPtr<nsIInputStream> stream = streamData.mOriginalStream; + stream.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Close() { + nsTArray<nsCOMPtr<nsIInputStream>> streams; + + // Let's take a copy of the streams becuase, calling close() it could trigger + // a nsIInputStreamCallback immediately and we don't want to create a deadlock + // with mutex. + { + MutexAutoLock lock(mLock); + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + if (NS_WARN_IF( + !streams.AppendElement(mStreams[i].mBufferedStream, fallible))) { + mStatus = NS_BASE_STREAM_CLOSED; + return NS_ERROR_OUT_OF_MEMORY; + } + } + mStatus = NS_BASE_STREAM_CLOSED; + } + + nsresult rv = NS_OK; + + uint32_t len = streams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsresult rv2 = streams[i]->Close(); + // We still want to close all streams, but we should return an error + if (NS_FAILED(rv2)) { + rv = rv2; + } + } + + return rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Available(uint64_t* aResult) { + *aResult = 0; + + MutexAutoLock lock(mLock); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + uint64_t avail = 0; + nsresult rv = NS_BASE_STREAM_CLOSED; + + uint32_t len = mStreams.Length(); + for (uint32_t i = mCurrentStream; i < len; i++) { + uint64_t streamAvail; + rv = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (rv == NS_BASE_STREAM_CLOSED) { + // If a stream is closed, we continue with the next one. + // If this is the current stream we move to the following stream. + if (mCurrentStream == i) { + NextStream(); + } + + // If this is the last stream, we want to return this error code. + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mStatus = rv; + return mStatus; + } + + // If the current stream is async, we have to return what we have so far + // without processing the following streams. This is needed because + // ::Available should return only what is currently available. In case of an + // nsIAsyncInputStream, we have to call AsyncWait() in order to read more. + if (mStreams[i].mAsyncStream) { + avail += streamAvail; + break; + } + + if (streamAvail == 0) { + // Nothing to read for this stream. Let's move to the next one. + continue; + } + + avail += streamAvail; + } + + // We still have something to read. We don't want to return an error code yet. + if (avail) { + *aResult = avail; + return NS_OK; + } + + // Let's propagate the last error message. + mStatus = rv; + return rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::StreamStatus() { + MutexAutoLock lock(mLock); + return mStatus; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) { + MutexAutoLock lock(mLock); + // It is tempting to implement this method in terms of ReadSegments, but + // that would prevent this class from being used with streams that only + // implement Read (e.g., file streams). + + *aResult = 0; + + if (mStatus == NS_BASE_STREAM_CLOSED) { + return NS_OK; + } + if (NS_FAILED(mStatus)) { + return mStatus; + } + + nsresult rv = NS_OK; + + uint32_t len = mStreams.Length(); + while (mCurrentStream < len && aCount) { + uint32_t read; + rv = mStreams[mCurrentStream].mBufferedStream->Read(aBuf, aCount, &read); + + // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. + // (This is a bug in those stream implementations) + if (rv == NS_BASE_STREAM_CLOSED) { + MOZ_ASSERT_UNREACHABLE( + "Input stream's Read method returned " + "NS_BASE_STREAM_CLOSED"); + rv = NS_OK; + read = 0; + } else if (NS_FAILED(rv)) { + break; + } + + if (read == 0) { + NextStream(); + } else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + *aResult += read; + aCount -= read; + aBuf += read; + mStartedReadingCurrent = true; + + mStreams[mCurrentStream].mCurrentPos += read; + } + } + return *aResult ? NS_OK : rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + MutexAutoLock lock(mLock); + + if (mStatus == NS_BASE_STREAM_CLOSED) { + *aResult = 0; + return NS_OK; + } + if (NS_FAILED(mStatus)) { + return mStatus; + } + + NS_ASSERTION(aWriter, "missing aWriter"); + + nsresult rv = NS_OK; + ReadSegmentsState state; + state.mThisStream = this; + state.mOffset = 0; + state.mWriter = aWriter; + state.mClosure = aClosure; + state.mDone = false; + + uint32_t len = mStreams.Length(); + while (mCurrentStream < len && aCount) { + uint32_t read; + rv = mStreams[mCurrentStream].mBufferedStream->ReadSegments( + ReadSegCb, &state, aCount, &read); + + // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. + // (This is a bug in those stream implementations) + if (rv == NS_BASE_STREAM_CLOSED) { + MOZ_ASSERT_UNREACHABLE( + "Input stream's Read method returned " + "NS_BASE_STREAM_CLOSED"); + rv = NS_OK; + read = 0; + } + + // if |aWriter| decided to stop reading segments... + if (state.mDone || NS_FAILED(rv)) { + break; + } + + // if stream is empty, then advance to the next stream. + if (read == 0) { + NextStream(); + } else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + state.mOffset += read; + aCount -= read; + mStartedReadingCurrent = true; + + mStreams[mCurrentStream].mCurrentPos += read; + } + } + + // if we successfully read some data, then this call succeeded. + *aResult = state.mOffset; + return state.mOffset ? NS_OK : rv; +} + +nsresult nsMultiplexInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + nsresult rv; + ReadSegmentsState* state = (ReadSegmentsState*)aClosure; + rv = (state->mWriter)(state->mThisStream, state->mClosure, aFromRawSegment, + aToOffset + state->mOffset, aCount, aWriteCount); + if (NS_FAILED(rv)) { + state->mDone = true; + } + return rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::IsNonBlocking(bool* aNonBlocking) { + MutexAutoLock lock(mLock); + + uint32_t len = mStreams.Length(); + if (len == 0) { + // Claim to be non-blocking, since we won't block the caller. + *aNonBlocking = true; + return NS_OK; + } + + for (uint32_t i = 0; i < len; ++i) { + nsresult rv = mStreams[i].mBufferedStream->IsNonBlocking(aNonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // If one is blocking the entire stream becomes blocking. + if (!*aNonBlocking) { + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Seek(int32_t aWhence, int64_t aOffset) { + MutexAutoLock lock(mLock); + + if (NS_FAILED(mStatus)) { + return mStatus; + } + + nsresult rv; + + uint32_t oldCurrentStream = mCurrentStream; + bool oldStartedReadingCurrent = mStartedReadingCurrent; + + if (aWhence == NS_SEEK_SET) { + int64_t remaining = aOffset; + if (aOffset == 0) { + mCurrentStream = 0; + } + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream; + if (!stream) { + return NS_ERROR_FAILURE; + } + + // See if all remaining streams should be rewound + if (remaining == 0) { + if (i < oldCurrentStream || + (i == oldCurrentStream && oldStartedReadingCurrent)) { + rv = stream->Seek(NS_SEEK_SET, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = 0; + continue; + } else { + break; + } + } + + // Get position in the current stream + int64_t streamPos; + if (i > oldCurrentStream || + (i == oldCurrentStream && !oldStartedReadingCurrent)) { + streamPos = 0; + } else { + streamPos = mStreams[i].mCurrentPos; + } + + // See if we need to seek the current stream forward or backward + if (remaining < streamPos) { + rv = stream->Seek(NS_SEEK_SET, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = remaining; + mCurrentStream = i; + mStartedReadingCurrent = remaining != 0; + + remaining = 0; + } else if (remaining > streamPos) { + if (i < oldCurrentStream) { + // We're already at end so no need to seek this stream + remaining -= streamPos; + NS_ASSERTION(remaining >= 0, "Remaining invalid"); + } else { + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t newPos = XPCOM_MIN(remaining, streamPos + (int64_t)avail); + + rv = stream->Seek(NS_SEEK_SET, newPos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = newPos; + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining -= newPos; + NS_ASSERTION(remaining >= 0, "Remaining invalid"); + } + } else { + NS_ASSERTION(remaining == streamPos, "Huh?"); + MOZ_ASSERT(remaining != 0, "Zero remaining should be handled earlier"); + remaining = 0; + mCurrentStream = i; + mStartedReadingCurrent = true; + } + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR && aOffset > 0) { + int64_t remaining = aOffset; + for (uint32_t i = mCurrentStream; remaining && i < mStreams.Length(); ++i) { + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t seek = XPCOM_MIN((int64_t)avail, remaining); + + rv = mStreams[i].mSeekableStream->Seek(NS_SEEK_CUR, seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos += seek; + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining -= seek; + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR && aOffset < 0) { + int64_t remaining = -aOffset; + for (uint32_t i = mCurrentStream; remaining && i != (uint32_t)-1; --i) { + int64_t pos = mStreams[i].mCurrentPos; + + int64_t seek = XPCOM_MIN(pos, remaining); + + rv = mStreams[i].mSeekableStream->Seek(NS_SEEK_CUR, -seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos -= seek; + mCurrentStream = i; + mStartedReadingCurrent = seek != -pos; + + remaining -= seek; + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR) { + NS_ASSERTION(aOffset == 0, "Should have handled all non-zero values"); + + return NS_OK; + } + + if (aWhence == NS_SEEK_END) { + if (aOffset > 0) { + return NS_ERROR_INVALID_ARG; + } + + int64_t remaining = aOffset; + int32_t i; + for (i = mStreams.Length() - 1; i >= 0; --i) { + nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream; + + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t streamLength = avail + mStreams[i].mCurrentPos; + + // The seek(END) can be completed in the current stream. + if (streamLength >= DeprecatedAbs(remaining)) { + rv = stream->Seek(NS_SEEK_END, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = streamLength + remaining; + mCurrentStream = i; + mStartedReadingCurrent = true; + break; + } + + // We are at the beginning of this stream. + rv = stream->Seek(NS_SEEK_SET, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + remaining += streamLength; + mStreams[i].mCurrentPos = 0; + } + + // Any other stream must be set to the end. + for (--i; i >= 0; --i) { + nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream; + + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t streamLength = avail + mStreams[i].mCurrentPos; + + rv = stream->Seek(NS_SEEK_END, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = streamLength; + } + + return NS_OK; + } + + // other Seeks not implemented yet + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Tell(int64_t* aResult) { + MutexAutoLock lock(mLock); + + if (NS_FAILED(mStatus)) { + return mStatus; + } + + int64_t ret64 = 0; +#ifdef DEBUG + bool zeroFound = false; +#endif + + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + ret64 += mStreams[i].mCurrentPos; + +#ifdef DEBUG + // When we see 1 stream with currentPos = 0, all the remaining streams must + // be set to 0 as well. + MOZ_ASSERT_IF(zeroFound, mStreams[i].mCurrentPos == 0); + if (mStreams[i].mCurrentPos == 0) { + zeroFound = true; + } +#endif + } + *aResult = ret64; + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::SetEOF() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsMultiplexInputStream::CloseWithStatus(nsresult aStatus) { return Close(); } + +// This class is used to inform nsMultiplexInputStream that it's time to execute +// the asyncWait callback. +class AsyncWaitRunnable final : public DiscardableRunnable { + RefPtr<nsMultiplexInputStream> mStream; + + public: + static void Create(nsMultiplexInputStream* aStream, + nsIEventTarget* aEventTarget) { + RefPtr<AsyncWaitRunnable> runnable = new AsyncWaitRunnable(aStream); + if (aEventTarget) { + aEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + } else { + runnable->Run(); + } + } + + NS_IMETHOD + Run() override { + mStream->AsyncWaitCompleted(); + return NS_OK; + } + + private: + explicit AsyncWaitRunnable(nsMultiplexInputStream* aStream) + : DiscardableRunnable("AsyncWaitRunnable"), mStream(aStream) { + MOZ_ASSERT(aStream); + } +}; + +NS_IMETHODIMP +nsMultiplexInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + { + MutexAutoLock lock(mLock); + + // We must execute the callback also when the stream is closed. + if (NS_FAILED(mStatus) && mStatus != NS_BASE_STREAM_CLOSED) { + return mStatus; + } + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + mAsyncWaitFlags = aFlags; + mAsyncWaitRequestedCount = aRequestedCount; + mAsyncWaitEventTarget = aEventTarget; + } + + return AsyncWaitInternal(); +} + +nsresult nsMultiplexInputStream::AsyncWaitInternal() { + nsCOMPtr<nsIAsyncInputStream> stream; + nsIInputStreamCallback* asyncWaitCallback = nullptr; + uint32_t asyncWaitFlags = 0; + uint32_t asyncWaitRequestedCount = 0; + nsCOMPtr<nsIEventTarget> asyncWaitEventTarget; + + { + MutexAutoLock lock(mLock); + + // Let's take the first async stream if we are not already closed, and if + // it has data to read or if it async. + if (mStatus != NS_BASE_STREAM_CLOSED) { + for (; mCurrentStream < mStreams.Length(); NextStream()) { + stream = mStreams[mCurrentStream].mAsyncStream; + if (stream) { + break; + } + + uint64_t avail = 0; + nsresult rv = AvailableMaybeSeek(mStreams[mCurrentStream], &avail); + if (rv == NS_BASE_STREAM_CLOSED || (NS_SUCCEEDED(rv) && avail == 0)) { + // Nothing to read here. Let's move on. + continue; + } + + if (NS_FAILED(rv)) { + return rv; + } + + break; + } + } + + asyncWaitCallback = mAsyncWaitCallback ? this : nullptr; + asyncWaitFlags = mAsyncWaitFlags; + asyncWaitRequestedCount = mAsyncWaitRequestedCount; + asyncWaitEventTarget = mAsyncWaitEventTarget; + + MOZ_ASSERT_IF(stream, NS_SUCCEEDED(mStatus)); + } + + // If we are here it's because we are already closed, or if the current stream + // is not async. In both case we have to execute the callback. + if (!stream) { + if (asyncWaitCallback) { + AsyncWaitRunnable::Create(this, asyncWaitEventTarget); + } + return NS_OK; + } + + return stream->AsyncWait(asyncWaitCallback, asyncWaitFlags, + asyncWaitRequestedCount, asyncWaitEventTarget); +} + +NS_IMETHODIMP +nsMultiplexInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + + // When OnInputStreamReady is called, we could be in 2 scenarios: + // a. there is something to read; + // b. the stream is closed. + // But if the stream is closed and it was not the last one, we must proceed + // with the following stream in order to have something to read by the callee. + + { + MutexAutoLock lock(mLock); + + // The callback has been nullified in the meantime. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + if (NS_SUCCEEDED(mStatus)) { + uint64_t avail = 0; + nsresult rv = NS_OK; + // Only check `Available()` if `aStream` is actually the current stream, + // otherwise we'll always want to re-poll, as we got the callback for the + // wrong stream. + if (mCurrentStream < mStreams.Length() && + aStream == mStreams[mCurrentStream].mAsyncStream) { + rv = aStream->Available(&avail); + } + if (rv == NS_BASE_STREAM_CLOSED || (NS_SUCCEEDED(rv) && avail == 0)) { + // This stream is either closed, has no data available, or is not the + // current stream. If it is closed and current, move to the next stream, + // otherwise re-wait on the current stream until it has data available + // or becomes closed. + // Unlike streams not implementing nsIAsyncInputStream, async streams + // cannot use `Available() == 0` to indicate EOF, so we re-poll in that + // situation. + if (NS_FAILED(rv)) { + NextStream(); + } + + // Unlock and invoke AsyncWaitInternal to wait again. If this succeeds, + // we'll be called again, otherwise fall through and notify. + MutexAutoUnlock unlock(mLock); + if (NS_SUCCEEDED(AsyncWaitInternal())) { + return NS_OK; + } + } + } + + mAsyncWaitCallback.swap(callback); + mAsyncWaitEventTarget = nullptr; + } + + return callback ? callback->OnInputStreamReady(this) : NS_OK; +} + +void nsMultiplexInputStream::AsyncWaitCompleted() { + nsCOMPtr<nsIInputStreamCallback> callback; + + { + MutexAutoLock lock(mLock); + + // The callback has been nullified in the meantime. + if (!mAsyncWaitCallback) { + return; + } + + mAsyncWaitCallback.swap(callback); + mAsyncWaitEventTarget = nullptr; + } + + callback->OnInputStreamReady(this); +} + +nsresult nsMultiplexInputStreamConstructor(REFNSIID aIID, void** aResult) { + *aResult = nullptr; + + RefPtr<nsMultiplexInputStream> inst = new nsMultiplexInputStream(); + + return inst->QueryInterface(aIID, aResult); +} + +void nsMultiplexInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + MutexAutoLock lock(mLock); + bool serializeAsPipe = false; + SerializedComplexityInternal(aMaxSize, aSizeUsed, aPipes, aTransferables, + &serializeAsPipe); +} + +void nsMultiplexInputStream::SerializedComplexityInternal( + uint32_t aMaxSize, uint32_t* aSizeUsed, uint32_t* aPipes, + uint32_t* aTransferables, bool* aSerializeAsPipe) { + mLock.AssertCurrentThreadOwns(); + CheckedUint32 totalSizeUsed = 0; + CheckedUint32 totalPipes = 0; + CheckedUint32 totalTransferables = 0; + CheckedUint32 maxSize = aMaxSize; + + uint32_t streamCount = mStreams.Length(); + + for (uint32_t index = 0; index < streamCount; index++) { + uint32_t sizeUsed = 0; + uint32_t pipes = 0; + uint32_t transferables = 0; + InputStreamHelper::SerializedComplexity(mStreams[index].mOriginalStream, + maxSize.value(), &sizeUsed, &pipes, + &transferables); + + MOZ_ASSERT(maxSize.value() >= sizeUsed); + + maxSize -= sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(maxSize.isValid()); + totalSizeUsed += sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(totalSizeUsed.isValid()); + totalPipes += pipes; + MOZ_DIAGNOSTIC_ASSERT(totalPipes.isValid()); + totalTransferables += transferables; + MOZ_DIAGNOSTIC_ASSERT(totalTransferables.isValid()); + } + + // If the combination of all streams when serialized independently is + // sufficiently complex, we may choose to serialize it as a pipe to limit the + // complexity of the payload. + if (totalTransferables.value() == 0) { + // If there are no transferables within our serialization, and it would + // contain at least one pipe, serialize the entire payload as a pipe for + // simplicity. + *aSerializeAsPipe = totalSizeUsed.value() > 0 && totalPipes.value() > 0; + } else { + // Otherwise, we may want to still serialize in segments to take advantage + // of the efficiency of serializing transferables. We'll only serialize as a + // pipe if the total attachment count exceeds kMaxAttachmentThreshold. + static constexpr uint32_t kMaxAttachmentThreshold = 8; + CheckedUint32 totalAttachments = totalPipes + totalTransferables; + *aSerializeAsPipe = !totalAttachments.isValid() || + totalAttachments.value() > kMaxAttachmentThreshold; + } + + if (*aSerializeAsPipe) { + NS_WARNING( + nsPrintfCString("Choosing to serialize multiplex stream as a pipe " + "(would be %u bytes, %u pipes, %u transferables)", + totalSizeUsed.value(), totalPipes.value(), + totalTransferables.value()) + .get()); + *aSizeUsed = 0; + *aPipes = 1; + *aTransferables = 0; + } else { + *aSizeUsed = totalSizeUsed.value(); + *aPipes = totalPipes.value(); + *aTransferables = totalTransferables.value(); + } +} + +void nsMultiplexInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MutexAutoLock lock(mLock); + + // Check if we should serialize this stream as a pipe to reduce complexity. + uint32_t dummySizeUsed = 0, dummyPipes = 0, dummyTransferables = 0; + bool serializeAsPipe = false; + SerializedComplexityInternal(aMaxSize, &dummySizeUsed, &dummyPipes, + &dummyTransferables, &serializeAsPipe); + if (serializeAsPipe) { + *aSizeUsed = 0; + MutexAutoUnlock unlock(mLock); + InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + MultiplexInputStreamParams params; + + CheckedUint32 totalSizeUsed = 0; + CheckedUint32 maxSize = aMaxSize; + + uint32_t streamCount = mStreams.Length(); + if (streamCount) { + nsTArray<InputStreamParams>& streams = params.streams(); + + streams.SetCapacity(streamCount); + for (uint32_t index = 0; index < streamCount; index++) { + uint32_t sizeUsed = 0; + InputStreamHelper::SerializeInputStream(mStreams[index].mOriginalStream, + *streams.AppendElement(), + maxSize.value(), &sizeUsed); + + MOZ_ASSERT(maxSize.value() >= sizeUsed); + + maxSize -= sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(maxSize.isValid()); + + totalSizeUsed += sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(totalSizeUsed.isValid()); + } + } + + params.currentStream() = mCurrentStream; + params.status() = mStatus; + params.startedReadingCurrent() = mStartedReadingCurrent; + + aParams = std::move(params); + + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = totalSizeUsed.value(); +} + +bool nsMultiplexInputStream::Deserialize(const InputStreamParams& aParams) { + if (aParams.type() != InputStreamParams::TMultiplexInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const MultiplexInputStreamParams& params = + aParams.get_MultiplexInputStreamParams(); + + const nsTArray<InputStreamParams>& streams = params.streams(); + + uint32_t streamCount = streams.Length(); + for (uint32_t index = 0; index < streamCount; index++) { + nsCOMPtr<nsIInputStream> stream = + InputStreamHelper::DeserializeInputStream(streams[index]); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + if (NS_FAILED(AppendStream(stream))) { + NS_WARNING("AppendStream failed!"); + return false; + } + } + + MutexAutoLock lock(mLock); + mCurrentStream = params.currentStream(); + mStatus = params.status(); + mStartedReadingCurrent = params.startedReadingCurrent(); + + return true; +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetCloneable(bool* aCloneable) { + MutexAutoLock lock(mLock); + // XXXnsm Cloning a multiplex stream which has started reading is not + // permitted right now. + if (mCurrentStream > 0 || mStartedReadingCurrent) { + *aCloneable = false; + return NS_OK; + } + + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsCOMPtr<nsICloneableInputStream> cis = + do_QueryInterface(mStreams[i].mBufferedStream); + if (!cis || !cis->GetCloneable()) { + *aCloneable = false; + return NS_OK; + } + } + + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Clone(nsIInputStream** aClone) { + MutexAutoLock lock(mLock); + + // XXXnsm Cloning a multiplex stream which has started reading is not + // permitted right now. + if (mCurrentStream > 0 || mStartedReadingCurrent) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsMultiplexInputStream> clone = new nsMultiplexInputStream(); + + nsresult rv; + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsCOMPtr<nsICloneableInputStream> substream = + do_QueryInterface(mStreams[i].mBufferedStream); + if (NS_WARN_IF(!substream)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStream> clonedSubstream; + rv = substream->Clone(getter_AddRefs(clonedSubstream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = clone->AppendStream(clonedSubstream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + clone.forget(aClone); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Length(int64_t* aLength) { + MutexAutoLock lock(mLock); + + if (mCurrentStream > 0 || mStartedReadingCurrent) { + return NS_ERROR_NOT_AVAILABLE; + } + + CheckedInt64 length = 0; + nsresult retval = NS_OK; + + for (uint32_t i = 0, len = mStreams.Length(); i < len; ++i) { + nsCOMPtr<nsIInputStreamLength> substream = + do_QueryInterface(mStreams[i].mBufferedStream); + if (!substream) { + // Let's use available as fallback. + uint64_t streamAvail = 0; + nsresult rv = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mStatus = rv; + return mStatus; + } + + length += streamAvail; + if (!length.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + continue; + } + + int64_t size = 0; + nsresult rv = substream->Length(&size); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (rv == NS_ERROR_NOT_AVAILABLE) { + return rv; + } + + // If one stream blocks, we all block. + if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // We want to return WOULD_BLOCK if there is 1 stream that blocks. But want + // to see if there are other streams with length = -1. + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + retval = NS_BASE_STREAM_WOULD_BLOCK; + continue; + } + + // If one of the stream doesn't know the size, we all don't know the size. + if (size == -1) { + *aLength = -1; + return NS_OK; + } + + length += size; + if (!length.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + *aLength = length.value(); + return retval; +} + +class nsMultiplexInputStream::AsyncWaitLengthHelper final + : public nsIInputStreamLengthCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + AsyncWaitLengthHelper() + : mStreamNotified(false), mLength(0), mNegativeSize(false) {} + + bool AddStream(nsIAsyncInputStreamLength* aStream) { + return mPendingStreams.AppendElement(aStream, fallible); + } + + bool AddSize(int64_t aSize) { + MOZ_ASSERT(!mNegativeSize); + + mLength += aSize; + return mLength.isValid(); + } + + void NegativeSize() { + MOZ_ASSERT(!mNegativeSize); + mNegativeSize = true; + } + + nsresult Proceed(nsMultiplexInputStream* aParentStream, + nsIEventTarget* aEventTarget, + const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(!mStream); + + // If we don't need to wait, let's inform the callback immediately. + if (mPendingStreams.IsEmpty() || mNegativeSize) { + RefPtr<nsMultiplexInputStream> parentStream = aParentStream; + int64_t length = -1; + if (!mNegativeSize && mLength.isValid()) { + length = mLength.value(); + } + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "AsyncWaitLengthHelper", [parentStream, length]() { + MutexAutoLock lock(parentStream->GetLock()); + parentStream->AsyncWaitCompleted(length, lock); + }); + return aEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + } + + // Let's store the callback and the parent stream until we have + // notifications from the async length streams. + + mStream = aParentStream; + + // Let's activate all the pending streams. + for (nsIAsyncInputStreamLength* stream : mPendingStreams) { + nsresult rv = stream->AsyncLengthWait(this, aEventTarget); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; + } + + NS_IMETHOD + OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) override { + MutexAutoLock lock(mStream->GetLock()); + + MOZ_ASSERT(mPendingStreams.Contains(aStream)); + mPendingStreams.RemoveElement(aStream); + + // Already notified. + if (mStreamNotified) { + return NS_OK; + } + + if (aLength == -1) { + mNegativeSize = true; + } else { + mLength += aLength; + if (!mLength.isValid()) { + mNegativeSize = true; + } + } + + // We need to wait. + if (!mNegativeSize && !mPendingStreams.IsEmpty()) { + return NS_OK; + } + + // Let's notify the parent stream. + mStreamNotified = true; + mStream->AsyncWaitCompleted(mNegativeSize ? -1 : mLength.value(), lock); + return NS_OK; + } + + private: + ~AsyncWaitLengthHelper() = default; + + RefPtr<nsMultiplexInputStream> mStream; + bool mStreamNotified; + + CheckedInt64 mLength; + bool mNegativeSize; + + nsTArray<nsCOMPtr<nsIAsyncInputStreamLength>> mPendingStreams; +}; + +NS_IMPL_ISUPPORTS(nsMultiplexInputStream::AsyncWaitLengthHelper, + nsIInputStreamLengthCallback) + +NS_IMETHODIMP +nsMultiplexInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + if (NS_WARN_IF(!aEventTarget)) { + return NS_ERROR_NULL_POINTER; + } + + MutexAutoLock lock(mLock); + + if (mCurrentStream > 0 || mStartedReadingCurrent) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!aCallback) { + mAsyncWaitLengthCallback = nullptr; + return NS_OK; + } + + // We have a pending operation! Let's use this instead of creating a new one. + if (mAsyncWaitLengthHelper) { + mAsyncWaitLengthCallback = aCallback; + return NS_OK; + } + + RefPtr<AsyncWaitLengthHelper> helper = new AsyncWaitLengthHelper(); + + for (uint32_t i = 0, len = mStreams.Length(); i < len; ++i) { + nsCOMPtr<nsIAsyncInputStreamLength> asyncStream = + do_QueryInterface(mStreams[i].mBufferedStream); + if (asyncStream) { + if (NS_WARN_IF(!helper->AddStream(asyncStream))) { + return NS_ERROR_OUT_OF_MEMORY; + } + continue; + } + + nsCOMPtr<nsIInputStreamLength> stream = + do_QueryInterface(mStreams[i].mBufferedStream); + if (!stream) { + // Let's use available as fallback. + uint64_t streamAvail = 0; + nsresult rv = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mStatus = rv; + return mStatus; + } + + if (NS_WARN_IF(!helper->AddSize(streamAvail))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + continue; + } + + int64_t size = 0; + nsresult rv = stream->Length(&size); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, + "A nsILengthInutStream returns NS_BASE_STREAM_WOULD_BLOCK but " + "it doesn't implement nsIAsyncInputStreamLength."); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (size == -1) { + helper->NegativeSize(); + break; + } + + if (NS_WARN_IF(!helper->AddSize(size))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + nsresult rv = helper->Proceed(this, aEventTarget, lock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mAsyncWaitLengthHelper = helper; + mAsyncWaitLengthCallback = aCallback; + return NS_OK; +} + +void nsMultiplexInputStream::AsyncWaitCompleted( + int64_t aLength, const MutexAutoLock& aProofOfLock) { + mLock.AssertCurrentThreadOwns(); + + nsCOMPtr<nsIInputStreamLengthCallback> callback; + callback.swap(mAsyncWaitLengthCallback); + + mAsyncWaitLengthHelper = nullptr; + + // Already canceled. + if (!callback) { + return; + } + + MutexAutoUnlock unlock(mLock); + callback->OnInputStreamLengthReady(this, aLength); +} + +#define MAYBE_UPDATE_VALUE_REAL(x, y) \ + if (y) { \ + ++x; \ + } + +#define MAYBE_UPDATE_VALUE(x, y) \ + { \ + nsCOMPtr<y> substream = do_QueryInterface(aStream.mBufferedStream); \ + MAYBE_UPDATE_VALUE_REAL(x, substream) \ + } + +#define MAYBE_UPDATE_BOOL(x, y) \ + if (!x) { \ + nsCOMPtr<y> substream = do_QueryInterface(aStream.mBufferedStream); \ + if (substream) { \ + x = true; \ + } \ + } + +void nsMultiplexInputStream::UpdateQIMap(StreamData& aStream) { + auto length = mStreams.Length(); + + MAYBE_UPDATE_VALUE_REAL(mSeekableStreams, aStream.mSeekableStream) + mIsSeekableStream = (mSeekableStreams == length); + MAYBE_UPDATE_VALUE(mIPCSerializableStreams, nsIIPCSerializableInputStream) + mIsIPCSerializableStream = (mIPCSerializableStreams == length); + MAYBE_UPDATE_VALUE(mCloneableStreams, nsICloneableInputStream) + mIsCloneableStream = (mCloneableStreams == length); + // nsMultiplexInputStream is nsIAsyncInputStream if at least 1 of the + // substream implements that interface + if (!mIsAsyncInputStream && aStream.mAsyncStream) { + mIsAsyncInputStream = true; + } + MAYBE_UPDATE_BOOL(mIsInputStreamLength, nsIInputStreamLength) + MAYBE_UPDATE_BOOL(mIsAsyncInputStreamLength, nsIAsyncInputStreamLength) +} + +#undef MAYBE_UPDATE_VALUE +#undef MAYBE_UPDATE_VALUE_REAL +#undef MAYBE_UPDATE_BOOL + +bool nsMultiplexInputStream::IsSeekable() const { return mIsSeekableStream; } + +bool nsMultiplexInputStream::IsIPCSerializable() const { + return mIsIPCSerializableStream; +} + +bool nsMultiplexInputStream::IsCloneable() const { return mIsCloneableStream; } + +bool nsMultiplexInputStream::IsAsyncInputStream() const { + // nsMultiplexInputStream is nsIAsyncInputStream if at least 1 of the + // substream implements that interface. + return mIsAsyncInputStream; +} + +bool nsMultiplexInputStream::IsInputStreamLength() const { + return mIsInputStreamLength; +} + +bool nsMultiplexInputStream::IsAsyncInputStreamLength() const { + return mIsAsyncInputStreamLength; +} diff --git a/xpcom/io/nsMultiplexInputStream.h b/xpcom/io/nsMultiplexInputStream.h new file mode 100644 index 0000000000..9b84697c34 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.h @@ -0,0 +1,27 @@ +/* -*- 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/. */ + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#ifndef _nsMultiplexInputStream_h_ +#define _nsMultiplexInputStream_h_ + +#define NS_MULTIPLEXINPUTSTREAM_CONTRACTID \ + "@mozilla.org/io/multiplex-input-stream;1" +#define NS_MULTIPLEXINPUTSTREAM_CID \ + { /* 565e3a2c-1dd2-11b2-8da1-b4cef17e568d */ \ + 0x565e3a2c, 0x1dd2, 0x11b2, { \ + 0x8d, 0xa1, 0xb4, 0xce, 0xf1, 0x7e, 0x56, 0x8d \ + } \ + } + +extern nsresult nsMultiplexInputStreamConstructor(REFNSIID aIID, + void** aResult); + +#endif // _nsMultiplexInputStream_h_ diff --git a/xpcom/io/nsNativeCharsetUtils.cpp b/xpcom/io/nsNativeCharsetUtils.cpp new file mode 100644 index 0000000000..847ae3666c --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.cpp @@ -0,0 +1,98 @@ +/* -*- 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/. */ + +//----------------------------------------------------------------------------- +// Non-Windows +//----------------------------------------------------------------------------- +#ifndef XP_WIN + +# include "nsAString.h" +# include "nsReadableUtils.h" +# include "nsString.h" + +nsresult NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) { + CopyUTF8toUTF16(aInput, aOutput); + return NS_OK; +} + +nsresult NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) { + CopyUTF16toUTF8(aInput, aOutput); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// XP_WIN +//----------------------------------------------------------------------------- +#else + +# include <windows.h> +# include "nsString.h" +# include "nsAString.h" +# include "nsReadableUtils.h" + +using namespace mozilla; + +nsresult NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) { + uint32_t inputLen = aInput.Length(); + + nsACString::const_iterator iter; + aInput.BeginReading(iter); + + const char* buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + int n = ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, nullptr, 0); + if (n > 0) { + resultLen += n; + } + + // allocate sufficient space + if (!aOutput.SetLength(resultLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (resultLen > 0) { + char16ptr_t result = aOutput.BeginWriting(); + ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, result, resultLen); + } + return NS_OK; +} + +nsresult NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) { + uint32_t inputLen = aInput.Length(); + + nsAString::const_iterator iter; + aInput.BeginReading(iter); + + char16ptr_t buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + + int n = ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, nullptr, 0, nullptr, + nullptr); + if (n > 0) { + resultLen += n; + } + + // allocate sufficient space + if (!aOutput.SetLength(resultLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (resultLen > 0) { + char* result = aOutput.BeginWriting(); + + // default "defaultChar" is '?', which is an illegal character on windows + // file system. That will cause file uncreatable. Change it to '_' + const char defaultChar = '_'; + + ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, result, resultLen, + &defaultChar, nullptr); + } + return NS_OK; +} + +#endif diff --git a/xpcom/io/nsNativeCharsetUtils.h b/xpcom/io/nsNativeCharsetUtils.h new file mode 100644 index 0000000000..dcbf60238f --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.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 nsNativeCharsetUtils_h__ +#define nsNativeCharsetUtils_h__ + +/*****************************************************************************\ + * * + * **** NOTICE **** * + * * + * *** THESE ARE NOT GENERAL PURPOSE CONVERTERS *** * + * * + * NS_CopyNativeToUnicode / NS_CopyUnicodeToNative should only be used * + * for converting *FILENAMES* between bytes and UTF-16. They are not * + * designed or tested for general encoding converter use. * + * * + * On Windows, these functions convert to and from the system's legacy * + * code page, which cannot represent all of Unicode. Elsewhere, these * + * convert to and from UTF-8. * + * * +\*****************************************************************************/ + +#include "nsError.h" +#include "nsStringFwd.h" + +/** + * thread-safe conversion routines that do not depend on uconv libraries. + */ +nsresult NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput); +nsresult NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput); + +/* + * This function indicates whether the character encoding used in the file + * system (more exactly what's used for |GetNativeFoo| and |SetNativeFoo| + * of |nsIFile|) is UTF-8 or not. Knowing that helps us avoid an + * unncessary encoding conversion in some cases. For instance, to get the leaf + * name in UTF-8 out of nsIFile, we can just use |GetNativeLeafName| rather + * than using |GetLeafName| and converting the result to UTF-8 if the file + * system encoding is UTF-8. + */ +inline constexpr bool NS_IsNativeUTF8() { +#ifdef XP_WIN + return false; +#else + return true; +#endif +} + +#endif // nsNativeCharsetUtils_h__ diff --git a/xpcom/io/nsPipe.h b/xpcom/io/nsPipe.h new file mode 100644 index 0000000000..c9e8b1dfd4 --- /dev/null +++ b/xpcom/io/nsPipe.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 nsPipe_h__ +#define nsPipe_h__ + +#define NS_PIPE_CONTRACTID "@mozilla.org/pipe;1" +#define NS_PIPE_CID \ + { /* e4a0ee4e-0775-457b-9118-b3ae97a7c758 */ \ + 0xe4a0ee4e, 0x0775, 0x457b, { \ + 0x91, 0x18, 0xb3, 0xae, 0x97, 0xa7, 0xc7, 0x58 \ + } \ + } + +// Generic factory constructor for the nsPipe class +nsresult nsPipeConstructor(REFNSIID iid, void** result); + +#endif // !defined(nsPipe_h__) diff --git a/xpcom/io/nsPipe3.cpp b/xpcom/io/nsPipe3.cpp new file mode 100644 index 0000000000..3d7486e673 --- /dev/null +++ b/xpcom/io/nsPipe3.cpp @@ -0,0 +1,1884 @@ +/* -*- 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 <algorithm> +#include "mozilla/Attributes.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsIBufferedStreams.h" +#include "nsICloneableInputStream.h" +#include "nsIPipe.h" +#include "nsIEventTarget.h" +#include "nsITellableStream.h" +#include "mozilla/RefPtr.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "mozilla/Logging.h" +#include "nsIClassInfoImpl.h" +#include "nsAlgorithm.h" +#include "nsPipe.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIInputStreamPriority.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +#ifdef LOG +# undef LOG +#endif +// +// set MOZ_LOG=nsPipe:5 +// +static LazyLogModule sPipeLog("nsPipe"); +#define LOG(args) MOZ_LOG(sPipeLog, mozilla::LogLevel::Debug, args) + +#define DEFAULT_SEGMENT_SIZE 4096 +#define DEFAULT_SEGMENT_COUNT 16 + +class nsPipe; +class nsPipeEvents; +class nsPipeInputStream; +class nsPipeOutputStream; +class AutoReadSegment; + +namespace { + +enum MonitorAction { DoNotNotifyMonitor, NotifyMonitor }; + +enum SegmentChangeResult { SegmentNotChanged, SegmentAdvanceBufferRead }; + +} // namespace + +//----------------------------------------------------------------------------- + +class CallbackHolder { + public: + CallbackHolder() = default; + MOZ_IMPLICIT CallbackHolder(std::nullptr_t) {} + + CallbackHolder(nsIAsyncInputStream* aStream, + nsIInputStreamCallback* aCallback, uint32_t aFlags, + nsIEventTarget* aEventTarget) + : mRunnable(aCallback ? NS_NewCancelableRunnableFunction( + "nsPipeInputStream AsyncWait Callback", + [stream = nsCOMPtr{aStream}, + callback = nsCOMPtr{aCallback}]() { + callback->OnInputStreamReady(stream); + }) + : nullptr), + mEventTarget(aEventTarget), + mFlags(aFlags) {} + + CallbackHolder(nsIAsyncOutputStream* aStream, + nsIOutputStreamCallback* aCallback, uint32_t aFlags, + nsIEventTarget* aEventTarget) + : mRunnable(aCallback ? NS_NewCancelableRunnableFunction( + "nsPipeOutputStream AsyncWait Callback", + [stream = nsCOMPtr{aStream}, + callback = nsCOMPtr{aCallback}]() { + callback->OnOutputStreamReady(stream); + }) + : nullptr), + mEventTarget(aEventTarget), + mFlags(aFlags) {} + + CallbackHolder(const CallbackHolder&) = delete; + CallbackHolder(CallbackHolder&&) = default; + CallbackHolder& operator=(const CallbackHolder&) = delete; + CallbackHolder& operator=(CallbackHolder&&) = default; + + CallbackHolder& operator=(std::nullptr_t) { + mRunnable = nullptr; + mEventTarget = nullptr; + mFlags = 0; + return *this; + } + + MOZ_IMPLICIT operator bool() const { return mRunnable; } + + uint32_t Flags() const { + MOZ_ASSERT(mRunnable, "Should only be called when a callback is present"); + return mFlags; + } + + void Notify() { + nsCOMPtr<nsIRunnable> runnable = mRunnable.forget(); + nsCOMPtr<nsIEventTarget> eventTarget = mEventTarget.forget(); + if (runnable) { + if (eventTarget) { + eventTarget->Dispatch(runnable.forget()); + } else { + runnable->Run(); + } + } + } + + private: + nsCOMPtr<nsIRunnable> mRunnable; + nsCOMPtr<nsIEventTarget> mEventTarget; + uint32_t mFlags = 0; +}; + +//----------------------------------------------------------------------------- + +// this class is used to delay notifications until the end of a particular +// scope. it helps avoid the complexity of issuing callbacks while inside +// a critical section. +class nsPipeEvents { + public: + nsPipeEvents() = default; + ~nsPipeEvents(); + + inline void NotifyReady(CallbackHolder aCallback) { + mCallbacks.AppendElement(std::move(aCallback)); + } + + private: + nsTArray<CallbackHolder> mCallbacks; +}; + +//----------------------------------------------------------------------------- + +// This class is used to maintain input stream state. Its broken out from the +// nsPipeInputStream class because generally the nsPipe should be modifying +// this state and not the input stream itself. +struct nsPipeReadState { + nsPipeReadState() + : mReadCursor(nullptr), + mReadLimit(nullptr), + mSegment(0), + mAvailable(0), + mActiveRead(false), + mNeedDrain(false) {} + + // All members of this type are guarded by the pipe monitor, however it cannot + // be named from this type, so the less-reliable MOZ_GUARDED_VAR is used + // instead. In the future it would be nice to avoid this, especially as + // MOZ_GUARDED_VAR is deprecated. + char* mReadCursor MOZ_GUARDED_VAR; + char* mReadLimit MOZ_GUARDED_VAR; + int32_t mSegment MOZ_GUARDED_VAR; + uint32_t mAvailable MOZ_GUARDED_VAR; + + // This flag is managed using the AutoReadSegment RAII stack class. + bool mActiveRead MOZ_GUARDED_VAR; + + // Set to indicate that the input stream has closed and should be drained, + // but that drain has been delayed due to an active read. When the read + // completes, this flag indicate the drain should then be performed. + bool mNeedDrain MOZ_GUARDED_VAR; +}; + +//----------------------------------------------------------------------------- + +// an input end of a pipe (maintained as a list of refs within the pipe) +class nsPipeInputStream final : public nsIAsyncInputStream, + public nsITellableStream, + public nsISearchableInputStream, + public nsICloneableInputStream, + public nsIClassInfo, + public nsIBufferedInputStream, + public nsIInputStreamPriority { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSISEARCHABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSICLASSINFO + NS_DECL_NSIBUFFEREDINPUTSTREAM + NS_DECL_NSIINPUTSTREAMPRIORITY + + explicit nsPipeInputStream(nsPipe* aPipe) + : mPipe(aPipe), + mLogicalOffset(0), + mInputStatus(NS_OK), + mBlocking(true), + mBlocked(false), + mPriority(nsIRunnablePriority::PRIORITY_NORMAL) {} + + nsPipeInputStream(const nsPipeInputStream& aOther) + : mPipe(aOther.mPipe), + mLogicalOffset(aOther.mLogicalOffset), + mInputStatus(aOther.mInputStatus), + mBlocking(aOther.mBlocking), + mBlocked(false), + mReadState(aOther.mReadState), + mPriority(nsIRunnablePriority::PRIORITY_NORMAL) {} + + void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } + + uint32_t Available() MOZ_REQUIRES(Monitor()); + + // synchronously wait for the pipe to become readable. + nsresult Wait(); + + // These two don't acquire the monitor themselves. Instead they + // expect their caller to have done so and to pass the monitor as + // evidence. + MonitorAction OnInputReadable(uint32_t aBytesWritten, nsPipeEvents&, + const ReentrantMonitorAutoEnter& ev) + MOZ_REQUIRES(Monitor()); + MonitorAction OnInputException(nsresult, nsPipeEvents&, + const ReentrantMonitorAutoEnter& ev) + MOZ_REQUIRES(Monitor()); + + nsPipeReadState& ReadState() { return mReadState; } + + const nsPipeReadState& ReadState() const { return mReadState; } + + nsresult Status() const; + + // A version of Status() that doesn't acquire the monitor. + nsresult Status(const ReentrantMonitorAutoEnter& ev) const + MOZ_REQUIRES(Monitor()); + + // The status of this input stream, ignoring the status of the underlying + // monitor. If this status is errored, the input stream has either already + // been removed from the pipe, or will be removed from the pipe shortly. + nsresult InputStatus(const ReentrantMonitorAutoEnter&) const + MOZ_REQUIRES(Monitor()) { + return mInputStatus; + } + + ReentrantMonitor& Monitor() const; + + private: + virtual ~nsPipeInputStream(); + + RefPtr<nsPipe> mPipe; + + int64_t mLogicalOffset; + // Individual input streams can be closed without effecting the rest of the + // pipe. So track individual input stream status separately. |mInputStatus| + // is protected by |mPipe->mReentrantMonitor|. + nsresult mInputStatus MOZ_GUARDED_BY(Monitor()); + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked MOZ_GUARDED_BY(Monitor()); + CallbackHolder mCallback MOZ_GUARDED_BY(Monitor()); + + // requires pipe's monitor to access members; usually treat as an opaque token + // to pass to nsPipe + nsPipeReadState mReadState; + Atomic<uint32_t, Relaxed> mPriority; +}; + +//----------------------------------------------------------------------------- + +// the output end of a pipe (allocated as a member of the pipe). +class nsPipeOutputStream : public nsIAsyncOutputStream, public nsIClassInfo { + public: + // since this class will be allocated as a member of the pipe, we do not + // need our own ref count. instead, we share the lifetime (the ref count) + // of the entire pipe. this macro is just convenience since it does not + // declare a mRefCount variable; however, don't let the name fool you... + // we are not inheriting from nsPipe ;-) + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSICLASSINFO + + explicit nsPipeOutputStream(nsPipe* aPipe) + : mPipe(aPipe), + mWriterRefCnt(0), + mLogicalOffset(0), + mBlocking(true), + mBlocked(false), + mWritable(true) {} + + void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } + void SetWritable(bool aWritable) MOZ_REQUIRES(Monitor()) { + mWritable = aWritable; + } + + // synchronously wait for the pipe to become writable. + nsresult Wait(); + + MonitorAction OnOutputWritable(nsPipeEvents&) MOZ_REQUIRES(Monitor()); + MonitorAction OnOutputException(nsresult, nsPipeEvents&) + MOZ_REQUIRES(Monitor()); + + ReentrantMonitor& Monitor() const; + + private: + nsPipe* mPipe; + + // separate refcnt so that we know when to close the producer + ThreadSafeAutoRefCnt mWriterRefCnt; + int64_t mLogicalOffset; + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked MOZ_GUARDED_BY(Monitor()); + bool mWritable MOZ_GUARDED_BY(Monitor()); + CallbackHolder mCallback MOZ_GUARDED_BY(Monitor()); +}; + +//----------------------------------------------------------------------------- + +class nsPipe final { + public: + friend class nsPipeInputStream; + friend class nsPipeOutputStream; + friend class AutoReadSegment; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsPipe) + + // public constructor + friend void NS_NewPipe2(nsIAsyncInputStream**, nsIAsyncOutputStream**, bool, + bool, uint32_t, uint32_t); + + private: + nsPipe(uint32_t aSegmentSize, uint32_t aSegmentCount); + ~nsPipe(); + + // + // Methods below may only be called while inside the pipe's monitor. Some + // of these methods require passing a ReentrantMonitorAutoEnter to prove the + // monitor is held. + // + + void PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, + char*& aCursor, char*& aLimit) + MOZ_REQUIRES(mReentrantMonitor); + SegmentChangeResult AdvanceReadSegment(nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) + MOZ_REQUIRES(mReentrantMonitor); + bool ReadSegmentBeingWritten(nsPipeReadState& aReadState) + MOZ_REQUIRES(mReentrantMonitor); + uint32_t CountSegmentReferences(int32_t aSegment) + MOZ_REQUIRES(mReentrantMonitor); + void SetAllNullReadCursors() MOZ_REQUIRES(mReentrantMonitor); + bool AllReadCursorsMatchWriteCursor() MOZ_REQUIRES(mReentrantMonitor); + void RollBackAllReadCursors(char* aWriteCursor) + MOZ_REQUIRES(mReentrantMonitor); + void UpdateAllReadCursors(char* aWriteCursor) MOZ_REQUIRES(mReentrantMonitor); + void ValidateAllReadCursors() MOZ_REQUIRES(mReentrantMonitor); + uint32_t GetBufferSegmentCount(const nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) const + MOZ_REQUIRES(mReentrantMonitor); + bool IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const + MOZ_REQUIRES(mReentrantMonitor); + + // + // methods below may be called while outside the pipe's monitor + // + + void DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents); + nsresult GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen); + void AdvanceWriteCursor(uint32_t aCount); + + void OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason); + void OnPipeException(nsresult aReason, bool aOutputOnly = false); + + nsresult CloneInputStream(nsPipeInputStream* aOriginal, + nsIInputStream** aCloneOut); + + // methods below should only be called by AutoReadSegment + nsresult GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment, + uint32_t& aLength); + void ReleaseReadSegment(nsPipeReadState& aReadState, nsPipeEvents& aEvents); + void AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aCount); + + // We can't inherit from both nsIInputStream and nsIOutputStream + // because they collide on their Close method. Consequently we nest their + // implementations to avoid the extra object allocation. + nsPipeOutputStream mOutput; + + // Since the input stream can be cloned, we may have more than one. Use + // a weak reference as the streams will clear their entry here in their + // destructor. Using a strong reference would create a reference cycle. + // Only usable while mReentrantMonitor is locked. + nsTArray<nsPipeInputStream*> mInputList MOZ_GUARDED_BY(mReentrantMonitor); + + ReentrantMonitor mReentrantMonitor; + nsSegmentedBuffer mBuffer MOZ_GUARDED_BY(mReentrantMonitor); + + // The maximum number of segments to allow to be buffered in advance + // of the fastest reader. This is collection of segments is called + // the "advance buffer". + uint32_t mMaxAdvanceBufferSegmentCount MOZ_GUARDED_BY(mReentrantMonitor); + + int32_t mWriteSegment MOZ_GUARDED_BY(mReentrantMonitor); + char* mWriteCursor MOZ_GUARDED_BY(mReentrantMonitor); + char* mWriteLimit MOZ_GUARDED_BY(mReentrantMonitor); + + // |mStatus| is protected by |mReentrantMonitor|. + nsresult mStatus MOZ_GUARDED_BY(mReentrantMonitor); +}; + +//----------------------------------------------------------------------------- + +// Declarations of Monitor() methods on the streams. +// +// These must be placed early to provide MOZ_RETURN_CAPABILITY annotations for +// the thread-safety analysis. This couldn't be done at the declaration due to +// nsPipe not yet being defined. + +ReentrantMonitor& nsPipeOutputStream::Monitor() const + MOZ_RETURN_CAPABILITY(mPipe->mReentrantMonitor) { + return mPipe->mReentrantMonitor; +} + +ReentrantMonitor& nsPipeInputStream::Monitor() const + MOZ_RETURN_CAPABILITY(mPipe->mReentrantMonitor) { + return mPipe->mReentrantMonitor; +} + +//----------------------------------------------------------------------------- + +// RAII class representing an active read segment. When it goes out of scope +// it automatically updates the read cursor and releases the read segment. +class MOZ_STACK_CLASS AutoReadSegment final { + public: + AutoReadSegment(nsPipe* aPipe, nsPipeReadState& aReadState, + uint32_t aMaxLength) + : mPipe(aPipe), + mReadState(aReadState), + mStatus(NS_ERROR_FAILURE), + mSegment(nullptr), + mLength(0), + mOffset(0) { + MOZ_DIAGNOSTIC_ASSERT(mPipe); + MOZ_DIAGNOSTIC_ASSERT(!mReadState.mActiveRead); + mStatus = mPipe->GetReadSegment(mReadState, mSegment, mLength); + if (NS_SUCCEEDED(mStatus)) { + MOZ_DIAGNOSTIC_ASSERT(mReadState.mActiveRead); + MOZ_DIAGNOSTIC_ASSERT(mSegment); + mLength = std::min(mLength, aMaxLength); + MOZ_DIAGNOSTIC_ASSERT(mLength); + } + } + + ~AutoReadSegment() { + if (NS_SUCCEEDED(mStatus)) { + if (mOffset) { + mPipe->AdvanceReadCursor(mReadState, mOffset); + } else { + nsPipeEvents events; + mPipe->ReleaseReadSegment(mReadState, events); + } + } + MOZ_DIAGNOSTIC_ASSERT(!mReadState.mActiveRead); + } + + nsresult Status() const { return mStatus; } + + const char* Data() const { + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_DIAGNOSTIC_ASSERT(mSegment); + return mSegment + mOffset; + } + + uint32_t Length() const { + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_DIAGNOSTIC_ASSERT(mLength >= mOffset); + return mLength - mOffset; + } + + void Advance(uint32_t aCount) { + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_DIAGNOSTIC_ASSERT(aCount <= (mLength - mOffset)); + mOffset += aCount; + } + + nsPipeReadState& ReadState() const { return mReadState; } + + private: + // guaranteed to remain alive due to limited stack lifetime of AutoReadSegment + nsPipe* mPipe; + nsPipeReadState& mReadState; + nsresult mStatus; + const char* mSegment; + uint32_t mLength; + uint32_t mOffset; +}; + +// +// NOTES on buffer architecture: +// +// +-----------------+ - - mBuffer.GetSegment(0) +// | | +// + - - - - - - - - + - - nsPipeReadState.mReadCursor +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ - - nsPipeReadState.mReadLimit +// | +// +-----------------+ +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ +// | +// +-----------------+ - - mBuffer.GetSegment(mWriteSegment) +// |/////////////////| +// |/////////////////| +// |/////////////////| +// + - - - - - - - - + - - mWriteCursor +// | | +// | | +// +-----------------+ - - mWriteLimit +// +// (shaded region contains data) +// +// NOTE: Each input stream produced by the nsPipe contains its own, separate +// nsPipeReadState. This means there are multiple mReadCursor and +// mReadLimit values in play. The pipe cannot discard old data until +// all mReadCursors have moved beyond that point in the stream. +// +// Likewise, each input stream reader will have it's own amount of +// buffered data. The pipe size threshold, however, is only applied +// to the input stream that is being read fastest. We call this +// the "advance buffer" in that its in advance of all readers. We +// allow slower input streams to buffer more data so that we don't +// stall processing of the faster input stream. +// +// NOTE: on some systems (notably OS/2), the heap allocator uses an arena for +// small allocations (e.g., 64 byte allocations). this means that buffers may +// be allocated back-to-back. in the diagram above, for example, mReadLimit +// would actually be pointing at the beginning of the next segment. when +// making changes to this file, please keep this fact in mind. +// + +//----------------------------------------------------------------------------- +// nsPipe methods: +//----------------------------------------------------------------------------- + +nsPipe::nsPipe(uint32_t aSegmentSize, uint32_t aSegmentCount) + : mOutput(this), + mReentrantMonitor("nsPipe.mReentrantMonitor"), + // protect against overflow + mMaxAdvanceBufferSegmentCount( + std::min(aSegmentCount, UINT32_MAX / aSegmentSize)), + mWriteSegment(-1), + mWriteCursor(nullptr), + mWriteLimit(nullptr), + mStatus(NS_OK) { + // The internal buffer is always "infinite" so that we can allow + // the size to expand when cloned streams are read at different + // rates. We enforce a limit on how much data can be buffered + // ahead of the fastest reader in GetWriteSegment(). + MOZ_ALWAYS_SUCCEEDS(mBuffer.Init(aSegmentSize)); +} + +nsPipe::~nsPipe() = default; + +void nsPipe::PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, + char*& aCursor, char*& aLimit) { + if (aIndex == 0) { + MOZ_DIAGNOSTIC_ASSERT(!aReadState.mReadCursor || mBuffer.GetSegmentCount()); + aCursor = aReadState.mReadCursor; + aLimit = aReadState.mReadLimit; + } else { + uint32_t absoluteIndex = aReadState.mSegment + aIndex; + uint32_t numSegments = mBuffer.GetSegmentCount(); + if (absoluteIndex >= numSegments) { + aCursor = aLimit = nullptr; + } else { + aCursor = mBuffer.GetSegment(absoluteIndex); + if (mWriteSegment == (int32_t)absoluteIndex) { + aLimit = mWriteCursor; + } else { + aLimit = aCursor + mBuffer.GetSegmentSize(); + } + } + } +} + +nsresult nsPipe::GetReadSegment(nsPipeReadState& aReadState, + const char*& aSegment, uint32_t& aLength) { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (aReadState.mReadCursor == aReadState.mReadLimit) { + return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_WOULD_BLOCK; + } + + // The input stream locks the pipe while getting the buffer to read from, + // but then unlocks while actual data copying is taking place. In + // order to avoid deleting the buffer out from under this lockless read + // set a flag to indicate a read is active. This flag is only modified + // while the lock is held. + MOZ_DIAGNOSTIC_ASSERT(!aReadState.mActiveRead); + aReadState.mActiveRead = true; + + aSegment = aReadState.mReadCursor; + aLength = aReadState.mReadLimit - aReadState.mReadCursor; + MOZ_DIAGNOSTIC_ASSERT(aLength <= aReadState.mAvailable); + + return NS_OK; +} + +void nsPipe::ReleaseReadSegment(nsPipeReadState& aReadState, + nsPipeEvents& aEvents) { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + MOZ_DIAGNOSTIC_ASSERT(aReadState.mActiveRead); + aReadState.mActiveRead = false; + + // When a read completes and releases the mActiveRead flag, we may have + // blocked a drain from completing. This occurs when the input stream is + // closed during the read. In these cases, we need to complete the drain as + // soon as the active read completes. + if (aReadState.mNeedDrain) { + aReadState.mNeedDrain = false; + DrainInputStream(aReadState, aEvents); + } +} + +void nsPipe::AdvanceReadCursor(nsPipeReadState& aReadState, + uint32_t aBytesRead) { + MOZ_DIAGNOSTIC_ASSERT(aBytesRead > 0); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("III advancing read cursor by %u\n", aBytesRead)); + MOZ_DIAGNOSTIC_ASSERT(aBytesRead <= mBuffer.GetSegmentSize()); + + aReadState.mReadCursor += aBytesRead; + MOZ_DIAGNOSTIC_ASSERT(aReadState.mReadCursor <= aReadState.mReadLimit); + + MOZ_DIAGNOSTIC_ASSERT(aReadState.mAvailable >= aBytesRead); + aReadState.mAvailable -= aBytesRead; + + // Check to see if we're at the end of the available read data. If we + // are, and this segment is not still being written, then we can possibly + // free up the segment. + if (aReadState.mReadCursor == aReadState.mReadLimit && + !ReadSegmentBeingWritten(aReadState)) { + // Advance the segment position. If we have read any segments from the + // advance buffer then we can potentially notify blocked writers. + mOutput.Monitor().AssertCurrentThreadIn(); + if (AdvanceReadSegment(aReadState, mon) == SegmentAdvanceBufferRead && + mOutput.OnOutputWritable(events) == NotifyMonitor) { + mon.NotifyAll(); + } + } + + ReleaseReadSegment(aReadState, events); + } +} + +SegmentChangeResult nsPipe::AdvanceReadSegment( + nsPipeReadState& aReadState, const ReentrantMonitorAutoEnter& ev) { + // Calculate how many segments are buffered for this stream to start. + uint32_t startBufferSegments = GetBufferSegmentCount(aReadState, ev); + + int32_t currentSegment = aReadState.mSegment; + + // Move to the next segment to read + aReadState.mSegment += 1; + + // If this was the last reference to the first segment, then remove it. + if (currentSegment == 0 && CountSegmentReferences(currentSegment) == 0) { + // shift write and read segment index (-1 indicates an empty buffer). + mWriteSegment -= 1; + + // Directly modify the current read state. If the associated input + // stream is closed simultaneous with reading, then it may not be + // in the mInputList any more. + aReadState.mSegment -= 1; + + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // Skip the current read state structure since we modify it manually + // before entering this loop. + if (&mInputList[i]->ReadState() == &aReadState) { + continue; + } + mInputList[i]->ReadState().mSegment -= 1; + } + + // done with this segment + mBuffer.DeleteFirstSegment(); + LOG(("III deleting first segment\n")); + } + + if (mWriteSegment < aReadState.mSegment) { + // read cursor has hit the end of written data, so reset it + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment == (aReadState.mSegment - 1)); + aReadState.mReadCursor = nullptr; + aReadState.mReadLimit = nullptr; + // also, the buffer is completely empty, so reset the write cursor + if (mWriteSegment == -1) { + mWriteCursor = nullptr; + mWriteLimit = nullptr; + } + } else { + // advance read cursor and limit to next buffer segment + aReadState.mReadCursor = mBuffer.GetSegment(aReadState.mSegment); + if (mWriteSegment == aReadState.mSegment) { + aReadState.mReadLimit = mWriteCursor; + } else { + aReadState.mReadLimit = aReadState.mReadCursor + mBuffer.GetSegmentSize(); + } + } + + // Calculate how many segments are buffered for the stream after + // reading. + uint32_t endBufferSegments = GetBufferSegmentCount(aReadState, ev); + + // If the stream has read a segment out of the set of advanced buffer + // segments, then the writer may advance. + if (startBufferSegments >= mMaxAdvanceBufferSegmentCount && + endBufferSegments < mMaxAdvanceBufferSegmentCount) { + return SegmentAdvanceBufferRead; + } + + // Otherwise there are no significant changes to the segment structure. + return SegmentNotChanged; +} + +void nsPipe::DrainInputStream(nsPipeReadState& aReadState, + nsPipeEvents& aEvents) { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // If a segment is actively being read in ReadSegments() for this input + // stream, then we cannot drain the stream. This can happen because + // ReadSegments() does not hold the lock while copying from the buffer. + // If we detect this condition, simply note that we need a drain once + // the read completes and return immediately. + if (aReadState.mActiveRead) { + MOZ_DIAGNOSTIC_ASSERT(!aReadState.mNeedDrain); + aReadState.mNeedDrain = true; + return; + } + + while (mWriteSegment >= aReadState.mSegment) { + // If the last segment to free is still being written to, we're done + // draining. We can't free any more. + if (ReadSegmentBeingWritten(aReadState)) { + break; + } + + // Don't bother checking if this results in an advance buffer segment + // read. Since we are draining the entire stream we will read an + // advance buffer segment no matter what. + AdvanceReadSegment(aReadState, mon); + } + + // Force the stream into an empty state. Make sure mAvailable, mCursor, and + // mReadLimit are consistent with one another. + aReadState.mAvailable = 0; + aReadState.mReadCursor = nullptr; + aReadState.mReadLimit = nullptr; + + // Remove the input stream from the pipe's list of streams. This will + // prevent the pipe from holding the stream alive or trying to update + // its read state any further. + DebugOnly<uint32_t> numRemoved = 0; + mInputList.RemoveElementsBy([&](nsPipeInputStream* aEntry) { + bool result = &aReadState == &aEntry->ReadState(); + numRemoved += result ? 1 : 0; + return result; + }); + MOZ_ASSERT(numRemoved == 1); + + // If we have read any segments from the advance buffer then we can + // potentially notify blocked writers. + mOutput.Monitor().AssertCurrentThreadIn(); + if (!IsAdvanceBufferFull(mon) && + mOutput.OnOutputWritable(aEvents) == NotifyMonitor) { + mon.NotifyAll(); + } +} + +bool nsPipe::ReadSegmentBeingWritten(nsPipeReadState& aReadState) { + mReentrantMonitor.AssertCurrentThreadIn(); + bool beingWritten = + mWriteSegment == aReadState.mSegment && mWriteLimit > mWriteCursor; + MOZ_DIAGNOSTIC_ASSERT(!beingWritten || aReadState.mReadLimit == mWriteCursor); + return beingWritten; +} + +nsresult nsPipe::GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen) { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (NS_FAILED(mStatus)) { + return mStatus; + } + + // write cursor and limit may both be null indicating an empty buffer. + if (mWriteCursor == mWriteLimit) { + // The pipe is full if we have hit our limit on advance data buffering. + // This means the fastest reader is still reading slower than data is + // being written into the pipe. + if (IsAdvanceBufferFull(mon)) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + // The nsSegmentedBuffer is configured to be "infinite", so this + // should never return nullptr here. + char* seg = mBuffer.AppendNewSegment(); + if (!seg) { + return NS_ERROR_OUT_OF_MEMORY; + } + + LOG(("OOO appended new segment\n")); + mWriteCursor = seg; + mWriteLimit = mWriteCursor + mBuffer.GetSegmentSize(); + ++mWriteSegment; + } + + // make sure read cursor is initialized + SetAllNullReadCursors(); + + // check to see if we can roll-back our read and write cursors to the + // beginning of the current/first segment. this is purely an optimization. + if (mWriteSegment == 0 && AllReadCursorsMatchWriteCursor()) { + char* head = mBuffer.GetSegment(0); + LOG(("OOO rolling back write cursor %" PRId64 " bytes\n", + static_cast<int64_t>(mWriteCursor - head))); + RollBackAllReadCursors(head); + mWriteCursor = head; + } + + aSegment = mWriteCursor; + aSegmentLen = mWriteLimit - mWriteCursor; + return NS_OK; +} + +void nsPipe::AdvanceWriteCursor(uint32_t aBytesWritten) { + MOZ_DIAGNOSTIC_ASSERT(aBytesWritten > 0); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("OOO advancing write cursor by %u\n", aBytesWritten)); + + char* newWriteCursor = mWriteCursor + aBytesWritten; + MOZ_DIAGNOSTIC_ASSERT(newWriteCursor <= mWriteLimit); + + // update read limit if reading in the same segment + UpdateAllReadCursors(newWriteCursor); + + mWriteCursor = newWriteCursor; + + ValidateAllReadCursors(); + + // update the writable flag on the output stream + if (mWriteCursor == mWriteLimit) { + mOutput.Monitor().AssertCurrentThreadIn(); + mOutput.SetWritable(!IsAdvanceBufferFull(mon)); + } + + // notify input stream that pipe now contains additional data + bool needNotify = false; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + mInputList[i]->Monitor().AssertCurrentThreadIn(); + if (mInputList[i]->OnInputReadable(aBytesWritten, events, mon) == + NotifyMonitor) { + needNotify = true; + } + } + + if (needNotify) { + mon.NotifyAll(); + } + } +} + +void nsPipe::OnInputStreamException(nsPipeInputStream* aStream, + nsresult aReason) { + MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // Its possible to re-enter this method when we call OnPipeException() or + // OnInputExection() below. If there is a caller stuck in our synchronous + // Wait() method, then they will get woken up with a failure code which + // re-enters this method. Therefore, gracefully handle unknown streams + // here. + + // If we only have one stream open and it is the given stream, then shut + // down the entire pipe. + if (mInputList.Length() == 1) { + if (mInputList[0] == aStream) { + OnPipeException(aReason); + } + return; + } + + // Otherwise just close the particular stream that hit an exception. + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (mInputList[i] != aStream) { + continue; + } + + mInputList[i]->Monitor().AssertCurrentThreadIn(); + MonitorAction action = + mInputList[i]->OnInputException(aReason, events, mon); + + // Notify after element is removed in case we re-enter as a result. + if (action == NotifyMonitor) { + mon.NotifyAll(); + } + + return; + } + } +} + +void nsPipe::OnPipeException(nsresult aReason, bool aOutputOnly) { + LOG(("PPP nsPipe::OnPipeException [reason=%" PRIx32 " output-only=%d]\n", + static_cast<uint32_t>(aReason), aOutputOnly)); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // if we've already hit an exception, then ignore this one. + if (NS_FAILED(mStatus)) { + return; + } + + mStatus = aReason; + + bool needNotify = false; + + // OnInputException() can drain the stream and remove it from + // mInputList. So iterate over a temp list instead. + nsTArray<nsPipeInputStream*> list = mInputList.Clone(); + for (uint32_t i = 0; i < list.Length(); ++i) { + // an output-only exception applies to the input end if the pipe has + // zero bytes available. + list[i]->Monitor().AssertCurrentThreadIn(); + if (aOutputOnly && list[i]->Available()) { + continue; + } + + if (list[i]->OnInputException(aReason, events, mon) == NotifyMonitor) { + needNotify = true; + } + } + + mOutput.Monitor().AssertCurrentThreadIn(); + if (mOutput.OnOutputException(aReason, events) == NotifyMonitor) { + needNotify = true; + } + + // Notify after we have removed any input streams from mInputList + if (needNotify) { + mon.NotifyAll(); + } + } +} + +nsresult nsPipe::CloneInputStream(nsPipeInputStream* aOriginal, + nsIInputStream** aCloneOut) { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + RefPtr<nsPipeInputStream> ref = new nsPipeInputStream(*aOriginal); + // don't add clones of closed pipes to mInputList. + ref->Monitor().AssertCurrentThreadIn(); + if (NS_SUCCEEDED(ref->InputStatus(mon))) { + mInputList.AppendElement(ref); + } + nsCOMPtr<nsIAsyncInputStream> upcast = std::move(ref); + upcast.forget(aCloneOut); + return NS_OK; +} + +uint32_t nsPipe::CountSegmentReferences(int32_t aSegment) { + mReentrantMonitor.AssertCurrentThreadIn(); + uint32_t count = 0; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (aSegment >= mInputList[i]->ReadState().mSegment) { + count += 1; + } + } + return count; +} + +void nsPipe::SetAllNullReadCursors() { + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + if (!readState.mReadCursor) { + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment == readState.mSegment); + readState.mReadCursor = readState.mReadLimit = mWriteCursor; + } + } +} + +bool nsPipe::AllReadCursorsMatchWriteCursor() { + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + const nsPipeReadState& readState = mInputList[i]->ReadState(); + if (readState.mSegment != mWriteSegment || + readState.mReadCursor != mWriteCursor) { + return false; + } + } + return true; +} + +void nsPipe::RollBackAllReadCursors(char* aWriteCursor) { + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment == readState.mSegment); + MOZ_DIAGNOSTIC_ASSERT(mWriteCursor == readState.mReadCursor); + MOZ_DIAGNOSTIC_ASSERT(mWriteCursor == readState.mReadLimit); + readState.mReadCursor = aWriteCursor; + readState.mReadLimit = aWriteCursor; + } +} + +void nsPipe::UpdateAllReadCursors(char* aWriteCursor) { + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + if (mWriteSegment == readState.mSegment && + readState.mReadLimit == mWriteCursor) { + readState.mReadLimit = aWriteCursor; + } + } +} + +void nsPipe::ValidateAllReadCursors() { + mReentrantMonitor.AssertCurrentThreadIn(); + // The only way mReadCursor == mWriteCursor is if: + // + // - mReadCursor is at the start of a segment (which, based on how + // nsSegmentedBuffer works, means that this segment is the "first" + // segment) + // - mWriteCursor points at the location past the end of the current + // write segment (so the current write filled the current write + // segment, so we've incremented mWriteCursor to point past the end + // of it) + // - the segment to which data has just been written is located + // exactly one segment's worth of bytes before the first segment + // where mReadCursor is located + // + // Consequently, the byte immediately after the end of the current + // write segment is the first byte of the first segment, so + // mReadCursor == mWriteCursor. (Another way to think about this is + // to consider the buffer architecture diagram above, but consider it + // with an arena allocator which allocates from the *end* of the + // arena to the *beginning* of the arena.) +#ifdef DEBUG + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + const nsPipeReadState& state = mInputList[i]->ReadState(); + MOZ_ASSERT(state.mReadCursor != mWriteCursor || + (mBuffer.GetSegment(state.mSegment) == state.mReadCursor && + mWriteCursor == mWriteLimit)); + } +#endif +} + +uint32_t nsPipe::GetBufferSegmentCount( + const nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) const { + // The write segment can be smaller than the current reader position + // in some cases. For example, when the first write segment has not + // been allocated yet mWriteSegment is negative. In these cases + // the stream is effectively using zero segments. + if (mWriteSegment < aReadState.mSegment) { + return 0; + } + + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment >= 0); + MOZ_DIAGNOSTIC_ASSERT(aReadState.mSegment >= 0); + + // Otherwise at least one segment is being used. We add one here + // since a single segment is being used when the write and read + // segment indices are the same. + return 1 + mWriteSegment - aReadState.mSegment; +} + +bool nsPipe::IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const { + // If we have fewer total segments than the limit we can immediately + // determine we are not full. Note, we must add one to mWriteSegment + // to convert from a index to a count. + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment >= -1); + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment < INT32_MAX); + uint32_t totalWriteSegments = mWriteSegment + 1; + if (totalWriteSegments < mMaxAdvanceBufferSegmentCount) { + return false; + } + + // Otherwise we must inspect all of our reader streams. We need + // to determine the buffer depth of the fastest reader. + uint32_t minBufferSegments = UINT32_MAX; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // Only count buffer segments from input streams that are open. + mInputList[i]->Monitor().AssertCurrentThreadIn(); + if (NS_FAILED(mInputList[i]->Status(ev))) { + continue; + } + const nsPipeReadState& state = mInputList[i]->ReadState(); + uint32_t bufferSegments = GetBufferSegmentCount(state, ev); + minBufferSegments = std::min(minBufferSegments, bufferSegments); + // We only care if any reader has fewer segments buffered than + // our threshold. We can stop once we hit that threshold. + if (minBufferSegments < mMaxAdvanceBufferSegmentCount) { + return false; + } + } + + // Note, its possible for minBufferSegments to exceed our + // mMaxAdvanceBufferSegmentCount here. This happens when a cloned + // reader gets far behind, but then the fastest reader stream is + // closed. This leaves us with a single stream that is buffered + // beyond our max. Naturally we continue to indicate the pipe + // is full at this point. + + return true; +} + +//----------------------------------------------------------------------------- +// nsPipeEvents methods: +//----------------------------------------------------------------------------- + +nsPipeEvents::~nsPipeEvents() { + // dispatch any pending events + for (auto& callback : mCallbacks) { + callback.Notify(); + } + mCallbacks.Clear(); +} + +//----------------------------------------------------------------------------- +// nsPipeInputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsPipeInputStream); +NS_IMPL_RELEASE(nsPipeInputStream); + +NS_INTERFACE_TABLE_HEAD(nsPipeInputStream) + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIAsyncInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsITellableStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISearchableInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsICloneableInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIBufferedInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIClassInfo) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIInputStreamPriority) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsIInputStream, + nsIAsyncInputStream) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsISupports, + nsIAsyncInputStream) + NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +NS_IMPL_CI_INTERFACE_GETTER(nsPipeInputStream, nsIInputStream, + nsIAsyncInputStream, nsITellableStream, + nsISearchableInputStream, nsICloneableInputStream, + nsIBufferedInputStream) + +NS_IMPL_THREADSAFE_CI(nsPipeInputStream) + +NS_IMETHODIMP +nsPipeInputStream::Init(nsIInputStream*, uint32_t) { + MOZ_CRASH( + "nsPipeInputStream should never be initialized with " + "nsIBufferedInputStream::Init!\n"); +} + +NS_IMETHODIMP +nsPipeInputStream::GetData(nsIInputStream** aResult) { + // as this was not created with init() we are not + // wrapping anything + return NS_ERROR_NOT_IMPLEMENTED; +} + +uint32_t nsPipeInputStream::Available() { + mPipe->mReentrantMonitor.AssertCurrentThreadIn(); + return mReadState.mAvailable; +} + +nsresult nsPipeInputStream::Wait() { + MOZ_DIAGNOSTIC_ASSERT(mBlocking); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + while (NS_SUCCEEDED(Status(mon)) && (mReadState.mAvailable == 0)) { + LOG(("III pipe input: waiting for data\n")); + + mBlocked = true; + mon.Wait(); + mBlocked = false; + + LOG(("III pipe input: woke up [status=%" PRIx32 " available=%u]\n", + static_cast<uint32_t>(Status(mon)), mReadState.mAvailable)); + } + + return Status(mon) == NS_BASE_STREAM_CLOSED ? NS_OK : Status(mon); +} + +MonitorAction nsPipeInputStream::OnInputReadable( + uint32_t aBytesWritten, nsPipeEvents& aEvents, + const ReentrantMonitorAutoEnter& ev) { + MonitorAction result = DoNotNotifyMonitor; + + mPipe->mReentrantMonitor.AssertCurrentThreadIn(); + mReadState.mAvailable += aBytesWritten; + + if (mCallback && !(mCallback.Flags() & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyReady(std::move(mCallback)); + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction nsPipeInputStream::OnInputException( + nsresult aReason, nsPipeEvents& aEvents, + const ReentrantMonitorAutoEnter& ev) { + LOG(("nsPipeInputStream::OnInputException [this=%p reason=%" PRIx32 "]\n", + this, static_cast<uint32_t>(aReason))); + + MonitorAction result = DoNotNotifyMonitor; + + MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); + + if (NS_SUCCEEDED(mInputStatus)) { + mInputStatus = aReason; + } + + // force count of available bytes to zero. + mPipe->DrainInputStream(mReadState, aEvents); + + if (mCallback) { + aEvents.NotifyReady(std::move(mCallback)); + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +NS_IMETHODIMP +nsPipeInputStream::CloseWithStatus(nsresult aReason) { + LOG(("III CloseWithStatus [this=%p reason=%" PRIx32 "]\n", this, + static_cast<uint32_t>(aReason))); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + if (NS_FAILED(mInputStatus)) { + return NS_OK; + } + + if (NS_SUCCEEDED(aReason)) { + aReason = NS_BASE_STREAM_CLOSED; + } + + mPipe->OnInputStreamException(this, aReason); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::SetPriority(uint32_t priority) { + mPriority = priority; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::GetPriority(uint32_t* priority) { + *priority = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } + +NS_IMETHODIMP +nsPipeInputStream::Available(uint64_t* aResult) { + // nsPipeInputStream supports under 4GB stream only + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if closed + if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { + return Status(mon); + } + + *aResult = (uint64_t)mReadState.mAvailable; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::StreamStatus() { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + return mReadState.mAvailable ? NS_OK : Status(mon); +} + +NS_IMETHODIMP +nsPipeInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aReadCount) { + LOG(("III ReadSegments [this=%p count=%u]\n", this, aCount)); + + nsresult rv = NS_OK; + + *aReadCount = 0; + while (aCount) { + AutoReadSegment segment(mPipe, mReadState, aCount); + rv = segment.Status(); + if (NS_FAILED(rv)) { + // ignore this error if we've already read something. + if (*aReadCount > 0) { + rv = NS_OK; + break; + } + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is empty + if (!mBlocking) { + break; + } + // wait for some data to be written to the pipe + rv = Wait(); + if (NS_SUCCEEDED(rv)) { + continue; + } + } + // ignore this error, just return. + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + break; + } + mPipe->OnInputStreamException(this, rv); + break; + } + + uint32_t writeCount; + while (segment.Length()) { + writeCount = 0; + + rv = aWriter(static_cast<nsIAsyncInputStream*>(this), aClosure, + segment.Data(), *aReadCount, segment.Length(), &writeCount); + + if (NS_FAILED(rv) || writeCount == 0) { + aCount = 0; + // any errors returned from the writer end here: do not + // propagate to the caller of ReadSegments. + rv = NS_OK; + break; + } + + MOZ_DIAGNOSTIC_ASSERT(writeCount <= segment.Length()); + segment.Advance(writeCount); + aCount -= writeCount; + *aReadCount += writeCount; + mLogicalOffset += writeCount; + } + } + + return rv; +} + +NS_IMETHODIMP +nsPipeInputStream::Read(char* aToBuf, uint32_t aBufLen, uint32_t* aReadCount) { + return ReadSegments(NS_CopySegmentToBuffer, aToBuf, aBufLen, aReadCount); +} + +NS_IMETHODIMP +nsPipeInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) { + LOG(("III AsyncWait [this=%p]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + + if (!aCallback) { + return NS_OK; + } + + CallbackHolder callback(this, aCallback, aFlags, aTarget); + + if (NS_FAILED(Status(mon)) || + (mReadState.mAvailable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or readable; post event. + pipeEvents.NotifyReady(std::move(callback)); + } else { + // queue up callback object to be notified when data becomes available + mCallback = std::move(callback); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Tell(int64_t* aOffset) { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if closed + if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { + return Status(mon); + } + + *aOffset = mLogicalOffset; + return NS_OK; +} + +static bool strings_equal(bool aIgnoreCase, const char* aS1, const char* aS2, + uint32_t aLen) { + return aIgnoreCase ? !nsCRT::strncasecmp(aS1, aS2, aLen) + : !strncmp(aS1, aS2, aLen); +} + +NS_IMETHODIMP +nsPipeInputStream::Search(const char* aForString, bool aIgnoreCase, + bool* aFound, uint32_t* aOffsetSearchedTo) { + LOG(("III Search [for=%s ic=%u]\n", aForString, aIgnoreCase)); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + char* cursor1; + char* limit1; + uint32_t index = 0, offset = 0; + uint32_t strLen = strlen(aForString); + + mPipe->PeekSegment(mReadState, 0, cursor1, limit1); + if (cursor1 == limit1) { + *aFound = false; + *aOffsetSearchedTo = 0; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + + while (true) { + uint32_t i, len1 = limit1 - cursor1; + + // check if the string is in the buffer segment + for (i = 0; i < len1 - strLen + 1; i++) { + if (strings_equal(aIgnoreCase, &cursor1[i], aForString, strLen)) { + *aFound = true; + *aOffsetSearchedTo = offset + i; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + } + + // get the next segment + char* cursor2; + char* limit2; + uint32_t len2; + + index++; + offset += len1; + + mPipe->PeekSegment(mReadState, index, cursor2, limit2); + if (cursor2 == limit2) { + *aFound = false; + *aOffsetSearchedTo = offset - strLen + 1; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + len2 = limit2 - cursor2; + + // check if the string is straddling the next buffer segment + uint32_t lim = XPCOM_MIN(strLen, len2 + 1); + for (i = 0; i < lim; ++i) { + uint32_t strPart1Len = strLen - i - 1; + uint32_t strPart2Len = strLen - strPart1Len; + const char* strPart2 = &aForString[strLen - strPart2Len]; + uint32_t bufSeg1Offset = len1 - strPart1Len; + if (strings_equal(aIgnoreCase, &cursor1[bufSeg1Offset], aForString, + strPart1Len) && + strings_equal(aIgnoreCase, cursor2, strPart2, strPart2Len)) { + *aFound = true; + *aOffsetSearchedTo = offset - strPart1Len; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + } + + // finally continue with the next buffer + cursor1 = cursor2; + limit1 = limit2; + } + + MOZ_ASSERT_UNREACHABLE("can't get here"); + return NS_ERROR_UNEXPECTED; // keep compiler happy +} + +NS_IMETHODIMP +nsPipeInputStream::GetCloneable(bool* aCloneableOut) { + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Clone(nsIInputStream** aCloneOut) { + return mPipe->CloneInputStream(this, aCloneOut); +} + +nsresult nsPipeInputStream::Status(const ReentrantMonitorAutoEnter& ev) const { + if (NS_FAILED(mInputStatus)) { + return mInputStatus; + } + + if (mReadState.mAvailable) { + // Still something to read and this input stream state is OK. + return NS_OK; + } + + // Nothing to read, just fall through to the pipe's state that + // may reflect state of its output stream side (already closed). + return mPipe->mStatus; +} + +nsresult nsPipeInputStream::Status() const { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + return Status(mon); +} + +nsPipeInputStream::~nsPipeInputStream() { Close(); } + +//----------------------------------------------------------------------------- +// nsPipeOutputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_QUERY_INTERFACE(nsPipeOutputStream, nsIOutputStream, + nsIAsyncOutputStream, nsIClassInfo) + +NS_IMPL_CI_INTERFACE_GETTER(nsPipeOutputStream, nsIOutputStream, + nsIAsyncOutputStream) + +NS_IMPL_THREADSAFE_CI(nsPipeOutputStream) + +nsresult nsPipeOutputStream::Wait() { + MOZ_DIAGNOSTIC_ASSERT(mBlocking); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + if (NS_SUCCEEDED(mPipe->mStatus) && !mWritable) { + LOG(("OOO pipe output: waiting for space\n")); + mBlocked = true; + mon.Wait(); + mBlocked = false; + LOG(("OOO pipe output: woke up [pipe-status=%" PRIx32 " writable=%u]\n", + static_cast<uint32_t>(mPipe->mStatus), mWritable)); + } + + return mPipe->mStatus == NS_BASE_STREAM_CLOSED ? NS_OK : mPipe->mStatus; +} + +MonitorAction nsPipeOutputStream::OnOutputWritable(nsPipeEvents& aEvents) { + MonitorAction result = DoNotNotifyMonitor; + + mWritable = true; + + if (mCallback && !(mCallback.Flags() & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyReady(std::move(mCallback)); + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction nsPipeOutputStream::OnOutputException(nsresult aReason, + nsPipeEvents& aEvents) { + LOG(("nsPipeOutputStream::OnOutputException [this=%p reason=%" PRIx32 "]\n", + this, static_cast<uint32_t>(aReason))); + + MonitorAction result = DoNotNotifyMonitor; + + MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); + mWritable = false; + + if (mCallback) { + aEvents.NotifyReady(std::move(mCallback)); + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipeOutputStream::AddRef() { + ++mWriterRefCnt; + return mPipe->AddRef(); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipeOutputStream::Release() { + if (--mWriterRefCnt == 0) { + Close(); + } + return mPipe->Release(); +} + +NS_IMETHODIMP +nsPipeOutputStream::CloseWithStatus(nsresult aReason) { + LOG(("OOO CloseWithStatus [this=%p reason=%" PRIx32 "]\n", this, + static_cast<uint32_t>(aReason))); + + if (NS_SUCCEEDED(aReason)) { + aReason = NS_BASE_STREAM_CLOSED; + } + + // input stream may remain open + mPipe->OnPipeException(aReason, true); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } + +NS_IMETHODIMP +nsPipeOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aWriteCount) { + LOG(("OOO WriteSegments [this=%p count=%u]\n", this, aCount)); + + nsresult rv = NS_OK; + + char* segment; + uint32_t segmentLen; + + *aWriteCount = 0; + while (aCount) { + rv = mPipe->GetWriteSegment(segment, segmentLen); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is full + if (!mBlocking) { + // ignore this error if we've already written something + if (*aWriteCount > 0) { + rv = NS_OK; + } + break; + } + // wait for the pipe to have an empty segment. + rv = Wait(); + if (NS_SUCCEEDED(rv)) { + continue; + } + } + mPipe->OnPipeException(rv); + break; + } + + // write no more than aCount + if (segmentLen > aCount) { + segmentLen = aCount; + } + + uint32_t readCount, originalLen = segmentLen; + while (segmentLen) { + readCount = 0; + + rv = aReader(this, aClosure, segment, *aWriteCount, segmentLen, + &readCount); + + if (NS_FAILED(rv) || readCount == 0) { + aCount = 0; + // any errors returned from the aReader end here: do not + // propagate to the caller of WriteSegments. + rv = NS_OK; + break; + } + + MOZ_DIAGNOSTIC_ASSERT(readCount <= segmentLen); + segment += readCount; + segmentLen -= readCount; + aCount -= readCount; + *aWriteCount += readCount; + mLogicalOffset += readCount; + } + + if (segmentLen < originalLen) { + mPipe->AdvanceWriteCursor(originalLen - segmentLen); + } + } + + return rv; +} + +NS_IMETHODIMP +nsPipeOutputStream::Write(const char* aFromBuf, uint32_t aBufLen, + uint32_t* aWriteCount) { + return WriteSegments(NS_CopyBufferToSegment, (void*)aFromBuf, aBufLen, + aWriteCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::Flush() { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::StreamStatus() { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + return mPipe->mStatus; +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* aWriteCount) { + return WriteSegments(NS_CopyStreamToSegment, aFromStream, aCount, + aWriteCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aTarget) { + LOG(("OOO AsyncWait [this=%p]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + + if (!aCallback) { + return NS_OK; + } + + CallbackHolder callback(this, aCallback, aFlags, aTarget); + + if (NS_FAILED(mPipe->mStatus) || + (mWritable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or writable; post event. + pipeEvents.NotifyReady(std::move(callback)); + } else { + // queue up callback object to be notified when data becomes available + mCallback = std::move(callback); + } + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +void NS_NewPipe(nsIInputStream** aPipeIn, nsIOutputStream** aPipeOut, + uint32_t aSegmentSize, uint32_t aMaxSize, + bool aNonBlockingInput, bool aNonBlockingOutput) { + if (aSegmentSize == 0) { + aSegmentSize = DEFAULT_SEGMENT_SIZE; + } + + // Handle aMaxSize of UINT32_MAX as a special case + uint32_t segmentCount; + if (aMaxSize == UINT32_MAX) { + segmentCount = UINT32_MAX; + } else { + segmentCount = aMaxSize / aSegmentSize; + } + + nsIAsyncInputStream* in; + nsIAsyncOutputStream* out; + NS_NewPipe2(&in, &out, aNonBlockingInput, aNonBlockingOutput, aSegmentSize, + segmentCount); + + *aPipeIn = in; + *aPipeOut = out; +} + +// Disable thread safety analysis as this is logically a constructor, and no +// additional threads can observe these objects yet. +void NS_NewPipe2(nsIAsyncInputStream** aPipeIn, nsIAsyncOutputStream** aPipeOut, + bool aNonBlockingInput, bool aNonBlockingOutput, + uint32_t aSegmentSize, + uint32_t aSegmentCount) MOZ_NO_THREAD_SAFETY_ANALYSIS { + RefPtr<nsPipe> pipe = + new nsPipe(aSegmentSize ? aSegmentSize : DEFAULT_SEGMENT_SIZE, + aSegmentCount ? aSegmentCount : DEFAULT_SEGMENT_COUNT); + + RefPtr<nsPipeInputStream> pipeIn = new nsPipeInputStream(pipe); + pipe->mInputList.AppendElement(pipeIn); + RefPtr<nsPipeOutputStream> pipeOut = &pipe->mOutput; + + pipeIn->SetNonBlocking(aNonBlockingInput); + pipeOut->SetNonBlocking(aNonBlockingOutput); + + pipeIn.forget(aPipeIn); + pipeOut.forget(aPipeOut); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Thin nsIPipe implementation for consumers of the component manager interface +// for creating pipes. Acts as a thin wrapper around NS_NewPipe2 for JS callers. +class nsPipeHolder final : public nsIPipe { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPIPE + + private: + ~nsPipeHolder() = default; + + nsCOMPtr<nsIAsyncInputStream> mInput; + nsCOMPtr<nsIAsyncOutputStream> mOutput; +}; + +NS_IMPL_ISUPPORTS(nsPipeHolder, nsIPipe) + +NS_IMETHODIMP +nsPipeHolder::Init(bool aNonBlockingInput, bool aNonBlockingOutput, + uint32_t aSegmentSize, uint32_t aSegmentCount) { + if (mInput || mOutput) { + return NS_ERROR_ALREADY_INITIALIZED; + } + NS_NewPipe2(getter_AddRefs(mInput), getter_AddRefs(mOutput), + aNonBlockingInput, aNonBlockingOutput, aSegmentSize, + aSegmentCount); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeHolder::GetInputStream(nsIAsyncInputStream** aInputStream) { + if (mInput) { + *aInputStream = do_AddRef(mInput).take(); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsPipeHolder::GetOutputStream(nsIAsyncOutputStream** aOutputStream) { + if (mOutput) { + *aOutputStream = do_AddRef(mOutput).take(); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +nsresult nsPipeConstructor(REFNSIID aIID, void** aResult) { + RefPtr<nsPipeHolder> pipe = new nsPipeHolder(); + nsresult rv = pipe->QueryInterface(aIID, aResult); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsScriptableBase64Encoder.cpp b/xpcom/io/nsScriptableBase64Encoder.cpp new file mode 100644 index 0000000000..8513a95380 --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.cpp @@ -0,0 +1,26 @@ +/* -*- 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 "nsScriptableBase64Encoder.h" +#include "mozilla/Base64.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsScriptableBase64Encoder, nsIScriptableBase64Encoder) + +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToCString(nsIInputStream* aStream, + uint32_t aLength, + nsACString& aResult) { + return Base64EncodeInputStream(aStream, aResult, aLength); +} + +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToString(nsIInputStream* aStream, + uint32_t aLength, + nsAString& aResult) { + return Base64EncodeInputStream(aStream, aResult, aLength); +} diff --git a/xpcom/io/nsScriptableBase64Encoder.h b/xpcom/io/nsScriptableBase64Encoder.h new file mode 100644 index 0000000000..8982657e59 --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.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 nsScriptableBase64Encoder_h__ +#define nsScriptableBase64Encoder_h__ + +#include "nsIScriptableBase64Encoder.h" +#include "mozilla/Attributes.h" + +#define NS_SCRIPTABLEBASE64ENCODER_CID \ + { \ + 0xaaf68860, 0xf849, 0x40ee, { \ + 0xbb, 0x7a, 0xb2, 0x29, 0xbc, 0xe0, 0x36, 0xa3 \ + } \ + } +#define NS_SCRIPTABLEBASE64ENCODER_CONTRACTID \ + "@mozilla.org/scriptablebase64encoder;1" + +class nsScriptableBase64Encoder final : public nsIScriptableBase64Encoder { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISCRIPTABLEBASE64ENCODER + private: + ~nsScriptableBase64Encoder() = default; +}; + +#endif diff --git a/xpcom/io/nsScriptableInputStream.cpp b/xpcom/io/nsScriptableInputStream.cpp new file mode 100644 index 0000000000..5f8b139c6f --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.cpp @@ -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/. */ + +#include "nsScriptableInputStream.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS(nsScriptableInputStream, nsIScriptableInputStream) + +// nsIScriptableInputStream methods +NS_IMETHODIMP +nsScriptableInputStream::Close() { + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + return mInputStream->Close(); +} + +NS_IMETHODIMP +nsScriptableInputStream::Init(nsIInputStream* aInputStream) { + if (!aInputStream) { + return NS_ERROR_NULL_POINTER; + } + mInputStream = aInputStream; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptableInputStream::Available(uint64_t* aResult) { + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + return mInputStream->Available(aResult); +} + +NS_IMETHODIMP +nsScriptableInputStream::Read(uint32_t aCount, char** aResult) { + nsresult rv = NS_OK; + uint64_t count64 = 0; + char* buffer = nullptr; + + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + + rv = mInputStream->Available(&count64); + if (NS_FAILED(rv)) { + return rv; + } + + // bug716556 - Ensure count+1 doesn't overflow + uint32_t count = + XPCOM_MIN((uint32_t)XPCOM_MIN<uint64_t>(count64, aCount), UINT32_MAX - 1); + buffer = (char*)malloc(count + 1); // make room for '\0' + if (!buffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = ReadHelper(buffer, count); + if (NS_FAILED(rv)) { + free(buffer); + return rv; + } + + buffer[count] = '\0'; + *aResult = buffer; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptableInputStream::ReadBytes(uint32_t aCount, nsACString& aResult) { + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (!aResult.SetLength(aCount, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_ASSERT(aResult.Length() == aCount); + char* ptr = aResult.BeginWriting(); + nsresult rv = ReadHelper(ptr, aCount); + if (NS_FAILED(rv)) { + aResult.Truncate(); + } + return rv; +} + +nsresult nsScriptableInputStream::ReadHelper(char* aBuffer, uint32_t aCount) { + uint32_t totalBytesRead = 0; + while (1) { + uint32_t bytesRead; + nsresult rv = mInputStream->Read(aBuffer + totalBytesRead, + aCount - totalBytesRead, &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + totalBytesRead += bytesRead; + if (totalBytesRead == aCount) { + break; + } + + // If we have read zero bytes, we have hit EOF. + if (bytesRead == 0) { + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +nsresult nsScriptableInputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsScriptableInputStream> sis = new nsScriptableInputStream(); + return sis->QueryInterface(aIID, aResult); +} diff --git a/xpcom/io/nsScriptableInputStream.h b/xpcom/io/nsScriptableInputStream.h new file mode 100644 index 0000000000..1dc8a80962 --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ___nsscriptableinputstream___h_ +#define ___nsscriptableinputstream___h_ + +#include "nsIScriptableInputStream.h" +#include "nsIInputStream.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +#define NS_SCRIPTABLEINPUTSTREAM_CID \ + { \ + 0x7225c040, 0xa9bf, 0x11d3, { \ + 0xa1, 0x97, 0x0, 0x50, 0x4, 0x1c, 0xaf, 0x44 \ + } \ + } + +#define NS_SCRIPTABLEINPUTSTREAM_CONTRACTID \ + "@mozilla.org/scriptableinputstream;1" + +class nsScriptableInputStream final : public nsIScriptableInputStream { + public: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIScriptableInputStream methods + NS_DECL_NSISCRIPTABLEINPUTSTREAM + + // nsScriptableInputStream methods + nsScriptableInputStream() = default; + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + ~nsScriptableInputStream() = default; + + nsresult ReadHelper(char* aBuffer, uint32_t aCount); + + nsCOMPtr<nsIInputStream> mInputStream; +}; + +#endif // ___nsscriptableinputstream___h_ diff --git a/xpcom/io/nsSegmentedBuffer.cpp b/xpcom/io/nsSegmentedBuffer.cpp new file mode 100644 index 0000000000..01d075368a --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSegmentedBuffer.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/ScopeExit.h" + +nsresult nsSegmentedBuffer::Init(uint32_t aSegmentSize) { + if (mSegmentArrayCount != 0) { + return NS_ERROR_FAILURE; // initialized more than once + } + mSegmentSize = aSegmentSize; + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; + return NS_OK; +} + +char* nsSegmentedBuffer::AppendNewSegment() { + if (!mSegmentArray) { + uint32_t bytes = mSegmentArrayCount * sizeof(char*); + mSegmentArray = (char**)moz_xmalloc(bytes); + memset(mSegmentArray, 0, bytes); + } + + if (IsFull()) { + mozilla::CheckedInt<uint32_t> newArraySize = + mozilla::CheckedInt<uint32_t>(mSegmentArrayCount) * 2; + mozilla::CheckedInt<uint32_t> bytes = newArraySize * sizeof(char*); + if (!bytes.isValid()) { + return nullptr; + } + + mSegmentArray = (char**)moz_xrealloc(mSegmentArray, bytes.value()); + // copy wrapped content to new extension + if (mFirstSegmentIndex > mLastSegmentIndex) { + // deal with wrap around case + memcpy(&mSegmentArray[mSegmentArrayCount], mSegmentArray, + mLastSegmentIndex * sizeof(char*)); + memset(mSegmentArray, 0, mLastSegmentIndex * sizeof(char*)); + mLastSegmentIndex += mSegmentArrayCount; + memset(&mSegmentArray[mLastSegmentIndex], 0, + (newArraySize.value() - mLastSegmentIndex) * sizeof(char*)); + } else { + memset(&mSegmentArray[mLastSegmentIndex], 0, + (newArraySize.value() - mLastSegmentIndex) * sizeof(char*)); + } + mSegmentArrayCount = newArraySize.value(); + } + + char* seg = (char*)malloc(mSegmentSize); + if (!seg) { + return nullptr; + } + mSegmentArray[mLastSegmentIndex] = seg; + mLastSegmentIndex = ModSegArraySize(mLastSegmentIndex + 1); + return seg; +} + +bool nsSegmentedBuffer::DeleteFirstSegment() { + NS_ASSERTION(mSegmentArray[mFirstSegmentIndex] != nullptr, + "deleting bad segment"); + FreeOMT(mSegmentArray[mFirstSegmentIndex]); + mSegmentArray[mFirstSegmentIndex] = nullptr; + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + if (mFirstSegmentIndex == last) { + mLastSegmentIndex = last; + return true; + } else { + mFirstSegmentIndex = ModSegArraySize(mFirstSegmentIndex + 1); + return false; + } +} + +bool nsSegmentedBuffer::DeleteLastSegment() { + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + NS_ASSERTION(mSegmentArray[last] != nullptr, "deleting bad segment"); + FreeOMT(mSegmentArray[last]); + mSegmentArray[last] = nullptr; + mLastSegmentIndex = last; + return (bool)(mLastSegmentIndex == mFirstSegmentIndex); +} + +bool nsSegmentedBuffer::ReallocLastSegment(size_t aNewSize) { + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + NS_ASSERTION(mSegmentArray[last] != nullptr, "realloc'ing bad segment"); + char* newSegment = (char*)realloc(mSegmentArray[last], aNewSize); + if (newSegment) { + mSegmentArray[last] = newSegment; + return true; + } + return false; +} + +void nsSegmentedBuffer::Empty() { + auto clearMembers = mozilla::MakeScopeExit([&] { + mSegmentArray = nullptr; + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; + mFirstSegmentIndex = mLastSegmentIndex = 0; + }); + + // If mSegmentArray is null, there's no need to actually free anything + if (!mSegmentArray) { + return; + } + + // Dispatch a task that frees up the array. This may run immediately or on + // a background thread. + FreeOMT([segmentArray = mSegmentArray, arrayCount = mSegmentArrayCount]() { + for (uint32_t i = 0; i < arrayCount; i++) { + if (segmentArray[i]) { + free(segmentArray[i]); + } + } + free(segmentArray); + }); +} + +void nsSegmentedBuffer::FreeOMT(void* aPtr) { + FreeOMT([aPtr]() { free(aPtr); }); +} + +void nsSegmentedBuffer::FreeOMT(std::function<void()>&& aTask) { + if (!NS_IsMainThread()) { + aTask(); + return; + } + + if (mFreeOMT) { + // There is a runnable pending which will handle this object + if (mFreeOMT->AddTask(std::move(aTask)) > 1) { + return; + } + } else { + mFreeOMT = mozilla::MakeRefPtr<FreeOMTPointers>(); + mFreeOMT->AddTask(std::move(aTask)); + } + + if (!mIOThread) { + mIOThread = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + } + + // During the shutdown we are not able to obtain the IOThread and/or the + // dispatching of runnable fails. + if (!mIOThread || NS_FAILED(mIOThread->Dispatch(NS_NewRunnableFunction( + "nsSegmentedBuffer::FreeOMT", + [obj = mFreeOMT]() { obj->FreeAll(); })))) { + mFreeOMT->FreeAll(); + } +} + +void nsSegmentedBuffer::FreeOMTPointers::FreeAll() { + // Take all the tasks from the object. If AddTask is called after we release + // the lock, then another runnable will be dispatched for that task. This is + // necessary to avoid blocking the main thread while memory is being freed. + nsTArray<std::function<void()>> tasks = [this]() { + auto t = mTasks.Lock(); + return std::move(*t); + }(); + + // Finally run all the tasks to free memory. + for (auto& task : tasks) { + task(); + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsSegmentedBuffer.h b/xpcom/io/nsSegmentedBuffer.h new file mode 100644 index 0000000000..c654956df9 --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.h @@ -0,0 +1,126 @@ +/* -*- 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 nsSegmentedBuffer_h__ +#define nsSegmentedBuffer_h__ + +#include <stddef.h> +#include <functional> + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsTArray.h" +#include "mozilla/DataMutex.h" + +class nsIEventTarget; + +class nsSegmentedBuffer { + public: + nsSegmentedBuffer() + : mSegmentSize(0), + mSegmentArray(nullptr), + mSegmentArrayCount(0), + mFirstSegmentIndex(0), + mLastSegmentIndex(0) {} + + ~nsSegmentedBuffer() { Empty(); } + + nsresult Init(uint32_t aSegmentSize); + + char* AppendNewSegment(); // pushes at end + + // returns true if no more segments remain: + bool DeleteFirstSegment(); // pops from beginning + + // returns true if no more segments remain: + bool DeleteLastSegment(); // pops from beginning + + // Call Realloc() on last segment. This is used to reduce memory + // consumption when data is not an exact multiple of segment size. + bool ReallocLastSegment(size_t aNewSize); + + void Empty(); // frees all segments + + inline uint32_t GetSegmentCount() { + if (mFirstSegmentIndex <= mLastSegmentIndex) { + return mLastSegmentIndex - mFirstSegmentIndex; + } else { + return mSegmentArrayCount + mLastSegmentIndex - mFirstSegmentIndex; + } + } + + inline uint32_t GetSegmentSize() { return mSegmentSize; } + + inline char* GetSegment(uint32_t aIndex) { + NS_ASSERTION(aIndex < GetSegmentCount(), "index out of bounds"); + int32_t i = ModSegArraySize(mFirstSegmentIndex + (int32_t)aIndex); + return mSegmentArray[i]; + } + + protected: + inline int32_t ModSegArraySize(int32_t aIndex) { + uint32_t result = aIndex & (mSegmentArrayCount - 1); + NS_ASSERTION(result == aIndex % mSegmentArrayCount, + "non-power-of-2 mSegmentArrayCount"); + return result; + } + + inline bool IsFull() { + return ModSegArraySize(mLastSegmentIndex + 1) == mFirstSegmentIndex; + } + + protected: + uint32_t mSegmentSize; + char** mSegmentArray; + uint32_t mSegmentArrayCount; + int32_t mFirstSegmentIndex; + int32_t mLastSegmentIndex; + + private: + class FreeOMTPointers { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FreeOMTPointers) + + public: + FreeOMTPointers() : mTasks("nsSegmentedBuffer::FreeOMTPointers") {} + + void FreeAll(); + + // Adds a task to the array. Returns the size of the array. + size_t AddTask(std::function<void()>&& aTask) { + auto tasks = mTasks.Lock(); + tasks->AppendElement(std::move(aTask)); + return tasks->Length(); + } + + private: + ~FreeOMTPointers() = default; + + mozilla::DataMutex<nsTArray<std::function<void()>>> mTasks; + }; + + void FreeOMT(void* aPtr); + void FreeOMT(std::function<void()>&& aTask); + + nsCOMPtr<nsIEventTarget> mIOThread; + + // This object is created the first time we need to dispatch to another thread + // to free segments. It is only freed when the nsSegmentedBufer is destroyed + // or when the runnable is finally handled and its refcount goes to 0. + RefPtr<FreeOMTPointers> mFreeOMT; +}; + +// NS_SEGMENTARRAY_INITIAL_SIZE: This number needs to start out as a +// power of 2 given how it gets used. We double the segment array +// when we overflow it, and use that fact that it's a power of 2 +// to compute a fast modulus operation in IsFull. +// +// 32 segment array entries can accommodate 128k of data if segments +// are 4k in size. That seems like a reasonable amount that will avoid +// needing to grow the segment array. +#define NS_SEGMENTARRAY_INITIAL_COUNT 32 + +#endif // nsSegmentedBuffer_h__ diff --git a/xpcom/io/nsStorageStream.cpp b/xpcom/io/nsStorageStream.cpp new file mode 100644 index 0000000000..cd3dfd6645 --- /dev/null +++ b/xpcom/io/nsStorageStream.cpp @@ -0,0 +1,680 @@ +/* -*- 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/. */ + +/* + * The storage stream provides an internal buffer that can be filled by a + * client using a single output stream. One or more independent input streams + * can be created to read the data out non-destructively. The implementation + * uses a segmented buffer internally to avoid realloc'ing of large buffers, + * with the attendant performance loss and heap fragmentation. + */ + +#include "mozilla/Mutex.h" +#include "nsAlgorithm.h" +#include "nsStorageStream.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "mozilla/Logging.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/ipc/InputStreamUtils.h" + +using mozilla::MutexAutoLock; +using mozilla::ipc::InputStreamParams; +using mozilla::ipc::StringInputStreamParams; + +// +// Log module for StorageStream logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=StorageStreamLog:5 +// set MOZ_LOG_FILE=storage.log +// +// This enables LogLevel::Debug level information and places all output in +// the file storage.log. +// +static mozilla::LazyLogModule sStorageStreamLog("nsStorageStream"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sStorageStreamLog, mozilla::LogLevel::Debug, args) + +nsStorageStream::nsStorageStream() { + LOG(("Creating nsStorageStream [%p].\n", this)); +} + +nsStorageStream::~nsStorageStream() { delete mSegmentedBuffer; } + +NS_IMPL_ISUPPORTS(nsStorageStream, nsIStorageStream, nsIOutputStream) + +NS_IMETHODIMP +nsStorageStream::Init(uint32_t aSegmentSize, uint32_t aMaxSize) { + MutexAutoLock lock(mMutex); + mSegmentedBuffer = new nsSegmentedBuffer(); + mSegmentSize = aSegmentSize; + mSegmentSizeLog2 = mozilla::FloorLog2(aSegmentSize); + mMaxLogicalLength = aMaxSize; + + // Segment size must be a power of two + if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) { + return NS_ERROR_INVALID_ARG; + } + + return mSegmentedBuffer->Init(aSegmentSize); +} + +NS_IMETHODIMP +nsStorageStream::GetOutputStream(int32_t aStartingOffset, + nsIOutputStream** aOutputStream) { + if (NS_WARN_IF(!aOutputStream)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWriteInProgress) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mActiveSegmentBorrows > 0) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = Seek(aStartingOffset); + if (NS_FAILED(rv)) { + return rv; + } + + // Enlarge the last segment in the buffer so that it is the same size as + // all the other segments in the buffer. (It may have been realloc'ed + // smaller in the Close() method.) + if (mLastSegmentNum >= 0) + if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) { + // Need to re-Seek, since realloc changed segment base pointer + rv = Seek(aStartingOffset); + if (NS_FAILED(rv)) { + return rv; + } + } + + NS_ADDREF(this); + *aOutputStream = static_cast<nsIOutputStream*>(this); + mWriteInProgress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Close() { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + mWriteInProgress = false; + + int32_t segmentOffset = SegOffset(mLogicalLength); + + // Shrink the final segment in the segmented buffer to the minimum size + // needed to contain the data, so as to conserve memory. + if (segmentOffset && !mActiveSegmentBorrows) { + mSegmentedBuffer->ReallocLastSegment(segmentOffset); + } + + mWriteCursor = 0; + mSegmentEnd = 0; + + LOG(("nsStorageStream [%p] Close mWriteCursor=%p mSegmentEnd=%p\n", this, + mWriteCursor, mSegmentEnd)); + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Flush() { return NS_OK; } + +NS_IMETHODIMP +nsStorageStream::StreamStatus() { + MutexAutoLock lock(mMutex); + if (!mSegmentedBuffer) { + return NS_ERROR_NOT_INITIALIZED; + } + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Write(const char* aBuffer, uint32_t aCount, + uint32_t* aNumWritten) { + if (NS_WARN_IF(!aNumWritten) || NS_WARN_IF(!aBuffer)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (NS_WARN_IF(mLogicalLength >= mMaxLogicalLength)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + LOG(("nsStorageStream [%p] Write mWriteCursor=%p mSegmentEnd=%p aCount=%d\n", + this, mWriteCursor, mSegmentEnd, aCount)); + + uint32_t remaining = aCount; + const char* readCursor = aBuffer; + + remaining = std::min(remaining, mMaxLogicalLength - mLogicalLength); + + auto onExit = mozilla::MakeScopeExit([&] { + mMutex.AssertCurrentThreadOwns(); + *aNumWritten = aCount - remaining; + mLogicalLength += *aNumWritten; + + LOG( + ("nsStorageStream [%p] Wrote mWriteCursor=%p mSegmentEnd=%p " + "numWritten=%d\n", + this, mWriteCursor, mSegmentEnd, *aNumWritten)); + }); + + // If no segments have been created yet, create one even if we don't have + // to write any data; this enables creating an input stream which reads from + // the very end of the data for any amount of data in the stream (i.e. + // this stream contains N bytes of data and newInputStream(N) is called), + // even for N=0 (with the caveat that we require .write("", 0) be called to + // initialize internal buffers). + bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0; + while (remaining || MOZ_UNLIKELY(firstTime)) { + firstTime = false; + uint32_t availableInSegment = mSegmentEnd - mWriteCursor; + if (!availableInSegment) { + mWriteCursor = mSegmentedBuffer->AppendNewSegment(); + if (!mWriteCursor) { + mSegmentEnd = 0; + return NS_ERROR_OUT_OF_MEMORY; + } + mLastSegmentNum++; + mSegmentEnd = mWriteCursor + mSegmentSize; + availableInSegment = mSegmentEnd - mWriteCursor; + LOG( + ("nsStorageStream [%p] Write (new seg) mWriteCursor=%p " + "mSegmentEnd=%p\n", + this, mWriteCursor, mSegmentEnd)); + } + + uint32_t count = XPCOM_MIN(availableInSegment, remaining); + memcpy(mWriteCursor, readCursor, count); + remaining -= count; + readCursor += count; + mWriteCursor += count; + LOG( + ("nsStorageStream [%p] Writing mWriteCursor=%p mSegmentEnd=%p " + "count=%d\n", + this, mWriteCursor, mSegmentEnd, count)); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount, + uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::GetLength(uint32_t* aLength) { + MutexAutoLock lock(mMutex); + *aLength = mLogicalLength; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::SetLength(uint32_t aLength) { + MutexAutoLock lock(mMutex); + return SetLengthLocked(aLength); +} + +// Truncate the buffer by deleting the end segments +nsresult nsStorageStream::SetLengthLocked(uint32_t aLength) { + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWriteInProgress) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mActiveSegmentBorrows) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (aLength > mLogicalLength) { + return NS_ERROR_INVALID_ARG; + } + + int32_t newLastSegmentNum = SegNum(aLength); + int32_t segmentOffset = SegOffset(aLength); + if (segmentOffset == 0) { + newLastSegmentNum--; + } + + while (newLastSegmentNum < mLastSegmentNum) { + mSegmentedBuffer->DeleteLastSegment(); + mLastSegmentNum--; + } + + mLogicalLength = aLength; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::GetWriteInProgress(bool* aWriteInProgress) { + MutexAutoLock lock(mMutex); + *aWriteInProgress = mWriteInProgress; + return NS_OK; +} + +nsresult nsStorageStream::Seek(int32_t aPosition) { + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // An argument of -1 means "seek to end of stream" + if (aPosition == -1) { + aPosition = mLogicalLength; + } + + // Seeking beyond the buffer end is illegal + if ((uint32_t)aPosition > mLogicalLength) { + return NS_ERROR_INVALID_ARG; + } + + // Seeking backwards in the write stream results in truncation + SetLengthLocked(aPosition); + + // Special handling for seek to start-of-buffer + if (aPosition == 0) { + mWriteCursor = 0; + mSegmentEnd = 0; + LOG(("nsStorageStream [%p] Seek mWriteCursor=%p mSegmentEnd=%p\n", this, + mWriteCursor, mSegmentEnd)); + return NS_OK; + } + + // Segment may have changed, so reset pointers + mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum); + NS_ASSERTION(mWriteCursor, "null mWriteCursor"); + mSegmentEnd = mWriteCursor + mSegmentSize; + + // Adjust write cursor for current segment offset. This test is necessary + // because SegNum may reference the next-to-be-allocated segment, in which + // case we need to be pointing at the end of the last segment. + int32_t segmentOffset = SegOffset(aPosition); + if (segmentOffset == 0 && (SegNum(aPosition) > (uint32_t)mLastSegmentNum)) { + mWriteCursor = mSegmentEnd; + } else { + mWriteCursor += segmentOffset; + } + + LOG(("nsStorageStream [%p] Seek mWriteCursor=%p mSegmentEnd=%p\n", this, + mWriteCursor, mSegmentEnd)); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// There can be many nsStorageInputStreams for a single nsStorageStream +class nsStorageInputStream final : public nsIInputStream, + public nsISeekableStream, + public nsIIPCSerializableInputStream, + public nsICloneableInputStream { + public: + nsStorageInputStream(nsStorageStream* aStorageStream, uint32_t aSegmentSize) + : mStorageStream(aStorageStream), + mReadCursor(0), + mSegmentEnd(0), + mSegmentNum(0), + mSegmentSize(aSegmentSize), + mLogicalCursor(0), + mStatus(NS_OK) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + private: + ~nsStorageInputStream() = default; + + protected: + nsresult Seek(uint32_t aPosition) MOZ_REQUIRES(mStorageStream->mMutex); + + friend class nsStorageStream; + + private: + RefPtr<nsStorageStream> mStorageStream; + uint32_t mReadCursor; // Next memory location to read byte, or 0 + uint32_t mSegmentEnd; // One byte past end of current buffer segment + uint32_t mSegmentNum; // Segment number containing read cursor + uint32_t mSegmentSize; // All segments, except the last, are of this size + uint32_t mLogicalCursor; // Logical offset into stream + nsresult mStatus; + + uint32_t SegNum(uint32_t aPosition) MOZ_REQUIRES(mStorageStream->mMutex) { + return aPosition >> mStorageStream->mSegmentSizeLog2; + } + uint32_t SegOffset(uint32_t aPosition) { + return aPosition & (mSegmentSize - 1); + } +}; + +NS_IMPL_ISUPPORTS(nsStorageInputStream, nsIInputStream, nsISeekableStream, + nsITellableStream, nsIIPCSerializableInputStream, + nsICloneableInputStream) + +NS_IMETHODIMP +nsStorageStream::NewInputStream(int32_t aStartingOffset, + nsIInputStream** aInputStream) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr<nsStorageInputStream> inputStream = + new nsStorageInputStream(this, mSegmentSize); + + inputStream->mStorageStream->mMutex.AssertCurrentThreadOwns(); + nsresult rv = inputStream->Seek(aStartingOffset); + if (NS_FAILED(rv)) { + return rv; + } + + inputStream.forget(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Close() { + mStatus = NS_BASE_STREAM_CLOSED; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Available(uint64_t* aAvailable) { + if (NS_FAILED(mStatus)) { + return mStatus; + } + + MutexAutoLock lock(mStorageStream->mMutex); + *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::StreamStatus() { return mStatus; } + +NS_IMETHODIMP +nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead) { + return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead); +} + +NS_IMETHODIMP +nsStorageInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aNumRead) { + *aNumRead = 0; + if (mStatus == NS_BASE_STREAM_CLOSED) { + return NS_OK; + } + if (NS_FAILED(mStatus)) { + return mStatus; + } + + uint32_t count, availableInSegment, remainingCapacity, bytesConsumed; + nsresult rv; + + remainingCapacity = aCount; + while (remainingCapacity) { + const char* cur = nullptr; + { + MutexAutoLock lock(mStorageStream->mMutex); + availableInSegment = mSegmentEnd - mReadCursor; + if (!availableInSegment) { + uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor; + if (!available) { + break; + } + + // We have data in the stream, but if mSegmentEnd is zero, then we + // were likely constructed prior to any data being written into + // the stream. Therefore, if mSegmentEnd is non-zero, we should + // move into the next segment; otherwise, we should stay in this + // segment so our input state can be updated and we can properly + // perform the initial read. + if (mSegmentEnd > 0) { + mSegmentNum++; + } + mReadCursor = 0; + mSegmentEnd = XPCOM_MIN(mSegmentSize, available); + availableInSegment = mSegmentEnd; + } + cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); + mStorageStream->mActiveSegmentBorrows++; + } + auto dropBorrow = mozilla::MakeScopeExit([&] { + MutexAutoLock lock(mStorageStream->mMutex); + mStorageStream->mActiveSegmentBorrows--; + }); + + count = XPCOM_MIN(availableInSegment, remainingCapacity); + rv = aWriter(this, aClosure, cur + mReadCursor, aCount - remainingCapacity, + count, &bytesConsumed); + if (NS_FAILED(rv) || (bytesConsumed == 0)) { + break; + } + remainingCapacity -= bytesConsumed; + mReadCursor += bytesConsumed; + mLogicalCursor += bytesConsumed; + } + + *aNumRead = aCount - remainingCapacity; + + bool isWriteInProgress = false; + if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress))) { + isWriteInProgress = false; + } + + if (*aNumRead == 0 && isWriteInProgress) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::IsNonBlocking(bool* aNonBlocking) { + // TODO: This class should implement nsIAsyncInputStream so that callers + // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors. + + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset) { + if (NS_FAILED(mStatus)) { + return mStatus; + } + + MutexAutoLock lock(mStorageStream->mMutex); + int64_t pos = aOffset; + + switch (aWhence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + pos += mLogicalCursor; + break; + case NS_SEEK_END: + pos += mStorageStream->mLogicalLength; + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected whence value"); + return NS_ERROR_UNEXPECTED; + } + if (pos == int64_t(mLogicalCursor)) { + return NS_OK; + } + + return Seek(pos); +} + +NS_IMETHODIMP +nsStorageInputStream::Tell(int64_t* aResult) { + if (NS_FAILED(mStatus)) { + return mStatus; + } + + *aResult = mLogicalCursor; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::SetEOF() { + MOZ_ASSERT_UNREACHABLE("nsStorageInputStream::SetEOF"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsStorageInputStream::Seek(uint32_t aPosition) { + uint32_t length = mStorageStream->mLogicalLength; + if (aPosition > length) { + return NS_ERROR_INVALID_ARG; + } + + if (length == 0) { + return NS_OK; + } + + mSegmentNum = SegNum(aPosition); + mReadCursor = SegOffset(aPosition); + uint32_t available = length - aPosition; + mSegmentEnd = mReadCursor + XPCOM_MIN(mSegmentSize - mReadCursor, available); + mLogicalCursor = aPosition; + return NS_OK; +} + +void nsStorageInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + uint64_t remaining = 0; + mozilla::DebugOnly<nsresult> rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (remaining >= aMaxSize) { + *aPipes = 1; + } else { + *aSizeUsed = remaining; + } +} + +void nsStorageInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + uint64_t remaining = 0; + mozilla::DebugOnly<nsresult> rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (remaining >= aMaxSize) { + mozilla::ipc::InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + *aSizeUsed = remaining; + + nsCString combined; + int64_t offset; + rv = Tell(&offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + auto handleOrErr = combined.BulkWrite(remaining, 0, false); + MOZ_ASSERT(!handleOrErr.isErr()); + + auto handle = handleOrErr.unwrap(); + + uint32_t numRead = 0; + + rv = Read(handle.Elements(), remaining, &numRead); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + MOZ_ASSERT(numRead == remaining); + handle.Finish(numRead, false); + + rv = Seek(NS_SEEK_SET, offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + StringInputStreamParams params; + params.data() = combined; + aParams = params; +} + +bool nsStorageInputStream::Deserialize(const InputStreamParams& aParams) { + MOZ_ASSERT_UNREACHABLE( + "We should never attempt to deserialize a storage " + "input stream."); + return false; +} + +NS_IMETHODIMP +nsStorageInputStream::GetCloneable(bool* aCloneableOut) { + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Clone(nsIInputStream** aCloneOut) { + return mStorageStream->NewInputStream(mLogicalCursor, aCloneOut); +} + +nsresult NS_NewStorageStream(uint32_t aSegmentSize, uint32_t aMaxSize, + nsIStorageStream** aResult) { + RefPtr<nsStorageStream> storageStream = new nsStorageStream(); + nsresult rv = storageStream->Init(aSegmentSize, aMaxSize); + if (NS_FAILED(rv)) { + return rv; + } + storageStream.forget(aResult); + return NS_OK; +} + +// Undefine LOG, so that other .cpp files (or their includes) won't complain +// about it already being defined, when we build in unified mode. +#undef LOG diff --git a/xpcom/io/nsStorageStream.h b/xpcom/io/nsStorageStream.h new file mode 100644 index 0000000000..78265b4c6b --- /dev/null +++ b/xpcom/io/nsStorageStream.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/. */ + +/* + * The storage stream provides an internal buffer that can be filled by a + * client using a single output stream. One or more independent input streams + * can be created to read the data out non-destructively. The implementation + * uses a segmented buffer internally to avoid realloc'ing of large buffers, + * with the attendant performance loss and heap fragmentation. + */ + +#ifndef _nsStorageStream_h_ +#define _nsStorageStream_h_ + +#include "nsIStorageStream.h" +#include "nsIOutputStream.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +#define NS_STORAGESTREAM_CID \ + { /* 669a9795-6ff7-4ed4-9150-c34ce2971b63 */ \ + 0x669a9795, 0x6ff7, 0x4ed4, { \ + 0x91, 0x50, 0xc3, 0x4c, 0xe2, 0x97, 0x1b, 0x63 \ + } \ + } + +#define NS_STORAGESTREAM_CONTRACTID "@mozilla.org/storagestream;1" + +class nsSegmentedBuffer; + +class nsStorageStream final : public nsIStorageStream, public nsIOutputStream { + public: + nsStorageStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTORAGESTREAM + NS_DECL_NSIOUTPUTSTREAM + + friend class nsStorageInputStream; + + private: + ~nsStorageStream(); + + mozilla::Mutex mMutex{"nsStorageStream"}; + nsSegmentedBuffer* mSegmentedBuffer MOZ_GUARDED_BY(mMutex) = nullptr; + // All segments, except possibly the last, are of this size. Must be + // power-of-2 + uint32_t mSegmentSize MOZ_GUARDED_BY(mMutex) = 0; + // log2(mSegmentSize) + uint32_t mSegmentSizeLog2 MOZ_GUARDED_BY(mMutex) = 0; + // true, if an un-Close'ed output stream exists + bool mWriteInProgress MOZ_GUARDED_BY(mMutex) = false; + // Last segment # in use, -1 initially + int32_t mLastSegmentNum MOZ_GUARDED_BY(mMutex) = -1; + // Pointer to next byte to be written + char* mWriteCursor MOZ_GUARDED_BY(mMutex) = nullptr; + // Pointer to one byte after end of segment containing the write cursor + char* mSegmentEnd MOZ_GUARDED_BY(mMutex) = nullptr; + // Maximum number of bytes which may be written to the stream + uint32_t mMaxLogicalLength MOZ_GUARDED_BY(mMutex) = 0; + // Number of bytes written to stream + uint32_t mLogicalLength MOZ_GUARDED_BY(mMutex) = 0; + // number of input streams actively reading a segment. + uint32_t mActiveSegmentBorrows MOZ_GUARDED_BY(mMutex) = 0; + + nsresult SetLengthLocked(uint32_t aLength) MOZ_REQUIRES(mMutex); + nsresult Seek(int32_t aPosition) MOZ_REQUIRES(mMutex); + uint32_t SegNum(uint32_t aPosition) MOZ_REQUIRES(mMutex) { + return aPosition >> mSegmentSizeLog2; + } + uint32_t SegOffset(uint32_t aPosition) MOZ_REQUIRES(mMutex) { + return aPosition & (mSegmentSize - 1); + } +}; + +#endif // _nsStorageStream_h_ diff --git a/xpcom/io/nsStreamUtils.cpp b/xpcom/io/nsStreamUtils.cpp new file mode 100644 index 0000000000..511e0fa300 --- /dev/null +++ b/xpcom/io/nsStreamUtils.cpp @@ -0,0 +1,976 @@ +/* -*- 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/Mutex.h" +#include "mozilla/Attributes.h" +#include "mozilla/InputStreamLengthWrapper.h" +#include "nsIInputStreamLength.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" +#include "nsICloneableInputStream.h" +#include "nsIEventTarget.h" +#include "nsICancelableRunnable.h" +#include "nsISafeOutputStream.h" +#include "nsString.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBufferedStreams.h" +#include "nsIPipe.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsITransport.h" +#include "nsIStreamTransportService.h" +#include "NonBlockingAsyncInputStream.h" + +using namespace mozilla; + +static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); + +//----------------------------------------------------------------------------- + +// This is a nsICancelableRunnable because we can dispatch it to Workers and +// those can be shut down at any time, and in these cases, Cancel() is called +// instead of Run(). +class nsInputStreamReadyEvent final : public CancelableRunnable, + public nsIInputStreamCallback, + public nsIRunnablePriority { + public: + NS_DECL_ISUPPORTS_INHERITED + + nsInputStreamReadyEvent(const char* aName, nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget, uint32_t aPriority) + : CancelableRunnable(aName), + mCallback(aCallback), + mTarget(aTarget), + mPriority(aPriority) {} + + private: + ~nsInputStreamReadyEvent() { + if (!mCallback) { + return; + } + // + // whoa!! looks like we never posted this event. take care to + // release mCallback on the correct thread. if mTarget lives on the + // calling thread, then we are ok. otherwise, we have to try to + // proxy the Release over the right thread. if that thread is dead, + // then there's nothing we can do... better to leak than crash. + // + bool val; + nsresult rv = mTarget->IsOnCurrentThread(&val); + if (NS_FAILED(rv) || !val) { + nsCOMPtr<nsIInputStreamCallback> event = NS_NewInputStreamReadyEvent( + "~nsInputStreamReadyEvent", mCallback, mTarget, mPriority); + mCallback = nullptr; + if (event) { + rv = event->OnInputStreamReady(nullptr); + if (NS_FAILED(rv)) { + MOZ_ASSERT_UNREACHABLE("leaking stream event"); + nsISupports* sup = event; + NS_ADDREF(sup); + } + } + } + } + + public: + NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aStream) override { + mStream = aStream; + + nsresult rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Dispatch failed"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + NS_IMETHOD Run() override { + if (mCallback) { + if (mStream) { + mCallback->OnInputStreamReady(mStream); + } + mCallback = nullptr; + } + return NS_OK; + } + + nsresult Cancel() override { + mCallback = nullptr; + return NS_OK; + } + + NS_IMETHOD GetPriority(uint32_t* aPriority) override { + *aPriority = mPriority; + return NS_OK; + } + + private: + nsCOMPtr<nsIAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mTarget; + uint32_t mPriority; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamReadyEvent, CancelableRunnable, + nsIInputStreamCallback, nsIRunnablePriority) + +//----------------------------------------------------------------------------- + +// This is a nsICancelableRunnable because we can dispatch it to Workers and +// those can be shut down at any time, and in these cases, Cancel() is called +// instead of Run(). +class nsOutputStreamReadyEvent final : public CancelableRunnable, + public nsIOutputStreamCallback { + public: + NS_DECL_ISUPPORTS_INHERITED + + nsOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback, + nsIEventTarget* aTarget) + : CancelableRunnable("nsOutputStreamReadyEvent"), + mCallback(aCallback), + mTarget(aTarget) {} + + private: + ~nsOutputStreamReadyEvent() { + if (!mCallback) { + return; + } + // + // whoa!! looks like we never posted this event. take care to + // release mCallback on the correct thread. if mTarget lives on the + // calling thread, then we are ok. otherwise, we have to try to + // proxy the Release over the right thread. if that thread is dead, + // then there's nothing we can do... better to leak than crash. + // + bool val; + nsresult rv = mTarget->IsOnCurrentThread(&val); + if (NS_FAILED(rv) || !val) { + nsCOMPtr<nsIOutputStreamCallback> event = + NS_NewOutputStreamReadyEvent(mCallback, mTarget); + mCallback = nullptr; + if (event) { + rv = event->OnOutputStreamReady(nullptr); + if (NS_FAILED(rv)) { + MOZ_ASSERT_UNREACHABLE("leaking stream event"); + nsISupports* sup = event; + NS_ADDREF(sup); + } + } + } + } + + public: + NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aStream) override { + mStream = aStream; + + nsresult rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("PostEvent failed"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + NS_IMETHOD Run() override { + if (mCallback) { + if (mStream) { + mCallback->OnOutputStreamReady(mStream); + } + mCallback = nullptr; + } + return NS_OK; + } + + nsresult Cancel() override { + mCallback = nullptr; + return NS_OK; + } + + private: + nsCOMPtr<nsIAsyncOutputStream> mStream; + nsCOMPtr<nsIOutputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsOutputStreamReadyEvent, CancelableRunnable, + nsIOutputStreamCallback) + +//----------------------------------------------------------------------------- + +already_AddRefed<nsIInputStreamCallback> NS_NewInputStreamReadyEvent( + const char* aName, nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget, uint32_t aPriority) { + NS_ASSERTION(aCallback, "null callback"); + NS_ASSERTION(aTarget, "null target"); + RefPtr<nsInputStreamReadyEvent> ev = + new nsInputStreamReadyEvent(aName, aCallback, aTarget, aPriority); + return ev.forget(); +} + +already_AddRefed<nsIOutputStreamCallback> NS_NewOutputStreamReadyEvent( + nsIOutputStreamCallback* aCallback, nsIEventTarget* aTarget) { + NS_ASSERTION(aCallback, "null callback"); + NS_ASSERTION(aTarget, "null target"); + RefPtr<nsOutputStreamReadyEvent> ev = + new nsOutputStreamReadyEvent(aCallback, aTarget); + return ev.forget(); +} + +//----------------------------------------------------------------------------- +// NS_AsyncCopy implementation + +// abstract stream copier... +class nsAStreamCopier : public nsIInputStreamCallback, + public nsIOutputStreamCallback, + public CancelableRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + + nsAStreamCopier() + : CancelableRunnable("nsAStreamCopier"), + mLock("nsAStreamCopier.mLock"), + mCallback(nullptr), + mProgressCallback(nullptr), + mClosure(nullptr), + mChunkSize(0), + mEventInProcess(false), + mEventIsPending(false), + mCloseSource(true), + mCloseSink(true), + mCanceled(false), + mCancelStatus(NS_OK) {} + + // kick off the async copy... + nsresult Start(nsIInputStream* aSource, nsIOutputStream* aSink, + nsIEventTarget* aTarget, nsAsyncCopyCallbackFun aCallback, + void* aClosure, uint32_t aChunksize, bool aCloseSource, + bool aCloseSink, nsAsyncCopyProgressFun aProgressCallback) { + mSource = aSource; + mSink = aSink; + mTarget = aTarget; + mCallback = aCallback; + mClosure = aClosure; + mChunkSize = aChunksize; + mCloseSource = aCloseSource; + mCloseSink = aCloseSink; + mProgressCallback = aProgressCallback; + + mAsyncSource = do_QueryInterface(mSource); + mAsyncSink = do_QueryInterface(mSink); + + return PostContinuationEvent(); + } + + // implemented by subclasses, returns number of bytes copied and + // sets source and sink condition before returning. + virtual uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) = 0; + + void Process() { + if (!mSource || !mSink) { + return; + } + + nsresult cancelStatus; + bool canceled; + { + MutexAutoLock lock(mLock); + canceled = mCanceled; + cancelStatus = mCancelStatus; + } + + // If the copy was canceled before Process() was even called, then + // sourceCondition and sinkCondition should be set to error results to + // ensure we don't call Finish() on a canceled nsISafeOutputStream. + MOZ_ASSERT(NS_FAILED(cancelStatus) == canceled, "cancel needs an error"); + nsresult sourceCondition = cancelStatus; + nsresult sinkCondition = cancelStatus; + + // Copy data from the source to the sink until we hit failure or have + // copied all the data. + for (;;) { + // Note: copyFailed will be true if the source or the sink have + // reported an error, or if we failed to write any bytes + // because we have consumed all of our data. + bool copyFailed = false; + if (!canceled) { + uint32_t n = DoCopy(&sourceCondition, &sinkCondition); + if (n > 0 && mProgressCallback) { + mProgressCallback(mClosure, n); + } + copyFailed = + NS_FAILED(sourceCondition) || NS_FAILED(sinkCondition) || n == 0; + + MutexAutoLock lock(mLock); + canceled = mCanceled; + cancelStatus = mCancelStatus; + } + if (copyFailed && !canceled) { + if (sourceCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSource) { + // need to wait for more data from source. while waiting for + // more source data, be sure to observe failures on output end. + mAsyncSource->AsyncWait(this, 0, 0, nullptr); + + if (mAsyncSink) { + mAsyncSink->AsyncWait(this, nsIAsyncOutputStream::WAIT_CLOSURE_ONLY, + 0, nullptr); + } + break; + } + if (sinkCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSink) { + // need to wait for more room in the sink. while waiting for + // more room in the sink, be sure to observer failures on the + // input end. + mAsyncSink->AsyncWait(this, 0, 0, nullptr); + + if (mAsyncSource) { + mAsyncSource->AsyncWait( + this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, nullptr); + } + break; + } + } + if (copyFailed || canceled) { + if (mAsyncSource) { + // cancel any previously-registered AsyncWait callbacks to avoid leaks + mAsyncSource->AsyncWait(nullptr, 0, 0, nullptr); + } + if (mCloseSource) { + // close source + if (mAsyncSource) { + mAsyncSource->CloseWithStatus(canceled ? cancelStatus + : sinkCondition); + } else { + mSource->Close(); + } + } + mAsyncSource = nullptr; + mSource = nullptr; + + if (mAsyncSink) { + // cancel any previously-registered AsyncWait callbacks to avoid leaks + mAsyncSink->AsyncWait(nullptr, 0, 0, nullptr); + } + if (mCloseSink) { + // close sink + if (mAsyncSink) { + mAsyncSink->CloseWithStatus(canceled ? cancelStatus + : sourceCondition); + } else { + // If we have an nsISafeOutputStream, and our + // sourceCondition and sinkCondition are not set to a + // failure state, finish writing. + nsCOMPtr<nsISafeOutputStream> sostream = do_QueryInterface(mSink); + if (sostream && NS_SUCCEEDED(sourceCondition) && + NS_SUCCEEDED(sinkCondition)) { + sostream->Finish(); + } else { + mSink->Close(); + } + } + } + mAsyncSink = nullptr; + mSink = nullptr; + + // notify state complete... + if (mCallback) { + nsresult status; + if (!canceled) { + status = sourceCondition; + if (NS_SUCCEEDED(status)) { + status = sinkCondition; + } + if (status == NS_BASE_STREAM_CLOSED) { + status = NS_OK; + } + } else { + status = cancelStatus; + } + mCallback(mClosure, status); + } + break; + } + } + } + + nsresult Cancel(nsresult aReason) { + MutexAutoLock lock(mLock); + if (mCanceled) { + return NS_ERROR_FAILURE; + } + + if (NS_SUCCEEDED(aReason)) { + NS_WARNING("cancel with non-failure status code"); + aReason = NS_BASE_STREAM_CLOSED; + } + + mCanceled = true; + mCancelStatus = aReason; + return NS_OK; + } + + NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aSource) override { + PostContinuationEvent(); + return NS_OK; + } + + NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aSink) override { + PostContinuationEvent(); + return NS_OK; + } + + // continuation event handler + NS_IMETHOD Run() override { + Process(); + + // clear "in process" flag and post any pending continuation event + MutexAutoLock lock(mLock); + mEventInProcess = false; + if (mEventIsPending) { + mEventIsPending = false; + PostContinuationEvent_Locked(); + } + + return NS_OK; + } + + nsresult Cancel() MOZ_MUST_OVERRIDE override = 0; + + nsresult PostContinuationEvent() { + // we cannot post a continuation event if there is currently + // an event in process. doing so could result in Process being + // run simultaneously on multiple threads, so we mark the event + // as pending, and if an event is already in process then we + // just let that existing event take care of posting the real + // continuation event. + + MutexAutoLock lock(mLock); + return PostContinuationEvent_Locked(); + } + + nsresult PostContinuationEvent_Locked() MOZ_REQUIRES(mLock) { + nsresult rv = NS_OK; + if (mEventInProcess) { + mEventIsPending = true; + } else { + rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) { + mEventInProcess = true; + } else { + NS_WARNING("unable to post continuation event"); + } + } + return rv; + } + + protected: + nsCOMPtr<nsIInputStream> mSource; + nsCOMPtr<nsIOutputStream> mSink; + nsCOMPtr<nsIAsyncInputStream> mAsyncSource; + nsCOMPtr<nsIAsyncOutputStream> mAsyncSink; + nsCOMPtr<nsIEventTarget> mTarget; + Mutex mLock; + nsAsyncCopyCallbackFun mCallback; + nsAsyncCopyProgressFun mProgressCallback; + void* mClosure; + uint32_t mChunkSize; + bool mEventInProcess MOZ_GUARDED_BY(mLock); + bool mEventIsPending MOZ_GUARDED_BY(mLock); + bool mCloseSource; + bool mCloseSink; + bool mCanceled MOZ_GUARDED_BY(mLock); + nsresult mCancelStatus MOZ_GUARDED_BY(mLock); + + // virtual since subclasses call superclass Release() + virtual ~nsAStreamCopier() = default; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsAStreamCopier, CancelableRunnable, + nsIInputStreamCallback, nsIOutputStreamCallback) + +class nsStreamCopierIB final : public nsAStreamCopier { + public: + nsStreamCopierIB() = default; + virtual ~nsStreamCopierIB() = default; + + struct MOZ_STACK_CLASS ReadSegmentsState { + // the nsIOutputStream will outlive the ReadSegmentsState on the stack + nsIOutputStream* MOZ_NON_OWNING_REF mSink; + nsresult mSinkCondition; + }; + + static nsresult ConsumeInputBuffer(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + ReadSegmentsState* state = (ReadSegmentsState*)aClosure; + + nsresult rv = state->mSink->Write(aBuffer, aCount, aCountWritten); + if (NS_FAILED(rv)) { + state->mSinkCondition = rv; + } else if (*aCountWritten == 0) { + state->mSinkCondition = NS_BASE_STREAM_CLOSED; + } + + return state->mSinkCondition; + } + + uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) override { + ReadSegmentsState state; + state.mSink = mSink; + state.mSinkCondition = NS_OK; + + uint32_t n; + *aSourceCondition = + mSource->ReadSegments(ConsumeInputBuffer, &state, mChunkSize, &n); + *aSinkCondition = NS_SUCCEEDED(state.mSinkCondition) && n == 0 + ? mSink->StreamStatus() + : state.mSinkCondition; + return n; + } + + nsresult Cancel() override { return NS_OK; } +}; + +class nsStreamCopierOB final : public nsAStreamCopier { + public: + nsStreamCopierOB() = default; + virtual ~nsStreamCopierOB() = default; + + struct MOZ_STACK_CLASS WriteSegmentsState { + // the nsIInputStream will outlive the WriteSegmentsState on the stack + nsIInputStream* MOZ_NON_OWNING_REF mSource; + nsresult mSourceCondition; + }; + + static nsresult FillOutputBuffer(nsIOutputStream* aOutStr, void* aClosure, + char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountRead) { + WriteSegmentsState* state = (WriteSegmentsState*)aClosure; + + nsresult rv = state->mSource->Read(aBuffer, aCount, aCountRead); + if (NS_FAILED(rv)) { + state->mSourceCondition = rv; + } else if (*aCountRead == 0) { + state->mSourceCondition = NS_BASE_STREAM_CLOSED; + } + + return state->mSourceCondition; + } + + uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) override { + WriteSegmentsState state; + state.mSource = mSource; + state.mSourceCondition = NS_OK; + + uint32_t n; + *aSinkCondition = + mSink->WriteSegments(FillOutputBuffer, &state, mChunkSize, &n); + *aSourceCondition = NS_SUCCEEDED(state.mSourceCondition) && n == 0 + ? mSource->StreamStatus() + : state.mSourceCondition; + return n; + } + + nsresult Cancel() override { return NS_OK; } +}; + +//----------------------------------------------------------------------------- + +nsresult NS_AsyncCopy(nsIInputStream* aSource, nsIOutputStream* aSink, + nsIEventTarget* aTarget, nsAsyncCopyMode aMode, + uint32_t aChunkSize, nsAsyncCopyCallbackFun aCallback, + void* aClosure, bool aCloseSource, bool aCloseSink, + nsISupports** aCopierCtx, + nsAsyncCopyProgressFun aProgressCallback) { + NS_ASSERTION(aTarget, "non-null target required"); + + nsresult rv; + nsAStreamCopier* copier; + + if (aMode == NS_ASYNCCOPY_VIA_READSEGMENTS) { + copier = new nsStreamCopierIB(); + } else { + copier = new nsStreamCopierOB(); + } + + // Start() takes an owning ref to the copier... + NS_ADDREF(copier); + rv = copier->Start(aSource, aSink, aTarget, aCallback, aClosure, aChunkSize, + aCloseSource, aCloseSink, aProgressCallback); + + if (aCopierCtx) { + *aCopierCtx = static_cast<nsISupports*>(static_cast<nsIRunnable*>(copier)); + NS_ADDREF(*aCopierCtx); + } + NS_RELEASE(copier); + + return rv; +} + +//----------------------------------------------------------------------------- + +nsresult NS_CancelAsyncCopy(nsISupports* aCopierCtx, nsresult aReason) { + nsAStreamCopier* copier = + static_cast<nsAStreamCopier*>(static_cast<nsIRunnable*>(aCopierCtx)); + return copier->Cancel(aReason); +} + +//----------------------------------------------------------------------------- + +namespace { +template <typename T> +struct ResultTraits {}; + +template <> +struct ResultTraits<nsACString> { + static void Clear(nsACString& aString) { aString.Truncate(); } + + static char* GetStorage(nsACString& aString) { + return aString.BeginWriting(); + } +}; + +template <> +struct ResultTraits<nsTArray<uint8_t>> { + static void Clear(nsTArray<uint8_t>& aArray) { aArray.Clear(); } + + static char* GetStorage(nsTArray<uint8_t>& aArray) { + return reinterpret_cast<char*>(aArray.Elements()); + } +}; +} // namespace + +template <typename T> +nsresult DoConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount, + T& aResult) { + nsresult rv = NS_OK; + ResultTraits<T>::Clear(aResult); + + while (aMaxCount) { + uint64_t avail64; + rv = aStream->Available(&avail64); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + } + break; + } + if (avail64 == 0) { + break; + } + + uint32_t avail = (uint32_t)XPCOM_MIN<uint64_t>(avail64, aMaxCount); + + // resize aResult buffer + uint32_t length = aResult.Length(); + CheckedInt<uint32_t> newLength = CheckedInt<uint32_t>(length) + avail; + if (!newLength.isValid()) { + return NS_ERROR_FILE_TOO_BIG; + } + + if (!aResult.SetLength(newLength.value(), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + char* buf = ResultTraits<T>::GetStorage(aResult) + length; + + uint32_t n; + rv = aStream->Read(buf, avail, &n); + if (NS_FAILED(rv)) { + break; + } + if (n != avail) { + MOZ_ASSERT(n < avail, "What happened there???"); + aResult.SetLength(length + n); + } + if (n == 0) { + break; + } + aMaxCount -= n; + } + + return rv; +} + +nsresult NS_ConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount, + nsACString& aResult) { + return DoConsumeStream(aStream, aMaxCount, aResult); +} + +nsresult NS_ConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount, + nsTArray<uint8_t>& aResult) { + return DoConsumeStream(aStream, aMaxCount, aResult); +} + +//----------------------------------------------------------------------------- + +static nsresult TestInputStream(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + bool* result = static_cast<bool*>(aClosure); + *result = true; + *aCountWritten = 0; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool NS_InputStreamIsBuffered(nsIInputStream* aStream) { + nsCOMPtr<nsIBufferedInputStream> bufferedIn = do_QueryInterface(aStream); + if (bufferedIn) { + return true; + } + + bool result = false; + uint32_t n; + nsresult rv = aStream->ReadSegments(TestInputStream, &result, 1, &n); + return result || rv != NS_ERROR_NOT_IMPLEMENTED; +} + +static nsresult TestOutputStream(nsIOutputStream* aOutStr, void* aClosure, + char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountRead) { + bool* result = static_cast<bool*>(aClosure); + *result = true; + *aCountRead = 0; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool NS_OutputStreamIsBuffered(nsIOutputStream* aStream) { + nsCOMPtr<nsIBufferedOutputStream> bufferedOut = do_QueryInterface(aStream); + if (bufferedOut) { + return true; + } + + bool result = false; + uint32_t n; + aStream->WriteSegments(TestOutputStream, &result, 1, &n); + return result; +} + +//----------------------------------------------------------------------------- + +nsresult NS_CopySegmentToStream(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + nsIOutputStream* outStr = static_cast<nsIOutputStream*>(aClosure); + *aCountWritten = 0; + while (aCount) { + uint32_t n; + nsresult rv = outStr->Write(aBuffer, aCount, &n); + if (NS_FAILED(rv)) { + return rv; + } + aBuffer += n; + aCount -= n; + *aCountWritten += n; + } + return NS_OK; +} + +nsresult NS_CopySegmentToBuffer(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + char* toBuf = static_cast<char*>(aClosure); + memcpy(&toBuf[aOffset], aBuffer, aCount); + *aCountWritten = aCount; + return NS_OK; +} + +nsresult NS_CopyBufferToSegment(nsIOutputStream* aOutStr, void* aClosure, + char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountRead) { + const char* fromBuf = static_cast<const char*>(aClosure); + memcpy(aBuffer, &fromBuf[aOffset], aCount); + *aCountRead = aCount; + return NS_OK; +} + +nsresult NS_CopyStreamToSegment(nsIOutputStream* aOutputStream, void* aClosure, + char* aToSegment, uint32_t aFromOffset, + uint32_t aCount, uint32_t* aReadCount) { + nsIInputStream* fromStream = static_cast<nsIInputStream*>(aClosure); + return fromStream->Read(aToSegment, aCount, aReadCount); +} + +nsresult NS_DiscardSegment(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + *aCountWritten = aCount; + return NS_OK; +} + +//----------------------------------------------------------------------------- + +nsresult NS_WriteSegmentThunk(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + nsWriteSegmentThunk* thunk = static_cast<nsWriteSegmentThunk*>(aClosure); + return thunk->mFun(thunk->mStream, thunk->mClosure, aBuffer, aOffset, aCount, + aCountWritten); +} + +nsresult NS_FillArray(FallibleTArray<char>& aDest, nsIInputStream* aInput, + uint32_t aKeep, uint32_t* aNewBytes) { + MOZ_ASSERT(aInput, "null stream"); + MOZ_ASSERT(aKeep <= aDest.Length(), "illegal keep count"); + + char* aBuffer = aDest.Elements(); + int64_t keepOffset = int64_t(aDest.Length()) - aKeep; + if (aKeep != 0 && keepOffset > 0) { + memmove(aBuffer, aBuffer + keepOffset, aKeep); + } + + nsresult rv = + aInput->Read(aBuffer + aKeep, aDest.Capacity() - aKeep, aNewBytes); + if (NS_FAILED(rv)) { + *aNewBytes = 0; + } + // NOTE: we rely on the fact that the new slots are NOT initialized by + // SetLengthAndRetainStorage here, see nsTArrayElementTraits::Construct() + // in nsTArray.h: + aDest.SetLengthAndRetainStorage(aKeep + *aNewBytes); + + MOZ_ASSERT(aDest.Length() <= aDest.Capacity(), "buffer overflow"); + return rv; +} + +bool NS_InputStreamIsCloneable(nsIInputStream* aSource) { + if (!aSource) { + return false; + } + + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource); + return cloneable && cloneable->GetCloneable(); +} + +nsresult NS_CloneInputStream(nsIInputStream* aSource, + nsIInputStream** aCloneOut, + nsIInputStream** aReplacementOut) { + if (NS_WARN_IF(!aSource)) { + return NS_ERROR_FAILURE; + } + + // Attempt to perform the clone directly on the source stream + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource); + if (cloneable && cloneable->GetCloneable()) { + if (aReplacementOut) { + *aReplacementOut = nullptr; + } + return cloneable->Clone(aCloneOut); + } + + // If we failed the clone and the caller does not want to replace their + // original stream, then we are done. Return error. + if (!aReplacementOut) { + return NS_ERROR_FAILURE; + } + + // The caller has opted-in to the fallback clone support that replaces + // the original stream. Copy the data to a pipe and return two cloned + // input streams. + + nsCOMPtr<nsIInputStream> reader; + nsCOMPtr<nsIInputStream> readerClone; + nsCOMPtr<nsIOutputStream> writer; + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), 0, + 0, // default segment size and max size + true, true); // non-blocking + + // Propagate length information provided by nsIInputStreamLength. We don't use + // InputStreamLengthHelper::GetSyncLength to avoid the risk of blocking when + // called off-main-thread. + int64_t length = -1; + if (nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(aSource); + streamLength && NS_SUCCEEDED(streamLength->Length(&length)) && + length != -1) { + reader = new mozilla::InputStreamLengthWrapper(reader.forget(), length); + } + + cloneable = do_QueryInterface(reader); + MOZ_ASSERT(cloneable && cloneable->GetCloneable()); + + nsresult rv = cloneable->Clone(getter_AddRefs(readerClone)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = NS_AsyncCopy(aSource, writer, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + readerClone.forget(aCloneOut); + reader.forget(aReplacementOut); + + return NS_OK; +} + +nsresult NS_MakeAsyncNonBlockingInputStream( + already_AddRefed<nsIInputStream> aSource, + nsIAsyncInputStream** aAsyncInputStream, bool aCloseWhenDone, + uint32_t aFlags, uint32_t aSegmentSize, uint32_t aSegmentCount) { + nsCOMPtr<nsIInputStream> source = std::move(aSource); + if (NS_WARN_IF(!aAsyncInputStream)) { + return NS_ERROR_FAILURE; + } + + bool nonBlocking = false; + nsresult rv = source->IsNonBlocking(&nonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(source); + + if (nonBlocking && asyncStream) { + // This stream is perfect! + asyncStream.forget(aAsyncInputStream); + return NS_OK; + } + + if (nonBlocking) { + // If the stream is non-blocking but not async, we wrap it. + return NonBlockingAsyncInputStream::Create(source.forget(), + aAsyncInputStream); + } + + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(kStreamTransportServiceCID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsITransport> transport; + rv = sts->CreateInputTransport(source, aCloseWhenDone, + getter_AddRefs(transport)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> wrapper; + rv = transport->OpenInputStream(aFlags, aSegmentSize, aSegmentCount, + getter_AddRefs(wrapper)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncStream = do_QueryInterface(wrapper); + MOZ_ASSERT(asyncStream); + + asyncStream.forget(aAsyncInputStream); + return NS_OK; +} diff --git a/xpcom/io/nsStreamUtils.h b/xpcom/io/nsStreamUtils.h new file mode 100644 index 0000000000..f274d9134a --- /dev/null +++ b/xpcom/io/nsStreamUtils.h @@ -0,0 +1,332 @@ +/* -*- 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 nsStreamUtils_h__ +#define nsStreamUtils_h__ + +#include "nsCOMPtr.h" +#include "nsStringFwd.h" +#include "nsIInputStream.h" +#include "nsTArray.h" +#include "nsIRunnable.h" + +class nsIAsyncInputStream; +class nsIOutputStream; +class nsIInputStreamCallback; +class nsIOutputStreamCallback; +class nsIEventTarget; + +/** + * A "one-shot" proxy of the OnInputStreamReady callback. The resulting + * proxy object's OnInputStreamReady function may only be called once! The + * proxy object ensures that the real notify object will be free'd on the + * thread corresponding to the given event target regardless of what thread + * the proxy object is destroyed on. + * + * This function is designed to be used to implement AsyncWait when the + * aTarget parameter is non-null. + * + * The optional aPriority parameter allows the input stream runnable events + * to be dispatched with a different priority than normal. + */ +extern already_AddRefed<nsIInputStreamCallback> NS_NewInputStreamReadyEvent( + const char* aName, nsIInputStreamCallback* aNotify, nsIEventTarget* aTarget, + uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL); + +/** + * A "one-shot" proxy of the OnOutputStreamReady callback. The resulting + * proxy object's OnOutputStreamReady function may only be called once! The + * proxy object ensures that the real notify object will be free'd on the + * thread corresponding to the given event target regardless of what thread + * the proxy object is destroyed on. + * + * This function is designed to be used to implement AsyncWait when the + * aTarget parameter is non-null. + */ +extern already_AddRefed<nsIOutputStreamCallback> NS_NewOutputStreamReadyEvent( + nsIOutputStreamCallback* aNotify, nsIEventTarget* aTarget); + +/* ------------------------------------------------------------------------- */ + +enum nsAsyncCopyMode { + NS_ASYNCCOPY_VIA_READSEGMENTS, + NS_ASYNCCOPY_VIA_WRITESEGMENTS +}; + +/** + * This function is called when a new chunk of data has been copied. The + * reported count is the size of the current chunk. + */ +typedef void (*nsAsyncCopyProgressFun)(void* closure, uint32_t count); + +/** + * This function is called when the async copy process completes. The reported + * status is NS_OK on success and some error code on failure. + */ +typedef void (*nsAsyncCopyCallbackFun)(void* closure, nsresult status); + +/** + * This function asynchronously copies data from the source to the sink. All + * data transfer occurs on the thread corresponding to the given event target. + * A null event target is not permitted. + * + * The copier handles blocking or non-blocking streams transparently. If a + * stream operation returns NS_BASE_STREAM_WOULD_BLOCK, then the stream will + * be QI'd to nsIAsync{In,Out}putStream and its AsyncWait method will be used + * to determine when to resume copying. + * + * Source and sink are closed by default when copying finishes or when error + * occurs. Caller can prevent closing source or sink by setting aCloseSource + * or aCloseSink to false. + * + * Caller can obtain aCopierCtx to be able to cancel copying. + */ +extern nsresult NS_AsyncCopy( + nsIInputStream* aSource, nsIOutputStream* aSink, nsIEventTarget* aTarget, + nsAsyncCopyMode aMode = NS_ASYNCCOPY_VIA_READSEGMENTS, + uint32_t aChunkSize = 4096, nsAsyncCopyCallbackFun aCallbackFun = nullptr, + void* aCallbackClosure = nullptr, bool aCloseSource = true, + bool aCloseSink = true, nsISupports** aCopierCtx = nullptr, + nsAsyncCopyProgressFun aProgressCallbackFun = nullptr); + +/** + * This function cancels copying started by function NS_AsyncCopy. + * + * @param aCopierCtx + * Copier context returned by NS_AsyncCopy. + * @param aReason + * A failure code indicating why the operation is being canceled. + * It is an error to pass a success code. + */ +extern nsresult NS_CancelAsyncCopy(nsISupports* aCopierCtx, nsresult aReason); + +/** + * This function copies all of the available data from the stream (up to at + * most aMaxCount bytes) into the given buffer. The buffer is truncated at + * the start of the function. + * + * If an error occurs while reading from the stream or while attempting to + * resize the buffer, then the corresponding error code is returned from this + * function, and any data that has already been read will be returned in the + * output buffer. This allows one to use this function with a non-blocking + * input stream that may return NS_BASE_STREAM_WOULD_BLOCK if it only has + * partial data available. + * + * @param aSource + * The input stream to read. + * @param aMaxCount + * The maximum number of bytes to consume from the stream. Pass the + * value UINT32_MAX to consume the entire stream. The number of + * bytes actually read is given by the length of aBuffer upon return. + * @param aBuffer + * The string object that will contain the stream data upon return. + * Note: The data copied to the string may contain null bytes and may + * contain non-ASCII values. + */ +extern nsresult NS_ConsumeStream(nsIInputStream* aSource, uint32_t aMaxCount, + nsACString& aBuffer); + +/** + * Just like the above, but consumes into an nsTArray<uint8_t>. + */ +extern nsresult NS_ConsumeStream(nsIInputStream* aSource, uint32_t aMaxCount, + nsTArray<uint8_t>& aBuffer); + +/** + * This function tests whether or not the input stream is buffered. A buffered + * input stream is one that implements readSegments. The test for this is to + * 1/ check whether the input stream implements nsIBufferedInputStream; + * 2/ if not, call readSegments, without actually consuming any data from the + * stream, to verify that it functions. + * + * NOTE: If the stream is non-blocking and has no data available yet, then this + * test will fail. In that case, we return false even though the test is not + * really conclusive. + * + * PERFORMANCE NOTE: If the stream does not implement nsIBufferedInputStream, + * calling readSegments may cause I/O. Therefore, you should avoid calling + * this function from the main thread. + * + * @param aInputStream + * The input stream to test. + */ +extern bool NS_InputStreamIsBuffered(nsIInputStream* aInputStream); + +/** + * This function tests whether or not the output stream is buffered. A + * buffered output stream is one that implements writeSegments. The test for + * this is to: + * 1/ check whether the output stream implements nsIBufferedOutputStream; + * 2/ if not, call writeSegments, without actually writing any data into + * the stream, to verify that it functions. + * + * NOTE: If the stream is non-blocking and has no available space yet, then + * this test will fail. In that case, we return false even though the test is + * not really conclusive. + * + * PERFORMANCE NOTE: If the stream does not implement nsIBufferedOutputStream, + * calling writeSegments may cause I/O. Therefore, you should avoid calling + * this function from the main thread. + * + * @param aOutputStream + * The output stream to test. + */ +extern bool NS_OutputStreamIsBuffered(nsIOutputStream* aOutputStream); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * copy data from the nsIInputStream into a nsIOutputStream passed as the + * aClosure parameter to the ReadSegments function. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern nsresult NS_CopySegmentToStream(nsIInputStream* aInputStream, + void* aClosure, const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * copy data from the nsIInputStream into a character buffer passed as the + * aClosure parameter to the ReadSegments function. The character buffer + * must be at least as large as the aCount parameter passed to ReadSegments. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern nsresult NS_CopySegmentToBuffer(nsIInputStream* aInputStream, + void* aClosure, const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount); + +/** + * This function is intended to be passed to nsIOutputStream::WriteSegments to + * copy data into the nsIOutputStream from a character buffer passed as the + * aClosure parameter to the WriteSegments function. + * + * @see nsIOutputStream.idl for a description of this function's parameters. + */ +extern nsresult NS_CopyBufferToSegment(nsIOutputStream* aOutputStream, + void* aClosure, char* aToSegment, + uint32_t aFromOffset, uint32_t aCount, + uint32_t* aReadCount); + +/** + * This function is intended to be passed to nsIOutputStream::WriteSegments to + * copy data into the nsIOutputStream from a nsIInputStream passed as the + * aClosure parameter to the WriteSegments function. + * + * @see nsIOutputStream.idl for a description of this function's parameters. + */ +extern nsresult NS_CopyStreamToSegment(nsIOutputStream* aOutputStream, + void* aClosure, char* aToSegment, + uint32_t aFromOffset, uint32_t aCount, + uint32_t* aReadCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * discard data from the nsIInputStream. This can be used to efficiently read + * data from the stream without actually copying any bytes. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern nsresult NS_DiscardSegment(nsIInputStream* aInputStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * adjust the aInputStream parameter passed to a consumer's WriteSegmentFun. + * The aClosure parameter must be a pointer to a nsWriteSegmentThunk object. + * The mStream and mClosure members of that object will be passed to the mFun + * function, with the remainder of the parameters being what are passed to + * NS_WriteSegmentThunk. + * + * This function comes in handy when implementing ReadSegments in terms of an + * inner stream's ReadSegments. + */ +extern nsresult NS_WriteSegmentThunk(nsIInputStream* aInputStream, + void* aClosure, const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount); + +struct MOZ_STACK_CLASS nsWriteSegmentThunk { + nsCOMPtr<nsIInputStream> mStream; + nsWriteSegmentFun mFun; + void* mClosure; +}; + +/** + * Read data from aInput and store in aDest. A non-zero aKeep will keep that + * many bytes from aDest (from the end). New data is appended after the kept + * bytes (if any). aDest's new length on returning from this function is + * aKeep + aNewBytes and is guaranteed to be less than or equal to aDest's + * current capacity. + * @param aDest the array to fill + * @param aInput the stream to read from + * @param aKeep number of bytes to keep (0 <= aKeep <= aDest.Length()) + * @param aNewBytes (out) number of bytes read from aInput or zero if Read() + * failed + * @return the result from aInput->Read(...) + */ +extern nsresult NS_FillArray(FallibleTArray<char>& aDest, + nsIInputStream* aInput, uint32_t aKeep, + uint32_t* aNewBytes); + +/** + * Return true if the given stream can be directly cloned. + */ +extern bool NS_InputStreamIsCloneable(nsIInputStream* aSource); + +/** + * Clone the provided source stream in the most efficient way possible. This + * first attempts to QI to nsICloneableInputStream to use Clone(). If that is + * not supported or its cloneable attribute is false, then a fallback clone is + * provided by copying the source to a pipe. In this case the caller must + * replace the source stream with the resulting replacement stream. The clone + * and the replacement stream are then cloneable using nsICloneableInputStream + * without duplicating memory. This fallback clone using the pipe is only + * performed if a replacement stream parameter is also passed in. + * @param aSource The input stream to clone. + * @param aCloneOut Required out parameter to hold resulting clone. + * @param aReplacementOut Optional out parameter to hold stream to replace + * original source stream after clone. If not + * provided then the fallback clone process is not + * supported and a non-cloneable source will result + * in failure. Replacement streams are non-blocking. + * @return NS_OK on successful clone. Error otherwise. + */ +extern nsresult NS_CloneInputStream(nsIInputStream* aSource, + nsIInputStream** aCloneOut, + nsIInputStream** aReplacementOut = nullptr); + +/* + * This function returns a non-blocking nsIAsyncInputStream. Internally, + * different approaches are used based on what |aSource| is and what it + * implements. + * + * Note that this component takes the owninship of aSource. + * + * If the |aSource| is already a non-blocking and async stream, + * |aAsyncInputStream| will be equal to |aSource|. + * + * Otherwise, if |aSource| is just non-blocking, NonBlockingAsyncInputStream + * class is used in order to make it async. + * + * The last step is to use nsIStreamTransportService and create a pipe in order + * to expose a non-blocking async inputStream and read |aSource| data from + * a separate thread. + * + * In case we need to create a pipe, |aCloseWhenDone| will be used to create the + * inputTransport, |aFlags|, |aSegmentSize|, |asegmentCount| will be used to + * open the inputStream. If true, the input stream will be closed after it has + * been read. Read more in nsITransport.idl. + */ +extern nsresult NS_MakeAsyncNonBlockingInputStream( + already_AddRefed<nsIInputStream> aSource, + nsIAsyncInputStream** aAsyncInputStream, bool aCloseWhenDone = true, + uint32_t aFlags = 0, uint32_t aSegmentSize = 0, uint32_t aSegmentCount = 0); + +#endif // !nsStreamUtils_h__ diff --git a/xpcom/io/nsStringStream.cpp b/xpcom/io/nsStringStream.cpp new file mode 100644 index 0000000000..b3e83a58ba --- /dev/null +++ b/xpcom/io/nsStringStream.cpp @@ -0,0 +1,591 @@ +/* -*- 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/. */ + +/** + * Based on original code from nsIStringStream.cpp + */ + +#include "ipc/IPCMessageUtils.h" + +#include "nsStringStream.h" +#include "nsStreamUtils.h" +#include "nsReadableUtils.h" +#include "nsICloneableInputStream.h" +#include "nsISeekableStream.h" +#include "nsISupportsPrimitives.h" +#include "nsCRT.h" +#include "prerror.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/StreamBufferSourceImpl.h" +#include "nsIIPCSerializableInputStream.h" +#include "XPCOMModule.h" + +using namespace mozilla::ipc; +using mozilla::fallible; +using mozilla::MakeRefPtr; +using mozilla::MallocSizeOf; +using mozilla::nsBorrowedSource; +using mozilla::nsCStringSource; +using mozilla::nsTArraySource; +using mozilla::ReentrantMonitorAutoEnter; +using mozilla::Span; +using mozilla::StreamBufferSource; + +//----------------------------------------------------------------------------- +// nsIStringInputStream implementation +//----------------------------------------------------------------------------- + +class nsStringInputStream final : public nsIStringInputStream, + public nsISeekableStream, + public nsISupportsCString, + public nsIIPCSerializableInputStream, + public nsICloneableInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISTRINGINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + nsStringInputStream() = default; + + nsresult Init(nsCString&& aString); + + nsresult Init(nsTArray<uint8_t>&& aArray); + + private: + ~nsStringInputStream() = default; + + size_t Length() const MOZ_REQUIRES(mMon) { + return mSource ? mSource->Data().Length() : 0; + } + + size_t LengthRemaining() const MOZ_REQUIRES(mMon) { + return Length() - mOffset; + } + + void Clear() MOZ_REQUIRES(mMon) { mSource = nullptr; } + + bool Closed() MOZ_REQUIRES(mMon) { return !mSource; } + + RefPtr<StreamBufferSource> mSource MOZ_GUARDED_BY(mMon); + size_t mOffset MOZ_GUARDED_BY(mMon) = 0; + + mutable mozilla::ReentrantMonitor mMon{"nsStringInputStream"}; +}; + +nsresult nsStringInputStream::Init(nsCString&& aString) { + nsCString string; + if (!string.Assign(std::move(aString), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +nsresult nsStringInputStream::Init(nsTArray<uint8_t>&& aArray) { + auto source = MakeRefPtr<nsTArraySource>(std::move(aArray)); + return SetDataSource(source); +} + +// This class needs to support threadsafe refcounting since people often +// allocate a string stream, and then read it from a background thread. +NS_IMPL_ADDREF(nsStringInputStream) +NS_IMPL_RELEASE(nsStringInputStream) + +NS_IMPL_CLASSINFO(nsStringInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_STRINGINPUTSTREAM_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsStringInputStream, nsIStringInputStream, + nsIInputStream, nsISupportsCString, + nsISeekableStream, nsITellableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) +NS_IMPL_CI_INTERFACE_GETTER(nsStringInputStream, nsIStringInputStream, + nsIInputStream, nsISupportsCString, + nsISeekableStream, nsITellableStream, + nsICloneableInputStream) + +///////// +// nsISupportsCString implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetType(uint16_t* aType) { + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::GetData(nsACString& data) { + ReentrantMonitorAutoEnter lock(mMon); + + // The stream doesn't have any data when it is closed. We could fake it + // and return an empty string here, but it seems better to keep this return + // value consistent with the behavior of the other 'getter' methods. + if (NS_WARN_IF(Closed())) { + return NS_BASE_STREAM_CLOSED; + } + + return mSource->GetData(data); +} + +NS_IMETHODIMP +nsStringInputStream::SetData(const nsACString& aData) { + nsCString string; + if (!string.Assign(aData, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +NS_IMETHODIMP +nsStringInputStream::ToString(char** aResult) { + // NOTE: This method may result in data loss, so we do not implement it. + return NS_ERROR_NOT_IMPLEMENTED; +} + +///////// +// nsIStringInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::SetData(const char* aData, int32_t aDataLen) { + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + + nsCString string; + if (NS_WARN_IF(!string.Assign(aData, aDataLen, fallible))) { + return NS_ERROR_OUT_OF_MEMORY; + } + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +NS_IMETHODIMP +nsStringInputStream::SetUTF8Data(const nsACString& aData) { + return nsStringInputStream::SetData(aData); +} + +NS_IMETHODIMP +nsStringInputStream::AdoptData(char* aData, int32_t aDataLen) { + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + + nsCString string; + string.Adopt(aData, aDataLen); + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +NS_IMETHODIMP +nsStringInputStream::ShareData(const char* aData, int32_t aDataLen) { + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + + size_t length = aDataLen < 0 ? strlen(aData) : size_t(aDataLen); + auto source = MakeRefPtr<nsBorrowedSource>(Span{aData, length}); + return SetDataSource(source); +} + +NS_IMETHODIMP +nsStringInputStream::SetDataSource(StreamBufferSource* aSource) { + ReentrantMonitorAutoEnter lock(mMon); + + if (NS_WARN_IF(!aSource)) { + return NS_ERROR_INVALID_ARG; + } + + mSource = aSource; + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP_(size_t) +nsStringInputStream::SizeOfIncludingThisIfUnshared(MallocSizeOf aMallocSizeOf) { + ReentrantMonitorAutoEnter lock(mMon); + + size_t n = aMallocSizeOf(this); + if (mSource) { + n += mSource->SizeOfIncludingThisIfUnshared(aMallocSizeOf); + } + return n; +} + +NS_IMETHODIMP_(size_t) +nsStringInputStream::SizeOfIncludingThisEvenIfShared( + MallocSizeOf aMallocSizeOf) { + ReentrantMonitorAutoEnter lock(mMon); + + size_t n = aMallocSizeOf(this); + if (mSource) { + n += mSource->SizeOfIncludingThisEvenIfShared(aMallocSizeOf); + } + return n; +} + +///////// +// nsIInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Close() { + ReentrantMonitorAutoEnter lock(mMon); + + Clear(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Available(uint64_t* aLength) { + ReentrantMonitorAutoEnter lock(mMon); + + NS_ASSERTION(aLength, "null ptr"); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + *aLength = LengthRemaining(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::StreamStatus() { + ReentrantMonitorAutoEnter lock(mMon); + return Closed() ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aReadCount) { + NS_ASSERTION(aBuf, "null ptr"); + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP +nsStringInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + ReentrantMonitorAutoEnter lock(mMon); + + NS_ASSERTION(aResult, "null ptr"); + NS_ASSERTION(Length() >= mOffset, "bad stream state"); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + // We may be at end-of-file + size_t maxCount = LengthRemaining(); + if (maxCount == 0) { + *aResult = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + RefPtr<StreamBufferSource> source = mSource; + size_t offset = mOffset; + + nsresult rv = aWriter(this, aClosure, source->Data().Elements() + offset, 0, + aCount, aResult); + + if (Closed()) { + NS_WARNING("nsStringInputStream was closed during ReadSegments"); + return NS_OK; + } + + MOZ_RELEASE_ASSERT(mSource == source, "String was replaced!"); + MOZ_RELEASE_ASSERT(mOffset == offset, "Nested read operation!"); + + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*aResult <= aCount, + "writer should not write more than we asked it to write"); + mOffset = offset + *aResult; + } + + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} + +///////// +// nsISeekableStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Seek(int32_t aWhence, int64_t aOffset) { + ReentrantMonitorAutoEnter lock(mMon); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + // Compute new stream position. The given offset may be a negative value. + + int64_t newPos = aOffset; + switch (aWhence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + newPos += (int64_t)mOffset; + break; + case NS_SEEK_END: + newPos += (int64_t)Length(); + break; + default: + NS_ERROR("invalid aWhence"); + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(newPos < 0) || NS_WARN_IF(newPos > (int64_t)Length())) { + return NS_ERROR_INVALID_ARG; + } + + mOffset = (size_t)newPos; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetEOF() { + ReentrantMonitorAutoEnter lock(mMon); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + mOffset = Length(); + return NS_OK; +} + +///////// +// nsITellableStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Tell(int64_t* aOutWhere) { + ReentrantMonitorAutoEnter lock(mMon); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + *aOutWhere = (int64_t)mOffset; + return NS_OK; +} + +///////// +// nsIIPCSerializableInputStream implementation +///////// + +void nsStringInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + ReentrantMonitorAutoEnter lock(mMon); + + if (Length() >= aMaxSize) { + *aPipes = 1; + } else { + *aSizeUsed = Length(); + } +} + +void nsStringInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + ReentrantMonitorAutoEnter lock(mMon); + + MOZ_DIAGNOSTIC_ASSERT(!Closed(), "cannot send a closed stream!"); + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + if (Length() >= aMaxSize) { + // If the input stream is non-owning (i.e. it was initialized with + // `ShareData`), create a new owning source so that it doesn't go away while + // async copying. + if (!mSource->Owning()) { + auto source = + MakeRefPtr<nsCStringSource>(nsDependentCSubstring(mSource->Data())); + mSource = source; + } + + InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + *aSizeUsed = Length(); + + StringInputStreamParams params; + mSource->GetData(params.data()); + aParams = params; +} + +bool nsStringInputStream::Deserialize(const InputStreamParams& aParams) { + if (aParams.type() != InputStreamParams::TStringInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const StringInputStreamParams& params = aParams.get_StringInputStreamParams(); + + if (NS_FAILED(SetData(params.data()))) { + NS_WARNING("SetData failed!"); + return false; + } + + return true; +} + +///////// +// nsICloneableInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetCloneable(bool* aCloneableOut) { + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Clone(nsIInputStream** aCloneOut) { + ReentrantMonitorAutoEnter lock(mMon); + + RefPtr<nsStringInputStream> ref = new nsStringInputStream(); + // Nothing else can access this yet, but suppress static analysis warnings + ReentrantMonitorAutoEnter reflock(ref->mMon); + if (mSource && !mSource->Owning()) { + auto data = mSource->Data(); + nsresult rv = ref->SetData(data.Elements(), data.Length()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + ref->mSource = mSource; + } + + // mOffset is overwritten by SetData(). + ref->mOffset = mOffset; + + ref.forget(aCloneOut); + return NS_OK; +} + +nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::Span<const char> aStringToRead, + nsAssignmentType aAssignment) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv; + switch (aAssignment) { + case NS_ASSIGNMENT_COPY: + rv = stream->SetData(aStringToRead.Elements(), aStringToRead.Length()); + break; + case NS_ASSIGNMENT_DEPEND: + rv = stream->ShareData(aStringToRead.Elements(), aStringToRead.Length()); + break; + case NS_ASSIGNMENT_ADOPT: + rv = stream->AdoptData(const_cast<char*>(aStringToRead.Elements()), + aStringToRead.Length()); + break; + default: + NS_ERROR("invalid assignment type"); + rv = NS_ERROR_INVALID_ARG; + } + + if (NS_FAILED(rv)) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + nsTArray<uint8_t>&& aArray) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->Init(std::move(aArray)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::StreamBufferSource* aSource) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->SetDataSource(aSource); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->SetData(aStringToRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + nsCString&& aStringToRead) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->Init(std::move(aStringToRead)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +// factory method for constructing a nsStringInputStream object +nsresult nsStringInputStreamConstructor(REFNSIID aIID, void** aResult) { + *aResult = nullptr; + + RefPtr<nsStringInputStream> inst = new nsStringInputStream(); + return inst->QueryInterface(aIID, aResult); +} diff --git a/xpcom/io/nsStringStream.h b/xpcom/io/nsStringStream.h new file mode 100644 index 0000000000..8568cc33f8 --- /dev/null +++ b/xpcom/io/nsStringStream.h @@ -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/. */ + +#ifndef nsStringStream_h__ +#define nsStringStream_h__ + +#include "nsIStringStream.h" +#include "nsString.h" +#include "nsTArray.h" + +/** + * Implements: + * nsIStringInputStream + * nsIInputStream + * nsISeekableStream + * nsITellableStream + * nsISupportsCString + */ +#define NS_STRINGINPUTSTREAM_CONTRACTID "@mozilla.org/io/string-input-stream;1" +#define NS_STRINGINPUTSTREAM_CID \ + { /* 0abb0835-5000-4790-af28-61b3ba17c295 */ \ + 0x0abb0835, 0x5000, 0x4790, { \ + 0xaf, 0x28, 0x61, 0xb3, 0xba, 0x17, 0xc2, 0x95 \ + } \ + } + +/** + * An enumeration type used to represent a method of assignment. + */ +enum nsAssignmentType { + NS_ASSIGNMENT_COPY, // copy by value + NS_ASSIGNMENT_DEPEND, // copy by reference + NS_ASSIGNMENT_ADOPT // copy by reference (take ownership of resource) +}; + +/** + * Factory method to get an nsInputStream from a byte buffer. Result will + * implement nsIStringInputStream, nsITellableStream and nsISeekableStream. + * + * If aAssignment is NS_ASSIGNMENT_COPY, then the resulting stream holds a copy + * of the given buffer (aStringToRead), and the caller is free to discard + * aStringToRead after this function returns. + * + * If aAssignment is NS_ASSIGNMENT_DEPEND, then the resulting stream refers + * directly to the given buffer (aStringToRead), so the caller must ensure that + * the buffer remains valid for the lifetime of the stream object. Use with + * care!! + * + * If aAssignment is NS_ASSIGNMENT_ADOPT, then the resulting stream refers + * directly to the given buffer (aStringToRead) and will free aStringToRead + * once the stream is closed. + */ +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::Span<const char> aStringToRead, + nsAssignmentType aAssignment); + +/** + * Factory method to get an nsIInputStream from an nsTArray representing a byte + * buffer. This will take ownership of the data and empty out the nsTArray. + * + * Result will implement nsIStringInputStream, nsITellableStream and + * nsISeekableStream. + */ +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + nsTArray<uint8_t>&& aArray); + +/** + * Factory method to get an nsIInputStream from an arbitrary StreamBufferSource. + * This will take a strong reference to the source. + * + * Result will implement nsIStringInputStream, nsITellableStream and + * nsISeekableStream. + */ +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::StreamBufferSource* aSource); + +/** + * Factory method to get an nsInputStream from an nsACString. Result will + * implement nsIStringInputStream, nsTellableStream and nsISeekableStream. + */ +extern nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead); +extern nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + nsCString&& aStringToRead); + +#endif // nsStringStream_h__ diff --git a/xpcom/io/nsUnicharInputStream.cpp b/xpcom/io/nsUnicharInputStream.cpp new file mode 100644 index 0000000000..1366ab1e34 --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.cpp @@ -0,0 +1,132 @@ +/* -*- 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 "nsUnicharInputStream.h" +#include "nsIInputStream.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "nsStreamUtils.h" +#include "nsConverterInputStream.h" +#include "mozilla/Attributes.h" +#include <fcntl.h> +#if defined(XP_WIN) +# include <io.h> +#else +# include <unistd.h> +#endif + +#define STRING_BUFFER_SIZE 8192 + +class StringUnicharInputStream final : public nsIUnicharInputStream { + public: + explicit StringUnicharInputStream(const nsAString& aString) + : mString(aString), mPos(0), mLen(aString.Length()) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIUNICHARINPUTSTREAM + + nsString mString; + uint32_t mPos; + uint32_t mLen; + + private: + ~StringUnicharInputStream() = default; +}; + +NS_IMETHODIMP +StringUnicharInputStream::Read(char16_t* aBuf, uint32_t aCount, + uint32_t* aReadCount) { + if (mPos >= mLen) { + *aReadCount = 0; + return NS_OK; + } + nsAString::const_iterator iter; + mString.BeginReading(iter); + const char16_t* us = iter.get(); + uint32_t amount = mLen - mPos; + if (amount > aCount) { + amount = aCount; + } + memcpy(aBuf, us + mPos, sizeof(char16_t) * amount); + mPos += amount; + *aReadCount = amount; + return NS_OK; +} + +NS_IMETHODIMP +StringUnicharInputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aReadCount) { + uint32_t bytesWritten; + uint32_t totalBytesWritten = 0; + + nsresult rv; + aCount = XPCOM_MIN<uint32_t>(mString.Length() - mPos, aCount); + + nsAString::const_iterator iter; + mString.BeginReading(iter); + + while (aCount) { + rv = aWriter(this, aClosure, iter.get() + mPos, totalBytesWritten, aCount, + &bytesWritten); + + if (NS_FAILED(rv)) { + // don't propagate errors to the caller + break; + } + + aCount -= bytesWritten; + totalBytesWritten += bytesWritten; + mPos += bytesWritten; + } + + *aReadCount = totalBytesWritten; + + return NS_OK; +} + +NS_IMETHODIMP +StringUnicharInputStream::ReadString(uint32_t aCount, nsAString& aString, + uint32_t* aReadCount) { + if (mPos >= mLen) { + *aReadCount = 0; + return NS_OK; + } + uint32_t amount = mLen - mPos; + if (amount > aCount) { + amount = aCount; + } + aString = Substring(mString, mPos, amount); + mPos += amount; + *aReadCount = amount; + return NS_OK; +} + +nsresult StringUnicharInputStream::Close() { + mPos = mLen; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(StringUnicharInputStream, nsIUnicharInputStream) + +//---------------------------------------------------------------------- + +nsresult NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream** aResult) { + *aResult = nullptr; + + // Create converter input stream + RefPtr<nsConverterInputStream> it = new nsConverterInputStream(); + nsresult rv = it->Init(aStreamToWrap, "UTF-8", STRING_BUFFER_SIZE, + nsIConverterInputStream::ERRORS_ARE_FATAL); + if (NS_FAILED(rv)) { + return rv; + } + + it.forget(aResult); + return NS_OK; +} diff --git a/xpcom/io/nsUnicharInputStream.h b/xpcom/io/nsUnicharInputStream.h new file mode 100644 index 0000000000..de0534f01c --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsUnicharInputStream_h__ +#define nsUnicharInputStream_h__ + +#include "nsIUnicharInputStream.h" + +nsresult NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream** aResult); + +#endif // nsUnicharInputStream_h__ diff --git a/xpcom/io/nsWildCard.cpp b/xpcom/io/nsWildCard.cpp new file mode 100644 index 0000000000..64546ca676 --- /dev/null +++ b/xpcom/io/nsWildCard.cpp @@ -0,0 +1,435 @@ +/* -*- 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/. */ + +/* * + * + * + * nsWildCard.cpp: shell-like wildcard match routines + * + * See nsIZipReader.findEntries documentation in nsIZipReader.idl for + * a description of the syntax supported by the routines in this file. + * + * Rob McCool + * + */ + +#include "nsWildCard.h" +#include "nsXPCOM.h" +#include "nsCRTGlue.h" +#include "nsCharTraits.h" + +/* -------------------- ASCII-specific character methods ------------------- */ + +typedef int static_assert_character_code_arrangement['a' > 'A' ? 1 : -1]; + +template <class T> +static int alpha(T aChar) { + return ('a' <= aChar && aChar <= 'z') || ('A' <= aChar && aChar <= 'Z'); +} + +template <class T> +static int alphanumeric(T aChar) { + return ('0' <= aChar && aChar <= '9') || ::alpha(aChar); +} + +template <class T> +static int lower(T aChar) { + return ('A' <= aChar && aChar <= 'Z') ? aChar + ('a' - 'A') : aChar; +} + +template <class T> +static int upper(T aChar) { + return ('a' <= aChar && aChar <= 'z') ? aChar - ('a' - 'A') : aChar; +} + +/* ----------------------------- _valid_subexp ---------------------------- */ + +template <class T> +static int _valid_subexp(const T* aExpr, T aStop1, T aStop2) { + int x; + int nsc = 0; /* Number of special characters */ + int np; /* Number of pipe characters in union */ + int tld = 0; /* Number of tilde characters */ + + for (x = 0; aExpr[x] && (aExpr[x] != aStop1) && (aExpr[x] != aStop2); ++x) { + switch (aExpr[x]) { + case '~': + if (tld) { /* at most one exclusion */ + return INVALID_SXP; + } + if (aStop1) { /* no exclusions within unions */ + return INVALID_SXP; + } + if (!aExpr[x + 1]) { /* exclusion cannot be last character */ + return INVALID_SXP; + } + if (!x) { /* exclusion cannot be first character */ + return INVALID_SXP; + } + ++tld; + [[fallthrough]]; + case '*': + case '?': + case '$': + ++nsc; + break; + case '[': + ++nsc; + if ((!aExpr[++x]) || (aExpr[x] == ']')) { + return INVALID_SXP; + } + for (; aExpr[x] && (aExpr[x] != ']'); ++x) { + if (aExpr[x] == '\\' && !aExpr[++x]) { + return INVALID_SXP; + } + } + if (!aExpr[x]) { + return INVALID_SXP; + } + break; + case '(': + ++nsc; + if (aStop1) { /* no nested unions */ + return INVALID_SXP; + } + np = -1; + do { + int t = ::_valid_subexp(&aExpr[++x], T(')'), T('|')); + if (t == 0 || t == INVALID_SXP) { + return INVALID_SXP; + } + x += t; + if (!aExpr[x]) { + return INVALID_SXP; + } + ++np; + } while (aExpr[x] == '|'); + if (np < 1) { /* must be at least one pipe */ + return INVALID_SXP; + } + break; + case ')': + case ']': + case '|': + return INVALID_SXP; + case '\\': + ++nsc; + if (!aExpr[++x]) { + return INVALID_SXP; + } + break; + default: + break; + } + } + if (!aStop1 && !nsc) { /* must be at least one special character */ + return NON_SXP; + } + return ((aExpr[x] == aStop1 || aExpr[x] == aStop2) ? x : INVALID_SXP); +} + +template <class T> +int NS_WildCardValid_(const T* aExpr) { + int x = ::_valid_subexp(aExpr, T('\0'), T('\0')); + return (x < 0 ? x : VALID_SXP); +} + +int NS_WildCardValid(const char* aExpr) { return NS_WildCardValid_(aExpr); } + +int NS_WildCardValid(const char16_t* aExpr) { return NS_WildCardValid_(aExpr); } + +/* ----------------------------- _shexp_match ----------------------------- */ + +#define MATCH 0 +#define NOMATCH 1 +#define ABORTED -1 + +template <class T> +static int _shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel); + +/** + * Count characters until we reach a NUL character or either of the + * two delimiter characters, stop1 or stop2. If we encounter a bracketed + * expression, look only for NUL or ']' inside it. Do not look for stop1 + * or stop2 inside it. Return ABORTED if bracketed expression is unterminated. + * Handle all escaping. + * Return index in input string of first stop found, or ABORTED if not found. + * If "dest" is non-nullptr, copy counted characters to it and null terminate. + */ +template <class T> +static int _scan_and_copy(const T* aExpr, T aStop1, T aStop2, T* aDest) { + int sx; /* source index */ + T cc; + + for (sx = 0; (cc = aExpr[sx]) && cc != aStop1 && cc != aStop2; ++sx) { + if (cc == '\\') { + if (!aExpr[++sx]) { + return ABORTED; /* should be impossible */ + } + } else if (cc == '[') { + while ((cc = aExpr[++sx]) && cc != ']') { + if (cc == '\\' && !aExpr[++sx]) { + return ABORTED; + } + } + if (!cc) { + return ABORTED; /* should be impossible */ + } + } + } + if (aDest && sx) { + /* Copy all but the closing delimiter. */ + memcpy(aDest, aExpr, sx * sizeof(T)); + aDest[sx] = 0; + } + return cc ? sx : ABORTED; /* index of closing delimiter */ +} + +/* On input, expr[0] is the opening parenthesis of a union. + * See if any of the alternatives in the union matches as a pattern. + * The strategy is to take each of the alternatives, in turn, and append + * the rest of the expression (after the closing ')' that marks the end of + * this union) to that alternative, and then see if the resultant expression + * matches the input string. Repeat this until some alternative matches, + * or we have an abort. + */ +template <class T> +static int _handle_union(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel) { + int sx; /* source index */ + int cp; /* source index of closing parenthesis */ + int count; + int ret = NOMATCH; + T* e2; + + /* Find the closing parenthesis that ends this union in the expression */ + cp = ::_scan_and_copy(aExpr, T(')'), T('\0'), static_cast<T*>(nullptr)); + if (cp == ABORTED || cp < 4) { /* must be at least "(a|b" before ')' */ + return ABORTED; + } + ++cp; /* now index of char after closing parenthesis */ + e2 = (T*)moz_xmalloc((1 + nsCharTraits<T>::length(aExpr)) * sizeof(T)); + for (sx = 1;; ++sx) { + /* Here, aExpr[sx] is one character past the preceding '(' or '|'. */ + /* Copy everything up to the next delimiter to e2 */ + count = ::_scan_and_copy(aExpr + sx, T(')'), T('|'), e2); + if (count == ABORTED || !count) { + ret = ABORTED; + break; + } + sx += count; + /* Append everything after closing parenthesis to e2. This is safe. */ + nsCharTraits<T>::copy(e2 + count, aExpr + cp, + nsCharTraits<T>::length(aExpr + cp) + 1); + ret = ::_shexp_match(aStr, e2, aCaseInsensitive, aLevel + 1); + if (ret != NOMATCH || !aExpr[sx] || aExpr[sx] == ')') { + break; + } + } + free(e2); + if (sx < 2) { + ret = ABORTED; + } + return ret; +} + +/* returns 1 if val is in range from start..end, case insensitive. */ +static int _is_char_in_range(unsigned char aStart, unsigned char aEnd, + unsigned char aVal) { + char map[256]; + memset(map, 0, sizeof(map)); + while (aStart <= aEnd) { + map[lower(aStart++)] = 1; + } + return map[lower(aVal)]; +} + +template <class T> +static int _shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel) { + int x; /* input string index */ + int y; /* expression index */ + int ret, neg; + + if (aLevel > 20) { /* Don't let the stack get too deep. */ + return ABORTED; + } + for (x = 0, y = 0; aExpr[y]; ++y, ++x) { + if (!aStr[x] && aExpr[y] != '$' && aExpr[y] != '*') { + return NOMATCH; + } + switch (aExpr[y]) { + case '$': + if (aStr[x]) { + return NOMATCH; + } + --x; /* we don't want loop to increment x */ + break; + case '*': + while (aExpr[++y] == '*') { + } + if (!aExpr[y]) { + return MATCH; + } + while (aStr[x]) { + ret = ::_shexp_match(&aStr[x++], &aExpr[y], aCaseInsensitive, + aLevel + 1); + switch (ret) { + case NOMATCH: + continue; + case ABORTED: + return ABORTED; + default: + return MATCH; + } + } + if (aExpr[y] == '$' && aExpr[y + 1] == '\0' && !aStr[x]) { + return MATCH; + } else { + return NOMATCH; + } + case '[': { + T start, end = 0; + int i; + ++y; + neg = (aExpr[y] == '^' && aExpr[y + 1] != ']'); + if (neg) { + ++y; + } + i = y; + start = aExpr[i++]; + if (start == '\\') { + start = aExpr[i++]; + } + if (::alphanumeric(start) && aExpr[i++] == '-') { + end = aExpr[i++]; + if (end == '\\') { + end = aExpr[i++]; + } + } + if (::alphanumeric(end) && aExpr[i] == ']') { + /* This is a range form: a-b */ + T val = aStr[x]; + if (end < start) { /* swap them */ + T tmp = end; + end = start; + start = tmp; + } + if (aCaseInsensitive && ::alpha(val)) { + val = ::_is_char_in_range((unsigned char)start, (unsigned char)end, + (unsigned char)val); + if (neg == val) { + return NOMATCH; + } + } else if (neg != (val < start || val > end)) { + return NOMATCH; + } + y = i; + } else { + /* Not range form */ + int matched = 0; + for (; aExpr[y] != ']'; ++y) { + if (aExpr[y] == '\\') { + ++y; + } + if (aCaseInsensitive) { + matched |= (::upper(aStr[x]) == ::upper(aExpr[y])); + } else { + matched |= (aStr[x] == aExpr[y]); + } + } + if (neg == matched) { + return NOMATCH; + } + } + } break; + case '(': + if (!aExpr[y + 1]) { + return ABORTED; + } + return ::_handle_union(&aStr[x], &aExpr[y], aCaseInsensitive, + aLevel + 1); + case '?': + break; + case ')': + case ']': + case '|': + return ABORTED; + case '\\': + ++y; + [[fallthrough]]; + default: + if (aCaseInsensitive) { + if (::upper(aStr[x]) != ::upper(aExpr[y])) { + return NOMATCH; + } + } else { + if (aStr[x] != aExpr[y]) { + return NOMATCH; + } + } + break; + } + } + return (aStr[x] ? NOMATCH : MATCH); +} + +template <class T> +static int ns_WildCardMatch(const T* aStr, const T* aXp, + bool aCaseInsensitive) { + T* expr = nullptr; + int ret = MATCH; + + if (!nsCharTraits<T>::find(aXp, nsCharTraits<T>::length(aXp), T('~'))) { + return ::_shexp_match(aStr, aXp, aCaseInsensitive, 0); + } + + expr = (T*)moz_xmalloc((nsCharTraits<T>::length(aXp) + 1) * sizeof(T)); + memcpy(expr, aXp, (nsCharTraits<T>::length(aXp) + 1) * sizeof(T)); + + int x = ::_scan_and_copy(expr, T('~'), T('\0'), static_cast<T*>(nullptr)); + if (x != ABORTED && expr[x] == '~') { + expr[x++] = '\0'; + ret = ::_shexp_match(aStr, &expr[x], aCaseInsensitive, 0); + switch (ret) { + case NOMATCH: + ret = MATCH; + break; + case MATCH: + ret = NOMATCH; + break; + default: + break; + } + } + if (ret == MATCH) { + ret = ::_shexp_match(aStr, expr, aCaseInsensitive, 0); + } + + free(expr); + return ret; +} + +template <class T> +int NS_WildCardMatch_(const T* aStr, const T* aExpr, bool aCaseInsensitive) { + int is_valid = NS_WildCardValid(aExpr); + switch (is_valid) { + case INVALID_SXP: + return -1; + default: + return ::ns_WildCardMatch(aStr, aExpr, aCaseInsensitive); + } +} + +int NS_WildCardMatch(const char* aStr, const char* aXp, bool aCaseInsensitive) { + return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive); +} + +int NS_WildCardMatch(const char16_t* aStr, const char16_t* aXp, + bool aCaseInsensitive) { + return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive); +} diff --git a/xpcom/io/nsWildCard.h b/xpcom/io/nsWildCard.h new file mode 100644 index 0000000000..e3205fa7a1 --- /dev/null +++ b/xpcom/io/nsWildCard.h @@ -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/. */ + +/* + * nsWildCard.h: Defines and prototypes for shell exp. match routines + * + * See nsIZipReader.findEntries docs in nsIZipReader.idl for a description of + * the supported expression syntax. + * + * Note that the syntax documentation explicitly says the results of certain + * expressions are undefined. This is intentional to require less robustness + * in the code. Regular expression parsing is hard; the smaller the set of + * features and interactions this code must support, the easier it is to + * ensure it works. + * + */ + +#ifndef nsWildCard_h__ +#define nsWildCard_h__ + +#include "nscore.h" + +/* --------------------------- Public routines ---------------------------- */ + +/* + * NS_WildCardValid takes a shell expression exp as input. It returns: + * + * NON_SXP if exp is a standard string + * INVALID_SXP if exp is a shell expression, but invalid + * VALID_SXP if exp is a valid shell expression + */ + +#define NON_SXP -1 +#define INVALID_SXP -2 +#define VALID_SXP 1 + +int NS_WildCardValid(const char* aExpr); + +int NS_WildCardValid(const char16_t* aExpr); + +/* return values for the search routines */ +#define MATCH 0 +#define NOMATCH 1 +#define ABORTED -1 + +/* + * NS_WildCardMatch + * + * Takes a prevalidated shell expression exp, and a string str. + * + * Returns 0 on match and 1 on non-match. + */ + +int NS_WildCardMatch(const char* aStr, const char* aExpr, + bool aCaseInsensitive); + +int NS_WildCardMatch(const char16_t* aStr, const char16_t* aExpr, + bool aCaseInsensitive); + +#endif /* nsWildCard_h__ */ diff --git a/xpcom/metrics.yaml b/xpcom/metrics.yaml new file mode 100644 index 0000000000..36d2e0fad3 --- /dev/null +++ b/xpcom/metrics.yaml @@ -0,0 +1,30 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Adding a new metric? We have docs for that! +# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 +$tags: + - "Core :: XPCOM" + +timer_thread: + timers_fired_per_wakeup: + type: custom_distribution + description: > + How many timers were processed in a single wake-up of the Timer Thread. + range_min: 0 + range_max: 80 + bucket_count: 20 + histogram_type: exponential + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1814718 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1814718 + data_sensitivity: + - technical + notification_emails: + - jlink@mozilla.com + expires: never diff --git a/xpcom/moz.build b/xpcom/moz.build new file mode 100644 index 0000000000..54c04ad846 --- /dev/null +++ b/xpcom/moz.build @@ -0,0 +1,48 @@ +# -*- 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "XPCOM") + +with Files("nsCycleCollect*"): + BUG_COMPONENT = ("Core", "Cycle Collector") + +DIRS += [ + "idl-parser/xpidl", + "geckoprocesstypes_generator/geckoprocesstypes", +] + +DIRS += [ + "string", + "glue", + "base", + "ds", + "io", + "components", + "threads", + "reflect", + "system", + "../chrome", + "build", +] + +if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["MOZ_DEBUG"]: + DIRS += ["windbgdlg"] + +TEST_DIRS += [ + "rust/gtest", + "tests", +] + +# Can't build internal xptcall tests that use symbols which are not exported. +# TEST_DIRS += [ +# 'reflect/xptcall/tests, +# ] + +SPHINX_TREES["/xpcom"] = "docs" + +with Files("docs/**"): + SCHEDULES.exclusive = ["docs"] diff --git a/xpcom/reflect/moz.build b/xpcom/reflect/moz.build new file mode 100644 index 0000000000..fca3fa4499 --- /dev/null +++ b/xpcom/reflect/moz.build @@ -0,0 +1,7 @@ +# -*- 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/. + +DIRS += ["xptinfo", "xptcall"] diff --git a/xpcom/reflect/xptcall/README b/xpcom/reflect/xptcall/README new file mode 100644 index 0000000000..0c401fe88d --- /dev/null +++ b/xpcom/reflect/xptcall/README @@ -0,0 +1,6 @@ +see: + +http://www.mozilla.org/scriptable/xptcall-faq.html +and +http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/porting.html + diff --git a/xpcom/reflect/xptcall/genstubs.pl b/xpcom/reflect/xptcall/genstubs.pl new file mode 100644 index 0000000000..29797ccc58 --- /dev/null +++ b/xpcom/reflect/xptcall/genstubs.pl @@ -0,0 +1,87 @@ +#!/usr/local/bin/perl +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 is used to generate stub entry points. We generate a file to +# be included in the declaraion and a file to be used for expanding macros +# to represent the implementation of the stubs. + +# +# if "$entry_count" is ever changed and the .inc files regenerated then +# the following issues need to be addressed: +# +# 1) The current Linux ARM code has a limitation of only having 256-3 stubs, +# as a result of the limitations of immediate values in ARM assembly. +# +# This number is verified by the IDL parser in xpcom/idl-parser/xpidl.py, as +# well as in xpcom/reflect/xptinfo/xptinfo.cpp, to prevent generating interfaces +# or loading xpt files that would cause the stubs to run off the entries. +# If you change this number, please update that location. + +# 3 entries are already 'used' by the 3 methods of nsISupports. +# 3+247+5=255 This should get us in under the Linux ARM limitation +$entry_count = 247; +$sentinel_count = 5; + +$decl_name = "xptcstubsdecl.inc"; +$def_name = "xptcstubsdef.inc"; + +## +## Write the declarations include file +## + +die "Can't open $decl_name" if !open(OUTFILE, ">$decl_name"); + +print OUTFILE "/* generated file - DO NOT EDIT */\n\n"; +print OUTFILE "/* includes ",$entry_count," stub entries, and ", + $sentinel_count," sentinel entries */\n\n"; +print OUTFILE "/*\n"; +print OUTFILE "* declarations of normal stubs...\n"; +print OUTFILE "* 0 is QueryInterface\n"; +print OUTFILE "* 1 is AddRef\n"; +print OUTFILE "* 2 is Release\n"; +print OUTFILE "*/\n"; +print OUTFILE "#if !defined(__ia64) || (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__))\n"; +for($i = 0; $i < $entry_count; $i++) { + print OUTFILE "NS_IMETHOD Stub",$i+3,"();\n"; +} +print OUTFILE "#else\n"; +for($i = 0; $i < $entry_count; $i++) { + print OUTFILE "NS_IMETHOD Stub",$i+3,"(uint64_t,uint64_t,\n"; + print OUTFILE " uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t);\n"; + +} +print OUTFILE "#endif\n"; + +print OUTFILE "\n/* declarations of sentinel stubs */\n"; + +for($i = 0; $i < $sentinel_count; $i++) { + print OUTFILE "NS_IMETHOD Sentinel",$i,"();\n"; +} +close(OUTFILE); + + +## +## Write the definitions include file. This assumes a macro will be used to +## expand the entries written... +## + +die "Can't open $def_name" if !open(OUTFILE, ">$def_name"); + +## Disabled for bug 275004 - followup to fix is Bug 419604 +my $warn_inc_is_generated = 0; +if ($warn_inc_is_generated) { +print OUTFILE "/* generated file - DO NOT EDIT */\n\n"; +print OUTFILE "/* includes ",$entry_count," stub entries, and ", + $sentinel_count," sentinel entries */\n\n"; +} + +for($i = 0; $i < $entry_count; $i++) { + print OUTFILE "STUB_ENTRY(",$i+3,")\n"; +} + +for($i = 0; $i < $sentinel_count; $i++) { + print OUTFILE "SENTINEL_ENTRY(",$i,")\n"; +} diff --git a/xpcom/reflect/xptcall/md/moz.build b/xpcom/reflect/xptcall/md/moz.build new file mode 100644 index 0000000000..fcf4dde72f --- /dev/null +++ b/xpcom/reflect/xptcall/md/moz.build @@ -0,0 +1,12 @@ +# -*- 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/. + +arch = CONFIG["OS_ARCH"] + +if arch == "WINNT": + DIRS += ["win32"] +else: + DIRS += ["unix"] diff --git a/xpcom/reflect/xptcall/md/test/README b/xpcom/reflect/xptcall/md/test/README new file mode 100644 index 0000000000..04850b2e01 --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/README @@ -0,0 +1,6 @@ +These are just simple test programs in which stripped down versions of the +XPConnect invoke and stubs code can be built and tested as the code is brought +up on various platforms. These probrams do not test the param sizing and copying +functionality of the routines. However, they do supply a place where the lowest +level assembly language code can be developed and debugged in the simplest of +contexts before it is moved into the real routines.
\ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/clean.bat b/xpcom/reflect/xptcall/md/test/clean.bat new file mode 100755 index 0000000000..f320e222c9 --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/clean.bat @@ -0,0 +1,5 @@ +@echo off +echo deleting intermediate files +if exist *.obj del *.obj > NUL +if exist *.ilk del *.ilk > NUL +if exist *.pdb del *.pdb > NUL
\ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/invoke_test.cpp b/xpcom/reflect/xptcall/md/test/invoke_test.cpp new file mode 100644 index 0000000000..f23a9ccf15 --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/invoke_test.cpp @@ -0,0 +1,187 @@ +/* -*- 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 <stdio.h> + +typedef unsigned nsresult; +typedef unsigned uint32_t; +typedef unsigned nsXPCVariant; + +#if defined(WIN32) +# define NS_IMETHOD virtual nsresult __stdcall +# define NS_IMETHODIMP nsresult __stdcall +#else +# define NS_IMETHOD virtual nsresult +# define NS_IMETHODIMP nsresult +#endif + +class base { + public: + NS_IMETHOD ignored() = 0; +}; + +class foo : public base { + public: + NS_IMETHOD callme1(int i, int j) = 0; + NS_IMETHOD callme2(int i, int j) = 0; + NS_IMETHOD callme3(int i, int j) = 0; +}; + +class bar : public foo { + public: + NS_IMETHOD ignored() override; + NS_IMETHOD callme1(int i, int j) override; + NS_IMETHOD callme2(int i, int j) override; + NS_IMETHOD callme3(int i, int j) override; +}; + +/* +class baz : public base { +public: + NS_IMETHOD ignored() override; + NS_IMETHOD callme1() override; + NS_IMETHOD callme2() override; + NS_IMETHOD callme3() override; + void setfoo(foo* f) {other = f;} + + foo* other; +}; +NS_IMETHODIMP baz::ignored(){return 0;} +*/ + +NS_IMETHODIMP bar::ignored() { return 0; } + +NS_IMETHODIMP bar::callme1(int i, int j) { + printf("called bar::callme1 with: %d %d\n", i, j); + return 5; +} + +NS_IMETHODIMP bar::callme2(int i, int j) { + printf("called bar::callme2 with: %d %d\n", i, j); + return 5; +} + +NS_IMETHODIMP bar::callme3(int i, int j) { + printf("called bar::callme3 with: %d %d\n", i, j); + return 5; +} + +void docall(foo* f, int i, int j) { f->callme1(i, j); } + +/***************************************************************************/ +#if defined(WIN32) + +static uint32_t __stdcall invoke_count_words(uint32_t paramCount, + nsXPCVariant* s) { + return paramCount; +} + +static void __stdcall invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + nsXPCVariant* s) { + for (uint32_t i = 0; i < paramCount; i++, d++, s++) { + *((uint32_t*)d) = *((uint32_t*)s); + } +} + +static nsresult __stdcall DoInvoke(void* that, uint32_t index, + uint32_t paramCount, nsXPCVariant* params) { + __asm { + push params + push paramCount + call invoke_count_words // stdcall, result in eax + shl eax,2 // *= 4 + sub esp,eax // make space for params + mov edx,esp + push params + push paramCount + push edx + call invoke_copy_to_stack // stdcall + mov ecx,that // instance in ecx + push ecx // push this + mov edx,[ecx] // vtable in edx + mov eax,index + shl eax,2 // *= 4 + add edx,eax + call [edx] // stdcall, i.e. callee cleans up stack. + } +} + +#else +/***************************************************************************/ +// just Linux_x86 now. Add other later... + +static uint32_t invoke_count_words(uint32_t paramCount, nsXPCVariant* s) { + return paramCount; +} + +static void invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + nsXPCVariant* s) { + for (uint32_t i = 0; i < paramCount; i++, d++, s++) { + *((uint32_t*)d) = *((uint32_t*)s); + } +} + +static nsresult DoInvoke(void* that, uint32_t index, uint32_t paramCount, + nsXPCVariant* params) { + uint32_t result; + void* fn_count = invoke_count_words; + void* fn_copy = invoke_copy_to_stack; + + __asm__ __volatile__( + "pushl %4\n\t" + "pushl %3\n\t" + "movl %5, %%eax\n\t" + "call *%%eax\n\t" /* count words */ + "addl $0x8, %%esp\n\t" + "shl $2, %%eax\n\t" /* *= 4 */ + "subl %%eax, %%esp\n\t" /* make room for params */ + "movl %%esp, %%edx\n\t" + "pushl %4\n\t" + "pushl %3\n\t" + "pushl %%edx\n\t" + "movl %6, %%eax\n\t" + "call *%%eax\n\t" /* copy params */ + "addl $0xc, %%esp\n\t" + "movl %1, %%ecx\n\t" + "pushl %%ecx\n\t" + "movl (%%ecx), %%edx\n\t" + "movl %2, %%eax\n\t" /* function index */ + "shl $2, %%eax\n\t" /* *= 4 */ + "addl $8, %%eax\n\t" /* += 8 */ + "addl %%eax, %%edx\n\t" + "call *(%%edx)\n\t" /* safe to not cleanup esp */ + "movl %%eax, %0" + : "=g"(result) /* %0 */ + : "g"(that), /* %1 */ + "g"(index), /* %2 */ + "g"(paramCount), /* %3 */ + "g"(params), /* %4 */ + "g"(fn_count), /* %5 */ + "g"(fn_copy) /* %6 */ + : "ax", "cx", "dx", "memory"); + + return result; +} + +#endif +/***************************************************************************/ + +int main() { + nsXPCVariant params1[2] = {1, 2}; + nsXPCVariant params2[2] = {2, 4}; + nsXPCVariant params3[2] = {3, 6}; + + foo* a = new bar(); + + // printf("calling via C++...\n"); + // docall(a, 12, 24); + + printf("calling via ASM...\n"); + DoInvoke(a, 1, 2, params1); + DoInvoke(a, 2, 2, params2); + DoInvoke(a, 3, 2, params3); + + return 0; +} diff --git a/xpcom/reflect/xptcall/md/test/mk_invoke.bat b/xpcom/reflect/xptcall/md/test/mk_invoke.bat new file mode 100755 index 0000000000..10a9be51de --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/mk_invoke.bat @@ -0,0 +1,9 @@ +@echo off +@echo deleing old output +if exist invoke_test.obj del invoke_test.obj > NUL +if exist invoke_test.ilk del invoke_test.ilk > NUL +if exist *.pdb del *.pdb > NUL +if exist invoke_test.exe del invoke_test.exe > NUL + +@echo building... +cl /nologo -Zi -DWIN32 invoke_test.cpp
\ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/mk_stub.bat b/xpcom/reflect/xptcall/md/test/mk_stub.bat new file mode 100755 index 0000000000..f9af17affe --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/mk_stub.bat @@ -0,0 +1,9 @@ +@echo off +@echo deleing old output +if exist stub_test.obj del stub_test.obj > NUL +if exist stub_test.ilk del stub_test.ilk > NUL +if exist *.pdb del *.pdb > NUL +if exist stub_test.exe del stub_test.exe > NUL + +@echo building... +cl /nologo -Zi -DWIN32 stub_test.cpp
\ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/moz.build b/xpcom/reflect/xptcall/md/test/moz.build new file mode 100644 index 0000000000..f31bcf64a3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/moz.build @@ -0,0 +1,11 @@ +# -*- 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/. + +SimplePrograms( + [ + "stub_test", + ] +) diff --git a/xpcom/reflect/xptcall/md/test/stub_test.cpp b/xpcom/reflect/xptcall/md/test/stub_test.cpp new file mode 100644 index 0000000000..c83eb93ccc --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/stub_test.cpp @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <stdio.h> + +typedef unsigned nsresult; +typedef unsigned uint32_t; +typedef unsigned nsXPCVariant; + +#if defined(WIN32) +# define NS_IMETHOD virtual nsresult __stdcall +# define NS_IMETHODIMP nsresult __stdcall +#else +# define NS_IMETHOD virtual nsresult +# define NS_IMETHODIMP nsresult +#endif + +class base { + public: + NS_IMETHOD ignored() = 0; +}; + +class foo : public base { + public: + NS_IMETHOD callme1(int i, int j) = 0; + NS_IMETHOD callme2(int i, int j) = 0; + NS_IMETHOD callme3(int i, int j) = 0; +}; + +class bar : public foo { + public: + NS_IMETHOD ignored() override; + NS_IMETHOD callme1(int i, int j) override; + NS_IMETHOD callme2(int i, int j) override; + NS_IMETHOD callme3(int i, int j) override; +}; + +class baz : public base { + public: + NS_IMETHOD ignored() override; + NS_IMETHOD callme1() override; + NS_IMETHOD callme2() override; + NS_IMETHOD callme3() override; + void setfoo(foo* f) { other = f; } + + foo* other; +}; +NS_IMETHODIMP baz::ignored() { return 0; } + +NS_IMETHODIMP bar::ignored() { return 0; } + +NS_IMETHODIMP bar::callme1(int i, int j) { + printf("called bar::callme1 with: %d %d\n", i, j); + return 15; +} + +NS_IMETHODIMP bar::callme2(int i, int j) { + printf("called bar::callme2 with: %d %d\n", i, j); + return 25; +} + +NS_IMETHODIMP bar::callme3(int i, int j) { + printf("called bar::callme3 with: %d %d\n", i, j); + return 35; +} + +void docall(foo* f, int i, int j) { f->callme1(i, j); } + +/***************************************************************************/ +#if defined(WIN32) + +static int __stdcall PrepareAndDispatch(baz* self, uint32_t methodIndex, + uint32_t* args, + uint32_t* stackBytesToPop) { + fprintf(stdout, "PrepareAndDispatch (%p, %d, %p)\n", (void*)self, methodIndex, + (void*)args); + foo* a = self->other; + int p1 = (int)*args; + int p2 = (int)*(args + 1); + int out = 0; + switch (methodIndex) { + case 1: + out = a->callme1(p1, p2); + break; + case 2: + out = a->callme2(p1, p2); + break; + case 3: + out = a->callme3(p1, p2); + break; + } + *stackBytesToPop = 2 * 4; + return out; +} + +# ifndef __GNUC__ +static __declspec(naked) void SharedStub(void) { + __asm { + push ebp // set up simple stack frame + mov ebp, esp // stack has: ebp/vtbl_index/retaddr/this/args + push ecx // make room for a ptr + lea eax, [ebp-4] // pointer to stackBytesToPop + push eax + lea ecx, [ebp+16] // pointer to args + push ecx + mov edx, [ebp+4] // vtbl_index + push edx + mov eax, [ebp+12] // this + push eax + call PrepareAndDispatch + mov edx, [ebp+8] // return address + mov ecx, [ebp-4] // stackBytesToPop + add ecx, 12 // for this, the index, and ret address + mov esp, ebp + pop ebp + add esp, ecx // fix up stack pointer + jmp edx // simulate __stdcall return + } +} + +// these macros get expanded (many times) in the file #included below +# define STUB_ENTRY(n) \ + __declspec(naked) nsresult __stdcall baz::callme##n() { \ + __asm push n __asm jmp SharedStub \ + } + +# else /* __GNUC__ */ + +# define STUB_ENTRY(n) \ + nsresult __stdcall baz::callme##n() { \ + uint32_t *args, stackBytesToPop; \ + int result = 0; \ + baz* obj; \ + __asm__ __volatile__( \ + "leal 0x0c(%%ebp), %0\n\t" /* args */ \ + "movl 0x08(%%ebp), %1\n\t" /* this */ \ + : "=r"(args), "=r"(obj)); \ + result = PrepareAndDispatch(obj, n, args, &stackBytesToPop); \ + fprintf(stdout, "stub returning: %d\n", result); \ + fprintf(stdout, "bytes to pop: %d\n", stackBytesToPop); \ + return result; \ + } + +# endif /* ! __GNUC__ */ + +#else +/***************************************************************************/ +// just Linux_x86 now. Add other later... + +static int PrepareAndDispatch(baz* self, uint32_t methodIndex, uint32_t* args) { + foo* a = self->other; + int p1 = (int)*args; + int p2 = (int)*(args + 1); + switch (methodIndex) { + case 1: + a->callme1(p1, p2); + break; + case 2: + a->callme2(p1, p2); + break; + case 3: + a->callme3(p1, p2); + break; + } + return 1; +} + +# define STUB_ENTRY(n) \ + nsresult baz::callme##n() { \ + void* method = PrepareAndDispatch; \ + nsresult result; \ + __asm__ __volatile__( \ + "leal 0x0c(%%ebp), %%ecx\n\t" /* args */ \ + "pushl %%ecx\n\t" \ + "pushl $" #n \ + "\n\t" /* method index */ \ + "movl 0x08(%%ebp), %%ecx\n\t" /* this */ \ + "pushl %%ecx\n\t" \ + "call *%%edx" /* PrepareAndDispatch */ \ + : "=a"(result) /* %0 */ \ + : "d"(method) /* %1 */ \ + : "memory"); \ + return result; \ + } + +#endif +/***************************************************************************/ + +STUB_ENTRY(1) +STUB_ENTRY(2) +STUB_ENTRY(3) + +int main() { + foo* a = new bar(); + baz* b = new baz(); + + /* here we make the global 'check for alloc failure' checker happy */ + if (!a || !b) return 1; + + foo* c = (foo*)b; + + b->setfoo(a); + c->callme1(1, 2); + c->callme2(2, 4); + c->callme3(3, 6); + + return 0; +} diff --git a/xpcom/reflect/xptcall/md/unix/moz.build b/xpcom/reflect/xptcall/md/unix/moz.build new file mode 100644 index 0000000000..e462ad0938 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/moz.build @@ -0,0 +1,282 @@ +# -*- 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/. + +if CONFIG["OS_ARCH"] == "Darwin": + SOURCES += [ + "xptcinvoke_darwin.cpp", + "xptcstubs_darwin.cpp", + ] + if CONFIG["TARGET_CPU"] == "ppc": + SOURCES += [ + "!xptcstubs_asm_ppc_darwin.s", + "xptcinvoke_asm_ppc_rhapsody.s", + ] + if CONFIG["TARGET_CPU"] == "x86_64": + SOURCES += [ + "xptcinvoke_asm_x86_64_unix.S", + ] + if CONFIG["TARGET_CPU"] == "aarch64": + SOURCES += [ + "xptcinvoke_asm_aarch64.S", + "xptcstubs_asm_aarch64.S", + ] + +if CONFIG["OS_ARCH"] == "GNU": + if CONFIG["TARGET_CPU"] == "x86": + SOURCES += ["xptcinvoke_gcc_x86_unix.cpp", "xptcstubs_gcc_x86_unix.cpp"] + +if CONFIG["OS_ARCH"] in ( + "Linux", + "Bitrig", + "DragonFly", + "FreeBSD", + "NetBSD", + "OpenBSD", + "SunOS", +) or CONFIG["OS_ARCH"].startswith("GNU_"): + if CONFIG["TARGET_CPU"] == "x86_64": + SOURCES += [ + "xptcinvoke_asm_x86_64_unix.S", + "xptcinvoke_x86_64_unix.cpp", + "xptcstubs_x86_64_linux.cpp", + ] + elif CONFIG["TARGET_CPU"] == "x86": + SOURCES += ["xptcinvoke_gcc_x86_unix.cpp", "xptcstubs_gcc_x86_unix.cpp"] + +if CONFIG["OS_ARCH"] in ("Linux", "FreeBSD"): + if CONFIG["TARGET_CPU"] == "ia64": + SOURCES += [ + "xptcinvoke_asm_ipf64.s", + "xptcinvoke_ipf64.cpp", + "xptcstubs_asm_ipf64.s", + "xptcstubs_ipf64.cpp", + ] + +if CONFIG["TARGET_CPU"] == "Alpha": + if CONFIG["OS_ARCH"] in ("Linux", "FreeBSD", "NetBSD"): + SOURCES += [ + "xptcinvoke_linux_alpha.cpp", + "xptcstubs_linux_alpha.cpp", + ] + elif CONFIG["OS_ARCH"] == "OpenBSD": + SOURCES += [ + "xptcinvoke_alpha_openbsd.cpp", + "xptcstubs_alpha_openbsd.cpp", + ] + +if CONFIG["TARGET_CPU"] == "arm": + if CONFIG["OS_ARCH"] == "Linux": + SOURCES += ["xptcinvoke_arm.cpp", "xptcstubs_arm.cpp"] + CXXFLAGS += ["-O2"] + elif CONFIG["OS_ARCH"] == "NetBSD": + SOURCES += [ + "xptcinvoke_arm_netbsd.cpp", + "xptcstubs_arm_netbsd.cpp", + ] + +if CONFIG["TARGET_CPU"] == "arm" and CONFIG["OS_ARCH"] in ("Bitrig", "OpenBSD"): + SOURCES += [ + "xptcinvoke_arm_openbsd.cpp", + "xptcstubs_arm_openbsd.cpp", + ] + +if CONFIG["OS_ARCH"] == "HP-UX": + if CONFIG["CC"] != "gcc": + if CONFIG["TARGET_CPU"] == "ia64": + SOURCES += [ + "xptcinvoke_asm_ipf32.s", + "xptcinvoke_ipf32.cpp", + "xptcstubs_asm_ipf32.s", + "xptcstubs_ipf32.cpp", + ] + else: + SOURCES += [ + "xptcinvoke_asm_pa32.s", + "xptcinvoke_pa32.cpp", + "xptcstubs_asm_pa32.s", + "xptcstubs_pa32.cpp", + ] + +if CONFIG["OS_ARCH"] == "Linux": + if CONFIG["TARGET_CPU"] == "hppa": + if CONFIG["CC_TYPE"] in ("clang", "gcc"): + SOURCES += [ + "xptcinvoke_asm_parisc_linux.s", + "xptcinvoke_pa32.cpp", + "xptcstubs_asm_parisc_linux.s", + "xptcstubs_pa32.cpp", + ] + elif CONFIG["COMPILE_ENVIRONMENT"]: + error("Unknown C++ compiler, xptcall assembly will probably be incorrect.") + +if CONFIG["OS_ARCH"] in ("Linux", "FreeBSD", "NetBSD", "OpenBSD"): + if CONFIG["TARGET_CPU"] == "aarch64": + SOURCES += [ + "xptcinvoke_aarch64.cpp", + "xptcinvoke_asm_aarch64.S", + "xptcstubs_aarch64.cpp", + "xptcstubs_asm_aarch64.S", + ] + if CONFIG["TARGET_CPU"] == "mips64": + SOURCES += [ + "xptcinvoke_asm_mips64.S", + "xptcinvoke_mips64.cpp", + "xptcstubs_asm_mips64.S", + "xptcstubs_mips64.cpp", + ] + if CONFIG["CC_TYPE"] == "clang": + ASFLAGS += [ + "-fno-integrated-as", + ] + if CONFIG["TARGET_CPU"] == "mips32": + SOURCES += [ + "xptcinvoke_asm_mips.S", + "xptcinvoke_mips.cpp", + "xptcstubs_asm_mips.S", + "xptcstubs_mips.cpp", + ] + if CONFIG["CC_TYPE"] == "clang": + ASFLAGS += [ + "-fno-integrated-as", + ] + +if CONFIG["OS_ARCH"] == "AIX": + if CONFIG["HAVE_64BIT_BUILD"]: + SOURCES += [ + "!xptcstubs_asm_ppc_aix64.s", + "xptcinvoke_asm_ppc_aix64.s", + "xptcinvoke_ppc_aix64.cpp", + "xptcstubs_ppc_aix64.cpp", + ] + else: + SOURCES += [ + "!xptcstubs_asm_ppc_aix.s", + "xptcinvoke_ppc_aix.cpp", + "xptcstubs_ppc_aix.cpp", + ] + if CONFIG["AIX_OBJMODEL"] == "ibm": + SOURCES += [ + "xptcinvoke_asm_ppc_ibmobj_aix.s", + ] + else: + SOURCES += [ + "xptcinvoke_asm_ppc_aix.s", + ] + +if CONFIG["TARGET_CPU"] == "ppc": + if CONFIG["OS_ARCH"] in ("Linux", "FreeBSD"): + SOURCES += [ + "xptcinvoke_asm_ppc_linux.S", + "xptcinvoke_ppc_linux.cpp", + "xptcstubs_asm_ppc_linux.S", + "xptcstubs_ppc_linux.cpp", + ] + +if CONFIG["TARGET_CPU"] == "ppc64": + if CONFIG["OS_ARCH"] in ("Linux", "FreeBSD"): + SOURCES += [ + "xptcinvoke_asm_ppc64_linux.S", + "xptcinvoke_ppc64_linux.cpp", + "xptcstubs_asm_ppc64_linux.S", + "xptcstubs_ppc64_linux.cpp", + ] + if CONFIG["CC_TYPE"] == "clang": + ASFLAGS += [ + "-fno-integrated-as", + ] + +if CONFIG["OS_ARCH"] == "OpenBSD" and CONFIG["TARGET_CPU"] == "ppc": + SOURCES += [ + "xptcinvoke_asm_ppc_openbsd.S", + "xptcinvoke_ppc_openbsd.cpp", + "xptcstubs_asm_ppc_openbsd.S", + "xptcstubs_ppc_openbsd.cpp", + ] + +if CONFIG["OS_ARCH"] == "Linux" and CONFIG["TARGET_CPU"] == "sparc": + SOURCES += [ + "xptcinvoke_asm_sparc_linux_GCC3.s", + "xptcinvoke_sparc_solaris.cpp", + "xptcstubs_asm_sparc_solaris.s", + "xptcstubs_sparc_solaris.cpp", + ] + +if CONFIG["OS_ARCH"] == "NetBSD" and CONFIG["TARGET_CPU"] == "sparc": + SOURCES += [ + "xptcinvoke_asm_sparc_netbsd.s", + "xptcinvoke_sparc_netbsd.cpp", + "xptcstubs_asm_sparc_netbsd.s", + "xptcstubs_sparc_netbsd.cpp", + ] + +if CONFIG["OS_ARCH"] == "OpenBSD" and CONFIG["TARGET_CPU"] == "sparc": + SOURCES += [ + "xptcinvoke_asm_sparc_openbsd.s", + "xptcinvoke_sparc_openbsd.cpp", + "xptcstubs_asm_sparc_openbsd.s", + "xptcstubs_sparc_openbsd.cpp", + ] + +if ( + CONFIG["OS_ARCH"] in ("OpenBSD", "FreeBSD", "Linux", "SunOS") + and CONFIG["TARGET_CPU"] == "sparc64" +): + SOURCES += [ + "xptcinvoke_asm_sparc64_openbsd.s", + "xptcinvoke_sparc64_openbsd.cpp", + "xptcstubs_asm_sparc64_openbsd.s", + "xptcstubs_sparc64_openbsd.cpp", + ] + +if CONFIG["OS_ARCH"] == "Linux": + if CONFIG["TARGET_CPU"] == "s390": + SOURCES += [ + "xptcinvoke_linux_s390.cpp", + "xptcstubs_linux_s390.cpp", + ] + CXXFLAGS += [ + "-fno-strict-aliasing", + "-fno-inline", + "-fomit-frame-pointer", + "-mbackchain", + ] + elif CONFIG["TARGET_CPU"] == "s390x": + SOURCES += [ + "xptcinvoke_linux_s390x.cpp", + "xptcstubs_linux_s390x.cpp", + ] + CXXFLAGS += [ + "-fno-strict-aliasing", + "-fno-inline", + "-fomit-frame-pointer", + "-mbackchain", + ] + if CONFIG["CC_TYPE"] == "clang": + CXXFLAGS += [ + "-fno-integrated-as", + ] + +if CONFIG["OS_ARCH"] in ("Linux", "OpenBSD") and CONFIG["TARGET_CPU"] == "riscv64": + SOURCES += [ + "xptcinvoke_asm_riscv64.S", + "xptcinvoke_riscv64.cpp", + "xptcstubs_asm_riscv64.S", + "xptcstubs_riscv64.cpp", + ] + +if CONFIG["OS_ARCH"] == "Linux" and CONFIG["TARGET_CPU"] == "loongarch64": + SOURCES += [ + "xptcinvoke_asm_loongarch64.S", + "xptcinvoke_loongarch64.cpp", + "xptcstubs_asm_loongarch64.S", + "xptcstubs_loongarch64.cpp", + ] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "../..", +] diff --git a/xpcom/reflect/xptcall/md/unix/vtable_layout_x86.cpp b/xpcom/reflect/xptcall/md/unix/vtable_layout_x86.cpp new file mode 100644 index 0000000000..9139d4b2aa --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/vtable_layout_x86.cpp @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 code contributed by Bert Driehuis <bert_driehuis@nl.compuware.com> */ + +#include <stdio.h> + +// Try to determine the vtable layout generated by G++ +// Produces the offset at which the first vtable entry can be +// found, and the factor to apply for subsequent entries on stdout. +// Example output: +// #define GCC_VTABLE_START 0xc +// #define GCC_VTABLE_FACTOR 0x8 + +class test { +public: + virtual int t1(void); + virtual int t2(void); + int x; +}; + +test::test() { this->x = 0x12121212; }; + +int test::t1(void) { return 1; } +int test::t2(void) { return 2; } + +void die(char *x) { + fprintf(stderr, "%s\n", x); + exit(1); +} + +int +main() +{ + int i; + test *t = new test(); + int *tp = (int *) t; + int off1 = -1; + int off2 = -1; + int factor; + int factorshift; + + if (*tp++ != 0x12121212) + die("Integer element test::x not found!"); + tp = (int *) *tp; + for (i = 0; i < 10; i++) { + if (tp[i] == (int) t->t1) + off1 = i; + if (tp[i] == (int) t->t2) + off2 = i; + } + if (off1 == -1 || off2 == -1) + die("Could not determine offset into vtable!"); + factor = (off2 - off1) * 4; + factorshift = -1; + while (factor) { + factorshift++; + factor >>= 1; + } + printf("/* Automatically generated by vtable_layout_x86.cpp */\n"); + printf("#define GCC_VTABLE_START\t0x%x\n", off1 * 4); + printf("#define GCC_VTABLE_FACTOR\t0x%x\n", (off2 - off1) * 4); + printf("#define GCC_VTABLE_SHIFT\t0x%x\n", factorshift); + exit(0); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h b/xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h new file mode 100644 index 0000000000..5d6e71c23c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h @@ -0,0 +1,16 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Special include file for xptc*_gcc_x86_unix.cpp */ + +// +// this may improve the static function calls, but may not. +// + +#ifdef MOZ_NEED_LEADING_UNDERSCORE +#define SYMBOL_UNDERSCORE "_" +#else +#define SYMBOL_UNDERSCORE +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp new file mode 100644 index 0000000000..385cba17ad --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp @@ -0,0 +1,168 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(__aarch64__) +#error "This code is for Linux AArch64 only." +#endif + + +/* "Procedure Call Standard for the ARM 64-bit Architecture" document, sections + * "5.4 Parameter Passing" and "6.1.2 Procedure Calling" contain all the + * needed information. + * + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf + */ + +#ifndef __AARCH64EL__ +#error "Only little endian compatibility was tested" +#endif + +// The AAPCS doesn't require argument widening, but Apple's calling convention +// does. If we are really fortunate, the compiler will clean up all the +// copying for us. +template<typename T> +inline uint64_t normalize_arg(T value) { + return (uint64_t)value; +} + +template<> +inline uint64_t normalize_arg(float value) { + uint64_t result = 0; + memcpy(&result, &value, sizeof(value)); + return result; +} + +template<> +inline uint64_t normalize_arg(double value) { + uint64_t result = 0; + memcpy(&result, &value, sizeof(value)); + return result; +} + +/* + * Allocation of function arguments to their appropriate place in registers + * if possible and then to the stack. Handling of 'that' argument which + * goes to register r0 is handled separately and does not belong here. + * + * Note that we are handling integer arguments and floating-point arguments + * identically, depending on which register area is passed to this function. + * + * 'reg_args' - pointer to the current position in the buffer, + * corresponding to the register arguments. + * 'reg_args_end' - pointer to the end of the registers argument + * buffer. + * 'stack_args' - pointer to the current position in the buffer, + * corresponding to the arguments on stack. + * 'data' - typed data to put on the stack. + */ +template<typename T> +static inline void alloc_arg(uint64_t* ®_args, + uint64_t* reg_args_end, + void* &stack_args, + T* data) +{ + if (reg_args < reg_args_end) { + *reg_args = normalize_arg(*data); + reg_args++; + } else { + // According to the ABI, types that are smaller than 8 bytes are + // passed in registers or 8-byte stack slots. This rule is only + // partially true on Apple platforms, where types smaller than 8 + // bytes occupy only the space they require on the stack and + // their stack slot must be properly aligned. +#ifdef __APPLE__ + const size_t aligned_size = sizeof(T); +#else + const size_t aligned_size = 8; +#endif + // Ensure the pointer is aligned for the type + uintptr_t addr = (reinterpret_cast<uintptr_t>(stack_args) + aligned_size - 1) & ~(aligned_size - 1); + memcpy(reinterpret_cast<void*>(addr), data, sizeof(T)); + // Point the stack to the next slot. + stack_args = reinterpret_cast<void*>(addr + aligned_size); + } +} + +extern "C" void +invoke_copy_to_stack(uint64_t* stk, uint64_t *end, + uint32_t paramCount, nsXPTCVariant* s) +{ + uint64_t* ireg_args = stk; + uint64_t* ireg_end = ireg_args + 8; + // Pun on integer and floating-point registers being the same size. + uint64_t* freg_args = ireg_end; + uint64_t* freg_end = freg_args + 8; + void* stack_args = freg_end; + + // leave room for 'that' argument in x0 + ++ireg_args; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsIndirect()) { + void* ptr = &s->val; + alloc_arg(ireg_args, ireg_end, stack_args, &ptr); + } else { + switch (s->type) { + case nsXPTType::T_FLOAT: + alloc_arg(freg_args, freg_end, stack_args, &s->val.f); + break; + case nsXPTType::T_DOUBLE: + alloc_arg(freg_args, freg_end, stack_args, &s->val.d); + break; + case nsXPTType::T_I8: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.i8); + break; + case nsXPTType::T_I16: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.i16); + break; + case nsXPTType::T_I32: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.i32); + break; + case nsXPTType::T_I64: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.i64); + break; + case nsXPTType::T_U8: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.u8); + break; + case nsXPTType::T_U16: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.u16); + break; + case nsXPTType::T_U32: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.u32); + break; + case nsXPTType::T_U64: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.u64); + break; + case nsXPTType::T_BOOL: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.b); + break; + case nsXPTType::T_CHAR: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.c); + break; + case nsXPTType::T_WCHAR: + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.wc); + break; + default: + // all the others are plain pointer types + alloc_arg(ireg_args, ireg_end, stack_args, &s->val.p); + break; + } + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp new file mode 100644 index 0000000000..dc111e4358 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp @@ -0,0 +1,144 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +__asm__("invoke_copy_to_stack") __attribute__((used)); + +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *d = (uint64_t)s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *d = (uint64_t)s->val.i8; break; + case nsXPTType::T_I16 : *d = (uint64_t)s->val.i16; break; + case nsXPTType::T_I32 : *d = (uint64_t)s->val.i32; break; + case nsXPTType::T_I64 : *d = (uint64_t)s->val.i64; break; + case nsXPTType::T_U8 : *d = (uint64_t)s->val.u8; break; + case nsXPTType::T_U16 : *d = (uint64_t)s->val.u16; break; + case nsXPTType::T_U32 : *d = (uint64_t)s->val.u32; break; + case nsXPTType::T_U64 : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // convert floats to doubles if they are to be passed + // via registers so we can just deal with doubles later + union { uint64_t u64; double d; } t; + t.d = (double)s->val.f; + *d = t.u64; + } + else + // otherwise copy to stack normally + *d = (uint64_t)s->val.u32; + break; + case nsXPTType::T_DOUBLE : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_BOOL : *d = (uint64_t)s->val.b; break; + case nsXPTType::T_CHAR : *d = (uint64_t)s->val.c; break; + case nsXPTType::T_WCHAR : *d = (uint64_t)s->val.wc; break; + default: + // all the others are plain pointer types + *d = (uint64_t)s->val.p; + break; + } + } +} + +/* + * EXPORT_XPCOM_API(nsresult) + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ +__asm__( + "#### NS_InvokeByIndex ####\n" +".text\n\t" + ".align 5\n\t" + ".globl NS_InvokeByIndex\n\t" + ".ent NS_InvokeByIndex\n" +"NS_InvokeByIndex:\n\t" + ".frame $15,32,$26,0\n\t" + ".mask 0x4008000,-32\n\t" + "ldgp $29,0($27)\n" +"$NS_InvokeByIndex..ng:\n\t" + "subq $30,32,$30\n\t" + "stq $26,0($30)\n\t" + "stq $15,8($30)\n\t" + "bis $30,$30,$15\n\t" + ".prologue 1\n\t" + + /* + * Allocate enough stack space to hold the greater of 6 or "paramCount"+1 + * parameters. (+1 for "this" pointer) Room for at least 6 parameters + * is required for storage of those passed via registers. + */ + + "bis $31,5,$2\n\t" /* count = MAX(5, "paramCount") */ + "cmplt $2,$18,$1\n\t" + "cmovne $1,$18,$2\n\t" + "s8addq $2,16,$1\n\t" /* room for count+1 params (8 bytes each) */ + "bic $1,15,$1\n\t" /* stack space is rounded up to 0 % 16 */ + "subq $30,$1,$30\n\t" + + "stq $16,0($30)\n\t" /* save "that" (as "this" pointer) */ + "stq $17,16($15)\n\t" /* save "methodIndex" */ + + "addq $30,8,$16\n\t" /* pass stack pointer */ + "bis $18,$18,$17\n\t" /* pass "paramCount" */ + "bis $19,$19,$18\n\t" /* pass "params" */ + "bsr $26,$invoke_copy_to_stack..ng\n\t" /* call invoke_copy_to_stack */ + + /* + * Copy the first 6 parameters to registers and remove from stack frame. + * Both the integer and floating point registers are set for each parameter + * except the first which is the "this" pointer. (integer only) + * The floating point registers are all set as doubles since the + * invoke_copy_to_stack function should have converted the floats. + */ + "ldq $16,0($30)\n\t" /* integer registers */ + "ldq $17,8($30)\n\t" + "ldq $18,16($30)\n\t" + "ldq $19,24($30)\n\t" + "ldq $20,32($30)\n\t" + "ldq $21,40($30)\n\t" + "ldt $f17,8($30)\n\t" /* floating point registers */ + "ldt $f18,16($30)\n\t" + "ldt $f19,24($30)\n\t" + "ldt $f20,32($30)\n\t" + "ldt $f21,40($30)\n\t" + + "addq $30,48,$30\n\t" /* remove params from stack */ + + /* + * Call the virtual function with the constructed stack frame. + */ + "bis $16,$16,$1\n\t" /* load "this" */ + "ldq $2,16($15)\n\t" /* load "methodIndex" */ + "ldq $1,0($1)\n\t" /* load vtable */ + "s8addq $2,$31,$2\n\t" /* vtable index = "methodIndex" * 8 */ + "addq $1,$2,$1\n\t" + "ldq $27,0($1)\n\t" /* load address of function */ + "jsr $26,($27),0\n\t" /* call virtual function */ + "ldgp $29,0($26)\n\t" + + "bis $15,$15,$30\n\t" + "ldq $26,0($30)\n\t" + "ldq $15,8($30)\n\t" + "addq $30,32,$30\n\t" + "ret $31,($26),1\n\t" + ".end NS_InvokeByIndex" + ); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp new file mode 100644 index 0000000000..39a6b406f4 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp @@ -0,0 +1,417 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#include "mozilla/Compiler.h" + +#if !defined(__arm__) || !(defined(LINUX) || defined(ANDROID) || defined(XP_IOS)) +#error "This code is for Linux/iOS ARM only. Check that it works on your system, too.\nBeware that this code is highly compiler dependent." +#endif + +#if MOZ_IS_GCC +#if defined(__ARM_EABI__) && !defined(__ARM_PCS_VFP) && !defined(__ARM_PCS) +#error "Can't identify floating point calling conventions.\nPlease ensure that your toolchain defines __ARM_PCS or __ARM_PCS_VFP." +#endif +#endif + +#ifndef __ARM_PCS_VFP + +/* This function copies a 64-bits word from dw to the given pointer in + * a buffer delimited by start and end, possibly wrapping around the + * buffer boundaries, and/or properly aligning the data at 64-bits word + * boundaries (for EABI). + * start and end are both assumed to be 64-bits aligned. + * Returns a pointer to the second 32-bits word copied (to accomodate + * the invoke_copy_to_stack loop). + */ +static uint32_t * +copy_double_word(uint32_t *start, uint32_t *current, uint32_t *end, uint64_t *dw) +{ +#ifdef __ARM_EABI__ + /* Aligning the pointer for EABI */ + current = (uint32_t *)(((uint32_t)current + 7) & ~7); + /* Wrap when reaching the end of the buffer */ + if (current == end) current = start; +#else + /* On non-EABI, 64-bits values are not aligned and when we reach the end + * of the buffer, we need to write half of the data at the end, and the + * other half at the beginning. */ + if (current == end - 1) { + *current = ((uint32_t*)dw)[0]; + *start = ((uint32_t*)dw)[1]; + return start; + } +#endif + + *((uint64_t*) current) = *dw; + return current + 1; +} + +/* See stack_space comment in NS_InvokeByIndex to see why this needs not to + * be static on DEBUG builds. */ +#ifndef DEBUG +static +#endif +void +invoke_copy_to_stack(uint32_t* stk, uint32_t *end, + uint32_t paramCount, nsXPTCVariant* s) +{ + /* The stack buffer is 64-bits aligned. The end argument points to its end. + * The caller is assumed to create a stack buffer of at least four 32-bits + * words. + * We use the last three 32-bit words to store the values for r1, r2 and r3 + * for the method call, i.e. the first words for arguments passing. + */ + uint32_t *d = end - 3; + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + /* Wrap when reaching the end of the stack buffer */ + if (d == end) d = stk; + NS_ASSERTION(d >= stk && d < end, + "invoke_copy_to_stack is copying outside its given buffer"); + if(s->IsIndirect()) + { + *((void**)d) = &s->val; + continue; + } + // According to the ARM EABI, integral types that are smaller than a word + // are to be sign/zero-extended to a full word and treated as 4-byte values. + + switch(s->type) + { + case nsXPTType::T_I8 : *((int32_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : + d = copy_double_word(stk, d, end, (uint64_t *)&s->val.i64); + break; + case nsXPTType::T_U8 : *((uint32_t*)d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : + d = copy_double_word(stk, d, end, (uint64_t *)&s->val.u64); + break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : + d = copy_double_word(stk, d, end, (uint64_t *)&s->val.d); + break; + case nsXPTType::T_BOOL : *((int32_t*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((int32_t*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +typedef nsresult (*vtable_func)(nsISupports *, uint32_t, uint32_t, uint32_t); + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_IGNORE +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + +/* This is to call a given method of class that. + * The parameters are in params, the number is in paramCount. + * The routine will issue calls to count the number of words + * required for argument passing and to copy the arguments to + * the stack. + * ACPS passes the first 3 params in r1-r3 (with exceptions for 64-bits + * arguments), and the remaining goes onto the stack. + * We allocate a buffer on the stack for a "worst case" estimate of how much + * stack might be needed for EABI, i.e. twice the number of parameters. + * The end of this buffer will be used to store r1 to r3, so that the start + * of the stack is the remaining parameters. + * The magic here is to call the method with "that" and three 32-bits + * arguments corresponding to r1-r3, so that the compiler generates the + * proper function call. The stack will also contain the remaining arguments. + * + * !!! IMPORTANT !!! + * This routine makes assumptions about the vtable layout of the c++ compiler. It's implemented + * for arm-linux GNU g++ >= 2.8.1 (including egcs and gcc-2.95.[1-3])! + * + */ + + vtable_func *vtable, func; + int base_size = (paramCount > 1) ? paramCount : 2; + +/* !!! IMPORTANT !!! + * On DEBUG builds, the NS_ASSERTION used in invoke_copy_to_stack needs to use + * the stack to pass the 5th argument to NS_DebugBreak. When invoke_copy_to_stack + * is inlined, this can result, depending on the compiler and flags, in the + * stack pointer not pointing at stack_space when the method is called at the + * end of this function. More generally, any function call requiring stack + * allocation of arguments is unsafe to be inlined in this function. + */ + uint32_t *stack_space = (uint32_t *) __builtin_alloca(base_size * 8); + + invoke_copy_to_stack(stack_space, &stack_space[base_size * 2], + paramCount, params); + + vtable = *reinterpret_cast<vtable_func **>(that); + func = vtable[methodIndex]; + + return func(that, stack_space[base_size * 2 - 3], + stack_space[base_size * 2 - 2], + stack_space[base_size * 2 - 1]); +} + +#else /* __ARM_PCS_VFP */ + +/* "Procedure Call Standard for the ARM Architecture" document, sections + * "5.5 Parameter Passing" and "6.1.2 Procedure Calling" contain all the + * needed information. + * + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf + */ + +#if defined(__thumb__) && !defined(__thumb2__) +#error "Thumb1 is not supported" +#endif + +#ifndef __ARMEL__ +#error "Only little endian compatibility was tested" +#endif + +/* + * Allocation of integer function arguments initially to registers r1-r3 + * and then to stack. Handling of 'this' argument which goes to r0 registers + * is handled separately and does not belong to these two inline functions. + * + * The doubleword arguments are allocated to even:odd + * register pairs or get aligned at 8-byte boundary on stack. The "holes" + * which may appear as a result of this realignment remain unused. + * + * 'ireg_args' - pointer to the current position in the buffer, + * corresponding to the register arguments + * 'stack_args' - pointer to the current position in the buffer, + * corresponding to the arguments on stack + * 'end' - pointer to the end of the registers argument + * buffer (it is guaranteed to be 8-bytes aligned) + */ + +static inline void copy_word(uint32_t* &ireg_args, + uint32_t* &stack_args, + uint32_t* end, + uint32_t data) +{ + if (ireg_args < end) { + *ireg_args = data; + ireg_args++; + } else { + *stack_args = data; + stack_args++; + } +} + +static inline void copy_dword(uint32_t* &ireg_args, + uint32_t* &stack_args, + uint32_t* end, + uint64_t data) +{ + if (ireg_args + 1 < end) { + if ((uint32_t)ireg_args & 4) { + ireg_args++; + } + *(uint64_t *)ireg_args = data; + ireg_args += 2; + } else { + ireg_args = end; + if ((uint32_t)stack_args & 4) { + stack_args++; + } + *(uint64_t *)stack_args = data; + stack_args += 2; + } +} + +/* + * Allocation of floating point arguments to VFP registers (s0-s15, d0-d7). + * + * Unlike integer registers allocation, "back-filling" needs to be + * supported. For example, the third floating point argument in the + * following function is going to be allocated to s1 register, back-filling + * the "hole": + * void f(float s0, double d1, float s1) + * + * Refer to the "Procedure Call Standard for the ARM Architecture" document + * for more details. + * + * 'vfp_s_args' - pointer to the current position in the buffer with + * the next unallocated single precision register + * 'vfp_d_args' - pointer to the current position in the buffer with + * the next unallocated double precision register, + * it has the same value as 'vfp_s_args' when back-filling + * is not used + * 'end' - pointer to the end of the vfp registers argument + * buffer (it is guaranteed to be 8-bytes aligned) + * + * Mozilla bugtracker has a test program attached which be used for + * experimenting with VFP registers allocation code and testing its + * correctness: + * https://bugzilla.mozilla.org/show_bug.cgi?id=601914#c19 + */ + +static inline bool copy_vfp_single(float* &vfp_s_args, double* &vfp_d_args, + float* end, float data) +{ + if (vfp_s_args >= end) + return false; + + *vfp_s_args = data; + vfp_s_args++; + if (vfp_s_args < (float *)vfp_d_args) { + // It was the case of back-filling, now the next free single precision + // register should overlap with the next free double precision register + vfp_s_args = (float *)vfp_d_args; + } else if (vfp_s_args > (float *)vfp_d_args) { + // also update the pointer to the next free double precision register + vfp_d_args++; + } + return true; +} + +static inline bool copy_vfp_double(float* &vfp_s_args, double* &vfp_d_args, + float* end, double data) +{ + if (vfp_d_args >= (double *)end) { + // The back-filling continues only so long as no VFP CPRC has been + // allocated to a slot on the stack. Basically no VFP registers can + // be allocated after this point. + vfp_s_args = end; + return false; + } + + if (vfp_s_args == (float *)vfp_d_args) { + // also update the pointer to the next free single precision register + vfp_s_args += 2; + } + *vfp_d_args = data; + vfp_d_args++; + return true; +} + +static void +invoke_copy_to_stack(uint32_t* stk, uint32_t *end, + uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t *ireg_args = end - 3; + float *vfp_s_args = (float *)end; + double *vfp_d_args = (double *)end; + float *vfp_end = vfp_s_args + 16; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsIndirect()) { + copy_word(ireg_args, stk, end, (uint32_t)&s->val); + continue; + } + // According to the ARM EABI, integral types that are smaller than a word + // are to be sign/zero-extended to a full word and treated as 4-byte values + switch (s->type) + { + case nsXPTType::T_FLOAT: + if (!copy_vfp_single(vfp_s_args, vfp_d_args, vfp_end, s->val.f)) { + copy_word(end, stk, end, reinterpret_cast<uint32_t&>(s->val.f)); + } + break; + case nsXPTType::T_DOUBLE: + if (!copy_vfp_double(vfp_s_args, vfp_d_args, vfp_end, s->val.d)) { + copy_dword(end, stk, end, reinterpret_cast<uint64_t&>(s->val.d)); + } + break; + case nsXPTType::T_I8: copy_word(ireg_args, stk, end, s->val.i8); break; + case nsXPTType::T_I16: copy_word(ireg_args, stk, end, s->val.i16); break; + case nsXPTType::T_I32: copy_word(ireg_args, stk, end, s->val.i32); break; + case nsXPTType::T_I64: copy_dword(ireg_args, stk, end, s->val.i64); break; + case nsXPTType::T_U8: copy_word(ireg_args, stk, end, s->val.u8); break; + case nsXPTType::T_U16: copy_word(ireg_args, stk, end, s->val.u16); break; + case nsXPTType::T_U32: copy_word(ireg_args, stk, end, s->val.u32); break; + case nsXPTType::T_U64: copy_dword(ireg_args, stk, end, s->val.u64); break; + case nsXPTType::T_BOOL: copy_word(ireg_args, stk, end, s->val.b); break; + case nsXPTType::T_CHAR: copy_word(ireg_args, stk, end, s->val.c); break; + case nsXPTType::T_WCHAR: copy_word(ireg_args, stk, end, s->val.wc); break; + default: + // all the others are plain pointer types + copy_word(ireg_args, stk, end, reinterpret_cast<uint32_t>(s->val.p)); + break; + } + } +} + +typedef uint32_t (*vtable_func)(nsISupports *, uint32_t, uint32_t, uint32_t); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + vtable_func *vtable = *reinterpret_cast<vtable_func **>(that); + vtable_func func = vtable[methodIndex]; + // 'register uint32_t result asm("r0")' could be used here, but it does not + // seem to be reliable in all cases: http://gcc.gnu.org/PR46164 + nsresult result; + asm ( + "mov r3, sp\n" + "mov %[stack_space_size], %[param_count_plus_2], lsl #3\n" + "tst r3, #4\n" /* check stack alignment */ + + "add %[stack_space_size], #(4 * 16)\n" /* space for VFP registers */ + "mov r3, %[params]\n" + + "it ne\n" + "addne %[stack_space_size], %[stack_space_size], #4\n" + "sub r0, sp, %[stack_space_size]\n" /* allocate space on stack */ + + "sub r2, %[param_count_plus_2], #2\n" + "mov sp, r0\n" + + "add r1, r0, %[param_count_plus_2], lsl #3\n" + "blx %[invoke_copy_to_stack]\n" + + "add ip, sp, %[param_count_plus_2], lsl #3\n" + "mov r0, %[that]\n" + "ldmdb ip, {r1, r2, r3}\n" + "vldm ip, {d0, d1, d2, d3, d4, d5, d6, d7}\n" + "blx %[func]\n" + + "add sp, sp, %[stack_space_size]\n" /* cleanup stack */ + "mov %[stack_space_size], r0\n" /* it's actually 'result' variable */ + : [stack_space_size] "=&r" (result) + : [func] "r" (func), + [that] "r" (that), + [params] "r" (params), + [param_count_plus_2] "r" (paramCount + 2), + [invoke_copy_to_stack] "r" (invoke_copy_to_stack) + : "cc", "memory", + // Mark all the scratch registers as clobbered because they may be + // modified by the functions, called from this inline assembly block + "r0", "r1", "r2", "r3", "ip", "lr", + "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", + // Also unconditionally mark d16-d31 registers as clobbered even though + // they actually don't exist in vfpv2 and vfpv3-d16 variants. There is + // no way to identify VFP variant using preprocessor at the momemnt + // (see http://gcc.gnu.org/PR46128 for more details), but fortunately + // current versions of gcc do not seem to complain about these registers + // even when this code is compiled with '-mfpu=vfpv3-d16' option. + // If gcc becomes more strict in the future and/or provides a way to + // identify VFP variant, the following d16-d31 registers list needs + // to be wrapped into some #ifdef + "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", + "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31" + ); + return result; +} + +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_netbsd.cpp new file mode 100644 index 0000000000..2624c1e892 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_netbsd.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +// Remember that these 'words' are 32bit DWORDS + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; +} + +static void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" +struct my_params_struct { + nsISupports* that; + uint32_t Index; + uint32_t Count; + nsXPTCVariant* params; + uint32_t fn_count; + uint32_t fn_copy; +}; + +XPTC_PUBLIC_API(nsresult) +XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + uint32_t result; + struct my_params_struct my_params; + my_params.that = that; + my_params.Index = methodIndex; + my_params.Count = paramCount; + my_params.params = params; + my_params.fn_copy = (uint32_t) &invoke_copy_to_stack; + my_params.fn_count = (uint32_t) &invoke_count_words; + +/* This is to call a given method of class that. + * The parameters are in params, the number is in paramCount. + * The routine will issue calls to count the number of words + * required for argument passing and to copy the arguments to + * the stack. + * Since APCS passes the first 3 params in r1-r3, we need to + * load the first three words from the stack and correct the stack + * pointer (sp) in the appropriate way. This means: + * + * 1.) more than 3 arguments: load r1-r3, correct sp and remember No. + * of bytes left on the stack in r4 + * + * 2.) <= 2 args: load r1-r3 (we won't be causing a stack overflow I hope), + * restore sp as if nothing had happened and set the marker r4 to zero. + * + * Afterwards sp will be restored using the value in r4 (which is not a temporary register + * and will be preserved by the function/method called according to APCS [ARM Procedure + * Calling Standard]). + * + * !!! IMPORTANT !!! + * This routine makes assumptions about the vtable layout of the c++ compiler. It's implemented + * for arm-linux GNU g++ >= 2.8.1 (including egcs and gcc-2.95.[1-3])! + * + */ + + __asm__ __volatile__( + "ldr r1, [%1, #12] \n\t" /* prepare to call invoke_count_words */ + "ldr ip, [%1, #16] \n\t" /* r0=paramCount, r1=params */ + "ldr r0, [%1, #8] \n\t" + "mov lr, pc \n\t" /* call it... */ + "mov pc, ip \n\t" + "mov r4, r0, lsl #2 \n\t" /* This is the amount of bytes needed. */ + "sub sp, sp, r4 \n\t" /* use stack space for the args... */ + "mov r0, sp \n\t" /* prepare a pointer an the stack */ + "ldr r1, [%1, #8] \n\t" /* =paramCount */ + "ldr r2, [%1, #12] \n\t" /* =params */ + "ldr ip, [%1, #20] \n\t" /* =invoke_copy_to_stack */ + "mov lr, pc \n\t" /* copy args to the stack like the */ + "mov pc, ip \n\t" /* compiler would. */ + "ldr r0, [%1] \n\t" /* =that */ + "ldr r1, [r0, #0] \n\t" /* get that->vtable offset */ + "ldr r2, [%1, #4] \n\t" + "add r2, r1, r2, lsl #3\n\t" /* a vtable_entry(x)=8 + (8 bytes * x) */ + "add r2, r2, #8 \n\t" /* with this compilers */ + "ldr r3, [r2] \n\t" /* get virtual offset from vtable */ + "mov r3, r3, lsl #16 \n\t" + "add r0, r0, r3, asr #16\n\t" + "ldr ip, [r2, #4] \n\t" /* get method address from vtable */ + "cmp r4, #12 \n\t" /* more than 3 arguments??? */ + "ldmgtia sp!, {r1, r2, r3}\n\t" /* yes: load arguments for r1-r3 */ + "subgt r4, r4, #12 \n\t" /* and correct the stack pointer */ + "ldmleia sp, {r1, r2, r3}\n\t" /* no: load r1-r3 from stack */ + "addle sp, sp, r4 \n\t" /* and restore stack pointer */ + "movle r4, #0 \n\t" /* a mark for restoring sp */ + "mov lr, pc \n\t" /* call mathod */ + "mov pc, ip \n\t" + "add sp, sp, r4 \n\t" /* restore stack pointer */ + "mov %0, r0 \n\t" /* the result... */ + : "=r" (result) + : "r" (&my_params) + : "r0", "r1", "r2", "r3", "r4", "ip", "lr" + ); + + return result; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_openbsd.cpp new file mode 100644 index 0000000000..60b0574200 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_openbsd.cpp @@ -0,0 +1,183 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +// Remember that these 'words' are 32bit DWORDS + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; +} + +static void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" { + struct my_params_struct { + nsISupports* that; + uint32_t Index; + uint32_t Count; + nsXPTCVariant* params; + uint32_t fn_count; + uint32_t fn_copy; + }; +} + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + uint32_t result; + struct my_params_struct my_params; + my_params.that = that; + my_params.Index = methodIndex; + my_params.Count = paramCount; + my_params.params = params; + my_params.fn_copy = (uint32_t) &invoke_copy_to_stack; + my_params.fn_count = (uint32_t) &invoke_count_words; + +/* This is to call a given method of class that. + * The parameters are in params, the number is in paramCount. + * The routine will issue calls to count the number of words + * required for argument passing and to copy the arguments to + * the stack. + * Since APCS passes the first 3 params in r1-r3, we need to + * load the first three words from the stack and correct the stack + * pointer (sp) in the appropriate way. This means: + * + * 1.) more than 3 arguments: load r1-r3, correct sp and remember No. + * of bytes left on the stack in r4 + * + * 2.) <= 2 args: load r1-r3 (we won't be causing a stack overflow I hope), + * restore sp as if nothing had happened and set the marker r4 to zero. + * + * Afterwards sp will be restored using the value in r4 (which is not a temporary register + * and will be preserved by the function/method called according to APCS [ARM Procedure + * Calling Standard]). + * + * !!! IMPORTANT !!! + * This routine makes assumptions about the vtable layout of the c++ compiler. It's implemented + * for arm-linux GNU g++ >= 2.8.1 (including egcs and gcc-2.95.[1-3])! + * + */ + +#ifdef __GNUC__ + __asm__ __volatile__( + "ldr r1, [%1, #12] \n\t" /* prepare to call invoke_count_words */ + "ldr ip, [%1, #16] \n\t" /* r0=paramCount, r1=params */ + "ldr r0, [%1, #8] \n\t" + "mov lr, pc \n\t" /* call it... */ + "mov pc, ip \n\t" + "mov r4, r0, lsl #2 \n\t" /* This is the amount of bytes needed. */ + "sub sp, sp, r4 \n\t" /* use stack space for the args... */ + "mov r0, sp \n\t" /* prepare a pointer an the stack */ + "ldr r1, [%1, #8] \n\t" /* =paramCount */ + "ldr r2, [%1, #12] \n\t" /* =params */ + "ldr ip, [%1, #20] \n\t" /* =invoke_copy_to_stack */ + "mov lr, pc \n\t" /* copy args to the stack like the */ + "mov pc, ip \n\t" /* compiler would. */ + "ldr r0, [%1] \n\t" /* =that */ + "ldr r1, [r0, #0] \n\t" /* get that->vtable offset */ + "ldr r2, [%1, #4] \n\t" + "mov r2, r2, lsl #2 \n\t" /* a vtable_entry(x)=8 + (4 bytes * x) */ + "ldr ip, [r1, r2] \n\t" /* get method adress from vtable */ + "cmp r4, #12 \n\t" /* more than 3 arguments??? */ + "ldmgtia sp!, {r1, r2, r3}\n\t" /* yes: load arguments for r1-r3 */ + "subgt r4, r4, #12 \n\t" /* and correct the stack pointer */ + "ldmleia sp, {r1, r2, r3}\n\t" /* no: load r1-r3 from stack */ + "addle sp, sp, r4 \n\t" /* and restore stack pointer */ + "movle r4, #0 \n\t" /* a mark for restoring sp */ + "ldr r0, [%1, #0] \n\t" /* get (self) */ + "mov lr, pc \n\t" /* call mathod */ + "mov pc, ip \n\t" + "add sp, sp, r4 \n\t" /* restore stack pointer */ + "mov %0, r0 \n\t" /* the result... */ + : "=r" (result) + : "r" (&my_params), "m" (my_params) + : "r0", "r1", "r2", "r3", "r4", "ip", "lr", "sp" + ); +#else +#error "Unsupported compiler. Use g++ >= 2.8 for OpenBSD/arm." +#endif /* G++ >= 2.8 */ + + return result; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_aarch64.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_aarch64.S new file mode 100644 index 0000000000..69a55b1c1c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_aarch64.S @@ -0,0 +1,92 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef __APPLE__ +#define SYM(x) _ ## x +#else +#define SYM(x) x +#endif + + .text + .align 2 + .globl SYM(_NS_InvokeByIndex) +#ifndef __APPLE__ + .type _NS_InvokeByIndex,@function +#endif + +/* + * _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ + +SYM(_NS_InvokeByIndex): + .cfi_startproc + # set up frame + stp x29, x30, [sp,#-32]! + .cfi_adjust_cfa_offset 32 + .cfi_rel_offset x29, 0 + .cfi_rel_offset x30, 8 + mov x29, sp + .cfi_def_cfa_register x29 + stp x19, x20, [sp,#16] + .cfi_rel_offset x19, 16 + .cfi_rel_offset x20, 24 + + # save methodIndex across function calls + mov w20, w1 + + # end of stack area passed to invoke_copy_to_stack + mov x1, sp + + # assume 8 bytes of stack for each argument with 16-byte alignment + add w19, w2, #1 + and w19, w19, #0xfffffffe + sub sp, sp, w19, uxth #3 + + # temporary place to store args passed in r0-r7,v0-v7 + sub sp, sp, #128 + + # save 'that' on stack + str x0, [sp] + + # start of stack area passed to invoke_copy_to_stack + mov x0, sp + bl SYM(invoke_copy_to_stack) + + # load arguments passed in r0-r7 + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp],#64 + + # load arguments passed in v0-v7 + ldp d6, d7, [sp, #48] + ldp d4, d5, [sp, #32] + ldp d2, d3, [sp, #16] + ldp d0, d1, [sp],#64 + + # call the method + ldr x16, [x0] + add x16, x16, w20, uxth #3 + ldr x16, [x16] + blr x16 + + add sp, sp, w19, uxth #3 + .cfi_def_cfa_register sp + ldp x19, x20, [sp,#16] + .cfi_restore x19 + .cfi_restore x20 + ldp x29, x30, [sp],#32 + .cfi_adjust_cfa_offset -32 + .cfi_restore x29 + .cfi_restore x30 + ret + .cfi_endproc + +#ifndef __APPLE__ + .size _NS_InvokeByIndex, . - _NS_InvokeByIndex + + .section .note.GNU-stack, "", @progbits +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s new file mode 100644 index 0000000000..794c4f5c17 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s @@ -0,0 +1,145 @@ + +// Select C numeric constant + .radix C +// for 64 bit mode, use .psr abi64 + .psr abi32 +// big endian + .psr msb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'NS_InvokeByIndex' + .proc NS_InvokeByIndex +// manual bundling + .explicit + +// extern "C" uint32_t +// invoke_copy_to_stack(uint64_t* d, +// const uint32_t paramCount, nsXPTCVariant* s) + .global invoke_copy_to_stack +// .exclass invoke_copy_to_stack, @fullyvisible + .type invoke_copy_to_stack,@function + +// .exclass NS_InvokeByIndex, @fullyvisible + .type NS_InvokeByIndex,@function + +// NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params); +NS_InvokeByIndex:: + .prologue + .save ar.pfs, r37 +// allocate 4 input args, 6 local args, and 8 output args + alloc r37 = ar.pfs, 4, 6, 8, 0 // M + nop.i 0 ;; // I + +// unwind table already knows gp, no need to specify anything + add r39 = 0, gp // A + .save rp, r36 + mov r36 = rp // I + .vframe r38 + add r38 = 0, sp ;; // A + +// We first calculate the amount of extra memory stack space required +// for the arguments, and register storage. +// We then call invoke_copy_to_stack() to write the argument contents +// to the specified memory regions. +// We then copy the integer arguments to integer registers, and floating +// arguments to float registers. +// Lastly we load the virtual table jump pointer, and call the target +// subroutine. + +// in0 : that +// in1 : methodIndex +// in2 : paramCount +// in3 : params + +// stack frame size is 16 + (8 * even(paramCount)) + 64 + 64 +// 16 byte frame header +// 8 * even(paramCount) memory argument area +// 64 bytes integer register area +// 64 bytes float register area +// This scheme makes sure stack fram size is a multiple of 16 + + .body + add r10 = 8, r0 // A +// r41 points to float register area + add r41 = -64, sp // A +// r40 points to int register area + add r40 = -128, sp ;; // A + + add out1 = 0, r40 // A + add out2 = 0, r41 // A + tbit.z p14,p15 = in2,0 ;; // I + +// compute 8 * even(paramCount) +(p14) shladd r11 = in2, 3, r0 ;; // A +(p15) shladd r11 = in2, 3, r10 ;; // A + sub out0 = r40, r11 ;; // A + +// advance the stack frame + add sp = -16, out0 // A + add out3 = 0, in2 // A + add out4 = 0, in3 // A + +// branch to invoke_copy_to_stack + br.call.sptk.few rp = invoke_copy_to_stack ;; // B + +// restore gp + add gp = 0, r39 // A + add out0 = 0, in0 // A + +// load the integer and float registers + ld8 out1 = [r40], 8 // M + ldfd f8 = [r41], 8 ;; // M + + ld8 out2 = [r40], 8 // M + ldfd f9 = [r41], 8 ;; // M + + ld8 out3 = [r40], 8 // M + ldfd f10 = [r41], 8 ;; // M + + ld8 out4 = [r40], 8 // M + ldfd f11 = [r41], 8 ;; // M + + ld8 out5 = [r40], 8 // M + ldfd f12 = [r41], 8 ;; // M +// 16 * methodIndex + shladd r11 = in1, 4, r0 // A + + ld8 out6 = [r40], 8 // M + ldfd f13 = [r41], 8 ;; // M + + ld8 out7 = [r40], 8 // M + ldfd f14 = [r41], 8 // M + addp4 r8 = 0, in0 ;; // A + +// look up virtual base table and dispatch to target subroutine +// This section assumes 32 bit pointer mode, and virtual base table +// layout from the ABI http://www.codesourcery.com/cxx-abi/abi.html + +// load base table + ld4 r8 = [r8] ;; // M + addp4 r8 = r11, r8 ;; // A + + // first entry is jump pointer, second entry is gp + addp4 r9 = 8, r8 ;; // A +// load jump pointer + ld8 r8 = [r8] + +// load gp + ld8 gp = [r9] ;; // M + mov b6 = r8 ;; // I + +// branch to target virtual function + br.call.sptk.few rp = b6 ;; // B + +// epilog + mov ar.pfs = r37 // I + mov rp = r36 ;; // I + + add sp = 0, r38 // A + add gp = 0, r39 // A + br.ret.sptk.few rp ;; // B + + .endp + + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s new file mode 100644 index 0000000000..3ee7b7971a --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s @@ -0,0 +1,146 @@ + +// Select C numeric constant + .radix C +// for 64 bit mode, use .psr abi64 + .psr abi64 +// little endian + .psr lsb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'NS_InvokeByIndex' + .proc NS_InvokeByIndex +// manual bundling + .explicit + +// extern "C" uint32_t +// invoke_copy_to_stack(uint64_t* d, +// const uint32_t paramCount, nsXPTCVariant* s) + .global invoke_copy_to_stack +// .exclass invoke_copy_to_stack, @fullyvisible + .type invoke_copy_to_stack,@function + +// .exclass NS_InvokeByIndex, @fullyvisible + .type NS_InvokeByIndex,@function + +// XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params); +NS_InvokeByIndex:: + .prologue + .save ar.pfs, r37 +// allocate 4 input args, 6 local args, and 8 output args + alloc r37 = ar.pfs, 4, 6, 8, 0 // M + nop.i 0 ;; // I + +// unwind table already knows gp, no need to specify anything + add r39 = 0, gp // A + .save rp, r36 + mov r36 = rp // I + .vframe r38 + add r38 = 0, sp ;; // A + +// We first calculate the amount of extra memory stack space required +// for the arguments, and register storage. +// We then call invoke_copy_to_stack() to write the argument contents +// to the specified memory regions. +// We then copy the integer arguments to integer registers, and floating +// arguments to float registers. +// Lastly we load the virtual table jump pointer, and call the target +// subroutine. + +// in0 : that +// in1 : methodIndex +// in2 : paramCount +// in3 : params + +// stack frame size is 16 + (8 * even(paramCount)) + 64 + 64 +// 16 byte frame header +// 8 * even(paramCount) memory argument area +// 64 bytes integer register area +// 64 bytes float register area +// This scheme makes sure stack fram size is a multiple of 16 + + .body + add r10 = 8, r0 // A +// r41 points to float register area + add r41 = -64, sp // A +// r40 points to int register area + add r40 = -128, sp ;; // A + + add out1 = 0, r40 // A + add out2 = 0, r41 // A + tbit.z p14,p15 = in2,0 ;; // I + +// compute 8 * even(paramCount) +(p14) shladd r11 = in2, 3, r0 ;; // A +(p15) shladd r11 = in2, 3, r10 ;; // A + sub out0 = r40, r11 ;; // A + +// advance the stack frame + add sp = -16, out0 // A + add out3 = 0, in2 // A + add out4 = 0, in3 // A + +// branch to invoke_copy_to_stack + br.call.sptk.few rp = invoke_copy_to_stack ;; // B + +// restore gp + add gp = 0, r39 // A + add out0 = 0, in0 // A + +// load the integer and float registers + ld8 out1 = [r40], 8 // M + ldfd f8 = [r41], 8 ;; // M + + ld8 out2 = [r40], 8 // M + ldfd f9 = [r41], 8 ;; // M + + ld8 out3 = [r40], 8 // M + ldfd f10 = [r41], 8 ;; // M + + ld8 out4 = [r40], 8 // M + ldfd f11 = [r41], 8 ;; // M + + ld8 out5 = [r40], 8 // M + ldfd f12 = [r41], 8 ;; // M +// 16 * methodIndex + shladd r11 = in1, 4, r0 // A + + ld8 out6 = [r40], 8 // M + ldfd f13 = [r41], 8 ;; // M + + ld8 out7 = [r40], 8 // M + ldfd f14 = [r41], 8 // M + add r8 = 0, in0 ;; // A + +// look up virtual base table and dispatch to target subroutine +// This section assumes 64 bit pointer mode, and virtual base table +// layout from the ABI http://www.codesourcery.com/cxx-abi/abi.html + +// load base table + ld8 r8 = [r8] ;; // M + add r8 = r11, r8 ;; // A + + // first entry is jump pointer, second entry is gp + add r9 = 8, r8 ;; // A +// load jump pointer + ld8 r8 = [r8] + +// load gp + ld8 gp = [r9] ;; // M + mov b6 = r8 ;; // I + +// branch to target virtual function + br.call.sptk.few rp = b6 ;; // B + +// epilog + mov ar.pfs = r37 // I + mov rp = r36 ;; // I + + add sp = 0, r38 // A + add gp = 0, r39 // A + br.ret.sptk.few rp ;; // B + + .endp + +/* Magic indicating no need for an executable stack */ + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_loongarch64.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_loongarch64.S new file mode 100644 index 0000000000..f0a6662986 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_loongarch64.S @@ -0,0 +1,92 @@ +/* This Source Code Form subject to the terms of Mozilla Public + * License, v. 2.0 If a copy of the MPL was not distributed with + * this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + .set NGPREGS, 8 + .set NFPREGS, 8 + + .text + .globl _NS_InvokeByIndex + .type _NS_InvokeByIndex, @function +/* + * _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ +_NS_InvokeByIndex: + .cfi_startproc + addi.d $sp, $sp, -32 + .cfi_def_cfa_offset 32 + st.d $s0, $sp, 16 + .cfi_offset 23, -16 + st.d $s1, $sp, 8 + .cfi_offset 24, -24 + st.d $s2, $sp, 0 + .cfi_offset 25, -32 + st.d $ra, $sp, 24 + .cfi_offset 1, -8 + + move $s2, $a0 + move $s1, $a1 + move $s0, $sp + .cfi_def_cfa_register 23 + + /* 16-bytes alignment */ + addi.d $a0, $a2, 1 + li.d $t4, 0xfffffffffffffffe + and $a0, $a0, $t4 + slli.d $a0, $a0, 3 + sub.d $sp, $sp, $a0 + move $a4, $sp + + addi.d $sp, $sp, -8*(NFPREGS+NGPREGS) + move $a0, $sp + addi.d $a1, $sp, 8*NGPREGS + + bl invoke_copy_to_stack + + /* 1st argument is this */ + move $a0, $s2 + + ld.d $a1, $sp, 8 + ld.d $a2, $sp, 16 + ld.d $a3, $sp, 24 + ld.d $a4, $sp, 32 + ld.d $a5, $sp, 40 + ld.d $a6, $sp, 48 + ld.d $a7, $sp, 56 + + fld.d $fa0, $sp, 64 + fld.d $fa1, $sp, 72 + fld.d $fa2, $sp, 80 + fld.d $fa3, $sp, 88 + fld.d $fa4, $sp, 96 + fld.d $fa5, $sp, 104 + fld.d $fa6, $sp, 112 + fld.d $fa7, $sp, 120 + + addi.d $sp, $sp, 8*(NGPREGS+NFPREGS) + + ld.d $s2, $s2, 0 + slli.w $s1, $s1, 3 + add.d $s2, $s2, $s1 + ld.d $t3, $s2, 0 + jirl $ra, $t3, 0 + + move $sp, $s0 + .cfi_def_cfa_register 3 + ld.d $s0, $sp, 16 + .cfi_restore 23 + ld.d $s1, $sp, 8 + .cfi_restore 24 + ld.d $s2, $sp, 0 + .cfi_restore 25 + ld.d $ra, $sp, 24 + .cfi_restore 1 + addi.d $sp, $sp, 32 + .cfi_def_cfa_offset -32 + jirl $zero, $ra, 0 + .cfi_endproc + .size _NS_InvokeByIndex, .-_NS_InvokeByIndex + .section .note.GNU-stack, "", @progbits + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips.S new file mode 100644 index 0000000000..32ff3b3565 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips.S @@ -0,0 +1,134 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 code is for MIPS using the O32 ABI. */ + +#ifdef ANDROID +#include <asm/regdef.h> +#include <asm/asm.h> +#include <machine/asm.h> +#else +#include <sys/regdef.h> +#include <sys/asm.h> +#endif + +# NARGSAVE is the argument space in the callers frame, including extra +# 'shadowed' space for the argument registers. The minimum of 4 +# argument slots is sometimes predefined in the header files. +#ifndef NARGSAVE +#define NARGSAVE 4 +#endif + +#define LOCALSZ 3 /* gp, fp, ra */ +#define FRAMESZ ((((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK) + +#define RAOFF (FRAMESZ - (1*SZREG)) +#define FPOFF (FRAMESZ - (2*SZREG)) +#define GPOFF (FRAMESZ - (3*SZREG)) + +#define A0OFF (FRAMESZ + (0*SZREG)) +#define A1OFF (FRAMESZ + (1*SZREG)) +#define A2OFF (FRAMESZ + (2*SZREG)) +#define A3OFF (FRAMESZ + (3*SZREG)) + + .text + +# +# _NS_InvokeByIndex(that, methodIndex, paramCount, params) +# a0 a1 a2 a3 + + .globl _NS_InvokeByIndex + .align 2 + .type _NS_InvokeByIndex,@function + .ent _NS_InvokeByIndex,0 + .frame fp, FRAMESZ, ra +_NS_InvokeByIndex: + SETUP_GP + subu sp, FRAMESZ + + # specify the save register mask for gp, fp, ra, a3 - a0 + .mask 0xD00000F0, RAOFF-FRAMESZ + + sw ra, RAOFF(sp) + sw fp, FPOFF(sp) + + # we can't use .cprestore in a variable stack frame + sw gp, GPOFF(sp) + + sw a0, A0OFF(sp) + sw a1, A1OFF(sp) + sw a2, A2OFF(sp) + sw a3, A3OFF(sp) + + # save bottom of fixed frame + move fp, sp + + # extern "C" uint32 + # invoke_count_words(uint32_t paramCount, nsXPTCVariant* s); + la t9, invoke_count_words + move a0, a2 + move a1, a3 + jalr t9 + lw gp, GPOFF(fp) + + # allocate variable stack, with a size of: + # wordsize (of 4 bytes) * result (already aligned to dword) + # but a minimum of 16 byte + sll v0, 2 + slt t0, v0, 16 + beqz t0, 1f + li v0, 16 +1: subu sp, v0 + + # let a0 point to the bottom of the variable stack, allocate + # another fixed stack for: + # extern "C" void + # invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + # nsXPTCVariant* s); + la t9, invoke_copy_to_stack + move a0, sp + lw a1, A2OFF(fp) + lw a2, A3OFF(fp) + subu sp, 16 + jalr t9 + lw gp, GPOFF(fp) + + # back to the variable stack frame + addu sp, 16 + + # calculate the function we need to jump to, which must then be + # stored in t9 + lw a0, A0OFF(fp) # a0 = set "that" to be "this" + lw t0, A1OFF(fp) # a1 = methodIndex + lw t9, 0(a0) + # t0 = methodIndex << PTRLOG + sll t0, t0, PTRLOG + addu t9, t0 + lw t9, (t9) + + # Set a1-a3 to what invoke_copy_to_stack told us. a0 is already + # the "this" pointer. We don't have to care about floating + # point arguments, the non-FP "this" pointer as first argument + # means they'll never be used. + lw a1, 1*SZREG(sp) + lw a2, 2*SZREG(sp) + lw a3, 3*SZREG(sp) + + jalr t9 + # Micro-optimization: There's no gp usage below this point, so + # we don't reload. + # lw gp, GPOFF(fp) + + # leave variable stack frame + move sp, fp + + lw ra, RAOFF(sp) + lw fp, FPOFF(sp) + + addiu sp, FRAMESZ + j ra +END(_NS_InvokeByIndex) diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S new file mode 100644 index 0000000000..d2c5595ab4 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S @@ -0,0 +1,121 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <sys/regdef.h> +#include <sys/asm.h> + +.text +.globl invoke_count_words +.globl invoke_copy_to_stack + +LOCALSZ=7 # a0, a1, a2, a3, s0, ra, gp +FRAMESZ=(((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK + +RAOFF=FRAMESZ-(1*SZREG) +A0OFF=FRAMESZ-(2*SZREG) +A1OFF=FRAMESZ-(3*SZREG) +A2OFF=FRAMESZ-(4*SZREG) +A3OFF=FRAMESZ-(5*SZREG) +S0OFF=FRAMESZ-(6*SZREG) +GPOFF=FRAMESZ-(7*SZREG) + +# +# _NS_InvokeByIndex(that, methodIndex, paramCount, params) +# a0 a1 a2 a3 + +NESTED(_NS_InvokeByIndex, FRAMESZ, ra) + PTR_SUBU sp, FRAMESZ + SETUP_GP64(GPOFF, _NS_InvokeByIndex) + + REG_S ra, RAOFF(sp) + REG_S a0, A0OFF(sp) + REG_S a1, A1OFF(sp) + REG_S a2, A2OFF(sp) + REG_S a3, A3OFF(sp) + REG_S s0, S0OFF(sp) + + # invoke_count_words(paramCount, params) + move a0, a2 + move a1, a3 + jal invoke_count_words + + # invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + # nsXPTCVariant* s, uint32_t *reg) + + REG_L a1, A2OFF(sp) # a1 - paramCount + REG_L a2, A3OFF(sp) # a2 - params + + # save sp before we copy the params to the stack + move t0, sp + + # assume full size of 16 bytes per param to be safe + sll v0, 4 # 16 bytes * num params + PTR_SUBU sp, sp, v0 # make room + move a0, sp # a0 - param stack address + + # create temporary stack space to write int and fp regs + PTR_SUBU sp, 64 # 64 = 8 regs of 8 bytes + move a3, sp + + # save the old sp and save the arg stack + PTR_SUBU sp, sp, 16 + REG_S t0, 0(sp) + REG_S a0, 8(sp) + + # copy the param into the stack areas + jal invoke_copy_to_stack + + REG_L t3, 8(sp) # get previous a0 + REG_L s0, 0(sp) # get orig sp back and save away our stack pointer + + REG_L a0, A0OFF(s0) # a0 - that + REG_L a1, A1OFF(s0) # a1 - methodIndex + + # t1 = methodIndex * pow(2, PTRLOG) + # (use shift instead of mult) + sll t1, a1, PTRLOG + + # calculate the function we need to jump to, + # which must then be saved in t9 + PTR_L t9, 0(a0) + PTR_ADDU t9, t9, t1 + PTR_L t9, (t9) + + # get register save area from invoke_copy_to_stack + PTR_SUBU t1, t3, 64 + + # a1..a7 and f13..f19 should now be set to what + # invoke_copy_to_stack told us. skip a0 and f12 + # because that's the "this" pointer + + REG_L a1, 0(t1) + REG_L a2, 8(t1) + REG_L a3, 16(t1) + REG_L a4, 24(t1) + REG_L a5, 32(t1) + REG_L a6, 40(t1) + REG_L a7, 48(t1) + + l.d $f13, 0(t1) + l.d $f14, 8(t1) + l.d $f15, 16(t1) + l.d $f16, 24(t1) + l.d $f17, 32(t1) + l.d $f18, 40(t1) + l.d $f19, 48(t1) + + # create the stack pointer for the function + move sp, t3 + + jalr t9 + + ## restore stack pointer. + move sp, s0 + + RESTORE_GP64 + REG_L ra, RAOFF(sp) + REG_L s0, S0OFF(sp) + PTR_ADDU sp, FRAMESZ + j ra +.end _NS_InvokeByIndex diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s new file mode 100644 index 0000000000..6fa9a86ba8 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s @@ -0,0 +1,131 @@ + .LEVEL 1.1 +framesz .EQU 128 + +; XPTC_InvokeByIndex(nsISuppots* that, uint32_t methodIndex, +; uint32_t paramCount, nsXPTCVariant* params); + +; g++ need to compile everything with -fvtable-thunks ! + + .SPACE $TEXT$,SORT=8 + .SUBSPA $CODE$,QUAD=0,ALIGN=4,ACCESS=0x2c,CODE_ONLY,SORT=24 +XPTC_InvokeByIndex + .PROC + .CALLINFO CALLER,FRAME=72,ENTRY_GR=%r3,SAVE_RP,SAVE_SP,ARGS_SAVED,ALLOCA_FRAME + + ; frame marker takes 48 bytes, + ; register spill area takes 8 bytes, + ; local stack area takes 72 bytes result in 128 bytes total + + .ENTRY + STW %rp,-20(%sp) + STW,MA %r3,128(%sp) + + LDO -framesz(%r30),%r28 + STW %r28,-4(%r30) ; save previous sp + STW %r19,-32(%r30) + + STW %r26,-36-framesz(%r30) ; save argument registers in + STW %r25,-40-framesz(%r30) ; in PREVIOUS frame + STW %r24,-44-framesz(%r30) ; + STW %r23,-48-framesz(%r30) ; + + B,L .+8,%r2 + ADDIL L'invoke_count_bytes-$PIC_pcrel$1+4,%r2,%r1 + LDO R'invoke_count_bytes-$PIC_pcrel$2+8(%r1),%r1 +$PIC_pcrel$1 + LDSID (%r1),%r31 +$PIC_pcrel$2 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26;out=28 + BE,L 0(%sr0,%r1),%r31 + COPY %r31,%r2 + + CMPIB,>= 0,%r28, .+76 + COPY %r30,%r3 ; copy stack ptr to saved stack ptr + ADD %r30,%r28,%r30 ; extend stack frame + LDW -4(%r3),%r28 ; move frame + STW %r28,-4(%r30) + LDW -8(%r3),%r28 + STW %r28,-8(%r30) + LDW -12(%r3),%r28 + STW %r28,-12(%r30) + LDW -16(%r3),%r28 + STW %r28,-16(%r30) + LDW -20(%r3),%r28 + STW %r28,-20(%r30) + LDW -24(%r3),%r28 + STW %r28,-24(%r30) + LDW -28(%r3),%r28 + STW %r28,-28(%r30) + LDW -32(%r3),%r28 + STW %r28,-32(%r30) + + LDO -40(%r30),%r26 ; load copy address + LDW -44-framesz(%r3),%r25 ; load rest of 2 arguments + LDW -48-framesz(%r3),%r24 ; + + LDW -32(%r30),%r19 ; shared lib call destroys r19; reload + B,L .+8,%r2 + ADDIL L'invoke_copy_to_stack-$PIC_pcrel$3+4,%r2,%r1 + LDO R'invoke_copy_to_stack-$PIC_pcrel$4+8(%r1),%r1 +$PIC_pcrel$3 + LDSID (%r1),%r31 +$PIC_pcrel$4 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26 + BE,L 0(%sr0,%r1),%r31 + COPY %r31,%r2 + + LDO -48(%r30),%r20 + EXTRW,U,= %r28,31,1,%r22 + FLDD 0(%r20),%fr7 ; load double arg 1 + EXTRW,U,= %r28,30,1,%r22 + FLDW 8(%r20),%fr5L ; load float arg 1 + EXTRW,U,= %r28,29,1,%r22 + FLDW 4(%r20),%fr6L ; load float arg 2 + EXTRW,U,= %r28,28,1,%r22 + FLDW 0(%r20),%fr7L ; load float arg 3 + + LDW -36-framesz(%r3),%r26 ; load ptr to 'that' + LDW -40(%r30),%r25 ; load the rest of dispatch argument registers + LDW -44(%r30),%r24 + LDW -48(%r30),%r23 + + LDW -36-framesz(%r3),%r20 ; load vtable addr + LDW -40-framesz(%r3),%r28 ; load index + LDW 0(%r20),%r20 ; follow vtable + LDO 16(%r20),%r20 ; offset vtable by 16 bytes (g++: 8, aCC: 16) + SH2ADDL %r28,%r20,%r28 ; add 4*index to vtable entry + LDW 0(%r28),%r22 ; load vtable entry + + B,L .+8,%r2 + ADDIL L'$$dyncall_external-$PIC_pcrel$5+4,%r2,%r1 + LDO R'$$dyncall_external-$PIC_pcrel$6+8(%r1),%r1 +$PIC_pcrel$5 + LDSID (%r1),%r31 +$PIC_pcrel$6 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR +;in=22-26;out=28; + BE,L 0(%sr0,%r1),%r31 + COPY %r31,%r2 + + LDW -32(%r30),%r19 + COPY %r3,%r30 ; restore saved stack ptr + + LDW -148(%sp),%rp + BVE (%rp) + .EXIT + LDW,MB -128(%sp),%r3 + + .PROCEND ;in=23,24,25,26; + + .ALIGN 8 + .SPACE $TEXT$ + .SUBSPA $CODE$ + .IMPORT $$dyncall_external,MILLICODE + .IMPORT invoke_count_bytes,CODE + .IMPORT invoke_copy_to_stack,CODE + .EXPORT XPTC_InvokeByIndex,ENTRY,PRIV_LEV=3,ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR,LONG_RETURN + .END + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_parisc_linux.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_parisc_linux.s new file mode 100644 index 0000000000..7a207addfd --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_parisc_linux.s @@ -0,0 +1,108 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .LEVEL 1.1 + .text + .align 4 + +framesz: + .equ 128 + +.globl NS_InvokeByIndex + .type NS_InvokeByIndex, @function + + +NS_InvokeByIndex: + .PROC + .CALLINFO FRAME=72, CALLER,SAVE_RP, SAVE_SP, ENTRY_GR=3 + .ENTRY + +; frame marker takes 48 bytes, +; register spill area takes 8 bytes, +; local stack area takes 72 bytes result in 128 bytes total + + STW %rp,-20(%sp) + STW,MA %r3,128(%sp) + + LDO -framesz(%r30),%r28 + STW %r28,-4(%r30) ; save previous sp + STW %r19,-32(%r30) + + STW %r26,-36-framesz(%r30) ; save argument registers in + STW %r25,-40-framesz(%r30) ; in PREVIOUS frame + STW %r24,-44-framesz(%r30) ; + STW %r23,-48-framesz(%r30) ; + + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26;out=28 + BL invoke_count_bytes,%r31 + COPY %r31,%r2 + + CMPIB,>= 0,%r28, .+76 + COPY %r30,%r3 ; copy stack ptr to saved stack ptr + ADD %r30,%r28,%r30 ; extend stack frame + LDW -4(%r3),%r28 ; move frame + STW %r28,-4(%r30) + LDW -8(%r3),%r28 + STW %r28,-8(%r30) + LDW -12(%r3),%r28 + STW %r28,-12(%r30) + LDW -16(%r3),%r28 + STW %r28,-16(%r30) + LDW -20(%r3),%r28 + STW %r28,-20(%r30) + LDW -24(%r3),%r28 + STW %r28,-24(%r30) + LDW -28(%r3),%r28 + STW %r28,-28(%r30) + LDW -32(%r3),%r28 + STW %r28,-32(%r30) + + LDO -40(%r30),%r26 ; load copy address + LDW -44-framesz(%r3),%r25 ; load rest of 2 arguments + LDW -48-framesz(%r3),%r24 ; + + LDW -32(%r30),%r19 ; shared lib call destroys r19; reload + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26 + BL invoke_copy_to_stack,%r31 + COPY %r31,%r2 + + LDO -48(%r30),%r20 + EXTRW,U,= %r28,31,1,%r22 + FLDD 0(%r20),%fr7 ; load double arg 1 + EXTRW,U,= %r28,30,1,%r22 + FLDW 8(%r20),%fr5L ; load float arg 1 + EXTRW,U,= %r28,29,1,%r22 + FLDW 4(%r20),%fr6L ; load float arg 2 + EXTRW,U,= %r28,28,1,%r22 + FLDW 0(%r20),%fr7L ; load float arg 3 + + LDW -36-framesz(%r3),%r26 ; load ptr to 'that' + LDW -40(%r30),%r25 ; load the rest of dispatch argument registers + LDW -44(%r30),%r24 + LDW -48(%r30),%r23 + + LDW -36-framesz(%r3),%r20 ; load vtable addr + LDW -40-framesz(%r3),%r28 ; load index + LDW 0(%r20),%r20 ; follow vtable + SH2ADDL %r28,%r20,%r28 ; add 4*index to vtable entry + LDW 0(%r28),%r22 ; load vtable entry + + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR ;in=22-26;out=28; + BL $$dyncall,%r31 + COPY %r31,%r2 + + LDW -32(%r30),%r19 + COPY %r3,%r30 ; restore saved stack ptr + + LDW -148(%sp),%rp + LDWM -128(%sp),%r3 + BV,N (%rp) + NOP + .EXIT + .PROCEND ;in=23,24,25,26; + .SIZE NS_InvokeByIndex, .-NS_InvokeByIndex + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S new file mode 100644 index 0000000000..d2cab6c813 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S @@ -0,0 +1,167 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set r1,1; .set r2,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + +# The ABI defines a fixed stack frame area of 4 doublewords (ELFv2) +# or 6 doublewords (ELFv1); the last of these doublewords is used +# as TOC pointer save area. The fixed area is followed by a parameter +# save area of 8 doublewords (used for vararg routines), followed +# by space for parameters passed on the stack. +# +# We set STACK_TOC to the offset of the TOC pointer save area, and +# STACK_PARAMS to the offset of the first on-stack parameter. + +#if _CALL_ELF == 2 +#define STACK_TOC 24 +#define STACK_PARAMS 96 +#else +#define STACK_TOC 40 +#define STACK_PARAMS 112 +#endif + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +#if _CALL_ELF == 2 + .section ".text" + .type NS_InvokeByIndex,@function + .globl NS_InvokeByIndex + .align 2 +NS_InvokeByIndex: +0: addis 2,12,(.TOC.-0b)@ha + addi 2,2,(.TOC.-0b)@l + .localentry NS_InvokeByIndex,.-NS_InvokeByIndex +#else + .section ".toc","aw" + .section ".text" + .align 2 + .globl NS_InvokeByIndex + .section ".opd","aw" + .align 3 +NS_InvokeByIndex: + .quad .NS_InvokeByIndex,.TOC.@tocbase + .previous + .type NS_InvokeByIndex,@function +.NS_InvokeByIndex: +#endif + mflr 0 + std 0,16(r1) + + std r29,-24(r1) + std r30,-16(r1) + std r31,-8(r1) + + mr r29,r3 # Save 'that' in r29 + mr r30,r4 # Save 'methodIndex' in r30 + mr r31,r1 # Save old frame + + # Allocate stack frame with space for params. Since at least the + # first 7 parameters (not including 'that') will be in registers, + # we don't actually need stack space for those. We must ensure + # that the stack remains 16-byte aligned. + # + # | (fixed area + | | 7 GP | 13 FP | 3 NV | + # | param. save) |(params)........| regs | regs | regs | + # (r1)......(+STACK_PARAMS)... (-23*8).(-16*8).(-3*8)..(r31) + + # +stack frame, -unused stack params, +regs storage, +1 for alignment + addi r7,r5,((STACK_PARAMS/8)-7+7+13+3+1) + rldicr r7,r7,3,59 # multiply by 8 and mask with ~15 + neg r7,r7 + stdux r1,r1,r7 + + + # Call invoke_copy_to_stack(uint64_t* gpregs, double* fpregs, + # uint32_t paramCount, nsXPTCVariant* s, + # uint64_t* d)) + + # r5, r6 are passed through intact (paramCount, params) + # r7 (d) has to be r1+STACK_PARAMS + # -- where parameters are passed on the stack. + # r3, r4 are above that, easier to address from r31 than from r1 + + subi r3,r31,(23*8) # r3 --> GPRS + subi r4,r31,(16*8) # r4 --> FPRS + addi r7,r1,STACK_PARAMS # r7 --> params + bl invoke_copy_to_stack + nop + + # Set up to invoke function + + ld r9,0(r29) # vtable (r29 is 'that') + mr r3,r29 # self is first arg, obviously + + sldi r30,r30,3 # Find function descriptor + add r9,r9,r30 + ld r12,0(r9) + + std r2,STACK_TOC(r1) # Save r2 (TOC pointer) + +#if _CALL_ELF == 2 + mtctr r12 +#else + ld r0,0(r12) # Actual address from fd. + mtctr 0 + ld r11,16(r12) # Environment pointer from fd. + ld r2,8(r12) # TOC pointer from fd. +#endif + + # Load FP and GP registers as required + ld r4, -(23*8)(r31) + ld r5, -(22*8)(r31) + ld r6, -(21*8)(r31) + ld r7, -(20*8)(r31) + ld r8, -(19*8)(r31) + ld r9, -(18*8)(r31) + ld r10, -(17*8)(r31) + + lfd f1, -(16*8)(r31) + lfd f2, -(15*8)(r31) + lfd f3, -(14*8)(r31) + lfd f4, -(13*8)(r31) + lfd f5, -(12*8)(r31) + lfd f6, -(11*8)(r31) + lfd f7, -(10*8)(r31) + lfd f8, -(9*8)(r31) + lfd f9, -(8*8)(r31) + lfd f10, -(7*8)(r31) + lfd f11, -(6*8)(r31) + lfd f12, -(5*8)(r31) + lfd f13, -(4*8)(r31) + + bctrl # Do it + + ld r2,STACK_TOC(r1) # Load our own TOC pointer + ld r1,0(r1) # Revert stack frame + ld 0,16(r1) # Reload lr + mtlr 0 + ld 29,-24(r1) # Restore NVGPRS + ld 30,-16(r1) + ld 31,-8(r1) + blr + +#if _CALL_ELF == 2 + .size NS_InvokeByIndex,.-NS_InvokeByIndex +#else + .size NS_InvokeByIndex,.-.NS_InvokeByIndex +#endif + + # Magic indicating no need for an executable stack + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix.s new file mode 100644 index 0000000000..eb6b6661d3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix.s @@ -0,0 +1,129 @@ +# +# -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +.set BO_IF,12 +.set CR0_EQ,2 + + + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.NS_InvokeByIndex{TC},"NS_InvokeByIndex" + + +# .text section + + .csect H.10.NO_SYMBOL{PR} + .globl .NS_InvokeByIndex + .globl NS_InvokeByIndex{DS} + .extern .invoke_copy_to_stack + .extern ._ptrgl{PR} + + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.NS_InvokeByIndex: + mflr r0 + stw r31,-4(sp) +# +# save off the incoming values in the caller's parameter area +# + stw r3,24(sp) # that + stw r4,28(sp) # methodIndex + stw r5,32(sp) # paramCount + stw r6,36(sp) # params + stw r0,8(sp) + stwu sp,-136(sp) # = 24 for linkage area, 8 * 13 for fprData area, 8 for saved registers + +# prepare args for 'invoke_copy_to_stack' call +# + lwz r4,168(sp) # paramCount + lwz r5,172(sp) # params + mr r6,sp # fprData + slwi r3,r4,3 # number of bytes of stack required + # at most 8*paramCount + addi r3,r3,28 # linkage area + mr r31,sp # save original stack top + subfc sp,r3,sp # bump the stack + addi r3,sp,28 # parameter pointer excludes linkage area size + 'this' + + bl .invoke_copy_to_stack + nop + + lfd f1,0(r31) # Restore floating point registers + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + lwz r3,160(r31) # that + lwz r4,0(r3) # get vTable from 'that' + lwz r5,164(r31) # methodIndex + slwi r5,r5,3 # methodIndex * 8 + addi r5,r5,8 # step over junk at start of vTable ! + lwzx r11,r5,r4 # get function pointer + + addi r5,r5,4 # We need to manually adjust the 'that' pointer, this is CFRONT based + lwzx r5,r4,r5 # offset = r4(vtable) + r5(methodIndex offset) - 4 + add r3,r5,r3 # adjust 'that' r3 = r3 + r5 + + lwz r4,28(sp) + lwz r5,32(sp) + lwz r6,36(sp) + lwz r7,40(sp) + lwz r8,44(sp) + lwz r9,48(sp) + lwz r10,52(sp) + + bl ._ptrgl{PR} + nop + + mr sp,r31 + lwz r0,144(sp) + addi sp,sp,136 + mtlr r0 + lwz r31,-4(sp) + blr + + +# .data section + + .toc # 0x00000038 +T.18.NS_InvokeByIndex: + .tc H.18.NS_InvokeByIndex{TC},NS_InvokeByIndex{DS} + + .csect NS_InvokeByIndex{DS} + .long .NS_InvokeByIndex # "\0\0\0\0" + .long TOC{TC0} # "\0\0\0008" + .long 0x00000000 # "\0\0\0\0" +# End csect NS_InvokeByIndex{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s new file mode 100644 index 0000000000..722583de5b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s @@ -0,0 +1,128 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +.set BO_IF,12 +.set CR0_EQ,2 + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.NS_InvokeByIndex{TC},"NS_InvokeByIndex" + + +# .text section + + .csect H.10.NO_SYMBOL{PR} + .globl .NS_InvokeByIndex + .globl NS_InvokeByIndex{DS} + .extern .invoke_copy_to_stack + .extern ._ptrgl{PR} + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.NS_InvokeByIndex: + mflr r0 + std r31,-8(sp) +# +# save off the incoming values in the caller's parameter area +# + std r3,48(sp) # that + std r4,56(sp) # methodIndex + std r5,64(sp) # paramCount + std r6,72(sp) # params + std r0,16(sp) + stdu sp,-168(sp) # 2*24=48 for linkage area, + # 8*13=104 for fprData area + # 16 for saved registers + +# prepare args for 'invoke_copy_to_stack' call +# + ld r4,232(sp) # paramCount (168+8+56) + ld r5,240(sp) # params + mr r6,sp # fprData + sldi r3,r4,3 # number of bytes of stack required + # is at most numParams*8 + addi r3,r3,56 # linkage area (48) + this (8) + mr r31,sp # save original stack top + subfc sp,r3,sp # bump the stack + addi r3,sp,56 # parameter pointer excludes linkage area + # size + 'this' + + bl .invoke_copy_to_stack + nop + + lfd f1,0(r31) # Restore floating point registers + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + ld r3,216(r31) # that (168+48) + ld r4,0(r3) # get vTable from 'that' + ld r5,224(r31) # methodIndex (168+56) + sldi r5,r5,3 # methodIndex * 8 + # No junk at the start of 64bit vtable !!! + ldx r11,r5,r4 # get function pointer (this jumps + # either to the function if no adjustment + # is needed (displacement = 0), or it + # jumps to the thunk code, which will jump + # to the function at the end) + + # No adjustment of the that pointer in 64bit mode, this is done + # by the thunk code + + ld r4,56(sp) + ld r5,64(sp) + ld r6,72(sp) + ld r7,80(sp) + ld r8,88(sp) + ld r9,96(sp) + ld r10,104(sp) + + bl ._ptrgl{PR} + nop + + mr sp,r31 + ld r0,184(sp) # 168+16 + addi sp,sp,168 + mtlr r0 + ld r31,-8(sp) + blr + +# .data section + + .toc # 0x00000038 +T.18.NS_InvokeByIndex: + .tc H.18.NS_InvokeByIndex{TC},NS_InvokeByIndex{DS} + + .csect NS_InvokeByIndex{DS} + .llong .NS_InvokeByIndex # "\0\0\0\0" + .llong TOC{TC0} # "\0\0\0008" + .llong 0x00000000 # "\0\0\0\0" +# End csect NS_InvokeByIndex{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s new file mode 100644 index 0000000000..414a6536fa --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s @@ -0,0 +1,124 @@ +# +# -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +.set BO_IF,12 +.set CR0_EQ,2 + + + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.NS_InvokeByIndex{TC},"NS_InvokeByIndex" + + +# .text section + + .csect H.10.NO_SYMBOL{PR} + .globl .NS_InvokeByIndex + .globl NS_InvokeByIndex{DS} + .extern .invoke_copy_to_stack + .extern ._ptrgl{PR} + + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.NS_InvokeByIndex: + mflr r0 + stw r31,-4(sp) +# +# save off the incoming values in the caller's parameter area +# + stw r3,24(sp) # that + stw r4,28(sp) # methodIndex + stw r5,32(sp) # paramCount + stw r6,36(sp) # params + stw r0,8(sp) + stwu sp,-136(sp) # = 24 for linkage area, 8 * 13 for fprData area, 8 for saved registers + +# prepare args for 'invoke_copy_to_stack' call +# + lwz r4,168(sp) # paramCount + lwz r5,172(sp) # params + mr r6,sp # fprData + slwi r3,r4,3 # number of bytes of stack required + # at most 8*paramCount + addi r3,r3,28 # linkage area + mr r31,sp # save original stack top + subfc sp,r3,sp # bump the stack + addi r3,sp,28 # parameter pointer excludes linkage area size + 'this' + + bl .invoke_copy_to_stack + nop + + lfd f1,0(r31) # Restore floating point registers + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + lwz r3,160(r31) # that + lwz r4,0(r3) # get vTable from 'that' + lwz r5,164(r31) # methodIndex + slwi r5,r5,2 # methodIndex * 4 + lwzx r11,r5,r4 # get function pointer + + lwz r4,28(sp) + lwz r5,32(sp) + lwz r6,36(sp) + lwz r7,40(sp) + lwz r8,44(sp) + lwz r9,48(sp) + lwz r10,52(sp) + + bl ._ptrgl{PR} + nop + + mr sp,r31 + lwz r0,144(sp) + addi sp,sp,136 + mtlr r0 + lwz r31,-4(sp) + blr + + +# .data section + + .toc # 0x00000038 +T.18.NS_InvokeByIndex: + .tc H.18.NS_InvokeByIndex{TC},NS_InvokeByIndex{DS} + + .csect NS_InvokeByIndex{DS} + .long .NS_InvokeByIndex # "\0\0\0\0" + .long TOC{TC0} # "\0\0\0008" + .long 0x00000000 # "\0\0\0\0" +# End csect NS_InvokeByIndex{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_linux.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_linux.S new file mode 100644 index 0000000000..619e428a9c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_linux.S @@ -0,0 +1,98 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl NS_InvokeByIndex + .type NS_InvokeByIndex,@function + +// +// NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params) +// + +NS_InvokeByIndex: + stwu sp,-32(sp) // setup standard stack frame + mflr r0 // save LR + stw r3,8(sp) // r3 <= that + stw r4,12(sp) // r4 <= methodIndex + stw r30,16(sp) + stw r31,20(sp) + + stw r0,36(sp) // store LR backchain + mr r31,sp + + rlwinm r10,r5,3,0,27 // r10 = (ParamCount * 2 * 4) & ~0x0f + addi r0,r10,96 // reserve stack for GPR and FPR register save area r0 = r10 + 96 + lwz r9,0(sp) // r9 = backchain + neg r0,r0 + stwux r9,sp,r0 // reserve stack space and save SP backchain + + addi r3,sp,8 // r3 <= args + mr r4,r5 // r4 <= paramCount + mr r5,r6 // r5 <= params + add r6,r3,r10 // r6 <= gpregs ( == args + r10 ) + mr r30,r6 // store in r30 for use later... +#ifndef __NO_FPRS__ + addi r7,r6,32 // r7 <= fpregs ( == gpregs + 32 ) +#else + li r7, 0 +#endif + + bl invoke_copy_to_stack@local // (args, paramCount, params, gpregs, fpregs) +#ifndef __NO_FPRS__ + lfd f1,32(r30) // load FP registers with method parameters + lfd f2,40(r30) + lfd f3,48(r30) + lfd f4,56(r30) + lfd f5,64(r30) + lfd f6,72(r30) + lfd f7,80(r30) + lfd f8,88(r30) +#endif + lwz r3,8(r31) // r3 <= that + lwz r4,12(r31) // r4 <= methodIndex + lwz r5,0(r3) // r5 <= vtable ( == *that ) + slwi r4,r4,2 // convert to offset ( *= 4 ) + lwzx r0,r5,r4 // r0 <= methodpointer ( == vtable + offset ) + + lwz r4,4(r30) // load GP regs with method parameters + lwz r5,8(r30) + lwz r6,12(r30) + lwz r7,16(r30) + lwz r8,20(r30) + lwz r9,24(r30) + lwz r10,28(r30) + + mtlr r0 // copy methodpointer to LR + blrl // call method + + lwz r30,16(r31) // restore r30 & r31 + lwz r31,20(r31) + + lwz r11,0(sp) // clean up the stack + lwz r0,4(r11) + mtlr r0 + mr sp,r11 + blr + + /* Magic indicating no need for an executable stack */ + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S new file mode 100644 index 0000000000..2f8dc73b2e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S @@ -0,0 +1,94 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl NS_InvokeByIndex + .type NS_InvokeByIndex,@function + +// +// NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params) +// + +NS_InvokeByIndex: + stwu sp,-32(sp) // setup standard stack frame + mflr r0 // save LR + stw r3,8(sp) // r3 <= that + stw r4,12(sp) // r4 <= methodIndex + stw r30,16(sp) + stw r31,20(sp) + + stw r0,36(sp) // store LR backchain + mr r31,sp + + rlwinm r10,r5,3,0,27 // r10 = (ParamCount * 2 * 4) & ~0x0f + addi r0,r10,96 // reserve stack for GPR and FPR register save area r0 = r10 + 96 + lwz r9,0(sp) // r9 = backchain + neg r0,r0 + stwux r9,sp,r0 // reserve stack space and save SP backchain + + addi r3,sp,8 // r3 <= args + mr r4,r5 // r4 <= paramCount + mr r5,r6 // r5 <= params + add r6,r3,r10 // r6 <= gpregs ( == args + r10 ) + mr r30,r6 // store in r30 for use later... + addi r7,r6,32 // r7 <= fpregs ( == gpregs + 32 ) + + bl invoke_copy_to_stack@local // (args, paramCount, params, gpregs, fpregs) + + lfd f1,32(r30) // load FP registers with method parameters + lfd f2,40(r30) + lfd f3,48(r30) + lfd f4,56(r30) + lfd f5,64(r30) + lfd f6,72(r30) + lfd f7,80(r30) + lfd f8,88(r30) + + lwz r3,8(r31) // r3 <= that + lwz r4,12(r31) // r4 <= methodIndex + lwz r5,0(r3) // r5 <= vtable ( == *that ) + slwi r4,r4,2 // convert to offset ( *= 4 ) + lwzx r0,r5,r4 // r0 <= methodpointer ( == vtable + offset ) + + lwz r4,4(r30) // load GP regs with method parameters + lwz r5,8(r30) + lwz r6,12(r30) + lwz r7,16(r30) + lwz r8,20(r30) + lwz r9,24(r30) + lwz r10,28(r30) + + mtlr r0 // copy methodpointer to LR + blrl // call method + + lwz r30,16(r31) // restore r30 & r31 + lwz r31,20(r31) + + lwz r11,0(sp) // clean up the stack + lwz r0,4(r11) + mtlr r0 + mr sp,r11 + blr + + // Magic indicating no need for an executable stack + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s new file mode 100644 index 0000000000..1dc12b2b30 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s @@ -0,0 +1,142 @@ +# +# -*- Mode: Asm -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# +# ** Assumed vtable layout (obtained by disassembling with gdb): +# ** 4 bytes per vtable entry, skip 0th and 1st entries, so the mapping +# ** from index to entry is (4 * index) + 8. +# + +.text + .align 2 +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.globl __NS_InvokeByIndex +__NS_InvokeByIndex: + mflr r0 + stw r31,-4(r1) +# +# save off the incoming values in the callers parameter area +# + stw r3,24(r1) ; that + stw r4,28(r1) ; methodIndex + stw r5,32(r1) ; paramCount + stw r6,36(r1) ; params + stw r0,8(r1) + stwu r1,-144(r1) ; 24 for linkage area, + ; 8*13 for fprData area, + ; 8 for saved registers, + ; 8 to keep stack 16-byte aligned + +# set up for and call 'invoke_count_words' to get new stack size +# + mr r3,r5 + mr r4,r6 + + stwu r1,-24(r1) + bl L_invoke_count_words$stub + lwz r1,0(r1) + +# prepare args for 'invoke_copy_to_stack' call +# + lwz r4,176(r1) ; paramCount + lwz r5,180(r1) ; params + mr r6,r1 ; fprData + slwi r3,r3,2 ; number of stack bytes required + addi r3,r3,28 ; linkage area + mr r31,r1 ; save original stack top + sub r1,r1,r3 ; bump the stack + clrrwi r1,r1,4 ; keep the stack 16-byte aligned + addi r3,r31,144 ; act like real alloca, so 0(sp) always + stw r3,0(r1) ; points back to previous stack frame + addi r3,r1,28 ; parameter pointer excludes linkage area size + 'this' + +# create "temporary" stack frame for _invoke_copy_to_stack to operate in. + stwu r1,-40(r1) + bl L_invoke_copy_to_stack$stub +# remove temporary stack frame. + lwz r1,0(r1) + + lfd f1,0(r31) + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + lwz r3,168(r31) ; that + lwz r4,0(r3) ; get vTable from 'that' + lwz r5,172(r31) ; methodIndex + slwi r5,r5,2 ; methodIndex * 4 + lwzx r12,r5,r4 ; get function pointer + + lwz r4,28(r1) + lwz r5,32(r1) + lwz r6,36(r1) + lwz r7,40(r1) + lwz r8,44(r1) + lwz r9,48(r1) + lwz r10,52(r1) + + mtlr r12 + blrl + + mr r1,r31 + lwz r0,152(r1) + addi r1,r1,144 + mtlr r0 + lwz r31,-4(r1) + + blr + +.picsymbol_stub +L_invoke_count_words$stub: + .indirect_symbol _invoke_count_words + mflr r0 + bcl 20,31,L1$pb +L1$pb: + mflr r11 + addis r11,r11,ha16(L1$lz-L1$pb) + mtlr r0 + lwz r12,lo16(L1$lz-L1$pb)(r11) + mtctr r12 + addi r11,r11,lo16(L1$lz-L1$pb) + bctr +.lazy_symbol_pointer +L1$lz: + .indirect_symbol _invoke_count_words + .long dyld_stub_binding_helper + + +.picsymbol_stub +L_invoke_copy_to_stack$stub: + .indirect_symbol _invoke_copy_to_stack + mflr r0 + bcl 20,31,L2$pb +L2$pb: + mflr r11 + addis r11,r11,ha16(L2$lz-L2$pb) + mtlr r0 + lwz r12,lo16(L2$lz-L2$pb)(r11) + mtctr r12 + addi r11,r11,lo16(L2$lz-L2$pb) + bctr +.lazy_symbol_pointer +L2$lz: + .indirect_symbol _invoke_copy_to_stack + .long dyld_stub_binding_helper + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_riscv64.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_riscv64.S new file mode 100644 index 0000000000..4606523296 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_riscv64.S @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .set NGPREGS, 8 + .set NFPREGS, 8 + + .text + .globl _NS_InvokeByIndex + .type _NS_InvokeByIndex, @function +/* + * _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ +_NS_InvokeByIndex: + .cfi_startproc + addi sp, sp, -32 + .cfi_adjust_cfa_offset 32 + sd s0, 16(sp) + .cfi_rel_offset s0, 16 + sd s1, 8(sp) + .cfi_rel_offset s1, 8 + sd s2, 0(sp) + .cfi_rel_offset s2, 0 + sd ra, 24(sp) + .cfi_rel_offset ra, 24 + + mv s2, a0 + mv s1, a1 + mv s0, sp + .cfi_def_cfa_register s0 + + /* 16-bytes alignment */ + addiw a0, a2, 1 + andi a0, a0, -2 + slli a0, a0, 3 + sub sp, sp, a0 + mv a4, sp + + addi sp, sp, -8*(NGPREGS+NFPREGS) + mv a0, sp + addi a1, sp, 8*NGPREGS + + call invoke_copy_to_stack + + /* 1st argument is this */ + mv a0, s2 + + ld a1, 8(sp) + ld a2, 16(sp) + ld a3, 24(sp) + ld a4, 32(sp) + ld a5, 40(sp) + ld a6, 48(sp) + ld a7, 56(sp) + + fld fa0, 64(sp) + fld fa1, 72(sp) + fld fa2, 80(sp) + fld fa3, 88(sp) + fld fa4, 96(sp) + fld fa5, 104(sp) + fld fa6, 112(sp) + fld fa7, 120(sp) + + addi sp, sp, 8*(NGPREGS+NFPREGS) + + ld s2, 0(s2) + slliw s1, s1, 3 + add s2, s2, s1 + ld t0, 0(s2) + jalr t0 + + mv sp, s0 + .cfi_def_cfa_register sp + ld s0, 16(sp) + .cfi_restore s0 + ld s1, 8(sp) + .cfi_restore s1 + ld s2, 0(sp) + .cfi_restore s2 + ld ra, 24(sp) + .cfi_restore ra + addi sp, sp, 32 + .cfi_adjust_cfa_offset -32 + ret + .cfi_endproc + .size _NS_InvokeByIndex, . - _NS_InvokeByIndex + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc64_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc64_openbsd.s new file mode 100644 index 0000000000..dfd6189f83 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc64_openbsd.s @@ -0,0 +1,86 @@ +/* -*- Mode: asm; 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/. */ + +/* + Platform specific code to invoke XPCOM methods on native objects + for sparcv9 Solaris. + + See the SPARC Compliance Definition (SCD) Chapter 3 + for more information about what is going on here, including + the use of BIAS (0x7ff). + The SCD is available from http://www.sparc.com/. +*/ + + .global NS_InvokeByIndex + .type NS_InvokeByIndex, #function + +/* + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +NS_InvokeByIndex: + save %sp,-(128 + 64),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 64 + sll %i2,4,%l0 ! assume the worst case + ! paramCount * 2 * 8 bytes + cmp %l0, 0 ! are there any args? If not, + be .invoke ! no need to copy args to stack + nop + + sub %sp,%l0,%sp ! create the additional stack space + add %sp,0x7ff+136,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params + +! +! load arguments from stack into the outgoing registers +! BIAS is 0x7ff (2047) +! + +! load the %o1..5 64bit (extended word) output registers registers + ldx [%sp + 0x7ff + 136],%o1 ! %i1 + ldx [%sp + 0x7ff + 144],%o2 ! %i2 + ldx [%sp + 0x7ff + 152],%o3 ! %i3 + ldx [%sp + 0x7ff + 160],%o4 ! %i4 + ldx [%sp + 0x7ff + 168],%o5 ! %i5 + +! load the even number double registers starting with %f2 + ldd [%sp + 0x7ff + 136],%f2 + ldd [%sp + 0x7ff + 144],%f4 + ldd [%sp + 0x7ff + 152],%f6 + ldd [%sp + 0x7ff + 160],%f8 + ldd [%sp + 0x7ff + 168],%f10 + ldd [%sp + 0x7ff + 176],%f12 + ldd [%sp + 0x7ff + 184],%f14 + ldd [%sp + 0x7ff + 192],%f16 + ldd [%sp + 0x7ff + 200],%f18 + ldd [%sp + 0x7ff + 208],%f20 + ldd [%sp + 0x7ff + 216],%f22 + ldd [%sp + 0x7ff + 224],%f24 + ldd [%sp + 0x7ff + 232],%f26 + ldd [%sp + 0x7ff + 240],%f28 + ldd [%sp + 0x7ff + 248],%f30 + +! +! calculate the target address from the vtable +! +.invoke: + sll %i1,3,%l0 ! index *= 8 +! add %l0,16,%l0 ! there are 2 extra entries in the vTable (16bytes) + ldx [%i0],%l1 ! *that --> address of vtable + ldx [%l0 + %l1],%l0 ! that->vtable[index * 8 + 16] --> address + + jmpl %l0,%o7 ! call the routine + mov %i0,%o0 ! move 'this' pointer to out register + + mov %o0,%i0 ! propagate return value + ret + restore + + .size NS_InvokeByIndex, .-NS_InvokeByIndex diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_linux_GCC3.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_linux_GCC3.s new file mode 100644 index 0000000000..36196805e8 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_linux_GCC3.s @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Platform specific code to invoke XPCOM methods on native objects for + * Linux/Sparc with gcc 3 ABI. + */ + .global NS_InvokeByIndex +/* + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params); + * + */ +NS_InvokeByIndex: + save %sp,-(64 + 16),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 16 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + ld [%i0],%l1 ! *that --> vTable + sll %i1,2,%i1 ! multiply index by 4 + add %i1,%l1,%l1 ! l1 now points to vTable entry + ld [%l1],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s new file mode 100644 index 0000000000..893432a5e8 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + .global XPTC_InvokeByIndex +/* + XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +XPTC_InvokeByIndex: + save %sp,-(64 + 16),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 16 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + add %i1,1,%i1 ! vTable is zero-based, index is 1 based (?) + ld [%i0],%l1 ! *that --> vTable + sll %i1,3,%i1 + add %i1,%l1,%l1 ! vTable[index * 8], l1 now points to vTable entry + lduh [%l1],%l0 ! this adjustor + sll %l0,16,%l0 ! sign extend to 32 bits + sra %l0,16,%l0 + add %l0,%i0,%i0 ! adjust this + ld [%l1 + 4],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s new file mode 100644 index 0000000000..1ef6953703 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + .global XPTC_InvokeByIndex +/* + XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +XPTC_InvokeByIndex: + save %sp,-(64 + 16),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 16 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + add %i1,1,%i1 ! vTable is zero-based, index is 1 based (?) + ld [%i0],%l1 ! *that --> vTable + sll %i1,3,%i1 + add %i1,%l1,%l1 ! vTable[index * 8], l1 now points to vTable entry + lduh [%l1],%l0 ! this adjustor + sll %l0,16,%l0 ! sign extend to 32 bits + sra %l0,16,%l0 + add %l0,%i0,%i0 ! adjust this + ld [%l1 + 4],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S new file mode 100644 index 0000000000..0cb7ff37c7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S @@ -0,0 +1,117 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Darwin gives a leading '_' to symbols defined in C code. +#ifdef XP_DARWIN +#define SYM(x) _ ## x +#else +#define SYM(x) x +#endif + +#define CFI_STARTPROC .cfi_startproc +#define CFI_ENDPROC .cfi_endproc +#define CFI_DEF_CFA_OFFSET(offset) .cfi_def_cfa_offset offset +#define CFI_OFFSET(reg, offset) .cfi_offset reg, offset +#define CFI_DEF_CFA_REGISTER(reg) .cfi_def_cfa_register reg +#define CFI_DEF_CFA(reg, offset) .cfi_def_cfa reg, offset + +.intel_syntax noprefix + +# nsresult NS_InvokeByIndex(nsISupports* this, uint32_t aVtableIndex, +# uint32_t argc, nsXPTCVariant* argv); +.text +.global SYM(NS_InvokeByIndex) +#ifndef XP_DARWIN +.type NS_InvokeByIndex, @function +#endif +.align 4 +SYM(NS_InvokeByIndex): + CFI_STARTPROC + push rbp + CFI_DEF_CFA_OFFSET(16) + CFI_OFFSET(6, -16) + mov rbp, rsp + CFI_DEF_CFA_REGISTER(6) + +# save r12 and r13 because we use them and they are callee saved. + push r12 + push r13 + CFI_OFFSET(12, -24) + CFI_OFFSET(13, -32) + +# save this and the vtable index because we need them after setting up the +# stack. + mov r12, rdi + mov r13, rsi + +# allocate space for stack arguments, in theory we only need 8 * (argc - 5) +# bytes because at least 5 arguments will go in registers, but for now it is +# just simpler to allocate 8 * argc bytes. Note that we treat the this +# pointer specially. + lea eax, [edx * 8] + sub rsp, rax + +# If there is an odd number of args the stack can be misaligned so realign it. + and rsp, 0xfffffffffffffff0 + +# pass the stack slot area to InvokeCopyToStack. + mov r8, rsp + +# setup space for the register slots: there are 5 integer ones and 8 floating +# point ones. So we need 104 bytes of space, but we allocate 112 to keep rsp +# aligned to 16 bytes. + sub rsp, 112 + +# the first argument to InvokeCopyToStack is the integer register area, and the +# second is the floating point area. + mov rdi, rsp + lea rsi, [rsp + 40] + +# The 3rd and 4th arguments to InvokeCopyToStack are already in the right +# registers. So now we can just call InvokeCopyToStack. + call SYM(InvokeCopyToStack) + +# setup this + mov rdi, r12 + +# copy the integer arguments into place. + mov rsi, [rsp] + mov rdx, [rsp + 8] + mov rcx, [rsp + 16] + mov r8, [rsp + 24] + mov r9, [rsp + 32] + +# copy the float arguments into place + movsd xmm0, [rsp + 40] + movsd xmm1, [rsp + 48] + movsd xmm2, [rsp + 56] + movsd xmm3, [rsp + 64] + movsd xmm4, [rsp + 72] + movsd xmm5, [rsp + 80] + movsd xmm6, [rsp + 88] + movsd xmm7, [rsp + 96] + +# get rid of the scratch space for registers + add rsp, 112 + +# load the function pointer and call + lea eax, [r13d * 8] + add rax, [rdi] + call [rax] + +# r12 and r13 were pushed relative to the old stack pointer which is now the +# frame pointer. + mov r12, [rbp - 0x8] + mov r13, [rbp - 0x10] + + mov rsp, rbp + pop rbp + CFI_DEF_CFA(7, 8) + ret + CFI_ENDPROC + +#ifndef XP_DARWIN +// Magic indicating no need for an executable stack +.section .note.GNU-stack, "", @progbits +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_darwin.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_darwin.cpp new file mode 100644 index 0000000000..e6b7932e8d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_darwin.cpp @@ -0,0 +1,18 @@ +/* -*- Mode: C -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(__i386__) +#include "xptcinvoke_gcc_x86_unix.cpp" +#elif defined(__x86_64__) +#include "xptcinvoke_x86_64_unix.cpp" +#elif defined(__ppc__) +#include "xptcinvoke_ppc_rhapsody.cpp" +#elif defined(__arm__) +#include "xptcinvoke_arm.cpp" +#elif defined(__aarch64__) +#include "xptcinvoke_aarch64.cpp" +#else +#error unknown cpu architecture +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp new file mode 100644 index 0000000000..2e834a7e3c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" +#include "xptc_gcc_x86_unix.h" + +extern "C" { +static void ATTRIBUTE_USED __attribute__ ((regparm(3))) +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint32_t* d) +{ + for(uint32_t i = paramCount; i >0; i--, d++, s++) + { + if(s->IsIndirect()) + { + *((void**)d) = &s->val; + continue; + } + + switch(s->type) + { + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + default : *((void**)d) = s->val.p; break; + } + } +} +} // extern "C" + +/* + EXPORT_XPCOM_API(nsresult) + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + + Each param takes at most two 4-byte words. + It doesn't matter if we push too many words, and calculating the exact + amount takes time. + + that = ebp + 0x08 + methodIndex = ebp + 0x0c + paramCount = ebp + 0x10 + params = ebp + 0x14 + +*/ + +__asm__ ( + ".text\n\t" +/* alignment here seems unimportant here; this was 16, now it's 2 which + is what xptcstubs uses. */ + ".align 2\n\t" + ".globl " SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t" +#ifndef XP_MACOSX + ".type " SYMBOL_UNDERSCORE "NS_InvokeByIndex,@function\n" +#endif + SYMBOL_UNDERSCORE "NS_InvokeByIndex:\n\t" + "pushl %ebp\n\t" + "movl %esp, %ebp\n\t" + "movl 0x10(%ebp), %eax\n\t" + "leal 0(,%eax,8),%edx\n\t" + + /* set up call frame for method. */ + "subl %edx, %esp\n\t" /* make room for params. */ +/* Align to maximum x86 data size: 128 bits == 16 bytes == XMM register size. + * This is to avoid protection faults where SSE+ alignment of stack pointer + * is assumed and required, e.g. by GCC4's -ftree-vectorize option. + */ + "andl $0xfffffff0, %esp\n\t" /* drop(?) stack ptr to 128-bit align */ +/* $esp should be aligned to a 16-byte boundary here (note we include an + * additional 4 bytes in a later push instruction). This will ensure $ebp + * in the function called below is aligned to a 0x8 boundary. SSE instructions + * like movapd/movdqa expect memory operand to be aligned on a 16-byte + * boundary. The GCC compiler will generate the memory operand using $ebp + * with an 8-byte offset. + */ + "subl $0xc, %esp\n\t" /* lower again; push/call below will re-align */ + "movl %esp, %ecx\n\t" /* ecx = d */ + "movl 8(%ebp), %edx\n\t" /* edx = this */ + "pushl %edx\n\t" /* push this. esp % 16 == 0 */ + + "movl 0x14(%ebp), %edx\n\t" + "call " SYMBOL_UNDERSCORE "invoke_copy_to_stack\n\t" + "movl 0x08(%ebp), %ecx\n\t" /* 'that' */ + "movl (%ecx), %edx\n\t" + "movl 0x0c(%ebp), %eax\n\t" /* function index */ + "leal (%edx,%eax,4), %edx\n\t" + "call *(%edx)\n\t" + "movl %ebp, %esp\n\t" + "popl %ebp\n\t" + "ret\n" +#ifndef XP_MACOSX + ".size " SYMBOL_UNDERSCORE "NS_InvokeByIndex, . -" SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t" +#endif +); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp new file mode 100644 index 0000000000..969029f13e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp @@ -0,0 +1,129 @@ + +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xptcprivate.h" + +// "This code is for IA64 only" + + +/* invoke_copy_to_stack() will copy from variant array 's' to + * the stack argument area 'mloc', the integer register area 'iloc', and + * the float register area 'floc'. + * + */ +extern "C" void +invoke_copy_to_stack(uint64_t* mloc, uint64_t* iloc, uint64_t* floc, + const uint32_t paramCount, nsXPTCVariant* s) +{ + uint64_t* dest = mloc; + uint32_t len = paramCount; + nsXPTCVariant* source = s; + + uint32_t indx; + uint32_t endlen; + endlen = (len > 7) ? 7 : len; + /* handle the memory arguments */ + for (indx = 7; indx < len; ++indx) + { + if (source[indx].IsPtrData()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].ptr; +#endif + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_DOUBLE: *(dest) = source[indx].val.u64; break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; +#else + { + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].val.p; + } +#endif + } + ++dest; + } + /* process register arguments */ + dest = iloc; + for (indx = 0; indx < endlen; ++indx) + { + if (source[indx].IsPtrData()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].ptr; +#endif + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : + *((double*) (floc++)) = (double) source[indx].val.f; + break; + case nsXPTType::T_DOUBLE: + *((double*) (floc++)) = source[indx].val.d; + break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; +#else + { + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].val.p; + } +#endif + } + ++dest; + } + +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp new file mode 100644 index 0000000000..acd9de081b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp @@ -0,0 +1,99 @@ + +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xptcprivate.h" + +#include <stdint.h> + +// "This code is for IA64 only" + + +/* invoke_copy_to_stack() will copy from variant array 's' to + * the stack argument area 'mloc', the integer register area 'iloc', and + * the float register area 'floc'. + * + */ +extern "C" void +invoke_copy_to_stack(uint64_t* mloc, uint64_t* iloc, uint64_t* floc, + const uint32_t paramCount, nsXPTCVariant* s) +{ + uint64_t* dest = mloc; + uint32_t len = paramCount; + nsXPTCVariant* source = s; + + uint32_t indx; + uint32_t endlen; + endlen = (len > 7) ? 7 : len; + /* handle the memory arguments */ + for (indx = 7; indx < len; ++indx) + { + if (source[indx].IsPtrData()) + { + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_DOUBLE: *(dest) = source[indx].val.u64; break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; + } + ++dest; + } + /* process register arguments */ + dest = iloc; + for (indx = 0; indx < endlen; ++indx) + { + if (source[indx].IsPtrData()) + { + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : + *((double*) (floc++)) = (double) source[indx].val.f; + break; + case nsXPTType::T_DOUBLE: + *((double*) (floc++)) = source[indx].val.d; + break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; + } + ++dest; + } + +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_alpha.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_alpha.cpp new file mode 100644 index 0000000000..dc111e4358 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_alpha.cpp @@ -0,0 +1,144 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +__asm__("invoke_copy_to_stack") __attribute__((used)); + +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *d = (uint64_t)s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *d = (uint64_t)s->val.i8; break; + case nsXPTType::T_I16 : *d = (uint64_t)s->val.i16; break; + case nsXPTType::T_I32 : *d = (uint64_t)s->val.i32; break; + case nsXPTType::T_I64 : *d = (uint64_t)s->val.i64; break; + case nsXPTType::T_U8 : *d = (uint64_t)s->val.u8; break; + case nsXPTType::T_U16 : *d = (uint64_t)s->val.u16; break; + case nsXPTType::T_U32 : *d = (uint64_t)s->val.u32; break; + case nsXPTType::T_U64 : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // convert floats to doubles if they are to be passed + // via registers so we can just deal with doubles later + union { uint64_t u64; double d; } t; + t.d = (double)s->val.f; + *d = t.u64; + } + else + // otherwise copy to stack normally + *d = (uint64_t)s->val.u32; + break; + case nsXPTType::T_DOUBLE : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_BOOL : *d = (uint64_t)s->val.b; break; + case nsXPTType::T_CHAR : *d = (uint64_t)s->val.c; break; + case nsXPTType::T_WCHAR : *d = (uint64_t)s->val.wc; break; + default: + // all the others are plain pointer types + *d = (uint64_t)s->val.p; + break; + } + } +} + +/* + * EXPORT_XPCOM_API(nsresult) + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ +__asm__( + "#### NS_InvokeByIndex ####\n" +".text\n\t" + ".align 5\n\t" + ".globl NS_InvokeByIndex\n\t" + ".ent NS_InvokeByIndex\n" +"NS_InvokeByIndex:\n\t" + ".frame $15,32,$26,0\n\t" + ".mask 0x4008000,-32\n\t" + "ldgp $29,0($27)\n" +"$NS_InvokeByIndex..ng:\n\t" + "subq $30,32,$30\n\t" + "stq $26,0($30)\n\t" + "stq $15,8($30)\n\t" + "bis $30,$30,$15\n\t" + ".prologue 1\n\t" + + /* + * Allocate enough stack space to hold the greater of 6 or "paramCount"+1 + * parameters. (+1 for "this" pointer) Room for at least 6 parameters + * is required for storage of those passed via registers. + */ + + "bis $31,5,$2\n\t" /* count = MAX(5, "paramCount") */ + "cmplt $2,$18,$1\n\t" + "cmovne $1,$18,$2\n\t" + "s8addq $2,16,$1\n\t" /* room for count+1 params (8 bytes each) */ + "bic $1,15,$1\n\t" /* stack space is rounded up to 0 % 16 */ + "subq $30,$1,$30\n\t" + + "stq $16,0($30)\n\t" /* save "that" (as "this" pointer) */ + "stq $17,16($15)\n\t" /* save "methodIndex" */ + + "addq $30,8,$16\n\t" /* pass stack pointer */ + "bis $18,$18,$17\n\t" /* pass "paramCount" */ + "bis $19,$19,$18\n\t" /* pass "params" */ + "bsr $26,$invoke_copy_to_stack..ng\n\t" /* call invoke_copy_to_stack */ + + /* + * Copy the first 6 parameters to registers and remove from stack frame. + * Both the integer and floating point registers are set for each parameter + * except the first which is the "this" pointer. (integer only) + * The floating point registers are all set as doubles since the + * invoke_copy_to_stack function should have converted the floats. + */ + "ldq $16,0($30)\n\t" /* integer registers */ + "ldq $17,8($30)\n\t" + "ldq $18,16($30)\n\t" + "ldq $19,24($30)\n\t" + "ldq $20,32($30)\n\t" + "ldq $21,40($30)\n\t" + "ldt $f17,8($30)\n\t" /* floating point registers */ + "ldt $f18,16($30)\n\t" + "ldt $f19,24($30)\n\t" + "ldt $f20,32($30)\n\t" + "ldt $f21,40($30)\n\t" + + "addq $30,48,$30\n\t" /* remove params from stack */ + + /* + * Call the virtual function with the constructed stack frame. + */ + "bis $16,$16,$1\n\t" /* load "this" */ + "ldq $2,16($15)\n\t" /* load "methodIndex" */ + "ldq $1,0($1)\n\t" /* load vtable */ + "s8addq $2,$31,$2\n\t" /* vtable index = "methodIndex" * 8 */ + "addq $1,$2,$1\n\t" + "ldq $27,0($1)\n\t" /* load address of function */ + "jsr $26,($27),0\n\t" /* call virtual function */ + "ldgp $29,0($26)\n\t" + + "bis $15,$15,$30\n\t" + "ldq $26,0($30)\n\t" + "ldq $15,8($30)\n\t" + "addq $30,32,$30\n\t" + "ret $31,($26),1\n\t" + ".end NS_InvokeByIndex" + ); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp new file mode 100644 index 0000000000..c5fa2f94bd --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t overflow = 0, gpr = 1 /*this*/, fpr = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + if (gpr < 5) gpr++; else overflow++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_I64 : + if (gpr < 4) gpr+=2; else gpr=5, overflow+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_U64 : + if (gpr < 4) gpr+=2; else gpr=5, overflow+=2; + break; + case nsXPTType::T_FLOAT : + if (fpr < 2) fpr++; else overflow++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 2) fpr++; else overflow+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + if (gpr < 5) gpr++; else overflow++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) gpr++; else overflow++; + break; + } + } + /* Round up number of overflow words to ensure stack + stays aligned to 8 bytes. */ + return (overflow + 1) & ~1; +} + +static void +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint32_t* d_ov, uint32_t overflow) +{ + uint32_t *d_gpr = d_ov + overflow; + uint64_t *d_fpr = (uint64_t *)(d_gpr + 4); + uint32_t gpr = 1 /*this*/, fpr = 0; + + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + if (gpr < 5) + *((void**)d_gpr) = s->ptr, d_gpr++, gpr++; + else + *((void**)d_ov ) = s->ptr, d_ov++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + *((int32_t*) d_gpr) = s->val.i8, d_gpr++, gpr++; + else + *((int32_t*) d_ov ) = s->val.i8, d_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + *((int32_t*) d_gpr) = s->val.i16, d_gpr++, gpr++; + else + *((int32_t*) d_ov ) = s->val.i16, d_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + *((int32_t*) d_gpr) = s->val.i32, d_gpr++, gpr++; + else + *((int32_t*) d_ov ) = s->val.i32, d_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 4) + *((int64_t*) d_gpr) = s->val.i64, d_gpr+=2, gpr+=2; + else + *((int64_t*) d_ov ) = s->val.i64, d_ov+=2, gpr=5; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + *((uint32_t*) d_gpr) = s->val.u8, d_gpr++, gpr++; + else + *((uint32_t*) d_ov ) = s->val.u8, d_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.u16, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.u16, d_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.u32, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.u32, d_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 4) + *((uint64_t*)d_gpr) = s->val.u64, d_gpr+=2, gpr+=2; + else + *((uint64_t*)d_ov ) = s->val.u64, d_ov+=2, gpr=5; + break; + case nsXPTType::T_FLOAT : + if (fpr < 2) + *((float*) d_fpr) = s->val.f, d_fpr++, fpr++; + else + *((float*) d_ov ) = s->val.f, d_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 2) + *((double*) d_fpr) = s->val.d, d_fpr++, fpr++; + else + *((double*) d_ov ) = s->val.d, d_ov+=2; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.b, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.b, d_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.c, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.c, d_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.wc, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.wc, d_ov++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) + *((void**) d_gpr) = s->val.p, d_gpr++, gpr++; + else + *((void**) d_ov ) = s->val.p, d_ov++; + break; + } + } +} + +typedef nsresult (*vtable_func)(nsISupports *, uint32_t, uint32_t, uint32_t, uint32_t, double, double); + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_IGNORE +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + vtable_func *vtable = *reinterpret_cast<vtable_func **>(that); + vtable_func method = vtable[methodIndex]; + uint32_t overflow = invoke_count_words (paramCount, params); + uint32_t *stack_space = reinterpret_cast<uint32_t *>(__builtin_alloca((overflow + 8 /* 4 32-bits gpr + 2 64-bits fpr */) * 4)); + + invoke_copy_to_stack(paramCount, params, stack_space, overflow); + + uint32_t *d_gpr = stack_space + overflow; + double *d_fpr = reinterpret_cast<double *>(d_gpr + 4); + + return method(that, d_gpr[0], d_gpr[1], d_gpr[2], d_gpr[3], d_fpr[0], d_fpr[1]); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp new file mode 100644 index 0000000000..1be12d4ad3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t overflow = 0, gpr = 1 /*this*/, fpr = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsIndirect()) + { + if (gpr < 5) gpr++; else overflow++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + case nsXPTType::T_I64 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + case nsXPTType::T_U64 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_FLOAT : + case nsXPTType::T_DOUBLE : + if (fpr < 4) fpr++; else overflow++; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + if (gpr < 5) gpr++; else overflow++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) gpr++; else overflow++; + break; + } + } + /* Round up number of overflow words to ensure stack + stays aligned to 8 bytes. */ + return (overflow + 1) & ~1; +} + +static void +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint64_t* d_ov, uint32_t overflow) +{ + uint64_t *d_gpr = d_ov + overflow; + uint64_t *d_fpr = (uint64_t *)(d_gpr + 4); + uint32_t gpr = 1 /*this*/, fpr = 0; + + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsIndirect()) + { + if (gpr < 5) + *((void**)d_gpr) = (void *) &s->val, d_gpr++, gpr++; + else + *((void**)d_ov ) = (void *) &s->val, d_ov++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i8, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i8, d_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i16, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i16, d_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i32, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i32, d_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i64, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i64, d_ov++; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + *((uint64_t*) d_gpr) = s->val.u8, d_gpr++, gpr++; + else + *((uint64_t*) d_ov ) = s->val.u8, d_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.u16, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.u16, d_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.u32, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.u32, d_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.u64, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.u64, d_ov++; + break; + case nsXPTType::T_FLOAT : + if (fpr < 4) + *((float*) d_fpr) = s->val.f, d_fpr++, fpr++; + else + *(((float*) d_ov )+1) = s->val.f, d_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 4) + *((double*) d_fpr) = s->val.d, d_fpr++, fpr++; + else + *((double*) d_ov ) = s->val.d, d_ov++; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.b, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.b, d_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.c, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.c, d_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.wc, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.wc, d_ov++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) + *((void**) d_gpr) = s->val.p, d_gpr++, gpr++; + else + *((void**) d_ov ) = s->val.p, d_ov++; + break; + } + } +} + +typedef nsresult (*vtable_func)(nsISupports *, uint64_t, uint64_t, uint64_t, uint64_t, double, double, double, double); + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_IGNORE +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + vtable_func *vtable = *reinterpret_cast<vtable_func **>(that); + vtable_func method = vtable[methodIndex]; + uint64_t overflow = invoke_count_words (paramCount, params); + uint64_t *stack_space = reinterpret_cast<uint64_t *>(__builtin_alloca((overflow + 8 /* 4 64-bits gpr + 4 64-bits fpr */) * 8)); + uint64_t result; + + invoke_copy_to_stack(paramCount, params, stack_space, overflow); + + uint64_t *d_gpr = stack_space + overflow; + double *d_fpr = reinterpret_cast<double *>(d_gpr + 4); + + return method(that, d_gpr[0], d_gpr[1], d_gpr[2], d_gpr[3], d_fpr[0], d_fpr[1], d_fpr[2], d_fpr[3]); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_loongarch64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_loongarch64.cpp new file mode 100644 index 0000000000..61bb7b2efd --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_loongarch64.cpp @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +#include "xptcprivate.h" + +extern "C" void invoke_copy_to_stack(uint64_t* gpregs, double* fpregs, + uint32_t paramCount, nsXPTCVariant* s, + uint64_t* d) { + static const uint32_t GPR_COUNT = 8; + static const uint32_t FPR_COUNT = 8; + + uint32_t nr_gpr = 1; // skip one GPR register for "this" + uint32_t nr_fpr = 0; + uint64_t value = 0; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsIndirect()) { + value = (uint64_t)&s->val; + } else { + switch (s->type) { + case nsXPTType::T_FLOAT: + break; + case nsXPTType::T_DOUBLE: + break; + case nsXPTType::T_I8: + value = s->val.i8; + break; + case nsXPTType::T_I16: + value = s->val.i16; + break; + case nsXPTType::T_I32: + value = s->val.i32; + break; + case nsXPTType::T_I64: + value = s->val.i64; + break; + case nsXPTType::T_U8: + value = s->val.u8; + break; + case nsXPTType::T_U16: + value = s->val.u16; + break; + case nsXPTType::T_U32: + value = s->val.u32; + break; + case nsXPTType::T_U64: + value = s->val.u64; + break; + case nsXPTType::T_BOOL: + value = s->val.b; + break; + case nsXPTType::T_CHAR: + value = s->val.c; + break; + case nsXPTType::T_WCHAR: + value = s->val.wc; + break; + default: + value = (uint64_t)s->val.p; + break; + } + } + + if (!s->IsIndirect() && s->type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) { + fpregs[nr_fpr++] = s->val.d; + } else if (nr_gpr < GPR_COUNT) { + memcpy(&gpregs[nr_gpr++], &(s->val.d), sizeof(s->val.d)); + } else { + memcpy(d++, &(s->val.d), sizeof(s->val.d)); + } + } else if (!s->IsIndirect() && s->type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) { + memcpy(&fpregs[nr_fpr++], &(s->val.f), sizeof(s->val.f)); + } else if (nr_gpr < GPR_COUNT) { + memcpy(&gpregs[nr_gpr++], &(s->val.f), sizeof(s->val.f)); + } else { + memcpy(d++, &(s->val.f), sizeof(s->val.f)); + } + } else { + if (nr_gpr < GPR_COUNT) { + gpregs[nr_gpr++] = value; + } else { + *d++ = value; + } + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, uint32_t paramCount, + nsXPTCVariant* params) { + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp new file mode 100644 index 0000000000..b811730c5b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 code is for MIPS using the O32 ABI. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#include <stdint.h> + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + // Count a word for a0 even though it's never stored or loaded + // We do this only for alignment of register pairs. + uint32_t result = 1; + for (uint32_t i = 0; i < paramCount; i++, result++, s++) + { + if (s->IsIndirect()) + continue; + + switch(s->type) + { + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : + if (result & 1) + result++; + result++; + break; + + default: + break; + } + } + return (result + 1) & ~(uint32_t)1; +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + nsXPTCVariant* s) +{ + // Skip the unused a0 slot, which we keep only for register pair alignment. + d++; + + for (uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if (s->IsIndirect()) + { + *((void**)d) = (void*) &s->val; + continue; + } + + switch(s->type) + { + case nsXPTType::T_I8 : *d = (uint32_t) s->val.i8; break; + case nsXPTType::T_I16 : *d = (uint32_t) s->val.i16; break; + case nsXPTType::T_I32 : *d = (uint32_t) s->val.i32; break; + case nsXPTType::T_I64 : + if ((intptr_t)d & 4) d++; + *((int64_t*) d) = s->val.i64; d++; + break; + case nsXPTType::T_U8 : *d = (uint32_t) s->val.u8; break; + case nsXPTType::T_U16 : *d = (uint32_t) s->val.u16; break; + case nsXPTType::T_U32 : *d = (uint32_t) s->val.u32; break; + case nsXPTType::T_U64 : + if ((intptr_t)d & 4) d++; + *((uint64_t*) d) = s->val.u64; d++; + break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : + if ((intptr_t)d & 4) d++; + *((double*) d) = s->val.d; d++; + break; + case nsXPTType::T_BOOL : *d = (bool) s->val.b; break; + case nsXPTType::T_CHAR : *d = (char) s->val.c; break; + case nsXPTType::T_WCHAR : *d = (wchar_t) s->val.wc; break; + default: + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips64.cpp new file mode 100644 index 0000000000..d10320a997 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips64.cpp @@ -0,0 +1,144 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if (_MIPS_SIM != _ABIN32) && (_MIPS_SIM != _ABI64) +#error "This code is for MIPS n32/n64 only" +#endif + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return paramCount; +} + +extern "C" void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, + nsXPTCVariant* s, uint64_t *regs) +{ +#define N_ARG_REGS 7 /* 8 regs minus 1 for "this" ptr */ + + for (uint32_t i = 0; i < paramCount; i++, s++) + { + if (s->IsIndirect()) { + if (i < N_ARG_REGS) + regs[i] = (uint64_t) &s->val; + else + *d++ = (uint64_t) &s->val; + continue; + } + switch (s->type) { + // + // signed types first + // + case nsXPTType::T_I8: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i8; + else + *d++ = s->val.i8; + break; + case nsXPTType::T_I16: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i16; + else + *d++ = s->val.i16; + break; + case nsXPTType::T_I32: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i32; + else + *d++ = s->val.i32; + break; + case nsXPTType::T_I64: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i64; + else + *d++ = s->val.i64; + break; + // + // unsigned types next + // + case nsXPTType::T_U8: + if (i < N_ARG_REGS) + regs[i] = s->val.u8; + else + *d++ = s->val.u8; + break; + case nsXPTType::T_U16: + if (i < N_ARG_REGS) + regs[i] = s->val.u16; + else + *d++ = s->val.u16; + break; + case nsXPTType::T_U32: + if (i < N_ARG_REGS) + // 32-bit values need to be sign-extended + // in register, so use the signed value. + regs[i] = s->val.i32; + else + *d++ = s->val.u32; + break; + case nsXPTType::T_U64: + if (i < N_ARG_REGS) + regs[i] = s->val.u64; + else + *d++ = s->val.u64; + break; + case nsXPTType::T_FLOAT: + // the float data formate must not be converted! + // Just only copy without conversion. + if (i < N_ARG_REGS) + *(float*)®s[i] = s->val.f; + else + *(float*)d++ = s->val.f; + break; + case nsXPTType::T_DOUBLE: + if (i < N_ARG_REGS) + *(double*)®s[i] = s->val.d; + else + *(double*)d++ = s->val.d; + break; + case nsXPTType::T_BOOL: + if (i < N_ARG_REGS) + regs[i] = s->val.b; + else + *d++ = s->val.b; + break; + case nsXPTType::T_CHAR: + if (i < N_ARG_REGS) + regs[i] = s->val.c; + else + *d++ = s->val.c; + break; + case nsXPTType::T_WCHAR: + if (i < N_ARG_REGS) + regs[i] = s->val.wc; + else + *d++ = s->val.wc; + break; + default: + // all the others are plain pointer types + if (i < N_ARG_REGS) + regs[i] = (uint64_t)s->val.p; + else + *d++ = (uint64_t)s->val.p; + break; + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp new file mode 100644 index 0000000000..c0c3e510fa --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp @@ -0,0 +1,148 @@ + +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xptcprivate.h" + +#if _HPUX +#error "This code is for HP-PA RISC 32 bit mode only" +#endif + +#include <alloca.h> + +typedef unsigned nsXPCVariant; + +extern "C" int32_t +invoke_count_bytes(nsISupports* that, const uint32_t methodIndex, + const uint32_t paramCount, const nsXPTCVariant* s) +{ + int32_t result = 4; /* variant records do not include self pointer */ + + /* counts the number of bytes required by the argument stack, + 64 bit integer, and double requires 8 bytes. All else requires + 4 bytes. + */ + + { + uint32_t indx; + for (indx = paramCount; indx > 0; --indx, ++s) + { + if (! s->IsPtrData()) + { + if (s->type == nsXPTType::T_I64 || s->type == nsXPTType::T_U64 || + s->type == nsXPTType::T_DOUBLE) + { + /* 64 bit integer and double aligned on 8 byte boundaries */ + result += (result & 4) + 8; + continue; + } + } + result += 4; /* all other cases use 4 bytes */ + } + } + result -= 72; /* existing stack buffer is 72 bytes */ + if (result < 0) + return 0; + { + /* round up to 64 bytes boundary */ + int32_t remainder = result & 63; + return (remainder == 0) ? result : (result + 64 - remainder); + } +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, + const uint32_t paramCount, nsXPTCVariant* s) +{ + + typedef struct + { + uint32_t hi; + uint32_t lo; + } DU; + + uint32_t* dest = d; + nsXPTCVariant* source = s; + /* we clobber param vars by copying stuff on stack, have to use local var */ + + uint32_t floatflags = 0; + /* flag indicating which floating point registers to load */ + + uint32_t regwords = 1; /* register 26 is reserved for ptr to 'that' */ + uint32_t indx; + + for (indx = paramCount; indx > 0; --indx, --dest, ++source) + { + if (source->IsPtrData()) + { + *((void**) dest) = source->ptr; + ++regwords; + continue; + } + switch (source->type) + { + case nsXPTType::T_I8 : *((int32_t*) dest) = source->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) dest) = source->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) dest) = source->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + if (regwords & 1) + { + /* align on double word boundary */ + --dest; + ++regwords; + } + *((uint32_t*) dest) = ((DU *) source)->lo; + *((uint32_t*) --dest) = ((DU *) source)->hi; + /* big endian - hi word in low addr */ + regwords += 2; + continue; + case nsXPTType::T_DOUBLE : + if (regwords & 1) + { + /* align on double word boundary */ + --dest; + ++regwords; + } + switch (regwords) /* load double precision float register */ + { + case 2: + floatflags |= 1; + } + *((uint32_t*) dest) = ((DU *) source)->lo; + *((uint32_t*) --dest) = ((DU *) source)->hi; + /* big endian - hi word in low addr */ + regwords += 2; + continue; + case nsXPTType::T_FLOAT : + switch (regwords) /* load single precision float register */ + { + case 1: + floatflags |= 2; + break; + case 2: + floatflags |= 4; + break; + case 3: + floatflags |= 8; + } + *((float*) dest) = source->val.f; + break; + case nsXPTType::T_U8 : *((uint32_t*) (dest)) = source->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) (dest)) = source->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) (dest)) = source->val.u32; break; + case nsXPTType::T_BOOL : *((uint32_t*) (dest)) = source->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) (dest)) = source->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) (dest)) = source->val.wc; break; + + default: + // all the others are plain pointer types + *((void**) dest) = source->val.p; + } + ++regwords; + } + return floatflags; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc64_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc64_linux.cpp new file mode 100644 index 0000000000..f7673bb29d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc64_linux.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +#include "xptcprivate.h" + +// The purpose of NS_InvokeByIndex() is to map a platform +// independent call to the platform ABI. To do that, +// NS_InvokeByIndex() has to determine the method to call via vtable +// access. The parameters for the method are read from the +// nsXPTCVariant* and prepared for the native ABI. +// +// Prior to POWER8, all 64-bit Power ISA systems used ELF v1 ABI, found +// here: +// https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html +// and in particular: +// https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html#FUNC-CALL +// Little-endian ppc64le, however, uses ELF v2 ABI, which is here: +// http://openpowerfoundation.org/wp-content/uploads/resources/leabi/leabi-20170510.pdf +// and in particular section 2.2, page 22. However, most big-endian ppc64 +// systems still use ELF v1, so this file should support both. + +// 7 integral parameters are passed in registers, not including |this| +// (i.e., r3-r10, with r3 being |this|). +const uint32_t GPR_COUNT = 7; + +// 13 floating point parameters are passed in registers, either single or +// double precision (i.e., f1-f13). +const uint32_t FPR_COUNT = 13; + +// Both ABIs use the same register assignment strategy, as per this +// example from V1 ABI section 3.2.3 and V2 ABI section 2.2.3.2 [page 43]: +// +// typedef struct { +// int a; +// double dd; +// } sparm; +// sparm s, t; +// int c, d, e; +// long double ld; +// double ff, gg, hh; +// +// x = func(c, ff, d, ld, s, gg, t, e, hh); +// +// Parameter Register Offset in parameter save area +// c r3 0-7 (not stored in parameter save area) +// ff f1 8-15 (not stored) +// d r5 16-23 (not stored) +// ld f2,f3 24-39 (not stored) +// s r8,r9 40-55 (not stored) +// gg f4 56-63 (not stored) +// t (none) 64-79 (stored in parameter save area) +// e (none) 80-87 (stored) +// hh f5 88-95 (not stored) +// +// i.e., each successive FPR usage skips a GPR, but not the other way around. + +extern "C" void invoke_copy_to_stack(uint64_t* gpregs, double* fpregs, + uint32_t paramCount, nsXPTCVariant* s, + uint64_t* d) +{ + uint32_t nr_gpr = 0u; + uint32_t nr_fpr = 0u; + uint64_t value = 0u; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsIndirect()) + value = (uint64_t) &s->val; + else { + switch (s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: value = s->val.i8; break; + case nsXPTType::T_I16: value = s->val.i16; break; + case nsXPTType::T_I32: value = s->val.i32; break; + case nsXPTType::T_I64: value = s->val.i64; break; + case nsXPTType::T_U8: value = s->val.u8; break; + case nsXPTType::T_U16: value = s->val.u16; break; + case nsXPTType::T_U32: value = s->val.u32; break; + case nsXPTType::T_U64: value = s->val.u64; break; + case nsXPTType::T_BOOL: value = s->val.b; break; + case nsXPTType::T_CHAR: value = s->val.c; break; + case nsXPTType::T_WCHAR: value = s->val.wc; break; + default: value = (uint64_t) s->val.p; break; + } + } + + if (!s->IsIndirect() && s->type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) { + fpregs[nr_fpr++] = s->val.d; + // Even if we have enough FPRs, still skip space in + // the parameter area if we ran out of placeholder GPRs. + if (nr_gpr < GPR_COUNT) { + nr_gpr++; + } else { + d++; + } + } else { + *((double *)d) = s->val.d; + d++; + } + } + else if (!s->IsIndirect() && s->type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) { + // Single-precision floats are passed in FPRs too. + fpregs[nr_fpr++] = s->val.f; + if (nr_gpr < GPR_COUNT) { + nr_gpr++; + } else { + d++; + } + } else { +#ifdef __LITTLE_ENDIAN__ + *((float *)d) = s->val.f; +#else + // Big endian needs adjustment to point to the least + // significant word. + float* p = (float*)d; + p++; + *p = s->val.f; +#endif + d++; + } + } + else { + if (nr_gpr < GPR_COUNT) { + gpregs[nr_gpr++] = value; + } else { + *d++ = value; + } + } + } +} + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, uint32_t paramCount, + nsXPTCVariant* params); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp new file mode 100644 index 0000000000..d894224539 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#ifndef AIX +#error "This code is for PowerPC only" +#endif + +extern "C" void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s, double *fprData) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount, fpCount = 0; + double *l_fprData = fprData; + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if(l_s->IsPtrData()) + { + *((void**)l_d) = l_s->ptr; + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_DOUBLE : + *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.d; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : + *((float*) l_d) = l_s->val.f; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.f; + break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix64.cpp new file mode 100644 index 0000000000..588f1266c0 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix64.cpp @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#ifdef _AIX + +extern "C" void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s, double *fprData) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint64_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount, fpCount = 0; + double *l_fprData = fprData; + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if(l_s->IsPtrData()) + { + *l_d = (uint64_t)l_s->ptr; + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8: *l_d = (uint64_t)l_s->val.i8; break; + case nsXPTType::T_I16: *l_d = (uint64_t)l_s->val.i16; break; + case nsXPTType::T_I32: *l_d = (uint64_t)l_s->val.i32; break; + case nsXPTType::T_I64: *l_d = (uint64_t)l_s->val.i64; break; + case nsXPTType::T_U8: *l_d = (uint64_t)l_s->val.u8; break; + case nsXPTType::T_U16: *l_d = (uint64_t)l_s->val.u16; break; + case nsXPTType::T_U32: *l_d = (uint64_t)l_s->val.u32; break; + case nsXPTType::T_U64: *l_d = (uint64_t)l_s->val.u64; break; + case nsXPTType::T_BOOL: *l_d = (uint64_t)l_s->val.b; break; + case nsXPTType::T_CHAR: *l_d = (uint64_t)l_s->val.c; break; + case nsXPTType::T_WCHAR: *l_d = (uint64_t)l_s->val.wc; break; + + case nsXPTType::T_DOUBLE: + *((double*)l_d) = l_s->val.d; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.d; + break; + case nsXPTType::T_FLOAT: + *((float*)l_d) = l_s->val.f; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.f; + break; + default: + // all the others are plain pointer types + *l_d = (uint64_t)l_s->val.p; + break; + } + } +} +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_linux.cpp new file mode 100644 index 0000000000..6af3608357 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_linux.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +// The purpose of NS_InvokeByIndex() is to map a platform +// indepenpent call to the platform ABI. To do that, +// NS_InvokeByIndex() has to determine the method to call via vtable +// access. The parameters for the method are read from the +// nsXPTCVariant* and prepared for th native ABI. For the Linux/PPC +// ABI this means that the first 8 integral and floating point +// parameters are passed in registers. + +#include "xptcprivate.h" + +// 8 integral parameters are passed in registers +#define GPR_COUNT 8 + +// With hardfloat support 8 floating point parameters are passed in registers, +// floats are promoted to doubles when passed in registers +// In Softfloat mode, everything is handled via gprs +#ifndef __NO_FPRS__ +#define FPR_COUNT 8 +#endif +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return uint32_t(((paramCount * 2) + 3) & ~3); +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, + uint32_t paramCount, + nsXPTCVariant* s, + uint32_t* gpregs, + double* fpregs) +{ + uint32_t gpr = 1; // skip one GP reg for 'that' +#ifndef __NO_FPRS__ + uint32_t fpr = 0; +#endif + uint32_t tempu32; + uint64_t tempu64; + + for(uint32_t i = 0; i < paramCount; i++, s++) { + if(s->IsPtrData()) { + if(s->type == nsXPTType::T_JSVAL) + tempu32 = (uint32_t) &s->ptr; + else + tempu32 = (uint32_t) s->ptr; + } + else { + switch(s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: tempu32 = s->val.i8; break; + case nsXPTType::T_I16: tempu32 = s->val.i16; break; + case nsXPTType::T_I32: tempu32 = s->val.i32; break; + case nsXPTType::T_I64: tempu64 = s->val.i64; break; + case nsXPTType::T_U8: tempu32 = s->val.u8; break; + case nsXPTType::T_U16: tempu32 = s->val.u16; break; + case nsXPTType::T_U32: tempu32 = s->val.u32; break; + case nsXPTType::T_U64: tempu64 = s->val.u64; break; + case nsXPTType::T_BOOL: tempu32 = s->val.b; break; + case nsXPTType::T_CHAR: tempu32 = s->val.c; break; + case nsXPTType::T_WCHAR: tempu32 = s->val.wc; break; + default: tempu32 = (uint32_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.d; +#else + if (gpr & 1) + gpr++; + if ((gpr + 1) < GPR_COUNT) { + *((double*) &gpregs[gpr]) = s->val.d; + gpr += 2; + } +#endif + else { + if ((uint32_t) d & 4) d++; // doubles are 8-byte aligned on stack + *((double*) d) = s->val.d; + d += 2; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.f; // if passed in registers, floats are promoted to doubles +#else + if (gpr < GPR_COUNT) + *((float*) &gpregs[gpr++]) = s->val.f; +#endif + else + *((float*) d++) = s->val.f; + } + else if (!s->IsPtrData() && (s->type == nsXPTType::T_I64 + || s->type == nsXPTType::T_U64)) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + if ((gpr + 1) < GPR_COUNT) { + *((uint64_t*) &gpregs[gpr]) = tempu64; + gpr += 2; + } + else { + if ((uint32_t) d & 4) d++; // longlongs are 8-byte aligned on stack + *((uint64_t*) d) = tempu64; + d += 2; + } + } + else { + if (gpr < GPR_COUNT) + gpregs[gpr++] = tempu32; + else + *d++ = tempu32; + } + + } +} + +extern "C" +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp new file mode 100644 index 0000000000..5c4f8b49fe --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +// The purpose of NS_InvokeByIndex() is to map a platform +// indepenpent call to the platform ABI. To do that, +// NS_InvokeByIndex() has to determine the method to call via vtable +// access. The parameters for the method are read from the +// nsXPTCVariant* and prepared for th native ABI. For the Linux/PPC +// ABI this means that the first 8 integral and floating point +// parameters are passed in registers. + +#include "xptcprivate.h" + +// 8 integral parameters are passed in registers +#define GPR_COUNT 8 + +// 8 floating point parameters are passed in registers, floats are +// promoted to doubles when passed in registers +#define FPR_COUNT 8 + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return uint32_t(((paramCount * 2) + 3) & ~3); +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, + uint32_t paramCount, + nsXPTCVariant* s, + uint32_t* gpregs, + double* fpregs) +{ + uint32_t gpr = 1; // skip one GP reg for 'that' + uint32_t fpr = 0; + uint32_t tempu32; + uint64_t tempu64; + + for(uint32_t i = 0; i < paramCount; i++, s++) { + if(s->IsPtrData()) { + if(s->type == nsXPTType::T_JSVAL) + tempu32 = (uint32_t) &(s->ptr); + else + tempu32 = (uint32_t) s->ptr; + } else { + switch(s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: tempu32 = s->val.i8; break; + case nsXPTType::T_I16: tempu32 = s->val.i16; break; + case nsXPTType::T_I32: tempu32 = s->val.i32; break; + case nsXPTType::T_I64: tempu64 = s->val.i64; break; + case nsXPTType::T_U8: tempu32 = s->val.u8; break; + case nsXPTType::T_U16: tempu32 = s->val.u16; break; + case nsXPTType::T_U32: tempu32 = s->val.u32; break; + case nsXPTType::T_U64: tempu64 = s->val.u64; break; + case nsXPTType::T_BOOL: tempu32 = s->val.b; break; + case nsXPTType::T_CHAR: tempu32 = s->val.c; break; + case nsXPTType::T_WCHAR: tempu32 = s->val.wc; break; + default: tempu32 = (uint32_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.d; + else { + if ((uint32_t) d & 4) d++; // doubles are 8-byte aligned on stack + *((double*) d) = s->val.d; + d += 2; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.f; // if passed in registers, floats are promoted to doubles + else + *((float*) d++) = s->val.f; + } + else if (!s->IsPtrData() && (s->type == nsXPTType::T_I64 + || s->type == nsXPTType::T_U64)) { + if ((gpr + 1) < GPR_COUNT) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + *((uint64_t*) &gpregs[gpr]) = tempu64; + gpr += 2; + } + else { + if ((uint32_t) d & 4) d++; // longlongs are 8-byte aligned on stack + *((uint64_t*) d) = tempu64; + d += 2; + } + } + else { + if (gpr < GPR_COUNT) + gpregs[gpr++] = tempu32; + else + *d++ = tempu32; + } + + } +} + +extern "C" +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp new file mode 100644 index 0000000000..4a434c426b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + /* fprintf(stderr,"invoke_count_words(%d,%p)\n",paramCount, s);*/ + + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s, double *fprData) +{ + uint32_t fpCount = 0; + + /* fprintf(stderr,"invoke_copy_to_stack(%p, %d, %p, %p)\n", d, paramCount, s, fprData);*/ + + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int32_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint32_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; + if (fpCount < 13) + fprData[fpCount++] = s->val.f; + break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; + if (fpCount < 13) + fprData[fpCount++] = s->val.d; + break; + case nsXPTType::T_BOOL : *((uint32_t*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((int32_t*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((uint32_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_riscv64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_riscv64.cpp new file mode 100644 index 0000000000..ddd6692156 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_riscv64.cpp @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +#if defined(__riscv_float_abi_soft) +# error "Not support soft float ABI" +#endif + +#include "xptcprivate.h" + +extern "C" void invoke_copy_to_stack(uint64_t* gpregs, double* fpregs, + uint32_t paramCount, nsXPTCVariant* s, + uint64_t* d) { + static const uint32_t GPR_COUNT = 8; + static const uint32_t FPR_COUNT = 8; + + uint32_t nr_gpr = 1; // skip one GPR register for "this" + uint32_t nr_fpr = 0; + uint64_t value = 0; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsIndirect()) { + value = (uint64_t)&s->val; + } else { + switch (s->type) { + case nsXPTType::T_FLOAT: + break; + case nsXPTType::T_DOUBLE: + break; + case nsXPTType::T_I8: + value = s->val.i8; + break; + case nsXPTType::T_I16: + value = s->val.i16; + break; + case nsXPTType::T_I32: + value = s->val.i32; + break; + case nsXPTType::T_I64: + value = s->val.i64; + break; + case nsXPTType::T_U8: + value = s->val.u8; + break; + case nsXPTType::T_U16: + value = s->val.u16; + break; + case nsXPTType::T_U32: + value = s->val.u32; + break; + case nsXPTType::T_U64: + value = s->val.u64; + break; + case nsXPTType::T_BOOL: + value = s->val.b; + break; + case nsXPTType::T_CHAR: + value = s->val.c; + break; + case nsXPTType::T_WCHAR: + value = s->val.wc; + break; + default: + value = (uint64_t)s->val.p; + break; + } + } + + if (!s->IsIndirect() && s->type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) { + fpregs[nr_fpr++] = s->val.d; + } else { + *((double*)d) = s->val.d; + d++; + } + } else if (!s->IsIndirect() && s->type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) { + // The value in %fa register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + fpregs[nr_fpr++] = s->val.d; + } else { + *((float*)d) = s->val.f; + d++; + } + } else { + if (nr_gpr < GPR_COUNT) { + gpregs[nr_gpr++] = value; + } else { + *d++ = value; + } + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, uint32_t paramCount, + nsXPTCVariant* params) { + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp new file mode 100644 index 0000000000..aa90988a17 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +extern "C" uint64_t +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + /* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. + */ + uint64_t *l_d = d; + nsXPTCVariant *l_s = s; + uint64_t l_paramCount = paramCount; + uint64_t regCount = 0; // return the number of registers to load from the stack + + for(uint64_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + + if (l_s->IsIndirect()) + { + *l_d = (uint64_t) &l_s->val; + continue; + } + switch (l_s->type) + { + case nsXPTType::T_I8 : *((int64_t*)l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int64_t*)l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int64_t*)l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*)l_d) = l_s->val.i64; break; + + case nsXPTType::T_U8 : *((uint64_t*)l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint64_t*)l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint64_t*)l_d) = l_s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)l_d) = l_s->val.u64; break; + + /* in the case of floats, we want to put the bits in to the + 64bit space right justified... floats in the parameter array on + sparcv9 use odd numbered registers.. %f1, %f3, so we have to skip + the space that would be occupied by %f0, %f2, etc. + */ + case nsXPTType::T_FLOAT : *(((float*)l_d) + 1) = l_s->val.f; break; + case nsXPTType::T_DOUBLE: *((double*)l_d) = l_s->val.d; break; + case nsXPTType::T_BOOL : *((int64_t*)l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint64_t*)l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int64_t*)l_d) = l_s->val.wc; break; + + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + + return regCount; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp new file mode 100644 index 0000000000..3ab0812622 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* solaris defines __sparc for workshop compilers and + linux defines __sparc__ */ + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +typedef unsigned nsXPCVariant; + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + // nuts, I know there's a cooler way of doing this, but it's late + // now and it'll probably come to me in the morning. + if (result & 0x3) result += 4 - (result & 0x3); // ensure q-word alignment + return result; +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount; + uint32_t regCount = 0; // return the number of registers to load from the stack + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + if(l_s->IsPtrData()) + { + if(l_s->type == nsXPTType::T_JSVAL) + { + // On SPARC, we need to pass a pointer to HandleValue + *((void**)l_d) = &l_s->ptr; + } else + { + *((void**)l_d) = l_s->ptr; + } + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + if (regCount < 5) regCount++; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + return regCount; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp new file mode 100644 index 0000000000..fb99c303f7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +typedef unsigned nsXPCVariant; + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + // nuts, I know there's a cooler way of doing this, but it's late + // now and it'll probably come to me in the morning. + if (result & 0x3) result += 4 - (result & 0x3); // ensure q-word alignment + return result; +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount; + uint32_t regCount = 0; // return the number of registers to load from the stack + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + if(l_s->IsPtrData()) + { + if(l_s->type == nsXPTType::T_JSVAL) + { + // On SPARC, we need to pass a pointer to HandleValue + *((void**)l_d) = &l_s->ptr; + } else + { + *((void**)l_d) = l_s->ptr; + } + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + if (regCount < 5) regCount++; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + return regCount; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp new file mode 100644 index 0000000000..3ab0812622 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* solaris defines __sparc for workshop compilers and + linux defines __sparc__ */ + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +typedef unsigned nsXPCVariant; + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + // nuts, I know there's a cooler way of doing this, but it's late + // now and it'll probably come to me in the morning. + if (result & 0x3) result += 4 - (result & 0x3); // ensure q-word alignment + return result; +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount; + uint32_t regCount = 0; // return the number of registers to load from the stack + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + if(l_s->IsPtrData()) + { + if(l_s->type == nsXPTType::T_JSVAL) + { + // On SPARC, we need to pass a pointer to HandleValue + *((void**)l_d) = &l_s->ptr; + } else + { + *((void**)l_d) = l_s->ptr; + } + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + if (regCount < 5) regCount++; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + return regCount; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp new file mode 100644 index 0000000000..ae034abf29 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +#include "xptcall.h" +#include "xptinfo.h" + +// 6 integral parameters are passed in registers, but 1 is |this| which isn't +// considered here. +const uint32_t GPR_COUNT = 5; + +// 8 floating point parameters are passed in SSE registers +const uint32_t FPR_COUNT = 8; + +extern "C" void +InvokeCopyToStack(uint64_t * gpregs, double * fpregs, + uint32_t paramCount, nsXPTCVariant * s, + uint64_t* d) +{ + uint32_t nr_gpr = 0u; + uint32_t nr_fpr = 0u; + uint64_t value = 0u; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsIndirect()) + value = (uint64_t) &s->val; + else { + switch (s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: value = s->val.i8; break; + case nsXPTType::T_I16: value = s->val.i16; break; + case nsXPTType::T_I32: value = s->val.i32; break; + case nsXPTType::T_I64: value = s->val.i64; break; + case nsXPTType::T_U8: value = s->val.u8; break; + case nsXPTType::T_U16: value = s->val.u16; break; + case nsXPTType::T_U32: value = s->val.u32; break; + case nsXPTType::T_U64: value = s->val.u64; break; + case nsXPTType::T_BOOL: value = s->val.b; break; + case nsXPTType::T_CHAR: value = s->val.c; break; + case nsXPTType::T_WCHAR: value = s->val.wc; break; + default: value = (uint64_t) s->val.p; break; + } + } + + if (!s->IsIndirect() && s->type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + fpregs[nr_fpr++] = s->val.d; + else { + *((double *)d) = s->val.d; + d++; + } + } + else if (!s->IsIndirect() && s->type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + fpregs[nr_fpr++] = s->val.d; + else { + *((float *)d) = s->val.f; + d++; + } + } + else { + if (nr_gpr < GPR_COUNT) + gpregs[nr_gpr++] = value; + else + *d++ = value; + } + } +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_aarch64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_aarch64.cpp new file mode 100644 index 0000000000..d535557259 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_aarch64.cpp @@ -0,0 +1,238 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xptcprivate.h" + +#ifndef __AARCH64EL__ +#error "Only little endian compatibility was tested" +#endif + +template<typename T> void +get_value_and_advance(T* aOutValue, void*& aStack) { +#ifdef __APPLE__ + const size_t aligned_size = sizeof(T); +#else + const size_t aligned_size = 8; +#endif + // Ensure the pointer is aligned for the type + uintptr_t addr = (reinterpret_cast<uintptr_t>(aStack) + aligned_size - 1) & ~(aligned_size - 1); + memcpy(aOutValue, reinterpret_cast<void*>(addr), sizeof(T)); + aStack = reinterpret_cast<void*>(addr + aligned_size); +} + +/* + * This is for AArch64 ABI + * + * When we're called, the "gp" registers are stored in gprData and + * the "fp" registers are stored in fprData. Each array has 8 regs + * but first reg in gprData is a placeholder for 'self'. + */ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, void* args, + uint64_t *gprData, double *fprData) +{ +#define PARAM_GPR_COUNT 8 +#define PARAM_FPR_COUNT 8 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + uint32_t paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + void* ap = args; + uint32_t next_gpr = 1; // skip first arg which is 'self' + uint32_t next_fpr = 0; + for (uint32_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (next_gpr < PARAM_GPR_COUNT) + next_gpr++; + else { + void* value; + get_value_and_advance(&value, ap); + } + } + + if (param.IsOut() || !type.IsArithmetic()) { + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.p = (void*)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.p, ap); + } + continue; + } + + switch (type) { + case nsXPTType::T_I8: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i8 = (int8_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.i8, ap); + } + break; + + case nsXPTType::T_I16: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i16 = (int16_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.i16, ap); + } + break; + + case nsXPTType::T_I32: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i32 = (int32_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.i32, ap); + } + break; + + case nsXPTType::T_I64: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i64 = (int64_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.i64, ap); + } + break; + + case nsXPTType::T_U8: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u8 = (uint8_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.u8, ap); + } + break; + + case nsXPTType::T_U16: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u16 = (uint16_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.u16, ap); + } + break; + + case nsXPTType::T_U32: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u32 = (uint32_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.u32, ap); + } + break; + + case nsXPTType::T_U64: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u64 = (uint64_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.u64, ap); + } + break; + + case nsXPTType::T_FLOAT: + if (next_fpr < PARAM_FPR_COUNT) { + memcpy(&dp->val.f, &fprData[next_fpr++], sizeof(dp->val.f)); + } else { + get_value_and_advance(&dp->val.f, ap); + } + break; + + case nsXPTType::T_DOUBLE: + if (next_fpr < PARAM_FPR_COUNT) { + memcpy(&dp->val.d, &fprData[next_fpr++], sizeof(dp->val.d)); + } else { + get_value_and_advance(&dp->val.d, ap); + } + break; + + case nsXPTType::T_BOOL: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.b = (bool)(uint8_t)gprData[next_gpr++]; + } else { + uint8_t value; + get_value_and_advance(&value, ap); + dp->val.b = (bool)value; + } + break; + + case nsXPTType::T_CHAR: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.c = (char)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.c, ap); + } + break; + + case nsXPTType::T_WCHAR: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.wc = (wchar_t)gprData[next_gpr++]; + } else { + get_value_and_advance(&dp->val.wc, ap); + } + break; + + default: + NS_ASSERTION(0, "bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#ifdef __APPLE__ +#define GNU(str) +#define UNDERSCORE "__" +#else +#define GNU(str) str +#define UNDERSCORE "_" +#endif + +// Load w17 with the constant 'n' and branch to SharedStub(). +# define STUB_ENTRY(n) \ + __asm__ ( \ + ".text\n\t" \ + ".align 2\n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl " UNDERSCORE "ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + GNU(".hidden _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t") \ + GNU(".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n")\ + "" UNDERSCORE "ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 100 \n\t" \ + ".globl " UNDERSCORE "ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + GNU(".hidden _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t") \ + GNU(".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n")\ + "" UNDERSCORE "ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 1000 \n\t" \ + ".globl " UNDERSCORE "ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + GNU(".hidden _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t") \ + GNU(".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n")\ + UNDERSCORE "ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + "mov w17,#"#n" \n\t" \ + "b SharedStub \n" \ +); + +#define SENTINEL_ENTRY(n) \ + nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ASSERTION(0,"nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_alpha_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_alpha_openbsd.cpp new file mode 100644 index 0000000000..248c658d68 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_alpha_openbsd.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +__asm__("PrepareAndDispatch") ATTRIBUTE_USED; + +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +{ + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + // args[0] to args[NUM_ARG_REGS] hold floating point register values + uint64_t* ap = args + NUM_ARG_REGS; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = (int8_t) *ap; break; + case nsXPTType::T_I16 : dp->val.i16 = (int16_t) *ap; break; + case nsXPTType::T_I32 : dp->val.i32 = (int32_t) *ap; break; + case nsXPTType::T_I64 : dp->val.i64 = (int64_t) *ap; break; + case nsXPTType::T_U8 : dp->val.u8 = (uint8_t) *ap; break; + case nsXPTType::T_U16 : dp->val.u16 = (uint16_t) *ap; break; + case nsXPTType::T_U32 : dp->val.u32 = (uint32_t) *ap; break; + case nsXPTType::T_U64 : dp->val.u64 = (uint64_t) *ap; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // floats passed via registers are stored as doubles + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (uint64_t) args[i]; + dp->val.f = (float) dp->val.d; // convert double to float + } + else + dp->val.u32 = (uint32_t) *ap; + break; + case nsXPTType::T_DOUBLE : + // doubles passed via registers are also stored + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (i < NUM_ARG_REGS) ? args[i] : *ap; + break; + case nsXPTType::T_BOOL : dp->val.b = (bool) *ap; break; + case nsXPTType::T_CHAR : dp->val.c = (char) *ap; break; + case nsXPTType::T_WCHAR : dp->val.wc = (char16_t) *ap; break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +/* + * SharedStub() + * Collects arguments and calls PrepareAndDispatch. The "methodIndex" is + * passed to this function via $1 to preserve the argument registers. + */ +__asm__( + "#### SharedStub ####\n" +".text\n\t" + ".align 5\n\t" + ".ent SharedStub\n" +"SharedStub:\n\t" + ".frame $30,96,$26,0\n\t" + ".mask 0x4000000,-96\n\t" + "ldgp $29,0($27)\n" +"$SharedStub..ng:\n\t" + "subq $30,96,$30\n\t" + "stq $26,0($30)\n\t" + ".prologue 1\n\t" + + /* + * Store arguments passed via registers to the stack. + * Floating point registers are stored as doubles and converted + * to floats in PrepareAndDispatch if necessary. + */ + "stt $f17,16($30)\n\t" /* floating point registers */ + "stt $f18,24($30)\n\t" + "stt $f19,32($30)\n\t" + "stt $f20,40($30)\n\t" + "stt $f21,48($30)\n\t" + "stq $17,56($30)\n\t" /* integer registers */ + "stq $18,64($30)\n\t" + "stq $19,72($30)\n\t" + "stq $20,80($30)\n\t" + "stq $21,88($30)\n\t" + + /* + * Call PrepareAndDispatch function. + */ + "bis $1,$1,$17\n\t" /* pass "methodIndex" */ + "addq $30,16,$18\n\t" /* pass "args" */ + "bsr $26,$PrepareAndDispatch..ng\n\t" + + "ldq $26,0($30)\n\t" + "addq $30,96,$30\n\t" + "ret $31,($26),1\n\t" + ".end SharedStub" + ); + +/* + * nsresult nsXPTCStubBase::Stub##n() + * Sets register $1 to "methodIndex" and jumps to SharedStub. + */ +#define STUB_MANGLED_ENTRY(n, symbol) \ + "#### Stub"#n" ####" "\n\t" \ + ".text" "\n\t" \ + ".align 5" "\n\t" \ + ".globl " symbol "\n\t" \ + ".ent " symbol "\n" \ +symbol ":" "\n\t" \ + ".frame $30,0,$26,0" "\n\t" \ + "ldgp $29,0($27)" "\n" \ +"$" symbol "..ng:" "\n\t" \ + ".prologue 1" "\n\t" \ + "lda $1,"#n "\n\t" \ + "br $31,$SharedStub..ng" "\n\t" \ + ".end " symbol + +#define STUB_ENTRY(n) \ +__asm__( \ + ".if "#n" < 10" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase5Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 100" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase6Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 1000" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase7Stub"#n"Ev") "\n\t" \ + ".else" "\n\t" \ + ".err \"Stub"#n" >= 1000 not yet supported.\"" "\n\t" \ + ".endif" \ + ); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp new file mode 100644 index 0000000000..da855b7535 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp @@ -0,0 +1,226 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if !defined(__arm__) && !(defined(LINUX) || defined(ANDROID) || defined(XP_DARWIN)) +#error "This code is for Linux/iOS ARM only. Please check if it works for you, too.\nDepends strongly on gcc behaviour." +#endif + +#ifdef __ARM_EABI__ +#define DOUBLEWORD_ALIGN(p) ((uint32_t *)((((uint32_t)(p)) + 7) & 0xfffffff8)) +#else +#define DOUBLEWORD_ALIGN(p) (p) +#endif + +// Apple's iOS toolchain is lame. +#ifdef __APPLE__ +#define GNU(str) +#define APPLE(str) str +#define UNDERSCORE "__" +#else +#define GNU(str) str +#define APPLE(str) +#define UNDERSCORE "_" +#endif + +#ifdef __thumb__ +#define THUMB_FUNC ".thumb_func\n" +#else +#define THUMB_FUNC +#endif + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : ap = DOUBLEWORD_ALIGN(ap); + dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : ap = DOUBLEWORD_ALIGN(ap); + dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : ap = DOUBLEWORD_ALIGN(ap); + dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +/* + * This is our shared stub. + * + * r0 = Self. + * + * The Rules: + * We pass an (undefined) number of arguments into this function. + * The first 3 C++ arguments are in r1 - r3, the rest are built + * by the calling function on the stack. + * + * We are allowed to corrupt r0 - r3, ip, and lr. + * + * Other Info: + * We pass the stub number in using `ip'. + * + * Implementation: + * - We save r1 to r3 inclusive onto the stack, which will be + * immediately below the caller saved arguments. + * - setup r2 (PrepareAndDispatch's args pointer) to point at + * the base of all these arguments + * - Save LR (for the return address) + * - Set r1 (PrepareAndDispatch's methodindex argument) from ip + * - r0 is passed through (self) + * - Call PrepareAndDispatch + * - When the call returns, we return by loading the PC off the + * stack, and undoing the stack (one instruction)! + * + */ +__asm__ ("\n" + GNU(".text\n") + APPLE(".section __TEXT,__text\n") + THUMB_FUNC + ".align 2\n" + "SharedStub:\n" + GNU(".fnstart\n") + GNU(".cfi_startproc\n") + "stmfd sp!, {r1, r2, r3}\n" + GNU(".save {r1, r2, r3}\n") + GNU(".cfi_def_cfa_offset 12\n") + GNU(".cfi_offset r3, -4\n") + GNU(".cfi_offset r2, -8\n") + GNU(".cfi_offset r1, -12\n") + "mov r2, sp\n" + "str lr, [sp, #-4]!\n" + GNU(".save {lr}\n") + GNU(".cfi_def_cfa_offset 16\n") + GNU(".cfi_offset lr, -16\n") + "mov r1, ip\n" + "bl PrepareAndDispatch\n" + "ldr pc, [sp], #16\n" + GNU(".cfi_endproc\n") + GNU(".fnend")); + +/* + * Create sets of stubs to call the SharedStub. + * We don't touch the stack here, nor any registers, other than IP. + * IP is defined to be corruptable by a called function, so we are + * safe to use it. + * + * This will work with or without optimisation. + */ + +/* + * Note : As G++3 ABI contains the length of the functionname in the + * mangled name, it is difficult to get a generic assembler mechanism like + * in the G++ 2.95 case. + * Create names would be like : + * _ZN14nsXPTCStubBase5Stub9Ev + * _ZN14nsXPTCStubBase6Stub13Ev + * _ZN14nsXPTCStubBase7Stub144Ev + * Use the assembler directives to get the names right... + */ + +#define STUB_ENTRY(n) \ + __asm__( \ + GNU(".section \".text\"\n") \ + APPLE(".section __TEXT,__text\n") \ +" .align 2\n" \ +" .if ("#n" - 10) < 0\n" \ +" .globl " UNDERSCORE "ZN14nsXPTCStubBase5Stub"#n"Ev\n" \ + THUMB_FUNC \ + GNU(".type _ZN14nsXPTCStubBase5Stub"#n"Ev,#function\n") \ +UNDERSCORE "ZN14nsXPTCStubBase5Stub"#n"Ev:\n" \ +" .else\n" \ +" .if ("#n" - 100) < 0\n" \ +" .globl " UNDERSCORE "ZN14nsXPTCStubBase6Stub"#n"Ev\n" \ + THUMB_FUNC \ + GNU(".type _ZN14nsXPTCStubBase6Stub"#n"Ev,#function\n") \ +UNDERSCORE "ZN14nsXPTCStubBase6Stub"#n"Ev:\n" \ +" .else\n" \ +" .if ("#n" - 1000) < 0\n" \ +" .globl " UNDERSCORE "ZN14nsXPTCStubBase7Stub"#n"Ev\n" \ + THUMB_FUNC \ + GNU(".type _ZN14nsXPTCStubBase7Stub"#n"Ev,#function\n") \ +UNDERSCORE "ZN14nsXPTCStubBase7Stub"#n"Ev:\n" \ +" .else\n" \ +" .err \"stub number "#n"> 1000 not yet supported\"\n" \ +" .endif\n" \ +" .endif\n" \ +" .endif\n" \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); + +#if 0 +/* + * This part is left in as comment : this is how the method definition + * should look like. + */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n () \ +{ \ + __asm__ ( \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); \ + return 0; /* avoid warnings */ \ +} +#endif + + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_netbsd.cpp new file mode 100644 index 0000000000..be7a36e948 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_netbsd.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +/* + * These stubs move just move the values passed in registers onto the stack, + * so they are contiguous with values passed on the stack, and then calls + * PrepareAndDispatch() to do the dirty work. + */ + +#define STUB_ENTRY(n) \ +__asm__( \ + ".global _Stub"#n"__14nsXPTCStubBase\n\t" \ +"_Stub"#n"__14nsXPTCStubBase:\n\t" \ + "stmfd sp!, {r1, r2, r3} \n\t" \ + "mov ip, sp \n\t" \ + "stmfd sp!, {fp, ip, lr, pc} \n\t" \ + "sub fp, ip, #4 \n\t" \ + "mov r1, #"#n" \n\t" /* = methodIndex */ \ + "add r2, sp, #16 \n\t" \ + "bl _PrepareAndDispatch__FP14nsXPTCStubBaseUiPUi \n\t" \ + "ldmea fp, {fp, sp, lr} \n\t" \ + "add sp, sp, #12 \n\t" \ + "mov pc, lr \n\t" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_openbsd.cpp new file mode 100644 index 0000000000..c0fb0df7d4 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_openbsd.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#ifdef __GNUC__ +/* This tells gcc3.4+ not to optimize away symbols. + * @see http://gcc.gnu.org/gcc-3.4/changes.html + */ +#define DONT_DROP_OR_WARN __attribute__((used)) +#else +/* This tells older gccs not to warn about unused vairables. + * @see http://docs.freebsd.org/info/gcc/gcc.info.Variable_Attributes.html + */ +#define DONT_DROP_OR_WARN __attribute__((unused)) +#endif + +/* Specify explicitly a symbol for this function, don't try to guess the c++ mangled symbol. */ +static nsresult PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) asm("_PrepareAndDispatch") +DONT_DROP_OR_WARN; + +static nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +/* + * This is our shared stub. + * + * r0 = Self. + * + * The Rules: + * We pass an (undefined) number of arguments into this function. + * The first 3 C++ arguments are in r1 - r3, the rest are built + * by the calling function on the stack. + * + * We are allowed to corrupt r0 - r3, ip, and lr. + * + * Other Info: + * We pass the stub number in using `ip'. + * + * Implementation: + * - We save r1 to r3 inclusive onto the stack, which will be + * immediately below the caller saved arguments. + * - setup r2 (PrepareAndDispatch's args pointer) to point at + * the base of all these arguments + * - Save LR (for the return address) + * - Set r1 (PrepareAndDispatch's methodindex argument) from ip + * - r0 is passed through (self) + * - Call PrepareAndDispatch + * - When the call returns, we return by loading the PC off the + * stack, and undoing the stack (one instruction)! + * + */ +__asm__ ("\n\ + .text \n\ + .align 2 \n\ +SharedStub: \n\ + stmfd sp!, {r1, r2, r3} \n\ + mov r2, sp \n\ + str lr, [sp, #-4]! \n\ + mov r1, ip \n\ + bl _PrepareAndDispatch \n\ + ldr pc, [sp], #16"); + +/* + * Create sets of stubs to call the SharedStub. + * We don't touch the stack here, nor any registers, other than IP. + * IP is defined to be corruptable by a called function, so we are + * safe to use it. + * + * This will work with or without optimisation. + */ + +/* + * Note : As G++3 ABI contains the length of the functionname in the + * mangled name, it is difficult to get a generic assembler mechanism like + * in the G++ 2.95 case. + * Create names would be like : + * _ZN14nsXPTCStubBase5Stub9Ev + * _ZN14nsXPTCStubBase6Stub13Ev + * _ZN14nsXPTCStubBase7Stub144Ev + * Use the assembler directives to get the names right... + */ + +#define STUB_ENTRY(n) \ + __asm__( \ + ".section \".text\"\n" \ +" .align 2\n" \ +" .iflt ("#n" - 10)\n" \ +" .globl _ZN14nsXPTCStubBase5Stub"#n"Ev\n" \ +" .type _ZN14nsXPTCStubBase5Stub"#n"Ev,#function\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev:\n" \ +" .else\n" \ +" .iflt ("#n" - 100)\n" \ +" .globl _ZN14nsXPTCStubBase6Stub"#n"Ev\n" \ +" .type _ZN14nsXPTCStubBase6Stub"#n"Ev,#function\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev:\n" \ +" .else\n" \ +" .iflt ("#n" - 1000)\n" \ +" .globl _ZN14nsXPTCStubBase7Stub"#n"Ev\n" \ +" .type _ZN14nsXPTCStubBase7Stub"#n"Ev,#function\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev:\n" \ +" .else\n" \ +" .err \"stub number "#n"> 1000 not yet supported\"\n" \ +" .endif\n" \ +" .endif\n" \ +" .endif\n" \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); + +#if 0 +/* + * This part is left in as comment : this is how the method definition + * should look like. + */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n () \ +{ \ + __asm__ ( \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); \ + return 0; /* avoid warnings */ \ +} +#endif + + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_aarch64.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_aarch64.S new file mode 100644 index 0000000000..f2394aa316 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_aarch64.S @@ -0,0 +1,62 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifdef __APPLE__ +#define SYM(x) _ ## x +#else +#define SYM(x) x +#endif + + .set NGPREGS,8 + .set NFPREGS,8 + + .text + .align 2 + .globl SharedStub +#ifndef __APPLE__ + .hidden SharedStub + .type SharedStub,@function +#endif +SharedStub: + .cfi_startproc + stp x29, x30, [sp,#-16]! + .cfi_adjust_cfa_offset 16 + .cfi_rel_offset x29, 0 + .cfi_rel_offset x30, 8 + mov x29, sp + .cfi_def_cfa_register x29 + + sub sp, sp, #8*(NGPREGS+NFPREGS) + stp x0, x1, [sp, #64+(0*8)] + stp x2, x3, [sp, #64+(2*8)] + stp x4, x5, [sp, #64+(4*8)] + stp x6, x7, [sp, #64+(6*8)] + stp d0, d1, [sp, #(0*8)] + stp d2, d3, [sp, #(2*8)] + stp d4, d5, [sp, #(4*8)] + stp d6, d7, [sp, #(6*8)] + + # methodIndex passed from stub + mov w1, w17 + + add x2, sp, #16+(8*(NGPREGS+NFPREGS)) + add x3, sp, #8*NFPREGS + add x4, sp, #0 + + bl SYM(PrepareAndDispatch) + + add sp, sp, #8*(NGPREGS+NFPREGS) + .cfi_def_cfa_register sp + ldp x29, x30, [sp],#16 + .cfi_adjust_cfa_offset -16 + .cfi_restore x29 + .cfi_restore x30 + ret + .cfi_endproc + +#ifndef __APPLE__ + .size SharedStub, . - SharedStub + + .section .note.GNU-stack, "", @progbits +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s new file mode 100644 index 0000000000..720dd6cc71 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s @@ -0,0 +1,123 @@ + +// Select C numeric constant + .radix C + .psr abi32 + .psr msb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'SharedStub' + .proc SharedStub +// manual bundling + .explicit + + .global PrepareAndDispatch +// .exclass PrepareAndDispatch, @fullyvisible + .type PrepareAndDispatch,@function + +SharedStub:: +// 10 arguments, first 8 are the input arguments of previous +// function call. The 9th one is methodIndex and the 10th is the +// pointer to the remaining input arguments. The last two arguments +// are passed in memory. + .prologue + .save ar.pfs , r41 +// allocate 8 input args, 4 local args, and 5 output args + alloc r41 = ar.pfs, 8, 4, 5, 0 // M + .save rp, r40 + mov r40 = rp // I + addp4 out4 = 28, sp ;; // I + + .save ar.unat, r42 + mov r42 = ar.unat // M + .fframe 144 + add sp = -144, sp // A +// unwind table already knows gp, don't need to specify anything + add r43 = 0, gp ;; // A + +// We have possible 8 integer registers and 8 float registers that could +// be arguments. We also have a stack region from the previous +// stack frame that may hold some stack arguments. +// We need to write the integer registers to a memory region, write +// the float registers to a memory region (making sure we don't step +// on NAT while touching the registers). We also mark the memory +// address of the stack arguments. +// We then call PrepareAndDispatch() specifying the three memory +// region pointers. + + + .body + add out0 = 0, in0 // A move self ptr +// 144 bytes = 16 byte stack header + 64 byte int space + 64 byte float space +// methodIndex is at 144 + 16 bytes away from current sp +// (current frame + previous frame header) + ld4 out4 = [out4] // A restarg address + add r11 = 160, sp ;; // A address of methodIndex + + ld8 out1 = [r11] // M load methodIndex +// sp + 16 is the start of intargs + add out2 = 16, sp // A address of intargs +// the intargs take up 64 bytes, so sp + 16 + 64 is the start of floatargs + add out3 = 80, sp ;; // A address of floatargs + + add r11 = 0, out2 ;; // A + st8.spill [r11] = in1, 8 // M + add r10 = 0, out3 ;; // A + + st8.spill [r11] = in2, 8 ;; // M + st8.spill [r11] = in3, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in4, 8 ;; // M + st8.spill [r11] = in5, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in6, 8 ;; // M + st8.spill [r11] = in7 // M + fclass.nm p14,p15 = f8,@nat ;; // F + +(p14) stfd [r10] = f8, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 = f9,@nat ;; // F + +(p12) stfd [r10] = f9, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f10,@nat ;; // F + +(p14) stfd [r10] = f10, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f11,@nat ;; // F + +(p12) stfd [r10] = f11, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f12,@nat ;; // F + +(p14) stfd [r10] = f12, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f13,@nat ;; // F + +(p12) stfd [r10] = f13, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f14,@nat ;; // F + +(p14) stfd [r10] = f14, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f15,@nat ;; // F + +(p12) stfd [r10] = f15, 8 // M +(p13) add r10 = 8, r10 // A + +// branch to PrepareAndDispatch + br.call.dptk.few rp = PrepareAndDispatch ;; // B + +// epilog + mov ar.unat = r42 // M + mov ar.pfs = r41 // I + mov rp = r40 ;; // I + + add gp = 0, r43 // A + add sp = 144, sp // A + br.ret.dptk.few rp ;; // B + + .endp + + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s new file mode 100644 index 0000000000..0b5816f1be --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s @@ -0,0 +1,124 @@ + +// Select C numeric constant + .radix C + .psr abi64 + .psr lsb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'SharedStub' + .proc SharedStub +// manual bundling + .explicit + + .global PrepareAndDispatch +// .exclass PrepareAndDispatch, @fullyvisible + .type PrepareAndDispatch,@function + +SharedStub:: +// 10 arguments, first 8 are the input arguments of previous +// function call. The 9th one is methodIndex and the 10th is the +// pointer to the remaining input arguments. The last two arguments +// are passed in memory. + .prologue + .save ar.pfs , r41 +// allocate 8 input args, 4 local args, and 5 output args + alloc r41 = ar.pfs, 8, 4, 5, 0 // M + .save rp, r40 + mov r40 = rp // I + add out4 = 24, sp ;; // I + + .save ar.unat, r42 + mov r42 = ar.unat // M + .fframe 144 + add sp = -144, sp // A +// unwind table already knows gp, don't need to specify anything + add r43 = 0, gp ;; // A + +// We have possible 8 integer registers and 8 float registers that could +// be arguments. We also have a stack region from the previous +// stack frame that may hold some stack arguments. +// We need to write the integer registers to a memory region, write +// the float registers to a memory region (making sure we don't step +// on NAT while touching the registers). We also mark the memory +// address of the stack arguments. +// We then call PrepareAndDispatch() specifying the three memory +// region pointers. + + + .body + add out0 = 0, in0 // A move self ptr +// 144 bytes = 16 byte stack header + 64 byte int space + 64 byte float space +// methodIndex is at 144 + 16 bytes away from current sp +// (current frame + previous frame header) + ld8 out4 = [out4] // M restarg address + add r11 = 160, sp ;; // A address of methodIndex + + ld8 out1 = [r11] // M load methodIndex +// sp + 16 is the start of intargs + add out2 = 16, sp // A address of intargs +// the intargs take up 64 bytes, so sp + 16 + 64 is the start of floatargs + add out3 = 80, sp ;; // A address of floatargs + + add r11 = 0, out2 ;; // A + st8.spill [r11] = in1, 8 // M + add r10 = 0, out3 ;; // A + + st8.spill [r11] = in2, 8 ;; // M + st8.spill [r11] = in3, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in4, 8 ;; // M + st8.spill [r11] = in5, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in6, 8 ;; // M + st8.spill [r11] = in7 // M + fclass.nm p14,p15 = f8,@nat ;; // F + +(p14) stfd [r10] = f8, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 = f9,@nat ;; // F + +(p12) stfd [r10] = f9, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f10,@nat ;; // F + +(p14) stfd [r10] = f10, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f11,@nat ;; // F + +(p12) stfd [r10] = f11, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f12,@nat ;; // F + +(p14) stfd [r10] = f12, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f13,@nat ;; // F + +(p12) stfd [r10] = f13, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f14,@nat ;; // F + +(p14) stfd [r10] = f14, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f15,@nat ;; // F + +(p12) stfd [r10] = f15, 8 // M +(p13) add r10 = 8, r10 // A + +// branch to PrepareAndDispatch + br.call.dptk.few rp = PrepareAndDispatch ;; // B + +// epilog + mov ar.unat = r42 // M + mov ar.pfs = r41 // I + mov rp = r40 ;; // I + + add gp = 0, r43 // A + add sp = 144, sp // A + br.ret.dptk.few rp ;; // B + + .endp + +/* Magic indicating no need for an executable stack */ +.section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_loongarch64.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_loongarch64.S new file mode 100644 index 0000000000..ae4e0cf73f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_loongarch64.S @@ -0,0 +1,52 @@ +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + .set NGPRGES, 8 + .set NFPREGS, 8 + + .text + .globl SharedStub + .hidden SharedStub + .type SharedStub,@function + +SharedStub: + .cfi_startproc + move $t0, $sp + addi.d $sp, $sp, -8*(NGPRGES+NFPREGS)-16 + .cfi_def_cfa_offset 8*(NGPRGES+NFPREGS)+16 + st.d $a0, $sp, 0 + st.d $a1, $sp, 8 + st.d $a2, $sp, 16 + st.d $a3, $sp, 24 + st.d $a4, $sp, 32 + st.d $a5, $sp, 40 + st.d $a6, $sp, 48 + st.d $a7, $sp, 56 + fst.d $fa0, $sp, 64 + fst.d $fa1, $sp, 72 + fst.d $fa2, $sp, 80 + fst.d $fa3, $sp, 88 + fst.d $fa4, $sp, 96 + fst.d $fa5, $sp, 104 + fst.d $fa6, $sp, 112 + fst.d $fa7, $sp, 120 + st.d $ra, $sp, 136 + .cfi_offset 1, 136 + + /* methodIndex is passed from stub */ + move $a1, $t6 + move $a2, $t0 + move $a3, $sp + addi.d $a4, $sp, 8*NGPRGES + + bl PrepareAndDispatch + + ld.d $ra, $sp, 136 + .cfi_restore 1 + addi.d $sp, $sp, 8*(NGPRGES+NFPREGS)+16 + .cfi_def_cfa_offset -8*(NGPRGES+NFPREGS)-16 + jirl $zero, $ra, 0 + .cfi_endproc + + .size SharedStub, .-SharedStub + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S new file mode 100644 index 0000000000..d17301634e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S @@ -0,0 +1,116 @@ +/* -*- Mode: asm; 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/. */ + +/* This code is for MIPS using the O32 ABI. */ + +#ifdef ANDROID +#include <asm/regdef.h> +#include <asm/asm.h> +#include <machine/asm.h> +#else +#include <sys/regdef.h> +#include <sys/asm.h> +#endif + +# NARGSAVE is the argument space in the callers frame, including extra +# 'shadowed' space for the argument registers. The minimum of 4 +# argument slots is sometimes predefined in the header files. +#ifndef NARGSAVE +#define NARGSAVE 4 +#endif + +#define LOCALSZ 2 /* gp, ra */ +#define FRAMESZ ((((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK) + +#define RAOFF (FRAMESZ - (1*SZREG)) +#define GPOFF (FRAMESZ - (2*SZREG)) + +#define A0OFF (FRAMESZ + (0*SZREG)) +#define A1OFF (FRAMESZ + (1*SZREG)) +#define A2OFF (FRAMESZ + (2*SZREG)) +#define A3OFF (FRAMESZ + (3*SZREG)) + + .text + +#define STUB_ENTRY(x) \ + .if x < 10; \ + .globl _ZN14nsXPTCStubBase5Stub ##x ##Ev; \ + .type _ZN14nsXPTCStubBase5Stub ##x ##Ev,@function; \ + .aent _ZN14nsXPTCStubBase5Stub ##x ##Ev,0; \ +_ZN14nsXPTCStubBase5Stub ##x ##Ev:; \ + SETUP_GP; \ + li t0,x; \ + b sharedstub; \ + .elseif x < 100; \ + .globl _ZN14nsXPTCStubBase6Stub ##x ##Ev; \ + .type _ZN14nsXPTCStubBase6Stub ##x ##Ev,@function; \ + .aent _ZN14nsXPTCStubBase6Stub ##x ##Ev,0; \ +_ZN14nsXPTCStubBase6Stub ##x ##Ev:; \ + SETUP_GP; \ + li t0,x; \ + b sharedstub; \ + .elseif x < 1000; \ + .globl _ZN14nsXPTCStubBase7Stub ##x ##Ev; \ + .type _ZN14nsXPTCStubBase7Stub ##x ##Ev,@function; \ + .aent _ZN14nsXPTCStubBase7Stub ##x ##Ev,0; \ +_ZN14nsXPTCStubBase7Stub ##x ##Ev:; \ + SETUP_GP; \ + li t0,x; \ + b sharedstub; \ + .else; \ + .err; \ + .endif + +# SENTINEL_ENTRY is handled in the cpp file. +#define SENTINEL_ENTRY(x) + +# +# open a dummy frame for the function entries +# + .align 2 + .type dummy,@function + .ent dummy, 0 + .frame sp, FRAMESZ, ra +dummy: + SETUP_GP + +#include "xptcstubsdef.inc" + +sharedstub: + subu sp, FRAMESZ + + # specify the save register mask for gp, ra, a0-a3 + .mask 0x900000F0, RAOFF-FRAMESZ + + sw ra, RAOFF(sp) + SAVE_GP(GPOFF) + + # Micro-optimization: a0 is already loaded, and its slot gets + # ignored by PrepareAndDispatch, so no need to save it here. + # sw a0, A0OFF(sp) + sw a1, A1OFF(sp) + sw a2, A2OFF(sp) + sw a3, A3OFF(sp) + + la t9, PrepareAndDispatch + + # t0 is methodIndex + move a1, t0 + # have a2 point to the begin of the argument space on stack + addiu a2, sp, FRAMESZ + + # PrepareAndDispatch(that, methodIndex, args) + jalr t9 + + # Micro-optimization: Using jalr explicitly has the side-effect + # of not triggering .cprestore. This is ok because we have no + # gp reference below this point. It also allows better + # instruction sscheduling. + # lw gp, GPOFF(fp) + + lw ra, RAOFF(sp) + addiu sp, FRAMESZ + j ra + END(dummy) diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.s.m4 new file mode 100644 index 0000000000..33c7b1492c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.s.m4 @@ -0,0 +1,75 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 code is for MIPS using the O32 ABI. */ + +#include <sys/regdef.h> +#include <sys/asm.h> + + .text + .globl PrepareAndDispatch + +NARGSAVE=4 # extra space for the callee to use. gccism + # we can put our a0-a3 in our callers space. +LOCALSZ=2 # gp, ra +FRAMESZ=(((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK + +define(STUB_NAME, `Stub'$1`__14nsXPTCStubBase') + +define(STUB_ENTRY, +` .globl 'STUB_NAME($1)` + .align 2 + .type 'STUB_NAME($1)`,@function + .ent 'STUB_NAME($1)`, 0 +'STUB_NAME($1)`: + .frame sp, FRAMESZ, ra + .set noreorder + .cpload t9 + .set reorder + subu sp, FRAMESZ + .cprestore 16 + li t0, '$1` + b sharedstub +.end 'STUB_NAME($1)` + +') + +define(SENTINEL_ENTRY, `') + +include(xptcstubsdef.inc) + + .globl sharedstub + .ent sharedstub +sharedstub: + + REG_S ra, 20(sp) + + REG_S a0, 24(sp) + REG_S a1, 28(sp) + REG_S a2, 32(sp) + REG_S a3, 36(sp) + + # t0 is methodIndex + move a1, t0 + + # put the start of a1, a2, a3, and stack + move a2, sp + addi a2, 24 # have a2 point to sp + 24 (where a0 is) + + # PrepareAndDispatch(that, methodIndex, args) + # a0 a1 a2 + # + jal PrepareAndDispatch + + REG_L ra, 20(sp) + REG_L a1, 28(sp) + REG_L a2, 32(sp) + + addu sp, FRAMESZ + j ra + +.end sharedstub diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips64.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips64.S new file mode 100644 index 0000000000..11d851536e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips64.S @@ -0,0 +1,111 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <sys/regdef.h> +#include <sys/asm.h> + +LOCALSZ=16 +FRAMESZ=(((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK + +A1OFF=FRAMESZ-(9*SZREG) +A2OFF=FRAMESZ-(8*SZREG) +A3OFF=FRAMESZ-(7*SZREG) +A4OFF=FRAMESZ-(6*SZREG) +A5OFF=FRAMESZ-(5*SZREG) +A6OFF=FRAMESZ-(4*SZREG) +A7OFF=FRAMESZ-(3*SZREG) +GPOFF=FRAMESZ-(2*SZREG) +RAOFF=FRAMESZ-(1*SZREG) + +F13OFF=FRAMESZ-(16*SZREG) +F14OFF=FRAMESZ-(15*SZREG) +F15OFF=FRAMESZ-(14*SZREG) +F16OFF=FRAMESZ-(13*SZREG) +F17OFF=FRAMESZ-(12*SZREG) +F18OFF=FRAMESZ-(11*SZREG) +F19OFF=FRAMESZ-(10*SZREG) + +#define SENTINEL_ENTRY(n) /* defined in cpp file, not here */ + +#define STUB_ENTRY(x) \ + .if x < 10; \ + MAKE_STUB(x, _ZN14nsXPTCStubBase5Stub ##x ##Ev); \ + .elseif x < 100; \ + MAKE_STUB(x, _ZN14nsXPTCStubBase6Stub ##x ##Ev); \ + .elseif x < 1000; \ + MAKE_STUB(x, _ZN14nsXPTCStubBase7Stub ##x ##Ev); \ + .else; \ + .err; \ + .endif + +#define MAKE_STUB(x, name) \ + .globl name; \ + .type name,@function; \ + .aent name,0; \ +name:; \ + PTR_SUBU sp,FRAMESZ; \ + SETUP_GP64(GPOFF, name); \ + li t0,x; \ + b sharedstub; \ + +# +# open a dummy frame for the function entries +# + .text + .align 2 + .type dummy,@function + .ent dummy, 0 +dummy: + .frame sp, FRAMESZ, ra + .mask 0x90000FF0, RAOFF-FRAMESZ + .fmask 0x000FF000, F19OFF-FRAMESZ + +#include "xptcstubsdef.inc" + +sharedstub: + + REG_S a1, A1OFF(sp) + REG_S a2, A2OFF(sp) + REG_S a3, A3OFF(sp) + REG_S a4, A4OFF(sp) + REG_S a5, A5OFF(sp) + REG_S a6, A6OFF(sp) + REG_S a7, A7OFF(sp) + REG_S ra, RAOFF(sp) + + s.d $f13, F13OFF(sp) + s.d $f14, F14OFF(sp) + s.d $f15, F15OFF(sp) + s.d $f16, F16OFF(sp) + s.d $f17, F17OFF(sp) + s.d $f18, F18OFF(sp) + s.d $f19, F19OFF(sp) + + # t0 is methodIndex + move a1, t0 + + # a2 is stack address where extra function params + # are stored that do not fit in registers + move a2, sp + PTR_ADDI a2, FRAMESZ + + # a3 is stack address of a1..a7 + move a3, sp + PTR_ADDI a3, A1OFF + + # a4 is stack address of f13..f19 + move a4, sp + PTR_ADDI a4, F13OFF + + # PrepareAndDispatch(that, methodIndex, args, gprArgs, fpArgs) + # a0 a1 a2 a3 a4 + # + jal PrepareAndDispatch + + REG_L ra, RAOFF(sp) + RESTORE_GP64 + + PTR_ADDU sp, FRAMESZ + j ra + END(dummy) diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s new file mode 100644 index 0000000000..9e86848fcf --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s @@ -0,0 +1,68 @@ + .LEVEL 1.1 + +curframesz .EQU 128 +; SharedStub has stack size of 128 bytes + +lastframesz .EQU 64 +; the StubN C++ function has a small stack size of 64 bytes + + .SPACE $TEXT$,SORT=8 + .SUBSPA $CODE$,QUAD=0,ALIGN=4,ACCESS=0x2c,CODE_ONLY,SORT=24 +SharedStub + .PROC + .CALLINFO CALLER,FRAME=80,SAVE_RP,ARGS_SAVED + + .ENTRY + STW %rp,-20(%sp) + LDO 128(%sp),%sp + + STW %r19,-32(%r30) + STW %r26,-36-curframesz(%r30) ; save arg0 in previous frame + + LDO -80(%r30),%r28 + FSTD,MA %fr5,8(%r28) ; save darg0 + FSTD,MA %fr7,8(%r28) ; save darg1 + FSTW,MA %fr4L,4(%r28) ; save farg0 + FSTW,MA %fr5L,4(%r28) ; save farg1 + FSTW,MA %fr6L,4(%r28) ; save farg2 + FSTW,MA %fr7L,4(%r28) ; save farg3 + + ; Former value of register 26 is already properly saved by StubN, + ; but register 25-23 are not because of the arguments mismatch + STW %r25,-40-curframesz-lastframesz(%r30) ; save r25 + STW %r24,-44-curframesz-lastframesz(%r30) ; save r24 + STW %r23,-48-curframesz-lastframesz(%r30) ; save r23 + COPY %r26,%r25 ; method index is arg1 + LDW -36-curframesz-lastframesz(%r30),%r26 ; self is arg0 + LDO -40-curframesz-lastframesz(%r30),%r24 ; normal args is arg2 + LDO -80(%r30),%r23 ; floating args is arg3 + + BL .+8,%r2 + ADDIL L'PrepareAndDispatch-$PIC_pcrel$0+4,%r2 + LDO R'PrepareAndDispatch-$PIC_pcrel$1+8(%r1),%r1 +$PIC_pcrel$0 + LDSID (%r1),%r31 +$PIC_pcrel$1 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR +;in=23-26;out=28; + BLE 0(%sr0,%r1) + COPY %r31,%r2 + + LDW -32(%r30),%r19 + + LDW -148(%sp),%rp + BVE (%rp) + .EXIT + LDO -128(%sp),%sp + + + .PROCEND ;in=26;out=28; + + .ALIGN 8 + .SPACE $TEXT$ + .SUBSPA $CODE$ + .IMPORT PrepareAndDispatch,CODE + .EXPORT SharedStub,ENTRY,PRIV_LEV=3,ARGW0=GR,RTNVAL=GR,LONG_RETURN + .END + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_parisc_linux.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_parisc_linux.s new file mode 100644 index 0000000000..b45d7763be --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_parisc_linux.s @@ -0,0 +1,73 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .LEVEL 1.1 + .TEXT + .ALIGN 4 + +curframesz: + .EQU 128 + + +; SharedStub has stack size of 128 bytes + +lastframesz: + .EQU 64 + +; the StubN C++ function has a small stack size of 64 bytes + + +.globl SharedStub + .type SharedStub, @function + +SharedStub: + .PROC + .CALLINFO CALLER,FRAME=80,SAVE_RP + + .ENTRY + STW %rp,-20(%sp) + LDO 128(%sp),%sp + + STW %r19,-32(%r30) + STW %r26,-36-curframesz(%r30) ; save arg0 in previous frame + + LDO -80(%r30),%r28 + FSTD,MA %fr5,8(%r28) ; save darg0 + FSTD,MA %fr7,8(%r28) ; save darg1 + FSTW,MA %fr4L,4(%r28) ; save farg0 + FSTW,MA %fr5L,4(%r28) ; save farg1 + FSTW,MA %fr6L,4(%r28) ; save farg2 + FSTW,MA %fr7L,4(%r28) ; save farg3 + + ; Former value of register 26 is already properly saved by StubN, + ; but register 25-23 are not because of the argument mismatch + STW %r25,-40-curframesz-lastframesz(%r30) ; save r25 + STW %r24,-44-curframesz-lastframesz(%r30) ; save r24 + STW %r23,-48-curframesz-lastframesz(%r30) ; save r23 + COPY %r26,%r25 ; method index is arg1 + LDW -36-curframesz-lastframesz(%r30),%r26 ; self is arg0 + LDO -40-curframesz-lastframesz(%r30),%r24 ; normal args is arg2 + LDO -80(%r30),%r23 ; floating args is arg3 + + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR ;in=23-26;out=28; + BL PrepareAndDispatch, %r31 + COPY %r31,%r2 + + LDW -32(%r30),%r19 + + LDW -148(%sp),%rp + LDO -128(%sp),%sp + + + BV,N (%rp) + NOP + NOP + + .EXIT + .PROCEND ;in=26;out=28; + + .SIZE SharedStub, .-SharedStub diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc64_linux.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc64_linux.S new file mode 100644 index 0000000000..0b884e1fdb --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc64_linux.S @@ -0,0 +1,112 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set r1,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + +#if _CALL_ELF == 2 +#define STACK_PARAMS 96 +#else +#define STACK_PARAMS 112 +#endif + +#if _CALL_ELF == 2 + .section ".text" + .type SharedStub,@function + .globl SharedStub + # Make the symbol hidden so that the branch from the stub does + # not go via a PLT. This is not only better for performance, + # but may be necessary to avoid linker errors since there is + # no place to restore the TOC register in a sibling call. + .hidden SharedStub + .align 2 +SharedStub: +0: addis 2,12,(.TOC.-0b)@ha + addi 2,2,(.TOC.-0b)@l + .localentry SharedStub,.-SharedStub +#else + .section ".text" + .align 2 + .globl SharedStub + # Make the symbol hidden so that the branch from the stub does + # not go via a PLT. This is not only better for performance, + # but may be necessary to avoid linker errors since there is + # no place to restore the TOC register in a sibling call. + .hidden SharedStub + .section ".opd","aw" + .align 3 + +SharedStub: + .quad .SharedStub,.TOC.@tocbase + .previous + .type SharedStub,@function + +.SharedStub: +#endif + mflr r0 + + std r4, -56(r1) # Save all GPRS + std r5, -48(r1) + std r6, -40(r1) + std r7, -32(r1) + std r8, -24(r1) + std r9, -16(r1) + std r10, -8(r1) + + stfd f13, -64(r1) # ... and FPRS + stfd f12, -72(r1) + stfd f11, -80(r1) + stfd f10, -88(r1) + stfd f9, -96(r1) + stfd f8, -104(r1) + stfd f7, -112(r1) + stfd f6, -120(r1) + stfd f5, -128(r1) + stfd f4, -136(r1) + stfd f3, -144(r1) + stfd f2, -152(r1) + stfd f1, -160(r1) + + subi r6,r1,56 # r6 --> gprData + subi r7,r1,160 # r7 --> fprData + addi r5,r1,STACK_PARAMS # r5 --> extra stack args + + std r0, 16(r1) + + stdu r1,-288(r1) + # r3 has the 'self' pointer + # already + + mr r4,r11 # r4 is methodIndex selector, + # passed via r11 in the + # nsNSStubBase::StubXX() call + + bl PrepareAndDispatch + nop + + ld 1,0(r1) # restore stack + ld r0,16(r1) # restore LR + mtlr r0 + blr + +#if _CALL_ELF == 2 + .size SharedStub,.-SharedStub +#else + .size SharedStub,.-.SharedStub +#endif + + # Magic indicating no need for an executable stack + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 new file mode 100644 index 0000000000..6dabf334da --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 @@ -0,0 +1,119 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + +# Define the correct name of the stub function based on the object model + +define(STUB_NAME, + ifelse(AIX_OBJMODEL, ibm, + `Stub'$1`__EI14nsXPTCStubBaseFv', + `Stub'$1`__14nsXPTCStubBaseFv')) + +define(STUB_ENTRY, ` + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.'STUB_NAME($1)`{TC},"'STUB_NAME($1)`" + .csect H.10.NO_SYMBOL{PR} + .globl .'STUB_NAME($1)` + .globl 'STUB_NAME($1)`{DS} + +.'STUB_NAME($1)`: + li r12, '$1` + b .SharedStub + nop + + + .toc +T.18.'STUB_NAME($1)`: + .tc H.18.'STUB_NAME($1)`{TC},'STUB_NAME($1)`{DS} + .csect 'STUB_NAME($1)`{DS} + .long .'STUB_NAME($1)` + .long TOC{TC0} + .long 0x00000000 +') + +define(SENTINEL_ENTRY, `') + +include(xptcstubsdef.inc) + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.SharedStub{TC},"SharedStub" + +# .text section + .csect H.10.NO_SYMBOL{PR} + .globl .SharedStub + .globl SharedStub{DS} + .extern .PrepareAndDispatch + +.SharedStub: + mflr r0 + stw r0,8(sp) + + stwu sp,-176(sp) # room for linkage (24), fprData (104), gprData(28) + # outgoing params to PrepareAndDispatch (20) + + stw r4,44(sp) # link area (24) + PrepareAndDispatch params (20) + stw r5,48(sp) + stw r6,52(sp) + stw r7,56(sp) + stw r8,60(sp) + stw r9,64(sp) + stw r10,68(sp) + stfd f1,72(sp) + stfd f2,80(sp) + stfd f3,88(sp) + stfd f4,96(sp) + stfd f5,104(sp) + stfd f6,112(sp) + stfd f7,120(sp) + stfd f8,128(sp) + stfd f9,136(sp) + stfd f10,144(sp) + stfd f11,152(sp) + stfd f12,156(sp) + stfd f13,164(sp) + + addi r6,sp,44 # gprData + + addi r7,sp,72 # fprData + # r3 has the 'self' pointer already + mr r4,r12 # methodIndex selector (it is now LATER) + addi r5,sp,232 # pointer to callers args area, beyond r3-r10 + # mapped range + + bl .PrepareAndDispatch + nop + + + lwz r0,184(sp) + addi sp,sp,176 + mtlr r0 + blr + +# .data section + + .toc # 0x00000038 +T.18.SharedStub: + .tc H.18.SharedStub{TC},SharedStub{DS} + + .csect SharedStub{DS} + .long .SharedStub # "\0\0\0\0" + .long TOC{TC0} # "\0\0\0008" + .long 0x00000000 # "\0\0\0\0" +# End csect SharedStub{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 new file mode 100644 index 0000000000..24d713cc9b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 @@ -0,0 +1,97 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +# Define the correct name of the stub function based on the object model +define(STUB_NAME, + ifelse(AIX_OBJMODEL, ibm, + `Stub'$1`__EI14nsXPTCStubBaseFv', + `Stub'$1`__14nsXPTCStubBaseFv')) +define(STUB_ENTRY, ` + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.'STUB_NAME($1)`{TC},"'STUB_NAME($1)`" + .csect H.10.NO_SYMBOL{PR} + .globl .'STUB_NAME($1)` + .globl 'STUB_NAME($1)`{DS} +.'STUB_NAME($1)`: + li r12, '$1` + b .SharedStub + nop + .toc +T.18.'STUB_NAME($1)`: + .tc H.18.'STUB_NAME($1)`{TC},'STUB_NAME($1)`{DS} + .csect 'STUB_NAME($1)`{DS} + .llong .'STUB_NAME($1)` + .llong TOC{TC0} + .llong 0x00000000 +') +define(SENTINEL_ENTRY, `') +include(xptcstubsdef.inc) + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.SharedStub{TC},"SharedStub" +# .text section + .csect H.10.NO_SYMBOL{PR} + .globl .SharedStub + .globl SharedStub{DS} + .extern .PrepareAndDispatch +.SharedStub: + mflr r0 + std r0,16(sp) + stdu sp,-248(sp) # room for linkage (24*2), fprData (104), gprData(28*2) + # outgoing params to PrepareAndDispatch (40) + std r4,88(sp) # link area (48) + PrepareAndDispatch params (20) + std r5,96(sp) + std r6,104(sp) + std r7,112(sp) + std r8,120(sp) + std r9,128(sp) + std r10,136(sp) + stfd f1,144(sp) + stfd f2,152(sp) + stfd f3,160(sp) + stfd f4,168(sp) + stfd f5,176(sp) + stfd f6,184(sp) + stfd f7,192(sp) + stfd f8,200(sp) + stfd f9,208(sp) + stfd f10,216(sp) + stfd f11,224(sp) + stfd f12,232(sp) + stfd f13,240(sp) + addi r6,sp,88 # gprData + addi r7,sp,144 # fprData + # r3 has the 'self' pointer already + mr r4,r12 # methodIndex selector (it is now LATER) + addi r5,sp,360 # pointer to callers args area, beyond r3-r10 + # mapped range + bl .PrepareAndDispatch + nop + ld r0,264(sp) + addi sp,sp,248 + mtlr r0 + blr +# .data section + .toc # 0x00000038 +T.18.SharedStub: + .tc H.18.SharedStub{TC},SharedStub{DS} + .csect SharedStub{DS} + .llong .SharedStub # "\0\0\0\0" + .llong TOC{TC0} # "\0\0\0008" + .llong 0x00000000 # "\0\0\0\0" +# End csect SharedStub{DS} +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 new file mode 100644 index 0000000000..dda5378503 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 @@ -0,0 +1,114 @@ +/* -*- Mode: asm -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .text + .globl _SharedStub +dnl +define(STUB_MANGLED_ENTRY, +` .globl '$2` + .align 2 +'$2`: + addi r12, 0,'$1` + b _SharedStub') +dnl +define(STUB_ENTRY, +` .if '$1` < 10 +STUB_MANGLED_ENTRY('$1`, `__ZN14nsXPTCStubBase5Stub'$1`Ev') + .elseif '$1` < 100 +STUB_MANGLED_ENTRY('$1`, `__ZN14nsXPTCStubBase6Stub'$1`Ev') + .elseif '$1` < 1000 +STUB_MANGLED_ENTRY('$1`, `__ZN14nsXPTCStubBase7Stub'$1`Ev') + .else + .err "Stub'$1` >= 1000 not yet supported." + .endif +') +dnl +define(SENTINEL_ENTRY, `') +dnl +include(xptcstubsdef.inc) +dnl +// See also xptcstubs_ppc_rhapsody.cpp:PrepareAndDispatch. +_SharedStub: + // Prolog(ue) + mflr r0 // Save the link register in the caller's + stw r0, 8(r1) // stack frame + stwu r1,-176(r1) // Allocate stack space for our own frame and + // adjust stack pointer + + // Linkage area, 0(r1) to 24(r1) + // Original sp saved at 0(r1) + + // Parameter area, 20 bytes from 24(r1) to + // 44(r1) to accomodate 5 arguments passed + // to PrepareAndDispatch + + // Local variables, 132 bytes from 44(r1) + // to 176(r1), to accomodate 5 words and + // 13 doubles + + stw r4, 44(r1) // Save parameters passed in GPRs r4-r10; + stw r5, 48(r1) // a pointer to here will be passed to + stw r6, 52(r1) // PrepareAndDispatch for access to + stw r7, 56(r1) // arguments passed in registers. r3, + stw r8, 60(r1) // the self pointer, is used for the + stw r9, 64(r1) // call but isn't otherwise needed in + stw r10, 68(r1) // PrepareAndDispatch, so it is not saved. + + stfd f1, 72(r1) // Do the same for floating-point parameters + stfd f2, 80(r1) // passed in FPRs f1-f13 + stfd f3, 88(r1) + stfd f4, 96(r1) + stfd f5, 104(r1) + stfd f6, 112(r1) + stfd f7, 120(r1) + stfd f8, 128(r1) + stfd f9, 136(r1) + stfd f10, 144(r1) + stfd f11, 152(r1) + stfd f12, 160(r1) + stfd f13, 168(r1) + + // Set up parameters for call to + // PrepareAndDispatch. argument= + // 0, pointer to self, already in r3 + mr r4,r12 // 1, stub number + addi r5, r1, 204 // 2, pointer to the parameter area in our + // caller's stack, for access to + // parameters beyond those passed in + // registers. Skip past the first parameter + // (corresponding to r3) for the same reason + // as above. 176 (size of our frame) + 24 + // (size of caller's linkage) + 4 (skipped + // parameter) + addi r6, r1, 44 // 3, pointer to saved GPRs + addi r7, r1, 72 // 4, pointer to saved FPRs + + bl L_PrepareAndDispatch$stub + // Do it + nop // Leave room for linker magic + + // Epilog(ue) + lwz r0, 184(r1) // Retrieve old link register value + addi r1, r1, 176 // Restore stack pointer + mtlr r0 // Restore link register + blr // Return + +.picsymbol_stub +L_PrepareAndDispatch$stub: // Standard PIC symbol stub + .indirect_symbol _PrepareAndDispatch + mflr r0 + bcl 20,31,L1$pb +L1$pb: + mflr r11 + addis r11,r11,ha16(L1$lz-L1$pb) + mtlr r0 + lwz r12,lo16(L1$lz-L1$pb)(r11) + mtctr r12 + addi r11,r11,lo16(L1$lz-L1$pb) + bctr +.lazy_symbol_pointer +L1$lz: + .indirect_symbol _PrepareAndDispatch + .long dyld_stub_binding_helper diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_linux.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_linux.S new file mode 100644 index 0000000000..e383468865 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_linux.S @@ -0,0 +1,77 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl SharedStub + .type SharedStub,@function + +SharedStub: + stwu sp,-112(sp) // room for + // linkage (8), + // gprData (32), + // fprData (64), + // stack alignment(8) + mflr r0 + stw r0,116(sp) // save LR backchain + + stw r4,12(sp) // save GP registers + stw r5,16(sp) // (n.b. that we don't save r3 + stw r6,20(sp) // because PrepareAndDispatch() is savvy) + stw r7,24(sp) + stw r8,28(sp) + stw r9,32(sp) + stw r10,36(sp) +#ifndef __NO_FPRS__ + stfd f1,40(sp) // save FP registers + stfd f2,48(sp) + stfd f3,56(sp) + stfd f4,64(sp) + stfd f5,72(sp) + stfd f6,80(sp) + stfd f7,88(sp) + stfd f8,96(sp) +#endif + + // r3 has the 'self' pointer already + + mr r4,r11 // r4 <= methodIndex selector, passed + // via r11 in the nsXPTCStubBase::StubXX() call + + addi r5,sp,120 // r5 <= pointer to callers args area, + // beyond r3-r10/f1-f8 mapped range + + addi r6,sp,8 // r6 <= gprData +#ifndef __NO_FPRS__ + addi r7,sp,40 // r7 <= fprData +#else + li r7, 0 // r7 should be unused +#endif + + bl PrepareAndDispatch@local // Go! + + lwz r0,116(sp) // restore LR + mtlr r0 + la sp,112(sp) // clean up the stack + blr + + /* Magic indicating no need for an executable stack */ + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S new file mode 100644 index 0000000000..968ad428f8 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S @@ -0,0 +1,72 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl SharedStub + .type SharedStub,@function + +SharedStub: + stwu sp,-112(sp) // room for + // linkage (8), + // gprData (32), + // fprData (64), + // stack alignment(8) + mflr r0 + stw r0,116(sp) // save LR backchain + + stw r4,12(sp) // save GP registers + stw r5,16(sp) // (n.b. that we don't save r3 + stw r6,20(sp) // because PrepareAndDispatch() is savvy) + stw r7,24(sp) + stw r8,28(sp) + stw r9,32(sp) + stw r10,36(sp) + + stfd f1,40(sp) // save FP registers + stfd f2,48(sp) + stfd f3,56(sp) + stfd f4,64(sp) + stfd f5,72(sp) + stfd f6,80(sp) + stfd f7,88(sp) + stfd f8,96(sp) + + // r3 has the 'self' pointer already + + mr r4,r11 // r4 <= methodIndex selector, passed + // via r11 in the nsXPTCStubBase::StubXX() call + + addi r5,sp,120 // r5 <= pointer to callers args area, + // beyond r3-r10/f1-f8 mapped range + + addi r6,sp,8 // r6 <= gprData + addi r7,sp,40 // r7 <= fprData + + bl PrepareAndDispatch@local // Go! + + lwz r0,116(sp) // restore LR + mtlr r0 + la sp,112(sp) // clean up the stack + blr + + // Magic indicating no need for an executable stack + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_riscv64.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_riscv64.S new file mode 100644 index 0000000000..02bb812d59 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_riscv64.S @@ -0,0 +1,53 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + .set NGPREGS, 8 + .set NFPREGS, 8 + + .text + .globl SharedStub + .hidden SharedStub + .type SharedStub,@function + +SharedStub: + .cfi_startproc + mv t1, sp + addi sp, sp, -8*(NGPREGS+NFPREGS)-16 + .cfi_adjust_cfa_offset 8*(NGPREGS+NFPREGS)+16 + sd a0, 0(sp) + sd a1, 8(sp) + sd a2, 16(sp) + sd a3, 24(sp) + sd a4, 32(sp) + sd a5, 40(sp) + sd a6, 48(sp) + sd a7, 56(sp) + fsd fa0, 64(sp) + fsd fa1, 72(sp) + fsd fa2, 80(sp) + fsd fa3, 88(sp) + fsd fa4, 96(sp) + fsd fa5, 104(sp) + fsd fa6, 112(sp) + fsd fa7, 120(sp) + sd ra, 136(sp) + .cfi_rel_offset ra, 136 + + /* methodIndex is passed from stub */ + mv a1, t0 + mv a2, t1 + mv a3, sp + addi a4, sp, 8*NGPREGS + + call PrepareAndDispatch + + ld ra, 136(sp) + .cfi_restore ra + addi sp, sp, 8*(NGPREGS+NFPREGS)+16 + .cfi_adjust_cfa_offset -8*(NGPREGS+NFPREGS)-16 + ret + .cfi_endproc + + .size SharedStub, . - SharedStub + .section .note.GNU-stack, "", @progbits diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc64_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc64_openbsd.s new file mode 100644 index 0000000000..ab97a890c3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc64_openbsd.s @@ -0,0 +1,50 @@ +/* -*- Mode: asm; 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/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 0x7ff + 136, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') + +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + stx %i1, [%fp + 0x7ff + 136] + stx %i2, [%fp + 0x7ff + 144] + stx %i3, [%fp + 0x7ff + 152] + stx %i4, [%fp + 0x7ff + 160] + stx %i5, [%fp + 0x7ff + 168] +! now we can build our own stack frame + save %sp,-(128 + 64),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 64 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s new file mode 100644 index 0000000000..9b448d7c7d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 72, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + st %i1, [%fp + 72] + st %i2, [%fp + 76] + st %i3, [%fp + 80] + st %i4, [%fp + 84] + st %i5, [%fp + 88] +! now we can build our own stack frame + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s new file mode 100644 index 0000000000..871556d4c6 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 72, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + st %i1, [%fp + 72] + st %i2, [%fp + 76] + st %i3, [%fp + 80] + st %i4, [%fp + 84] + st %i5, [%fp + 88] +! now we can build our own stack frame + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s new file mode 100644 index 0000000000..9b448d7c7d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 72, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + st %i1, [%fp + 72] + st %i2, [%fp + 76] + st %i3, [%fp + 80] + st %i4, [%fp + 84] + st %i5, [%fp + 88] +! now we can build our own stack frame + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp new file mode 100644 index 0000000000..3d1addedaf --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp @@ -0,0 +1,18 @@ +/* -*- Mode: C -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(__i386__) +#include "xptcstubs_gcc_x86_unix.cpp" +#elif defined(__x86_64__) +#include "xptcstubs_x86_64_darwin.cpp" +#elif defined(__ppc__) +#include "xptcstubs_ppc_rhapsody.cpp" +#elif defined(__arm__) +#include "xptcstubs_arm.cpp" +#elif defined(__aarch64__) +#include "xptcstubs_aarch64.cpp" +#else +#error unknown cpu architecture +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp new file mode 100644 index 0000000000..5f0c3ba04e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptc_gcc_x86_unix.h" + +extern "C" { +static nsresult ATTRIBUTE_USED +__attribute__ ((regparm (3))) +PrepareAndDispatch(uint32_t methodIndex, nsXPTCStubBase* self, uint32_t* args) +{ + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + dp->val.p = (void*) *ap; + switch(type) + { + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + default : break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} +} // extern "C" + +#if !defined(XP_MACOSX) + +#define STUB_HEADER(a, b) ".hidden " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t" \ + ".type " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,@function\n" + +#define STUB_SIZE(a, b) ".size " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,.-" SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t" + +#else + +#define STUB_HEADER(a, b) +#define STUB_SIZE(a, b) + +#endif + +// gcc3 mangling tends to insert the length of the method name +#define STUB_ENTRY(n) \ +asm(".text\n\t" \ + ".align 2\n\t" \ + ".if " #n " < 10\n\t" \ + ".globl " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + STUB_HEADER(5, n) \ + SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase5Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".globl " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + STUB_HEADER(6, n) \ + SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase6Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 1000\n\t" \ + ".globl " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + STUB_HEADER(7, n) \ + SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase7Stub" #n "Ev:\n\t" \ + ".else\n\t" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n\t" \ + ".endif\n\t" \ + "movl $" #n ", %eax\n\t" \ + "jmp " SYMBOL_UNDERSCORE "SharedStub\n\t" \ + ".if " #n " < 10\n\t" \ + STUB_SIZE(5, n) \ + ".elseif " #n " < 100\n\t" \ + STUB_SIZE(6, n) \ + ".else\n\t" \ + STUB_SIZE(7, n) \ + ".endif"); + +// static nsresult SharedStub(uint32_t methodIndex) __attribute__((regparm(1))) +asm(".text\n\t" + ".align 2\n\t" +#if !defined(XP_MACOSX) + ".type " SYMBOL_UNDERSCORE "SharedStub,@function\n\t" +#endif + SYMBOL_UNDERSCORE "SharedStub:\n\t" + "leal 0x08(%esp), %ecx\n\t" + "movl 0x04(%esp), %edx\n\t" + "jmp " SYMBOL_UNDERSCORE "PrepareAndDispatch\n\t" +#if !defined(XP_MACOSX) + ".size " SYMBOL_UNDERSCORE "SharedStub,.-" SYMBOL_UNDERSCORE "SharedStub" +#endif +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +void +xptc_dummy() +{ +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp new file mode 100644 index 0000000000..4bee192886 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp @@ -0,0 +1,139 @@ + +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "xptcprivate.h" + +#include <stddef.h> +#include <stdlib.h> + +// "This code is for IA64 only" + +/* Implement shared vtbl methods. */ + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint64_t* intargs, uint64_t* floatargs, uint64_t* restargs) +{ + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint64_t* iargs = intargs; + uint64_t* fargs = floatargs; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + for(i = 0; i < paramCount; ++i) + { + int isfloat = 0; + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + MOZ_CRASH("NYI: support implicit JSContext*, bug 1475699"); + + if(param.IsOut() || !type.IsArithmetic()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + dp->val.p = (void*) *iargs; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) iargs; + dp->val.p = (void*) (*(adr+1)); +#endif + } + else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *(iargs); break; + case nsXPTType::T_I16 : dp->val.i16 = *(iargs); break; + case nsXPTType::T_I32 : dp->val.i32 = *(iargs); break; + case nsXPTType::T_I64 : dp->val.i64 = *(iargs); break; + case nsXPTType::T_U8 : dp->val.u8 = *(iargs); break; + case nsXPTType::T_U16 : dp->val.u16 = *(iargs); break; + case nsXPTType::T_U32 : dp->val.u32 = *(iargs); break; + case nsXPTType::T_U64 : dp->val.u64 = *(iargs); break; + case nsXPTType::T_FLOAT : + isfloat = 1; + if (i < 7) + dp->val.f = (float) *((double*) fargs); /* register */ + else + dp->val.u32 = *(fargs); /* memory */ + break; + case nsXPTType::T_DOUBLE : + isfloat = 1; + dp->val.u64 = *(fargs); + break; + case nsXPTType::T_BOOL : dp->val.b = *(iargs); break; + case nsXPTType::T_CHAR : dp->val.c = *(iargs); break; + case nsXPTType::T_WCHAR : dp->val.wc = *(iargs); break; + default: + NS_ERROR("bad type"); + break; + } + if (i < 7) + { + /* we are parsing register arguments */ + if (i == 6) + { + // run out of register arguments, move on to memory arguments + iargs = restargs; + fargs = restargs; + } + else + { + ++iargs; // advance one integer register slot + if (isfloat) ++fargs; // advance float register slot if isfloat + } + } + else + { + /* we are parsing memory arguments */ + ++iargs; + ++fargs; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t) methodIndex, info, + paramBuffer); + + return result; +} + +extern "C" nsresult SharedStub(uint64_t,uint64_t,uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t *); + +/* Variable a0-a7 were put there so we can have access to the 8 input + registers on Stubxyz entry */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n(uint64_t a1, \ +uint64_t a2,uint64_t a3,uint64_t a4,uint64_t a5,uint64_t a6,uint64_t a7, \ +uint64_t a8) \ +{ uint64_t a0 = (uint64_t) this; \ + return SharedStub(a0,a1,a2,a3,a4,a5,a6,a7,(uint64_t) n, &a8); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp new file mode 100644 index 0000000000..cc74e4db57 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp @@ -0,0 +1,142 @@ + +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "xptcprivate.h" + +#include <stddef.h> +#include <stdlib.h> +#include <stdint.h> + +// "This code is for IA64 only" + +/* Implement shared vtbl methods. */ + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint64_t* intargs, uint64_t* floatargs, uint64_t* restargs) +{ + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint64_t* iargs = intargs; + uint64_t* fargs = floatargs; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + for(i = 0; i < paramCount; ++i) + { + int isfloat = 0; + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + MOZ_CRASH("NYI: support implicit JSContext*, bug 1475699"); + + if(param.IsOut() || !type.IsArithmetic()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + dp->val.p = (void*) *iargs; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) iargs; + dp->val.p = (void*) (*(adr+1)); +#endif + } + else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *(iargs); break; + case nsXPTType::T_I16 : dp->val.i16 = *(iargs); break; + case nsXPTType::T_I32 : dp->val.i32 = *(iargs); break; + case nsXPTType::T_I64 : dp->val.i64 = *(iargs); break; + case nsXPTType::T_U8 : dp->val.u8 = *(iargs); break; + case nsXPTType::T_U16 : dp->val.u16 = *(iargs); break; + case nsXPTType::T_U32 : dp->val.u32 = *(iargs); break; + case nsXPTType::T_U64 : dp->val.u64 = *(iargs); break; + case nsXPTType::T_FLOAT : + isfloat = 1; + if (i < 7) + dp->val.f = (float) *((double*) fargs); /* register */ + else + dp->val.u32 = *(fargs); /* memory */ + break; + case nsXPTType::T_DOUBLE : + isfloat = 1; + dp->val.u64 = *(fargs); + break; + case nsXPTType::T_BOOL : dp->val.b = *(iargs); break; + case nsXPTType::T_CHAR : dp->val.c = *(iargs); break; + case nsXPTType::T_WCHAR : dp->val.wc = *(iargs); break; + default: + NS_ERROR("bad type"); + break; + } + if (i < 7) + { + /* we are parsing register arguments */ + if (i == 6) + { + // run out of register arguments, move on to memory arguments + iargs = restargs; + fargs = restargs; + } + else + { + ++iargs; // advance one integer register slot + if (isfloat) ++fargs; // advance float register slot if isfloat + } + } + else + { + /* we are parsing memory arguments */ + ++iargs; + ++fargs; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t) methodIndex, info, + paramBuffer); + + return result; +} + +extern "C" nsresult SharedStub(uint64_t,uint64_t,uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t *); + +/* Variable a0-a7 were put there so we can have access to the 8 input + registers on Stubxyz entry */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n(uint64_t a1, \ +uint64_t a2,uint64_t a3,uint64_t a4,uint64_t a5,uint64_t a6,uint64_t a7, \ +uint64_t a8) \ +{ uint64_t a0 = (uint64_t) this; \ + return SharedStub(a0,a1,a2,a3,a4,a5,a6,a7,(uint64_t) n, &a8); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_alpha.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_alpha.cpp new file mode 100644 index 0000000000..74ffa0c539 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_alpha.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +__asm__("PrepareAndDispatch") ATTRIBUTE_USED; + +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +{ + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + // args[0] to args[NUM_ARG_REGS] hold floating point register values + uint64_t* ap = args + NUM_ARG_REGS; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = (int8_t) *ap; break; + case nsXPTType::T_I16 : dp->val.i16 = (int16_t) *ap; break; + case nsXPTType::T_I32 : dp->val.i32 = (int32_t) *ap; break; + case nsXPTType::T_I64 : dp->val.i64 = (int64_t) *ap; break; + case nsXPTType::T_U8 : dp->val.u8 = (uint8_t) *ap; break; + case nsXPTType::T_U16 : dp->val.u16 = (uint16_t) *ap; break; + case nsXPTType::T_U32 : dp->val.u32 = (uint32_t) *ap; break; + case nsXPTType::T_U64 : dp->val.u64 = (uint64_t) *ap; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // floats passed via registers are stored as doubles + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (uint64_t) args[i]; + dp->val.f = (float) dp->val.d; // convert double to float + } + else + dp->val.u32 = (uint32_t) *ap; + break; + case nsXPTType::T_DOUBLE : + // doubles passed via registers are also stored + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (i < NUM_ARG_REGS) ? args[i] : *ap; + break; + case nsXPTType::T_BOOL : dp->val.b = (bool) *ap; break; + case nsXPTType::T_CHAR : dp->val.c = (char) *ap; break; + case nsXPTType::T_WCHAR : dp->val.wc = (char16_t) *ap; break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +/* + * SharedStub() + * Collects arguments and calls PrepareAndDispatch. The "methodIndex" is + * passed to this function via $1 to preserve the argument registers. + */ +__asm__( + "#### SharedStub ####\n" +".text\n\t" + ".align 5\n\t" + ".ent SharedStub\n" +"SharedStub:\n\t" + ".frame $30,96,$26,0\n\t" + ".mask 0x4000000,-96\n\t" + "ldgp $29,0($27)\n" +"$SharedStub..ng:\n\t" + "subq $30,96,$30\n\t" + "stq $26,0($30)\n\t" + ".prologue 1\n\t" + + /* + * Store arguments passed via registers to the stack. + * Floating point registers are stored as doubles and converted + * to floats in PrepareAndDispatch if necessary. + */ + "stt $f17,16($30)\n\t" /* floating point registers */ + "stt $f18,24($30)\n\t" + "stt $f19,32($30)\n\t" + "stt $f20,40($30)\n\t" + "stt $f21,48($30)\n\t" + "stq $17,56($30)\n\t" /* integer registers */ + "stq $18,64($30)\n\t" + "stq $19,72($30)\n\t" + "stq $20,80($30)\n\t" + "stq $21,88($30)\n\t" + + /* + * Call PrepareAndDispatch function. + */ + "bis $1,$1,$17\n\t" /* pass "methodIndex" */ + "addq $30,16,$18\n\t" /* pass "args" */ + "bsr $26,$PrepareAndDispatch..ng\n\t" + + "ldq $26,0($30)\n\t" + "addq $30,96,$30\n\t" + "ret $31,($26),1\n\t" + ".end SharedStub" + ); + +/* + * nsresult nsXPTCStubBase::Stub##n() + * Sets register $1 to "methodIndex" and jumps to SharedStub. + */ +#define STUB_MANGLED_ENTRY(n, symbol) \ + "#### Stub"#n" ####" "\n\t" \ + ".text" "\n\t" \ + ".align 5" "\n\t" \ + ".globl " symbol "\n\t" \ + ".ent " symbol "\n" \ +symbol ":" "\n\t" \ + ".frame $30,0,$26,0" "\n\t" \ + "ldgp $29,0($27)" "\n" \ +"$" symbol "..ng:" "\n\t" \ + ".prologue 1" "\n\t" \ + "lda $1,"#n "\n\t" \ + "br $31,$SharedStub..ng" "\n\t" \ + ".end " symbol + +#define STUB_ENTRY(n) \ +__asm__( \ + ".if "#n" < 10" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase5Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 100" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase6Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 1000" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase7Stub"#n"Ev") "\n\t" \ + ".else" "\n\t" \ + ".err \"Stub"#n" >= 1000 not yet supported.\"" "\n\t" \ + ".endif" \ + ); + + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390.cpp new file mode 100644 index 0000000000..15665de872 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +static nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint32_t* a_gpr, uint64_t *a_fpr, uint32_t *a_ov) +{ + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t gpr = 1, fpr = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (gpr < 5) + a_gpr++, gpr++; + else + a_ov++; + } + + if(param.IsOut() || !type.IsArithmetic()) + { + if (gpr < 5) + dp->val.p = (void*) *a_gpr++, gpr++; + else + dp->val.p = (void*) *a_ov++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + dp->val.i8 = *((int32_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i8 = *((int32_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + dp->val.i16 = *((int32_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i16 = *((int32_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + dp->val.i32 = *((int32_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i32 = *((int32_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 4) + dp->val.i64 = *((int64_t*) a_gpr), a_gpr+=2, gpr+=2; + else + dp->val.i64 = *((int64_t*) a_ov ), a_ov+=2, gpr=5; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + dp->val.u8 = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u8 = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + dp->val.u16 = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u16 = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + dp->val.u32 = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u32 = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 4) + dp->val.u64 = *((uint64_t*)a_gpr), a_gpr+=2, gpr+=2; + else + dp->val.u64 = *((uint64_t*)a_ov ), a_ov+=2, gpr=5; + break; + case nsXPTType::T_FLOAT : + if (fpr < 2) + dp->val.f = *((float*) a_fpr), a_fpr++, fpr++; + else + dp->val.f = *((float*) a_ov ), a_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 2) + dp->val.d = *((double*) a_fpr), a_fpr++, fpr++; + else + dp->val.d = *((double*) a_ov ), a_ov+=2; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + dp->val.b = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.b = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + dp->val.c = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.c = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + dp->val.wc = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.wc = *((uint32_t*)a_ov ), a_ov++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + uint32_t a_gpr[4]; \ + uint64_t a_fpr[2]; \ + uint32_t *a_ov; \ + \ + __asm__ __volatile__ \ + ( \ + "l %0,0(15)\n\t" \ + "ahi %0,96\n\t" \ + "stm 3,6,0(%3)\n\t" \ + "std 0,%1\n\t" \ + "std 2,%2\n\t" \ + : "=&a" (a_ov), \ + "=m" (a_fpr[0]), \ + "=m" (a_fpr[1]) \ + : "a" (a_gpr) \ + : "memory", "cc", \ + "3", "4", "5", "6" \ + ); \ + \ + return PrepareAndDispatch(this, n, a_gpr, a_fpr, a_ov); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390x.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390x.cpp new file mode 100644 index 0000000000..75ecd83795 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390x.cpp @@ -0,0 +1,182 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +static nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint64_t* a_gpr, uint64_t *a_fpr, uint64_t *a_ov) +{ + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t gpr = 1, fpr = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (gpr < 5) + a_gpr++, gpr++; + else + a_ov++; + } + + if(param.IsOut() || !type.IsArithmetic()) + { + if (gpr < 5) + dp->val.p = (void*) *a_gpr++, gpr++; + else + dp->val.p = (void*) *a_ov++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + dp->val.i8 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i8 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + dp->val.i16 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i16 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + dp->val.i32 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i32 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 5) + dp->val.i64 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i64 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + dp->val.u8 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u8 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + dp->val.u16 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u16 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + dp->val.u32 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u32 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 5) + dp->val.u64 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u64 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_FLOAT : + if (fpr < 4) + dp->val.f = *((float*) a_fpr), a_fpr++, fpr++; + else + dp->val.f = *(((float*) a_ov )+1), a_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 4) + dp->val.d = *((double*) a_fpr), a_fpr++, fpr++; + else + dp->val.d = *((double*) a_ov ), a_ov++; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + dp->val.b = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.b = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + dp->val.c = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.c = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + dp->val.wc = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.wc = *((uint64_t*)a_ov ), a_ov++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + uint64_t a_gpr[4]; \ + uint64_t a_fpr[4]; \ + uint64_t *a_ov; \ + \ + __asm__ __volatile__ \ + ( \ + "lg %0,0(15)\n\t" \ + "aghi %0,160\n\t" \ + "stmg 3,6,0(%5)\n\t"\ + "std 0,%1\n\t" \ + "std 2,%2\n\t" \ + "std 4,%3\n\t" \ + "std 6,%4\n\t" \ + : "=&a" (a_ov), \ + "=m" (a_fpr[0]), \ + "=m" (a_fpr[1]), \ + "=m" (a_fpr[2]), \ + "=m" (a_fpr[3]) \ + : "a" (a_gpr) \ + : "memory", "cc", \ + "3", "4", "5", "6" \ + ); \ + \ + return PrepareAndDispatch(this, n, a_gpr, a_fpr, a_ov); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_loongarch64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_loongarch64.cpp new file mode 100644 index 0000000000..02ba47d134 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_loongarch64.cpp @@ -0,0 +1,159 @@ +/* -*- 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/. */ + +#include "xptcprivate.h" + +extern "C" nsresult ATTRIBUTE_USED PrepareAndDispatch(nsXPTCStubBase* self, + uint32_t methodIndex, + uint64_t* args, + uint64_t* gpregs, + double* fpregs) { + static const uint32_t GPR_COUNT = 8; + static const uint32_t FPR_COUNT = 8; + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + + uint32_t paramCount = info->GetParamCount(); + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t nr_gpr = 1; // skip the arg which is 'self' + uint32_t nr_fpr = 0; + uint64_t value; + + for (uint32_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (nr_gpr < GPR_COUNT) + nr_gpr++; + else + ap++; + } + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) { + dp->val.d = fpregs[nr_fpr++]; + } else if (nr_gpr < GPR_COUNT) { + memcpy(&dp->val.d, &gpregs[nr_gpr++], sizeof(dp->val.d)); + } else { + memcpy(&dp->val.d, ap++, sizeof(dp->val.d)); + } + continue; + } + + if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) { + memcpy(&dp->val.f, &fpregs[nr_fpr++], sizeof(dp->val.f)); + } else if (nr_gpr < GPR_COUNT) { + memcpy(&dp->val.f, &gpregs[nr_gpr++], sizeof(dp->val.f)); + } else { + memcpy(&dp->val.f, ap++, sizeof(dp->val.f)); + } + continue; + } + + if (nr_gpr < GPR_COUNT) { + value = gpregs[nr_gpr++]; + } else { + value = *ap++; + } + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*)value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: + dp->val.i8 = (int8_t)value; + break; + case nsXPTType::T_I16: + dp->val.i16 = (int16_t)value; + break; + case nsXPTType::T_I32: + dp->val.i32 = (int32_t)value; + break; + case nsXPTType::T_I64: + dp->val.i64 = (int64_t)value; + break; + case nsXPTType::T_U8: + dp->val.u8 = (uint8_t)value; + break; + case nsXPTType::T_U16: + dp->val.u16 = (uint16_t)value; + break; + case nsXPTType::T_U32: + dp->val.u32 = (uint32_t)value; + break; + case nsXPTType::T_U64: + dp->val.u64 = (uint64_t)value; + break; + case nsXPTType::T_BOOL: + dp->val.b = (bool)(uint8_t)value; + break; + case nsXPTType::T_CHAR: + dp->val.c = (char)value; + break; + case nsXPTType::T_WCHAR: + dp->val.wc = (wchar_t)value; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + return result; +} + +// Load $t6 with the constant 'n' and branch to SharedStub(). +// clang-format off +#define STUB_ENTRY(n) \ + __asm__( \ + ".text\n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + "li.d $t6, "#n" \n\t" \ + "b SharedStub \n" \ + ".if "#n" < 10 \n\t" \ + ".size _ZN14nsXPTCStubBase5Stub"#n"Ev,.-_ZN14nsXPTCStubBase5Stub"#n"Ev\n\t" \ + ".elseif "#n" < 100 \n\t" \ + ".size _ZN14nsXPTCStubBase6Stub"#n"Ev,.-_ZN14nsXPTCStubBase6Stub"#n"Ev\n\t" \ + ".else \n\t" \ + ".size _ZN14nsXPTCStubBase7Stub"#n"Ev,.-_ZN14nsXPTCStubBase7Stub"#n"Ev\n\t" \ + ".endif" \ +); +// clang-format on + +#define SENTINEL_ENTRY(n) \ + nsresult nsXPTCStubBase::Sentinel##n() { \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ + } + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp new file mode 100644 index 0000000000..7e686b2b08 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "xptcprivate.h" + +#include <stdint.h> + +/* + * This is for MIPS O32 ABI + * Args contains a0-3 and then the stack. + * Because a0 is 'this', we want to skip it + */ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + args++; // always skip over a0 + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + + switch(type) + { + case nsXPTType::T_I64 : + if ((intptr_t)ap & 4) ap++; + dp->val.i64 = *((int64_t*) ap); ap++; + break; + case nsXPTType::T_U64 : + if ((intptr_t)ap & 4) ap++; + dp->val.u64 = *((int64_t*) ap); ap++; + break; + case nsXPTType::T_DOUBLE: + if ((intptr_t)ap & 4) ap++; + dp->val.d = *((double*) ap); ap++; + break; +#ifdef IS_LITTLE_ENDIAN + default: + dp->val.p = (void*) *ap; + break; +#else + case nsXPTType::T_I8 : dp->val.i8 = (int8_t) *ap; break; + case nsXPTType::T_I16 : dp->val.i16 = (int16_t) *ap; break; + case nsXPTType::T_I32 : dp->val.i32 = (int32_t) *ap; break; + case nsXPTType::T_U8 : dp->val.u8 = (uint8_t) *ap; break; + case nsXPTType::T_U16 : dp->val.u16 = (uint16_t) *ap; break; + case nsXPTType::T_U32 : dp->val.u32 = (uint32_t) *ap; break; + case nsXPTType::T_BOOL : dp->val.b = (bool) *ap; break; + case nsXPTType::T_CHAR : dp->val.c = (char) *ap; break; + case nsXPTType::T_WCHAR : dp->val.wc = (wchar_t) *ap; break; + case nsXPTType::T_FLOAT : dp->val.f = *(float *) ap; break; + default: + NS_ASSERTION(0, "bad type"); + break; +#endif + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) // done in the .s file + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp new file mode 100644 index 0000000000..0c2bdcdcbb --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp @@ -0,0 +1,183 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xptcprivate.h" + +#if (_MIPS_SIM != _ABIN32) && (_MIPS_SIM != _ABI64) +#error "This code is for MIPS n32/n64 only" +#endif + +/* + * This is for MIPS n32/n64 ABI + * + * When we're called, the "gp" registers are stored in gprData and + * the "fp" registers are stored in fprData. There are 8 regs + * available which correspond to the first 7 parameters of the + * function and the "this" pointer. If there are additional parms, + * they are stored on the stack at address "args". + * + */ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args, + uint64_t *gprData, double *fprData) +{ +#define PARAM_GPR_COUNT 7 +#define PARAM_FPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t iCount = 0; + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (iCount < PARAM_GPR_COUNT) + iCount++; + else + ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*)gprData[iCount++]; + else + dp->val.p = (void*)*ap++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8: + if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t)gprData[iCount++]; + else + dp->val.i8 = (int8_t)*ap++; + break; + + case nsXPTType::T_I16: + if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t)gprData[iCount++]; + else + dp->val.i16 = (int16_t)*ap++; + break; + + case nsXPTType::T_I32: + if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t)gprData[iCount++]; + else + dp->val.i32 = (int32_t)*ap++; + break; + + case nsXPTType::T_I64: + if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t)gprData[iCount++]; + else + dp->val.i64 = (int64_t)*ap++; + break; + + case nsXPTType::T_U8: + if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t)gprData[iCount++]; + else + dp->val.u8 = (uint8_t)*ap++; + break; + + case nsXPTType::T_U16: + if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t)gprData[iCount++]; + else + dp->val.u16 = (uint16_t)*ap++; + break; + + case nsXPTType::T_U32: + if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t)gprData[iCount++]; + else + dp->val.u32 = (uint32_t)*ap++; + break; + + case nsXPTType::T_U64: + if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t)gprData[iCount++]; + else + dp->val.u64 = (uint64_t)*ap++; + break; + + case nsXPTType::T_FLOAT: + // the float data formate must not be converted! + // Just only copy without conversion. + if (iCount < PARAM_FPR_COUNT) + dp->val.f = *(float*)&fprData[iCount++]; + else + dp->val.f = *((float*)ap++); + break; + + case nsXPTType::T_DOUBLE: + if (iCount < PARAM_FPR_COUNT) + dp->val.d = (double)fprData[iCount++]; + else + dp->val.d = *((double*)ap++); + break; + + case nsXPTType::T_BOOL: + if (iCount < PARAM_GPR_COUNT) + dp->val.b = (bool)gprData[iCount++]; + else + dp->val.b = (bool)*ap++; + break; + + case nsXPTType::T_CHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char)gprData[iCount++]; + else + dp->val.c = (char)*ap++; + break; + + case nsXPTType::T_WCHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t)gprData[iCount++]; + else + dp->val.wc = (wchar_t)*ap++; + break; + + default: + NS_ASSERTION(0, "bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) /* defined in the assembly file */ + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ASSERTION(0,"nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp new file mode 100644 index 0000000000..f833a7d902 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp @@ -0,0 +1,141 @@ + +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if _HPUX +#error "This code is for HP-PA RISC 32 bit mode only" +#endif + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint32_t* args, uint32_t* floatargs) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + int32_t regwords = 1; /* self pointer is not in the variant records */ + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + for(i = 0; i < paramCount; ++i, --args) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + MOZ_CRASH("NYI: support implicit JSContext*, bug 1475699"); + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *args; + ++regwords; + continue; + } + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) args); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) args); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) args); break; + case nsXPTType::T_DOUBLE : + if (regwords & 1) + { + ++regwords; /* align on double word */ + --args; + } + if (regwords == 0 || regwords == 2) + { + dp->val.d=*((double*) (floatargs + regwords)); + --args; + } + else + { + dp->val.d = *((double*) --args); + } + regwords += 2; + continue; + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : + if (regwords & 1) + { + ++regwords; /* align on double word */ + --args; + } + ((DU *)dp)->lo = *((uint32_t*) args); + ((DU *)dp)->hi = *((uint32_t*) --args); + regwords += 2; + continue; + case nsXPTType::T_FLOAT : + if (regwords >= 4) + dp->val.f = *((float*) args); + else + dp->val.f = *((float*) floatargs+4+regwords); + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*) args); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*) args); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*) args); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*) args); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*) args); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) args); break; + default: + NS_ERROR("bad type"); + break; + } + ++regwords; + } + + nsresult result = self->mOuter->CallMethod((uint16_t) methodIndex, info, + paramBuffer); + + return result; +} + +extern "C" nsresult SharedStub(int); + +#ifdef __GNUC__ +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + /* Save arg0 in its stack slot. This assumes the frame size is 64. */ \ + __asm__ __volatile__ ("STW %r26, -36-64(%sp)"); \ + return SharedStub(n); \ +} +#else +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + return SharedStub(n); \ +} +#endif + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc64_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc64_linux.cpp new file mode 100644 index 0000000000..5bbb6f9e5f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc64_linux.cpp @@ -0,0 +1,280 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement shared vtbl methods. + +#include "xptcprivate.h" + +// Prior to POWER8, all 64-bit Power ISA systems used ELF v1 ABI, found +// here: +// https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html +// and in particular: +// https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html#FUNC-CALL +// Little-endian ppc64le, however, uses ELF v2 ABI, which is here: +// http://openpowerfoundation.org/wp-content/uploads/resources/leabi/leabi-20170510.pdf +// and in particular section 2.2, page 22. However, most big-endian ppc64 +// systems still use ELF v1, so this file should support both. +// +// Both ABIs pass the first 8 integral parameters and the first 13 floating +// point parameters in registers r3-r10 and f1-f13. No stack space is +// allocated for these by the caller. The rest of the parameters are passed +// in the caller's stack area. The stack pointer must stay 16-byte aligned. + +const uint32_t GPR_COUNT = 7; +const uint32_t FPR_COUNT = 13; + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gpregs[]' contains the arguments passed in integer registers +// - 'fpregs[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. +// +// Both ABIs use the same register assignment strategy, as per this +// example from V1 ABI section 3.2.3 and V2 ABI section 2.2.3.2 [page 43]: +// +// typedef struct { +// int a; +// double dd; +// } sparm; +// sparm s, t; +// int c, d, e; +// long double ld; +// double ff, gg, hh; +// +// x = func(c, ff, d, ld, s, gg, t, e, hh); +// +// Parameter Register Offset in parameter save area +// c r3 0-7 (not stored in parameter save area) +// ff f1 8-15 (not stored) +// d r5 16-23 (not stored) +// ld f2,f3 24-39 (not stored) +// s r8,r9 40-55 (not stored) +// gg f4 56-63 (not stored) +// t (none) 64-79 (stored in parameter save area) +// e (none) 80-87 (stored) +// hh f5 88-95 (not stored) +// +// i.e., each successive FPR usage skips a GPR, but not the other way around. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gpregs, double *fpregs) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + // |that| is implicit in the calling convention; we really do start at the + // first GPR (as opposed to x86_64). + uint32_t nr_gpr = 0; + uint32_t nr_fpr = 0; + uint64_t value; + + for(i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (nr_gpr < GPR_COUNT) + nr_gpr++; + else + ap++; + } + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) { + dp->val.d = fpregs[nr_fpr++]; + // Even if we have enough FPRs, still skip space in + // the parameter area if we ran out of placeholder GPRs. + if (nr_gpr < GPR_COUNT) { + nr_gpr++; + } else { + ap++; + } + } else { + dp->val.d = *(double*)ap++; + } + continue; + } + if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) { + // Single-precision floats are passed in FPRs too. + dp->val.f = (float)fpregs[nr_fpr++]; + if (nr_gpr < GPR_COUNT) { + nr_gpr++; + } else { + ap++; + } + } else { +#ifdef __LITTLE_ENDIAN__ + dp->val.f = *(float*)ap++; +#else + // Big endian needs adjustment to point to the least + // significant word. + float* p = (float*)ap; + p++; + dp->val.f = *p; + ap++; +#endif + } + continue; + } + if (nr_gpr < GPR_COUNT) + value = gpregs[nr_gpr++]; + else + value = *ap++; + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*) value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) value; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) value; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) value; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) value; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) value; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) value; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) value; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) value; break; + case nsXPTType::T_BOOL: dp->val.b = (bool) value; break; + case nsXPTType::T_CHAR: dp->val.c = (char) value; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) value; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t) methodIndex, info, + paramBuffer); + + return result; +} + +// Load r11 with the constant 'n' and branch to SharedStub(). +// +// As G++3 ABI contains the length of the functionname in the mangled +// name, it is difficult to get a generic assembler mechanism like +// in the G++ 2.95 case. +// XXX Yes, it's ugly that we're relying on gcc's name-mangling here; +// however, it's quick, dirty, and'll break when the ABI changes on +// us, which is what we want ;-). +// Create names would be like: +// _ZN14nsXPTCStubBase5Stub1Ev +// _ZN14nsXPTCStubBase6Stub12Ev +// _ZN14nsXPTCStubBase7Stub123Ev +// _ZN14nsXPTCStubBase8Stub1234Ev +// etc. +// Use assembler directives to get the names right. + +#if _CALL_ELF == 2 +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".section \".text\" \n\t" \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + "0: addis 2,12,.TOC.-0b@ha \n\t" \ + "addi 2,2,.TOC.-0b@l \n\t" \ + ".localentry _ZN14nsXPTCStubBase5Stub"#n"Ev,.-_ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + "0: addis 2,12,.TOC.-0b@ha \n\t" \ + "addi 2,2,.TOC.-0b@l \n\t" \ + ".localentry _ZN14nsXPTCStubBase6Stub"#n"Ev,.-_ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + "0: addis 2,12,.TOC.-0b@ha \n\t" \ + "addi 2,2,.TOC.-0b@l \n\t" \ + ".localentry _ZN14nsXPTCStubBase7Stub"#n"Ev,.-_ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub \n" \ +); +#else +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".section \".toc\",\"aw\" \n\t" \ + ".section \".text\" \n\t" \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".section \".opd\",\"aw\" \n\t" \ + ".align 3 \n\t" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + ".quad ._ZN14nsXPTCStubBase5Stub"#n"Ev,.TOC.@tocbase \n\t" \ + ".previous \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"._ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".section \".opd\",\"aw\" \n\t" \ + ".align 3 \n\t" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + ".quad ._ZN14nsXPTCStubBase6Stub"#n"Ev,.TOC.@tocbase \n\t" \ + ".previous \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"._ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".section \".opd\",\"aw\" \n\t" \ + ".align 3 \n\t" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + ".quad ._ZN14nsXPTCStubBase7Stub"#n"Ev,.TOC.@tocbase \n\t" \ + ".previous \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"._ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub \n" \ +); +#endif + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp new file mode 100644 index 0000000000..28eee7c394 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(AIX) + +/* + For PPC (AIX & MAC), the first 8 integral and the first 13 f.p. parameters + arrive in a separate chunk of data that has been loaded from the registers. + The args pointer has been set to the start of the parameters BEYOND the ones + arriving in registers +*/ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args, uint32_t *gprData, double *fprData) +{ + typedef struct { + uint32_t hi; + uint32_t lo; // have to move 64 bit entities as 32 bit halves since + } DU; // stack slots are not guaranteed 16 byte aligned + +#define PARAM_GPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + uint32_t iCount = 0; + uint32_t fpCount = 0; + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (iCount < PARAM_GPR_COUNT) + iCount++; + else + ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*) gprData[iCount++]; + else + dp->val.p = (void*) *ap++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t) gprData[iCount++]; + else + dp->val.i8 = (int8_t) *ap++; + break; + case nsXPTType::T_I16 : if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t) gprData[iCount++]; + else + dp->val.i16 = (int16_t) *ap++; + break; + case nsXPTType::T_I32 : if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t) gprData[iCount++]; + else + dp->val.i32 = (int32_t) *ap++; + break; + case nsXPTType::T_I64 : if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->hi = (int32_t) gprData[iCount++]; + else + ((DU *)dp)->hi = (int32_t) *ap++; + if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->lo = (uint32_t) gprData[iCount++]; + else + ((DU *)dp)->lo = (uint32_t) *ap++; + break; + case nsXPTType::T_U8 : if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t) gprData[iCount++]; + else + dp->val.u8 = (uint8_t) *ap++; + break; + case nsXPTType::T_U16 : if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t) gprData[iCount++]; + else + dp->val.u16 = (uint16_t) *ap++; + break; + case nsXPTType::T_U32 : if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t) gprData[iCount++]; + else + dp->val.u32 = (uint32_t) *ap++; + break; + case nsXPTType::T_U64 : if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->hi = (uint32_t) gprData[iCount++]; + else + ((DU *)dp)->hi = (uint32_t) *ap++; + if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->lo = (uint32_t) gprData[iCount++]; + else + ((DU *)dp)->lo = (uint32_t) *ap++; + break; + case nsXPTType::T_FLOAT : if (fpCount < 13) { + dp->val.f = (float) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else + dp->val.f = *((float*) ap++); + break; + case nsXPTType::T_DOUBLE : if (fpCount < 13) { + dp->val.d = (double) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else { + dp->val.f = *((double*) ap); + ap += 2; + } + break; + case nsXPTType::T_BOOL : if (iCount < PARAM_GPR_COUNT) + dp->val.b = (bool) gprData[iCount++]; + else + dp->val.b = (bool) *ap++; + break; + case nsXPTType::T_CHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char) gprData[iCount++]; + else + dp->val.c = (char) *ap++; + break; + case nsXPTType::T_WCHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t) gprData[iCount++]; + else + dp->val.wc = (wchar_t) *ap++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* AIX */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix64.cpp new file mode 100644 index 0000000000..297ed3e3bb --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix64.cpp @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(AIX) + +/* + For PPC (AIX & MAC), the first 8 integral and the first 13 f.p. parameters + arrive in a separate chunk of data that has been loaded from the registers. + The args pointer has been set to the start of the parameters BEYOND the ones + arriving in registers +*/ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint64_t methodIndex, uint64_t* args, uint64_t *gprData, double *fprData) +{ + +#define PARAM_GPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t iCount = 0; + uint32_t fpCount = 0; + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (iCount < PARAM_GPR_COUNT) + iCount++; + else + ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*) gprData[iCount++]; + else + dp->val.p = (void*) *ap++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t) gprData[iCount++]; + else + dp->val.i8 = (int8_t) *ap++; + break; + case nsXPTType::T_I16 : if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t) gprData[iCount++]; + else + dp->val.i16 = (int16_t) *ap++; + break; + case nsXPTType::T_I32 : if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t) gprData[iCount++]; + else + dp->val.i32 = (int32_t) *ap++; + break; + case nsXPTType::T_I64 : if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t) gprData[iCount++]; + else + dp->val.i64 = (int64_t) *ap++; + break; + case nsXPTType::T_U8 : if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t) gprData[iCount++]; + else + dp->val.u8 = (uint8_t) *ap++; + break; + case nsXPTType::T_U16 : if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t) gprData[iCount++]; + else + dp->val.u16 = (uint16_t) *ap++; + break; + case nsXPTType::T_U32 : if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t) gprData[iCount++]; + else + dp->val.u32 = (uint32_t) *ap++; + break; + case nsXPTType::T_U64 : if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t) gprData[iCount++]; + else + dp->val.u64 = (uint64_t) *ap++; + break; + case nsXPTType::T_FLOAT : if (fpCount < 13) { + dp->val.f = (float) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else + dp->val.f = *((float*) ap++); + break; + case nsXPTType::T_DOUBLE : if (fpCount < 13) { + dp->val.d = (double) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else { + dp->val.f = *((double*) ap); + ap += 2; + } + break; + case nsXPTType::T_BOOL : if (iCount < PARAM_GPR_COUNT) + dp->val.b = (bool) gprData[iCount++]; + else + dp->val.b = (bool) *ap++; + break; + case nsXPTType::T_CHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char) gprData[iCount++]; + else + dp->val.c = (char) *ap++; + break; + case nsXPTType::T_WCHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t) gprData[iCount++]; + else + dp->val.wc = (wchar_t) *ap++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* AIX */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_linux.cpp new file mode 100644 index 0000000000..e644428f77 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_linux.cpp @@ -0,0 +1,211 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement shared vtbl methods. + +#include "xptcprivate.h" + +// The Linux/PPC ABI (aka PPC/SYSV ABI) passes the first 8 integral +// parameters and the first 8 floating point parameters in registers +// (r3-r10 and f1-f8), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. The stack pointer has to retain 16-byte alignment, longlongs +// and doubles are aligned on 8-byte boundaries. +#ifndef __NO_FPRS__ +#define GPR_COUNT 8 +#define FPR_COUNT 8 +#else +#define GPR_COUNT 8 +#endif +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gprData[]' contains the arguments passed in integer registers +// - 'fprData[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, + uint32_t methodIndex, + uint32_t* args, + uint32_t *gprData, + double *fprData) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info = nullptr; + uint32_t paramCount; + uint32_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + uint32_t gpr = 1; // skip one GPR register +#ifndef __NO_FPRS__ + uint32_t fpr = 0; +#endif + uint32_t tempu32; + uint64_t tempu64; + + for(i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (gpr < GPR_COUNT) + gpr++; + else + ap++; + } + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + dp->val.d = fprData[fpr++]; +#else + if (gpr & 1) + gpr++; + if (gpr + 1 < GPR_COUNT) { + dp->val.d = *(double*) &gprData[gpr]; + gpr += 2; + } +#endif + else { + if ((uint32_t) ap & 4) ap++; // doubles are 8-byte aligned on stack + dp->val.d = *(double*) ap; + ap += 2; + } + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + dp->val.f = (float) fprData[fpr++]; // in registers floats are passed as doubles +#else + if (gpr < GPR_COUNT) + dp->val.f = *(float*) &gprData[gpr++]; +#endif + else + dp->val.f = *(float*) ap++; + continue; + } + else if (!param.IsOut() && (type == nsXPTType::T_I64 + || type == nsXPTType::T_U64)) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + if ((gpr + 1) < GPR_COUNT) { + tempu64 = *(uint64_t*) &gprData[gpr]; + gpr += 2; + } + else { + if ((uint32_t) ap & 4) ap++; // longlongs are 8-byte aligned on stack + tempu64 = *(uint64_t*) ap; + ap += 2; + } + } + else { + if (gpr < GPR_COUNT) + tempu32 = gprData[gpr++]; + else + tempu32 = *ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) tempu32); + else + dp->val.p = (void*) tempu32; + continue; + } + + switch(type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) tempu32; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) tempu32; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) tempu32; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) tempu64; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) tempu32; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) tempu32; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) tempu32; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) tempu64; break; + case nsXPTType::T_BOOL: dp->val.b = (bool) tempu32; break; + case nsXPTType::T_CHAR: dp->val.c = (char) tempu32; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) tempu32; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, + info, + paramBuffer); + + return result; +} + +// Load r11 with the constant 'n' and branch to SharedStub(). +// +// XXX Yes, it's ugly that we're relying on gcc's name-mangling here; +// however, it's quick, dirty, and'll break when the ABI changes on +// us, which is what we want ;-). + +// gcc-3 version +// +// As G++3 ABI contains the length of the functionname in the mangled +// name, it is difficult to get a generic assembler mechanism like +// in the G++ 2.95 case. +// Create names would be like: +// _ZN14nsXPTCStubBase5Stub1Ev +// _ZN14nsXPTCStubBase6Stub12Ev +// _ZN14nsXPTCStubBase7Stub123Ev +// _ZN14nsXPTCStubBase8Stub1234Ev +// etc. +// Use assembler directives to get the names right... + +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub@local \n" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_openbsd.cpp new file mode 100644 index 0000000000..121ff48556 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_openbsd.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement shared vtbl methods. + +#include "xptcprivate.h" + +// The Linux/PPC ABI (aka PPC/SYSV ABI) passes the first 8 integral +// parameters and the first 8 floating point parameters in registers +// (r3-r10 and f1-f8), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. The stack pointer has to retain 16-byte alignment, longlongs +// and doubles are aligned on 8-byte boundaries. + +#define GPR_COUNT 8 +#define FPR_COUNT 8 + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gprData[]' contains the arguments passed in integer registers +// - 'fprData[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, + uint32_t methodIndex, + uint32_t* args, + uint32_t *gprData, + double *fprData) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info = nullptr; + uint32_t paramCount; + uint32_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + uint32_t gpr = 1; // skip one GPR register + uint32_t fpr = 0; + uint32_t tempu32; + uint64_t tempu64; + + for(i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (gpr < GPR_COUNT) + gpr++; + else + ap++; + } + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (fpr < FPR_COUNT) + dp->val.d = fprData[fpr++]; + else { + if ((uint32_t) ap & 4) ap++; // doubles are 8-byte aligned on stack + dp->val.d = *(double*) ap; + ap += 2; + } + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (fpr < FPR_COUNT) + dp->val.f = (float) fprData[fpr++]; // in registers floats are passed as doubles + else + dp->val.f = *(float*) ap++; + continue; + } + else if (!param.IsOut() && (type == nsXPTType::T_I64 + || type == nsXPTType::T_U64)) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + if ((gpr + 1) < GPR_COUNT) { + tempu64 = *(uint64_t*) &gprData[gpr]; + gpr += 2; + } + else { + if ((uint32_t) ap & 4) ap++; // longlongs are 8-byte aligned on stack + tempu64 = *(uint64_t*) ap; + ap += 2; + } + } + else { + if (gpr < GPR_COUNT) + tempu32 = gprData[gpr++]; + else + tempu32 = *ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) tempu32); + else + dp->val.p = (void*) tempu32; + continue; + } + + switch(type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) tempu32; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) tempu32; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) tempu32; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) tempu64; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) tempu32; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) tempu32; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) tempu32; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) tempu64; break; + case nsXPTType::T_BOOL: dp->val.b = (bool) tempu32; break; + case nsXPTType::T_CHAR: dp->val.c = (char) tempu32; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) tempu32; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, + info, + paramBuffer); + + return result; +} + +// Load r11 with the constant 'n' and branch to SharedStub(). +// +// XXX Yes, it's ugly that we're relying on gcc's name-mangling here; +// however, it's quick, dirty, and'll break when the ABI changes on +// us, which is what we want ;-). + + +// gcc-3 version +// +// As G++3 ABI contains the length of the functionname in the mangled +// name, it is difficult to get a generic assembler mechanism like +// in the G++ 2.95 case. +// Create names would be like: +// _ZN14nsXPTCStubBase5Stub1Ev +// _ZN14nsXPTCStubBase6Stub12Ev +// _ZN14nsXPTCStubBase7Stub123Ev +// _ZN14nsXPTCStubBase8Stub1234Ev +// etc. +// Use assembler directives to get the names right... + +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub@local \n" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp new file mode 100644 index 0000000000..43b4f029c6 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "xptcprivate.h" + +/* Under the Mac OS X PowerPC ABI, the first 8 integer and 13 floating point + * parameters are delivered in registers and are not on the stack, although + * stack space is allocated for them. The integer parameters are delivered + * in GPRs r3 through r10. The first 8 words of the parameter area on the + * stack shadow these registers. A word will either be in a register or on + * the stack, but not in both. Although the first floating point parameters + * are passed in floating point registers, GPR space and stack space is + * reserved for them as well. + * + * SharedStub has passed pointers to the parameter section of the stack + * and saved copies of the GPRs and FPRs used for parameter passing. We + * don't care about the first parameter (which is delivered here as the self + * pointer), so SharedStub pointed us past that. argsGPR thus points to GPR + * r4 (corresponding to the first argument after the self pointer) and + * argsStack points to the parameter section of the caller's stack frame + * reserved for the same argument. This way, it is possible to reference + * either argsGPR or argsStack with the same index. + * + * Contrary to the assumption made by the previous implementation, the + * Mac OS X PowerPC ABI doesn't impose any special alignment restrictions on + * parameter sections of stacks. Values that are 64 bits wide appear on the + * stack without any special padding. + * + * See also xptcstubs_asm_ppc_darwin.s.m4:_SharedStub. + * + * ABI reference: + * http://developer.apple.com/documentation/DeveloperTools/Conceptual/ + * MachORuntime/PowerPCConventions/chapter_3_section_1.html */ + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch( + nsXPTCStubBase *self, + uint32_t methodIndex, + uint32_t *argsStack, + uint32_t *argsGPR, + double *argsFPR) { +#define PARAM_FPR_COUNT 13 +#define PARAM_GPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo *methodInfo; + uint8_t paramCount; + uint8_t i; + uint32_t argIndex = 0; + uint32_t fprIndex = 0; + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; + + NS_ASSERTION(self, "no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &methodInfo); + NS_ASSERTION(methodInfo, "no method info"); + + paramCount = methodInfo->GetParamCount(); + + for(i = 0; i < paramCount; i++, argIndex++) { + const nsXPTParamInfo ¶m = methodInfo->GetParam(i); + const nsXPTType &type = param.GetType(); + nsXPTCMiniVariant *dp = ¶mBuffer[i]; + uint32_t theParam; + + if(argIndex < PARAM_GPR_COUNT) + theParam = argsGPR[argIndex]; + else + theParam = argsStack[argIndex]; + + if(param.IsOut() || !type.IsArithmetic()) + dp->val.p = (void *) theParam; + else { + switch(type) { + case nsXPTType::T_I8: + dp->val.i8 = (int8_t) theParam; + break; + case nsXPTType::T_I16: + dp->val.i16 = (int16_t) theParam; + break; + case nsXPTType::T_I32: + dp->val.i32 = (int32_t) theParam; + break; + case nsXPTType::T_U8: + dp->val.u8 = (uint8_t) theParam; + break; + case nsXPTType::T_U16: + dp->val.u16 = (uint16_t) theParam; + break; + case nsXPTType::T_U32: + dp->val.u32 = (uint32_t) theParam; + break; + case nsXPTType::T_I64: + case nsXPTType::T_U64: + ((DU *)dp)->hi = (uint32_t) theParam; + if(++argIndex < PARAM_GPR_COUNT) + ((DU *)dp)->lo = (uint32_t) argsGPR[argIndex]; + else + ((DU *)dp)->lo = (uint32_t) argsStack[argIndex]; + break; + case nsXPTType::T_BOOL: + dp->val.b = (bool) theParam; + break; + case nsXPTType::T_CHAR: + dp->val.c = (char) theParam; + break; + case nsXPTType::T_WCHAR: + dp->val.wc = (wchar_t) theParam; + break; + case nsXPTType::T_FLOAT: + if(fprIndex < PARAM_FPR_COUNT) + dp->val.f = (float) argsFPR[fprIndex++]; + else + dp->val.f = *(float *) &argsStack[argIndex]; + break; + case nsXPTType::T_DOUBLE: + if(fprIndex < PARAM_FPR_COUNT) + dp->val.d = argsFPR[fprIndex++]; + else + dp->val.d = *(double *) &argsStack[argIndex]; + argIndex++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + } + + nsresult result = self->mOuter-> + CallMethod((uint16_t)methodIndex, methodInfo, paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_riscv64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_riscv64.cpp new file mode 100644 index 0000000000..b50fc23ffd --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_riscv64.cpp @@ -0,0 +1,160 @@ +/* -*- 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/. */ + +#if defined(__riscv_float_abi_soft) +# error "Not support soft float ABI" +#endif + +#include "xptcprivate.h" + +extern "C" nsresult ATTRIBUTE_USED PrepareAndDispatch(nsXPTCStubBase* self, + uint32_t methodIndex, + uint64_t* args, + uint64_t* gpregs, + double* fpregs) { + static const uint32_t GPR_COUNT = 8; + static const uint32_t FPR_COUNT = 8; + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + + uint32_t paramCount = info->GetParamCount(); + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t nr_gpr = 1; // skip one GPR register for 'self' + uint32_t nr_fpr = 0; + uint64_t value; + + for (uint32_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (nr_gpr < GPR_COUNT) + nr_gpr++; + else + ap++; + } + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) { + dp->val.d = fpregs[nr_fpr++]; + } else { + dp->val.d = *(double*)ap++; + } + continue; + } + + if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) { + dp->val.d = fpregs[nr_fpr++]; + } else { + dp->val.f = *(float*)ap++; + } + continue; + } + + if (nr_gpr < GPR_COUNT) { + value = gpregs[nr_gpr++]; + } else { + value = *ap++; + } + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*)value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: + dp->val.i8 = (int8_t)value; + break; + case nsXPTType::T_I16: + dp->val.i16 = (int16_t)value; + break; + case nsXPTType::T_I32: + dp->val.i32 = (int32_t)value; + break; + case nsXPTType::T_I64: + dp->val.i64 = (int64_t)value; + break; + case nsXPTType::T_U8: + dp->val.u8 = (uint8_t)value; + break; + case nsXPTType::T_U16: + dp->val.u16 = (uint16_t)value; + break; + case nsXPTType::T_U32: + dp->val.u32 = (uint32_t)value; + break; + case nsXPTType::T_U64: + dp->val.u64 = (uint64_t)value; + break; + case nsXPTType::T_BOOL: + dp->val.b = (bool)(uint8_t)value; + break; + case nsXPTType::T_CHAR: + dp->val.c = (char)value; + break; + case nsXPTType::T_WCHAR: + dp->val.wc = (wchar_t)value; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = + self->mOuter->CallMethod((uint16_t)methodIndex, info, paramBuffer); + + return result; +} + +// Load t0 with the constant 'n' and branch to SharedStub(). +// clang-format off +#define STUB_ENTRY(n) \ + __asm__( \ + ".text\n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + "li t0, "#n" \n\t" \ + "j SharedStub \n" \ + ".if "#n" < 10\n\t" \ + ".size _ZN14nsXPTCStubBase5Stub"#n"Ev,.-_ZN14nsXPTCStubBase5Stub"#n"Ev\n\t" \ + ".elseif "#n" < 100\n\t" \ + ".size _ZN14nsXPTCStubBase6Stub"#n"Ev,.-_ZN14nsXPTCStubBase6Stub"#n"Ev\n\t" \ + ".else\n\t" \ + ".size _ZN14nsXPTCStubBase7Stub"#n"Ev,.-_ZN14nsXPTCStubBase7Stub"#n"Ev\n\t" \ + ".endif" \ +); +// clang-format on + +#define SENTINEL_ENTRY(n) \ + nsresult nsXPTCStubBase::Sentinel##n() { \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ + } + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp new file mode 100644 index 0000000000..79ecc6ab89 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint64_t methodIndex, uint64_t* args) +{ + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_BOOL : dp->val.b = *((int64_t*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint64_t*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int64_t*) ap); break; + case nsXPTType::T_I8 : dp->val.i8 = *((int64_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int64_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int64_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint64_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint64_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint64_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*) ap); break; + case nsXPTType::T_FLOAT : dp->val.f = ((float*) ap)[1]; break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +/* + * Avoid GCC stack protector to wipe out input registers since the compiler + * thinks the function takes no arguments. + */ +#ifndef nostackprotector +#define nostackprotector __attribute__((__optimize__("no-stack-protector"))) +#endif + +#define STUB_ENTRY(n) \ +nsresult nostackprotector nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_netbsd.cpp new file mode 100644 index 0000000000..33b839ffa4 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_netbsd.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) *ap); + else + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_DOUBLE : + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : ((DU *)dp)->hi = ((DU *)ap)->hi; + ((DU *)dp)->lo = ((DU *)ap)->lo; + ap++; + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_openbsd.cpp new file mode 100644 index 0000000000..33b839ffa4 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_openbsd.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) *ap); + else + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_DOUBLE : + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : ((DU *)dp)->hi = ((DU *)ap)->hi; + ((DU *)dp)->lo = ((DU *)ap)->lo; + ap++; + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp new file mode 100644 index 0000000000..488c3e7895 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) *ap); + else + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_DOUBLE : + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : ((DU *)dp)->hi = ((DU *)ap)->hi; + ((DU *)dp)->lo = ((DU *)ap)->lo; + ap++; + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_darwin.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_darwin.cpp new file mode 100644 index 0000000000..48beb32aff --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_darwin.cpp @@ -0,0 +1,183 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement shared vtbl methods. + +// Keep this in sync with the linux version. + +#include "xptcprivate.h" + +// The Darwin/x86-64 ABI passes the first 6 integer parameters and the +// first 8 floating point parameters in registers (rdi, rsi, rdx, rcx, +// r8, r9 and xmm0-xmm7), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. + +const uint32_t GPR_COUNT = 6; +const uint32_t FPR_COUNT = 8; + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gpregs[]' contains the arguments passed in integer registers +// - 'fpregs[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gpregs, double *fpregs) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t nr_gpr = 1; // skip one GPR register for 'that' + uint32_t nr_fpr = 0; + uint64_t value; + + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (nr_gpr < GPR_COUNT) + nr_gpr++; + else + ap++; + } + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.d = *(double*) ap++; + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.f = *(float*) ap++; + continue; + } + else { + if (nr_gpr < GPR_COUNT) + value = gpregs[nr_gpr++]; + else + value = *ap++; + } + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*) value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) value; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) value; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) value; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) value; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) value; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) value; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) value; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) value; break; + // Cast to uint8_t first, to remove garbage on upper 56 bits. + case nsXPTType::T_BOOL: dp->val.b = (bool)(uint8_t) value; break; + case nsXPTType::T_CHAR: dp->val.c = (char) value; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) value; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t) methodIndex, info, + paramBuffer); + + return result; +} + +// Darwin/x86-64 uses gcc >= 4.2 + +#define STUB_ENTRY(n) \ +asm(".section __TEXT,__text\n\t" \ + ".align 3\n\t" \ + ".if " #n " < 10\n\t" \ + ".globl __ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + "__ZN14nsXPTCStubBase5Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".globl __ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + "__ZN14nsXPTCStubBase6Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 1000\n\t" \ + ".globl __ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + "__ZN14nsXPTCStubBase7Stub" #n "Ev:\n\t" \ + ".else\n\t" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n\t" \ + ".endif\n\t" \ + "movl $" #n ", %eax\n\t" \ + "jmp SharedStub\n\t"); + +// static nsresult SharedStub(uint32_t methodIndex) +asm(".section __TEXT,__text\n\t" + ".align 3\n\t" + "SharedStub:\n\t" + // make room for gpregs (48), fpregs (64) + "pushq %rbp\n\t" + "movq %rsp,%rbp\n\t" + "subq $112,%rsp\n\t" + // save GP registers + "movq %rdi,-112(%rbp)\n\t" + "movq %rsi,-104(%rbp)\n\t" + "movq %rdx, -96(%rbp)\n\t" + "movq %rcx, -88(%rbp)\n\t" + "movq %r8 , -80(%rbp)\n\t" + "movq %r9 , -72(%rbp)\n\t" + "leaq -112(%rbp),%rcx\n\t" + // save FP registers + "movsd %xmm0,-64(%rbp)\n\t" + "movsd %xmm1,-56(%rbp)\n\t" + "movsd %xmm2,-48(%rbp)\n\t" + "movsd %xmm3,-40(%rbp)\n\t" + "movsd %xmm4,-32(%rbp)\n\t" + "movsd %xmm5,-24(%rbp)\n\t" + "movsd %xmm6,-16(%rbp)\n\t" + "movsd %xmm7, -8(%rbp)\n\t" + "leaq -64(%rbp),%r8\n\t" + // rdi has the 'self' pointer already + "movl %eax,%esi\n\t" + "leaq 16(%rbp),%rdx\n\t" + "call _PrepareAndDispatch\n\t" + "leave\n\t" + "ret\n\t"); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_linux.cpp new file mode 100644 index 0000000000..e392365be4 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_linux.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement shared vtbl methods. + +// Keep this in sync with the darwin version. + +#include "xptcprivate.h" + +// The Linux/x86-64 ABI passes the first 6 integer parameters and the +// first 8 floating point parameters in registers (rdi, rsi, rdx, rcx, +// r8, r9 and xmm0-xmm7), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. + +const uint32_t GPR_COUNT = 6; +const uint32_t FPR_COUNT = 8; + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gpregs[]' contains the arguments passed in integer registers +// - 'fpregs[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gpregs, double *fpregs) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t nr_gpr = 1; // skip one GPR register for 'that' + uint32_t nr_fpr = 0; + uint64_t value; + + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (nr_gpr < GPR_COUNT) + nr_gpr++; + else + ap++; + } + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.d = *(double*)ap++; + continue; + } + if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.f = *(float*)ap++; + continue; + } + if (nr_gpr < GPR_COUNT) + value = gpregs[nr_gpr++]; + else + value = *ap++; + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*) value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) value; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) value; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) value; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) value; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) value; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) value; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) value; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) value; break; + // Cast to uint8_t first, to remove garbage on upper 56 bits. + case nsXPTType::T_BOOL: dp->val.b = (bool)(uint8_t) value; break; + case nsXPTType::T_CHAR: dp->val.c = (char) value; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) value; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t) methodIndex, info, + paramBuffer); + + return result; +} + +// Linux/x86-64 uses gcc >= 3.1 +// We don't include .cfi_startproc/endproc directives for the individual stubs +// because there's no extra CFI bits to define beyond the default CIE. +#define STUB_ENTRY(n) \ +asm(".section \".text\"\n\t" \ + ".align 2\n\t" \ + ".if " #n " < 10\n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + ".hidden _ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + ".type _ZN14nsXPTCStubBase5Stub" #n "Ev,@function\n" \ + "_ZN14nsXPTCStubBase5Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + ".hidden _ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + ".type _ZN14nsXPTCStubBase6Stub" #n "Ev,@function\n" \ + "_ZN14nsXPTCStubBase6Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 1000\n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + ".hidden _ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + ".type _ZN14nsXPTCStubBase7Stub" #n "Ev,@function\n" \ + "_ZN14nsXPTCStubBase7Stub" #n "Ev:\n\t" \ + ".else\n\t" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n\t" \ + ".endif\n\t" \ + "movl $" #n ", %eax\n\t" \ + "jmp SharedStub\n\t" \ + ".if " #n " < 10\n\t" \ + ".size _ZN14nsXPTCStubBase5Stub" #n "Ev,.-_ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".size _ZN14nsXPTCStubBase6Stub" #n "Ev,.-_ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + ".else\n\t" \ + ".size _ZN14nsXPTCStubBase7Stub" #n "Ev,.-_ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + ".endif"); + +// static nsresult SharedStub(uint32_t methodIndex) +asm(".section \".text\"\n\t" + ".align 2\n\t" + ".type SharedStub,@function\n\t" + "SharedStub:\n\t" + ".cfi_startproc\n\t" + // make room for gpregs (48), fpregs (64) + "pushq %rbp\n\t" + ".cfi_def_cfa_offset 16\n\t" + ".cfi_offset 6, -16\n\t" + "movq %rsp,%rbp\n\t" + ".cfi_def_cfa_register 6\n\t" + "subq $112,%rsp\n\t" + // save GP registers + "movq %rdi,-112(%rbp)\n\t" + "movq %rsi,-104(%rbp)\n\t" + "movq %rdx, -96(%rbp)\n\t" + "movq %rcx, -88(%rbp)\n\t" + "movq %r8 , -80(%rbp)\n\t" + "movq %r9 , -72(%rbp)\n\t" + ".cfi_offset 5, -24\n\t" // rdi + ".cfi_offset 4, -32\n\t" // rsi + ".cfi_offset 1, -40\n\t" // rdx + ".cfi_offset 2, -48\n\t" // rcx + ".cfi_offset 8, -56\n\t" // r8 + ".cfi_offset 9, -64\n\t" // r9 + "leaq -112(%rbp),%rcx\n\t" + // save FP registers + "movsd %xmm0,-64(%rbp)\n\t" + "movsd %xmm1,-56(%rbp)\n\t" + "movsd %xmm2,-48(%rbp)\n\t" + "movsd %xmm3,-40(%rbp)\n\t" + "movsd %xmm4,-32(%rbp)\n\t" + "movsd %xmm5,-24(%rbp)\n\t" + "movsd %xmm6,-16(%rbp)\n\t" + "movsd %xmm7, -8(%rbp)\n\t" + "leaq -64(%rbp),%r8\n\t" + // rdi has the 'self' pointer already + "movl %eax,%esi\n\t" + "leaq 16(%rbp),%rdx\n\t" + "call PrepareAndDispatch@plt\n\t" + "leave\n\t" + ".cfi_def_cfa 7, 8\n\t" + "ret\n\t" + ".cfi_endproc\n\t" + ".size SharedStub,.-SharedStub"); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/win32/moz.build b/xpcom/reflect/xptcall/md/win32/moz.build new file mode 100644 index 0000000000..8c169135f4 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/moz.build @@ -0,0 +1,53 @@ +# -*- 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/. + +if CONFIG["TARGET_CPU"] == "x86_64": + if CONFIG["CC_TYPE"] in ("clang", "gcc"): + SOURCES += [ + "xptcinvoke_x86_64.cpp", + "xptcstubs_x86_64_gnu.cpp", + ] + SOURCES += ["xptcinvoke_asm_x86_64_gnu.s"] + else: + SOURCES += ["xptcinvoke_x86_64.cpp", "xptcstubs_x86_64.cpp"] + SOURCES += ["xptcinvoke_asm_x86_64.asm", "xptcstubs_asm_x86_64.asm"] +elif CONFIG["TARGET_CPU"] == "x86": + if CONFIG["CC_TYPE"] in ("clang", "gcc"): + SOURCES += [ + "xptcinvoke_x86_gnu.cpp", + "xptcstubs.cpp", + ] + else: + SOURCES += [ + "xptcinvoke.cpp", + "xptcinvoke_asm_x86_msvc.asm", + "xptcstubs.cpp", + ] + SOURCES["xptcinvoke_asm_x86_msvc.asm"].flags += ["-safeseh"] +elif CONFIG["TARGET_CPU"] == "aarch64": + SOURCES += [ + "xptcinvoke_aarch64.cpp", + "xptcstubs_aarch64.cpp", + ] + asm_files = [ + "xptcinvoke_asm_aarch64.asm", + "xptcstubs_asm_aarch64.asm", + ] + + # make gets confused if the srcdir and objdir files have the same name, so + # we generate different names for the objdir files + for src in asm_files: + obj = src.replace("_asm_aarch64", "") + GeneratedFile( + obj, script="preprocess.py", entry_point="preprocess", inputs=[src] + ) + SOURCES += ["!%s" % obj] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "../..", +] diff --git a/xpcom/reflect/xptcall/md/win32/preprocess.py b/xpcom/reflect/xptcall/md/win32/preprocess.py new file mode 100644 index 0000000000..a2f3232c34 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/preprocess.py @@ -0,0 +1,44 @@ +# -*- 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/. + +import errno +import os +import shlex +import subprocess +import sys +import tempfile + +import buildconfig +from mozfile import which + + +def preprocess(out, asm_file): + cxx = shlex.split(buildconfig.substs["CXX"]) + if not os.path.exists(cxx[0]): + tool = cxx[0] + cxx[0] = which(tool) + if not cxx[0]: + raise OSError(errno.ENOENT, "Could not find {} on PATH.".format(tool)) + + cppflags = buildconfig.substs["OS_CPPFLAGS"] + + # subprocess.Popen(stdout=) only accepts actual file objects, which `out`, + # above, is not. So fake a temporary file to write to. + (outhandle, tmpout) = tempfile.mkstemp(suffix=".cpp") + + # #line directives will confuse armasm64, and /EP is the only way to + # preprocess without emitting #line directives. + command = cxx + ["/EP"] + cppflags + ["/TP", asm_file] + with open(tmpout, "wb") as t: + result = subprocess.Popen(command, stdout=t).wait() + if result != 0: + sys.exit(result) + + with open(tmpout, "rb") as t: + out.write(t.read()) + + os.close(outhandle) + os.remove(tmpout) diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp b/xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp new file mode 100644 index 0000000000..e4e63edc4b --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#ifndef WIN32 +#error "This code is for Win32 only" +#endif + +extern "C" void __fastcall +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(; paramCount > 0; paramCount--, d++, s++) + { + if(s->IsIndirect()) + { + *((void**)d) = &s->val; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_aarch64.cpp b/xpcom/reflect/xptcall/md/win32/xptcinvoke_aarch64.cpp new file mode 100644 index 0000000000..1c1860fbeb --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_aarch64.cpp @@ -0,0 +1,141 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(_MSC_VER) && !defined(_M_ARM64) +#error "This code is for AArch64 Windows only" +#endif + +/* "Procedure Call Standard for the ARM 64-bit Architecture" document, sections + * "5.4 Parameter Passing" and "6.1.2 Procedure Calling" contain all the + * needed information. + * + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf + * + * Windows follows this document, see: + * + * https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions + */ + +/* + * Allocation of integer function arguments initially to registers r1-r7 + * and then to stack. Handling of 'that' argument which goes to register r0 + * is handled separately and does not belong here. + * + * 'ireg_args' - pointer to the current position in the buffer, + * corresponding to the register arguments + * 'stack_args' - pointer to the current position in the buffer, + * corresponding to the arguments on stack + * 'end' - pointer to the end of the registers argument + * buffer. + */ +static inline void alloc_word(uint64_t* &ireg_args, + uint64_t* &stack_args, + uint64_t* end, + uint64_t data) +{ + if (ireg_args < end) { + *ireg_args = data; + ireg_args++; + } else { + *stack_args = data; + stack_args++; + } +} + +static inline void alloc_double(double* &freg_args, + uint64_t* &stack_args, + double* end, + double data) +{ + if (freg_args < end) { + *freg_args = data; + freg_args++; + } else { + memcpy(stack_args, &data, sizeof(data)); + stack_args++; + } +} + +static inline void alloc_float(double* &freg_args, + uint64_t* &stack_args, + double* end, + float data) +{ + if (freg_args < end) { + memcpy(freg_args, &data, sizeof(data)); + freg_args++; + } else { + memcpy(stack_args, &data, sizeof(data)); + stack_args++; + } +} + + +extern "C" void +invoke_copy_to_stack(uint64_t* stk, uint64_t *end, + uint32_t paramCount, nsXPTCVariant* s) +{ + uint64_t *ireg_args = stk; + uint64_t *ireg_end = ireg_args + 8; + double *freg_args = (double *)ireg_end; + double *freg_end = freg_args + 8; + uint64_t *stack_args = (uint64_t *)freg_end; + + // leave room for 'that' argument in x0 + ++ireg_args; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + uint64_t word; + + if (s->IsIndirect()) { + word = (uint64_t)&s->val; + } else { + // According to the ABI, integral types that are smaller than 8 + // bytes are to be passed in 8-byte registers or 8-byte stack + // slots. + switch (s->type) { + case nsXPTType::T_FLOAT: + alloc_float(freg_args, stack_args, freg_end, s->val.f); + continue; + case nsXPTType::T_DOUBLE: + alloc_double(freg_args, stack_args, freg_end, s->val.d); + continue; + case nsXPTType::T_I8: word = s->val.i8; break; + case nsXPTType::T_I16: word = s->val.i16; break; + case nsXPTType::T_I32: word = s->val.i32; break; + case nsXPTType::T_I64: word = s->val.i64; break; + case nsXPTType::T_U8: word = s->val.u8; break; + case nsXPTType::T_U16: word = s->val.u16; break; + case nsXPTType::T_U32: word = s->val.u32; break; + case nsXPTType::T_U64: word = s->val.u64; break; + case nsXPTType::T_BOOL: word = s->val.b; break; + case nsXPTType::T_CHAR: word = s->val.c; break; + case nsXPTType::T_WCHAR: word = s->val.wc; break; + default: + // all the others are plain pointer types + word = reinterpret_cast<uint64_t>(s->val.p); + break; + } + } + + alloc_word(ireg_args, stack_args, ireg_end, word); + } +} + +extern "C" nsresult +XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +extern "C" +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return XPTC__InvokebyIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_aarch64.asm b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_aarch64.asm new file mode 100644 index 0000000000..21f80f4cb3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_aarch64.asm @@ -0,0 +1,66 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.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 "ksarm64.h" + + IMPORT |invoke_copy_to_stack| + TEXTAREA + +; +;XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, +; uint32_t paramCount, nsXPTCVariant* params) +; + + NESTED_ENTRY XPTC__InvokebyIndex + + ; set up frame + PROLOG_SAVE_REG_PAIR fp, lr, #-32! + PROLOG_SAVE_REG_PAIR x19, x20, #16 + + ; save methodIndex across function calls + mov w20, w1 + + ; end of stack area passed to invoke_copy_to_stack + mov x1, sp + + ; assume 8 bytes of stack for each argument with 16-byte alignment + add w19, w2, #1 + and w19, w19, #0xfffffffe + sub sp, sp, w19, uxth #3 + + ; temporary place to store args passed in r0-r7,v0-v7 + sub sp, sp, #128 + + ; save 'that' on stack + str x0, [sp] + + ; start of stack area passed to invoke_copy_to_stack + mov x0, sp + bl invoke_copy_to_stack + + ; load arguments passed in r0-r7 + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp],#64 + + ; load arguments passed in v0-v7 + ldp d6, d7, [sp, #48] + ldp d4, d5, [sp, #32] + ldp d2, d3, [sp, #16] + ldp d0, d1, [sp],#64 + + ; call the method + ldr x16, [x0] + add x16, x16, w20, uxth #3 + ldr x16, [x16] + blr x16 + + EPILOG_STACK_RESTORE + EPILOG_RESTORE_REG_PAIR x19, x20, #16 + EPILOG_RESTORE_REG_PAIR x29, x30, #32! + EPILOG_RETURN + + NESTED_END XPTC__InvokebyIndex + END diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64.asm b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64.asm new file mode 100644 index 0000000000..bf7c2ef0c3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64.asm @@ -0,0 +1,107 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +extrn invoke_copy_to_stack:PROC + + +.CODE + +; +;XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, +; uint32_t paramCount, nsXPTCVariant* params) +; + +XPTC__InvokebyIndex PROC FRAME + + ; store register parameters + + mov qword ptr [rsp+32], r9 ; params + mov dword ptr [rsp+24], r8d ; paramCount + mov dword ptr [rsp+16], edx ; methodIndex + mov qword ptr [rsp+8], rcx ; that + + push rbp + .PUSHREG rbp + mov rbp, rsp ; store current RSP to RBP + .SETFRAME rbp, 0 + .ENDPROLOG + + sub rsp, 32 + + ; maybe we don't have any parameters to copy + + test r8d, r8d + jz noparams + + ; + ; Build stack for stdcall + ; + + ; 1st parameter is space for parameters + + mov eax, r8d + or eax, 1 + shl rax, 3 ; *= 8 + sub rsp, rax + mov rcx, rsp + + ; 2nd parameter is parameter count + + mov edx, r8d + + ; 3rd parameter is params + + mov r8, r9 + + sub rsp, 40 + call invoke_copy_to_stack ; rcx = d + ; edx = paramCount + ; r8 = s + add rsp, 32 + + ; Current stack is the following. + ; + ; 0h: [space (for this)] + ; 8h: [1st parameter] + ; 10h: [2nd parameter] + ; 18h: [3rd parameter] + ; 20h: [4th parameter] + ; ... + ; + ; On Win64 ABI, the first 4 parameters are passed using registers, + ; and others are on stack. + + ; 1st, 2nd and 3rd arguments are passed via registers + + mov rdx, qword ptr [rsp+8] ; 1st parameter + movsd xmm1, qword ptr [rsp+8] ; for double + + mov r8, qword ptr [rsp+16] ; 2nd parameter + movsd xmm2, qword ptr [rsp+16] ; for double + + mov r9, qword ptr [rsp+24] ; 3rd parameter + movsd xmm3, qword ptr [rsp+24] ; for double + + ; rcx register is this + + mov rcx, qword ptr [rbp+8+8] ; that + +noparams: + + ; calculate call address + + mov r11, qword ptr [rcx] + mov eax, dword ptr [rbp+16+8] ; methodIndex + + call qword ptr [r11+rax*8] ; stdcall, i.e. callee cleans up stack. + + mov rsp, rbp + pop rbp + + ret + +XPTC__InvokebyIndex ENDP + + +END diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s new file mode 100644 index 0000000000..5cc19b4c57 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s @@ -0,0 +1,110 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.extern invoke_copy_to_stack + + +.text +.intel_syntax noprefix + +# +#_XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.globl XPTC__InvokebyIndex +.def XPTC__InvokebyIndex + .scl 2 + .type 46 +.endef +XPTC__InvokebyIndex: + + # store register parameters + + mov qword ptr [rsp+32], r9 # params + mov dword ptr [rsp+24], r8d # paramCount + mov dword ptr [rsp+16], edx # methodIndex + mov qword ptr [rsp+8], rcx # that + + push rbp + # .PUSHREG rbp + mov rbp, rsp # store current RSP to RBP + # .SETFRAME rbp, 0 + # .ENDPROLOG + + sub rsp, 32 + + # maybe we don't have any parameters to copy + + test r8d, r8d + jz noparams + + # + # Build stack for stdcall + # + + # 1st parameter is space for parameters + + mov eax, r8d + or eax, 1 + shl rax, 3 # *= 8 + sub rsp, rax + mov rcx, rsp + + # 2nd parameter is parameter count + + mov edx, r8d + + # 3rd parameter is params + + mov r8, r9 + + sub rsp, 40 + call invoke_copy_to_stack # rcx = d + # edx = paramCount + # r8 = s + add rsp, 32 + + # Current stack is the following. + # + # 0h: [space (for this)] + # 8h: [1st parameter] + # 10h: [2nd parameter] + # 18h: [3rd parameter] + # 20h: [4th parameter] + # ... + # + # On Win64 ABI, the first 4 parameters are passed using registers, + # and others are on stack. + + # 1st, 2nd and 3rd arguments are passed via registers + + mov rdx, qword ptr [rsp+8] # 1st parameter + movsd xmm1, qword ptr [rsp+8] # for double + + mov r8, qword ptr [rsp+16] # 2nd parameter + movsd xmm2, qword ptr [rsp+16] # for double + + mov r9, qword ptr [rsp+24] # 3rd parameter + movsd xmm3, qword ptr [rsp+24] # for double + + # rcx register is this + + mov rcx, qword ptr [rbp+8+8] # that + +noparams: + + # calculate call address + + mov r11, qword ptr [rcx] + mov eax, dword ptr [rbp+16+8] # methodIndex + + call qword ptr [r11+rax*8] # stdcall, i.e. callee cleans up stack. + + mov rsp, rbp + pop rbp + + ret + + diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm new file mode 100644 index 0000000000..f3b7a1826d --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm @@ -0,0 +1,63 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + + TITLE xptcinvoke_asm_x86_msvc.asm + .686P + .model flat + +PUBLIC _NS_InvokeByIndex +EXTRN @invoke_copy_to_stack@12:PROC + +; +; extern "C" nsresult __cdecl +; NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +; uint32_t paramCount, nsXPTCVariant* params) +; + +_TEXT SEGMENT +_NS_InvokeByIndex PROC + + ; Build frame + push ebp + mov ebp, esp + + ; Save paramCount for later + mov edx, dword ptr [ebp+16] + + ; Do we have any parameters? + test edx, edx + jz noparams + + ; Build call for copy_to_stack, which is __fastcall + + ; Allocate space for parameters. 8 is the biggest size + ; any parameter can be, so assume that all our parameters + ; are that large. + mov eax, edx + shl eax, 3 + sub esp, eax + + mov ecx, esp + push dword ptr [ebp+20] + call @invoke_copy_to_stack@12 +noparams: + ; Push the `this' parameter for the call. + mov ecx, dword ptr [ebp+8] + push ecx + + ; Load the vtable. + mov edx, dword ptr [ecx] + + ; Call the vtable index at `methodIndex'. + mov eax, dword ptr [ebp+12] + call dword ptr [edx+eax*4] + + ; Reset and return. + mov esp, ebp + pop ebp + ret +_NS_InvokeByIndex ENDP +_TEXT ENDS + +END diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp new file mode 100644 index 0000000000..47c891acea --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +extern "C" void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(; paramCount > 0; paramCount--, d++, s++) + { + if(s->IsIndirect()) + { + *((void**)d) = &s->val; + continue; + } + + /* + * AMD64 platform uses 8 bytes align. + */ + + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" nsresult +XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +extern "C" +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return XPTC__InvokebyIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_gnu.cpp b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_gnu.cpp new file mode 100644 index 0000000000..7576919b5a --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_gnu.cpp @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +extern "C" { +void __attribute__ ((__used__)) __attribute__ ((regparm(3))) +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint32_t* d) +{ + for(uint32_t i = paramCount; i >0; i--, d++, s++) + { + if(s->IsIndirect()) + { + *((void**)d) = &s->val; + continue; + } + + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} +} // extern "C" + +/* + EXPORT_XPCOM_API(nsresult) + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + + Each param takes at most two 4-byte words. + It doesn't matter if we push too many words, and calculating the exact + amount takes time. + + that = ebp + 0x08 + methodIndex = ebp + 0x0c + paramCount = ebp + 0x10 + params = ebp + 0x14 + +*/ + +__asm__ ( + ".text\n\t" +/* alignment here seems unimportant here; this was 16, now it's 2 which + is what xptcstubs uses. */ + ".align 2\n\t" + ".globl _NS_InvokeByIndex\n\t" + "_NS_InvokeByIndex:\n\t" + "pushl %ebp\n\t" + "movl %esp, %ebp\n\t" + "movl 0x10(%ebp), %eax\n\t" + "leal 0(,%eax,8),%edx\n\t" + + /* set up call frame for method. */ + "subl %edx, %esp\n\t" /* make room for params. */ +/* Align to maximum x86 data size: 128 bits == 16 bytes == XMM register size. + * This is to avoid protection faults where SSE+ alignment of stack pointer + * is assumed and required, e.g. by GCC4's -ftree-vectorize option. + */ + "andl $0xfffffff0, %esp\n\t" /* drop(?) stack ptr to 128-bit align */ +/* $esp should be aligned to a 16-byte boundary here (note we include an + * additional 4 bytes in a later push instruction). This will ensure $ebp + * in the function called below is aligned to a 0x8 boundary. SSE instructions + * like movapd/movdqa expect memory operand to be aligned on a 16-byte + * boundary. The GCC compiler will generate the memory operand using $ebp + * with an 8-byte offset. + */ + "subl $0xc, %esp\n\t" /* lower again; push/call below will re-align */ + "movl %esp, %ecx\n\t" /* ecx = d */ + "movl 8(%ebp), %edx\n\t" /* edx = this */ + "pushl %edx\n\t" /* push this. esp % 16 == 0 */ + + "movl 0x14(%ebp), %edx\n\t" + "call _invoke_copy_to_stack\n\t" + "movl 0x08(%ebp), %ecx\n\t" /* 'that' */ + "movl (%ecx), %edx\n\t" + "movl 0x0c(%ebp), %eax\n\t" /* function index */ + "leal (%edx,%eax,4), %edx\n\t" + "call *(%edx)\n\t" + "movl %ebp, %esp\n\t" + "popl %ebp\n\t" + "ret\n" + ".section .drectve\n\t" + ".ascii \" -export:NS_InvokeByIndex\"\n\t" + ".text\n\t" +); diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp b/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp new file mode 100644 index 0000000000..d669aafddf --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#ifndef WIN32 +#error "This code is for Win32 only" +#endif + +extern "C" { + +static +nsresult __stdcall +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint32_t* args, uint32_t* stackBytesToPop) +{ + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + + // If anything fails before stackBytesToPop can be set then + // the failure is completely catastrophic! + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) + ap++; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + *stackBytesToPop = ((uint32_t)ap) - ((uint32_t)args); + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +} // extern "C" + +static MOZ_NAKED void SharedStub(void) +{ + __asm { + push ebp // set up simple stack frame + mov ebp, esp // stack has: ebp/vtbl_index/retaddr/this/args + push ecx // make room for a ptr + lea eax, [ebp-4] // pointer to stackBytesToPop + push eax + lea eax, [ebp+12] // pointer to args + push eax + push ecx // vtbl_index + mov eax, [ebp+8] // this + push eax + call PrepareAndDispatch + mov edx, [ebp+4] // return address + mov ecx, [ebp-4] // stackBytesToPop + add ecx, 8 // for 'this' and return address + mov esp, ebp + pop ebp + add esp, ecx // fix up stack pointer + jmp edx // simulate __stdcall return + } +} + +// these macros get expanded (many times) in the file #included below +#define STUB_ENTRY(n) \ +MOZ_NAKED nsresult __stdcall nsXPTCStubBase::Stub##n() \ +{ __asm mov ecx, n __asm jmp SharedStub } + +#define SENTINEL_ENTRY(n) \ +nsresult __stdcall nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4035) // OK to have no return value +#endif +#include "xptcstubsdef.inc" +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +void +xptc_dummy() +{ +} diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_aarch64.cpp b/xpcom/reflect/xptcall/md/win32/xptcstubs_aarch64.cpp new file mode 100644 index 0000000000..57d78c5a7a --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_aarch64.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +/* + * This is for AArch64 ABI + * + * When we're called, the "gp" registers are stored in gprData and + * the "fp" registers are stored in fprData. Each array has 8 regs + * but first reg in gprData is a placeholder for 'self'. + */ +extern "C" nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args, + uint64_t *gprData, double *fprData) +{ +#define PARAM_GPR_COUNT 8 +#define PARAM_FPR_COUNT 8 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + uint32_t paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t next_gpr = 1; // skip first arg which is 'self' + uint32_t next_fpr = 0; + for (uint32_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (next_gpr < PARAM_GPR_COUNT) + next_gpr++; + else + ap++; + } + + if (param.IsOut() || !type.IsArithmetic()) { + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.p = (void*)gprData[next_gpr++]; + } else { + dp->val.p = (void*)*ap++; + } + continue; + } + + switch (type) { + case nsXPTType::T_I8: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i8 = (int8_t)gprData[next_gpr++]; + } else { + dp->val.i8 = (int8_t)*ap++; + } + break; + + case nsXPTType::T_I16: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i16 = (int16_t)gprData[next_gpr++]; + } else { + dp->val.i16 = (int16_t)*ap++; + } + break; + + case nsXPTType::T_I32: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i32 = (int32_t)gprData[next_gpr++]; + } else { + dp->val.i32 = (int32_t)*ap++; + } + break; + + case nsXPTType::T_I64: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i64 = (int64_t)gprData[next_gpr++]; + } else { + dp->val.i64 = (int64_t)*ap++; + } + break; + + case nsXPTType::T_U8: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u8 = (uint8_t)gprData[next_gpr++]; + } else { + dp->val.u8 = (uint8_t)*ap++; + } + break; + + case nsXPTType::T_U16: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u16 = (uint16_t)gprData[next_gpr++]; + } else { + dp->val.u16 = (uint16_t)*ap++; + } + break; + + case nsXPTType::T_U32: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u32 = (uint32_t)gprData[next_gpr++]; + } else { + dp->val.u32 = (uint32_t)*ap++; + } + break; + + case nsXPTType::T_U64: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u64 = (uint64_t)gprData[next_gpr++]; + } else { + dp->val.u64 = (uint64_t)*ap++; + } + break; + + case nsXPTType::T_FLOAT: + if (next_fpr < PARAM_FPR_COUNT) { + memcpy(&dp->val.f, &fprData[next_fpr++], sizeof(dp->val.f)); + } else { + memcpy(&dp->val.f, ap++, sizeof(dp->val.f)); + } + break; + + case nsXPTType::T_DOUBLE: + if (next_fpr < PARAM_FPR_COUNT) { + memcpy(&dp->val.d, &fprData[next_fpr++], sizeof(dp->val.d)); + } else { + memcpy(&dp->val.d, ap++, sizeof(dp->val.d)); + } + break; + + case nsXPTType::T_BOOL: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.b = (bool)(uint8_t)gprData[next_gpr++]; + } else { + dp->val.b = (bool)(uint8_t)*ap++; + } + break; + + case nsXPTType::T_CHAR: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.c = (char)gprData[next_gpr++]; + } else { + dp->val.c = (char)*ap++; + } + break; + + case nsXPTType::T_WCHAR: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.wc = (wchar_t)gprData[next_gpr++]; + } else { + dp->val.wc = (wchar_t)*ap++; + } + break; + + default: + NS_ASSERTION(0, "bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) /* defined in the assembly file */ + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_aarch64.asm b/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_aarch64.asm new file mode 100644 index 0000000000..28b3d8866f --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_aarch64.asm @@ -0,0 +1,306 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.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 "ksarm64.h" + +NGPREGS EQU 8 +NFPREGS EQU 8 + + IMPORT |PrepareAndDispatch| + + TEXTAREA + + NESTED_ENTRY SharedStub + + PROLOG_SAVE_REG_PAIR x29, x30, #-16! + + sub sp, sp, #128 ; 8*(NGPREGS+NFPREGS) + stp x0, x1, [sp, #64+(0*8)] + stp x2, x3, [sp, #64+(2*8)] + stp x4, x5, [sp, #64+(4*8)] + stp x6, x7, [sp, #64+(6*8)] + stp d0, d1, [sp, #(0*8)] + stp d2, d3, [sp, #(2*8)] + stp d4, d5, [sp, #(4*8)] + stp d6, d7, [sp, #(6*8)] + + ; methodIndex passed from stub + mov w1, w17 + + add x2, sp, #144 ; 16+(8*(NGPREGS+NFPREGS)) + add x3, sp, #64 ; 8*NFPREGS + add x4, sp, #0 + + bl PrepareAndDispatch + + EPILOG_STACK_RESTORE + EPILOG_RESTORE_REG_PAIR x29, x30, #16! + EPILOG_RETURN + + NESTED_END SharedStub + + + MACRO + STUBENTRY $functionname, $paramcount + EXPORT |$functionname| + TEXTAREA + + LEAF_ENTRY |$functionname| + + mov w17, $paramcount + b SharedStub + + LEAF_END |$functionname| + + MEND + + STUBENTRY ?Stub3@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 3 + STUBENTRY ?Stub4@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 4 + STUBENTRY ?Stub5@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 5 + STUBENTRY ?Stub6@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 6 + STUBENTRY ?Stub7@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 7 + STUBENTRY ?Stub8@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 8 + STUBENTRY ?Stub9@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 9 + STUBENTRY ?Stub10@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 10 + STUBENTRY ?Stub11@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 11 + STUBENTRY ?Stub12@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 12 + STUBENTRY ?Stub13@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 13 + STUBENTRY ?Stub14@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 14 + STUBENTRY ?Stub15@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 15 + STUBENTRY ?Stub16@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 16 + STUBENTRY ?Stub17@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 17 + STUBENTRY ?Stub18@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 18 + STUBENTRY ?Stub19@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 19 + STUBENTRY ?Stub20@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 20 + STUBENTRY ?Stub21@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 21 + STUBENTRY ?Stub22@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 22 + STUBENTRY ?Stub23@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 23 + STUBENTRY ?Stub24@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 24 + STUBENTRY ?Stub25@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 25 + STUBENTRY ?Stub26@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 26 + STUBENTRY ?Stub27@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 27 + STUBENTRY ?Stub28@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 28 + STUBENTRY ?Stub29@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 29 + STUBENTRY ?Stub30@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 30 + STUBENTRY ?Stub31@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 31 + STUBENTRY ?Stub32@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 32 + STUBENTRY ?Stub33@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 33 + STUBENTRY ?Stub34@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 34 + STUBENTRY ?Stub35@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 35 + STUBENTRY ?Stub36@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 36 + STUBENTRY ?Stub37@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 37 + STUBENTRY ?Stub38@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 38 + STUBENTRY ?Stub39@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 39 + STUBENTRY ?Stub40@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 40 + STUBENTRY ?Stub41@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 41 + STUBENTRY ?Stub42@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 42 + STUBENTRY ?Stub43@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 43 + STUBENTRY ?Stub44@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 44 + STUBENTRY ?Stub45@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 45 + STUBENTRY ?Stub46@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 46 + STUBENTRY ?Stub47@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 47 + STUBENTRY ?Stub48@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 48 + STUBENTRY ?Stub49@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 49 + STUBENTRY ?Stub50@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 50 + STUBENTRY ?Stub51@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 51 + STUBENTRY ?Stub52@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 52 + STUBENTRY ?Stub53@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 53 + STUBENTRY ?Stub54@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 54 + STUBENTRY ?Stub55@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 55 + STUBENTRY ?Stub56@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 56 + STUBENTRY ?Stub57@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 57 + STUBENTRY ?Stub58@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 58 + STUBENTRY ?Stub59@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 59 + STUBENTRY ?Stub60@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 60 + STUBENTRY ?Stub61@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 61 + STUBENTRY ?Stub62@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 62 + STUBENTRY ?Stub63@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 63 + STUBENTRY ?Stub64@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 64 + STUBENTRY ?Stub65@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 65 + STUBENTRY ?Stub66@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 66 + STUBENTRY ?Stub67@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 67 + STUBENTRY ?Stub68@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 68 + STUBENTRY ?Stub69@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 69 + STUBENTRY ?Stub70@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 70 + STUBENTRY ?Stub71@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 71 + STUBENTRY ?Stub72@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 72 + STUBENTRY ?Stub73@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 73 + STUBENTRY ?Stub74@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 74 + STUBENTRY ?Stub75@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 75 + STUBENTRY ?Stub76@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 76 + STUBENTRY ?Stub77@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 77 + STUBENTRY ?Stub78@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 78 + STUBENTRY ?Stub79@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 79 + STUBENTRY ?Stub80@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 80 + STUBENTRY ?Stub81@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 81 + STUBENTRY ?Stub82@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 82 + STUBENTRY ?Stub83@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 83 + STUBENTRY ?Stub84@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 84 + STUBENTRY ?Stub85@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 85 + STUBENTRY ?Stub86@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 86 + STUBENTRY ?Stub87@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 87 + STUBENTRY ?Stub88@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 88 + STUBENTRY ?Stub89@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 89 + STUBENTRY ?Stub90@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 90 + STUBENTRY ?Stub91@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 91 + STUBENTRY ?Stub92@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 92 + STUBENTRY ?Stub93@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 93 + STUBENTRY ?Stub94@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 94 + STUBENTRY ?Stub95@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 95 + STUBENTRY ?Stub96@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 96 + STUBENTRY ?Stub97@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 97 + STUBENTRY ?Stub98@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 98 + STUBENTRY ?Stub99@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 99 + STUBENTRY ?Stub100@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 100 + STUBENTRY ?Stub101@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 101 + STUBENTRY ?Stub102@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 102 + STUBENTRY ?Stub103@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 103 + STUBENTRY ?Stub104@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 104 + STUBENTRY ?Stub105@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 105 + STUBENTRY ?Stub106@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 106 + STUBENTRY ?Stub107@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 107 + STUBENTRY ?Stub108@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 108 + STUBENTRY ?Stub109@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 109 + STUBENTRY ?Stub110@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 110 + STUBENTRY ?Stub111@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 111 + STUBENTRY ?Stub112@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 112 + STUBENTRY ?Stub113@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 113 + STUBENTRY ?Stub114@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 114 + STUBENTRY ?Stub115@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 115 + STUBENTRY ?Stub116@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 116 + STUBENTRY ?Stub117@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 117 + STUBENTRY ?Stub118@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 118 + STUBENTRY ?Stub119@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 119 + STUBENTRY ?Stub120@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 120 + STUBENTRY ?Stub121@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 121 + STUBENTRY ?Stub122@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 122 + STUBENTRY ?Stub123@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 123 + STUBENTRY ?Stub124@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 124 + STUBENTRY ?Stub125@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 125 + STUBENTRY ?Stub126@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 126 + STUBENTRY ?Stub127@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 127 + STUBENTRY ?Stub128@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 128 + STUBENTRY ?Stub129@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 129 + STUBENTRY ?Stub130@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 130 + STUBENTRY ?Stub131@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 131 + STUBENTRY ?Stub132@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 132 + STUBENTRY ?Stub133@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 133 + STUBENTRY ?Stub134@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 134 + STUBENTRY ?Stub135@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 135 + STUBENTRY ?Stub136@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 136 + STUBENTRY ?Stub137@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 137 + STUBENTRY ?Stub138@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 138 + STUBENTRY ?Stub139@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 139 + STUBENTRY ?Stub140@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 140 + STUBENTRY ?Stub141@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 141 + STUBENTRY ?Stub142@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 142 + STUBENTRY ?Stub143@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 143 + STUBENTRY ?Stub144@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 144 + STUBENTRY ?Stub145@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 145 + STUBENTRY ?Stub146@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 146 + STUBENTRY ?Stub147@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 147 + STUBENTRY ?Stub148@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 148 + STUBENTRY ?Stub149@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 149 + STUBENTRY ?Stub150@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 150 + STUBENTRY ?Stub151@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 151 + STUBENTRY ?Stub152@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 152 + STUBENTRY ?Stub153@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 153 + STUBENTRY ?Stub154@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 154 + STUBENTRY ?Stub155@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 155 + STUBENTRY ?Stub156@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 156 + STUBENTRY ?Stub157@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 157 + STUBENTRY ?Stub158@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 158 + STUBENTRY ?Stub159@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 159 + STUBENTRY ?Stub160@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 160 + STUBENTRY ?Stub161@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 161 + STUBENTRY ?Stub162@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 162 + STUBENTRY ?Stub163@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 163 + STUBENTRY ?Stub164@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 164 + STUBENTRY ?Stub165@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 165 + STUBENTRY ?Stub166@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 166 + STUBENTRY ?Stub167@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 167 + STUBENTRY ?Stub168@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 168 + STUBENTRY ?Stub169@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 169 + STUBENTRY ?Stub170@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 170 + STUBENTRY ?Stub171@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 171 + STUBENTRY ?Stub172@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 172 + STUBENTRY ?Stub173@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 173 + STUBENTRY ?Stub174@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 174 + STUBENTRY ?Stub175@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 175 + STUBENTRY ?Stub176@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 176 + STUBENTRY ?Stub177@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 177 + STUBENTRY ?Stub178@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 178 + STUBENTRY ?Stub179@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 179 + STUBENTRY ?Stub180@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 180 + STUBENTRY ?Stub181@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 181 + STUBENTRY ?Stub182@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 182 + STUBENTRY ?Stub183@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 183 + STUBENTRY ?Stub184@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 184 + STUBENTRY ?Stub185@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 185 + STUBENTRY ?Stub186@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 186 + STUBENTRY ?Stub187@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 187 + STUBENTRY ?Stub188@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 188 + STUBENTRY ?Stub189@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 189 + STUBENTRY ?Stub190@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 190 + STUBENTRY ?Stub191@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 191 + STUBENTRY ?Stub192@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 192 + STUBENTRY ?Stub193@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 193 + STUBENTRY ?Stub194@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 194 + STUBENTRY ?Stub195@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 195 + STUBENTRY ?Stub196@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 196 + STUBENTRY ?Stub197@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 197 + STUBENTRY ?Stub198@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 198 + STUBENTRY ?Stub199@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 199 + STUBENTRY ?Stub200@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 200 + STUBENTRY ?Stub201@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 201 + STUBENTRY ?Stub202@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 202 + STUBENTRY ?Stub203@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 203 + STUBENTRY ?Stub204@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 204 + STUBENTRY ?Stub205@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 205 + STUBENTRY ?Stub206@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 206 + STUBENTRY ?Stub207@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 207 + STUBENTRY ?Stub208@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 208 + STUBENTRY ?Stub209@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 209 + STUBENTRY ?Stub210@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 210 + STUBENTRY ?Stub211@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 211 + STUBENTRY ?Stub212@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 212 + STUBENTRY ?Stub213@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 213 + STUBENTRY ?Stub214@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 214 + STUBENTRY ?Stub215@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 215 + STUBENTRY ?Stub216@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 216 + STUBENTRY ?Stub217@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 217 + STUBENTRY ?Stub218@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 218 + STUBENTRY ?Stub219@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 219 + STUBENTRY ?Stub220@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 220 + STUBENTRY ?Stub221@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 221 + STUBENTRY ?Stub222@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 222 + STUBENTRY ?Stub223@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 223 + STUBENTRY ?Stub224@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 224 + STUBENTRY ?Stub225@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 225 + STUBENTRY ?Stub226@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 226 + STUBENTRY ?Stub227@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 227 + STUBENTRY ?Stub228@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 228 + STUBENTRY ?Stub229@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 229 + STUBENTRY ?Stub230@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 230 + STUBENTRY ?Stub231@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 231 + STUBENTRY ?Stub232@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 232 + STUBENTRY ?Stub233@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 233 + STUBENTRY ?Stub234@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 234 + STUBENTRY ?Stub235@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 235 + STUBENTRY ?Stub236@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 236 + STUBENTRY ?Stub237@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 237 + STUBENTRY ?Stub238@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 238 + STUBENTRY ?Stub239@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 239 + STUBENTRY ?Stub240@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 240 + STUBENTRY ?Stub241@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 241 + STUBENTRY ?Stub242@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 242 + STUBENTRY ?Stub243@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 243 + STUBENTRY ?Stub244@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 244 + STUBENTRY ?Stub245@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 245 + STUBENTRY ?Stub246@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 246 + STUBENTRY ?Stub247@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 247 + STUBENTRY ?Stub248@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 248 + STUBENTRY ?Stub249@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 249 + + END diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_x86_64.asm b/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_x86_64.asm new file mode 100644 index 0000000000..db2cc16bee --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_x86_64.asm @@ -0,0 +1,335 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +extrn PrepareAndDispatch:PROC + +.code + +SharedStub PROC FRAME + sub rsp, 104 + .ALLOCSTACK 104 + .ENDPROLOG + + ; rcx is this pointer. Need backup for optimized build + + mov qword ptr [rsp+88], rcx + + ; + ; fist 4 parameters (1st is "this" pointer) are passed in registers. + ; + + ; for floating value + + movsd qword ptr [rsp+64], xmm1 + movsd qword ptr [rsp+72], xmm2 + movsd qword ptr [rsp+80], xmm3 + + ; for integer value + + mov qword ptr [rsp+40], rdx + mov qword ptr [rsp+48], r8 + mov qword ptr [rsp+56], r9 + + ; + ; Call PrepareAndDispatch function + ; + + ; 5th parameter (floating parameters) of PrepareAndDispatch + + lea r9, qword ptr [rsp+64] + mov qword ptr [rsp+32], r9 + + ; 4th parameter (normal parameters) of PrepareAndDispatch + + lea r9, qword ptr [rsp+40] + + ; 3rd parameter (pointer to args on stack) + + lea r8, qword ptr [rsp+40+104] + + ; 2nd parameter (vtbl_index) + + mov rdx, r11 + + ; 1st parameter (this) (rcx) + + call PrepareAndDispatch + + ; restore rcx + + mov rcx, qword ptr [rsp+88] + + ; + ; clean up register + ; + + add rsp, 104+8 + + ; set return address + + mov rdx, qword ptr [rsp-8] + + ; simulate __stdcall return + + jmp rdx + +SharedStub ENDP + + +STUBENTRY MACRO functionname, paramcount +functionname PROC + mov r11, paramcount + jmp SharedStub +functionname ENDP +ENDM + + STUBENTRY ?Stub3@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 3 + STUBENTRY ?Stub4@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 4 + STUBENTRY ?Stub5@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 5 + STUBENTRY ?Stub6@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 6 + STUBENTRY ?Stub7@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 7 + STUBENTRY ?Stub8@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 8 + STUBENTRY ?Stub9@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 9 + STUBENTRY ?Stub10@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 10 + STUBENTRY ?Stub11@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 11 + STUBENTRY ?Stub12@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 12 + STUBENTRY ?Stub13@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 13 + STUBENTRY ?Stub14@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 14 + STUBENTRY ?Stub15@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 15 + STUBENTRY ?Stub16@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 16 + STUBENTRY ?Stub17@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 17 + STUBENTRY ?Stub18@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 18 + STUBENTRY ?Stub19@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 19 + STUBENTRY ?Stub20@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 20 + STUBENTRY ?Stub21@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 21 + STUBENTRY ?Stub22@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 22 + STUBENTRY ?Stub23@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 23 + STUBENTRY ?Stub24@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 24 + STUBENTRY ?Stub25@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 25 + STUBENTRY ?Stub26@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 26 + STUBENTRY ?Stub27@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 27 + STUBENTRY ?Stub28@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 28 + STUBENTRY ?Stub29@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 29 + STUBENTRY ?Stub30@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 30 + STUBENTRY ?Stub31@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 31 + STUBENTRY ?Stub32@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 32 + STUBENTRY ?Stub33@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 33 + STUBENTRY ?Stub34@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 34 + STUBENTRY ?Stub35@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 35 + STUBENTRY ?Stub36@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 36 + STUBENTRY ?Stub37@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 37 + STUBENTRY ?Stub38@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 38 + STUBENTRY ?Stub39@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 39 + STUBENTRY ?Stub40@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 40 + STUBENTRY ?Stub41@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 41 + STUBENTRY ?Stub42@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 42 + STUBENTRY ?Stub43@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 43 + STUBENTRY ?Stub44@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 44 + STUBENTRY ?Stub45@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 45 + STUBENTRY ?Stub46@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 46 + STUBENTRY ?Stub47@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 47 + STUBENTRY ?Stub48@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 48 + STUBENTRY ?Stub49@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 49 + STUBENTRY ?Stub50@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 50 + STUBENTRY ?Stub51@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 51 + STUBENTRY ?Stub52@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 52 + STUBENTRY ?Stub53@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 53 + STUBENTRY ?Stub54@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 54 + STUBENTRY ?Stub55@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 55 + STUBENTRY ?Stub56@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 56 + STUBENTRY ?Stub57@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 57 + STUBENTRY ?Stub58@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 58 + STUBENTRY ?Stub59@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 59 + STUBENTRY ?Stub60@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 60 + STUBENTRY ?Stub61@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 61 + STUBENTRY ?Stub62@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 62 + STUBENTRY ?Stub63@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 63 + STUBENTRY ?Stub64@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 64 + STUBENTRY ?Stub65@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 65 + STUBENTRY ?Stub66@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 66 + STUBENTRY ?Stub67@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 67 + STUBENTRY ?Stub68@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 68 + STUBENTRY ?Stub69@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 69 + STUBENTRY ?Stub70@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 70 + STUBENTRY ?Stub71@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 71 + STUBENTRY ?Stub72@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 72 + STUBENTRY ?Stub73@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 73 + STUBENTRY ?Stub74@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 74 + STUBENTRY ?Stub75@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 75 + STUBENTRY ?Stub76@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 76 + STUBENTRY ?Stub77@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 77 + STUBENTRY ?Stub78@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 78 + STUBENTRY ?Stub79@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 79 + STUBENTRY ?Stub80@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 80 + STUBENTRY ?Stub81@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 81 + STUBENTRY ?Stub82@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 82 + STUBENTRY ?Stub83@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 83 + STUBENTRY ?Stub84@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 84 + STUBENTRY ?Stub85@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 85 + STUBENTRY ?Stub86@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 86 + STUBENTRY ?Stub87@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 87 + STUBENTRY ?Stub88@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 88 + STUBENTRY ?Stub89@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 89 + STUBENTRY ?Stub90@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 90 + STUBENTRY ?Stub91@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 91 + STUBENTRY ?Stub92@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 92 + STUBENTRY ?Stub93@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 93 + STUBENTRY ?Stub94@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 94 + STUBENTRY ?Stub95@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 95 + STUBENTRY ?Stub96@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 96 + STUBENTRY ?Stub97@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 97 + STUBENTRY ?Stub98@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 98 + STUBENTRY ?Stub99@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 99 + STUBENTRY ?Stub100@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 100 + STUBENTRY ?Stub101@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 101 + STUBENTRY ?Stub102@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 102 + STUBENTRY ?Stub103@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 103 + STUBENTRY ?Stub104@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 104 + STUBENTRY ?Stub105@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 105 + STUBENTRY ?Stub106@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 106 + STUBENTRY ?Stub107@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 107 + STUBENTRY ?Stub108@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 108 + STUBENTRY ?Stub109@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 109 + STUBENTRY ?Stub110@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 110 + STUBENTRY ?Stub111@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 111 + STUBENTRY ?Stub112@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 112 + STUBENTRY ?Stub113@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 113 + STUBENTRY ?Stub114@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 114 + STUBENTRY ?Stub115@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 115 + STUBENTRY ?Stub116@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 116 + STUBENTRY ?Stub117@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 117 + STUBENTRY ?Stub118@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 118 + STUBENTRY ?Stub119@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 119 + STUBENTRY ?Stub120@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 120 + STUBENTRY ?Stub121@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 121 + STUBENTRY ?Stub122@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 122 + STUBENTRY ?Stub123@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 123 + STUBENTRY ?Stub124@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 124 + STUBENTRY ?Stub125@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 125 + STUBENTRY ?Stub126@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 126 + STUBENTRY ?Stub127@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 127 + STUBENTRY ?Stub128@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 128 + STUBENTRY ?Stub129@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 129 + STUBENTRY ?Stub130@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 130 + STUBENTRY ?Stub131@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 131 + STUBENTRY ?Stub132@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 132 + STUBENTRY ?Stub133@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 133 + STUBENTRY ?Stub134@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 134 + STUBENTRY ?Stub135@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 135 + STUBENTRY ?Stub136@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 136 + STUBENTRY ?Stub137@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 137 + STUBENTRY ?Stub138@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 138 + STUBENTRY ?Stub139@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 139 + STUBENTRY ?Stub140@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 140 + STUBENTRY ?Stub141@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 141 + STUBENTRY ?Stub142@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 142 + STUBENTRY ?Stub143@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 143 + STUBENTRY ?Stub144@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 144 + STUBENTRY ?Stub145@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 145 + STUBENTRY ?Stub146@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 146 + STUBENTRY ?Stub147@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 147 + STUBENTRY ?Stub148@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 148 + STUBENTRY ?Stub149@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 149 + STUBENTRY ?Stub150@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 150 + STUBENTRY ?Stub151@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 151 + STUBENTRY ?Stub152@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 152 + STUBENTRY ?Stub153@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 153 + STUBENTRY ?Stub154@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 154 + STUBENTRY ?Stub155@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 155 + STUBENTRY ?Stub156@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 156 + STUBENTRY ?Stub157@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 157 + STUBENTRY ?Stub158@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 158 + STUBENTRY ?Stub159@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 159 + STUBENTRY ?Stub160@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 160 + STUBENTRY ?Stub161@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 161 + STUBENTRY ?Stub162@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 162 + STUBENTRY ?Stub163@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 163 + STUBENTRY ?Stub164@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 164 + STUBENTRY ?Stub165@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 165 + STUBENTRY ?Stub166@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 166 + STUBENTRY ?Stub167@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 167 + STUBENTRY ?Stub168@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 168 + STUBENTRY ?Stub169@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 169 + STUBENTRY ?Stub170@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 170 + STUBENTRY ?Stub171@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 171 + STUBENTRY ?Stub172@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 172 + STUBENTRY ?Stub173@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 173 + STUBENTRY ?Stub174@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 174 + STUBENTRY ?Stub175@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 175 + STUBENTRY ?Stub176@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 176 + STUBENTRY ?Stub177@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 177 + STUBENTRY ?Stub178@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 178 + STUBENTRY ?Stub179@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 179 + STUBENTRY ?Stub180@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 180 + STUBENTRY ?Stub181@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 181 + STUBENTRY ?Stub182@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 182 + STUBENTRY ?Stub183@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 183 + STUBENTRY ?Stub184@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 184 + STUBENTRY ?Stub185@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 185 + STUBENTRY ?Stub186@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 186 + STUBENTRY ?Stub187@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 187 + STUBENTRY ?Stub188@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 188 + STUBENTRY ?Stub189@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 189 + STUBENTRY ?Stub190@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 190 + STUBENTRY ?Stub191@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 191 + STUBENTRY ?Stub192@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 192 + STUBENTRY ?Stub193@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 193 + STUBENTRY ?Stub194@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 194 + STUBENTRY ?Stub195@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 195 + STUBENTRY ?Stub196@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 196 + STUBENTRY ?Stub197@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 197 + STUBENTRY ?Stub198@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 198 + STUBENTRY ?Stub199@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 199 + STUBENTRY ?Stub200@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 200 + STUBENTRY ?Stub201@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 201 + STUBENTRY ?Stub202@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 202 + STUBENTRY ?Stub203@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 203 + STUBENTRY ?Stub204@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 204 + STUBENTRY ?Stub205@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 205 + STUBENTRY ?Stub206@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 206 + STUBENTRY ?Stub207@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 207 + STUBENTRY ?Stub208@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 208 + STUBENTRY ?Stub209@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 209 + STUBENTRY ?Stub210@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 210 + STUBENTRY ?Stub211@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 211 + STUBENTRY ?Stub212@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 212 + STUBENTRY ?Stub213@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 213 + STUBENTRY ?Stub214@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 214 + STUBENTRY ?Stub215@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 215 + STUBENTRY ?Stub216@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 216 + STUBENTRY ?Stub217@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 217 + STUBENTRY ?Stub218@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 218 + STUBENTRY ?Stub219@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 219 + STUBENTRY ?Stub220@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 220 + STUBENTRY ?Stub221@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 221 + STUBENTRY ?Stub222@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 222 + STUBENTRY ?Stub223@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 223 + STUBENTRY ?Stub224@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 224 + STUBENTRY ?Stub225@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 225 + STUBENTRY ?Stub226@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 226 + STUBENTRY ?Stub227@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 227 + STUBENTRY ?Stub228@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 228 + STUBENTRY ?Stub229@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 229 + STUBENTRY ?Stub230@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 230 + STUBENTRY ?Stub231@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 231 + STUBENTRY ?Stub232@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 232 + STUBENTRY ?Stub233@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 233 + STUBENTRY ?Stub234@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 234 + STUBENTRY ?Stub235@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 235 + STUBENTRY ?Stub236@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 236 + STUBENTRY ?Stub237@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 237 + STUBENTRY ?Stub238@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 238 + STUBENTRY ?Stub239@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 239 + STUBENTRY ?Stub240@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 240 + STUBENTRY ?Stub241@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 241 + STUBENTRY ?Stub242@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 242 + STUBENTRY ?Stub243@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 243 + STUBENTRY ?Stub244@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 244 + STUBENTRY ?Stub245@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 245 + STUBENTRY ?Stub246@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 246 + STUBENTRY ?Stub247@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 247 + STUBENTRY ?Stub248@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 248 + STUBENTRY ?Stub249@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 249 + +END diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64.cpp b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64.cpp new file mode 100644 index 0000000000..5f767a3988 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +/* + * This is for Windows XP 64-Bit Edition / Server 2003 for AMD64 or later. + */ + +extern "C" nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args, + uint64_t *gprData, double *fprData) +{ +// +// "this" pointer is first parameter, so parameter count is 3. +// +#define PARAM_GPR_COUNT 3 +#define PARAM_FPR_COUNT 3 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t iCount = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (iCount < PARAM_GPR_COUNT) + iCount++; + else + ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*)gprData[iCount++]; + else + dp->val.p = (void*)*ap++; + + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8: + if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t)gprData[iCount++]; + else + dp->val.i8 = *((int8_t*)ap++); + break; + + case nsXPTType::T_I16: + if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t)gprData[iCount++]; + else + dp->val.i16 = *((int16_t*)ap++); + break; + + case nsXPTType::T_I32: + if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t)gprData[iCount++]; + else + dp->val.i32 = *((int32_t*)ap++); + break; + + case nsXPTType::T_I64: + if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t)gprData[iCount++]; + else + dp->val.i64 = *((int64_t*)ap++); + break; + + case nsXPTType::T_U8: + if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t)gprData[iCount++]; + else + dp->val.u8 = *((uint8_t*)ap++); + break; + + case nsXPTType::T_U16: + if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t)gprData[iCount++]; + else + dp->val.u16 = *((uint16_t*)ap++); + break; + + case nsXPTType::T_U32: + if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t)gprData[iCount++]; + else + dp->val.u32 = *((uint32_t*)ap++); + break; + + case nsXPTType::T_U64: + if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t)gprData[iCount++]; + else + dp->val.u64 = *((uint64_t*)ap++); + break; + + case nsXPTType::T_FLOAT: + if (iCount < PARAM_FPR_COUNT) + // The value in xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = (double)fprData[iCount++]; + else + dp->val.f = *((float*)ap++); + break; + + case nsXPTType::T_DOUBLE: + if (iCount < PARAM_FPR_COUNT) + dp->val.d = (double)fprData[iCount++]; + else + dp->val.d = *((double*)ap++); + break; + + case nsXPTType::T_BOOL: + if (iCount < PARAM_GPR_COUNT) + // We need the cast to uint8_t to remove garbage on upper 56-bit + // at first. + dp->val.b = (bool)(uint8_t)gprData[iCount++]; + else + dp->val.b = *((bool*)ap++); + break; + + case nsXPTType::T_CHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char)gprData[iCount++]; + else + dp->val.c = *((char*)ap++); + break; + + case nsXPTType::T_WCHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t)gprData[iCount++]; + else + dp->val.wc = *((wchar_t*)ap++); + break; + + default: + NS_ERROR("bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +#define STUB_ENTRY(n) /* defined in the assembly file */ + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +void +xptc_dummy() +{ +} diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64_gnu.cpp b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64_gnu.cpp new file mode 100644 index 0000000000..9c9d245c7f --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64_gnu.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xptcprivate.h" + +/* + * This is for Windows 64 bit (x86_64) using GCC syntax + * Code was copied from the MSVC version. + */ + +#if !defined(_AMD64_) || !defined(__GNUC__) +# error xptcstubs_x86_64_gnu.cpp being used unexpectedly +#endif + +extern "C" nsresult __attribute__((__used__)) +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gprData, double *fprData) +{ +// +// "this" pointer is first parameter, so parameter count is 3. +// +#define PARAM_GPR_COUNT 3 +#define PARAM_FPR_COUNT 3 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self, "no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info, "no method info"); + + paramCount = info->GetParamCount(); + + // + // setup variant array pointer + // + + const uint8_t indexOfJSContext = info->IndexOfJSContext(); + + uint64_t* ap = args; + uint32_t iCount = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = ¶mBuffer[i]; + + if (i == indexOfJSContext) { + if (iCount < PARAM_GPR_COUNT) + iCount++; + else + ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*)gprData[iCount++]; + else + dp->val.p = (void*)*ap++; + + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8: + if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t)gprData[iCount++]; + else + dp->val.i8 = *((int8_t*)ap++); + break; + + case nsXPTType::T_I16: + if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t)gprData[iCount++]; + else + dp->val.i16 = *((int16_t*)ap++); + break; + + case nsXPTType::T_I32: + if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t)gprData[iCount++]; + else + dp->val.i32 = *((int32_t*)ap++); + break; + + case nsXPTType::T_I64: + if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t)gprData[iCount++]; + else + dp->val.i64 = *((int64_t*)ap++); + break; + + case nsXPTType::T_U8: + if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t)gprData[iCount++]; + else + dp->val.u8 = *((uint8_t*)ap++); + break; + + case nsXPTType::T_U16: + if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t)gprData[iCount++]; + else + dp->val.u16 = *((uint16_t*)ap++); + break; + + case nsXPTType::T_U32: + if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t)gprData[iCount++]; + else + dp->val.u32 = *((uint32_t*)ap++); + break; + + case nsXPTType::T_U64: + if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t)gprData[iCount++]; + else + dp->val.u64 = *((uint64_t*)ap++); + break; + + case nsXPTType::T_FLOAT: + if (iCount < PARAM_FPR_COUNT) + // The value in xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = (double)fprData[iCount++]; + else + dp->val.f = *((float*)ap++); + break; + + case nsXPTType::T_DOUBLE: + if (iCount < PARAM_FPR_COUNT) + dp->val.d = (double)fprData[iCount++]; + else + dp->val.d = *((double*)ap++); + break; + + case nsXPTType::T_BOOL: + if (iCount < PARAM_GPR_COUNT) + // We need the cast to uint8_t to remove garbage on upper 56-bit + // at first. + dp->val.b = (bool)(uint8_t)gprData[iCount++]; + else + dp->val.b = *((bool*)ap++); + break; + + case nsXPTType::T_CHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char)gprData[iCount++]; + else + dp->val.c = *((char*)ap++); + break; + + case nsXPTType::T_WCHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t)gprData[iCount++]; + else + dp->val.wc = *((wchar_t*)ap++); + break; + + default: + NS_ASSERTION(0, "bad type"); + break; + } + } + + nsresult result = self->mOuter->CallMethod((uint16_t)methodIndex, info, + paramBuffer); + + return result; +} + +__asm__ ( + ".text\n" + ".intel_syntax noprefix\n" /* switch to Intel syntax to look like the MSVC assembly */ + ".globl SharedStub\n" + ".def SharedStub ; .scl 3 ; .type 46 ; .endef \n" + "SharedStub:\n" + "sub rsp, 104\n" + + /* rcx is this pointer. Need backup for optimized build */ + + "mov qword ptr [rsp+88], rcx\n" + + /* + * fist 4 parameters (1st is "this" pointer) are passed in registers. + */ + + /* for floating value */ + + "movsd qword ptr [rsp+64], xmm1\n" + "movsd qword ptr [rsp+72], xmm2\n" + "movsd qword ptr [rsp+80], xmm3\n" + + /* for integer value */ + + "mov qword ptr [rsp+40], rdx\n" + "mov qword ptr [rsp+48], r8\n" + "mov qword ptr [rsp+56], r9\n" + + /* + * Call PrepareAndDispatch function + */ + + /* 5th parameter (floating parameters) of PrepareAndDispatch */ + + "lea r9, qword ptr [rsp+64]\n" + "mov qword ptr [rsp+32], r9\n" + + /* 4th parameter (normal parameters) of PrepareAndDispatch */ + + "lea r9, qword ptr [rsp+40]\n" + + /* 3rd parameter (pointer to args on stack) */ + + "lea r8, qword ptr [rsp+40+104]\n" + + /* 2nd parameter (vtbl_index) */ + + "mov rdx, r11\n" + + /* 1st parameter (this) (rcx) */ + + "call PrepareAndDispatch\n" + + /* restore rcx */ + + "mov rcx, qword ptr [rsp+88]\n" + + /* + * clean up register + */ + + "add rsp, 104+8\n" + + /* set return address */ + + "mov rdx, qword ptr [rsp-8]\n" + + /* simulate __stdcall return */ + + "jmp rdx\n" + + /* back to AT&T syntax */ + ".att_syntax\n" +); + +#define STUB_ENTRY(n) \ +asm(".intel_syntax noprefix\n" /* this is in intel syntax */ \ + ".text\n" \ + ".align 2\n" \ + ".if " #n " < 10\n" \ + ".globl _ZN14nsXPTCStubBase5Stub" #n "Ev\n" \ + ".def _ZN14nsXPTCStubBase5Stub" #n "Ev\n" \ + ".scl 2\n" /* external */ \ + ".type 46\n" /* function returning unsigned int */ \ + ".endef\n" \ + "_ZN14nsXPTCStubBase5Stub" #n "Ev:\n" \ + ".elseif " #n " < 100\n" \ + ".globl _ZN14nsXPTCStubBase6Stub" #n "Ev\n" \ + ".def _ZN14nsXPTCStubBase6Stub" #n "Ev\n" \ + ".scl 2\n" /* external */\ + ".type 46\n" /* function returning unsigned int */ \ + ".endef\n" \ + "_ZN14nsXPTCStubBase6Stub" #n "Ev:\n" \ + ".elseif " #n " < 1000\n" \ + ".globl _ZN14nsXPTCStubBase7Stub" #n "Ev\n" \ + ".def _ZN14nsXPTCStubBase7Stub" #n "Ev\n" \ + ".scl 2\n" /* external */ \ + ".type 46\n" /* function returning unsigned int */ \ + ".endef\n" \ + "_ZN14nsXPTCStubBase7Stub" #n "Ev:\n" \ + ".else\n" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n" \ + ".endif\n" \ + "mov r11, " #n "\n" \ + "jmp SharedStub\n" \ + ".att_syntax\n" /* back to AT&T syntax */ \ + ""); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ASSERTION(0,"nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/moz.build b/xpcom/reflect/xptcall/moz.build new file mode 100644 index 0000000000..16923a3357 --- /dev/null +++ b/xpcom/reflect/xptcall/moz.build @@ -0,0 +1,20 @@ +# -*- 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/. + +DIRS += ["md"] + +SOURCES += [ + "xptcall.cpp", +] + +EXPORTS += [ + "nsXPTCUtils.h", + "xptcall.h", + "xptcstubsdecl.inc", + "xptcstubsdef.inc", +] + +FINAL_LIBRARY = "xul" diff --git a/xpcom/reflect/xptcall/nsXPTCUtils.h b/xpcom/reflect/xptcall/nsXPTCUtils.h new file mode 100644 index 0000000000..4f5060360d --- /dev/null +++ b/xpcom/reflect/xptcall/nsXPTCUtils.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsXPTCUtils_h__ +#define nsXPTCUtils_h__ + +#include "xptcall.h" +#include "mozilla/MemoryReporting.h" + +/** + * A helper class that initializes an xptcall helper at construction + * and releases it at destruction. + */ +class nsAutoXPTCStub : protected nsIXPTCProxy { + public: + nsISomeInterface* mXPTCStub; + + protected: + nsAutoXPTCStub() : mXPTCStub(nullptr) {} + + nsresult InitStub(const nsIID& aIID) { + return NS_GetXPTCallStub(aIID, this, &mXPTCStub); + } + + ~nsAutoXPTCStub() { + if (mXPTCStub) { + NS_DestroyXPTCallStub(mXPTCStub); + } + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mXPTCStub + ? NS_SizeOfIncludingThisXPTCallStub(mXPTCStub, aMallocSizeOf) + : 0; + } +}; + +#endif // nsXPTCUtils_h__ diff --git a/xpcom/reflect/xptcall/porting.html b/xpcom/reflect/xptcall/porting.html new file mode 100644 index 0000000000..a149aa144a --- /dev/null +++ b/xpcom/reflect/xptcall/porting.html @@ -0,0 +1,237 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<html> + <head> + <title>xptcall Porting Guide</title> + </head> + <body bgcolor="white"> + <h2><center>xptcall Porting Guide</center></h2> + + <h3>Overview</h3> + + <blockquote> + <a href="http://www.mozilla.org/scriptable/xptcall-faq.html"> xptcall</a> + is a library that supports both invoking methods on arbitrary xpcom + objects and implementing classes whose objects can impersonate any xpcom + interface. It does this using platform specific assembly language code. + This code needs to be ported to all platforms that want to support xptcall + (and thus mozilla). + </blockquote> + + <h3>The tree</h3> + + <blockquote> + <pre> +<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall">mozilla/xpcom/reflect/xptcall</a> + +--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/public">public</a> // exported headers + +--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/src">src</a> // core source + | \--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md">md</a> // platform specific parts + | +--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/mac">mac</a> // mac ppc + | +--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/unix">unix</a> // all unix + | \--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/win32">win32</a> // win32 + | +--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/test">test</a> // simple tests to get started + \--<a href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/tests">tests</a> // full tests via api +</pre> + + Porters are free to create subdirectories under the base <code>md</code> + directory for their given platforms and to integrate into the build system + as appropriate for their platform. + </blockquote> + + <h3>Theory of operation</h3> + + <blockquote> + There are really two pieces of functionality: <i>invoke</i> and + <i>stubs</i>... + + <p> + The <b><i>invoke</i></b> functionality requires the implementation of + the following on each platform (from + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/xptcall.h" + >xptcall/xptcall.h</a + >): + </p> + + <pre> +XPTC_PUBLIC_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); +</pre + > + + Calling code is expected to supply an array of + <code>nsXPTCVariant</code> structs. These are discriminated unions + describing the type and value of each parameter of the target function. + The platform specific code then builds a call frame and invokes the method + indicated by the index <code>methodIndex</code> on the xpcom interface + <code>that</code>. + + <p> + Here are examples of this implementation for + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp" + >Win32</a + > + and + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/unix/xptcinvoke_unixish_x86.cpp" + >Linux x86, NetBSD x86, and FreeBSD</a + >. Both of these implementations use the basic strategy of: figure out + how much stack space is needed for the params, make the space in a new + frame, copy the params to that space, invoke the method, cleanup and + return. C++ is used where appropriate, Assembly language is used where + necessary. Inline assembly language is used here, but it is equally + valid to use separate assembly language source files. Porters can decide + how best to do this for their platforms. + </p> + + <p> + The <b><i>stubs</i></b> functionality is more complex. The goal here is + a class whose vtbl can look like the vtbl of any arbitrary xpcom + interface. Objects of this class can then be built to impersonate any + xpcom object. The base interface for this is (from + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/xptcall.h" + >xptcall/xptcall.h</a + >): + </p> + + <pre> +class nsXPTCStubBase : public nsISupports +{ +public: + // Include generated vtbl stub declarations. + // These are virtual and *also* implemented by this class.. +#include "xptcstubsdecl.inc" + + // The following methods must be provided by inheritor of this class. + + // return a refcounted pointer to the InterfaceInfo for this object + // NOTE: on some platforms this MUST not fail or we crash! + NS_IMETHOD GetInterfaceInfo(const nsXPTInterfaceInfo** info) = 0; + + // call this method and return result + NS_IMETHOD CallMethod(uint16_t methodIndex, + const nsXPTMethodInfo* info, + nsXPTCMiniVariant* params) = 0; +}; +</pre + > + + Code that wishes to make use of this <i>stubs</i> functionality (such as + <a href="http://www.mozilla.org/scriptable/">XPConnect</a>) implement a + class which inherits from <code>nsXPTCStubBase</code> and implements the + <code>GetInterfaceInfo</code> and <code>CallMethod</code> to let the + platform specific code know how to get interface information and how to + dispatch methods once their parameters have been pulled out of the + platform specific calling frame. + + <p> + Porters of this functionality implement the platform specific code for + the + <i>stub</i> methods that fill the vtbl for this class. The idea here is + that the class has a vtbl full of a large number of generic stubs. All + instances of this class share that vtbl and the same stubs. The stubs + forward calls to a platform specific method that uses the interface + information supplied by the overridden <code>GetInterfaceInfo</code> to + extract the parameters and build an array of platform independent + <code>nsXPTCMiniVariant</code> structs which are in turn passed on to + the overridden <code>CallMethod</code>. The platform dependent code is + responsible for doing any cleanup and returning. + </p> + + <p> + The stub methods are declared in + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/xptcstubsdecl.inc" + >xptcall/xptcstubsdecl.inc</a + >. These are '#included' into the declaration of + <code>nsXPTCStubBase</code>. A similar include file (<a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/xptcstubsdef.inc" + >xptcall/xptcstubsdef.inc</a + >) is expanded using platform specific macros to define the stub + functions. These '.inc' files are checked into cvs. However, they can be + regenerated as necessary (i.e. to change the number of stubs or to + change their specific declaration) using the Perl script + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/genstubs.pl" + >xptcall/genstubs.pl</a + >. + </p> + + <p> + Here are examples of this implementation for + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp" + >Win32</a + > + and + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/unix/xptcstubs_unixish_x86.cpp" + >Linux x86, NetBSD x86, and FreeBSD</a + >. Both of these examples use inline assembly language. That is just how + I decided to do it. You can do it as you choose. + </p> + + <p> + The Win32 version is somewhat tighter because the __declspec(naked) + feature allows for very small stubs. However, the __stdcall requires the + callee to clean up the stack, so it is imperative that the interface + information scheme allow the code to determine the correct stack pointer + fixup for return without fail, else the process will crash. + </p> + + <p> + I opted to use inline assembler for the gcc Linux x86 port. I ended up + with larger stubs than I would have preferred rather than battle the + compiler over what would happen to the stack before my asm code began + running. + </p> + + <p> + I believe that the non-assembly parts of these files can be copied and + reused with minimal (but not zero) platform specific tweaks. Feel free + to copy and paste as necessary. Please remember that safety and + reliability are more important than speed optimizations. This code is + primarily used to connect XPCOM components with JavaScript; function + call overhead is a <b>tiny</b> part of the time involved. + </p> + + <p> + I put together + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/md/test" + >xptcall/md/test + </a> + as a place to evolve the basic functionality as a port is coming + together. Not all of the functionality is exercised, but it is a place + to get started. + <a + href="http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/tests" + >xptcall/tests + </a> + has an api level test for <code>NS_InvokeByIndex</code>, but no tests + for the <i>stubs</i> functionality. Such a test ought to be written, but + this has not yet been done. + </p> + + <p> + A full 'test' at this point requires building the client and running the + XPConnect test called <i>TestXPC</i> in + <a href="http://lxr.mozilla.org/mozilla/source/js/xpconnect/tests" + >mozilla/js/xpconnect/tests </a + >. + </p> + </blockquote> + + <hr /> + <b>Author:</b> + <a href="mailto:jband@netscape.com" + >John Bandhauer <jband@netscape.com></a + ><br /> + <b>Last modified:</b> 31 May 1999 + </body> +</html> diff --git a/xpcom/reflect/xptcall/xptcall.cpp b/xpcom/reflect/xptcall/xptcall.cpp new file mode 100644 index 0000000000..0b28791b54 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcall.cpp @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +/* entry point wrappers. */ + +#include "xptcprivate.h" + +using namespace mozilla; + +NS_IMETHODIMP +nsXPTCStubBase::QueryInterface(REFNSIID aIID, void** aInstancePtr) { + if (aIID.Equals(mEntry->IID())) { + NS_ADDREF_THIS(); + *aInstancePtr = static_cast<nsISupports*>(this); + return NS_OK; + } + + return mOuter->QueryInterface(aIID, aInstancePtr); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXPTCStubBase::AddRef() { return mOuter->AddRef(); } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXPTCStubBase::Release() { return mOuter->Release(); } + +EXPORT_XPCOM_API(nsresult) +NS_GetXPTCallStub(REFNSIID aIID, nsIXPTCProxy* aOuter, + nsISomeInterface** aResult) { + if (NS_WARN_IF(!aOuter) || NS_WARN_IF(!aResult)) return NS_ERROR_INVALID_ARG; + + const nsXPTInterfaceInfo* iie = nsXPTInterfaceInfo::ByIID(aIID); + if (!iie || iie->IsBuiltinClass()) { + return NS_ERROR_FAILURE; + } + + *aResult = new nsXPTCStubBase(aOuter, iie); + return NS_OK; +} + +EXPORT_XPCOM_API(void) +NS_DestroyXPTCallStub(nsISomeInterface* aStub) { + nsXPTCStubBase* stub = static_cast<nsXPTCStubBase*>(aStub); + delete (stub); +} + +EXPORT_XPCOM_API(size_t) +NS_SizeOfIncludingThisXPTCallStub(const nsISomeInterface* aStub, + mozilla::MallocSizeOf aMallocSizeOf) { + // We could cast aStub to nsXPTCStubBase, but that class doesn't seem to own + // anything, so just measure the size of the object itself. + return aMallocSizeOf(aStub); +} diff --git a/xpcom/reflect/xptcall/xptcall.h b/xpcom/reflect/xptcall/xptcall.h new file mode 100644 index 0000000000..a36ca53d23 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcall.h @@ -0,0 +1,173 @@ +/* -*- 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/. */ + +/* Public declarations for xptcall. */ + +#ifndef xptcall_h___ +#define xptcall_h___ + +#include "nscore.h" +#include "nsISupports.h" +#include "xptinfo.h" +#include "js/Value.h" +#include "mozilla/MemoryReporting.h" + +struct nsXPTCMiniVariant { + // No ctors or dtors so that we can use arrays of these on the stack + // with no penalty. + union Union { + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + float f; + double d; + bool b; + char c; + char16_t wc; + void* p; + }; + + Union val; +}; + +static_assert(offsetof(nsXPTCMiniVariant, val) == 0, + "nsXPTCMiniVariant must be a thin wrapper"); + +struct nsXPTCVariant { + union ExtendedVal { + // ExtendedVal is an extension on nsXPTCMiniVariant. It contains types + // unknown to the assembly implementations which must be passed by indirect + // semantics. + // + // nsXPTCVariant contains enough space to store ExtendedVal inline, which + // can be used to store these types when IsIndirect() is true. + nsXPTCMiniVariant mini; + + nsID nsid; + nsCString nscstr; + nsString nsstr; + JS::Value jsval; + xpt::detail::UntypedTArray array; + + // This type contains non-standard-layout types, so needs an explicit + // Ctor/Dtor - we'll just delete them. + ExtendedVal() = delete; + ~ExtendedVal() = delete; + }; + + union { + // The `val` field from nsXPTCMiniVariant. + nsXPTCMiniVariant::Union val; + + // Storage for any extended variants. + ExtendedVal ext; + }; + + nsXPTType type; + uint8_t flags; + + // Clear to a valid, null state. + nsXPTCVariant() { + memset(this, 0, sizeof(nsXPTCVariant)); + type = nsXPTType::T_VOID; + } + + enum { + // + // Bitflag definitions + // + + // Indicates that we &val.p should be passed n the stack, i.e. that + // val should be passed by reference. + IS_INDIRECT = 0x1, + }; + + void ClearFlags() { flags = 0; } + void SetIndirect() { flags |= IS_INDIRECT; } + + bool IsIndirect() const { return 0 != (flags & IS_INDIRECT); } + + // Implicitly convert to nsXPTCMiniVariant. + operator nsXPTCMiniVariant&() { return *(nsXPTCMiniVariant*)&val; } + operator const nsXPTCMiniVariant&() const { + return *(const nsXPTCMiniVariant*)&val; + } + + // As this type contains an anonymous union, we need to provide an explicit + // destructor. + ~nsXPTCVariant() {} +}; + +static_assert(offsetof(nsXPTCVariant, val) == offsetof(nsXPTCVariant, ext), + "nsXPTCVariant::{ext,val} must have matching offsets"); + +// static_assert that nsXPTCVariant::ExtendedVal is large enough and +// well-aligned enough for every XPT-supported type. +#define XPT_CHECK_SIZEOF(xpt, type) \ + static_assert(sizeof(nsXPTCVariant::ExtendedVal) >= sizeof(type), \ + "nsXPTCVariant::ext not big enough for " #xpt " (" #type ")"); \ + static_assert(MOZ_ALIGNOF(nsXPTCVariant::ExtendedVal) >= MOZ_ALIGNOF(type), \ + "nsXPTCVariant::ext not aligned enough for " #xpt " (" #type \ + ")"); +XPT_FOR_EACH_TYPE(XPT_CHECK_SIZEOF) +#undef XPT_CHECK_SIZEOF + +class nsIXPTCProxy : public nsISupports { + public: + NS_IMETHOD CallMethod(uint16_t aMethodIndex, const nsXPTMethodInfo* aInfo, + nsXPTCMiniVariant* aParams) = 0; +}; + +/** + * This is a typedef to avoid confusion between the canonical + * nsISupports* that provides object identity and an interface pointer + * for inheriting interfaces that aren't known at compile-time. + */ +typedef nsISupports nsISomeInterface; + +/** + * Get a proxy object to implement the specified interface. + * + * @param aIID The IID of the interface to implement. + * @param aOuter An object to receive method calls from the proxy object. + * The stub forwards QueryInterface/AddRef/Release to the + * outer object. The proxy object does not hold a reference to + * the outer object; it is the caller's responsibility to + * ensure that this pointer remains valid until the stub has + * been destroyed. + * @param aStub Out parameter for the new proxy object. The object is + * not addrefed. The object never destroys itself. It must be + * explicitly destroyed by calling + * NS_DestroyXPTCallStub when it is no longer needed. + */ +XPCOM_API(nsresult) +NS_GetXPTCallStub(REFNSIID aIID, nsIXPTCProxy* aOuter, + nsISomeInterface** aStub); + +/** + * Destroys an XPTCall stub previously created with NS_GetXPTCallStub. + */ +XPCOM_API(void) +NS_DestroyXPTCallStub(nsISomeInterface* aStub); + +/** + * Measures the size of an XPTCall stub previously created with + * NS_GetXPTCallStub. + */ +XPCOM_API(size_t) +NS_SizeOfIncludingThisXPTCallStub(const nsISomeInterface* aStub, + mozilla::MallocSizeOf aMallocSizeOf); + +// this is extern "C" because on some platforms it is implemented in assembly +extern "C" nsresult NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +#endif /* xptcall_h___ */ diff --git a/xpcom/reflect/xptcall/xptcprivate.h b/xpcom/reflect/xptcall/xptcprivate.h new file mode 100644 index 0000000000..02f408dc38 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcprivate.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +/* All the xptcall private declarations - only include locally. */ + +#ifndef xptcprivate_h___ +#define xptcprivate_h___ + +#include "xptcall.h" +#include "mozilla/Attributes.h" + +#if !defined(__ia64) || \ + (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__)) +# define STUB_ENTRY(n) NS_IMETHOD Stub##n() = 0; +#else +# define STUB_ENTRY(n) \ + NS_IMETHOD Stub##n(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, \ + uint64_t, uint64_t, uint64_t) = 0; +#endif + +#define SENTINEL_ENTRY(n) NS_IMETHOD Sentinel##n() = 0; + +class nsIXPTCStubBase : public nsISupports { + public: +#include "xptcstubsdef.inc" +}; + +#undef STUB_ENTRY +#undef SENTINEL_ENTRY + +#if !defined(__ia64) || \ + (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__)) +# define STUB_ENTRY(n) NS_IMETHOD Stub##n() override; +#else +# define STUB_ENTRY(n) \ + NS_IMETHOD Stub##n(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, \ + uint64_t, uint64_t, uint64_t) override; +#endif + +#define SENTINEL_ENTRY(n) NS_IMETHOD Sentinel##n() override; + +class nsXPTCStubBase final : public nsIXPTCStubBase { + public: + NS_DECL_ISUPPORTS_INHERITED + +#include "xptcstubsdef.inc" + + nsXPTCStubBase(nsIXPTCProxy* aOuter, const nsXPTInterfaceInfo* aEntry) + : mOuter(aOuter), mEntry(aEntry) {} + + nsIXPTCProxy* mOuter; + const nsXPTInterfaceInfo* mEntry; + + ~nsXPTCStubBase() = default; +}; + +#undef STUB_ENTRY +#undef SENTINEL_ENTRY + +#if defined(__clang__) || defined(__GNUC__) +# define ATTRIBUTE_USED __attribute__((__used__)) +#else +# define ATTRIBUTE_USED +#endif + +#endif /* xptcprivate_h___ */ diff --git a/xpcom/reflect/xptcall/xptcstubsdecl.inc b/xpcom/reflect/xptcall/xptcstubsdecl.inc new file mode 100644 index 0000000000..91d35fe322 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcstubsdecl.inc @@ -0,0 +1,761 @@ +/* generated file - DO NOT EDIT */ + +/* includes 247 stub entries, and 5 sentinel entries */ + +/* +* declarations of normal stubs... +* 0 is QueryInterface +* 1 is AddRef +* 2 is Release +*/ +#if !defined(__ia64) || (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__)) +NS_IMETHOD Stub3(); +NS_IMETHOD Stub4(); +NS_IMETHOD Stub5(); +NS_IMETHOD Stub6(); +NS_IMETHOD Stub7(); +NS_IMETHOD Stub8(); +NS_IMETHOD Stub9(); +NS_IMETHOD Stub10(); +NS_IMETHOD Stub11(); +NS_IMETHOD Stub12(); +NS_IMETHOD Stub13(); +NS_IMETHOD Stub14(); +NS_IMETHOD Stub15(); +NS_IMETHOD Stub16(); +NS_IMETHOD Stub17(); +NS_IMETHOD Stub18(); +NS_IMETHOD Stub19(); +NS_IMETHOD Stub20(); +NS_IMETHOD Stub21(); +NS_IMETHOD Stub22(); +NS_IMETHOD Stub23(); +NS_IMETHOD Stub24(); +NS_IMETHOD Stub25(); +NS_IMETHOD Stub26(); +NS_IMETHOD Stub27(); +NS_IMETHOD Stub28(); +NS_IMETHOD Stub29(); +NS_IMETHOD Stub30(); +NS_IMETHOD Stub31(); +NS_IMETHOD Stub32(); +NS_IMETHOD Stub33(); +NS_IMETHOD Stub34(); +NS_IMETHOD Stub35(); +NS_IMETHOD Stub36(); +NS_IMETHOD Stub37(); +NS_IMETHOD Stub38(); +NS_IMETHOD Stub39(); +NS_IMETHOD Stub40(); +NS_IMETHOD Stub41(); +NS_IMETHOD Stub42(); +NS_IMETHOD Stub43(); +NS_IMETHOD Stub44(); +NS_IMETHOD Stub45(); +NS_IMETHOD Stub46(); +NS_IMETHOD Stub47(); +NS_IMETHOD Stub48(); +NS_IMETHOD Stub49(); +NS_IMETHOD Stub50(); +NS_IMETHOD Stub51(); +NS_IMETHOD Stub52(); +NS_IMETHOD Stub53(); +NS_IMETHOD Stub54(); +NS_IMETHOD Stub55(); +NS_IMETHOD Stub56(); +NS_IMETHOD Stub57(); +NS_IMETHOD Stub58(); +NS_IMETHOD Stub59(); +NS_IMETHOD Stub60(); +NS_IMETHOD Stub61(); +NS_IMETHOD Stub62(); +NS_IMETHOD Stub63(); +NS_IMETHOD Stub64(); +NS_IMETHOD Stub65(); +NS_IMETHOD Stub66(); +NS_IMETHOD Stub67(); +NS_IMETHOD Stub68(); +NS_IMETHOD Stub69(); +NS_IMETHOD Stub70(); +NS_IMETHOD Stub71(); +NS_IMETHOD Stub72(); +NS_IMETHOD Stub73(); +NS_IMETHOD Stub74(); +NS_IMETHOD Stub75(); +NS_IMETHOD Stub76(); +NS_IMETHOD Stub77(); +NS_IMETHOD Stub78(); +NS_IMETHOD Stub79(); +NS_IMETHOD Stub80(); +NS_IMETHOD Stub81(); +NS_IMETHOD Stub82(); +NS_IMETHOD Stub83(); +NS_IMETHOD Stub84(); +NS_IMETHOD Stub85(); +NS_IMETHOD Stub86(); +NS_IMETHOD Stub87(); +NS_IMETHOD Stub88(); +NS_IMETHOD Stub89(); +NS_IMETHOD Stub90(); +NS_IMETHOD Stub91(); +NS_IMETHOD Stub92(); +NS_IMETHOD Stub93(); +NS_IMETHOD Stub94(); +NS_IMETHOD Stub95(); +NS_IMETHOD Stub96(); +NS_IMETHOD Stub97(); +NS_IMETHOD Stub98(); +NS_IMETHOD Stub99(); +NS_IMETHOD Stub100(); +NS_IMETHOD Stub101(); +NS_IMETHOD Stub102(); +NS_IMETHOD Stub103(); +NS_IMETHOD Stub104(); +NS_IMETHOD Stub105(); +NS_IMETHOD Stub106(); +NS_IMETHOD Stub107(); +NS_IMETHOD Stub108(); +NS_IMETHOD Stub109(); +NS_IMETHOD Stub110(); +NS_IMETHOD Stub111(); +NS_IMETHOD Stub112(); +NS_IMETHOD Stub113(); +NS_IMETHOD Stub114(); +NS_IMETHOD Stub115(); +NS_IMETHOD Stub116(); +NS_IMETHOD Stub117(); +NS_IMETHOD Stub118(); +NS_IMETHOD Stub119(); +NS_IMETHOD Stub120(); +NS_IMETHOD Stub121(); +NS_IMETHOD Stub122(); +NS_IMETHOD Stub123(); +NS_IMETHOD Stub124(); +NS_IMETHOD Stub125(); +NS_IMETHOD Stub126(); +NS_IMETHOD Stub127(); +NS_IMETHOD Stub128(); +NS_IMETHOD Stub129(); +NS_IMETHOD Stub130(); +NS_IMETHOD Stub131(); +NS_IMETHOD Stub132(); +NS_IMETHOD Stub133(); +NS_IMETHOD Stub134(); +NS_IMETHOD Stub135(); +NS_IMETHOD Stub136(); +NS_IMETHOD Stub137(); +NS_IMETHOD Stub138(); +NS_IMETHOD Stub139(); +NS_IMETHOD Stub140(); +NS_IMETHOD Stub141(); +NS_IMETHOD Stub142(); +NS_IMETHOD Stub143(); +NS_IMETHOD Stub144(); +NS_IMETHOD Stub145(); +NS_IMETHOD Stub146(); +NS_IMETHOD Stub147(); +NS_IMETHOD Stub148(); +NS_IMETHOD Stub149(); +NS_IMETHOD Stub150(); +NS_IMETHOD Stub151(); +NS_IMETHOD Stub152(); +NS_IMETHOD Stub153(); +NS_IMETHOD Stub154(); +NS_IMETHOD Stub155(); +NS_IMETHOD Stub156(); +NS_IMETHOD Stub157(); +NS_IMETHOD Stub158(); +NS_IMETHOD Stub159(); +NS_IMETHOD Stub160(); +NS_IMETHOD Stub161(); +NS_IMETHOD Stub162(); +NS_IMETHOD Stub163(); +NS_IMETHOD Stub164(); +NS_IMETHOD Stub165(); +NS_IMETHOD Stub166(); +NS_IMETHOD Stub167(); +NS_IMETHOD Stub168(); +NS_IMETHOD Stub169(); +NS_IMETHOD Stub170(); +NS_IMETHOD Stub171(); +NS_IMETHOD Stub172(); +NS_IMETHOD Stub173(); +NS_IMETHOD Stub174(); +NS_IMETHOD Stub175(); +NS_IMETHOD Stub176(); +NS_IMETHOD Stub177(); +NS_IMETHOD Stub178(); +NS_IMETHOD Stub179(); +NS_IMETHOD Stub180(); +NS_IMETHOD Stub181(); +NS_IMETHOD Stub182(); +NS_IMETHOD Stub183(); +NS_IMETHOD Stub184(); +NS_IMETHOD Stub185(); +NS_IMETHOD Stub186(); +NS_IMETHOD Stub187(); +NS_IMETHOD Stub188(); +NS_IMETHOD Stub189(); +NS_IMETHOD Stub190(); +NS_IMETHOD Stub191(); +NS_IMETHOD Stub192(); +NS_IMETHOD Stub193(); +NS_IMETHOD Stub194(); +NS_IMETHOD Stub195(); +NS_IMETHOD Stub196(); +NS_IMETHOD Stub197(); +NS_IMETHOD Stub198(); +NS_IMETHOD Stub199(); +NS_IMETHOD Stub200(); +NS_IMETHOD Stub201(); +NS_IMETHOD Stub202(); +NS_IMETHOD Stub203(); +NS_IMETHOD Stub204(); +NS_IMETHOD Stub205(); +NS_IMETHOD Stub206(); +NS_IMETHOD Stub207(); +NS_IMETHOD Stub208(); +NS_IMETHOD Stub209(); +NS_IMETHOD Stub210(); +NS_IMETHOD Stub211(); +NS_IMETHOD Stub212(); +NS_IMETHOD Stub213(); +NS_IMETHOD Stub214(); +NS_IMETHOD Stub215(); +NS_IMETHOD Stub216(); +NS_IMETHOD Stub217(); +NS_IMETHOD Stub218(); +NS_IMETHOD Stub219(); +NS_IMETHOD Stub220(); +NS_IMETHOD Stub221(); +NS_IMETHOD Stub222(); +NS_IMETHOD Stub223(); +NS_IMETHOD Stub224(); +NS_IMETHOD Stub225(); +NS_IMETHOD Stub226(); +NS_IMETHOD Stub227(); +NS_IMETHOD Stub228(); +NS_IMETHOD Stub229(); +NS_IMETHOD Stub230(); +NS_IMETHOD Stub231(); +NS_IMETHOD Stub232(); +NS_IMETHOD Stub233(); +NS_IMETHOD Stub234(); +NS_IMETHOD Stub235(); +NS_IMETHOD Stub236(); +NS_IMETHOD Stub237(); +NS_IMETHOD Stub238(); +NS_IMETHOD Stub239(); +NS_IMETHOD Stub240(); +NS_IMETHOD Stub241(); +NS_IMETHOD Stub242(); +NS_IMETHOD Stub243(); +NS_IMETHOD Stub244(); +NS_IMETHOD Stub245(); +NS_IMETHOD Stub246(); +NS_IMETHOD Stub247(); +NS_IMETHOD Stub248(); +NS_IMETHOD Stub249(); +#else +NS_IMETHOD Stub3(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub4(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub5(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub6(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub7(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub8(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub9(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub10(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub11(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub12(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub13(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub14(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub15(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub16(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub17(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub18(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub19(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub20(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub21(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub22(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub23(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub24(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub25(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub26(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub27(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub28(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub29(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub30(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub31(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub32(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub33(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub34(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub35(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub36(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub37(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub38(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub39(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub40(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub41(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub42(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub43(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub44(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub45(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub46(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub47(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub48(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub49(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub50(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub51(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub52(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub53(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub54(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub55(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub56(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub57(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub58(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub59(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub60(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub61(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub62(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub63(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub64(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub65(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub66(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub67(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub68(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub69(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub70(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub71(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub72(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub73(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub74(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub75(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub76(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub77(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub78(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub79(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub80(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub81(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub82(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub83(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub84(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub85(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub86(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub87(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub88(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub89(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub90(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub91(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub92(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub93(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub94(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub95(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub96(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub97(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub98(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub99(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub100(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub101(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub102(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub103(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub104(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub105(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub106(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub107(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub108(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub109(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub110(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub111(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub112(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub113(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub114(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub115(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub116(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub117(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub118(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub119(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub120(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub121(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub122(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub123(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub124(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub125(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub126(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub127(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub128(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub129(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub130(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub131(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub132(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub133(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub134(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub135(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub136(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub137(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub138(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub139(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub140(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub141(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub142(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub143(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub144(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub145(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub146(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub147(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub148(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub149(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub150(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub151(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub152(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub153(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub154(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub155(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub156(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub157(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub158(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub159(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub160(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub161(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub162(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub163(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub164(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub165(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub166(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub167(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub168(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub169(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub170(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub171(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub172(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub173(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub174(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub175(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub176(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub177(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub178(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub179(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub180(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub181(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub182(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub183(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub184(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub185(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub186(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub187(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub188(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub189(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub190(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub191(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub192(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub193(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub194(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub195(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub196(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub197(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub198(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub199(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub200(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub201(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub202(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub203(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub204(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub205(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub206(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub207(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub208(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub209(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub210(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub211(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub212(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub213(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub214(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub215(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub216(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub217(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub218(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub219(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub220(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub221(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub222(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub223(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub224(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub225(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub226(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub227(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub228(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub229(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub230(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub231(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub232(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub233(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub234(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub235(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub236(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub237(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub238(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub239(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub240(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub241(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub242(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub243(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub244(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub245(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub246(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub247(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub248(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub249(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +#endif + +/* declarations of sentinel stubs */ +NS_IMETHOD Sentinel0(); +NS_IMETHOD Sentinel1(); +NS_IMETHOD Sentinel2(); +NS_IMETHOD Sentinel3(); +NS_IMETHOD Sentinel4(); diff --git a/xpcom/reflect/xptcall/xptcstubsdef.inc b/xpcom/reflect/xptcall/xptcstubsdef.inc new file mode 100644 index 0000000000..2df455406a --- /dev/null +++ b/xpcom/reflect/xptcall/xptcstubsdef.inc @@ -0,0 +1,252 @@ +STUB_ENTRY(3) +STUB_ENTRY(4) +STUB_ENTRY(5) +STUB_ENTRY(6) +STUB_ENTRY(7) +STUB_ENTRY(8) +STUB_ENTRY(9) +STUB_ENTRY(10) +STUB_ENTRY(11) +STUB_ENTRY(12) +STUB_ENTRY(13) +STUB_ENTRY(14) +STUB_ENTRY(15) +STUB_ENTRY(16) +STUB_ENTRY(17) +STUB_ENTRY(18) +STUB_ENTRY(19) +STUB_ENTRY(20) +STUB_ENTRY(21) +STUB_ENTRY(22) +STUB_ENTRY(23) +STUB_ENTRY(24) +STUB_ENTRY(25) +STUB_ENTRY(26) +STUB_ENTRY(27) +STUB_ENTRY(28) +STUB_ENTRY(29) +STUB_ENTRY(30) +STUB_ENTRY(31) +STUB_ENTRY(32) +STUB_ENTRY(33) +STUB_ENTRY(34) +STUB_ENTRY(35) +STUB_ENTRY(36) +STUB_ENTRY(37) +STUB_ENTRY(38) +STUB_ENTRY(39) +STUB_ENTRY(40) +STUB_ENTRY(41) +STUB_ENTRY(42) +STUB_ENTRY(43) +STUB_ENTRY(44) +STUB_ENTRY(45) +STUB_ENTRY(46) +STUB_ENTRY(47) +STUB_ENTRY(48) +STUB_ENTRY(49) +STUB_ENTRY(50) +STUB_ENTRY(51) +STUB_ENTRY(52) +STUB_ENTRY(53) +STUB_ENTRY(54) +STUB_ENTRY(55) +STUB_ENTRY(56) +STUB_ENTRY(57) +STUB_ENTRY(58) +STUB_ENTRY(59) +STUB_ENTRY(60) +STUB_ENTRY(61) +STUB_ENTRY(62) +STUB_ENTRY(63) +STUB_ENTRY(64) +STUB_ENTRY(65) +STUB_ENTRY(66) +STUB_ENTRY(67) +STUB_ENTRY(68) +STUB_ENTRY(69) +STUB_ENTRY(70) +STUB_ENTRY(71) +STUB_ENTRY(72) +STUB_ENTRY(73) +STUB_ENTRY(74) +STUB_ENTRY(75) +STUB_ENTRY(76) +STUB_ENTRY(77) +STUB_ENTRY(78) +STUB_ENTRY(79) +STUB_ENTRY(80) +STUB_ENTRY(81) +STUB_ENTRY(82) +STUB_ENTRY(83) +STUB_ENTRY(84) +STUB_ENTRY(85) +STUB_ENTRY(86) +STUB_ENTRY(87) +STUB_ENTRY(88) +STUB_ENTRY(89) +STUB_ENTRY(90) +STUB_ENTRY(91) +STUB_ENTRY(92) +STUB_ENTRY(93) +STUB_ENTRY(94) +STUB_ENTRY(95) +STUB_ENTRY(96) +STUB_ENTRY(97) +STUB_ENTRY(98) +STUB_ENTRY(99) +STUB_ENTRY(100) +STUB_ENTRY(101) +STUB_ENTRY(102) +STUB_ENTRY(103) +STUB_ENTRY(104) +STUB_ENTRY(105) +STUB_ENTRY(106) +STUB_ENTRY(107) +STUB_ENTRY(108) +STUB_ENTRY(109) +STUB_ENTRY(110) +STUB_ENTRY(111) +STUB_ENTRY(112) +STUB_ENTRY(113) +STUB_ENTRY(114) +STUB_ENTRY(115) +STUB_ENTRY(116) +STUB_ENTRY(117) +STUB_ENTRY(118) +STUB_ENTRY(119) +STUB_ENTRY(120) +STUB_ENTRY(121) +STUB_ENTRY(122) +STUB_ENTRY(123) +STUB_ENTRY(124) +STUB_ENTRY(125) +STUB_ENTRY(126) +STUB_ENTRY(127) +STUB_ENTRY(128) +STUB_ENTRY(129) +STUB_ENTRY(130) +STUB_ENTRY(131) +STUB_ENTRY(132) +STUB_ENTRY(133) +STUB_ENTRY(134) +STUB_ENTRY(135) +STUB_ENTRY(136) +STUB_ENTRY(137) +STUB_ENTRY(138) +STUB_ENTRY(139) +STUB_ENTRY(140) +STUB_ENTRY(141) +STUB_ENTRY(142) +STUB_ENTRY(143) +STUB_ENTRY(144) +STUB_ENTRY(145) +STUB_ENTRY(146) +STUB_ENTRY(147) +STUB_ENTRY(148) +STUB_ENTRY(149) +STUB_ENTRY(150) +STUB_ENTRY(151) +STUB_ENTRY(152) +STUB_ENTRY(153) +STUB_ENTRY(154) +STUB_ENTRY(155) +STUB_ENTRY(156) +STUB_ENTRY(157) +STUB_ENTRY(158) +STUB_ENTRY(159) +STUB_ENTRY(160) +STUB_ENTRY(161) +STUB_ENTRY(162) +STUB_ENTRY(163) +STUB_ENTRY(164) +STUB_ENTRY(165) +STUB_ENTRY(166) +STUB_ENTRY(167) +STUB_ENTRY(168) +STUB_ENTRY(169) +STUB_ENTRY(170) +STUB_ENTRY(171) +STUB_ENTRY(172) +STUB_ENTRY(173) +STUB_ENTRY(174) +STUB_ENTRY(175) +STUB_ENTRY(176) +STUB_ENTRY(177) +STUB_ENTRY(178) +STUB_ENTRY(179) +STUB_ENTRY(180) +STUB_ENTRY(181) +STUB_ENTRY(182) +STUB_ENTRY(183) +STUB_ENTRY(184) +STUB_ENTRY(185) +STUB_ENTRY(186) +STUB_ENTRY(187) +STUB_ENTRY(188) +STUB_ENTRY(189) +STUB_ENTRY(190) +STUB_ENTRY(191) +STUB_ENTRY(192) +STUB_ENTRY(193) +STUB_ENTRY(194) +STUB_ENTRY(195) +STUB_ENTRY(196) +STUB_ENTRY(197) +STUB_ENTRY(198) +STUB_ENTRY(199) +STUB_ENTRY(200) +STUB_ENTRY(201) +STUB_ENTRY(202) +STUB_ENTRY(203) +STUB_ENTRY(204) +STUB_ENTRY(205) +STUB_ENTRY(206) +STUB_ENTRY(207) +STUB_ENTRY(208) +STUB_ENTRY(209) +STUB_ENTRY(210) +STUB_ENTRY(211) +STUB_ENTRY(212) +STUB_ENTRY(213) +STUB_ENTRY(214) +STUB_ENTRY(215) +STUB_ENTRY(216) +STUB_ENTRY(217) +STUB_ENTRY(218) +STUB_ENTRY(219) +STUB_ENTRY(220) +STUB_ENTRY(221) +STUB_ENTRY(222) +STUB_ENTRY(223) +STUB_ENTRY(224) +STUB_ENTRY(225) +STUB_ENTRY(226) +STUB_ENTRY(227) +STUB_ENTRY(228) +STUB_ENTRY(229) +STUB_ENTRY(230) +STUB_ENTRY(231) +STUB_ENTRY(232) +STUB_ENTRY(233) +STUB_ENTRY(234) +STUB_ENTRY(235) +STUB_ENTRY(236) +STUB_ENTRY(237) +STUB_ENTRY(238) +STUB_ENTRY(239) +STUB_ENTRY(240) +STUB_ENTRY(241) +STUB_ENTRY(242) +STUB_ENTRY(243) +STUB_ENTRY(244) +STUB_ENTRY(245) +STUB_ENTRY(246) +STUB_ENTRY(247) +STUB_ENTRY(248) +STUB_ENTRY(249) +SENTINEL_ENTRY(0) +SENTINEL_ENTRY(1) +SENTINEL_ENTRY(2) +SENTINEL_ENTRY(3) +SENTINEL_ENTRY(4) diff --git a/xpcom/reflect/xptinfo/moz.build b/xpcom/reflect/xptinfo/moz.build new file mode 100644 index 0000000000..9e4ce23258 --- /dev/null +++ b/xpcom/reflect/xptinfo/moz.build @@ -0,0 +1,21 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + "xptinfo.cpp", +] + +SOURCES += [ + "!xptdata.cpp", +] + +EXPORTS += [ + "xptinfo.h", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/xpcom/reflect/xptinfo/xptcodegen.py b/xpcom/reflect/xptinfo/xptcodegen.py new file mode 100644 index 0000000000..d2859703b8 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptcodegen.py @@ -0,0 +1,646 @@ +#!/usr/bin/env python +# jsonlink.py - Merge JSON typelib files into a .cpp file +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +from collections import OrderedDict + +import buildconfig +from perfecthash import PerfectHash + +# Pick a nice power-of-two size for our intermediate PHF tables. +PHFSIZE = 512 + + +def indented(s): + return s.replace("\n", "\n ") + + +def cpp(v): + if type(v) == bool: + return "true" if v else "false" + return str(v) + + +def mkstruct(*fields): + def mk(comment, **vals): + assert len(fields) == len(vals) + r = "{ // " + comment + r += indented(",".join("\n/* %s */ %s" % (k, cpp(vals[k])) for k in fields)) + r += "\n}" + return r + + return mk + + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTInterfaceInfo = mkstruct( + "mIID", + "mName", + "mParent", + "mBuiltinClass", + "mMainProcessScriptableOnly", + "mMethods", + "mConsts", + "mFunction", + "mNumMethods", + "mNumConsts", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTType = mkstruct( + "mTag", + "mInParam", + "mOutParam", + "mOptionalParam", + "mData1", + "mData2", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTParamInfo = mkstruct( + "mType", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTMethodInfo = mkstruct( + "mName", + "mParams", + "mNumParams", + "mGetter", + "mSetter", + "mReflectable", + "mOptArgc", + "mContext", + "mHasRetval", + "mIsSymbol", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTDOMObjectInfo = mkstruct( + "mUnwrap", + "mWrap", + "mCleanup", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTConstantInfo = mkstruct( + "mName", + "mSigned", + "mValue", +) + + +# Helper functions for dealing with IIDs. +# +# Unfortunately, the way we represent IIDs in memory depends on the endianness +# of the target architecture. We store an nsIID as a 16-byte, 4-tuple of: +# +# (uint32_t, uint16_t, uint16_t, [uint8_t; 8]) +# +# Unfortunately, this means that when we hash the bytes of the nsIID on a +# little-endian target system, we need to hash them in little-endian order. +# These functions let us split the input hexadecimal string into components, +# encoding each as a little-endian value, and producing an accurate bytearray. +# +# It would be nice to have a consistent representation of IIDs in memory such +# that we don't have to do these gymnastics to get an accurate hash. + + +def split_at_idxs(s, lengths): + idx = 0 + for length in lengths: + yield s[idx : idx + length] + idx += length + assert idx == len(s) + + +def split_iid(iid): # Get the individual components out of an IID string. + iid = iid.replace("-", "") # Strip any '-' delimiters + return tuple(split_at_idxs(iid, (8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2))) + + +def iid_bytes(iid): # Get the byte representation of the IID for hashing. + bs = bytearray() + for num in split_iid(iid): + b = bytearray.fromhex(num) + # Match endianness of the target platform for each component + if buildconfig.substs["TARGET_ENDIANNESS"] == "little": + b.reverse() + bs += b + return bs + + +# Split a 16-bit integer into its high and low 8 bits +def splitint(i): + assert i < 2**16 + return (i >> 8, i & 0xFF) + + +# Occasionally in xpconnect, we need to fabricate types to pass into the +# conversion methods. In some cases, these types need to be arrays, which hold +# indicies into the extra types array. +# +# These are some types which should have known indexes into the extra types +# array. +utility_types = [ + {"tag": "TD_INT8"}, + {"tag": "TD_UINT8"}, + {"tag": "TD_INT16"}, + {"tag": "TD_UINT16"}, + {"tag": "TD_INT32"}, + {"tag": "TD_UINT32"}, + {"tag": "TD_INT64"}, + {"tag": "TD_UINT64"}, + {"tag": "TD_FLOAT"}, + {"tag": "TD_DOUBLE"}, + {"tag": "TD_BOOL"}, + {"tag": "TD_CHAR"}, + {"tag": "TD_WCHAR"}, + {"tag": "TD_NSIDPTR"}, + {"tag": "TD_PSTRING"}, + {"tag": "TD_PWSTRING"}, + {"tag": "TD_INTERFACE_IS_TYPE", "iid_is": 0}, +] + + +# Core of the code generator. Takes a list of raw JSON XPT interfaces, and +# writes out a file containing the necessary static declarations into fd. +def link_to_cpp(interfaces, fd, header_fd): + # Perfect Hash from IID to interface. + iid_phf = PerfectHash(interfaces, PHFSIZE, key=lambda i: iid_bytes(i["uuid"])) + for idx, iface in enumerate(iid_phf.entries): + iface["idx"] = idx # Store the index in iid_phf of the entry. + + # Perfect Hash from name to iid_phf index. + name_phf = PerfectHash(interfaces, PHFSIZE, key=lambda i: i["name"].encode("ascii")) + + def interface_idx(name): + entry = name and name_phf.get_entry(name.encode("ascii")) + if entry: + return entry["idx"] + 1 # 1-based, use 0 as a sentinel. + return 0 + + # NOTE: State used while linking. This is done with closures rather than a + # class due to how this file's code evolved. + includes = set() + types = [] + type_cache = {} + params = [] + param_cache = {} + methods = [] + max_params = 0 + method_with_max_params = None + consts = [] + domobjects = [] + domobject_cache = {} + strings = OrderedDict() + + def lower_uuid(uuid): + return ( + "{0x%s, 0x%s, 0x%s, {0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s}}" + % split_iid(uuid) + ) + + def lower_domobject(do): + assert do["tag"] == "TD_DOMOBJECT" + + idx = domobject_cache.get(do["name"]) + if idx is None: + idx = domobject_cache[do["name"]] = len(domobjects) + + includes.add(do["headerFile"]) + domobjects.append( + nsXPTDOMObjectInfo( + "%d = %s" % (idx, do["name"]), + # These methods are defined at the top of the generated file. + mUnwrap="UnwrapDOMObject<mozilla::dom::prototypes::id::%s, %s>" + % (do["name"], do["native"]), + mWrap="WrapDOMObject<%s>" % do["native"], + mCleanup="CleanupDOMObject<%s>" % do["native"], + ) + ) + + return idx + + def lower_string(s): + if s in strings: + # We've already seen this string. + return strings[s] + elif len(strings): + # Get the last string we inserted (should be O(1) on OrderedDict). + last_s = next(reversed(strings)) + strings[s] = strings[last_s] + len(last_s) + 1 + else: + strings[s] = 0 + return strings[s] + + def lower_symbol(s): + return "uint32_t(JS::SymbolCode::%s)" % s + + def lower_extra_type(type): + key = describe_type(type) + idx = type_cache.get(key) + if idx is None: + idx = type_cache[key] = len(types) + # Make sure `types` is the proper length for any recursive calls + # to `lower_extra_type` that might happen from within `lower_type`. + types.append(None) + realtype = lower_type(type) + types[idx] = realtype + return idx + + def describe_type(type): # Create the type's documentation comment. + tag = type["tag"][3:].lower() + if tag == "legacy_array": + return "%s[size_is=%d]" % (describe_type(type["element"]), type["size_is"]) + elif tag == "array": + return "Array<%s>" % describe_type(type["element"]) + elif tag == "interface_type" or tag == "domobject": + return type["name"] + elif tag == "interface_is_type": + return "iid_is(%d)" % type["iid_is"] + elif tag.endswith("_size_is"): + return "%s(size_is=%d)" % (tag, type["size_is"]) + return tag + + def lower_type(type, in_=False, out=False, optional=False): + tag = type["tag"] + d1 = d2 = 0 + + # TD_VOID is used for types that can't be represented in JS, so they + # should not be represented in the XPT info. + assert tag != "TD_VOID" + + if tag == "TD_LEGACY_ARRAY": + d1 = type["size_is"] + d2 = lower_extra_type(type["element"]) + + elif tag == "TD_ARRAY": + # NOTE: TD_ARRAY can hold 16 bits of type index, while + # TD_LEGACY_ARRAY can only hold 8. + d1, d2 = splitint(lower_extra_type(type["element"])) + + elif tag == "TD_INTERFACE_TYPE": + d1, d2 = splitint(interface_idx(type["name"])) + + elif tag == "TD_INTERFACE_IS_TYPE": + d1 = type["iid_is"] + + elif tag == "TD_DOMOBJECT": + d1, d2 = splitint(lower_domobject(type)) + + elif tag.endswith("_SIZE_IS"): + d1 = type["size_is"] + + assert d1 < 256 and d2 < 256, "Data values too large" + return nsXPTType( + describe_type(type), + mTag=tag, + mData1=d1, + mData2=d2, + mInParam=in_, + mOutParam=out, + mOptionalParam=optional, + ) + + def lower_param(param, paramname): + params.append( + nsXPTParamInfo( + "%d = %s" % (len(params), paramname), + mType=lower_type( + param["type"], + in_="in" in param["flags"], + out="out" in param["flags"], + optional="optional" in param["flags"], + ), + ) + ) + + def is_type_reflectable(type): + # All native types end up getting tagged as void*, or as wrapper types around void* + if type["tag"] == "TD_VOID": + return False + if type["tag"] in ("TD_ARRAY", "TD_LEGACY_ARRAY"): + return is_type_reflectable(type["element"]) + return True + + def is_method_reflectable(method): + if "hidden" in method["flags"]: + return False + + for param in method["params"]: + # Reflected methods can't use non-reflectable types. + if not is_type_reflectable(param["type"]): + return False + + return True + + def lower_method(method, ifacename): + methodname = "%s::%s" % (ifacename, method["name"]) + + isSymbol = "symbol" in method["flags"] + reflectable = is_method_reflectable(method) + + if not reflectable: + # Hide the parameters of methods that can't be called from JS to + # reduce the size of the file. + paramidx = name = numparams = 0 + else: + if isSymbol: + name = lower_symbol(method["name"]) + else: + name = lower_string(method["name"]) + + numparams = len(method["params"]) + + # Check cache for parameters + cachekey = json.dumps(method["params"], sort_keys=True) + paramidx = param_cache.get(cachekey) + if paramidx is None: + paramidx = param_cache[cachekey] = len(params) + for idx, param in enumerate(method["params"]): + lower_param(param, "%s[%d]" % (methodname, idx)) + + nonlocal max_params, method_with_max_params + if numparams > max_params: + max_params = numparams + method_with_max_params = methodname + methods.append( + nsXPTMethodInfo( + "%d = %s" % (len(methods), methodname), + mName=name, + mParams=paramidx, + mNumParams=numparams, + # Flags + mGetter="getter" in method["flags"], + mSetter="setter" in method["flags"], + mReflectable=reflectable, + mOptArgc="optargc" in method["flags"], + mContext="jscontext" in method["flags"], + mHasRetval="hasretval" in method["flags"], + mIsSymbol=isSymbol, + ) + ) + + def lower_const(const, ifacename): + assert const["type"]["tag"] in [ + "TD_INT16", + "TD_INT32", + "TD_UINT8", + "TD_UINT16", + "TD_UINT32", + ] + is_signed = const["type"]["tag"] in ["TD_INT16", "TD_INT32"] + + # Constants are always either signed or unsigned 16 or 32 bit integers, + # which we will only need to convert to JS values. To save on space, + # don't bother storing the type, and instead just store a 32-bit + # unsigned integer, and stash whether to interpret it as signed. + consts.append( + nsXPTConstantInfo( + "%d = %s::%s" % (len(consts), ifacename, const["name"]), + mName=lower_string(const["name"]), + mSigned=is_signed, + mValue="(uint32_t)%d" % const["value"], + ) + ) + + def ancestors(iface): + yield iface + while iface["parent"]: + iface = name_phf.get_entry(iface["parent"].encode("ascii")) + yield iface + + def lower_iface(iface): + method_cnt = sum(len(i["methods"]) for i in ancestors(iface)) + const_cnt = sum(len(i["consts"]) for i in ancestors(iface)) + + # The number of maximum methods is not arbitrary. It is the same value + # as in xpcom/reflect/xptcall/genstubs.pl; do not change this value + # without changing that one or you WILL see problems. + # + # In addition, mNumMethods and mNumConsts are stored as a 8-bit ints, + # meaning we cannot exceed 255 methods/consts on any interface. + assert method_cnt < 250, "%s has too many methods" % iface["name"] + assert const_cnt < 256, "%s has too many constants" % iface["name"] + + # Store the lowered interface as 'cxx' on the iface object. + iface["cxx"] = nsXPTInterfaceInfo( + "%d = %s" % (iface["idx"], iface["name"]), + mIID=lower_uuid(iface["uuid"]), + mName=lower_string(iface["name"]), + mParent=interface_idx(iface["parent"]), + mMethods=len(methods), + mNumMethods=method_cnt, + mConsts=len(consts), + mNumConsts=const_cnt, + # Flags + mBuiltinClass="builtinclass" in iface["flags"], + mMainProcessScriptableOnly="main_process_only" in iface["flags"], + mFunction="function" in iface["flags"], + ) + + # Lower methods and constants used by this interface + for method in iface["methods"]: + lower_method(method, iface["name"]) + for const in iface["consts"]: + lower_const(const, iface["name"]) + + # Lower the types which have fixed indexes first, and check that the indexes + # seem correct. + for expected, ty in enumerate(utility_types): + got = lower_extra_type(ty) + assert got == expected, "Wrong index when lowering" + + # Lower interfaces in the order of the IID phf's entries lookup. + for iface in iid_phf.entries: + lower_iface(iface) + + # Write out the final output files + fd.write("/* THIS FILE WAS GENERATED BY xptcodegen.py - DO NOT EDIT */\n\n") + header_fd.write("/* THIS FILE WAS GENERATED BY xptcodegen.py - DO NOT EDIT */\n\n") + + header_fd.write( + """ +#ifndef xptdata_h +#define xptdata_h + +enum class nsXPTInterface : uint16_t { +""" + ) + + for entry in iid_phf.entries: + header_fd.write(" %s,\n" % entry["name"]) + + header_fd.write( + """ +}; + +#endif +""" + ) + + # Include any bindings files which we need to include for webidl types + for include in sorted(includes): + fd.write('#include "%s"\n' % include) + + # Write out our header + fd.write( + """ +#include "xptinfo.h" +#include "mozilla/PerfectHash.h" +#include "mozilla/dom/BindingUtils.h" + +// These template methods are specialized to be used in the sDOMObjects table. +template<mozilla::dom::prototypes::ID PrototypeID, typename T> +static nsresult UnwrapDOMObject(JS::Handle<JS::Value> aHandle, void** aObj, JSContext* aCx) +{ + RefPtr<T> p; + nsresult rv = mozilla::dom::UnwrapObject<PrototypeID, T>(aHandle, p, aCx); + p.forget(aObj); + return rv; +} + +template<typename T> +static bool WrapDOMObject(JSContext* aCx, void* aObj, JS::MutableHandle<JS::Value> aHandle) +{ + return mozilla::dom::GetOrCreateDOMReflector(aCx, reinterpret_cast<T*>(aObj), aHandle); +} + +template<typename T> +static void CleanupDOMObject(void* aObj) +{ + RefPtr<T> p = already_AddRefed<T>(reinterpret_cast<T*>(aObj)); +} + +namespace xpt { +namespace detail { + +""" + ) + + # Static data arrays + def array(ty, name, els): + fd.write( + "const %s %s[] = {%s\n};\n\n" + % (ty, name, ",".join(indented("\n" + str(e)) for e in els)) + ) + + array("nsXPTType", "sTypes", types) + array("nsXPTParamInfo", "sParams", params) + array("nsXPTMethodInfo", "sMethods", methods) + # Verify that stack-allocated buffers will do for xptcall implementations. + msg = ( + "Too many method arguments in %s. " + "Either reduce the number of arguments " + "or increase PARAM_BUFFER_COUNT." % method_with_max_params + ) + fd.write('static_assert(%s <= PARAM_BUFFER_COUNT, "%s");\n\n' % (max_params, msg)) + array("nsXPTDOMObjectInfo", "sDOMObjects", domobjects) + array("nsXPTConstantInfo", "sConsts", consts) + + # The strings array. We write out individual characters to avoid MSVC restrictions. + fd.write("const char sStrings[] = {\n") + for s, off in strings.items(): + fd.write(" // %d = %s\n '%s','\\0',\n" % (off, s, "','".join(s))) + fd.write("};\n\n") + + # Build the perfect hash table for InterfaceByIID + fd.write( + iid_phf.cxx_codegen( + name="InterfaceByIID", + entry_type="nsXPTInterfaceInfo", + entries_name="sInterfaces", + lower_entry=lambda iface: iface["cxx"], + # Check that the IIDs match to support IID keys not in the map. + return_type="const nsXPTInterfaceInfo*", + return_entry="return entry.IID().Equals(aKey) ? &entry : nullptr;", + key_type="const nsIID&", + key_bytes="reinterpret_cast<const char*>(&aKey)", + key_length="sizeof(nsIID)", + ) + ) + fd.write("\n") + + # Build the perfect hash table for InterfaceByName + fd.write( + name_phf.cxx_codegen( + name="InterfaceByName", + entry_type="uint16_t", + lower_entry=lambda iface: "%-4d /* %s */" % (iface["idx"], iface["name"]), + # Get the actual nsXPTInterfaceInfo from sInterfaces, and + # double-check that names match. + return_type="const nsXPTInterfaceInfo*", + return_entry="return strcmp(sInterfaces[entry].Name(), aKey) == 0" + " ? &sInterfaces[entry] : nullptr;", + ) + ) + fd.write("\n") + + # Generate some checks that the indexes for the utility types match the + # declared ones in xptinfo.h + for idx, ty in enumerate(utility_types): + fd.write( + 'static_assert(%d == (uint8_t)nsXPTType::Idx::%s, "Bad idx");\n' + % (idx, ty["tag"][3:]) + ) + + fd.write( + """ +const uint16_t sInterfacesSize = mozilla::ArrayLength(sInterfaces); + +} // namespace detail +} // namespace xpt +""" + ) + + +def link_and_write(files, outfile, outheader): + interfaces = [] + for file in files: + with open(file, "r") as fd: + interfaces += json.load(fd) + + iids = set() + names = set() + for interface in interfaces: + assert interface["uuid"] not in iids, "duplicated UUID %s" % interface["uuid"] + assert interface["name"] not in names, "duplicated name %s" % interface["name"] + iids.add(interface["uuid"]) + names.add(interface["name"]) + + link_to_cpp(interfaces, outfile, outheader) + + +def main(): + import sys + from argparse import ArgumentParser + + parser = ArgumentParser() + parser.add_argument("outfile", help="Output C++ file to generate") + parser.add_argument("outheader", help="Output C++ header file to generate") + parser.add_argument("xpts", nargs="*", help="source xpt files") + + args = parser.parse_args(sys.argv[1:]) + with open(args.outfile, "w") as fd, open(args.outheader, "w") as header_fd: + link_and_write(args.xpts, fd, header_fd) + + +if __name__ == "__main__": + main() diff --git a/xpcom/reflect/xptinfo/xptinfo.cpp b/xpcom/reflect/xptinfo/xptinfo.cpp new file mode 100644 index 0000000000..c44e97cad4 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptinfo.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 https://mozilla.org/MPL/2.0/. */ + +#include "xptinfo.h" +#include "nsISupports.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/ArrayUtils.h" + +#include "jsfriendapi.h" +#include "js/Symbol.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace xpt::detail; + +//////////////////////////////////// +// Constant Lookup Helper Methods // +//////////////////////////////////// + +bool nsXPTInterfaceInfo::HasAncestor(const nsIID& aIID) const { + for (const auto* info = this; info; info = info->GetParent()) { + if (info->IID() == aIID) { + return true; + } + } + return false; +} + +const nsXPTConstantInfo& nsXPTInterfaceInfo::Constant(uint16_t aIndex) const { + MOZ_ASSERT(aIndex < ConstantCount()); + + if (const nsXPTInterfaceInfo* pi = GetParent()) { + if (aIndex < pi->ConstantCount()) { + return pi->Constant(aIndex); + } + aIndex -= pi->ConstantCount(); + } + + return xpt::detail::GetConstant(mConsts + aIndex); +} + +const nsXPTMethodInfo& nsXPTInterfaceInfo::Method(uint16_t aIndex) const { + MOZ_ASSERT(aIndex < MethodCount()); + + if (const nsXPTInterfaceInfo* pi = GetParent()) { + if (aIndex < pi->MethodCount()) { + return pi->Method(aIndex); + } + aIndex -= pi->MethodCount(); + } + + return xpt::detail::GetMethod(mMethods + aIndex); +} + +nsresult nsXPTInterfaceInfo::GetMethodInfo( + uint16_t aIndex, const nsXPTMethodInfo** aInfo) const { + *aInfo = aIndex < MethodCount() ? &Method(aIndex) : nullptr; + return *aInfo ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsXPTInterfaceInfo::GetConstant(uint16_t aIndex, + JS::MutableHandle<JS::Value> aConstant, + char** aName) const { + if (aIndex < ConstantCount()) { + aConstant.set(Constant(aIndex).JSValue()); + *aName = moz_xstrdup(Constant(aIndex).Name()); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +//////////////////////////////////// +// nsXPTMethodInfo symbol helpers // +//////////////////////////////////// + +const char* nsXPTMethodInfo::SymbolDescription() const { + switch (GetSymbolCode()) { +#define XPC_WELL_KNOWN_SYMBOL_DESCR_CASE(name) \ + case JS::SymbolCode::name: \ + return #name; + JS_FOR_EACH_WELL_KNOWN_SYMBOL(XPC_WELL_KNOWN_SYMBOL_DESCR_CASE) +#undef XPC_WELL_KNOWN_SYMBOL_DESCR_CASE + + default: + return ""; + } +} + +bool nsXPTMethodInfo::GetId(JSContext* aCx, jsid& aId) const { + if (IsSymbol()) { + aId = JS::PropertyKey::Symbol(GetSymbol(aCx)); + return true; + } + + JSString* str = JS_AtomizeString(aCx, Name()); + if (!str) { + return false; + } + aId = JS::PropertyKey::NonIntAtom(str); + return true; +} diff --git a/xpcom/reflect/xptinfo/xptinfo.h b/xpcom/reflect/xptinfo/xptinfo.h new file mode 100644 index 0000000000..2456c2c2b5 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptinfo.h @@ -0,0 +1,711 @@ +/* -*- 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/. */ + +/** + * Structures and methods with information about XPCOM interfaces for use by + * XPConnect. The static backing data structures used by this file are generated + * from xpidl interfaces by the jsonxpt.py and xptcodegen.py scripts. + */ + +#ifndef xptinfo_h +#define xptinfo_h + +#include <stdint.h> +#include "nsID.h" +#include "mozilla/Assertions.h" +#include "jsapi.h" +#include "js/Symbol.h" +#include "js/Value.h" +#include "nsString.h" +#include "nsTArray.h" + +// Forward Declarations +namespace mozilla { +namespace dom { +struct NativePropertyHooks; +} // namespace dom +} // namespace mozilla + +struct nsXPTInterfaceInfo; +struct nsXPTType; +struct nsXPTParamInfo; +struct nsXPTMethodInfo; +struct nsXPTConstantInfo; +struct nsXPTDOMObjectInfo; + +enum class nsXPTInterface : uint16_t; + +// Internal helper methods. +namespace xpt { +namespace detail { + +inline const nsXPTInterfaceInfo* GetInterface(uint16_t aIndex); +inline const nsXPTType& GetType(uint16_t aIndex); +inline const nsXPTParamInfo& GetParam(uint16_t aIndex); +inline const nsXPTMethodInfo& GetMethod(uint16_t aIndex); +inline const nsXPTConstantInfo& GetConstant(uint16_t aIndex); +inline const nsXPTDOMObjectInfo& GetDOMObjectInfo(uint16_t aIndex); +inline const char* GetString(uint32_t aIndex); + +const nsXPTInterfaceInfo* InterfaceByIID(const nsIID& aIID); +const nsXPTInterfaceInfo* InterfaceByName(const char* aName); + +extern const uint16_t sInterfacesSize; + +} // namespace detail +} // namespace xpt + +/* + * An Interface describes a single XPCOM interface, including all of its + * methods. We don't record non-scriptable interfaces. + */ +struct nsXPTInterfaceInfo { + // High efficiency getters for Interfaces based on perfect hashes. + static const nsXPTInterfaceInfo* ByIID(const nsIID& aIID) { + return xpt::detail::InterfaceByIID(aIID); + } + static const nsXPTInterfaceInfo* ByName(const char* aName) { + return xpt::detail::InterfaceByName(aName); + } + + static const nsXPTInterfaceInfo* Get(nsXPTInterface aID) { + return ByIndex(uint16_t(aID)); + } + + // These are only needed for Components_interfaces's enumerator. + static const nsXPTInterfaceInfo* ByIndex(uint16_t aIndex) { + // NOTE: We add 1 here, as the internal index 0 is reserved for null. + return xpt::detail::GetInterface(aIndex + 1); + } + static uint16_t InterfaceCount() { return xpt::detail::sInterfacesSize; } + + // Interface flag getters + bool IsFunction() const { return mFunction; } + bool IsBuiltinClass() const { return mBuiltinClass; } + bool IsMainProcessScriptableOnly() const { + return mMainProcessScriptableOnly; + } + + const char* Name() const { return xpt::detail::GetString(mName); } + const nsIID& IID() const { return mIID; } + + // Get the parent interface, or null if this interface doesn't have a parent. + const nsXPTInterfaceInfo* GetParent() const { + return xpt::detail::GetInterface(mParent); + } + + // Do we have an ancestor interface with the given IID? + bool HasAncestor(const nsIID& aIID) const; + + // Get methods & constants + uint16_t ConstantCount() const { return mNumConsts; } + const nsXPTConstantInfo& Constant(uint16_t aIndex) const; + uint16_t MethodCount() const { return mNumMethods; } + const nsXPTMethodInfo& Method(uint16_t aIndex) const; + + nsresult GetMethodInfo(uint16_t aIndex, const nsXPTMethodInfo** aInfo) const; + nsresult GetConstant(uint16_t aIndex, JS::MutableHandle<JS::Value> constant, + char** aName) const; + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + nsID mIID; + uint32_t mName; // Index into xpt::detail::sStrings + + uint16_t mParent : 14; + uint16_t mBuiltinClass : 1; + // XXX(nika): Do we need this if we don't have addons anymore? + uint16_t mMainProcessScriptableOnly : 1; + + uint16_t mMethods; // Index into xpt::detail::sMethods + + uint16_t mConsts : 14; // Index into xpt::detail::sConsts + uint16_t mFunction : 1; + // uint16_t unused : 1; + + uint8_t mNumMethods; // NOTE(24/04/18): largest=nsIDocShell (193) + uint8_t mNumConsts; // NOTE(24/04/18): largest=nsIAccessibleRole (175) +}; + +// The fields in nsXPTInterfaceInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTInterfaceInfo) == 28, "wrong size?"); + +/* + * The following enum represents contains the different tag types which + * can be found in nsXPTTypeInfo::mTag. + * + * WARNING: mTag is 5 bits wide, supporting at most 32 tags. + */ +enum nsXPTTypeTag : uint8_t { + // Arithmetic (POD) Types + // - Do not require cleanup, + // - All bit patterns are valid, + // - Outparams may be uninitialized by caller, + // - Directly supported in xptcall. + // + // NOTE: The name 'Arithmetic' comes from Harbison/Steele. Despite being a tad + // unclear, it is used frequently in xptcall, so is unlikely to be changed. + TD_INT8 = 0, + TD_INT16 = 1, + TD_INT32 = 2, + TD_INT64 = 3, + TD_UINT8 = 4, + TD_UINT16 = 5, + TD_UINT32 = 6, + TD_UINT64 = 7, + TD_FLOAT = 8, + TD_DOUBLE = 9, + TD_BOOL = 10, + TD_CHAR = 11, + TD_WCHAR = 12, + _TD_LAST_ARITHMETIC = TD_WCHAR, + + // Pointer Types + // - Require cleanup unless NULL, + // - All-zeros (NULL) bit pattern is valid, + // - Outparams may be uninitialized by caller, + // - Supported in xptcall as raw pointer. + TD_VOID = 13, + TD_NSIDPTR = 14, + TD_PSTRING = 15, + TD_PWSTRING = 16, + TD_INTERFACE_TYPE = 17, + TD_INTERFACE_IS_TYPE = 18, + TD_LEGACY_ARRAY = 19, + TD_PSTRING_SIZE_IS = 20, + TD_PWSTRING_SIZE_IS = 21, + TD_DOMOBJECT = 22, + TD_PROMISE = 23, + _TD_LAST_POINTER = TD_PROMISE, + + // Complex Types + // - Require cleanup, + // - Always passed indirectly, + // - Outparams must be initialized by caller, + // - Supported in xptcall due to indirection. + TD_UTF8STRING = 24, + TD_CSTRING = 25, + TD_ASTRING = 26, + TD_NSID = 27, + TD_JSVAL = 28, + TD_ARRAY = 29, + _TD_LAST_COMPLEX = TD_ARRAY +}; + +static_assert(_TD_LAST_COMPLEX < 32, "nsXPTTypeTag must fit in 5 bits"); + +/* + * A nsXPTType is a union used to identify the type of a method argument or + * return value. The internal data is stored as an 5-bit tag, and two 8-bit + * integers, to keep alignment requirements low. + * + * nsXPTType contains 3 extra bits, reserved for use by nsXPTParamInfo. + */ +struct nsXPTType { + nsXPTTypeTag Tag() const { return static_cast<nsXPTTypeTag>(mTag); } + + // The index in the function argument list which should be used when + // determining the iid_is or size_is properties of this dependent type. + uint8_t ArgNum() const { + MOZ_ASSERT(Tag() == TD_INTERFACE_IS_TYPE || Tag() == TD_PSTRING_SIZE_IS || + Tag() == TD_PWSTRING_SIZE_IS || Tag() == TD_LEGACY_ARRAY); + return mData1; + } + + private: + // Helper for reading 16-bit data values split between mData1 and mData2. + uint16_t Data16() const { + return static_cast<uint16_t>(mData1 << 8) | mData2; + } + + public: + // Get the type of the element in the current array or sequence. Arrays only + // fit 8 bits of type data, while sequences support up to 16 bits of type data + // due to not needing to store an ArgNum. + const nsXPTType& ArrayElementType() const { + if (Tag() == TD_LEGACY_ARRAY) { + return xpt::detail::GetType(mData2); + } + MOZ_ASSERT(Tag() == TD_ARRAY); + return xpt::detail::GetType(Data16()); + } + + // We store the 16-bit iface value as two 8-bit values in order to + // avoid 16-bit alignment requirements for XPTTypeDescriptor, which + // reduces its size and also the size of XPTParamDescriptor. + const nsXPTInterfaceInfo* GetInterface() const { + MOZ_ASSERT(Tag() == TD_INTERFACE_TYPE); + return xpt::detail::GetInterface(Data16()); + } + + const nsXPTDOMObjectInfo& GetDOMObjectInfo() const { + MOZ_ASSERT(Tag() == TD_DOMOBJECT); + return xpt::detail::GetDOMObjectInfo(Data16()); + } + + // See the comments in nsXPTTypeTag for an explanation as to what each of + // these categories mean. + bool IsArithmetic() const { return Tag() <= _TD_LAST_ARITHMETIC; } + bool IsPointer() const { + return !IsArithmetic() && Tag() <= _TD_LAST_POINTER; + } + bool IsComplex() const { return Tag() > _TD_LAST_POINTER; } + + bool IsInterfacePointer() const { + return Tag() == TD_INTERFACE_TYPE || Tag() == TD_INTERFACE_IS_TYPE; + } + + bool IsDependent() const { + return (Tag() == TD_ARRAY && InnermostType().IsDependent()) || + Tag() == TD_INTERFACE_IS_TYPE || Tag() == TD_LEGACY_ARRAY || + Tag() == TD_PSTRING_SIZE_IS || Tag() == TD_PWSTRING_SIZE_IS; + } + + // Unwrap a nested type to its innermost value (e.g. through arrays). + const nsXPTType& InnermostType() const { + if (Tag() == TD_LEGACY_ARRAY || Tag() == TD_ARRAY) { + return ArrayElementType().InnermostType(); + } + return *this; + } + + // In-memory size of native type in bytes. + inline size_t Stride() const; + + // Offset the given base pointer to reference the element at the given index. + void* ElementPtr(const void* aBase, uint32_t aIndex) const { + return (char*)aBase + (aIndex * Stride()); + } + + // Zero out a native value of the given type. The type must not be 'complex'. + void ZeroValue(void* aValue) const { + MOZ_RELEASE_ASSERT(!IsComplex(), "Cannot zero a complex value"); + memset(aValue, 0, Stride()); + } + + // Indexes into the extra types array of a small set of known types. + enum class Idx : uint8_t { + INT8 = 0, + UINT8, + INT16, + UINT16, + INT32, + UINT32, + INT64, + UINT64, + FLOAT, + DOUBLE, + BOOL, + CHAR, + WCHAR, + NSIDPTR, + PSTRING, + PWSTRING, + INTERFACE_IS_TYPE + }; + + // Helper methods for fabricating nsXPTType values used by xpconnect. + static nsXPTType MkArrayType(Idx aInner) { + MOZ_ASSERT(aInner <= Idx::INTERFACE_IS_TYPE); + return {TD_LEGACY_ARRAY, false, false, false, 0, (uint8_t)aInner}; + } + static const nsXPTType& Get(Idx aInner) { + MOZ_ASSERT(aInner <= Idx::INTERFACE_IS_TYPE); + return xpt::detail::GetType((uint8_t)aInner); + } + + /////////////////////////////////////// + // nsXPTType backwards compatibility // + /////////////////////////////////////// + + nsXPTType& operator=(nsXPTTypeTag aPrefix) { + mTag = aPrefix; + return *this; + } + operator nsXPTTypeTag() const { return Tag(); } + +#define TD_ALIAS_(name_, value_) static constexpr nsXPTTypeTag name_ = value_ + TD_ALIAS_(T_I8, TD_INT8); + TD_ALIAS_(T_I16, TD_INT16); + TD_ALIAS_(T_I32, TD_INT32); + TD_ALIAS_(T_I64, TD_INT64); + TD_ALIAS_(T_U8, TD_UINT8); + TD_ALIAS_(T_U16, TD_UINT16); + TD_ALIAS_(T_U32, TD_UINT32); + TD_ALIAS_(T_U64, TD_UINT64); + TD_ALIAS_(T_FLOAT, TD_FLOAT); + TD_ALIAS_(T_DOUBLE, TD_DOUBLE); + TD_ALIAS_(T_BOOL, TD_BOOL); + TD_ALIAS_(T_CHAR, TD_CHAR); + TD_ALIAS_(T_WCHAR, TD_WCHAR); + TD_ALIAS_(T_VOID, TD_VOID); + TD_ALIAS_(T_NSIDPTR, TD_NSIDPTR); + TD_ALIAS_(T_CHAR_STR, TD_PSTRING); + TD_ALIAS_(T_WCHAR_STR, TD_PWSTRING); + TD_ALIAS_(T_INTERFACE, TD_INTERFACE_TYPE); + TD_ALIAS_(T_INTERFACE_IS, TD_INTERFACE_IS_TYPE); + TD_ALIAS_(T_LEGACY_ARRAY, TD_LEGACY_ARRAY); + TD_ALIAS_(T_PSTRING_SIZE_IS, TD_PSTRING_SIZE_IS); + TD_ALIAS_(T_PWSTRING_SIZE_IS, TD_PWSTRING_SIZE_IS); + TD_ALIAS_(T_UTF8STRING, TD_UTF8STRING); + TD_ALIAS_(T_CSTRING, TD_CSTRING); + TD_ALIAS_(T_ASTRING, TD_ASTRING); + TD_ALIAS_(T_NSID, TD_NSID); + TD_ALIAS_(T_JSVAL, TD_JSVAL); + TD_ALIAS_(T_DOMOBJECT, TD_DOMOBJECT); + TD_ALIAS_(T_PROMISE, TD_PROMISE); + TD_ALIAS_(T_ARRAY, TD_ARRAY); +#undef TD_ALIAS_ + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + uint8_t mTag : 5; + + // Parameter bitflags are packed into the XPTTypeDescriptor to save space. + // When the TypeDescriptor is not in a parameter, these flags are ignored. + uint8_t mInParam : 1; + uint8_t mOutParam : 1; + uint8_t mOptionalParam : 1; + + // The data for the different variants is stored in these two data fields. + // These should only be accessed via the getter methods above, which will + // assert if the tag is invalid. + uint8_t mData1; + uint8_t mData2; +}; + +// The fields in nsXPTType were carefully ordered to minimize size. +static_assert(sizeof(nsXPTType) == 3, "wrong size"); + +/* + * A nsXPTParamInfo is used to describe either a single argument to a method or + * a method's result. It stores its flags in the type descriptor to save space. + */ +struct nsXPTParamInfo { + bool IsIn() const { return mType.mInParam; } + bool IsOut() const { return mType.mOutParam; } + bool IsOptional() const { return mType.mOptionalParam; } + bool IsShared() const { return false; } // XXX remove (backcompat) + + // Get the type of this parameter. + const nsXPTType& Type() const { return mType; } + const nsXPTType& GetType() const { + return Type(); + } // XXX remove (backcompat) + + // Whether this parameter is passed indirectly on the stack. All out/inout + // params are passed indirectly, and complex types are always passed + // indirectly. + bool IsIndirect() const { return IsOut() || Type().IsComplex(); } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + nsXPTType mType; +}; + +// The fields in nsXPTParamInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTParamInfo) == 3, "wrong size"); + +/* + * A nsXPTMethodInfo is used to describe a single interface method. + */ +struct nsXPTMethodInfo { + bool IsGetter() const { return mGetter; } + bool IsSetter() const { return mSetter; } + bool IsReflectable() const { return mReflectable; } + bool IsSymbol() const { return mIsSymbol; } + bool WantsOptArgc() const { return mOptArgc; } + bool WantsContext() const { return mContext; } + uint8_t ParamCount() const { return mNumParams; } + + const char* Name() const { + MOZ_ASSERT(!IsSymbol()); + return xpt::detail::GetString(mName); + } + const nsXPTParamInfo& Param(uint8_t aIndex) const { + MOZ_ASSERT(aIndex < mNumParams); + return xpt::detail::GetParam(mParams + aIndex); + } + + bool HasRetval() const { return mHasRetval; } + const nsXPTParamInfo* GetRetval() const { + return mHasRetval ? &Param(mNumParams - 1) : nullptr; + } + + // If this is an [implicit_jscontext] method, returns the index of the + // implicit JSContext* argument in the C++ method's argument list. + // Otherwise returns UINT8_MAX. + uint8_t IndexOfJSContext() const { + if (!WantsContext()) { + return UINT8_MAX; + } + if (IsGetter() || IsSetter()) { + // Getters/setters always have the context as first argument. + return 0; + } + // The context comes before the return value, if there is one. + MOZ_ASSERT_IF(HasRetval(), ParamCount() > 0); + return ParamCount() - uint8_t(HasRetval()); + } + + JS::SymbolCode GetSymbolCode() const { + MOZ_ASSERT(IsSymbol()); + return JS::SymbolCode(mName); + } + + JS::Symbol* GetSymbol(JSContext* aCx) const { + return JS::GetWellKnownSymbol(aCx, GetSymbolCode()); + } + + const char* SymbolDescription() const; + + const char* NameOrDescription() const { + if (IsSymbol()) { + return SymbolDescription(); + } + return Name(); + } + + bool GetId(JSContext* aCx, jsid& aId) const; + + ///////////////////////////////////////////// + // nsXPTMethodInfo backwards compatibility // + ///////////////////////////////////////////// + + const char* GetName() const { return Name(); } + + uint8_t GetParamCount() const { return ParamCount(); } + const nsXPTParamInfo& GetParam(uint8_t aIndex) const { return Param(aIndex); } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + uint32_t mName; // Index into xpt::detail::sStrings. + uint16_t mParams; // Index into xpt::detail::sParams. + uint8_t mNumParams; + + uint8_t mGetter : 1; + uint8_t mSetter : 1; + uint8_t mReflectable : 1; + uint8_t mOptArgc : 1; + uint8_t mContext : 1; + uint8_t mHasRetval : 1; + uint8_t mIsSymbol : 1; +}; + +// The fields in nsXPTMethodInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size"); + +// This number is chosen to be no larger than the maximum number of parameters +// any XPIDL-defined function needs; there is a static assert in the generated +// code from xptcodegen.py to verify that decision. It is therefore also the +// maximum number of stack allocated nsXPTCMiniVariant structures for argument +// passing purposes in PrepareAndDispatch implementations. +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) +# define PARAM_BUFFER_COUNT 18 +#else +# define PARAM_BUFFER_COUNT 14 +#endif + +/** + * A nsXPTConstantInfo is used to describe a single interface constant. + */ +struct nsXPTConstantInfo { + const char* Name() const { return xpt::detail::GetString(mName); } + + JS::Value JSValue() const { + if (mSigned || mValue <= uint32_t(INT32_MAX)) { + return JS::Int32Value(int32_t(mValue)); + } + return JS::DoubleValue(mValue); + } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + uint32_t mName : 31; // Index into xpt::detail::mStrings. + + // Whether the value should be interpreted as a int32_t or uint32_t. + uint32_t mSigned : 1; + uint32_t mValue; // The value stored as a u32 +}; + +// The fields in nsXPTConstantInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTConstantInfo) == 8, "wrong size"); + +/** + * Object representing the information required to wrap and unwrap DOMObjects. + * + * This object will not live in rodata as it contains relocations. + */ +struct nsXPTDOMObjectInfo { + nsresult Unwrap(JS::Handle<JS::Value> aHandle, void** aObj, + JSContext* aCx) const { + return mUnwrap(aHandle, aObj, aCx); + } + + bool Wrap(JSContext* aCx, void* aObj, + JS::MutableHandle<JS::Value> aHandle) const { + return mWrap(aCx, aObj, aHandle); + } + + void Cleanup(void* aObj) const { return mCleanup(aObj); } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + nsresult (*mUnwrap)(JS::Handle<JS::Value> aHandle, void** aObj, + JSContext* aCx); + bool (*mWrap)(JSContext* aCx, void* aObj, + JS::MutableHandle<JS::Value> aHandle); + void (*mCleanup)(void* aObj); +}; + +namespace xpt { +namespace detail { + +// The UntypedTArray type allows low-level access from XPConnect to nsTArray +// internals without static knowledge of the array element type in question. +class UntypedTArray : public nsTArray_base<nsTArrayFallibleAllocator, + nsTArray_RelocateUsingMemutils> { + public: + void* Elements() const { return static_cast<void*>(Hdr() + 1); } + + // Changes the length and capacity to be at least large enough for aTo + // elements. + bool SetLength(const nsXPTType& aEltTy, uint32_t aTo) { + if (!EnsureCapacity<nsTArrayFallibleAllocator>(aTo, aEltTy.Stride())) { + return false; + } + + if (mHdr != EmptyHdr()) { + mHdr->mLength = aTo; + } + + return true; + } + + // Free backing memory for the nsTArray object. + void Clear() { + if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) { + nsTArrayFallibleAllocator::Free(mHdr); + } + mHdr = EmptyHdr(); + } +}; + +////////////////////////////////////////////// +// Raw typelib data stored in const statics // +////////////////////////////////////////////// + +// XPIDL information +extern const nsXPTInterfaceInfo sInterfaces[]; +extern const nsXPTType sTypes[]; +extern const nsXPTParamInfo sParams[]; +extern const nsXPTMethodInfo sMethods[]; +extern const nsXPTConstantInfo sConsts[]; +extern const nsXPTDOMObjectInfo sDOMObjects[]; + +extern const char sStrings[]; + +////////////////////////////////////// +// Helper Methods for fetching data // +////////////////////////////////////// + +inline const nsXPTInterfaceInfo* GetInterface(uint16_t aIndex) { + if (aIndex > 0 && aIndex <= sInterfacesSize) { + return &sInterfaces[aIndex - 1]; // 1-based as 0 is a marker. + } + return nullptr; +} + +inline const nsXPTType& GetType(uint16_t aIndex) { return sTypes[aIndex]; } + +inline const nsXPTParamInfo& GetParam(uint16_t aIndex) { + return sParams[aIndex]; +} + +inline const nsXPTMethodInfo& GetMethod(uint16_t aIndex) { + return sMethods[aIndex]; +} + +inline const nsXPTConstantInfo& GetConstant(uint16_t aIndex) { + return sConsts[aIndex]; +} + +inline const nsXPTDOMObjectInfo& GetDOMObjectInfo(uint16_t aIndex) { + return sDOMObjects[aIndex]; +} + +inline const char* GetString(uint32_t aIndex) { return &sStrings[aIndex]; } + +} // namespace detail +} // namespace xpt + +#define XPT_FOR_EACH_ARITHMETIC_TYPE(MACRO) \ + MACRO(TD_INT8, int8_t) \ + MACRO(TD_INT16, int16_t) \ + MACRO(TD_INT32, int32_t) \ + MACRO(TD_INT64, int64_t) \ + MACRO(TD_UINT8, uint8_t) \ + MACRO(TD_UINT16, uint16_t) \ + MACRO(TD_UINT32, uint32_t) \ + MACRO(TD_UINT64, uint64_t) \ + MACRO(TD_FLOAT, float) \ + MACRO(TD_DOUBLE, double) \ + MACRO(TD_BOOL, bool) \ + MACRO(TD_CHAR, char) \ + MACRO(TD_WCHAR, char16_t) + +#define XPT_FOR_EACH_POINTER_TYPE(MACRO) \ + MACRO(TD_VOID, void*) \ + MACRO(TD_NSIDPTR, nsID*) \ + MACRO(TD_PSTRING, char*) \ + MACRO(TD_PWSTRING, wchar_t*) \ + MACRO(TD_INTERFACE_TYPE, nsISupports*) \ + MACRO(TD_INTERFACE_IS_TYPE, nsISupports*) \ + MACRO(TD_LEGACY_ARRAY, void*) \ + MACRO(TD_PSTRING_SIZE_IS, char*) \ + MACRO(TD_PWSTRING_SIZE_IS, wchar_t*) \ + MACRO(TD_DOMOBJECT, void*) \ + MACRO(TD_PROMISE, mozilla::dom::Promise*) + +#define XPT_FOR_EACH_COMPLEX_TYPE(MACRO) \ + MACRO(TD_UTF8STRING, nsCString) \ + MACRO(TD_CSTRING, nsCString) \ + MACRO(TD_ASTRING, nsString) \ + MACRO(TD_NSID, nsID) \ + MACRO(TD_JSVAL, JS::Value) \ + MACRO(TD_ARRAY, xpt::detail::UntypedTArray) + +#define XPT_FOR_EACH_TYPE(MACRO) \ + XPT_FOR_EACH_ARITHMETIC_TYPE(MACRO) \ + XPT_FOR_EACH_POINTER_TYPE(MACRO) \ + XPT_FOR_EACH_COMPLEX_TYPE(MACRO) + +inline size_t nsXPTType::Stride() const { + // Compute the stride to use when walking an array of the given type. + switch (Tag()) { +#define XPT_TYPE_STRIDE(tag, type) \ + case tag: \ + return sizeof(type); + XPT_FOR_EACH_TYPE(XPT_TYPE_STRIDE) +#undef XPT_TYPE_STRIDE + } + + MOZ_CRASH("Unknown type"); +} + +#endif /* xptinfo_h */ diff --git a/xpcom/rust/gecko_logger/Cargo.toml b/xpcom/rust/gecko_logger/Cargo.toml new file mode 100644 index 0000000000..2f8ca84a97 --- /dev/null +++ b/xpcom/rust/gecko_logger/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gecko_logger" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +edition = "2018" +license = "MPL-2.0" + +[dependencies] +lazy_static = "1" +log = {version = "0.4", features = ["release_max_level_info"]} +env_logger = {version = "0.10", default-features = false, features = ["color"]} # disable `regex` to reduce code size +app_services_logger = { path = "../../../services/common/app_services_logger" } diff --git a/xpcom/rust/gecko_logger/src/lib.rs b/xpcom/rust/gecko_logger/src/lib.rs new file mode 100644 index 0000000000..5da85b9d89 --- /dev/null +++ b/xpcom/rust/gecko_logger/src/lib.rs @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of 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/. */ + +//! This provides a way to direct rust logging into the gecko logger. + +#[macro_use] +extern crate lazy_static; + +use app_services_logger::{AppServicesLogger, LOGGERS_BY_TARGET}; +use log::Log; +use log::{Level, LevelFilter}; +use std::boxed::Box; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::RwLock; +use std::{cmp, env}; + +extern "C" { + fn ExternMozLog(tag: *const c_char, prio: c_int, text: *const c_char); + fn gfx_critical_note(msg: *const c_char); + #[cfg(target_os = "android")] + fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int; +} + +lazy_static! { + // This could be a proper static once [1] is fixed or parking_lot's const fn + // support is not nightly-only. + // + // [1]: https://github.com/rust-lang/rust/issues/73714 + static ref LOG_MODULE_MAP: RwLock<HashMap<String, (LevelFilter, bool)>> = RwLock::new(HashMap::new()); +} + +/// This tells us whether LOG_MODULE_MAP is possibly non-empty. +static LOGGING_ACTIVE: AtomicBool = AtomicBool::new(false); + +/// This function searches for the module's name in the hashmap. If that is not +/// found, it proceeds to search for the parent modules. +/// It returns a tuple containing the matched string, if the matched module +/// was a pattern match, and the level we found in the hashmap. +/// If none is found, it will return the module's name and LevelFilter::Off +fn get_level_for_module<'a>( + map: &HashMap<String, (LevelFilter, bool)>, + key: &'a str, +) -> (&'a str, bool, LevelFilter) { + if let Some((level, is_pattern_match)) = map.get(key) { + return (key, *is_pattern_match, level.clone()); + } + + let mut mod_name = &key[..]; + while let Some(pos) = mod_name.rfind("::") { + mod_name = &mod_name[..pos]; + if let Some((level, is_pattern_match)) = map.get(mod_name) { + return (mod_name, *is_pattern_match, level.clone()); + } + } + + return (key, false, LevelFilter::Off); +} + +/// This function takes a record to maybe log to Gecko. +/// It returns true if the record was handled by Gecko's logging, and false +/// otherwise. +pub fn log_to_gecko(record: &log::Record) -> bool { + if !LOGGING_ACTIVE.load(Ordering::Relaxed) { + return false; + } + + let key = match record.module_path() { + Some(key) => key, + None => return false, + }; + + let (mod_name, is_pattern_match, level) = { + let map = LOG_MODULE_MAP.read().unwrap(); + get_level_for_module(&map, &key) + }; + + if level == LevelFilter::Off { + return false; + } + + if level < record.metadata().level() { + return false; + } + + // Map the log::Level to mozilla::LogLevel. + let moz_log_level = match record.metadata().level() { + Level::Error => 1, // Error + Level::Warn => 2, // Warning + Level::Info => 3, // Info + Level::Debug => 4, // Debug + Level::Trace => 5, // Verbose + }; + + // If it was a pattern match, we need to append ::* to the matched string. + let (tag, msg) = if is_pattern_match { + ( + CString::new(format!("{}::*", mod_name)).unwrap(), + CString::new(format!("[{}] {}", key, record.args())).unwrap(), + ) + } else { + ( + CString::new(key).unwrap(), + CString::new(format!("{}", record.args())).unwrap(), + ) + }; + + unsafe { + ExternMozLog(tag.as_ptr(), moz_log_level, msg.as_ptr()); + } + + return true; +} + +#[no_mangle] +pub unsafe extern "C" fn set_rust_log_level(module: *const c_char, level: u8) { + // Convert the Gecko level to a rust LevelFilter. + let rust_level = match level { + 1 => LevelFilter::Error, + 2 => LevelFilter::Warn, + 3 => LevelFilter::Info, + 4 => LevelFilter::Debug, + 5 => LevelFilter::Trace, + _ => LevelFilter::Off, + }; + + // This is the name of the rust module that we're trying to log in Gecko. + let mut mod_name = CStr::from_ptr(module).to_string_lossy().into_owned(); + + let is_pattern_match = mod_name.ends_with("::*"); + + // If this is a pattern, remove the last "::*" from it so we can search it + // in the map. + if is_pattern_match { + let len = mod_name.len() - 3; + mod_name.truncate(len); + } + + LOGGING_ACTIVE.store(true, Ordering::Relaxed); + let mut map = LOG_MODULE_MAP.write().unwrap(); + map.insert(mod_name, (rust_level, is_pattern_match)); + + // Figure out the max level of all the modules. + let max = map + .values() + .map(|(lvl, _)| lvl) + .max() + .unwrap_or(&LevelFilter::Off); + log::set_max_level(*max); +} + +pub struct GeckoLogger { + logger: env_logger::Logger, +} + +impl GeckoLogger { + pub fn new() -> GeckoLogger { + let mut builder = env_logger::Builder::new(); + let default_level = if cfg!(debug_assertions) { + "warn" + } else { + "error" + }; + let logger = match env::var("RUST_LOG") { + Ok(v) => builder.parse_filters(&v).build(), + _ => builder.parse_filters(default_level).build(), + }; + + GeckoLogger { logger } + } + + pub fn init() -> Result<(), log::SetLoggerError> { + let gecko_logger = Self::new(); + + // The max level may have already been set by gecko_logger. Don't + // set it to a lower level. + let level = cmp::max(log::max_level(), gecko_logger.logger.filter()); + log::set_max_level(level); + log::set_boxed_logger(Box::new(gecko_logger)) + } + + fn should_log_to_app_services(target: &str) -> bool { + return AppServicesLogger::is_app_services_logger_registered(target.into()); + } + + fn maybe_log_to_app_services(&self, record: &log::Record) { + if Self::should_log_to_app_services(record.target()) { + if let Some(l) = LOGGERS_BY_TARGET.read().unwrap().get(record.target()) { + l.log(record); + } + } + } + + fn should_log_to_gfx_critical_note(record: &log::Record) -> bool { + record.level() == log::Level::Error && record.target().contains("webrender") + } + + fn maybe_log_to_gfx_critical_note(&self, record: &log::Record) { + if Self::should_log_to_gfx_critical_note(record) { + let msg = CString::new(format!("{}", record.args())).unwrap(); + unsafe { + gfx_critical_note(msg.as_ptr()); + } + } + } + + #[cfg(not(target_os = "android"))] + fn log_out(&self, record: &log::Record) { + // If the log wasn't handled by the gecko platform logger, just pass it + // to the env_logger. + if !log_to_gecko(record) { + self.logger.log(record); + } + } + + #[cfg(target_os = "android")] + fn log_out(&self, record: &log::Record) { + if !self.logger.matches(record) { + return; + } + + let msg = CString::new(format!("{}", record.args())).unwrap(); + let tag = CString::new(record.module_path().unwrap()).unwrap(); + let prio = match record.metadata().level() { + Level::Error => 6, /* ERROR */ + Level::Warn => 5, /* WARN */ + Level::Info => 4, /* INFO */ + Level::Debug => 3, /* DEBUG */ + Level::Trace => 2, /* VERBOSE */ + }; + // Output log directly to android log, since env_logger can output log + // only to stderr or stdout. + unsafe { + __android_log_write(prio, tag.as_ptr(), msg.as_ptr()); + } + } +} + +impl log::Log for GeckoLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.logger.enabled(metadata) || GeckoLogger::should_log_to_app_services(metadata.target()) + } + + fn log(&self, record: &log::Record) { + // Forward log to gfxCriticalNote, if the log should be in gfx crash log. + self.maybe_log_to_gfx_critical_note(record); + self.maybe_log_to_app_services(record); + self.log_out(record); + } + + fn flush(&self) {} +} diff --git a/xpcom/rust/gkrust_utils/Cargo.toml b/xpcom/rust/gkrust_utils/Cargo.toml new file mode 100644 index 0000000000..e398725260 --- /dev/null +++ b/xpcom/rust/gkrust_utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gkrust_utils" +version = "0.1.0" +authors = ["Jonathan Kingston <jkt@mozilla.com>"] +license = "MPL-2.0" + +[dependencies] +semver = "1.0" +nsstring = { path = "../nsstring" } diff --git a/xpcom/rust/gkrust_utils/cbindgen.toml b/xpcom/rust/gkrust_utils/cbindgen.toml new file mode 100644 index 0000000000..967fbefd47 --- /dev/null +++ b/xpcom/rust/gkrust_utils/cbindgen.toml @@ -0,0 +1,31 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. + * To generate this file: + * 1. Get the latest cbindgen using `cargo install --force cbindgen` + * a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release + * 2. Run `rustup run nightly cbindgen xpcom/rust/gkrust_utils --lockfile Cargo.lock --crate gkrust_utils -o xpcom/base/gk_rust_utils_ffi_generated.h` + */ +#include "nsError.h" +#include "nsString.h" +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +namespaces = ["mozilla"] + +[export] +# Skip constants because we don't have any +item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions"] + +[enum] +add_sentinel = true +derive_helper_methods = true + +[defines] +"target_os = windows" = "XP_WIN" +"target_os = macos" = "XP_MACOSX" +"target_os = android" = "ANDROID" diff --git a/xpcom/rust/gkrust_utils/src/lib.rs b/xpcom/rust/gkrust_utils/src/lib.rs new file mode 100644 index 0000000000..c519d7e002 --- /dev/null +++ b/xpcom/rust/gkrust_utils/src/lib.rs @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate nsstring; +extern crate semver; +use nsstring::nsACString; + +#[no_mangle] +pub unsafe extern "C" fn GkRustUtils_ParseSemVer( + ver: &nsACString, + out_major: *mut u64, + out_minor: *mut u64, + out_patch: *mut u64, +) -> bool { + let version = match semver::Version::parse(&ver.to_utf8()) { + Ok(ver) => ver, + Err(_) => return false, + }; + *out_major = version.major; + *out_minor = version.minor; + *out_patch = version.patch; + true +} diff --git a/xpcom/rust/gtest/bench-collections/Bench.cpp b/xpcom/rust/gtest/bench-collections/Bench.cpp new file mode 100644 index 0000000000..0035d27ed6 --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/Bench.cpp @@ -0,0 +1,297 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Overview +// -------- +// This file measures the speed of various implementations of C++ and Rust +// collections (hash tables, etc.) used within the codebase. There are a small +// number of benchmarks for each collection type; each benchmark tests certain +// operations (insertion, lookup, iteration, etc.) More benchmarks could easily +// be envisioned, but this small number is enough to characterize the major +// differences between implementations, while keeping the file size and +// complexity low. +// +// Details +// ------- +// The file uses `MOZ_GTEST_BENCH_F` so that results are integrated into +// PerfHerder. It is also designed so that individual test benchmarks can be +// run under a profiler. +// +// The C++ code uses `MOZ_RELEASE_ASSERT` extensively to check values and +// ensure operations aren't optimized away by the compiler. The Rust code uses +// `assert!()`. These should be roughly equivalent, but aren't guaranteed to be +// the same. As a result, the intra-C++ comparisons should be reliable, and the +// intra-Rust comparisons should be reliable, but the C++ vs. Rust comparisons +// may be less reliable. +// +// Note that the Rust implementations run very slowly without --enable-release. +// +// Profiling +// --------- +// If you want to measure a particular implementation under a profiler such as +// Callgrind, do something like this: +// +// MOZ_RUN_GTEST=1 GTEST_FILTER='*BenchCollections*$IMPL*' +// valgrind --tool=callgrind --callgrind-out-file=clgout +// $OBJDIR/dist/bin/firefox -unittest +// callgrind_annotate --auto=yes clgout > clgann +// +// where $IMPL is part of an implementation name in a test (e.g. "PLDHash", +// "MozHash") and $OBJDIR is an objdir containing a --enable-release build. +// +// Note that multiple processes are spawned, so `clgout` gets overwritten +// multiple times, but the last process to write its profiling data to file is +// the one of interest. (Alternatively, use --callgrind-out-file=clgout.%p to +// get separate output files for each process, with a PID suffix.) + +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH +#include "mozilla/AllocPolicy.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "PLDHashTable.h" +#include <unordered_set> + +using namespace mozilla; + +// This function gives a pseudo-random sequence with the following properties: +// - Deterministic and platform-independent. +// - No duplicates in the first VALS_LEN results, which is useful for ensuring +// the tables get to a particular size, and also for guaranteeing lookups +// that fail. +static uintptr_t MyRand() { + static uintptr_t s = 0; + s = s * 1103515245 + 12345; + return s; +} + +// Keep this in sync with Params in bench.rs. +struct Params { + const char* mConfigName; + size_t mNumInserts; // Insert this many unique keys + size_t mNumSuccessfulLookups; // Does mNumInserts lookups each time + size_t mNumFailingLookups; // Does mNumInserts lookups each time + size_t mNumIterations; // Iterates the full table each time + bool mRemoveInserts; // Remove all entries at end? +}; + +// We don't use std::unordered_{set,map}, but it's an interesting thing to +// benchmark against. +// +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_unordered_set(const Params* aParams, void** aVals, + size_t aLen) { + std::unordered_set<void*> hs; + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.insert(aVals[j]); + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.find(aVals[j]) != hs.end()); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(hs.find(aVals[j]) == hs.end()); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (const auto& elem : hs) { + (void)elem; + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.size() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.erase(aVals[j]) == 1); + } + MOZ_RELEASE_ASSERT(hs.size() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.size() == aParams->mNumInserts); + } +} + +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_PLDHashTable(const Params* aParams, void** aVals, + size_t aLen) { + PLDHashTable hs(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub)); + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + auto entry = static_cast<PLDHashEntryStub*>(hs.Add(aVals[j])); + MOZ_RELEASE_ASSERT(!entry->key); + entry->key = aVals[j]; + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.Search(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(!hs.Search(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (auto iter = hs.Iter(); !iter.Done(); iter.Next()) { + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.EntryCount() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.Remove(aVals[j]); + } + MOZ_RELEASE_ASSERT(hs.EntryCount() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.EntryCount() == aParams->mNumInserts); + } +} + +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_MozHashSet(const Params* aParams, void** aVals, + size_t aLen) { + mozilla::HashSet<void*, mozilla::DefaultHasher<void*>, MallocAllocPolicy> hs; + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.put(aVals[j])); + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.has(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(!hs.has(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (auto iter = hs.iter(); !iter.done(); iter.next()) { + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.count() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.remove(aVals[j]); + } + MOZ_RELEASE_ASSERT(hs.count() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.count() == aParams->mNumInserts); + } +} + +extern "C" { +void Bench_Rust_HashSet(const Params* params, void** aVals, size_t aLen); +void Bench_Rust_FnvHashSet(const Params* params, void** aVals, size_t aLen); +void Bench_Rust_FxHashSet(const Params* params, void** aVals, size_t aLen); +} + +static const size_t VALS_LEN = 131072; + +// Each benchmark measures a different aspect of performance. +// Note that no "Inserts" value can exceed VALS_LEN. +// Also, if any failing lookups are done, Inserts must be <= VALS_LEN/2. +const Params gParamsList[] = { + // clang-format off + // Successful Failing Remove + // Inserts lookups lookups Iterations inserts + { "succ_lookups", 1024, 5000, 0, 0, false }, + { "fail_lookups", 1024, 0, 5000, 0, false }, + { "insert_remove", VALS_LEN, 0, 0, 0, true }, + { "iterate", 1024, 0, 0, 5000, false }, + // clang-format on +}; + +class BenchCollections : public ::testing::Test { + protected: + void SetUp() override { + StaticMutexAutoLock lock(sValsMutex); + + if (!sVals) { + sVals = (void**)malloc(VALS_LEN * sizeof(void*)); + for (size_t i = 0; i < VALS_LEN; i++) { + // This leaves the high 32 bits zero on 64-bit platforms, but that + // should still be enough randomness to get typical behaviour. + sVals[i] = reinterpret_cast<void*>(uintptr_t(MyRand())); + } + } + + printf("\n"); + for (size_t i = 0; i < ArrayLength(gParamsList); i++) { + const Params* params = &gParamsList[i]; + printf("%14s", params->mConfigName); + } + printf("%14s\n", "total"); + } + + public: + void BenchImpl(void (*aBench)(const Params*, void**, size_t)) { + StaticMutexAutoLock lock(sValsMutex); + + double total = 0; + for (size_t i = 0; i < ArrayLength(gParamsList); i++) { + const Params* params = &gParamsList[i]; + TimeStamp t1 = TimeStamp::Now(); + aBench(params, sVals, VALS_LEN); + TimeStamp t2 = TimeStamp::Now(); + double t = (t2 - t1).ToMilliseconds(); + printf("%11.1f ms", t); + total += t; + } + printf("%11.1f ms\n", total); + } + + private: + // Random values used in the benchmarks. + static void** sVals MOZ_GUARDED_BY(sValsMutex); + + // A mutex that protects all benchmark operations, ensuring that two + // benchmarks never run concurrently. + static StaticMutex sValsMutex; +}; + +void** BenchCollections::sVals; +StaticMutex BenchCollections::sValsMutex; + +MOZ_GTEST_BENCH_F(BenchCollections, unordered_set, + [this] { BenchImpl(Bench_Cpp_unordered_set); }); + +MOZ_GTEST_BENCH_F(BenchCollections, PLDHash, + [this] { BenchImpl(Bench_Cpp_PLDHashTable); }); + +MOZ_GTEST_BENCH_F(BenchCollections, MozHash, + [this] { BenchImpl(Bench_Cpp_MozHashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustHash, + [this] { BenchImpl(Bench_Rust_HashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustFnvHash, + [this] { BenchImpl(Bench_Rust_FnvHashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustFxHash, + [this] { BenchImpl(Bench_Rust_FxHashSet); }); diff --git a/xpcom/rust/gtest/bench-collections/Cargo.toml b/xpcom/rust/gtest/bench-collections/Cargo.toml new file mode 100644 index 0000000000..857d87cffd --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bench-collections-gtest" +version = "0.1.0" +license = "MPL-2.0" +description = "Benchmarks for various collections" + +[dependencies] +fnv = "1.0" +fxhash = "0.2.1" + +[lib] +path = "bench.rs" diff --git a/xpcom/rust/gtest/bench-collections/bench.rs b/xpcom/rust/gtest/bench-collections/bench.rs new file mode 100644 index 0000000000..1aba69f01f --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/bench.rs @@ -0,0 +1,101 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case)] + +extern crate fnv; +extern crate fxhash; + +use fnv::FnvHashSet; +use fxhash::FxHashSet; +use std::collections::HashSet; +use std::os::raw::{c_char, c_void}; +use std::slice; + +/// Keep this in sync with Params in Bench.cpp. +#[derive(Debug)] +#[repr(C)] +pub struct Params { + config_name: *const c_char, + num_inserts: usize, + num_successful_lookups: usize, + num_failing_lookups: usize, + num_iterations: usize, + remove_inserts: bool, +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_HashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs: HashSet<_> = std::collections::HashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_FnvHashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs = FnvHashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_FxHashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs = FxHashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +// Keep this in sync with all the other Bench_*() functions. +fn Bench_Rust<H: std::hash::BuildHasher>( + mut hs: HashSet<*const c_void, H>, + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let params = unsafe { &*params }; + let vals = unsafe { slice::from_raw_parts(vals, len) }; + + for j in 0..params.num_inserts { + hs.insert(vals[j]); + } + + for _i in 0..params.num_successful_lookups { + for j in 0..params.num_inserts { + assert!(hs.contains(&vals[j])); + } + } + + for _i in 0..params.num_failing_lookups { + for j in params.num_inserts..params.num_inserts * 2 { + assert!(!hs.contains(&vals[j])); + } + } + + for _i in 0..params.num_iterations { + let mut n = 0; + for _ in hs.iter() { + n += 1; + } + assert!(params.num_inserts == n); + assert!(hs.len() == n); + } + + if params.remove_inserts { + for j in 0..params.num_inserts { + assert!(hs.remove(&vals[j])); + } + assert!(hs.is_empty()); + } else { + assert!(hs.len() == params.num_inserts); + } +} diff --git a/xpcom/rust/gtest/moz.build b/xpcom/rust/gtest/moz.build new file mode 100644 index 0000000000..d3457930c8 --- /dev/null +++ b/xpcom/rust/gtest/moz.build @@ -0,0 +1,14 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + "bench-collections/Bench.cpp", + "moz_task/TestMozTask.cpp", + "nsstring/TestnsString.cpp", + "xpcom/TestXpcom.cpp", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/xpcom/rust/gtest/moz_task/Cargo.toml b/xpcom/rust/gtest/moz_task/Cargo.toml new file mode 100644 index 0000000000..09d240c309 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "moz_task-gtest" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom event target types" +edition = "2018" + +[dependencies] +moz_task = { path = "../../moz_task" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/moz_task/TestMozTask.cpp b/xpcom/rust/gtest/moz_task/TestMozTask.cpp new file mode 100644 index 0000000000..6c40f1bc97 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/TestMozTask.cpp @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "gtest/gtest.h" + +extern "C" void Rust_Future(bool* aItWorked); + +TEST(RustMozTask, Future) +{ + bool itWorked = false; + Rust_Future(&itWorked); + EXPECT_TRUE(itWorked); +} diff --git a/xpcom/rust/gtest/moz_task/test.rs b/xpcom/rust/gtest/moz_task/test.rs new file mode 100644 index 0000000000..fa4c0af852 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/test.rs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use moz_task; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +/// Demo `Future` to demonstrate executing futures to completion via `nsIEventTarget`. +struct MyFuture { + poll_count: u32, + waker: Option<Waker>, + expect_main_thread: bool, +} + +impl MyFuture { + fn new(expect_main_thread: bool) -> Self { + Self { + poll_count: 0, + waker: None, + expect_main_thread, + } + } +} + +impl Future for MyFuture { + type Output = u32; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> { + assert_eq!(moz_task::is_main_thread(), self.expect_main_thread); + + self.poll_count += 1; + + if let Some(waker) = &mut self.waker { + if !waker.will_wake(cx.waker()) { + *waker = cx.waker().clone(); + } + } else { + let waker = cx.waker().clone(); + self.waker = Some(waker); + } + + println!("Poll count = {}", self.poll_count); + if self.poll_count > 5 { + Poll::Ready(self.poll_count) + } else { + // Just notify the task that we need to re-polled. + if let Some(waker) = &self.waker { + waker.wake_by_ref(); + } + Poll::Pending + } + } +} + +#[no_mangle] +pub extern "C" fn Rust_Future(it_worked: *mut bool) { + let future = async move { + assert_eq!(MyFuture::new(true).await, 6); + assert_eq!( + moz_task::spawn_local("Rust_Future inner spawn_local", MyFuture::new(true)).await, + 6 + ); + assert_eq!( + moz_task::spawn("Rust_Future inner spawn", MyFuture::new(false)).await, + 6 + ); + unsafe { + *it_worked = true; + } + }; + unsafe { + moz_task::gtest_only::spin_event_loop_until("Rust_Future", future).unwrap(); + }; +} diff --git a/xpcom/rust/gtest/nsstring/Cargo.toml b/xpcom/rust/gtest/nsstring/Cargo.toml new file mode 100644 index 0000000000..fd997926a7 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nsstring-gtest" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom string types" + +[dependencies] +nsstring = { path = "../../nsstring" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/nsstring/TestnsString.cpp b/xpcom/rust/gtest/nsstring/TestnsString.cpp new file mode 100644 index 0000000000..4e14c7bca5 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/TestnsString.cpp @@ -0,0 +1,177 @@ +#include "gtest/gtest.h" +#include <stdint.h> +#include <utility> +#include "nsString.h" + +extern "C" { +// This function is called by the rust code in test.rs if a non-fatal test +// failure occurs. +void GTest_ExpectFailure(const char* aMessage) { EXPECT_STREQ(aMessage, ""); } +} + +#define SIZE_ALIGN_CHECK(Clazz) \ + extern "C" void Rust_Test_ReprSizeAlign_##Clazz(size_t* size, \ + size_t* align); \ + TEST(RustNsString, ReprSizeAlign_##Clazz) \ + { \ + size_t size, align; \ + Rust_Test_ReprSizeAlign_##Clazz(&size, &align); \ + EXPECT_EQ(size, sizeof(Clazz)); \ + EXPECT_EQ(align, alignof(Clazz)); \ + } + +SIZE_ALIGN_CHECK(nsString) +SIZE_ALIGN_CHECK(nsCString) + +#define MEMBER_CHECK(Clazz, Member) \ + extern "C" void Rust_Test_Member_##Clazz##_##Member( \ + size_t* size, size_t* align, size_t* offset); \ + TEST(RustNsString, ReprMember_##Clazz##_##Member) \ + { \ + class Hack : public Clazz { \ + public: \ + static void RunTest() { \ + size_t size, align, offset; \ + Rust_Test_Member_##Clazz##_##Member(&size, &align, &offset); \ + EXPECT_EQ(size, sizeof(std::declval<Hack>().Member)); \ + EXPECT_EQ(align, alignof(decltype(std::declval<Hack>().Member))); \ + EXPECT_EQ(offset, offsetof(Hack, Member)); \ + } \ + }; \ + static_assert(sizeof(Clazz) == sizeof(Hack), "Hack matches class"); \ + Hack::RunTest(); \ + } + +MEMBER_CHECK(nsString, mData) +MEMBER_CHECK(nsString, mLength) +MEMBER_CHECK(nsString, mDataFlags) +MEMBER_CHECK(nsString, mClassFlags) +MEMBER_CHECK(nsCString, mData) +MEMBER_CHECK(nsCString, mLength) +MEMBER_CHECK(nsCString, mDataFlags) +MEMBER_CHECK(nsCString, mClassFlags) + +extern "C" void Rust_Test_NsStringFlags( + uint16_t* f_terminated, uint16_t* f_voided, uint16_t* f_refcounted, + uint16_t* f_owned, uint16_t* f_inline, uint16_t* f_literal, + uint16_t* f_class_inline, uint16_t* f_class_null_terminated); +TEST(RustNsString, NsStringFlags) +{ + uint16_t f_terminated, f_voided, f_refcounted, f_owned, f_inline, f_literal, + f_class_inline, f_class_null_terminated; + Rust_Test_NsStringFlags(&f_terminated, &f_voided, &f_refcounted, &f_owned, + &f_inline, &f_literal, &f_class_inline, + &f_class_null_terminated); + EXPECT_EQ(f_terminated, uint16_t(nsAString::DataFlags::TERMINATED)); + EXPECT_EQ(f_terminated, uint16_t(nsACString::DataFlags::TERMINATED)); + EXPECT_EQ(f_voided, uint16_t(nsAString::DataFlags::VOIDED)); + EXPECT_EQ(f_voided, uint16_t(nsACString::DataFlags::VOIDED)); + EXPECT_EQ(f_refcounted, uint16_t(nsAString::DataFlags::REFCOUNTED)); + EXPECT_EQ(f_refcounted, uint16_t(nsACString::DataFlags::REFCOUNTED)); + EXPECT_EQ(f_owned, uint16_t(nsAString::DataFlags::OWNED)); + EXPECT_EQ(f_owned, uint16_t(nsACString::DataFlags::OWNED)); + EXPECT_EQ(f_inline, uint16_t(nsAString::DataFlags::INLINE)); + EXPECT_EQ(f_inline, uint16_t(nsACString::DataFlags::INLINE)); + EXPECT_EQ(f_literal, uint16_t(nsAString::DataFlags::LITERAL)); + EXPECT_EQ(f_literal, uint16_t(nsACString::DataFlags::LITERAL)); + EXPECT_EQ(f_class_inline, uint16_t(nsAString::ClassFlags::INLINE)); + EXPECT_EQ(f_class_inline, uint16_t(nsACString::ClassFlags::INLINE)); + EXPECT_EQ(f_class_null_terminated, + uint16_t(nsAString::ClassFlags::NULL_TERMINATED)); + EXPECT_EQ(f_class_null_terminated, + uint16_t(nsACString::ClassFlags::NULL_TERMINATED)); +} + +extern "C" void Rust_StringFromCpp(const nsACString* aCStr, + const nsAString* aStr); +TEST(RustNsString, StringFromCpp) +{ + nsAutoCString foo; + foo.AssignASCII("Hello, World!"); + + nsAutoString bar; + bar.AssignASCII("Hello, World!"); + + Rust_StringFromCpp(&foo, &bar); +} + +extern "C" void Rust_AssignFromRust(nsACString* aCStr, nsAString* aStr); +TEST(RustNsString, AssignFromRust) +{ + nsAutoCString cs; + nsAutoString s; + Rust_AssignFromRust(&cs, &s); + EXPECT_TRUE(cs.EqualsASCII("Hello, World!")); + EXPECT_TRUE(s.EqualsASCII("Hello, World!")); +} + +extern "C" { +void Cpp_AssignFromCpp(nsACString* aCStr, nsAString* aStr) { + aCStr->AssignASCII("Hello, World!"); + aStr->AssignASCII("Hello, World!"); +} +} +extern "C" void Rust_AssignFromCpp(); +TEST(RustNsString, AssignFromCpp) +{ Rust_AssignFromCpp(); } + +extern "C" void Rust_StringWrite(); +TEST(RustNsString, StringWrite) +{ Rust_StringWrite(); } + +extern "C" void Rust_FromEmptyRustString(); +TEST(RustNsString, FromEmptyRustString) +{ Rust_FromEmptyRustString(); } + +extern "C" void Rust_WriteToBufferFromRust(nsACString* aCStr, nsAString* aStr, + nsACString* aFallibleCStr, + nsAString* aFallibleStr); +TEST(RustNsString, WriteToBufferFromRust) +{ + nsAutoCString cStr; + nsAutoString str; + nsAutoCString fallibleCStr; + nsAutoString fallibleStr; + + cStr.AssignLiteral("abc"); + str.AssignLiteral("abc"); + fallibleCStr.AssignLiteral("abc"); + fallibleStr.AssignLiteral("abc"); + + Rust_WriteToBufferFromRust(&cStr, &str, &fallibleCStr, &fallibleStr); + + EXPECT_TRUE(cStr.EqualsASCII("ABC")); + EXPECT_TRUE(str.EqualsASCII("ABC")); + EXPECT_TRUE(fallibleCStr.EqualsASCII("ABC")); + EXPECT_TRUE(fallibleStr.EqualsASCII("ABC")); +} + +extern "C" void Rust_InlineCapacityFromRust(const nsACString* aCStr, + const nsAString* aStr, + size_t* aCStrCapacity, + size_t* aStrCapacity); +TEST(RustNsString, InlineCapacityFromRust) +{ + size_t cStrCapacity; + size_t strCapacity; + nsAutoCStringN<93> cs; + nsAutoStringN<93> s; + Rust_InlineCapacityFromRust(&cs, &s, &cStrCapacity, &strCapacity); + EXPECT_EQ(cStrCapacity, 92U); + EXPECT_EQ(strCapacity, 92U); +} + +extern "C" void Rust_VoidStringFromRust(nsACString* aCStr, nsAString* aStr); +TEST(RustNsString, VoidStringFromRust) +{ + nsAutoCString cs; + nsAutoString s; + + EXPECT_FALSE(cs.IsVoid()); + EXPECT_FALSE(s.IsVoid()); + + Rust_VoidStringFromRust(&cs, &s); + + EXPECT_TRUE(cs.IsVoid()); + EXPECT_TRUE(s.IsVoid()); +} diff --git a/xpcom/rust/gtest/nsstring/test.rs b/xpcom/rust/gtest/nsstring/test.rs new file mode 100644 index 0000000000..a5d142f2b2 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/test.rs @@ -0,0 +1,131 @@ +#![allow(non_snake_case)] + +extern crate nsstring; + +use nsstring::*; +use std::ffi::CString; +use std::fmt::Write; +use std::os::raw::c_char; + +fn nonfatal_fail(msg: String) { + extern "C" { + fn GTest_ExpectFailure(message: *const c_char); + } + unsafe { + let msg = CString::new(msg).unwrap(); + GTest_ExpectFailure(msg.as_ptr()); + } +} + +/// This macro checks if the two arguments are equal, and causes a non-fatal +/// GTest test failure if they are not. +macro_rules! expect_eq { + ($x:expr, $y:expr) => { + match (&$x, &$y) { + (x, y) => { + if *x != *y { + nonfatal_fail(format!( + "check failed: (`{:?}` == `{:?}`) at {}:{}", + x, + y, + file!(), + line!() + )) + } + } + } + }; +} + +#[no_mangle] +pub extern "C" fn Rust_StringFromCpp(cs: *const nsACString, s: *const nsAString) { + unsafe { + expect_eq!(&*cs, "Hello, World!"); + expect_eq!(&*s, "Hello, World!"); + } +} + +#[no_mangle] +pub extern "C" fn Rust_AssignFromRust(cs: *mut nsACString, s: *mut nsAString) { + unsafe { + (*cs).assign(&nsCString::from("Hello, World!")); + expect_eq!(&*cs, "Hello, World!"); + (*s).assign(&nsString::from("Hello, World!")); + expect_eq!(&*s, "Hello, World!"); + } +} + +extern "C" { + fn Cpp_AssignFromCpp(cs: *mut nsACString, s: *mut nsAString); +} + +#[no_mangle] +pub extern "C" fn Rust_AssignFromCpp() { + let mut cs = nsCString::new(); + let mut s = nsString::new(); + unsafe { + Cpp_AssignFromCpp(&mut *cs, &mut *s); + } + expect_eq!(cs, "Hello, World!"); + expect_eq!(s, "Hello, World!"); +} + +#[no_mangle] +pub extern "C" fn Rust_StringWrite() { + let mut cs = nsCString::new(); + let mut s = nsString::new(); + + write!(s, "a").unwrap(); + write!(cs, "a").unwrap(); + expect_eq!(s, "a"); + expect_eq!(cs, "a"); + write!(s, "bc").unwrap(); + write!(cs, "bc").unwrap(); + expect_eq!(s, "abc"); + expect_eq!(cs, "abc"); + write!(s, "{}", 123).unwrap(); + write!(cs, "{}", 123).unwrap(); + expect_eq!(s, "abc123"); + expect_eq!(cs, "abc123"); +} + +#[no_mangle] +pub extern "C" fn Rust_FromEmptyRustString() { + let mut test = nsString::from("Blah"); + test.assign_utf8(&nsCString::from(String::new())); + assert!(test.is_empty()); +} + +#[no_mangle] +pub extern "C" fn Rust_WriteToBufferFromRust( + cs: *mut nsACString, + s: *mut nsAString, + fallible_cs: *mut nsACString, + fallible_s: *mut nsAString, +) { + unsafe { + let cs_buf = (*cs).to_mut(); + let s_buf = (*s).to_mut(); + let fallible_cs_buf = (*fallible_cs).fallible_to_mut().unwrap(); + let fallible_s_buf = (*fallible_s).fallible_to_mut().unwrap(); + + cs_buf[0] = b'A'; + cs_buf[1] = b'B'; + cs_buf[2] = b'C'; + s_buf[0] = b'A' as u16; + s_buf[1] = b'B' as u16; + s_buf[2] = b'C' as u16; + fallible_cs_buf[0] = b'A'; + fallible_cs_buf[1] = b'B'; + fallible_cs_buf[2] = b'C'; + fallible_s_buf[0] = b'A' as u16; + fallible_s_buf[1] = b'B' as u16; + fallible_s_buf[2] = b'C' as u16; + } +} + +#[no_mangle] +pub extern "C" fn Rust_VoidStringFromRust(cs: &mut nsACString, s: &mut nsAString) { + cs.set_is_void(true); + s.set_is_void(true); +} diff --git a/xpcom/rust/gtest/xpcom/Cargo.toml b/xpcom/rust/gtest/xpcom/Cargo.toml new file mode 100644 index 0000000000..777080b33b --- /dev/null +++ b/xpcom/rust/gtest/xpcom/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xpcom-gtest" +version = "0.1.0" +authors = ["michael@thelayzells.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom interfaces" + +[dependencies] +xpcom = { path = "../../xpcom" } +nserror = { path = "../../nserror" } +nsstring = { path = "../../nsstring" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/xpcom/TestXpcom.cpp b/xpcom/rust/gtest/xpcom/TestXpcom.cpp new file mode 100644 index 0000000000..69425274f6 --- /dev/null +++ b/xpcom/rust/gtest/xpcom/TestXpcom.cpp @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "gtest/gtest.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsIObserver.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" + +extern "C" nsIObserverService* Rust_ObserveFromRust(); + +TEST(RustXpcom, ObserverFromRust) +{ + nsCOMPtr<nsIObserverService> rust = Rust_ObserveFromRust(); + nsCOMPtr<nsIObserverService> cpp = mozilla::services::GetObserverService(); + EXPECT_EQ(rust, cpp); +} + +extern "C" void Rust_ImplementRunnableInRust(bool* aItWorked, + nsIRunnable** aRunnable); + +TEST(RustXpcom, ImplementRunnableInRust) +{ + bool itWorked = false; + nsCOMPtr<nsIRunnable> runnable; + Rust_ImplementRunnableInRust(&itWorked, getter_AddRefs(runnable)); + + EXPECT_TRUE(runnable); + EXPECT_FALSE(itWorked); + runnable->Run(); + EXPECT_TRUE(itWorked); +} + +extern "C" void Rust_GetMultipleInterfaces(nsIRunnable** aRunnable, + nsIObserver** aObserver); + +TEST(RustXpcom, DynamicCastVoid) +{ + nsCOMPtr<nsIRunnable> runnable; + nsCOMPtr<nsIObserver> observer; + Rust_GetMultipleInterfaces(getter_AddRefs(runnable), + getter_AddRefs(observer)); + + // They should have different addresses when `static_cast` to void* + EXPECT_NE(static_cast<void*>(runnable.get()), + static_cast<void*>(observer.get())); + + // These should be the same object + nsCOMPtr<nsISupports> runnableSupports = do_QueryInterface(runnable); + nsCOMPtr<nsISupports> observerSupports = do_QueryInterface(observer); + EXPECT_EQ(runnableSupports.get(), observerSupports.get()); + +#ifndef XP_WIN + // They should have the same address when dynamic_cast to void* + // dynamic_cast<void*> is not supported without rtti on windows. + EXPECT_EQ(dynamic_cast<void*>(runnable.get()), + dynamic_cast<void*>(observer.get())); + + // The nsISupports pointer from `do_QueryInterface` should match + // `dynamic_cast<void*>` + EXPECT_EQ(dynamic_cast<void*>(observer.get()), + static_cast<void*>(observerSupports.get())); +#endif +} diff --git a/xpcom/rust/gtest/xpcom/test.rs b/xpcom/rust/gtest/xpcom/test.rs new file mode 100644 index 0000000000..f26a0140f3 --- /dev/null +++ b/xpcom/rust/gtest/xpcom/test.rs @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case)] + +#[macro_use] +extern crate xpcom; + +extern crate nserror; + +use nserror::{nsresult, NS_OK}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::ptr; +use xpcom::{interfaces, RefPtr}; + +#[no_mangle] +pub unsafe extern "C" fn Rust_ObserveFromRust() -> *const interfaces::nsIObserverService { + let obssvc: RefPtr<interfaces::nsIObserverService> = + xpcom::components::Observer::service().unwrap(); + + // Define an observer + #[xpcom(implement(nsIObserver), nonatomic)] + struct Observer { + run: *mut bool, + } + impl Observer { + unsafe fn Observe( + &self, + _subject: *const interfaces::nsISupports, + topic: *const c_char, + _data: *const u16, + ) -> nsresult { + *self.run = true; + assert!(CStr::from_ptr(topic).to_str() == Ok("test-rust-observe")); + NS_OK + } + } + + let topic = CString::new("test-rust-observe").unwrap(); + + let mut run = false; + let observer = Observer::allocate(InitObserver { run: &mut run }); + let rv = obssvc.AddObserver( + observer.coerce::<interfaces::nsIObserver>(), + topic.as_ptr(), + false, + ); + assert!(rv.succeeded()); + + let rv = obssvc.NotifyObservers(ptr::null(), topic.as_ptr(), ptr::null()); + assert!(rv.succeeded()); + assert!(run, "The observer should have been run!"); + + let rv = obssvc.RemoveObserver(observer.coerce::<interfaces::nsIObserver>(), topic.as_ptr()); + assert!(rv.succeeded()); + + assert!( + observer.coerce::<interfaces::nsISupports>() as *const _ + == &*observer + .query_interface::<interfaces::nsISupports>() + .unwrap() as *const _ + ); + + &*obssvc +} + +#[no_mangle] +pub unsafe extern "C" fn Rust_ImplementRunnableInRust( + it_worked: *mut bool, + runnable: *mut *const interfaces::nsIRunnable, +) { + // Define a type which implements nsIRunnable in rust. + #[xpcom(implement(nsIRunnable), atomic)] + struct RunnableFn<F: Fn() + 'static> { + run: F, + } + + impl<F: Fn() + 'static> RunnableFn<F> { + unsafe fn Run(&self) -> nsresult { + (self.run)(); + NS_OK + } + } + + let my_runnable = RunnableFn::allocate(InitRunnableFn { + run: move || { + *it_worked = true; + }, + }); + my_runnable + .query_interface::<interfaces::nsIRunnable>() + .unwrap() + .forget(&mut *runnable); +} + +#[no_mangle] +pub unsafe extern "C" fn Rust_GetMultipleInterfaces( + runnable: *mut *const interfaces::nsIRunnable, + observer: *mut *const interfaces::nsIObserver, +) { + // Define a type which implements nsIRunnable and nsIObserver in rust, and + // hand both references back to c++ + #[xpcom(implement(nsIRunnable, nsIObserver), atomic)] + struct MultipleInterfaces {} + + impl MultipleInterfaces { + unsafe fn Run(&self) -> nsresult { + NS_OK + } + unsafe fn Observe( + &self, + _subject: *const interfaces::nsISupports, + _topic: *const c_char, + _data: *const u16, + ) -> nsresult { + NS_OK + } + } + + let instance = MultipleInterfaces::allocate(InitMultipleInterfaces {}); + instance + .query_interface::<interfaces::nsIRunnable>() + .unwrap() + .forget(&mut *runnable); + instance + .query_interface::<interfaces::nsIObserver>() + .unwrap() + .forget(&mut *observer); +} diff --git a/xpcom/rust/malloc_size_of_derive/Cargo.toml b/xpcom/rust/malloc_size_of_derive/Cargo.toml new file mode 100644 index 0000000000..cd37a9da1f --- /dev/null +++ b/xpcom/rust/malloc_size_of_derive/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "malloc_size_of_derive" +version = "0.1.3" +authors = ["The Servo Project Developers"] +license = "MIT/Apache-2.0" +description = "Crate for Firefox memory reporting, not intended for external use" + +[lib] +path = "lib.rs" +proc-macro = true + +[dependencies] +proc-macro2 = "1" +syn = { version = "2", features = ["parsing"] } +synstructure = "0.13" diff --git a/xpcom/rust/malloc_size_of_derive/LICENSE-APACHE b/xpcom/rust/malloc_size_of_derive/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/xpcom/rust/malloc_size_of_derive/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/xpcom/rust/malloc_size_of_derive/LICENSE-MIT b/xpcom/rust/malloc_size_of_derive/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/xpcom/rust/malloc_size_of_derive/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/xpcom/rust/malloc_size_of_derive/README.md b/xpcom/rust/malloc_size_of_derive/README.md new file mode 100644 index 0000000000..5848d79be9 --- /dev/null +++ b/xpcom/rust/malloc_size_of_derive/README.md @@ -0,0 +1,5 @@ +# malloc_size_of_derive + +This crate is used for Firefox memory reporting. It's not intended for external +use, but is published so that it can be used by WebRender, which can be built +and used separately from Firefox. diff --git a/xpcom/rust/malloc_size_of_derive/lib.rs b/xpcom/rust/malloc_size_of_derive/lib.rs new file mode 100644 index 0000000000..9179c93391 --- /dev/null +++ b/xpcom/rust/malloc_size_of_derive/lib.rs @@ -0,0 +1,143 @@ +// Copyright 2016-2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A crate for deriving the MallocSizeOf trait. + +extern crate proc_macro2; +#[macro_use] +extern crate syn; +#[macro_use] +extern crate synstructure; + +#[cfg(not(test))] +decl_derive!([MallocSizeOf, attributes(ignore_malloc_size_of, conditional_malloc_size_of)] => malloc_size_of_derive); + +fn malloc_size_of_derive(s: synstructure::Structure) -> proc_macro2::TokenStream { + let match_body = s.each(|binding| { + let mut ignore = false; + let mut conditional = false; + for attr in binding.ast().attrs.iter() { + match attr.meta { + syn::Meta::Path(ref path) | syn::Meta::List(syn::MetaList { ref path, .. }) => { + assert!( + !path.is_ident("ignore_malloc_size_of"), + "#[ignore_malloc_size_of] should have an explanation, \ + e.g. #[ignore_malloc_size_of = \"because reasons\"]" + ); + if path.is_ident("conditional_malloc_size_of") { + conditional = true; + } + } + syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) => { + if path.is_ident("ignore_malloc_size_of") { + ignore = true; + } + if path.is_ident("conditional_malloc_size_of") { + conditional = true; + } + } + } + } + + assert!( + !ignore || !conditional, + "ignore_malloc_size_of and conditional_malloc_size_of are incompatible" + ); + + if ignore { + return None; + } + + let path = if conditional { + quote! { ::malloc_size_of::MallocConditionalSizeOf::conditional_size_of } + } else { + quote! { ::malloc_size_of::MallocSizeOf::size_of } + }; + + if let syn::Type::Array(..) = binding.ast().ty { + Some(quote! { + for item in #binding.iter() { + sum += #path(item, ops); + } + }) + } else { + Some(quote! { + sum += #path(#binding, ops); + }) + } + }); + + let ast = s.ast(); + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let mut where_clause = where_clause.unwrap_or(&parse_quote!(where)).clone(); + for param in ast.generics.type_params() { + let ident = ¶m.ident; + where_clause + .predicates + .push(parse_quote!(#ident: ::malloc_size_of::MallocSizeOf)); + } + + let tokens = quote! { + impl #impl_generics ::malloc_size_of::MallocSizeOf for #name #ty_generics #where_clause { + #[inline] + #[allow(unused_variables, unused_mut, unreachable_code)] + fn size_of(&self, ops: &mut ::malloc_size_of::MallocSizeOfOps) -> usize { + let mut sum = 0; + match *self { + #match_body + } + sum + } + } + }; + + tokens +} + +#[test] +fn test_struct() { + let source = syn::parse_str( + "struct Foo<T> { bar: Bar, baz: T, #[ignore_malloc_size_of = \"\"] z: Arc<T> }", + ) + .unwrap(); + let source = synstructure::Structure::new(&source); + + let expanded = malloc_size_of_derive(source).to_string(); + let mut no_space = expanded.replace(" ", ""); + macro_rules! match_count { + ($e: expr, $count: expr) => { + assert_eq!( + no_space.matches(&$e.replace(" ", "")).count(), + $count, + "counting occurences of {:?} in {:?} (whitespace-insensitive)", + $e, + expanded + ) + }; + } + match_count!("struct", 0); + match_count!("ignore_malloc_size_of", 0); + match_count!("impl<T> ::malloc_size_of::MallocSizeOf for Foo<T> where T: ::malloc_size_of::MallocSizeOf {", 1); + match_count!("sum += ::malloc_size_of::MallocSizeOf::size_of(", 2); + + let source = syn::parse_str("struct Bar([Baz; 3]);").unwrap(); + let source = synstructure::Structure::new(&source); + let expanded = malloc_size_of_derive(source).to_string(); + no_space = expanded.replace(" ", ""); + match_count!("for item in", 1); +} + +#[should_panic(expected = "should have an explanation")] +#[test] +fn test_no_reason() { + let input = syn::parse_str("struct A { #[ignore_malloc_size_of] b: C }").unwrap(); + malloc_size_of_derive(synstructure::Structure::new(&input)); +} diff --git a/xpcom/rust/moz_task/Cargo.toml b/xpcom/rust/moz_task/Cargo.toml new file mode 100644 index 0000000000..2e3e43ef7a --- /dev/null +++ b/xpcom/rust/moz_task/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "moz_task" +version = "0.1.0" +authors = ["Myk Melez <myk@mykzilla.org>"] +license = "MPL-2.0" +description = "Rust wrappers around XPCOM threading functions" +edition = "2018" + +[dependencies] +log = "0.4" +cstr = "0.2" +libc = "0.2" +async-task = { version = "4.3" } +nserror = { path = "../nserror" } +nsstring = { path = "../nsstring" } +xpcom = { path = "../xpcom" } diff --git a/xpcom/rust/moz_task/src/dispatcher.rs b/xpcom/rust/moz_task/src/dispatcher.rs new file mode 100644 index 0000000000..17ad9ceb81 --- /dev/null +++ b/xpcom/rust/moz_task/src/dispatcher.rs @@ -0,0 +1,153 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::{ + dispatch_background_task_runnable, dispatch_runnable, get_current_thread, DispatchOptions, +}; +use nserror::{nsresult, NS_OK}; +use nsstring::nsACString; +use std::sync::Mutex; +use xpcom::interfaces::{nsIEventTarget, nsIRunnablePriority}; +use xpcom::xpcom; + +/// Basic wrapper to convert a FnOnce callback into a `nsIRunnable` to be +/// dispatched using XPCOM. +#[xpcom(implement(nsIRunnable, nsINamed, nsIRunnablePriority), atomic)] +struct RunnableFunction<F: FnOnce() + 'static> { + name: &'static str, + priority: u32, + function: Mutex<Option<F>>, +} + +impl<F: FnOnce() + 'static> RunnableFunction<F> { + #[allow(non_snake_case)] + fn Run(&self) -> nsresult { + let function = self.function.lock().unwrap().take(); + debug_assert!(function.is_some(), "runnable invoked twice?"); + if let Some(function) = function { + function(); + } + NS_OK + } + + #[allow(non_snake_case)] + unsafe fn GetName(&self, result: *mut nsACString) -> nsresult { + (*result).assign(self.name); + NS_OK + } + + #[allow(non_snake_case)] + unsafe fn GetPriority(&self, result: *mut u32) -> nsresult { + *result = self.priority; + NS_OK + } +} + +pub struct RunnableBuilder<F> { + name: &'static str, + function: F, + priority: u32, + options: DispatchOptions, +} + +impl<F> RunnableBuilder<F> { + pub fn new(name: &'static str, function: F) -> Self { + RunnableBuilder { + name, + function, + priority: nsIRunnablePriority::PRIORITY_NORMAL, + options: DispatchOptions::default(), + } + } + + pub fn priority(mut self, priority: u32) -> Self { + self.priority = priority; + self + } + + pub fn options(mut self, options: DispatchOptions) -> Self { + self.options = options; + self + } + + pub fn may_block(mut self, may_block: bool) -> Self { + self.options = self.options.may_block(may_block); + self + } + + pub unsafe fn at_end(mut self, at_end: bool) -> Self { + self.options = self.options.at_end(at_end); + self + } +} + +impl<F> RunnableBuilder<F> +where + F: FnOnce() + Send + 'static, +{ + /// Dispatch this Runnable to the specified EventTarget. The runnable function must be `Send`. + pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), nsresult> { + let runnable = RunnableFunction::allocate(InitRunnableFunction { + name: self.name, + priority: self.priority, + function: Mutex::new(Some(self.function)), + }); + unsafe { dispatch_runnable(runnable.coerce(), target, self.options) } + } + + /// Dispatch this Runnable to the specified EventTarget as a background + /// task. The runnable function must be `Send`. + pub fn dispatch_background_task(self) -> Result<(), nsresult> { + let runnable = RunnableFunction::allocate(InitRunnableFunction { + name: self.name, + priority: self.priority, + function: Mutex::new(Some(self.function)), + }); + unsafe { dispatch_background_task_runnable(runnable.coerce(), self.options) } + } +} + +impl<F> RunnableBuilder<F> +where + F: FnOnce() + 'static, +{ + /// Dispatch this Runnable to the current thread. + /// + /// Unlike `dispatch` and `dispatch_background_task`, the runnable does not + /// need to be `Send` to dispatch to the current thread. + pub fn dispatch_local(self) -> Result<(), nsresult> { + let target = get_current_thread()?; + let runnable = RunnableFunction::allocate(InitRunnableFunction { + name: self.name, + priority: self.priority, + function: Mutex::new(Some(self.function)), + }); + unsafe { dispatch_runnable(runnable.coerce(), target.coerce(), self.options) } + } +} + +pub fn dispatch_onto<F>( + name: &'static str, + target: &nsIEventTarget, + function: F, +) -> Result<(), nsresult> +where + F: FnOnce() + Send + 'static, +{ + RunnableBuilder::new(name, function).dispatch(target) +} + +pub fn dispatch_background_task<F>(name: &'static str, function: F) -> Result<(), nsresult> +where + F: FnOnce() + Send + 'static, +{ + RunnableBuilder::new(name, function).dispatch_background_task() +} + +pub fn dispatch_local<F>(name: &'static str, function: F) -> Result<(), nsresult> +where + F: FnOnce() + 'static, +{ + RunnableBuilder::new(name, function).dispatch_local() +} diff --git a/xpcom/rust/moz_task/src/event_loop.rs b/xpcom/rust/moz_task/src/event_loop.rs new file mode 100644 index 0000000000..f8d113ed57 --- /dev/null +++ b/xpcom/rust/moz_task/src/event_loop.rs @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate nsstring; + +use cstr::cstr; +use nserror::{nsresult, NS_ERROR_SERVICE_NOT_AVAILABLE, NS_ERROR_UNEXPECTED, NS_OK}; +use nsstring::*; +use std::cell::RefCell; +use std::future::Future; +use xpcom::{interfaces::nsIThreadManager, xpcom, xpcom_method}; + +#[xpcom(implement(nsINestedEventLoopCondition), nonatomic)] +struct FutureCompleteCondition<T: 'static> { + value: RefCell<Option<T>>, +} + +impl<T: 'static> FutureCompleteCondition<T> { + xpcom_method!(is_done => IsDone() -> bool); + fn is_done(&self) -> Result<bool, nsresult> { + Ok(self.value.borrow().is_some()) + } +} + +/// Spin the event loop on the current thread until `future` is resolved. +/// +/// # Safety +/// +/// Spinning a nested event loop should always be avoided when possible, as it +/// can cause hangs, break JS run-to-completion guarantees, and break other C++ +/// code currently on the stack relying on heap invariants. While in a pure-rust +/// codebase this method would only be ill-advised and not technically "unsafe", +/// it is marked as unsafe due to the potential for triggering unsafety in +/// unrelated C++ code. +pub unsafe fn spin_event_loop_until<F>( + reason: &'static str, + future: F, +) -> Result<F::Output, nsresult> +where + F: Future + 'static, + F::Output: 'static, +{ + let thread_manager = + xpcom::get_service::<nsIThreadManager>(cstr!("@mozilla.org/thread-manager;1")) + .ok_or(NS_ERROR_SERVICE_NOT_AVAILABLE)?; + + let cond = FutureCompleteCondition::<F::Output>::allocate(InitFutureCompleteCondition { + value: RefCell::new(None), + }); + + // Spawn our future onto the current thread event loop, and record the + // completed value as it completes. + let cond2 = cond.clone(); + crate::spawn_local(reason, async move { + let rv = future.await; + *cond2.value.borrow_mut() = Some(rv); + }) + .detach(); + + thread_manager + .SpinEventLoopUntil(&*nsCStr::from(reason), cond.coerce()) + .to_result()?; + let rv = cond.value.borrow_mut().take(); + rv.ok_or(NS_ERROR_UNEXPECTED) +} diff --git a/xpcom/rust/moz_task/src/executor.rs b/xpcom/rust/moz_task/src/executor.rs new file mode 100644 index 0000000000..0016839373 --- /dev/null +++ b/xpcom/rust/moz_task/src/executor.rs @@ -0,0 +1,291 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::{get_current_thread, DispatchOptions, RunnableBuilder}; +use std::{ + cell::Cell, + fmt::Debug, + future::Future, + pin::Pin, + ptr, + sync::Arc, + task::{Context, Poll}, +}; +use xpcom::interfaces::{nsIEventTarget, nsIRunnablePriority}; +use xpcom::RefPtr; + +/// A spawned task. +/// +/// A [`AsyncTask`] can be awaited to retrieve the output of its future. +/// +/// Dropping an [`AsyncTask`] cancels it, which means its future won't be polled +/// again. To drop the [`AsyncTask`] handle without canceling it, use +/// [`detach()`][`AsyncTask::detach()`] instead. To cancel a task gracefully and +/// wait until it is fully destroyed, use the [`cancel()`][AsyncTask::cancel()] +/// method. +/// +/// A task which is cancelled due to the nsIEventTarget it was dispatched to no +/// longer accepting events will never be resolved. +#[derive(Debug)] +#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"] +pub struct AsyncTask<T> { + task: async_task::FallibleTask<T>, +} + +impl<T> AsyncTask<T> { + fn new(task: async_task::Task<T>) -> Self { + AsyncTask { + task: task.fallible(), + } + } + + /// Detaches the task to let it keep running in the background. + pub fn detach(self) { + self.task.detach() + } + + /// Cancels the task and waits for it to stop running. + /// + /// Returns the task's output if it was completed just before it got canceled, or [`None`] if + /// it didn't complete. + /// + /// While it's possible to simply drop the [`Task`] to cancel it, this is a cleaner way of + /// canceling because it also waits for the task to stop running. + pub async fn cancel(self) -> Option<T> { + self.task.cancel().await + } +} + +impl<T> Future for AsyncTask<T> { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + // Wrap the future produced by `AsyncTask` to never resolve if the + // Runnable was dropped, and the task was cancelled. + match Pin::new(&mut self.task).poll(cx) { + Poll::Ready(Some(t)) => Poll::Ready(t), + Poll::Ready(None) | Poll::Pending => Poll::Pending, + } + } +} + +enum SpawnTarget { + BackgroundTask, + EventTarget(RefPtr<nsIEventTarget>), +} + +// SAFETY: All XPCOM interfaces are considered !Send + !Sync, however all +// well-behaved nsIEventTarget instances must be threadsafe. +unsafe impl Send for SpawnTarget {} +unsafe impl Sync for SpawnTarget {} + +/// Information used by tasks as they are spawned. Stored in an Arc such that +/// their identity can be used for `POLLING_TASK`. +struct TaskSpawnConfig { + name: &'static str, + priority: u32, + options: DispatchOptions, + target: SpawnTarget, +} + +thread_local! { + /// Raw pointer to the TaskSpawnConfig for the currently polling task. Used + /// to detect scheduling callbacks for a runnable while it is polled, to set + /// `DISPATCH_AT_END` on the notification. + static POLLING_TASK: Cell<*const TaskSpawnConfig> = Cell::new(ptr::null()); +} + +fn schedule(config: Arc<TaskSpawnConfig>, runnable: async_task::Runnable) { + // If we're dispatching this task while it is currently running on the same + // thread, set the `DISPATCH_AT_END` flag in the dispatch options to tell + // our threadpool target to not bother to spin up another thread. + let currently_polling = POLLING_TASK.with(|t| t.get() == Arc::as_ptr(&config)); + + // SAFETY: We use the POLLING_TASK thread local to check if we meet the + // requirements for `at_end`. + let options = unsafe { config.options.at_end(currently_polling) }; + + // Build the RunnableBuilder for our task to be dispatched. + let config2 = config.clone(); + let builder = RunnableBuilder::new(config.name, move || { + // Record the pointer for the currently executing task in the + // POLLING_TASK thread-local so that nested dispatches can detect it. + POLLING_TASK.with(|t| { + let prev = t.get(); + t.set(Arc::as_ptr(&config2)); + runnable.run(); + t.set(prev); + }); + }) + .priority(config.priority) + .options(options); + + let rv = match &config.target { + SpawnTarget::BackgroundTask => builder.dispatch_background_task(), + SpawnTarget::EventTarget(target) => builder.dispatch(&*target), + }; + if let Err(err) = rv { + log::warn!( + "dispatch for spawned task '{}' failed: {:?}", + config.name, + err + ); + } +} + +/// Helper for starting an async task which will run a future to completion. +#[derive(Debug)] +pub struct TaskBuilder<F> { + name: &'static str, + future: F, + priority: u32, + options: DispatchOptions, +} + +impl<F> TaskBuilder<F> { + pub fn new(name: &'static str, future: F) -> TaskBuilder<F> { + TaskBuilder { + name, + future, + priority: nsIRunnablePriority::PRIORITY_NORMAL, + options: DispatchOptions::default(), + } + } + + /// Specify the priority of the task's runnables. + pub fn priority(mut self, priority: u32) -> Self { + self.priority = priority; + self + } + + /// Specify options to use when dispatching the task. + pub fn options(mut self, options: DispatchOptions) -> Self { + self.options = options; + self + } + + /// Set whether or not the event may block, and should be run on the IO + /// thread pool. + pub fn may_block(mut self, may_block: bool) -> Self { + self.options = self.options.may_block(may_block); + self + } +} + +impl<F> TaskBuilder<F> +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + /// Run the future on the background task pool. + pub fn spawn(self) -> AsyncTask<F::Output> { + let config = Arc::new(TaskSpawnConfig { + name: self.name, + priority: self.priority, + options: self.options, + target: SpawnTarget::BackgroundTask, + }); + let (runnable, task) = async_task::spawn(self.future, move |runnable| { + schedule(config.clone(), runnable) + }); + runnable.schedule(); + AsyncTask::new(task) + } + + /// Run the future on the specified nsIEventTarget. + pub fn spawn_onto(self, target: &nsIEventTarget) -> AsyncTask<F::Output> { + let config = Arc::new(TaskSpawnConfig { + name: self.name, + priority: self.priority, + options: self.options, + target: SpawnTarget::EventTarget(RefPtr::new(target)), + }); + let (runnable, task) = async_task::spawn(self.future, move |runnable| { + schedule(config.clone(), runnable) + }); + runnable.schedule(); + AsyncTask::new(task) + } +} + +impl<F> TaskBuilder<F> +where + F: Future + 'static, + F::Output: 'static, +{ + /// Run the future on the current thread. + /// + /// Unlike the other `spawn` methods, this method supports non-Send futures. + /// + /// # Panics + /// + /// This method may panic if run on a thread which cannot run local futures + /// (e.g. due to it is not being an XPCOM thread, or if we are very late + /// during shutdown). + pub fn spawn_local(self) -> AsyncTask<F::Output> { + let current_thread = get_current_thread().expect("cannot get current thread"); + let config = Arc::new(TaskSpawnConfig { + name: self.name, + priority: self.priority, + options: self.options, + target: SpawnTarget::EventTarget(RefPtr::new(current_thread.coerce())), + }); + let (runnable, task) = async_task::spawn_local(self.future, move |runnable| { + schedule(config.clone(), runnable) + }); + runnable.schedule(); + AsyncTask::new(task) + } +} + +/// Spawn a future onto the background task pool. The future will not be run on +/// the main thread. +pub fn spawn<F>(name: &'static str, future: F) -> AsyncTask<F::Output> +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future).spawn() +} + +/// Spawn a potentially-blocking future onto the background task pool. The +/// future will not be run on the main thread. +pub fn spawn_blocking<F>(name: &'static str, future: F) -> AsyncTask<F::Output> +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future).may_block(true).spawn() +} + +/// Spawn a local future onto the current thread. +pub fn spawn_local<F>(name: &'static str, future: F) -> AsyncTask<F::Output> +where + F: Future + 'static, + F::Output: 'static, +{ + TaskBuilder::new(name, future).spawn_local() +} + +pub fn spawn_onto<F>(name: &'static str, target: &nsIEventTarget, future: F) -> AsyncTask<F::Output> +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future).spawn_onto(target) +} + +pub fn spawn_onto_blocking<F>( + name: &'static str, + target: &nsIEventTarget, + future: F, +) -> AsyncTask<F::Output> +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future) + .may_block(true) + .spawn_onto(target) +} diff --git a/xpcom/rust/moz_task/src/lib.rs b/xpcom/rust/moz_task/src/lib.rs new file mode 100644 index 0000000000..2f0c0cfd0a --- /dev/null +++ b/xpcom/rust/moz_task/src/lib.rs @@ -0,0 +1,378 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 module wraps XPCOM threading functions with Rust functions +//! to make it safer and more convenient to call the XPCOM functions. +//! It also provides the Task trait and TaskRunnable struct, +//! which make it easier to dispatch tasks to threads. + +mod dispatcher; +pub use dispatcher::{dispatch_background_task, dispatch_local, dispatch_onto, RunnableBuilder}; +mod event_loop; +mod executor; +pub use executor::{ + spawn, spawn_blocking, spawn_local, spawn_onto, spawn_onto_blocking, AsyncTask, TaskBuilder, +}; + +// Expose functions intended to be used only in gtest via this module. +// We don't use a feature gate here to stop the need to compile all crates that +// depend upon `moz_task` twice. +pub mod gtest_only { + pub use crate::event_loop::spin_event_loop_until; +} + +use nserror::nsresult; +use nsstring::{nsACString, nsCString}; +use std::{ffi::CStr, marker::PhantomData, mem, ptr}; +use xpcom::{ + getter_addrefs, + interfaces::{nsIEventTarget, nsIRunnable, nsISerialEventTarget, nsISupports, nsIThread}, + AtomicRefcnt, RefCounted, RefPtr, XpCom, +}; + +extern "C" { + fn NS_GetCurrentThreadRust(result: *mut *const nsIThread) -> nsresult; + fn NS_GetMainThreadRust(result: *mut *const nsIThread) -> nsresult; + fn NS_IsMainThread() -> bool; + fn NS_NewNamedThreadWithDefaultStackSize( + name: *const nsACString, + result: *mut *const nsIThread, + event: *const nsIRunnable, + ) -> nsresult; + fn NS_IsOnCurrentThread(target: *const nsIEventTarget) -> bool; + fn NS_ProxyReleaseISupports( + name: *const libc::c_char, + target: *const nsIEventTarget, + doomed: *const nsISupports, + always_proxy: bool, + ); + fn NS_CreateBackgroundTaskQueue( + name: *const libc::c_char, + target: *mut *const nsISerialEventTarget, + ) -> nsresult; + fn NS_DispatchBackgroundTask(event: *const nsIRunnable, flags: u32) -> nsresult; +} + +pub fn get_current_thread() -> Result<RefPtr<nsIThread>, nsresult> { + getter_addrefs(|p| unsafe { NS_GetCurrentThreadRust(p) }) +} + +pub fn get_main_thread() -> Result<RefPtr<nsIThread>, nsresult> { + getter_addrefs(|p| unsafe { NS_GetMainThreadRust(p) }) +} + +pub fn is_main_thread() -> bool { + unsafe { NS_IsMainThread() } +} + +// There's no OS requirement that thread names be static, but dynamic thread +// names tend to conceal more than they reveal when processing large numbers of +// crash reports. +pub fn create_thread(name: &'static str) -> Result<RefPtr<nsIThread>, nsresult> { + getter_addrefs(|p| unsafe { + NS_NewNamedThreadWithDefaultStackSize(&*nsCString::from(name), p, ptr::null()) + }) +} + +pub fn is_on_current_thread(target: &nsIEventTarget) -> bool { + unsafe { NS_IsOnCurrentThread(target) } +} + +/// Creates a queue that runs tasks on the background thread pool. The tasks +/// will run in the order they're dispatched, one after the other. +pub fn create_background_task_queue( + name: &'static CStr, +) -> Result<RefPtr<nsISerialEventTarget>, nsresult> { + getter_addrefs(|p| unsafe { NS_CreateBackgroundTaskQueue(name.as_ptr(), p) }) +} + +/// Dispatches a one-shot runnable to an event target, like a thread or a +/// task queue, with the given options. +/// +/// This function leaks the runnable if dispatch fails. +/// +/// # Safety +/// +/// As there is no guarantee that the runnable is actually `Send + Sync`, we +/// can't know that it's safe to dispatch an `nsIRunnable` to any +/// `nsIEventTarget`. +pub unsafe fn dispatch_runnable( + runnable: &nsIRunnable, + target: &nsIEventTarget, + options: DispatchOptions, +) -> Result<(), nsresult> { + // NOTE: DispatchFromScript performs an AddRef on `runnable` which is + // why this function leaks on failure. + target + .DispatchFromScript(runnable, options.flags()) + .to_result() +} + +/// Dispatches a one-shot task runnable to the background thread pool with the +/// given options. The task may run concurrently with other background tasks. +/// If you need tasks to run in a specific order, please create a background +/// task queue using `create_background_task_queue`, and dispatch tasks to it +/// instead. +/// +/// This function leaks the runnable if dispatch fails. This avoids a race where +/// a runnable can be destroyed on either the original or target thread, which +/// is important if the runnable holds thread-unsafe members. +/// +/// ### Safety +/// +/// As there is no guarantee that the runnable is actually `Send + Sync`, we +/// can't know that it's safe to dispatch an `nsIRunnable` to any +/// `nsIEventTarget`. +pub unsafe fn dispatch_background_task_runnable( + runnable: &nsIRunnable, + options: DispatchOptions, +) -> Result<(), nsresult> { + // This eventually calls the non-`already_AddRefed<nsIRunnable>` overload of + // `nsIEventTarget::Dispatch` (see xpcom/threads/nsIEventTarget.idl#20-25), + // which adds an owning reference and leaks if dispatch fails. + NS_DispatchBackgroundTask(runnable, options.flags()).to_result() +} + +/// Options to control how task runnables are dispatched. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct DispatchOptions(u32); + +impl Default for DispatchOptions { + #[inline] + fn default() -> Self { + DispatchOptions(nsIEventTarget::DISPATCH_NORMAL) + } +} + +impl DispatchOptions { + /// Creates a blank set of options. The runnable will be dispatched using + /// the default mode. + #[inline] + pub fn new() -> Self { + DispatchOptions::default() + } + + /// Indicates whether or not the dispatched runnable may block its target + /// thread by waiting on I/O. If `true`, the runnable may be dispatched to a + /// dedicated thread pool, leaving the main pool free for CPU-bound tasks. + #[inline] + pub fn may_block(self, may_block: bool) -> DispatchOptions { + const FLAG: u32 = nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK; + if may_block { + DispatchOptions(self.flags() | FLAG) + } else { + DispatchOptions(self.flags() & !FLAG) + } + } + + /// Specifies that the dispatch is occurring from a running event that was + /// dispatched to the same event target, and that event is about to finish. + /// + /// A thread pool can use this as an optimization hint to not spin up + /// another thread, since the current thread is about to become idle. + /// + /// Setting this flag is unsafe, as it may only be used from the target + /// event target when the event is about to finish. + #[inline] + pub unsafe fn at_end(self, may_block: bool) -> DispatchOptions { + const FLAG: u32 = nsIEventTarget::DISPATCH_AT_END; + if may_block { + DispatchOptions(self.flags() | FLAG) + } else { + DispatchOptions(self.flags() & !FLAG) + } + } + + /// Returns the set of bitflags to pass to `DispatchFromScript`. + #[inline] + fn flags(self) -> u32 { + self.0 + } +} + +/// A task represents an operation that asynchronously executes on a target +/// thread, and returns its result to the original thread. +/// +/// # Alternatives +/// +/// This trait is no longer necessary for basic tasks to be dispatched to +/// another thread with a callback on the originating thread. `moz_task` now has +/// a series of more rust-like primitives which can be used instead. For +/// example, it may be preferable to use the async executor over `Task`: +/// +/// ```ignore +/// // Spawn a task onto the background task pool, and capture the result of its +/// // execution. +/// let bg_task = moz_task::spawn("Example", async move { +/// do_background_work(captured_state) +/// }); +/// +/// // Spawn another task on the calling thread which will await on the result +/// // of the async operation, and invoke a non-Send callback. This task won't +/// // be awaited on, so needs to be `detach`-ed. +/// moz_task::spawn_local("Example", async move { +/// callback.completed(bg_task.await); +/// }) +/// .detach(); +/// ``` +/// +/// If no result is needed, the task returned from `spawn` may be also detached +/// directly. +pub trait Task { + // FIXME: These could accept `&mut`. + fn run(&self); + fn done(&self) -> Result<(), nsresult>; +} + +pub struct TaskRunnable { + name: &'static str, + task: Box<dyn Task + Send + Sync>, +} + +impl TaskRunnable { + // XXX: Fixme: clean up this old API. (bug 1744312) + pub fn new( + name: &'static str, + task: Box<dyn Task + Send + Sync>, + ) -> Result<TaskRunnable, nsresult> { + Ok(TaskRunnable { name, task }) + } + + pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), nsresult> { + self.dispatch_with_options(target, DispatchOptions::default()) + } + + pub fn dispatch_with_options( + self, + target: &nsIEventTarget, + options: DispatchOptions, + ) -> Result<(), nsresult> { + // Perform `task.run()` on a background thread. + let task = self.task; + let handle = TaskBuilder::new(self.name, async move { + task.run(); + task + }) + .options(options) + .spawn_onto(target); + + // Run `task.done()` on the starting thread once the background thread + // is done with the task. + spawn_local(self.name, async move { + let task = handle.await; + let _ = task.done(); + }) + .detach(); + Ok(()) + } + + pub fn dispatch_background_task_with_options( + self, + options: DispatchOptions, + ) -> Result<(), nsresult> { + // Perform `task.run()` on a background thread. + let task = self.task; + let handle = TaskBuilder::new(self.name, async move { + task.run(); + task + }) + .options(options) + .spawn(); + + // Run `task.done()` on the starting thread once the background thread + // is done with the task. + spawn_local(self.name, async move { + let task = handle.await; + let _ = task.done(); + }) + .detach(); + Ok(()) + } +} + +pub type ThreadPtrHandle<T> = RefPtr<ThreadPtrHolder<T>>; + +/// A Rust analog to `nsMainThreadPtrHolder` that wraps an `nsISupports` object +/// with thread-safe refcounting. The holder keeps one reference to the wrapped +/// object that's released when the holder's refcount reaches zero. +pub struct ThreadPtrHolder<T: XpCom + 'static> { + ptr: *const T, + marker: PhantomData<T>, + name: &'static CStr, + owning_thread: RefPtr<nsIThread>, + refcnt: AtomicRefcnt, +} + +unsafe impl<T: XpCom + 'static> Send for ThreadPtrHolder<T> {} +unsafe impl<T: XpCom + 'static> Sync for ThreadPtrHolder<T> {} + +unsafe impl<T: XpCom + 'static> RefCounted for ThreadPtrHolder<T> { + unsafe fn addref(&self) { + self.refcnt.inc(); + } + + unsafe fn release(&self) { + let rc = self.refcnt.dec(); + if rc == 0 { + // Once the holder's count reaches zero, release the wrapped + // object... + if !self.ptr.is_null() { + // The holder can be released on any thread. If we're on the + // owning thread, we can release the object directly. Otherwise, + // we need to post a proxy release event to release the object + // on the owning thread. + if is_on_current_thread(&self.owning_thread) { + (*self.ptr).release() + } else { + NS_ProxyReleaseISupports( + self.name.as_ptr(), + self.owning_thread.coerce(), + self.ptr as *const T as *const nsISupports, + false, + ); + } + } + // ...And deallocate the holder. + mem::drop(Box::from_raw(self as *const Self as *mut Self)); + } + } +} + +impl<T: XpCom + 'static> ThreadPtrHolder<T> { + /// Creates a new owning thread pointer holder. Returns an error if the + /// thread manager has shut down. Panics if `name` isn't a valid C string. + pub fn new(name: &'static CStr, ptr: RefPtr<T>) -> Result<RefPtr<Self>, nsresult> { + let owning_thread = get_current_thread()?; + // Take ownership of the `RefPtr`. This does _not_ decrement its + // refcount, which is what we want. Once we've released all references + // to the holder, we'll release the wrapped `RefPtr`. + let raw: *const T = &*ptr; + mem::forget(ptr); + unsafe { + let boxed = Box::new(ThreadPtrHolder { + name, + ptr: raw, + marker: PhantomData, + owning_thread, + refcnt: AtomicRefcnt::new(), + }); + Ok(RefPtr::from_raw(Box::into_raw(boxed)).unwrap()) + } + } + + /// Returns the wrapped object's owning thread. + pub fn owning_thread(&self) -> &nsIThread { + &self.owning_thread + } + + /// Returns the wrapped object if called from the owning thread, or + /// `None` if called from any other thread. + pub fn get(&self) -> Option<&T> { + if is_on_current_thread(&self.owning_thread) && !self.ptr.is_null() { + unsafe { Some(&*self.ptr) } + } else { + None + } + } +} diff --git a/xpcom/rust/nserror/Cargo.toml b/xpcom/rust/nserror/Cargo.toml new file mode 100644 index 0000000000..4fca5a4c2c --- /dev/null +++ b/xpcom/rust/nserror/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nserror" +version = "0.1.0" +authors = ["Nika Layzell <nika@thelayzells.com>"] +license = "MPL-2.0" +description = "Rust bindings to xpcom nsresult and NS_ERROR_ values" +edition = "2018" + +[dependencies] +nsstring = { path = "../nsstring" } +mozbuild = "0.1" diff --git a/xpcom/rust/nserror/src/lib.rs b/xpcom/rust/nserror/src/lib.rs new file mode 100644 index 0000000000..e4107d1b57 --- /dev/null +++ b/xpcom/rust/nserror/src/lib.rs @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use nsstring::{nsACString, nsCString}; +use std::error::Error; +use std::fmt; + +/// The type of errors in gecko. Uses a newtype to provide additional type +/// safety in Rust and #[repr(transparent)] to ensure the same representation +/// as the C++ equivalent. +#[repr(transparent)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct nsresult(pub u32); + +impl nsresult { + pub fn failed(self) -> bool { + (self.0 >> 31) != 0 + } + + pub fn succeeded(self) -> bool { + !self.failed() + } + + pub fn to_result(self) -> Result<(), nsresult> { + if self.failed() { + Err(self) + } else { + Ok(()) + } + } + + /// Get a printable name for the nsresult error code. This function returns + /// a nsCString<'static>, which implements `Display`. + pub fn error_name(self) -> nsCString { + let mut cstr = nsCString::new(); + unsafe { + Gecko_GetErrorName(self, &mut *cstr); + } + cstr + } +} + +impl fmt::Display for nsresult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.error_name()) + } +} + +impl fmt::Debug for nsresult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.error_name()) + } +} + +impl<T, E> From<Result<T, E>> for nsresult +where + E: Into<nsresult>, +{ + fn from(result: Result<T, E>) -> nsresult { + match result { + Ok(_) => NS_OK, + Err(e) => e.into(), + } + } +} + +impl Error for nsresult {} + +extern "C" { + fn Gecko_GetErrorName(rv: nsresult, cstr: *mut nsACString); +} + +mod error_list { + include!(mozbuild::objdir_path!("xpcom/base/error_list.rs")); +} + +pub use error_list::*; diff --git a/xpcom/rust/nsstring/Cargo.toml b/xpcom/rust/nsstring/Cargo.toml new file mode 100644 index 0000000000..7d4b3e5456 --- /dev/null +++ b/xpcom/rust/nsstring/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nsstring" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Rust bindings to xpcom string types" +edition = "2018" + +[features] +gecko_debug = [] + +[dependencies] +bitflags = "2" +encoding_rs = "0.8.0" diff --git a/xpcom/rust/nsstring/src/conversions.rs b/xpcom/rust/nsstring/src/conversions.rs new file mode 100644 index 0000000000..c72c195c08 --- /dev/null +++ b/xpcom/rust/nsstring/src/conversions.rs @@ -0,0 +1,751 @@ +/* This Source Code Form is subject to the terms of 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/. */ + +use crate::{ + nsACString, nsAString, nsCStringLike, BulkWriteOk, Gecko_FallibleAssignCString, + Latin1StringLike, +}; +use encoding_rs::mem::*; +use encoding_rs::Encoding; +use std::slice; + +/// Required math stated in the docs of +/// `convert_utf16_to_utf8()`. +#[inline(always)] +fn times_three(a: usize) -> Option<usize> { + a.checked_mul(3) +} + +#[inline(always)] +fn identity(a: usize) -> Option<usize> { + Some(a) +} + +#[inline(always)] +fn plus_one(a: usize) -> Option<usize> { + a.checked_add(1) +} + +/// Typical cache line size per +/// https://stackoverflow.com/questions/14707803/line-size-of-l1-and-l2-caches +/// +/// For consistent behavior, not trying to use 128 on aarch64 +/// or other fanciness like that. +const CACHE_LINE: usize = 64; + +const CACHE_LINE_MASK: usize = CACHE_LINE - 1; + +/// Returns true if the string is both longer than a cache line +/// and the first cache line is ASCII. +#[inline(always)] +fn long_string_starts_with_ascii(buffer: &[u8]) -> bool { + // We examine data only up to the end of the cache line + // to make this check minimally disruptive. + if buffer.len() <= CACHE_LINE { + return false; + } + let bound = CACHE_LINE - ((buffer.as_ptr() as usize) & CACHE_LINE_MASK); + is_ascii(&buffer[..bound]) +} + +/// Returns true if the string is both longer than two cache lines +/// and the first two cache lines are Basic Latin. +#[inline(always)] +fn long_string_stars_with_basic_latin(buffer: &[u16]) -> bool { + // We look at two cache lines with code unit size of two. There is need + // to look at more than one cache line in the UTF-16 case, because looking + // at just one cache line wouldn't catch non-ASCII Latin with high enough + // probability with Latin-script languages that have relatively infrequent + // non-ASCII characters. + if buffer.len() <= CACHE_LINE { + return false; + } + let bound = (CACHE_LINE * 2 - ((buffer.as_ptr() as usize) & CACHE_LINE_MASK)) / 2; + is_basic_latin(&buffer[..bound]) +} + +// Ignoring the copy avoidance complications of conversions between Latin1 and +// UTF-8, a conversion function has the outward form of +// `fn F(&mut self, other: &[T], old_len: usize) -> Result<BulkWriteOk, ()>`, +// where `T` is either `u8` or `u16`. `other` is the slice whose converted +// content are to be appended to `self` and `old_len` indicates how many +// code unit of `self` are to be preserved (0 for the assignment case and +// `self.len()` for the appending case). +// +// As implementation parameters a conversion function needs to know the +// math for computing the worst case conversion length in code units given +// the input length in code units. For a _constant conversion_ the number +// of code units the conversion produces equals the number of code units +// in the input. For a _shinking conversion_ the maximum number of code +// units the conversion can produce equals the number of code units in +// the input, but the conversion can produce fewer code units. Still, due +// to implementation details, the function might want _one_ unit more of +// output space. For an _expanding conversion_ (no need for macro), the +// minimum number of code units produced by the conversion is the number +// of code units in the input, but the conversion can produce more. +// +// Copy avoidance conversions avoid copying a refcounted buffer when it's +// ASCII-only. +// +// Internally, a conversion function needs to know the underlying +// encoding_rs conversion function, the math for computing the required +// output buffer size and, depending on the case, the underlying +// encoding_rs ASCII prefix handling function. + +/// A conversion where the number of code units in the output is potentially +/// smaller than the number of code units in the input. +/// +/// Takes the name of the method to be generated, the name of the conversion +/// function and the type of the input slice. +/// +/// `$name` is the name of the function to generate +/// `$convert` is the underlying `encoding_rs::mem` function to use +/// `$other_ty` is the type of the input slice +/// `$math` is the worst-case length math that `$convert` expects +macro_rules! shrinking_conversion { + (name = $name:ident, + convert = $convert:ident, + other_ty = $other_ty:ty, + math = $math:ident) => { + fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<BulkWriteOk, ()> { + let needed = $math(other.len()).ok_or(())?; + let mut handle = + unsafe { self.bulk_write(old_len.checked_add(needed).ok_or(())?, old_len, false)? }; + let written = $convert(other, &mut handle.as_mut_slice()[old_len..]); + let new_len = old_len + written; + Ok(handle.finish(new_len, new_len > CACHE_LINE)) + } + }; +} + +/// A conversion where the number of code units in the output is always equal +/// to the number of code units in the input. +/// +/// Takes the name of the method to be generated, the name of the conversion +/// function and the type of the input slice. +/// +/// `$name` is the name of the function to generate +/// `$convert` is the underlying `encoding_rs::mem` function to use +/// `$other_ty` is the type of the input slice +macro_rules! constant_conversion { + (name = $name:ident, + convert = $convert:ident, + other_ty = $other_ty:ty) => { + fn $name( + &mut self, + other: $other_ty, + old_len: usize, + allow_shrinking: bool, + ) -> Result<BulkWriteOk, ()> { + let new_len = old_len.checked_add(other.len()).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len, old_len, allow_shrinking)? }; + $convert(other, &mut handle.as_mut_slice()[old_len..]); + Ok(handle.finish(new_len, false)) + } + }; +} + +/// An intermediate check for avoiding a copy and having an `nsStringBuffer` +/// refcount increment instead when both `self` and `other` are `nsACString`s, +/// `other` is entirely ASCII and all old data in `self` is discarded. +/// +/// `$name` is the name of the function to generate +/// `$impl` is the underlying conversion that takes a slice and that is used +/// when we can't just adopt the incoming buffer as-is +/// `$string_like` is the kind of input taken +macro_rules! ascii_copy_avoidance { + (name = $name:ident, + implementation = $implementation:ident, + string_like = $string_like:ident) => { + fn $name<T: $string_like + ?Sized>( + &mut self, + other: &T, + old_len: usize, + ) -> Result<BulkWriteOk, ()> { + let adapter = other.adapt(); + let other_slice = adapter.as_ref(); + let num_ascii = if adapter.is_abstract() && old_len == 0 { + let up_to = Encoding::ascii_valid_up_to(other_slice); + if up_to == other_slice.len() { + // Calling something whose argument can be obtained from + // the adapter rather than an nsStringLike avoids a huge + // lifetime mess by keeping nsStringLike and + // Latin1StringLike free of lifetime interdependencies. + if unsafe { Gecko_FallibleAssignCString(self, other.adapt().as_ptr()) } { + return Ok(BulkWriteOk {}); + } else { + return Err(()); + } + } + Some(up_to) + } else { + None + }; + self.$implementation(other_slice, old_len, num_ascii) + } + }; +} + +impl nsAString { + // Valid UTF-8 to UTF-16 + + // Documentation says the destination buffer needs to have + // as many code units as the input. + shrinking_conversion!( + name = fallible_append_str_impl, + convert = convert_str_to_utf16, + other_ty = &str, + math = identity + ); + + /// Convert a valid UTF-8 string into valid UTF-16 and replace the content + /// of this string with the conversion result. + pub fn assign_str(&mut self, other: &str) { + self.fallible_append_str_impl(other, 0) + .expect("Out of memory"); + } + + /// Convert a valid UTF-8 string into valid UTF-16 and fallibly replace the + /// content of this string with the conversion result. + pub fn fallible_assign_str(&mut self, other: &str) -> Result<(), ()> { + self.fallible_append_str_impl(other, 0).map(|_| ()) + } + + /// Convert a valid UTF-8 string into valid UTF-16 and append the conversion + /// to this string. + pub fn append_str(&mut self, other: &str) { + let len = self.len(); + self.fallible_append_str_impl(other, len) + .expect("Out of memory"); + } + + /// Convert a valid UTF-8 string into valid UTF-16 and fallibly append the + /// conversion to this string. + pub fn fallible_append_str(&mut self, other: &str) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_str_impl(other, len).map(|_| ()) + } + + // Potentially-invalid UTF-8 to UTF-16 + + // Documentation says the destination buffer needs to have + // one more code unit than the input. + shrinking_conversion!( + name = fallible_append_utf8_impl, + convert = convert_utf8_to_utf16, + other_ty = &[u8], + math = plus_one + ); + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// replace the content of this string with the conversion result. + pub fn assign_utf8(&mut self, other: &[u8]) { + self.fallible_append_utf8_impl(other, 0) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly replace the content of this string with the conversion result. + pub fn fallible_assign_utf8(&mut self, other: &[u8]) -> Result<(), ()> { + self.fallible_append_utf8_impl(other, 0).map(|_| ()) + } + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// append the conversion result to this string. + pub fn append_utf8(&mut self, other: &[u8]) { + let len = self.len(); + self.fallible_append_utf8_impl(other, len) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly append the conversion result to this string. + pub fn fallible_append_utf8(&mut self, other: &[u8]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf8_impl(other, len).map(|_| ()) + } + + // Latin1 to UTF-16 + + constant_conversion!( + name = fallible_append_latin1_impl, + convert = convert_latin1_to_utf16, + other_ty = &[u8] + ); + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and replace the content of this string with the conversion result. + pub fn assign_latin1(&mut self, other: &[u8]) { + self.fallible_append_latin1_impl(other, 0, true) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and fallibly replace the content of this string with the + /// conversion result. + pub fn fallible_assign_latin1(&mut self, other: &[u8]) -> Result<(), ()> { + self.fallible_append_latin1_impl(other, 0, true).map(|_| ()) + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and append the conversion result to this string. + pub fn append_latin1(&mut self, other: &[u8]) { + let len = self.len(); + self.fallible_append_latin1_impl(other, len, false) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and fallibly append the conversion result to this string. + pub fn fallible_append_latin1(&mut self, other: &[u8]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_latin1_impl(other, len, false) + .map(|_| ()) + } +} + +impl nsACString { + // UTF-16 to UTF-8 + + fn fallible_append_utf16_to_utf8_impl( + &mut self, + other: &[u16], + old_len: usize, + ) -> Result<BulkWriteOk, ()> { + // We first size the buffer for ASCII if the first two cache lines are ASCII. If that turns out + // not to be enough, we size for the worst case given the length of the remaining input at that + // point. BUT if the worst case fits inside the inline capacity of an autostring, we skip + // the ASCII stuff. + let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() { + let worst_case = times_three(other.len()).ok_or(())?; + if worst_case <= inline_capacity { + Some(worst_case) + } else { + None + } + } else { + None + }; + let (filled, read, mut handle) = + if worst_case_needed.is_none() && long_string_stars_with_basic_latin(other) { + let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len_with_ascii, old_len, false)? }; + let (read, written) = + convert_utf16_to_utf8_partial(other, &mut handle.as_mut_slice()[old_len..]); + let left = other.len() - read; + if left == 0 { + return Ok(handle.finish(old_len + written, true)); + } + let filled = old_len + written; + let needed = times_three(left).ok_or(())?; + let new_len = filled.checked_add(needed).ok_or(())?; + unsafe { + handle.restart_bulk_write(new_len, filled, false)?; + } + (filled, read, handle) + } else { + // Started with non-ASCII. Compute worst case + let needed = if let Some(n) = worst_case_needed { + n + } else { + times_three(other.len()).ok_or(())? + }; + let new_len = old_len.checked_add(needed).ok_or(())?; + let handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + (old_len, 0, handle) + }; + let written = convert_utf16_to_utf8(&other[read..], &mut handle.as_mut_slice()[filled..]); + Ok(handle.finish(filled + written, true)) + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// replace the content of this string with the conversion result. + pub fn assign_utf16_to_utf8(&mut self, other: &[u16]) { + self.fallible_append_utf16_to_utf8_impl(other, 0) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly replace the content of this string with the conversion result. + pub fn fallible_assign_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> { + self.fallible_append_utf16_to_utf8_impl(other, 0) + .map(|_| ()) + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// append the conversion result to this string. + pub fn append_utf16_to_utf8(&mut self, other: &[u16]) { + let len = self.len(); + self.fallible_append_utf16_to_utf8_impl(other, len) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly append the conversion result to this string. + pub fn fallible_append_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf16_to_utf8_impl(other, len) + .map(|_| ()) + } + + // UTF-16 to Latin1 + + constant_conversion!( + name = fallible_append_utf16_to_latin1_lossy_impl, + convert = convert_utf16_to_latin1_lossy, + other_ty = &[u16] + ); + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) { + self.fallible_append_utf16_to_latin1_lossy_impl(other, 0, true) + .expect("Out of memory"); + } + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> { + self.fallible_append_utf16_to_latin1_lossy_impl(other, 0, true) + .map(|_| ()) + } + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn append_utf16_to_latin1_lossy(&mut self, other: &[u16]) { + let len = self.len(); + self.fallible_append_utf16_to_latin1_lossy_impl(other, len, false) + .expect("Out of memory"); + } + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_append_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf16_to_latin1_lossy_impl(other, len, false) + .map(|_| ()) + } + + // UTF-8 to Latin1 + + ascii_copy_avoidance!( + name = fallible_append_utf8_to_latin1_lossy_check, + implementation = fallible_append_utf8_to_latin1_lossy_impl, + string_like = nsCStringLike + ); + + fn fallible_append_utf8_to_latin1_lossy_impl( + &mut self, + other: &[u8], + old_len: usize, + maybe_num_ascii: Option<usize>, + ) -> Result<BulkWriteOk, ()> { + let new_len = old_len.checked_add(other.len()).ok_or(())?; + let num_ascii = maybe_num_ascii.unwrap_or(0); + // Already checked for overflow above, so this can't overflow. + let old_len_plus_num_ascii = old_len + num_ascii; + let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + let written = { + let buffer = handle.as_mut_slice(); + if num_ascii != 0 { + (&mut buffer[old_len..old_len_plus_num_ascii]).copy_from_slice(&other[..num_ascii]); + } + convert_utf8_to_latin1_lossy(&other[num_ascii..], &mut buffer[old_len_plus_num_ascii..]) + }; + Ok(handle.finish(old_len_plus_num_ascii + written, true)) + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn assign_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(&mut self, other: &T) { + self.fallible_append_utf8_to_latin1_lossy_check(other, 0) + .expect("Out of memory"); + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_assign_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>( + &mut self, + other: &T, + ) -> Result<(), ()> { + self.fallible_append_utf8_to_latin1_lossy_check(other, 0) + .map(|_| ()) + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn append_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(&mut self, other: &T) { + let len = self.len(); + self.fallible_append_utf8_to_latin1_lossy_check(other, len) + .expect("Out of memory"); + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_append_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>( + &mut self, + other: &T, + ) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf8_to_latin1_lossy_check(other, len) + .map(|_| ()) + } + + // Latin1 to UTF-8 CString + + ascii_copy_avoidance!( + name = fallible_append_latin1_to_utf8_check, + implementation = fallible_append_latin1_to_utf8_impl, + string_like = Latin1StringLike + ); + + fn fallible_append_latin1_to_utf8_impl( + &mut self, + other: &[u8], + old_len: usize, + maybe_num_ascii: Option<usize>, + ) -> Result<BulkWriteOk, ()> { + let (filled, read, mut handle) = if let Some(num_ascii) = maybe_num_ascii { + // Wrapper checked for ASCII + let left = other.len() - num_ascii; + let filled = old_len + num_ascii; + let needed = left.checked_mul(2).ok_or(())?; + let new_len = filled.checked_add(needed).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + if num_ascii != 0 { + (&mut handle.as_mut_slice()[old_len..filled]).copy_from_slice(&other[..num_ascii]); + } + (filled, num_ascii, handle) + } else { + let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() { + let worst_case = other.len().checked_mul(2).ok_or(())?; + if worst_case <= inline_capacity { + Some(worst_case) + } else { + None + } + } else { + None + }; + if worst_case_needed.is_none() && long_string_starts_with_ascii(other) { + // Wrapper didn't check for ASCII, so let's see if `other` starts with ASCII + // `other` starts with ASCII, so let's first size the buffer + // with optimism that it's ASCII-only. + let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len_with_ascii, old_len, false)? }; + let (read, written) = + convert_latin1_to_utf8_partial(other, &mut handle.as_mut_slice()[old_len..]); + let left = other.len() - read; + let filled = old_len + written; + if left == 0 { + // `other` fit in the initial allocation + return Ok(handle.finish(filled, true)); + } + let needed = left.checked_mul(2).ok_or(())?; + let new_len = filled.checked_add(needed).ok_or(())?; + unsafe { + handle.restart_bulk_write(new_len, filled, false)?; + } + (filled, read, handle) + } else { + // Started with non-ASCII. Assume worst case. + let needed = if let Some(n) = worst_case_needed { + n + } else { + other.len().checked_mul(2).ok_or(())? + }; + let new_len = old_len.checked_add(needed).ok_or(())?; + let handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + (old_len, 0, handle) + } + }; + let written = convert_latin1_to_utf8(&other[read..], &mut handle.as_mut_slice()[filled..]); + Ok(handle.finish(filled + written, true)) + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and replace the content of this string with the conversion result. + pub fn assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) { + self.fallible_append_latin1_to_utf8_check(other, 0) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and fallibly replace the content of this string with the + /// conversion result. + pub fn fallible_assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>( + &mut self, + other: &T, + ) -> Result<(), ()> { + self.fallible_append_latin1_to_utf8_check(other, 0) + .map(|_| ()) + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and append the conversion result to this string. + pub fn append_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) { + let len = self.len(); + self.fallible_append_latin1_to_utf8_check(other, len) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and fallibly append the conversion result to this string. + pub fn fallible_append_latin1_to_utf8<T: Latin1StringLike + ?Sized>( + &mut self, + other: &T, + ) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_latin1_to_utf8_check(other, len) + .map(|_| ()) + } +} + +#[no_mangle] +pub unsafe extern "C" fn nsstring_fallible_append_utf8_impl( + this: *mut nsAString, + other: *const u8, + other_len: usize, + old_len: usize, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_utf8_impl(other_slice, old_len) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nsstring_fallible_append_latin1_impl( + this: *mut nsAString, + other: *const u8, + other_len: usize, + old_len: usize, + allow_shrinking: bool, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_latin1_impl(other_slice, old_len, allow_shrinking) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_utf16_to_utf8_impl( + this: *mut nsACString, + other: *const u16, + other_len: usize, + old_len: usize, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_utf16_to_utf8_impl(other_slice, old_len) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_utf16_to_latin1_lossy_impl( + this: *mut nsACString, + other: *const u16, + other_len: usize, + old_len: usize, + allow_shrinking: bool, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_utf16_to_latin1_lossy_impl(other_slice, old_len, allow_shrinking) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_utf8_to_latin1_lossy_check( + this: *mut nsACString, + other: *const nsACString, + old_len: usize, +) -> bool { + (*this) + .fallible_append_utf8_to_latin1_lossy_check(&*other, old_len) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_latin1_to_utf8_check( + this: *mut nsACString, + other: *const nsACString, + old_len: usize, +) -> bool { + (*this) + .fallible_append_latin1_to_utf8_check(&*other, old_len) + .is_ok() +} diff --git a/xpcom/rust/nsstring/src/lib.rs b/xpcom/rust/nsstring/src/lib.rs new file mode 100644 index 0000000000..64d50df8fe --- /dev/null +++ b/xpcom/rust/nsstring/src/lib.rs @@ -0,0 +1,1545 @@ +/* This Source Code Form is subject to the terms of 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/. */ + +//! This module provides rust bindings for the XPCOM string types. +//! +//! # TL;DR (what types should I use) +//! +//! Use `&{mut,} nsA[C]String` for functions in rust which wish to take or +//! mutate XPCOM strings. The other string types `Deref` to this type. +//! +//! Use `ns[C]String` (`ns[C]String` in C++) for string struct members, and as +//! an intermediate between rust string data structures (such as `String` or +//! `Vec<u16>`) and `&{mut,} nsA[C]String` (using `ns[C]String::from(value)`). +//! These conversions will attempt to re-use the passed-in buffer, appending a +//! null. +//! +//! Use `ns[C]Str` (`nsDependent[C]String` in C++) as an intermediate between +//! borrowed rust data structures (such as `&str` and `&[u16]`) and `&{mut,} +//! nsA[C]String` (using `ns[C]Str::from(value)`). These conversions should not +//! perform any allocations. This type is not safe to share with `C++` as a +//! struct field, but passing the borrowed `&{mut,} nsA[C]String` over FFI is +//! safe. +//! +//! Use `*{const,mut} nsA[C]String` (`{const,} nsA[C]String*` in C++) for +//! function arguments passed across the rust/C++ language boundary. +//! +//! There is currently no Rust equivalent to `nsAuto[C]String`. Implementing a +//! type that contains a pointer to an inline buffer is difficult in Rust due +//! to its move semantics, which require that it be safe to move a value by +//! copying its bits. If such a type is genuinely needed at some point, +//! <https://bugzilla.mozilla.org/show_bug.cgi?id=1403506#c6> has a sketch of +//! how to emulate it via macros. +//! +//! # String Types +//! +//! ## `nsA[C]String` +//! +//! The core types in this module are `nsAString` and `nsACString`. These types +//! are zero-sized as far as rust is concerned, and are safe to pass around +//! behind both references (in rust code), and pointers (in C++ code). They +//! represent a handle to a XPCOM string which holds either `u16` or `u8` +//! characters respectively. The backing character buffer is guaranteed to live +//! as long as the reference to the `nsAString` or `nsACString`. +//! +//! These types in rust are simply used as dummy types. References to them +//! represent a pointer to the beginning of a variable-sized `#[repr(C)]` struct +//! which is common between both C++ and Rust implementations. In C++, their +//! corresponding types are also named `nsAString` or `nsACString`, and they are +//! defined within the `nsTSubstring.{cpp,h}` file. +//! +//! ### Valid Operations +//! +//! An `&nsA[C]String` acts like rust's `&str`, in that it is a borrowed +//! reference to the backing data. When used as an argument to other functions +//! on `&mut nsA[C]String`, optimizations can be performed to avoid copying +//! buffers, as information about the backing storage is preserved. +//! +//! An `&mut nsA[C]String` acts like rust's `&mut Cow<str>`, in that it is a +//! mutable reference to a potentially borrowed string, which when modified will +//! ensure that it owns its own backing storage. This type can be appended to +//! with the methods `.append`, `.append_utf{8,16}`, and with the `write!` +//! macro, and can be assigned to with `.assign`. +//! +//! ## `ns[C]Str<'a>` +//! +//! This type is an maybe-owned string type. It acts similarially to a +//! `Cow<[{u8,u16}]>`. This type provides `Deref` and `DerefMut` implementations +//! to `nsA[C]String`, which provides the methods for manipulating this type. +//! This type's lifetime parameter, `'a`, represents the lifetime of the backing +//! storage. When modified this type may re-allocate in order to ensure that it +//! does not mutate its backing storage. +//! +//! `ns[C]Str`s can be constructed either with `ns[C]Str::new()`, which creates +//! an empty `ns[C]Str<'static>`, or through one of the provided `From` +//! implementations. Only `nsCStr` can be constructed `From<'a str>`, as +//! constructing a `nsStr` would require transcoding. Use `ns[C]String` instead. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! When passing this type across the language boundary, pass it as `*const +//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a +//! mutable reference. +//! +//! ## `ns[C]String` +//! +//! This type is an owned, null-terminated string type. This type provides +//! `Deref` and `DerefMut` implementations to `nsA[C]String`, which provides the +//! methods for manipulating this type. +//! +//! `ns[C]String`s can be constructed either with `ns[C]String::new()`, which +//! creates an empty `ns[C]String`, or through one of the provided `From` +//! implementations, which will try to avoid reallocating when possible, +//! although a terminating `null` will be added. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! When passing this type across the language boundary, pass it as `*const +//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a +//! mutable reference. This struct may also be included in `#[repr(C)]` structs +//! shared with C++. +//! +//! ## `ns[C]StringRepr` +//! +//! This crate also provides the type `ns[C]StringRepr` which acts conceptually +//! similar to an `ns[C]String`, however, it does not have a `Drop` +//! implementation. +//! +//! If this type is dropped in rust, it will not free its backing storage. This +//! can be useful when implementing FFI types which contain `ns[C]String` members +//! which invoke their member's destructors through C++ code. + +#![allow(non_camel_case_types)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::new_without_default)] +#![allow(clippy::result_unit_err)] + +use bitflags::bitflags; +use std::borrow; +use std::cmp; +use std::fmt; +use std::marker::PhantomData; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::os::raw::c_void; +use std::ptr; +use std::slice; +use std::str; + +mod conversions; + +pub use self::conversions::nscstring_fallible_append_latin1_to_utf8_check; +pub use self::conversions::nscstring_fallible_append_utf16_to_latin1_lossy_impl; +pub use self::conversions::nscstring_fallible_append_utf16_to_utf8_impl; +pub use self::conversions::nscstring_fallible_append_utf8_to_latin1_lossy_check; +pub use self::conversions::nsstring_fallible_append_latin1_impl; +pub use self::conversions::nsstring_fallible_append_utf8_impl; + +/// A type for showing that `finish()` was called on a `BulkWriteHandle`. +/// Instantiating this type from elsewhere is basically an assertion that +/// there is no `BulkWriteHandle` around, so be very careful with instantiating +/// this type! +pub struct BulkWriteOk; + +/// Semi-arbitrary threshold below which we don't care about shrinking +/// buffers to size. Currently matches `CACHE_LINE` in the `conversions` +/// module. +const SHRINKING_THRESHOLD: usize = 64; + +/////////////////////////////////// +// Internal Implementation Flags // +/////////////////////////////////// + +bitflags! { + // While this has the same layout as u16, it cannot be passed + // over FFI safely as a u16. + #[repr(C)] + #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] + struct DataFlags: u16 { + const TERMINATED = 1 << 0; // IsTerminated returns true + const VOIDED = 1 << 1; // IsVoid returns true + const REFCOUNTED = 1 << 2; // mData points to a heap-allocated, shareable, refcounted + // buffer + const OWNED = 1 << 3; // mData points to a heap-allocated, raw buffer + const INLINE = 1 << 4; // mData points to a writable, inline buffer + const LITERAL = 1 << 5; // mData points to a string literal; TERMINATED will also be set + } +} + +bitflags! { + // While this has the same layout as u16, it cannot be passed + // over FFI safely as a u16. + #[repr(C)] + #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] + struct ClassFlags: u16 { + const INLINE = 1 << 0; // |this|'s buffer is inline + const NULL_TERMINATED = 1 << 1; // |this| requires its buffer is null-terminated + } +} + +//////////////////////////////////// +// Generic String Bindings Macros // +//////////////////////////////////// + +macro_rules! string_like { + { + char_t = $char_t: ty; + + AString = $AString: ident; + String = $String: ident; + Str = $Str: ident; + + StringLike = $StringLike: ident; + StringAdapter = $StringAdapter: ident; + } => { + /// This trait is implemented on types which are `ns[C]String`-like, in + /// that they can at very low cost be converted to a borrowed + /// `&nsA[C]String`. Unfortunately, the intermediate type + /// `ns[C]StringAdapter` is required as well due to types like `&[u8]` + /// needing to be (cheaply) wrapped in a `nsCString` on the stack to + /// create the `&nsACString`. + /// + /// This trait is used to DWIM when calling the methods on + /// `nsA[C]String`. + pub trait $StringLike { + fn adapt(&self) -> $StringAdapter; + } + + impl<'a, T: $StringLike + ?Sized> $StringLike for &'a T { + fn adapt(&self) -> $StringAdapter { + <T as $StringLike>::adapt(*self) + } + } + + impl<'a, T> $StringLike for borrow::Cow<'a, T> + where T: $StringLike + borrow::ToOwned + ?Sized { + fn adapt(&self) -> $StringAdapter { + <T as $StringLike>::adapt(self.as_ref()) + } + } + + impl $StringLike for $AString { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Abstract(self) + } + } + + impl<'a> $StringLike for $Str<'a> { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Abstract(self) + } + } + + impl $StringLike for $String { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Abstract(self) + } + } + + impl $StringLike for [$char_t] { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Borrowed($Str::from(self)) + } + } + + impl $StringLike for Vec<$char_t> { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Borrowed($Str::from(&self[..])) + } + } + + impl $StringLike for Box<[$char_t]> { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Borrowed($Str::from(&self[..])) + } + } + } +} + +impl<'a> Drop for nsAStringBulkWriteHandle<'a> { + /// This only runs in error cases. In success cases, `finish()` + /// calls `forget(self)`. + fn drop(&mut self) { + if self.capacity == 0 { + // If capacity is 0, the string is a zero-length + // string, so we have nothing to do. + return; + } + // The old zero terminator may be gone by now, so we need + // to write a new one somewhere and make length match. + // We can use a length between 1 and self.capacity. + // Seems prudent to overwrite the uninitialized memory. + // Using the length 1 leaves the shortest memory to overwrite. + // U+FFFD is the safest placeholder. Merely truncating the + // string to a zero-length string might be dangerous in some + // scenarios. See + // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences + // for closely related scenario. + unsafe { + let mut this = self.string.as_repr_mut(); + this.as_mut().length = 1u32; + *(this.as_mut().data.as_mut()) = 0xFFFDu16; + *(this.as_mut().data.as_ptr().add(1)) = 0; + } + } +} + +impl<'a> Drop for nsACStringBulkWriteHandle<'a> { + /// This only runs in error cases. In success cases, `finish()` + /// calls `forget(self)`. + fn drop(&mut self) { + if self.capacity == 0 { + // If capacity is 0, the string is a zero-length + // string, so we have nothing to do. + return; + } + // The old zero terminator may be gone by now, so we need + // to write a new one somewhere and make length match. + // We can use a length between 1 and self.capacity. + // Seems prudent to overwrite the uninitialized memory. + // Using the length 1 leaves the shortest memory to overwrite. + // U+FFFD is the safest placeholder, but when it doesn't fit, + // let's use ASCII substitute. Merely truncating the + // string to a zero-length string might be dangerous in some + // scenarios. See + // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences + // for closely related scenario. + unsafe { + let mut this = self.string.as_repr_mut(); + if self.capacity >= 3 { + this.as_mut().length = 3u32; + *(this.as_mut().data.as_mut()) = 0xEFu8; + *(this.as_mut().data.as_ptr().add(1)) = 0xBFu8; + *(this.as_mut().data.as_ptr().add(2)) = 0xBDu8; + *(this.as_mut().data.as_ptr().add(3)) = 0; + } else { + this.as_mut().length = 1u32; + *(this.as_mut().data.as_mut()) = 0x1Au8; // U+FFFD doesn't fit + *(this.as_mut().data.as_ptr().add(1)) = 0; + } + } + } +} + +macro_rules! define_string_types { + { + char_t = $char_t: ty; + + AString = $AString: ident; + String = $String: ident; + Str = $Str: ident; + + StringLike = $StringLike: ident; + StringAdapter = $StringAdapter: ident; + + StringRepr = $StringRepr: ident; + AutoStringRepr = $AutoStringRepr: ident; + + BulkWriteHandle = $BulkWriteHandle: ident; + + drop = $drop: ident; + assign = $assign: ident, $fallible_assign: ident; + take_from = $take_from: ident, $fallible_take_from: ident; + append = $append: ident, $fallible_append: ident; + set_length = $set_length: ident, $fallible_set_length: ident; + begin_writing = $begin_writing: ident, $fallible_begin_writing: ident; + start_bulk_write = $start_bulk_write: ident; + } => { + /// The representation of a `ns[C]String` type in C++. This type is + /// used internally by our definition of `ns[C]String` to ensure layout + /// compatibility with the C++ `ns[C]String` type. + /// + /// This type may also be used in place of a C++ `ns[C]String` inside of + /// struct definitions which are shared with C++, as it has identical + /// layout to our `ns[C]String` type. + /// + /// This struct will leak its data if dropped from rust. See the module + /// documentation for more information on this type. + #[repr(C)] + #[derive(Debug)] + pub struct $StringRepr { + data: ptr::NonNull<$char_t>, + length: u32, + dataflags: DataFlags, + classflags: ClassFlags, + } + + impl $StringRepr { + fn new(classflags: ClassFlags) -> $StringRepr { + static NUL: $char_t = 0; + $StringRepr { + data: unsafe { ptr::NonNull::new_unchecked(&NUL as *const _ as *mut _) }, + length: 0, + dataflags: DataFlags::TERMINATED | DataFlags::LITERAL, + classflags, + } + } + } + + impl Deref for $StringRepr { + type Target = $AString; + fn deref(&self) -> &$AString { + unsafe { + &*(self as *const _ as *const $AString) + } + } + } + + impl DerefMut for $StringRepr { + fn deref_mut(&mut self) -> &mut $AString { + unsafe { + &mut *(self as *mut _ as *mut $AString) + } + } + } + + #[repr(C)] + #[derive(Debug)] + pub struct $AutoStringRepr { + super_repr: $StringRepr, + inline_capacity: u32, + } + + pub struct $BulkWriteHandle<'a> { + string: &'a mut $AString, + capacity: usize, + } + + impl<'a> $BulkWriteHandle<'a> { + fn new(string: &'a mut $AString, capacity: usize) -> Self { + $BulkWriteHandle{ string, capacity } + } + + pub unsafe fn restart_bulk_write(&mut self, + capacity: usize, + units_to_preserve: usize, + allow_shrinking: bool) -> Result<(), ()> { + self.capacity = + self.string.start_bulk_write_impl(capacity, + units_to_preserve, + allow_shrinking)?; + Ok(()) + } + + pub fn finish(mut self, length: usize, allow_shrinking: bool) -> BulkWriteOk { + // NOTE: Drop is implemented outside the macro earlier in this file, + // because it needs to deal with different code unit representations + // for the REPLACEMENT CHARACTER in the UTF-16 and UTF-8 cases and + // needs to deal with a REPLACEMENT CHARACTER not fitting in the + // buffer in the UTF-8 case. + assert!(length <= self.capacity); + if length == 0 { + // `truncate()` is OK even when the string + // is in invalid state. + self.string.truncate(); + mem::forget(self); // Don't run the failure path in drop() + return BulkWriteOk{}; + } + if allow_shrinking && length > SHRINKING_THRESHOLD { + unsafe { + let _ = self.restart_bulk_write(length, length, true); + } + } + unsafe { + let mut this = self.string.as_repr_mut(); + this.as_mut().length = length as u32; + *(this.as_mut().data.as_ptr().add(length)) = 0; + if cfg!(debug_assertions) { + // Overwrite the unused part in debug builds. Note + // that capacity doesn't include space for the zero + // terminator, so starting after the zero-terminator + // we wrote ends up overwriting the terminator space + // not reflected in the capacity number. + // write_bytes() takes care of multiplying the length + // by the size of T. + ptr::write_bytes(this.as_mut().data.as_ptr().add(length + 1), + 0xE4u8, + self.capacity - length); + } + // We don't have a Rust interface for mozilla/MemoryChecking.h, + // so let's just not communicate with MSan/Valgrind here. + } + mem::forget(self); // Don't run the failure path in drop() + BulkWriteOk{} + } + + pub fn as_mut_slice(&mut self) -> &mut [$char_t] { + unsafe { + let mut this = self.string.as_repr_mut(); + slice::from_raw_parts_mut(this.as_mut().data.as_ptr(), self.capacity) + } + } + } + + /// This type is the abstract type which is used for interacting with + /// strings in rust. Each string type can derefence to an instance of + /// this type, which provides the useful operations on strings. + /// + /// NOTE: Rust thinks this type has a size of 0, because the data + /// associated with it is not necessarially safe to move. It is not safe + /// to construct a nsAString yourself, unless it is received by + /// dereferencing one of these types. + /// + /// NOTE: The `[u8; 0]` member is zero sized, and only exists to prevent + /// the construction by code outside of this module. It is used instead + /// of a private `()` member because the `improper_ctypes` lint complains + /// about some ZST members in `extern "C"` function declarations. + #[repr(C)] + pub struct $AString { + _prohibit_constructor: [u8; 0], + } + + impl $AString { + /// Assign the value of `other` into self, overwriting any value + /// currently stored. Performs an optimized assignment when possible + /// if `other` is a `nsA[C]String`. + pub fn assign<T: $StringLike + ?Sized>(&mut self, other: &T) { + unsafe { $assign(self, other.adapt().as_ptr()) }; + } + + /// Assign the value of `other` into self, overwriting any value + /// currently stored. Performs an optimized assignment when possible + /// if `other` is a `nsA[C]String`. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub fn fallible_assign<T: $StringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> { + if unsafe { $fallible_assign(self, other.adapt().as_ptr()) } { + Ok(()) + } else { + Err(()) + } + } + + /// Take the value of `other` and set `self`, overwriting any value + /// currently stored. The passed-in string will be truncated. + pub fn take_from(&mut self, other: &mut $AString) { + unsafe { $take_from(self, other) }; + } + + /// Take the value of `other` and set `self`, overwriting any value + /// currently stored. If this function fails, the source string will + /// be left untouched, otherwise it will be truncated. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub fn fallible_take_from(&mut self, other: &mut $AString) -> Result<(), ()> { + if unsafe { $fallible_take_from(self, other) } { + Ok(()) + } else { + Err(()) + } + } + + /// Append the value of `other` into self. + pub fn append<T: $StringLike + ?Sized>(&mut self, other: &T) { + unsafe { $append(self, other.adapt().as_ptr()) }; + } + + /// Append the value of `other` into self. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub fn fallible_append<T: $StringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> { + if unsafe { $fallible_append(self, other.adapt().as_ptr()) } { + Ok(()) + } else { + Err(()) + } + } + + /// Mark the string's data as void. If `true`, the string will be truncated. + /// + /// A void string is generally converted to a `null` JS value by bindings code. + pub fn set_is_void(&mut self, is_void: bool) { + if is_void { + self.truncate(); + } + unsafe { + self.as_repr_mut().as_mut().dataflags.set(DataFlags::VOIDED, is_void); + } + } + + /// Returns whether the string's data is voided. + pub fn is_void(&self) -> bool { + self.as_repr().dataflags.contains(DataFlags::VOIDED) + } + + /// Set the length of the string to the passed-in length, and expand + /// the backing capacity to match. This method is unsafe as it can + /// expose uninitialized memory when len is greater than the current + /// length of the string. + pub unsafe fn set_length(&mut self, len: u32) { + $set_length(self, len); + } + + /// Set the length of the string to the passed-in length, and expand + /// the backing capacity to match. This method is unsafe as it can + /// expose uninitialized memory when len is greater than the current + /// length of the string. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub unsafe fn fallible_set_length(&mut self, len: u32) -> Result<(), ()> { + if $fallible_set_length(self, len) { + Ok(()) + } else { + Err(()) + } + } + + pub fn truncate(&mut self) { + unsafe { + self.set_length(0); + } + } + + /// Get a `&mut` reference to the backing data for this string. + /// This method will allocate and copy if the current backing buffer + /// is immutable or shared. + pub fn to_mut(&mut self) -> &mut [$char_t] { + unsafe { + let len = self.len(); + if len == 0 { + // Use an arbitrary but aligned non-null value as the pointer + slice::from_raw_parts_mut(ptr::NonNull::<$char_t>::dangling().as_ptr(), 0) + } else { + slice::from_raw_parts_mut($begin_writing(self), len) + } + } + } + + /// Get a `&mut` reference to the backing data for this string. + /// This method will allocate and copy if the current backing buffer + /// is immutable or shared. + /// + /// Returns `Ok(&mut [T])` on success, and `Err(())` if the + /// allocation failed. + pub fn fallible_to_mut(&mut self) -> Result<&mut [$char_t], ()> { + unsafe { + let len = self.len(); + if len == 0 { + // Use an arbitrary but aligned non-null value as the pointer + Ok(slice::from_raw_parts_mut( + ptr::NonNull::<$char_t>::dangling().as_ptr() as *mut $char_t, 0)) + } else { + let ptr = $fallible_begin_writing(self); + if ptr.is_null() { + Err(()) + } else { + Ok(slice::from_raw_parts_mut(ptr, len)) + } + } + } + } + + /// Unshares the buffer of the string and returns a handle + /// from which a writable slice whose length is the rounded-up + /// capacity can be obtained. + /// + /// Fails also if the new length doesn't fit in 32 bits. + /// + /// # Safety + /// + /// Unsafe because of exposure of uninitialized memory. + pub unsafe fn bulk_write(&mut self, + capacity: usize, + units_to_preserve: usize, + allow_shrinking: bool) -> Result<$BulkWriteHandle, ()> { + let capacity = + self.start_bulk_write_impl(capacity, units_to_preserve, allow_shrinking)?; + Ok($BulkWriteHandle::new(self, capacity)) + } + + unsafe fn start_bulk_write_impl(&mut self, + capacity: usize, + units_to_preserve: usize, + allow_shrinking: bool) -> Result<usize, ()> { + if capacity > u32::MAX as usize { + Err(()) + } else { + let capacity32 = capacity as u32; + let rounded = $start_bulk_write(self, + capacity32, + units_to_preserve as u32, + allow_shrinking && capacity > SHRINKING_THRESHOLD); + if rounded == u32::MAX { + return Err(()) + } + Ok(rounded as usize) + } + } + + fn as_repr(&self) -> &$StringRepr { + // All $AString values point to a struct prefix which is + // identical to $StringRepr, thus we can cast `self` + // into *const $StringRepr to get the reference to the + // underlying data. + unsafe { + &*(self as *const _ as *const $StringRepr) + } + } + + fn as_repr_mut(&mut self) -> ptr::NonNull<$StringRepr> { + unsafe { ptr::NonNull::new_unchecked(self as *mut _ as *mut $StringRepr)} + } + + fn as_auto_string_repr(&self) -> Option<&$AutoStringRepr> { + if !self.as_repr().classflags.contains(ClassFlags::INLINE) { + return None; + } + + unsafe { + Some(&*(self as *const _ as *const $AutoStringRepr)) + } + } + + /// If this is an autostring, returns the capacity (excluding the + /// zero terminator) of the inline buffer within `Some()`. Otherwise + /// returns `None`. + pub fn inline_capacity(&self) -> Option<usize> { + Some(self.as_auto_string_repr()?.inline_capacity as usize) + } + } + + impl Deref for $AString { + type Target = [$char_t]; + fn deref(&self) -> &[$char_t] { + unsafe { + // All $AString values point to a struct prefix which is + // identical to $StringRepr, thus we can cast `self` + // into *const $StringRepr to get the reference to the + // underlying data. + let this = &*(self as *const _ as *const $StringRepr); + slice::from_raw_parts(this.data.as_ptr(), this.length as usize) + } + } + } + + impl AsRef<[$char_t]> for $AString { + fn as_ref(&self) -> &[$char_t] { + self + } + } + + impl cmp::PartialEq for $AString { + fn eq(&self, other: &$AString) -> bool { + &self[..] == &other[..] + } + } + + impl cmp::PartialEq<[$char_t]> for $AString { + fn eq(&self, other: &[$char_t]) -> bool { + &self[..] == other + } + } + + impl cmp::PartialEq<$String> for $AString { + fn eq(&self, other: &$String) -> bool { + self.eq(&**other) + } + } + + impl<'a> cmp::PartialEq<$Str<'a>> for $AString { + fn eq(&self, other: &$Str<'a>) -> bool { + self.eq(&**other) + } + } + + #[repr(C)] + pub struct $Str<'a> { + hdr: $StringRepr, + _marker: PhantomData<&'a [$char_t]>, + } + + impl $Str<'static> { + pub fn new() -> $Str<'static> { + $Str { + hdr: $StringRepr::new(ClassFlags::empty()), + _marker: PhantomData, + } + } + } + + impl<'a> Drop for $Str<'a> { + fn drop(&mut self) { + unsafe { + $drop(&mut **self); + } + } + } + + impl<'a> Deref for $Str<'a> { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr + } + } + + impl<'a> DerefMut for $Str<'a> { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr + } + } + + impl<'a> AsRef<[$char_t]> for $Str<'a> { + fn as_ref(&self) -> &[$char_t] { + &self + } + } + + impl<'a> From<&'a [$char_t]> for $Str<'a> { + fn from(s: &'a [$char_t]) -> $Str<'a> { + assert!(s.len() < (u32::MAX as usize)); + if s.is_empty() { + return $Str::new(); + } + $Str { + hdr: $StringRepr { + data: unsafe { ptr::NonNull::new_unchecked(s.as_ptr() as *mut _) }, + length: s.len() as u32, + dataflags: DataFlags::empty(), + classflags: ClassFlags::empty(), + }, + _marker: PhantomData, + } + } + } + + impl<'a> From<&'a Vec<$char_t>> for $Str<'a> { + fn from(s: &'a Vec<$char_t>) -> $Str<'a> { + $Str::from(&s[..]) + } + } + + impl<'a> From<&'a $AString> for $Str<'a> { + fn from(s: &'a $AString) -> $Str<'a> { + $Str::from(&s[..]) + } + } + + impl<'a> fmt::Write for $Str<'a> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl<'a> fmt::Display for $Str<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl<'a> fmt::Debug for $Str<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl<'a> cmp::PartialEq for $Str<'a> { + fn eq(&self, other: &$Str<'a>) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<[$char_t]> for $Str<'a> { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $Str<'a> { + fn eq(&self, other: &&'b [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> cmp::PartialEq<str> for $Str<'a> { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b str> for $Str<'a> { + fn eq(&self, other: &&'b str) -> bool { + $AString::eq(self, *other) + } + } + + #[repr(C)] + pub struct $String { + hdr: $StringRepr, + } + + unsafe impl Send for $String {} + unsafe impl Sync for $String {} + + impl $String { + pub fn new() -> $String { + $String { + hdr: $StringRepr::new(ClassFlags::NULL_TERMINATED), + } + } + + /// Converts this String into a StringRepr, which will leak if the + /// repr is not passed to something that knows how to free it. + pub fn into_repr(mut self) -> $StringRepr { + mem::replace(&mut self.hdr, $StringRepr::new(ClassFlags::NULL_TERMINATED)) + } + } + + impl Drop for $String { + fn drop(&mut self) { + unsafe { + $drop(&mut **self); + } + } + } + + impl Deref for $String { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr + } + } + + impl DerefMut for $String { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr + } + } + + impl Clone for $String { + fn clone(&self) -> Self { + let mut copy = $String::new(); + copy.assign(self); + copy + } + } + + impl AsRef<[$char_t]> for $String { + fn as_ref(&self) -> &[$char_t] { + &self + } + } + + impl<'a> From<&'a [$char_t]> for $String { + fn from(s: &'a [$char_t]) -> $String { + let mut res = $String::new(); + res.assign(&$Str::from(&s[..])); + res + } + } + + impl<'a> From<&'a Vec<$char_t>> for $String { + fn from(s: &'a Vec<$char_t>) -> $String { + $String::from(&s[..]) + } + } + + impl<'a> From<&'a $AString> for $String { + fn from(s: &'a $AString) -> $String { + $String::from(&s[..]) + } + } + + impl From<Box<[$char_t]>> for $String { + fn from(s: Box<[$char_t]>) -> $String { + s.into_vec().into() + } + } + + impl From<Vec<$char_t>> for $String { + fn from(mut s: Vec<$char_t>) -> $String { + assert!(s.len() < (u32::MAX as usize)); + if s.is_empty() { + return $String::new(); + } + + let length = s.len() as u32; + s.push(0); // null terminator + + // SAFETY NOTE: This method produces an data_flags::OWNED + // ns[C]String from a Box<[$char_t]>. this is only safe + // because in the Gecko tree, we use the same allocator for + // Rust code as for C++ code, meaning that our box can be + // legally freed with libc::free(). + let ptr = s.as_mut_ptr(); + mem::forget(s); + unsafe { + Gecko_IncrementStringAdoptCount(ptr as *mut _); + } + $String { + hdr: $StringRepr { + data: unsafe { ptr::NonNull::new_unchecked(ptr) }, + length, + dataflags: DataFlags::OWNED | DataFlags::TERMINATED, + classflags: ClassFlags::NULL_TERMINATED, + } + } + } + } + + impl fmt::Write for $String { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl fmt::Display for $String { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl fmt::Debug for $String { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl cmp::PartialEq for $String { + fn eq(&self, other: &$String) -> bool { + $AString::eq(self, other) + } + } + + impl cmp::PartialEq<[$char_t]> for $String { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<&'a [$char_t]> for $String { + fn eq(&self, other: &&'a [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl cmp::PartialEq<str> for $String { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<&'a str> for $String { + fn eq(&self, other: &&'a str) -> bool { + $AString::eq(self, *other) + } + } + + /// An adapter type to allow for passing both types which coerce to + /// &[$char_type], and &$AString to a function, while still performing + /// optimized operations when passed the $AString. + pub enum $StringAdapter<'a> { + Borrowed($Str<'a>), + Abstract(&'a $AString), + } + + impl<'a> $StringAdapter<'a> { + fn as_ptr(&self) -> *const $AString { + &**self + } + } + + impl<'a> Deref for $StringAdapter<'a> { + type Target = $AString; + + fn deref(&self) -> &$AString { + match *self { + $StringAdapter::Borrowed(ref s) => s, + $StringAdapter::Abstract(ref s) => s, + } + } + } + + impl<'a> $StringAdapter<'a> { + #[allow(dead_code)] + fn is_abstract(&self) -> bool { + match *self { + $StringAdapter::Borrowed(_) => false, + $StringAdapter::Abstract(_) => true, + } + } + } + + string_like! { + char_t = $char_t; + + AString = $AString; + String = $String; + Str = $Str; + + StringLike = $StringLike; + StringAdapter = $StringAdapter; + } + } +} + +/////////////////////////////////////////// +// Bindings for nsCString (u8 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u8; + + AString = nsACString; + String = nsCString; + Str = nsCStr; + + StringLike = nsCStringLike; + StringAdapter = nsCStringAdapter; + + StringRepr = nsCStringRepr; + AutoStringRepr = nsAutoCStringRepr; + + BulkWriteHandle = nsACStringBulkWriteHandle; + + drop = Gecko_FinalizeCString; + assign = Gecko_AssignCString, Gecko_FallibleAssignCString; + take_from = Gecko_TakeFromCString, Gecko_FallibleTakeFromCString; + append = Gecko_AppendCString, Gecko_FallibleAppendCString; + set_length = Gecko_SetLengthCString, Gecko_FallibleSetLengthCString; + begin_writing = Gecko_BeginWritingCString, Gecko_FallibleBeginWritingCString; + start_bulk_write = Gecko_StartBulkWriteCString; +} + +impl nsACString { + /// Gets a CString as an utf-8 str or a String, trying to avoid copies, and + /// replacing invalid unicode sequences with replacement characters. + #[inline] + pub fn to_utf8(&self) -> borrow::Cow<str> { + String::from_utf8_lossy(&self[..]) + } + + #[inline] + pub unsafe fn as_str_unchecked(&self) -> &str { + if cfg!(debug_assertions) { + str::from_utf8(self).expect("Should be utf-8") + } else { + str::from_utf8_unchecked(self) + } + } +} + +impl<'a> From<&'a str> for nsCStr<'a> { + fn from(s: &'a str) -> nsCStr<'a> { + s.as_bytes().into() + } +} + +impl<'a> From<&'a String> for nsCStr<'a> { + fn from(s: &'a String) -> nsCStr<'a> { + nsCStr::from(&s[..]) + } +} + +impl<'a> From<&'a str> for nsCString { + fn from(s: &'a str) -> nsCString { + s.as_bytes().into() + } +} + +impl<'a> From<&'a String> for nsCString { + fn from(s: &'a String) -> nsCString { + nsCString::from(&s[..]) + } +} + +impl From<Box<str>> for nsCString { + fn from(s: Box<str>) -> nsCString { + s.into_string().into() + } +} + +impl From<String> for nsCString { + fn from(s: String) -> nsCString { + s.into_bytes().into() + } +} + +// Support for the write!() macro for appending to nsACStrings +impl fmt::Write for nsACString { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.append(s); + Ok(()) + } +} + +impl fmt::Display for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&self.to_utf8(), f) + } +} + +impl fmt::Debug for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&self.to_utf8(), f) + } +} + +impl cmp::PartialEq<str> for nsACString { + fn eq(&self, other: &str) -> bool { + &self[..] == other.as_bytes() + } +} + +impl nsCStringLike for str { + fn adapt(&self) -> nsCStringAdapter { + nsCStringAdapter::Borrowed(nsCStr::from(self)) + } +} + +impl nsCStringLike for String { + fn adapt(&self) -> nsCStringAdapter { + nsCStringAdapter::Borrowed(nsCStr::from(&self[..])) + } +} + +impl nsCStringLike for Box<str> { + fn adapt(&self) -> nsCStringAdapter { + nsCStringAdapter::Borrowed(nsCStr::from(&self[..])) + } +} + +// This trait is implemented on types which are Latin1 `nsCString`-like, +// in that they can at very low cost be converted to a borrowed +// `&nsACString` and do not denote UTF-8ness in the Rust type system. +// +// This trait is used to DWIM when calling the methods on +// `nsACString`. +string_like! { + char_t = u8; + + AString = nsACString; + String = nsCString; + Str = nsCStr; + + StringLike = Latin1StringLike; + StringAdapter = nsCStringAdapter; +} + +/////////////////////////////////////////// +// Bindings for nsString (u16 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u16; + + AString = nsAString; + String = nsString; + Str = nsStr; + + StringLike = nsStringLike; + StringAdapter = nsStringAdapter; + + StringRepr = nsStringRepr; + AutoStringRepr = nsAutoStringRepr; + + BulkWriteHandle = nsAStringBulkWriteHandle; + + drop = Gecko_FinalizeString; + assign = Gecko_AssignString, Gecko_FallibleAssignString; + take_from = Gecko_TakeFromString, Gecko_FallibleTakeFromString; + append = Gecko_AppendString, Gecko_FallibleAppendString; + set_length = Gecko_SetLengthString, Gecko_FallibleSetLengthString; + begin_writing = Gecko_BeginWritingString, Gecko_FallibleBeginWritingString; + start_bulk_write = Gecko_StartBulkWriteString; +} + +// NOTE: The From impl for a string slice for nsString produces a <'static> +// lifetime, as it allocates. +impl<'a> From<&'a str> for nsString { + fn from(s: &'a str) -> nsString { + s.encode_utf16().collect::<Vec<u16>>().into() + } +} + +impl<'a> From<&'a String> for nsString { + fn from(s: &'a String) -> nsString { + nsString::from(&s[..]) + } +} + +// Support for the write!() macro for writing to nsStrings +impl fmt::Write for nsAString { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + // Directly invoke gecko's routines for appending utf8 strings to + // nsAString values, to avoid as much overhead as possible + self.append_str(s); + Ok(()) + } +} + +impl nsAString { + /// Turns this utf-16 string into a string, replacing invalid unicode + /// sequences with replacement characters. + /// + /// This is needed because the default ToString implementation goes through + /// fmt::Display, and thus allocates the string twice. + #[allow(clippy::inherent_to_string_shadow_display)] + pub fn to_string(&self) -> String { + String::from_utf16_lossy(&self[..]) + } +} + +impl fmt::Display for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&self.to_string(), f) + } +} + +impl fmt::Debug for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&self.to_string(), f) + } +} + +impl cmp::PartialEq<str> for nsAString { + fn eq(&self, other: &str) -> bool { + other.encode_utf16().eq(self.iter().cloned()) + } +} + +#[cfg(not(feature = "gecko_debug"))] +#[allow(non_snake_case)] +unsafe fn Gecko_IncrementStringAdoptCount(_: *mut c_void) {} + +extern "C" { + #[cfg(feature = "gecko_debug")] + fn Gecko_IncrementStringAdoptCount(data: *mut c_void); + + // Gecko implementation in nsSubstring.cpp + fn Gecko_FinalizeCString(this: *mut nsACString); + + fn Gecko_AssignCString(this: *mut nsACString, other: *const nsACString); + fn Gecko_TakeFromCString(this: *mut nsACString, other: *mut nsACString); + fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString); + fn Gecko_SetLengthCString(this: *mut nsACString, length: u32); + fn Gecko_BeginWritingCString(this: *mut nsACString) -> *mut u8; + fn Gecko_FallibleAssignCString(this: *mut nsACString, other: *const nsACString) -> bool; + fn Gecko_FallibleTakeFromCString(this: *mut nsACString, other: *mut nsACString) -> bool; + fn Gecko_FallibleAppendCString(this: *mut nsACString, other: *const nsACString) -> bool; + fn Gecko_FallibleSetLengthCString(this: *mut nsACString, length: u32) -> bool; + fn Gecko_FallibleBeginWritingCString(this: *mut nsACString) -> *mut u8; + fn Gecko_StartBulkWriteCString( + this: *mut nsACString, + capacity: u32, + units_to_preserve: u32, + allow_shrinking: bool, + ) -> u32; + + fn Gecko_FinalizeString(this: *mut nsAString); + + fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString); + fn Gecko_TakeFromString(this: *mut nsAString, other: *mut nsAString); + fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString); + fn Gecko_SetLengthString(this: *mut nsAString, length: u32); + fn Gecko_BeginWritingString(this: *mut nsAString) -> *mut u16; + fn Gecko_FallibleAssignString(this: *mut nsAString, other: *const nsAString) -> bool; + fn Gecko_FallibleTakeFromString(this: *mut nsAString, other: *mut nsAString) -> bool; + fn Gecko_FallibleAppendString(this: *mut nsAString, other: *const nsAString) -> bool; + fn Gecko_FallibleSetLengthString(this: *mut nsAString, length: u32) -> bool; + fn Gecko_FallibleBeginWritingString(this: *mut nsAString) -> *mut u16; + fn Gecko_StartBulkWriteString( + this: *mut nsAString, + capacity: u32, + units_to_preserve: u32, + allow_shrinking: bool, + ) -> u32; +} + +////////////////////////////////////// +// Repr Validation Helper Functions // +////////////////////////////////////// + +pub mod test_helpers { + //! This module only exists to help with ensuring that the layout of the + //! structs inside of rust and C++ are identical. + //! + //! It is public to ensure that these testing functions are avaliable to + //! gtest code. + + use super::{nsACString, nsAString}; + use super::{nsCStr, nsCString, nsCStringRepr}; + use super::{nsStr, nsString, nsStringRepr}; + use super::{ClassFlags, DataFlags}; + use std::mem; + + /// Generates an #[no_mangle] extern "C" function which returns the size and + /// alignment of the given type with the given name. + macro_rules! size_align_check { + ($T:ty, $fname:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn $fname(size: *mut usize, align: *mut usize) { + *size = mem::size_of::<$T>(); + *align = mem::align_of::<$T>(); + } + }; + ($T:ty, $U:ty, $V:ty, $fname:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn $fname(size: *mut usize, align: *mut usize) { + *size = mem::size_of::<$T>(); + *align = mem::align_of::<$T>(); + + assert_eq!(*size, mem::size_of::<$U>()); + assert_eq!(*align, mem::align_of::<$U>()); + assert_eq!(*size, mem::size_of::<$V>()); + assert_eq!(*align, mem::align_of::<$V>()); + } + }; + } + + size_align_check!( + nsStringRepr, + nsString, + nsStr<'static>, + Rust_Test_ReprSizeAlign_nsString + ); + size_align_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + Rust_Test_ReprSizeAlign_nsCString + ); + + /// Generates a $[no_mangle] extern "C" function which returns the size, + /// alignment and offset in the parent struct of a given member, with the + /// given name. + /// + /// This method can trigger Undefined Behavior if the accessing the member + /// $member on a given type would use that type's `Deref` implementation. + macro_rules! member_check { + ($T:ty, $U:ty, $V:ty, $member:ident, $method:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn $method( + size: *mut usize, + align: *mut usize, + offset: *mut usize, + ) { + // Create a temporary value of type T to get offsets, sizes + // and alignments from. + let tmp: mem::MaybeUninit<$T> = mem::MaybeUninit::uninit(); + // FIXME: This should use &raw references when available, + // this is technically UB as it creates a reference to + // uninitialized memory, but there's no better way to do + // this right now. + let tmp = &*tmp.as_ptr(); + *size = mem::size_of_val(&tmp.$member); + *align = mem::align_of_val(&tmp.$member); + *offset = (&tmp.$member as *const _ as usize) - (tmp as *const $T as usize); + + let tmp: mem::MaybeUninit<$U> = mem::MaybeUninit::uninit(); + let tmp = &*tmp.as_ptr(); + assert_eq!(*size, mem::size_of_val(&tmp.hdr.$member)); + assert_eq!(*align, mem::align_of_val(&tmp.hdr.$member)); + assert_eq!( + *offset, + (&tmp.hdr.$member as *const _ as usize) - (tmp as *const $U as usize) + ); + + let tmp: mem::MaybeUninit<$V> = mem::MaybeUninit::uninit(); + let tmp = &*tmp.as_ptr(); + assert_eq!(*size, mem::size_of_val(&tmp.hdr.$member)); + assert_eq!(*align, mem::align_of_val(&tmp.hdr.$member)); + assert_eq!( + *offset, + (&tmp.hdr.$member as *const _ as usize) - (tmp as *const $V as usize) + ); + } + }; + } + + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + data, + Rust_Test_Member_nsString_mData + ); + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + length, + Rust_Test_Member_nsString_mLength + ); + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + dataflags, + Rust_Test_Member_nsString_mDataFlags + ); + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + classflags, + Rust_Test_Member_nsString_mClassFlags + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + data, + Rust_Test_Member_nsCString_mData + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + length, + Rust_Test_Member_nsCString_mLength + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + dataflags, + Rust_Test_Member_nsCString_mDataFlags + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + classflags, + Rust_Test_Member_nsCString_mClassFlags + ); + + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn Rust_Test_NsStringFlags( + f_terminated: *mut u16, + f_voided: *mut u16, + f_refcounted: *mut u16, + f_owned: *mut u16, + f_inline: *mut u16, + f_literal: *mut u16, + f_class_inline: *mut u16, + f_class_null_terminated: *mut u16, + ) { + *f_terminated = DataFlags::TERMINATED.bits(); + *f_voided = DataFlags::VOIDED.bits(); + *f_refcounted = DataFlags::REFCOUNTED.bits(); + *f_owned = DataFlags::OWNED.bits(); + *f_inline = DataFlags::INLINE.bits(); + *f_literal = DataFlags::LITERAL.bits(); + *f_class_inline = ClassFlags::INLINE.bits(); + *f_class_null_terminated = ClassFlags::NULL_TERMINATED.bits(); + } + + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn Rust_InlineCapacityFromRust( + cstring: *const nsACString, + string: *const nsAString, + cstring_capacity: *mut usize, + string_capacity: *mut usize, + ) { + *cstring_capacity = (*cstring).inline_capacity().unwrap(); + *string_capacity = (*string).inline_capacity().unwrap(); + } +} diff --git a/xpcom/rust/xpcom/Cargo.toml b/xpcom/rust/xpcom/Cargo.toml new file mode 100644 index 0000000000..9c8a1ec83b --- /dev/null +++ b/xpcom/rust/xpcom/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "xpcom" +version = "0.1.0" +authors = ["Nika Layzell <nika@thelayzells.com>"] +edition = "2018" +license = "MPL-2.0" + +[dependencies] +cstr = "0.2" +libc = "0.2" +nsstring = { path = "../nsstring" } +nserror = { path = "../nserror" } +threadbound = "0.1" +xpcom_macros = { path = "xpcom_macros" } +thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } +mozbuild = "0.1" + +[features] +thread_sanitizer = [] +gecko_refcount_logging = [] diff --git a/xpcom/rust/xpcom/src/base.rs b/xpcom/rust/xpcom/src/base.rs new file mode 100644 index 0000000000..556768a179 --- /dev/null +++ b/xpcom/rust/xpcom/src/base.rs @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::interfaces::{nsIInterfaceRequestor, nsISupports}; +use crate::{GetterAddrefs, RefCounted, RefPtr}; + +#[repr(C)] +#[derive(Copy, Clone, Eq, PartialEq)] +/// A "unique identifier". This is modeled after OSF DCE UUIDs. +pub struct nsID(pub u32, pub u16, pub u16, pub [u8; 8]); + +/// Interface IDs +pub type nsIID = nsID; +/// Class IDs +pub type nsCID = nsID; + +/// A type which implements XpCom must follow the following rules: +/// +/// * It must be a legal XPCOM interface. +/// * The result of a QueryInterface or similar call, passing IID, must return a +/// valid reference to an object of the given type. +/// * It must be valid to cast a &self reference to a &nsISupports reference. +pub unsafe trait XpCom: RefCounted { + const IID: nsIID; + + /// Perform a QueryInterface call on this object, attempting to dynamically + /// cast it to the requested interface type. Returns Some(RefPtr<T>) if the + /// cast succeeded, and None otherwise. + fn query_interface<T: XpCom>(&self) -> Option<RefPtr<T>> { + let mut ga = GetterAddrefs::<T>::new(); + unsafe { + if (*(self as *const Self as *const nsISupports)) + .QueryInterface(&T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } + } + + /// Perform a `GetInterface` call on this object, returning `None` if the + /// object doesn't implement `nsIInterfaceRequestor`, or can't access the + /// interface `T`. + fn get_interface<T: XpCom>(&self) -> Option<RefPtr<T>> { + let ireq = self.query_interface::<nsIInterfaceRequestor>()?; + + let mut ga = GetterAddrefs::<T>::new(); + unsafe { + if ireq.GetInterface(&T::IID, ga.void_ptr()).succeeded() { + ga.refptr() + } else { + None + } + } + } +} diff --git a/xpcom/rust/xpcom/src/components.rs b/xpcom/rust/xpcom/src/components.rs new file mode 100644 index 0000000000..c83e2df705 --- /dev/null +++ b/xpcom/rust/xpcom/src/components.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 module contains convenient accessors for static XPCOM components. +//! +//! The contents of this file are generated from +//! `xpcom/components/gen_static_components.py`. + +extern "C" { + fn Gecko_GetServiceByModuleID( + id: ModuleID, + iid: &crate::nsIID, + result: *mut *mut libc::c_void, + ) -> nserror::nsresult; + fn Gecko_CreateInstanceByModuleID( + id: ModuleID, + iid: &crate::nsIID, + result: *mut *mut libc::c_void, + ) -> nserror::nsresult; +} + +include!(mozbuild::objdir_path!("xpcom/components/components.rs")); diff --git a/xpcom/rust/xpcom/src/interfaces/idl.rs b/xpcom/rust/xpcom/src/interfaces/idl.rs new file mode 100644 index 0000000000..c8d62ce716 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/idl.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(bad_style)] + +use crate::interfaces::*; +use crate::*; + +// NOTE: This file contains a series of `include!()` invocations, defining all +// idl interfaces directly within this module. +include!(mozbuild::objdir_path!("dist/xpcrs/rt/all.rs")); diff --git a/xpcom/rust/xpcom/src/interfaces/mod.rs b/xpcom/rust/xpcom/src/interfaces/mod.rs new file mode 100644 index 0000000000..5b9de5cef5 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/mod.rs @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 module contains the xpcom interfaces exposed to rust code. +//! +//! The items in this module come in a few flavours: +//! +//! 1. `nsI*`: These are the types for XPCOM interfaces. They should always be +//! passed behind a reference, pointer, or `RefPtr`. They may be coerced to +//! their base interfaces using the `coerce` method. +//! +//! 2. `nsI*Coerce`: These traits provide the implementation mechanics for the +//! `coerce` method, and can usually be ignored. *These traits are hidden in +//! rustdoc* +//! +//! 3. `nsI*VTable`: These structs are the vtable definitions for each type. +//! They contain the base interface's vtable, followed by pointers for each +//! of the vtable's methods. If direct access is needed, a `*const nsI*` can +//! be safely transmuted to a `*const nsI*VTable`. *These structs are hidden +//! in rustdoc* +//! +//! 4. Typedefs used in idl file definitions. + +// Interfaces defined in .idl files +mod idl; +pub use self::idl::*; + +// Other interfaces which are needed to compile +mod nonidl; +pub use self::nonidl::*; diff --git a/xpcom/rust/xpcom/src/interfaces/nonidl.rs b/xpcom/rust/xpcom/src/interfaces/nonidl.rs new file mode 100644 index 0000000000..b9e3f1abe2 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/nonidl.rs @@ -0,0 +1,180 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 module contains definitions of interfaces which are used in idl files +//! as forward declarations, but are not actually defined in an idl file. +//! +//! NOTE: The IIDs in these files must be kept in sync with the IDL definitions +//! in the corresponding C++ files. + +use crate::nsID; + +// XXX: This macro should have an option for a custom base interface instead of +// nsISupports, such that Document can have nsINode as a base, etc. For now, +// query_interface should be sufficient. +macro_rules! nonidl { + ($name:ident, $iid:expr) => { + /// This interface is referenced from idl files, but not defined in + /// them. It exports no methods to rust code. + #[repr(C)] + pub struct $name { + _vtable: *const $crate::interfaces::nsISupportsVTable, + } + + unsafe impl $crate::XpCom for $name { + const IID: $crate::nsIID = $iid; + } + + unsafe impl $crate::RefCounted for $name { + #[inline] + unsafe fn addref(&self) { + self.AddRef(); + } + #[inline] + unsafe fn release(&self) { + self.Release(); + } + } + + impl ::std::ops::Deref for $name { + type Target = $crate::interfaces::nsISupports; + #[inline] + fn deref(&self) -> &$crate::interfaces::nsISupports { + unsafe { ::std::mem::transmute(self) } + } + } + }; +} + +// Must be kept in sync with Document.h +nonidl!( + Document, + nsID( + 0xce1f7627, + 0x7109, + 0x4977, + [0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa] + ) +); + +// Must be kept in sync with nsINode.h +nonidl!( + nsINode, + nsID( + 0x70ba4547, + 0x7699, + 0x44fc, + [0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a] + ) +); + +// Must be kept in sync with nsIContent.h +nonidl!( + nsIContent, + nsID( + 0x8e1bab9d, + 0x8815, + 0x4d2c, + [0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22] + ) +); + +// Must be kept in sync with nsIConsoleReportCollector.h +nonidl!( + nsIConsoleReportCollector, + nsID( + 0xdd98a481, + 0xd2c4, + 0x4203, + [0x8d, 0xfa, 0x85, 0xbf, 0xd7, 0xdc, 0xd7, 0x05] + ) +); + +// Must be kept in sync with nsIGlobalObject.h +nonidl!( + nsIGlobalObject, + nsID( + 0x11afa8be, + 0xd997, + 0x4e07, + [0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f] + ) +); + +// Must be kept in sync with nsIScriptElement.h +nonidl!( + nsIScriptElement, + nsID( + 0xe60fca9b, + 0x1b96, + 0x4e4e, + [0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c] + ) +); + +// Must be kept in sync with nsPIDOMWindow.h +nonidl!( + nsPIDOMWindowOuter, + nsID( + 0x769693d4, + 0xb009, + 0x4fe2, + [0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf] + ) +); + +// Must be kept in sync with nsPIDOMWindow.h +nonidl!( + nsPIDOMWindowInner, + nsID( + 0x775dabc9, + 0x8f43, + 0x4277, + [0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb] + ) +); + +// Must be kept in sync with nsIScriptContext.h +nonidl!( + nsIScriptContext, + nsID( + 0x54cbe9cf, + 0x7282, + 0x421a, + [0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0] + ) +); + +// Must be kept in sync with nsIScriptGlobalObject.h +nonidl!( + nsIScriptGlobalObject, + nsID( + 0x876f83bd, + 0x6314, + 0x460a, + [0xa0, 0x45, 0x1c, 0x8f, 0x46, 0x2f, 0xb8, 0xe1] + ) +); + +// Must be kept in sync with nsIScrollObserver.h +nonidl!( + nsIScrollObserver, + nsID( + 0xaa5026eb, + 0x2f88, + 0x4026, + [0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00] + ) +); + +// Must be kept in sync with nsIWidget.h +nonidl!( + nsIWidget, + nsID( + 0x06396bf6, + 0x2dd8, + 0x45e5, + [0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80] + ) +); diff --git a/xpcom/rust/xpcom/src/lib.rs b/xpcom/rust/xpcom/src/lib.rs new file mode 100644 index 0000000000..ac039ebe76 --- /dev/null +++ b/xpcom/rust/xpcom/src/lib.rs @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 crate contains the functionality required in order to both implement +//! and call XPCOM methods from rust code. +//! +//! For documentation on how to implement XPCOM methods, see the documentation +//! for the [`xpcom_macros`](../xpcom_macros/index.html) crate. + +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +// re-export the xpcom_macros macro +pub use xpcom_macros::xpcom; + +// Helper functions and data structures are exported in the root of the crate. +mod base; +pub use base::*; + +// Declarative macro to generate XPCOM method stubs. +mod method; +pub use method::*; + +// dom::Promise resolving. +mod promise; +pub use promise::*; + +mod refptr; +pub use refptr::*; + +mod statics; +pub use statics::*; + +// XPCOM interface definitions. +pub mod interfaces; + +// XPCOM component getters. +pub mod components; + +// Implementation details of the xpcom_macros crate. +#[doc(hidden)] +pub mod reexports; diff --git a/xpcom/rust/xpcom/src/method.rs b/xpcom/rust/xpcom/src/method.rs new file mode 100644 index 0000000000..66c0510bd9 --- /dev/null +++ b/xpcom/rust/xpcom/src/method.rs @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use nserror::{nsresult, NS_ERROR_NULL_POINTER}; + +/// The xpcom_method macro generates a Rust XPCOM method stub that converts +/// raw pointer arguments to references, calls a Rustic implementation +/// of the method, writes its return value into the XPCOM method's outparameter, +/// and returns an nsresult. +/// +/// In other words, given an XPCOM method like: +/// +/// ```ignore +/// interface nsIFooBarBaz : nsISupports { +/// nsIVariant foo(in AUTF8String bar, [optional] in bool baz); +/// } +/// ``` +/// +/// And a Rust implementation that uses #[xpcom] to implement it: +/// +/// ```ignore +/// #[xpcom(implement(nsIFooBarBaz), atomic)] +/// struct FooBarBaz { +/// // … +/// } +/// ``` +/// +/// With the appropriate extern crate and use declarations +/// +/// ```ignore +/// extern crate xpcom; +/// use xpcom::xpcom_method; +/// ``` +/// +/// Invoking the macro with the name of the XPCOM method, the name of its +/// Rustic implementation, the set of its arguments, and its return value: +/// +/// ```ignore +/// impl FooBarBaz { +/// xpcom_method!( +/// foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant +/// ); +/// } +/// ``` +/// +/// Results in the macro generating an XPCOM stub like the following: +/// +/// ```ignore +/// unsafe fn Foo(&self, bar: *const nsACString, baz: bool, retval: *mut *const nsIVariant) -> nsresult { +/// let bar = match Ensure::ensure(bar) { +/// Ok(val) => val, +/// Err(result) => return result, +/// }; +/// let baz = match Ensure::ensure(baz) { +/// Ok(val) => val, +/// Err(result) => return result, +/// }; +/// +/// match self.foo(bar, baz) { +/// Ok(val) => { +/// val.forget(&mut *retval); +/// NS_OK +/// } +/// Err(error) => { +/// error!("{}", error); +/// error.into() +/// } +/// } +/// } +/// ``` +/// +/// Which calls a Rustic implementation (that you implement) like the following: +/// +/// ```ignore +/// impl FooBarBaz { +/// fn foo(&self, bar: &nsACString, baz: bool) -> Result<RefPtr<nsIVariant>, nsresult> { +/// // … +/// } +/// } +/// ``` +/// +/// Notes: +/// +/// On error, the Rustic implementation can return an Err(nsresult) or any +/// other type that implements Into<nsresult>. So you can define and return +/// a custom error type, which the XPCOM stub will convert to nsresult. +/// +/// This macro assumes that all non-null pointer arguments are valid! +/// It does ensure that they aren't null, using the `ensure_param` macro. +/// But it doesn't otherwise check their validity. That makes the function +/// unsafe, so callers must ensure that they only call it with valid pointer +/// arguments. +#[macro_export] +macro_rules! xpcom_method { + // This rule is provided to ensure external modules don't need to import + // internal implementation details of xpcom_method. + // The @ensure_param rule converts raw pointer arguments to references, + // returning NS_ERROR_NULL_POINTER if the argument is_null(). + // + // Notes: + // + // This rule can be called on a non-pointer copy parameter, but there's no + // benefit to doing so. The macro will just set the value of the parameter + // to itself. (This macro does this anyway due to limitations in declarative + // macros; it isn't currently possible to distinguish between pointer and + // copy types when processing a set of parameters.) + // + // The macro currently supports only in-parameters (*const nsIFoo); It + // doesn't (yet?) support out-parameters (*mut nsIFoo). The xpcom_method + // macro itself does, however, support the return value out-parameter. + (@ensure_param $name:ident) => { + let $name = match $crate::Ensure::ensure($name) { + Ok(val) => val, + Err(result) => return result, + }; + }; + + // `#[allow(non_snake_case)]` is used for each method because `$xpcom_name` + // is almost always UpperCamelCase, and Rust gives a warning that it should + // be snake_case. It isn't reasonable to rename the XPCOM methods, so + // silence the warning. + + // A method whose return value is a *mut *const nsISomething type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> *const $retval:ty) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut *const $retval) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + val.forget(&mut *retval); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a *mut nsAString type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsAString + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsAString) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsAString) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + (*retval).assign(&val); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a *mut nsACString type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsACString + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsACString) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsACString) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + (*retval).assign(&val); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a non-nsA[C]String *mut type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> bool + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> $retval:ty) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut $retval) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + *retval = val; + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method that doesn't have a return value. + // Example: foo => Foo(bar: *const nsACString, baz: bool) + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*)) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)*) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(_) => NS_OK, + Err(error) => { + error.into() + } + } + } + }; +} + +/// A trait that ensures that a raw pointer isn't null and converts it to +/// a reference. Because of limitations in declarative macros, this includes an +/// implementation for types that are Copy, which simply returns the value +/// itself. +#[doc(hidden)] +pub trait Ensure<T> { + unsafe fn ensure(value: T) -> Self; +} + +impl<'a, T: 'a> Ensure<*const T> for Result<&'a T, nsresult> { + unsafe fn ensure(ptr: *const T) -> Result<&'a T, nsresult> { + if ptr.is_null() { + Err(NS_ERROR_NULL_POINTER) + } else { + Ok(&*ptr) + } + } +} + +impl<'a, T: 'a> Ensure<*const T> for Result<Option<&'a T>, nsresult> { + unsafe fn ensure(ptr: *const T) -> Result<Option<&'a T>, nsresult> { + Ok(if ptr.is_null() { None } else { Some(&*ptr) }) + } +} + +impl<T: Copy> Ensure<T> for Result<T, nsresult> { + unsafe fn ensure(copyable: T) -> Result<T, nsresult> { + Ok(copyable) + } +} diff --git a/xpcom/rust/xpcom/src/promise.rs b/xpcom/rust/xpcom/src/promise.rs new file mode 100644 index 0000000000..0fdab9b6aa --- /dev/null +++ b/xpcom/rust/xpcom/src/promise.rs @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of 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/. */ + +use crate::{ + create_instance, + interfaces::{nsIVariant, nsIWritableVariant}, + RefCounted, +}; + +use cstr::*; + +mod ffi { + use super::*; + + extern "C" { + // These are implemented in dom/promise/Promise.cpp + pub fn DomPromise_AddRef(promise: *const Promise); + pub fn DomPromise_Release(promise: *const Promise); + pub fn DomPromise_RejectWithVariant(promise: *const Promise, variant: *const nsIVariant); + pub fn DomPromise_ResolveWithVariant(promise: *const Promise, variant: *const nsIVariant); + } +} + +#[repr(C)] +pub struct Promise { + private: [u8; 0], + + /// This field is a phantomdata to ensure that the Promise type and any + /// struct containing it is not safe to send across threads, as DOM is + /// generally not threadsafe. + __nosync: ::std::marker::PhantomData<::std::rc::Rc<u8>>, +} + +impl Promise { + pub fn reject_with_undefined(&self) { + let variant = create_instance::<nsIWritableVariant>(cstr!("@mozilla.org/variant;1")) + .expect("Failed to create writable variant"); + unsafe { + variant.SetAsVoid(); + } + self.reject_with_variant(&variant); + } + + pub fn reject_with_variant(&self, variant: &nsIVariant) { + unsafe { ffi::DomPromise_RejectWithVariant(self, variant) } + } + + pub fn resolve_with_variant(&self, variant: &nsIVariant) { + unsafe { ffi::DomPromise_ResolveWithVariant(self, variant) } + } +} + +unsafe impl RefCounted for Promise { + unsafe fn addref(&self) { + ffi::DomPromise_AddRef(self) + } + + unsafe fn release(&self) { + ffi::DomPromise_Release(self) + } +} diff --git a/xpcom/rust/xpcom/src/reexports.rs b/xpcom/rust/xpcom/src/reexports.rs new file mode 100644 index 0000000000..d198899497 --- /dev/null +++ b/xpcom/rust/xpcom/src/reexports.rs @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 automatically generated code from `xpcom_macros` depends on some types +//! which are defined in other libraries which `xpcom` depends on, but which may +//! not be `extern crate`-ed into the crate the macros are expanded into. This +//! module re-exports those types from `xpcom` so that they can be used from the +//! macro. +// re-export libc so it can be used by the procedural macro. +pub extern crate libc; + +pub use nsstring::{nsACString, nsAString, nsCString, nsString}; + +pub use nserror::{nsresult, NS_ERROR_NO_INTERFACE, NS_OK}; + +pub use std::ops::Deref; + +/// Helper method used by the xpcom codegen, it is not public API or meant for +/// calling outside of that context. +/// +/// Takes a reference to the `this` pointer received from XPIDL, and offsets and +/// casts it to a reference to the concrete rust `struct` type, `U`. +/// +/// `vtable_index` is the index, and therefore the offset in pointers, of the +/// vtable for `T` in `U`. +/// +/// A reference to `this` is taken, instead of taking `*const T` by value, to use +/// as a lifetime bound, such that the returned `&U` reference has a bounded +/// lifetime when used to call the implementation method. +#[inline] +pub unsafe fn transmute_from_vtable_ptr<'a, T, U>( + this: &'a *const T, + vtable_index: usize, +) -> &'a U { + &*((*this as *const *const ()).sub(vtable_index) as *const U) +} + +/// On some ABIs, extra information is included before the vtable's function +/// pointers which are used to implement RTTI. We build Gecko with RTTI +/// disabled, however these fields may still be present to support +/// `dynamic_cast<void*>` on our rust VTables in case they are accessed. +/// +/// Itanium ABI Layout: https://refspecs.linuxbase.org/cxxabi-1.83.html#vtable +#[repr(C)] +pub struct VTableExtra<T> { + #[cfg(not(windows))] + pub offset: isize, + #[cfg(not(windows))] + pub typeinfo: *const libc::c_void, + pub vtable: T, +} diff --git a/xpcom/rust/xpcom/src/refptr.rs b/xpcom/rust/xpcom/src/refptr.rs new file mode 100644 index 0000000000..8549b6d2f0 --- /dev/null +++ b/xpcom/rust/xpcom/src/refptr.rs @@ -0,0 +1,388 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::interfaces::nsISupports; +use libc; +use nserror::{nsresult, NS_OK}; +use std::cell::Cell; +use std::convert::TryInto; +use std::fmt; +use std::marker::PhantomData; +use std::mem; +use std::ops::Deref; +use std::ptr::{self, NonNull}; +use std::sync::atomic::{self, AtomicUsize, Ordering}; +use threadbound::ThreadBound; + +// This should match the definition in mfbt/RefCountType.h, modulo the delicate +// effort at maintaining binary compatibility with Microsoft COM on Windows. +pub type MozExternalRefCountType = u32; + +/// A trait representing a type which can be reference counted invasively. +/// The object is responsible for freeing its backing memory when its +/// reference count reaches 0. +pub unsafe trait RefCounted { + /// Increment the reference count. + unsafe fn addref(&self); + /// Decrement the reference count, potentially freeing backing memory. + unsafe fn release(&self); +} + +/// A smart pointer holding a RefCounted object. The object itself manages its +/// own memory. RefPtr will invoke the addref and release methods at the +/// appropriate times to facilitate the bookkeeping. +#[repr(transparent)] +pub struct RefPtr<T: RefCounted + 'static> { + _ptr: NonNull<T>, + // Tell dropck that we own an instance of T. + _marker: PhantomData<T>, +} + +impl<T: RefCounted + 'static> RefPtr<T> { + /// Construct a new RefPtr from a reference to the refcounted object. + #[inline] + pub fn new(p: &T) -> RefPtr<T> { + unsafe { + p.addref(); + } + RefPtr { + _ptr: p.into(), + _marker: PhantomData, + } + } + + /// Construct a RefPtr from a raw pointer, addrefing it. + #[inline] + pub unsafe fn from_raw(p: *const T) -> Option<RefPtr<T>> { + let ptr = NonNull::new(p as *mut T)?; + ptr.as_ref().addref(); + Some(RefPtr { + _ptr: ptr, + _marker: PhantomData, + }) + } + + /// Construct a RefPtr from a raw pointer, without addrefing it. + #[inline] + pub unsafe fn from_raw_dont_addref(p: *const T) -> Option<RefPtr<T>> { + Some(RefPtr { + _ptr: NonNull::new(p as *mut T)?, + _marker: PhantomData, + }) + } + + /// Write this RefPtr's value into an outparameter. + #[inline] + pub fn forget(self, into: &mut *const T) { + *into = Self::forget_into_raw(self); + } + + #[inline] + pub fn forget_into_raw(this: RefPtr<T>) -> *const T { + let into = &*this as *const T; + mem::forget(this); + into + } +} + +impl<T: RefCounted + 'static> Deref for RefPtr<T> { + type Target = T; + #[inline] + fn deref(&self) -> &T { + unsafe { self._ptr.as_ref() } + } +} + +impl<T: RefCounted + 'static> Drop for RefPtr<T> { + #[inline] + fn drop(&mut self) { + unsafe { + self._ptr.as_ref().release(); + } + } +} + +impl<T: RefCounted + 'static> Clone for RefPtr<T> { + #[inline] + fn clone(&self) -> RefPtr<T> { + RefPtr::new(self) + } +} + +impl<T: RefCounted + 'static + fmt::Debug> fmt::Debug for RefPtr<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RefPtr<{:?}>", self.deref()) + } +} + +// Both `Send` and `Sync` bounds are required for `RefPtr<T>` to implement +// either, as sharing a `RefPtr<T>` also allows transferring ownership, and +// vice-versa. +unsafe impl<T: RefCounted + 'static + Send + Sync> Send for RefPtr<T> {} +unsafe impl<T: RefCounted + 'static + Send + Sync> Sync for RefPtr<T> {} + +macro_rules! assert_layout_eq { + ($T:ty, $U:ty) => { + const _: [(); std::mem::size_of::<$T>()] = [(); std::mem::size_of::<$U>()]; + const _: [(); std::mem::align_of::<$T>()] = [(); std::mem::align_of::<$U>()]; + }; +} + +// Assert that `RefPtr<nsISupports>` has the correct memory layout. +assert_layout_eq!(RefPtr<nsISupports>, *const nsISupports); +// Assert that the null-pointer optimization applies to `RefPtr<nsISupports>`. +assert_layout_eq!(RefPtr<nsISupports>, Option<RefPtr<nsISupports>>); + +/// A wrapper that binds a RefCounted value to its original thread, +/// preventing retrieval from other threads and panicking if the value +/// is dropped on a different thread. +/// +/// These limitations enable values of this type to be Send + Sync, which is +/// useful when creating a struct that holds a RefPtr<T> type while being +/// Send + Sync. Such a struct can hold a ThreadBoundRefPtr<T> type instead. +pub struct ThreadBoundRefPtr<T: RefCounted + 'static>(ThreadBound<*const T>); + +impl<T: RefCounted + 'static> ThreadBoundRefPtr<T> { + pub fn new(ptr: RefPtr<T>) -> Self { + let raw: *const T = &*ptr; + mem::forget(ptr); + ThreadBoundRefPtr(ThreadBound::new(raw)) + } + + pub fn get_ref(&self) -> Option<&T> { + self.0.get_ref().map(|raw| unsafe { &**raw }) + } +} + +impl<T: RefCounted + 'static> Drop for ThreadBoundRefPtr<T> { + fn drop(&mut self) { + unsafe { + RefPtr::from_raw_dont_addref(self.get_ref().expect("drop() called on wrong thread!")); + } + } +} + +/// A helper struct for constructing `RefPtr<T>` from raw pointer outparameters. +/// Holds a `*const T` internally which will be released if non null when +/// destructed, and can be easily transformed into an `Option<RefPtr<T>>`. +/// +/// It many cases it may be easier to use the `getter_addrefs` method. +pub struct GetterAddrefs<T: RefCounted + 'static> { + _ptr: *const T, + _marker: PhantomData<T>, +} + +impl<T: RefCounted + 'static> GetterAddrefs<T> { + /// Create a `GetterAddrefs`, initializing it with the null pointer. + #[inline] + pub fn new() -> GetterAddrefs<T> { + GetterAddrefs { + _ptr: ptr::null(), + _marker: PhantomData, + } + } + + /// Get a reference to the internal `*const T`. This method is unsafe, + /// as the destructor of this class depends on the internal `*const T` + /// being either a valid reference to a value of type `T`, or null. + #[inline] + pub unsafe fn ptr(&mut self) -> &mut *const T { + &mut self._ptr + } + + /// Get a reference to the internal `*const T` as a `*mut libc::c_void`. + /// This is useful to pass to functions like `GetInterface` which take a + /// void pointer outparameter. + #[inline] + pub unsafe fn void_ptr(&mut self) -> *mut *mut libc::c_void { + &mut self._ptr as *mut *const T as *mut *mut libc::c_void + } + + /// Transform this `GetterAddrefs` into an `Option<RefPtr<T>>`, without + /// performing any addrefs or releases. + #[inline] + pub fn refptr(self) -> Option<RefPtr<T>> { + let p = self._ptr; + // Don't run the destructor because we don't want to release the stored + // pointer. + mem::forget(self); + unsafe { RefPtr::from_raw_dont_addref(p) } + } +} + +impl<T: RefCounted + 'static> Drop for GetterAddrefs<T> { + #[inline] + fn drop(&mut self) { + if !self._ptr.is_null() { + unsafe { + (*self._ptr).release(); + } + } + } +} + +/// Helper method for calling XPCOM methods which return a reference counted +/// value through an outparameter. Takes a lambda, which is called with a valid +/// outparameter argument (`*mut *const T`), and returns a `nsresult`. Returns +/// either a `RefPtr<T>` with the value returned from the outparameter, or a +/// `nsresult`. +/// +/// # NOTE: +/// +/// Can return `Err(NS_OK)` if the call succeeded, but the outparameter was set +/// to NULL. +/// +/// # Usage +/// +/// ``` +/// let x: Result<RefPtr<T>, nsresult> = +/// getter_addrefs(|p| iosvc.NewURI(uri, ptr::null(), ptr::null(), p)); +/// ``` +#[inline] +pub fn getter_addrefs<T: RefCounted, F>(f: F) -> Result<RefPtr<T>, nsresult> +where + F: FnOnce(*mut *const T) -> nsresult, +{ + let mut ga = GetterAddrefs::<T>::new(); + let rv = f(unsafe { ga.ptr() }); + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(NS_OK) +} + +/// The type of the reference count type for xpcom structs. +/// +/// `#[xpcom(nonatomic)]` will use this type for the `__refcnt` field. +#[derive(Debug)] +pub struct Refcnt(Cell<usize>); +impl Refcnt { + /// Create a new reference count value. This is unsafe as manipulating + /// Refcnt values is an easy footgun. + pub unsafe fn new() -> Self { + Refcnt(Cell::new(0)) + } + + /// Increment the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn inc(&self) -> MozExternalRefCountType { + // XXX: Checked add? + let new = self.0.get() + 1; + self.0.set(new); + new.try_into().unwrap() + } + + /// Decrement the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn dec(&self) -> MozExternalRefCountType { + // XXX: Checked sub? + let new = self.0.get() - 1; + self.0.set(new); + new.try_into().unwrap() + } + + /// Get the current value of the reference count. + pub fn get(&self) -> usize { + self.0.get() + } +} + +/// The type of the atomic reference count used for xpcom structs. +/// +/// `#[xpcom(atomic)]` will use this type for the `__refcnt` field. +/// +/// See `nsISupportsImpl.h`'s `ThreadSafeAutoRefCnt` class for reasoning behind +/// memory ordering decisions. +#[derive(Debug)] +pub struct AtomicRefcnt(AtomicUsize); +impl AtomicRefcnt { + /// Create a new reference count value. This is unsafe as manipulating + /// Refcnt values is an easy footgun. + pub unsafe fn new() -> Self { + AtomicRefcnt(AtomicUsize::new(0)) + } + + /// Increment the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn inc(&self) -> MozExternalRefCountType { + let result = self.0.fetch_add(1, Ordering::Relaxed) + 1; + result.try_into().unwrap() + } + + /// Decrement the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn dec(&self) -> MozExternalRefCountType { + let result = self.0.fetch_sub(1, Ordering::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. + if cfg!(feature = "thread_sanitizer") { + // TSan doesn't understand atomic::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. + self.0.load(Ordering::Acquire); + } else { + atomic::fence(Ordering::Acquire); + } + } + result.try_into().unwrap() + } + + /// Get the current value of the reference count. + pub fn get(&self) -> usize { + self.0.load(Ordering::Acquire) + } +} + +#[cfg(feature = "gecko_refcount_logging")] +pub mod trace_refcnt { + extern "C" { + pub fn NS_LogCtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32); + pub fn NS_LogDtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32); + pub fn NS_LogAddRef( + aPtr: *mut libc::c_void, + aRefcnt: usize, + aClass: *const libc::c_char, + aClassSize: u32, + ); + pub fn NS_LogRelease( + aPtr: *mut libc::c_void, + aRefcnt: usize, + aClass: *const libc::c_char, + aClassSize: u32, + ); + } +} + +// stub inline methods for the refcount logging functions for when the feature +// is disabled. +#[cfg(not(feature = "gecko_refcount_logging"))] +pub mod trace_refcnt { + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogCtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {} + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogDtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {} + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogAddRef( + _: *mut libc::c_void, + _: usize, + _: *const libc::c_char, + _: u32, + ) { + } + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogRelease( + _: *mut libc::c_void, + _: usize, + _: *const libc::c_char, + _: u32, + ) { + } +} diff --git a/xpcom/rust/xpcom/src/statics.rs b/xpcom/rust/xpcom/src/statics.rs new file mode 100644 index 0000000000..27f66fdb4b --- /dev/null +++ b/xpcom/rust/xpcom/src/statics.rs @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::interfaces::{nsIComponentManager, nsIComponentRegistrar, nsIServiceManager}; +use crate::{GetterAddrefs, RefPtr, XpCom}; +use std::ffi::CStr; + +/// Get a reference to the global `nsIComponentManager`. +/// +/// Can return `None` during shutdown. +#[inline] +pub fn component_manager() -> Option<RefPtr<nsIComponentManager>> { + unsafe { RefPtr::from_raw(Gecko_GetComponentManager()) } +} + +/// Get a reference to the global `nsIServiceManager`. +/// +/// Can return `None` during shutdown. +#[inline] +pub fn service_manager() -> Option<RefPtr<nsIServiceManager>> { + unsafe { RefPtr::from_raw(Gecko_GetServiceManager()) } +} + +/// Get a reference to the global `nsIComponentRegistrar` +/// +/// Can return `None` during shutdown. +#[inline] +pub fn component_registrar() -> Option<RefPtr<nsIComponentRegistrar>> { + unsafe { RefPtr::from_raw(Gecko_GetComponentRegistrar()) } +} + +/// Helper for calling `nsIComponentManager::CreateInstanceByContractID` on the +/// global `nsIComponentRegistrar`. +/// +/// This method is similar to `do_CreateInstance` in C++. +#[inline] +pub fn create_instance<T: XpCom>(id: &CStr) -> Option<RefPtr<T>> { + unsafe { + let mut ga = GetterAddrefs::<T>::new(); + if component_manager()? + .CreateInstanceByContractID(id.as_ptr(), &T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } +} + +/// Helper for calling `nsIServiceManager::GetServiceByContractID` on the global +/// `nsIServiceManager`. +/// +/// This method is similar to `do_GetService` in C++. +#[inline] +pub fn get_service<T: XpCom>(id: &CStr) -> Option<RefPtr<T>> { + unsafe { + let mut ga = GetterAddrefs::<T>::new(); + if service_manager()? + .GetServiceByContractID(id.as_ptr(), &T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } +} + +extern "C" { + fn Gecko_GetComponentManager() -> *const nsIComponentManager; + fn Gecko_GetServiceManager() -> *const nsIServiceManager; + fn Gecko_GetComponentRegistrar() -> *const nsIComponentRegistrar; +} diff --git a/xpcom/rust/xpcom/xpcom_macros/Cargo.toml b/xpcom/rust/xpcom/xpcom_macros/Cargo.toml new file mode 100644 index 0000000000..78238ae6d1 --- /dev/null +++ b/xpcom/rust/xpcom/xpcom_macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "xpcom_macros" +version = "0.1.0" +authors = ["Nika Layzell <nika@thelayzells.com>"] +edition = "2018" +license = "MPL-2.0" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" +lazy_static = "1.0" +mozbuild = "0.1" diff --git a/xpcom/rust/xpcom/xpcom_macros/src/lib.rs b/xpcom/rust/xpcom/xpcom_macros/src/lib.rs new file mode 100644 index 0000000000..5c40f699aa --- /dev/null +++ b/xpcom/rust/xpcom/xpcom_macros/src/lib.rs @@ -0,0 +1,812 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 crate provides the `#[xpcom]` custom attribute. This custom attribute +//! is used in order to implement [`xpcom`] interfaces. +//! +//! # Usage +//! +//! The easiest way to explain this crate is probably with a usage example. I'll +//! show you the example, and then we'll destructure it and walk through what +//! each component is doing. +//! +//! ```ignore +//! // Declaring an XPCOM Struct +//! #[xpcom(implement(nsIRunnable), atomic)] +//! struct ImplRunnable { +//! i: i32, +//! } +//! +//! // Implementing methods on an XPCOM Struct +//! impl ImplRunnable { +//! unsafe fn Run(&self) -> nsresult { +//! println!("{}", self.i); +//! NS_OK +//! } +//! } +//! ``` +//! +//! ## Declaring an XPCOM Struct +//! +//! ```ignore +//! // This derive should be placed on the initialization struct in order to +//! // trigger the procedural macro. +//! #[xpcom( +//! // The implement argument should be passed the names of the IDL +//! // interfaces which you want to implement. These can be separated by +//! // commas if you want to implement multiple interfaces. +//! // +//! // Some methods use types which we cannot bind to in rust. Interfaces +//! // like those cannot be implemented, and a compile-time error will occur +//! // if they are listed in this attribute. +//! implement(nsIRunnable), +//! +//! // The refcount kind can be specified as one of the following values: +//! // * `atomic` == atomic reference count +//! // ~= NS_DECL_THREADSAFE_ISUPPORTS in C++ +//! // * `nonatomic` == non atomic reference count +//! // ~= NS_DECL_ISUPPORTS in C++ +//! atomic, +//! )] +//! +//! // It is a compile time error to put the `#[xpcom]` attribute on +//! // an enum, union, or tuple struct. +//! // +//! // The macro will generate both the named struct, as well as a version with +//! // its name prefixed with `Init` which can be used to initialize the type. +//! struct ImplRunnable { +//! i: i32, +//! } +//! ``` +//! +//! The above example will generate `ImplRunnable` and `InitImplRunnable` +//! structs. The `ImplRunnable` struct will implement the [`nsIRunnable`] XPCOM +//! interface, and cannot be constructed directly. +//! +//! The following methods will be automatically implemented on it: +//! +//! ```ignore +//! // Automatic nsISupports implementation +//! unsafe fn AddRef(&self) -> MozExternalRefCountType; +//! unsafe fn Release(&self) -> MozExternalRefCountType; +//! unsafe fn QueryInterface(&self, uuid: &nsIID, result: *mut *mut libc::c_void) -> nsresult; +//! +//! // Allocates and initializes a new instance of this type. The values will +//! // be moved from the `Init` struct which is passed in. +//! fn allocate(init: InitImplRunnable) -> RefPtr<Self>; +//! +//! // Helper for performing the `query_interface` operation to case to a +//! // specific interface. +//! fn query_interface<T: XpCom>(&self) -> Option<RefPtr<T>>; +//! +//! // Coerce function for cheaply casting to our base interfaces. +//! fn coerce<T: ImplRunnableCoerce>(&self) -> &T; +//! ``` +//! +//! The [`RefCounted`] interface will also be implemented, so that the type can +//! be used within the [`RefPtr`] type. +//! +//! The `coerce` and `query_interface` methods are backed by the generated +//! `*Coerce` trait. This trait is impl-ed for every interface implemented by +//! the trait. For example: +//! +//! ```ignore +//! pub trait ImplRunnableCoerce { +//! fn coerce_from(x: &ImplRunnable) -> &Self; +//! } +//! impl ImplRunnableCoerce for nsIRunnable { .. } +//! impl ImplRunnableCoerce for nsISupports { .. } +//! ``` +//! +//! ## Implementing methods on an XPCOM Struct +//! +//! ```ignore +//! // Methods should be implemented directly on the generated struct. All +//! // methods other than `AddRef`, `Release`, and `QueryInterface` must be +//! // implemented manually. +//! impl ImplRunnable { +//! // The method should have the same name as the corresponding C++ method. +//! unsafe fn Run(&self) -> nsresult { +//! // Fields defined on the template struct will be directly on the +//! // generated struct. +//! println!("{}", self.i); +//! NS_OK +//! } +//! } +//! ``` +//! +//! XPCOM methods implemented in Rust have signatures similar to methods +//! implemented in C++. +//! +//! ```ignore +//! // nsISupports foo(in long long bar, in AString baz); +//! unsafe fn Foo(&self, bar: i64, baz: *const nsAString, +//! _retval: *mut *const nsISupports) -> nsresult; +//! +//! // AString qux(in nsISupports ham); +//! unsafe fn Qux(&self, ham: *const nsISupports, +//! _retval: *mut nsAString) -> nsresult; +//! ``` +//! +//! This is a little tedious, so the `xpcom_method!` macro provides a convenient +//! way to generate wrappers around more idiomatic Rust methods. +//! +//! [`xpcom`]: ../xpcom/index.html +//! [`nsIRunnable`]: ../xpcom/struct.nsIRunnable.html +//! [`RefCounted`]: ../xpcom/struct.RefCounted.html +//! [`RefPtr`]: ../xpcom/struct.RefPtr.html + +use lazy_static::lazy_static; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use std::collections::{HashMap, HashSet}; +use syn::meta::ParseNestedMeta; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, parse_quote, Field, Fields, Ident, ItemStruct, Token, Type}; + +macro_rules! bail { + (@($t:expr), $s:expr) => { + return Err(syn::Error::new_spanned(&$t, &$s[..])) + }; + (@($t:expr), $f:expr, $($e:expr),*) => { + return Err(syn::Error::new_spanned(&$t, &format!($f, $($e),*)[..])) + }; + ($s:expr) => { + return Err(syn::Error::new(Span::call_site(), &$s[..])) + }; + ($f:expr, $($e:expr),*) => { + return Err(syn::Error::new(Span::call_site(), &format!($f, $($e),*)[..])) + }; +} + +/* These are the structs generated by the rust_macros.py script */ + +/// A single parameter to an XPCOM method. +#[derive(Debug)] +struct Param { + name: &'static str, + ty: &'static str, +} + +/// A single method on an XPCOM interface. +#[derive(Debug)] +struct Method { + name: &'static str, + params: &'static [Param], + ret: &'static str, +} + +/// An XPCOM interface. `methods` will be `Err("reason")` if the interface +/// cannot be implemented in rust code. +#[derive(Debug)] +struct Interface { + name: &'static str, + base: Option<&'static str>, + sync: bool, + methods: Result<&'static [Method], &'static str>, +} + +impl Interface { + fn base(&self) -> Option<&'static Interface> { + Some(IFACES[self.base?]) + } + + fn methods(&self) -> Result<&'static [Method], syn::Error> { + match self.methods { + Ok(methods) => Ok(methods), + Err(reason) => Err(syn::Error::new( + Span::call_site(), + format!( + "Interface {} cannot be implemented in rust \ + because {} is not supported yet", + self.name, reason + ), + )), + } + } +} + +lazy_static! { + /// This item contains the information generated by the procedural macro in + /// the form of a `HashMap` from interface names to their descriptions. + static ref IFACES: HashMap<&'static str, &'static Interface> = { + let lists: &[&[Interface]] = + include!(mozbuild::objdir_path!("dist/xpcrs/bt/all.rs")); + + let mut hm = HashMap::new(); + for &list in lists { + for iface in list { + hm.insert(iface.name, iface); + } + } + hm + }; +} + +/// The type of the reference count to use for the struct. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +enum RefcntKind { + Atomic, + NonAtomic, +} + +/// Produces the tokens for the type representation. +impl ToTokens for RefcntKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match *self { + RefcntKind::NonAtomic => quote!(xpcom::Refcnt).to_tokens(tokens), + RefcntKind::Atomic => quote!(xpcom::AtomicRefcnt).to_tokens(tokens), + } + } +} + +/// Extract the fields list from the input struct. +fn get_fields(si: &ItemStruct) -> Result<&Punctuated<Field, Token![,]>, syn::Error> { + match si.fields { + Fields::Named(ref named) => Ok(&named.named), + _ => bail!(@(si), "The initializer struct must be a standard named \ + value struct definition"), + } +} + +/// Takes the template struct in, and generates `ItemStruct` for the "real" and +/// "init" structs. +fn gen_structs( + template: &ItemStruct, + bases: &[&Interface], + refcnt_ty: RefcntKind, +) -> Result<(ItemStruct, ItemStruct), syn::Error> { + let real_ident = &template.ident; + let init_ident = format_ident!("Init{}", real_ident); + let vis = &template.vis; + + let bases = bases.iter().map(|base| { + let ident = format_ident!("__base_{}", base.name); + let vtable = format_ident!("{}VTable", base.name); + quote!(#ident : &'static xpcom::interfaces::#vtable) + }); + + let fields = get_fields(template)?; + let (impl_generics, _, where_clause) = template.generics.split_for_impl(); + Ok(( + parse_quote! { + #[repr(C)] + #vis struct #real_ident #impl_generics #where_clause { + #(#bases,)* + __refcnt: #refcnt_ty, + #fields + } + }, + parse_quote! { + #vis struct #init_ident #impl_generics #where_clause { + #fields + } + }, + )) +} + +/// Generates the `extern "system"` methods which are actually included in the +/// VTable for the given interface. +/// +/// `idx` must be the offset in pointers of the pointer to this vtable in the +/// struct `real`. This is soundness-critical, as it will be used to offset +/// pointers received from xpcom back to the concrete implementation. +fn gen_vtable_methods( + real: &ItemStruct, + iface: &Interface, + vtable_index: usize, +) -> Result<TokenStream, syn::Error> { + let base_ty = format_ident!("{}", iface.name); + + let base_methods = if let Some(base) = iface.base() { + gen_vtable_methods(real, base, vtable_index)? + } else { + quote! {} + }; + + let ty_name = &real.ident; + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + + let mut method_defs = Vec::new(); + for method in iface.methods()? { + let ret = syn::parse_str::<Type>(method.ret)?; + + let mut params = Vec::new(); + let mut args = Vec::new(); + for param in method.params { + let name = format_ident!("{}", param.name); + let ty = syn::parse_str::<Type>(param.ty)?; + + params.push(quote! {#name : #ty,}); + args.push(quote! {#name,}); + } + + let name = format_ident!("{}", method.name); + method_defs.push(quote! { + unsafe extern "system" fn #name #impl_generics ( + this: *const #base_ty, #(#params)* + ) -> #ret #where_clause { + let this: &#ty_name #ty_generics = + ::xpcom::reexports::transmute_from_vtable_ptr(&this, #vtable_index); + this.#name(#(#args)*) + } + }); + } + + Ok(quote! { + #base_methods + #(#method_defs)* + }) +} + +/// Generates the VTable for a given base interface. This assumes that the +/// implementations of each of the `extern "system"` methods are in scope. +fn gen_inner_vtable(real: &ItemStruct, iface: &Interface) -> Result<TokenStream, syn::Error> { + let vtable_ty = format_ident!("{}VTable", iface.name); + + // Generate the vtable for the base interface. + let base_vtable = if let Some(base) = iface.base() { + let vt = gen_inner_vtable(real, base)?; + quote! {__base: #vt,} + } else { + quote! {} + }; + + // Include each of the method definitions for this interface. + let (_, ty_generics, _) = real.generics.split_for_impl(); + let turbofish = ty_generics.as_turbofish(); + let vtable_init = iface + .methods()? + .iter() + .map(|method| { + let name = format_ident!("{}", method.name); + quote! { #name : #name #turbofish, } + }) + .collect::<Vec<_>>(); + + Ok(quote!(#vtable_ty { + #base_vtable + #(#vtable_init)* + })) +} + +fn gen_root_vtable( + real: &ItemStruct, + base: &Interface, + idx: usize, +) -> Result<TokenStream, syn::Error> { + let field = format_ident!("__base_{}", base.name); + let vtable_ty = format_ident!("{}VTable", base.name); + + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + let turbofish = ty_generics.as_turbofish(); + + let methods = gen_vtable_methods(real, base, idx)?; + let vtable = gen_inner_vtable(real, base)?; + + // Define the `recover_self` method. This performs an offset calculation to + // recover a pointer to the original struct from a pointer to the given + // VTable field. + Ok(quote! {#field: { + // The method implementations which will be used to build the vtable. + #methods + + // The actual VTable definition. This is in a separate method in order + // to allow it to be generic. + #[inline] + fn get_vtable #impl_generics () -> &'static ::xpcom::reexports::VTableExtra<#vtable_ty> #where_clause { + &::xpcom::reexports::VTableExtra { + #[cfg(not(windows))] + offset: { + // NOTE: workaround required to avoid depending on the + // unstable const expression feature `const {}`. + const OFFSET: isize = -((::std::mem::size_of::<usize>() * #idx) as isize); + OFFSET + }, + #[cfg(not(windows))] + typeinfo: 0 as *const _, + vtable: #vtable, + } + } + &get_vtable #turbofish ().vtable + },}) +} + +/// Generate the cast implementations. This generates the implementation details +/// for the `Coerce` trait, and the `QueryInterface` method. The first return +/// value is the `QueryInterface` implementation, and the second is the `Coerce` +/// implementation. +fn gen_casts( + seen: &mut HashSet<&'static str>, + iface: &Interface, + real: &ItemStruct, + coerce_name: &Ident, + vtable_field: &Ident, +) -> Result<(TokenStream, TokenStream), syn::Error> { + if !seen.insert(iface.name) { + return Ok((quote! {}, quote! {})); + } + + // Generate the cast implementations for the base interfaces. + let (base_qi, base_coerce) = if let Some(base) = iface.base() { + gen_casts(seen, base, real, coerce_name, vtable_field)? + } else { + (quote! {}, quote! {}) + }; + + // Add the if statment to QueryInterface for the base class. + let base_name = format_ident!("{}", iface.name); + + let qi = quote! { + #base_qi + if *uuid == #base_name::IID { + // Implement QueryInterface in terms of coercions. + self.addref(); + *result = self.coerce::<#base_name>() + as *const #base_name + as *const ::xpcom::reexports::libc::c_void + as *mut ::xpcom::reexports::libc::c_void; + return ::xpcom::reexports::NS_OK; + } + }; + + // Add an implementation of the `*Coerce` trait for the base interface. + let name = &real.ident; + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + let coerce = quote! { + #base_coerce + + impl #impl_generics #coerce_name #ty_generics for ::xpcom::interfaces::#base_name #where_clause { + fn coerce_from(v: &#name #ty_generics) -> &Self { + unsafe { + // Get the address of the VTable field. This should be a + // pointer to a pointer to a vtable, which we can then cast + // into a pointer to our interface. + &*(&(v.#vtable_field) + as *const &'static _ + as *const ::xpcom::interfaces::#base_name) + } + } + } + }; + + Ok((qi, coerce)) +} + +fn check_generics(generics: &syn::Generics) -> Result<(), syn::Error> { + for param in &generics.params { + let tp = match param { + syn::GenericParam::Type(tp) => tp, + syn::GenericParam::Lifetime(lp) => bail!( + @(lp), + "Cannot use #[xpcom] on types with lifetime parameters. \ + Implementors of XPCOM interfaces must not contain non-'static \ + lifetimes.", + ), + // XXX: Once const generics become stable, it may be as simple as + // removing this bail! to support them. + syn::GenericParam::Const(cp) => { + bail!(@(cp), "Cannot use #[xpcom] on types with const generics.") + } + }; + + let mut static_lt = false; + for bound in &tp.bounds { + match bound { + syn::TypeParamBound::Lifetime(lt) if lt.ident == "static" => { + static_lt = true; + break; + } + _ => {} + } + } + + if !static_lt { + bail!( + @(param), + "Every generic parameter for xpcom implementation must have a \ + 'static lifetime bound declared in the generics. Implicit \ + lifetime bounds or lifetime bounds in where clauses are not \ + detected by the macro and will be ignored. \ + Implementors of XPCOM interfaces must not contain non-'static \ + lifetimes.", + ); + } + } + Ok(()) +} + +#[derive(Default)] +struct Options { + bases: Vec<&'static Interface>, + refcnt: Option<RefcntKind>, +} + +impl Options { + fn parse(&mut self, meta: ParseNestedMeta) -> Result<(), syn::Error> { + if meta.path.is_ident("atomic") || meta.path.is_ident("nonatomic") { + if self.refcnt.is_some() { + bail!(@(meta.path), "Duplicate refcnt atomicity specifier"); + } + self.refcnt = Some(if meta.path.is_ident("atomic") { + RefcntKind::Atomic + } else { + RefcntKind::NonAtomic + }); + Ok(()) + } else if meta.path.is_ident("implement") { + meta.parse_nested_meta(|meta| { + let ident = match meta.path.get_ident() { + Some(ref iface) => iface.to_string(), + _ => bail!(@(meta.path), "Interface name must be unqualified"), + }; + if let Some(&iface) = IFACES.get(ident.as_str()) { + self.bases.push(iface); + } else { + bail!(@(meta.path), "Invalid base interface `{}`", ident); + } + Ok(()) + }) + } else { + bail!(@(meta.path), "Unexpected argument to #[xpcom]") + } + } + + fn validate(self) -> Result<Self, syn::Error> { + if self.bases.is_empty() { + bail!( + "Types with #[xpcom(..)] must implement at least one \ + interface. Interfaces can be implemented by adding an \ + implements(nsIFoo, nsIBar) parameter to the #[xpcom] attribute" + ); + } + + if self.refcnt.is_none() { + bail!("Must specify refcnt kind in #[xpcom] attribute"); + } + + Ok(self) + } +} + +/// The root xpcom procedural macro definition. +fn xpcom_impl(options: Options, template: ItemStruct) -> Result<TokenStream, syn::Error> { + check_generics(&template.generics)?; + + let bases = options.bases; + + // Ensure that all our base interface methods have unique names. + let mut method_names = HashMap::new(); + for base in &bases { + for method in base.methods()? { + if let Some(existing) = method_names.insert(method.name, base.name) { + bail!( + "The method `{0}` is declared on both `{1}` and `{2}`, + but a Rust type cannot implement two methods with the \ + same name. You can add the `[binaryname(Renamed{0})]` \ + XPIDL attribute to one of the declarations to rename it.", + method.name, + existing, + base.name + ); + } + } + } + + // Determine what reference count type to use, and generate the real struct. + let refcnt_ty = options.refcnt.unwrap(); + let (real, init) = gen_structs(&template, &bases, refcnt_ty)?; + + let name_init = &init.ident; + let name = &real.ident; + let coerce_name = format_ident!("{}Coerce", name); + + // Generate a VTable for each of the base interfaces. + let mut vtables = Vec::new(); + for (idx, base) in bases.iter().enumerate() { + vtables.push(gen_root_vtable(&real, base, idx)?); + } + + // Generate the field initializers for the final struct, moving each field + // out of the original __init struct. + let inits = get_fields(&init)?.iter().map(|field| { + let id = &field.ident; + quote! { #id : __init.#id, } + }); + + let vis = &real.vis; + + // Generate the implementation for QueryInterface and Coerce. + let mut seen = HashSet::new(); + let mut qi_impl = Vec::new(); + let mut coerce_impl = Vec::new(); + for base in &bases { + let (qi, coerce) = gen_casts( + &mut seen, + base, + &real, + &coerce_name, + &format_ident!("__base_{}", base.name), + )?; + qi_impl.push(qi); + coerce_impl.push(coerce); + } + + let assert_sync = if bases.iter().any(|iface| iface.sync) { + quote! { + // Helper for asserting that for all instantiations, this + // object implements Send + Sync. + fn xpcom_type_must_be_send_sync<T: Send + Sync>(t: &T) {} + xpcom_type_must_be_send_sync(&*boxed); + } + } else { + quote! {} + }; + + let size_for_logs = if real.generics.params.is_empty() { + quote!(::std::mem::size_of::<Self>() as u32) + } else { + // Refcount logging requires all types with the same name to have the + // same size, and generics aren't taken into account when creating our + // name string, so we need to make sure that all possible instantiations + // report the same size. To do that, we fake a size based on the number + // of vtable pointers and the known refcount field. + let fake_size_npointers = bases.len() + 1; + quote!((::std::mem::size_of::<usize>() * #fake_size_npointers) as u32) + }; + + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + let name_for_logs = quote!( + concat!(module_path!(), "::", stringify!(#name #ty_generics), "\0").as_ptr() + as *const ::xpcom::reexports::libc::c_char + ); + Ok(quote! { + #init + + #real + + impl #impl_generics #name #ty_generics #where_clause { + /// This method is used for + fn allocate(__init: #name_init #ty_generics) -> ::xpcom::RefPtr<Self> { + #[allow(unused_imports)] + use ::xpcom::*; + #[allow(unused_imports)] + use ::xpcom::interfaces::*; + #[allow(unused_imports)] + use ::xpcom::reexports::{ + libc, nsACString, nsAString, nsCString, nsString, nsresult + }; + + // Helper for asserting that for all instantiations, this + // object has the 'static lifetime. + fn xpcom_types_must_be_static<T: 'static>(t: &T) {} + + unsafe { + // NOTE: This is split into multiple lines to make the + // output more readable. + let value = #name { + #(#vtables)* + __refcnt: #refcnt_ty::new(), + #(#inits)* + }; + let boxed = ::std::boxed::Box::new(value); + xpcom_types_must_be_static(&*boxed); + #assert_sync + let raw = ::std::boxed::Box::into_raw(boxed); + ::xpcom::RefPtr::from_raw(raw).unwrap() + } + } + + /// Automatically generated implementation of AddRef for nsISupports. + #vis unsafe fn AddRef(&self) -> ::xpcom::MozExternalRefCountType { + let new = self.__refcnt.inc(); + ::xpcom::trace_refcnt::NS_LogAddRef( + self as *const _ as *mut ::xpcom::reexports::libc::c_void, + new as usize, + #name_for_logs, + #size_for_logs, + ); + new + } + + /// Automatically generated implementation of Release for nsISupports. + #vis unsafe fn Release(&self) -> ::xpcom::MozExternalRefCountType { + let new = self.__refcnt.dec(); + ::xpcom::trace_refcnt::NS_LogRelease( + self as *const _ as *mut ::xpcom::reexports::libc::c_void, + new as usize, + #name_for_logs, + #size_for_logs, + ); + if new == 0 { + // dealloc + ::std::mem::drop(::std::boxed::Box::from_raw(self as *const Self as *mut Self)); + } + new + } + + /// Automatically generated implementation of QueryInterface for + /// nsISupports. + #vis unsafe fn QueryInterface(&self, + uuid: *const ::xpcom::nsIID, + result: *mut *mut ::xpcom::reexports::libc::c_void) + -> ::xpcom::reexports::nsresult { + #[allow(unused_imports)] + use ::xpcom::*; + #[allow(unused_imports)] + use ::xpcom::interfaces::*; + + #(#qi_impl)* + + ::xpcom::reexports::NS_ERROR_NO_INTERFACE + } + + /// Perform a QueryInterface call on this object, attempting to + /// dynamically cast it to the requested interface type. Returns + /// Some(RefPtr<T>) if the cast succeeded, and None otherwise. + #vis fn query_interface<XPCOM_InterfaceType: ::xpcom::XpCom>(&self) + -> ::std::option::Option<::xpcom::RefPtr<XPCOM_InterfaceType>> + { + let mut ga = ::xpcom::GetterAddrefs::<XPCOM_InterfaceType>::new(); + unsafe { + if self.QueryInterface(&XPCOM_InterfaceType::IID, ga.void_ptr()).succeeded() { + ga.refptr() + } else { + None + } + } + } + + /// Coerce this type safely to any of the interfaces which it + /// implements without `AddRef`ing it. + #vis fn coerce<XPCOM_InterfaceType: #coerce_name #ty_generics>(&self) -> &XPCOM_InterfaceType { + XPCOM_InterfaceType::coerce_from(self) + } + } + + /// This trait is implemented on the interface types which this + /// `#[xpcom]` type can be safely ane cheaply coerced to using the + /// `coerce` method. + /// + /// The trait and its method should usually not be used directly, but + /// rather acts as a trait bound and implementation for the `coerce` + /// methods. + #[doc(hidden)] + #vis trait #coerce_name #impl_generics #where_clause { + /// Convert a value of the `#[xpcom]` type into the implementing + /// interface type. + fn coerce_from(v: &#name #ty_generics) -> &Self; + } + + #(#coerce_impl)* + + unsafe impl #impl_generics ::xpcom::RefCounted for #name #ty_generics #where_clause { + unsafe fn addref(&self) { + self.AddRef(); + } + + unsafe fn release(&self) { + self.Release(); + } + } + }) +} + +#[proc_macro_attribute] +pub fn xpcom( + args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let mut options = Options::default(); + let xpcom_parser = syn::meta::parser(|meta| options.parse(meta)); + parse_macro_input!(args with xpcom_parser); + let input = parse_macro_input!(input as ItemStruct); + match options + .validate() + .and_then(|options| xpcom_impl(options, input)) + { + Ok(ts) => ts.into(), + Err(err) => err.to_compile_error().into(), + } +} diff --git a/xpcom/string/README.html b/xpcom/string/README.html new file mode 100644 index 0000000000..ea81688121 --- /dev/null +++ b/xpcom/string/README.html @@ -0,0 +1,11 @@ +<html> + <!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + <body> + <h1><span class="LXRSHORTDESC">managing sequences of characters</span></h1> + <p> + <span class="LXRLONGDESC"></span> + </p> + </body> +</html> diff --git a/xpcom/string/RustRegex.h b/xpcom/string/RustRegex.h new file mode 100644 index 0000000000..80b8140bb5 --- /dev/null +++ b/xpcom/string/RustRegex.h @@ -0,0 +1,707 @@ +/* -*- 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_RustRegex_h +#define mozilla_RustRegex_h + +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "rure.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +// This header is a thin wrapper around the `rure.h` header file, which declares +// the C API for interacting with the rust `regex` crate. This is intended to +// make the type more ergonomic to use with mozilla types. + +class RustRegex; +class RustRegexSet; +class RustRegexOptions; +class RustRegexCaptures; +class RustRegexIter; +class RustRegexIterCaptureNames; + +using RustRegexMatch = rure_match; + +/* + * RustRegexCaptures represents storage for sub-capture locations of a match. + * + * Computing the capture groups of a match can carry a significant performance + * penalty, so their use in the API is optional. + * + * A RustRegexCaptures value may outlive its corresponding RustRegex and can be + * freed independently. + * + * It is not safe to use from multiple threads simultaneously. + */ +class RustRegexCaptures final { + public: + RustRegexCaptures() = default; + + // Check if the `RustRegexCaptures` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * CaptureAt returns Some if and only if the capturing group at the + * index given was part of the match. If so, the returned RustRegexMatch + * object contains the start and end offsets (in bytes) of the match. + * + * If no capture group with the index aIdx exists, or the group was not part + * of the match, then Nothing is returned. (A capturing group exists if and + * only if aIdx is less than Length().) + * + * Note that index 0 corresponds to the full match. + */ + Maybe<RustRegexMatch> CaptureAt(size_t aIdx) const { + RustRegexMatch match; + if (mPtr && rure_captures_at(mPtr.get(), aIdx, &match)) { + return Some(match); + } + return Nothing(); + } + Maybe<RustRegexMatch> operator[](size_t aIdx) const { + return CaptureAt(aIdx); + } + + /* + * Returns the number of capturing groups in this `RustRegexCaptures`. + */ + size_t Length() const { return mPtr ? rure_captures_len(mPtr.get()) : 0; } + + private: + friend class RustRegex; + friend class RustRegexIter; + + explicit RustRegexCaptures(rure* aRe) + : mPtr(aRe ? rure_captures_new(aRe) : nullptr) {} + + struct Deleter { + void operator()(rure_captures* ptr) const { rure_captures_free(ptr); } + }; + UniquePtr<rure_captures, Deleter> mPtr; +}; + +/* + * RustRegexIterCaptureNames is an iterator over the list of capture group names + * in this particular RustRegex. + * + * A RustRegexIterCaptureNames value may not outlive its corresponding + * RustRegex, and should be destroyed before its corresponding RustRegex is + * destroyed. + * + * It is not safe to use from multiple threads simultaneously. + */ +class RustRegexIterCaptureNames { + public: + RustRegexIterCaptureNames() = delete; + + // Check if the `RustRegexIterCaptureNames` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * Advances the iterator and returns true if and only if another capture group + * name exists. + * + * The value of the capture group name is written to the provided pointer. + */ + mozilla::Maybe<const char*> Next() { + char* next = nullptr; + if (mPtr && rure_iter_capture_names_next(mPtr.get(), &next)) { + return Some(next); + } + return Nothing(); + } + + private: + friend class RustRegex; + + explicit RustRegexIterCaptureNames(rure* aRe) + : mPtr(aRe ? rure_iter_capture_names_new(aRe) : nullptr) {} + + struct Deleter { + void operator()(rure_iter_capture_names* ptr) const { + rure_iter_capture_names_free(ptr); + } + }; + UniquePtr<rure_iter_capture_names, Deleter> mPtr; +}; + +/* + * RustRegexIter is an iterator over successive non-overlapping matches in a + * particular haystack. + * + * A RustRegexIter value may not outlive its corresponding RustRegex and should + * be destroyed before its corresponding RustRegex is destroyed. + * + * It is not safe to use from multiple threads simultaneously. + */ +class RustRegexIter { + public: + RustRegexIter() = delete; + + // Check if the `RustRegexIter` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * Next() returns Some if and only if this regex matches anywhere in haystack. + * The returned RustRegexMatch object contains the start and end offsets (in + * bytes) of the match. + * + * If no match is found, then subsequent calls will return Nothing() + * indefinitely. + * + * Next() should be preferred to NextCaptures() since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + mozilla::Maybe<RustRegexMatch> Next() { + RustRegexMatch match{}; + if (mPtr && + rure_iter_next(mPtr.get(), mHaystackPtr, mHaystackSize, &match)) { + return Some(match); + } + return Nothing(); + } + + /* + * NextCaptures returns a valid RustRegexCaptures if and only if this regex + * matches anywhere in haystack. If a match is found, then all of its capture + * locations are stored in the returned RustRegexCaptures object. + * + * If no match is found, then subsequent calls will return an invalid + * `RustRegexCaptures` indefinitely. + * + * Only use this function if you specifically need access to capture + * locations. It is not necessary to use this function just because your + * regular expression contains capturing groups. + * + * Capture locations can be accessed using the methods on RustRegexCaptures. + * + * N.B. The performance of this search can be impacted by the number of + * capturing groups. If you're using this function, it may be beneficial to + * use non-capturing groups (e.g., `(?:re)`) where possible. + */ + RustRegexCaptures NextCaptures() { + RustRegexCaptures captures(mRe); + if (mPtr && rure_iter_next_captures(mPtr.get(), mHaystackPtr, mHaystackSize, + captures.mPtr.get())) { + return captures; + } + return {}; + } + + private: + friend class RustRegex; + RustRegexIter(rure* aRe, const std::string_view& aHaystack) + : mRe(aRe), + mHaystackPtr(reinterpret_cast<const uint8_t*>(aHaystack.data())), + mHaystackSize(aHaystack.size()), + mPtr(aRe ? rure_iter_new(aRe) : nullptr) {} + + rure* MOZ_NON_OWNING_REF mRe; + const uint8_t* MOZ_NON_OWNING_REF mHaystackPtr; + size_t mHaystackSize; + + struct Deleter { + void operator()(rure_iter* ptr) const { rure_iter_free(ptr); } + }; + UniquePtr<rure_iter, Deleter> mPtr; +}; + +/* + * RustRegexOptions is the set of configuration options for compiling a regular + * expression. + * + * All flags on this type can be used to set default flags while compiling, and + * can be toggled in the expression itself using standard syntax, e.g. `(?i)` + * turns case-insensitive matching on, and `(?-i)` disables it. + * + * In addition, two non-flag options are available: setting the size limit of + * the compiled program and setting the size limit of the cache of states that + * the DFA uses while searching. + * + * For most uses, the default settings will work fine, and a default-constructed + * RustRegexOptions can be passed. + */ +class RustRegexOptions { + public: + RustRegexOptions() = default; + + /* + * Set the value for the case insensitive (i) flag. + * + * When enabled, letters in the pattern will match both upper case and lower + * case variants. + */ + RustRegexOptions& CaseInsensitive(bool aYes) { + return SetFlag(aYes, RURE_FLAG_CASEI); + } + + /* + * Set the value for the multi-line matching (m) flag. + * + * When enabled, ^ matches the beginning of lines and $ matches the end of + * lines. + * + * By default, they match beginning/end of the input. + */ + RustRegexOptions& MultiLine(bool aYes) { + return SetFlag(aYes, RURE_FLAG_MULTI); + } + + /* + * Set the value for the any character (s) flag, where in . matches anything + * when s is set and matches anything except for new line when it is not set + * (the default). + * + * N.B. “matches anything†means “any byte†when Unicode is disabled and means + * “any valid UTF-8 encoding of any Unicode scalar value†when Unicode is + * enabled. + */ + RustRegexOptions& DotMatchesNewLine(bool aYes) { + return SetFlag(aYes, RURE_FLAG_DOTNL); + } + + /* + * Set the value for the greedy swap (U) flag. + * + * When enabled, a pattern like a* is lazy (tries to find shortest match) and + * a*? is greedy (tries to find longest match). + * + * By default, a* is greedy and a*? is lazy. + */ + RustRegexOptions& SwapGreed(bool aYes) { + return SetFlag(aYes, RURE_FLAG_SWAP_GREED); + } + + /* + * Set the value for the ignore whitespace (x) flag. + * + * When enabled, whitespace such as new lines and spaces will be ignored + * between expressions of the pattern, and # can be used to start a comment + * until the next new line. + */ + RustRegexOptions& IgnoreWhitespace(bool aYes) { + return SetFlag(aYes, RURE_FLAG_SPACE); + } + + /* + * Set the value for the Unicode (u) flag. + * + * Enabled by default. When disabled, character classes such as \w only match + * ASCII word characters instead of all Unicode word characters. + */ + RustRegexOptions& Unicode(bool aYes) { + return SetFlag(aYes, RURE_FLAG_UNICODE); + } + + /* + * SizeLimit sets the appoximate size limit of the compiled regular + * expression. + * + * This size limit roughly corresponds to the number of bytes occupied by + * a single compiled program. If the program would exceed this number, + * then an invalid RustRegex will be constructed. + */ + RustRegexOptions& SizeLimit(size_t aLimit) { + mSizeLimit = Some(aLimit); + return *this; + } + + /* + * DFASizeLimit sets the approximate size of the cache used by the DFA during + * search. + * + * This roughly corresponds to the number of bytes that the DFA will use while + * searching. + * + * Note that this is a *per thread* limit. There is no way to set a global + * limit. In particular, if a regular expression is used from multiple threads + * simultaneously, then each thread may use up to the number of bytes + * specified here. + */ + RustRegexOptions& DFASizeLimit(size_t aLimit) { + mDFASizeLimit = Some(aLimit); + return *this; + } + + private: + friend class RustRegex; + friend class RustRegexSet; + + struct OptionsDeleter { + void operator()(rure_options* ptr) const { rure_options_free(ptr); } + }; + + UniquePtr<rure_options, OptionsDeleter> GetOptions() const { + UniquePtr<rure_options, OptionsDeleter> options; + if (mSizeLimit || mDFASizeLimit) { + options.reset(rure_options_new()); + if (mSizeLimit) { + rure_options_size_limit(options.get(), *mSizeLimit); + } + if (mDFASizeLimit) { + rure_options_dfa_size_limit(options.get(), *mDFASizeLimit); + } + } + return options; + } + + uint32_t GetFlags() const { return mFlags; } + + RustRegexOptions& SetFlag(bool aYes, uint32_t aFlag) { + if (aYes) { + mFlags |= aFlag; + } else { + mFlags &= ~aFlag; + } + return *this; + } + + uint32_t mFlags = RURE_DEFAULT_FLAGS; + Maybe<size_t> mSizeLimit; + Maybe<size_t> mDFASizeLimit; +}; + +/* + * RustRegex is the type of a compiled regular expression. + * + * A RustRegex can be safely used from multiple threads simultaneously. + * + * When calling the matching methods on this type, they will generally have the + * following parameters: + * + * aHaystack + * may contain arbitrary bytes, but ASCII compatible text is more useful. + * UTF-8 is even more useful. Other text encodings aren't supported. + * + * aStart + * the position in bytes at which to start searching. Note that setting the + * start position is distinct from using a substring for `aHaystack`, since + * the regex engine may look at bytes before the start position to determine + * match information. For example, if the start position is greater than 0, + * then the \A ("begin text") anchor can never match. + */ +class RustRegex final { + public: + // Create a new invalid RustRegex object + RustRegex() = default; + + /* + * Compiles the given pattern into a regular expression. The pattern must be + * valid UTF-8 and the length corresponds to the number of bytes in the + * pattern. + * + * If an error occurs, the constructed RustRegex will be `!IsValid()`. + * + * The compiled expression returned may be used from multiple threads + * simultaneously. + */ + explicit RustRegex(const std::string_view& aPattern, + const RustRegexOptions& aOptions = {}) { +#ifdef DEBUG + rure_error* error = rure_error_new(); +#else + rure_error* error = nullptr; +#endif + mPtr.reset(rure_compile(reinterpret_cast<const uint8_t*>(aPattern.data()), + aPattern.size(), aOptions.GetFlags(), + aOptions.GetOptions().get(), error)); +#ifdef DEBUG + if (!mPtr) { + NS_WARNING(nsPrintfCString("RustRegex compile failed: %s", + rure_error_message(error)) + .get()); + } + rure_error_free(error); +#endif + } + + // Check if the compiled `RustRegex` is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * IsMatch returns true if and only if this regex matches anywhere in + * aHaystack. + * + * See the type-level comment for details on aHaystack and aStart. + * + * IsMatch() should be preferred to Find() since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + bool IsMatch(const std::string_view& aHaystack, size_t aStart = 0) const { + return mPtr && + rure_is_match(mPtr.get(), + reinterpret_cast<const uint8_t*>(aHaystack.data()), + aHaystack.size(), aStart); + } + + /* + * Find returns Some if and only if this regex matches anywhere in + * haystack. The returned RustRegexMatch object contains the start and end + * offsets (in bytes) of the match. + * + * See the type-level comment for details on aHaystack and aStart. + * + * Find() should be preferred to FindCaptures() since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + Maybe<RustRegexMatch> Find(const std::string_view& aHaystack, + size_t aStart = 0) const { + RustRegexMatch match{}; + if (mPtr && rure_find(mPtr.get(), + reinterpret_cast<const uint8_t*>(aHaystack.data()), + aHaystack.size(), aStart, &match)) { + return Some(match); + } + return Nothing(); + } + + /* + * FindCaptures() returns a valid RustRegexCaptures if and only if this + * regex matches anywhere in haystack. If a match is found, then all of its + * capture locations are stored in the returned RustRegexCaptures object. + * + * See the type-level comment for details on aHaystack and aStart. + * + * Only use this function if you specifically need access to capture + * locations. It is not necessary to use this function just because your + * regular expression contains capturing groups. + * + * Capture locations can be accessed using the methods on RustRegexCaptures. + * + * N.B. The performance of this search can be impacted by the number of + * capturing groups. If you're using this function, it may be beneficial to + * use non-capturing groups (e.g., `(?:re)`) where possible. + */ + RustRegexCaptures FindCaptures(const std::string_view& aHaystack, + size_t aStart = 0) const { + RustRegexCaptures captures(mPtr.get()); + if (mPtr && + rure_find_captures(mPtr.get(), + reinterpret_cast<const uint8_t*>(aHaystack.data()), + aHaystack.size(), aStart, captures.mPtr.get())) { + return captures; + } + return {}; + } + + /* + * ShortestMatch() returns Some if and only if this regex matches anywhere + * in haystack. If a match is found, then its end location is stored in the + * pointer given. The end location is the place at which the regex engine + * determined that a match exists, but may occur before the end of the + * proper leftmost-first match. + * + * See the type-level comment for details on aHaystack and aStart. + * + * ShortestMatch should be preferred to Find since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + Maybe<size_t> ShortestMatch(const std::string_view& aHaystack, + size_t aStart = 0) const { + size_t end = 0; + if (mPtr && + rure_shortest_match(mPtr.get(), + reinterpret_cast<const uint8_t*>(aHaystack.data()), + aHaystack.size(), aStart, &end)) { + return Some(end); + } + return Nothing(); + } + + /* + * Create an iterator over all successive non-overlapping matches of this + * regex in aHaystack. + * + * See the type-level comment for details on aHaystack. + * + * Both aHaystack and this regex must remain valid until the returned + * `RustRegexIter` is destroyed. + */ + RustRegexIter IterMatches(const std::string_view& aHaystack) const { + return RustRegexIter(mPtr.get(), aHaystack); + } + + /* + * Returns the capture index for the name given. If no such named capturing + * group exists in this regex, then -1 is returned. + * + * The capture index may be used with RustRegexCaptures::CaptureAt. + * + * This function never returns 0 since the first capture group always + * corresponds to the entire match and is always unnamed. + */ + int32_t CaptureNameIndex(const char* aName) const { + return mPtr ? rure_capture_name_index(mPtr.get(), aName) : -1; + } + + /* + * Create an iterator over the list of capture group names in this particular + * regex. + * + * This regex must remain valid until the returned `RustRegexIterCaptureNames` + * is destroyed. + */ + RustRegexIterCaptureNames IterCaptureNames() const { + return RustRegexIterCaptureNames(mPtr.get()); + } + + /* + * Count the number of successive non-overlapping matches of this regex in + * aHaystack. + * + * See the type-level comment for details on aHaystack. + */ + size_t CountMatches(const std::string_view& aHaystack) const { + size_t count = 0; + auto iter = IterMatches(aHaystack); + while (iter.Next()) { + count++; + } + return count; + } + + private: + struct Deleter { + void operator()(rure* ptr) const { rure_free(ptr); } + }; + UniquePtr<rure, Deleter> mPtr; +}; + +/* + * RustRegexSet is the type of a set of compiled regular expression. + * + * A RustRegexSet can be safely used from multiple threads simultaneously. + * + * When calling the matching methods on this type, they will generally have the + * following parameters: + * + * aHaystack + * may contain arbitrary bytes, but ASCII compatible text is more useful. + * UTF-8 is even more useful. Other text encodings aren't supported. + * + * aStart + * the position in bytes at which to start searching. Note that setting the + * start position is distinct from using a substring for `aHaystack`, since + * the regex engine may look at bytes before the start position to determine + * match information. For example, if the start position is greater than 0, + * then the \A ("begin text") anchor can never match. + */ +class RustRegexSet final { + public: + /* + * Compiles the given range of patterns into a single regular expression which + * can be matched in a linear-scan. Each pattern in aPatterns must be valid + * UTF-8, and implicitly coerce to `std::string_view`. + * + * If an error occurs, the constructed RustRegexSet will be `!IsValid()`. + * + * The compiled expression returned may be used from multiple threads + * simultaneously. + */ + template <typename Patterns> + explicit RustRegexSet(Patterns&& aPatterns, + const RustRegexOptions& aOptions = {}) { +#ifdef DEBUG + rure_error* error = rure_error_new(); +#else + rure_error* error = nullptr; +#endif + AutoTArray<const uint8_t*, 4> patternPtrs; + AutoTArray<size_t, 4> patternSizes; + for (auto&& pattern : std::forward<Patterns>(aPatterns)) { + std::string_view view = pattern; + patternPtrs.AppendElement(reinterpret_cast<const uint8_t*>(view.data())); + patternSizes.AppendElement(view.size()); + } + mPtr.reset(rure_compile_set(patternPtrs.Elements(), patternSizes.Elements(), + patternPtrs.Length(), aOptions.GetFlags(), + aOptions.GetOptions().get(), error)); +#ifdef DEBUG + if (!mPtr) { + NS_WARNING(nsPrintfCString("RustRegexSet compile failed: %s", + rure_error_message(error)) + .get()); + } + rure_error_free(error); +#endif + } + + // Check if the `RustRegexSet` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * IsMatch returns true if and only if any regexes within the set + * match anywhere in the haystack. Once a match has been located, the + * matching engine will quit immediately. + * + * See the type-level comment for details on aHaystack and aStart. + */ + bool IsMatch(const std::string_view& aHaystack, size_t aStart = 0) const { + return mPtr && + rure_set_is_match(mPtr.get(), + reinterpret_cast<const uint8_t*>(aHaystack.data()), + aHaystack.size(), aStart); + } + + struct SetMatches { + bool matchedAny = false; + nsTArray<bool> matches; + }; + + /* + * Matches() compares each regex in the set against the haystack and + * returns a list with the match result of each pattern. Match results are + * ordered in the same way as the regex set was compiled. For example, index 0 + * of matches corresponds to the first pattern passed to the constructor. + * + * See the type-level comment for details on aHaystack and aStart. + * + * Only use this function if you specifically need to know which regexes + * matched within the set. To determine if any of the regexes matched without + * caring which, use IsMatch. + */ + SetMatches Matches(const std::string_view& aHaystack, + size_t aStart = 0) const { + nsTArray<bool> matches; + matches.SetLength(Length()); + bool any = mPtr && rure_set_matches( + mPtr.get(), + reinterpret_cast<const uint8_t*>(aHaystack.data()), + aHaystack.size(), aStart, matches.Elements()); + return SetMatches{any, std::move(matches)}; + } + + /* + * Returns the number of patterns the regex set was compiled with. + */ + size_t Length() const { return mPtr ? rure_set_len(mPtr.get()) : 0; } + + private: + struct Deleter { + void operator()(rure_set* ptr) const { rure_set_free(ptr); } + }; + UniquePtr<rure_set, Deleter> mPtr; +}; + +} // namespace mozilla + +#endif // mozilla_RustRegex_h diff --git a/xpcom/string/RustStringAPI.cpp b/xpcom/string/RustStringAPI.cpp new file mode 100644 index 0000000000..55ce6b9eeb --- /dev/null +++ b/xpcom/string/RustStringAPI.cpp @@ -0,0 +1,123 @@ +/* -*- 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.h" +#include "nsString.h" + +// Extern "C" utilities used by the rust nsString bindings. + +// Provide rust bindings to the nsA[C]String types +extern "C" { + +// This is a no-op on release, so we ifdef it out such that using it in release +// results in a linker error. +#ifdef DEBUG +void Gecko_IncrementStringAdoptCount(void* aData) { + MOZ_LOG_CTOR(aData, "StringAdopt", 1); +} +#elif defined(MOZ_DEBUG_RUST) +void Gecko_IncrementStringAdoptCount(void* aData) {} +#endif + +void Gecko_FinalizeCString(nsACString* aThis) { aThis->~nsACString(); } + +void Gecko_AssignCString(nsACString* aThis, const nsACString* aOther) { + aThis->Assign(*aOther); +} + +void Gecko_TakeFromCString(nsACString* aThis, nsACString* aOther) { + aThis->Assign(std::move(*aOther)); +} + +void Gecko_AppendCString(nsACString* aThis, const nsACString* aOther) { + aThis->Append(*aOther); +} + +void Gecko_SetLengthCString(nsACString* aThis, uint32_t aLength) { + aThis->SetLength(aLength); +} + +bool Gecko_FallibleAssignCString(nsACString* aThis, const nsACString* aOther) { + return aThis->Assign(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleTakeFromCString(nsACString* aThis, nsACString* aOther) { + return aThis->Assign(std::move(*aOther), mozilla::fallible); +} + +bool Gecko_FallibleAppendCString(nsACString* aThis, const nsACString* aOther) { + return aThis->Append(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleSetLengthCString(nsACString* aThis, uint32_t aLength) { + return aThis->SetLength(aLength, mozilla::fallible); +} + +char* Gecko_BeginWritingCString(nsACString* aThis) { + return aThis->BeginWriting(); +} + +char* Gecko_FallibleBeginWritingCString(nsACString* aThis) { + return aThis->BeginWriting(mozilla::fallible); +} + +uint32_t Gecko_StartBulkWriteCString(nsACString* aThis, uint32_t aCapacity, + uint32_t aUnitsToPreserve, + bool aAllowShrinking) { + return aThis->StartBulkWriteImpl(aCapacity, aUnitsToPreserve, aAllowShrinking) + .unwrapOr(UINT32_MAX); +} + +void Gecko_FinalizeString(nsAString* aThis) { aThis->~nsAString(); } + +void Gecko_AssignString(nsAString* aThis, const nsAString* aOther) { + aThis->Assign(*aOther); +} + +void Gecko_TakeFromString(nsAString* aThis, nsAString* aOther) { + aThis->Assign(std::move(*aOther)); +} + +void Gecko_AppendString(nsAString* aThis, const nsAString* aOther) { + aThis->Append(*aOther); +} + +void Gecko_SetLengthString(nsAString* aThis, uint32_t aLength) { + aThis->SetLength(aLength); +} + +bool Gecko_FallibleAssignString(nsAString* aThis, const nsAString* aOther) { + return aThis->Assign(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleTakeFromString(nsAString* aThis, nsAString* aOther) { + return aThis->Assign(std::move(*aOther), mozilla::fallible); +} + +bool Gecko_FallibleAppendString(nsAString* aThis, const nsAString* aOther) { + return aThis->Append(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleSetLengthString(nsAString* aThis, uint32_t aLength) { + return aThis->SetLength(aLength, mozilla::fallible); +} + +char16_t* Gecko_BeginWritingString(nsAString* aThis) { + return aThis->BeginWriting(); +} + +char16_t* Gecko_FallibleBeginWritingString(nsAString* aThis) { + return aThis->BeginWriting(mozilla::fallible); +} + +uint32_t Gecko_StartBulkWriteString(nsAString* aThis, uint32_t aCapacity, + uint32_t aUnitsToPreserve, + bool aAllowShrinking) { + return aThis->StartBulkWriteImpl(aCapacity, aUnitsToPreserve, aAllowShrinking) + .unwrapOr(UINT32_MAX); +} + +} // extern "C" diff --git a/xpcom/string/crashtests/1113005-frame.html b/xpcom/string/crashtests/1113005-frame.html new file mode 100644 index 0000000000..505fc22f1e --- /dev/null +++ b/xpcom/string/crashtests/1113005-frame.html @@ -0,0 +1,5 @@ +<form method=post enctype=multipart/form-data action="data:text/html,"><textarea name='file"; filename="filename.ext + '></textarea> +<script> +document.forms[0].submit(); +</script> diff --git a/xpcom/string/crashtests/1113005.html b/xpcom/string/crashtests/1113005.html new file mode 100644 index 0000000000..e377bb637f --- /dev/null +++ b/xpcom/string/crashtests/1113005.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<iframe src="1113005-frame.html"></iframe> diff --git a/xpcom/string/crashtests/394275-1.html b/xpcom/string/crashtests/394275-1.html new file mode 100644 index 0000000000..b589c4d359 --- /dev/null +++ b/xpcom/string/crashtests/394275-1.html @@ -0,0 +1,9 @@ +<html> +<body> +<script> +style = document.createElement("style"); // eslint-disable-line no-undef +document.documentElement.appendChild(style); // eslint-disable-line no-undef +style.textContent = "tz\uDAB2 "; // eslint-disable-line no-undef +</script> +</body> +</html> diff --git a/xpcom/string/crashtests/395651-1.html b/xpcom/string/crashtests/395651-1.html new file mode 100644 index 0000000000..bbed371fd6 --- /dev/null +++ b/xpcom/string/crashtests/395651-1.html @@ -0,0 +1,30 @@ +<html> +<head> +<script> + +function X() { dump("X\n"); } +function Y() { dump("Y\n"); } + +function boom() { + dump("Start9\n"); + + var div = document.getElementById("v"); + + var textNode = document.createTextNode(String.fromCharCode(0xDAAF)); // high surrogate + div.appendChild(textNode); + + document.addEventListener("DOMCharacterDataModified", X, true); + textNode.data += "B"; + document.removeEventListener("DOMCharacterDataModified", X, true); + + document.addEventListener("DOMAttrModified", Y, true); + textNode.data += String.fromCharCode(0xDF53); // low surrogate + document.removeEventListener("DOMAttrModified", Y, true); +} + +</script> +</head> + +<body onload="boom();"><div id="v"></div></body> + +</html> diff --git a/xpcom/string/crashtests/crashtests.list b/xpcom/string/crashtests/crashtests.list new file mode 100644 index 0000000000..d464166e85 --- /dev/null +++ b/xpcom/string/crashtests/crashtests.list @@ -0,0 +1,3 @@ +load 394275-1.html +load 395651-1.html +skip-if(gtkWidget||winWidget) load 1113005.html # Bug 1683062 diff --git a/xpcom/string/moz.build b/xpcom/string/moz.build new file mode 100644 index 0000000000..c0f8091b8f --- /dev/null +++ b/xpcom/string/moz.build @@ -0,0 +1,62 @@ +# -*- 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "String") + +EXPORTS += [ + "nsASCIIMask.h", + "nsAString.h", + "nsCharTraits.h", + "nsDependentString.h", + "nsDependentSubstring.h", + "nsLiteralString.h", + "nsPrintfCString.h", + "nsPromiseFlatString.h", + "nsReadableUtils.h", + "nsString.h", + "nsStringBuffer.h", + "nsStringFlags.h", + "nsStringFwd.h", + "nsStringIterator.h", + "nsTDependentString.h", + "nsTDependentSubstring.h", + "nsTextFormatter.h", + "nsTLiteralString.h", + "nsTPromiseFlatString.h", + "nsTString.h", + "nsTStringHasher.h", + "nsTStringRepr.h", + "nsTSubstring.h", + "nsTSubstringTuple.h", + "nsUTF8Utils.h", +] + +EXPORTS.mozilla += [ + "RustRegex.h", +] + +UNIFIED_SOURCES += [ + "nsASCIIMask.cpp", + "nsReadableUtils.cpp", + "nsStringBuffer.cpp", + "nsTDependentString.cpp", + "nsTDependentSubstring.cpp", + "nsTextFormatter.cpp", + "nsTLiteralString.cpp", + "nsTPromiseFlatString.cpp", + "nsTString.cpp", + "nsTStringComparator.cpp", + "nsTStringRepr.cpp", + "nsTSubstring.cpp", + "nsTSubstringTuple.cpp", + "RustStringAPI.cpp", +] + +if CONFIG["MOZ_DEBUG"]: + UNIFIED_SOURCES += ["nsStringStats.cpp"] + +FINAL_LIBRARY = "xul" diff --git a/xpcom/string/nsASCIIMask.cpp b/xpcom/string/nsASCIIMask.cpp new file mode 100644 index 0000000000..abcff70306 --- /dev/null +++ b/xpcom/string/nsASCIIMask.cpp @@ -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/. */ + +#include "nsASCIIMask.h" + +namespace mozilla { + +constexpr bool TestWhitespace(char c) { + return c == '\f' || c == '\t' || c == '\r' || c == '\n' || c == ' '; +} +constexpr ASCIIMaskArray sWhitespaceMask = CreateASCIIMask(TestWhitespace); + +constexpr bool TestCRLF(char c) { return c == '\r' || c == '\n'; } +constexpr ASCIIMaskArray sCRLFMask = CreateASCIIMask(TestCRLF); + +constexpr bool TestCRLFTab(char c) { + return c == '\r' || c == '\n' || c == '\t'; +} +constexpr ASCIIMaskArray sCRLFTabMask = CreateASCIIMask(TestCRLFTab); + +constexpr bool TestZeroToNine(char c) { + return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || + c == '6' || c == '7' || c == '8' || c == '9'; +} +constexpr ASCIIMaskArray sZeroToNineMask = CreateASCIIMask(TestZeroToNine); + +const ASCIIMaskArray& ASCIIMask::MaskWhitespace() { return sWhitespaceMask; } + +const ASCIIMaskArray& ASCIIMask::MaskCRLF() { return sCRLFMask; } + +const ASCIIMaskArray& ASCIIMask::MaskCRLFTab() { return sCRLFTabMask; } + +const ASCIIMaskArray& ASCIIMask::Mask0to9() { return sZeroToNineMask; } + +} // namespace mozilla diff --git a/xpcom/string/nsASCIIMask.h b/xpcom/string/nsASCIIMask.h new file mode 100644 index 0000000000..54f51d8957 --- /dev/null +++ b/xpcom/string/nsASCIIMask.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 nsASCIIMask_h_ +#define nsASCIIMask_h_ + +#include <array> +#include <utility> + +#include "mozilla/Attributes.h" + +typedef std::array<bool, 128> ASCIIMaskArray; + +namespace mozilla { + +// Boolean arrays, fixed size and filled in at compile time, meant to +// record something about each of the (standard) ASCII characters. +// No extended ASCII for now, there has been no use case. +// If you have loops that go through a string character by character +// and test for equality to a certain set of characters before deciding +// on a course of action, chances are building up one of these arrays +// and using it is going to be faster, especially if the set of +// characters is more than one long, and known at compile time. +class ASCIIMask { + public: + // Preset masks for some common character groups + // When testing, you must check if the index is < 128 or use IsMasked() + // + // if (someChar < 128 && MaskCRLF()[someChar]) this is \r or \n + + static const ASCIIMaskArray& MaskCRLF(); + static const ASCIIMaskArray& Mask0to9(); + static const ASCIIMaskArray& MaskCRLFTab(); + static const ASCIIMaskArray& MaskWhitespace(); + + static MOZ_ALWAYS_INLINE bool IsMasked(const ASCIIMaskArray& aMask, + uint32_t aChar) { + return aChar < 128 && aMask[aChar]; + } +}; + +// Outside of the preset ones, use these templates to create more masks. +// +// The example creation will look like this: +// +// constexpr bool TestABC(char c) { return c == 'A' || c == 'B' || c == 'C'; } +// constexpr std::array<bool, 128> sABCMask = CreateASCIIMask(TestABC); +// ... +// if (someChar < 128 && sABCMask[someChar]) this is A or B or C + +namespace asciimask_details { +template <typename F, size_t... Indices> +constexpr std::array<bool, 128> CreateASCIIMask( + F fun, std::index_sequence<Indices...>) { + return {{fun(Indices)...}}; +} +} // namespace asciimask_details + +template <typename F> +constexpr std::array<bool, 128> CreateASCIIMask(F fun) { + return asciimask_details::CreateASCIIMask(fun, + std::make_index_sequence<128>{}); +} + +} // namespace mozilla + +#endif // nsASCIIMask_h_ diff --git a/xpcom/string/nsAString.h b/xpcom/string/nsAString.h new file mode 100644 index 0000000000..3893ff8e37 --- /dev/null +++ b/xpcom/string/nsAString.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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsAString_h___ +#define nsAString_h___ + +#include "nsStringFwd.h" +#include "nsStringIterator.h" +#include "mozilla/TypedEnumBits.h" + +#include <string.h> +#include <stdarg.h> + +#include "nsStringFlags.h" +#include "nsTStringRepr.h" +#include "nsTSubstring.h" +#include "nsTSubstringTuple.h" + +/** + * ASCII case-insensitive comparator. (for Unicode case-insensitive + * comparision, see nsUnicharUtils.h) + */ +int nsCaseInsensitiveCStringComparator(const char*, const char*, size_t, + size_t); + +class nsCaseInsensitiveCStringArrayComparator { + public: + template <class A, class B> + bool Equals(const A& aStrA, const B& aStrB) const { + return aStrA.Equals(aStrB, nsCaseInsensitiveCStringComparator); + } +}; + +#endif // !defined(nsAString_h___) diff --git a/xpcom/string/nsCharTraits.h b/xpcom/string/nsCharTraits.h new file mode 100644 index 0000000000..c81c2f5b2d --- /dev/null +++ b/xpcom/string/nsCharTraits.h @@ -0,0 +1,486 @@ +/* -*- 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 nsCharTraits_h___ +#define nsCharTraits_h___ + +#include <ctype.h> // for |EOF|, |WEOF| +#include <stdint.h> // for |uint32_t| +#include <string.h> // for |memcpy|, et al +#include "mozilla/MemoryChecking.h" + +// This file may be used (through nsUTF8Utils.h) from non-XPCOM code, in +// particular the standalone software updater. In that case stub out +// the macros provided by nsDebug.h which are only usable when linking XPCOM + +#ifdef NS_NO_XPCOM +# define NS_WARNING(msg) +# define NS_ASSERTION(cond, msg) +# define NS_ERROR(msg) +#else +# include "nsDebug.h" // for NS_ASSERTION +#endif + +/* + * Some macros for converting char16_t (UTF-16) to and from Unicode scalar + * values. + * + * Note that UTF-16 represents all Unicode scalar values up to U+10FFFF by + * using "surrogate pairs". These consist of a high surrogate, i.e. a code + * point in the range U+D800 - U+DBFF, and a low surrogate, i.e. a code point + * in the range U+DC00 - U+DFFF, like this: + * + * U+D800 U+DC00 = U+10000 + * U+D800 U+DC01 = U+10001 + * ... + * U+DBFF U+DFFE = U+10FFFE + * U+DBFF U+DFFF = U+10FFFF + * + * These surrogate code points U+D800 - U+DFFF are not themselves valid Unicode + * scalar values and are not well-formed UTF-16 except as high-surrogate / + * low-surrogate pairs. + */ + +#define PLANE1_BASE uint32_t(0x00010000) +// High surrogates are in the range 0xD800 -- OxDBFF +#define NS_IS_HIGH_SURROGATE(u) ((uint32_t(u) & 0xFFFFFC00) == 0xD800) +// Low surrogates are in the range 0xDC00 -- 0xDFFF +#define NS_IS_LOW_SURROGATE(u) ((uint32_t(u) & 0xFFFFFC00) == 0xDC00) +// Easier to type than NS_IS_HIGH_SURROGATE && NS_IS_LOW_SURROGATE +#define NS_IS_SURROGATE_PAIR(h, l) \ + (NS_IS_HIGH_SURROGATE(h) && NS_IS_LOW_SURROGATE(l)) +// Faster than testing NS_IS_HIGH_SURROGATE || NS_IS_LOW_SURROGATE +#define IS_SURROGATE(u) ((uint32_t(u) & 0xFFFFF800) == 0xD800) + +// Everything else is not a surrogate: 0x000 -- 0xD7FF, 0xE000 -- 0xFFFF + +// N = (H - 0xD800) * 0x400 + 0x10000 + (L - 0xDC00) +// I wonder whether we could somehow assert that H is a high surrogate +// and L is a low surrogate +#define SURROGATE_TO_UCS4(h, l) \ + (((uint32_t(h) & 0x03FF) << 10) + (uint32_t(l) & 0x03FF) + PLANE1_BASE) + +// Extract surrogates from a UCS4 char +// Reference: the Unicode standard 4.0, section 3.9 +// Since (c - 0x10000) >> 10 == (c >> 10) - 0x0080 and +// 0xD7C0 == 0xD800 - 0x0080, +// ((c - 0x10000) >> 10) + 0xD800 can be simplified to +#define H_SURROGATE(c) char16_t(char16_t(uint32_t(c) >> 10) + char16_t(0xD7C0)) +// where it's to be noted that 0xD7C0 is not bitwise-OR'd +// but added. + +// Since 0x10000 & 0x03FF == 0, +// (c - 0x10000) & 0x03FF == c & 0x03FF so that +// ((c - 0x10000) & 0x03FF) | 0xDC00 is equivalent to +#define L_SURROGATE(c) \ + char16_t(char16_t(uint32_t(c) & uint32_t(0x03FF)) | char16_t(0xDC00)) + +#define IS_IN_BMP(ucs) (uint32_t(ucs) < PLANE1_BASE) +#define UCS2_REPLACEMENT_CHAR char16_t(0xFFFD) + +#define UCS_END uint32_t(0x00110000) +#define IS_VALID_CHAR(c) ((uint32_t(c) < UCS_END) && !IS_SURROGATE(c)) +#define ENSURE_VALID_CHAR(c) (IS_VALID_CHAR(c) ? (c) : UCS2_REPLACEMENT_CHAR) + +template <class CharT> +struct nsCharTraits {}; + +template <> +struct nsCharTraits<char16_t> { + typedef char16_t char_type; + typedef uint16_t unsigned_char_type; + typedef char incompatible_char_type; + + static char_type* const sEmptyBuffer; + + // integer representation of characters: + typedef int int_type; + + static char_type to_char_type(int_type aChar) { return char_type(aChar); } + + static int_type to_int_type(char_type aChar) { + return int_type(static_cast<unsigned_char_type>(aChar)); + } + + static bool eq_int_type(int_type aLhs, int_type aRhs) { return aLhs == aRhs; } + + // |char_type| comparisons: + + static bool eq(char_type aLhs, char_type aRhs) { return aLhs == aRhs; } + + static bool lt(char_type aLhs, char_type aRhs) { return aLhs < aRhs; } + + // operations on s[n] arrays: + + static char_type* move(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast<char_type*>( + memmove(aStr1, aStr2, aN * sizeof(char_type))); + } + + static char_type* copy(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast<char_type*>( + memcpy(aStr1, aStr2, aN * sizeof(char_type))); + } + + static void uninitialize(char_type* aStr, size_t aN) { +#ifdef DEBUG + memset(aStr, 0xE4, aN * sizeof(char_type)); +#endif + MOZ_MAKE_MEM_UNDEFINED(aStr, aN * sizeof(char_type)); + } + + static char_type* copyASCII(char_type* aStr1, const char* aStr2, size_t aN) { + for (char_type* s = aStr1; aN--; ++s, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + *s = static_cast<char_type>(*aStr2); + } + return aStr1; + } + + static int compare(const char_type* aStr1, const char_type* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + if (!eq(*aStr1, *aStr2)) { + return to_int_type(*aStr1) - to_int_type(*aStr2); + } + } + + return 0; + } + + static int compareASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + if (!eq_int_type(to_int_type(*aStr1), + to_int_type(static_cast<char_type>(*aStr2)))) { + return to_int_type(*aStr1) - + to_int_type(static_cast<char_type>(*aStr2)); + } + } + + return 0; + } + + static bool equalsLatin1(const char_type* aStr1, const char* aStr2, + const size_t aN) { + for (size_t i = aN; i > 0; --i, ++aStr1, ++aStr2) { + if (*aStr1 != static_cast<char_type>(*aStr2)) { + return false; + } + } + + return true; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareASCIINullTerminated(const char_type* aStr1, size_t aN, + const char* aStr2) { + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + if (!eq_int_type(to_int_type(*aStr1), + to_int_type(static_cast<char_type>(*aStr2)))) { + return to_int_type(*aStr1) - + to_int_type(static_cast<char_type>(*aStr2)); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + /** + * Convert c to its lower-case form, but only if c is in the ASCII + * range. Otherwise leave it alone. + */ + static char_type ASCIIToLower(char_type aChar) { + if (aChar >= 'A' && aChar <= 'Z') { + return char_type(aChar + ('a' - 'A')); + } + + return aChar; + } + + static int compareLowerCaseToASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != static_cast<char_type>(*aStr2)) { + return to_int_type(lower_s1) - + to_int_type(static_cast<char_type>(*aStr2)); + } + } + + return 0; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareLowerCaseToASCIINullTerminated(const char_type* aStr1, + size_t aN, + const char* aStr2) { + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != static_cast<char_type>(*aStr2)) { + return to_int_type(lower_s1) - + to_int_type(static_cast<char_type>(*aStr2)); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + static size_t length(const char_type* aStr) { + size_t result = 0; + while (!eq(*aStr++, char_type(0))) { + ++result; + } + return result; + } + + static const char_type* find(const char_type* aStr, size_t aN, + char_type aChar) { + while (aN--) { + if (eq(*aStr, aChar)) { + return aStr; + } + ++aStr; + } + + return 0; + } +}; + +template <> +struct nsCharTraits<char> { + typedef char char_type; + typedef unsigned char unsigned_char_type; + typedef char16_t incompatible_char_type; + + static char_type* const sEmptyBuffer; + + // integer representation of characters: + + typedef int int_type; + + static char_type to_char_type(int_type aChar) { return char_type(aChar); } + + static int_type to_int_type(char_type aChar) { + return int_type(static_cast<unsigned_char_type>(aChar)); + } + + static bool eq_int_type(int_type aLhs, int_type aRhs) { return aLhs == aRhs; } + + // |char_type| comparisons: + + static bool eq(char_type aLhs, char_type aRhs) { return aLhs == aRhs; } + + static bool lt(char_type aLhs, char_type aRhs) { return aLhs < aRhs; } + + // operations on s[n] arrays: + + static char_type* move(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast<char_type*>( + memmove(aStr1, aStr2, aN * sizeof(char_type))); + } + + static char_type* copy(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast<char_type*>( + memcpy(aStr1, aStr2, aN * sizeof(char_type))); + } + + static void uninitialize(char_type* aStr, size_t aN) { +#ifdef DEBUG + memset(aStr, 0xE4, aN * sizeof(char_type)); +#endif + MOZ_MAKE_MEM_UNDEFINED(aStr, aN * sizeof(char_type)); + } + + static char_type* copyASCII(char_type* aStr1, const char* aStr2, size_t aN) { + return copy(aStr1, aStr2, aN); + } + + static int compare(const char_type* aStr1, const char_type* aStr2, + size_t aN) { + return memcmp(aStr1, aStr2, aN); + } + + static int compareASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { +#ifdef DEBUG + for (size_t i = 0; i < aN; ++i) { + NS_ASSERTION(!(aStr2[i] & ~0x7F), "Unexpected non-ASCII character"); + } +#endif + return compare(aStr1, aStr2, aN); + } + + static bool equalsLatin1(const char_type* aStr1, const char* aStr2, + size_t aN) { + return memcmp(aStr1, aStr2, aN) == 0; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareASCIINullTerminated(const char_type* aStr1, size_t aN, + const char* aStr2) { + // can't use strcmp here because we don't want to stop when aStr1 + // contains a null + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + if (*aStr1 != *aStr2) { + return to_int_type(*aStr1) - to_int_type(*aStr2); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + /** + * Convert c to its lower-case form, but only if c is ASCII. + */ + static char_type ASCIIToLower(char_type aChar) { + if (aChar >= 'A' && aChar <= 'Z') { + return char_type(aChar + ('a' - 'A')); + } + + return aChar; + } + + static int compareLowerCaseToASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != *aStr2) { + return to_int_type(lower_s1) - to_int_type(*aStr2); + } + } + return 0; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareLowerCaseToASCIINullTerminated(const char_type* aStr1, + size_t aN, + const char* aStr2) { + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != *aStr2) { + return to_int_type(lower_s1) - to_int_type(*aStr2); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + static size_t length(const char_type* aStr) { return strlen(aStr); } + + static const char_type* find(const char_type* aStr, size_t aN, + char_type aChar) { + return reinterpret_cast<const char_type*>( + memchr(aStr, to_int_type(aChar), aN)); + } +}; + +template <class InputIterator> +struct nsCharSourceTraits { + typedef typename InputIterator::difference_type difference_type; + + static difference_type readable_distance(const InputIterator& aFirst, + const InputIterator& aLast) { + // assumes single fragment + return aLast.get() - aFirst.get(); + } + + static const typename InputIterator::value_type* read( + const InputIterator& aIter) { + return aIter.get(); + } + + static void advance(InputIterator& aStr, difference_type aN) { + aStr.advance(aN); + } +}; + +template <class CharT> +struct nsCharSourceTraits<CharT*> { + typedef ptrdiff_t difference_type; + + static difference_type readable_distance(CharT* aStr) { + return nsCharTraits<CharT>::length(aStr); + } + + static difference_type readable_distance(CharT* aFirst, CharT* aLast) { + return aLast - aFirst; + } + + static const CharT* read(CharT* aStr) { return aStr; } + + static void advance(CharT*& aStr, difference_type aN) { aStr += aN; } +}; + +template <class OutputIterator> +struct nsCharSinkTraits { + static void write(OutputIterator& aIter, + const typename OutputIterator::value_type* aStr, + size_t aN) { + aIter.write(aStr, aN); + } +}; + +template <class CharT> +struct nsCharSinkTraits<CharT*> { + static void write(CharT*& aIter, const CharT* aStr, size_t aN) { + nsCharTraits<CharT>::move(aIter, aStr, aN); + aIter += aN; + } +}; + +#endif // !defined(nsCharTraits_h___) diff --git a/xpcom/string/nsDependentString.h b/xpcom/string/nsDependentString.h new file mode 100644 index 0000000000..4896c8d086 --- /dev/null +++ b/xpcom/string/nsDependentString.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDependentString_h___ +#define nsDependentString_h___ + +#include "nsString.h" +#include "nsDebug.h" + +#include "nsTDependentString.h" + +#endif /* !defined(nsDependentString_h___) */ diff --git a/xpcom/string/nsDependentSubstring.h b/xpcom/string/nsDependentSubstring.h new file mode 100644 index 0000000000..cb6cef5d77 --- /dev/null +++ b/xpcom/string/nsDependentSubstring.h @@ -0,0 +1,13 @@ +/* -*- 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 nsDependentSubstring_h___ +#define nsDependentSubstring_h___ + +#include "nsAString.h" +#include "nsTDependentSubstring.h" + +#endif /* !defined(nsDependentSubstring_h___) */ diff --git a/xpcom/string/nsLiteralString.h b/xpcom/string/nsLiteralString.h new file mode 100644 index 0000000000..f982724ce4 --- /dev/null +++ b/xpcom/string/nsLiteralString.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 nsLiteralString_h___ +#define nsLiteralString_h___ + +#include "nscore.h" +#include "nsString.h" + +#include "nsTLiteralString.h" + +#include "mozilla/Char16.h" + +#define NS_CSTRING_LITERAL_AS_STRING_LITERAL(s) u"" s + +#define NS_LITERAL_STRING_FROM_CSTRING(s) \ + static_cast<const nsLiteralString&>( \ + nsLiteralString(NS_CSTRING_LITERAL_AS_STRING_LITERAL(s))) + +constexpr auto operator""_ns(const char* aStr, std::size_t aLen) { + return nsLiteralCString{aStr, aLen}; +} + +constexpr auto operator""_ns(const char16_t* aStr, std::size_t aLen) { + return nsLiteralString{aStr, aLen}; +} + +#endif /* !defined(nsLiteralString_h___) */ diff --git a/xpcom/string/nsPrintfCString.h b/xpcom/string/nsPrintfCString.h new file mode 100644 index 0000000000..f722888705 --- /dev/null +++ b/xpcom/string/nsPrintfCString.h @@ -0,0 +1,64 @@ +/* -*- 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 nsPrintfCString_h___ +#define nsPrintfCString_h___ + +#include "nsString.h" + +/** + * nsPrintfCString lets you create a nsCString using a printf-style format + * string. For example: + * + * NS_WARNING(nsPrintfCString("Unexpected value: %f", 13.917).get()); + * + * nsPrintfCString has a small built-in auto-buffer. For larger strings, it + * will allocate on the heap. + * + * See also nsCString::AppendPrintf(). + */ +class nsPrintfCString : public nsAutoCStringN<16> { + typedef nsCString string_type; + + public: + explicit nsPrintfCString(const char_type* aFormat, ...) + MOZ_FORMAT_PRINTF(2, 3) { + va_list ap; + va_start(ap, aFormat); + AppendVprintf(aFormat, ap); + va_end(ap); + } +}; + +/** + * + * + * nsVPrintfCString is like nsPrinfCString but is created using vprintf style + * args. This is useful for functions that have already received variadic + * arguments and want to create a nsPrintfCString. For example: + * + * void LogToSeveralLocations(const char* aFormat,...) { + * va_list ap; + * va_start(ap, aFormat); + * nsPrintfCString logString(aFormat, ap); + * va_end(ap); + * // Use logString + * } + * + * See also nsCString::AppendVprintf(). + */ + +class nsVprintfCString : public nsAutoCStringN<16> { + typedef nsCString string_type; + + public: + nsVprintfCString(const char_type* aFormat, va_list aArgs) + MOZ_FORMAT_PRINTF(2, 0) { + AppendVprintf(aFormat, aArgs); + } +}; + +#endif // !defined(nsPrintfCString_h___) diff --git a/xpcom/string/nsPromiseFlatString.h b/xpcom/string/nsPromiseFlatString.h new file mode 100644 index 0000000000..98541ceb4a --- /dev/null +++ b/xpcom/string/nsPromiseFlatString.h @@ -0,0 +1,14 @@ +/* -*- 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 nsPromiseFlatString_h___ +#define nsPromiseFlatString_h___ + +#include "nsString.h" + +#include "nsTPromiseFlatString.h" + +#endif /* !defined(nsPromiseFlatString_h___) */ diff --git a/xpcom/string/nsReadableUtils.cpp b/xpcom/string/nsReadableUtils.cpp new file mode 100644 index 0000000000..fa4c4bc69b --- /dev/null +++ b/xpcom/string/nsReadableUtils.cpp @@ -0,0 +1,630 @@ +/* -*- 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 "nsReadableUtils.h" + +#include <algorithm> + +#include "mozilla/CheckedInt.h" +#include "mozilla/Utf8.h" + +#include "nscore.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsUTF8Utils.h" + +using mozilla::Span; + +/** + * A helper function that allocates a buffer of the desired character type big + * enough to hold a copy of the supplied string (plus a zero terminator). + * + * @param aSource an string you will eventually be making a copy of + * @return a new buffer which you must free with |free|. + * + */ +template <class FromStringT, class CharT> +inline CharT* AllocateStringCopy(const FromStringT& aSource, CharT*) { + return static_cast<CharT*>( + malloc((size_t(aSource.Length()) + 1) * sizeof(CharT))); +} + +char* ToNewCString(const nsAString& aSource) { + char* str = ToNewCString(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char* ToNewCString(const nsAString& aSource, + const mozilla::fallible_t& aFallible) { + char* dest = AllocateStringCopy(aSource, (char*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + LossyConvertUtf16toLatin1(aSource, Span(dest, len)); + dest[len] = 0; + return dest; +} + +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count, + const mozilla::fallible_t& aFallible) { + auto len = aSource.Length(); + // The uses of this function seem temporary enough that it's not + // worthwhile to be fancy about the allocation size. Let's just use + // the worst case. + // Times 3 plus 1, because ConvertUTF16toUTF8 requires times 3 and + // then we have the terminator. + // Using CheckedInt<uint32_t>, because aUTF8Count is uint32_t* for + // historical reasons. + mozilla::CheckedInt<uint32_t> destLen(len); + destLen *= 3; + destLen += 1; + if (!destLen.isValid()) { + return nullptr; + } + size_t destLenVal = destLen.value(); + char* dest = static_cast<char*>(malloc(destLenVal)); + if (!dest) { + return nullptr; + } + + size_t written = ConvertUtf16toUtf8(aSource, Span(dest, destLenVal)); + dest[written] = 0; + + if (aUTF8Count) { + *aUTF8Count = written; + } + + return dest; +} + +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count) { + char* str = ToNewUTF8String(aSource, aUTF8Count, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char* ToNewCString(const nsACString& aSource) { + char* str = ToNewCString(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char* ToNewCString(const nsACString& aSource, + const mozilla::fallible_t& aFallible) { + // no conversion needed, just allocate a buffer of the correct length and copy + // into it + + char* dest = AllocateStringCopy(aSource, (char*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + memcpy(dest, aSource.BeginReading(), len * sizeof(char)); + dest[len] = 0; + return dest; +} + +char16_t* ToNewUnicode(const nsAString& aSource) { + char16_t* str = ToNewUnicode(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char16_t* ToNewUnicode(const nsAString& aSource, + const mozilla::fallible_t& aFallible) { + // no conversion needed, just allocate a buffer of the correct length and copy + // into it + + char16_t* dest = AllocateStringCopy(aSource, (char16_t*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + memcpy(dest, aSource.BeginReading(), len * sizeof(char16_t)); + dest[len] = 0; + return dest; +} + +char16_t* ToNewUnicode(const nsACString& aSource) { + char16_t* str = ToNewUnicode(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char16_t* ToNewUnicode(const nsACString& aSource, + const mozilla::fallible_t& aFallible) { + char16_t* dest = AllocateStringCopy(aSource, (char16_t*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + ConvertLatin1toUtf16(aSource, Span(dest, len)); + dest[len] = 0; + return dest; +} + +char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count, + const mozilla::fallible_t& aFallible) { + // Compute length plus one as required by ConvertUTF8toUTF16 + uint32_t lengthPlusOne = aSource.Length() + 1; // Can't overflow + + mozilla::CheckedInt<size_t> allocLength(lengthPlusOne); + // Add space for zero-termination + allocLength += 1; + // We need UTF-16 units + allocLength *= sizeof(char16_t); + + if (!allocLength.isValid()) { + return nullptr; + } + + char16_t* dest = (char16_t*)malloc(allocLength.value()); + if (!dest) { + return nullptr; + } + + size_t written = ConvertUtf8toUtf16(aSource, Span(dest, lengthPlusOne)); + dest[written] = 0; + + if (aUTF16Count) { + *aUTF16Count = written; + } + + return dest; +} + +char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count) { + char16_t* str = UTF8ToNewUnicode(aSource, aUTF16Count, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char16_t* CopyUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset, + char16_t* aDest, uint32_t aLength) { + MOZ_ASSERT(aSrcOffset + aLength <= aSource.Length()); + memcpy(aDest, aSource.BeginReading() + aSrcOffset, + size_t(aLength) * sizeof(char16_t)); + return aDest; +} + +void ToUpperCase(nsACString& aCString) { + char* cp = aCString.BeginWriting(); + char* end = cp + aCString.Length(); + while (cp != end) { + char ch = *cp; + if (ch >= 'a' && ch <= 'z') { + *cp = ch - ('a' - 'A'); + } + ++cp; + } +} + +void ToUpperCase(const nsACString& aSource, nsACString& aDest) { + aDest.SetLength(aSource.Length()); + const char* src = aSource.BeginReading(); + const char* end = src + aSource.Length(); + char* dst = aDest.BeginWriting(); + while (src != end) { + char ch = *src; + if (ch >= 'a' && ch <= 'z') { + *dst = ch - ('a' - 'A'); + } else { + *dst = ch; + } + ++src; + ++dst; + } +} + +void ToLowerCase(nsACString& aCString) { + char* cp = aCString.BeginWriting(); + char* end = cp + aCString.Length(); + while (cp != end) { + char ch = *cp; + if (ch >= 'A' && ch <= 'Z') { + *cp = ch + ('a' - 'A'); + } + ++cp; + } +} + +void ToLowerCase(const nsACString& aSource, nsACString& aDest) { + aDest.SetLength(aSource.Length()); + const char* src = aSource.BeginReading(); + const char* end = src + aSource.Length(); + char* dst = aDest.BeginWriting(); + while (src != end) { + char ch = *src; + if (ch >= 'A' && ch <= 'Z') { + *dst = ch + ('a' - 'A'); + } else { + *dst = ch; + } + ++src; + ++dst; + } +} + +void ParseString(const nsACString& aSource, char aDelimiter, + nsTArray<nsCString>& aArray) { + nsACString::const_iterator start, end; + aSource.BeginReading(start); + aSource.EndReading(end); + + for (;;) { + nsACString::const_iterator delimiter = start; + FindCharInReadable(aDelimiter, delimiter, end); + + if (delimiter != start) { + aArray.AppendElement(Substring(start, delimiter)); + } + + if (delimiter == end) { + break; + } + start = ++delimiter; + if (start == end) { + break; + } + } +} + +template <class StringT, class IteratorT> +bool FindInReadable_Impl( + const StringT& aPattern, IteratorT& aSearchStart, IteratorT& aSearchEnd, + nsTStringComparator<typename StringT::char_type> aCompare) { + bool found_it = false; + + // only bother searching at all if we're given a non-empty range to search + if (aSearchStart != aSearchEnd) { + IteratorT aPatternStart, aPatternEnd; + aPattern.BeginReading(aPatternStart); + aPattern.EndReading(aPatternEnd); + + // outer loop keeps searching till we find it or run out of string to search + while (!found_it) { + // fast inner loop (that's what it's called, not what it is) looks for a + // potential match + while (aSearchStart != aSearchEnd && + aCompare(aPatternStart.get(), aSearchStart.get(), 1, 1)) { + ++aSearchStart; + } + + // if we broke out of the `fast' loop because we're out of string ... + // we're done: no match + if (aSearchStart == aSearchEnd) { + break; + } + + // otherwise, we're at a potential match, let's see if we really hit one + IteratorT testPattern(aPatternStart); + IteratorT testSearch(aSearchStart); + + // slow inner loop verifies the potential match (found by the `fast' loop) + // at the current position + for (;;) { + // we already compared the first character in the outer loop, + // so we'll advance before the next comparison + ++testPattern; + ++testSearch; + + // if we verified all the way to the end of the pattern, then we found + // it! + if (testPattern == aPatternEnd) { + found_it = true; + aSearchEnd = testSearch; // return the exact found range through the + // parameters + break; + } + + // if we got to end of the string we're searching before we hit the end + // of the + // pattern, we'll never find what we're looking for + if (testSearch == aSearchEnd) { + aSearchStart = aSearchEnd; + break; + } + + // else if we mismatched ... it's time to advance to the next search + // position + // and get back into the `fast' loop + if (aCompare(testPattern.get(), testSearch.get(), 1, 1)) { + ++aSearchStart; + break; + } + } + } + } + + return found_it; +} + +/** + * This searches the entire string from right to left, and returns the first + * match found, if any. + */ +template <class StringT, class IteratorT> +bool RFindInReadable_Impl( + const StringT& aPattern, IteratorT& aSearchStart, IteratorT& aSearchEnd, + nsTStringComparator<typename StringT::char_type> aCompare) { + IteratorT patternStart, patternEnd, searchEnd = aSearchEnd; + aPattern.BeginReading(patternStart); + aPattern.EndReading(patternEnd); + + // Point to the last character in the pattern + --patternEnd; + // outer loop keeps searching till we run out of string to search + while (aSearchStart != searchEnd) { + // Point to the end position of the next possible match + --searchEnd; + + // Check last character, if a match, explore further from here + if (aCompare(patternEnd.get(), searchEnd.get(), 1, 1) == 0) { + // We're at a potential match, let's see if we really hit one + IteratorT testPattern(patternEnd); + IteratorT testSearch(searchEnd); + + // inner loop verifies the potential match at the current position + do { + // if we verified all the way to the end of the pattern, then we found + // it! + if (testPattern == patternStart) { + aSearchStart = testSearch; // point to start of match + aSearchEnd = ++searchEnd; // point to end of match + return true; + } + + // if we got to end of the string we're searching before we hit the end + // of the + // pattern, we'll never find what we're looking for + if (testSearch == aSearchStart) { + aSearchStart = aSearchEnd; + return false; + } + + // test previous character for a match + --testPattern; + --testSearch; + } while (aCompare(testPattern.get(), testSearch.get(), 1, 1) == 0); + } + } + + aSearchStart = aSearchEnd; + return false; +} + +bool FindInReadable(const nsAString& aPattern, + nsAString::const_iterator& aSearchStart, + nsAString::const_iterator& aSearchEnd, + nsStringComparator aComparator) { + return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool FindInReadable(const nsACString& aPattern, + nsACString::const_iterator& aSearchStart, + nsACString::const_iterator& aSearchEnd, + nsCStringComparator aComparator) { + return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool CaseInsensitiveFindInReadable(const nsACString& aPattern, + nsACString::const_iterator& aSearchStart, + nsACString::const_iterator& aSearchEnd) { + return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, + nsCaseInsensitiveCStringComparator); +} + +bool RFindInReadable(const nsAString& aPattern, + nsAString::const_iterator& aSearchStart, + nsAString::const_iterator& aSearchEnd, + const nsStringComparator aComparator) { + return RFindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool RFindInReadable(const nsACString& aPattern, + nsACString::const_iterator& aSearchStart, + nsACString::const_iterator& aSearchEnd, + const nsCStringComparator aComparator) { + return RFindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool FindCharInReadable(char16_t aChar, nsAString::const_iterator& aSearchStart, + const nsAString::const_iterator& aSearchEnd) { + ptrdiff_t fragmentLength = aSearchEnd.get() - aSearchStart.get(); + + const char16_t* charFoundAt = + nsCharTraits<char16_t>::find(aSearchStart.get(), fragmentLength, aChar); + if (charFoundAt) { + aSearchStart.advance(charFoundAt - aSearchStart.get()); + return true; + } + + aSearchStart.advance(fragmentLength); + return false; +} + +bool FindCharInReadable(char aChar, nsACString::const_iterator& aSearchStart, + const nsACString::const_iterator& aSearchEnd) { + ptrdiff_t fragmentLength = aSearchEnd.get() - aSearchStart.get(); + + const char* charFoundAt = + nsCharTraits<char>::find(aSearchStart.get(), fragmentLength, aChar); + if (charFoundAt) { + aSearchStart.advance(charFoundAt - aSearchStart.get()); + return true; + } + + aSearchStart.advance(fragmentLength); + return false; +} + +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring); +} + +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator aComparator) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring, aComparator); +} + +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring); +} + +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator aComparator) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring, aComparator); +} + +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len).Equals(aSubstring); +} + +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator aComparator) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len) + .Equals(aSubstring, aComparator); +} + +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len).Equals(aSubstring); +} + +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator aComparator) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len) + .Equals(aSubstring, aComparator); +} + +static const char16_t empty_buffer[1] = {'\0'}; + +const nsString& EmptyString() { + static const nsDependentString sEmpty(empty_buffer); + + return sEmpty; +} + +const nsCString& EmptyCString() { + static const nsDependentCString sEmpty((const char*)empty_buffer); + + return sEmpty; +} + +const nsString& VoidString() { + static const nsString sNull(mozilla::detail::StringDataFlags::VOIDED); + + return sNull; +} + +const nsCString& VoidCString() { + static const nsCString sNull(mozilla::detail::StringDataFlags::VOIDED); + + return sNull; +} + +int32_t CompareUTF8toUTF16(const nsACString& aUTF8String, + const nsAString& aUTF16String, bool* aErr) { + const char* u8; + const char* u8end; + aUTF8String.BeginReading(u8); + aUTF8String.EndReading(u8end); + + const char16_t* u16; + const char16_t* u16end; + aUTF16String.BeginReading(u16); + aUTF16String.EndReading(u16end); + + for (;;) { + if (u8 == u8end) { + if (u16 == u16end) { + return 0; + } + return -1; + } + if (u16 == u16end) { + return 1; + } + // No need for ASCII optimization, since both NextChar() + // calls get inlined. + uint32_t scalar8 = UTF8CharEnumerator::NextChar(&u8, u8end, aErr); + uint32_t scalar16 = UTF16CharEnumerator::NextChar(&u16, u16end, aErr); + if (scalar16 == scalar8) { + continue; + } + if (scalar8 < scalar16) { + return -1; + } + return 1; + } +} + +void AppendUCS4ToUTF16(const uint32_t aSource, nsAString& aDest) { + NS_ASSERTION(IS_VALID_CHAR(aSource), "Invalid UCS4 char"); + if (IS_IN_BMP(aSource)) { + aDest.Append(char16_t(aSource)); + } else { + aDest.Append(H_SURROGATE(aSource)); + aDest.Append(L_SURROGATE(aSource)); + } +} diff --git a/xpcom/string/nsReadableUtils.h b/xpcom/string/nsReadableUtils.h new file mode 100644 index 0000000000..803c6b5d2f --- /dev/null +++ b/xpcom/string/nsReadableUtils.h @@ -0,0 +1,610 @@ +/* -*- 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 "nsString.h" + +#ifndef nsReadableUtils_h___ +#define nsReadableUtils_h___ + +/** + * I guess all the routines in this file are all mis-named. + * According to our conventions, they should be |NS_xxx|. + */ + +#include "mozilla/Assertions.h" +#include "nsAString.h" +#include "mozilla/TextUtils.h" + +#include "nsTArrayForwardDeclare.h" + +// From the nsstring crate +extern "C" { +bool nsstring_fallible_append_utf8_impl(nsAString* aThis, const char* aOther, + size_t aOtherLen, size_t aOldLen); + +bool nsstring_fallible_append_latin1_impl(nsAString* aThis, const char* aOther, + size_t aOtherLen, size_t aOldLen, + bool aAllowShrinking); + +bool nscstring_fallible_append_utf16_to_utf8_impl(nsACString* aThis, + const char16_t*, + size_t aOtherLen, + size_t aOldLen); + +bool nscstring_fallible_append_utf16_to_latin1_lossy_impl(nsACString* aThis, + const char16_t*, + size_t aOtherLen, + size_t aOldLen, + bool aAllowShrinking); + +bool nscstring_fallible_append_utf8_to_latin1_lossy_check( + nsACString* aThis, const nsACString* aOther, size_t aOldLen); + +bool nscstring_fallible_append_latin1_to_utf8_check(nsACString* aThis, + const nsACString* aOther, + size_t aOldLen); +} + +inline size_t Distance(const nsReadingIterator<char16_t>& aStart, + const nsReadingIterator<char16_t>& aEnd) { + MOZ_ASSERT(aStart.get() <= aEnd.get()); + return static_cast<size_t>(aEnd.get() - aStart.get()); +} + +inline size_t Distance(const nsReadingIterator<char>& aStart, + const nsReadingIterator<char>& aEnd) { + MOZ_ASSERT(aStart.get() <= aEnd.get()); + return static_cast<size_t>(aEnd.get() - aStart.get()); +} + +// NOTE: Operations that don't need an operand to be an XPCOM string +// are in mozilla/TextUtils.h and mozilla/Utf8.h. + +// UTF-8 to UTF-16 +// Invalid UTF-8 byte sequences are replaced with the REPLACEMENT CHARACTER. + +[[nodiscard]] inline bool CopyUTF8toUTF16(mozilla::Span<const char> aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_utf8_impl(&aDest, aSource.Elements(), + aSource.Length(), 0); +} + +inline void CopyUTF8toUTF16(mozilla::Span<const char> aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!CopyUTF8toUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendUTF8toUTF16(mozilla::Span<const char> aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_utf8_impl(&aDest, aSource.Elements(), + aSource.Length(), aDest.Length()); +} + +inline void AppendUTF8toUTF16(mozilla::Span<const char> aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!AppendUTF8toUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// Latin1 to UTF-16 +// Interpret each incoming unsigned byte value as a Unicode scalar value (not +// windows-1252!). The function names say "ASCII" instead of "Latin1" for +// legacy reasons. + +[[nodiscard]] inline bool CopyASCIItoUTF16(mozilla::Span<const char> aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_latin1_impl(&aDest, aSource.Elements(), + aSource.Length(), 0, true); +} + +inline void CopyASCIItoUTF16(mozilla::Span<const char> aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!CopyASCIItoUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendASCIItoUTF16(mozilla::Span<const char> aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_latin1_impl( + &aDest, aSource.Elements(), aSource.Length(), aDest.Length(), false); +} + +inline void AppendASCIItoUTF16(mozilla::Span<const char> aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!AppendASCIItoUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// UTF-16 to UTF-8 +// Unpaired surrogates are replaced with the REPLACEMENT CHARACTER. + +[[nodiscard]] inline bool CopyUTF16toUTF8(mozilla::Span<const char16_t> aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_utf8_impl( + &aDest, aSource.Elements(), aSource.Length(), 0); +} + +inline void CopyUTF16toUTF8(mozilla::Span<const char16_t> aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!CopyUTF16toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendUTF16toUTF8( + mozilla::Span<const char16_t> aSource, nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_utf8_impl( + &aDest, aSource.Elements(), aSource.Length(), aDest.Length()); +} + +inline void AppendUTF16toUTF8(mozilla::Span<const char16_t> aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!AppendUTF16toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// UTF-16 to Latin1 +// If all code points in the input are below U+0100, represents each scalar +// value as an unsigned byte. (This is not windows-1252!) If there are code +// points above U+00FF, memory-safely produces garbage and will likely start +// asserting in future debug builds. The nature of the garbage may differ +// based on CPU architecture and must not be relied upon. The names say +// "ASCII" instead of "Latin1" for legacy reasons. + +[[nodiscard]] inline bool LossyCopyUTF16toASCII( + mozilla::Span<const char16_t> aSource, nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_latin1_lossy_impl( + &aDest, aSource.Elements(), aSource.Length(), 0, true); +} + +inline void LossyCopyUTF16toASCII(mozilla::Span<const char16_t> aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!LossyCopyUTF16toASCII(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool LossyAppendUTF16toASCII( + mozilla::Span<const char16_t> aSource, nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_latin1_lossy_impl( + &aDest, aSource.Elements(), aSource.Length(), aDest.Length(), false); +} + +inline void LossyAppendUTF16toASCII(mozilla::Span<const char16_t> aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY( + !LossyAppendUTF16toASCII(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// Latin1 to UTF-8 +// Interpret each incoming unsigned byte value as a Unicode scalar value (not +// windows-1252!). +// If the input is ASCII, the heap-allocated nsStringBuffer is shared if +// possible. + +[[nodiscard]] inline bool CopyLatin1toUTF8(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_latin1_to_utf8_check(&aDest, &aSource, 0); +} + +inline void CopyLatin1toUTF8(const nsACString& aSource, nsACString& aDest) { + if (MOZ_UNLIKELY(!CopyLatin1toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendLatin1toUTF8(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_latin1_to_utf8_check(&aDest, &aSource, + aDest.Length()); +} + +inline void AppendLatin1toUTF8(const nsACString& aSource, nsACString& aDest) { + if (MOZ_UNLIKELY(!AppendLatin1toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// UTF-8 to Latin1 +// If all code points in the input are below U+0100, represents each scalar +// value as an unsigned byte. (This is not windows-1252!) If there are code +// points above U+00FF, memory-safely produces garbage in release builds and +// asserts in debug builds. The nature of the garbage may differ +// based on CPU architecture and must not be relied upon. +// If the input is ASCII, the heap-allocated nsStringBuffer is shared if +// possible. + +[[nodiscard]] inline bool LossyCopyUTF8toLatin1(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf8_to_latin1_lossy_check(&aDest, &aSource, + 0); +} + +inline void LossyCopyUTF8toLatin1(const nsACString& aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!LossyCopyUTF8toLatin1(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool LossyAppendUTF8toLatin1(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf8_to_latin1_lossy_check(&aDest, &aSource, + aDest.Length()); +} + +inline void LossyAppendUTF8toLatin1(const nsACString& aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY( + !LossyAppendUTF8toLatin1(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. + * Performs a conversion with LossyConvertUTF16toLatin1() writing into the + * newly-allocated buffer. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a 16-bit wide string + * @return a new |char| buffer you must free with |free|. + */ +char* ToNewCString(const nsAString& aSource); + +/* A fallible version of ToNewCString. Returns nullptr on failure. */ +char* ToNewCString(const nsAString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource an 8-bit wide string + * @return a new |char| buffer you must free with |free|. + */ +char* ToNewCString(const nsACString& aSource); + +/* A fallible version of ToNewCString. Returns nullptr on failure. */ +char* ToNewCString(const nsACString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. + * Performs an encoding conversion from a UTF-16 string to a UTF-8 string with + * unpaired surrogates replaced with the REPLACEMENT CHARACTER copying + * |aSource| to your new buffer. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a UTF-16 string (made of char16_t's) + * @param aUTF8Count the number of 8-bit units that was returned + * @return a new |char| buffer you must free with |free|. + */ +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count = nullptr); + +/* A fallible version of ToNewUTF8String. Returns nullptr on failure. */ +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy + * of |aSource|. + * + * Infallibly allocates and returns a new |char16_t| buffer which you must + * free with |free|. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a UTF-16 string + * @return a new |char16_t| buffer you must free with |free|. + */ +char16_t* ToNewUnicode(const nsAString& aSource); + +/* A fallible version of ToNewUnicode. Returns nullptr on failure. */ +char16_t* ToNewUnicode(const nsAString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy + * of |aSource|. + * + * Infallibly allocates and returns a new |char16_t| buffer which you must + * free with|free|. + * + * Performs an encoding conversion by 0-padding 8-bit wide characters up to + * 16-bits wide (i.e. Latin1 to UTF-16 conversion) while copying |aSource| + * to your new buffer. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a Latin1 string + * @return a new |char16_t| buffer you must free with |free|. + */ +char16_t* ToNewUnicode(const nsACString& aSource); + +/* A fallible version of ToNewUnicode. Returns nullptr on failure. */ +char16_t* ToNewUnicode(const nsACString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy + * of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. Performs an encoding conversion from UTF-8 to UTF-16 + * while copying |aSource| to your new buffer. Malformed byte sequences + * are replaced with the REPLACEMENT CHARACTER. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource an 8-bit wide string, UTF-8 encoded + * @param aUTF16Count the number of 16-bit units that was returned + * @return a new |char16_t| buffer you must free with |free|. + * (UTF-16 encoded) + */ +char16_t* UTF8ToNewUnicode(const nsACString& aSource, + uint32_t* aUTF16Count = nullptr); + +/* A fallible version of UTF8ToNewUnicode. Returns nullptr on failure. */ +char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count, + const mozilla::fallible_t& aFallible); + +/** + * Copies |aLength| 16-bit code units from the start of |aSource| to the + * |char16_t| buffer |aDest|. + * + * After this operation |aDest| is not null terminated. + * + * @param aSource a UTF-16 string + * @param aSrcOffset start offset in the source string + * @param aDest a |char16_t| buffer + * @param aLength the number of 16-bit code units to copy + * @return pointer to destination buffer - identical to |aDest| + */ +char16_t* CopyUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset, + char16_t* aDest, uint32_t aLength); + +/** + * Replaces unpaired surrogates with U+FFFD in the argument. + * + * Copies a shared string buffer or an otherwise read-only + * buffer only if there are unpaired surrogates. + */ +[[nodiscard]] inline bool EnsureUTF16Validity(nsAString& aString) { + size_t upTo = mozilla::Utf16ValidUpTo(aString); + size_t len = aString.Length(); + if (upTo == len) { + return true; + } + char16_t* ptr = aString.BeginWriting(mozilla::fallible); + if (!ptr) { + return false; + } + auto span = mozilla::Span(ptr, len); + span[upTo] = 0xFFFD; + mozilla::EnsureUtf16ValiditySpan(span.From(upTo + 1)); + return true; +} + +void ParseString(const nsACString& aSource, char aDelimiter, + nsTArray<nsCString>& aArray); + +namespace mozilla::detail { + +constexpr auto kStringJoinAppendDefault = + [](auto& aResult, const auto& aValue) { aResult.Append(aValue); }; + +} // namespace mozilla::detail + +/** + * Join a sequence of items, each optionally transformed to a string, with a + * given separator, appending to a given string. + * + * \tparam CharType char or char16_t + * \tparam InputRange a range usable with range-based for + * \tparam Func optionally, a functor accepting a nsTSubstring<CharType>& and + * an item of InputRange which appends the latter to the former + */ +template < + typename CharType, typename InputRange, + typename Func = const decltype(mozilla::detail::kStringJoinAppendDefault)&> +void StringJoinAppend( + nsTSubstring<CharType>& aOutput, + const nsTLiteralString<CharType>& aSeparator, const InputRange& aInputRange, + Func&& aFunc = mozilla::detail::kStringJoinAppendDefault) { + bool first = true; + for (const auto& item : aInputRange) { + if (first) { + first = false; + } else { + aOutput.Append(aSeparator); + } + + aFunc(aOutput, item); + } +} + +/** + * Join a sequence of items, each optionally transformed to a string, with a + * given separator, returning a new string. + * + * \tparam CharType char or char16_t + * \tparam InputRange a range usable with range-based for + * \tparam Func optionally, a functor accepting a nsTSubstring<CharType>& and + * an item of InputRange which appends the latter to the former + + */ +template < + typename CharType, typename InputRange, + typename Func = const decltype(mozilla::detail::kStringJoinAppendDefault)&> +auto StringJoin(const nsTLiteralString<CharType>& aSeparator, + const InputRange& aInputRange, + Func&& aFunc = mozilla::detail::kStringJoinAppendDefault) { + nsTAutoString<CharType> res; + StringJoinAppend(res, aSeparator, aInputRange, std::forward<Func>(aFunc)); + return res; +} + +/** + * Converts case in place in the argument string. + */ +void ToUpperCase(nsACString&); + +void ToLowerCase(nsACString&); + +void ToUpperCase(nsACString&); + +void ToLowerCase(nsACString&); + +/** + * Converts case from string aSource to aDest. + */ +void ToUpperCase(const nsACString& aSource, nsACString& aDest); + +void ToLowerCase(const nsACString& aSource, nsACString& aDest); + +/** + * Finds the leftmost occurrence of |aPattern|, if any in the range + * |aSearchStart|..|aSearchEnd|. + * + * Returns |true| if a match was found, and adjusts |aSearchStart| and + * |aSearchEnd| to point to the match. If no match was found, returns |false| + * and makes |aSearchStart == aSearchEnd|. + * + * Currently, this is equivalent to the O(m*n) implementation previously on + * |ns[C]String|. + * + * If we need something faster, then we can implement that later. + */ + +bool FindInReadable(const nsAString& aPattern, nsAString::const_iterator&, + nsAString::const_iterator&, + nsStringComparator = nsTDefaultStringComparator); +bool FindInReadable(const nsACString& aPattern, nsACString::const_iterator&, + nsACString::const_iterator&, + nsCStringComparator = nsTDefaultStringComparator); + +/* sometimes we don't care about where the string was, just that we + * found it or not */ +inline bool FindInReadable( + const nsAString& aPattern, const nsAString& aSource, + nsStringComparator aCompare = nsTDefaultStringComparator) { + nsAString::const_iterator start, end; + aSource.BeginReading(start); + aSource.EndReading(end); + return FindInReadable(aPattern, start, end, aCompare); +} + +inline bool FindInReadable( + const nsACString& aPattern, const nsACString& aSource, + nsCStringComparator aCompare = nsTDefaultStringComparator) { + nsACString::const_iterator start, end; + aSource.BeginReading(start); + aSource.EndReading(end); + return FindInReadable(aPattern, start, end, aCompare); +} + +bool CaseInsensitiveFindInReadable(const nsACString& aPattern, + nsACString::const_iterator&, + nsACString::const_iterator&); + +/** + * Finds the rightmost occurrence of |aPattern| + * Returns |true| if a match was found, and adjusts |aSearchStart| and + * |aSearchEnd| to point to the match. If no match was found, returns |false| + * and makes |aSearchStart == aSearchEnd|. + */ +bool RFindInReadable(const nsAString& aPattern, nsAString::const_iterator&, + nsAString::const_iterator&, + nsStringComparator = nsTDefaultStringComparator); +bool RFindInReadable(const nsACString& aPattern, nsACString::const_iterator&, + nsACString::const_iterator&, + nsCStringComparator = nsTDefaultStringComparator); + +/** + * Finds the leftmost occurrence of |aChar|, if any in the range + * |aSearchStart|..|aSearchEnd|. + * + * Returns |true| if a match was found, and adjusts |aSearchStart| to + * point to the match. If no match was found, returns |false| and + * makes |aSearchStart == aSearchEnd|. + */ +bool FindCharInReadable(char16_t aChar, nsAString::const_iterator& aSearchStart, + const nsAString::const_iterator& aSearchEnd); +bool FindCharInReadable(char aChar, nsACString::const_iterator& aSearchStart, + const nsACString::const_iterator& aSearchEnd); + +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring); +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator); +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring); +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator); +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring); +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator); +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring); +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator); + +const nsString& EmptyString(); +const nsCString& EmptyCString(); + +const nsString& VoidString(); +const nsCString& VoidCString(); + +/** + * Compare a UTF-8 string to an UTF-16 string. + * + * Returns 0 if the strings are equal, -1 if aUTF8String is less + * than aUTF16Count, and 1 in the reverse case. Errors are replaced + * with U+FFFD and then the U+FFFD is compared as if it had occurred + * in the input. If aErr is not nullptr, *aErr is set to true if + * either string had malformed sequences. + */ +int32_t CompareUTF8toUTF16(const nsACString& aUTF8String, + const nsAString& aUTF16String, bool* aErr = nullptr); + +void AppendUCS4ToUTF16(const uint32_t aSource, nsAString& aDest); + +#endif // !defined(nsReadableUtils_h___) diff --git a/xpcom/string/nsString.h b/xpcom/string/nsString.h new file mode 100644 index 0000000000..e86ea594ac --- /dev/null +++ b/xpcom/string/nsString.h @@ -0,0 +1,171 @@ +/* -*- 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 nsString_h___ +#define nsString_h___ + +#include <ostream> + +#include "mozilla/Attributes.h" + +#include "nsStringFwd.h" + +#include "nsAString.h" +#include "nsDependentSubstring.h" +#include "nsReadableUtils.h" + +#include "nsTString.h" + +static_assert(sizeof(char16_t) == 2, "size of char16_t must be 2"); +static_assert(sizeof(nsString::char_type) == 2, + "size of nsString::char_type must be 2"); +static_assert(nsString::char_type(-1) > nsString::char_type(0), + "nsString::char_type must be unsigned"); +static_assert(sizeof(nsCString::char_type) == 1, + "size of nsCString::char_type must be 1"); + +static_assert(sizeof(nsTLiteralString<char>) == sizeof(nsTString<char>), + "nsLiteralCString can masquerade as nsCString, " + "so they must have identical layout"); + +static_assert(sizeof(nsTLiteralString<char16_t>) == sizeof(nsTString<char16_t>), + "nsTLiteralString can masquerade as nsString, " + "so they must have identical layout"); + +/** + * A helper class that converts a UTF-16 string to ASCII in a lossy manner + */ +class NS_LossyConvertUTF16toASCII : public nsAutoCString { + public: + explicit NS_LossyConvertUTF16toASCII(const char16ptr_t aString) { + LossyAppendUTF16toASCII(mozilla::MakeStringSpan(aString), *this); + } + + NS_LossyConvertUTF16toASCII(const char16ptr_t aString, size_t aLength) { + LossyAppendUTF16toASCII( + Substring(static_cast<const char16_t*>(aString), aLength), *this); + } + + explicit NS_LossyConvertUTF16toASCII(const nsAString& aString) { + LossyAppendUTF16toASCII(aString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_LossyConvertUTF16toASCII(char) = delete; +}; + +class NS_ConvertASCIItoUTF16 : public nsAutoString { + public: + explicit NS_ConvertASCIItoUTF16(const char* aCString) { + AppendASCIItoUTF16(mozilla::MakeStringSpan(aCString), *this); + } + + NS_ConvertASCIItoUTF16(const char* aCString, size_t aLength) { + AppendASCIItoUTF16(Substring(aCString, aLength), *this); + } + + explicit NS_ConvertASCIItoUTF16(const nsACString& aCString) { + AppendASCIItoUTF16(aCString, *this); + } + + explicit NS_ConvertASCIItoUTF16(mozilla::Span<const char> aCString) { + AppendASCIItoUTF16(aCString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_ConvertASCIItoUTF16(char16_t) = delete; +}; + +/** + * A helper class that converts a UTF-16 string to UTF-8 + */ +class NS_ConvertUTF16toUTF8 : public nsAutoCString { + public: + explicit NS_ConvertUTF16toUTF8(const char16ptr_t aString) { + AppendUTF16toUTF8(mozilla::MakeStringSpan(aString), *this); + } + + NS_ConvertUTF16toUTF8(const char16ptr_t aString, size_t aLength) { + AppendUTF16toUTF8(Substring(static_cast<const char16_t*>(aString), aLength), + *this); + } + + explicit NS_ConvertUTF16toUTF8(const nsAString& aString) { + AppendUTF16toUTF8(aString, *this); + } + + explicit NS_ConvertUTF16toUTF8(mozilla::Span<const char16_t> aString) { + AppendUTF16toUTF8(aString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_ConvertUTF16toUTF8(char) = delete; +}; + +class NS_ConvertUTF8toUTF16 : public nsAutoString { + public: + explicit NS_ConvertUTF8toUTF16(const char* aCString) { + AppendUTF8toUTF16(mozilla::MakeStringSpan(aCString), *this); + } + + NS_ConvertUTF8toUTF16(const char* aCString, size_t aLength) { + AppendUTF8toUTF16(Substring(aCString, aLength), *this); + } + + explicit NS_ConvertUTF8toUTF16(const nsACString& aCString) { + AppendUTF8toUTF16(aCString, *this); + } + + explicit NS_ConvertUTF8toUTF16(mozilla::Span<const char> aCString) { + AppendUTF8toUTF16(aCString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_ConvertUTF8toUTF16(char16_t) = delete; +}; + +/** + * Converts an integer (signed/unsigned, 32/64bit) to its decimal string + * representation and returns it as an nsAutoCString/nsAutoString. + */ +template <typename T, typename U> +nsTAutoString<T> IntToTString(const U aInt, const int aRadix = 10) { + nsTAutoString<T> string; + string.AppendInt(aInt, aRadix); + return string; +} + +template <typename U> +nsAutoCString IntToCString(const U aInt, const int aRadix = 10) { + return IntToTString<char>(aInt, aRadix); +} + +template <typename U> +nsAutoString IntToString(const U aInt, const int aRadix = 10) { + return IntToTString<char16_t>(aInt, aRadix); +} + +// MOZ_DBG support + +inline std::ostream& operator<<(std::ostream& aOut, const nsACString& aString) { + aOut.write(aString.Data(), aString.Length()); + return aOut; +} + +inline std::ostream& operator<<(std::ostream& aOut, const nsAString& aString) { + return aOut << NS_ConvertUTF16toUTF8(aString); +} + +// the following are included/declared for backwards compatibility +#include "nsDependentString.h" +#include "nsLiteralString.h" +#include "nsPromiseFlatString.h" + +#endif // !defined(nsString_h___) diff --git a/xpcom/string/nsStringBuffer.cpp b/xpcom/string/nsStringBuffer.cpp new file mode 100644 index 0000000000..b5d506333f --- /dev/null +++ b/xpcom/string/nsStringBuffer.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 "nsStringBuffer.h" + +#include "mozilla/MemoryReporting.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +#ifdef DEBUG +# include "nsStringStats.h" +#else +# define STRING_STAT_INCREMENT(_s) +#endif + +void nsStringBuffer::AddRef() { + // 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. +#ifdef NS_BUILD_REFCNT_LOGGING + uint32_t count = +#endif + mRefCount.fetch_add(1, std::memory_order_relaxed) +#ifdef NS_BUILD_REFCNT_LOGGING + + 1 +#endif + ; + STRING_STAT_INCREMENT(Share); + NS_LOG_ADDREF(this, count, "nsStringBuffer", sizeof(*this)); +} + +void nsStringBuffer::Release() { + // 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. + uint32_t count = mRefCount.fetch_sub(1, std::memory_order_release) - 1; + NS_LOG_RELEASE(this, count, "nsStringBuffer"); + if (count == 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. + count = mRefCount.load(std::memory_order_acquire); + + STRING_STAT_INCREMENT(Free); + free(this); // we were allocated with |malloc| + } +} + +/** + * Alloc returns a pointer to a new string header with set capacity. + */ +already_AddRefed<nsStringBuffer> nsStringBuffer::Alloc(size_t aSize) { + NS_ASSERTION(aSize != 0, "zero capacity allocation not allowed"); + NS_ASSERTION(sizeof(nsStringBuffer) + aSize <= size_t(uint32_t(-1)) && + sizeof(nsStringBuffer) + aSize > aSize, + "mStorageSize will truncate"); + + auto* hdr = (nsStringBuffer*)malloc(sizeof(nsStringBuffer) + aSize); + if (hdr) { + STRING_STAT_INCREMENT(Alloc); + + hdr->mRefCount = 1; + hdr->mStorageSize = aSize; + NS_LOG_ADDREF(hdr, 1, "nsStringBuffer", sizeof(*hdr)); + } + return already_AddRefed(hdr); +} + +template <typename CharT> +static already_AddRefed<nsStringBuffer> DoCreate(const CharT* aData, + size_t aLength) { + RefPtr<nsStringBuffer> buffer = + nsStringBuffer::Alloc((aLength + 1) * sizeof(CharT)); + if (MOZ_UNLIKELY(!buffer)) { + return nullptr; + } + auto* data = reinterpret_cast<CharT*>(buffer->Data()); + memcpy(data, aData, aLength * sizeof(CharT)); + data[aLength] = 0; + return buffer.forget(); +} + +already_AddRefed<nsStringBuffer> nsStringBuffer::Create(const char* aData, + size_t aLength) { + return DoCreate(aData, aLength); +} + +already_AddRefed<nsStringBuffer> nsStringBuffer::Create(const char16_t* aData, + size_t aLength) { + return DoCreate(aData, aLength); +} + +nsStringBuffer* nsStringBuffer::Realloc(nsStringBuffer* aHdr, size_t aSize) { + STRING_STAT_INCREMENT(Realloc); + + NS_ASSERTION(aSize != 0, "zero capacity allocation not allowed"); + NS_ASSERTION(sizeof(nsStringBuffer) + aSize <= size_t(uint32_t(-1)) && + sizeof(nsStringBuffer) + aSize > aSize, + "mStorageSize will truncate"); + + // no point in trying to save ourselves if we hit this assertion + NS_ASSERTION(!aHdr->IsReadonly(), "|Realloc| attempted on readonly string"); + + // Treat this as a release and addref for refcounting purposes, since we + // just asserted that the refcount is 1. If we don't do that, refcount + // logging will claim we've leaked all sorts of stuff. + NS_LOG_RELEASE(aHdr, 0, "nsStringBuffer"); + + aHdr = (nsStringBuffer*)realloc(aHdr, sizeof(nsStringBuffer) + aSize); + if (aHdr) { + NS_LOG_ADDREF(aHdr, 1, "nsStringBuffer", sizeof(*aHdr)); + aHdr->mStorageSize = aSize; + } + + return aHdr; +} + +nsStringBuffer* nsStringBuffer::FromString(const nsAString& aStr) { + if (!(aStr.mDataFlags & nsAString::DataFlags::REFCOUNTED)) { + return nullptr; + } + + return FromData(aStr.mData); +} + +nsStringBuffer* nsStringBuffer::FromString(const nsACString& aStr) { + if (!(aStr.mDataFlags & nsACString::DataFlags::REFCOUNTED)) { + return nullptr; + } + + return FromData(aStr.mData); +} + +void nsStringBuffer::ToString(uint32_t aLen, nsAString& aStr, + bool aMoveOwnership) { + char16_t* data = static_cast<char16_t*>(Data()); + + MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char16_t(0), + "data should be null terminated"); + + nsAString::DataFlags flags = + nsAString::DataFlags::REFCOUNTED | nsAString::DataFlags::TERMINATED; + + if (!aMoveOwnership) { + AddRef(); + } + aStr.Finalize(); + aStr.SetData(data, aLen, flags); +} + +void nsStringBuffer::ToString(uint32_t aLen, nsACString& aStr, + bool aMoveOwnership) { + char* data = static_cast<char*>(Data()); + + MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char(0), + "data should be null terminated"); + + nsACString::DataFlags flags = + nsACString::DataFlags::REFCOUNTED | nsACString::DataFlags::TERMINATED; + + if (!aMoveOwnership) { + AddRef(); + } + aStr.Finalize(); + aStr.SetData(data, aLen, flags); +} + +size_t nsStringBuffer::SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return IsReadonly() ? 0 : aMallocSizeOf(this); +} + +size_t nsStringBuffer::SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this); +} diff --git a/xpcom/string/nsStringBuffer.h b/xpcom/string/nsStringBuffer.h new file mode 100644 index 0000000000..43628d6668 --- /dev/null +++ b/xpcom/string/nsStringBuffer.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsStringBuffer_h__ +#define nsStringBuffer_h__ + +#include <atomic> +#include "mozilla/MemoryReporting.h" +#include "nsStringFwd.h" + +template <class T> +struct already_AddRefed; + +/** + * This structure precedes the string buffers "we" allocate. It may be the + * case that nsTAString::mData does not point to one of these special + * buffers. The mDataFlags member variable distinguishes the buffer type. + * + * When this header is in use, it enables reference counting, and capacity + * tracking. NOTE: A string buffer can be modified only if its reference + * count is 1. + */ +class nsStringBuffer { + private: + friend class CheckStaticAtomSizes; + + std::atomic<uint32_t> mRefCount; + uint32_t mStorageSize; + + public: + /** + * Allocates a new string buffer, with given size in bytes and a + * reference count of one. When the string buffer is no longer needed, + * it should be released via Release. + * + * It is up to the caller to set the bytes corresponding to the string + * buffer by calling the Data method to fetch the raw data pointer. Care + * must be taken to properly null terminate the character array. The + * storage size can be greater than the length of the actual string + * (i.e., it is not required that the null terminator appear in the last + * storage unit of the string buffer's data). + * + * This guarantees that StorageSize() returns aStorageSize if the returned + * buffer is non-null. Some callers like nsAttrValue rely on it. + * + * @return new string buffer or null if out of memory. + */ + static already_AddRefed<nsStringBuffer> Alloc(size_t aStorageSize); + + /** + * Returns a string buffer initialized with the given string on it, or null on + * OOM. + * Note that this will allocate extra space for the trailing null byte, which + * this method will add. + */ + static already_AddRefed<nsStringBuffer> Create(const char16_t* aData, + size_t aLength); + static already_AddRefed<nsStringBuffer> Create(const char* aData, + size_t aLength); + + /** + * Resizes the given string buffer to the specified storage size. This + * method must not be called on a readonly string buffer. Use this API + * carefully!! + * + * This method behaves like the ANSI-C realloc function. (i.e., If the + * allocation fails, null will be returned and the given string buffer + * will remain unmodified.) + * + * @see IsReadonly + */ + static nsStringBuffer* Realloc(nsStringBuffer* aBuf, size_t aStorageSize); + + /** + * Increment the reference count on this string buffer. + */ + void NS_FASTCALL AddRef(); + + /** + * Decrement the reference count on this string buffer. The string + * buffer will be destroyed when its reference count reaches zero. + */ + void NS_FASTCALL Release(); + + /** + * This method returns the string buffer corresponding to the given data + * pointer. The data pointer must have been returned previously by a + * call to the nsStringBuffer::Data method. + */ + static nsStringBuffer* FromData(void* aData) { + return reinterpret_cast<nsStringBuffer*>(aData) - 1; + } + + /** + * This method returns the data pointer for this string buffer. + */ + void* Data() const { + return const_cast<char*>(reinterpret_cast<const char*>(this + 1)); + } + + /** + * This function returns the storage size of a string buffer in bytes. + * This value is the same value that was originally passed to Alloc (or + * Realloc). + */ + uint32_t StorageSize() const { return mStorageSize; } + + /** + * If this method returns false, then the caller can be sure that their + * reference to the string buffer is the only reference to the string + * buffer, and therefore it has exclusive access to the string buffer and + * associated data. However, if this function returns true, then other + * consumers may rely on the data in this buffer being immutable and + * other threads may access this buffer simultaneously. + */ + bool IsReadonly() const { + // This doesn't lead to the destruction of the buffer, so we don't + // need to perform acquire memory synchronization for the normal + // reason that a reference count needs acquire synchronization + // (ensuring that all writes to the object made on other threads are + // visible to the thread destroying the object). + // + // We then need to consider the possibility that there were prior + // writes to the buffer on a different thread: one that has either + // since released its reference count, or one that also has access + // to this buffer through the same reference. There are two ways + // for that to happen: either the buffer pointer or a data structure + // (e.g., string object) pointing to the buffer was transferred from + // one thread to another, or the data structure pointing to the + // buffer was already visible on both threads. In the first case + // (transfer), the transfer of data from one thread to another would + // have handled the memory synchronization. In the latter case + // (data structure visible on both threads), the caller needed some + // sort of higher level memory synchronization to protect against + // the string object being mutated at the same time on multiple + // threads. + + // See bug 1603504. TSan might complain about a race when using + // memory_order_relaxed, so use memory_order_acquire for making TSan + // happy. +#if defined(MOZ_TSAN) + return mRefCount.load(std::memory_order_acquire) > 1; +#else + return mRefCount.load(std::memory_order_relaxed) > 1; +#endif + } + + /** + * The FromString methods return a string buffer for the given string + * object or null if the string object does not have a string buffer. + * The reference count of the string buffer is NOT incremented by these + * methods. If the caller wishes to hold onto the returned value, then + * the returned string buffer must have its reference count incremented + * via a call to the AddRef method. + */ + static nsStringBuffer* FromString(const nsAString& aStr); + static nsStringBuffer* FromString(const nsACString& aStr); + + /** + * The ToString methods assign this string buffer to a given string + * object. If the string object does not support sharable string + * buffers, then its value will be set to a copy of the given string + * buffer. Otherwise, these methods increment the reference count of the + * given string buffer. It is important to specify the length (in + * storage units) of the string contained in the string buffer since the + * length of the string may be less than its storage size. The string + * must have a null terminator at the offset specified by |len|. + * + * NOTE: storage size is measured in bytes even for wide strings; + * however, string length is always measured in storage units + * (2-byte units for wide strings). + */ + void ToString(uint32_t aLen, nsAString& aStr, bool aMoveOwnership = false); + void ToString(uint32_t aLen, nsACString& aStr, bool aMoveOwnership = false); + + /** + * This measures the size only if the StringBuffer is unshared. + */ + size_t SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const; + + /** + * This measures the size regardless of whether the StringBuffer is + * unshared. + * + * WARNING: Only use this if you really know what you are doing, because + * it can easily lead to double-counting strings. If you do use them, + * please explain clearly in a comment why it's safe and won't lead to + * double-counting. + */ + size_t SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const; +}; + +#endif /* !defined(nsStringBuffer_h__ */ diff --git a/xpcom/string/nsStringFlags.h b/xpcom/string/nsStringFlags.h new file mode 100644 index 0000000000..d0ba05c8db --- /dev/null +++ b/xpcom/string/nsStringFlags.h @@ -0,0 +1,95 @@ +/* -*- 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 nsStringFlags_h +#define nsStringFlags_h + +#include <stdint.h> +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +namespace detail { +// NOTE: these flags are declared public _only_ for convenience inside +// the string implementation. And they are outside of the string +// class so that the type is the same for both narrow and wide +// strings. + +// bits for mDataFlags +enum class StringDataFlags : uint16_t { + // Some terminology: + // + // "dependent buffer" A dependent buffer is one that the string class + // does not own. The string class relies on some + // external code to ensure the lifetime of the + // dependent buffer. + // + // "refcounted buffer" A refcounted buffer is one that the string class + // allocates. When it allocates a refcounted string + // buffer, it allocates some additional space at + // the beginning of the buffer for additional + // fields, including a reference count and a + // buffer length. See nsStringHeader. + // + // "adopted buffer" An adopted buffer is a raw string buffer + // allocated on the heap (using moz_xmalloc) + // of which the string class subsumes ownership. + // + // Some comments about the string data flags: + // + // REFCOUNTED, OWNED, and INLINE are all mutually exlusive. They + // indicate the allocation type of mData. If none of these flags + // are set, then the string buffer is dependent. + // + // REFCOUNTED, OWNED, or INLINE imply TERMINATED. This is because + // the string classes always allocate null-terminated buffers, and + // non-terminated substrings are always dependent. + // + // VOIDED implies TERMINATED, and moreover it implies that mData + // points to char_traits::sEmptyBuffer. Therefore, VOIDED is + // mutually exclusive with REFCOUNTED, OWNED, and INLINE. + // + // INLINE requires StringClassFlags::INLINE to be set on the type. + + // IsTerminated returns true + TERMINATED = 1 << 0, + + // IsVoid returns true + VOIDED = 1 << 1, + + // mData points to a heap-allocated, shareable, refcounted buffer + REFCOUNTED = 1 << 2, + + // mData points to a heap-allocated, raw buffer + OWNED = 1 << 3, + + // mData points to a writable, inline buffer + INLINE = 1 << 4, + + // mData points to a string literal; DataFlags::TERMINATED will also be set + LITERAL = 1 << 5, + + // used to check for invalid flags -- all bits above the last item + INVALID_MASK = (uint16_t) ~((LITERAL << 1) - 1) +}; + +// bits for mClassFlags +enum class StringClassFlags : uint16_t { + // |this|'s buffer is inline, and requires the type to be binary-compatible + // with nsTAutoStringN + INLINE = 1 << 0, + // |this| requires its buffer is null-terminated + NULL_TERMINATED = 1 << 1, + // used to check for invalid flags -- all bits above the last item + INVALID_MASK = (uint16_t) ~((NULL_TERMINATED << 1) - 1) +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StringDataFlags) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StringClassFlags) + +} // namespace detail +} // namespace mozilla + +#endif diff --git a/xpcom/string/nsStringFwd.h b/xpcom/string/nsStringFwd.h new file mode 100644 index 0000000000..f737545163 --- /dev/null +++ b/xpcom/string/nsStringFwd.h @@ -0,0 +1,92 @@ +/* -*- 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/. */ + +/* nsStringFwd.h --- forward declarations for string classes */ + +#ifndef nsStringFwd_h +#define nsStringFwd_h + +#include "nscore.h" + +static constexpr int32_t kNotFound = -1; + +namespace mozilla { +namespace detail { + +template <typename T> +class nsTStringRepr; + +using nsStringRepr = nsTStringRepr<char16_t>; +using nsCStringRepr = nsTStringRepr<char>; + +} // namespace detail +} // namespace mozilla + +static const size_t AutoStringDefaultStorageSize = 64; + +template <typename T> +class nsTSubstring; +template <typename T> +class nsTSubstringTuple; +template <typename T> +class nsTString; +template <typename T, size_t N> +class nsTAutoStringN; +template <typename T> +class nsTDependentString; +template <typename T> +class nsTDependentSubstring; +template <typename T> +class nsTPromiseFlatString; +template <typename T> +class nsTLiteralString; +template <typename T> +class nsTSubstringSplitter; + +template <typename T> +using nsTStringComparator = int (*)(const T*, const T*, size_t, size_t); + +// The default string comparator (case-sensitive comparision) +template <typename T> +int nsTDefaultStringComparator(const T*, const T*, size_t, size_t); + +// We define this version without a size param instead of providing a +// default value for N so that so there is a default typename that doesn't +// require angle brackets. +template <typename T> +using nsTAutoString = nsTAutoStringN<T, AutoStringDefaultStorageSize>; + +// Double-byte (char16_t) string types. + +using nsAString = nsTSubstring<char16_t>; +using nsSubstringTuple = nsTSubstringTuple<char16_t>; +using nsString = nsTString<char16_t>; +using nsAutoString = nsTAutoString<char16_t>; +template <size_t N> +using nsAutoStringN = nsTAutoStringN<char16_t, N>; +using nsDependentString = nsTDependentString<char16_t>; +using nsDependentSubstring = nsTDependentSubstring<char16_t>; +using nsPromiseFlatString = nsTPromiseFlatString<char16_t>; +using nsStringComparator = nsTStringComparator<char16_t>; +using nsLiteralString = nsTLiteralString<char16_t>; +using nsSubstringSplitter = nsTSubstringSplitter<char16_t>; + +// Single-byte (char) string types. + +using nsACString = nsTSubstring<char>; +using nsCSubstringTuple = nsTSubstringTuple<char>; +using nsCString = nsTString<char>; +using nsAutoCString = nsTAutoString<char>; +template <size_t N> +using nsAutoCStringN = nsTAutoStringN<char, N>; +using nsDependentCString = nsTDependentString<char>; +using nsDependentCSubstring = nsTDependentSubstring<char>; +using nsPromiseFlatCString = nsTPromiseFlatString<char>; +using nsCStringComparator = nsTStringComparator<char>; +using nsLiteralCString = nsTLiteralString<char>; +using nsCSubstringSplitter = nsTSubstringSplitter<char>; + +#endif /* !defined(nsStringFwd_h) */ diff --git a/xpcom/string/nsStringIterator.h b/xpcom/string/nsStringIterator.h new file mode 100644 index 0000000000..db14efdaca --- /dev/null +++ b/xpcom/string/nsStringIterator.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 nsStringIterator_h___ +#define nsStringIterator_h___ + +#include "nsCharTraits.h" +#include "nsAlgorithm.h" +#include "nsDebug.h" + +/** + * @see nsTAString + */ + +template <class CharT> +class nsReadingIterator { + public: + typedef nsReadingIterator<CharT> self_type; + typedef ptrdiff_t difference_type; + typedef size_t size_type; + typedef CharT value_type; + typedef const CharT* pointer; + typedef const CharT& reference; + + private: + friend class mozilla::detail::nsTStringRepr<CharT>; + + // unfortunately, the API for nsReadingIterator requires that the + // iterator know its start and end positions. this was needed when + // we supported multi-fragment strings, but now it is really just + // extra baggage. we should remove mStart and mEnd at some point. + + const CharT* mStart; + const CharT* mEnd; + const CharT* mPosition; + + public: + nsReadingIterator() : mStart(nullptr), mEnd(nullptr), mPosition(nullptr) {} + // clang-format off + // nsReadingIterator( const nsReadingIterator<CharT>& ); // auto-generated copy-constructor OK + // nsReadingIterator<CharT>& operator=( const nsReadingIterator<CharT>& ); // auto-generated copy-assignment operator OK + // clang-format on + + pointer get() const { return mPosition; } + + CharT operator*() const { return *get(); } + + self_type& operator++() { + ++mPosition; + return *this; + } + + self_type operator++(int) { + self_type result(*this); + ++mPosition; + return result; + } + + self_type& operator--() { + --mPosition; + return *this; + } + + self_type operator--(int) { + self_type result(*this); + --mPosition; + return result; + } + + self_type& advance(difference_type aN) { + if (aN > 0) { + difference_type step = XPCOM_MIN(aN, mEnd - mPosition); + + NS_ASSERTION( + step > 0, + "can't advance a reading iterator beyond the end of a string"); + + mPosition += step; + } else if (aN < 0) { + difference_type step = XPCOM_MAX(aN, -(mPosition - mStart)); + + NS_ASSERTION(step < 0, + "can't advance (backward) a reading iterator beyond the end " + "of a string"); + + mPosition += step; + } + return *this; + } + + // We return an unsigned type here (with corresponding assert) rather than + // the more usual difference_type because we want to make this class go + // away in favor of mozilla::RangedPtr. Since RangedPtr has the same + // requirement we are enforcing here, the transition ought to be much + // smoother. + size_type operator-(const self_type& aOther) const { + MOZ_ASSERT(mPosition >= aOther.mPosition); + return mPosition - aOther.mPosition; + } +}; + +template <class CharT> +inline bool operator==(const nsReadingIterator<CharT>& aLhs, + const nsReadingIterator<CharT>& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template <class CharT> +inline bool operator!=(const nsReadingIterator<CharT>& aLhs, + const nsReadingIterator<CharT>& aRhs) { + return aLhs.get() != aRhs.get(); +} + +#endif /* !defined(nsStringIterator_h___) */ diff --git a/xpcom/string/nsStringStats.cpp b/xpcom/string/nsStringStats.cpp new file mode 100644 index 0000000000..7fc3d82ad5 --- /dev/null +++ b/xpcom/string/nsStringStats.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 "nsStringStats.h" + +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/MemoryReporting.h" +#include "nsString.h" + +#include <stdint.h> +#include <stdio.h> + +#ifdef XP_WIN +# include <windows.h> +# include <process.h> +#else +# include <unistd.h> +# include <pthread.h> +#endif + +nsStringStats gStringStats; + +nsStringStats::~nsStringStats() { + // this is a hack to suppress duplicate string stats printing + // in seamonkey as a result of the string code being linked + // into seamonkey and libxpcom! :-( + if (!mAllocCount && !mAdoptCount) { + return; + } + + // Only print the stats if we detect a leak. + if (mAllocCount <= mFreeCount && mAdoptCount <= mAdoptFreeCount) { + return; + } + + printf("nsStringStats\n"); + printf(" => mAllocCount: % 10d\n", int(mAllocCount)); + printf(" => mReallocCount: % 10d\n", int(mReallocCount)); + printf(" => mFreeCount: % 10d", int(mFreeCount)); + if (mAllocCount > mFreeCount) { + printf(" -- LEAKED %d !!!\n", mAllocCount - mFreeCount); + } else { + printf("\n"); + } + printf(" => mShareCount: % 10d\n", int(mShareCount)); + printf(" => mAdoptCount: % 10d\n", int(mAdoptCount)); + printf(" => mAdoptFreeCount: % 10d", int(mAdoptFreeCount)); + if (mAdoptCount > mAdoptFreeCount) { + printf(" -- LEAKED %d !!!\n", mAdoptCount - mAdoptFreeCount); + } else { + printf("\n"); + } + +#ifdef XP_WIN + auto pid = uintptr_t(_getpid()); + auto tid = uintptr_t(GetCurrentThreadId()); +#else + auto pid = uintptr_t(getpid()); + auto tid = uintptr_t(pthread_self()); +#endif + + printf(" => Process ID: %" PRIuPTR ", Thread ID: %" PRIuPTR "\n", pid, tid); +} diff --git a/xpcom/string/nsStringStats.h b/xpcom/string/nsStringStats.h new file mode 100644 index 0000000000..a38304c2b7 --- /dev/null +++ b/xpcom/string/nsStringStats.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 nsStringStats_h +#define nsStringStats_h + +#include "mozilla/Atomics.h" + +class nsStringStats { + public: + nsStringStats() = default; + + ~nsStringStats(); + + using AtomicInt = mozilla::Atomic<int32_t, mozilla::SequentiallyConsistent>; + + AtomicInt mAllocCount{0}; + AtomicInt mReallocCount{0}; + AtomicInt mFreeCount{0}; + AtomicInt mShareCount{0}; + AtomicInt mAdoptCount{0}; + AtomicInt mAdoptFreeCount{0}; +}; + +extern nsStringStats gStringStats; + +#define STRING_STAT_INCREMENT(_s) (gStringStats.m##_s##Count)++ + +#endif // nsStringStats_h diff --git a/xpcom/string/nsTDependentString.cpp b/xpcom/string/nsTDependentString.cpp new file mode 100644 index 0000000000..83cfa39687 --- /dev/null +++ b/xpcom/string/nsTDependentString.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTDependentString.h" + +template <typename T> +nsTDependentString<T>::nsTDependentString(const char_type* aStart, + const char_type* aEnd) + : string_type(const_cast<char_type*>(aStart), aEnd - aStart, + DataFlags::TERMINATED, ClassFlags(0)) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + this->AssertValidDependentString(); +} + +template <typename T> +void nsTDependentString<T>::Rebind(const string_type& str, + index_type startPos) { + MOZ_ASSERT(str.GetDataFlags() & DataFlags::TERMINATED, + "Unterminated flat string"); + + // If we currently own a buffer, release it. + this->Finalize(); + + size_type strLength = str.Length(); + + if (startPos > strLength) { + startPos = strLength; + } + + char_type* newData = + const_cast<char_type*>(static_cast<const char_type*>(str.Data())) + + startPos; + size_type newLen = strLength - startPos; + DataFlags newDataFlags = + str.GetDataFlags() & (DataFlags::TERMINATED | DataFlags::LITERAL); + this->SetData(newData, newLen, newDataFlags); +} + +template <typename T> +void nsTDependentString<T>::Rebind(const char_type* aStart, + const char_type* aEnd) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + this->Rebind(aStart, aEnd - aStart); +} + +template class nsTDependentString<char>; +template class nsTDependentString<char16_t>; diff --git a/xpcom/string/nsTDependentString.h b/xpcom/string/nsTDependentString.h new file mode 100644 index 0000000000..c7194a677f --- /dev/null +++ b/xpcom/string/nsTDependentString.h @@ -0,0 +1,126 @@ +/* -*- 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 nsTDependentString_h +#define nsTDependentString_h + +#include "nsTString.h" + +/** + * nsTDependentString + * + * Stores a null-terminated, immutable sequence of characters. + * + * Subclass of nsTString that restricts string value to an immutable + * character sequence. This class does not own its data, so the creator + * of objects of this type must take care to ensure that a + * nsTDependentString continues to reference valid memory for the + * duration of its use. + */ +template <typename T> +class nsTDependentString : public nsTString<T> { + public: + typedef nsTDependentString<T> self_type; + typedef nsTString<T> base_string_type; + typedef typename base_string_type::string_type string_type; + + typedef typename base_string_type::fallible_t fallible_t; + + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::char_traits char_traits; + typedef + typename base_string_type::incompatible_char_type incompatible_char_type; + + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + + typedef typename base_string_type::const_iterator const_iterator; + typedef typename base_string_type::iterator iterator; + + typedef typename base_string_type::comparator_type comparator_type; + + typedef typename base_string_type::const_char_iterator const_char_iterator; + + typedef typename base_string_type::string_view string_view; + + typedef typename base_string_type::index_type index_type; + typedef typename base_string_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + + public: + /** + * constructors + */ + + nsTDependentString(const char_type* aStart, const char_type* aEnd); + + nsTDependentString(const char_type* aData, size_type aLength) + : string_type(const_cast<char_type*>(aData), aLength, + DataFlags::TERMINATED, ClassFlags(0)) { + this->AssertValidDependentString(); + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + nsTDependentString(char16ptr_t aData, size_type aLength) + : nsTDependentString(static_cast<const char16_t*>(aData), aLength) {} +#endif + + explicit nsTDependentString(const char_type* aData) + : string_type(const_cast<char_type*>(aData), char_traits::length(aData), + DataFlags::TERMINATED, ClassFlags(0)) { + string_type::AssertValidDependentString(); + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + explicit nsTDependentString(char16ptr_t aData) + : nsTDependentString(static_cast<const char16_t*>(aData)) {} +#endif + + nsTDependentString(const string_type& aStr, index_type aStartPos) + : string_type() { + Rebind(aStr, aStartPos); + } + + // Create a nsTDependentSubstring to be bound later + nsTDependentString() : string_type() {} + + // auto-generated destructor OK + + nsTDependentString(self_type&& aStr) : string_type() { + Rebind(aStr, /* aStartPos = */ 0); + aStr.SetToEmptyBuffer(); + } + + explicit nsTDependentString(const self_type& aStr) : string_type() { + Rebind(aStr, /* aStartPos = */ 0); + } + + /** + * allow this class to be bound to a different string... + */ + + using nsTString<T>::Rebind; + void Rebind(const char_type* aData) { + Rebind(aData, char_traits::length(aData)); + } + + void Rebind(const char_type* aStart, const char_type* aEnd); + void Rebind(const string_type&, index_type aStartPos); + + private: + // NOT USED + nsTDependentString(const substring_tuple_type&) = delete; + self_type& operator=(const self_type& aStr) = delete; +}; + +extern template class nsTDependentString<char>; +extern template class nsTDependentString<char16_t>; + +#endif diff --git a/xpcom/string/nsTDependentSubstring.cpp b/xpcom/string/nsTDependentSubstring.cpp new file mode 100644 index 0000000000..ba1620f98b --- /dev/null +++ b/xpcom/string/nsTDependentSubstring.cpp @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +// FIXME: Due to an include cycle, we need to include `nsTSubstring` first. +#include "nsTSubstring.h" +#include "nsTDependentSubstring.h" + +template <typename T> +void nsTDependentSubstring<T>::Rebind(const substring_type& str, + size_type startPos, size_type length) { + // If we currently own a buffer, release it. + this->Finalize(); + + size_type strLength = str.Length(); + + if (startPos > strLength) { + startPos = strLength; + } + + char_type* newData = + const_cast<char_type*>(static_cast<const char_type*>(str.Data())) + + startPos; + size_type newLength = XPCOM_MIN(length, strLength - startPos); + DataFlags newDataFlags = DataFlags(0); + this->SetData(newData, newLength, newDataFlags); +} + +template <typename T> +void nsTDependentSubstring<T>::Rebind(const char_type* data, size_type length) { + NS_ASSERTION(data, "nsTDependentSubstring must wrap a non-NULL buffer"); + + // If we currently own a buffer, release it. + this->Finalize(); + + char_type* newData = + const_cast<char_type*>(static_cast<const char_type*>(data)); + size_type newLength = length; + DataFlags newDataFlags = DataFlags(0); + this->SetData(newData, newLength, newDataFlags); +} + +template <typename T> +void nsTDependentSubstring<T>::Rebind(const char_type* aStart, + const char_type* aEnd) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + this->Rebind(aStart, size_type(aEnd - aStart)); +} + +template <typename T> +nsTDependentSubstring<T>::nsTDependentSubstring(const char_type* aStart, + const char_type* aEnd) + : substring_type(const_cast<char_type*>(aStart), aEnd - aStart, + DataFlags(0), ClassFlags(0)) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); +} + +#if defined(MOZ_USE_CHAR16_WRAPPER) +template <typename T> +template <typename Q, typename EnableIfChar16> +nsTDependentSubstring<T>::nsTDependentSubstring(char16ptr_t aStart, + char16ptr_t aEnd) + : substring_type(static_cast<const char16_t*>(aStart), + static_cast<const char16_t*>(aEnd)) { + MOZ_RELEASE_ASSERT(static_cast<const char16_t*>(aStart) <= + static_cast<const char16_t*>(aEnd), + "Overflow!"); +} +#endif + +template <typename T> +nsTDependentSubstring<T>::nsTDependentSubstring(const const_iterator& aStart, + const const_iterator& aEnd) + : substring_type(const_cast<char_type*>(aStart.get()), + aEnd.get() - aStart.get(), DataFlags(0), ClassFlags(0)) { + MOZ_RELEASE_ASSERT(aStart.get() <= aEnd.get(), "Overflow!"); +} + +template <typename T> +const nsTDependentSubstring<T> Substring(const T* aStart, const T* aEnd) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + return nsTDependentSubstring<T>(aStart, aEnd); +} + +template nsTDependentSubstring<char> const Substring<char>(char const*, + char const*); +template nsTDependentSubstring<char16_t> const Substring<char16_t>( + char16_t const*, char16_t const*); + +#if defined(MOZ_USE_CHAR16_WRAPPER) +const nsTDependentSubstring<char16_t> Substring(char16ptr_t aData, + size_t aLength) { + return nsTDependentSubstring<char16_t>(aData, aLength); +} + +const nsTDependentSubstring<char16_t> Substring(char16ptr_t aStart, + char16ptr_t aEnd) { + return Substring(static_cast<const char16_t*>(aStart), + static_cast<const char16_t*>(aEnd)); +} +#endif + +template class nsTDependentSubstring<char>; +template class nsTDependentSubstring<char16_t>; diff --git a/xpcom/string/nsTDependentSubstring.h b/xpcom/string/nsTDependentSubstring.h new file mode 100644 index 0000000000..b5198ff2b5 --- /dev/null +++ b/xpcom/string/nsTDependentSubstring.h @@ -0,0 +1,162 @@ +/* -*- 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 "nsString.h" + +#ifndef nsTDependentSubstring_h +#define nsTDependentSubstring_h + +#include "nsTSubstring.h" +#include "nsTLiteralString.h" +#include "mozilla/Span.h" + +/** + * nsTDependentSubstring_CharT + * + * A string class which wraps an external array of string characters. It + * is the client code's responsibility to ensure that the external buffer + * remains valid for a long as the string is alive. + * + * NAMES: + * nsDependentSubstring for wide characters + * nsDependentCSubstring for narrow characters + */ +template <typename T> +class nsTDependentSubstring : public nsTSubstring<T> { + public: + typedef nsTDependentSubstring<T> self_type; + typedef nsTSubstring<T> substring_type; + typedef typename substring_type::fallible_t fallible_t; + + typedef typename substring_type::char_type char_type; + typedef typename substring_type::char_traits char_traits; + typedef + typename substring_type::incompatible_char_type incompatible_char_type; + + typedef typename substring_type::substring_tuple_type substring_tuple_type; + + typedef typename substring_type::const_iterator const_iterator; + typedef typename substring_type::iterator iterator; + + typedef typename substring_type::comparator_type comparator_type; + + typedef typename substring_type::const_char_iterator const_char_iterator; + + typedef typename substring_type::string_view string_view; + + typedef typename substring_type::index_type index_type; + typedef typename substring_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename substring_type::DataFlags DataFlags; + typedef typename substring_type::ClassFlags ClassFlags; + + public: + void Rebind(const substring_type&, size_type aStartPos, + size_type aLength = size_type(-1)); + + void Rebind(const char_type* aData, size_type aLength); + + void Rebind(const char_type* aStart, const char_type* aEnd); + + nsTDependentSubstring(const substring_type& aStr, size_type aStartPos, + size_type aLength = size_type(-1)) + : substring_type() { + Rebind(aStr, aStartPos, aLength); + } + + nsTDependentSubstring(const char_type* aData, size_type aLength) + : substring_type(const_cast<char_type*>(aData), aLength, DataFlags(0), + ClassFlags(0)) {} + + explicit nsTDependentSubstring(mozilla::Span<const char_type> aData) + : nsTDependentSubstring(aData.Elements(), aData.Length()) {} + + nsTDependentSubstring(const char_type* aStart, const char_type* aEnd); + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + nsTDependentSubstring(char16ptr_t aData, size_type aLength) + : nsTDependentSubstring(static_cast<const char16_t*>(aData), aLength) {} + + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + nsTDependentSubstring(char16ptr_t aStart, char16ptr_t aEnd); +#endif + + nsTDependentSubstring(const const_iterator& aStart, + const const_iterator& aEnd); + + // Create a nsTDependentSubstring to be bound later + nsTDependentSubstring() : substring_type() {} + + // auto-generated copy-constructor OK (XXX really?? what about base class + // copy-ctor?) + nsTDependentSubstring(const nsTDependentSubstring&) = default; + + private: + // NOT USED + void operator=(const self_type&) = + delete; // we're immutable, you can't assign into a substring +}; + +extern template class nsTDependentSubstring<char>; +extern template class nsTDependentSubstring<char16_t>; + +template <typename T> +inline const nsTDependentSubstring<T> Substring(const nsTSubstring<T>& aStr, + size_t aStartPos, + size_t aLength = size_t(-1)) { + return nsTDependentSubstring<T>(aStr, aStartPos, aLength); +} + +template <typename T> +inline const nsTDependentSubstring<T> Substring(const nsTLiteralString<T>& aStr, + size_t aStartPos, + size_t aLength = size_t(-1)) { + return nsTDependentSubstring<T>(aStr, aStartPos, aLength); +} + +template <typename T> +inline const nsTDependentSubstring<T> Substring( + const nsReadingIterator<T>& aStart, const nsReadingIterator<T>& aEnd) { + return nsTDependentSubstring<T>(aStart.get(), aEnd.get()); +} + +template <typename T> +inline const nsTDependentSubstring<T> Substring(const T* aData, + size_t aLength) { + return nsTDependentSubstring<T>(aData, aLength); +} + +template <typename T> +const nsTDependentSubstring<T> Substring(const T* aStart, const T* aEnd); + +extern template const nsTDependentSubstring<char> Substring(const char* aStart, + const char* aEnd); + +extern template const nsTDependentSubstring<char16_t> Substring( + const char16_t* aStart, const char16_t* aEnd); + +#if defined(MOZ_USE_CHAR16_WRAPPER) +inline const nsTDependentSubstring<char16_t> Substring(char16ptr_t aData, + size_t aLength); + +const nsTDependentSubstring<char16_t> Substring(char16ptr_t aStart, + char16ptr_t aEnd); +#endif + +template <typename T> +inline const nsTDependentSubstring<T> StringHead(const nsTSubstring<T>& aStr, + size_t aCount) { + return nsTDependentSubstring<T>(aStr, 0, aCount); +} + +template <typename T> +inline const nsTDependentSubstring<T> StringTail(const nsTSubstring<T>& aStr, + size_t aCount) { + return nsTDependentSubstring<T>(aStr, aStr.Length() - aCount, aCount); +} + +#endif diff --git a/xpcom/string/nsTLiteralString.cpp b/xpcom/string/nsTLiteralString.cpp new file mode 100644 index 0000000000..79454f7783 --- /dev/null +++ b/xpcom/string/nsTLiteralString.cpp @@ -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/. */ + +#include "nsTLiteralString.h" + +template class nsTLiteralString<char>; +template class nsTLiteralString<char16_t>; diff --git a/xpcom/string/nsTLiteralString.h b/xpcom/string/nsTLiteralString.h new file mode 100644 index 0000000000..38ffd32bdb --- /dev/null +++ b/xpcom/string/nsTLiteralString.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/. */ + +#ifndef nsTLiteralString_h +#define nsTLiteralString_h + +#include "nsTStringRepr.h" + +/** + * nsTLiteralString_CharT + * + * Stores a null-terminated, immutable sequence of characters. + * + * nsTString-lookalike that restricts its string value to a literal character + * sequence. Can be implicitly cast to const nsTString& (the const is + * essential, since this class's data are not writable). The data are assumed + * to be static (permanent) and therefore, as an optimization, this class + * does not have a destructor. + */ +template <typename T> +class nsTLiteralString : public mozilla::detail::nsTStringRepr<T> { + public: + typedef nsTLiteralString<T> self_type; + +#ifdef __clang__ + // bindgen w/ clang 3.9 at least chokes on a typedef, but using is okay. + using typename mozilla::detail::nsTStringRepr<T>::base_string_type; +#else + // On the other hand msvc chokes on the using statement. It seems others + // don't care either way so we lump them in here. + typedef typename mozilla::detail::nsTStringRepr<T>::base_string_type + base_string_type; +#endif + + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::size_type size_type; + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + + public: + /** + * constructor + */ + + template <size_type N> + explicit constexpr nsTLiteralString(const char_type (&aStr)[N]) + : nsTLiteralString(aStr, N - 1) {} + + nsTLiteralString(const nsTLiteralString&) = default; + + /** + * For compatibility with existing code that requires const ns[C]String*. + * Use sparingly. If possible, rewrite code to use const ns[C]String& + * and the implicit cast will just work. + */ + const nsTString<T>& AsString() const MOZ_LIFETIME_BOUND { + return *reinterpret_cast<const nsTString<T>*>(this); + } + + operator const nsTString<T>&() const MOZ_LIFETIME_BOUND { return AsString(); } + + template <typename N, typename Dummy> + struct raw_type { + typedef N* type; + }; + +#ifdef MOZ_USE_CHAR16_WRAPPER + template <typename Dummy> + struct raw_type<char16_t, Dummy> { + typedef char16ptr_t type; + }; +#endif + + /** + * Prohibit get() on temporaries as in "x"_ns.get(). + * These should be written as just "x", using a string literal directly. + */ + const typename raw_type<T, int>::type get() const&& = delete; + const typename raw_type<T, int>::type get() const& { return this->mData; } + +// At least older gcc versions do not accept these friend declarations, +// complaining about an "invalid argument list" here, but not where the actual +// operators are defined or used. We make the supposed-to-be-private constructor +// public when building with gcc, relying on the default clang builds to fail if +// any non-private use of that constructor would get into the codebase. +#if defined(__clang__) + private: + friend constexpr auto operator"" _ns(const char* aStr, std::size_t aLen); + friend constexpr auto operator"" _ns(const char16_t* aStr, std::size_t aLen); +#else + public: +#endif + // Only for use by operator"" + constexpr nsTLiteralString(const char_type* aStr, size_t aLen) + : base_string_type(const_cast<char_type*>(aStr), aLen, + DataFlags::TERMINATED | DataFlags::LITERAL, + ClassFlags::NULL_TERMINATED) {} + + public: + // NOT TO BE IMPLEMENTED + template <size_type N> + nsTLiteralString(char_type (&aStr)[N]) = delete; + + nsTLiteralString& operator=(const nsTLiteralString&) = delete; +}; + +extern template class nsTLiteralString<char>; +extern template class nsTLiteralString<char16_t>; + +#endif diff --git a/xpcom/string/nsTPromiseFlatString.cpp b/xpcom/string/nsTPromiseFlatString.cpp new file mode 100644 index 0000000000..1243300033 --- /dev/null +++ b/xpcom/string/nsTPromiseFlatString.cpp @@ -0,0 +1,26 @@ +/* -*- 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 "nsTPromiseFlatString.h" + +template <typename T> +void nsTPromiseFlatString<T>::Init(const substring_type& str) { + if (str.IsTerminated()) { + char_type* newData = + const_cast<char_type*>(static_cast<const char_type*>(str.Data())); + size_type newLength = str.Length(); + DataFlags newDataFlags = + str.GetDataFlags() & (DataFlags::TERMINATED | DataFlags::LITERAL); + // does not promote DataFlags::VOIDED + + this->SetData(newData, newLength, newDataFlags); + } else { + this->Assign(str); + } +} + +template class nsTPromiseFlatString<char>; +template class nsTPromiseFlatString<char16_t>; diff --git a/xpcom/string/nsTPromiseFlatString.h b/xpcom/string/nsTPromiseFlatString.h new file mode 100644 index 0000000000..0033d074ce --- /dev/null +++ b/xpcom/string/nsTPromiseFlatString.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTPromiseFlatString_h +#define nsTPromiseFlatString_h + +#include "mozilla/Attributes.h" +#include "nsTString.h" + +/** + * NOTE: + * + * Try to avoid flat strings. |PromiseFlat[C]String| will help you as a last + * resort, and this may be necessary when dealing with legacy or OS calls, + * but in general, requiring a null-terminated array of characters kills many + * of the performance wins the string classes offer. Write your own code to + * use |nsA[C]String&|s for parameters. Write your string proccessing + * algorithms to exploit iterators. If you do this, you will benefit from + * being able to chain operations without copying or allocating and your code + * will be significantly more efficient. Remember, a function that takes an + * |const nsA[C]String&| can always be passed a raw character pointer by + * wrapping it (for free) in a |nsDependent[C]String|. But a function that + * takes a character pointer always has the potential to force allocation and + * copying. + * + * + * How to use it: + * + * A |nsPromiseFlat[C]String| doesn't necessarily own the characters it + * promises. You must never use it to promise characters out of a string + * with a shorter lifespan. The typical use will be something like this: + * + * SomeOSFunction( PromiseFlatCString(aCSubstring).get() ); // GOOD + * + * Here's a BAD use: + * + * const char* buffer = PromiseFlatCString(aCSubstring).get(); + * SomeOSFunction(buffer); // BAD!! |buffer| is a dangling pointer + * + * The only way to make one is with the function |PromiseFlat[C]String|, + * which produce a |const| instance. ``What if I need to keep a promise + * around for a little while?'' you might ask. In that case, you can keep a + * reference, like so: + * + * const nsCString& flat = PromiseFlatString(aCSubstring); + * // Temporaries usually die after the full expression containing the + * // expression that created the temporary is evaluated. But when a + * // temporary is assigned to a local reference, the temporary's lifetime + * // is extended to the reference's lifetime (C++11 [class.temporary]p5). + * // + * // This reference holds the anonymous temporary alive. But remember: it + * // must _still_ have a lifetime shorter than that of |aCSubstring|, and + * // |aCSubstring| must not be changed while the PromiseFlatString lives. + * + * SomeOSFunction(flat.get()); + * SomeOtherOSFunction(flat.get()); + * + * + * How does it work? + * + * A |nsPromiseFlat[C]String| is just a wrapper for another string. If you + * apply it to a string that happens to be flat, your promise is just a + * dependent reference to the string's data. If you apply it to a non-flat + * string, then a temporary flat string is created for you, by allocating and + * copying. In the event that you end up assigning the result into a sharing + * string (e.g., |nsTString|), the right thing happens. + */ + +template <typename T> +class MOZ_STACK_CLASS nsTPromiseFlatString : public nsTString<T> { + public: + typedef nsTPromiseFlatString<T> self_type; + typedef nsTString<T> base_string_type; + typedef typename base_string_type::substring_type substring_type; + typedef typename base_string_type::string_type string_type; + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + + private: + void Init(const substring_type&); + + // NOT TO BE IMPLEMENTED + void operator=(const self_type&) = delete; + + // NOT TO BE IMPLEMENTED + nsTPromiseFlatString(const self_type&) = delete; + + // NOT TO BE IMPLEMENTED + nsTPromiseFlatString() = delete; + + // NOT TO BE IMPLEMENTED + nsTPromiseFlatString(const string_type& aStr) = delete; + + public: + explicit nsTPromiseFlatString(const substring_type& aStr) : string_type() { + Init(aStr); + } + + explicit nsTPromiseFlatString(const substring_tuple_type& aTuple) + : string_type() { + // nothing else to do here except assign the value of the tuple + // into ourselves. + this->Assign(aTuple); + } +}; + +extern template class nsTPromiseFlatString<char>; +extern template class nsTPromiseFlatString<char16_t>; + +// We template this so that the constructor is chosen based on the type of the +// parameter. This allows us to reject attempts to promise a flat flat string. +template <class T> +const nsTPromiseFlatString<T> TPromiseFlatString( + const typename nsTPromiseFlatString<T>::substring_type& aString) { + return nsTPromiseFlatString<T>(aString); +} + +template <class T> +const nsTPromiseFlatString<T> TPromiseFlatString( + const typename nsTPromiseFlatString<T>::substring_tuple_type& aString) { + return nsTPromiseFlatString<T>(aString); +} + +#ifndef PromiseFlatCString +# define PromiseFlatCString TPromiseFlatString<char> +#endif + +#ifndef PromiseFlatString +# define PromiseFlatString TPromiseFlatString<char16_t> +#endif + +#endif diff --git a/xpcom/string/nsTString.cpp b/xpcom/string/nsTString.cpp new file mode 100644 index 0000000000..4e845f62df --- /dev/null +++ b/xpcom/string/nsTString.cpp @@ -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/. */ + +#include "nsTString.h" +#include "nsString.h" +#include "prdtoa.h" + +/** + * nsTString::SetCharAt + */ + +template <typename T> +bool nsTString<T>::SetCharAt(char16_t aChar, index_type aIndex) { + if (aIndex >= this->mLength) { + return false; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + + this->mData[aIndex] = char_type(aChar); + return true; +} + +template <typename T> +void nsTString<T>::Rebind(const char_type* data, size_type length) { + // If we currently own a buffer, release it. + this->Finalize(); + + this->SetData(const_cast<char_type*>(data), length, DataFlags::TERMINATED); + this->AssertValidDependentString(); +} + +template class nsTString<char>; +template class nsTString<char16_t>; + +template class nsTAutoStringN<char, 64>; +template class nsTAutoStringN<char16_t, 64>; diff --git a/xpcom/string/nsTString.h b/xpcom/string/nsTString.h new file mode 100644 index 0000000000..9793f70e3b --- /dev/null +++ b/xpcom/string/nsTString.h @@ -0,0 +1,447 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsTString_h +#define nsTString_h + +#include "nsTSubstring.h" + +/** + * This is the canonical null-terminated string class. All subclasses + * promise null-terminated storage. Instances of this class allocate + * strings on the heap. + * + * NAMES: + * nsString for wide characters + * nsCString for narrow characters + * + * This class is also known as nsAFlat[C]String, where "flat" is used + * to denote a null-terminated string. + */ +template <typename T> +class nsTString : public nsTSubstring<T> { + public: + typedef nsTString<T> self_type; + + using repr_type = mozilla::detail::nsTStringRepr<T>; + +#ifdef __clang__ + // bindgen w/ clang 3.9 at least chokes on a typedef, but using is okay. + using typename nsTSubstring<T>::substring_type; +#else + // On the other hand msvc chokes on the using statement. It seems others + // don't care either way so we lump them in here. + typedef typename nsTSubstring<T>::substring_type substring_type; +#endif + + typedef typename substring_type::fallible_t fallible_t; + + typedef typename substring_type::char_type char_type; + typedef typename substring_type::char_traits char_traits; + typedef + typename substring_type::incompatible_char_type incompatible_char_type; + + typedef typename substring_type::substring_tuple_type substring_tuple_type; + + typedef typename substring_type::const_iterator const_iterator; + typedef typename substring_type::iterator iterator; + + typedef typename substring_type::comparator_type comparator_type; + + typedef typename substring_type::const_char_iterator const_char_iterator; + + typedef typename substring_type::string_view string_view; + + typedef typename substring_type::index_type index_type; + typedef typename substring_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename substring_type::DataFlags DataFlags; + typedef typename substring_type::ClassFlags ClassFlags; + + public: + /** + * constructors + */ + + nsTString() : substring_type(ClassFlags::NULL_TERMINATED) {} + + explicit nsTString(const char_type* aData, size_type aLength = size_type(-1)) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aData, aLength); + } + + explicit nsTString(mozilla::Span<const char_type> aData) + : nsTString(aData.Elements(), aData.Length()) {} + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + explicit nsTString(char16ptr_t aStr, size_type aLength = size_type(-1)) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(static_cast<const char16_t*>(aStr), aLength); + } +#endif + + nsTString(const self_type& aStr) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aStr); + } + + nsTString(self_type&& aStr) : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(std::move(aStr)); + } + + MOZ_IMPLICIT nsTString(const substring_tuple_type& aTuple) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aTuple); + } + + explicit nsTString(const substring_type& aReadable) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aReadable); + } + + explicit nsTString(substring_type&& aReadable) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(std::move(aReadable)); + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) { + this->Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) { + this->Assign(aData); + return *this; + } + self_type& operator=(const self_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(self_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + self_type& operator=(const char16ptr_t aStr) { + this->Assign(static_cast<const char16_t*>(aStr)); + return *this; + } +#endif + self_type& operator=(const substring_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(substring_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) { + this->Assign(aTuple); + return *this; + } + + /** + * returns the null-terminated string + */ + + template <typename U, typename Dummy> + struct raw_type { + typedef const U* type; + }; +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Dummy> + struct raw_type<char16_t, Dummy> { + typedef char16ptr_t type; + }; +#endif + + MOZ_NO_DANGLING_ON_TEMPORARIES typename raw_type<T, int>::type get() const { + return this->mData; + } + + /** + * returns character at specified index. + * + * NOTE: unlike nsTSubstring::CharAt, this function allows you to index + * the null terminator character. + */ + + char_type CharAt(index_type aIndex) const { + MOZ_ASSERT(aIndex <= this->Length(), "index exceeds allowable range"); + return this->mData[aIndex]; + } + + char_type operator[](index_type aIndex) const { return CharAt(aIndex); } + + /** + * Set a char inside this string at given index + * + * @param aChar is the char you want to write into this string + * @param anIndex is the ofs where you want to write the given char + * @return TRUE if successful + */ + bool SetCharAt(char16_t aChar, index_type aIndex); + + /** + * Allow this string to be bound to a character buffer + * until the string is rebound or mutated; the caller + * must ensure that the buffer outlives the string. + */ + void Rebind(const char_type* aData, size_type aLength); + + /** + * verify restrictions for dependent strings + */ + void AssertValidDependentString() { + MOZ_ASSERT(this->mData, "nsTDependentString must wrap a non-NULL buffer"); + MOZ_ASSERT(this->mData[substring_type::mLength] == 0, + "nsTDependentString must wrap only null-terminated strings. " + "You are probably looking for nsTDependentSubstring."); + } + + protected: + // allow subclasses to initialize fields directly + nsTString(char_type* aData, size_type aLength, DataFlags aDataFlags, + ClassFlags aClassFlags) + : substring_type(aData, aLength, aDataFlags, + aClassFlags | ClassFlags::NULL_TERMINATED) {} + + friend const nsTString<char>& VoidCString(); + friend const nsTString<char16_t>& VoidString(); + + // Used by Null[C]String. + explicit nsTString(DataFlags aDataFlags) + : substring_type(char_traits::sEmptyBuffer, 0, + aDataFlags | DataFlags::TERMINATED, + ClassFlags::NULL_TERMINATED) {} +}; + +extern template class nsTString<char>; +extern template class nsTString<char16_t>; + +/** + * nsTAutoStringN + * + * Subclass of nsTString that adds support for stack-based string + * allocation. It is normally not a good idea to use this class on the + * heap, because it will allocate space which may be wasted if the string + * it contains is significantly smaller or any larger than 64 characters. + * + * NAMES: + * nsAutoStringN / nsTAutoString for wide characters + * nsAutoCStringN / nsTAutoCString for narrow characters + */ +template <typename T, size_t N> +class MOZ_NON_MEMMOVABLE nsTAutoStringN : public nsTString<T> { + public: + typedef nsTAutoStringN<T, N> self_type; + + typedef nsTString<T> base_string_type; + typedef typename base_string_type::string_type string_type; + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::char_traits char_traits; + typedef typename base_string_type::substring_type substring_type; + typedef typename base_string_type::size_type size_type; + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + typedef typename base_string_type::LengthStorage LengthStorage; + + public: + /** + * constructors + */ + + nsTAutoStringN() + : string_type(mStorage, 0, DataFlags::TERMINATED | DataFlags::INLINE, + ClassFlags::INLINE), + mInlineCapacity(N - 1) { + // null-terminate + mStorage[0] = char_type(0); + } + + explicit nsTAutoStringN(char_type aChar) : self_type() { + this->Assign(aChar); + } + + explicit nsTAutoStringN(const char_type* aData, + size_type aLength = size_type(-1)) + : self_type() { + this->Assign(aData, aLength); + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + explicit nsTAutoStringN(char16ptr_t aData, size_type aLength = size_type(-1)) + : self_type(static_cast<const char16_t*>(aData), aLength) {} +#endif + + nsTAutoStringN(const self_type& aStr) : self_type() { this->Assign(aStr); } + + nsTAutoStringN(self_type&& aStr) : self_type() { + this->Assign(std::move(aStr)); + } + + explicit nsTAutoStringN(const substring_type& aStr) : self_type() { + this->Assign(aStr); + } + + explicit nsTAutoStringN(substring_type&& aStr) : self_type() { + this->Assign(std::move(aStr)); + } + + MOZ_IMPLICIT nsTAutoStringN(const substring_tuple_type& aTuple) + : self_type() { + this->Assign(aTuple); + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) { + this->Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) { + this->Assign(aData); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + self_type& operator=(char16ptr_t aStr) { + this->Assign(aStr); + return *this; + } +#endif + self_type& operator=(const self_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(self_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(substring_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) { + this->Assign(aTuple); + return *this; + } + + static const size_t kStorageSize = N; + + protected: + friend class nsTSubstring<T>; + + const LengthStorage mInlineCapacity; + + private: + char_type mStorage[N]; +}; + +// Externs for the most common nsTAutoStringN variations. +extern template class nsTAutoStringN<char, 64>; +extern template class nsTAutoStringN<char16_t, 64>; + +// +// nsAutoString stores pointers into itself which are invalidated when an +// nsTArray is resized, so nsTArray must not be instantiated with nsAutoString +// elements! +// +template <class E> +class nsTArrayElementTraits; +template <typename T> +class nsTArrayElementTraits<nsTAutoString<T>> { + public: + template <class A> + struct Dont_Instantiate_nsTArray_of; + template <class A> + struct Instead_Use_nsTArray_of; + + static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Construct( + Instead_Use_nsTArray_of<nsTString<T>>* aE) { + return 0; + } + template <class A> + static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Construct( + Instead_Use_nsTArray_of<nsTString<T>>* aE, const A& aArg) { + return 0; + } + template <class... Args> + static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Construct( + Instead_Use_nsTArray_of<nsTString<T>>* aE, Args&&... aArgs) { + return 0; + } + static Dont_Instantiate_nsTArray_of<nsTAutoString<T>>* Destruct( + Instead_Use_nsTArray_of<nsTString<T>>* aE) { + return 0; + } +}; + +/** + * getter_Copies support for adopting raw string out params that are + * heap-allocated, e.g.: + * + * char* gStr; + * void GetBlah(char** aStr) + * { + * *aStr = strdup(gStr); + * } + * + * // This works, but is clumsy. + * void Inelegant() + * { + * char* buf; + * GetBlah(&buf); + * nsCString str; + * str.Adopt(buf); + * // ... + * } + * + * // This is nicer. + * void Elegant() + * { + * nsCString str; + * GetBlah(getter_Copies(str)); + * // ... + * } + */ +template <typename T> +class MOZ_STACK_CLASS nsTGetterCopies { + public: + typedef T char_type; + + explicit nsTGetterCopies(nsTSubstring<T>& aStr) + : mString(aStr), mData(nullptr) {} + + ~nsTGetterCopies() { + mString.Adopt(mData); // OK if mData is null + } + + operator char_type**() { return &mData; } + + private: + nsTSubstring<T>& mString; + char_type* mData; +}; + +// See the comment above nsTGetterCopies_CharT for how to use this. +template <typename T> +inline nsTGetterCopies<T> getter_Copies(nsTSubstring<T>& aString) { + return nsTGetterCopies<T>(aString); +} + +#endif diff --git a/xpcom/string/nsTStringComparator.cpp b/xpcom/string/nsTStringComparator.cpp new file mode 100644 index 0000000000..801a3623b9 --- /dev/null +++ b/xpcom/string/nsTStringComparator.cpp @@ -0,0 +1,91 @@ +/* -*- 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 "plstr.h" + +template <typename T> +int NS_FASTCALL Compare(const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs, + const nsTStringComparator<T> comp) { + typedef typename nsTSubstring<T>::size_type size_type; + typedef typename nsTSubstring<T>::const_iterator const_iterator; + + if (&aLhs == &aRhs) { + return 0; + } + + const_iterator leftIter, rightIter; + aLhs.BeginReading(leftIter); + aRhs.BeginReading(rightIter); + + size_type lLength = aLhs.Length(); + size_type rLength = aRhs.Length(); + size_type lengthToCompare = XPCOM_MIN(lLength, rLength); + + int result; + if ((result = comp(leftIter.get(), rightIter.get(), lengthToCompare, + lengthToCompare)) == 0) { + if (lLength < rLength) { + result = -1; + } else if (rLength < lLength) { + result = 1; + } else { + result = 0; + } + } + + return result; +} + +template int NS_FASTCALL Compare<char>( + mozilla::detail::nsTStringRepr<char> const&, + mozilla::detail::nsTStringRepr<char> const&, nsTStringComparator<char>); + +template int NS_FASTCALL +Compare<char16_t>(mozilla::detail::nsTStringRepr<char16_t> const&, + mozilla::detail::nsTStringRepr<char16_t> const&, + nsTStringComparator<char16_t>); + +template <typename T> +int nsTDefaultStringComparator(const T* aLhs, const T* aRhs, size_t aLLength, + size_t aRLength) { + return aLLength == aRLength ? nsCharTraits<T>::compare(aLhs, aRhs, aLLength) + : (aLLength > aRLength) ? 1 + : -1; +} + +template int nsTDefaultStringComparator(const char*, const char*, size_t, + size_t); +template int nsTDefaultStringComparator(const char16_t*, const char16_t*, + size_t, size_t); + +int nsCaseInsensitiveCStringComparator(const char* aLhs, const char* aRhs, + size_t aLhsLength, size_t aRhsLength) { +#if defined(LIBFUZZER) && defined(LINUX) + // Make sure libFuzzer can see this string compare by calling the POSIX + // native function which is intercepted. We also call this if the lengths + // don't match so libFuzzer can at least see a partial string, but we throw + // away the result afterwards again. + int32_t result = + int32_t(strncasecmp(aLhs, aRhs, std::min(aLhsLength, aRhsLength))); + + if (aLhsLength != aRhsLength) { + return (aLhsLength > aRhsLength) ? 1 : -1; + } +#else + if (aLhsLength != aRhsLength) { + return (aLhsLength > aRhsLength) ? 1 : -1; + } + int32_t result = int32_t(PL_strncasecmp(aLhs, aRhs, aLhsLength)); +#endif + // Egads. PL_strncasecmp is returning *very* negative numbers. + // Some folks expect -1,0,1, so let's temper its enthusiasm. + if (result < 0) { + result = -1; + } + return result; +} diff --git a/xpcom/string/nsTStringHasher.h b/xpcom/string/nsTStringHasher.h new file mode 100644 index 0000000000..7b3f42ba58 --- /dev/null +++ b/xpcom/string/nsTStringHasher.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 nsTStringHasher_h___ +#define nsTStringHasher_h___ + +#include "mozilla/HashTable.h" // mozilla::{DefaultHasher, HashNumber, HashString} + +namespace mozilla { + +template <typename T> +struct DefaultHasher<nsTString<T>> { + using Key = nsTString<T>; + using Lookup = nsTString<T>; + + static mozilla::HashNumber hash(const Lookup& aLookup) { + return mozilla::HashString(aLookup.get()); + } + + static bool match(const Key& aKey, const Lookup& aLookup) { + return aKey.Equals(aLookup); + } +}; + +} // namespace mozilla + +#endif // !defined(nsTStringHasher_h___) diff --git a/xpcom/string/nsTStringRepr.cpp b/xpcom/string/nsTStringRepr.cpp new file mode 100644 index 0000000000..405696fd2b --- /dev/null +++ b/xpcom/string/nsTStringRepr.cpp @@ -0,0 +1,273 @@ +/* -*- 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 "nsTStringRepr.h" + +#include "double-conversion/string-to-double.h" +#include "mozilla/FloatingPoint.h" +#include "nsError.h" +#include "nsString.h" + +namespace mozilla::detail { + +template <typename T> +typename nsTStringRepr<T>::char_type nsTStringRepr<T>::First() const { + MOZ_RELEASE_ASSERT(this->mLength > 0, "|First()| called on an empty string"); + return this->mData[0]; +} + +template <typename T> +typename nsTStringRepr<T>::char_type nsTStringRepr<T>::Last() const { + MOZ_RELEASE_ASSERT(this->mLength > 0, "|Last()| called on an empty string"); + return this->mData[this->mLength - 1]; +} + +template <typename T> +bool nsTStringRepr<T>::Equals(const self_type& aStr) const { + return this->mLength == aStr.mLength && + char_traits::compare(this->mData, aStr.mData, this->mLength) == 0; +} + +template <typename T> +bool nsTStringRepr<T>::Equals(const self_type& aStr, + comparator_type aComp) const { + return this->mLength == aStr.mLength && + aComp(this->mData, aStr.mData, this->mLength, aStr.mLength) == 0; +} + +template <typename T> +bool nsTStringRepr<T>::Equals(const substring_tuple_type& aTuple) const { + return Equals(substring_type(aTuple)); +} + +template <typename T> +bool nsTStringRepr<T>::Equals(const substring_tuple_type& aTuple, + comparator_type aComp) const { + return Equals(substring_type(aTuple), aComp); +} + +template <typename T> +bool nsTStringRepr<T>::Equals(const char_type* aData) const { + // unfortunately, some callers pass null :-( + if (!aData) { + MOZ_ASSERT_UNREACHABLE("null data pointer"); + return this->mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return this->mLength == length && + char_traits::compare(this->mData, aData, this->mLength) == 0; +} + +template <typename T> +bool nsTStringRepr<T>::Equals(const char_type* aData, + comparator_type aComp) const { + // unfortunately, some callers pass null :-( + if (!aData) { + MOZ_ASSERT_UNREACHABLE("null data pointer"); + return this->mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return this->mLength == length && + aComp(this->mData, aData, this->mLength, length) == 0; +} + +template <typename T> +bool nsTStringRepr<T>::EqualsASCII(const char* aData, size_type aLen) const { + return this->mLength == aLen && + char_traits::compareASCII(this->mData, aData, aLen) == 0; +} + +template <typename T> +bool nsTStringRepr<T>::EqualsASCII(const char* aData) const { + return char_traits::compareASCIINullTerminated(this->mData, this->mLength, + aData) == 0; +} + +template <typename T> +bool nsTStringRepr<T>::EqualsLatin1(const char* aData, + const size_type aLength) const { + return (this->mLength == aLength) && + char_traits::equalsLatin1(this->mData, aData, aLength); +} + +template <typename T> +bool nsTStringRepr<T>::LowerCaseEqualsASCII(const char* aData, + size_type aLen) const { + return this->mLength == aLen && + char_traits::compareLowerCaseToASCII(this->mData, aData, aLen) == 0; +} + +template <typename T> +bool nsTStringRepr<T>::LowerCaseEqualsASCII(const char* aData) const { + return char_traits::compareLowerCaseToASCIINullTerminated( + this->mData, this->mLength, aData) == 0; +} + +template <typename T> +int32_t nsTStringRepr<T>::Find(const string_view& aString, + index_type aOffset) const { + auto idx = View().find(aString, aOffset); + return idx == string_view::npos ? kNotFound : idx; +} + +template <typename T> +int32_t nsTStringRepr<T>::LowerCaseFindASCII(const std::string_view& aString, + index_type aOffset) const { + if (aOffset > Length()) { + return kNotFound; + } + auto begin = BeginReading(); + auto end = EndReading(); + auto it = + std::search(begin + aOffset, end, aString.begin(), aString.end(), + [](char_type l, char r) { + MOZ_ASSERT(!(r & ~0x7F), "Unexpected non-ASCII character"); + MOZ_ASSERT(char_traits::ASCIIToLower(r) == char_type(r), + "Search string must be ASCII lowercase"); + return char_traits::ASCIIToLower(l) == char_type(r); + }); + return it == end ? kNotFound : std::distance(begin, it); +} + +template <typename T> +int32_t nsTStringRepr<T>::RFind(const string_view& aString) const { + auto idx = View().rfind(aString); + return idx == string_view::npos ? kNotFound : idx; +} + +template <typename T> +typename nsTStringRepr<T>::size_type nsTStringRepr<T>::CountChar( + char_type aChar) const { + return std::count(BeginReading(), EndReading(), aChar); +} + +template <typename T> +int32_t nsTStringRepr<T>::FindChar(char_type aChar, index_type aOffset) const { + auto idx = View().find(aChar, aOffset); + return idx == string_view::npos ? kNotFound : idx; +} + +template <typename T> +int32_t nsTStringRepr<T>::RFindChar(char_type aChar, int32_t aOffset) const { + auto idx = View().rfind(aChar, aOffset != -1 ? aOffset : string_view::npos); + return idx == string_view::npos ? kNotFound : idx; +} + +template <typename T> +int32_t nsTStringRepr<T>::FindCharInSet(const string_view& aSet, + index_type aOffset) const { + auto idx = View().find_first_of(aSet, aOffset); + return idx == string_view::npos ? kNotFound : idx; +} + +template <typename T> +int32_t nsTStringRepr<T>::RFindCharInSet(const string_view& aSet, + int32_t aOffset) const { + auto idx = + View().find_last_of(aSet, aOffset != -1 ? aOffset : string_view::npos); + return idx == string_view::npos ? kNotFound : idx; +} + +template <typename T> +bool nsTStringRepr<T>::EqualsIgnoreCase(const std::string_view& aString) const { + return std::equal(BeginReading(), EndReading(), aString.begin(), + aString.end(), [](char_type l, char r) { + return char_traits::ASCIIToLower(l) == + char_traits::ASCIIToLower(char_type(r)); + }); +} + +// We can't use the method `StringToDoubleConverter::ToDouble` due to linking +// issues on Windows as it's in mozglue. Instead, implement the selection logic +// using an overload set. +// +// StringToFloat is used instead of StringToDouble for floats due to differences +// in rounding behaviour. +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char* aData, size_t aLength, int* aProcessed, float* aResult) { + *aResult = aConverter.StringToFloat(aData, aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char* aData, size_t aLength, int* aProcessed, double* aResult) { + *aResult = aConverter.StringToDouble(aData, aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char16_t* aData, size_t aLength, int* aProcessed, float* aResult) { + *aResult = aConverter.StringToFloat(reinterpret_cast<const uc16*>(aData), + aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char16_t* aData, size_t aLength, int* aProcessed, double* aResult) { + *aResult = aConverter.StringToDouble(reinterpret_cast<const uc16*>(aData), + aLength, aProcessed); +} + +template <typename FloatT, typename CharT> +static FloatT ParseFloatingPoint(const nsTStringRepr<CharT>& aString, + bool aAllowTrailingChars, + nsresult* aErrorCode) { + // Performs conversion to double following the "rules for parsing + // floating-point number values" from the HTML standard. + // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values + // + // This behaviour allows for leading spaces, and will not generate infinity or + // NaN values except in error conditions. + int flags = double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES; + if (aAllowTrailingChars) { + flags |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK; + } + double_conversion::StringToDoubleConverter converter( + flags, mozilla::UnspecifiedNaN<double>(), + mozilla::UnspecifiedNaN<double>(), nullptr, nullptr); + + FloatT result; + int processed; + StringToFP(converter, aString.Data(), aString.Length(), &processed, &result); + + *aErrorCode = std::isfinite(result) ? NS_OK : NS_ERROR_ILLEGAL_VALUE; + return result; +} + +template <typename T> +double nsTStringRepr<T>::ToDouble(nsresult* aErrorCode) const { + return ParseFloatingPoint<double, T>(*this, /* aAllowTrailingChars */ false, + aErrorCode); +} + +template <typename T> +double nsTStringRepr<T>::ToDoubleAllowTrailingChars( + nsresult* aErrorCode) const { + return ParseFloatingPoint<double, T>(*this, /* aAllowTrailingChars */ true, + aErrorCode); +} + +template <typename T> +float nsTStringRepr<T>::ToFloat(nsresult* aErrorCode) const { + return ParseFloatingPoint<float, T>(*this, /* aAllowTrailingChars */ false, + aErrorCode); +} + +template <typename T> +float nsTStringRepr<T>::ToFloatAllowTrailingChars(nsresult* aErrorCode) const { + return ParseFloatingPoint<float, T>(*this, /* aAllowTrailingChars */ true, + aErrorCode); +} + +} // namespace mozilla::detail + +template class mozilla::detail::nsTStringRepr<char>; +template class mozilla::detail::nsTStringRepr<char16_t>; diff --git a/xpcom/string/nsTStringRepr.h b/xpcom/string/nsTStringRepr.h new file mode 100644 index 0000000000..81a01fb418 --- /dev/null +++ b/xpcom/string/nsTStringRepr.h @@ -0,0 +1,558 @@ +/* -*- 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 nsTStringRepr_h +#define nsTStringRepr_h + +#include <limits> +#include <string_view> +#include <type_traits> // std::enable_if + +#include "mozilla/Char16.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/fallible.h" +#include "nsStringBuffer.h" +#include "nsStringFlags.h" +#include "nsStringFwd.h" +#include "nsStringIterator.h" +#include "nsCharTraits.h" + +template <typename T> +class nsTSubstringTuple; + +namespace mozilla { + +// This is mainly intended to be used in the context of nsTStrings where +// we want to enable a specific function only for a given character class. In +// order for this technique to work the member function needs to be templated +// on something other than `T`. We keep this in the `mozilla` namespace rather +// than `nsTStringRepr` as it's intentionally not dependent on `T`. +// +// The 'T' at the end of `Char[16]OnlyT` is refering to the `::type` portion +// which will only be defined if the character class is correct. This is similar +// to `std::enable_if_t` which is available in C++14, but not C++11. +// +// `CharType` is generally going to be a shadowed type of `T`. +// +// Example usage of a function that will only be defined if `T` == `char`: +// +// template <typename T> +// class nsTSubstring : public nsTStringRepr<T> { +// template <typename Q = T, typename EnableForChar = typename CharOnlyT<Q>> +// int Foo() { return 42; } +// }; +// +// Please note that we had to use a separate type `Q` for this to work. You +// will get a semi-decent compiler error if you use `T` directly. + +template <typename CharType> +using CharOnlyT = + typename std::enable_if<std::is_same<char, CharType>::value>::type; + +template <typename CharType> +using Char16OnlyT = + typename std::enable_if<std::is_same<char16_t, CharType>::value>::type; + +namespace detail { + +// nsTStringLengthStorage is a helper class which holds the string's length and +// provides getters and setters for converting to and from `size_t`. This is +// done to allow the length to be stored in a `uint32_t` using assertions. +template <typename T> +class nsTStringLengthStorage { + public: + // The maximum byte capacity for a `nsTString` must fit within an `int32_t`, + // with enough room for a trailing null, as consumers often cast `Length()` + // and `Capacity()` to smaller types like `int32_t`. + static constexpr size_t kMax = + size_t{std::numeric_limits<int32_t>::max()} / sizeof(T) - 1; + static_assert( + (kMax + 1) * sizeof(T) <= std::numeric_limits<int32_t>::max(), + "nsTString's maximum length, including the trailing null, must fit " + "within `int32_t`, as callers will cast to `int32_t` occasionally"); + static_assert(((CheckedInt<uint32_t>{kMax} + 1) * sizeof(T) + + sizeof(nsStringBuffer)) + .isValid(), + "Math required to allocate a nsStringBuffer for a " + "maximum-capacity string must not overflow uint32_t"); + + // Implicit conversion and assignment from `size_t` which assert that the + // value is in-range. + MOZ_IMPLICIT constexpr nsTStringLengthStorage(size_t aLength) + : mLength(static_cast<uint32_t>(aLength)) { + MOZ_RELEASE_ASSERT(aLength <= kMax, "string is too large"); + } + constexpr nsTStringLengthStorage& operator=(size_t aLength) { + MOZ_RELEASE_ASSERT(aLength <= kMax, "string is too large"); + mLength = static_cast<uint32_t>(aLength); + return *this; + } + MOZ_IMPLICIT constexpr operator size_t() const { return mLength; } + + private: + uint32_t mLength = 0; +}; + +// nsTStringRepr defines a string's memory layout and some accessor methods. +// This class exists so that nsTLiteralString can avoid inheriting +// nsTSubstring's destructor. All methods on this class must be const because +// literal strings are not writable. +// +// This class is an implementation detail and should not be instantiated +// directly, nor used in any way outside of the string code itself. It is +// buried in a namespace to discourage its use in function parameters. +// If you need to take a parameter, use [const] ns[C]Substring&. +// If you need to instantiate a string, use ns[C]String or descendents. +// +// NAMES: +// nsStringRepr for wide characters +// nsCStringRepr for narrow characters +template <typename T> +class nsTStringRepr { + public: + typedef mozilla::fallible_t fallible_t; + + typedef T char_type; + + typedef nsCharTraits<char_type> char_traits; + typedef typename char_traits::incompatible_char_type incompatible_char_type; + + typedef nsTStringRepr<T> self_type; + typedef self_type base_string_type; + + typedef nsTSubstring<T> substring_type; + typedef nsTSubstringTuple<T> substring_tuple_type; + + typedef nsReadingIterator<char_type> const_iterator; + typedef char_type* iterator; + + typedef nsTStringComparator<char_type> comparator_type; + + typedef const char_type* const_char_iterator; + + typedef std::basic_string_view<char_type> string_view; + + typedef size_t index_type; + typedef size_t size_type; + + // These are only for internal use within the string classes: + typedef StringDataFlags DataFlags; + typedef StringClassFlags ClassFlags; + typedef nsTStringLengthStorage<T> LengthStorage; + + // Reading iterators. + constexpr const_char_iterator BeginReading() const { return mData; } + constexpr const_char_iterator EndReading() const { return mData + mLength; } + + // Deprecated reading iterators. + const_iterator& BeginReading(const_iterator& aIter) const { + aIter.mStart = mData; + aIter.mEnd = mData + mLength; + aIter.mPosition = aIter.mStart; + return aIter; + } + + const_iterator& EndReading(const_iterator& aIter) const { + aIter.mStart = mData; + aIter.mEnd = mData + mLength; + aIter.mPosition = aIter.mEnd; + return aIter; + } + + const_char_iterator& BeginReading(const_char_iterator& aIter) const { + return aIter = mData; + } + + const_char_iterator& EndReading(const_char_iterator& aIter) const { + return aIter = mData + mLength; + } + + // Accessors. + template <typename U, typename Dummy> + struct raw_type { + typedef const U* type; + }; +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Dummy> + struct raw_type<char16_t, Dummy> { + typedef char16ptr_t type; + }; +#endif + + // Returns pointer to string data (not necessarily null-terminated) + constexpr typename raw_type<T, int>::type Data() const { return mData; } + + constexpr size_type Length() const { return static_cast<size_type>(mLength); } + + constexpr string_view View() const { return string_view(Data(), Length()); } + + constexpr operator string_view() const { return View(); } + + constexpr DataFlags GetDataFlags() const { return mDataFlags; } + + constexpr bool IsEmpty() const { return mLength == 0; } + + constexpr bool IsLiteral() const { + return !!(mDataFlags & DataFlags::LITERAL); + } + + constexpr bool IsVoid() const { return !!(mDataFlags & DataFlags::VOIDED); } + + constexpr bool IsTerminated() const { + return !!(mDataFlags & DataFlags::TERMINATED); + } + + constexpr char_type CharAt(index_type aIndex) const { + NS_ASSERTION(aIndex < Length(), "index exceeds allowable range"); + return mData[aIndex]; + } + + constexpr char_type operator[](index_type aIndex) const { + return CharAt(aIndex); + } + + char_type First() const; + + char_type Last() const; + + // Equality. + bool NS_FASTCALL Equals(const self_type&) const; + bool NS_FASTCALL Equals(const self_type&, comparator_type) const; + + bool NS_FASTCALL Equals(const substring_tuple_type& aTuple) const; + bool NS_FASTCALL Equals(const substring_tuple_type& aTuple, + comparator_type) const; + + bool NS_FASTCALL Equals(const char_type* aData) const; + bool NS_FASTCALL Equals(const char_type* aData, comparator_type) const; + + /** + * Compare this string and another ASCII-case-insensitively. + * + * This method is similar to `LowerCaseEqualsASCII` however both strings are + * lowercased, meaning that `aString` need not be all lowercase. + * + * @param aString is the string to check + * @return boolean + */ + bool EqualsIgnoreCase(const std::string_view& aString) const; + +#ifdef __cpp_char8_t + template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>> + bool NS_FASTCALL Equals(const char8_t* aData) const { + return Equals(reinterpret_cast<const char*>(aData)); + } + + template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>> + bool NS_FASTCALL Equals(const char8_t* aData, comparator_type aComp) const { + return Equals(reinterpret_cast<const char*>(aData), aComp); + } +#endif + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = Char16OnlyT<Q>> + bool NS_FASTCALL Equals(char16ptr_t aData) const { + return Equals(static_cast<const char16_t*>(aData)); + } + template <typename Q = T, typename EnableIfChar16 = Char16OnlyT<Q>> + bool NS_FASTCALL Equals(char16ptr_t aData, comparator_type aComp) const { + return Equals(static_cast<const char16_t*>(aData), aComp); + } +#endif + + // An efficient comparison with ASCII that can be used even + // for wide strings. Call this version when you know the + // length of 'data'. + bool NS_FASTCALL EqualsASCII(const char* aData, size_type aLen) const; + // An efficient comparison with ASCII that can be used even + // for wide strings. Call this version when 'data' is + // null-terminated. + bool NS_FASTCALL EqualsASCII(const char* aData) const; + + // An efficient comparison with Latin1 characters that can be used even for + // wide strings. + bool EqualsLatin1(const char* aData, size_type aLength) const; + + // EqualsLiteral must ONLY be called with an actual literal string, or + // a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use EqualsASCII for other char array variables. + // (Although this method may happen to produce expected results for other + // char arrays that have bound one greater than the sequence of interest, + // such use is discouraged for reasons of readability and maintainability.) + // The template trick to acquire the array bound at compile time without + // using a macro is due to Corey Kosak, with much thanks. + template <int N> + inline bool EqualsLiteral(const char (&aStr)[N]) const { + return EqualsASCII(aStr, N - 1); + } + + // EqualsLiteral must ONLY be called with an actual literal string, or + // a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use EqualsASCII for other char array variables. + // (Although this method may happen to produce expected results for other + // char arrays that have bound one greater than the sequence of interest, + // such use is discouraged for reasons of readability and maintainability.) + // The template trick to acquire the array bound at compile time without + // using a macro is due to Corey Kosak, with much thanks. + template <size_t N, typename = std::enable_if_t<!std::is_same_v< + const char (&)[N], const char_type (&)[N]>>> + inline bool EqualsLiteral(const char_type (&aStr)[N]) const { + return *this == nsTLiteralString<char_type>(aStr); + } + + // The LowerCaseEquals methods compare the ASCII-lowercase version of + // this string (lowercasing only ASCII uppercase characters) to some + // ASCII/Literal string. The ASCII string is *not* lowercased for + // you. If you compare to an ASCII or literal string that contains an + // uppercase character, it is guaranteed to return false. We will + // throw assertions too. + bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData, + size_type aLen) const; + bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData) const; + + // LowerCaseEqualsLiteral must ONLY be called with an actual literal string, + // or a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use LowerCaseEqualsASCII for other char array variables. + // (Although this method may happen to produce expected results for other + // char arrays that have bound one greater than the sequence of interest, + // such use is discouraged for reasons of readability and maintainability.) + template <int N> + bool LowerCaseEqualsLiteral(const char (&aStr)[N]) const { + return LowerCaseEqualsASCII(aStr, N - 1); + } + + // Returns true if this string overlaps with the given string fragment. + bool IsDependentOn(const char_type* aStart, const char_type* aEnd) const { + // If it _isn't_ the case that one fragment starts after the other ends, + // or ends before the other starts, then, they conflict: + // + // !(f2.begin >= f1.aEnd || f2.aEnd <= f1.begin) + // + // Simplified, that gives us (To avoid relying on Undefined Behavior + // from comparing pointers from different allocations (which in + // principle gives the optimizer the permission to assume elsewhere + // that the pointers are from the same allocation), the comparisons + // are done on integers, which merely relies on implementation-defined + // behavior of converting pointers to integers. std::less and + // std::greater implementations don't actually provide the guarantees + // that they should.): + return (reinterpret_cast<uintptr_t>(aStart) < + reinterpret_cast<uintptr_t>(mData + mLength) && + reinterpret_cast<uintptr_t>(aEnd) > + reinterpret_cast<uintptr_t>(mData)); + } + + /** + * Search for the given substring within this string. + * + * @param aString is substring to be sought in this + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t Find(const string_view& aString, index_type aOffset = 0) const; + + // Previously there was an overload of `Find()` which took a bool second + // argument. Avoid issues by explicitly preventing that overload. + // TODO: Remove this at some point. + template <typename I, + typename = std::enable_if_t<!std::is_same_v<I, index_type> && + std::is_convertible_v<I, index_type>>> + int32_t Find(const string_view& aString, I aOffset) const { + static_assert(!std::is_same_v<I, bool>, "offset must not be `bool`"); + return Find(aString, static_cast<index_type>(aOffset)); + } + + /** + * Search for the given ASCII substring within this string, ignoring case. + * + * @param aString is substring to be sought in this + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t LowerCaseFindASCII(const std::string_view& aString, + index_type aOffset = 0) const; + + /** + * Scan the string backwards, looking for the given substring. + * + * @param aString is substring to be sought in this + * @return offset in string, or kNotFound + */ + int32_t RFind(const string_view& aString) const; + + size_type CountChar(char_type) const; + + bool Contains(char_type aChar) const { return FindChar(aChar) != kNotFound; } + + /** + * Search for the first instance of a given char within this string + * + * @param aChar is the character to search for + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t FindChar(char_type aChar, index_type aOffset = 0) const; + + /** + * Search for the last instance of a given char within this string + * + * @param aChar is the character to search for + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t RFindChar(char_type aChar, int32_t aOffset = -1) const; + + /** + * This method searches this string for the first character found in + * the given string. + * + * @param aSet contains set of chars to be found + * @param aOffset tells us where in this string to start searching + * (counting from left) + * @return offset in string, or kNotFound + */ + + int32_t FindCharInSet(const string_view& aSet, index_type aOffset = 0) const; + + /** + * This method searches this string for the last character found in + * the given string. + * + * @param aSet contains set of chars to be found + * @param aOffset tells us where in this string to start searching + * (counting from left) + * @return offset in string, or kNotFound + */ + + int32_t RFindCharInSet(const string_view& aSet, int32_t aOffset = -1) const; + + /** + * Perform locale-independent string to double-precision float conversion. + * + * Leading spaces in the string will be ignored. The returned value will be + * finite unless aErrorCode is set to a failed status. + * + * @param aErrorCode will contain error if one occurs + * @return double-precision float rep of string value + */ + double ToDouble(nsresult* aErrorCode) const; + + /** + * Perform locale-independent string to single-precision float conversion. + * + * Leading spaces in the string will be ignored. The returned value will be + * finite unless aErrorCode is set to a failed status. + * + * @param aErrorCode will contain error if one occurs + * @return single-precision float rep of string value + */ + float ToFloat(nsresult* aErrorCode) const; + + /** + * Similar to above ToDouble and ToFloat but allows trailing characters that + * are not converted. + */ + double ToDoubleAllowTrailingChars(nsresult* aErrorCode) const; + float ToFloatAllowTrailingChars(nsresult* aErrorCode) const; + + protected: + nsTStringRepr() = delete; // Never instantiate directly + + constexpr nsTStringRepr(char_type* aData, size_type aLength, + DataFlags aDataFlags, ClassFlags aClassFlags) + : mData(aData), + mLength(aLength), + mDataFlags(aDataFlags), + mClassFlags(aClassFlags) {} + + static constexpr size_type kMaxCapacity = LengthStorage::kMax; + + /** + * Checks if the given capacity is valid for this string type. + */ + [[nodiscard]] static constexpr bool CheckCapacity(size_type aCapacity) { + return aCapacity <= kMaxCapacity; + } + + char_type* mData; + LengthStorage mLength; + DataFlags mDataFlags; + ClassFlags const mClassFlags; +}; + +extern template class nsTStringRepr<char>; +extern template class nsTStringRepr<char16_t>; + +} // namespace detail +} // namespace mozilla + +template <typename T> +int NS_FASTCALL Compare(const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs, + nsTStringComparator<T> = nsTDefaultStringComparator<T>); + +extern template int NS_FASTCALL Compare<char>( + const mozilla::detail::nsTStringRepr<char>&, + const mozilla::detail::nsTStringRepr<char>&, nsTStringComparator<char>); + +extern template int NS_FASTCALL +Compare<char16_t>(const mozilla::detail::nsTStringRepr<char16_t>&, + const mozilla::detail::nsTStringRepr<char16_t>&, + nsTStringComparator<char16_t>); + +template <typename T> +inline constexpr bool operator!=( + const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs) { + return !aLhs.Equals(aRhs); +} + +template <typename T> +inline constexpr bool operator!=(const mozilla::detail::nsTStringRepr<T>& aLhs, + const T* aRhs) { + return !aLhs.Equals(aRhs); +} + +template <typename T> +inline bool operator<(const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs) { + return Compare(aLhs, aRhs) < 0; +} + +template <typename T> +inline bool operator<=(const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs) { + return Compare(aLhs, aRhs) <= 0; +} + +template <typename T> +inline bool operator==(const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs) { + return aLhs.Equals(aRhs); +} + +template <typename T> +inline bool operator==(const mozilla::detail::nsTStringRepr<T>& aLhs, + const T* aRhs) { + return aLhs.Equals(aRhs); +} + +template <typename T> +inline bool operator>=(const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs) { + return Compare(aLhs, aRhs) >= 0; +} + +template <typename T> +inline bool operator>(const mozilla::detail::nsTStringRepr<T>& aLhs, + const mozilla::detail::nsTStringRepr<T>& aRhs) { + return Compare(aLhs, aRhs) > 0; +} + +#endif diff --git a/xpcom/string/nsTSubstring.cpp b/xpcom/string/nsTSubstring.cpp new file mode 100644 index 0000000000..ae9fda73c8 --- /dev/null +++ b/xpcom/string/nsTSubstring.cpp @@ -0,0 +1,1706 @@ +/* -*- 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 "double-conversion/double-conversion.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Printf.h" +#include "mozilla/ResultExtensions.h" + +#include "nsASCIIMask.h" +#include "nsCharTraits.h" +#include "nsISupports.h" +#include "nsString.h" +#include "nsTArray.h" + +#ifdef DEBUG +# include "nsStringStats.h" +#else +# define STRING_STAT_INCREMENT(_s) +#endif + +// It's not worthwhile to reallocate the buffer and memcpy the +// contents over when the size difference isn't large. With +// power-of-two allocation buckets and 64 as the typical inline +// capacity, considering that above 1000 there performance aspects +// of realloc and memcpy seem to be absorbed, relative to the old +// code, by the performance benefits of the new code being exact, +// we need to choose which transitions of 256 to 128, 512 to 256 +// and 1024 to 512 to allow. As a guess, let's pick the middle +// one as the the largest potential transition that we forgo. So +// we'll shrink from 1024 bucket to 512 bucket but not from 512 +// bucket to 256 bucket. We'll decide by comparing the difference +// of capacities. As bucket differences, the differences are 256 +// and 512. Since the capacities have various overheads, we +// can't compare with 256 or 512 exactly but it's easier to +// compare to some number that's between the two, so it's +// far away from either to ignore the overheads. +const uint32_t kNsStringBufferShrinkingThreshold = 384; + +using double_conversion::DoubleToStringConverter; + +// --------------------------------------------------------------------------- + +static const char16_t gNullChar = 0; + +char* const nsCharTraits<char>::sEmptyBuffer = + (char*)const_cast<char16_t*>(&gNullChar); +char16_t* const nsCharTraits<char16_t>::sEmptyBuffer = + const_cast<char16_t*>(&gNullChar); + +// --------------------------------------------------------------------------- + +static void ReleaseData(void* aData, nsAString::DataFlags aFlags) { + if (aFlags & nsAString::DataFlags::REFCOUNTED) { + nsStringBuffer::FromData(aData)->Release(); + } else if (aFlags & nsAString::DataFlags::OWNED) { + // Treat this as destruction of a "StringAdopt" object for leak + // tracking purposes. + MOZ_LOG_DTOR(aData, "StringAdopt", 1); + + free(aData); + STRING_STAT_INCREMENT(AdoptFree); + } + // otherwise, nothing to do. +} + +// --------------------------------------------------------------------------- + +#ifdef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE +template <typename T> +nsTSubstring<T>::nsTSubstring(char_type* aData, size_type aLength, + DataFlags aDataFlags, ClassFlags aClassFlags) + : ::mozilla::detail::nsTStringRepr<T>(aData, aLength, aDataFlags, + aClassFlags) { + AssertValid(); + + if (aDataFlags & DataFlags::OWNED) { + STRING_STAT_INCREMENT(Adopt); + MOZ_LOG_CTOR(this->mData, "StringAdopt", 1); + } +} +#endif /* XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE */ + +/** + * helper function for down-casting a nsTSubstring to an nsTAutoString. + */ +template <typename T> +inline const nsTAutoString<T>* AsAutoString(const nsTSubstring<T>* aStr) { + return static_cast<const nsTAutoString<T>*>(aStr); +} + +template <typename T> +mozilla::Result<mozilla::BulkWriteHandle<T>, nsresult> +nsTSubstring<T>::BulkWrite(size_type aCapacity, size_type aPrefixToPreserve, + bool aAllowShrinking) { + auto r = StartBulkWriteImpl(aCapacity, aPrefixToPreserve, aAllowShrinking); + if (MOZ_UNLIKELY(r.isErr())) { + return r.propagateErr(); + } + return mozilla::BulkWriteHandle<T>(this, r.unwrap()); +} + +template <typename T> +auto nsTSubstring<T>::StartBulkWriteImpl(size_type aCapacity, + size_type aPrefixToPreserve, + bool aAllowShrinking, + size_type aSuffixLength, + size_type aOldSuffixStart, + size_type aNewSuffixStart) + -> mozilla::Result<size_type, nsresult> { + // Note! Capacity does not include room for the terminating null char. + + MOZ_ASSERT(aPrefixToPreserve <= aCapacity, + "Requested preservation of an overlong prefix."); + MOZ_ASSERT(aNewSuffixStart + aSuffixLength <= aCapacity, + "Requesed move of suffix to out-of-bounds location."); + // Can't assert aOldSuffixStart, because mLength may not be valid anymore, + // since this method allows itself to be called more than once. + + // If zero capacity is requested, set the string to the special empty + // string. + if (MOZ_UNLIKELY(!aCapacity)) { + ReleaseData(this->mData, this->mDataFlags); + SetToEmptyBuffer(); + return 0; + } + + // Note! Capacity() returns 0 when the string is immutable. + const size_type curCapacity = Capacity(); + + bool shrinking = false; + + // We've established that aCapacity > 0. + // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we + // need to allocate a new buffer. We cannot use the existing buffer even + // though it might be large enough. + + if (aCapacity <= curCapacity) { + if (aAllowShrinking) { + shrinking = true; + } else { + char_traits::move(this->mData + aNewSuffixStart, + this->mData + aOldSuffixStart, aSuffixLength); + if (aSuffixLength) { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(aNewSuffixStart - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + char_traits::uninitialize( + this->mData + aNewSuffixStart + aSuffixLength, + XPCOM_MIN(curCapacity + 1 - aNewSuffixStart - aSuffixLength, + kNsStringBufferMaxPoison)); + } else { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(curCapacity + 1 - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + } + return curCapacity; + } + } + + char_type* oldData = this->mData; + DataFlags oldFlags = this->mDataFlags; + + char_type* newData; + DataFlags newDataFlags; + size_type newCapacity; + + // If this is an nsTAutoStringN, it's possible that we can use the inline + // buffer. + if ((this->mClassFlags & ClassFlags::INLINE) && + (aCapacity <= AsAutoString(this)->mInlineCapacity)) { + newCapacity = AsAutoString(this)->mInlineCapacity; + newData = (char_type*)AsAutoString(this)->mStorage; + newDataFlags = DataFlags::TERMINATED | DataFlags::INLINE; + } else { + // If |aCapacity > kMaxCapacity|, then our doubling algorithm may not be + // able to allocate it. Just bail out in cases like that. We don't want + // to be allocating 2GB+ strings anyway. + static_assert((sizeof(nsStringBuffer) & 0x1) == 0, + "bad size for nsStringBuffer"); + if (MOZ_UNLIKELY(!this->CheckCapacity(aCapacity))) { + return mozilla::Err(NS_ERROR_OUT_OF_MEMORY); + } + + // We increase our capacity so that the allocated buffer grows + // exponentially, which gives us amortized O(1) appending. Below the + // threshold, we use powers-of-two. Above the threshold, we grow by at + // least 1.125, rounding up to the nearest MiB. + const size_type slowGrowthThreshold = 8 * 1024 * 1024; + + // nsStringBuffer allocates sizeof(nsStringBuffer) + passed size, and + // storageSize below wants extra 1 * sizeof(char_type). + const size_type neededExtraSpace = + sizeof(nsStringBuffer) / sizeof(char_type) + 1; + + size_type temp; + if (aCapacity >= slowGrowthThreshold) { + size_type minNewCapacity = + curCapacity + (curCapacity >> 3); // multiply by 1.125 + temp = XPCOM_MAX(aCapacity, minNewCapacity) + neededExtraSpace; + + // Round up to the next multiple of MiB, but ensure the expected + // capacity doesn't include the extra space required by nsStringBuffer + // and null-termination. + const size_t MiB = 1 << 20; + temp = (MiB * ((temp + MiB - 1) / MiB)) - neededExtraSpace; + } else { + // Round up to the next power of two. + temp = + mozilla::RoundUpPow2(aCapacity + neededExtraSpace) - neededExtraSpace; + } + + newCapacity = XPCOM_MIN(temp, base_string_type::kMaxCapacity); + MOZ_ASSERT(newCapacity >= aCapacity, + "should have hit the early return at the top"); + // Avoid shrinking if the new buffer size is close to the old. Note that + // unsigned underflow is defined behavior. + if ((curCapacity - newCapacity) <= kNsStringBufferShrinkingThreshold && + (this->mDataFlags & DataFlags::REFCOUNTED)) { + MOZ_ASSERT(aAllowShrinking, "How come we didn't return earlier?"); + // We're already close enough to the right size. + newData = oldData; + newCapacity = curCapacity; + } else { + size_type storageSize = (newCapacity + 1) * sizeof(char_type); + // Since we allocate only by powers of 2 we always fit into a full + // mozjemalloc bucket, it's not useful to use realloc, which may spend + // time uselessly copying too much. + nsStringBuffer* newHdr = nsStringBuffer::Alloc(storageSize).take(); + if (newHdr) { + newData = (char_type*)newHdr->Data(); + } else if (shrinking) { + // We're still in a consistent state. + // + // Since shrinking is just a memory footprint optimization, we + // don't propagate OOM if we tried to shrink in order to avoid + // OOM crashes from infallible callers. If we're lucky, soon enough + // a fallible caller reaches OOM and is able to deal or we end up + // disposing of this string before reaching OOM again. + newData = oldData; + newCapacity = curCapacity; + } else { + return mozilla::Err(NS_ERROR_OUT_OF_MEMORY); + } + } + newDataFlags = DataFlags::TERMINATED | DataFlags::REFCOUNTED; + } + + this->mData = newData; + this->mDataFlags = newDataFlags; + + if (oldData == newData) { + char_traits::move(newData + aNewSuffixStart, oldData + aOldSuffixStart, + aSuffixLength); + if (aSuffixLength) { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(aNewSuffixStart - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + char_traits::uninitialize( + this->mData + aNewSuffixStart + aSuffixLength, + XPCOM_MIN(newCapacity + 1 - aNewSuffixStart - aSuffixLength, + kNsStringBufferMaxPoison)); + } else { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(newCapacity + 1 - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + } + } else { + char_traits::copy(newData, oldData, aPrefixToPreserve); + char_traits::copy(newData + aNewSuffixStart, oldData + aOldSuffixStart, + aSuffixLength); + ReleaseData(oldData, oldFlags); + } + + return newCapacity; +} + +template <typename T> +void nsTSubstring<T>::FinishBulkWriteImpl(size_type aLength) { + if (aLength) { + FinishBulkWriteImplImpl(aLength); + } else { + ReleaseData(this->mData, this->mDataFlags); + SetToEmptyBuffer(); + } + AssertValid(); +} + +template <typename T> +void nsTSubstring<T>::Finalize() { + ReleaseData(this->mData, this->mDataFlags); + // this->mData, this->mLength, and this->mDataFlags are purposefully left + // dangling +} + +template <typename T> +bool nsTSubstring<T>::ReplacePrep(index_type aCutStart, size_type aCutLength, + size_type aNewLength) { + aCutLength = XPCOM_MIN(aCutLength, this->mLength - aCutStart); + + mozilla::CheckedInt<size_type> newTotalLen = this->Length(); + newTotalLen += aNewLength; + newTotalLen -= aCutLength; + if (!newTotalLen.isValid()) { + return false; + } + + if (aCutStart == this->mLength && Capacity() > newTotalLen.value()) { + this->mDataFlags &= ~DataFlags::VOIDED; + this->mData[newTotalLen.value()] = char_type(0); + this->mLength = newTotalLen.value(); + return true; + } + + return ReplacePrepInternal(aCutStart, aCutLength, aNewLength, + newTotalLen.value()); +} + +template <typename T> +bool nsTSubstring<T>::ReplacePrepInternal(index_type aCutStart, + size_type aCutLen, size_type aFragLen, + size_type aNewLen) { + size_type newSuffixStart = aCutStart + aFragLen; + size_type oldSuffixStart = aCutStart + aCutLen; + size_type suffixLength = this->mLength - oldSuffixStart; + + mozilla::Result<size_type, nsresult> r = StartBulkWriteImpl( + aNewLen, aCutStart, false, suffixLength, oldSuffixStart, newSuffixStart); + if (r.isErr()) { + return false; + } + FinishBulkWriteImpl(aNewLen); + return true; +} + +template <typename T> +typename nsTSubstring<T>::size_type nsTSubstring<T>::Capacity() const { + // return 0 to indicate an immutable or 0-sized buffer + + size_type capacity; + if (this->mDataFlags & DataFlags::REFCOUNTED) { + // if the string is readonly, then we pretend that it has no capacity. + nsStringBuffer* hdr = nsStringBuffer::FromData(this->mData); + if (hdr->IsReadonly()) { + capacity = 0; + } else { + capacity = (size_t(hdr->StorageSize()) / sizeof(char_type)) - 1; + } + } else if (this->mDataFlags & DataFlags::INLINE) { + MOZ_ASSERT(this->mClassFlags & ClassFlags::INLINE); + capacity = AsAutoString(this)->mInlineCapacity; + } else if (this->mDataFlags & DataFlags::OWNED) { + // we don't store the capacity of an adopted buffer because that would + // require an additional member field. the best we can do is base the + // capacity on our length. remains to be seen if this is the right + // trade-off. + capacity = this->mLength; + } else { + capacity = 0; + } + + return capacity; +} + +template <typename T> +bool nsTSubstring<T>::EnsureMutable(size_type aNewLen) { + if (aNewLen == size_type(-1) || aNewLen == this->mLength) { + if (this->mDataFlags & (DataFlags::INLINE | DataFlags::OWNED)) { + return true; + } + if ((this->mDataFlags & DataFlags::REFCOUNTED) && + !nsStringBuffer::FromData(this->mData)->IsReadonly()) { + return true; + } + + aNewLen = this->mLength; + } + return SetLength(aNewLen, mozilla::fallible); +} + +// --------------------------------------------------------------------------- + +// This version of Assign is optimized for single-character assignment. +template <typename T> +void nsTSubstring<T>::Assign(char_type aChar) { + if (MOZ_UNLIKELY(!Assign(aChar, mozilla::fallible))) { + AllocFailed(1); + } +} + +template <typename T> +bool nsTSubstring<T>::Assign(char_type aChar, const fallible_t&) { + auto r = StartBulkWriteImpl(1, 0, true); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + *this->mData = aChar; + FinishBulkWriteImpl(1); + return true; +} + +template <typename T> +void nsTSubstring<T>::Assign(const char_type* aData, size_type aLength) { + if (MOZ_UNLIKELY(!Assign(aData, aLength, mozilla::fallible))) { + AllocFailed(aLength == size_type(-1) ? char_traits::length(aData) + : aLength); + } +} + +template <typename T> +bool nsTSubstring<T>::Assign(const char_type* aData, + const fallible_t& aFallible) { + return Assign(aData, size_type(-1), aFallible); +} + +template <typename T> +bool nsTSubstring<T>::Assign(const char_type* aData, size_type aLength, + const fallible_t& aFallible) { + if (!aData || aLength == 0) { + Truncate(); + return true; + } + + if (MOZ_UNLIKELY(aLength == size_type(-1))) { + aLength = char_traits::length(aData); + } + + if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) { + return Assign(string_type(aData, aLength), aFallible); + } + + auto r = StartBulkWriteImpl(aLength, 0, true); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copy(this->mData, aData, aLength); + FinishBulkWriteImpl(aLength); + return true; +} + +template <typename T> +void nsTSubstring<T>::AssignASCII(const char* aData, size_type aLength) { + if (MOZ_UNLIKELY(!AssignASCII(aData, aLength, mozilla::fallible))) { + AllocFailed(aLength); + } +} + +template <typename T> +bool nsTSubstring<T>::AssignASCII(const char* aData, size_type aLength, + const fallible_t& aFallible) { + MOZ_ASSERT(aLength != size_type(-1)); + + // A Unicode string can't depend on an ASCII string buffer, + // so this dependence check only applies to CStrings. + if constexpr (std::is_same_v<T, char>) { + if (this->IsDependentOn(aData, aData + aLength)) { + return Assign(string_type(aData, aLength), aFallible); + } + } + + auto r = StartBulkWriteImpl(aLength, 0, true); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copyASCII(this->mData, aData, aLength); + FinishBulkWriteImpl(aLength); + return true; +} + +template <typename T> +void nsTSubstring<T>::AssignLiteral(const char_type* aData, size_type aLength) { + ReleaseData(this->mData, this->mDataFlags); + SetData(const_cast<char_type*>(aData), aLength, + DataFlags::TERMINATED | DataFlags::LITERAL); +} + +template <typename T> +void nsTSubstring<T>::Assign(const self_type& aStr) { + if (!Assign(aStr, mozilla::fallible)) { + AllocFailed(aStr.Length()); + } +} + +template <typename T> +bool nsTSubstring<T>::Assign(const self_type& aStr, + const fallible_t& aFallible) { + // |aStr| could be sharable. We need to check its flags to know how to + // deal with it. + + if (&aStr == this) { + return true; + } + + if (!aStr.mLength) { + Truncate(); + this->mDataFlags |= aStr.mDataFlags & DataFlags::VOIDED; + return true; + } + + if (aStr.mDataFlags & DataFlags::REFCOUNTED) { + // nice! we can avoid a string copy :-) + + // |aStr| should be null-terminated + NS_ASSERTION(aStr.mDataFlags & DataFlags::TERMINATED, + "shared, but not terminated"); + + ReleaseData(this->mData, this->mDataFlags); + + SetData(aStr.mData, aStr.mLength, + DataFlags::TERMINATED | DataFlags::REFCOUNTED); + + // get an owning reference to the this->mData + nsStringBuffer::FromData(this->mData)->AddRef(); + return true; + } + if (aStr.mDataFlags & DataFlags::LITERAL) { + MOZ_ASSERT(aStr.mDataFlags & DataFlags::TERMINATED, "Unterminated literal"); + + AssignLiteral(aStr.mData, aStr.mLength); + return true; + } + + // else, treat this like an ordinary assignment. + return Assign(aStr.Data(), aStr.Length(), aFallible); +} + +template <typename T> +void nsTSubstring<T>::Assign(self_type&& aStr) { + if (!Assign(std::move(aStr), mozilla::fallible)) { + AllocFailed(aStr.Length()); + } +} + +template <typename T> +void nsTSubstring<T>::AssignOwned(self_type&& aStr) { + MOZ_ASSERT(aStr.mDataFlags & (DataFlags::REFCOUNTED | DataFlags::OWNED), + "neither shared nor owned"); + + // If they have a REFCOUNTED or OWNED buffer, we can avoid a copy - so steal + // their buffer and reset them to the empty string. + + // |aStr| should be null-terminated + MOZ_ASSERT(aStr.mDataFlags & DataFlags::TERMINATED, + "shared or owned, but not terminated"); + + ReleaseData(this->mData, this->mDataFlags); + + SetData(aStr.mData, aStr.mLength, aStr.mDataFlags); + aStr.SetToEmptyBuffer(); +} + +template <typename T> +bool nsTSubstring<T>::Assign(self_type&& aStr, const fallible_t& aFallible) { + // We're moving |aStr| in this method, so we need to try to steal the data, + // and in the fallback perform a copy-assignment followed by a truncation of + // the original string. + + if (&aStr == this) { + NS_WARNING("Move assigning a string to itself?"); + return true; + } + + if (aStr.mDataFlags & (DataFlags::REFCOUNTED | DataFlags::OWNED)) { + AssignOwned(std::move(aStr)); + return true; + } + + // Otherwise treat this as a normal assignment, and truncate the moved string. + // We don't truncate the source string if the allocation failed. + if (!Assign(aStr, aFallible)) { + return false; + } + aStr.Truncate(); + return true; +} + +template <typename T> +void nsTSubstring<T>::Assign(const substring_tuple_type& aTuple) { + if (!Assign(aTuple, mozilla::fallible)) { + AllocFailed(aTuple.Length()); + } +} + +template <typename T> +bool nsTSubstring<T>::AssignNonDependent(const substring_tuple_type& aTuple, + size_type aTupleLength, + const mozilla::fallible_t& aFallible) { + NS_ASSERTION(aTuple.Length() == aTupleLength, "wrong length passed"); + + auto r = StartBulkWriteImpl(aTupleLength); + if (r.isErr()) { + return false; + } + + aTuple.WriteTo(this->mData, aTupleLength); + + FinishBulkWriteImpl(aTupleLength); + return true; +} + +template <typename T> +bool nsTSubstring<T>::Assign(const substring_tuple_type& aTuple, + const fallible_t& aFallible) { + const auto [isDependentOnThis, tupleLength] = + aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength); + if (isDependentOnThis) { + string_type temp; + self_type& tempSubstring = temp; + if (!tempSubstring.AssignNonDependent(aTuple, tupleLength, aFallible)) { + return false; + } + AssignOwned(std::move(temp)); + return true; + } + + return AssignNonDependent(aTuple, tupleLength, aFallible); +} + +template <typename T> +void nsTSubstring<T>::Adopt(char_type* aData, size_type aLength) { + if (aData) { + ReleaseData(this->mData, this->mDataFlags); + + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + SetData(aData, aLength, DataFlags::TERMINATED | DataFlags::OWNED); + + STRING_STAT_INCREMENT(Adopt); + // Treat this as construction of a "StringAdopt" object for leak + // tracking purposes. + MOZ_LOG_CTOR(this->mData, "StringAdopt", 1); + } else { + SetIsVoid(true); + } +} + +// This version of Replace is optimized for single-character replacement. +template <typename T> +void nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) { + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (ReplacePrep(aCutStart, aCutLength, 1)) { + this->mData[aCutStart] = aChar; + } +} + +template <typename T> +bool nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar, const fallible_t&) { + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (!ReplacePrep(aCutStart, aCutLength, 1)) { + return false; + } + + this->mData[aCutStart] = aChar; + + return true; +} + +template <typename T> +void nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength) { + if (!Replace(aCutStart, aCutLength, aData, aLength, mozilla::fallible)) { + AllocFailed(this->Length() - aCutLength + 1); + } +} + +template <typename T> +bool nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength, + const fallible_t& aFallible) { + // unfortunately, some callers pass null :-( + if (!aData) { + aLength = 0; + } else { + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + if (this->IsDependentOn(aData, aData + aLength)) { + nsTAutoString<T> temp(aData, aLength); + return Replace(aCutStart, aCutLength, temp, aFallible); + } + } + + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + bool ok = ReplacePrep(aCutStart, aCutLength, aLength); + if (!ok) { + return false; + } + + if (aLength > 0) { + char_traits::copy(this->mData + aCutStart, aData, aLength); + } + + return true; +} + +template <typename T> +void nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength, + const substring_tuple_type& aTuple) { + const auto [isDependentOnThis, tupleLength] = + aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength); + + if (isDependentOnThis) { + nsTAutoString<T> temp; + if (!temp.AssignNonDependent(aTuple, tupleLength, mozilla::fallible)) { + AllocFailed(tupleLength); + } + Replace(aCutStart, aCutLength, temp); + return; + } + + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (ReplacePrep(aCutStart, aCutLength, tupleLength) && tupleLength > 0) { + aTuple.WriteTo(this->mData + aCutStart, tupleLength); + } +} + +template <typename T> +void nsTSubstring<T>::ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength) { + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (!aCutStart && aCutLength == this->Length() && + !(this->mDataFlags & DataFlags::REFCOUNTED)) { + // Check for REFCOUNTED above to avoid undoing the effect of + // SetCapacity(). + AssignLiteral(aData, aLength); + } else if (ReplacePrep(aCutStart, aCutLength, aLength) && aLength > 0) { + char_traits::copy(this->mData + aCutStart, aData, aLength); + } +} + +template <typename T> +void nsTSubstring<T>::Append(char_type aChar) { + if (MOZ_UNLIKELY(!Append(aChar, mozilla::fallible))) { + AllocFailed(this->mLength + 1); + } +} + +template <typename T> +bool nsTSubstring<T>::Append(char_type aChar, const fallible_t& aFallible) { + size_type oldLen = this->mLength; + size_type newLen = oldLen + 1; // Can't overflow + auto r = StartBulkWriteImpl(newLen, oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + this->mData[oldLen] = aChar; + FinishBulkWriteImpl(newLen); + return true; +} + +template <typename T> +void nsTSubstring<T>::Append(const char_type* aData, size_type aLength) { + if (MOZ_UNLIKELY(!Append(aData, aLength, mozilla::fallible))) { + AllocFailed(this->mLength + (aLength == size_type(-1) + ? char_traits::length(aData) + : aLength)); + } +} + +template <typename T> +bool nsTSubstring<T>::Append(const char_type* aData, size_type aLength, + const fallible_t& aFallible) { + if (MOZ_UNLIKELY(aLength == size_type(-1))) { + aLength = char_traits::length(aData); + } + + if (MOZ_UNLIKELY(!aLength)) { + // Avoid undoing the effect of SetCapacity() if both + // mLength and aLength are zero. + return true; + } + + if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) { + return Append(string_type(aData, aLength), mozilla::fallible); + } + size_type oldLen = this->mLength; + mozilla::CheckedInt<size_type> newLen(oldLen); + newLen += aLength; + if (MOZ_UNLIKELY(!newLen.isValid())) { + return false; + } + auto r = StartBulkWriteImpl(newLen.value(), oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copy(this->mData + oldLen, aData, aLength); + FinishBulkWriteImpl(newLen.value()); + return true; +} + +template <typename T> +void nsTSubstring<T>::AppendASCII(const char* aData, size_type aLength) { + if (MOZ_UNLIKELY(!AppendASCII(aData, aLength, mozilla::fallible))) { + AllocFailed(this->mLength + + (aLength == size_type(-1) ? strlen(aData) : aLength)); + } +} + +template <typename T> +bool nsTSubstring<T>::AppendASCII(const char* aData, + const fallible_t& aFallible) { + return AppendASCII(aData, size_type(-1), aFallible); +} + +template <typename T> +bool nsTSubstring<T>::AppendASCII(const char* aData, size_type aLength, + const fallible_t& aFallible) { + if (MOZ_UNLIKELY(aLength == size_type(-1))) { + aLength = strlen(aData); + } + + if (MOZ_UNLIKELY(!aLength)) { + // Avoid undoing the effect of SetCapacity() if both + // mLength and aLength are zero. + return true; + } + + if constexpr (std::is_same_v<T, char>) { + // 16-bit string can't depend on an 8-bit buffer + if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) { + return Append(string_type(aData, aLength), mozilla::fallible); + } + } + + size_type oldLen = this->mLength; + mozilla::CheckedInt<size_type> newLen(oldLen); + newLen += aLength; + if (MOZ_UNLIKELY(!newLen.isValid())) { + return false; + } + auto r = StartBulkWriteImpl(newLen.value(), oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copyASCII(this->mData + oldLen, aData, aLength); + FinishBulkWriteImpl(newLen.value()); + return true; +} + +template <typename T> +void nsTSubstring<T>::Append(const self_type& aStr) { + if (MOZ_UNLIKELY(!Append(aStr, mozilla::fallible))) { + AllocFailed(this->mLength + aStr.Length()); + } +} + +template <typename T> +bool nsTSubstring<T>::Append(const self_type& aStr, + const fallible_t& aFallible) { + // Check refcounted to avoid undoing the effects of SetCapacity(). + if (MOZ_UNLIKELY(!this->mLength && + !(this->mDataFlags & DataFlags::REFCOUNTED))) { + return Assign(aStr, mozilla::fallible); + } + return Append(aStr.BeginReading(), aStr.Length(), mozilla::fallible); +} + +template <typename T> +void nsTSubstring<T>::Append(const substring_tuple_type& aTuple) { + if (MOZ_UNLIKELY(!Append(aTuple, mozilla::fallible))) { + AllocFailed(this->mLength + aTuple.Length()); + } +} + +template <typename T> +bool nsTSubstring<T>::Append(const substring_tuple_type& aTuple, + const fallible_t& aFallible) { + const auto [isDependentOnThis, tupleLength] = + aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength); + + if (MOZ_UNLIKELY(!tupleLength)) { + // Avoid undoing the effect of SetCapacity() if both + // mLength and tupleLength are zero. + return true; + } + + if (MOZ_UNLIKELY(isDependentOnThis)) { + return Append(string_type(aTuple), aFallible); + } + + size_type oldLen = this->mLength; + mozilla::CheckedInt<size_type> newLen(oldLen); + newLen += tupleLength; + if (MOZ_UNLIKELY(!newLen.isValid())) { + return false; + } + auto r = StartBulkWriteImpl(newLen.value(), oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + aTuple.WriteTo(this->mData + oldLen, tupleLength); + FinishBulkWriteImpl(newLen.value()); + return true; +} + +template <typename T> +void nsTSubstring<T>::SetCapacity(size_type aCapacity) { + if (!SetCapacity(aCapacity, mozilla::fallible)) { + AllocFailed(aCapacity); + } +} + +template <typename T> +bool nsTSubstring<T>::SetCapacity(size_type aCapacity, const fallible_t&) { + size_type length = this->mLength; + // This method can no longer be used to shorten the + // logical length. + size_type capacity = XPCOM_MAX(aCapacity, length); + + auto r = StartBulkWriteImpl(capacity, length, true); + if (r.isErr()) { + return false; + } + + if (MOZ_UNLIKELY(!capacity)) { + // Zero capacity was requested on a zero-length + // string. In this special case, we are pointing + // to the special empty buffer, which is already + // zero-terminated and not writable, so we must + // not attempt to zero-terminate it. + AssertValid(); + return true; + } + + // FinishBulkWriteImpl with argument zero releases + // the heap-allocated buffer. However, SetCapacity() + // is a special case that allows mLength to be zero + // while a heap-allocated buffer exists. + // By calling FinishBulkWriteImplImpl, we skip the + // zero case handling that's inappropriate in the + // SetCapacity() case. + FinishBulkWriteImplImpl(length); + return true; +} + +template <typename T> +void nsTSubstring<T>::SetLength(size_type aLength) { + if (!SetLength(aLength, mozilla::fallible)) { + AllocFailed(aLength); + } +} + +template <typename T> +bool nsTSubstring<T>::SetLength(size_type aLength, + const fallible_t& aFallible) { + size_type preserve = XPCOM_MIN(aLength, this->Length()); + auto r = StartBulkWriteImpl(aLength, preserve, true); + if (r.isErr()) { + return false; + } + + FinishBulkWriteImpl(aLength); + + return true; +} + +template <typename T> +void nsTSubstring<T>::Truncate() { + ReleaseData(this->mData, this->mDataFlags); + SetToEmptyBuffer(); + AssertValid(); +} + +template <typename T> +void nsTSubstring<T>::SetIsVoid(bool aVal) { + if (aVal) { + Truncate(); + this->mDataFlags |= DataFlags::VOIDED; + } else { + this->mDataFlags &= ~DataFlags::VOIDED; + } +} + +template <typename T> +void nsTSubstring<T>::StripChar(char_type aChar) { + if (this->mLength == 0) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(this->mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = this->mData; + char_type* from = this->mData; + char_type* end = this->mData + this->mLength; + + while (from < end) { + char_type theChar = *from++; + if (aChar != theChar) { + *to++ = theChar; + } + } + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template <typename T> +void nsTSubstring<T>::StripChars(const char_type* aChars) { + if (this->mLength == 0) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(this->mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = this->mData; + char_type* from = this->mData; + char_type* end = this->mData + this->mLength; + + while (from < end) { + char_type theChar = *from++; + const char_type* test = aChars; + + for (; *test && *test != theChar; ++test) + ; + + if (!*test) { + // Not stripped, copy this char. + *to++ = theChar; + } + } + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template <typename T> +void nsTSubstring<T>::StripTaggedASCII(const ASCIIMaskArray& aToStrip) { + if (this->mLength == 0) { + return; + } + + size_t untaggedPrefixLength = 0; + for (; untaggedPrefixLength < this->mLength; ++untaggedPrefixLength) { + uint32_t theChar = (uint32_t)this->mData[untaggedPrefixLength]; + if (mozilla::ASCIIMask::IsMasked(aToStrip, theChar)) { + break; + } + } + + if (untaggedPrefixLength == this->mLength) { + return; + } + + if (!EnsureMutable()) { + AllocFailed(this->mLength); + } + + char_type* to = this->mData + untaggedPrefixLength; + char_type* from = to; + char_type* end = this->mData + this->mLength; + + while (from < end) { + uint32_t theChar = (uint32_t)*from++; + // Replacing this with a call to ASCIIMask::IsMasked + // regresses performance somewhat, so leaving it inlined. + if (!mozilla::ASCIIMask::IsMasked(aToStrip, theChar)) { + // Not stripped, copy this char. + *to++ = (char_type)theChar; + } + } + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template <typename T> +void nsTSubstring<T>::StripCRLF() { + // Expanding this call to copy the code from StripTaggedASCII + // instead of just calling it does somewhat help with performance + // but it is not worth it given the duplicated code. + StripTaggedASCII(mozilla::ASCIIMask::MaskCRLF()); +} + +template <typename T> +struct MOZ_STACK_CLASS PrintfAppend : public mozilla::PrintfTarget { + explicit PrintfAppend(nsTSubstring<T>* aString) : mString(aString) {} + + bool append(const char* aStr, size_t aLen) override { + if (aLen == 0) { + return true; + } + + mString->AppendASCII(aStr, aLen); + return true; + } + + private: + nsTSubstring<T>* mString; +}; + +template <typename T> +void nsTSubstring<T>::AppendPrintf(const char* aFormat, ...) { + PrintfAppend<T> appender(this); + va_list ap; + va_start(ap, aFormat); + bool r = appender.vprint(aFormat, ap); + if (!r) { + MOZ_CRASH("Allocation or other failure in PrintfTarget::print"); + } + va_end(ap); +} + +template <typename T> +void nsTSubstring<T>::AppendVprintf(const char* aFormat, va_list aAp) { + PrintfAppend<T> appender(this); + bool r = appender.vprint(aFormat, aAp); + if (!r) { + MOZ_CRASH("Allocation or other failure in PrintfTarget::print"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntDec(int32_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntDec(uint32_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntOct(uint32_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntOct(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntHex(uint32_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntHex(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntDec(int64_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntDec(uint64_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntOct(uint64_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntOct(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template <typename T> +void nsTSubstring<T>::AppendIntHex(uint64_t aInteger) { + PrintfAppend<T> appender(this); + bool r = appender.appendIntHex(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +// Returns the length of the formatted aDouble in aBuf. +static int FormatWithoutTrailingZeros(char (&aBuf)[40], double aDouble, + int aPrecision) { + static const DoubleToStringConverter converter( + DoubleToStringConverter::UNIQUE_ZERO | + DoubleToStringConverter::NO_TRAILING_ZERO | + DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN, + "Infinity", "NaN", 'e', -6, 21, 6, 1); + double_conversion::StringBuilder builder(aBuf, sizeof(aBuf)); + converter.ToPrecision(aDouble, aPrecision, &builder); + int length = builder.position(); + builder.Finalize(); + return length; +} + +template <typename T> +void nsTSubstring<T>::AppendFloat(float aFloat) { + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 6); + AppendASCII(buf, length); +} + +template <typename T> +void nsTSubstring<T>::AppendFloat(double aFloat) { + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 15); + AppendASCII(buf, length); +} + +template <typename T> +size_t nsTSubstring<T>::SizeOfExcludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const { + if (this->mDataFlags & DataFlags::REFCOUNTED) { + return nsStringBuffer::FromData(this->mData) + ->SizeOfIncludingThisIfUnshared(aMallocSizeOf); + } + if (this->mDataFlags & DataFlags::OWNED) { + return aMallocSizeOf(this->mData); + } + + // If we reach here, exactly one of the following must be true: + // - DataFlags::VOIDED is set, and this->mData points to sEmptyBuffer; + // - DataFlags::INLINE is set, and this->mData points to a buffer within a + // string object (e.g. nsAutoString); + // - None of DataFlags::REFCOUNTED, DataFlags::OWNED, DataFlags::INLINE is + // set, and this->mData points to a buffer owned by something else. + // + // In all three cases, we don't measure it. + return 0; +} + +template <typename T> +size_t nsTSubstring<T>::SizeOfExcludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const { + // This is identical to SizeOfExcludingThisIfUnshared except for the + // DataFlags::REFCOUNTED case. + if (this->mDataFlags & DataFlags::REFCOUNTED) { + return nsStringBuffer::FromData(this->mData) + ->SizeOfIncludingThisEvenIfShared(aMallocSizeOf); + } + if (this->mDataFlags & DataFlags::OWNED) { + return aMallocSizeOf(this->mData); + } + return 0; +} + +template <typename T> +size_t nsTSubstring<T>::SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +template <typename T> +size_t nsTSubstring<T>::SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThisEvenIfShared(aMallocSizeOf); +} + +template <typename T> +nsTSubstringSplitter<T> nsTSubstring<T>::Split(const char_type aChar) const { + return nsTSubstringSplitter<T>( + nsTCharSeparatedTokenizerTemplate< + NS_TokenizerIgnoreNothing, T, + nsTokenizerFlags::IncludeEmptyTokenAtEnd>(*this, aChar)); +} + +// Common logic for nsTSubstring<T>::ToInteger and nsTSubstring<T>::ToInteger64. +template <typename T, typename int_type> +int_type ToIntegerCommon(const nsTSubstring<T>& aSrc, nsresult* aErrorCode, + uint32_t aRadix) { + MOZ_ASSERT(aRadix == 10 || aRadix == 16); + + // Initial value, override if we find an integer. + *aErrorCode = NS_ERROR_ILLEGAL_VALUE; + + // Begin by skipping over leading chars that shouldn't be part of the number. + auto cp = aSrc.BeginReading(); + auto endcp = aSrc.EndReading(); + bool negate = false; + bool done = false; + + // NB: For backwards compatibility I'm not going to change this logic but + // it seems really odd. Previously there was logic to auto-detect the + // radix if kAutoDetect was passed in. In practice this value was never + // used, so it pretended to auto detect and skipped some preceding + // letters (excluding valid hex digits) but never used the result. + // + // For example if you pass in "Get the number: 10", aRadix = 10 we'd + // skip the 'G', and then fail to parse "et the number: 10". If aRadix = + // 16 we'd skip the 'G', and parse just 'e' returning 14. + while ((cp < endcp) && (!done)) { + switch (*cp++) { + // clang-format off + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + done = true; + break; + // clang-format on + case '-': + negate = true; + break; + default: + break; + } + } + + if (!done) { + // No base 16 or base 10 digits were found. + return 0; + } + + // Step back. + cp--; + + mozilla::CheckedInt<int_type> result; + + // Now iterate the numeric chars and build our result. + while (cp < endcp) { + auto theChar = *cp++; + if (('0' <= theChar) && (theChar <= '9')) { + result = (aRadix * result) + (theChar - '0'); + } else if ((theChar >= 'A') && (theChar <= 'F')) { + if (10 == aRadix) { + // Invalid base 10 digit, error out. + return 0; + } + result = (aRadix * result) + ((theChar - 'A') + 10); + } else if ((theChar >= 'a') && (theChar <= 'f')) { + if (10 == aRadix) { + // Invalid base 10 digit, error out. + return 0; + } + result = (aRadix * result) + ((theChar - 'a') + 10); + } else if ((('X' == theChar) || ('x' == theChar)) && result == 0) { + // For some reason we support a leading 'x' regardless of radix. For + // example: "000000x500", aRadix = 10 would be parsed as 500 rather + // than 0. + continue; + } else { + // We've encountered a char that's not a legal number or sign and we can + // terminate processing. + break; + } + + if (!result.isValid()) { + // Overflow! + return 0; + } + } + + // Integer found. + *aErrorCode = NS_OK; + + if (negate) { + result = -result; + } + + return result.value(); +} + +template <typename T> +int32_t nsTSubstring<T>::ToInteger(nsresult* aErrorCode, + uint32_t aRadix) const { + return ToIntegerCommon<T, int32_t>(*this, aErrorCode, aRadix); +} + +/** + * nsTSubstring::ToInteger64 + */ +template <typename T> +int64_t nsTSubstring<T>::ToInteger64(nsresult* aErrorCode, + uint32_t aRadix) const { + return ToIntegerCommon<T, int64_t>(*this, aErrorCode, aRadix); +} + +/** + * nsTSubstring::Mid + */ +template <typename T> +typename nsTSubstring<T>::size_type nsTSubstring<T>::Mid( + self_type& aResult, index_type aStartPos, size_type aLengthToCopy) const { + if (aStartPos == 0 && aLengthToCopy >= this->mLength) { + aResult = *this; + } else { + aResult = Substring(*this, aStartPos, aLengthToCopy); + } + + return aResult.mLength; +} + +/** + * nsTSubstring::StripWhitespace + */ + +template <typename T> +void nsTSubstring<T>::StripWhitespace() { + if (!StripWhitespace(mozilla::fallible)) { + this->AllocFailed(this->mLength); + } +} + +template <typename T> +bool nsTSubstring<T>::StripWhitespace(const fallible_t&) { + if (!this->EnsureMutable()) { + return false; + } + + this->StripTaggedASCII(mozilla::ASCIIMask::MaskWhitespace()); + return true; +} + +/** + * nsTSubstring::ReplaceChar,ReplaceSubstring + */ + +template <typename T> +void nsTSubstring<T>::ReplaceChar(char_type aOldChar, char_type aNewChar) { + int32_t i = this->FindChar(aOldChar); + if (i == kNotFound) { + return; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + for (; i != kNotFound; i = this->FindChar(aOldChar, i + 1)) { + this->mData[i] = aNewChar; + } +} + +template <typename T> +void nsTSubstring<T>::ReplaceChar(const string_view& aSet, char_type aNewChar) { + int32_t i = this->FindCharInSet(aSet); + if (i == kNotFound) { + return; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + for (; i != kNotFound; i = this->FindCharInSet(aSet, i + 1)) { + this->mData[i] = aNewChar; + } +} + +template <typename T> +void nsTSubstring<T>::ReplaceSubstring(const char_type* aTarget, + const char_type* aNewValue) { + ReplaceSubstring(nsTDependentString<T>(aTarget), + nsTDependentString<T>(aNewValue)); +} + +template <typename T> +bool nsTSubstring<T>::ReplaceSubstring(const char_type* aTarget, + const char_type* aNewValue, + const fallible_t& aFallible) { + return ReplaceSubstring(nsTDependentString<T>(aTarget), + nsTDependentString<T>(aNewValue), aFallible); +} + +template <typename T> +void nsTSubstring<T>::ReplaceSubstring(const self_type& aTarget, + const self_type& aNewValue) { + if (!ReplaceSubstring(aTarget, aNewValue, mozilla::fallible)) { + // Note that this may wildly underestimate the allocation that failed, as + // we could have been replacing multiple copies of aTarget. + this->AllocFailed(this->mLength + (aNewValue.Length() - aTarget.Length())); + } +} + +template <typename T> +bool nsTSubstring<T>::ReplaceSubstring(const self_type& aTarget, + const self_type& aNewValue, + const fallible_t&) { + struct Segment { + uint32_t mBegin, mLength; + Segment(uint32_t aBegin, uint32_t aLength) + : mBegin(aBegin), mLength(aLength) {} + }; + + if (aTarget.Length() == 0) { + return true; + } + + // Remember all of the non-matching parts. + AutoTArray<Segment, 16> nonMatching; + uint32_t i = 0; + mozilla::CheckedUint32 newLength; + while (true) { + int32_t r = this->Find(aTarget, i); + int32_t until = (r == kNotFound) ? this->Length() - i : r - i; + nonMatching.AppendElement(Segment(i, until)); + newLength += until; + if (r == kNotFound) { + break; + } + + newLength += aNewValue.Length(); + i = r + aTarget.Length(); + if (i >= this->Length()) { + // Add an auxiliary entry at the end of the list to help as an edge case + // for the algorithms below. + nonMatching.AppendElement(Segment(this->Length(), 0)); + break; + } + } + + if (!newLength.isValid()) { + return false; + } + + // If there's only one non-matching segment, then the target string was not + // found, and there's nothing to do. + if (nonMatching.Length() == 1) { + MOZ_ASSERT( + nonMatching[0].mBegin == 0 && nonMatching[0].mLength == this->Length(), + "We should have the correct non-matching segment."); + return true; + } + + // Make sure that we can mutate our buffer. + // Note that we always allocate at least an this->mLength sized buffer, + // because the rest of the algorithm relies on having access to all of the + // original string. In other words, we over-allocate in the shrinking case. + uint32_t oldLen = this->Length(); + auto r = + this->StartBulkWriteImpl(XPCOM_MAX(oldLen, newLength.value()), oldLen); + if (r.isErr()) { + return false; + } + + if (aTarget.Length() >= aNewValue.Length()) { + // In the shrinking case, start filling the buffer from the beginning. + const uint32_t delta = (aTarget.Length() - aNewValue.Length()); + for (i = 1; i < nonMatching.Length(); ++i) { + // When we move the i'th non-matching segment into position, we need to + // account for the characters deleted by the previous |i| replacements by + // subtracting |i * delta|. + const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin; + char_type* destinationSegmentPtr = + this->mData + nonMatching[i].mBegin - i * delta; + // Write the i'th replacement immediately before the new i'th non-matching + // segment. + char_traits::copy(destinationSegmentPtr - aNewValue.Length(), + aNewValue.Data(), aNewValue.Length()); + char_traits::move(destinationSegmentPtr, sourceSegmentPtr, + nonMatching[i].mLength); + } + } else { + // In the growing case, start filling the buffer from the end. + const uint32_t delta = (aNewValue.Length() - aTarget.Length()); + for (i = nonMatching.Length() - 1; i > 0; --i) { + // When we move the i'th non-matching segment into position, we need to + // account for the characters added by the previous |i| replacements by + // adding |i * delta|. + const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin; + char_type* destinationSegmentPtr = + this->mData + nonMatching[i].mBegin + i * delta; + char_traits::move(destinationSegmentPtr, sourceSegmentPtr, + nonMatching[i].mLength); + // Write the i'th replacement immediately before the new i'th non-matching + // segment. + char_traits::copy(destinationSegmentPtr - aNewValue.Length(), + aNewValue.Data(), aNewValue.Length()); + } + } + + // Adjust the length and make sure the string is null terminated. + this->FinishBulkWriteImpl(newLength.value()); + + return true; +} + +/** + * nsTSubstring::Trim + */ + +template <typename T> +void nsTSubstring<T>::Trim(const std::string_view& aSet, bool aTrimLeading, + bool aTrimTrailing, bool aIgnoreQuotes) { + char_type* start = this->mData; + char_type* end = this->mData + this->mLength; + + // skip over quotes if requested + if (aIgnoreQuotes && this->mLength > 2 && + this->mData[0] == this->mData[this->mLength - 1] && + (this->mData[0] == '\'' || this->mData[0] == '"')) { + ++start; + --end; + } + + if (aTrimLeading) { + uint32_t cutStart = start - this->mData; + uint32_t cutLength = 0; + + // walk forward from start to end + for (; start != end; ++start, ++cutLength) { + if ((*start & ~0x7F) || // non-ascii + aSet.find(char(*start)) == std::string_view::npos) { + break; + } + } + + if (cutLength) { + this->Cut(cutStart, cutLength); + + // reset iterators + start = this->mData + cutStart; + end = this->mData + this->mLength - cutStart; + } + } + + if (aTrimTrailing) { + uint32_t cutEnd = end - this->mData; + uint32_t cutLength = 0; + + // walk backward from end to start + --end; + for (; end >= start; --end, ++cutLength) { + if ((*end & ~0x7F) || // non-ascii + aSet.find(char(*end)) == std::string_view::npos) { + break; + } + } + + if (cutLength) { + this->Cut(cutEnd - cutLength, cutLength); + } + } +} + +/** + * nsTSubstring::CompressWhitespace. + */ + +template <typename T> +void nsTSubstring<T>::CompressWhitespace(bool aTrimLeading, + bool aTrimTrailing) { + // Quick exit + if (this->mLength == 0) { + return; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + + const ASCIIMaskArray& mask = mozilla::ASCIIMask::MaskWhitespace(); + + char_type* to = this->mData; + char_type* from = this->mData; + char_type* end = this->mData + this->mLength; + + // Compresses runs of whitespace down to a normal space ' ' and convert + // any whitespace to a normal space. This assumes that whitespace is + // all standard 7-bit ASCII. + bool skipWS = aTrimLeading; + while (from < end) { + uint32_t theChar = *from++; + if (mozilla::ASCIIMask::IsMasked(mask, theChar)) { + if (!skipWS) { + *to++ = ' '; + skipWS = true; + } + } else { + *to++ = theChar; + skipWS = false; + } + } + + // If we need to trim the trailing whitespace, back up one character. + if (aTrimTrailing && skipWS && to > this->mData) { + to--; + } + + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template class nsTSubstring<char>; +template class nsTSubstring<char16_t>; diff --git a/xpcom/string/nsTSubstring.h b/xpcom/string/nsTSubstring.h new file mode 100644 index 0000000000..0b4022823f --- /dev/null +++ b/xpcom/string/nsTSubstring.h @@ -0,0 +1,1454 @@ +/* -*- 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 "nsString.h" + +#ifndef nsTSubstring_h +#define nsTSubstring_h + +#include <iterator> +#include <type_traits> + +#include "mozilla/Casting.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/IntegerTypeTraits.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Span.h" +#include "mozilla/Try.h" +#include "mozilla/Unused.h" + +#include "nsTStringRepr.h" + +#ifndef MOZILLA_INTERNAL_API +# error "Using XPCOM strings is limited to code linked into libxul." +#endif + +// The max number of logically uninitialized code units to +// fill with a marker byte or to mark as unintialized for +// memory checking. (Limited to avoid quadratic behavior.) +const size_t kNsStringBufferMaxPoison = 16; + +class nsStringBuffer; +template <typename T> +class nsTSubstringSplitter; +template <typename T> +class nsTString; +template <typename T> +class nsTSubstring; + +namespace mozilla { + +/** + * This handle represents permission to perform low-level writes + * the storage buffer of a string in a manner that's aware of the + * actual capacity of the storage buffer allocation and that's + * cache-friendly in the sense that the writing of zero terminator + * for C compatibility can happen in linear memory access order + * (i.e. the zero terminator write takes place after writing + * new content to the string as opposed to the zero terminator + * write happening first causing a non-linear memory write for + * cache purposes). + * + * If you requested a prefix to be preserved when starting + * or restarting the bulk write, the prefix is present at the + * start of the buffer exposed by this handle as Span or + * as a raw pointer, and it's your responsibility to start + * writing after after the preserved prefix (which you + * presumably wanted not to overwrite since you asked for + * it to be preserved). + * + * In a success case, you must call Finish() with the new + * length of the string. In failure cases, it's OK to return + * early from the function whose local variable this handle is. + * The destructor of this class takes care of putting the + * string in a valid and mostly harmless state in that case + * by setting the value of a non-empty string to a single + * REPLACEMENT CHARACTER or in the case of nsACString that's + * too short for a REPLACEMENT CHARACTER to fit, an ASCII + * SUBSTITUTE. + * + * You must not allow this handle to outlive the string you + * obtained it from. + * + * You must not access the string you obtained this handle + * from in any way other than through this handle until + * you call Finish() on the handle or the handle goes out + * of scope. + * + * Once you've called Finish(), you must not call any + * methods on this handle and must not use values previously + * obtained. + * + * Once you call RestartBulkWrite(), you must not use + * values previously obtained from this handle and must + * reobtain the new corresponding values. + */ +template <typename T> +class BulkWriteHandle final { + friend class nsTSubstring<T>; + + public: + typedef typename mozilla::detail::nsTStringRepr<T> base_string_type; + typedef typename base_string_type::size_type size_type; + + /** + * Pointer to the start of the writable buffer. Never nullptr. + * + * This pointer is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + T* Elements() const { + MOZ_ASSERT(mString); + return mString->mData; + } + + /** + * How many code units can be written to the buffer. + * (Note: This is not the same as the string's Length().) + * + * This value is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + size_type Length() const { + MOZ_ASSERT(mString); + return mCapacity; + } + + /** + * Pointer past the end of the buffer. + * + * This pointer is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + T* End() const { return Elements() + Length(); } + + /** + * The writable buffer as Span. + * + * This Span is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + auto AsSpan() const { return mozilla::Span<T>{Elements(), Length()}; } + + /** + * Autoconvert to the buffer as writable Span. + * + * This Span is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + operator mozilla::Span<T>() const { return AsSpan(); } + + /** + * Restart the bulk write with a different capacity. + * + * This method invalidates previous return values + * of the other methods above. + * + * Can fail if out of memory leaving the buffer + * in the state before this call. + * + * @param aCapacity the new requested capacity + * @param aPrefixToPreserve the number of code units at + * the start of the string to + * copy over to the new buffer + * @param aAllowShrinking whether the string is + * allowed to attempt to + * allocate a smaller buffer + * for its content and copy + * the data over. + */ + mozilla::Result<mozilla::Ok, nsresult> RestartBulkWrite( + size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) { + MOZ_ASSERT(mString); + MOZ_TRY_VAR(mCapacity, mString->StartBulkWriteImpl( + aCapacity, aPrefixToPreserve, aAllowShrinking)); + return mozilla::Ok(); + } + + /** + * Indicate that the bulk write finished successfully. + * + * @param aLength the number of code units written; + * must not exceed Length() + * @param aAllowShrinking whether the string is + * allowed to attempt to + * allocate a smaller buffer + * for its content and copy + * the data over. + */ + void Finish(size_type aLength, bool aAllowShrinking) { + MOZ_ASSERT(mString); + MOZ_ASSERT(aLength <= mCapacity); + if (!aLength) { + // Truncate is safe even when the string is in an invalid state + mString->Truncate(); + mString = nullptr; + return; + } + if (aAllowShrinking) { + mozilla::Unused << mString->StartBulkWriteImpl(aLength, aLength, true); + } + mString->FinishBulkWriteImpl(aLength); + mString = nullptr; + } + + BulkWriteHandle(BulkWriteHandle&& aOther) + : mString(aOther.Forget()), mCapacity(aOther.mCapacity) {} + + ~BulkWriteHandle() { + if (!mString || !mCapacity) { + return; + } + // The old zero terminator may be gone by now, so we need + // to write a new one somewhere and make length match. + // We can use a length between 1 and self.capacity. + // The contents of the string can be partially uninitialized + // or partially initialized in a way that would be dangerous + // if parsed by some recipient. It's prudent to write something + // same as the contents of the string. U+FFFD is the safest + // placeholder, but when it doesn't fit, let's use ASCII + // substitute. Merely truncating the string to a zero-length + // string might be dangerous in some scenarios. See + // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences + // for closely related scenario. + auto ptr = Elements(); + // Cast the pointer below to silence warnings + if (sizeof(T) == 1) { + unsigned char* charPtr = reinterpret_cast<unsigned char*>(ptr); + if (mCapacity >= 3) { + *charPtr++ = 0xEF; + *charPtr++ = 0xBF; + *charPtr++ = 0xBD; + mString->mLength = 3; + } else { + *charPtr++ = 0x1A; + mString->mLength = 1; + } + *charPtr = 0; + } else if (sizeof(T) == 2) { + char16_t* charPtr = reinterpret_cast<char16_t*>(ptr); + *charPtr++ = 0xFFFD; + *charPtr = 0; + mString->mLength = 1; + } else { + MOZ_ASSERT_UNREACHABLE("Only 8-bit and 16-bit code units supported."); + } + } + + BulkWriteHandle() = delete; + BulkWriteHandle(const BulkWriteHandle&) = delete; + BulkWriteHandle& operator=(const BulkWriteHandle&) = delete; + + private: + BulkWriteHandle(nsTSubstring<T>* aString, size_type aCapacity) + : mString(aString), mCapacity(aCapacity) {} + + nsTSubstring<T>* Forget() { + auto string = mString; + mString = nullptr; + return string; + } + + nsTSubstring<T>* mString; // nullptr upon finish + size_type mCapacity; +}; + +} // namespace mozilla + +/** + * nsTSubstring is an abstract string class. From an API perspective, this + * class is the root of the string class hierarchy. It represents a single + * contiguous array of characters, which may or may not be null-terminated. + * This type is not instantiated directly. A sub-class is instantiated + * instead. For example, see nsTString. + * + * NAMES: + * nsAString for wide characters + * nsACString for narrow characters + * + */ +template <typename T> +class nsTSubstring : public mozilla::detail::nsTStringRepr<T> { + friend class mozilla::BulkWriteHandle<T>; + friend class nsStringBuffer; + + public: + typedef nsTSubstring<T> self_type; + + typedef nsTString<T> string_type; + + typedef typename mozilla::detail::nsTStringRepr<T> base_string_type; + typedef typename base_string_type::substring_type substring_type; + + typedef typename base_string_type::fallible_t fallible_t; + + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::char_traits char_traits; + typedef + typename base_string_type::incompatible_char_type incompatible_char_type; + + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + + typedef typename base_string_type::const_iterator const_iterator; + typedef typename base_string_type::iterator iterator; + + typedef typename base_string_type::comparator_type comparator_type; + + typedef typename base_string_type::const_char_iterator const_char_iterator; + + typedef typename base_string_type::string_view string_view; + + typedef typename base_string_type::index_type index_type; + typedef typename base_string_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + typedef typename base_string_type::LengthStorage LengthStorage; + + // this acts like a virtual destructor + ~nsTSubstring() { Finalize(); } + + /** + * writing iterators + * + * BeginWriting() makes the string mutable (if it isn't + * already) and returns (or writes into an outparam) a + * pointer that provides write access to the string's buffer. + * + * Note: Consider if BulkWrite() suits your use case better + * than BeginWriting() combined with SetLength(). + * + * Note: Strings autoconvert into writable mozilla::Span, + * which may suit your use case better than calling + * BeginWriting() directly. + * + * When writing via the pointer obtained from BeginWriting(), + * you are allowed to write at most the number of code units + * indicated by Length() or, alternatively, write up to, but + * not including, the position indicated by EndWriting(). + * + * In particular, calling SetCapacity() does not affect what + * the above paragraph says. + */ + + iterator BeginWriting() { + if (!EnsureMutable()) { + AllocFailed(base_string_type::mLength); + } + + return base_string_type::mData; + } + + iterator BeginWriting(const fallible_t&) { + return EnsureMutable() ? base_string_type::mData : iterator(0); + } + + iterator EndWriting() { + if (!EnsureMutable()) { + AllocFailed(base_string_type::mLength); + } + + return base_string_type::mData + base_string_type::mLength; + } + + iterator EndWriting(const fallible_t&) { + return EnsureMutable() + ? (base_string_type::mData + base_string_type::mLength) + : iterator(0); + } + + /** + * Perform string to int conversion. + * @param aErrorCode will contain error if one occurs + * @param aRadix is the radix to use. Only 10 and 16 are supported. + * @return int rep of string value, and possible (out) error code + */ + int32_t ToInteger(nsresult* aErrorCode, uint32_t aRadix = 10) const; + + /** + * Perform string to 64-bit int conversion. + * @param aErrorCode will contain error if one occurs + * @param aRadix is the radix to use. Only 10 and 16 are supported. + * @return 64-bit int rep of string value, and possible (out) error code + */ + int64_t ToInteger64(nsresult* aErrorCode, uint32_t aRadix = 10) const; + + /** + * assignment + */ + + void NS_FASTCALL Assign(char_type aChar); + [[nodiscard]] bool NS_FASTCALL Assign(char_type aChar, const fallible_t&); + + void NS_FASTCALL Assign(const char_type* aData, + size_type aLength = size_type(-1)); + [[nodiscard]] bool NS_FASTCALL Assign(const char_type* aData, + const fallible_t&); + [[nodiscard]] bool NS_FASTCALL Assign(const char_type* aData, + size_type aLength, const fallible_t&); + + void NS_FASTCALL Assign(const self_type&); + [[nodiscard]] bool NS_FASTCALL Assign(const self_type&, const fallible_t&); + + void NS_FASTCALL Assign(self_type&&); + [[nodiscard]] bool NS_FASTCALL Assign(self_type&&, const fallible_t&); + + void NS_FASTCALL Assign(const substring_tuple_type&); + [[nodiscard]] bool NS_FASTCALL Assign(const substring_tuple_type&, + const fallible_t&); + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + void Assign(char16ptr_t aData) { + Assign(static_cast<const char16_t*>(aData)); + } + + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + void Assign(char16ptr_t aData, size_type aLength) { + Assign(static_cast<const char16_t*>(aData), aLength); + } + + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + [[nodiscard]] bool Assign(char16ptr_t aData, size_type aLength, + const fallible_t& aFallible) { + return Assign(static_cast<const char16_t*>(aData), aLength, aFallible); + } +#endif + + void NS_FASTCALL AssignASCII(const char* aData, size_type aLength); + [[nodiscard]] bool NS_FASTCALL AssignASCII(const char* aData, + size_type aLength, + const fallible_t&); + + void NS_FASTCALL AssignASCII(const char* aData) { + AssignASCII(aData, strlen(aData)); + } + [[nodiscard]] bool NS_FASTCALL AssignASCII(const char* aData, + const fallible_t& aFallible) { + return AssignASCII(aData, strlen(aData), aFallible); + } + + // AssignLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Assign or AssignASCII for other character array variables. + // + // This method does not need a fallible version, because it uses the + // POD buffer of the literal as the string's buffer without allocating. + // The literal does not need to be ASCII. If this a 16-bit string, this + // method takes a u"" literal. (The overload on 16-bit strings that takes + // a "" literal takes only ASCII.) + template <int N> + void AssignLiteral(const char_type (&aStr)[N]) { + AssignLiteral(aStr, N - 1); + } + + // AssignLiteral must ONLY be called with an actual literal string, or + // a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use AssignASCII for other char array variables. + // + // This method takes an 8-bit (ASCII-only!) string that is expanded + // into a 16-bit string at run time causing a run-time allocation. + // To avoid the run-time allocation (at the cost of the literal + // taking twice the size in the binary), use the above overload that + // takes a u"" string instead. Using the overload that takes a u"" + // literal is generally preferred when working with 16-bit strings. + // + // There is not a fallible version of this method because it only really + // applies to small allocations that we wouldn't want to check anyway. + template <int N, typename Q = T, + typename EnableIfChar16 = typename mozilla::Char16OnlyT<Q>> + void AssignLiteral(const incompatible_char_type (&aStr)[N]) { + AssignASCII(aStr, N - 1); + } + + self_type& operator=(char_type aChar) { + Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) { + Assign(aData); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + self_type& operator=(char16ptr_t aData) { + Assign(aData); + return *this; + } +#endif + self_type& operator=(const self_type& aStr) { + Assign(aStr); + return *this; + } + self_type& operator=(self_type&& aStr) { + Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) { + Assign(aTuple); + return *this; + } + + // Adopt a heap-allocated char sequence for this string; is Voided if aData + // is null. Useful for e.g. converting an strdup'd C string into an + // nsCString. See also getter_Copies(), which is a useful wrapper. + void NS_FASTCALL Adopt(char_type* aData, size_type aLength = size_type(-1)); + + /** + * buffer manipulation + */ + + void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength, + char_type aChar); + [[nodiscard]] bool NS_FASTCALL Replace(index_type aCutStart, + size_type aCutLength, char_type aChar, + const fallible_t&); + void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength = size_type(-1)); + [[nodiscard]] bool NS_FASTCALL Replace(index_type aCutStart, + size_type aCutLength, + const char_type* aData, + size_type aLength, const fallible_t&); + void Replace(index_type aCutStart, size_type aCutLength, + const self_type& aStr) { + Replace(aCutStart, aCutLength, aStr.Data(), aStr.Length()); + } + [[nodiscard]] bool Replace(index_type aCutStart, size_type aCutLength, + const self_type& aStr, + const fallible_t& aFallible) { + return Replace(aCutStart, aCutLength, aStr.Data(), aStr.Length(), + aFallible); + } + void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength, + const substring_tuple_type& aTuple); + + // ReplaceLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Replace for other character array variables. + template <int N> + void ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type (&aStr)[N]) { + ReplaceLiteral(aCutStart, aCutLength, aStr, N - 1); + } + + /** + * |Left|, |Mid|, and |Right| are annoying signatures that seem better almost + * any _other_ way than they are now. Consider these alternatives + * + * // ...a member function that returns a |Substring| + * aWritable = aReadable.Left(17); + * // ...a global function that returns a |Substring| + * aWritable = Left(aReadable, 17); + * // ...a global function that does the assignment + * Left(aReadable, 17, aWritable); + * + * as opposed to the current signature + * + * // ...a member function that does the assignment + * aReadable.Left(aWritable, 17); + * + * or maybe just stamping them out in favor of |Substring|, they are just + * duplicate functionality + * + * aWritable = Substring(aReadable, 0, 17); + */ + size_type Mid(self_type& aResult, index_type aStartPos, + size_type aCount) const; + + size_type Left(self_type& aResult, size_type aCount) const { + return Mid(aResult, 0, aCount); + } + + size_type Right(self_type& aResult, size_type aCount) const { + aCount = XPCOM_MIN(this->Length(), aCount); + return Mid(aResult, this->mLength - aCount, aCount); + } + + /** + * This method strips whitespace throughout the string. + */ + void StripWhitespace(); + bool StripWhitespace(const fallible_t&); + + /** + * This method is used to remove all occurrences of aChar from this + * string. + * + * @param aChar -- char to be stripped + */ + void StripChar(char_type aChar); + + /** + * This method is used to remove all occurrences of aChars from this + * string. + * + * @param aChars -- chars to be stripped + */ + void StripChars(const char_type* aChars); + + /** + * This method is used to remove all occurrences of some characters this + * from this string. The characters removed have the corresponding + * entries in the bool array set to true; we retain all characters + * with code beyond 127. + * THE CALLER IS RESPONSIBLE for making sure the complete boolean + * array, 128 entries, is properly initialized. + * + * See also: ASCIIMask class. + * + * @param aToStrip -- Array where each entry is true if the + * corresponding ASCII character is to be stripped. All + * characters beyond code 127 are retained. Note that this + * parameter is of ASCIIMaskArray type, but we expand the typedef + * to avoid having to include nsASCIIMask.h in this include file + * as it brings other includes. + */ + void StripTaggedASCII(const std::array<bool, 128>& aToStrip); + + /** + * A shortcut to strip \r and \n. + */ + void StripCRLF(); + + /** + * swaps occurence of 1 string for another + */ + void ReplaceChar(char_type aOldChar, char_type aNewChar); + void ReplaceChar(const string_view& aSet, char_type aNewChar); + + /** + * Replace all occurrences of aTarget with aNewValue. + * The complexity of this function is O(n+m), n being the length of the string + * and m being the length of aNewValue. + */ + void ReplaceSubstring(const self_type& aTarget, const self_type& aNewValue); + void ReplaceSubstring(const char_type* aTarget, const char_type* aNewValue); + [[nodiscard]] bool ReplaceSubstring(const self_type& aTarget, + const self_type& aNewValue, + const fallible_t&); + [[nodiscard]] bool ReplaceSubstring(const char_type* aTarget, + const char_type* aNewValue, + const fallible_t&); + + /** + * This method trims characters found in aSet from either end of the + * underlying string. + * + * @param aSet -- contains chars to be trimmed from both ends + * @param aTrimLeading + * @param aTrimTrailing + * @param aIgnoreQuotes -- if true, causes surrounding quotes to be ignored + * @return this + */ + void Trim(const std::string_view& aSet, bool aTrimLeading = true, + bool aTrimTrailing = true, bool aIgnoreQuotes = false); + + /** + * This method strips whitespace from string. + * You can control whether whitespace is yanked from start and end of + * string as well. + * + * @param aTrimLeading controls stripping of leading ws + * @param aTrimTrailing controls stripping of trailing ws + */ + void CompressWhitespace(bool aTrimLeading = true, bool aTrimTrailing = true); + + void Append(char_type aChar); + + [[nodiscard]] bool Append(char_type aChar, const fallible_t& aFallible); + + void Append(const char_type* aData, size_type aLength = size_type(-1)); + + [[nodiscard]] bool Append(const char_type* aData, size_type aLength, + const fallible_t& aFallible); + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + void Append(char16ptr_t aData, size_type aLength = size_type(-1)) { + Append(static_cast<const char16_t*>(aData), aLength); + } +#endif + + void Append(const self_type& aStr); + + [[nodiscard]] bool Append(const self_type& aStr, const fallible_t& aFallible); + + void Append(const substring_tuple_type& aTuple); + + [[nodiscard]] bool Append(const substring_tuple_type& aTuple, + const fallible_t& aFallible); + + void AppendASCII(const char* aData, size_type aLength = size_type(-1)); + + [[nodiscard]] bool AppendASCII(const char* aData, + const fallible_t& aFallible); + + [[nodiscard]] bool AppendASCII(const char* aData, size_type aLength, + const fallible_t& aFallible); + + // Appends a literal string ("" literal in the 8-bit case and u"" literal + // in the 16-bit case) to the string. + // + // AppendLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Append or AppendASCII for other character array variables. + template <int N> + void AppendLiteral(const char_type (&aStr)[N]) { + // The case where base_string_type::mLength is zero is intentionally + // left unoptimized (could be optimized as call to AssignLiteral), + // because it's rare/nonexistent. If you add that optimization, + // please be sure to also check that + // !(base_string_type::mDataFlags & DataFlags::REFCOUNTED) + // to avoid undoing the effects of SetCapacity(). + Append(aStr, N - 1); + } + + template <int N> + void AppendLiteral(const char_type (&aStr)[N], const fallible_t& aFallible) { + // The case where base_string_type::mLength is zero is intentionally + // left unoptimized (could be optimized as call to AssignLiteral), + // because it's rare/nonexistent. If you add that optimization, + // please be sure to also check that + // !(base_string_type::mDataFlags & DataFlags::REFCOUNTED) + // to avoid undoing the effects of SetCapacity(). + return Append(aStr, N - 1, aFallible); + } + + // Only enable for T = char16_t + // + // Appends an 8-bit literal string ("" literal) to a 16-bit string by + // expanding it. The literal must only contain ASCII. + // + // Using u"" literals with 16-bit strings is generally preferred. + template <int N, typename Q = T, + typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + void AppendLiteral(const incompatible_char_type (&aStr)[N]) { + AppendASCII(aStr, N - 1); + } + + // Only enable for T = char16_t + template <int N, typename Q = T, + typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + [[nodiscard]] bool AppendLiteral(const incompatible_char_type (&aStr)[N], + const fallible_t& aFallible) { + return AppendASCII(aStr, N - 1, aFallible); + } + + /** + * Append a formatted string to the current string. Uses the + * standard printf format codes. This uses NSPR formatting, which will be + * locale-aware for floating-point values. You probably don't want to use + * this with floating-point values as a result. + */ + void AppendPrintf(const char* aFormat, ...) MOZ_FORMAT_PRINTF(2, 3); + void AppendVprintf(const char* aFormat, va_list aAp) MOZ_FORMAT_PRINTF(2, 0); + void AppendInt(int32_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(int32_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(static_cast<uint32_t>(aInteger)); + } else { + AppendIntHex(static_cast<uint32_t>(aInteger)); + } + } + void AppendInt(uint32_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(uint32_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(aInteger); + } else { + AppendIntHex(aInteger); + } + } + void AppendInt(int64_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(int64_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(static_cast<uint64_t>(aInteger)); + } else { + AppendIntHex(static_cast<uint64_t>(aInteger)); + } + } + void AppendInt(uint64_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(uint64_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(aInteger); + } else { + AppendIntHex(aInteger); + } + } + + private: + void AppendIntDec(int32_t); + void AppendIntDec(uint32_t); + void AppendIntOct(uint32_t); + void AppendIntHex(uint32_t); + void AppendIntDec(int64_t); + void AppendIntDec(uint64_t); + void AppendIntOct(uint64_t); + void AppendIntHex(uint64_t); + + public: + /** + * Append the given float to this string + */ + void NS_FASTCALL AppendFloat(float aFloat); + void NS_FASTCALL AppendFloat(double aFloat); + + self_type& operator+=(char_type aChar) { + Append(aChar); + return *this; + } + self_type& operator+=(const char_type* aData) { + Append(aData); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + self_type& operator+=(char16ptr_t aData) { + Append(aData); + return *this; + } +#endif + self_type& operator+=(const self_type& aStr) { + Append(aStr); + return *this; + } + self_type& operator+=(const substring_tuple_type& aTuple) { + Append(aTuple); + return *this; + } + + void Insert(char_type aChar, index_type aPos) { Replace(aPos, 0, aChar); } + void Insert(const char_type* aData, index_type aPos, + size_type aLength = size_type(-1)) { + Replace(aPos, 0, aData, aLength); + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + void Insert(char16ptr_t aData, index_type aPos, + size_type aLength = size_type(-1)) { + Insert(static_cast<const char16_t*>(aData), aPos, aLength); + } +#endif + void Insert(const self_type& aStr, index_type aPos) { + Replace(aPos, 0, aStr); + } + void Insert(const substring_tuple_type& aTuple, index_type aPos) { + Replace(aPos, 0, aTuple); + } + + // InsertLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Insert for other character array variables. + template <int N> + void InsertLiteral(const char_type (&aStr)[N], index_type aPos) { + ReplaceLiteral(aPos, 0, aStr, N - 1); + } + + void Cut(index_type aCutStart, size_type aCutLength) { + Replace(aCutStart, aCutLength, char_traits::sEmptyBuffer, 0); + } + + nsTSubstringSplitter<T> Split(const char_type aChar) const; + + /** + * buffer sizing + */ + + /** + * Attempts to set the capacity to the given size in number of + * code units without affecting the length of the string in + * order to avoid reallocation during a subsequent sequence of + * appends. + * + * This method is appropriate to use before a sequence of multiple + * operations from the following list (without operations that are + * not on the list between the SetCapacity() call and operations + * from the list): + * + * Append() + * AppendASCII() + * AppendLiteral() (except if the string is empty: bug 1487606) + * AppendPrintf() + * AppendInt() + * AppendFloat() + * LossyAppendUTF16toASCII() + * AppendASCIItoUTF16() + * + * DO NOT call SetCapacity() if the subsequent operations on the + * string do not meet the criteria above. Operations that undo + * the benefits of SetCapacity() include but are not limited to: + * + * SetLength() + * Truncate() + * Assign() + * AssignLiteral() + * Adopt() + * CopyASCIItoUTF16() + * LossyCopyUTF16toASCII() + * AppendUTF16toUTF8() + * AppendUTF8toUTF16() + * CopyUTF16toUTF8() + * CopyUTF8toUTF16() + * + * If your string is an nsAuto[C]String and you are calling + * SetCapacity() with a constant N, please instead declare the + * string as nsAuto[C]StringN<N+1> without calling SetCapacity(). + * + * There is no need to include room for the null terminator: it is + * the job of the string class. + * + * Note: Calling SetCapacity() does not give you permission to + * use the pointer obtained from BeginWriting() to write + * past the current length (as returned by Length()) of the + * string. Please use either BulkWrite() or SetLength() + * instead. + * + * Note: SetCapacity() won't make the string shorter if + * called with an argument smaller than the length of the + * string. + * + * Note: You must not use previously obtained iterators + * or spans after calling SetCapacity(). + */ + void NS_FASTCALL SetCapacity(size_type aNewCapacity); + [[nodiscard]] bool NS_FASTCALL SetCapacity(size_type aNewCapacity, + const fallible_t&); + + /** + * Changes the logical length of the string, potentially + * allocating a differently-sized buffer for the string. + * + * When making the string shorter, this method never + * reports allocation failure. + * + * Exposes uninitialized memory if the string got longer. + * + * If called with the argument 0, releases the + * heap-allocated buffer, if any. (But the no-argument + * overload of Truncate() is a more idiomatic and efficient + * option than SetLength(0).) + * + * Note: You must not use previously obtained iterators + * or spans after calling SetLength(). + */ + void NS_FASTCALL SetLength(size_type aNewLength); + [[nodiscard]] bool NS_FASTCALL SetLength(size_type aNewLength, + const fallible_t&); + + /** + * Like SetLength() but asserts in that the string + * doesn't become longer. Never fails, so doesn't need a + * fallible variant. + * + * Note: You must not use previously obtained iterators + * or spans after calling Truncate(). + */ + void Truncate(size_type aNewLength) { + MOZ_RELEASE_ASSERT(aNewLength <= base_string_type::mLength, + "Truncate cannot make string longer"); + mozilla::DebugOnly<bool> success = SetLength(aNewLength, mozilla::fallible); + MOZ_ASSERT(success); + } + + /** + * A more efficient overload for Truncate(0). Releases the + * heap-allocated buffer if any. + */ + void Truncate(); + + /** + * buffer access + */ + + /** + * Get a const pointer to the string's internal buffer. The caller + * MUST NOT modify the characters at the returned address. + * + * @returns The length of the buffer in characters. + */ + inline size_type GetData(const char_type** aData) const { + *aData = base_string_type::mData; + return base_string_type::mLength; + } + + /** + * Get a pointer to the string's internal buffer, optionally resizing + * the buffer first. If size_type(-1) is passed for newLen, then the + * current length of the string is used. The caller MAY modify the + * characters at the returned address (up to but not exceeding the + * length of the string). + * + * @returns The length of the buffer in characters or 0 if unable to + * satisfy the request due to low-memory conditions. + */ + size_type GetMutableData(char_type** aData, + size_type aNewLen = size_type(-1)) { + if (!EnsureMutable(aNewLen)) { + AllocFailed(aNewLen == size_type(-1) ? base_string_type::Length() + : aNewLen); + } + + *aData = base_string_type::mData; + return base_string_type::Length(); + } + + size_type GetMutableData(char_type** aData, size_type aNewLen, + const fallible_t&) { + if (!EnsureMutable(aNewLen)) { + *aData = nullptr; + return 0; + } + + *aData = base_string_type::mData; + return base_string_type::mLength; + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + size_type GetMutableData(wchar_t** aData, size_type aNewLen = size_type(-1)) { + return GetMutableData(reinterpret_cast<char16_t**>(aData), aNewLen); + } + + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + size_type GetMutableData(wchar_t** aData, size_type aNewLen, + const fallible_t& aFallible) { + return GetMutableData(reinterpret_cast<char16_t**>(aData), aNewLen, + aFallible); + } +#endif + + mozilla::Span<char_type> GetMutableData(size_type aNewLen = size_type(-1)) { + if (!EnsureMutable(aNewLen)) { + AllocFailed(aNewLen == size_type(-1) ? base_string_type::Length() + : aNewLen); + } + + return mozilla::Span{base_string_type::mData, base_string_type::Length()}; + } + + mozilla::Maybe<mozilla::Span<char_type>> GetMutableData(size_type aNewLen, + const fallible_t&) { + if (!EnsureMutable(aNewLen)) { + return mozilla::Nothing(); + } + return Some( + mozilla::Span{base_string_type::mData, base_string_type::Length()}); + } + + /** + * Span integration + */ + + operator mozilla::Span<const char_type>() const { + return mozilla::Span{base_string_type::BeginReading(), + base_string_type::Length()}; + } + + void Append(mozilla::Span<const char_type> aSpan) { + Append(aSpan.Elements(), aSpan.Length()); + } + + [[nodiscard]] bool Append(mozilla::Span<const char_type> aSpan, + const fallible_t& aFallible) { + return Append(aSpan.Elements(), aSpan.Length(), aFallible); + } + + void NS_FASTCALL AssignASCII(mozilla::Span<const char> aData) { + AssignASCII(aData.Elements(), aData.Length()); + } + [[nodiscard]] bool NS_FASTCALL AssignASCII(mozilla::Span<const char> aData, + const fallible_t& aFallible) { + return AssignASCII(aData.Elements(), aData.Length(), aFallible); + } + + void AppendASCII(mozilla::Span<const char> aData) { + AppendASCII(aData.Elements(), aData.Length()); + } + + template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>> + operator mozilla::Span<const uint8_t>() const { + return mozilla::Span{ + reinterpret_cast<const uint8_t*>(base_string_type::BeginReading()), + base_string_type::Length()}; + } + + template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>> + void Append(mozilla::Span<const uint8_t> aSpan) { + Append(reinterpret_cast<const char*>(aSpan.Elements()), aSpan.Length()); + } + + template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>> + [[nodiscard]] bool Append(mozilla::Span<const uint8_t> aSpan, + const fallible_t& aFallible) { + return Append(reinterpret_cast<const char*>(aSpan.Elements()), + aSpan.Length(), aFallible); + } + + void Insert(mozilla::Span<const char_type> aSpan, index_type aPos) { + Insert(aSpan.Elements(), aPos, aSpan.Length()); + } + + /** + * string data is never null, but can be marked void. if true, the + * string will be truncated. @see nsTSubstring::IsVoid + */ + + void NS_FASTCALL SetIsVoid(bool); + + /** + * If the string uses a shared buffer, this method + * clears the pointer without releasing the buffer. + */ + void ForgetSharedBuffer() { + if (base_string_type::mDataFlags & DataFlags::REFCOUNTED) { + SetToEmptyBuffer(); + } + } + + protected: + void AssertValid() { + MOZ_DIAGNOSTIC_ASSERT(!(this->mClassFlags & ClassFlags::INVALID_MASK)); + MOZ_DIAGNOSTIC_ASSERT(!(this->mDataFlags & DataFlags::INVALID_MASK)); + MOZ_ASSERT(!(this->mClassFlags & ClassFlags::NULL_TERMINATED) || + (this->mDataFlags & DataFlags::TERMINATED), + "String classes whose static type guarantees a null-terminated " + "buffer must not be assigned a non-null-terminated buffer."); + } + + public: + /** + * this is public to support automatic conversion of tuple to string + * base type, which helps avoid converting to nsTAString. + */ + MOZ_IMPLICIT nsTSubstring(const substring_tuple_type& aTuple) + : base_string_type(nullptr, 0, DataFlags(0), ClassFlags(0)) { + AssertValid(); + Assign(aTuple); + } + + size_t SizeOfExcludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const; + + /** + * WARNING: Only use these functions if you really know what you are + * doing, because they can easily lead to double-counting strings. If + * you do use them, please explain clearly in a comment why it's safe + * and won't lead to double-counting. + */ + size_t SizeOfExcludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const; + + template <class N> + void NS_ABORT_OOM(T) { + struct never {}; // a compiler-friendly way to do static_assert(false) + static_assert( + std::is_same_v<N, never>, + "In string classes, use AllocFailed to account for sizeof(char_type). " + "Use the global ::NS_ABORT_OOM if you really have a count of bytes."); + } + + MOZ_ALWAYS_INLINE void AllocFailed(size_t aLength) { + ::NS_ABORT_OOM(aLength * sizeof(char_type)); + } + + protected: + // default initialization + nsTSubstring() + : base_string_type(char_traits::sEmptyBuffer, 0, DataFlags::TERMINATED, + ClassFlags(0)) { + AssertValid(); + } + + // copy-constructor, constructs as dependent on given object + // (NOTE: this is for internal use only) + nsTSubstring(const self_type& aStr) + : base_string_type(aStr.base_string_type::mData, + aStr.base_string_type::mLength, + aStr.base_string_type::mDataFlags & + (DataFlags::TERMINATED | DataFlags::VOIDED), + ClassFlags(0)) { + AssertValid(); + } + + // initialization with ClassFlags + explicit nsTSubstring(ClassFlags aClassFlags) + : base_string_type(char_traits::sEmptyBuffer, 0, DataFlags::TERMINATED, + aClassFlags) { + AssertValid(); + } + + /** + * allows for direct initialization of a nsTSubstring object. + */ + nsTSubstring(char_type* aData, size_type aLength, DataFlags aDataFlags, + ClassFlags aClassFlags) +#if defined(NS_BUILD_REFCNT_LOGGING) +# define XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE + ; +#else +# undef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE + : base_string_type(aData, aLength, aDataFlags, aClassFlags) { + AssertValid(); + } +#endif /* NS_BUILD_REFCNT_LOGGING */ + + void SetToEmptyBuffer() { + base_string_type::mData = char_traits::sEmptyBuffer; + base_string_type::mLength = 0; + base_string_type::mDataFlags = DataFlags::TERMINATED; + AssertValid(); + } + + void SetData(char_type* aData, LengthStorage aLength, DataFlags aDataFlags) { + base_string_type::mData = aData; + base_string_type::mLength = aLength; + base_string_type::mDataFlags = aDataFlags; + AssertValid(); + } + + /** + * this function releases mData and does not change the value of + * any of its member variables. in other words, this function acts + * like a destructor. + */ + void NS_FASTCALL Finalize(); + + public: + /** + * Starts a low-level write transaction to the string. + * + * Prepares the string for mutation such that the capacity + * of the string is at least aCapacity. The returned handle + * exposes the actual, potentially larger, capacity. + * + * If meeting the capacity or mutability requirement requires + * reallocation, aPrefixToPreserve code units are copied from the + * start of the old buffer to the start of the new buffer. + * aPrefixToPreserve must not be greater than the string's current + * length or greater than aCapacity. + * + * aAllowShrinking indicates whether an allocation may be + * performed when the string is already mutable and the requested + * capacity is smaller than the current capacity. + * + * If this method returns successfully, you must not access + * the string except through the returned BulkWriteHandle + * until either the BulkWriteHandle goes out of scope or + * you call Finish() on the BulkWriteHandle. + * + * Compared to SetLength() and BeginWriting(), this more + * complex API accomplishes two things: + * 1) It exposes the actual capacity which may be larger + * than the requested capacity, which is useful in some + * multi-step write operations that don't allocate for + * the worst case up front. + * 2) It writes the zero terminator after the string + * content has been written, which results in a + * cache-friendly linear write pattern. + */ + mozilla::Result<mozilla::BulkWriteHandle<T>, nsresult> NS_FASTCALL BulkWrite( + size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking); + + /** + * THIS IS NOT REALLY A PUBLIC METHOD! DO NOT CALL FROM OUTSIDE + * THE STRING IMPLEMENTATION. (It's public only because friend + * declarations don't allow extern or static and this needs to + * be called from Rust FFI glue.) + * + * Prepares mData to be mutated such that the capacity of the string + * (not counting the zero-terminator) is at least aCapacity. + * Returns the actual capacity, which may be larger than what was + * requested or Err(NS_ERROR_OUT_OF_MEMORY) on allocation failure. + * + * mLength is ignored by this method. If the buffer is reallocated, + * aUnitsToPreserve specifies how many code units to copy over to + * the new buffer. The old buffer is freed if applicable. + * + * Unless the return value is Err(NS_ERROR_OUT_OF_MEMORY) to signal + * failure or 0 to signal that the string has been set to + * the special empty state, this method leaves the string in an + * invalid state! The caller is responsible for calling + * FinishBulkWrite() (or in Rust calling + * nsA[C]StringBulkWriteHandle::finish()), which put the string + * into a valid state by setting mLength and zero-terminating. + * This method sets the flag to claim that the string is + * zero-terminated before it actually is. + * + * Once this method has been called and before FinishBulkWrite() + * has been called, only accessing mData or calling this method + * again are valid operations. Do not call any other methods or + * access other fields between calling this method and + * FinishBulkWrite(). + * + * @param aCapacity The requested capacity. The return value + * will be greater than or equal to this value. + * @param aPrefixToPreserve The number of code units at the start + * of the old buffer to copy into the + * new buffer. + * @parem aAllowShrinking If true, an allocation may be performed + * if the requested capacity is smaller + * than the current capacity. + * @param aSuffixLength The length, in code units, of a suffix + * to move. + * @param aOldSuffixStart The old start index of the suffix to + * move. + * @param aNewSuffixStart The new start index of the suffix to + * move. + * + */ + mozilla::Result<size_type, nsresult> NS_FASTCALL StartBulkWriteImpl( + size_type aCapacity, size_type aPrefixToPreserve = 0, + bool aAllowShrinking = true, size_type aSuffixLength = 0, + size_type aOldSuffixStart = 0, size_type aNewSuffixStart = 0); + + private: + void AssignOwned(self_type&& aStr); + bool AssignNonDependent(const substring_tuple_type& aTuple, + size_type aTupleLength, + const mozilla::fallible_t& aFallible); + + /** + * Do not call this except from within FinishBulkWriteImpl() and + * SetCapacity(). + */ + MOZ_ALWAYS_INLINE void NS_FASTCALL + FinishBulkWriteImplImpl(LengthStorage aLength) { + base_string_type::mData[aLength] = char_type(0); + base_string_type::mLength = aLength; +#ifdef DEBUG + // ifdefed in order to avoid the call to Capacity() in non-debug + // builds. + // + // Our string is mutable, so Capacity() doesn't return zero. + // Capacity() doesn't include the space for the zero terminator, + // but we want to unitialize that slot, too. Since we start + // counting after the zero terminator the we just wrote above, + // we end up overwriting the space for terminator not reflected + // in the capacity number. + char_traits::uninitialize( + base_string_type::mData + aLength + 1, + XPCOM_MIN(size_t(Capacity() - aLength), kNsStringBufferMaxPoison)); +#endif + } + + protected: + /** + * Restores the string to a valid state after a call to StartBulkWrite() + * that returned a non-error result. The argument to this method + * must be less than or equal to the value returned by the most recent + * StartBulkWrite() call. + */ + void NS_FASTCALL FinishBulkWriteImpl(size_type aLength); + + /** + * this function prepares a section of mData to be modified. if + * necessary, this function will reallocate mData and possibly move + * existing data to open up the specified section. + * + * @param aCutStart specifies the starting offset of the section + * @param aCutLength specifies the length of the section to be replaced + * @param aNewLength specifies the length of the new section + * + * for example, suppose mData contains the string "abcdef" then + * + * ReplacePrep(2, 3, 4); + * + * would cause mData to look like "ab____f" where the characters + * indicated by '_' have an unspecified value and can be freely + * modified. this function will null-terminate mData upon return. + * + * this function returns false if is unable to allocate sufficient + * memory. + */ + [[nodiscard]] bool ReplacePrep(index_type aCutStart, size_type aCutLength, + size_type aNewLength); + + [[nodiscard]] bool NS_FASTCALL ReplacePrepInternal(index_type aCutStart, + size_type aCutLength, + size_type aNewFragLength, + size_type aNewTotalLength); + + /** + * returns the number of writable storage units starting at mData. + * the value does not include space for the null-terminator character. + * + * NOTE: this function returns 0 if mData is immutable (or the buffer + * is 0-sized). + */ + size_type NS_FASTCALL Capacity() const; + + /** + * this helper function can be called prior to directly manipulating + * the contents of mData. see, for example, BeginWriting. + */ + [[nodiscard]] bool NS_FASTCALL + EnsureMutable(size_type aNewLen = size_type(-1)); + + void NS_FASTCALL ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength); + + public: + // NOTE: this method is declared public _only_ for convenience for + // callers who don't have access to the original nsLiteralString_CharT. + void NS_FASTCALL AssignLiteral(const char_type* aData, size_type aLength); +}; + +extern template class nsTSubstring<char>; +extern template class nsTSubstring<char16_t>; + +static_assert(sizeof(nsTSubstring<char>) == + sizeof(mozilla::detail::nsTStringRepr<char>), + "Don't add new data fields to nsTSubstring_CharT. " + "Add to nsTStringRepr<T> instead."); + +#include "nsCharSeparatedTokenizer.h" +#include "nsTDependentSubstring.h" + +/** + * Span integration + */ +namespace mozilla { +Span(const nsTSubstring<char>&) -> Span<const char>; +Span(const nsTSubstring<char16_t>&) -> Span<const char16_t>; + +} // namespace mozilla + +#endif diff --git a/xpcom/string/nsTSubstringTuple.cpp b/xpcom/string/nsTSubstringTuple.cpp new file mode 100644 index 0000000000..3219cb19fa --- /dev/null +++ b/xpcom/string/nsTSubstringTuple.cpp @@ -0,0 +1,92 @@ +/* -*- 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 "nsTSubstringTuple.h" +#include "mozilla/CheckedInt.h" + +/** + * computes the aggregate string length + */ + +template <typename T> +typename nsTSubstringTuple<T>::size_type nsTSubstringTuple<T>::Length() const { + mozilla::CheckedInt<size_type> len; + if (mHead) { + len = mHead->Length(); + } else { + len = mFragA->Length(); + } + + len += mFragB->Length(); + MOZ_RELEASE_ASSERT(len.isValid(), "Substring tuple length is invalid"); + return len.value(); +} + +/** + * writes the aggregate string to the given buffer. aBufLen is assumed + * to be equal to or greater than the value returned by the Length() + * method. the string written to |aBuf| is not null-terminated. + */ + +template <typename T> +void nsTSubstringTuple<T>::WriteTo(char_type* aBuf, size_type aBufLen) const { + MOZ_RELEASE_ASSERT(aBufLen >= mFragB->Length(), "buffer too small"); + size_type headLen = aBufLen - mFragB->Length(); + if (mHead) { + mHead->WriteTo(aBuf, headLen); + } else { + MOZ_RELEASE_ASSERT(mFragA->Length() == headLen, "buffer incorrectly sized"); + char_traits::copy(aBuf, mFragA->Data(), mFragA->Length()); + } + + char_traits::copy(aBuf + headLen, mFragB->Data(), mFragB->Length()); +} + +/** + * returns true if this tuple is dependent on (i.e., overlapping with) + * the given char sequence. + */ + +template <typename T> +bool nsTSubstringTuple<T>::IsDependentOn(const char_type* aStart, + const char_type* aEnd) const { + // we start with the right-most fragment since it is faster to check. + + if (mFragB->IsDependentOn(aStart, aEnd)) { + return true; + } + + if (mHead) { + return mHead->IsDependentOn(aStart, aEnd); + } + + return mFragA->IsDependentOn(aStart, aEnd); +} + +template <typename T> +auto nsTSubstringTuple<T>::IsDependentOnWithLength(const char_type* aStart, + const char_type* aEnd) const + -> std::pair<bool, size_type> { + // we start with the right-most fragment since it is faster to check for + // dependency. + const bool rightDependentOn = mFragB->IsDependentOn(aStart, aEnd); + + if (rightDependentOn) { + return {true, Length()}; + } + + const auto [leftDependentOn, leftLen] = + mHead ? mHead->IsDependentOnWithLength(aStart, aEnd) + : std::pair{mFragA->IsDependentOn(aStart, aEnd), mFragA->Length()}; + + const auto checkedLen = + mozilla::CheckedInt<size_type>{leftLen} + mFragB->Length(); + MOZ_RELEASE_ASSERT(checkedLen.isValid(), "Substring tuple length is invalid"); + return {leftDependentOn, checkedLen.value()}; +} + +template class nsTSubstringTuple<char>; +template class nsTSubstringTuple<char16_t>; diff --git a/xpcom/string/nsTSubstringTuple.h b/xpcom/string/nsTSubstringTuple.h new file mode 100644 index 0000000000..dff8dedca9 --- /dev/null +++ b/xpcom/string/nsTSubstringTuple.h @@ -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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsTSubstringTuple_h +#define nsTSubstringTuple_h + +#include "mozilla/Attributes.h" +#include "nsTStringRepr.h" + +/** + * nsTSubstringTuple + * + * Represents a tuple of string fragments. Built as a recursive binary tree. + * It is used to implement the concatenation of two or more string objects. + * + * NOTE: This class is a private implementation detail and should never be + * referenced outside the string code. + */ +template <typename T> +class MOZ_TEMPORARY_CLASS nsTSubstringTuple { + public: + typedef T char_type; + typedef nsCharTraits<char_type> char_traits; + + typedef nsTSubstringTuple<T> self_type; + typedef mozilla::detail::nsTStringRepr<char_type> base_string_type; + typedef size_t size_type; + + public: + nsTSubstringTuple(const base_string_type* aStrA, + const base_string_type* aStrB) + : mHead(nullptr), mFragA(aStrA), mFragB(aStrB) {} + + nsTSubstringTuple(const self_type& aHead, const base_string_type* aStrB) + : mHead(&aHead), + mFragA(nullptr), // this fragment is ignored when aHead != nullptr + mFragB(aStrB) {} + + /** + * computes the aggregate string length + */ + size_type Length() const; + + /** + * writes the aggregate string to the given buffer. bufLen is assumed + * to be equal to or greater than the value returned by the Length() + * method. the string written to |buf| is not null-terminated. + */ + void WriteTo(char_type* aBuf, size_type aBufLen) const; + + /** + * returns true if this tuple is dependent on (i.e., overlapping with) + * the given char sequence. + */ + bool IsDependentOn(const char_type* aStart, const char_type* aEnd) const; + + /** + * returns a pair of the results of IsDependentOn() and Length(). This is more + * efficient than calling both functions subsequently, as this traverses the + * tree only once. + */ + std::pair<bool, size_type> IsDependentOnWithLength( + const char_type* aStart, const char_type* aEnd) const; + + private: + const self_type* const mHead; + const base_string_type* const mFragA; + const base_string_type* const mFragB; +}; + +template <typename T> +inline const nsTSubstringTuple<T> operator+( + const mozilla::detail::nsTStringRepr<T>& aStrA, + const mozilla::detail::nsTStringRepr<T>& aStrB) { + return nsTSubstringTuple<T>(&aStrA, &aStrB); +} + +template <typename T> +inline const nsTSubstringTuple<T> operator+( + const nsTSubstringTuple<T>& aHead, + const mozilla::detail::nsTStringRepr<T>& aStrB) { + return nsTSubstringTuple<T>(aHead, &aStrB); +} + +#endif diff --git a/xpcom/string/nsTextFormatter.cpp b/xpcom/string/nsTextFormatter.cpp new file mode 100644 index 0000000000..4db5338e2b --- /dev/null +++ b/xpcom/string/nsTextFormatter.cpp @@ -0,0 +1,895 @@ +/* -*- 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/. */ + +/* + * Portable safe sprintf code. + * + * Code based on mozilla/nsprpub/src/io/prprf.c rev 3.7 + * + * Contributor(s): + * Kipp E.B. Hickman <kipp@netscape.com> (original author) + * Frank Yung-Fong Tang <ftang@netscape.com> + * Daniele Nicolodi <daniele@grinta.net> + */ + +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include "prdtoa.h" +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" +#include "nsCRTGlue.h" +#include "nsTextFormatter.h" + +struct nsTextFormatter::SprintfStateStr { + int (*stuff)(SprintfStateStr* aState, const char16_t* aStr, uint32_t aLen); + + char16_t* base; + char16_t* cur; + uint32_t maxlen; + + void* stuffclosure; +}; + +#define _LEFT 0x1 +#define _SIGNED 0x2 +#define _SPACED 0x4 +#define _ZEROS 0x8 +#define _NEG 0x10 +#define _UNSIGNED 0x20 + +#define ELEMENTS_OF(array_) (sizeof(array_) / sizeof(array_[0])) + +/* +** Fill into the buffer using the data in src +*/ +int nsTextFormatter::fill2(SprintfStateStr* aState, const char16_t* aSrc, + int aSrcLen, int aWidth, int aFlags) { + char16_t space = ' '; + int rv; + + aWidth -= aSrcLen; + /* Right adjusting */ + if ((aWidth > 0) && ((aFlags & _LEFT) == 0)) { + if (aFlags & _ZEROS) { + space = '0'; + } + while (--aWidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + + /* Copy out the source data */ + rv = (*aState->stuff)(aState, aSrc, aSrcLen); + if (rv < 0) { + return rv; + } + + /* Left adjusting */ + if ((aWidth > 0) && ((aFlags & _LEFT) != 0)) { + while (--aWidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + return 0; +} + +/* +** Fill a number. The order is: optional-sign zero-filling conversion-digits +*/ +int nsTextFormatter::fill_n(nsTextFormatter::SprintfStateStr* aState, + const char16_t* aSrc, int aSrcLen, int aWidth, + int aPrec, int aFlags) { + int zerowidth = 0; + int precwidth = 0; + int signwidth = 0; + int leftspaces = 0; + int rightspaces = 0; + int cvtwidth; + int rv; + char16_t sign; + char16_t space = ' '; + char16_t zero = '0'; + + if ((aFlags & _UNSIGNED) == 0) { + if (aFlags & _NEG) { + sign = '-'; + signwidth = 1; + } else if (aFlags & _SIGNED) { + sign = '+'; + signwidth = 1; + } else if (aFlags & _SPACED) { + sign = ' '; + signwidth = 1; + } + } + cvtwidth = signwidth + aSrcLen; + + if (aPrec > 0) { + if (aPrec > aSrcLen) { + /* Need zero filling */ + precwidth = aPrec - aSrcLen; + cvtwidth += precwidth; + } + } + + if ((aFlags & _ZEROS) && (aPrec < 0)) { + if (aWidth > cvtwidth) { + /* Zero filling */ + zerowidth = aWidth - cvtwidth; + cvtwidth += zerowidth; + } + } + + if (aFlags & _LEFT) { + if (aWidth > cvtwidth) { + /* Space filling on the right (i.e. left adjusting) */ + rightspaces = aWidth - cvtwidth; + } + } else { + if (aWidth > cvtwidth) { + /* Space filling on the left (i.e. right adjusting) */ + leftspaces = aWidth - cvtwidth; + } + } + while (--leftspaces >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + if (signwidth) { + rv = (*aState->stuff)(aState, &sign, 1); + if (rv < 0) { + return rv; + } + } + while (--precwidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + while (--zerowidth >= 0) { + rv = (*aState->stuff)(aState, &zero, 1); + if (rv < 0) { + return rv; + } + } + rv = (*aState->stuff)(aState, aSrc, aSrcLen); + if (rv < 0) { + return rv; + } + while (--rightspaces >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + return 0; +} + +/* +** Convert a 64-bit integer into its printable form +*/ +int nsTextFormatter::cvt_ll(SprintfStateStr* aState, uint64_t aNum, int aWidth, + int aPrec, int aRadix, int aFlags, + const char16_t* aHexStr) { + char16_t cvtbuf[100]; + char16_t* cvt; + int digits; + + /* according to the man page this needs to happen */ + if (aPrec == 0 && aNum == 0) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + cvt = &cvtbuf[0] + ELEMENTS_OF(cvtbuf); + digits = 0; + while (aNum != 0) { + uint64_t quot = aNum / aRadix; + uint64_t rem = aNum % aRadix; + *--cvt = aHexStr[rem & 0xf]; + digits++; + aNum = quot; + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(aState, cvt, digits, aWidth, aPrec, aFlags); +} + +/* +** Convert a double precision floating point number into its printable +** form. +*/ +int nsTextFormatter::cvt_f(SprintfStateStr* aState, double aDouble, int aWidth, + int aPrec, const char16_t aType, int aFlags) { + int mode = 2; + int decpt; + int sign; + char buf[256]; + char* bufp = buf; + int bufsz = 256; + char num[256]; + char* nump; + char* endnum; + int numdigits = 0; + char exp = 'e'; + + if (aPrec == -1) { + aPrec = 6; + } else if (aPrec > 50) { + // limit precision to avoid PR_dtoa bug 108335 + // and to prevent buffers overflows + aPrec = 50; + } + + switch (aType) { + case 'f': + numdigits = aPrec; + mode = 3; + break; + case 'E': + exp = 'E'; + [[fallthrough]]; + case 'e': + numdigits = aPrec + 1; + mode = 2; + break; + case 'G': + exp = 'E'; + [[fallthrough]]; + case 'g': + if (aPrec == 0) { + aPrec = 1; + } + numdigits = aPrec; + mode = 2; + break; + default: + NS_ERROR("invalid aType passed to cvt_f"); + } + + if (PR_dtoa(aDouble, mode, numdigits, &decpt, &sign, &endnum, num, bufsz) == + PR_FAILURE) { + buf[0] = '\0'; + return -1; + } + numdigits = endnum - num; + nump = num; + + if (sign) { + *bufp++ = '-'; + } else if (aFlags & _SIGNED) { + *bufp++ = '+'; + } + + if (decpt == 9999) { + while ((*bufp++ = *nump++)) { + } + } else { + switch (aType) { + case 'E': + case 'e': + + *bufp++ = *nump++; + if (aPrec > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + aPrec--; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + *bufp++ = exp; + + ::snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1); + break; + + case 'f': + + if (decpt < 1) { + *bufp++ = '0'; + if (aPrec > 0) { + *bufp++ = '.'; + while (decpt++ && aPrec-- > 0) { + *bufp++ = '0'; + } + while (*nump && aPrec-- > 0) { + *bufp++ = *nump++; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + } else { + while (*nump && decpt-- > 0) { + *bufp++ = *nump++; + } + while (decpt-- > 0) { + *bufp++ = '0'; + } + if (aPrec > 0) { + *bufp++ = '.'; + while (*nump && aPrec-- > 0) { + *bufp++ = *nump++; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + } + *bufp = '\0'; + break; + + case 'G': + case 'g': + + if ((decpt < -3) || ((decpt - 1) >= aPrec)) { + *bufp++ = *nump++; + numdigits--; + if (numdigits > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + } + } + *bufp++ = exp; + ::snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1); + } else { + if (decpt < 1) { + *bufp++ = '0'; + if (aPrec > 0) { + *bufp++ = '.'; + while (decpt++) { + *bufp++ = '0'; + } + while (*nump) { + *bufp++ = *nump++; + } + } + } else { + while (*nump && decpt-- > 0) { + *bufp++ = *nump++; + numdigits--; + } + while (decpt-- > 0) { + *bufp++ = '0'; + } + if (numdigits > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + } + } + } + *bufp = '\0'; + } + } + } + + char16_t rbuf[256]; + char16_t* rbufp = rbuf; + bufp = buf; + // cast to char16_t + while ((*rbufp++ = *bufp++)) { + } + *rbufp = '\0'; + + return fill2(aState, rbuf, NS_strlen(rbuf), aWidth, aFlags); +} + +/* +** Convert a string into its printable form. |aWidth| is the output +** width. |aPrec| is the maximum number of characters of |aStr| to output, +** where -1 means until NUL. +*/ +int nsTextFormatter::cvt_S(SprintfStateStr* aState, const char16_t* aStr, + int aWidth, int aPrec, int aFlags) { + int slen; + + if (aPrec == 0) { + return 0; + } + + /* Limit string length by precision value */ + slen = aStr ? NS_strlen(aStr) : 6; + if (aPrec > 0) { + if (aPrec < slen) { + slen = aPrec; + } + } + + /* and away we go */ + return fill2(aState, aStr ? aStr : u"(null)", slen, aWidth, aFlags); +} + +/* +** Convert a string into its printable form. |aWidth| is the output +** width. |aPrec| is the maximum number of characters of |aStr| to output, +** where -1 means until NUL. +*/ +int nsTextFormatter::cvt_s(nsTextFormatter::SprintfStateStr* aState, + const char* aStr, int aWidth, int aPrec, + int aFlags) { + // Be sure to handle null the same way as %S. + if (aStr == nullptr) { + return cvt_S(aState, nullptr, aWidth, aPrec, aFlags); + } + NS_ConvertUTF8toUTF16 utf16Val(aStr); + return cvt_S(aState, utf16Val.get(), aWidth, aPrec, aFlags); +} + +/* +** The workhorse sprintf code. +*/ +int nsTextFormatter::dosprintf(SprintfStateStr* aState, const char16_t* aFmt, + mozilla::Span<BoxedValue> aValues) { + static const char16_t space = ' '; + static const char16_t hex[] = u"0123456789abcdef"; + static const char16_t HEX[] = u"0123456789ABCDEF"; + static const BoxedValue emptyString(u""); + + char16_t c; + int flags, width, prec, radix; + + const char16_t* hexp; + + // Next argument for non-numbered arguments. + size_t nextNaturalArg = 0; + // True if we ever saw a numbered argument. + bool sawNumberedArg = false; + + while ((c = *aFmt++) != 0) { + int rv; + + if (c != '%') { + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + + // Save the location of the "%" in case we decide it isn't a + // format and want to just emit the text from the format string. + const char16_t* percentPointer = aFmt - 1; + + /* + ** Gobble up the % format string. Hopefully we have handled all + ** of the strange cases! + */ + flags = 0; + c = *aFmt++; + if (c == '%') { + /* quoting a % with %% */ + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + + // Check for a numbered argument. + bool sawWidth = false; + const BoxedValue* thisArg = nullptr; + if (c >= '0' && c <= '9') { + size_t argNumber = 0; + while (c && c >= '0' && c <= '9') { + argNumber = (argNumber * 10) + (c - '0'); + c = *aFmt++; + } + + if (c == '$') { + // Mixing numbered arguments and implicit arguments is + // disallowed. + if (nextNaturalArg > 0) { + return -1; + } + + c = *aFmt++; + + // Numbered arguments start at 1. + --argNumber; + if (argNumber >= aValues.Length()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + thisArg = &emptyString; + } else { + thisArg = &aValues[argNumber]; + } + sawNumberedArg = true; + } else { + width = argNumber; + sawWidth = true; + } + } + + if (!sawWidth) { + /* + * Examine optional flags. Note that we do not implement the + * '#' flag of sprintf(). The ANSI C spec. of the '#' flag is + * somewhat ambiguous and not ideal, which is perhaps why + * the various sprintf() implementations are inconsistent + * on this feature. + */ + while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { + if (c == '-') { + flags |= _LEFT; + } + if (c == '+') { + flags |= _SIGNED; + } + if (c == ' ') { + flags |= _SPACED; + } + if (c == '0') { + flags |= _ZEROS; + } + c = *aFmt++; + } + if (flags & _SIGNED) { + flags &= ~_SPACED; + } + if (flags & _LEFT) { + flags &= ~_ZEROS; + } + + /* width */ + if (c == '*') { + // Not supported with numbered arguments. + if (sawNumberedArg) { + return -1; + } + + if (nextNaturalArg >= aValues.Length() || + !aValues[nextNaturalArg].IntCompatible()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + width = 0; + } else { + width = aValues[nextNaturalArg++].mValue.mInt; + } + c = *aFmt++; + } else { + width = 0; + while ((c >= '0') && (c <= '9')) { + width = (width * 10) + (c - '0'); + c = *aFmt++; + } + } + } + + /* precision */ + prec = -1; + if (c == '.') { + c = *aFmt++; + if (c == '*') { + // Not supported with numbered arguments. + if (sawNumberedArg) { + return -1; + } + + if (nextNaturalArg >= aValues.Length() || + !aValues[nextNaturalArg].IntCompatible()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + } else { + prec = aValues[nextNaturalArg++].mValue.mInt; + } + c = *aFmt++; + } else { + prec = 0; + while ((c >= '0') && (c <= '9')) { + prec = (prec * 10) + (c - '0'); + c = *aFmt++; + } + } + } + + // If the argument isn't known yet, find it now. This is done + // after the width and precision code, in case '*' was used. + if (thisArg == nullptr) { + // Mixing numbered arguments and implicit arguments is + // disallowed. + if (sawNumberedArg) { + return -1; + } + + if (nextNaturalArg >= aValues.Length()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + thisArg = &emptyString; + } else { + thisArg = &aValues[nextNaturalArg++]; + } + } + + /* Size. Defaults to 32 bits. */ + uint64_t mask = UINT32_MAX; + if (c == 'h') { + c = *aFmt++; + mask = UINT16_MAX; + } else if (c == 'L') { + c = *aFmt++; + mask = UINT64_MAX; + } else if (c == 'l') { + c = *aFmt++; + if (c == 'l') { + c = *aFmt++; + mask = UINT64_MAX; + } else { + mask = UINT32_MAX; + } + } + + /* format */ + hexp = hex; + radix = 10; + // Several `MOZ_ASSERT`s below check for argument compatibility + // with the format specifier. These are only debug assertions, + // not release assertions, and exist to catch problems in C++ + // callers of `nsTextFormatter`, as we do not have compile-time + // checking of format strings. In release mode, these assertions + // will be no-ops, and we will fall through to printing the + // argument based on the known type of the argument. + switch (c) { + case 'd': + case 'i': /* decimal/integer */ + MOZ_ASSERT(thisArg->IntCompatible()); + break; + + case 'o': /* octal */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 8; + flags |= _UNSIGNED; + break; + + case 'u': /* unsigned decimal */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 10; + flags |= _UNSIGNED; + break; + + case 'x': /* unsigned hex */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 16; + flags |= _UNSIGNED; + break; + + case 'X': /* unsigned HEX */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 16; + hexp = HEX; + flags |= _UNSIGNED; + break; + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + MOZ_ASSERT(thisArg->mKind == DOUBLE); + // Type-based printing below. + break; + + case 'S': + case 's': + MOZ_ASSERT(thisArg->mKind == STRING || thisArg->mKind == STRING16); + // Type-based printing below. + break; + + case 'c': { + if (!thisArg->IntCompatible()) { + MOZ_ASSERT(false); + // Type-based printing below. + break; + } + + if ((flags & _LEFT) == 0) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + char16_t ch = thisArg->mValue.mInt; + rv = (*aState->stuff)(aState, &ch, 1); + if (rv < 0) { + return rv; + } + if (flags & _LEFT) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + } + continue; + + case 'p': + if (!thisArg->PointerCompatible()) { + MOZ_ASSERT(false); + break; + } + static_assert(sizeof(uint64_t) >= sizeof(void*), + "pointers are larger than 64 bits"); + rv = cvt_ll(aState, uintptr_t(thisArg->mValue.mPtr), width, prec, 16, + flags | _UNSIGNED, hexp); + if (rv < 0) { + return rv; + } + continue; + + case 'n': + if (thisArg->mKind != INTPOINTER) { + return -1; + } + + if (thisArg->mValue.mIntPtr != nullptr) { + *thisArg->mValue.mIntPtr = aState->cur - aState->base; + } + continue; + + default: + /* Not a % token after all... skip it */ + rv = (*aState->stuff)(aState, percentPointer, aFmt - percentPointer); + if (rv < 0) { + return rv; + } + continue; + } + + // If we get here, we want to handle the argument according to its + // actual type; modified by the flags as appropriate. + switch (thisArg->mKind) { + case INT: + case UINT: { + int64_t val = thisArg->mValue.mInt; + if ((flags & _UNSIGNED) == 0 && val < 0) { + val = -val; + flags |= _NEG; + } + rv = cvt_ll(aState, uint64_t(val) & mask, width, prec, radix, flags, + hexp); + } break; + case INTPOINTER: + case POINTER: + // Always treat these as unsigned hex, no matter the format. + static_assert(sizeof(uint64_t) >= sizeof(void*), + "pointers are larger than 64 bits"); + rv = cvt_ll(aState, uintptr_t(thisArg->mValue.mPtr), width, prec, 16, + flags | _UNSIGNED, hexp); + break; + case DOUBLE: + if (c != 'f' && c != 'E' && c != 'e' && c != 'G' && c != 'g') { + // Pick some default. + c = 'g'; + } + rv = cvt_f(aState, thisArg->mValue.mDouble, width, prec, c, flags); + break; + case STRING: + rv = cvt_s(aState, thisArg->mValue.mString, width, prec, flags); + break; + case STRING16: + rv = cvt_S(aState, thisArg->mValue.mString16, width, prec, flags); + break; + default: + // Can't happen. + MOZ_ASSERT(0); + } + + if (rv < 0) { + return rv; + } + } + + return 0; +} + +/************************************************************************/ + +int nsTextFormatter::StringStuff(nsTextFormatter::SprintfStateStr* aState, + const char16_t* aStr, uint32_t aLen) { + ptrdiff_t off = aState->cur - aState->base; + + nsAString* str = static_cast<nsAString*>(aState->stuffclosure); + str->Append(aStr, aLen); + + aState->base = str->BeginWriting(); + aState->cur = aState->base + off; + + return 0; +} + +void nsTextFormatter::vssprintf(nsAString& aOut, const char16_t* aFmt, + mozilla::Span<BoxedValue> aValues) { + SprintfStateStr ss; + ss.stuff = StringStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + ss.stuffclosure = &aOut; + + aOut.Truncate(); + dosprintf(&ss, aFmt, aValues); +} + +/* +** Stuff routine that discards overflow data +*/ +int nsTextFormatter::LimitStuff(SprintfStateStr* aState, const char16_t* aStr, + uint32_t aLen) { + uint32_t limit = aState->maxlen - (aState->cur - aState->base); + + if (aLen > limit) { + aLen = limit; + } + while (aLen) { + --aLen; + *aState->cur++ = *aStr++; + } + return 0; +} + +uint32_t nsTextFormatter::vsnprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, + mozilla::Span<BoxedValue> aValues) { + SprintfStateStr ss; + + MOZ_ASSERT((int32_t)aOutLen > 0); + if ((int32_t)aOutLen <= 0) { + return 0; + } + + ss.stuff = LimitStuff; + ss.base = aOut; + ss.cur = aOut; + ss.maxlen = aOutLen; + int result = dosprintf(&ss, aFmt, aValues); + + if (ss.cur == ss.base) { + return 0; + } + + // Append a NUL. However, be sure not to count it in the returned + // length. + if (ss.cur - ss.base >= ptrdiff_t(ss.maxlen)) { + --ss.cur; + } + *ss.cur = '\0'; + + // Check the result now, so that an unterminated string can't + // possibly escape. + if (result < 0) { + return -1; + } + + return ss.cur - ss.base; +} diff --git a/xpcom/string/nsTextFormatter.h b/xpcom/string/nsTextFormatter.h new file mode 100644 index 0000000000..f571043da2 --- /dev/null +++ b/xpcom/string/nsTextFormatter.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/. */ + +/* + * This code was copied from xpcom/ds/nsTextFormatter r1.3 + * Memory model and Frozen linkage changes only. + * -- Prasad <prasad@medhas.org> + */ + +#ifndef nsTextFormatter_h___ +#define nsTextFormatter_h___ + +/* + ** API for PR printf like routines. Supports the following formats + ** %d - decimal + ** %u - unsigned decimal + ** %x - unsigned hex + ** %X - unsigned uppercase hex + ** %o - unsigned octal + ** %hd, %hu, %hx, %hX, %ho - 16-bit versions of above + ** %ld, %lu, %lx, %lX, %lo - 32-bit versions of above + ** %lld, %llu, %llx, %llX, %llo - 64 bit versions of above + ** %s - utf8 string + ** %S - char16_t string + ** %c - character + ** %p - pointer (deals with machine dependent pointer size) + ** %f - float + ** %g - float + */ +#include <stdio.h> +#include <stdarg.h> +#include "nscore.h" +#include "nsString.h" +#include "mozilla/Span.h" + +#ifdef XPCOM_GLUE +# error \ + "nsTextFormatter is not available in the standalone glue due to NSPR dependencies." +#endif + +class nsTextFormatter { + public: + /* + * sprintf into a fixed size buffer. Guarantees that the buffer is null + * terminated. Returns the length of the written output, NOT including the + * null terminator, or (uint32_t)-1 if an error occurs. + */ + template <typename... T> + static uint32_t snprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, T... aArgs) { + BoxedValue values[] = {BoxedValue(aArgs)...}; + return vsnprintf(aOut, aOutLen, aFmt, + mozilla::Span(values, sizeof...(aArgs))); + } + + /* + * sprintf into an existing nsAString, overwriting any contents it already + * has. Infallible. + */ + template <typename... T> + static void ssprintf(nsAString& aOut, const char16_t* aFmt, T... aArgs) { + BoxedValue values[] = {BoxedValue(aArgs)...}; + vssprintf(aOut, aFmt, mozilla::Span(values, sizeof...(aArgs))); + } + + private: + enum ArgumentKind { + INT, + UINT, + POINTER, + DOUBLE, + STRING, + STRING16, + INTPOINTER, + }; + + union ValueUnion { + int64_t mInt; + uint64_t mUInt; + void const* mPtr; + double mDouble; + char const* mString; + char16_t const* mString16; + int* mIntPtr; + }; + + struct BoxedValue { + ArgumentKind mKind; + ValueUnion mValue; + + explicit BoxedValue(int aArg) : mKind(INT) { mValue.mInt = aArg; } + + explicit BoxedValue(unsigned int aArg) : mKind(UINT) { + mValue.mUInt = aArg; + } + + explicit BoxedValue(long aArg) : mKind(INT) { mValue.mInt = aArg; } + + explicit BoxedValue(unsigned long aArg) : mKind(UINT) { + mValue.mUInt = aArg; + } + + explicit BoxedValue(long long aArg) : mKind(INT) { mValue.mInt = aArg; } + + explicit BoxedValue(unsigned long long aArg) : mKind(UINT) { + mValue.mUInt = aArg; + } + + explicit BoxedValue(const void* aArg) : mKind(POINTER) { + mValue.mPtr = aArg; + } + + explicit BoxedValue(double aArg) : mKind(DOUBLE) { mValue.mDouble = aArg; } + + explicit BoxedValue(const char* aArg) : mKind(STRING) { + mValue.mString = aArg; + } + + explicit BoxedValue(const char16_t* aArg) : mKind(STRING16) { + mValue.mString16 = aArg; + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + explicit BoxedValue(const char16ptr_t aArg) : mKind(STRING16) { + mValue.mString16 = aArg; + } + +#endif + + explicit BoxedValue(int* aArg) : mKind(INTPOINTER) { + mValue.mIntPtr = aArg; + } + + bool IntCompatible() const { return mKind == INT || mKind == UINT; } + + bool PointerCompatible() const { + return mKind == POINTER || mKind == STRING || mKind == STRING16 || + mKind == INTPOINTER; + } + }; + + struct SprintfStateStr; + + static int fill2(SprintfStateStr* aState, const char16_t* aSrc, int aSrcLen, + int aWidth, int aFlags); + static int fill_n(SprintfStateStr* aState, const char16_t* aSrc, int aSrcLen, + int aWidth, int aPrec, int aFlags); + static int cvt_ll(SprintfStateStr* aState, uint64_t aNum, int aWidth, + int aPrec, int aRadix, int aFlags, const char16_t* aHexStr); + static int cvt_f(SprintfStateStr* aState, double aDouble, int aWidth, + int aPrec, const char16_t aType, int aFlags); + static int cvt_S(SprintfStateStr* aState, const char16_t* aStr, int aWidth, + int aPrec, int aFlags); + static int cvt_s(SprintfStateStr* aState, const char* aStr, int aWidth, + int aPrec, int aFlags); + static int dosprintf(SprintfStateStr* aState, const char16_t* aFmt, + mozilla::Span<BoxedValue> aValues); + static int StringStuff(SprintfStateStr* aState, const char16_t* aStr, + uint32_t aLen); + static int LimitStuff(SprintfStateStr* aState, const char16_t* aStr, + uint32_t aLen); + static uint32_t vsnprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, + mozilla::Span<BoxedValue> aValues); + static void vssprintf(nsAString& aOut, const char16_t* aFmt, + mozilla::Span<BoxedValue> aValues); +}; + +#endif /* nsTextFormatter_h___ */ diff --git a/xpcom/string/nsUTF8Utils.h b/xpcom/string/nsUTF8Utils.h new file mode 100644 index 0000000000..0145011ec1 --- /dev/null +++ b/xpcom/string/nsUTF8Utils.h @@ -0,0 +1,247 @@ +/* -*- 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 nsUTF8Utils_h_ +#define nsUTF8Utils_h_ + +// NB: This code may be used from non-XPCOM code, in particular, the +// standalone updater executable. That is, this file may be used in +// two ways: if MOZILLA_INTERNAL_API is defined, this file will +// provide signatures for the Mozilla abstract string types. It will +// use XPCOM assertion/debugging macros, etc. + +#include <type_traits> + +#include "mozilla/Assertions.h" +#include "mozilla/EndianUtils.h" + +#include "nsCharTraits.h" + +#ifdef MOZILLA_INTERNAL_API +# define UTF8UTILS_WARNING(msg) NS_WARNING(msg) +#else +# define UTF8UTILS_WARNING(msg) +#endif + +class UTF8traits { + public: + static bool isASCII(char aChar) { return (aChar & 0x80) == 0x00; } + static bool isInSeq(char aChar) { return (aChar & 0xC0) == 0x80; } + static bool is2byte(char aChar) { return (aChar & 0xE0) == 0xC0; } + static bool is3byte(char aChar) { return (aChar & 0xF0) == 0xE0; } + static bool is4byte(char aChar) { return (aChar & 0xF8) == 0xF0; } + static bool is5byte(char aChar) { return (aChar & 0xFC) == 0xF8; } + static bool is6byte(char aChar) { return (aChar & 0xFE) == 0xFC; } + // return the number of bytes in a sequence beginning with aChar + static int bytes(char aChar) { + if (isASCII(aChar)) { + return 1; + } + if (is2byte(aChar)) { + return 2; + } + if (is3byte(aChar)) { + return 3; + } + if (is4byte(aChar)) { + return 4; + } + MOZ_ASSERT_UNREACHABLE("should not be used for in-sequence characters"); + return 1; + } +}; + +/** + * Extract the next Unicode scalar value from the buffer and return it. The + * pointer passed in is advanced to the start of the next character in the + * buffer. Upon error, the return value is 0xFFFD, *aBuffer is advanced + * over the maximal valid prefix and *aErr is set to true (if aErr is not + * null). + * + * Note: This method never sets *aErr to false to allow error accumulation + * across multiple calls. + * + * Precondition: *aBuffer < aEnd + */ +class UTF8CharEnumerator { + public: + static inline char32_t NextChar(const char** aBuffer, const char* aEnd, + bool* aErr = nullptr) { + MOZ_ASSERT(aBuffer, "null buffer pointer pointer"); + MOZ_ASSERT(aEnd, "null end pointer"); + + const unsigned char* p = reinterpret_cast<const unsigned char*>(*aBuffer); + const unsigned char* end = reinterpret_cast<const unsigned char*>(aEnd); + + MOZ_ASSERT(p, "null buffer"); + MOZ_ASSERT(p < end, "Bogus range"); + + unsigned char first = *p; + ++p; + + if (MOZ_LIKELY(first < 0x80U)) { + *aBuffer = reinterpret_cast<const char*>(p); + return first; + } + + // Unsigned underflow is defined behavior + if (MOZ_UNLIKELY((p == end) || ((first - 0xC2U) >= (0xF5U - 0xC2U)))) { + *aBuffer = reinterpret_cast<const char*>(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } + + unsigned char second = *p; + + if (first < 0xE0U) { + // Two-byte + if (MOZ_LIKELY((second & 0xC0U) == 0x80U)) { + ++p; + *aBuffer = reinterpret_cast<const char*>(p); + return ((uint32_t(first) & 0x1FU) << 6) | (uint32_t(second) & 0x3FU); + } + *aBuffer = reinterpret_cast<const char*>(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } + + if (MOZ_LIKELY(first < 0xF0U)) { + // Three-byte + unsigned char lower = 0x80U; + unsigned char upper = 0xBFU; + if (first == 0xE0U) { + lower = 0xA0U; + } else if (first == 0xEDU) { + upper = 0x9FU; + } + if (MOZ_LIKELY(second >= lower && second <= upper)) { + ++p; + if (MOZ_LIKELY(p != end)) { + unsigned char third = *p; + if (MOZ_LIKELY((third & 0xC0U) == 0x80U)) { + ++p; + *aBuffer = reinterpret_cast<const char*>(p); + return ((uint32_t(first) & 0xFU) << 12) | + ((uint32_t(second) & 0x3FU) << 6) | + (uint32_t(third) & 0x3FU); + } + } + } + *aBuffer = reinterpret_cast<const char*>(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } + + // Four-byte + unsigned char lower = 0x80U; + unsigned char upper = 0xBFU; + if (first == 0xF0U) { + lower = 0x90U; + } else if (first == 0xF4U) { + upper = 0x8FU; + } + if (MOZ_LIKELY(second >= lower && second <= upper)) { + ++p; + if (MOZ_LIKELY(p != end)) { + unsigned char third = *p; + if (MOZ_LIKELY((third & 0xC0U) == 0x80U)) { + ++p; + if (MOZ_LIKELY(p != end)) { + unsigned char fourth = *p; + if (MOZ_LIKELY((fourth & 0xC0U) == 0x80U)) { + ++p; + *aBuffer = reinterpret_cast<const char*>(p); + return ((uint32_t(first) & 0x7U) << 18) | + ((uint32_t(second) & 0x3FU) << 12) | + ((uint32_t(third) & 0x3FU) << 6) | + (uint32_t(fourth) & 0x3FU); + } + } + } + } + } + *aBuffer = reinterpret_cast<const char*>(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } +}; + +/** + * Extract the next Unicode scalar value from the buffer and return it. The + * pointer passed in is advanced to the start of the next character in the + * buffer. Upon error, the return value is 0xFFFD, *aBuffer is advanced over + * the unpaired surrogate and *aErr is set to true (if aErr is not null). + * + * Note: This method never sets *aErr to false to allow error accumulation + * across multiple calls. + * + * Precondition: *aBuffer < aEnd + */ +class UTF16CharEnumerator { + public: + static inline char32_t NextChar(const char16_t** aBuffer, + const char16_t* aEnd, bool* aErr = nullptr) { + MOZ_ASSERT(aBuffer, "null buffer pointer pointer"); + MOZ_ASSERT(aEnd, "null end pointer"); + + const char16_t* p = *aBuffer; + + MOZ_ASSERT(p, "null buffer"); + MOZ_ASSERT(p < aEnd, "Bogus range"); + + char16_t c = *p++; + + // Let's use encoding_rs-style code golf here. + // Unsigned underflow is defined behavior + char16_t cMinusSurrogateStart = c - 0xD800U; + if (MOZ_LIKELY(cMinusSurrogateStart > (0xDFFFU - 0xD800U))) { + *aBuffer = p; + return c; + } + if (MOZ_LIKELY(cMinusSurrogateStart <= (0xDBFFU - 0xD800U))) { + // High surrogate + if (MOZ_LIKELY(p != aEnd)) { + char16_t second = *p; + // Unsigned underflow is defined behavior + if (MOZ_LIKELY((second - 0xDC00U) <= (0xDFFFU - 0xDC00U))) { + *aBuffer = ++p; + return (uint32_t(c) << 10) + uint32_t(second) - + (((0xD800U << 10) - 0x10000U) + 0xDC00U); + } + } + } + // Unpaired surrogate + *aBuffer = p; + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } +}; + +template <typename Char, typename UnsignedT> +inline UnsignedT RewindToPriorUTF8Codepoint(const Char* utf8Chars, + UnsignedT index) { + static_assert(std::is_same_v<Char, char> || + std::is_same_v<Char, unsigned char> || + std::is_same_v<Char, signed char>, + "UTF-8 data must be in 8-bit units"); + static_assert(std::is_unsigned_v<UnsignedT>, "index type must be unsigned"); + while (index > 0 && (utf8Chars[index] & 0xC0) == 0x80) --index; + + return index; +} + +#undef UTF8UTILS_WARNING + +#endif /* !defined(nsUTF8Utils_h_) */ diff --git a/xpcom/system/moz.build b/xpcom/system/moz.build new file mode 100644 index 0000000000..ced4fa16d2 --- /dev/null +++ b/xpcom/system/moz.build @@ -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/. + +XPIDL_SOURCES += [ + "nsIBlocklistService.idl", + "nsICrashReporter.idl", + "nsIDeviceSensors.idl", + "nsIGeolocationProvider.idl", + "nsIGIOService.idl", + "nsIGSettingsService.idl", + "nsIHapticFeedback.idl", + "nsIPlatformInfo.idl", + "nsISystemInfo.idl", + "nsIXULAppInfo.idl", + "nsIXULRuntime.idl", +] + +XPIDL_MODULE = "xpcom_system" + +with Files("nsIBlocklistService.idl"): + BUG_COMPONENT = ("Toolkit", "Blocklist Implementation") diff --git a/xpcom/system/nsIBlocklistService.idl b/xpcom/system/nsIBlocklistService.idl new file mode 100644 index 0000000000..37fb864111 --- /dev/null +++ b/xpcom/system/nsIBlocklistService.idl @@ -0,0 +1,24 @@ +/* -*- 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" + +[scriptable, uuid(a6dcc76e-9f62-4cc1-a470-b483a1a6f096)] +interface nsIBlocklistService : nsISupports +{ + // Indicates that the item does not appear in the blocklist. + const unsigned long STATE_NOT_BLOCKED = 0; + // Indicates that the item is in the blocklist but the problem is not severe + // enough to warant forcibly blocking. + const unsigned long STATE_SOFTBLOCKED = 1; + // Indicates that the item should be blocked and never used. + const unsigned long STATE_BLOCKED = 2; + + // Unused; Please increment if we add more blocklist states. + const unsigned long STATE_MAX = 3; + + readonly attribute boolean isLoaded; +}; diff --git a/xpcom/system/nsICrashReporter.idl b/xpcom/system/nsICrashReporter.idl new file mode 100644 index 0000000000..6fd0967e75 --- /dev/null +++ b/xpcom/system/nsICrashReporter.idl @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIURL; + +/** + * Provides access to crash reporting functionality. + * + * @status UNSTABLE - This interface is not frozen and will probably change in + * future releases. + */ + +[scriptable, uuid(4b74c39a-cf69-4a8a-8e63-169d81ad1ecf)] +interface nsICrashReporter : nsISupports +{ + /** + * Get the enabled status of the crash reporter. + */ + readonly attribute boolean crashReporterEnabled; + + /** + * Enable or disable crash reporting at runtime. Not available to script + * because the JS engine relies on proper exception handler chaining. + */ + [noscript] + void setEnabled(in bool enabled); + + /** + * Get or set the URL to which crash reports will be submitted. + * Only https and http URLs are allowed, as the submission is handled + * by OS-native networking libraries. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting is not initialized + * @throw NS_ERROR_INVALID_ARG on set if a non-http(s) URL is assigned + * @throw NS_ERROR_FAILURE on get if no URL is set + */ + attribute nsIURL serverURL; + + /** + * Get or set the path on the local system to which minidumps will be + * written when a crash happens. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting is not initialized + */ + attribute nsIFile minidumpPath; + + /** + * Get the minidump file corresponding to the specified ID. + * + * @param id + * ID of the crash. Likely a UUID. + * + * @return The minidump file associated with the ID. + * + * @throw NS_ERROR_FILE_NOT_FOUND if the minidump could not be found + */ + nsIFile getMinidumpForID(in AString id); + + /** + * Get the extra file corresponding to the specified ID. + * + * @param id + * ID of the crash. Likely a UUID. + * + * @return The extra file associated with the ID. + * + * @throw NS_ERROR_FILE_NOT_FOUND if the extra file could not be found + */ + nsIFile getExtraFileForID(in AString id); + + /** + * Add some extra data to be submitted with a crash report. + * + * @param key + * Name of a known crash annotation constant. + * @param data + * Data to be added. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_INVALID_ARG if key contains an invalid value or data + * contains invalid characters. Invalid + * character for data is '\0'. + */ + void annotateCrashReport(in AUTF8String key, in AUTF8String data); + + /** + * Remove a crash report annotation. + * + * @param key + * Name of a known crash annotation constant. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_INVALID_ARG if key contains an invalid value. + */ + void removeCrashReportAnnotation(in AUTF8String key); + + /** + * Checks if an annotation is allowed for inclusion in the crash ping. + * + * @param key + * Name of a known crash annotation constant. + * + * @return True if the specified value is a valid annotation and can be + included in the crash ping, false otherwise. + * @throw NS_ERROR_INVALID_ARG if key contains an invalid value. + */ + boolean isAnnotationAllowedForPing(in ACString value); + + /** + * Append some data to the "Notes" field, to be submitted with a crash report. + * Unlike annotateCrashReport, this method will append to existing data. + * + * @param data + * Data to be added. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_INVALID_ARG if data contains invalid characters. + * The only invalid character is '\0'. + */ + void appendAppNotesToCrashReport(in ACString data); + + /** + * Register a given memory range to be included in the crash report. + * + * @param ptr + * The starting address for the bytes. + * @param size + * The number of bytes to include. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_NOT_IMPLEMENTED if unavailable on the current OS + */ + void registerAppMemory(in unsigned long long ptr, in unsigned long long size); + + /** + * Write a minidump immediately, with the user-supplied exception + * information. This is implemented on Windows only, because + * SEH (structured exception handling) exists on Windows only. + * + * @param aExceptionInfo EXCEPTION_INFO* provided by Window's SEH + */ + [noscript] void writeMinidumpForException(in voidPtr aExceptionInfo); + + /** + * Append note containing an Obj-C exception's info. + * + * @param aException NSException object to append note for + */ + [noscript] void appendObjCExceptionInfoToAppNotes(in voidPtr aException); + + /** + * User preference for submitting crash reports. + */ + attribute boolean submitReports; + + /** + * Cause the crash reporter to re-evaluate where crash events should go. + * + * This should be called during application startup and whenever profiles + * change. + */ + void UpdateCrashEventsDir(); + + /** + * Save an anonymized memory report file for inclusion in a future crash + * report in this session. + * + * @throws NS_ERROR_NOT_INITIALIZED if crash reporting is disabled. + */ + void saveMemoryReport(); +}; diff --git a/xpcom/system/nsIDeviceSensors.idl b/xpcom/system/nsIDeviceSensors.idl new file mode 100644 index 0000000000..dcb67cb925 --- /dev/null +++ b/xpcom/system/nsIDeviceSensors.idl @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIDOMWindow; + +[scriptable, uuid(0462247e-fe8c-4aa5-b675-3752547e485f)] +interface nsIDeviceSensorData : nsISupports +{ + // Keep in sync with hal/HalSensor.h + + // 1. TYPE_ORIENTATION: absolute device orientation, not spec-conform and + // deprecated on Android. + // 2. TYPE_ROTATION_VECTOR: absolute device orientation affected by drift. + // 3. TYPE_GAME_ROTATION_VECTOR: relative device orientation less affected by drift. + // Preferred fallback priorities on Android are [3, 2, 1] for the general case + // and [2, 1] if absolute orientation (compass heading) is required. + const unsigned long TYPE_ORIENTATION = 0; + const unsigned long TYPE_ACCELERATION = 1; + const unsigned long TYPE_PROXIMITY = 2; + const unsigned long TYPE_LINEAR_ACCELERATION = 3; + const unsigned long TYPE_GYROSCOPE = 4; + const unsigned long TYPE_LIGHT = 5; + const unsigned long TYPE_ROTATION_VECTOR = 6; + const unsigned long TYPE_GAME_ROTATION_VECTOR = 7; + + readonly attribute unsigned long type; + + readonly attribute double x; + readonly attribute double y; + readonly attribute double z; +}; + +[scriptable, uuid(e46e47c7-55ff-44c4-abce-21b14ba07f86)] +interface nsIDeviceSensors : nsISupports +{ + /** + * Returns true if the given window has any listeners of the given type + */ + bool hasWindowListener(in unsigned long aType, in nsIDOMWindow aWindow); + + // Holds pointers, not AddRef objects -- it is up to the caller + // to call RemoveWindowListener before the window is deleted. + + [noscript] void addWindowListener(in unsigned long aType, in nsIDOMWindow aWindow); + [noscript] void removeWindowListener(in unsigned long aType, in nsIDOMWindow aWindow); + [noscript] void removeWindowAsListener(in nsIDOMWindow aWindow); +}; + +%{C++ + +#define NS_DEVICE_SENSORS_CID \ +{ 0xecba5203, 0x77da, 0x465a, \ +{ 0x86, 0x5e, 0x78, 0xb7, 0xaf, 0x10, 0xd8, 0xf7 } } + +#define NS_DEVICE_SENSORS_CONTRACTID "@mozilla.org/devicesensors;1" + +%} diff --git a/xpcom/system/nsIGIOService.idl b/xpcom/system/nsIGIOService.idl new file mode 100644 index 0000000000..432a519e3a --- /dev/null +++ b/xpcom/system/nsIGIOService.idl @@ -0,0 +1,89 @@ +/* -*- 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" +#include "nsIMIMEInfo.idl" + +interface nsIUTF8StringEnumerator; +interface nsIURI; +interface nsIFile; +interface nsIMutableArray; + +/* nsIGIOMimeApp holds information about an application that is looked up + with nsIGIOService::GetAppForMimeType. */ +// 66009894-9877-405b-9321-bf30420e34e6 prev uuid + +[scriptable, uuid(ca6bad0c-8a48-48ac-82c7-27bb8f510fbe)] +interface nsIGIOMimeApp : nsIHandlerApp +{ + const long EXPECTS_URIS = 0; + const long EXPECTS_PATHS = 1; + const long EXPECTS_URIS_FOR_NON_FILES = 2; + + readonly attribute AUTF8String id; + readonly attribute AUTF8String command; + readonly attribute long expectsURIs; // see constants above + readonly attribute nsIUTF8StringEnumerator supportedURISchemes; + + void setAsDefaultForMimeType(in AUTF8String mimeType); + void setAsDefaultForFileExtensions(in AUTF8String extensions); + void setAsDefaultForURIScheme(in AUTF8String uriScheme); +}; + +/* + * The VFS service makes use of two distinct registries. + * + * The application registry holds information about applications (uniquely + * identified by id), such as which MIME types and URI schemes they are + * capable of handling, whether they run in a terminal, etc. + * + * The MIME registry holds information about MIME types, such as which + * extensions map to a given MIME type. The MIME registry also stores the + * id of the application selected to handle each MIME type. + */ + +// prev id dea20bf0-4e4d-48c5-b932-dc3e116dc64b +[scriptable, builtinclass, uuid(eda22a30-84e1-4e16-9ca0-cd1553c2b34a)] +interface nsIGIOService : nsISupports +{ + + /*** MIME registry methods ***/ + + /* Obtain the MIME type registered for an extension. The extension + should not include a leading dot. */ + AUTF8String getMimeTypeFromExtension(in AUTF8String extension); + + /* Obtain the preferred application for opening a given URI scheme */ + nsIHandlerApp getAppForURIScheme(in AUTF8String aURIScheme); + + /* Obtain list of application capable of opening given URI scheme */ + nsIMutableArray getAppsForURIScheme(in AUTF8String aURIScheme); + + /* Obtain the preferred application for opening a given MIME type */ + nsIHandlerApp getAppForMimeType(in AUTF8String mimeType); + + /* Create application info for given command and name */ + nsIGIOMimeApp createAppFromCommand(in AUTF8String cmd, + in AUTF8String appName); + + /* Find the application info by given command */ + nsIGIOMimeApp findAppFromCommand(in AUTF8String cmd); + + /* Obtain a description for the given MIME type */ + AUTF8String getDescriptionForMimeType(in AUTF8String mimeType); + + /*** Misc. methods ***/ + [infallible] readonly attribute boolean isRunningUnderFlatpak; + [infallible] readonly attribute boolean isRunningUnderSnap; + + /* Open the given URI in the default application */ + [noscript] void showURI(in nsIURI uri); + [noscript] void revealFile(in nsIFile file); + [noscript] void launchFile(in ACString path); +}; + +%{C++ +#define NS_GIOSERVICE_CONTRACTID "@mozilla.org/gio-service;1" +%} diff --git a/xpcom/system/nsIGSettingsService.idl b/xpcom/system/nsIGSettingsService.idl new file mode 100644 index 0000000000..26d86a77e0 --- /dev/null +++ b/xpcom/system/nsIGSettingsService.idl @@ -0,0 +1,30 @@ +/* -*- 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" + +interface nsIArray; + +[scriptable, uuid(16d5b0ed-e756-4f1b-a8ce-9132e869acd8)] +interface nsIGSettingsCollection : nsISupports +{ + void setString(in AUTF8String key, in AUTF8String value); + void setBoolean(in AUTF8String key, in boolean value); + void setInt(in AUTF8String key, in long value); + AUTF8String getString(in AUTF8String key); + boolean getBoolean(in AUTF8String key); + long getInt(in AUTF8String key); + nsIArray getStringList(in AUTF8String key); +}; + +[scriptable, uuid(849c088b-57d1-4f24-b7b2-3dc4acb04c0a)] +interface nsIGSettingsService : nsISupports +{ + nsIGSettingsCollection getCollectionForSchema(in AUTF8String schema); +}; + +%{C++ +#define NS_GSETTINGSSERVICE_CONTRACTID "@mozilla.org/gsettings-service;1" +%} diff --git a/xpcom/system/nsIGeolocationProvider.idl b/xpcom/system/nsIGeolocationProvider.idl new file mode 100644 index 0000000000..ff2f5a5004 --- /dev/null +++ b/xpcom/system/nsIGeolocationProvider.idl @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIURI; +interface nsIDOMGeoPosition; +interface nsIGeolocationPrompt; + +/** + + * Interface provides a way for a geolocation provider to + * notify the system that a new location is available. + */ +[scriptable, uuid(643dc5e9-b911-4b2c-8d44-603162696baf)] +interface nsIGeolocationUpdate : nsISupports { + + /** + * Notify the geolocation service that a new geolocation + * has been discovered. + * This must be called on the main thread + */ + void update(in nsIDOMGeoPosition position); + /** + * Notify the geolocation service of an error. + * This must be called on the main thread. + * The parameter refers to one of the constants in the + * nsIDOMGeoPositionError interface. + * Use this to report spurious errors coming from the + * provider; for errors occurring inside the methods in + * the nsIGeolocationProvider interface, just use the return + * value. + */ + [can_run_script] + void notifyError(in unsigned short error); +}; + + +/** + * Interface provides location information to the nsGeolocator + * via the nsIDOMGeolocationCallback interface. After + * startup is called, any geo location change should call + * callback.update(). + */ +[scriptable, uuid(AC4A133B-9F92-4F7C-B369-D40CB6B17650)] +interface nsIGeolocationProvider : nsISupports { + + /** + * Start up the provider. This is called before any other + * method. may be called multiple times. + */ + void startup(); + + /** + * watch + * When a location change is observed, notify the callback. + */ + void watch(in nsIGeolocationUpdate callback); + + /** + * shutdown + * Shuts down the location device. + */ + void shutdown(); + + /** + * hint to provide to use any amount of power to provide a better result + */ + void setHighAccuracy(in boolean enable); + +}; + +%{C++ +/* + This must be implemented by geolocation providers. It + must support nsIGeolocationProvider. +*/ +#define NS_GEOLOCATION_PROVIDER_CONTRACTID "@mozilla.org/geolocation/provider;1" +%} diff --git a/xpcom/system/nsIHapticFeedback.idl b/xpcom/system/nsIHapticFeedback.idl new file mode 100644 index 0000000000..25d0d8e7bb --- /dev/null +++ b/xpcom/system/nsIHapticFeedback.idl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + + +[scriptable, uuid(91917c98-a8f3-4c98-8f10-4afb872f54c7)] +interface nsIHapticFeedback : nsISupports { + + const long ShortPress = 0; + const long LongPress = 1; + + /** + * Perform haptic feedback + * + * @param isLongPress + * indicate whether feedback is for a long press (vs. short press) + */ + void performSimpleAction(in long isLongPress); +}; diff --git a/xpcom/system/nsIPlatformInfo.idl b/xpcom/system/nsIPlatformInfo.idl new file mode 100644 index 0000000000..fba78b6ecc --- /dev/null +++ b/xpcom/system/nsIPlatformInfo.idl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +[scriptable, uuid(ab6650cf-0806-4aea-b8f2-40fdae74f1cc)] +interface nsIPlatformInfo : nsISupports +{ + /** + * The version of the XULRunner platform. + */ + readonly attribute ACString platformVersion; + + /** + * The build ID/date of gecko and the XULRunner platform. + */ + readonly attribute ACString platformBuildID; +}; diff --git a/xpcom/system/nsISystemInfo.idl b/xpcom/system/nsISystemInfo.idl new file mode 100644 index 0000000000..0cadb7e498 --- /dev/null +++ b/xpcom/system/nsISystemInfo.idl @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + + +[scriptable, uuid(09a0502b-cedc-4cae-bf7c-35662dbd1249)] +interface nsISystemInfo : nsISupports +{ + /** + * Asynchronously get info about what types of disks we're using for the + * profile and binary. + * Note: only implemented on Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise diskInfo; + + /** + * Asynchronously get CountryCode info. + * Note: only implemented on macOS and Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise countryCode; + + /** + * Asynchronously gets OS info on the system's install year. + * Note: only implemented on Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise osInfo; + + /** + * Asynchronously gets process info that indicates if the process is running + * under Wow64 and WowARM64. + * Note: only implemented on Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise processInfo; +}; diff --git a/xpcom/system/nsIXULAppInfo.idl b/xpcom/system/nsIXULAppInfo.idl new file mode 100644 index 0000000000..502052eb79 --- /dev/null +++ b/xpcom/system/nsIXULAppInfo.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIPlatformInfo.idl" + +/** + * A scriptable interface to the nsXULAppAPI structure. See nsXULAppAPI.h for + * a detailed description of each attribute. + */ + +[scriptable, uuid(ddea4f31-3c5e-4769-ac68-21ab4b3d7845)] +interface nsIXULAppInfo : nsIPlatformInfo +{ + /** + * @see XREAppData.vendor + * @returns an empty string if XREAppData.vendor is not set. + */ + readonly attribute ACString vendor; + + /** + * @see XREAppData.name + */ + readonly attribute ACString name; + + /** + * @see XREAppData.ID + * @returns an empty string if XREAppData.ID is not set. + */ + readonly attribute ACString ID; + + /** + * The version of the XUL application. It is different than the + * version of the XULRunner platform. Be careful about which one you want. + * + * @see XREAppData.version + * @returns an empty string if XREAppData.version is not set. + */ + readonly attribute ACString version; + + /** + * The build ID/date of the application. For xulrunner applications, + * this will be different than the build ID of the platform. Be careful + * about which one you want. + */ + readonly attribute ACString appBuildID; + + /** + * @see XREAppData.UAName + * @returns an empty string if XREAppData.UAName is not set. + */ + readonly attribute ACString UAName; + + /** + * @see XREAppData.sourceURL + * @returns an empty string if XREAppData.sourceURL is not set. + */ + readonly attribute ACString sourceURL; + + /** + * @see XREAppData.updateURL + */ + readonly attribute ACString updateURL; +}; diff --git a/xpcom/system/nsIXULRuntime.idl b/xpcom/system/nsIXULRuntime.idl new file mode 100644 index 0000000000..8beadae9f1 --- /dev/null +++ b/xpcom/system/nsIXULRuntime.idl @@ -0,0 +1,393 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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++ + +namespace mozilla { +// Simple C++ getter for nsIXULRuntime::browserTabsRemoteAutostart +// This getter is a temporary function that checks for special +// conditions in which e10s support is not great yet, and should +// therefore be disabled. Bug 1065561 tracks its removal. +bool BrowserTabsRemoteAutostart(); +uint32_t GetMaxWebProcessCount(); + +// Returns the value of the fission.autostart pref. Since fission can be +// disabled on a per-window basis, this should only be used when you need the +// global value of the pref. For other use cases, you should use +// nsILoadContext::UseRemoteSubframes instead. This will also check for special +// conditions, like safe mode, which may require fission to be disabled, or +// environment variables MOZ_FORCE_ENABLE_FISSION and MOZ_FORCE_DISABLE_FISSION, +// used by mach run to enable/disable fission regardless of pref settings. +bool FissionAutostart(); + +// Returns whether or not we are currently enrolled in the fission experiment. +bool FissionExperimentEnrolled(); + +// Returns true if FissionAutostart() is true or +// fission.disableSessionHistoryInParent is false. +bool SessionHistoryInParent(); + +// Returns true if SessionHistoryInParent() returns true and +// fission.bfcacheInParent is true. +bool BFCacheInParent(); +} + +%} + +/** + * Provides information about the XUL runtime. + * @status UNSTABLE - This interface is not frozen and will probably change in + * future releases. If you need this functionality to be + * stable/frozen, please contact Benjamin Smedberg. + */ + +[scriptable, uuid(03602fac-fa3f-4a50-9baa-b88456fb4a0f)] +interface nsIXULRuntime : nsISupports +{ + /** + * Whether the application was launched in safe mode. + */ + readonly attribute boolean inSafeMode; + + /** + * The status of a given normandy experiment. + */ + cenum ExperimentStatus : 8 { + // The user is not actively enrolled in the experiment. + eExperimentStatusUnenrolled = 0, + // The user is enrolled in the control group, and should see the default + // behavior. + eExperimentStatusControl = 1, + // The user is enrolled in the treatment group, and should see the + // experimental behavior which is being tested. + eExperimentStatusTreatment = 2, + // The user was enrolled in the experiment, but became ineligible due to + // manually modifying a relevant preference. + eExperimentStatusDisqualified = 3, + // The user was selected for the phased Fission rollout. + eExperimentStatusRollout = 4, + + eExperimentStatusCount, + }; + + // If you update this enum, don't forget to raise the limit in + // TelemetryEnvironmentTesting.sys.mjs and record the new value in + // environment.rst + cenum ContentWin32kLockdownState : 8 { + LockdownEnabled = 1, // no longer used + MissingWebRender = 2, + OperatingSystemNotSupported = 3, + PrefNotSet = 4, // no longer used + MissingRemoteWebGL = 5, + MissingNonNativeTheming = 6, + DisabledByEnvVar = 7, // - MOZ_ENABLE_WIN32K is set + DisabledBySafeMode = 8, + DisabledByE10S = 9, // - E10S is disabled for whatever reason + DisabledByUserPref = 10, // - The user manually set + // security.sandbox.content.win32k-disable to false + EnabledByUserPref = 11, // The user manually set + // security.sandbox.content.win32k-disable to true + DisabledByControlGroup = + 12, // The user is in the Control Group, so it is disabled + EnabledByTreatmentGroup = + 13, // The user is in the Treatment Group, so it is enabled + DisabledByDefault = 14, // The default value of the pref is false + EnabledByDefault = 15, // The default value of the pref is true + DecodersArentRemote = 16, + IncompatibleMitigationPolicy = 17, // Some incompatible Windows Exploit Mitigation policies are enabled + }; + + // This is the current value of the experiment for the session + readonly attribute nsIXULRuntime_ExperimentStatus win32kExperimentStatus; + // This will return what the browser thinks is the _current_ status of win32k lockdown + // but this should only be used for testing + readonly attribute nsIXULRuntime_ContentWin32kLockdownState win32kLiveStatusTestingOnly; + // This is the current value of win32k lockdown for the session. It is set at startup, + // and never changed. + readonly attribute nsIXULRuntime_ContentWin32kLockdownState win32kSessionStatus; + + // NOTE: Please do not add new values to this enum without also updating the + // mapping in aboutSupport.js + cenum FissionDecisionStatus : 8 { + eFissionStatusUnknown = 0, + // Fission is disabled because the user is in the control group of a + // Normandy experiment. + eFissionExperimentControl = 1, + // Fission is enabled because the user is in the treatment group of a + // Normandy experiment. + eFissionExperimentTreatment = 2, + // Fission is disabled because the `MOZ_FORCE_DISABLE_E10S` environment + // variable is set. + eFissionDisabledByE10sEnv = 3, + // Fission is enabled because the `MOZ_FORCE_ENABLE_FISSION` environment + // variable is set. + eFissionEnabledByEnv = 4, + // Fission is disabled because the `MOZ_FORCE_DISABLE_FISSION` environment + // variable is set. + eFissionDisabledByEnv = 5, + // Fission is enabled because the "fission.autostart" preference is true + // by default. + eFissionEnabledByDefault = 7, + // Fission is disabled because the "fission.autostart" preference is false + // by default. + eFissionDisabledByDefault = 8, + // Fission is enabled because the "fission.autostart" preference was + // turned on by the user. + eFissionEnabledByUserPref = 9, + // Fission is enabled because the "fission.autostart" preference was + // turned on by the user. + eFissionDisabledByUserPref = 10, + // Fission is disabled because e10s is disabled for some other reason. + eFissionDisabledByE10sOther = 11, + // Fission is enabled by a Normandy phased rollout. + eFissionEnabledByRollout = 12, + }; + + /** + * Whether Fission should be automatically enabled for new browser windows. + * This may not match the value of the 'fission.autostart' pref. + * + * This value is guaranteed to remain constant for the length of a browser + * session. + */ + readonly attribute boolean fissionAutostart; + + /** + * The deciding factor which caused Fission to be enabled or disabled in + * this session. The string version is the same of the name of the constant, + * without the leading `eFission`, and with an initial lower-case letter. + */ + readonly attribute nsIXULRuntime_FissionDecisionStatus fissionDecisionStatus; + readonly attribute ACString fissionDecisionStatusString; + + /** + * Whether session history is stored in the parent process. + */ + readonly attribute boolean sessionHistoryInParent; + + /** + * Whether to write console errors to a log file. If a component + * encounters startup errors that might prevent the app from showing + * proper UI, it should set this flag to "true". + */ + attribute boolean logConsoleErrors; + + /** + * A string tag identifying the current operating system. This is taken + * from the OS_TARGET configure variable. It will always be available. + */ + readonly attribute AUTF8String OS; + + /** + * A string tag identifying the binary ABI of the current processor and + * compiler vtable. This is taken from the TARGET_XPCOM_ABI configure + * variable. It may not be available on all platforms, especially + * unusual processor or compiler combinations. + * + * The result takes the form <processor>-<compilerABI>, for example: + * x86-msvc + * ppc-gcc3 + * + * This value should almost always be used in combination with "OS". + * + * @throw NS_ERROR_NOT_AVAILABLE if not available. + */ + readonly attribute AUTF8String XPCOMABI; + + /** + * A string tag identifying the target widget toolkit in use. + * This is taken from the MOZ_WIDGET_TOOLKIT configure variable. + */ + readonly attribute AUTF8String widgetToolkit; + + /** + * The legal values of processType. + */ + const unsigned long PROCESS_TYPE_DEFAULT = 0; + const unsigned long PROCESS_TYPE_CONTENT = 2; + const unsigned long PROCESS_TYPE_IPDLUNITTEST = 3; + const unsigned long PROCESS_TYPE_GMPLUGIN = 4; + const unsigned long PROCESS_TYPE_GPU = 5; + const unsigned long PROCESS_TYPE_VR = 6; + const unsigned long PROCESS_TYPE_RDD = 7; + const unsigned long PROCESS_TYPE_SOCKET = 8; + const unsigned long PROCESS_TYPE_REMOTESANDBOXBROKER = 9; + const unsigned long PROCESS_TYPE_FORKSERVER = 10; + const unsigned long PROCESS_TYPE_UTILITY = 11; + + /** + * The type of the caller's process. Returns one of the values above. + */ + readonly attribute unsigned long processType; + + /** + * The system process ID of the caller's process. + */ + readonly attribute unsigned long processID; + + /** + * A globally unique and non-recycled ID of the caller's process. + */ + readonly attribute uint64_t uniqueProcessID; + + /** + * The type of remote content process we're running in. + * null if we're in the parent/chrome process. This can contain + * a URI if Fission is enabled, so don't use it for any kind of + * telemetry. + */ + readonly attribute AUTF8String remoteType; + + /** + * If true, browser tabs may be opened by default in a different process + * from the main browser UI. + */ + readonly attribute boolean browserTabsRemoteAutostart; + + /** + * Returns the number of content processes to use for normal web pages. If + * this value is > 1, then e10s-multi should be considered to be "on". + * + * NB: If browserTabsRemoteAutostart is false, then this value has no + * meaning and e10s should be considered to be "off"! + */ + readonly attribute uint32_t maxWebProcessCount; + + /** + * The current e10s-multi experiment number. Set dom.ipc.multiOptOut to (at + * least) this to disable it until the next experiment. + */ + const uint32_t E10S_MULTI_EXPERIMENT = 1; + + /** + * If true, the accessibility service is running. + */ + readonly attribute boolean accessibilityEnabled; + + /** + * Executable of Windows service that activated accessibility. + */ + readonly attribute AString accessibilityInstantiator; + + /** + * Indicates whether the current Firefox build is 64-bit. + */ + readonly attribute boolean is64Bit; + + /** + * Indicates whether or not text recognition of images supported by the OS. + */ + readonly attribute boolean isTextRecognitionSupported; + + /** + * Signal the apprunner to invalidate caches on the next restart. + * This will cause components to be autoregistered and all + * fastload data to be re-created. + */ + void invalidateCachesOnRestart(); + + /** + * Starts a child process. This method is intented to pre-start a + * content child process so that when it is actually needed, it is + * ready to go. + * + * @throw NS_ERROR_NOT_AVAILABLE if not available. + */ + void ensureContentProcess(); + + /** + * Modification time of the profile lock before the profile was locked on + * this startup. Used to know the last time the profile was used and not + * closed cleanly. This is set to 0 if there was no existing profile lock. + */ + readonly attribute PRTime replacedLockTime; + + /** + * The default update channel (MOZ_UPDATE_CHANNEL). + */ + readonly attribute AUTF8String defaultUpdateChannel; + + /** + * The distribution ID for this build (MOZ_DISTRIBUTION_ID). + */ + readonly attribute AUTF8String distributionID; + + /** + * True if Windows DLL blocklist initialized correctly. This is + * primarily for automated testing purposes. + */ + readonly attribute boolean windowsDLLBlocklistStatus; + + /** + * True if this application was started by the OS as part of an automatic + * restart mechanism (such as RegisterApplicationRestart on Windows). + */ + readonly attribute boolean restartedByOS; + + /** Whether the chrome color-scheme is dark */ + readonly attribute boolean chromeColorSchemeIsDark; + + /** Whether the content color-scheme derived from the app theme is dark */ + readonly attribute boolean contentThemeDerivedColorSchemeIsDark; + + /** Whether the user prefers reduced motion */ + readonly attribute boolean prefersReducedMotion; + + /** Whether we should draw over the titlebar */ + readonly attribute boolean drawInTitlebar; + + /** Returns the desktop environment identifier. Only meaningful on GTK */ + readonly attribute ACString desktopEnvironment; + + /** Whether we use Wayland. Only meaningful on GTK */ + readonly attribute boolean isWayland; + + /** + * The path of the shortcut used to start the current process, or "" if none. + * + * Windows Main process only, otherwise throws NS_ERROR_NOT_AVAILABLE + * + * May be mising in some cases where the user did launch from a shortcut: + * - If the updater ran on startup + * - If the AUMID was set before the shortcut could be saved + * + * @throw NS_ERROR_NOT_AVAILABLE if not available. + */ + readonly attribute AString processStartupShortcut; + + /** + * Returns a value corresponding to one of the + * |mozilla::LauncherRegistryInfo::EnabledState| values. + */ + readonly attribute uint32_t launcherProcessState; + + /** + * Returns the last application version that used the current profile or null + * if the last version could not be found (compatibility.ini was either + * missing or invalid). Throws NS_ERROR_UNAVAILABLE if called from a content + * process. + */ + readonly attribute ACString lastAppVersion; + + /** + * Returns the last application build ID that used the current profile or null + * if the last build ID could not be found (compatibility.ini was either + * missing or invalid). Throws NS_ERROR_UNAVAILABLE if called from a content + * process. + */ + readonly attribute ACString lastAppBuildID; +}; + + +%{C++ + +namespace mozilla { + +nsIXULRuntime::ContentWin32kLockdownState GetWin32kLockdownState(); + +} + +%} diff --git a/xpcom/tests/NotXPCOMTest.idl b/xpcom/tests/NotXPCOMTest.idl new file mode 100644 index 0000000000..acc1574e79 --- /dev/null +++ b/xpcom/tests/NotXPCOMTest.idl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +[scriptable, uuid(93142a4f-e4cf-424a-b833-e638f87d2607)] +interface nsIScriptableOK : nsISupports +{ + void method1(); +}; + +[scriptable, builtinclass, uuid(237d01a3-771e-4c6e-adf9-c97f9aab2950)] +interface nsIScriptableWithNotXPCOM : nsISupports +{ + [notxpcom] void method2(); +}; diff --git a/xpcom/tests/RegFactory.cpp b/xpcom/tests/RegFactory.cpp new file mode 100644 index 0000000000..7130ee5e1c --- /dev/null +++ b/xpcom/tests/RegFactory.cpp @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <iostream.h> +#include "prlink.h" +#include "nsIComponentRegistrar.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +static bool gUnreg = false; + +void print_err(nsresult err) { + switch (err) { + case NS_ERROR_FACTORY_NOT_LOADED: + cerr << "Factory not loaded"; + break; + case NS_NOINTERFACE: + cerr << "No Interface"; + break; + case NS_ERROR_NULL_POINTER: + cerr << "Null pointer"; + break; + case NS_ERROR_OUT_OF_MEMORY: + cerr << "Out of memory"; + break; + default: + cerr << hex << err << dec; + } +} + +nsresult Register(nsIComponentRegistrar* registrar, const char* path) { + nsCOMPtr<nsIFile> file; + nsresult rv = + NS_NewLocalFile(NS_ConvertUTF8toUTF16(path), true, getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + rv = registrar->AutoRegister(file); + return rv; +} + +nsresult Unregister(const char* path) { + /* NEEDS IMPLEMENTATION */ +#if 0 + nsresult res = nsComponentManager::AutoUnregisterComponent(path); + return res; +#else + return NS_ERROR_FAILURE; +#endif +} + +int ProcessArgs(nsIComponentRegistrar* registrar, int argc, char* argv[]) { + int i = 1; + nsresult res; + + while (i < argc) { + if (argv[i][0] == '-') { + int j; + for (j = 1; argv[i][j] != '\0'; j++) { + switch (argv[i][j]) { + case 'u': + gUnreg = true; + break; + default: + cerr << "Unknown option '" << argv[i][j] << "'\n"; + } + } + i++; + } else { + if (gUnreg) { + res = Unregister(argv[i]); + if (NS_SUCCEEDED(res)) { + cout << "Successfully unregistered: " << argv[i] << "\n"; + } else { + cerr << "Unregister failed ("; + print_err(res); + cerr << "): " << argv[i] << "\n"; + } + } else { + res = Register(registrar, argv[i]); + if (NS_SUCCEEDED(res)) { + cout << "Successfully registered: " << argv[i] << "\n"; + } else { + cerr << "Register failed ("; + print_err(res); + cerr << "): " << argv[i] << "\n"; + } + } + i++; + } + } + return 0; +} + +int main(int argc, char* argv[]) { + int ret = 0; + nsresult rv; + { + nsCOMPtr<nsIServiceManager> servMan; + rv = NS_InitXPCOM(getter_AddRefs(servMan), nullptr, nullptr); + if (NS_FAILED(rv)) return -1; + nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan); + NS_ASSERTION(registrar, "Null nsIComponentRegistrar"); + + /* With no arguments, RegFactory will autoregister */ + if (argc <= 1) { + rv = registrar->AutoRegister(nullptr); + ret = (NS_FAILED(rv)) ? -1 : 0; + } else + ret = ProcessArgs(registrar, argc, argv); + } // this scopes the nsCOMPtrs + // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM + rv = NS_ShutdownXPCOM(nullptr); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); + return ret; +} diff --git a/xpcom/tests/SizeTest01.cpp b/xpcom/tests/SizeTest01.cpp new file mode 100644 index 0000000000..790b0fa032 --- /dev/null +++ b/xpcom/tests/SizeTest01.cpp @@ -0,0 +1,107 @@ +// Test01.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsINode); + +/* + This test file compares the generated code size of similar functions + between raw COM interface pointers (|AddRef|ing and |Release|ing by hand) and + |nsCOMPtr|s. + + Function size results were determined by examining dissassembly of the + generated code. mXXX is the size of the generated code on the Macintosh. wXXX + is the size on Windows. For these tests, all reasonable optimizations were + enabled and exceptions were disabled (just as we build for release). + + The tests in this file explore only the simplest functionality: + assigning a pointer to be reference counted into a [raw, nsCOMPtr] object; + ensuring that it is |AddRef|ed and |Release|d appropriately; calling through + the pointer to a function supplied by the underlying COM interface. + + Windows: + raw_optimized + 31 bytes raw, nsCOMPtr* + 34 nsCOMPtr_optimized* + 38 nsCOMPtr_optimized + 42 nsCOMPtr + 46 + + Macintosh: + raw_optimized, nsCOMPtr_optimized + 112 bytes (1.0000) nsCOMPtr + 120 (1.0714) i.e., 7.14% bigger than + raw_optimized et al + raw + 140 (1.2500) + + The overall difference in size between Windows and Macintosh is caused + by the the PowerPC RISC architecture where every instruction is 4 bytes. + + On Macintosh, nsCOMPtr generates out-of-line destructors which are + not referenced, and which can be stripped by the linker. +*/ + +void Test01_raw(nsINode* aDOMNode, nsString* aResult) +// m140, w34 +{ + /* + This test is designed to be more like a typical large function where, + because you are working with several resources, you don't just return + when one of them is |nullptr|. Similarly: |Test01_nsCOMPtr00|, and + |Test01_nsIPtr00|. + */ + + nsINode* node = aDOMNode; + NS_IF_ADDREF(node); + + if (node) node->GetNodeName(*aResult); + + NS_IF_RELEASE(node); +} + +void Test01_raw_optimized(nsINode* aDOMNode, nsString* aResult) +// m112, w31 +{ + /* + This test simulates smaller functions where you _do_ just return + |nullptr| at the first sign of trouble. Similarly: + |Test01_nsCOMPtr01|, and |Test01_nsIPtr01|. + */ + + /* + This test produces smaller code that |Test01_raw| because it avoids + the three tests: |NS_IF_...|, and |if ( node )|. + */ + + // -- the following code is assumed, but is commented out so we compare only + // the relevent generated code + + // if ( !aDOMNode ) + // return; + + nsINode* node = aDOMNode; + NS_ADDREF(node); + node->GetNodeName(*aResult); + NS_RELEASE(node); +} + +void Test01_nsCOMPtr(nsINode* aDOMNode, nsString* aResult) +// m120, w46/34 +{ + nsCOMPtr<nsINode> node = aDOMNode; + + if (node) node->GetNodeName(*aResult); +} + +void Test01_nsCOMPtr_optimized(nsINode* aDOMNode, nsString* aResult) +// m112, w42/38 +{ + // if ( !aDOMNode ) + // return; + + nsCOMPtr<nsINode> node = aDOMNode; + node->GetNodeName(*aResult); +} diff --git a/xpcom/tests/SizeTest02.cpp b/xpcom/tests/SizeTest02.cpp new file mode 100644 index 0000000000..2673bbe70d --- /dev/null +++ b/xpcom/tests/SizeTest02.cpp @@ -0,0 +1,87 @@ +// Test02.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsINode); + +/* + This test file compares the generated code size of similar functions + between raw COM interface pointers (|AddRef|ing and |Release|ing by hand) and + |nsCOMPtr|s. + + Function size results were determined by examining dissassembly of the + generated code. mXXX is the size of the generated code on the Macintosh. wXXX + is the size on Windows. For these tests, all reasonable optimizations were + enabled and exceptions were disabled (just as we build for release). + + The tests in this file explore more complicated functionality: assigning + a pointer to be reference counted into a [raw, nsCOMPtr] object using + |QueryInterface|; ensuring that it is |AddRef|ed and |Release|d + appropriately; calling through the pointer to a function supplied by the + underlying COM interface. The tests in this file expand on the tests in + "Test01.cpp" by adding |QueryInterface|. + + Windows: + raw01 + 52 nsCOMPtr 63 raw + 66 nsCOMPtr* 68 + + Macintosh: + nsCOMPtr 120 (1.0000) Raw01 + 128 (1.1429) i.e., 14.29% bigger than nsCOMPtr Raw00 + 144 (1.2000) +*/ + +void // nsresult +Test02_Raw00(nsISupports* aDOMNode, nsString* aResult) +// m144, w66 +{ + // -- the following code is assumed, but is commented out so we compare only + // the relevent generated code + + // if ( !aDOMNode ) + // return NS_ERROR_NULL_POINTER; + + nsINode* node = 0; + nsresult status = + aDOMNode->QueryInterface(NS_GET_IID(nsINode), (void**)&node); + if (NS_SUCCEEDED(status)) { + node->GetNodeName(*aResult); + } + + NS_IF_RELEASE(node); + + // return status; +} + +void // nsresult +Test02_Raw01(nsISupports* aDOMNode, nsString* aResult) +// m128, w52 +{ + // if ( !aDOMNode ) + // return NS_ERROR_NULL_POINTER; + + nsINode* node; + nsresult status = + aDOMNode->QueryInterface(NS_GET_IID(nsINode), (void**)&node); + if (NS_SUCCEEDED(status)) { + node->GetNodeName(*aResult); + NS_RELEASE(node); + } + + // return status; +} + +void // nsresult +Test02_nsCOMPtr(nsISupports* aDOMNode, nsString* aResult) +// m120, w63/68 +{ + nsresult status; + nsCOMPtr<nsINode> node = do_QueryInterface(aDOMNode, &status); + + if (node) node->GetNodeName(*aResult); + + // return status; +} diff --git a/xpcom/tests/SizeTest03.cpp b/xpcom/tests/SizeTest03.cpp new file mode 100644 index 0000000000..055db264cd --- /dev/null +++ b/xpcom/tests/SizeTest03.cpp @@ -0,0 +1,94 @@ +// Test03.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsINode); + +/* + Windows: + nsCOMPtr_optimized* + 45 raw_optimized + 48 nsCOMPtr_optimized + 50 nsCOMPtr + 54 nsCOMPtr* + 59 raw + 62 + + Macintosh: + nsCOMPtr_optimized 112 + (1.0000) + raw_optimized 124 bytes + (1.1071) i.e., 10.71% bigger than nsCOMPtr_optimized nsCOMPtr + 144 (1.2857) +*/ + +void // nsresult +Test03_raw(nsINode* aDOMNode, nsString* aResult) +// m140, w62 +{ + // -- the following code is assumed, but is commented out so we compare only + // the relevent generated code + + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsINode* parent = 0; + nsresult status = aDOMNode->GetParentNode(&parent); + + if (NS_SUCCEEDED(status)) { + parent->GetNodeName(*aResult); + } + + NS_IF_RELEASE(parent); + + // return status; +} + +void // nsresult +Test03_raw_optimized(nsINode* aDOMNode, nsString* aResult) +// m124, w48 +{ + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsINode* parent; + nsresult status = aDOMNode->GetParentNode(&parent); + + if (NS_SUCCEEDED(status)) { + parent->GetNodeName(*aResult); + NS_RELEASE(parent); + } + + // return status; +} + +void // nsresult +Test03_nsCOMPtr(nsINode* aDOMNode, nsString* aResult) +// m144, w54/59 +{ + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsINode> parent; + nsresult status = aDOMNode->GetParentNode(getter_AddRefs(parent)); + if (parent) parent->GetNodeName(*aResult); + + // return status; +} + +void // nsresult +Test03_nsCOMPtr_optimized(nsINode* aDOMNode, nsString* aResult) +// m112, w50/45 +{ + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsINode* temp; + nsresult status = aDOMNode->GetParentNode(&temp); + nsCOMPtr<nsINode> parent(dont_AddRef(temp)); + if (parent) parent->GetNodeName(*aResult); + + // return status; +} diff --git a/xpcom/tests/SizeTest04.cpp b/xpcom/tests/SizeTest04.cpp new file mode 100644 index 0000000000..c026a1cabf --- /dev/null +++ b/xpcom/tests/SizeTest04.cpp @@ -0,0 +1,61 @@ +// Test04.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsINode); + +/* + Windows: + nsCOMPtr 13 raw + 36 + + Macintosh: + nsCOMPtr + 36 bytes (1.0000) raw + 120 (3.3333) i.e., 333.33% bigger + than nsCOMPtr +*/ + +class Test04_Raw { + public: + Test04_Raw(); + ~Test04_Raw(); + + void /*nsresult*/ SetNode(nsINode* newNode); + + private: + nsINode* mNode; +}; + +Test04_Raw::Test04_Raw() : mNode(0) { + // nothing else to do here +} + +Test04_Raw::~Test04_Raw() { NS_IF_RELEASE(mNode); } + +void // nsresult +Test04_Raw::SetNode(nsINode* newNode) +// m120, w36 +{ + NS_IF_ADDREF(newNode); + NS_IF_RELEASE(mNode); + mNode = newNode; + + // return NS_OK; +} + +class Test04_nsCOMPtr { + public: + void /*nsresult*/ SetNode(nsINode* newNode); + + private: + nsCOMPtr<nsINode> mNode; +}; + +void // nsresult +Test04_nsCOMPtr::SetNode(nsINode* newNode) +// m36, w13/13 +{ + mNode = newNode; +} diff --git a/xpcom/tests/SizeTest05.cpp b/xpcom/tests/SizeTest05.cpp new file mode 100644 index 0000000000..4c813a0dc3 --- /dev/null +++ b/xpcom/tests/SizeTest05.cpp @@ -0,0 +1,65 @@ +// Test05.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsINode); + +/* + Windows: + raw, nsCOMPtr 21 bytes + + Macintosh: + Raw, nsCOMPtr 64 bytes +*/ + +class Test05_Raw { + public: + Test05_Raw(); + ~Test05_Raw(); + + void /*nsresult*/ GetNode(nsINode** aNode); + + private: + nsINode* mNode; +}; + +Test05_Raw::Test05_Raw() : mNode(0) { + // nothing else to do here +} + +Test05_Raw::~Test05_Raw() { NS_IF_RELEASE(mNode); } + +void // nsresult +Test05_Raw::GetNode(nsINode** aNode) +// m64, w21 +{ + // if ( !aNode ) + // return NS_ERROR_NULL_POINTER; + + *aNode = mNode; + NS_IF_ADDREF(*aNode); + + // return NS_OK; +} + +class Test05_nsCOMPtr { + public: + void /*nsresult*/ GetNode(nsINode** aNode); + + private: + nsCOMPtr<nsINode> mNode; +}; + +void // nsresult +Test05_nsCOMPtr::GetNode(nsINode** aNode) +// m64, w21 +{ + // if ( !aNode ) + // return NS_ERROR_NULL_POINTER; + + *aNode = mNode; + NS_IF_ADDREF(*aNode); + + // return NS_OK; +} diff --git a/xpcom/tests/SizeTest06.cpp b/xpcom/tests/SizeTest06.cpp new file mode 100644 index 0000000000..11fb352320 --- /dev/null +++ b/xpcom/tests/SizeTest06.cpp @@ -0,0 +1,148 @@ +// Test06.cpp + +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIBaseWindow.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsPIDOMWindow); +NS_DEF_PTR(nsIBaseWindow); + +/* + Windows: + nsCOMPtr_optimized 176 + nsCOMPtr_as_found 181 + nsCOMPtr_optimized* 182 + nsCOMPtr02* 184 + nsCOMPtr02 187 + nsCOMPtr02* 188 + nsCOMPtr03 189 + raw_optimized, nsCOMPtr00 191 + nsCOMPtr00* 199 + nsCOMPtr_as_found* 201 + raw 214 + + Macintosh: + nsCOMPtr_optimized 300 (1.0000) + nsCOMPtr02 320 (1.0667) i.e., 6.67% bigger than + nsCOMPtr_optimized nsCOMPtr00 328 (1.0933) raw_optimized, + nsCOMPtr03 332 (1.1067) nsCOMPtr_as_found 344 (1.1467) raw + 388 (1.2933) + +*/ + +void // nsresult +Test06_raw(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m388, w214 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + nsPIDOMWindow* window = 0; + nsresult status = + aDOMWindow->QueryInterface(NS_GET_IID(nsPIDOMWindow), (void**)&window); + nsIDocShell* docShell = 0; + if (window) window->GetDocShell(&docShell); + nsIWebShell* rootWebShell = 0; + NS_IF_RELEASE(rootWebShell); + NS_IF_RELEASE(docShell); + NS_IF_RELEASE(window); + // return status; +} + +void // nsresult +Test06_raw_optimized(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m332, w191 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsPIDOMWindow* window; + nsresult status = + aDOMWindow->QueryInterface(NS_GET_IID(nsPIDOMWindow), (void**)&window); + if (NS_SUCCEEDED(status)) { + nsIDocShell* docShell = 0; + window->GetDocShell(&docShell); + if (docShell) { + NS_RELEASE(docShell); + } + NS_RELEASE(window); + } + // return status; +} + +void Test06_nsCOMPtr_as_found(nsIDOMWindow* aDOMWindow, + nsCOMPtr<nsIBaseWindow>* aBaseWindow) +// m344, w181/201 +{ + // if (!aDOMWindow) + // return; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow); + nsCOMPtr<nsIDocShell> docShell; + if (window) window->GetDocShell(getter_AddRefs(docShell)); +} + +void // nsresult +Test06_nsCOMPtr00(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m328, w191/199 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + nsIDocShell* temp0 = 0; + if (window) window->GetDocShell(&temp0); + nsCOMPtr<nsIDocShell> docShell = dont_AddRef(temp0); + (*aBaseWindow) = 0; + // return status; +} + +void // nsresult +Test06_nsCOMPtr_optimized(nsIDOMWindow* aDOMWindow, + nsCOMPtr<nsIBaseWindow>* aBaseWindow) +// m300, w176/182 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + nsIDocShell* temp0 = 0; + if (window) window->GetDocShell(&temp0); + (*aBaseWindow) = do_QueryInterface(nullptr, &status); + // return status; +} + +void // nsresult +Test06_nsCOMPtr02(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m320, w187/184 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + if (window) { + nsIDocShell* temp0; + window->GetDocShell(&temp0); + } + // return status; +} + +void // nsresult +Test06_nsCOMPtr03(nsIDOMWindow* aDOMWindow, + nsCOMPtr<nsIBaseWindow>* aBaseWindow) +// m332, w189/188 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + if (window) { + nsIDocShell* temp0; + window->GetDocShell(&temp0); + nsCOMPtr<nsIDocShell> docShell = dont_AddRef(temp0); + if (docShell) { + } + } + // return status; +} diff --git a/xpcom/tests/TestArguments.cpp b/xpcom/tests/TestArguments.cpp new file mode 100644 index 0000000000..ab162d31fb --- /dev/null +++ b/xpcom/tests/TestArguments.cpp @@ -0,0 +1,16 @@ +#include <string.h> + +int main(int argc, char* argv[]) { + if (argc != 9) return -1; + + if (strcmp("mozilla", argv[1]) != 0) return 1; + if (strcmp("firefox", argv[2]) != 0) return 2; + if (strcmp("thunderbird", argv[3]) != 0) return 3; + if (strcmp("seamonkey", argv[4]) != 0) return 4; + if (strcmp("foo", argv[5]) != 0) return 5; + if (strcmp("bar", argv[6]) != 0) return 6; + if (strcmp("argument with spaces", argv[7]) != 0) return 7; + if (strcmp(R"("argument with quotes")", argv[8]) != 0) return 8; + + return 0; +} diff --git a/xpcom/tests/TestBlockingProcess.cpp b/xpcom/tests/TestBlockingProcess.cpp new file mode 100644 index 0000000000..a1996aefdb --- /dev/null +++ b/xpcom/tests/TestBlockingProcess.cpp @@ -0,0 +1,8 @@ +#include <stdio.h> +#include "mozilla/Unused.h" + +int main() { + char tmp; + mozilla::Unused << fread(&tmp, sizeof(tmp), 1, stdin); + return 0; +} diff --git a/xpcom/tests/TestHarness.h b/xpcom/tests/TestHarness.h new file mode 100644 index 0000000000..e575497746 --- /dev/null +++ b/xpcom/tests/TestHarness.h @@ -0,0 +1,268 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test harness for XPCOM objects, providing a scoped XPCOM initializer, + * nsCOMPtr, nsRefPtr, do_CreateInstance, do_GetService, ns(Auto|C|)String, + * and stdio.h/stdlib.h. + */ + +#ifndef TestHarness_h__ +#define TestHarness_h__ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" + +#include "prenv.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include "mozilla/AppShutdown.h" + +static uint32_t gFailCount = 0; + +/** + * Prints the given failure message and arguments using printf, prepending + * "TEST-UNEXPECTED-FAIL " for the benefit of the test harness and + * appending "\n" to eliminate having to type it at each call site. + */ +MOZ_FORMAT_PRINTF(1, 2) void fail(const char* msg, ...) { + va_list ap; + + printf("TEST-UNEXPECTED-FAIL | "); + + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + + putchar('\n'); + ++gFailCount; +} + +/** + * Prints the given success message and arguments using printf, prepending + * "TEST-PASS " for the benefit of the test harness and + * appending "\n" to eliminate having to type it at each call site. + */ +MOZ_FORMAT_PRINTF(1, 2) void passed(const char* msg, ...) { + va_list ap; + + printf("TEST-PASS | "); + + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + + putchar('\n'); +} + +//----------------------------------------------------------------------------- + +class ScopedXPCOM : public nsIDirectoryServiceProvider2 { + public: + NS_DECL_ISUPPORTS + + explicit ScopedXPCOM(const char* testName, + nsIDirectoryServiceProvider* dirSvcProvider = nullptr) + : mDirSvcProvider(dirSvcProvider) { + mTestName = testName; + printf("Running %s tests...\n", mTestName); + + mInitRv = NS_InitXPCOM(nullptr, nullptr, this); + if (NS_FAILED(mInitRv)) { + fail("NS_InitXPCOM returned failure code 0x%" PRIx32, + static_cast<uint32_t>(mInitRv)); + return; + } + } + + ~ScopedXPCOM() { + // If we created a profile directory, we need to remove it. + if (mProfD) { + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownNetTeardown); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownTeardown); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdown); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownQM); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownTelemetry); + + if (NS_FAILED(mProfD->Remove(true))) { + NS_WARNING("Problem removing profile directory"); + } + + mProfD = nullptr; + } + + if (NS_SUCCEEDED(mInitRv)) { + nsresult rv = NS_ShutdownXPCOM(nullptr); + if (NS_FAILED(rv)) { + fail("XPCOM shutdown failed with code 0x%" PRIx32, + static_cast<uint32_t>(rv)); + exit(1); + } + } + + printf("Finished running %s tests.\n", mTestName); + } + + bool failed() { return NS_FAILED(mInitRv); } + + already_AddRefed<nsIFile> GetProfileDirectory() { + if (mProfD) { + nsCOMPtr<nsIFile> copy = mProfD; + return copy.forget(); + } + + // Create a unique temporary folder to use for this test. + // Note that runcppunittests.py will run tests with a temp + // directory as the cwd, so just put something under that. + nsCOMPtr<nsIFile> profD; + nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_PROCESS_DIR, + getter_AddRefs(profD)); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = profD->Append(u"cpp-unit-profd"_ns); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = profD->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, nullptr); + + mProfD = profD; + return profD.forget(); + } + + already_AddRefed<nsIFile> GetGREDirectory() { + if (mGRED) { + nsCOMPtr<nsIFile> copy = mGRED; + return copy.forget(); + } + + char* env = PR_GetEnv("MOZ_XRE_DIR"); + nsCOMPtr<nsIFile> greD; + if (env) { + NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(greD)); + } + + mGRED = greD; + return greD.forget(); + } + + already_AddRefed<nsIFile> GetGREBinDirectory() { + if (mGREBinD) { + nsCOMPtr<nsIFile> copy = mGREBinD; + return copy.forget(); + } + + nsCOMPtr<nsIFile> greD = GetGREDirectory(); + if (!greD) { + return greD.forget(); + } + greD->Clone(getter_AddRefs(mGREBinD)); + +#ifdef XP_MACOSX + nsAutoCString leafName; + mGREBinD->GetNativeLeafName(leafName); + if (leafName.EqualsLiteral("Resources")) { + mGREBinD->SetNativeLeafName("MacOS"_ns); + } +#endif + + nsCOMPtr<nsIFile> copy = mGREBinD; + return copy.forget(); + } + + //////////////////////////////////////////////////////////////////////////// + //// nsIDirectoryServiceProvider + + NS_IMETHOD GetFile(const char* aProperty, bool* _persistent, + nsIFile** _result) override { + // If we were supplied a directory service provider, ask it first. + if (mDirSvcProvider && NS_SUCCEEDED(mDirSvcProvider->GetFile( + aProperty, _persistent, _result))) { + return NS_OK; + } + + // Otherwise, the test harness provides some directories automatically. + if (0 == strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) || + 0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR) || + 0 == strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) { + nsCOMPtr<nsIFile> profD = GetProfileDirectory(); + NS_ENSURE_TRUE(profD, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFile> clone; + nsresult rv = profD->Clone(getter_AddRefs(clone)); + NS_ENSURE_SUCCESS(rv, rv); + + *_persistent = true; + clone.forget(_result); + return NS_OK; + } else if (0 == strcmp(aProperty, NS_GRE_DIR)) { + nsCOMPtr<nsIFile> greD = GetGREDirectory(); + NS_ENSURE_TRUE(greD, NS_ERROR_FAILURE); + + *_persistent = true; + greD.forget(_result); + return NS_OK; + } else if (0 == strcmp(aProperty, NS_GRE_BIN_DIR)) { + nsCOMPtr<nsIFile> greBinD = GetGREBinDirectory(); + NS_ENSURE_TRUE(greBinD, NS_ERROR_FAILURE); + + *_persistent = true; + greBinD.forget(_result); + return NS_OK; + } + + return NS_ERROR_FAILURE; + } + + //////////////////////////////////////////////////////////////////////////// + //// nsIDirectoryServiceProvider2 + + NS_IMETHOD GetFiles(const char* aProperty, + nsISimpleEnumerator** _enum) override { + // If we were supplied a directory service provider, ask it first. + nsCOMPtr<nsIDirectoryServiceProvider2> provider = + do_QueryInterface(mDirSvcProvider); + if (provider && NS_SUCCEEDED(provider->GetFiles(aProperty, _enum))) { + return NS_OK; + } + + return NS_ERROR_FAILURE; + } + + private: + const char* mTestName; + nsresult mInitRv = NS_ERROR_NOT_INITIALIZED; + nsCOMPtr<nsIDirectoryServiceProvider> mDirSvcProvider; + nsCOMPtr<nsIFile> mProfD; + nsCOMPtr<nsIFile> mGRED; + nsCOMPtr<nsIFile> mGREBinD; +}; + +NS_IMPL_QUERY_INTERFACE(ScopedXPCOM, nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + +NS_IMETHODIMP_(MozExternalRefCountType) +ScopedXPCOM::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +ScopedXPCOM::Release() { return 1; } + +#endif // TestHarness_h__ diff --git a/xpcom/tests/TestMemoryPressureWatcherLinux.cpp b/xpcom/tests/TestMemoryPressureWatcherLinux.cpp new file mode 100644 index 0000000000..8adc9f1092 --- /dev/null +++ b/xpcom/tests/TestMemoryPressureWatcherLinux.cpp @@ -0,0 +1,65 @@ +/* -*- 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/AvailableMemoryWatcherUtils.h" + +#include <fstream> + +using namespace mozilla; + +const char* kMemInfoPath = "/proc/meminfo"; +const char* kTestfilePath = "testdata"; + +// Test that we are reading some value from /proc/meminfo. +// If the values are nonzero, the test is a success. +void TestFromProc() { + MemoryInfo memInfo{0, 0}; + ReadMemoryFile(kMemInfoPath, memInfo); + MOZ_RELEASE_ASSERT(memInfo.memTotal != 0); + MOZ_RELEASE_ASSERT(memInfo.memAvailable != 0); +} + +// Test a file using expected syntax. +void TestFromFile() { + MemoryInfo memInfo{0, 0}; + std::ofstream aFile(kTestfilePath); + aFile << "MemTotal: 12345 kB\n"; + aFile << "MemFree: 99999 kB\n"; + aFile << "MemAvailable: 54321 kB\n"; + aFile.close(); + + ReadMemoryFile(kTestfilePath, memInfo); + + MOZ_RELEASE_ASSERT(memInfo.memTotal == 12345); + MOZ_RELEASE_ASSERT(memInfo.memAvailable == 54321); + + // remove our dummy file + remove(kTestfilePath); +} + +// Test a file with useless data. Results should be +// the starting struct with {0,0}. +void TestInvalidFile() { + MemoryInfo memInfo{0, 0}; + std::ofstream aFile(kTestfilePath); + aFile << "foo: 12345 kB\n"; + aFile << "bar"; + aFile.close(); + + ReadMemoryFile(kTestfilePath, memInfo); + + MOZ_RELEASE_ASSERT(memInfo.memTotal == 0); + MOZ_RELEASE_ASSERT(memInfo.memAvailable == 0); + + // remove our dummy file + remove(kTestfilePath); +} + +int main() { + TestFromProc(); + TestFromFile(); + TestInvalidFile(); + return 0; +} diff --git a/xpcom/tests/TestPRIntN.cpp b/xpcom/tests/TestPRIntN.cpp new file mode 100644 index 0000000000..0873d16ef8 --- /dev/null +++ b/xpcom/tests/TestPRIntN.cpp @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <stdint.h> +#include "prtypes.h" + +// This test is NOT intended to be run. It's a test to make sure +// PRInt{N} matches int{N}_t. If they don't match, we should get a +// compiler warning or error in main(). + +static void ClearNSPRIntTypes(PRInt8* a, PRInt16* b, PRInt32* c, PRInt64* d) { + *a = 0; + *b = 0; + *c = 0; + *d = 0; +} + +static void ClearStdIntTypes(int8_t* w, int16_t* x, int32_t* y, int64_t* z) { + *w = 0; + *x = 0; + *y = 0; + *z = 0; +} + +int main() { + PRInt8 a; + PRInt16 b; + PRInt32 c; + PRInt64 d; + int8_t w; + int16_t x; + int32_t y; + int64_t z; + + ClearNSPRIntTypes(&w, &x, &y, &z); + ClearStdIntTypes(&a, &b, &c, &d); + return 0; +} diff --git a/xpcom/tests/TestQuickReturn.cpp b/xpcom/tests/TestQuickReturn.cpp new file mode 100644 index 0000000000..07500bde9c --- /dev/null +++ b/xpcom/tests/TestQuickReturn.cpp @@ -0,0 +1,5 @@ +int main(int argc, char* argv[]) { + if (argc != 1) return -1; + + return 42; +} diff --git a/xpcom/tests/TestShutdown.cpp b/xpcom/tests/TestShutdown.cpp new file mode 100644 index 0000000000..4b277d423c --- /dev/null +++ b/xpcom/tests/TestShutdown.cpp @@ -0,0 +1,37 @@ +/* -*- 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 "nsIServiceManager.h" + +// Gee this seems simple! It's for testing for memory leaks with Purify. + +void main(int argc, char* argv[]) { + nsIServiceManager* servMgr; + nsresult rv = NS_InitXPCOM(&servMgr, nullptr, nullptr); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "NS_InitXPCOM failed"); + + // try loading a component and releasing it to see if it leaks + if (argc > 1 && argv[1] != nullptr) { + char* cidStr = argv[1]; + nsISupports* obj = nullptr; + if (cidStr[0] == '{') { + nsCID cid; + cid.Parse(cidStr); + rv = CallCreateInstance(cid, &obj); + } else { + // contractID case: + rv = CallCreateInstance(cidStr, &obj); + } + if (NS_SUCCEEDED(rv)) { + printf("Successfully created %s\n", cidStr); + NS_RELEASE(obj); + } else { + printf("Failed to create %s (%x)\n", cidStr, rv); + } + } + + rv = NS_ShutdownXPCOM(servMgr); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); +} diff --git a/xpcom/tests/TestStreamUtils.cpp b/xpcom/tests/TestStreamUtils.cpp new file mode 100644 index 0000000000..1a24656617 --- /dev/null +++ b/xpcom/tests/TestStreamUtils.cpp @@ -0,0 +1,63 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +//---- + +static bool test_consume_stream() { + const char kData[] = + "Get your facts first, and then you can distort them as much as you " + "please."; + + nsCOMPtr<nsIInputStream> input; + nsCOMPtr<nsIOutputStream> output; + NS_NewPipe(getter_AddRefs(input), getter_AddRefs(output), 10, UINT32_MAX); + if (!input || !output) return false; + + uint32_t n = 0; + output->Write(kData, sizeof(kData) - 1, &n); + if (n != (sizeof(kData) - 1)) return false; + output = nullptr; // close output + + nsCString buf; + if (NS_FAILED(NS_ConsumeStream(input, UINT32_MAX, buf))) return false; + + if (!buf.Equals(kData)) return false; + + return true; +} + +//---- + +typedef bool (*TestFunc)(); +#define DECL_TEST(name) \ + { #name, name } + +static const struct Test { + const char* name; + TestFunc func; +} tests[] = {DECL_TEST(test_consume_stream), {nullptr, nullptr}}; + +int main(int argc, char** argv) { + int count = 1; + if (argc > 1) count = atoi(argv[1]); + + if (NS_FAILED(NS_InitXPCOM(nullptr, nullptr, nullptr))) return -1; + + while (count--) { + for (const Test* t = tests; t->name != nullptr; ++t) { + printf("%25s : %s\n", t->name, t->func() ? "SUCCESS" : "FAILURE"); + } + } + + NS_ShutdownXPCOM(nullptr); + return 0; +} diff --git a/xpcom/tests/TestUnicodeArguments.cpp b/xpcom/tests/TestUnicodeArguments.cpp new file mode 100644 index 0000000000..a44f8a2f2e --- /dev/null +++ b/xpcom/tests/TestUnicodeArguments.cpp @@ -0,0 +1,73 @@ +/** + * On Windows, a Unicode argument is passed as UTF-16 using ShellExecuteExW. + * On other platforms, it is passed as UTF-8 + */ + +static const int args_length = 4; +#if defined(XP_WIN) && defined(_MSC_VER) +# define _UNICODE +# include <tchar.h> +# include <stdio.h> + +static const _TCHAR* expected_utf16[args_length] = { + // Latin-1 + L"M\xF8z\xEEll\xE5", + // Cyrillic + L"\x41C\x43E\x437\x438\x43B\x43B\x430", + // Bengali + L"\x9AE\x9CB\x99C\x9BF\x9B2\x9BE", + // Cuneiform + L"\xD808\xDE2C\xD808\xDF63\xD808\xDDB7"}; + +int wmain(int argc, _TCHAR* argv[]) { + printf("argc = %d\n", argc); + + if (argc != args_length + 1) return -1; + + for (int i = 1; i < argc; ++i) { + printf("expected[%d]: ", i - 1); + for (size_t j = 0; j < _tcslen(expected_utf16[i - 1]); ++j) { + printf("%x ", *(expected_utf16[i - 1] + j)); + } + printf("\n"); + + printf("argv[%d]: ", i); + for (size_t j = 0; j < _tcslen(argv[i]); ++j) { + printf("%x ", *(argv[i] + j)); + } + printf("\n"); + + if (_tcscmp(expected_utf16[i - 1], argv[i])) { + return i; + } + } + + return 0; +} +#else +# include <string.h> +# include <stdio.h> + +static const char* expected_utf8[args_length] = { + // Latin-1 + "M\xC3\xB8z\xC3\xAEll\xC3\xA5", + // Cyrillic + "\xD0\x9C\xD0\xBE\xD0\xB7\xD0\xB8\xD0\xBB\xD0\xBB\xD0\xB0", + // Bengali + "\xE0\xA6\xAE\xE0\xA7\x8B\xE0\xA6\x9C\xE0\xA6\xBF\xE0\xA6\xB2\xE0\xA6\xBE", + // Cuneiform + "\xF0\x92\x88\xAC\xF0\x92\x8D\xA3\xF0\x92\x86\xB7"}; + +int main(int argc, char* argv[]) { + if (argc != args_length + 1) return -1; + + for (int i = 1; i < argc; ++i) { + printf("argv[%d] = %s; expected = %s\n", i, argv[i], expected_utf8[i - 1]); + if (strcmp(expected_utf8[i - 1], argv[i])) { + return i; + } + } + + return 0; +} +#endif diff --git a/xpcom/tests/TestWinReg.js b/xpcom/tests/TestWinReg.js new file mode 100644 index 0000000000..8462f3ff04 --- /dev/null +++ b/xpcom/tests/TestWinReg.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 script is intended to be run using xpcshell + */ + +const nsIWindowsRegKey = Ci.nsIWindowsRegKey; +const BASE_PATH = "SOFTWARE\\Mozilla\\Firefox"; + +function idump(indent, str) { + for (var j = 0; j < indent; ++j) { + dump(" "); + } + dump(str); +} + +function list_values(indent, key) { + idump(indent, "{\n"); + var count = key.valueCount; + for (var i = 0; i < count; ++i) { + var vn = key.getValueName(i); + var val = ""; + if (key.getValueType(vn) == nsIWindowsRegKey.TYPE_STRING) { + val = key.readStringValue(vn); + } + if (vn == "") { + idump(indent + 1, '(Default): "' + val + '"\n'); + } else { + idump(indent + 1, vn + ': "' + val + '"\n'); + } + } + idump(indent, "}\n"); +} + +function list_children(indent, key) { + list_values(indent, key); + + var count = key.childCount; + for (var i = 0; i < count; ++i) { + var cn = key.getChildName(i); + idump(indent, "[" + cn + "]\n"); + list_children(indent + 1, key.openChild(cn, nsIWindowsRegKey.ACCESS_READ)); + } +} + +// enumerate everything under BASE_PATH +var key = + Cc["@mozilla.org/windows-registry-key;1"].createInstance(nsIWindowsRegKey); +key.open( + nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + BASE_PATH, + nsIWindowsRegKey.ACCESS_READ +); +list_children(1, key); + +key.close(); +key.open( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + BASE_PATH, + nsIWindowsRegKey.ACCESS_READ +); +list_children(1, key); diff --git a/xpcom/tests/TestingAtomList.h b/xpcom/tests/TestingAtomList.h new file mode 100644 index 0000000000..ffeada6057 --- /dev/null +++ b/xpcom/tests/TestingAtomList.h @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +TESTING_ATOM(foo, "foo") +TESTING_ATOM(bar, "bar") diff --git a/xpcom/tests/crashtests/bug-1714685.html b/xpcom/tests/crashtests/bug-1714685.html new file mode 100644 index 0000000000..a06e9d44cf --- /dev/null +++ b/xpcom/tests/crashtests/bug-1714685.html @@ -0,0 +1,13 @@ +<script> +document.addEventListener('DOMContentLoaded', async () => { + let a = new PromiseRejectionEvent('rejectionhandled', { + 'promise': new Promise(async (resolve, reject) => { + self.addEventListener('load', () => { + a.visible = false + }); + }), + 'reason':'' + }); + SpecialPowers.forceGC(); +}) +</script> diff --git a/xpcom/tests/crashtests/crashtests.list b/xpcom/tests/crashtests/crashtests.list new file mode 100644 index 0000000000..7c1435dedd --- /dev/null +++ b/xpcom/tests/crashtests/crashtests.list @@ -0,0 +1 @@ +load bug-1714685.html diff --git a/xpcom/tests/gtest/Helpers.cpp b/xpcom/tests/gtest/Helpers.cpp new file mode 100644 index 0000000000..84053cbeb3 --- /dev/null +++ b/xpcom/tests/gtest/Helpers.cpp @@ -0,0 +1,201 @@ +/* -*- 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/. */ + +/* Helper routines for xpcom gtests. */ + +#include "Helpers.h" + +#include <algorithm> +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsIOutputStream.h" +#include "nsStreamUtils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +namespace testing { + +// Populate an array with the given number of bytes. Data is lorem ipsum +// random text, but deterministic across multiple calls. +void CreateData(uint32_t aNumBytes, nsTArray<char>& aDataOut) { + static const char data[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec egestas " + "purus eu condimentum iaculis. In accumsan leo eget odio porttitor, non " + "rhoncus nulla vestibulum. Etiam lacinia consectetur nisl nec " + "sollicitudin. Sed fringilla accumsan diam, pulvinar varius massa. Duis " + "mollis dignissim felis, eget tempus nisi tristique ut. Fusce euismod, " + "lectus non lacinia tempor, tellus diam suscipit quam, eget hendrerit " + "lacus nunc fringilla ante. Sed ultrices massa vitae risus molestie, ut " + "finibus quam laoreet nullam."; + static const uint32_t dataLength = sizeof(data) - 1; + + aDataOut.SetCapacity(aNumBytes); + + while (aNumBytes > 0) { + uint32_t amount = std::min(dataLength, aNumBytes); + aDataOut.AppendElements(data, amount); + aNumBytes -= amount; + } +} + +// Write the given number of bytes out to the stream. Loop until expected +// bytes count is reached or an error occurs. +void Write(nsIOutputStream* aStream, const nsTArray<char>& aData, + uint32_t aOffset, uint32_t aNumBytes) { + uint32_t remaining = + std::min(aNumBytes, static_cast<uint32_t>(aData.Length() - aOffset)); + + while (remaining > 0) { + uint32_t numWritten; + nsresult rv = + aStream->Write(aData.Elements() + aOffset, remaining, &numWritten); + ASSERT_NS_SUCCEEDED(rv); + if (numWritten < 1) { + break; + } + aOffset += numWritten; + remaining -= numWritten; + } +} + +// Write the given number of bytes and then close the stream. +void WriteAllAndClose(nsIOutputStream* aStream, const nsTArray<char>& aData) { + Write(aStream, aData, 0, aData.Length()); + aStream->Close(); +} + +// Synchronously consume the given input stream and validate the resulting data +// against the given array of expected values. +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsTArray<char>& aExpectedData) { + nsDependentCSubstring data(aExpectedData.Elements(), aExpectedData.Length()); + ConsumeAndValidateStream(aStream, data); +} + +// Synchronously consume the given input stream and validate the resulting data +// against the given string of expected values. +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsACString& aExpectedData) { + nsAutoCString outputData; + nsresult rv = NS_ConsumeStream(aStream, UINT32_MAX, outputData); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(aExpectedData.Length(), outputData.Length()); + ASSERT_TRUE(aExpectedData.Equals(outputData)); +} + +NS_IMPL_ISUPPORTS(OutputStreamCallback, nsIOutputStreamCallback); + +OutputStreamCallback::OutputStreamCallback() : mCalled(false) {} + +OutputStreamCallback::~OutputStreamCallback() = default; + +NS_IMETHODIMP +OutputStreamCallback::OnOutputStreamReady(nsIAsyncOutputStream* aStream) { + mCalled = true; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback); + +InputStreamCallback::InputStreamCallback() : mCalled(false) {} + +InputStreamCallback::~InputStreamCallback() = default; + +NS_IMETHODIMP +InputStreamCallback::OnInputStreamReady(nsIAsyncInputStream* aStream) { + mCalled = true; + return NS_OK; +} + +AsyncStringStream::AsyncStringStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); +} + +NS_IMETHODIMP +AsyncStringStream::Available(uint64_t* aLength) { + return mStream->Available(aLength); +} + +NS_IMETHODIMP +AsyncStringStream::StreamStatus() { return mStream->StreamStatus(); } + +NS_IMETHODIMP +AsyncStringStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) { + return mStream->Read(aBuffer, aCount, aReadCount); +} + +NS_IMETHODIMP +AsyncStringStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +AsyncStringStream::Close() { + nsresult rv = mStream->Close(); + if (NS_SUCCEEDED(rv)) { + MaybeExecCallback(mCallback, mCallbackEventTarget); + } + return rv; +} + +NS_IMETHODIMP +AsyncStringStream::IsNonBlocking(bool* aNonBlocking) { + return mStream->IsNonBlocking(aNonBlocking); +} + +NS_IMETHODIMP +AsyncStringStream::CloseWithStatus(nsresult aStatus) { return Close(); } + +NS_IMETHODIMP +AsyncStringStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + if (aFlags & nsIAsyncInputStream::WAIT_CLOSURE_ONLY) { + mCallback = aCallback; + mCallbackEventTarget = aEventTarget; + return NS_OK; + } + + MaybeExecCallback(aCallback, aEventTarget); + return NS_OK; +} + +void AsyncStringStream::MaybeExecCallback(nsIInputStreamCallback* aCallback, + nsIEventTarget* aEventTarget) { + if (!aCallback) { + return; + } + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback; + nsCOMPtr<nsIAsyncInputStream> self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "AsyncWait", [callback, self]() { callback->OnInputStreamReady(self); }); + + if (aEventTarget) { + aEventTarget->Dispatch(r.forget()); + } else { + r->Run(); + } +} + +NS_IMPL_ISUPPORTS(AsyncStringStream, nsIAsyncInputStream, nsIInputStream) + +NS_IMPL_ADDREF(LengthInputStream); +NS_IMPL_RELEASE(LengthInputStream); + +NS_INTERFACE_MAP_BEGIN(LengthInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, mIsInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + mIsAsyncInputStreamLength) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +NS_IMPL_ISUPPORTS(LengthCallback, nsIInputStreamLengthCallback) + +} // namespace testing diff --git a/xpcom/tests/gtest/Helpers.h b/xpcom/tests/gtest/Helpers.h new file mode 100644 index 0000000000..cfd7d6fc2e --- /dev/null +++ b/xpcom/tests/gtest/Helpers.h @@ -0,0 +1,195 @@ +/* -*- 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 __Helpers_h +#define __Helpers_h + +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIInputStreamLength.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsTArrayForwardDeclare.h" +#include "nsThreadUtils.h" +#include <stdint.h> + +class nsIInputStream; +class nsIOutputStream; + +namespace testing { + +void CreateData(uint32_t aNumBytes, nsTArray<char>& aDataOut); + +void Write(nsIOutputStream* aStream, const nsTArray<char>& aData, + uint32_t aOffset, uint32_t aNumBytes); + +void WriteAllAndClose(nsIOutputStream* aStream, const nsTArray<char>& aData); + +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsTArray<char>& aExpectedData); + +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsACString& aExpectedData); + +class OutputStreamCallback final : public nsIOutputStreamCallback { + public: + OutputStreamCallback(); + + bool Called() const { return mCalled; } + + private: + ~OutputStreamCallback(); + + bool mCalled; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAMCALLBACK +}; + +class InputStreamCallback final : public nsIInputStreamCallback { + public: + InputStreamCallback(); + + bool Called() const { return mCalled; } + + private: + ~InputStreamCallback(); + + bool mCalled; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAMCALLBACK +}; + +class AsyncStringStream final : public nsIAsyncInputStream { + nsCOMPtr<nsIInputStream> mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit AsyncStringStream(const nsACString& aBuffer); + + private: + ~AsyncStringStream() = default; + + void MaybeExecCallback(nsIInputStreamCallback* aCallback, + nsIEventTarget* aEventTarget); + + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackEventTarget; +}; + +// This class implements a simple nsIInputStreamLength stream. +class LengthInputStream : public nsIInputStream, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength { + nsCOMPtr<nsIInputStream> mStream; + bool mIsInputStreamLength; + bool mIsAsyncInputStreamLength; + nsresult mLengthRv; + bool mNegativeValue; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + LengthInputStream(const nsACString& aBuffer, bool aIsInputStreamLength, + bool aIsAsyncInputStreamLength, nsresult aLengthRv = NS_OK, + bool aNegativeValue = false) + : mIsInputStreamLength(aIsInputStreamLength), + mIsAsyncInputStreamLength(aIsAsyncInputStreamLength), + mLengthRv(aLengthRv), + mNegativeValue(aNegativeValue) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + NS_IMETHOD + Length(int64_t* aLength) override { + if (mNegativeValue) { + *aLength = -1; + } else { + mStream->Available((uint64_t*)aLength); + } + return mLengthRv; + } + + NS_IMETHOD + AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) override { + RefPtr<LengthInputStream> self = this; + nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback; + + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("AsyncLengthWait", [self, callback]() { + int64_t length; + self->Length(&length); + callback->OnInputStreamLengthReady(self, length); + }); + + return aEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + } + + protected: + virtual ~LengthInputStream() = default; +}; + +class LengthCallback final : public nsIInputStreamLengthCallback { + bool mCalled; + int64_t mSize; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + LengthCallback() : mCalled(false), mSize(0) {} + + NS_IMETHOD + OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) override { + mCalled = true; + mSize = aLength; + return NS_OK; + } + + bool Called() const { return mCalled; } + + int64_t Size() const { return mSize; } + + private: + ~LengthCallback() = default; +}; + +} // namespace testing + +#endif // __Helpers_h diff --git a/xpcom/tests/gtest/TestAllocReplacement.cpp b/xpcom/tests/gtest/TestAllocReplacement.cpp new file mode 100644 index 0000000000..4b2c41b0f3 --- /dev/null +++ b/xpcom/tests/gtest/TestAllocReplacement.cpp @@ -0,0 +1,106 @@ +/* -*- 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 "mozmemory.h" +#include "gtest/gtest.h" + +// We want to ensure that various functions are hooked properly and that +// allocations are getting routed through jemalloc. The strategy +// pursued below relies on jemalloc_info_ptr knowing about the pointers +// returned by the allocator. If the function has been hooked correctly, +// then jemalloc_info_ptr returns a TagLiveAlloc tag, or TagUnknown +// otherwise. +// We could also check the hooking of |free| and similar functions: once +// we free() the returned pointer, jemalloc_info_ptr would return a tag +// that is not TagLiveAlloc. However, in the GTests environment, with +// other threads running in the background, it is possible for some of +// them to get a new allocation at the same location we just freed, and +// jemalloc_info_ptr would return a TagLiveAlloc tag. + +#define ASSERT_ALLOCATION_HAPPENED(lambda) \ + ASSERT_TRUE(ValidateHookedAllocation(lambda, free)); + +// We do run the risk of OOM'ing when we allocate something...all we can +// do is try to allocate something so small that OOM'ing is unlikely. +const size_t kAllocAmount = 16; + +static bool ValidateHookedAllocation(void* (*aAllocator)(void), + void (*aFreeFunction)(void*)) { + void* p = aAllocator(); + + if (!p) { + return false; + } + + jemalloc_ptr_info_t info; + jemalloc_ptr_info(p, &info); + + // Regardless of whether that call succeeded or failed, we are done with + // the allocated buffer now. + aFreeFunction(p); + + return (info.tag == PtrInfoTag::TagLiveAlloc); +} + +TEST(AllocReplacement, malloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { return malloc(kAllocAmount); }); +} + +TEST(AllocReplacement, calloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { return calloc(1, kAllocAmount); }); +} + +TEST(AllocReplacement, realloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { return realloc(nullptr, kAllocAmount); }); +} + +#if defined(HAVE_POSIX_MEMALIGN) +TEST(AllocReplacement, posix_memalign_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { + void* p = nullptr; + int result = posix_memalign(&p, sizeof(void*), kAllocAmount); + if (result != 0) { + return static_cast<void*>(nullptr); + } + return p; + }); +} +#endif + +#if defined(XP_WIN) +# include <windows.h> + +# undef ASSERT_ALLOCATION_HAPPENED +# define ASSERT_ALLOCATION_HAPPENED(lambda) \ + ASSERT_TRUE(ValidateHookedAllocation( \ + lambda, [](void* p) { HeapFree(GetProcessHeap(), 0, p); })); + +TEST(AllocReplacement, HeapAlloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { + HANDLE h = GetProcessHeap(); + return HeapAlloc(h, 0, kAllocAmount); + }); +} + +TEST(AllocReplacement, HeapReAlloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { + HANDLE h = GetProcessHeap(); + void* p = HeapAlloc(h, 0, kAllocAmount / 2); + + if (!p) { + return static_cast<void*>(nullptr); + } + + return HeapReAlloc(h, 0, p, kAllocAmount); + }); +} +#endif diff --git a/xpcom/tests/gtest/TestArenaAllocator.cpp b/xpcom/tests/gtest/TestArenaAllocator.cpp new file mode 100644 index 0000000000..fb11952927 --- /dev/null +++ b/xpcom/tests/gtest/TestArenaAllocator.cpp @@ -0,0 +1,310 @@ +/* -*- 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/ArenaAllocator.h" +#include "mozilla/ArenaAllocatorExtensions.h" +#include "nsIMemoryReporter.h" // MOZ_MALLOC_SIZE_OF + +#include "gtest/gtest.h" + +using mozilla::ArenaAllocator; + +TEST(ArenaAllocator, Constructor) +{ ArenaAllocator<4096, 4> a; } + +TEST(ArenaAllocator, DefaultAllocate) +{ + // Test default 1-byte alignment. + ArenaAllocator<1024> a; + void* x = a.Allocate(101); + void* y = a.Allocate(101); + + // Given 1-byte aligment, we expect the allocations to follow + // each other exactly. + EXPECT_EQ(uintptr_t(x) + 101, uintptr_t(y)); +} + +TEST(ArenaAllocator, AllocateAlignment) +{ + // Test non-default 8-byte alignment. + static const size_t kAlignment = 8; + ArenaAllocator<1024, kAlignment> a; + + // Make sure aligment is correct for 1-8. + for (size_t i = 1; i <= kAlignment; i++) { + // All of these should be 8 bytes + void* x = a.Allocate(i); + void* y = a.Allocate(i); + EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y)); + } + + // Test with slightly larger than specified alignment. + void* x = a.Allocate(kAlignment + 1); + void* y = a.Allocate(kAlignment + 1); + + // Given 8-byte aligment, and a non-8-byte aligned request we expect the + // allocations to be padded. + EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y)); + + // We expect 7 bytes of padding to have been added. + EXPECT_EQ(uintptr_t(x) + kAlignment * 2, uintptr_t(y)); +} + +#if 0 +TEST(ArenaAllocator, AllocateZeroBytes) +{ + // This would have to be a death test. Since we chose to provide an + // infallible allocator we can't just return nullptr in the 0 case as + // there's no way to differentiate that from the OOM case. + ArenaAllocator<1024> a; + void* x = a.Allocate(0); + EXPECT_FALSE(x); +} + +TEST(ArenaAllocator, BadAlignment) +{ + // This test causes build failures by triggering the static assert enforcing + // a power-of-two alignment. + ArenaAllocator<256, 3> a; + ArenaAllocator<256, 7> b; + ArenaAllocator<256, 17> c; +} +#endif + +TEST(ArenaAllocator, AllocateMultipleSizes) +{ + // Test non-default 4-byte alignment. + ArenaAllocator<4096, 4> a; + + for (int i = 1; i < 50; i++) { + void* x = a.Allocate(i); + // All the allocations should be aligned properly. + EXPECT_EQ(uintptr_t(x) % 4, uintptr_t(0)); + } + + // Test a large 64-byte alignment + ArenaAllocator<8192, 64> b; + for (int i = 1; i < 100; i++) { + void* x = b.Allocate(i); + // All the allocations should be aligned properly. + EXPECT_EQ(uintptr_t(x) % 64, uintptr_t(0)); + } +} + +TEST(ArenaAllocator, AllocateInDifferentChunks) +{ + // Test default 1-byte alignment. + ArenaAllocator<4096> a; + void* x = a.Allocate(4000); + void* y = a.Allocate(4000); + EXPECT_NE(uintptr_t(x) + 4000, uintptr_t(y)); +} + +TEST(ArenaAllocator, AllocateLargerThanArenaSize) +{ + // Test default 1-byte alignment. + ArenaAllocator<256> a; + void* x = a.Allocate(4000); + void* y = a.Allocate(4000); + EXPECT_TRUE(x); + EXPECT_TRUE(y); + + // Now try a normal allocation, it should behave as expected. + x = a.Allocate(8); + y = a.Allocate(8); + EXPECT_EQ(uintptr_t(x) + 8, uintptr_t(y)); +} + +TEST(ArenaAllocator, AllocationsPerChunk) +{ + // Test that expected number of allocations fit in one chunk. + // We use an alignment of 64-bytes to avoid worrying about differences in + // the header size on 32 and 64-bit platforms. + const size_t kArenaSize = 1024; + const size_t kAlignment = 64; + ArenaAllocator<kArenaSize, kAlignment> a; + + // With an alignment of 64 bytes we expect the header to take up the first + // alignment sized slot leaving bytes leaving the rest available for + // allocation. + const size_t kAllocationsPerChunk = (kArenaSize / kAlignment) - 1; + void* x = nullptr; + void* y = a.Allocate(kAlignment); + EXPECT_TRUE(y); + for (size_t i = 1; i < kAllocationsPerChunk; i++) { + x = y; + y = a.Allocate(kAlignment); + EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y)); + } + + // The next allocation should be in a different chunk. + x = y; + y = a.Allocate(kAlignment); + EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y)); +} + +TEST(ArenaAllocator, MemoryIsValid) +{ + // Make multiple allocations and actually access the memory. This is + // expected to trip up ASAN or valgrind if out of bounds memory is + // accessed. + static const size_t kArenaSize = 1024; + static const size_t kAlignment = 64; + static const char kMark = char(0xBC); + ArenaAllocator<kArenaSize, kAlignment> a; + + // Single allocation that should fill the arena. + size_t sz = kArenaSize - kAlignment; + char* x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } + + // Allocation over arena size. + sz = kArenaSize * 2; + x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } + + // Allocation half the arena size. + sz = kArenaSize / 2; + x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } + + // Repeat, this should actually end up in a new chunk. + x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } +} + +MOZ_DEFINE_MALLOC_SIZE_OF(TestSizeOf); + +TEST(ArenaAllocator, SizeOf) +{ + // This tests the sizeof functionality. We can't test for equality as we + // can't reliably guarantee what sizes the underlying allocator is going to + // choose, so we just test that things grow (or not) as expected. + static const size_t kArenaSize = 4096; + ArenaAllocator<kArenaSize> a; + + // Excluding *this we expect an empty arena allocator to have no overhead. + size_t sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, size_t(0)); + + // Cause one chunk to be allocated. + (void)a.Allocate(kArenaSize / 2); + size_t prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Allocate within the current chunk. + (void)a.Allocate(kArenaSize / 4); + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, prev_sz); + + // Overflow to a new chunk. + (void)a.Allocate(kArenaSize / 2); + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Allocate an oversized chunk with enough room for a header to fit in page + // size. We expect the underlying allocator to round up to page alignment. + (void)a.Allocate((kArenaSize * 2) - 64); + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); +} + +TEST(ArenaAllocator, Clear) +{ + // Tests that the Clear function works as expected. The best proxy for + // checking if a clear is successful is to measure the size. If it's empty we + // expect the size to be 0. + static const size_t kArenaSize = 128; + ArenaAllocator<kArenaSize> a; + + // Clearing an empty arena should work. + a.Clear(); + + size_t sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, size_t(0)); + + // Allocating should work after clearing an empty arena. + void* x = a.Allocate(10); + EXPECT_TRUE(x); + + size_t prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Allocate enough for a few arena chunks to be necessary. + for (size_t i = 0; i < kArenaSize * 2; i++) { + x = a.Allocate(1); + EXPECT_TRUE(x); + } + + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Clearing should reduce the size back to zero. + a.Clear(); + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, size_t(0)); + + // Allocating should work after clearing an arena with allocations. + x = a.Allocate(10); + EXPECT_TRUE(x); + + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); +} + +TEST(ArenaAllocator, Extensions) +{ + ArenaAllocator<4096, 8> a; + + // Test with raw strings. + const char* const kTestCStr = "This is a test string."; + char* c_dup = mozilla::ArenaStrdup(kTestCStr, a); + EXPECT_STREQ(c_dup, kTestCStr); + + const char16_t* const kTestStr = u"This is a wide test string."; + char16_t* dup = mozilla::ArenaStrdup(kTestStr, a); + EXPECT_TRUE(nsString(dup).Equals(kTestStr)); + + // Make sure it works with literal strings. + constexpr auto wideStr = u"A wide string."_ns; + nsLiteralString::char_type* wide = mozilla::ArenaStrdup(wideStr, a); + EXPECT_TRUE(wideStr.Equals(wide)); + + constexpr auto cStr = "A c-string."_ns; + nsLiteralCString::char_type* cstr = mozilla::ArenaStrdup(cStr, a); + EXPECT_TRUE(cStr.Equals(cstr)); + + // Make sure it works with normal strings. + nsAutoString x(u"testing wide"); + nsAutoString::char_type* x_copy = mozilla::ArenaStrdup(x, a); + EXPECT_TRUE(x.Equals(x_copy)); + + nsAutoCString y("testing c-string"); + nsAutoCString::char_type* y_copy = mozilla::ArenaStrdup(y, a); + EXPECT_TRUE(y.Equals(y_copy)); +} diff --git a/xpcom/tests/gtest/TestArrayAlgorithm.cpp b/xpcom/tests/gtest/TestArrayAlgorithm.cpp new file mode 100644 index 0000000000..fbaf9a6a24 --- /dev/null +++ b/xpcom/tests/gtest/TestArrayAlgorithm.cpp @@ -0,0 +1,108 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "nsTArray.h" + +using namespace mozilla; +using std::begin; +using std::end; + +namespace { +constexpr static int32_t arr1[3] = {1, 2, 3}; +} + +TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, NoError_Fallible) +{ + auto res = TransformIntoNewArrayAbortOnErr( + begin(arr1), end(arr1), + [](const int32_t value) -> Result<int64_t, nsresult> { + return value * 10; + }, + fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray<int64_t>& out = res.inspect(); + + const nsTArray<int64_t> expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} + +TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, NoError_Fallible_Range) +{ + auto res = TransformIntoNewArrayAbortOnErr( + arr1, + [](const int32_t value) -> Result<int64_t, nsresult> { + return value * 10; + }, + fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray<int64_t>& out = res.inspect(); + + const nsTArray<int64_t> expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} + +TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, ErrorOnOther_Fallible) +{ + auto res = TransformIntoNewArrayAbortOnErr( + begin(arr1), end(arr1), + [](const int32_t value) -> Result<int64_t, nsresult> { + if (value > 1) { + return Err(NS_ERROR_FAILURE); + } + return value * 10; + }, + fallible); + ASSERT_TRUE(res.isErr()); + ASSERT_EQ(NS_ERROR_FAILURE, res.inspectErr()); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError) +{ + auto res = TransformIntoNewArray( + begin(arr1), end(arr1), + [](const int32_t value) -> int64_t { return value * 10; }); + + const nsTArray<int64_t> expected = {10, 20, 30}; + ASSERT_EQ(expected, res); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError_Range) +{ + auto res = TransformIntoNewArray( + arr1, [](const int32_t value) -> int64_t { return value * 10; }); + + const nsTArray<int64_t> expected = {10, 20, 30}; + ASSERT_EQ(expected, res); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError_Fallible) +{ + auto res = TransformIntoNewArray( + begin(arr1), end(arr1), + [](const int32_t value) -> int64_t { return value * 10; }, fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray<int64_t>& out = res.inspect(); + + const nsTArray<int64_t> expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError_Fallible_Range) +{ + auto res = TransformIntoNewArray( + arr1, [](const int32_t value) -> int64_t { return value * 10; }, + fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray<int64_t>& out = res.inspect(); + + const nsTArray<int64_t> expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} diff --git a/xpcom/tests/gtest/TestAtoms.cpp b/xpcom/tests/gtest/TestAtoms.cpp new file mode 100644 index 0000000000..53148895d2 --- /dev/null +++ b/xpcom/tests/gtest/TestAtoms.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nsAtom.h" +#include "nsString.h" +#include "UTFStrings.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using namespace mozilla; + +int32_t NS_GetUnusedAtomCount(void); + +namespace TestAtoms { + +TEST(Atoms, Basic) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + nsDependentString str16(ValidStrings[i].m16); + nsDependentCString str8(ValidStrings[i].m8); + + RefPtr<nsAtom> atom = NS_Atomize(str16); + + EXPECT_TRUE(atom->Equals(str16)); + + nsString tmp16; + nsCString tmp8; + atom->ToString(tmp16); + atom->ToUTF8String(tmp8); + EXPECT_TRUE(str16.Equals(tmp16)); + EXPECT_TRUE(str8.Equals(tmp8)); + + EXPECT_TRUE(nsDependentString(atom->GetUTF16String()).Equals(str16)); + + EXPECT_TRUE(nsAtomString(atom).Equals(str16)); + EXPECT_TRUE(nsDependentAtomString(atom).Equals(str16)); + EXPECT_TRUE(nsAtomCString(atom).Equals(str8)); + } +} + +TEST(Atoms, 16vs8) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + RefPtr<nsAtom> atom16 = NS_Atomize(ValidStrings[i].m16); + RefPtr<nsAtom> atom8 = NS_Atomize(ValidStrings[i].m8); + EXPECT_EQ(atom16, atom8); + } +} + +TEST(Atoms, Null) +{ + nsAutoString str(u"string with a \0 char"_ns); + nsDependentString strCut(str.get()); + + EXPECT_FALSE(str.Equals(strCut)); + + RefPtr<nsAtom> atomCut = NS_Atomize(strCut); + RefPtr<nsAtom> atom = NS_Atomize(str); + + EXPECT_EQ(atom->GetLength(), str.Length()); + EXPECT_TRUE(atom->Equals(str)); + EXPECT_NE(atom, atomCut); + EXPECT_TRUE(atomCut->Equals(strCut)); +} + +TEST(Atoms, Invalid) +{ + for (unsigned int i = 0; i < ArrayLength(Invalid16Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + { + RefPtr<nsAtom> atom16 = NS_Atomize(Invalid16Strings[i].m16); + EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid16Strings[i].m16))); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } +#ifndef DEBUG + // Don't run this test in debug builds as that intentionally asserts. + for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + { + RefPtr<nsAtom> atom8 = NS_Atomize(Invalid8Strings[i].m8); + RefPtr<nsAtom> atom16 = NS_Atomize(Invalid8Strings[i].m16); + EXPECT_EQ(atom16, atom8); + EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid8Strings[i].m16))); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } + + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + { + RefPtr<nsAtom> atom8 = NS_Atomize(Malformed8Strings[i].m8); + RefPtr<nsAtom> atom16 = NS_Atomize(Malformed8Strings[i].m16); + EXPECT_EQ(atom8, atom16); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } +#endif +} + +#define FIRST_ATOM_STR "first static atom. Hello!" +#define SECOND_ATOM_STR "second static atom. @World!" +#define THIRD_ATOM_STR "third static atom?!" + +static bool isStaticAtom(nsAtom* atom) { + // Don't use logic && in order to ensure that all addrefs/releases are always + // run, even if one of the tests fail. This allows us to run this code on a + // non-static atom without affecting its refcount. + bool rv = (atom->AddRef() == 2); + rv &= (atom->AddRef() == 2); + rv &= (atom->AddRef() == 2); + + rv &= (atom->Release() == 1); + rv &= (atom->Release() == 1); + rv &= (atom->Release() == 1); + return rv; +} + +TEST(Atoms, Table) +{ + nsrefcnt count = NS_GetNumberOfAtoms(); + + RefPtr<nsAtom> thirdDynamic = NS_Atomize(THIRD_ATOM_STR); + + EXPECT_FALSE(isStaticAtom(thirdDynamic)); + + EXPECT_TRUE(thirdDynamic); + EXPECT_EQ(NS_GetNumberOfAtoms(), count + 1); +} + +static void AccessAtoms(void*) { + for (int i = 0; i < 10000; i++) { + RefPtr<nsAtom> atom = NS_Atomize(u"A Testing Atom"); + } +} + +TEST(Atoms, ConcurrentAccessing) +{ + static const size_t kThreadCount = 4; + // Force a GC before so that we don't have any unused atom. + NS_GetNumberOfAtoms(); + EXPECT_EQ(NS_GetUnusedAtomCount(), int32_t(0)); + + // Spawn PRThreads to do the concurrent atom access, to make sure we don't + // spin the main thread event loop. Spinning the event loop may run a task + // that uses an atom, leading to a false positive test failure. + PRThread* threads[kThreadCount]; + for (size_t i = 0; i < kThreadCount; i++) { + threads[i] = PR_CreateThread(PR_USER_THREAD, AccessAtoms, nullptr, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + EXPECT_TRUE(threads[i]); + } + + for (size_t i = 0; i < kThreadCount; i++) { + EXPECT_EQ(PR_SUCCESS, PR_JoinThread(threads[i])); + } + + // We should have one unused atom from this test. + EXPECT_EQ(NS_GetUnusedAtomCount(), int32_t(1)); +} + +} // namespace TestAtoms diff --git a/xpcom/tests/gtest/TestAutoRefCnt.cpp b/xpcom/tests/gtest/TestAutoRefCnt.cpp new file mode 100644 index 0000000000..92cc0d2538 --- /dev/null +++ b/xpcom/tests/gtest/TestAutoRefCnt.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 "nsISupportsImpl.h" + +#include "mozilla/Atomics.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using namespace mozilla; + +class nsThreadSafeAutoRefCntRunner final : public Runnable { + public: + NS_IMETHOD Run() final { + for (int i = 0; i < 10000; i++) { + if (++sRefCnt == 1) { + sIncToOne++; + } + if (--sRefCnt == 0) { + sDecToZero++; + } + } + return NS_OK; + } + + static ThreadSafeAutoRefCnt sRefCnt; + static Atomic<uint32_t, Relaxed> sIncToOne; + static Atomic<uint32_t, Relaxed> sDecToZero; + + nsThreadSafeAutoRefCntRunner() : Runnable("nsThreadSafeAutoRefCntRunner") {} + + private: + ~nsThreadSafeAutoRefCntRunner() = default; +}; + +ThreadSafeAutoRefCnt nsThreadSafeAutoRefCntRunner::sRefCnt; +Atomic<uint32_t, Relaxed> nsThreadSafeAutoRefCntRunner::sIncToOne(0); +Atomic<uint32_t, Relaxed> nsThreadSafeAutoRefCntRunner::sDecToZero(0); + +// When a refcounted object is actually owned by a cache, we may not +// want to release the object after last reference gets released. In +// this pattern, the cache may rely on the balance of increment to one +// and decrement to zero, so that it can maintain a counter for GC. +TEST(AutoRefCnt, ThreadSafeAutoRefCntBalance) +{ + static const size_t kThreadCount = 4; + nsCOMPtr<nsIThread> threads[kThreadCount]; + for (size_t i = 0; i < kThreadCount; i++) { + nsresult rv = + NS_NewNamedThread("AutoRefCnt Test", getter_AddRefs(threads[i]), + new nsThreadSafeAutoRefCntRunner); + EXPECT_NS_SUCCEEDED(rv); + } + for (size_t i = 0; i < kThreadCount; i++) { + threads[i]->Shutdown(); + } + EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sRefCnt, nsrefcnt(0)); + EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sIncToOne, + nsThreadSafeAutoRefCntRunner::sDecToZero); +} diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp new file mode 100644 index 0000000000..56d6f03ff8 --- /dev/null +++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp @@ -0,0 +1,227 @@ +/* -*- 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/mman.h> // For memory-locking. + +#include "gtest/gtest.h" + +#include "AvailableMemoryWatcher.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPrefs_browser.h" +#include "nsIObserverService.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" + +using namespace mozilla; + +namespace { + +// Dummy tab unloader whose one job is to dispatch a low memory event. +class MockTabUnloader final : public nsITabUnloader { + NS_DECL_THREADSAFE_ISUPPORTS + public: + MockTabUnloader() = default; + + NS_IMETHOD UnloadTabAsync() override { + // We want to issue a memory pressure event for + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + return NS_OK; + } + + private: + ~MockTabUnloader() = default; +}; + +NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader) + +// Class that gradually increases the percent memory threshold +// until it reaches 100%, which should guarantee a memory pressure +// notification. +class AvailableMemoryChecker final : public nsITimerCallback, public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + AvailableMemoryChecker(); + void Init(); + void Shutdown(); + + private: + ~AvailableMemoryChecker() = default; + + bool mResolved; + nsCOMPtr<nsITimer> mTimer; + RefPtr<nsAvailableMemoryWatcherBase> mWatcher; + RefPtr<MockTabUnloader> mTabUnloader; + + const uint32_t kPollingInterval = 50; + const uint32_t kPrefIncrement = 5; +}; + +AvailableMemoryChecker::AvailableMemoryChecker() : mResolved(false) {} + +NS_IMPL_ISUPPORTS(AvailableMemoryChecker, nsITimerCallback, nsINamed); + +void AvailableMemoryChecker::Init() { + mTabUnloader = new MockTabUnloader; + + mWatcher = nsAvailableMemoryWatcherBase::GetSingleton(); + mWatcher->RegisterTabUnloader(mTabUnloader); + + mTimer = NS_NewTimer(); + mTimer->InitWithCallback(this, kPollingInterval, + nsITimer::TYPE_REPEATING_SLACK); +} + +void AvailableMemoryChecker::Shutdown() { + if (mTimer) { + mTimer->Cancel(); + } + Preferences::ClearUser("browser.low_commit_space_threshold_percent"); +} + +// Timer callback to increase the pref threshold. +NS_IMETHODIMP +AvailableMemoryChecker::Notify(nsITimer* aTimer) { + uint32_t threshold = + StaticPrefs::browser_low_commit_space_threshold_percent(); + if (threshold >= 100) { + mResolved = true; + return NS_OK; + } + threshold += kPrefIncrement; + Preferences::SetUint("browser.low_commit_space_threshold_percent", threshold); + return NS_OK; +} + +NS_IMETHODIMP AvailableMemoryChecker::GetName(nsACString& aName) { + aName.AssignLiteral("AvailableMemoryChecker"); + return NS_OK; +} + +// Class that listens for a given notification, then records +// if it was received. +class Spinner final : public nsIObserver { + nsCOMPtr<nsIObserverService> mObserverSvc; + nsDependentCString mTopic; + bool mTopicObserved; + + ~Spinner() = default; + + public: + NS_DECL_ISUPPORTS + + Spinner(nsIObserverService* aObserverSvc, const char* aTopic) + : mObserverSvc(aObserverSvc), mTopic(aTopic), mTopicObserved(false) {} + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (mTopic == aTopic) { + mTopicObserved = true; + mObserverSvc->RemoveObserver(this, aTopic); + + // Force the loop to move in case there is no event in the queue. + nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__); + NS_DispatchToMainThread(dummyEvent); + } + return NS_OK; + } + void StartListening() { + mObserverSvc->AddObserver(this, mTopic.get(), false); + } + bool TopicObserved() { return mTopicObserved; } + bool WaitForNotification(); +}; +NS_IMPL_ISUPPORTS(Spinner, nsIObserver); + +bool Spinner::WaitForNotification() { + bool isTimeout = false; + + nsCOMPtr<nsITimer> timer; + + // This timer should time us out if we never observe our notification. + // Set to 5000 since the memory checker should finish incrementing the + // pref by then, and if it hasn't then it is probably stuck somehow. + NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), + [](nsITimer*, void* isTimeout) { + *reinterpret_cast<bool*>(isTimeout) = true; + }, + &isTimeout, 5000, nsITimer::TYPE_ONE_SHOT, __func__); + + SpinEventLoopUntil("Spinner:WaitForNotification"_ns, [&]() -> bool { + if (isTimeout) { + return true; + } + return mTopicObserved; + }); + return !isTimeout; +} + +void StartUserInteraction(const nsCOMPtr<nsIObserverService>& aObserverSvc) { + aObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr); +} + +TEST(AvailableMemoryWatcher, BasicTest) +{ + nsCOMPtr<nsIObserverService> observerSvc = services::GetObserverService(); + RefPtr<Spinner> aSpinner = new Spinner(observerSvc, "memory-pressure"); + aSpinner->StartListening(); + + // Start polling for low memory. + StartUserInteraction(observerSvc); + + RefPtr<AvailableMemoryChecker> checker = new AvailableMemoryChecker(); + checker->Init(); + + aSpinner->WaitForNotification(); + + // The checker should have dispatched a low memory event before reaching 100% + // memory pressure threshold, so the topic should be observed by the spinner. + EXPECT_TRUE(aSpinner->TopicObserved()); + checker->Shutdown(); +} + +TEST(AvailableMemoryWatcher, MemoryLowToHigh) +{ + // Setting this pref to 100 ensures we start in a low memory scenario. + Preferences::SetUint("browser.low_commit_space_threshold_percent", 100); + + nsCOMPtr<nsIObserverService> observerSvc = services::GetObserverService(); + RefPtr<Spinner> lowMemorySpinner = + new Spinner(observerSvc, "memory-pressure"); + lowMemorySpinner->StartListening(); + + StartUserInteraction(observerSvc); + + // Start polling for low memory. We should start with low memory when we start + // the checker. + RefPtr<AvailableMemoryChecker> checker = new AvailableMemoryChecker(); + checker->Init(); + + lowMemorySpinner->WaitForNotification(); + + EXPECT_TRUE(lowMemorySpinner->TopicObserved()); + + RefPtr<Spinner> highMemorySpinner = + new Spinner(observerSvc, "memory-pressure-stop"); + highMemorySpinner->StartListening(); + + // Now that we are definitely low on memory, let's reset the pref to 0 to + // exit low memory. + Preferences::SetUint("browser.low_commit_space_threshold_percent", 0); + + highMemorySpinner->WaitForNotification(); + + EXPECT_TRUE(highMemorySpinner->TopicObserved()); + + checker->Shutdown(); +} +} // namespace diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp new file mode 100644 index 0000000000..cbd564b7db --- /dev/null +++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.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 "gtest/gtest.h" + +#include "AvailableMemoryWatcher.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/Unused.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; + +namespace { + +template <typename ConditionT> +bool WaitUntil(const ConditionT& aCondition, uint32_t aTimeoutMs) { + bool isTimeout = false; + + // The message queue can be empty and the loop stops + // waiting for a new event before detecting timeout. + // Creating a timer to fire a timeout event. + nsCOMPtr<nsITimer> timer; + NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), + [](nsITimer*, void* isTimeout) { + *reinterpret_cast<bool*>(isTimeout) = true; + }, + &isTimeout, aTimeoutMs, nsITimer::TYPE_ONE_SHOT, __func__); + + SpinEventLoopUntil("TestAvailableMemoryWatcherMac"_ns, [&]() -> bool { + if (isTimeout) { + return true; + } + return aCondition(); + }); + + return !isTimeout; +} + +class Spinner final : public nsIObserver { + nsCOMPtr<nsIObserverService> mObserverSvc; + nsDependentCString mTopicToWatch; + bool mTopicObserved; + + ~Spinner() = default; + + public: + NS_DECL_ISUPPORTS + + Spinner(nsIObserverService* aObserverSvc, const char* const aTopic, + const char16_t* const aSubTopic) + : mObserverSvc(aObserverSvc), + mTopicToWatch(aTopic), + mTopicObserved(false) {} + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (mTopicToWatch == aTopic) { + mTopicObserved = true; + mObserverSvc->RemoveObserver(this, aTopic); + + // Force the loop to move in case that there is no event in the queue. + nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__); + NS_DispatchToMainThread(dummyEvent); + } + return NS_OK; + } + + void StartListening() { + mTopicObserved = false; + mObserverSvc->AddObserver(this, mTopicToWatch.get(), false); + } + + bool Wait(uint32_t aTimeoutMs) { + return WaitUntil([this]() { return this->mTopicObserved; }, aTimeoutMs); + } +}; + +NS_IMPL_ISUPPORTS(Spinner, nsIObserver) + +class MockTabUnloader final : public nsITabUnloader { + ~MockTabUnloader() = default; + + uint32_t mCounter; + + public: + MockTabUnloader() : mCounter(0) {} + + NS_DECL_THREADSAFE_ISUPPORTS + + void ResetCounter() { mCounter = 0; } + uint32_t GetCounter() const { return mCounter; } + + NS_IMETHOD UnloadTabAsync() override { + ++mCounter; + // Issue a memory-pressure to verify OnHighMemory issues + // a memory-pressure-stop event. + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader) + +} // namespace + +class AvailableMemoryWatcherFixture : public TelemetryTestFixture { + nsCOMPtr<nsIObserverService> mObserverSvc; + + protected: + RefPtr<nsAvailableMemoryWatcherBase> mWatcher; + RefPtr<Spinner> mHighMemoryObserver; + RefPtr<Spinner> mLowMemoryObserver; + RefPtr<MockTabUnloader> mTabUnloader; + + static constexpr uint32_t kStateChangeTimeoutMs = 20000; + static constexpr uint32_t kNotificationTimeoutMs = 20000; + + void SetUp() override { + TelemetryTestFixture::SetUp(); + + mObserverSvc = do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + ASSERT_TRUE(mObserverSvc); + + mHighMemoryObserver = + new Spinner(mObserverSvc, "memory-pressure-stop", nullptr); + mLowMemoryObserver = new Spinner(mObserverSvc, "memory-pressure", nullptr); + + mTabUnloader = new MockTabUnloader; + + mWatcher = nsAvailableMemoryWatcherBase::GetSingleton(); + mWatcher->RegisterTabUnloader(mTabUnloader); + } +}; + +class MemoryWatcherTelemetryEvent { + static nsLiteralString sEventCategory; + static nsLiteralString sEventMethod; + static nsLiteralString sEventObject; + uint32_t mLastCountOfEvents; + + public: + explicit MemoryWatcherTelemetryEvent(JSContext* aCx) : mLastCountOfEvents(0) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + mLastCountOfEvents = eventValues.Length(); + } + + void ValidateLastEvent(JSContext* aCx) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + + // A new event was generated. + EXPECT_EQ(eventValues.Length(), mLastCountOfEvents + 1); + if (eventValues.IsEmpty()) { + return; + } + + // Update mLastCountOfEvents for a subsequent call to ValidateLastEvent + ++mLastCountOfEvents; + + nsTArray<nsString> tokens; + for (const nsAString& token : eventValues.LastElement().Split(',')) { + tokens.AppendElement(token); + } + EXPECT_EQ(tokens.Length(), 3U); + + // The third token should be a valid floating number. + nsresult rv; + tokens[2].ToDouble(&rv); + EXPECT_NS_SUCCEEDED(rv); + } +}; + +nsLiteralString MemoryWatcherTelemetryEvent::sEventCategory = + u"memory_watcher"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventMethod = + u"on_high_memory"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventObject = u"stats"_ns; + +/* + * Test the browser memory pressure reponse by artificially putting the system + * into the "critical" level and ensuring 1) a tab unload attempt occurs and + * 2) the Gecko memory-pressure notitificiation start and stop events occur. + */ +TEST_F(AvailableMemoryWatcherFixture, MemoryPressureResponse) { + // Set the memory pressure state to normal in case we are already + // running in a low memory pressure state. + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eNormal); + + // Reset state + mTabUnloader->ResetCounter(); + AutoJSContextWithGlobal cx(mCleanGlobal); + MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext()); + + // Simulate a low memory OS callback and make sure we observe + // a memory-pressure event and a tab unload. + mLowMemoryObserver->StartListening(); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eWarning); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eCritical); + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + EXPECT_TRUE(mLowMemoryObserver->Wait(kStateChangeTimeoutMs)); + + // Simulate the normal memory pressure OS callback and make + // sure we observe a memory-pressure-stop event. + mHighMemoryObserver->StartListening(); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eWarning); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eNormal); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); + + telemetryEvent.ValidateLastEvent(cx.GetJSContext()); +} diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp new file mode 100644 index 0000000000..409d547aaa --- /dev/null +++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp @@ -0,0 +1,663 @@ +/* -*- 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 <algorithm> +#include <windows.h> +#include <memoryapi.h> +#include "gtest/gtest.h" + +#include "AvailableMemoryWatcher.h" +#include "mozilla/Atomics.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/Unused.h" +#include "mozilla/Vector.h" +#include "nsComponentManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "nsWindowsHelpers.h" +#include "nsIWindowsRegKey.h" +#include "nsXULAppAPI.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; + +namespace { + +static constexpr size_t kBytesInMB = 1024 * 1024; + +template <typename ConditionT> +bool WaitUntil(const ConditionT& aCondition, uint32_t aTimeoutMs) { + const uint64_t t0 = ::GetTickCount64(); + bool isTimeout = false; + + // The message queue can be empty and the loop stops + // waiting for a new event before detecting timeout. + // Creating a timer to fire a timeout event. + nsCOMPtr<nsITimer> timer; + NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), + [](nsITimer*, void* isTimeout) { + *reinterpret_cast<bool*>(isTimeout) = true; + }, + &isTimeout, aTimeoutMs, nsITimer::TYPE_ONE_SHOT, __func__); + + SpinEventLoopUntil("xpcom-tests:WaitUntil"_ns, [&]() -> bool { + if (isTimeout) { + return true; + } + + bool done = aCondition(); + if (done) { + fprintf(stderr, "Done in %llu msec\n", ::GetTickCount64() - t0); + } + return done; + }); + + return !isTimeout; +} + +class Spinner final : public nsIObserver { + nsCOMPtr<nsIObserverService> mObserverSvc; + nsDependentCString mTopicToWatch; + Maybe<nsDependentString> mSubTopicToWatch; + bool mTopicObserved; + + ~Spinner() = default; + + public: + NS_DECL_ISUPPORTS + + Spinner(nsIObserverService* aObserverSvc, const char* const aTopic, + const char16_t* const aSubTopic) + : mObserverSvc(aObserverSvc), + mTopicToWatch(aTopic), + mSubTopicToWatch(aSubTopic ? Some(nsDependentString(aSubTopic)) + : Nothing()), + mTopicObserved(false) {} + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (mTopicToWatch == aTopic) { + if ((mSubTopicToWatch.isNothing() && !aData) || + mSubTopicToWatch.ref() == aData) { + mTopicObserved = true; + mObserverSvc->RemoveObserver(this, aTopic); + + // Force the loop to move in case that there is no event in the queue. + nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__); + NS_DispatchToMainThread(dummyEvent); + } + } else { + fprintf(stderr, "Unexpected topic: %s\n", aTopic); + } + + return NS_OK; + } + + void StartListening() { + mTopicObserved = false; + mObserverSvc->AddObserver(this, mTopicToWatch.get(), false); + } + + bool Wait(uint32_t aTimeoutMs) { + return WaitUntil([this]() { return this->mTopicObserved; }, aTimeoutMs); + } +}; + +NS_IMPL_ISUPPORTS(Spinner, nsIObserver) + +/** + * Starts a new thread with a message queue to process + * memory allocation/free requests + */ +class MemoryEater { + using PageT = UniquePtr<void, VirtualFreeDeleter>; + + static DWORD WINAPI ThreadStart(LPVOID aParam) { + return reinterpret_cast<MemoryEater*>(aParam)->ThreadProc(); + } + + static void TouchMemory(void* aAddr, size_t aSize) { + constexpr uint32_t kPageSize = 4096; + volatile uint8_t x = 0; + auto base = reinterpret_cast<uint8_t*>(aAddr); + for (int64_t i = 0, pages = aSize / kPageSize; i < pages; ++i) { + // Pick a random place in every allocated page + // and dereference it. + x ^= *(base + i * kPageSize + rand() % kPageSize); + } + (void)x; + } + + static uint32_t GetAvailablePhysicalMemoryInMb() { + MEMORYSTATUSEX statex = {sizeof(statex)}; + if (!::GlobalMemoryStatusEx(&statex)) { + return 0; + } + + return static_cast<uint32_t>(statex.ullAvailPhys / kBytesInMB); + } + + static bool AddWorkingSet(size_t aSize, Vector<PageT>& aOutput) { + constexpr size_t kMinGranularity = 64 * 1024; + + size_t currentSize = aSize; + while (aSize >= kMinGranularity) { + if (!GetAvailablePhysicalMemoryInMb()) { + // If the available physical memory is less than 1MB, we finish + // allocation though there may be still the available commit space. + fprintf(stderr, "No enough physical memory.\n"); + return false; + } + + PageT page(::VirtualAlloc(nullptr, currentSize, MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE)); + if (!page) { + DWORD gle = ::GetLastError(); + if (gle != ERROR_COMMITMENT_LIMIT) { + return false; + } + + // Try again with a smaller allocation size. + currentSize /= 2; + continue; + } + + aSize -= currentSize; + + // VirtualAlloc consumes the commit space, but we need to *touch* memory + // to consume physical memory + TouchMemory(page.get(), currentSize); + Unused << aOutput.emplaceBack(std::move(page)); + } + return true; + } + + DWORD mThreadId; + nsAutoHandle mThread; + nsAutoHandle mMessageQueueReady; + Atomic<bool> mTaskStatus; + + enum class TaskType : UINT { + Alloc = WM_USER, // WPARAM = Allocation size + Free, + + Last, + }; + + DWORD ThreadProc() { + Vector<PageT> stock; + MSG msg; + + // Force the system to create a message queue + ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + + // Ready to get a message. Unblock the main thread. + ::SetEvent(mMessageQueueReady.get()); + + for (;;) { + BOOL result = ::GetMessage(&msg, reinterpret_cast<HWND>(-1), WM_QUIT, + static_cast<UINT>(TaskType::Last)); + if (result == -1) { + return ::GetLastError(); + } + if (!result) { + // Got WM_QUIT + break; + } + + switch (static_cast<TaskType>(msg.message)) { + case TaskType::Alloc: + mTaskStatus = AddWorkingSet(msg.wParam, stock); + break; + case TaskType::Free: + stock = Vector<PageT>(); + mTaskStatus = true; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected message in the queue"); + break; + } + } + + return static_cast<DWORD>(msg.wParam); + } + + bool PostTask(TaskType aTask, WPARAM aW = 0, LPARAM aL = 0) const { + return !!::PostThreadMessageW(mThreadId, static_cast<UINT>(aTask), aW, aL); + } + + public: + MemoryEater() + : mThread(::CreateThread(nullptr, 0, ThreadStart, this, 0, &mThreadId)), + mMessageQueueReady(::CreateEventW(nullptr, /*bManualReset*/ TRUE, + /*bInitialState*/ FALSE, nullptr)) { + ::WaitForSingleObject(mMessageQueueReady.get(), INFINITE); + } + + ~MemoryEater() { + ::PostThreadMessageW(mThreadId, WM_QUIT, 0, 0); + if (::WaitForSingleObject(mThread.get(), 30000) != WAIT_OBJECT_0) { + ::TerminateThread(mThread.get(), 0); + } + } + + bool GetTaskStatus() const { return mTaskStatus; } + void RequestAlloc(size_t aSize) { PostTask(TaskType::Alloc, aSize); } + void RequestFree() { PostTask(TaskType::Free); } +}; + +class MockTabUnloader final : public nsITabUnloader { + ~MockTabUnloader() = default; + + uint32_t mCounter; + + public: + MockTabUnloader() : mCounter(0) {} + + NS_DECL_THREADSAFE_ISUPPORTS + + void ResetCounter() { mCounter = 0; } + uint32_t GetCounter() const { return mCounter; } + + NS_IMETHOD UnloadTabAsync() override { + ++mCounter; + // Issue a memory-pressure to verify OnHighMemory issues + // a memory-pressure-stop event. + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader) + +} // namespace + +class AvailableMemoryWatcherFixture : public TelemetryTestFixture { + static const char kPrefLowCommitSpaceThreshold[]; + + RefPtr<nsAvailableMemoryWatcherBase> mWatcher; + nsCOMPtr<nsIObserverService> mObserverSvc; + + protected: + static bool IsPageFileExpandable() { + const auto kMemMgmtKey = + u"SYSTEM\\CurrentControlSet\\Control\\" + u"Session Manager\\Memory Management"_ns; + + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kMemMgmtKey, + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + return false; + } + + nsAutoString pagingFiles; + rv = regKey->ReadStringValue(u"PagingFiles"_ns, pagingFiles); + if (NS_FAILED(rv)) { + return false; + } + + // The value data is REG_MULTI_SZ and each element is "<path> <min> <max>". + // If the page file size is automatically managed for all drives, the <path> + // is set to "?:\pagefile.sys". + // If the page file size is configured per drive, for a drive whose page + // file is set to "system managed size", both <min> and <max> are set to 0. + return !pagingFiles.IsEmpty() && + (pagingFiles[0] == u'?' || FindInReadable(u" 0 0"_ns, pagingFiles)); + } + + static size_t GetAllocationSizeToTriggerMemoryNotification() { + // The percentage of the used physical memory to the total physical memory + // size which is big enough to trigger a memory resource notification. + constexpr uint32_t kThresholdPercentage = 98; + // If the page file is not expandable, leave a little commit space. + const uint32_t kMinimumSafeCommitSpaceMb = + IsPageFileExpandable() ? 0 : 1024; + + MEMORYSTATUSEX statex = {sizeof(statex)}; + EXPECT_TRUE(::GlobalMemoryStatusEx(&statex)); + + // How much memory needs to be used to trigger the notification + const size_t targetUsedTotalMb = + (statex.ullTotalPhys / kBytesInMB) * kThresholdPercentage / 100; + + // How much memory is currently consumed + const size_t currentConsumedMb = + (statex.ullTotalPhys - statex.ullAvailPhys) / kBytesInMB; + + if (currentConsumedMb >= targetUsedTotalMb) { + fprintf(stderr, "The available physical memory is already low.\n"); + return 0; + } + + // How much memory we need to allocate to trigger the notification + const uint32_t allocMb = targetUsedTotalMb - currentConsumedMb; + + // If we allocate the target amount, how much commit space will be + // left available. + const uint32_t estimtedAvailCommitSpace = std::max( + 0, + static_cast<int32_t>((statex.ullAvailPageFile / kBytesInMB) - allocMb)); + + // If the available commit space will be too low, we should not continue + if (estimtedAvailCommitSpace < kMinimumSafeCommitSpaceMb) { + fprintf(stderr, "The available commit space will be short - %d\n", + estimtedAvailCommitSpace); + return 0; + } + + fprintf(stderr, + "Total physical memory = %ul\n" + "Available commit space = %ul\n" + "Amount to allocate = %ul\n" + "Future available commit space after allocation = %d\n", + static_cast<uint32_t>(statex.ullTotalPhys / kBytesInMB), + static_cast<uint32_t>(statex.ullAvailPageFile / kBytesInMB), + allocMb, estimtedAvailCommitSpace); + return allocMb * kBytesInMB; + } + + static void SetThresholdAsPercentageOfCommitSpace(uint32_t aPercentage) { + aPercentage = std::min(100u, aPercentage); + + MEMORYSTATUSEX statex = {sizeof(statex)}; + EXPECT_TRUE(::GlobalMemoryStatusEx(&statex)); + + const uint32_t newVal = static_cast<uint32_t>( + (statex.ullAvailPageFile / kBytesInMB) * aPercentage / 100); + fprintf(stderr, "Setting %s to %u\n", kPrefLowCommitSpaceThreshold, newVal); + + Preferences::SetUint(kPrefLowCommitSpaceThreshold, newVal); + } + + static constexpr uint32_t kStateChangeTimeoutMs = 20000; + static constexpr uint32_t kNotificationTimeoutMs = 20000; + + RefPtr<Spinner> mHighMemoryObserver; + RefPtr<MockTabUnloader> mTabUnloader; + MemoryEater mMemEater; + nsAutoHandle mLowMemoryHandle; + + void SetUp() override { + TelemetryTestFixture::SetUp(); + + mObserverSvc = do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + ASSERT_TRUE(mObserverSvc); + + mHighMemoryObserver = + new Spinner(mObserverSvc, "memory-pressure-stop", nullptr); + mTabUnloader = new MockTabUnloader; + + mWatcher = nsAvailableMemoryWatcherBase::GetSingleton(); + mWatcher->RegisterTabUnloader(mTabUnloader); + + mLowMemoryHandle.own( + ::CreateMemoryResourceNotification(LowMemoryResourceNotification)); + ASSERT_TRUE(mLowMemoryHandle); + + // We set the threshold to 50% of the current available commit space. + // This means we declare low-memory when the available commit space + // gets lower than this threshold, otherwise we declare high-memory. + SetThresholdAsPercentageOfCommitSpace(50); + } + + void TearDown() override { + StopUserInteraction(); + Preferences::ClearUser(kPrefLowCommitSpaceThreshold); + } + + bool WaitForMemoryResourceNotification() { + uint64_t t0 = ::GetTickCount64(); + if (::WaitForSingleObject(mLowMemoryHandle, kNotificationTimeoutMs) != + WAIT_OBJECT_0) { + fprintf(stderr, "The memory notification was not triggered.\n"); + return false; + } + fprintf(stderr, "Notified in %llu msec\n", ::GetTickCount64() - t0); + return true; + } + + void StartUserInteraction() { + mObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr); + } + + void StopUserInteraction() { + mObserverSvc->NotifyObservers(nullptr, "user-interaction-inactive", + nullptr); + } +}; + +const char AvailableMemoryWatcherFixture::kPrefLowCommitSpaceThreshold[] = + "browser.low_commit_space_threshold_mb"; + +class MemoryWatcherTelemetryEvent { + static nsLiteralString sEventCategory; + static nsLiteralString sEventMethod; + static nsLiteralString sEventObject; + + uint32_t mLastCountOfEvents; + + public: + explicit MemoryWatcherTelemetryEvent(JSContext* aCx) : mLastCountOfEvents(0) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + mLastCountOfEvents = eventValues.Length(); + } + + void ValidateLastEvent(JSContext* aCx) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + + // A new event was generated. + EXPECT_EQ(eventValues.Length(), mLastCountOfEvents + 1); + if (eventValues.IsEmpty()) { + return; + } + + // Update mLastCountOfEvents for a subsequent call to ValidateLastEvent + ++mLastCountOfEvents; + + nsTArray<nsString> tokens; + for (const nsAString& token : eventValues.LastElement().Split(',')) { + tokens.AppendElement(token); + } + EXPECT_EQ(tokens.Length(), 3U); + if (tokens.Length() != 3U) { + const wchar_t* valueStr = eventValues.LastElement().get(); + fprintf(stderr, "Unexpected event value: %S\n", valueStr); + return; + } + + // Since this test does not involve TabUnloader, the first two numbers + // are always expected to be zero. + EXPECT_STREQ(tokens[0].get(), L"0"); + EXPECT_STREQ(tokens[1].get(), L"0"); + + // The third token should be a valid floating number. + nsresult rv; + tokens[2].ToDouble(&rv); + EXPECT_NS_SUCCEEDED(rv); + } +}; + +nsLiteralString MemoryWatcherTelemetryEvent::sEventCategory = + u"memory_watcher"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventMethod = + u"on_high_memory"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventObject = u"stats"_ns; + +TEST_F(AvailableMemoryWatcherFixture, AlwaysActive) { + AutoJSContextWithGlobal cx(mCleanGlobal); + MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext()); + StartUserInteraction(); + + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mHighMemoryObserver->StartListening(); + mMemEater.RequestFree(); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); + + telemetryEvent.ValidateLastEvent(cx.GetJSContext()); +} + +TEST_F(AvailableMemoryWatcherFixture, InactiveToActive) { + AutoJSContextWithGlobal cx(mCleanGlobal); + MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext()); + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + mHighMemoryObserver->StartListening(); + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mMemEater.RequestFree(); + + // OnHighMemory should not be triggered during no user interaction + // eve after all memory was freed. Expecting false. + EXPECT_FALSE(mHighMemoryObserver->Wait(3000)); + + StartUserInteraction(); + + // After user is active, we expect true. + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); + + telemetryEvent.ValidateLastEvent(cx.GetJSContext()); +} + +TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_AlwaysActive) { + // Setting a low threshold simulates a high commit space. + SetThresholdAsPercentageOfCommitSpace(1); + StartUserInteraction(); + + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + // Tab unload will not be triggered because the commit space is not low. + EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs / 2)); + + mMemEater.RequestFree(); + ::Sleep(kStateChangeTimeoutMs / 2); + + // Set a high threshold and make sure the watcher will trigger the tab + // unloader next time. + SetThresholdAsPercentageOfCommitSpace(50); + + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + return; + } + + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mHighMemoryObserver->StartListening(); + mMemEater.RequestFree(); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); +} + +TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_InactiveToActive) { + // Setting a low threshold simulates a high commit space. + SetThresholdAsPercentageOfCommitSpace(1); + + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + // Tab unload will not be triggered because the commit space is not low. + EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs / 2)); + + mMemEater.RequestFree(); + ::Sleep(kStateChangeTimeoutMs / 2); + + // Set a high threshold and make sure the watcher will trigger the tab + // unloader next time. + SetThresholdAsPercentageOfCommitSpace(50); + + // When the user becomes active, the watcher will resume the timer. + StartUserInteraction(); + + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + return; + } + + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mHighMemoryObserver->StartListening(); + mMemEater.RequestFree(); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); +} diff --git a/xpcom/tests/gtest/TestBase64.cpp b/xpcom/tests/gtest/TestBase64.cpp new file mode 100644 index 0000000000..55fbcefe38 --- /dev/null +++ b/xpcom/tests/gtest/TestBase64.cpp @@ -0,0 +1,454 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "mozilla/Base64.h" +#include "nsComponentManagerUtils.h" +#include "nsIScriptableBase64Encoder.h" +#include "nsIInputStream.h" +#include "nsString.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +struct Chunk { + Chunk(uint32_t l, const char* c) : mLength(l), mData(c) {} + + uint32_t mLength; + const char* mData; +}; + +struct Test { + Test(Chunk* c, const char* r) : mChunks(c), mResult(r) {} + + Chunk* mChunks; + const char* mResult; +}; + +static Chunk kTest1Chunks[] = {Chunk(9, "Hello sir"), Chunk(0, nullptr)}; + +static Chunk kTest2Chunks[] = {Chunk(3, "Hel"), Chunk(3, "lo "), + Chunk(3, "sir"), Chunk(0, nullptr)}; + +static Chunk kTest3Chunks[] = {Chunk(1, "I"), Chunk(0, nullptr)}; + +static Chunk kTest4Chunks[] = {Chunk(2, "Hi"), Chunk(0, nullptr)}; + +static Chunk kTest5Chunks[] = {Chunk(1, "B"), Chunk(2, "ob"), + Chunk(0, nullptr)}; + +static Chunk kTest6Chunks[] = {Chunk(2, "Bo"), Chunk(1, "b"), + Chunk(0, nullptr)}; + +static Chunk kTest7Chunks[] = {Chunk(1, "F"), // Carry over 1 + Chunk(4, "iref"), // Carry over 2 + Chunk(2, "ox"), // 1 + Chunk(4, " is "), // 2 + Chunk(2, "aw"), // 1 + Chunk(4, "esom"), // 2 + Chunk(2, "e!"), Chunk(0, nullptr)}; + +static Chunk kTest8Chunks[] = {Chunk(5, "ALL T"), + Chunk(1, "H"), + Chunk(4, "ESE "), + Chunk(2, "WO"), + Chunk(21, "RLDS ARE YOURS EXCEPT"), + Chunk(9, " EUROPA. "), + Chunk(25, "ATTEMPT NO LANDING THERE."), + Chunk(0, nullptr)}; + +static Test kTests[] = { + // Test 1, test a simple round string in one chunk + Test(kTest1Chunks, "SGVsbG8gc2ly"), + // Test 2, test a simple round string split into round chunks + Test(kTest2Chunks, "SGVsbG8gc2ly"), + // Test 3, test a single chunk that's 2 short + Test(kTest3Chunks, "SQ=="), + // Test 4, test a single chunk that's 1 short + Test(kTest4Chunks, "SGk="), + // Test 5, test a single chunk that's 2 short, followed by a chunk of 2 + Test(kTest5Chunks, "Qm9i"), + // Test 6, test a single chunk that's 1 short, followed by a chunk of 1 + Test(kTest6Chunks, "Qm9i"), + // Test 7, test alternating carryovers + Test(kTest7Chunks, "RmlyZWZveCBpcyBhd2Vzb21lIQ=="), + // Test 8, test a longish string + Test(kTest8Chunks, + "QUxMIFRIRVNFIFdPUkxEUyBBUkUgWU9VUlMgRVhDRVBUIEVVUk9QQS4gQVRURU1QVCBOT" + "yBMQU5ESU5HIFRIRVJFLg=="), + // Terminator + Test(nullptr, nullptr)}; + +class FakeInputStream final : public nsIInputStream { + ~FakeInputStream() = default; + + public: + FakeInputStream() + : mTestNumber(0), + mTest(&kTests[0]), + mChunk(&mTest->mChunks[0]), + mClosed(false) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + + void Reset(); + bool NextTest(); + void CheckTest(nsACString& aResult); + void CheckTest(nsAString& aResult); + + private: + uint32_t mTestNumber; + const Test* mTest; + const Chunk* mChunk; + bool mClosed; +}; + +NS_IMPL_ISUPPORTS(FakeInputStream, nsIInputStream) + +NS_IMETHODIMP +FakeInputStream::Close() { + mClosed = true; + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::Available(uint64_t* aAvailable) { + *aAvailable = 0; + + if (mClosed) return NS_BASE_STREAM_CLOSED; + + const Chunk* chunk = mChunk; + while (chunk->mLength) { + *aAvailable += chunk->mLength; + chunk++; + } + + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::StreamStatus() { + return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aOut) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FakeInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aRead) { + *aRead = 0; + + if (mClosed) return NS_BASE_STREAM_CLOSED; + + while (mChunk->mLength) { + uint32_t written = 0; + + nsresult rv = (*aWriter)(this, aClosure, mChunk->mData, *aRead, + mChunk->mLength, &written); + + *aRead += written; + NS_ENSURE_SUCCESS(rv, rv); + + mChunk++; + } + + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::IsNonBlocking(bool* aIsBlocking) { + *aIsBlocking = false; + return NS_OK; +} + +void FakeInputStream::Reset() { + mClosed = false; + mChunk = &mTest->mChunks[0]; +} + +bool FakeInputStream::NextTest() { + mTestNumber++; + mTest = &kTests[mTestNumber]; + mChunk = &mTest->mChunks[0]; + mClosed = false; + + return mTest->mChunks != nullptr; +} + +void FakeInputStream::CheckTest(nsACString& aResult) { + ASSERT_STREQ(aResult.BeginReading(), mTest->mResult); +} + +void FakeInputStream::CheckTest(nsAString& aResult) { + ASSERT_TRUE(aResult.EqualsASCII(mTest->mResult)) + << "Actual: " << NS_ConvertUTF16toUTF8(aResult).get() << '\n' + << "Expected: " << mTest->mResult; +} + +TEST(Base64, StreamEncoder) +{ + nsCOMPtr<nsIScriptableBase64Encoder> encoder = + do_CreateInstance("@mozilla.org/scriptablebase64encoder;1"); + ASSERT_TRUE(encoder); + + RefPtr<FakeInputStream> stream = new FakeInputStream(); + do { + nsString wideString; + nsCString string; + + nsresult rv; + rv = encoder->EncodeToString(stream, 0, wideString); + ASSERT_NS_SUCCEEDED(rv); + + stream->Reset(); + + rv = encoder->EncodeToCString(stream, 0, string); + ASSERT_NS_SUCCEEDED(rv); + + stream->CheckTest(wideString); + stream->CheckTest(string); + } while (stream->NextTest()); +} + +struct EncodeDecodeTestCase { + const char* mInput; + const char* mOutput; +}; + +static EncodeDecodeTestCase sRFC4648TestCases[] = { + {"", ""}, + {"f", "Zg=="}, + {"fo", "Zm8="}, + {"foo", "Zm9v"}, + {"foob", "Zm9vYg=="}, + {"fooba", "Zm9vYmE="}, + {"foobar", "Zm9vYmFy"}, +}; + +TEST(Base64, RFC4648Encoding) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoCString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } + + for (auto& testcase : sRFC4648TestCases) { + NS_ConvertUTF8toUTF16 in(testcase.mInput); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Encoding_TransformAndAppend_EmptyPrefix) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoString out; + nsresult rv = + mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Encoding_TransformAndAppend_NonEmptyPrefix) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoString out{u"foo"_ns}; + nsresult rv = + mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(StringBeginsWith(out, u"foo"_ns)); + ASSERT_TRUE(Substring(out, 3).EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Decoding) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString out(testcase.mOutput); + nsAutoCString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.EqualsASCII(testcase.mInput)); + } + + for (auto& testcase : sRFC4648TestCases) { + NS_ConvertUTF8toUTF16 out(testcase.mOutput); + nsAutoString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.EqualsASCII(testcase.mInput)); + } +} + +TEST(Base64, RFC4648DecodingRawPointers) +{ + for (auto& testcase : sRFC4648TestCases) { + size_t outputLength = strlen(testcase.mOutput); + size_t inputLength = strlen(testcase.mInput); + + // This will be allocated by Base64Decode. + char* buffer = nullptr; + + uint32_t binaryLength = 0; + nsresult rv = mozilla::Base64Decode(testcase.mOutput, outputLength, &buffer, + &binaryLength); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(binaryLength, inputLength); + ASSERT_STREQ(testcase.mInput, buffer); + free(buffer); + } +} + +static EncodeDecodeTestCase sNonASCIITestCases[] = { + {"\x80", "gA=="}, + {"\xff", "/w=="}, + {"\x80\x80", "gIA="}, + {"\x80\x81", "gIE="}, + {"\xff\xff", "//8="}, + {"\x80\x80\x80", "gICA"}, + {"\xff\xff\xff", "////"}, + {"\x80\x80\x80\x80", "gICAgA=="}, + {"\xff\xff\xff\xff", "/////w=="}, +}; + +TEST(Base64, NonASCIIEncoding) +{ + for (auto& testcase : sNonASCIITestCases) { + nsDependentCString in(testcase.mInput); + nsAutoCString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, NonASCIIEncodingWideString) +{ + for (auto& testcase : sNonASCIITestCases) { + nsAutoString in, out; + // XXX Handles Latin1 despite the name + AppendASCIItoUTF16(nsDependentCString(testcase.mInput), in); + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, NonASCIIDecoding) +{ + for (auto& testcase : sNonASCIITestCases) { + nsDependentCString out(testcase.mOutput); + nsAutoCString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.Equals(testcase.mInput)); + } +} + +TEST(Base64, NonASCIIDecodingWideString) +{ + for (auto& testcase : sNonASCIITestCases) { + nsAutoString in, out; + // XXX Handles Latin1 despite the name + AppendASCIItoUTF16(nsDependentCString(testcase.mOutput), out); + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + // Can't use EqualsASCII, because our comparison string isn't ASCII. + for (size_t i = 0; i < in.Length(); ++i) { + ASSERT_TRUE(((unsigned int)in[i] & 0xff00) == 0); + ASSERT_EQ((unsigned char)in[i], (unsigned char)testcase.mInput[i]); + } + ASSERT_TRUE(strlen(testcase.mInput) == in.Length()); + } +} + +// For historical reasons, our wide string base64 encode routines mask off +// the high bits of non-latin1 wide strings. +TEST(Base64, EncodeNon8BitWideString) +{ + { + const nsAutoString non8Bit(u"\x1ff"); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsLiteral("/w==")); + } + { + const nsAutoString non8Bit(u"\xfff"); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsLiteral("/w==")); + } +} + +// For historical reasons, our wide string base64 decode routines mask off +// the high bits of non-latin1 wide strings. +TEST(Base64, DecodeNon8BitWideString) +{ + { + // This would be "/w==" in a nsCString + const nsAutoString non8Bit(u"\x12f\x177=="); + const nsAutoString expectedOutput(u"\xff"); + ASSERT_EQ(non8Bit.Length(), 4u); + nsAutoString out; + nsresult rv = mozilla::Base64Decode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.Equals(expectedOutput)); + } + { + const nsAutoString non8Bit(u"\xf2f\xf77=="); + const nsAutoString expectedOutput(u"\xff"); + nsAutoString out; + nsresult rv = mozilla::Base64Decode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.Equals(expectedOutput)); + } +} + +TEST(Base64, DecodeWideTo8Bit) +{ + for (auto& testCase : sRFC4648TestCases) { + const nsAutoCString in8bit(testCase.mOutput); + const NS_ConvertUTF8toUTF16 inWide(testCase.mOutput); + nsAutoCString out2; + nsAutoCString out1; + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(inWide, out1))); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(in8bit, out2))); + ASSERT_EQ(out1, out2); + } +} + +TEST(Base64, TruncateOnInvalidDecodeCString) +{ + constexpr auto invalid = "@@=="_ns; + nsAutoCString out("I should be truncated!"); + nsresult rv = mozilla::Base64Decode(invalid, out); + ASSERT_NS_FAILED(rv); + ASSERT_EQ(out.Length(), 0u); +} + +TEST(Base64, TruncateOnInvalidDecodeWideString) +{ + constexpr auto invalid = u"@@=="_ns; + nsAutoString out(u"I should be truncated!"); + nsresult rv = mozilla::Base64Decode(invalid, out); + ASSERT_NS_FAILED(rv); + ASSERT_EQ(out.Length(), 0u); +} + +// TODO: Add tests for OOM handling. diff --git a/xpcom/tests/gtest/TestCOMArray.cpp b/xpcom/tests/gtest/TestCOMArray.cpp new file mode 100644 index 0000000000..9e29e15230 --- /dev/null +++ b/xpcom/tests/gtest/TestCOMArray.cpp @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=4:et:sw=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 "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "gtest/gtest.h" + +// {9e70a320-be02-11d1-8031-006008159b5a} +#define NS_IFOO_IID \ + { \ + 0x9e70a320, 0xbe02, 0x11d1, { \ + 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \ + } \ + } + +class IFoo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + NS_IMETHOD_(MozExternalRefCountType) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID) + +class Foo final : public IFoo { + ~Foo(); + + public: + explicit Foo(int32_t aID); + + // nsISupports implementation + NS_DECL_ISUPPORTS + + // IFoo implementation + NS_IMETHOD_(MozExternalRefCountType) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return mID; } + + static int32_t gCount; + + int32_t mID; +}; + +int32_t Foo::gCount = 0; + +Foo::Foo(int32_t aID) { + mID = aID; + ++gCount; +} + +Foo::~Foo() { --gCount; } + +NS_IMPL_ISUPPORTS(Foo, IFoo) + +// {0e70a320-be02-11d1-8031-006008159b5a} +#define NS_IBAR_IID \ + { \ + 0x0e70a320, 0xbe02, 0x11d1, { \ + 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \ + } \ + } + +class IBar : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBAR_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IBar, NS_IBAR_IID) + +class Bar final : public IBar { + public: + explicit Bar(nsCOMArray<IBar>& aArray); + + // nsISupports implementation + NS_DECL_ISUPPORTS + + static int32_t sReleaseCalled; + + private: + ~Bar(); + + nsCOMArray<IBar>& mArray; +}; + +int32_t Bar::sReleaseCalled = 0; + +typedef nsCOMArray<IBar> Array2; + +Bar::Bar(Array2& aArray) : mArray(aArray) {} + +Bar::~Bar() { EXPECT_FALSE(mArray.RemoveObject(this)); } + +NS_IMPL_ADDREF(Bar) +NS_IMPL_QUERY_INTERFACE(Bar, IBar) + +NS_IMETHODIMP_(MozExternalRefCountType) +Bar::Release(void) { + ++Bar::sReleaseCalled; + EXPECT_GT(int(mRefCnt), 0); + NS_ASSERT_OWNINGTHREAD(_class); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "Bar"); + if (mRefCnt == 0) { + mRefCnt = 1; /* stabilize */ + delete this; + return 0; + } + return mRefCnt; +} + +TEST(COMArray, Sizing) +{ + nsCOMArray<IFoo> arr; + + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr<IFoo> foo = new Foo(i); + arr.AppendObject(foo); + } + + ASSERT_EQ(arr.Count(), int32_t(20)); + ASSERT_EQ(Foo::gCount, int32_t(20)); + + arr.TruncateLength(10); + + ASSERT_EQ(arr.Count(), int32_t(10)); + ASSERT_EQ(Foo::gCount, int32_t(10)); + + arr.SetCount(30); + + ASSERT_EQ(arr.Count(), int32_t(30)); + ASSERT_EQ(Foo::gCount, int32_t(10)); + + for (int32_t i = 0; i < 10; ++i) { + ASSERT_NE(arr[i], nullptr); + } + + for (int32_t i = 10; i < 30; ++i) { + ASSERT_EQ(arr[i], nullptr); + } +} + +TEST(COMArray, ObjectFunctions) +{ + int32_t base; + { + nsCOMArray<IBar> arr2; + + IBar *thirdObject = nullptr, *fourthObject = nullptr, + *fifthObject = nullptr, *ninthObject = nullptr; + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr<IBar> bar = new Bar(arr2); + switch (i) { + case 2: + thirdObject = bar; + break; + case 3: + fourthObject = bar; + break; + case 4: + fifthObject = bar; + break; + case 8: + ninthObject = bar; + break; + } + arr2.AppendObject(bar); + } + + base = Bar::sReleaseCalled; + + arr2.SetCount(10); + ASSERT_EQ(Bar::sReleaseCalled, base + 10); + ASSERT_EQ(arr2.Count(), int32_t(10)); + + arr2.RemoveObjectAt(9); + ASSERT_EQ(Bar::sReleaseCalled, base + 11); + ASSERT_EQ(arr2.Count(), int32_t(9)); + + arr2.RemoveObject(ninthObject); + ASSERT_EQ(Bar::sReleaseCalled, base + 12); + ASSERT_EQ(arr2.Count(), int32_t(8)); + + arr2.RemoveObjectsAt(2, 3); + ASSERT_EQ(Bar::sReleaseCalled, base + 15); + ASSERT_EQ(arr2.Count(), int32_t(5)); + for (int32_t j = 0; j < arr2.Count(); ++j) { + ASSERT_NE(arr2.ObjectAt(j), thirdObject); + ASSERT_NE(arr2.ObjectAt(j), fourthObject); + ASSERT_NE(arr2.ObjectAt(j), fifthObject); + } + + arr2.RemoveObjectsAt(4, 1); + ASSERT_EQ(Bar::sReleaseCalled, base + 16); + ASSERT_EQ(arr2.Count(), int32_t(4)); + + arr2.Clear(); + ASSERT_EQ(Bar::sReleaseCalled, base + 20); + } +} + +TEST(COMArray, ElementFunctions) +{ + int32_t base; + { + nsCOMArray<IBar> arr2; + + IBar *thirdElement = nullptr, *fourthElement = nullptr, + *fifthElement = nullptr, *ninthElement = nullptr; + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr<IBar> bar = new Bar(arr2); + switch (i) { + case 2: + thirdElement = bar; + break; + case 3: + fourthElement = bar; + break; + case 4: + fifthElement = bar; + break; + case 8: + ninthElement = bar; + break; + } + arr2.AppendElement(bar); + } + + base = Bar::sReleaseCalled; + + arr2.TruncateLength(10); + ASSERT_EQ(Bar::sReleaseCalled, base + 10); + ASSERT_EQ(arr2.Length(), uint32_t(10)); + + arr2.RemoveElementAt(9); + ASSERT_EQ(Bar::sReleaseCalled, base + 11); + ASSERT_EQ(arr2.Length(), uint32_t(9)); + + arr2.RemoveElement(ninthElement); + ASSERT_EQ(Bar::sReleaseCalled, base + 12); + ASSERT_EQ(arr2.Length(), uint32_t(8)); + + arr2.RemoveElementsAt(2, 3); + ASSERT_EQ(Bar::sReleaseCalled, base + 15); + ASSERT_EQ(arr2.Length(), uint32_t(5)); + for (uint32_t j = 0; j < arr2.Length(); ++j) { + ASSERT_NE(arr2.ElementAt(j), thirdElement); + ASSERT_NE(arr2.ElementAt(j), fourthElement); + ASSERT_NE(arr2.ElementAt(j), fifthElement); + } + + arr2.RemoveElementsAt(4, 1); + ASSERT_EQ(Bar::sReleaseCalled, base + 16); + ASSERT_EQ(arr2.Length(), uint32_t(4)); + + arr2.Clear(); + ASSERT_EQ(Bar::sReleaseCalled, base + 20); + } +} + +TEST(COMArray, Destructor) +{ + int32_t base; + Bar::sReleaseCalled = 0; + + { + nsCOMArray<IBar> arr2; + + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr<IBar> bar = new Bar(arr2); + arr2.AppendObject(bar); + } + + base = Bar::sReleaseCalled; + + // Let arr2 be destroyed + } + ASSERT_EQ(Bar::sReleaseCalled, base + 20); +} + +TEST(COMArray, ConvertIteratorToConstIterator) +{ + nsCOMArray<IFoo> array; + + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr<IFoo> foo = new Foo(i); + array.AppendObject(foo); + } + + nsCOMArray<IFoo>::const_iterator it = array.begin(); + ASSERT_EQ(array.cbegin(), it); +} diff --git a/xpcom/tests/gtest/TestCOMPtr.cpp b/xpcom/tests/gtest/TestCOMPtr.cpp new file mode 100644 index 0000000000..01fde5632a --- /dev/null +++ b/xpcom/tests/gtest/TestCOMPtr.cpp @@ -0,0 +1,436 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "gtest/gtest.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Unused.h" + +#define NS_IFOO_IID \ + { \ + 0x6f7652e0, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +namespace TestCOMPtr { + +class IFoo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + public: + IFoo(); + // virtual dtor because IBar uses our Release() + virtual ~IFoo(); + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override; + NS_IMETHOD_(MozExternalRefCountType) Release() override; + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + unsigned int refcount_; + + static int total_constructions_; + static int total_destructions_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID) + +int IFoo::total_constructions_; +int IFoo::total_destructions_; +int IFoo::total_queries_; + +IFoo::IFoo() : refcount_(0) { ++total_constructions_; } + +IFoo::~IFoo() { ++total_destructions_; } + +MozExternalRefCountType IFoo::AddRef() { + ++refcount_; + return refcount_; +} + +MozExternalRefCountType IFoo::Release() { + int newcount = --refcount_; + + if (newcount == 0) { + delete this; + } + + return newcount; +} + +nsresult IFoo::QueryInterface(const nsIID& aIID, void** aResult) { + total_queries_++; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(IFoo))) + rawPtr = this; + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast<nsISupports*>(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +static nsresult CreateIFoo(void** result) +// a typical factory function (that calls AddRef) +{ + auto* foop = new IFoo; + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +static void set_a_IFoo(nsCOMPtr<IFoo>* result) { + // Various places in this file do a static_cast to nsISupports* in order to + // make the QI non-trivial, to avoid hitting a static assert. + nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IFoo))); + *result = foop; +} + +static nsCOMPtr<IFoo> return_a_IFoo() { + nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IFoo))); + return foop; +} + +#define NS_IBAR_IID \ + { \ + 0x6f7652e1, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class IBar : public IFoo { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBAR_IID) + + public: + IBar(); + ~IBar() override; + + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + static int total_destructions_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IBar, NS_IBAR_IID) + +int IBar::total_destructions_; +int IBar::total_queries_; + +IBar::IBar() = default; + +IBar::~IBar() { total_destructions_++; } + +nsresult IBar::QueryInterface(const nsID& aIID, void** aResult) { + total_queries_++; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(IBar))) + rawPtr = this; + else if (aIID.Equals(NS_GET_IID(IFoo))) + rawPtr = static_cast<IFoo*>(this); + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast<nsISupports*>(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +static nsresult CreateIBar(void** result) +// a typical factory function (that calls AddRef) +{ + auto* barp = new IBar; + + barp->AddRef(); + *result = barp; + + return NS_OK; +} + +static void AnIFooPtrPtrContext(IFoo**) {} + +static void AVoidPtrPtrContext(void**) {} + +static void AnISupportsPtrPtrContext(nsISupports**) {} + +} // namespace TestCOMPtr + +using namespace TestCOMPtr; + +TEST(COMPtr, Bloat_Raw_Unsafe) +{ + // ER: I'm not sure what this is testing... + IBar* barP = 0; + nsresult rv = CreateIBar(reinterpret_cast<void**>(&barP)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(barP); + + IFoo* fooP = 0; + rv = barP->QueryInterface(NS_GET_IID(IFoo), reinterpret_cast<void**>(&fooP)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(fooP); + + NS_RELEASE(fooP); + NS_RELEASE(barP); +} + +TEST(COMPtr, Bloat_Smart) +{ + // ER: I'm not sure what this is testing... + nsCOMPtr<IBar> barP; + nsresult rv = CreateIBar(getter_AddRefs(barP)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(barP); + + nsCOMPtr<IFoo> fooP(do_QueryInterface(static_cast<nsISupports*>(barP), &rv)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(fooP); +} + +TEST(COMPtr, AddRefAndRelease) +{ + IFoo::total_constructions_ = 0; + IFoo::total_destructions_ = 0; + IBar::total_destructions_ = 0; + + { + nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IFoo))); + ASSERT_EQ(foop->refcount_, (unsigned int)1); + ASSERT_EQ(IFoo::total_constructions_, 1); + ASSERT_EQ(IFoo::total_destructions_, 0); + + foop = do_QueryInterface(static_cast<nsISupports*>(new IFoo)); + ASSERT_EQ(foop->refcount_, (unsigned int)1); + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 1); + + // [Shouldn't compile] Is it a compile time error to try to |AddRef| by + // hand? + // foop->AddRef(); + + // [Shouldn't compile] Is it a compile time error to try to |Release| be + // hand? + // foop->Release(); + + // [Shouldn't compile] Is it a compile time error to try to |delete| an + // |nsCOMPtr|? + // delete foop; + + static_cast<IFoo*>(foop)->AddRef(); + ASSERT_EQ(foop->refcount_, (unsigned int)2); + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 1); + + static_cast<IFoo*>(foop)->Release(); + ASSERT_EQ(foop->refcount_, (unsigned int)1); + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 1); + } + + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 2); + + { + nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IBar))); + mozilla::Unused << foop; + } + + ASSERT_EQ(IBar::total_destructions_, 1); +} + +TEST(COMPtr, Comparison) +{ + IFoo::total_constructions_ = 0; + IFoo::total_destructions_ = 0; + + { + nsCOMPtr<IFoo> foo1p( + do_QueryInterface(static_cast<nsISupports*>(new IFoo))); + nsCOMPtr<IFoo> foo2p( + do_QueryInterface(static_cast<nsISupports*>(new IFoo))); + + ASSERT_EQ(IFoo::total_constructions_, 2); + + // Test != operator + ASSERT_NE(foo1p, foo2p); + ASSERT_NE(foo1p, foo2p.get()); + + // Test == operator + foo1p = foo2p; + + ASSERT_EQ(IFoo::total_destructions_, 1); + + ASSERT_EQ(foo1p, foo2p); + ASSERT_EQ(foo2p, foo2p.get()); + ASSERT_EQ(foo2p.get(), foo2p); + + // Test () operator + ASSERT_TRUE(foo1p); + + ASSERT_EQ(foo1p->refcount_, (unsigned int)2); + ASSERT_EQ(foo2p->refcount_, (unsigned int)2); + } + + ASSERT_EQ(IFoo::total_destructions_, 2); +} + +TEST(COMPtr, DontAddRef) +{ + { + auto* raw_foo1p = new IFoo; + raw_foo1p->AddRef(); + + auto* raw_foo2p = new IFoo; + raw_foo2p->AddRef(); + + nsCOMPtr<IFoo> foo1p(dont_AddRef(raw_foo1p)); + ASSERT_EQ(raw_foo1p, foo1p); + ASSERT_EQ(foo1p->refcount_, (unsigned int)1); + + nsCOMPtr<IFoo> foo2p; + foo2p = dont_AddRef(raw_foo2p); + ASSERT_EQ(raw_foo2p, foo2p); + ASSERT_EQ(foo2p->refcount_, (unsigned int)1); + } +} + +TEST(COMPtr, AssignmentHelpers) +{ + IFoo::total_constructions_ = 0; + IFoo::total_destructions_ = 0; + + { + nsCOMPtr<IFoo> foop; + ASSERT_FALSE(foop); + CreateIFoo(nsGetterAddRefs<IFoo>(foop)); + ASSERT_TRUE(foop); + } + + ASSERT_EQ(IFoo::total_constructions_, 1); + ASSERT_EQ(IFoo::total_destructions_, 1); + + { + nsCOMPtr<IFoo> foop; + ASSERT_FALSE(foop); + CreateIFoo(getter_AddRefs(foop)); + ASSERT_TRUE(foop); + } + + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 2); + + { + nsCOMPtr<IFoo> foop; + ASSERT_FALSE(foop); + set_a_IFoo(address_of(foop)); + ASSERT_TRUE(foop); + + ASSERT_EQ(IFoo::total_constructions_, 3); + ASSERT_EQ(IFoo::total_destructions_, 2); + + foop = return_a_IFoo(); + ASSERT_TRUE(foop); + + ASSERT_EQ(IFoo::total_constructions_, 4); + ASSERT_EQ(IFoo::total_destructions_, 3); + } + + ASSERT_EQ(IFoo::total_constructions_, 4); + ASSERT_EQ(IFoo::total_destructions_, 4); + + { + nsCOMPtr<IFoo> fooP(do_QueryInterface(static_cast<nsISupports*>(new IFoo))); + ASSERT_TRUE(fooP); + + ASSERT_EQ(IFoo::total_constructions_, 5); + ASSERT_EQ(IFoo::total_destructions_, 4); + + nsCOMPtr<IFoo> fooP2(std::move(fooP)); + ASSERT_TRUE(fooP2); + + ASSERT_EQ(IFoo::total_constructions_, 5); + ASSERT_EQ(IFoo::total_destructions_, 4); + } + + ASSERT_EQ(IFoo::total_constructions_, 5); + ASSERT_EQ(IFoo::total_destructions_, 5); +} + +TEST(COMPtr, QueryInterface) +{ + IFoo::total_queries_ = 0; + IBar::total_queries_ = 0; + + { + nsCOMPtr<IFoo> fooP; + ASSERT_FALSE(fooP); + fooP = do_QueryInterface(static_cast<nsISupports*>(new IFoo)); + ASSERT_TRUE(fooP); + ASSERT_EQ(IFoo::total_queries_, 1); + + nsCOMPtr<IFoo> foo2P; + + // Test that |QueryInterface| _not_ called when assigning a smart-pointer + // of the same type.); + foo2P = fooP; + ASSERT_EQ(IFoo::total_queries_, 1); + } + + { + nsCOMPtr<IBar> barP(do_QueryInterface(static_cast<nsISupports*>(new IBar))); + ASSERT_EQ(IBar::total_queries_, 1); + + // Test that |QueryInterface| is called when assigning a smart-pointer of + // a different type. + nsCOMPtr<IFoo> fooP(do_QueryInterface(static_cast<nsISupports*>(barP))); + ASSERT_EQ(IBar::total_queries_, 2); + ASSERT_EQ(IFoo::total_queries_, 1); + ASSERT_TRUE(fooP); + } +} + +TEST(COMPtr, GetterConversions) +{ + // This is just a compilation test. We add a few asserts to keep gtest happy. + { + nsCOMPtr<IFoo> fooP; + ASSERT_FALSE(fooP); + + AnIFooPtrPtrContext(getter_AddRefs(fooP)); + AVoidPtrPtrContext(getter_AddRefs(fooP)); + } + + { + nsCOMPtr<nsISupports> supportsP; + ASSERT_FALSE(supportsP); + + AVoidPtrPtrContext(getter_AddRefs(supportsP)); + AnISupportsPtrPtrContext(getter_AddRefs(supportsP)); + } +} diff --git a/xpcom/tests/gtest/TestCOMPtrEq.cpp b/xpcom/tests/gtest/TestCOMPtrEq.cpp new file mode 100644 index 0000000000..2056ce1368 --- /dev/null +++ b/xpcom/tests/gtest/TestCOMPtrEq.cpp @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This attempts to test all the possible variations of |operator==| + * used with |nsCOMPtr|s. + */ + +#include "nsCOMPtr.h" +#include "gtest/gtest.h" + +#define NS_ICOMPTREQTESTFOO_IID \ + { \ + 0x8eb5bbef, 0xd1a3, 0x4659, { \ + 0x9c, 0xf6, 0xfd, 0xf3, 0xe4, 0xd2, 0x00, 0x0e \ + } \ + } + +class nsICOMPtrEqTestFoo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICOMPTREQTESTFOO_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICOMPtrEqTestFoo, NS_ICOMPTREQTESTFOO_IID) + +TEST(COMPtrEq, NullEquality) +{ + nsCOMPtr<nsICOMPtrEqTestFoo> s; + nsICOMPtrEqTestFoo* r = nullptr; + const nsCOMPtr<nsICOMPtrEqTestFoo> sc; + const nsICOMPtrEqTestFoo* rc = nullptr; + nsICOMPtrEqTestFoo* const rk = nullptr; + const nsICOMPtrEqTestFoo* const rkc = nullptr; + nsICOMPtrEqTestFoo* d = s; + + ASSERT_EQ(s, s); + ASSERT_EQ(s, r); + ASSERT_EQ(s, sc); + ASSERT_EQ(s, rc); + ASSERT_EQ(s, rk); + ASSERT_EQ(s, rkc); + ASSERT_EQ(s, d); + ASSERT_EQ(r, s); + ASSERT_EQ(r, sc); + ASSERT_EQ(r, rc); + ASSERT_EQ(r, rk); + ASSERT_EQ(r, rkc); + ASSERT_EQ(r, d); + ASSERT_EQ(sc, s); + ASSERT_EQ(sc, r); + ASSERT_EQ(sc, sc); + ASSERT_EQ(sc, rc); + ASSERT_EQ(sc, rk); + ASSERT_EQ(sc, rkc); + ASSERT_EQ(sc, d); + ASSERT_EQ(rc, s); + ASSERT_EQ(rc, r); + ASSERT_EQ(rc, sc); + ASSERT_EQ(rc, rk); + ASSERT_EQ(rc, rkc); + ASSERT_EQ(rc, d); + ASSERT_EQ(rk, s); + ASSERT_EQ(rk, r); + ASSERT_EQ(rk, sc); + ASSERT_EQ(rk, rc); + ASSERT_EQ(rk, rkc); + ASSERT_EQ(rk, d); + ASSERT_EQ(rkc, s); + ASSERT_EQ(rkc, r); + ASSERT_EQ(rkc, sc); + ASSERT_EQ(rkc, rc); + ASSERT_EQ(rkc, rk); + ASSERT_EQ(rkc, d); + ASSERT_EQ(d, s); + ASSERT_EQ(d, r); + ASSERT_EQ(d, sc); + ASSERT_EQ(d, rc); + ASSERT_EQ(d, rk); + ASSERT_EQ(d, rkc); +} diff --git a/xpcom/tests/gtest/TestCRT.cpp b/xpcom/tests/gtest/TestCRT.cpp new file mode 100644 index 0000000000..22e161fcdd --- /dev/null +++ b/xpcom/tests/gtest/TestCRT.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 "nsCRT.h" +#include "nsString.h" +#include "plstr.h" +#include <stdlib.h> +#include "gtest/gtest.h" + +namespace TestCRT { + +// The return from strcmp etc is only defined to be postive, zero or +// negative. The magnitude of a non-zero return is irrelevant. +static int sign(int val) { + if (val == 0) { + return 0; + } else { + if (val > 0) { + return 1; + } else { + return -1; + } + } +} + +// Verify that nsCRT versions of string comparison routines get the +// same answers as the native non-unicode versions. We only pass in +// iso-latin-1 strings, so the comparison must be valid. +static void Check(const char* s1, const char* s2, size_t n) { + bool longerThanN = strlen(s1) > n || strlen(s2) > n; + + int clib = PL_strcmp(s1, s2); + int clib_n = PL_strncmp(s1, s2, n); + + if (!longerThanN) { + EXPECT_EQ(sign(clib), sign(clib_n)); + } + + nsAutoString t1, t2; + CopyASCIItoUTF16(mozilla::MakeStringSpan(s1), t1); + CopyASCIItoUTF16(mozilla::MakeStringSpan(s2), t2); + const char16_t* us1 = t1.get(); + const char16_t* us2 = t2.get(); + + int u2, u2_n; + u2 = nsCRT::strcmp(us1, us2); + + EXPECT_EQ(sign(clib), sign(u2)); + + u2 = NS_strcmp(us1, us2); + u2_n = NS_strncmp(us1, us2, n); + + EXPECT_EQ(sign(clib), sign(u2)); + EXPECT_EQ(sign(clib_n), sign(u2_n)); +} + +struct Test { + const char* s1; + const char* s2; + size_t n; +}; + +static Test tests[] = { + {"foo", "foo", 3}, {"foo", "fo", 3}, + + {"foo", "bar", 3}, {"foo", "ba", 3}, + + {"foo", "zap", 3}, {"foo", "za", 3}, + + {"bar", "foo", 3}, {"bar", "fo", 3}, + + {"bar", "foo", 3}, {"bar", "fo", 3}, + + {"foo", "foobar", 3}, {"foobar", "foo", 3}, + {"foobar", "foozap", 3}, {"foozap", "foobar", 3}, +}; +#define NUM_TESTS int((sizeof(tests) / sizeof(tests[0]))) + +TEST(CRT, main) +{ + TestCRT::Test* tp = tests; + for (int i = 0; i < NUM_TESTS; i++, tp++) { + Check(tp->s1, tp->s2, tp->n); + } +} + +} // namespace TestCRT diff --git a/xpcom/tests/gtest/TestCallTemplates.cpp b/xpcom/tests/gtest/TestCallTemplates.cpp new file mode 100644 index 0000000000..90a03a8a8f --- /dev/null +++ b/xpcom/tests/gtest/TestCallTemplates.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim:cindent:ts=8:et:sw=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/. */ + +/* + * This test is NOT intended to be run. It's a test to make sure + * a group of functions BUILD correctly. + */ + +#include "nsISupportsUtils.h" +#include "nsIWeakReference.h" +#include "nsWeakReference.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Attributes.h" + +#define NS_ITESTSERVICE_IID \ + { \ + 0x127b5253, 0x37b1, 0x43c7, { \ + 0x96, 0x2b, 0xab, 0xf1, 0x2d, 0x22, 0x56, 0xae \ + } \ + } + +class NS_NO_VTABLE nsITestService : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITESTSERVICE_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITestService, NS_ITESTSERVICE_IID) + +#define NS_ITESTSERVICE2_IID \ + { \ + 0x137b5253, 0x37b1, 0x43c7, { \ + 0x96, 0x2b, 0xab, 0xf1, 0x2d, 0x22, 0x56, 0xaf \ + } \ + } + +class NS_NO_VTABLE nsITestService2 : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITESTSERVICE2_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITestService2, NS_ITESTSERVICE2_IID) + +class nsTestService final : public nsITestService, + public nsSupportsWeakReference { + ~nsTestService() = default; + + public: + NS_DECL_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(nsTestService, nsITestService, nsISupportsWeakReference) + +#define NS_TEST_SERVICE_CONTRACTID "@mozilla.org/test/testservice;1" +#define NS_TEST_SERVICE_CID \ + { \ + 0xa00c1406, 0x283a, 0x45c9, { \ + 0xae, 0xd2, 0x1a, 0xb6, 0xdd, 0xba, 0xfe, 0x53 \ + } \ + } +static NS_DEFINE_CID(kTestServiceCID, NS_TEST_SERVICE_CID); + +inline void JustTestingCompilation() { + /* + * NOTE: This does NOT demonstrate how these functions are + * intended to be used. They are intended for filling in out + * parameters that need to be |AddRef|ed. I'm just too lazy + * to write lots of little getter functions for a test program + * when I don't need to. + */ + + MOZ_ASSERT_UNREACHABLE("This test is not intended to run, only to compile!"); + + /* Test CallQueryInterface */ + + nsISupports* mySupportsPtr = reinterpret_cast<nsISupports*>(0x1000); + + nsITestService* myITestService = nullptr; + CallQueryInterface(mySupportsPtr, &myITestService); + + nsTestService* myTestService = + reinterpret_cast<nsTestService*>(mySupportsPtr); + nsITestService2* myTestService2; + CallQueryInterface(myTestService, &myTestService2); + + nsCOMPtr<nsISupports> mySupportsCOMPtr = mySupportsPtr; + CallQueryInterface(mySupportsCOMPtr, &myITestService); + + RefPtr<nsTestService> myTestServiceRefPtr = myTestService; + CallQueryInterface(myTestServiceRefPtr, &myTestService2); + + /* Test CallQueryReferent */ + + nsIWeakReference* myWeakRef = static_cast<nsIWeakReference*>(mySupportsPtr); + CallQueryReferent(myWeakRef, &myITestService); + + /* Test CallCreateInstance */ + CallCreateInstance(kTestServiceCID, &myITestService); + CallCreateInstance(NS_TEST_SERVICE_CONTRACTID, &myITestService); + + /* Test CallGetService */ + CallGetService(kTestServiceCID, &myITestService); + CallGetService(NS_TEST_SERVICE_CONTRACTID, &myITestService); + + /* Test CallGetInterface */ + nsIInterfaceRequestor* myInterfaceRequestor = + static_cast<nsIInterfaceRequestor*>(mySupportsPtr); + CallGetInterface(myInterfaceRequestor, &myITestService); +} diff --git a/xpcom/tests/gtest/TestCloneInputStream.cpp b/xpcom/tests/gtest/TestCloneInputStream.cpp new file mode 100644 index 0000000000..9a3b400854 --- /dev/null +++ b/xpcom/tests/gtest/TestCloneInputStream.cpp @@ -0,0 +1,236 @@ +/* -*- 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 "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Unused.h" +#include "nsICloneableInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsComponentManagerUtils.h" + +TEST(CloneInputStream, InvalidInput) +{ + nsCOMPtr<nsIInputStream> clone; + nsresult rv = NS_CloneInputStream(nullptr, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + ASSERT_FALSE(clone); +} + +TEST(CloneInputStream, CloneableInput) +{ + nsTArray<char> inputData; + testing::CreateData(4 * 1024, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + testing::ConsumeAndValidateStream(stream, inputString); + testing::ConsumeAndValidateStream(clone, inputString); +} + +class NonCloneableInputStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonCloneableInputStream( + already_AddRefed<nsIInputStream> aInputStream) + : mStream(aInputStream) {} + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + private: + ~NonCloneableInputStream() = default; + + nsCOMPtr<nsIInputStream> mStream; +}; + +NS_IMPL_ISUPPORTS(NonCloneableInputStream, nsIInputStream) + +TEST(CloneInputStream, NonCloneableInput_NoFallback) +{ + nsTArray<char> inputData; + testing::CreateData(4 * 1024, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr<nsIInputStream> base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> stream = new NonCloneableInputStream(base.forget()); + + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable == nullptr); + + nsCOMPtr<nsIInputStream> clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + ASSERT_TRUE(clone == nullptr); + + testing::ConsumeAndValidateStream(stream, inputString); +} + +TEST(CloneInputStream, NonCloneableInput_Fallback) +{ + nsTArray<char> inputData; + testing::CreateData(4 * 1024, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr<nsIInputStream> base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> stream = new NonCloneableInputStream(base.forget()); + + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable == nullptr); + + nsCOMPtr<nsIInputStream> clone; + nsCOMPtr<nsIInputStream> replacement; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone), + getter_AddRefs(replacement)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(clone != nullptr); + ASSERT_TRUE(replacement != nullptr); + ASSERT_TRUE(stream.get() != replacement.get()); + ASSERT_TRUE(clone.get() != replacement.get()); + + stream = std::move(replacement); + + // The stream is being copied asynchronously on the STS event target. Spin + // a yield loop here until the data is available. Yes, this is a bit hacky, + // but AFAICT, gtest does not support async test completion. + uint64_t available; + do { + mozilla::Unused << PR_Sleep(PR_INTERVAL_NO_WAIT); + rv = stream->Available(&available); + ASSERT_NS_SUCCEEDED(rv); + } while (available < inputString.Length()); + + testing::ConsumeAndValidateStream(stream, inputString); + testing::ConsumeAndValidateStream(clone, inputString); +} + +TEST(CloneInputStream, CloneMultiplexStream) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + nsTArray<char> inputData; + testing::CreateData(1024, inputData); + for (uint32_t i = 0; i < 2; ++i) { + nsCString inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr<nsIInputStream> base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(base); + ASSERT_NS_SUCCEEDED(rv); + } + + // Unread stream should clone successfully. + nsTArray<char> doubled; + doubled.AppendElements(inputData); + doubled.AppendElements(inputData); + + nsCOMPtr<nsIInputStream> clone; + nsresult rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + testing::ConsumeAndValidateStream(clone, doubled); + + // Stream that has been read should fail. + char buffer[512]; + uint32_t read; + rv = stream->Read(buffer, 512, &read); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> clone2; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone2)); + ASSERT_NS_FAILED(rv); +} + +TEST(CloneInputStream, CloneMultiplexStreamPartial) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + nsTArray<char> inputData; + testing::CreateData(1024, inputData); + for (uint32_t i = 0; i < 2; ++i) { + nsCString inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr<nsIInputStream> base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(base); + ASSERT_NS_SUCCEEDED(rv); + } + + // Fail when first stream read, but second hasn't been started. + char buffer[1024]; + uint32_t read; + nsresult rv = stream->Read(buffer, 1024, &read); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + + // Fail after beginning read of second stream. + rv = stream->Read(buffer, 512, &read); + ASSERT_TRUE(NS_SUCCEEDED(rv) && read == 512); + + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + + // Fail at the end. + nsAutoCString consumed; + rv = NS_ConsumeStream(stream, UINT32_MAX, consumed); + ASSERT_NS_SUCCEEDED(rv); + + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); +} diff --git a/xpcom/tests/gtest/TestDafsa.cpp b/xpcom/tests/gtest/TestDafsa.cpp new file mode 100644 index 0000000000..f52bf74256 --- /dev/null +++ b/xpcom/tests/gtest/TestDafsa.cpp @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Dafsa.h" +#include "gtest/gtest.h" + +#include "nsString.h" + +using mozilla::Dafsa; + +namespace dafsa_test_1 { +#include "dafsa_test_1.inc" // kDafsa +} + +TEST(Dafsa, Constructor) +{ Dafsa d(dafsa_test_1::kDafsa); } + +TEST(Dafsa, StringsFound) +{ + Dafsa d(dafsa_test_1::kDafsa); + + int tag = d.Lookup("foo.bar.baz"_ns); + EXPECT_EQ(tag, 1); + + tag = d.Lookup("a.test.string"_ns); + EXPECT_EQ(tag, 0); + + tag = d.Lookup("a.test.string2"_ns); + EXPECT_EQ(tag, 2); + + tag = d.Lookup("aaaa"_ns); + EXPECT_EQ(tag, 4); +} + +TEST(Dafsa, StringsNotFound) +{ + Dafsa d(dafsa_test_1::kDafsa); + + // Matches all but last letter. + int tag = d.Lookup("foo.bar.ba"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Matches prefix with extra letter. + tag = d.Lookup("a.test.strings"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Matches small portion. + tag = d.Lookup("a.test"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Matches repeating pattern with extra letters. + tag = d.Lookup("aaaaa"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Empty string. + tag = d.Lookup(""_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); +} + +TEST(Dafsa, HugeString) +{ + Dafsa d(dafsa_test_1::kDafsa); + + int tag = d.Lookup(nsLiteralCString( + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. ")); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); +} diff --git a/xpcom/tests/gtest/TestDeadlockDetector.cpp b/xpcom/tests/gtest/TestDeadlockDetector.cpp new file mode 100644 index 0000000000..c02ba13da2 --- /dev/null +++ b/xpcom/tests/gtest/TestDeadlockDetector.cpp @@ -0,0 +1,314 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "prthread.h" + +#include "nsCOMPtr.h" +#include "nsTArray.h" + +#include "mozilla/CondVar.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" + +#include "mozilla/gtest/MozHelpers.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +// The code in this file is also used by +// storage/test/gtest/test_deadlock_detector.cpp. The following two macros are +// used to provide the necessary differentiation between this file and that +// file. +#ifndef MUTEX +# define MUTEX mozilla::Mutex +#endif +#ifndef TESTNAME +# define TESTNAME(name) XPCOM##name +#endif + +static PRThread* spawn(void (*run)(void*), void* arg) { + return PR_CreateThread(PR_SYSTEM_THREAD, run, arg, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); +} + +/** + * Simple test fixture that makes sure the gdb sleep setup in the + * ah crap handler is bypassed during the death tests. + */ +class TESTNAME(DeadlockDetectorTest) : public ::testing::Test { + protected: + void SetUp() final { SAVE_GDB_SLEEP_GLOBAL(mOldSleepDuration); } + + void TearDown() final { RESTORE_GDB_SLEEP_GLOBAL(mOldSleepDuration); } + + private: +#if defined(HAS_GDB_SLEEP_DURATION) + unsigned int mOldSleepDuration; +#endif // defined(HAS_GDB_SLEEP_DURATION) +}; + +//----------------------------------------------------------------------------- +// Single-threaded sanity tests + +// Stupidest possible deadlock. +static int Sanity_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + MUTEX m1("dd.sanity.m1"); + m1.Lock(); + m1.Lock(); + return 0; // not reached +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(SanityDeathTest)) { + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.sanity.m1.*" + "=== Cycle completed at.*--- Mutex : dd.sanity.m1.*" + "###!!! Deadlock may happen NOW!.*" // better catch these easy cases... + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(Sanity_Child(), regex); +} + +// Slightly less stupid deadlock. +static int Sanity2_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + MUTEX m1("dd.sanity2.m1"); + MUTEX m2("dd.sanity2.m2"); + m1.Lock(); + m2.Lock(); + m1.Lock(); + return 0; // not reached +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity2DeathTest)) { + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.sanity2.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity2.m2.*" + "=== Cycle completed at.*--- Mutex : dd.sanity2.m1.*" + "###!!! Deadlock may happen NOW!.*" // better catch these easy cases... + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(Sanity2_Child(), regex); +} + +#if 0 +// Temporarily disabled, see bug 1370644. +int +Sanity3_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS +{ + mozilla::gtest::DisableCrashReporter(); + + MUTEX m1("dd.sanity3.m1"); + MUTEX m2("dd.sanity3.m2"); + MUTEX m3("dd.sanity3.m3"); + MUTEX m4("dd.sanity3.m4"); + + m1.Lock(); + m2.Lock(); + m3.Lock(); + m4.Lock(); + m4.Unlock(); + m3.Unlock(); + m2.Unlock(); + m1.Unlock(); + + m4.Lock(); + m1.Lock(); + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity3DeathTest)) +{ + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.sanity3.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity3.m2.*" + "--- Next dependency:.*--- Mutex : dd.sanity3.m3.*" + "--- Next dependency:.*--- Mutex : dd.sanity3.m4.*" + "=== Cycle completed at.*--- Mutex : dd.sanity3.m1.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(Sanity3_Child(), regex); +} +#endif + +static int Sanity4_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + mozilla::ReentrantMonitor m1 MOZ_UNANNOTATED("dd.sanity4.m1"); + MUTEX m2("dd.sanity4.m2"); + m1.Enter(); + m2.Lock(); + m1.Enter(); + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity4DeathTest)) { + const char* const regex = + "Re-entering ReentrantMonitor after acquiring other resources.*" + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- ReentrantMonitor : " + "dd.sanity4.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*" + "=== Cycle completed at.*--- ReentrantMonitor : dd.sanity4.m1.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + ASSERT_DEATH_IF_SUPPORTED(Sanity4_Child(), regex); +} + +static int Sanity5_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + mozilla::RecursiveMutex m1 MOZ_UNANNOTATED("dd.sanity4.m1"); + MUTEX m2("dd.sanity4.m2"); + m1.Lock(); + m2.Lock(); + m1.Lock(); + return 0; +} + +#if !defined(DISABLE_STORAGE_SANITY5_DEATH_TEST) +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity5DeathTest)) { + const char* const regex = + "Re-entering RecursiveMutex after acquiring other resources.*" + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- RecursiveMutex : dd.sanity4.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*" + "=== Cycle completed at.*--- RecursiveMutex : dd.sanity4.m1.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + ASSERT_DEATH_IF_SUPPORTED(Sanity5_Child(), regex); +} +#endif + +//----------------------------------------------------------------------------- +// Multithreaded tests + +/** + * Helper for passing state to threads in the multithread tests. + */ +struct ThreadState { + /** + * Locks to use during the test. This is just a reference and is owned by + * the main test thread. + */ + const nsTArray<MUTEX*>& locks; + + /** + * Integer argument used to identify each thread. + */ + int id; +}; + +#if 0 +// Temporarily disabled, see bug 1370644. +static void +TwoThreads_thread(void* arg) MOZ_NO_THREAD_SAFETY_ANALYSIS +{ + ThreadState* state = static_cast<ThreadState*>(arg); + + MUTEX* ttM1 = state->locks[0]; + MUTEX* ttM2 = state->locks[1]; + + if (state->id) { + ttM1->Lock(); + ttM2->Lock(); + ttM2->Unlock(); + ttM1->Unlock(); + } + else { + ttM2->Lock(); + ttM1->Lock(); + ttM1->Unlock(); + ttM2->Unlock(); + } +} + +int +TwoThreads_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS +{ + mozilla::gtest::DisableCrashReporter(); + + nsTArray<MUTEX*> locks = { + new MUTEX("dd.twothreads.m1"), + new MUTEX("dd.twothreads.m2") + }; + + ThreadState state_1 {locks, 0}; + PRThread* t1 = spawn(TwoThreads_thread, &state_1); + PR_JoinThread(t1); + + ThreadState state_2 {locks, 1}; + PRThread* t2 = spawn(TwoThreads_thread, &state_2); + PR_JoinThread(t2); + + for (auto& lock : locks) { + delete lock; + } + + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(TwoThreadsDeathTest)) +{ + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.twothreads.m2.*" + "--- Next dependency:.*--- Mutex : dd.twothreads.m1.*" + "=== Cycle completed at.*--- Mutex : dd.twothreads.m2.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(TwoThreads_Child(), regex); +} +#endif + +static void ContentionNoDeadlock_thread(void* arg) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + const uint32_t K = 100000; + + ThreadState* state = static_cast<ThreadState*>(arg); + int32_t starti = static_cast<int32_t>(state->id); + auto& cndMs = state->locks; + + for (uint32_t k = 0; k < K; ++k) { + for (int32_t i = starti; i < (int32_t)cndMs.Length(); ++i) cndMs[i]->Lock(); + // comment out the next two lines for deadlocking fun! + for (int32_t i = cndMs.Length() - 1; i >= starti; --i) cndMs[i]->Unlock(); + + starti = (starti + 1) % 3; + } +} + +static int ContentionNoDeadlock_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + const size_t kMutexCount = 4; + + PRThread* threads[3]; + nsTArray<MUTEX*> locks; + ThreadState states[] = {{locks, 0}, {locks, 1}, {locks, 2}}; + + for (uint32_t i = 0; i < kMutexCount; ++i) + locks.AppendElement(new MUTEX("dd.cnd.ms")); + + for (int32_t i = 0; i < (int32_t)ArrayLength(threads); ++i) + threads[i] = spawn(ContentionNoDeadlock_thread, states + i); + + for (uint32_t i = 0; i < ArrayLength(threads); ++i) PR_JoinThread(threads[i]); + + for (uint32_t i = 0; i < locks.Length(); ++i) delete locks[i]; + + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(ContentionNoDeadlock)) { + // Just check that this test runs to completion. + ASSERT_EQ(ContentionNoDeadlock_Child(), 0); +} diff --git a/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp new file mode 100644 index 0000000000..40519f43ed --- /dev/null +++ b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Avoid DMD-specific parts of MOZ_DEFINE_MALLOC_SIZE_OF +#undef MOZ_DMD + +#include "nsIMemoryReporter.h" +#include "mozilla/Mutex.h" + +#include "gtest/gtest.h" + +//----------------------------------------------------------------------------- + +static void AllocLockRecurseUnlockFree(int i) { + if (0 == i) return; + + mozilla::Mutex* lock = new mozilla::Mutex("deadlockDetector.scalability.t1"); + { + mozilla::MutexAutoLock _(*lock); + AllocLockRecurseUnlockFree(i - 1); + } + delete lock; +} + +// This test creates a resource dependency chain N elements long, then +// frees all the resources in the chain. +TEST(DeadlockDetectorScalability, LengthNDepChain) +{ + const int N = 1 << 14; // 16K + AllocLockRecurseUnlockFree(N); + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +// This test creates a single lock that is ordered < N resources, then +// repeatedly exercises this order k times. +// +// NB: It takes a minute or two to run so it is disabled by default. +TEST(DeadlockDetectorScalability, DISABLED_OneLockNDeps) +{ + // NB: Using a larger test size to stress our traversal logic. + const int N = 1 << 17; // 131k + const int K = 100; + + mozilla::Mutex* lock = + new mozilla::Mutex("deadlockDetector.scalability.t2.master"); + mozilla::Mutex** locks = new mozilla::Mutex*[N]; + if (!locks) MOZ_CRASH("couldn't allocate lock array"); + + for (int i = 0; i < N; ++i) + locks[i] = new mozilla::Mutex("deadlockDetector.scalability.t2.dep"); + + // establish orders + { + mozilla::MutexAutoLock m(*lock); + for (int i = 0; i < N; ++i) mozilla::MutexAutoLock s(*locks[i]); + } + + // exercise order check + { + mozilla::MutexAutoLock m(*lock); + for (int i = 0; i < K; ++i) + for (int j = 0; j < N; ++j) mozilla::MutexAutoLock s(*locks[i]); + } + + for (int i = 0; i < N; ++i) delete locks[i]; + delete[] locks; + + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +// This test creates N resources and adds the theoretical maximum number +// of dependencies, O(N^2). It then repeats that sequence of +// acquisitions k times. Finally, all resources are freed. +// +// It's very difficult to perform well on this test. It's put forth as a +// challenge problem. + +TEST(DeadlockDetectorScalability, MaxDepsNsq) +{ + const int N = 1 << 10; // 1k + const int K = 10; + + mozilla::Mutex** locks = new mozilla::Mutex*[N]; + if (!locks) MOZ_CRASH("couldn't allocate lock array"); + + for (int i = 0; i < N; ++i) + locks[i] = new mozilla::Mutex("deadlockDetector.scalability.t3"); + + for (int i = 0; i < N; ++i) { + mozilla::MutexAutoLock al1(*locks[i]); + for (int j = i + 1; j < N; ++j) mozilla::MutexAutoLock al2(*locks[j]); + } + + for (int i = 0; i < K; ++i) { + for (int j = 0; j < N; ++j) { + mozilla::MutexAutoLock al1(*locks[j]); + for (int k = j + 1; k < N; ++k) mozilla::MutexAutoLock al2(*locks[k]); + } + } + + for (int i = 0; i < N; ++i) delete locks[i]; + delete[] locks; + + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +// This test creates a single lock that is ordered < N resources. The +// resources are allocated, exercised K times, and deallocated one at +// a time. + +TEST(DeadlockDetectorScalability, OneLockNDepsUsedSeveralTimes) +{ + const size_t N = 1 << 17; // 131k + const size_t K = 3; + + // Create master lock. + mozilla::Mutex* lock_1 = + new mozilla::Mutex("deadlockDetector.scalability.t4.master"); + for (size_t n = 0; n < N; n++) { + // Create child lock. + mozilla::Mutex* lock_2 = + new mozilla::Mutex("deadlockDetector.scalability.t4.child"); + + // First lock the master. + mozilla::MutexAutoLock m(*lock_1); + + // Now lock and unlock the child a few times. + for (size_t k = 0; k < K; k++) { + mozilla::MutexAutoLock c(*lock_2); + } + + // Destroy the child lock. + delete lock_2; + } + + // Cleanup the master lock. + delete lock_1; + + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +MOZ_DEFINE_MALLOC_SIZE_OF(DeadlockDetectorMallocSizeOf) + +// This is a simple test that exercises the deadlock detector memory reporting +// functionality. +TEST(DeadlockDetectorScalability, SizeOf) +{ + size_t memory_used = mozilla::BlockingResourceBase::SizeOfDeadlockDetector( + DeadlockDetectorMallocSizeOf); + + ASSERT_GT(memory_used, size_t(0)); +} diff --git a/xpcom/tests/gtest/TestDelayedRunnable.cpp b/xpcom/tests/gtest/TestDelayedRunnable.cpp new file mode 100644 index 0000000000..b612522b55 --- /dev/null +++ b/xpcom/tests/gtest/TestDelayedRunnable.cpp @@ -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/. */ + +#include "mozilla/DelayedRunnable.h" +#include "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskQueue.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "MediaTimer.h" +#include "mozilla/media/MediaUtils.h" +#include "VideoUtils.h" + +using mozilla::Atomic; +using mozilla::MakeRefPtr; +using mozilla::Monitor; +using mozilla::MonitorAutoLock; +using mozilla::TaskQueue; + +namespace { +struct ReleaseDetector { + explicit ReleaseDetector(Atomic<bool>* aActive) : mActive(aActive) { + *mActive = true; + } + ReleaseDetector(ReleaseDetector&& aOther) noexcept : mActive(aOther.mActive) { + aOther.mActive = nullptr; + } + ReleaseDetector(const ReleaseDetector&) = delete; + ~ReleaseDetector() { + if (mActive) { + *mActive = false; + } + } + Atomic<bool>* mActive; +}; +} // namespace + +TEST(DelayedRunnable, TaskQueueShutdownLeak) +{ + Atomic<bool> active{false}; + auto taskQueue = TaskQueue::Create( + GetMediaThreadPool(mozilla::MediaThreadType::SUPERVISOR), + "TestDelayedRunnable TaskQueueShutdownLeak"); + taskQueue->DelayedDispatch( + NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}), + 60e3 /* 1 minute */); + EXPECT_TRUE(active); + taskQueue->BeginShutdown(); + taskQueue->AwaitIdle(); + // Leaks are often detected after process shutdown. This doesn't wait that + // long, but leaking past thread shutdown would be equally bad since the + // runnable can no longer be released on the target thread. This is also the + // reason why timers assert that they don't release the last reference to + // their callbacks when dispatch fails (like when the target has been + // shutdown). + EXPECT_FALSE(active); +} + +TEST(DelayedRunnable, nsThreadShutdownLeak) +{ + Atomic<bool> active{false}; + nsCOMPtr<nsIThread> thread; + ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK); + thread->DelayedDispatch( + NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}), + 60e3 /* 1 minute */); + EXPECT_TRUE(active); + ASSERT_EQ(thread->Shutdown(), NS_OK); + // Leaks are often detected after process shutdown. This doesn't wait that + // long, but leaking past thread shutdown would be equally bad since the + // runnable can no longer be released on the target thread. This is also the + // reason why timers assert that they don't release the last reference to + // their callbacks when dispatch fails (like when the target has been + // shutdown). + EXPECT_FALSE(active); +} + +/* + * This tests a case where we create a background TaskQueue that lives until + * xpcom shutdown. This test will fail (by assertion failure) if the TaskQueue + * shutdown task is dispatched too late in the shutdown sequence, or: + * If the background thread pool is then empty, the TaskQueue shutdown task will + * when dispatched require creating a new nsThread, which is forbidden too late + * in the shutdown sequence. + */ +TEST(DelayedRunnable, BackgroundTaskQueueShutdownTask) +{ + nsCOMPtr<nsISerialEventTarget> taskQueue; + nsresult rv = NS_CreateBackgroundTaskQueue("TestDelayedRunnable", + getter_AddRefs(taskQueue)); + ASSERT_NS_SUCCEEDED(rv); + + // Leak the queue, so it gets cleaned up by xpcom-shutdown. + nsISerialEventTarget* tq = taskQueue.forget().take(); + mozilla::Unused << tq; +} + +/* + * Like BackgroundTaskQueueShutdownTask but for nsThread, since both background + * TaskQueues and nsThreads are managed by nsThreadManager. For nsThread things + * are different and the shutdown task doesn't use Dispatch, but timings are + * similar. + */ +TEST(DelayedRunnable, nsThreadShutdownTask) +{ + nsCOMPtr<nsIThread> thread; + ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK); + + // Leak the thread, so it gets cleaned up by xpcom-shutdown. + nsIThread* t = thread.forget().take(); + mozilla::Unused << t; +} + +TEST(DelayedRunnable, TimerFiresBeforeRunnableRuns) +{ + RefPtr<mozilla::SharedThreadPool> pool = + mozilla::SharedThreadPool::Get("Test Pool"_ns); + auto tailTaskQueue1 = + TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue1", + /* aSupportsTailDispatch = */ true); + auto tailTaskQueue2 = + TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue2", + /* aSupportsTailDispatch = */ true); + auto noTailTaskQueue = + TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable noTailTaskQueue", + /* aSupportsTailDispatch = */ false); + enum class State : uint8_t { + Start, + TimerRan, + TasksFinished, + } state = State::Start; + Monitor monitor MOZ_UNANNOTATED(__func__); + MonitorAutoLock lock(monitor); + MOZ_ALWAYS_SUCCEEDS( + tailTaskQueue1->Dispatch(NS_NewRunnableFunction(__func__, [&] { + // This will tail dispatch the delayed runnable, making it prone to + // lose a race against the directly-initiated timer firing (and + // dispatching another non-tail-dispatched runnable). + EXPECT_TRUE(tailTaskQueue1->RequiresTailDispatch(tailTaskQueue2)); + tailTaskQueue2->DelayedDispatch( + NS_NewRunnableFunction(__func__, [&] {}), 1); + MonitorAutoLock lock(monitor); + auto timer = MakeRefPtr<mozilla::MediaTimer>(); + timer->WaitFor(mozilla::TimeDuration::FromMilliseconds(1), __func__) + ->Then(noTailTaskQueue, __func__, [&] { + MonitorAutoLock lock(monitor); + state = State::TimerRan; + monitor.NotifyAll(); + }); + // Wait until the timer has run. It should have dispatched the + // TimerEvent to tailTaskQueue2 by then. The tail dispatch happens when + // we leave scope. + while (state != State::TimerRan) { + monitor.Wait(); + } + // Notify main thread that we've finished the async steps. + state = State::TasksFinished; + monitor.Notify(); + }))); + // Wait for async steps before wrapping up the test case. + while (state != State::TasksFinished) { + monitor.Wait(); + } +} diff --git a/xpcom/tests/gtest/TestEncoding.cpp b/xpcom/tests/gtest/TestEncoding.cpp new file mode 100644 index 0000000000..2abbc5d708 --- /dev/null +++ b/xpcom/tests/gtest/TestEncoding.cpp @@ -0,0 +1,108 @@ +/* -*- 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 <stdlib.h> +#include "nsString.h" +#include "gtest/gtest.h" + +TEST(Encoding, GoodSurrogatePair) +{ + // When this string is decoded, the surrogate pair is U+10302 and the rest of + // the string is specified by indexes 2 onward. + const char16_t goodPairData[] = {0xD800, 0xDF02, 0x65, 0x78, 0x0}; + nsDependentString goodPair16(goodPairData); + + uint32_t byteCount = 0; + char* goodPair8 = ToNewUTF8String(goodPair16, &byteCount); + EXPECT_TRUE(!!goodPair8); + + EXPECT_EQ(byteCount, 6u); + + const unsigned char expected8[] = {0xF0, 0x90, 0x8C, 0x82, 0x65, 0x78, 0x0}; + EXPECT_EQ(0, memcmp(expected8, goodPair8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, goodPair16)); + + free(goodPair8); +} + +TEST(Encoding, BackwardsSurrogatePair) +{ + // When this string is decoded, the two surrogates are wrongly ordered and + // must each be interpreted as U+FFFD. + const char16_t backwardsPairData[] = {0xDDDD, 0xD863, 0x65, 0x78, 0x0}; + nsDependentString backwardsPair16(backwardsPairData); + + uint32_t byteCount = 0; + char* backwardsPair8 = ToNewUTF8String(backwardsPair16, &byteCount); + EXPECT_TRUE(!!backwardsPair8); + + EXPECT_EQ(byteCount, 8u); + + const unsigned char expected8[] = {0xEF, 0xBF, 0xBD, 0xEF, 0xBF, + 0xBD, 0x65, 0x78, 0x0}; + EXPECT_EQ(0, memcmp(expected8, backwardsPair8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, backwardsPair16)); + + free(backwardsPair8); +} + +TEST(Encoding, MalformedUTF16OrphanHighSurrogate) +{ + // When this string is decoded, the high surrogate should be replaced and the + // rest of the string is specified by indexes 1 onward. + const char16_t highSurrogateData[] = {0xD863, 0x74, 0x65, 0x78, 0x74, 0x0}; + nsDependentString highSurrogate16(highSurrogateData); + + uint32_t byteCount = 0; + char* highSurrogate8 = ToNewUTF8String(highSurrogate16, &byteCount); + EXPECT_TRUE(!!highSurrogate8); + + EXPECT_EQ(byteCount, 7u); + + const unsigned char expected8[] = {0xEF, 0xBF, 0xBD, 0x74, + 0x65, 0x78, 0x74, 0x0}; + EXPECT_EQ(0, memcmp(expected8, highSurrogate8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, highSurrogate16)); + + free(highSurrogate8); +} + +TEST(Encoding, MalformedUTF16OrphanLowSurrogate) +{ + // When this string is decoded, the low surrogate should be replaced and the + // rest of the string is specified by indexes 1 onward. + const char16_t lowSurrogateData[] = {0xDDDD, 0x74, 0x65, 0x78, 0x74, 0x0}; + nsDependentString lowSurrogate16(lowSurrogateData); + + uint32_t byteCount = 0; + char* lowSurrogate8 = ToNewUTF8String(lowSurrogate16, &byteCount); + EXPECT_TRUE(!!lowSurrogate8); + + EXPECT_EQ(byteCount, 7u); + + const unsigned char expected8[] = {0xEF, 0xBF, 0xBD, 0x74, + 0x65, 0x78, 0x74, 0x0}; + EXPECT_EQ(0, memcmp(expected8, lowSurrogate8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, lowSurrogate16)); + + free(lowSurrogate8); +} diff --git a/xpcom/tests/gtest/TestEscape.cpp b/xpcom/tests/gtest/TestEscape.cpp new file mode 100644 index 0000000000..6834d5fa13 --- /dev/null +++ b/xpcom/tests/gtest/TestEscape.cpp @@ -0,0 +1,238 @@ +/* -*- 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 "nsEscape.h" +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "nsNetUtil.h" + +using namespace mozilla; + +// Testing for failure here would be somewhat hard in automation. Locally you +// could use something like ulimit to create a failure. + +TEST(Escape, FallibleNoEscape) +{ + // Tests the fallible version of NS_EscapeURL works as expected when no + // escaping is necessary. + nsCString toEscape("data:,Hello%2C%20World!"); + nsCString escaped; + nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible); + EXPECT_EQ(rv, NS_OK); + // Nothing should have been escaped, they should be the same string. + EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading()); + // We expect them to point at the same buffer. + EXPECT_EQ(toEscape.BeginReading(), escaped.BeginReading()); +} + +TEST(Escape, FallibleEscape) +{ + // Tests the fallible version of NS_EscapeURL works as expected when + // escaping is necessary. + nsCString toEscape("data:,Hello%2C%20World!\xC4\x9F"); + nsCString escaped; + nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible); + EXPECT_EQ(rv, NS_OK); + EXPECT_STRNE(toEscape.BeginReading(), escaped.BeginReading()); + const char* const kExpected = "data:,Hello%2C%20World!%C4%9F"; + EXPECT_STREQ(escaped.BeginReading(), kExpected); +} + +TEST(Escape, BadEscapeSequences) +{ + { + char bad[] = "%s\0fa"; + + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 2); + EXPECT_STREQ(bad, "%s"); + } + { + char bad[] = "%a"; + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 2); + EXPECT_STREQ(bad, "%a"); + } + { + char bad[] = "%"; + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 1); + EXPECT_STREQ(bad, "%"); + } + { + char bad[] = "%s/%s"; + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 5); + EXPECT_STREQ(bad, "%s/%s"); + } +} + +TEST(Escape, nsAppendEscapedHTML) +{ + const char* srcs[] = { + "a", "bcdefgh", "<", ">", "&", "\"", + "'", "'bad'", "Foo<T>& foo", "'\"&><abc", "", + }; + + const char* dsts1[] = { + "a", + "bcdefgh", + "<", + ">", + "&", + """, + "'", + "'bad'", + "Foo<T>& foo", + "'"&><abc", + "", + }; + + const char* dsts2[] = { + "a", + "abcdefgh", + "abcdefgh<", + "abcdefgh<>", + "abcdefgh<>&", + "abcdefgh<>&"", + "abcdefgh<>&"'", + "abcdefgh<>&"''bad'", + "abcdefgh<>&"''bad'Foo<T>& foo", + "abcdefgh<>&"''bad'Foo<T>& " + "foo'"&><abc", + "abcdefgh<>&"''bad'Foo<T>& " + "foo'"&><abc", + }; + + ASSERT_EQ(ArrayLength(srcs), ArrayLength(dsts1)); + ASSERT_EQ(ArrayLength(srcs), ArrayLength(dsts2)); + + // Test when the destination is empty. + for (size_t i = 0; i < ArrayLength(srcs); i++) { + nsCString src(srcs[i]); + nsCString dst; + nsAppendEscapedHTML(src, dst); + ASSERT_TRUE(dst.Equals(dsts1[i])); + } + + // Test when the destination is non-empty. + nsCString dst; + for (size_t i = 0; i < ArrayLength(srcs); i++) { + nsCString src(srcs[i]); + nsAppendEscapedHTML(src, dst); + ASSERT_TRUE(dst.Equals(dsts2[i])); + } +} + +TEST(Escape, EscapeSpaces) +{ + // Tests the fallible version of NS_EscapeURL works as expected when no + // escaping is necessary. + nsCString toEscape("data:\x0D\x0A spa ces\xC4\x9F"); + nsCString escaped; + nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible); + EXPECT_EQ(rv, NS_OK); + // Only non-ASCII and C0 + EXPECT_STREQ(escaped.BeginReading(), "data:%0D%0A spa ces%C4%9F"); + + escaped.Truncate(); + rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII | esc_Spaces, escaped, fallible); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(escaped.BeginReading(), "data:%0D%0A%20spa%20ces%C4%9F"); +} + +TEST(Escape, AppleNSURLEscapeHash) +{ + nsCString toEscape("#"); + nsCString escaped; + bool isEscapedOK = NS_Escape(toEscape, escaped, url_NSURLRef); + EXPECT_EQ(isEscapedOK, true); + EXPECT_STREQ(escaped.BeginReading(), "%23"); +} + +TEST(Escape, AppleNSURLEscapeNoDouble) +{ + // The '%' in "%23" shouldn't be encoded again. + nsCString toEscape("%23"); + nsCString escaped; + bool isEscapedOK = NS_Escape(toEscape, escaped, url_NSURLRef); + EXPECT_EQ(isEscapedOK, true); + EXPECT_STREQ(escaped.BeginReading(), "%23"); +} + +// Test escaping of URLs that shouldn't be changed by escaping. +TEST(Escape, AppleNSURLEscapeLists) +{ + // Pairs of URLs (un-encoded, encoded) + nsTArray<std::pair<nsCString, nsCString>> pairs{ + {"https://chat.mozilla.org/#/room/#macdev:mozilla.org"_ns, + "https://chat.mozilla.org/#/room/%23macdev:mozilla.org"_ns}, + }; + + for (std::pair<nsCString, nsCString>& pair : pairs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, pair.first); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(pair.second.BeginReading(), escaped.BeginReading()); + } + + // A list of URLs that should not be changed by encoding. + nsTArray<nsCString> unchangedURLs{ + // '=' In the query + "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854"_ns, + // Escaped character in the fragment + "https://html.spec.whatwg.org/multipage/dom.html#the-document%27s-address"_ns, + // Misc query + "https://www.google.com/search?q=firefox+web+browser&client=firefox-b-1-d&ei=abc&ved=abc&abc=5&oq=firefox+web+browser&gs_lcp=abc&sclient=gws-wiz"_ns, + // Check for double encoding. % encoded octals should not be re-encoded. + "https://chat.mozilla.org/#/room/%23macdev%3Amozilla.org"_ns, + "https://searchfox.org/mozilla-central/search?q=symbol%3AE_%3CT_mozilla%3A%3AWebGLExtensionID%3E_EXT_color_buffer_half_float&path="_ns, + // Other + "https://site.com/script?foo=bar#this_ref"_ns, + }; + + for (nsCString& toEscape : unchangedURLs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, toEscape); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading()); + } +} + +// Test external handler URLs are properly escaped. +TEST(Escape, EscapeURLExternalHandlerURLs) +{ + const nsCString input[] = { + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;/?:@&=+$,!'()*-._~#[]"_ns, + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"_ns, + "custom_proto:Hello World"_ns, + "custom_proto:Hello%20World"_ns, + "myApp://\"foo\" 'bar' `foo`"_ns, + "translator://en-de?view=übersicht"_ns, + "foo:some\\path\\here"_ns, + "web+foo://user:1234@example.com:8080?foo=bar"_ns, + "ext+bar://id='myId'"_ns}; + + const nsCString expected[] = { + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;/?:@&=+$,!'()*-._~#[]"_ns, + "%20!%22#$%&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~"_ns, + "custom_proto:Hello%20World"_ns, + "custom_proto:Hello%20World"_ns, + "myApp://%22foo%22%20'bar'%20%60foo%60"_ns, + "translator://en-de?view=%C3%BCbersicht"_ns, + "foo:some%5Cpath%5Chere"_ns, + "web+foo://user:1234@example.com:8080?foo=bar"_ns, + "ext+bar://id='myId'"_ns}; + + for (size_t i = 0; i < ArrayLength(input); i++) { + nsCString src(input[i]); + nsCString dst; + nsresult rv = + NS_EscapeURL(src, esc_ExtHandler | esc_AlwaysCopy, dst, fallible); + EXPECT_EQ(rv, NS_OK); + ASSERT_TRUE(dst.Equals(expected[i])); + } +} diff --git a/xpcom/tests/gtest/TestEventPriorities.cpp b/xpcom/tests/gtest/TestEventPriorities.cpp new file mode 100644 index 0000000000..874fc81a33 --- /dev/null +++ b/xpcom/tests/gtest/TestEventPriorities.cpp @@ -0,0 +1,91 @@ +/* -*- 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" +#include "nsIRunnable.h" +#include "nsXPCOM.h" +#include "nsThreadUtils.h" +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include <functional> + +using namespace mozilla; + +class TestEvent final : public Runnable, nsIRunnablePriority { + public: + explicit TestEvent(int* aCounter, std::function<void()>&& aCheck, + uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL) + : Runnable("TestEvent"), + mCounter(aCounter), + mCheck(std::move(aCheck)), + mPriority(aPriority) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD GetPriority(uint32_t* aPriority) override { + *aPriority = mPriority; + return NS_OK; + } + + NS_IMETHODIMP Run() override { + (*mCounter)++; + mCheck(); + return NS_OK; + } + + private: + ~TestEvent() = default; + + int* mCounter; + std::function<void()> mCheck; + uint32_t mPriority; +}; + +NS_IMPL_ISUPPORTS_INHERITED(TestEvent, Runnable, nsIRunnablePriority) + +TEST(EventPriorities, IdleAfterNormal) +{ + int normalRan = 0, idleRan = 0; + + RefPtr<TestEvent> evNormal = + new TestEvent(&normalRan, [&] { ASSERT_EQ(idleRan, 0); }); + RefPtr<TestEvent> evIdle = + new TestEvent(&idleRan, [&] { ASSERT_EQ(normalRan, 3); }); + + NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(EventPriorities, IdleAfterNormal)"_ns, + [&]() { return normalRan == 3 && idleRan == 3; })); +} + +TEST(EventPriorities, HighNormal) +{ + int normalRan = 0, highRan = 0; + + RefPtr<TestEvent> evNormal = new TestEvent( + &normalRan, [&] { ASSERT_TRUE((highRan - normalRan) >= 0); }); + RefPtr<TestEvent> evHigh = new TestEvent( + &highRan, [&] { ASSERT_TRUE((highRan - normalRan) >= 0); }, + nsIRunnablePriority::PRIORITY_VSYNC); + + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evHigh); + NS_DispatchToMainThread(evHigh); + NS_DispatchToMainThread(evHigh); + + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(EventPriorities, HighNormal)"_ns, + [&]() { return normalRan == 3 && highRan == 3; })); +} diff --git a/xpcom/tests/gtest/TestEventTargetQI.cpp b/xpcom/tests/gtest/TestEventTargetQI.cpp new file mode 100644 index 0000000000..6131b5e63e --- /dev/null +++ b/xpcom/tests/gtest/TestEventTargetQI.cpp @@ -0,0 +1,83 @@ +/* -*- 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/LazyIdleThread.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/ThrottledEventQueue.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +// Cast the pointer to nsISupports* through nsIEventTarget* before doing the QI +// in order to avoid a static assert intended to prevent trivial QIs, while also +// avoiding ambiguous base errors. +template <typename TargetInterface, typename SourcePtr> +bool TestQITo(SourcePtr& aPtr1) { + nsCOMPtr<TargetInterface> aPtr2 = do_QueryInterface( + static_cast<nsISupports*>(static_cast<nsIEventTarget*>(aPtr1.get()))); + return (bool)aPtr2; +} + +TEST(TestEventTargetQI, ThreadPool) +{ + nsCOMPtr<nsIThreadPool> thing = new nsThreadPool(); + + EXPECT_FALSE(TestQITo<nsISerialEventTarget>(thing)); + + EXPECT_TRUE(TestQITo<nsIEventTarget>(thing)); + + thing->Shutdown(); +} + +TEST(TestEventTargetQI, SharedThreadPool) +{ + nsCOMPtr<nsIThreadPool> thing = SharedThreadPool::Get("TestPool"_ns, 1); + EXPECT_TRUE(thing); + + EXPECT_FALSE(TestQITo<nsISerialEventTarget>(thing)); + + EXPECT_TRUE(TestQITo<nsIEventTarget>(thing)); +} + +TEST(TestEventTargetQI, Thread) +{ + nsCOMPtr<nsIThread> thing = do_GetCurrentThread(); + EXPECT_TRUE(thing); + + EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing)); + + EXPECT_TRUE(TestQITo<nsIEventTarget>(thing)); +} + +TEST(TestEventTargetQI, ThrottledEventQueue) +{ + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + RefPtr<ThrottledEventQueue> thing = + ThrottledEventQueue::Create(thread, "test queue"); + EXPECT_TRUE(thing); + + EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing)); + + EXPECT_TRUE(TestQITo<nsIEventTarget>(thing)); +} + +TEST(TestEventTargetQI, LazyIdleThread) +{ + RefPtr<LazyIdleThread> thing = new LazyIdleThread(0, "TestThread"); + EXPECT_TRUE(thing); + + EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing)); + + EXPECT_TRUE(TestQITo<nsIEventTarget>(thing)); + + thing->Shutdown(); +} diff --git a/xpcom/tests/gtest/TestExpirationTracker.cpp b/xpcom/tests/gtest/TestExpirationTracker.cpp new file mode 100644 index 0000000000..de55d6f907 --- /dev/null +++ b/xpcom/tests/gtest/TestExpirationTracker.cpp @@ -0,0 +1,192 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <prthread.h> +#include "nsExpirationTracker.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "prinrval.h" +#include "nsThreadUtils.h" +#include "mozilla/UniquePtr.h" +#include "gtest/gtest.h" + +namespace TestExpirationTracker { + +struct Object { + Object() : mExpired(false) { Touch(); } + void Touch() { + mLastUsed = PR_IntervalNow(); + mExpired = false; + } + + nsExpirationState mExpiration; + nsExpirationState* GetExpirationState() { return &mExpiration; } + + PRIntervalTime mLastUsed; + bool mExpired; +}; + +static bool error; +static uint32_t periodMS = 100; +static uint32_t ops = 1000; +static uint32_t iterations = 2; +static bool logging = 0; +static uint32_t sleepPeriodMS = 50; +static uint32_t upperBoundSlackMS = 1200; // allow this much error +static uint32_t lowerBoundSlackMS = 60; + +template <uint32_t K> +class Tracker : public nsExpirationTracker<Object, K> { + public: + Tracker() : nsExpirationTracker<Object, K>(periodMS, "Tracker") { + Object* obj = new Object(); + mUniverse.AppendElement(obj); + LogAction(obj, "Created"); + } + + nsTArray<mozilla::UniquePtr<Object>> mUniverse; + + void LogAction(Object* aObj, const char* aAction) { + if (logging) { + printf("%d %p(%d): %s\n", PR_IntervalNow(), static_cast<void*>(aObj), + aObj->mLastUsed, aAction); + } + } + + void DoRandomOperation() { + using mozilla::UniquePtr; + + Object* obj; + switch (rand() & 0x7) { + case 0: { + if (mUniverse.Length() < 50) { + obj = new Object(); + mUniverse.AppendElement(obj); + nsExpirationTracker<Object, K>::AddObject(obj); + LogAction(obj, "Created and added"); + } + break; + } + case 4: { + if (mUniverse.Length() < 50) { + obj = new Object(); + mUniverse.AppendElement(obj); + LogAction(obj, "Created"); + } + break; + } + case 1: { + UniquePtr<Object>& objref = + mUniverse[uint32_t(rand()) % mUniverse.Length()]; + if (objref->mExpiration.IsTracked()) { + nsExpirationTracker<Object, K>::RemoveObject(objref.get()); + LogAction(objref.get(), "Removed"); + } + break; + } + case 2: { + UniquePtr<Object>& objref = + mUniverse[uint32_t(rand()) % mUniverse.Length()]; + if (!objref->mExpiration.IsTracked()) { + objref->Touch(); + nsExpirationTracker<Object, K>::AddObject(objref.get()); + LogAction(objref.get(), "Added"); + } + break; + } + case 3: { + UniquePtr<Object>& objref = + mUniverse[uint32_t(rand()) % mUniverse.Length()]; + if (objref->mExpiration.IsTracked()) { + objref->Touch(); + nsExpirationTracker<Object, K>::MarkUsed(objref.get()); + LogAction(objref.get(), "Marked used"); + } + break; + } + } + } + + protected: + void NotifyExpired(Object* aObj) override { + LogAction(aObj, "Expired"); + PRIntervalTime now = PR_IntervalNow(); + uint32_t timeDiffMS = (now - aObj->mLastUsed) * 1000 / PR_TicksPerSecond(); + // See the comment for NotifyExpired in nsExpirationTracker.h for these + // bounds + uint32_t lowerBoundMS = (K - 1) * periodMS - lowerBoundSlackMS; + uint32_t upperBoundMS = K * (periodMS + sleepPeriodMS) + upperBoundSlackMS; + if (logging) { + printf("Checking: %d-%d = %d [%d,%d]\n", now, aObj->mLastUsed, timeDiffMS, + lowerBoundMS, upperBoundMS); + } + if (timeDiffMS < lowerBoundMS || timeDiffMS > upperBoundMS) { + EXPECT_LT(timeDiffMS, periodMS); + EXPECT_TRUE(aObj->mExpired); + } + aObj->Touch(); + aObj->mExpired = true; + DoRandomOperation(); + DoRandomOperation(); + DoRandomOperation(); + } +}; + +template <uint32_t K> +static bool test_random() { + srand(K); + error = false; + + for (uint32_t j = 0; j < iterations; ++j) { + Tracker<K> tracker; + + uint32_t i = 0; + for (i = 0; i < ops; ++i) { + if ((rand() & 0xF) == 0) { + // Simulate work that takes time + if (logging) { + printf("SLEEPING for %dms (%d)\n", sleepPeriodMS, PR_IntervalNow()); + } + PR_Sleep(PR_MillisecondsToInterval(sleepPeriodMS)); + // Process pending timer events + NS_ProcessPendingEvents(nullptr); + } + tracker.DoRandomOperation(); + } + } + + return !error; +} + +static bool test_random3() { return test_random<3>(); } +static bool test_random4() { return test_random<4>(); } +static bool test_random8() { return test_random<8>(); } + +typedef bool (*TestFunc)(); +#define DECL_TEST(name) \ + { #name, name } + +static const struct Test { + const char* name; + TestFunc func; +} tests[] = {DECL_TEST(test_random3), + DECL_TEST(test_random4), + DECL_TEST(test_random8), + {nullptr, nullptr}}; + +TEST(ExpirationTracker, main) +{ + for (const TestExpirationTracker::Test* t = tests; t->name != nullptr; ++t) { + EXPECT_TRUE(t->func()); + } +} + +} // namespace TestExpirationTracker diff --git a/xpcom/tests/gtest/TestFile.cpp b/xpcom/tests/gtest/TestFile.cpp new file mode 100644 index 0000000000..6e95366584 --- /dev/null +++ b/xpcom/tests/gtest/TestFile.cpp @@ -0,0 +1,576 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "prio.h" +#include "prsystem.h" + +#include "nsIFile.h" +#ifdef XP_WIN +# include "nsILocalFileWin.h" +#endif +#include "nsComponentManagerUtils.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsPrintfCString.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +#ifdef XP_WIN +bool gTestWithPrefix_Win = false; +#endif + +static bool VerifyResult(nsresult aRV, const char* aMsg) { + bool failed = NS_FAILED(aRV); + EXPECT_FALSE(failed) << aMsg << " rv=" << std::hex << (unsigned int)aRV; + return !failed; +} + +#ifdef XP_WIN +static void SetUseDOSDevicePathSyntax(nsIFile* aFile) { + if (gTestWithPrefix_Win) { + nsresult rv; + nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(aFile, &rv); + VerifyResult(rv, "Querying nsILocalFileWin"); + + MOZ_ASSERT(winFile); + winFile->SetUseDOSDevicePathSyntax(true); + } +} +#endif + +static already_AddRefed<nsIFile> NewFile(nsIFile* aBase) { + nsresult rv; + nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + VerifyResult(rv, "Creating nsIFile"); + rv = file->InitWithFile(aBase); + VerifyResult(rv, "InitWithFile"); + +#ifdef XP_WIN + SetUseDOSDevicePathSyntax(file); +#endif + + return file.forget(); +} + +template <typename char_type> +static nsTString<char_type> FixName(const char_type* aName) { + nsTString<char_type> name; + for (uint32_t i = 0; aName[i]; ++i) { + char_type ch = aName[i]; + // PR_GetPathSeparator returns the wrong value on Mac so don't use it +#if defined(XP_WIN) + if (ch == '/') { + ch = '\\'; + } +#endif + name.Append(ch); + } + return name; +} + +// Test nsIFile::AppendNative, verifying that aName is not a valid file name +static bool TestInvalidFileName(nsIFile* aBase, const char* aName) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (NS_SUCCEEDED(rv)) { + EXPECT_NS_FAILED(rv) << "AppendNative with invalid filename " << name.get(); + return false; + } + + return true; +} + +// Test nsIFile::Create, verifying that the file exists and did not exist +// before, and leaving it there for future tests +static bool TestCreate(nsIFile* aBase, const char* aName, int32_t aType, + int32_t aPerm) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " already exists"; + if (exists) { + return false; + } + + rv = file->Create(aType, aPerm); + if (!VerifyResult(rv, "Create")) return false; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_TRUE(exists) << "File " << name.get() << " was not created"; + if (!exists) { + return false; + } + + return true; +} + +// Test nsIFile::CreateUnique, verifying that the new file exists and if it +// existed before, the new file has a different name. The new file is left in +// place. +static bool TestCreateUnique(nsIFile* aBase, const char* aName, int32_t aType, + int32_t aPerm) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool existsBefore; + rv = file->Exists(&existsBefore); + if (!VerifyResult(rv, "Exists (before)")) return false; + + rv = file->CreateUnique(aType, aPerm); + if (!VerifyResult(rv, "Create")) return false; + + bool existsAfter; + rv = file->Exists(&existsAfter); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_TRUE(existsAfter) << "File " << name.get() << " was not created"; + if (!existsAfter) { + return false; + } + + if (existsBefore) { + nsAutoCString leafName; + rv = file->GetNativeLeafName(leafName); + if (!VerifyResult(rv, "GetNativeLeafName")) return false; + EXPECT_FALSE(leafName.Equals(name)) + << "File " << name.get() << " was not given a new name by CreateUnique"; + if (leafName.Equals(name)) { + return false; + } + } + + return true; +} + +// Test nsIFile::OpenNSPRFileDesc with DELETE_ON_CLOSE, verifying that the file +// exists and did not exist before, and leaving it there for future tests +static bool TestDeleteOnClose(nsIFile* aBase, const char* aName, int32_t aFlags, + int32_t aPerm) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " already exists"; + if (exists) { + return false; + } + + PRFileDesc* fileDesc; + rv = file->OpenNSPRFileDesc(aFlags | nsIFile::DELETE_ON_CLOSE, aPerm, + &fileDesc); + if (!VerifyResult(rv, "OpenNSPRFileDesc")) return false; + PRStatus status = PR_Close(fileDesc); + EXPECT_EQ(status, PR_SUCCESS) + << "File " << name.get() << " could not be closed"; + if (status != PR_SUCCESS) { + return false; + } + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " was not removed on close"; + if (exists) { + return false; + } + + return true; +} + +// Test nsIFile::Remove, verifying that the file does not exist and did before +static bool TestRemove(nsIFile* aBase, const char* aName, bool aRecursive, + uint32_t aExpectedRemoveCount = 1) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_TRUE(exists); + if (!exists) { + return false; + } + + uint32_t removeCount = 0; + rv = file->Remove(aRecursive, &removeCount); + if (!VerifyResult(rv, "Remove")) return false; + EXPECT_EQ(removeCount, aExpectedRemoveCount) << "Removal count was wrong"; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " was not removed"; + if (exists) { + return false; + } + + return true; +} + +// Test nsIFile::MoveToNative, verifying that the file did not exist at the new +// location before and does afterward, and that it does not exist at the old +// location anymore +static bool TestMove(nsIFile* aBase, nsIFile* aDestDir, const char* aName, + const char* aNewName) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_TRUE(exists); + if (!exists) { + return false; + } + + nsCOMPtr<nsIFile> newFile = NewFile(file); + nsCString newName = FixName(aNewName); + rv = newFile->MoveToNative(aDestDir, newName); + if (!VerifyResult(rv, "MoveToNative")) return false; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " was not moved"; + if (exists) { + return false; + } + + file = NewFile(aDestDir); + if (!file) return false; + rv = file->AppendNative(newName); + if (!VerifyResult(rv, "AppendNative")) return false; + bool equal; + rv = file->Equals(newFile, &equal); + if (!VerifyResult(rv, "Equals")) return false; + EXPECT_TRUE(equal) << "File object was not updated to destination"; + if (!equal) { + return false; + } + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (new after)")) return false; + EXPECT_TRUE(exists) << "Destination file " << newName.get() + << " was not created"; + if (!exists) { + return false; + } + + return true; +} + +// Test nsIFile::CopyToNative, verifying that the file did not exist at the new +// location before and does afterward, and that it does exist at the old +// location too +static bool TestCopy(nsIFile* aBase, nsIFile* aDestDir, const char* aName, + const char* aNewName) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_TRUE(exists); + if (!exists) { + return false; + } + + nsCOMPtr<nsIFile> newFile = NewFile(file); + nsCString newName = FixName(aNewName); + rv = newFile->CopyToNative(aDestDir, newName); + if (!VerifyResult(rv, "MoveToNative")) return false; + bool equal; + rv = file->Equals(newFile, &equal); + if (!VerifyResult(rv, "Equals")) return false; + EXPECT_TRUE(equal) << "File object updated unexpectedly"; + if (!equal) { + return false; + } + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_TRUE(exists) << "File " << name.get() << " was removed"; + if (!exists) { + return false; + } + + file = NewFile(aDestDir); + if (!file) return false; + rv = file->AppendNative(newName); + if (!VerifyResult(rv, "AppendNative")) return false; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (new after)")) return false; + EXPECT_TRUE(exists) << "Destination file " << newName.get() + << " was not created"; + if (!exists) { + return false; + } + + return true; +} + +// Test nsIFile::GetParent +static bool TestParent(nsIFile* aBase, nsIFile* aStart) { + nsCOMPtr<nsIFile> file = NewFile(aStart); + if (!file) return false; + + nsCOMPtr<nsIFile> parent; + nsresult rv = file->GetParent(getter_AddRefs(parent)); + VerifyResult(rv, "GetParent"); + + bool equal; + rv = parent->Equals(aBase, &equal); + VerifyResult(rv, "Equals"); + EXPECT_TRUE(equal) << "Incorrect parent"; + if (!equal) { + return false; + } + + return true; +} + +// Test nsIFile::Normalize and native path setting/getting +static bool TestNormalizeNativePath(nsIFile* aBase, nsIFile* aStart) { + nsCOMPtr<nsIFile> file = NewFile(aStart); + if (!file) return false; + + auto path = file->NativePath(); +#ifdef XP_WIN + path.Append(FixName(u"/./..")); + nsresult rv = file->InitWithPath(path); + VerifyResult(rv, "InitWithPath"); +#else + path.Append(FixName("/./..")); + nsresult rv = file->InitWithNativePath(path); + VerifyResult(rv, "InitWithNativePath"); +#endif + rv = file->Normalize(); + VerifyResult(rv, "Normalize"); + path = file->NativePath(); + + auto basePath = aBase->NativePath(); + VerifyResult(rv, "GetNativePath (base)"); + + EXPECT_TRUE(path.Equals(basePath)) + << "Incorrect normalization: " << file->HumanReadablePath().get() << " - " + << aBase->HumanReadablePath().get(); + if (!path.Equals(basePath)) { + return false; + } + + return true; +} + +// Test nsIFile::GetDiskSpaceAvailable +static bool TestDiskSpaceAvailable(nsIFile* aBase) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + int64_t diskSpaceAvailable = 0; + nsresult rv = file->GetDiskSpaceAvailable(&diskSpaceAvailable); + VerifyResult(rv, "GetDiskSpaceAvailable"); + + EXPECT_GE(diskSpaceAvailable, 0); + + return true; +} + +// Test nsIFile::GetDiskCapacity +static bool TestDiskCapacity(nsIFile* aBase) { + nsCOMPtr<nsIFile> file = NewFile(aBase); + if (!file) return false; + + int64_t diskCapacity = 0; + nsresult rv = file->GetDiskCapacity(&diskCapacity); + VerifyResult(rv, "GetDiskCapacity"); + + EXPECT_GE(diskCapacity, 0); + + return true; +} + +static void SetupAndTestFunctions(const nsAString& aDirName, + bool aTestCreateUnique, bool aTestNormalize) { + nsCOMPtr<nsIFile> base; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base)); + ASSERT_TRUE(VerifyResult(rv, "Getting temp directory")); + +#ifdef XP_WIN + SetUseDOSDevicePathSyntax(base); +#endif + + rv = base->Append(aDirName); + ASSERT_TRUE( + VerifyResult(rv, nsPrintfCString("Appending %s to temp directory name", + NS_ConvertUTF16toUTF8(aDirName).get()) + .get())); + + // Remove the directory in case tests failed and left it behind. + // don't check result since it might not be there + base->Remove(true); + + // Now create the working directory we're going to use + rv = base->Create(nsIFile::DIRECTORY_TYPE, 0700); + ASSERT_TRUE(VerifyResult(rv, "Creating temp directory")); + + // Now we can safely normalize the path + if (aTestNormalize) { + rv = base->Normalize(); + ASSERT_TRUE(VerifyResult(rv, "Normalizing temp directory name")); + } + + // Initialize subdir object for later use + nsCOMPtr<nsIFile> subdir = NewFile(base); + ASSERT_TRUE(subdir); + + rv = subdir->AppendNative(nsDependentCString("subdir")); + ASSERT_TRUE(VerifyResult(rv, "Appending 'subdir' to test dir name")); + + // --------------- + // End setup code. + // --------------- + + // Test leafName + nsString leafName; + rv = base->GetLeafName(leafName); + ASSERT_TRUE(VerifyResult(rv, "Getting leafName")); + ASSERT_TRUE(leafName.Equals(aDirName)); + + // Test path parsing + ASSERT_TRUE(TestInvalidFileName(base, "a/b")); + ASSERT_TRUE(TestParent(base, subdir)); + + // Test file creation + ASSERT_TRUE(TestCreate(base, "file.txt", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE(TestRemove(base, "file.txt", false)); + + // Test directory creation + ASSERT_TRUE(TestCreate(base, "subdir", nsIFile::DIRECTORY_TYPE, 0700)); + + // Test move and copy in the base directory + ASSERT_TRUE(TestCreate(base, "file.txt", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE(TestMove(base, base, "file.txt", "file2.txt")); + ASSERT_TRUE(TestCopy(base, base, "file2.txt", "file3.txt")); + + // Test moving across directories + ASSERT_TRUE(TestMove(base, subdir, "file2.txt", "file2.txt")); + + // Test moving across directories and renaming at the same time + ASSERT_TRUE(TestMove(subdir, base, "file2.txt", "file4.txt")); + + // Test copying across directories + ASSERT_TRUE(TestCopy(base, subdir, "file4.txt", "file5.txt")); + + if (aTestNormalize) { + // Run normalization tests while the directory exists + ASSERT_TRUE(TestNormalizeNativePath(base, subdir)); + } + + // Test recursive directory removal + ASSERT_TRUE(TestRemove(base, "subdir", true, 2)); + + if (aTestCreateUnique) { + ASSERT_TRUE(TestCreateUnique(base, "foo", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE(TestCreateUnique(base, "foo", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE( + TestCreateUnique(base, "bar.xx", nsIFile::DIRECTORY_TYPE, 0700)); + ASSERT_TRUE( + TestCreateUnique(base, "bar.xx", nsIFile::DIRECTORY_TYPE, 0700)); + } + + ASSERT_TRUE( + TestDeleteOnClose(base, "file7.txt", PR_RDWR | PR_CREATE_FILE, 0600)); + + ASSERT_TRUE(TestDiskSpaceAvailable(base)); + ASSERT_TRUE(TestDiskCapacity(base)); + + // Clean up temporary stuff + rv = base->Remove(true); + VerifyResult(rv, "Cleaning up temp directory"); +} + +TEST(TestFile, Unprefixed) +{ +#ifdef XP_WIN + gTestWithPrefix_Win = false; +#endif + + SetupAndTestFunctions(u"mozfiletests"_ns, + /* aTestCreateUnique */ true, + /* aTestNormalize */ true); + +#ifdef XP_WIN + gTestWithPrefix_Win = true; +#endif +} + +// This simulates what QM_NewLocalFile does (NS_NewLocalFiles and then +// SetUseDOSDevicePathSyntax if it's on Windows for NewFile) +TEST(TestFile, PrefixedOnWin) +{ + SetupAndTestFunctions(u"mozfiletests"_ns, + /* aTestCreateUnique */ true, + /* aTestNormalize */ true); +} + +TEST(TestFile, PrefixedOnWin_PathExceedsMaxPath) +{ + // We want to verify if the prefix would allow as to create a file with over + // 260 char for its path. However, on Windows, the maximum length of filename + // is 255. Given the base file path and we are going append some other file + // to the current base file, let's assume the file path will exceed 260 so + // that we are able to verify if the prefix works or not. + nsString dirName; + dirName.AssignLiteral("mozfiletests"); + for (uint32_t i = 255 - dirName.Length(); i > 0; --i) { + dirName.AppendLiteral("a"); + } + + // Bypass the test for CreateUnique because there is a check for the max + // length of the root on all platforms. + SetupAndTestFunctions(dirName, /* aTestCreateUnique */ false, + /* aTestNormalize */ true); +} + +TEST(TestFile, PrefixedOnWin_ComponentEndsWithPeriod) +{ + // Bypass the normalization for this because it would strip the trailing + // period. + SetupAndTestFunctions(u"mozfiletests."_ns, + /* aTestCreateUnique */ true, + /* aTestNormalize */ false); +} diff --git a/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp b/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp new file mode 100644 index 0000000000..39b73a7148 --- /dev/null +++ b/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp @@ -0,0 +1,289 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "prio.h" +#include "prsystem.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "nsComponentManagerUtils.h" +#include "nsIFile.h" +#include "nsILocalFileWin.h" +#include "nsString.h" + +#define MAX_PATH 260 + +#include "gtest/gtest.h" + +static void CanInitWith(const char* aPath, bool aShouldWork) { + nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + nsresult rv = file->InitWithNativePath(nsDependentCString(aPath)); + bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv); + EXPECT_TRUE(success) << "'" << aPath << "' rv=" << std::hex + << (unsigned int)rv; +} + +static void CanAppend(const char* aRoot, const char* aPath, bool aShouldWork) { + nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + file->InitWithNativePath(nsDependentCString(aRoot)); + nsAutoCString basePath; + file->GetNativeTarget(basePath); + + nsresult rv = file->AppendNative(nsDependentCString(aPath)); + bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv); + EXPECT_TRUE(success) << "'" << basePath.get() << "' + '" << aPath + << "' rv=" << std::hex << (unsigned int)rv; +} + +static void CanSetLeafName(const char* aRoot, const char* aPath, + bool aShouldWork) { + nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + file->InitWithNativePath(nsDependentCString(aRoot)); + nsAutoCString basePath; + file->GetNativeTarget(basePath); + + nsresult rv = + file->SetLeafName(NS_ConvertUTF8toUTF16(nsDependentCString(aPath))); + bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv); + EXPECT_TRUE(success) << "'" << basePath.get() << "' set leaf to '" << aPath + << "' rv=" << std::hex << (unsigned int)rv; +} + +TEST(TestFileNTFSSpecialPaths, PlainPaths) +{ + CanInitWith("C:\\", true); + CanInitWith("C:\\foo", true); + CanInitWith("C:\\bar\\foo", true); + CanInitWith("C:\\bar\\foo\\", true); + + CanAppend("C:\\", "foo", true); + CanAppend("C:\\", "bar", true); + CanAppend("C:\\bar", "foo", true); + + CanSetLeafName("C:\\a", "foo", true); + CanSetLeafName("C:\\a", "bar", true); +} + +TEST(TestFileNTFSSpecialPaths, AllowedSpecialChars) +{ + CanInitWith("C:\\$foo", true); + CanInitWith("C:\\bar\\$foo", true); + CanInitWith("C:\\foo:Zone.Identifier", true); + CanInitWith("C:\\$foo:Zone.Identifier", true); + CanInitWith("C:\\bar\\$foo:Zone.Identifier", true); + + CanAppend("C:\\", "$foo", true); + CanAppend("C:\\bar\\", "$foo", true); + CanAppend("C:\\", "foo:Zone.Identifier", true); + CanAppend("C:\\", "$foo:Zone.Identifier", true); + CanAppend("C:\\bar\\", "$foo:Zone.Identifier", true); + + CanSetLeafName("C:\\a", "$foo", true); + CanSetLeafName("C:\\a", "foo:Zone.Identifier", true); + CanSetLeafName("C:\\a", "$foo:Zone.Identifier", true); +} + +TEST(TestFileNTFSSpecialPaths, ForbiddenAttributes) +{ + CanInitWith("C:\\:$MFT", false); + CanInitWith("C:\\:$mft", false); + CanInitWith("C:\\:$foo", false); + // nsLocalFileWin strips the trailing slash so this should also fail: + CanInitWith("C:\\:$MFT\\", false); + CanInitWith("C:\\:$mft\\", false); + CanInitWith("C:\\:$foo\\", false); + + // We just block these everywhere, not just at the root: + CanInitWith("C:\\bar\\:$mft", false); + CanInitWith("C:\\bar\\:$mft\\", false); + CanInitWith("C:\\bar\\:$foo", false); + CanInitWith("C:\\bar\\:$foo\\", false); + + // Now do the same for appending. + CanAppend("C:\\", ":$MFT", false); + CanAppend("C:\\", ":$mft", false); + CanAppend("C:\\", ":$foo", false); + // nsLocalFileWin strips the trailing slash so this should also fail: + CanAppend("C:\\", ":$MFT\\", false); + CanAppend("C:\\", ":$mft\\", false); + CanAppend("C:\\", ":$foo\\", false); + + // We just block these everywhere, not just at the root: + CanAppend("C:\\bar\\", ":$mft", false); + CanAppend("C:\\bar\\", ":$mft\\", false); + CanAppend("C:\\bar\\", ":$foo", false); + CanAppend("C:\\bar\\", ":$foo\\", false); + + // And the same thing for leaf names: + CanSetLeafName("C:\\a", ":$MFT", false); + CanSetLeafName("C:\\a", ":$mft", false); + CanSetLeafName("C:\\a", ":$foo", false); + + CanSetLeafName("C:\\a", ":$MFT\\", false); + CanSetLeafName("C:\\a", ":$mft\\", false); + CanSetLeafName("C:\\a", ":$foo\\", false); + + CanSetLeafName("C:\\bar\\foo", ":$mft", false); + CanSetLeafName("C:\\bar\\foo", ":$mft\\", false); + CanSetLeafName("C:\\bar\\foo", ":$foo", false); + CanSetLeafName("C:\\bar\\foo", ":$foo\\", false); +} + +TEST(TestFileNTFSSpecialPaths, ForbiddenMetaFiles) +{ + CanInitWith("C:\\$MFT", false); + CanInitWith("C:\\$mft", false); + CanInitWith("C:\\$bitmap", false); + + CanAppend("C:\\", "$MFT", false); + CanAppend("C:\\", "$mft", false); + CanAppend("C:\\", "$bitmap", false); + + CanSetLeafName("C:\\a", "$MFT", false); + CanSetLeafName("C:\\a", "$mft", false); + CanSetLeafName("C:\\a", "$bitmap", false); + + // nsLocalFileWin strips the trailing slash so this should also fail: + CanInitWith("C:\\$MFT\\", false); + CanInitWith("C:\\$mft\\", false); + CanInitWith("C:\\$bitmap\\", false); + + CanAppend("C:\\", "$MFT\\", false); + CanAppend("C:\\", "$mft\\", false); + CanAppend("C:\\", "$bitmap\\", false); + + CanSetLeafName("C:\\a", "$MFT\\", false); + CanSetLeafName("C:\\a", "$mft\\", false); + CanSetLeafName("C:\\a", "$bitmap\\", false); + + // Shouldn't be able to bypass this by asking for ADS stuff: + CanInitWith("C:\\$MFT:Zone.Identifier", false); + CanInitWith("C:\\$mft:Zone.Identifier", false); + CanInitWith("C:\\$bitmap:Zone.Identifier", false); + + CanAppend("C:\\", "$MFT:Zone.Identifier", false); + CanAppend("C:\\", "$mft:Zone.Identifier", false); + CanAppend("C:\\", "$bitmap:Zone.Identifier", false); + + CanSetLeafName("C:\\a", "$MFT:Zone.Identifier", false); + CanSetLeafName("C:\\a", "$mft:Zone.Identifier", false); + CanSetLeafName("C:\\a", "$bitmap:Zone.Identifier", false); +} + +TEST(TestFileNTFSSpecialPaths, ForbiddenMetaFilesOtherRoots) +{ + // Should still block them for UNC and volume roots + CanInitWith("\\\\LOCALHOST\\C$\\$MFT", false); + CanInitWith("\\\\?\\Volume{1234567}\\$MFT", false); + + CanAppend("\\\\LOCALHOST\\", "C$\\$MFT", false); + CanAppend("\\\\LOCALHOST\\C$\\", "$MFT", false); + CanAppend("\\\\?\\Volume{1234567}\\", "$MFT", false); + CanAppend("\\\\Blah\\", "Volume{1234567}\\$MFT", false); + + CanSetLeafName("\\\\LOCALHOST\\C$", "C$\\$MFT", false); + CanSetLeafName("\\\\LOCALHOST\\C$\\foo", "$MFT", false); + CanSetLeafName("\\\\?\\Volume{1234567}\\foo", "$MFT", false); + CanSetLeafName("\\\\Blah\\foo", "Volume{1234567}\\$MFT", false); + + // Root detection should cope with un-normalized paths: + CanInitWith("C:\\foo\\..\\$MFT", false); + CanInitWith("C:\\foo\\..\\$mft\\", false); + CanInitWith("\\\\LOCALHOST\\C$\\blah\\..\\$MFT", false); + CanInitWith("\\\\?\\Volume{13455635}\\blah\\..\\$MFT", false); + // As well as different or duplicated separators: + CanInitWith("C:\\foo\\..\\\\$MFT\\", false); + CanInitWith("\\\\?\\Volume{1234567}/$MFT", false); + CanInitWith("\\\\LOCALHOST\\C$/blah//../$MFT", false); + + // There are no "append" equivalents for the preceding set of tests, + // because append does not allow '..' to be used as a relative path + // component, nor does it allow forward slashes: + CanAppend("C:\\foo", "..\\", false); + CanAppend("C:\\foo", "bar/baz", false); + + // But this is (strangely) allowed for SetLeafName. Yes, really. + CanSetLeafName("C:\\foo\\bar", "..\\$MFT", false); + CanSetLeafName("C:\\foo\\bar", "..\\$mft\\", false); + CanSetLeafName("\\\\LOCALHOST\\C$\\bl", "ah\\..\\$MFT", false); + CanSetLeafName("\\\\?\\Volume{13455635}\\bla", "ah\\..\\$MFT", false); + + CanSetLeafName("C:\\foo\\bar", "..\\\\$MFT\\", false); + CanSetLeafName("\\\\?\\Volume{1234567}\\bar", "/$MFT", false); + CanSetLeafName("\\\\LOCALHOST\\C$/blah/", "\\../$MFT", false); +} + +TEST(TestFileNTFSSpecialPaths, NotQuiteMetaFiles) +{ + // These files should not be blocked away from the root: + CanInitWith("C:\\bar\\$bitmap", true); + CanInitWith("C:\\bar\\$mft", true); + + // Same for append: + CanAppend("C:\\bar\\", "$bitmap", true); + CanAppend("C:\\bar\\", "$mft", true); + + // And SetLeafName: + CanSetLeafName("C:\\bar\\foo", "$bitmap", true); + CanSetLeafName("C:\\bar\\foo", "$mft", true); + + // And we shouldn't block on substring matches: + CanInitWith("C:\\$MFT stocks", true); + CanAppend("C:\\", "$MFT stocks", true); + CanSetLeafName("C:\\", "$MFT stocks", true); +} + +TEST(TestFileNTFSSpecialPaths, Normalization) +{ + // First determine the working directory: + wchar_t workingDir[MAX_PATH]; + if (nullptr == _wgetcwd(workingDir, MAX_PATH - 1)) { + EXPECT_FALSE(true) << "Getting working directory failed."; + return; + } + + nsString normalizedPath(workingDir); + // Need at least 3 chars for the root, at least 2 more to get another subdir + // in there. This test will fail if cwd is the root of a drive. + if (normalizedPath.Length() < 5 || + !mozilla::IsAsciiAlpha(normalizedPath.First()) || + normalizedPath.CharAt(1) != L':' || normalizedPath.CharAt(2) != L'\\') { + EXPECT_FALSE(true) << "Working directory not long enough?!"; + return; + } + + // Copy the drive and colon, but NOT the backslash. + nsAutoString startingFilePath(Substring(normalizedPath, 0, 2)); + normalizedPath.Cut(0, 3); + + // Then determine the number of path components in cwd: + nsAString::const_iterator begin, end; + normalizedPath.BeginReading(begin); + normalizedPath.EndReading(end); + if (!FindCharInReadable(L'\\', begin, end)) { + EXPECT_FALSE(true) << "Working directory was at a root"; + return; + } + auto numberOfComponentsAboveRoot = 1; + while (FindCharInReadable(L'\\', begin, end)) { + begin++; + numberOfComponentsAboveRoot++; + } + + // Then set up a file with that many `..\` components: + startingFilePath.SetCapacity(3 + numberOfComponentsAboveRoot * 3 + 9); + while (numberOfComponentsAboveRoot--) { + startingFilePath.AppendLiteral(u"..\\"); + } + startingFilePath.AppendLiteral(u"$mft"); + + nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + // This should fail immediately, rather than waiting for a call to + // nsIFile::Normalize, because normalization doesn't happen reliably, + // and where it does happen consumers often don't check for errors. + nsresult rv = file->InitWithPath(startingFilePath); + EXPECT_NS_FAILED(rv) << " from normalizing '" + << NS_ConvertUTF16toUTF8(startingFilePath).get() + << "' rv=" << std::hex << (unsigned int)rv; +} diff --git a/xpcom/tests/gtest/TestFilePreferencesUnix.cpp b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp new file mode 100644 index 0000000000..93c6506b7d --- /dev/null +++ b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp @@ -0,0 +1,233 @@ +#include "gtest/gtest.h" + +#include "mozilla/FilePreferences.h" + +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/ScopeExit.h" +#include "nsIDirectoryEnumerator.h" + +using namespace mozilla; + +const char kForbiddenPathsPref[] = "network.file.path_blacklist"; + +TEST(TestFilePreferencesUnix, Parsing) +{ +#define kForbidden "/tmp/forbidden" +#define kForbiddenDir "/tmp/forbidden/" +#define kForbiddenFile "/tmp/forbidden/file" +#define kOther "/tmp/other" +#define kOtherDir "/tmp/other/" +#define kOtherFile "/tmp/other/file" +#define kAllowed "/tmp/allowed" + + // This is run on exit of this function to make sure we clear the pref + // and that behaviour with the pref cleared is correct. + auto cleanup = MakeScopeExit([&] { + nsresult rv = Preferences::ClearUser(kForbiddenPathsPref); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbidden)), + true); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)), + true); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenFile)), + true); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kAllowed)), true); + }); + + auto CheckPrefs = [](const nsACString& aPaths) { + nsresult rv; + rv = Preferences::SetCString(kForbiddenPathsPref, aPaths); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenFile)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbidden)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kAllowed)), true); + }; + + CheckPrefs(nsLiteralCString(kForbidden)); + CheckPrefs(nsLiteralCString(kForbidden "," kOther)); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kOtherFile)), + false); + CheckPrefs(nsLiteralCString(kForbidden "," kOther ",")); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kOtherFile)), + false); +} + +TEST(TestFilePreferencesUnix, Simple) +{ + nsAutoCString tempPath; + + // This is the directory we will forbid + nsCOMPtr<nsIFile> forbiddenDir; + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(forbiddenDir)); + ASSERT_EQ(rv, NS_OK); + rv = forbiddenDir->GetNativePath(tempPath); + ASSERT_EQ(rv, NS_OK); + rv = forbiddenDir->AppendNative("forbidden_dir"_ns); + ASSERT_EQ(rv, NS_OK); + + // This is executed at exit to clean up after ourselves. + auto cleanup = MakeScopeExit([&] { + nsresult rv = Preferences::ClearUser(kForbiddenPathsPref); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + + rv = forbiddenDir->Remove(true); + ASSERT_EQ(rv, NS_OK); + }); + + // Create the directory + rv = forbiddenDir->Create(nsIFile::DIRECTORY_TYPE, 0666); + ASSERT_EQ(rv, NS_OK); + + // This is the file we will try to access + nsCOMPtr<nsIFile> forbiddenFile; + rv = forbiddenDir->Clone(getter_AddRefs(forbiddenFile)); + ASSERT_EQ(rv, NS_OK); + rv = forbiddenFile->AppendNative("test_file"_ns); + + // Create the file + ASSERT_EQ(rv, NS_OK); + rv = forbiddenFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + + // Get the forbidden path + nsAutoCString forbiddenPath; + rv = forbiddenDir->GetNativePath(forbiddenPath); + ASSERT_EQ(rv, NS_OK); + + // Set the pref and make sure it is enforced + rv = Preferences::SetCString(kForbiddenPathsPref, forbiddenPath); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + + // Check that we can't access some of the file attributes + int64_t size; + rv = forbiddenFile->GetFileSize(&size); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + bool exists; + rv = forbiddenFile->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that we can't enumerate the directory + nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator; + rv = forbiddenDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + nsCOMPtr<nsIFile> newPath; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative("."_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative("forbidden_dir"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + rv = newPath->AppendNative("test_file"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that ./ does not bypass the filter + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendRelativeNativePath("./forbidden_dir/file"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that .. does not bypass the filter + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendRelativeNativePath("allowed/../forbidden_dir/file"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_UNRECOGNIZED_PATH); + + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative("allowed"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative(".."_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_UNRECOGNIZED_PATH); + +#if defined(XP_UNIX) && !defined(ANDROID) + nsAutoCString homePath; + NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(newPath)); + newPath->GetNativePath(homePath); + + newPath->InitWithNativePath("~"_ns); + ASSERT_TRUE(newPath->NativePath().Equals(homePath)); + + newPath->InitWithNativePath("~/foo"_ns); + ASSERT_TRUE(newPath->NativePath().Equals(homePath + "/foo"_ns)); + + nsLiteralCString homeBase = +# ifdef XP_MACOSX + "/Users"_ns; +# else + "/home"_ns; +# endif + + newPath->InitWithNativePath("~foo"_ns); + ASSERT_TRUE(newPath->NativePath().Equals(homeBase + "/foo"_ns)); + + newPath->InitWithNativePath("~foo/bar"_ns); + ASSERT_TRUE(newPath->NativePath().Equals(homeBase + "/foo/bar"_ns)); +#endif + + nsAutoCString trickyPath(tempPath); + trickyPath.AppendLiteral("/allowed/../forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that we can't construct a path that is functionally the same + // as the forbidden one and bypasses the filter. + trickyPath = tempPath; + trickyPath.AppendLiteral("/./forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + trickyPath = tempPath; + trickyPath.AppendLiteral("//forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + trickyPath.Truncate(); + trickyPath.AppendLiteral("//"); + trickyPath.Append(tempPath); + trickyPath.AppendLiteral("/forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + trickyPath.Truncate(); + trickyPath.AppendLiteral("//"); + trickyPath.Append(tempPath); + trickyPath.AppendLiteral("//forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that if the forbidden string is a directory, we only block access + // to subresources, not the directory itself. + nsAutoCString forbiddenDirPath(forbiddenPath); + forbiddenDirPath.Append("/"); + rv = Preferences::SetCString(kForbiddenPathsPref, forbiddenDirPath); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + + // This should work, since we only block subresources + rv = forbiddenDir->Exists(&exists); + ASSERT_EQ(rv, NS_OK); + + rv = forbiddenDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); +} diff --git a/xpcom/tests/gtest/TestFilePreferencesWin.cpp b/xpcom/tests/gtest/TestFilePreferencesWin.cpp new file mode 100644 index 0000000000..dfee139970 --- /dev/null +++ b/xpcom/tests/gtest/TestFilePreferencesWin.cpp @@ -0,0 +1,196 @@ +#include "gtest/gtest.h" + +#include "mozilla/FilePreferences.h" +#include "nsComponentManagerUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsXPCOMCID.h" + +TEST(FilePreferencesWin, Normalization) +{ + nsAutoString normalized; + + mozilla::FilePreferences::testing::NormalizePath(u"foo"_ns, normalized); + ASSERT_TRUE(normalized == u"foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\foo"_ns, normalized); + ASSERT_TRUE(normalized == u"\\foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo"_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"foo\\some"_ns, normalized); + ASSERT_TRUE(normalized == u"foo\\some"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\foo"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\."_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\"_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\."_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\."_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\.\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\..\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\.."_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\..\\bar\\..\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\..\\bar"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\bar"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\..\\..\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath( + u"\\\\foo\\bar\\.\\..\\.\\..\\"_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + bool result; + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.."_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\..\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\..\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + u"\\\\foo\\\\bar"_ns, normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + u"\\\\foo\\bar\\..\\..\\..\\..\\"_ns, normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\..\\\\"_ns, + normalized); + ASSERT_FALSE(result); +} + +TEST(FilePreferencesWin, AccessUNC) +{ + nsCOMPtr<nsIFile> lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + + nsresult rv; + + mozilla::FilePreferences::testing::SetBlockUNCPaths(false); + + rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns); + ASSERT_EQ(rv, NS_OK); + + mozilla::FilePreferences::testing::SetBlockUNCPaths(true); + + rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + mozilla::FilePreferences::testing::AddDirectoryToAllowlist(u"\\\\nice"_ns); + + rv = lf->InitWithPath(u"\\\\nice\\share"_ns); + ASSERT_EQ(rv, NS_OK); + + rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); +} + +TEST(FilePreferencesWin, AccessDOSDevicePath) +{ + const auto devicePathSpecifier = u"\\\\?\\"_ns; + + nsCOMPtr<nsIFile> lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + + nsresult rv; + + mozilla::FilePreferences::testing::SetBlockUNCPaths(true); + + rv = lf->InitWithPath(devicePathSpecifier + u"evil\\z:\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + rv = lf->InitWithPath(devicePathSpecifier + u"UNC\\evil\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + rv = lf->InitWithPath(devicePathSpecifier + u"C:\\"_ns); + ASSERT_EQ(rv, NS_OK); + + nsCOMPtr<nsIFile> base; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base)); + ASSERT_EQ(rv, NS_OK); + + nsAutoString path; + rv = base->GetPath(path); + ASSERT_EQ(rv, NS_OK); + + rv = lf->InitWithPath(devicePathSpecifier + path); + ASSERT_EQ(rv, NS_OK); +} + +TEST(FilePreferencesWin, StartsWithDiskDesignatorAndBackslash) +{ + bool result; + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"\\\\UNC\\path"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"\\single\\backslash"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"C:relative"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"\\\\?\\C:\\"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"C:\\"_ns); + ASSERT_TRUE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"c:\\"_ns); + ASSERT_TRUE(result); +} diff --git a/xpcom/tests/gtest/TestGCPostBarriers.cpp b/xpcom/tests/gtest/TestGCPostBarriers.cpp new file mode 100644 index 0000000000..c32cd48c86 --- /dev/null +++ b/xpcom/tests/gtest/TestGCPostBarriers.cpp @@ -0,0 +1,165 @@ +/* -*- 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/. */ + +/* + * Tests that generational garbage collection post-barriers are correctly + * implemented for nsTArrays that contain JavaScript Values. + */ + +#include "mozilla/UniquePtr.h" + +#include "jsapi.h" +#include "nsTArray.h" + +#include "gtest/gtest.h" + +#include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty +#include "js/TracingAPI.h" +#include "js/HeapAPI.h" + +#include "mozilla/CycleCollectedJSContext.h" + +using namespace mozilla; + +template <class ArrayT> +static void TraceArray(JSTracer* trc, void* data) { + ArrayT* array = static_cast<ArrayT*>(data); + for (unsigned i = 0; i < array->Length(); ++i) { + JS::TraceEdge(trc, &array->ElementAt(i), "array-element"); + } +} + +/* + * Use arrays with initial size much smaller than the final number of elements + * to test that moving Heap<T> elements works correctly. + */ +const size_t ElementCount = 100; +const size_t InitialElements = ElementCount / 10; + +template <class ArrayT> +static void TestGrow(JSContext* cx) { + JS_GC(cx); + + auto array = MakeUnique<ArrayT>(); + ASSERT_TRUE(array != nullptr); + + JS_AddExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get()); + + /* + * Create the array and fill it with new JS objects. With GGC these will be + * allocated in the nursery. + */ + JS::RootedValue value(cx); + const char* property = "foo"; + for (size_t i = 0; i < ElementCount; ++i) { + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_FALSE(JS::ObjectIsTenured(obj)); + value = JS::Int32Value(static_cast<int32_t>(i)); + ASSERT_TRUE(JS_SetProperty(cx, obj, property, value)); + ASSERT_TRUE(array->AppendElement(obj, fallible)); + } + + /* + * If postbarriers are not working, we will crash here when we try to mark + * objects that have been moved to the tenured heap. + */ + JS_GC(cx); + + /* + * Sanity check that our array contains what we expect. + */ + ASSERT_EQ(array->Length(), ElementCount); + for (size_t i = 0; i < array->Length(); i++) { + JS::RootedObject obj(cx, array->ElementAt(i)); + ASSERT_TRUE(JS::ObjectIsTenured(obj)); + ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value)); + ASSERT_TRUE(value.isInt32()); + ASSERT_EQ(static_cast<int32_t>(i), value.toInt32()); + } + + JS_RemoveExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get()); +} + +template <class ArrayT> +static void TestShrink(JSContext* cx) { + JS_GC(cx); + + auto array = MakeUnique<ArrayT>(); + ASSERT_TRUE(array != nullptr); + + JS_AddExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get()); + + /* + * Create the array and fill it with new JS objects. With GGC these will be + * allocated in the nursery. + */ + JS::RootedValue value(cx); + const char* property = "foo"; + for (size_t i = 0; i < ElementCount; ++i) { + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_FALSE(JS::ObjectIsTenured(obj)); + value = JS::Int32Value(static_cast<int32_t>(i)); + ASSERT_TRUE(JS_SetProperty(cx, obj, property, value)); + ASSERT_TRUE(array->AppendElement(obj, fallible)); + } + + /* Shrink and compact the array */ + array->RemoveElementsAt(InitialElements, ElementCount - InitialElements); + array->Compact(); + + JS_GC(cx); + + ASSERT_EQ(array->Length(), InitialElements); + for (size_t i = 0; i < array->Length(); i++) { + JS::RootedObject obj(cx, array->ElementAt(i)); + ASSERT_TRUE(JS::ObjectIsTenured(obj)); + ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value)); + ASSERT_TRUE(value.isInt32()); + ASSERT_EQ(static_cast<int32_t>(i), value.toInt32()); + } + + JS_RemoveExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get()); +} + +template <class ArrayT> +static void TestArrayType(JSContext* cx) { + TestGrow<ArrayT>(cx); + TestShrink<ArrayT>(cx); +} + +static void CreateGlobalAndRunTest(JSContext* cx) { + static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + + JS::RealmOptions options; + // dummy + options.behaviors().setReduceTimerPrecisionCallerType( + JS::RTPCallerTypeToken{0}); + JS::PersistentRootedObject global(cx); + global = JS_NewGlobalObject(cx, &GlobalClass, nullptr, + JS::FireOnNewGlobalHook, options); + ASSERT_TRUE(global != nullptr); + + JS::Realm* oldRealm = JS::EnterRealm(cx, global); + + using ElementT = JS::Heap<JSObject*>; + + TestArrayType<nsTArray<ElementT>>(cx); + TestArrayType<FallibleTArray<ElementT>>(cx); + TestArrayType<AutoTArray<ElementT, 1>>(cx); + + JS::LeaveRealm(cx, oldRealm); +} + +TEST(GCPostBarriers, nsTArray) +{ + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); + ASSERT_TRUE(ccjscx != nullptr); + JSContext* cx = ccjscx->Context(); + ASSERT_TRUE(cx != nullptr); + + CreateGlobalAndRunTest(cx); +} diff --git a/xpcom/tests/gtest/TestHandleWatcher.cpp b/xpcom/tests/gtest/TestHandleWatcher.cpp new file mode 100644 index 0000000000..c003a026a1 --- /dev/null +++ b/xpcom/tests/gtest/TestHandleWatcher.cpp @@ -0,0 +1,580 @@ +/* -*- 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 "gtest/gtest.h" + +#include <minwindef.h> +#include <handleapi.h> +#include <synchapi.h> + +#include "mozilla/Assertions.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Result.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/WinHandleWatcher.h" +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsIEventTarget.h" +#include "nsITargetShutdownTask.h" +#include "nsIThread.h" +#include "nsIThreadShutdown.h" +#include "nsITimer.h" +#include "nsTHashMap.h" +#include "nsThreadUtils.h" +// #include "nscore.h" + +namespace details { +static nsCString MakeTargetName(const char* name) { + const char* testName = + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + nsCString ret; + ret.AppendPrintf("%s: %s", testName, name); + return ret; +} +} // namespace details + +using HandleWatcher = mozilla::HandleWatcher; + +/////////////////////////////////////////////////////////////////////// +// Error handling + +// nsresult_fatal_err_: auxiliary function for testing-macros. +[[noreturn]] void nsresult_fatal_err_(const char* file, size_t line, + const char* expr, nsresult res) { + // implementation details from the MOZ_CRASH* family of macros + MOZ_Crash(file, static_cast<int>(line), + MOZ_CrashPrintf("%s gave nsresult %s(%" PRIX32 ")", expr, + mozilla::GetStaticErrorName(res), uint32_t(res))); +} + +// UNWRAP: testing-oriented variant of Result::unwrap. +// +// We make no use of gtest's `ASSERT_*` family of macros, since they assume +// that a `return;` statement is sufficient to abort the test. +template <typename T> +T unwrap_impl_(const char* file, size_t line, const char* expr, + mozilla::Result<T, nsresult> res) { + if (MOZ_LIKELY(res.isOk())) { + return res.unwrap(); + } + nsresult_fatal_err_(file, line, expr, res.unwrapErr()); +} + +#define UNWRAP(expr) unwrap_impl_(__FILE__, __LINE__, #expr, expr) + +/////////////////////////////////////////////////////////////////////// +// Milliseconds() +// +// Convenience declaration for millisecond-based mozilla::TimeDurations. +static mozilla::TimeDuration Milliseconds(double d) { + return mozilla::TimeDuration::FromMilliseconds(d); +} + +/////////////////////////////////////////////////////////////////////// +// TestHandleWatcher +// +// GTest test fixture. Provides shared resources. +class TestHandleWatcher : public testing::Test { + protected: + static void SetUpTestSuite() { sIsLive = true; } + static void TearDownTestSuite() { + sPool = nullptr; + sIsLive = false; + } + + public: + static already_AddRefed<mozilla::SharedThreadPool> GetPool() { + AssertIsLive(); + if (!sPool) { + sPool = mozilla::SharedThreadPool::Get("Test Pool"_ns); + } + return do_AddRef(sPool); + } + + private: + static bool sIsLive; // just for confirmation + static void AssertIsLive() { + MOZ_ASSERT(sIsLive, + "attempted to use `class TestHandleWatcher` outside test group"); + } + + static RefPtr<mozilla::SharedThreadPool> sPool; +}; + +/* static */ +bool TestHandleWatcher::sIsLive = false; +/* static */ +RefPtr<mozilla::SharedThreadPool> TestHandleWatcher::sPool = nullptr; + +/////////////////////////////////////////////////////////////////////// +// WindowsEventObject +// +// Convenient interface to a Windows `event` object. (This is a synchronization +// object that's usually the wrong thing to use.) +struct WindowsEventObject { + HANDLE const handle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + WindowsEventObject() = default; + ~WindowsEventObject() { ::CloseHandle(handle); } + + WindowsEventObject(WindowsEventObject const&) = delete; + WindowsEventObject(WindowsEventObject&&) = delete; + WindowsEventObject& operator=(WindowsEventObject const&) = delete; + WindowsEventObject& operator=(WindowsEventObject&&) = delete; + + void Set() { ::SetEvent(handle); } +}; + +/////////////////////////////////////////////////////////////////////// +// SpawnNewThread +// +nsCOMPtr<nsIThread> SpawnNewThread(const char* name) { + nsCOMPtr<nsIThread> thread; + MOZ_ALWAYS_SUCCEEDS( + NS_NewNamedThread(details::MakeTargetName(name), getter_AddRefs(thread))); + return thread; +} + +/////////////////////////////////////////////////////////////////////// +// SpawnNewBackgroundQueue +// +// (mozilla::TaskQueue expects the supplied name to outlive the queue, so we +// just use a static string.) +RefPtr<mozilla::TaskQueue> SpawnNewBackgroundQueue() { + return mozilla::TaskQueue::Create(TestHandleWatcher::GetPool(), + "task queue for TestHandleWatcher"); +} + +/////////////////////////////////////////////////////////////////////// +// SpinEventLoopUntil +// +// Local equivalent of `mozilla::SpinEventLoopUntil`, extended with a timeout. +// +// Spin the current thread's event loop until either a specified predicate is +// satisfied or a specified time-interval has passed. +// +struct SpinEventLoopUntilRet { + enum Value { Ok, TimedOut, InternalError } value; + bool ok() const { return value == Value::Ok; } + bool timedOut() const { return value == Value::TimedOut; } + + MOZ_IMPLICIT SpinEventLoopUntilRet(Value v) : value(v) {} +}; +template <typename Predicate> +SpinEventLoopUntilRet SpinEventLoopUntil( + Predicate const& aPredicate, + mozilla::TimeDuration aDuration = Milliseconds(500)) { + using Value = SpinEventLoopUntilRet::Value; + nsIThread* currentThread = NS_GetCurrentThread(); + + // Set up timer. + bool timedOut = false; + auto timer = UNWRAP(NS_NewTimerWithCallback( + [&](nsITimer*) { timedOut = true; }, aDuration, nsITimer::TYPE_ONE_SHOT, + "SpinEventLoop timer", currentThread)); + auto onExitCancelTimer = mozilla::MakeScopeExit([&] { timer->Cancel(); }); + + bool const ret = mozilla::SpinEventLoopUntil( + "TestHandleWatcher"_ns, [&] { return timedOut || aPredicate(); }); + if (!ret) return Value::InternalError; + if (timedOut) return Value::TimedOut; + return Value::Ok; +} + +// metatest for `SpinEventLoopUntil` +TEST_F(TestHandleWatcher, SpinEventLoopUntil) { + auto should_fail = SpinEventLoopUntil([] { return false; }, Milliseconds(1)); + ASSERT_TRUE(should_fail.timedOut()); + auto should_pass = SpinEventLoopUntil([] { return true; }, Milliseconds(50)); + ASSERT_TRUE(should_pass.ok()); +} + +/////////////////////////////////////////////////////////////////////// +// PingMainThread +// +// Post a do-nothing message to the main thread's event queue. (This will signal +// it to wake up and check its predicate, if it's waiting for that to happen.) +void PingMainThread() { + MOZ_ALWAYS_SUCCEEDS( + NS_DispatchToMainThread(NS_NewRunnableFunction("Ping", [] {}))); +} + +/////////////////////////////////////////////////////////////////////// +// Individual tests + +// Test basic creation and destruction. +TEST_F(TestHandleWatcher, Trivial) { HandleWatcher hw; } + +// Test interaction before a Watch is created. +TEST_F(TestHandleWatcher, Empty) { + HandleWatcher hw; + ASSERT_TRUE(hw.IsStopped()); + hw.Stop(); +} + +// Start and trigger an HandleWatcher directly from the main thread. +TEST_F(TestHandleWatcher, Simple) { + WindowsEventObject event; + HandleWatcher hw; + + std::atomic<bool> run = false; + + hw.Watch( + event.handle, NS_GetCurrentThread(), + NS_NewRunnableFunction("TestHandleWatcher::Simple", [&] { run = true; })); + + ASSERT_FALSE(run.load()); + event.Set(); + // Attempt to force a race below. + ::Sleep(0); + // This should not race. The HandleWatcher doesn't execute its delegate; it + // just queues a mozilla::Task to do that onto our thread's event queue, + // and that Task hasn't been permitted to run yet. + ASSERT_FALSE(run.load()); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return run.load(); }).ok()); +} + +// Test that calling Stop() stops the watcher. +TEST_F(TestHandleWatcher, Stop) { + WindowsEventObject event; + HandleWatcher hw; + std::atomic<bool> run = false; + + hw.Watch( + event.handle, NS_GetCurrentThread(), + NS_NewRunnableFunction("TestHandleWatcher::Stop", [&] { run = true; })); + + ASSERT_FALSE(hw.IsStopped()); + hw.Stop(); + ASSERT_TRUE(hw.IsStopped()); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return run.load(); }, Milliseconds(25)) + .timedOut()); +} + +// Test that the target's destruction stops the watch. +TEST_F(TestHandleWatcher, TargetDestroyed) { + WindowsEventObject event; + HandleWatcher hw; + bool run = false; + + auto queue = SpawnNewThread("target thread"); + hw.Watch(event.handle, queue.get(), + NS_NewRunnableFunction("never called", [&] { run = true; })); + + ASSERT_FALSE(hw.IsStopped()); + // synchronous shutdown before destruction + queue->Shutdown(); + + ASSERT_TRUE(hw.IsStopped()); + ASSERT_FALSE(run); +} + +// Test that calling `Watch` again stops the current watch. +TEST_F(TestHandleWatcher, Rewatch) { + WindowsEventObject event; + HandleWatcher hw; + + bool b1 = false; + bool b2 = false; + + { + auto queue = SpawnNewThread("target thread"); + + hw.Watch(event.handle, queue.get(), NS_NewRunnableFunction("b1", [&] { + b1 = true; + PingMainThread(); + })); + hw.Watch(event.handle, queue.get(), NS_NewRunnableFunction("b2", [&] { + b2 = true; + PingMainThread(); + })); + + event.Set(); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return b1 || b2; }).ok()); + queue->Shutdown(); + } + ASSERT_FALSE(b1); + ASSERT_TRUE(b2); +} + +// Test that watching a HANDLE which is _already_ signaled still fires the +// associated task. +TEST_F(TestHandleWatcher, Presignalled) { + WindowsEventObject event; + HandleWatcher hw; + + bool run = false; + event.Set(); + hw.Watch(event.handle, NS_GetCurrentThread(), + NS_NewRunnableFunction("TestHandleWatcher::Presignalled", + [&] { run = true; })); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return run; }).ok()); +} + +/////////////////////////////////////////////////////////////////////// +// Systematic tests: normal activation +// +// Test that a handle becoming signalled on target A correctly enqueues a task +// on target B, regardless of whether A == B. +// +struct ActivationTestSetup { + enum TargetType { Main, Side, Background }; + + WindowsEventObject event; + HandleWatcher watcher; + + std::atomic<bool> run = false; + nsCOMPtr<nsIThread> sideThread; + nsCOMPtr<nsISerialEventTarget> backgroundQueue; + + private: + nsIEventTarget* GetQueue(TargetType targetTyoe) { + MOZ_ASSERT(NS_IsMainThread()); + switch (targetTyoe) { + case TargetType::Main: + return NS_GetCurrentThread(); + + case TargetType::Side: { + if (!sideThread) { + sideThread = SpawnNewThread("side thread"); + } + return sideThread; + } + + case TargetType::Background: { + if (!backgroundQueue) { + backgroundQueue = SpawnNewBackgroundQueue(); + } + return backgroundQueue.get(); + } + } + } + + void OnSignaled() { + run = true; + // If we're not running on the main thread, it may be blocked waiting for + // events. + PingMainThread(); + } + + public: + void Setup(TargetType from, TargetType to) { + watcher.Watch( + event.handle, GetQueue(to), + NS_NewRunnableFunction("Reaction", [this] { this->OnSignaled(); })); + + MOZ_ALWAYS_SUCCEEDS(GetQueue(from)->Dispatch( + NS_NewRunnableFunction("Action", [this] { event.Set(); }))); + } + + bool Execute() { + MOZ_ASSERT(NS_IsMainThread()); + bool const spin = SpinEventLoopUntil([this] { + MOZ_ASSERT(NS_IsMainThread()); + return run.load(); + }).ok(); + return spin && watcher.IsStopped(); + } + + ~ActivationTestSetup() { watcher.Stop(); } +}; + +#define MOZ_HANDLEWATCHER_GTEST_FROM_TO(FROM, TO) \ + TEST_F(TestHandleWatcher, FROM##To##TO) { \ + ActivationTestSetup s; \ + s.Setup(ActivationTestSetup::TargetType::FROM, \ + ActivationTestSetup::TargetType::TO); \ + ASSERT_TRUE(s.Execute()); \ + } + +// Note that `Main -> Main` is subtly different from `Simple`, above: `Simple` +// sets the event before spinning, while `Main -> Main` merely enqueues a Task +// that will set the event during the spin. +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Main); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Side); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Background); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Main); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Side); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Background); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Main); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Side); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Background); + +/////////////////////////////////////////////////////////////////////// +// Ad-hoc tests: reentrancy +// +// Test that HandleWatcher neither deadlocks nor loses data if its release of a +// referenced object causes the invocation of another method on HandleWatcher. + +// Reentrancy case 1/2: the event target. +namespace { +class MockEventTarget final : public nsIEventTarget { + NS_DECL_THREADSAFE_ISUPPORTS + + private: + // Map from registered shutdown tasks to whether or not they have been (or are + // being) executed. (This should probably guarantee some deterministic order, + // and also be mutex-protected; but that doesn't matter here.) + nsTHashMap<RefPtr<nsITargetShutdownTask>, bool> mShutdownTasks; + // Out-of band task to be run last at destruction time, regardless of anything + // else. + std::function<void(void)> mDeathAction; + + ~MockEventTarget() { + for (auto& task : mShutdownTasks) { + task.SetData(true); + task.GetKey()->TargetShutdown(); + } + if (mDeathAction) { + mDeathAction(); + } + } + + public: + // shutdown task handling + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* task) override { + mShutdownTasks.WithEntryHandle(task, [&](auto entry) { + if (entry.HasEntry()) { + MOZ_CRASH("attempted to double-register shutdown task"); + } + entry.Insert(false); + }); + return NS_OK; + } + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* task) override { + mozilla::Maybe<bool> res = mShutdownTasks.Extract(task); + if (!res.isSome()) { + MOZ_CRASH("attempted to unregister non-registered task"); + } + if (res.value()) { + MOZ_CRASH("attempted to unregister already-executed shutdown task"); + } + return NS_OK; + } + void RegisterDeathAction(std::function<void(void)>&& f) { + mDeathAction = std::move(f); + } + + // other nsIEventTarget methods (that we don't actually use) + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) { return false; } + NS_IMETHOD IsOnCurrentThread(bool* _retval) { + *_retval = false; + return NS_OK; + } + NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD DispatchFromScript(nsIRunnable*, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; + } +}; +NS_IMPL_ISUPPORTS(MockEventTarget, nsIEventTarget) +} // anonymous namespace + +// Test that a HandleWatcher neither deadlocks nor loses data if it's invoked +// when it releases its target. +TEST_F(TestHandleWatcher, TargetDestructionRecurrency) { + WindowsEventObject e1, e2; + bool b1 = false, b2 = false; + HandleWatcher hw; + + { + RefPtr<MockEventTarget> p = mozilla::MakeRefPtr<MockEventTarget>(); + + hw.Watch(e1.handle, p.get(), NS_NewRunnableFunction("first callback", [&] { + b1 = true; + PingMainThread(); + })); + + p->RegisterDeathAction([&] { + hw.Watch(e2.handle, mozilla::GetMainThreadSerialEventTarget(), + NS_NewRunnableFunction("second callback", [&] { b2 = true; })); + }); + } + + ASSERT_FALSE(hw.IsStopped()); + hw.Stop(); + ASSERT_FALSE(hw.IsStopped()); // [sic] + + e1.Set(); // should do nothing + e2.Set(); + ASSERT_TRUE(SpinEventLoopUntil([&] { return b1 || b2; }).ok()); + ASSERT_FALSE(b1); + ASSERT_TRUE(b2); +} + +// Reentrancy case 2/2: the runnable. +namespace { +class MockRunnable final : public nsIRunnable { + NS_DECL_THREADSAFE_ISUPPORTS + NS_IMETHOD Run() override { + MOZ_CRASH("MockRunnable was invoked"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + std::function<void(void)> mDeathAction; + + public: + void RegisterDeathAction(std::function<void(void)>&& f) { + mDeathAction = std::move(f); + } + + private: + ~MockRunnable() { + if (mDeathAction) { + mDeathAction(); + } + } +}; +NS_IMPL_ISUPPORTS(MockRunnable, nsIRunnable) +} // anonymous namespace + +// Test that a HandleWatcher neither deadlocks nor loses data if it's invoked +// when it releases its task. +TEST_F(TestHandleWatcher, TaskDestructionRecurrency) { + WindowsEventObject e1, e2; + bool run = false; + HandleWatcher hw; + + auto thread = SpawnNewBackgroundQueue(); + + { + RefPtr<MockRunnable> runnable = mozilla::MakeRefPtr<MockRunnable>(); + + runnable->RegisterDeathAction([&] { + hw.Watch(e2.handle, thread, NS_NewRunnableFunction("callback", [&] { + run = true; + PingMainThread(); + })); + }); + + hw.Watch(e1.handle, thread.get(), runnable.forget()); + } + + ASSERT_FALSE(hw.IsStopped()); + hw.Stop(); + ASSERT_FALSE(hw.IsStopped()); // [sic] + + e1.Set(); + // give MockRunnable a chance to run (and therefore crash) if it somehow + // hasn't been discmnnected + ASSERT_TRUE( + SpinEventLoopUntil([&] { return false; }, Milliseconds(10)).timedOut()); + + e2.Set(); + ASSERT_TRUE(SpinEventLoopUntil([&] { return run; }).ok()); + ASSERT_TRUE(run); +} diff --git a/xpcom/tests/gtest/TestHashtables.cpp b/xpcom/tests/gtest/TestHashtables.cpp new file mode 100644 index 0000000000..fe1e6a3611 --- /dev/null +++ b/xpcom/tests/gtest/TestHashtables.cpp @@ -0,0 +1,1617 @@ +/* -*- 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 "nsTHashtable.h" +#include "nsBaseHashtable.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsRefCountedHashtable.h" + +#include "nsCOMPtr.h" +#include "nsIMemoryReporter.h" +#include "nsISupports.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/Unused.h" + +#include "gtest/gtest.h" + +#include <numeric> + +using mozilla::MakeRefPtr; +using mozilla::MakeUnique; +using mozilla::UniquePtr; + +namespace TestHashtables { + +class TestUniChar // for nsClassHashtable +{ + public: + explicit TestUniChar(uint32_t aWord) { mWord = aWord; } + + ~TestUniChar() = default; + + uint32_t GetChar() const { return mWord; } + + private: + uint32_t mWord; +}; + +class TestUniCharDerived : public TestUniChar { + using TestUniChar::TestUniChar; +}; + +class TestUniCharRefCounted // for nsRefPtrHashtable +{ + public: + explicit TestUniCharRefCounted(uint32_t aWord, + uint32_t aExpectedAddRefCnt = 0) + : mExpectedAddRefCnt(aExpectedAddRefCnt), + mAddRefCnt(0), + mRefCnt(0), + mWord(aWord) {} + + uint32_t AddRef() { + mRefCnt++; + mAddRefCnt++; + return mRefCnt; + } + + uint32_t Release() { + EXPECT_TRUE(mRefCnt > 0); + mRefCnt--; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; + } + + uint32_t GetChar() const { return mWord; } + + private: + ~TestUniCharRefCounted() { + if (mExpectedAddRefCnt > 0) { + EXPECT_EQ(mAddRefCnt, mExpectedAddRefCnt); + } + } + + uint32_t mExpectedAddRefCnt; + uint32_t mAddRefCnt; + uint32_t mRefCnt; + uint32_t mWord; +}; + +struct EntityNode { + const char* mStr; // never owns buffer + uint32_t mUnicode; + + bool operator<(const EntityNode& aOther) const { + return mUnicode < aOther.mUnicode || + (mUnicode == aOther.mUnicode && strcmp(mStr, aOther.mStr) < 0); + } +}; + +static const EntityNode gEntities[] = { + {"nbsp", 160}, {"iexcl", 161}, {"cent", 162}, {"pound", 163}, + {"curren", 164}, {"yen", 165}, {"brvbar", 166}, {"sect", 167}, + {"uml", 168}, {"copy", 169}, {"ordf", 170}, {"laquo", 171}, + {"not", 172}, {"shy", 173}, {"reg", 174}, {"macr", 175}}; + +#define ENTITY_COUNT (unsigned(sizeof(gEntities) / sizeof(EntityNode))) + +class EntityToUnicodeEntry : public PLDHashEntryHdr { + public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit EntityToUnicodeEntry(const char* aKey) { mNode = nullptr; } + EntityToUnicodeEntry(const EntityToUnicodeEntry& aEntry) { + mNode = aEntry.mNode; + } + ~EntityToUnicodeEntry() = default; + + bool KeyEquals(const char* aEntity) const { + return !strcmp(mNode->mStr, aEntity); + } + static const char* KeyToPointer(const char* aEntity) { return aEntity; } + static PLDHashNumber HashKey(const char* aEntity) { + return mozilla::HashString(aEntity); + } + enum { ALLOW_MEMMOVE = true }; + + const EntityNode* mNode; +}; + +static uint32_t nsTIterPrint(nsTHashtable<EntityToUnicodeEntry>& hash) { + uint32_t n = 0; + for (auto iter = hash.Iter(); !iter.Done(); iter.Next()) { + n++; + } + return n; +} + +static uint32_t nsTIterPrintRemove(nsTHashtable<EntityToUnicodeEntry>& hash) { + uint32_t n = 0; + for (auto iter = hash.Iter(); !iter.Done(); iter.Next()) { + iter.Remove(); + n++; + } + return n; +} + +static void testTHashtable(nsTHashtable<EntityToUnicodeEntry>& hash, + uint32_t numEntries) { + uint32_t i; + for (i = 0; i < numEntries; ++i) { + EntityToUnicodeEntry* entry = hash.PutEntry(gEntities[i].mStr); + + EXPECT_TRUE(entry); + + EXPECT_FALSE(entry->mNode); + entry->mNode = &gEntities[i]; + } + + for (i = 0; i < numEntries; ++i) { + EntityToUnicodeEntry* entry = hash.GetEntry(gEntities[i].mStr); + + EXPECT_TRUE(entry); + } + + EntityToUnicodeEntry* entry = hash.GetEntry("xxxy"); + + EXPECT_FALSE(entry); + + uint32_t count = nsTIterPrint(hash); + EXPECT_EQ(count, numEntries); + + for (const auto& entry : + const_cast<const nsTHashtable<EntityToUnicodeEntry>&>(hash)) { + static_assert(std::is_same_v<decltype(entry), const EntityToUnicodeEntry&>); + } + for (auto& entry : hash) { + static_assert(std::is_same_v<decltype(entry), EntityToUnicodeEntry&>); + } + + EXPECT_EQ(numEntries == ENTITY_COUNT ? 6 : 0, + std::count_if(hash.cbegin(), hash.cend(), [](const auto& entry) { + return entry.mNode->mUnicode >= 170; + })); +} + +// +// all this nsIFoo stuff was copied wholesale from TestCOMPtr.cpp +// + +#define NS_IFOO_IID \ + { \ + 0x6f7652e0, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class IFoo final : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + IFoo(); + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override; + NS_IMETHOD_(MozExternalRefCountType) Release() override; + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + NS_IMETHOD SetString(const nsACString& /*in*/ aString); + NS_IMETHOD GetString(nsACString& /*out*/ aString); + + static void print_totals(); + + private: + ~IFoo(); + + unsigned int refcount_; + + static unsigned int total_constructions_; + static unsigned int total_destructions_; + nsCString mString; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID) + +unsigned int IFoo::total_constructions_; +unsigned int IFoo::total_destructions_; + +void IFoo::print_totals() {} + +IFoo::IFoo() : refcount_(0) { ++total_constructions_; } + +IFoo::~IFoo() { ++total_destructions_; } + +MozExternalRefCountType IFoo::AddRef() { + ++refcount_; + return refcount_; +} + +MozExternalRefCountType IFoo::Release() { + int newcount = --refcount_; + if (newcount == 0) { + delete this; + } + + return newcount; +} + +nsresult IFoo::QueryInterface(const nsIID& aIID, void** aResult) { + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(IFoo))) + rawPtr = this; + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast<nsISupports*>(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +nsresult IFoo::SetString(const nsACString& aString) { + mString = aString; + return NS_OK; +} + +nsresult IFoo::GetString(nsACString& aString) { + aString = mString; + return NS_OK; +} + +static nsresult CreateIFoo(IFoo** result) +// a typical factory function (that calls AddRef) +{ + auto* foop = new IFoo(); + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +class DefaultConstructible { + public: + // Allow default construction. + DefaultConstructible() = default; + + // Construct/assign from a ref counted char. + explicit DefaultConstructible(RefPtr<TestUniCharRefCounted> aChar) + : mChar(std::move(aChar)) {} + + const RefPtr<TestUniCharRefCounted>& CharRef() const { return mChar; } + + // DefaultConstructible can be copied and moved. + DefaultConstructible(const DefaultConstructible&) = default; + DefaultConstructible& operator=(const DefaultConstructible&) = default; + DefaultConstructible(DefaultConstructible&&) = default; + DefaultConstructible& operator=(DefaultConstructible&&) = default; + + private: + RefPtr<TestUniCharRefCounted> mChar; +}; + +class MovingNonDefaultConstructible; + +class NonDefaultConstructible { + public: + // Construct/assign from a ref counted char. + explicit NonDefaultConstructible(RefPtr<TestUniCharRefCounted> aChar) + : mChar(std::move(aChar)) {} + + // Disallow default construction. + NonDefaultConstructible() = delete; + + MOZ_IMPLICIT NonDefaultConstructible(MovingNonDefaultConstructible&& aOther); + + const RefPtr<TestUniCharRefCounted>& CharRef() const { return mChar; } + + // NonDefaultConstructible can be copied, but not trivially (efficiently) + // moved. + NonDefaultConstructible(const NonDefaultConstructible&) = default; + NonDefaultConstructible& operator=(const NonDefaultConstructible&) = default; + + private: + RefPtr<TestUniCharRefCounted> mChar; +}; + +class MovingNonDefaultConstructible { + public: + // Construct/assign from a ref counted char. + explicit MovingNonDefaultConstructible(RefPtr<TestUniCharRefCounted> aChar) + : mChar(std::move(aChar)) {} + + MovingNonDefaultConstructible() = delete; + + MOZ_IMPLICIT MovingNonDefaultConstructible( + const NonDefaultConstructible& aSrc) + : mChar(aSrc.CharRef()) {} + + RefPtr<TestUniCharRefCounted> unwrapChar() && { return std::move(mChar); } + + // MovingNonDefaultConstructible can be moved, but not copied. + MovingNonDefaultConstructible(const MovingNonDefaultConstructible&) = delete; + MovingNonDefaultConstructible& operator=( + const MovingNonDefaultConstructible&) = delete; + MovingNonDefaultConstructible(MovingNonDefaultConstructible&&) = default; + MovingNonDefaultConstructible& operator=(MovingNonDefaultConstructible&&) = + default; + + private: + RefPtr<TestUniCharRefCounted> mChar; +}; + +NonDefaultConstructible::NonDefaultConstructible( + MovingNonDefaultConstructible&& aOther) + : mChar(std::move(aOther).unwrapChar()) {} + +struct DefaultConstructible_DefaultConstructible { + using DataType = DefaultConstructible; + using UserDataType = DefaultConstructible; + + static constexpr uint32_t kExpectedAddRefCnt_Contains = 1; + static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_Count = 1; + static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 1; + static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 3; + static constexpr uint32_t kExpectedAddRefCnt_Get = 3; + static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 3; + static constexpr uint32_t kExpectedAddRefCnt_LookupOrInsert = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible = + 1; + static constexpr uint32_t kExpectedAddRefCnt_Remove_OutputParam = 1; + static constexpr uint32_t kExpectedAddRefCnt_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Extract = 1; + static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd = 1; + static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd_OrInsert = 1; + static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd_OrRemove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Iter = 1; + static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 1; + static constexpr uint32_t kExpectedAddRefCnt_begin_end = 1; + static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 1; + static constexpr uint32_t kExpectedAddRefCnt_Clear = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 1; + static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 1; +}; + +struct NonDefaultConstructible_NonDefaultConstructible { + using DataType = NonDefaultConstructible; + using UserDataType = NonDefaultConstructible; + + static constexpr uint32_t kExpectedAddRefCnt_Contains = 2; + static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 2; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 3; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 3; + static constexpr uint32_t kExpectedAddRefCnt_Count = 2; + static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 2; + static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 5; + static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 5; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible = + 2; + static constexpr uint32_t kExpectedAddRefCnt_Remove = 2; + static constexpr uint32_t kExpectedAddRefCnt_Extract = 3; + static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 2; + static constexpr uint32_t kExpectedAddRefCnt_Lookup = 2; + static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 2; + static constexpr uint32_t kExpectedAddRefCnt_Iter = 2; + static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 2; + static constexpr uint32_t kExpectedAddRefCnt_begin_end = 2; + static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 2; + static constexpr uint32_t kExpectedAddRefCnt_Clear = 2; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 2; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 2; + static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 2; + static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 2; +}; + +struct NonDefaultConstructible_MovingNonDefaultConstructible { + using DataType = NonDefaultConstructible; + using UserDataType = MovingNonDefaultConstructible; + + static constexpr uint32_t kExpectedAddRefCnt_Contains = 1; + static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_Count = 1; + static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 1; + static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 2; + static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible = + 1; + static constexpr uint32_t kExpectedAddRefCnt_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Extract = 2; + static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Iter = 1; + static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 1; + static constexpr uint32_t kExpectedAddRefCnt_begin_end = 1; + static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 1; + static constexpr uint32_t kExpectedAddRefCnt_Clear = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 1; + static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 1; +}; + +template <bool flag = false> +void UnsupportedType() { + static_assert(flag, "Unsupported type!"); +} + +class TypeNames { + public: + template <typename T> + static std::string GetName(int) { + if constexpr (std::is_same<T, + DefaultConstructible_DefaultConstructible>()) { + return "DefaultConstructible_DefaultConstructible"; + } else if constexpr ( + std::is_same<T, NonDefaultConstructible_NonDefaultConstructible>()) { + return "NonDefaultConstructible_NonDefaultConstructible"; + } else if constexpr ( + std::is_same<T, + NonDefaultConstructible_MovingNonDefaultConstructible>()) { + return "NonDefaultConstructible_MovingNonDefaultConstructible"; + } else { + UnsupportedType(); + } + } +}; + +template <typename TypeParam> +auto MakeEmptyBaseHashtable() { + nsBaseHashtable<nsUint64HashKey, typename TypeParam::DataType, + typename TypeParam::UserDataType> + table; + + return table; +} + +template <typename TypeParam> +auto MakeBaseHashtable(const uint32_t aExpectedAddRefCnt) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + auto myChar = MakeRefPtr<TestUniCharRefCounted>(42, aExpectedAddRefCnt); + + table.InsertOrUpdate(1, typename TypeParam::UserDataType(std::move(myChar))); + + return table; +} + +template <typename TypeParam> +typename TypeParam::DataType GetDataFrom( + typename TypeParam::UserDataType& aUserData) { + if constexpr (std::is_same_v<TypeParam, + DefaultConstructible_DefaultConstructible> || + std::is_same_v< + TypeParam, + NonDefaultConstructible_NonDefaultConstructible>) { + return aUserData; + } else if constexpr ( + std::is_same_v<TypeParam, + NonDefaultConstructible_MovingNonDefaultConstructible>) { + return std::move(aUserData); + } else { + UnsupportedType(); + } +} + +template <typename TypeParam> +typename TypeParam::DataType GetDataFrom( + mozilla::Maybe<typename TypeParam::UserDataType>& aMaybeUserData) { + return GetDataFrom<TypeParam>(*aMaybeUserData); +} + +} // namespace TestHashtables + +using namespace TestHashtables; + +TEST(Hashtable, THashtable) +{ + // check an nsTHashtable + nsTHashtable<EntityToUnicodeEntry> EntityToUnicode(ENTITY_COUNT); + + testTHashtable(EntityToUnicode, 5); + + uint32_t count = nsTIterPrintRemove(EntityToUnicode); + ASSERT_EQ(count, uint32_t(5)); + + count = nsTIterPrint(EntityToUnicode); + ASSERT_EQ(count, uint32_t(0)); + + testTHashtable(EntityToUnicode, ENTITY_COUNT); + + EntityToUnicode.Clear(); + + count = nsTIterPrint(EntityToUnicode); + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtable, PtrHashtable) +{ + nsTHashtable<nsPtrHashKey<int>> hash; + + for (const auto& entry : + const_cast<const nsTHashtable<nsPtrHashKey<int>>&>(hash)) { + static_assert(std::is_same_v<decltype(entry), const nsPtrHashKey<int>&>); + } + for (auto& entry : hash) { + static_assert(std::is_same_v<decltype(entry), nsPtrHashKey<int>&>); + } +} + +TEST(Hashtable, Move) +{ + const void* kPtr = reinterpret_cast<void*>(static_cast<uintptr_t>(0xbadc0de)); + + nsTHashtable<nsPtrHashKey<const void>> table; + table.PutEntry(kPtr); + + nsTHashtable<nsPtrHashKey<const void>> moved = std::move(table); + ASSERT_EQ(table.Count(), 0u); + ASSERT_EQ(moved.Count(), 1u); + + EXPECT_TRUE(moved.Contains(kPtr)); + EXPECT_FALSE(table.Contains(kPtr)); +} + +TEST(Hashtable, Keys) +{ + static constexpr uint64_t count = 10; + + nsTHashtable<nsUint64HashKey> table; + for (uint64_t i = 0; i < count; i++) { + table.PutEntry(i); + } + + nsTArray<uint64_t> keys; + for (const uint64_t& key : table.Keys()) { + keys.AppendElement(key); + } + keys.Sort(); + + EXPECT_EQ((nsTArray<uint64_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +template <typename TypeParam> +class BaseHashtableTest : public ::testing::Test {}; + +TYPED_TEST_SUITE_P(BaseHashtableTest); + +TYPED_TEST_P(BaseHashtableTest, Contains) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Contains); + + auto res = table.Contains(1); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, GetGeneration) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_GetGeneration); + + auto res = table.GetGeneration(); + EXPECT_GT(res, 0u); +} + +MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); + +TYPED_TEST_P(BaseHashtableTest, SizeOfExcludingThis) { + // This doesn't compile at the moment, since nsBaseHashtableET lacks + // SizeOfExcludingThis implementation. Bug 1689214. +#if 0 + auto table = MakeBaseHashtable<TypeParam>( + TypeParam::kExpectedAddRefCnt_SizeOfExcludingThis); + + auto res = table.SizeOfExcludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +#endif +} + +TYPED_TEST_P(BaseHashtableTest, SizeOfIncludingThis) { + // This doesn't compile at the moment, since nsBaseHashtableET lacks + // SizeOfIncludingThis implementation. Bug 1689214. +#if 0 + auto table = MakeBaseHashtable<TypeParam>( + TypeParam::kExpectedAddRefCnt_SizeOfIncludingThis); + + auto res = table.SizeOfIncludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +#endif +} + +TYPED_TEST_P(BaseHashtableTest, Count) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Count); + + auto res = table.Count(); + EXPECT_EQ(res, 1u); +} + +TYPED_TEST_P(BaseHashtableTest, IsEmpty) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_IsEmpty); + + auto res = table.IsEmpty(); + EXPECT_EQ(res, false); +} + +TYPED_TEST_P(BaseHashtableTest, Get_OutputParam) { + auto table = MakeBaseHashtable<TypeParam>( + TypeParam::kExpectedAddRefCnt_Get_OutputParam); + + typename TypeParam::UserDataType userData(nullptr); + auto res = table.Get(1, &userData); + EXPECT_TRUE(res); + + auto data = GetDataFrom<TypeParam>(userData); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, Get) { + // The Get overload can't support non-default-constructible UserDataType. + if constexpr (std::is_default_constructible_v< + typename TypeParam::UserDataType>) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Get); + + auto userData = table.Get(1); + + auto data = GetDataFrom<TypeParam>(userData); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, MaybeGet) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_MaybeGet); + + auto maybeUserData = table.MaybeGet(1); + EXPECT_TRUE(maybeUserData); + + auto data = GetDataFrom<TypeParam>(maybeUserData); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_Default) { + if constexpr (std::is_default_constructible_v<typename TypeParam::DataType>) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + typename TypeParam::DataType& data = table.LookupOrInsert(1); + EXPECT_EQ(data.CharRef(), nullptr); + + data = typename TypeParam::DataType(MakeRefPtr<TestUniCharRefCounted>( + 42, TypeParam::kExpectedAddRefCnt_LookupOrInsert)); + } +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_NonDefault) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + typename TypeParam::DataType& data = table.LookupOrInsert( + 1, typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)}); + EXPECT_NE(data.CharRef(), nullptr); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_NonDefault_AlreadyPresent) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + typename TypeParam::DataType& data1 = table.LookupOrInsert( + 1, typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)}); + TestUniCharRefCounted* const address = data1.CharRef(); + typename TypeParam::DataType& data2 = table.LookupOrInsert( + 1, + typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42, 1)}); + EXPECT_EQ(&data1, &data2); + EXPECT_EQ(address, data2.CharRef()); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsertWith) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + typename TypeParam::DataType& data = table.LookupOrInsertWith(1, [] { + return typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)}; + }); + EXPECT_NE(data.CharRef(), nullptr); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsertWith_AlreadyPresent) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + table.LookupOrInsertWith(1, [] { + return typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)}; + }); + table.LookupOrInsertWith(1, [] { + ADD_FAILURE(); + return typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)}; + }); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + auto myChar = MakeRefPtr<TestUniCharRefCounted>( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate); + + table.InsertOrUpdate(1, typename TypeParam::UserDataType(std::move(myChar))); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Fallible) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + auto myChar = MakeRefPtr<TestUniCharRefCounted>( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Fallible); + + auto res = table.InsertOrUpdate( + 1, typename TypeParam::UserDataType(std::move(myChar)), + mozilla::fallible); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Rvalue) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + auto myChar = MakeRefPtr<TestUniCharRefCounted>( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Rvalue); + + table.InsertOrUpdate( + 1, std::move(typename TypeParam::UserDataType(std::move(myChar)))); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Rvalue_Fallible) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + auto myChar = MakeRefPtr<TestUniCharRefCounted>( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible); + + auto res = table.InsertOrUpdate( + 1, std::move(typename TypeParam::UserDataType(std::move(myChar))), + mozilla::fallible); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, Remove_OutputParam) { + // The Remove overload can't support non-default-constructible DataType. + if constexpr (std::is_default_constructible_v<typename TypeParam::DataType>) { + auto table = MakeBaseHashtable<TypeParam>( + TypeParam::kExpectedAddRefCnt_Remove_OutputParam); + + typename TypeParam::DataType data; + auto res = table.Remove(1, &data); + EXPECT_TRUE(res); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, Remove) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Remove); + + auto res = table.Remove(1); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, Extract) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Extract); + + auto maybeData = table.Extract(1); + EXPECT_TRUE(maybeData); + EXPECT_EQ(maybeData->CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, RemoveIf) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_RemoveIf); + + table.RemoveIf([](const auto&) { return true; }); +} + +TYPED_TEST_P(BaseHashtableTest, Lookup) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Lookup); + + auto res = table.Lookup(1); + EXPECT_TRUE(res); + EXPECT_EQ(res.Data().CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, Lookup_Remove) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Lookup_Remove); + + auto res = table.Lookup(1); + EXPECT_TRUE(res); + EXPECT_EQ(res.Data().CharRef()->GetChar(), 42u); + + res.Remove(); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NoOp) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + table.WithEntryHandle(1, [](auto&&) {}); + + EXPECT_FALSE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsert) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsert(typename TypeParam::UserDataType( + MakeRefPtr<TestUniCharRefCounted>(42))); + }); + + EXPECT_TRUE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsertFrom) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([] { + return typename TypeParam::UserDataType( + MakeRefPtr<TestUniCharRefCounted>(42)); + }); + }); + + EXPECT_TRUE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsertFrom_Exists) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([] { + return typename TypeParam::UserDataType( + MakeRefPtr<TestUniCharRefCounted>(42)); + }); + }); + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([]() -> typename TypeParam::UserDataType { + ADD_FAILURE(); + return typename TypeParam::UserDataType( + MakeRefPtr<TestUniCharRefCounted>(42)); + }); + }); + + EXPECT_TRUE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrRemove) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + table.WithEntryHandle(1, [](auto&& entry) { entry.OrRemove(); }); + + EXPECT_FALSE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrRemove_Exists) { + auto table = MakeEmptyBaseHashtable<TypeParam>(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([] { + return typename TypeParam::UserDataType( + MakeRefPtr<TestUniCharRefCounted>(42)); + }); + }); + table.WithEntryHandle(1, [](auto&& entry) { entry.OrRemove(); }); + + EXPECT_FALSE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, Iter) { + auto table = MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Iter); + + for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + EXPECT_EQ(iter.Data().CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, ConstIter) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_ConstIter); + + for (auto iter = table.ConstIter(); !iter.Done(); iter.Next()) { + EXPECT_EQ(iter.Data().CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, begin_end) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_begin_end); + + auto res = std::count_if(table.begin(), table.end(), [](const auto& entry) { + return entry.GetData().CharRef()->GetChar() == 42; + }); + EXPECT_EQ(res, 1); +} + +TYPED_TEST_P(BaseHashtableTest, cbegin_cend) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_cbegin_cend); + + auto res = std::count_if(table.cbegin(), table.cend(), [](const auto& entry) { + return entry.GetData().CharRef()->GetChar() == 42; + }); + EXPECT_EQ(res, 1); +} + +TYPED_TEST_P(BaseHashtableTest, Clear) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Clear); + + table.Clear(); +} + +TYPED_TEST_P(BaseHashtableTest, ShallowSizeOfExcludingThis) { + auto table = MakeBaseHashtable<TypeParam>( + TypeParam::kExpectedAddRefCnt_ShallowSizeOfExcludingThis); + + auto res = table.ShallowSizeOfExcludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +} + +TYPED_TEST_P(BaseHashtableTest, ShallowSizeOfIncludingThis) { + // Make this work with ASAN builds, bug 1689549. +#if !defined(MOZ_ASAN) + auto table = MakeBaseHashtable<TypeParam>( + TypeParam::kExpectedAddRefCnt_ShallowSizeOfIncludingThis); + + auto res = table.ShallowSizeOfIncludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +#endif +} + +TYPED_TEST_P(BaseHashtableTest, SwapElements) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_SwapElements); + + auto table2 = MakeEmptyBaseHashtable<TypeParam>(); + + table.SwapElements(table2); +} + +TYPED_TEST_P(BaseHashtableTest, MarkImmutable) { + auto table = + MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_MarkImmutable); + + table.MarkImmutable(); +} + +REGISTER_TYPED_TEST_SUITE_P( + BaseHashtableTest, Contains, GetGeneration, SizeOfExcludingThis, + SizeOfIncludingThis, Count, IsEmpty, Get_OutputParam, Get, MaybeGet, + LookupOrInsert_Default, LookupOrInsert_NonDefault, + LookupOrInsert_NonDefault_AlreadyPresent, LookupOrInsertWith, + LookupOrInsertWith_AlreadyPresent, InsertOrUpdate, InsertOrUpdate_Fallible, + InsertOrUpdate_Rvalue, InsertOrUpdate_Rvalue_Fallible, Remove_OutputParam, + Remove, Extract, RemoveIf, Lookup, Lookup_Remove, WithEntryHandle_NoOp, + WithEntryHandle_NotFound_OrInsert, WithEntryHandle_NotFound_OrInsertFrom, + WithEntryHandle_NotFound_OrInsertFrom_Exists, + WithEntryHandle_NotFound_OrRemove, WithEntryHandle_NotFound_OrRemove_Exists, + Iter, ConstIter, begin_end, cbegin_cend, Clear, ShallowSizeOfExcludingThis, + ShallowSizeOfIncludingThis, SwapElements, MarkImmutable); + +using BaseHashtableTestTypes = + ::testing::Types<DefaultConstructible_DefaultConstructible, + NonDefaultConstructible_NonDefaultConstructible, + NonDefaultConstructible_MovingNonDefaultConstructible>; + +INSTANTIATE_TYPED_TEST_SUITE_P(Hashtables, BaseHashtableTest, + BaseHashtableTestTypes, TypeNames); + +TEST(Hashtables, DataHashtable) +{ + // check a data-hashtable + nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr); + } + + const char* str; + + for (auto& entity : gEntities) { + ASSERT_TRUE(UniToEntity.Get(entity.mUnicode, &str)); + } + + ASSERT_FALSE(UniToEntity.Get(99446, &str)); + + uint32_t count = 0; + for (auto iter = UniToEntity.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + UniToEntity.Clear(); + + count = 0; + for (auto iter = UniToEntity.Iter(); !iter.Done(); iter.Next()) { + printf(" enumerated %u = \"%s\"\n", iter.Key(), iter.Data()); + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, DataHashtable_STLIterators) +{ + using mozilla::Unused; + + nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr); + } + + // operators, including conversion from iterator to const_iterator + nsTHashMap<nsUint32HashKey, const char*>::const_iterator ci = + UniToEntity.begin(); + ++ci; + ASSERT_EQ(1, std::distance(UniToEntity.cbegin(), ci++)); + ASSERT_EQ(2, std::distance(UniToEntity.cbegin(), ci)); + ASSERT_TRUE(ci == ci); + auto otherCi = ci; + ++otherCi; + ++ci; + ASSERT_TRUE(&*ci == &*otherCi); + + // STL algorithms (just to check that the iterator sufficiently conforms + // with the actual syntactical requirements of those algorithms). + std::for_each(UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& entry) {}); + Unused << std::find_if( + UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& entry) { return entry.GetKey() == 42; }); + Unused << std::accumulate( + UniToEntity.cbegin(), UniToEntity.cend(), 0u, + [](size_t sum, const auto& entry) { return sum + entry.GetKey(); }); + Unused << std::any_of(UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& entry) { return entry.GetKey() == 42; }); + Unused << std::max_element(UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& lhs, const auto& rhs) { + return lhs.GetKey() > rhs.GetKey(); + }); + + // const range-based for + { + std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT); + for (const auto& entity : + const_cast<const nsTHashMap<nsUint32HashKey, const char*>&>( + UniToEntity)) { + ASSERT_EQ(1u, + entities.erase(EntityNode{entity.GetData(), entity.GetKey()})); + } + ASSERT_TRUE(entities.empty()); + } + + // non-const range-based for + { + std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT); + for (auto& entity : UniToEntity) { + ASSERT_EQ(1u, + entities.erase(EntityNode{entity.GetData(), entity.GetKey()})); + + entity.SetData(nullptr); + ASSERT_EQ(nullptr, entity.GetData()); + } + ASSERT_TRUE(entities.empty()); + } +} + +TEST(Hashtables, DataHashtable_RemoveIf) +{ + // check a data-hashtable + nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr); + } + + UniToEntity.RemoveIf([](const auto& iter) { return iter.Key() >= 170; }); + + ASSERT_EQ(10u, UniToEntity.Count()); +} + +TEST(Hashtables, ClassHashtable) +{ + // check a class-hashtable + nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT); + + for (auto& entity : gEntities) { + // Insert a sub-class of TestUniChar to test if this is accepted by + // InsertOrUpdate. + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + mozilla::MakeUnique<TestUniCharDerived>(entity.mUnicode)); + } + + TestUniChar* myChar; + + for (auto& entity : gEntities) { + ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(entity.mStr), &myChar)); + } + + ASSERT_FALSE(EntToUniClass.Get("xxxx"_ns, &myChar)); + + uint32_t count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + EntToUniClass.Clear(); + + count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, ClassHashtable_RangeBasedFor) +{ + // check a class-hashtable + nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate(nsDependentCString(entity.mStr), + MakeUnique<TestUniChar>(entity.mUnicode)); + } + + // const range-based for + { + std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT); + for (const auto& entity : + const_cast<const nsClassHashtable<nsCStringHashKey, TestUniChar>&>( + EntToUniClass)) { + const char* str; + entity.GetKey().GetData(&str); + ASSERT_EQ(1u, + entities.erase(EntityNode{str, entity.GetData()->GetChar()})); + } + ASSERT_TRUE(entities.empty()); + } + + // non-const range-based for + { + std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT); + for (auto& entity : EntToUniClass) { + const char* str; + entity.GetKey().GetData(&str); + ASSERT_EQ(1u, + entities.erase(EntityNode{str, entity.GetData()->GetChar()})); + + entity.SetData(UniquePtr<TestUniChar>{}); + ASSERT_EQ(nullptr, entity.GetData()); + } + ASSERT_TRUE(entities.empty()); + } +} + +TEST(Hashtables, DataHashtableWithInterfaceKey) +{ + // check a data-hashtable with an interface key + nsTHashMap<nsISupportsHashKey, uint32_t> EntToUniClass2(ENTITY_COUNT); + + nsCOMArray<IFoo> fooArray; + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + nsCOMPtr<IFoo> foo; + CreateIFoo(getter_AddRefs(foo)); + foo->SetString(nsDependentCString(gEntities[i].mStr)); + + fooArray.InsertObjectAt(foo, i); + + EntToUniClass2.InsertOrUpdate(foo, gEntities[i].mUnicode); + } + + uint32_t myChar2; + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + ASSERT_TRUE(EntToUniClass2.Get(fooArray[i], &myChar2)); + } + + ASSERT_FALSE(EntToUniClass2.Get((nsISupports*)0x55443316, &myChar2)); + + uint32_t count = 0; + for (auto iter = EntToUniClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + nsCOMPtr<IFoo> foo = do_QueryInterface(iter.Key()); + foo->GetString(s); + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + EntToUniClass2.Clear(); + + count = 0; + for (auto iter = EntToUniClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + nsCOMPtr<IFoo> foo = do_QueryInterface(iter.Key()); + foo->GetString(s); + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, InterfaceHashtable) +{ + // check an interface-hashtable with an uint32_t key + nsInterfaceHashtable<nsUint32HashKey, IFoo> UniToEntClass2(ENTITY_COUNT); + + for (auto& entity : gEntities) { + nsCOMPtr<IFoo> foo; + CreateIFoo(getter_AddRefs(foo)); + foo->SetString(nsDependentCString(entity.mStr)); + + UniToEntClass2.InsertOrUpdate(entity.mUnicode, foo); + } + + for (auto& entity : gEntities) { + nsCOMPtr<IFoo> myEnt; + ASSERT_TRUE(UniToEntClass2.Get(entity.mUnicode, getter_AddRefs(myEnt))); + + nsAutoCString myEntStr; + myEnt->GetString(myEntStr); + } + + nsCOMPtr<IFoo> myEnt; + ASSERT_FALSE(UniToEntClass2.Get(9462, getter_AddRefs(myEnt))); + + uint32_t count = 0; + for (auto iter = UniToEntClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + iter.UserData()->GetString(s); + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + UniToEntClass2.Clear(); + + count = 0; + for (auto iter = UniToEntClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + iter.Data()->GetString(s); + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, DataHashtable_WithEntryHandle) +{ + // check WithEntryHandle/OrInsertWith + nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, [&entity](auto&& entry) { + EXPECT_FALSE(entry); + const char* const val = + entry.OrInsertWith([&entity]() { return entity.mStr; }); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == entity.mStr); + EXPECT_TRUE(entry.Data() == entity.mStr); + }); + } + + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, + [](auto&& entry) { EXPECT_TRUE(entry); }); + } + + // 0 should not be found + size_t count = UniToEntity.Count(); + UniToEntity.Lookup(0U).Remove(); + ASSERT_TRUE(count == UniToEntity.Count()); + + // Lookup should find all entries + count = 0; + for (auto& entity : gEntities) { + if (UniToEntity.Lookup(entity.mUnicode)) { + count++; + } + } + ASSERT_TRUE(count == UniToEntity.Count()); + + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, + [](auto&& entry) { EXPECT_TRUE(entry); }); + } + + // Lookup().Remove() should remove all entries. + for (auto& entity : gEntities) { + if (auto entry = UniToEntity.Lookup(entity.mUnicode)) { + entry.Remove(); + } + } + ASSERT_TRUE(0 == UniToEntity.Count()); + + // Remove newly added entries via OrRemove. + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, [](auto&& entry) { + EXPECT_FALSE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == UniToEntity.Count()); + + // Remove existing entries via OrRemove. + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, [&entity](auto&& entry) { + EXPECT_FALSE(entry); + const char* const val = entry.OrInsert(entity.mStr); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == entity.mStr); + EXPECT_TRUE(entry.Data() == entity.mStr); + }); + + UniToEntity.WithEntryHandle(entity.mUnicode, [](auto&& entry) { + EXPECT_TRUE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == UniToEntity.Count()); +} + +TEST(Hashtables, ClassHashtable_WithEntryHandle) +{ + // check a class-hashtable WithEntryHandle with null values + nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle( + nsDependentCString(entity.mStr), [](auto&& entry) { + EXPECT_FALSE(entry); + const TestUniChar* val = entry.OrInsert(nullptr).get(); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == nullptr); + EXPECT_TRUE(entry.Data() == nullptr); + }); + } + + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { EXPECT_TRUE(entry); }); + EntToUniClass.WithEntryHandle( + nsDependentCString(entity.mStr), + [](auto&& entry) { EXPECT_TRUE(entry.Data() == nullptr); }); + } + + // "" should not be found + size_t count = EntToUniClass.Count(); + EntToUniClass.Lookup(nsDependentCString("")).Remove(); + ASSERT_TRUE(count == EntToUniClass.Count()); + + // Lookup should find all entries. + count = 0; + for (auto& entity : gEntities) { + if (EntToUniClass.Lookup(nsDependentCString(entity.mStr))) { + count++; + } + } + ASSERT_TRUE(count == EntToUniClass.Count()); + + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { EXPECT_TRUE(entry); }); + } + + // Lookup().Remove() should remove all entries. + for (auto& entity : gEntities) { + if (auto entry = EntToUniClass.Lookup(nsDependentCString(entity.mStr))) { + entry.Remove(); + } + } + ASSERT_TRUE(0 == EntToUniClass.Count()); + + // Remove newly added entries via OrRemove. + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { + EXPECT_FALSE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == EntToUniClass.Count()); + + // Remove existing entries via OrRemove. + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle( + nsDependentCString(entity.mStr), [](auto&& entry) { + EXPECT_FALSE(entry); + const TestUniChar* val = entry.OrInsert(nullptr).get(); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == nullptr); + EXPECT_TRUE(entry.Data() == nullptr); + }); + + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { + EXPECT_TRUE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == EntToUniClass.Count()); +} + +TEST(Hashtables, ClassHashtable_GetOrInsertNew_Present) +{ + nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT); + + for (const auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + mozilla::MakeUnique<TestUniCharDerived>(entity.mUnicode)); + } + + auto* entry = EntToUniClass.GetOrInsertNew("uml"_ns, 42); + EXPECT_EQ(168u, entry->GetChar()); +} + +TEST(Hashtables, ClassHashtable_GetOrInsertNew_NotPresent) +{ + nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT); + + // This is going to insert a TestUniChar. + auto* entry = EntToUniClass.GetOrInsertNew("uml"_ns, 42); + EXPECT_EQ(42u, entry->GetChar()); +} + +TEST(Hashtables, ClassHashtable_LookupOrInsertWith_Present) +{ + nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT); + + for (const auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + mozilla::MakeUnique<TestUniCharDerived>(entity.mUnicode)); + } + + const auto& entry = EntToUniClass.LookupOrInsertWith( + "uml"_ns, [] { return mozilla::MakeUnique<TestUniCharDerived>(42); }); + EXPECT_EQ(168u, entry->GetChar()); +} + +TEST(Hashtables, ClassHashtable_LookupOrInsertWith_NotPresent) +{ + nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT); + + // This is going to insert a TestUniCharDerived. + const auto& entry = EntToUniClass.LookupOrInsertWith( + "uml"_ns, [] { return mozilla::MakeUnique<TestUniCharDerived>(42); }); + EXPECT_EQ(42u, entry->GetChar()); +} + +TEST(Hashtables, RefPtrHashtable) +{ + // check a RefPtr-hashtable + nsRefPtrHashtable<nsCStringHashKey, TestUniCharRefCounted> EntToUniClass( + ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + MakeRefPtr<TestUniCharRefCounted>(entity.mUnicode)); + } + + TestUniCharRefCounted* myChar; + + for (auto& entity : gEntities) { + ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(entity.mStr), &myChar)); + } + + ASSERT_FALSE(EntToUniClass.Get("xxxx"_ns, &myChar)); + + uint32_t count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + EntToUniClass.Clear(); + + count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, RefPtrHashtable_Clone) +{ + // check a RefPtr-hashtable + nsRefPtrHashtable<nsCStringHashKey, TestUniCharRefCounted> EntToUniClass( + ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + MakeRefPtr<TestUniCharRefCounted>(entity.mUnicode)); + } + + auto clone = EntToUniClass.Clone(); + static_assert(std::is_same_v<decltype(clone), decltype(EntToUniClass)>); + + EXPECT_EQ(clone.Count(), EntToUniClass.Count()); + + for (const auto& entry : EntToUniClass) { + auto cloneEntry = clone.Lookup(entry.GetKey()); + + EXPECT_TRUE(cloneEntry); + EXPECT_EQ(cloneEntry.Data(), entry.GetWeak()); + } +} + +TEST(Hashtables, Clone) +{ + static constexpr uint64_t count = 10; + + nsTHashMap<nsUint64HashKey, uint64_t> table; + for (uint64_t i = 0; i < count; i++) { + table.InsertOrUpdate(42 + i, i); + } + + auto clone = table.Clone(); + + static_assert(std::is_same_v<decltype(clone), decltype(table)>); + + EXPECT_EQ(clone.Count(), table.Count()); + + for (const auto& entry : table) { + auto cloneEntry = clone.Lookup(entry.GetKey()); + + EXPECT_TRUE(cloneEntry); + EXPECT_EQ(cloneEntry.Data(), entry.GetData()); + } +} + +TEST(Hashtables, Values) +{ + static constexpr uint64_t count = 10; + + nsTHashMap<nsUint64HashKey, uint64_t> table; + for (uint64_t i = 0; i < count; i++) { + table.InsertOrUpdate(42 + i, i); + } + + nsTArray<uint64_t> values; + for (const uint64_t& value : table.Values()) { + values.AppendElement(value); + } + values.Sort(); + + EXPECT_EQ((nsTArray<uint64_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), values); +} diff --git a/xpcom/tests/gtest/TestID.cpp b/xpcom/tests/gtest/TestID.cpp new file mode 100644 index 0000000000..65b41a2b26 --- /dev/null +++ b/xpcom/tests/gtest/TestID.cpp @@ -0,0 +1,33 @@ +/* -*- 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 "nsID.h" + +#include "gtest/gtest.h" + +static const char* const ids[] = { + "5C347B10-D55C-11D1-89B7-006008911B81", + "{5C347B10-D55C-11D1-89B7-006008911B81}", + "5c347b10-d55c-11d1-89b7-006008911b81", + "{5c347b10-d55c-11d1-89b7-006008911b81}", + + "FC347B10-D55C-F1D1-F9B7-006008911B81", + "{FC347B10-D55C-F1D1-F9B7-006008911B81}", + "fc347b10-d55c-f1d1-f9b7-006008911b81", + "{fc347b10-d55c-f1d1-f9b7-006008911b81}", +}; +#define NUM_IDS ((int)(sizeof(ids) / sizeof(ids[0]))) + +TEST(nsID, StringConversion) +{ + nsID id; + for (int i = 0; i < NUM_IDS; i++) { + const char* idstr = ids[i]; + ASSERT_TRUE(id.Parse(idstr)); + + auto cp = id.ToString(); + ASSERT_STREQ(cp.get(), ids[4 * (i / 4) + 3]); + } +} diff --git a/xpcom/tests/gtest/TestIDUtils.cpp b/xpcom/tests/gtest/TestIDUtils.cpp new file mode 100644 index 0000000000..adf6a96611 --- /dev/null +++ b/xpcom/tests/gtest/TestIDUtils.cpp @@ -0,0 +1,36 @@ +/* -*- 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 "nsID.h" +#include "nsIDUtils.h" + +#include "gtest/gtest.h" + +static const char* const bare_ids[] = { + "5c347b10-d55c-11d1-89b7-006008911b81", + "fc347b10-d55c-f1d1-f9b7-006008911b81", +}; + +TEST(nsIDUtils, NSID_TrimBracketsUTF16) +{ + nsID id{}; + for (const auto* idstr : bare_ids) { + ASSERT_TRUE(id.Parse(idstr)); + + NSID_TrimBracketsUTF16 trimmed(id); + ASSERT_TRUE(trimmed.EqualsASCII(idstr)); + } +} + +TEST(nsIDUtils, NSID_TrimBracketsASCII) +{ + nsID id{}; + for (const auto* idstr : bare_ids) { + ASSERT_TRUE(id.Parse(idstr)); + + NSID_TrimBracketsASCII trimmed(id); + ASSERT_TRUE(trimmed.EqualsASCII(idstr)); + } +} diff --git a/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp b/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp new file mode 100644 index 0000000000..5bb3f2bbe4 --- /dev/null +++ b/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp @@ -0,0 +1,161 @@ +#include "gtest/gtest.h" + +#include "mozilla/InputStreamLengthHelper.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "Helpers.h" + +using namespace mozilla; + +TEST(TestInputStreamLengthHelper, NonLengthStream) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(int64_t(buf.Length()), aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, NonLengthStream)"_ns, + [&]() { return called; })); +} + +class LengthStream final : public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStream { + public: + NS_DECL_ISUPPORTS + + LengthStream(int64_t aLength, nsresult aLengthRv, uint64_t aAvailable, + bool aIsAsyncLength) + : mLength(aLength), + mLengthRv(aLengthRv), + mAvailable(aAvailable), + mIsAsyncLength(aIsAsyncLength) {} + + NS_IMETHOD Close(void) override { MOZ_CRASH("Invalid call!"); } + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override { + MOZ_CRASH("Invalid call!"); + } + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) override { + MOZ_CRASH("Invalid call!"); + } + NS_IMETHOD IsNonBlocking(bool* _retval) override { + MOZ_CRASH("Invalid call!"); + } + + NS_IMETHOD Length(int64_t* aLength) override { + *aLength = mLength; + return mLengthRv; + } + + NS_IMETHOD AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) override { + if (aCallback) { + aCallback->OnInputStreamLengthReady(this, mLength); + } + return NS_OK; + } + + NS_IMETHOD Available(uint64_t* aAvailable) override { + *aAvailable = mAvailable; + return NS_OK; + } + + NS_IMETHOD StreamStatus() override { return NS_OK; } + + private: + ~LengthStream() = default; + + int64_t mLength; + nsresult mLengthRv; + uint64_t mAvailable; + + bool mIsAsyncLength; +}; + +NS_IMPL_ADDREF(LengthStream); +NS_IMPL_RELEASE(LengthStream); + +NS_INTERFACE_MAP_BEGIN(LengthStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, mIsAsyncLength) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +TEST(TestInputStreamLengthHelper, LengthStream) +{ + nsCOMPtr<nsIInputStream> stream = new LengthStream(42, NS_OK, 0, false); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(42, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, LengthStream)"_ns, + [&]() { return called; })); +} + +TEST(TestInputStreamLengthHelper, InvalidLengthStream) +{ + nsCOMPtr<nsIInputStream> stream = + new LengthStream(42, NS_ERROR_NOT_AVAILABLE, 0, false); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(-1, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, InvalidLengthStream)"_ns, + [&]() { return called; })); +} + +TEST(TestInputStreamLengthHelper, AsyncLengthStream) +{ + nsCOMPtr<nsIInputStream> stream = + new LengthStream(22, NS_BASE_STREAM_WOULD_BLOCK, 123, true); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(22, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, AsyncLengthStream)"_ns, + [&]() { return called; })); +} + +TEST(TestInputStreamLengthHelper, FallbackLengthStream) +{ + nsCOMPtr<nsIInputStream> stream = + new LengthStream(-1, NS_BASE_STREAM_WOULD_BLOCK, 123, false); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(123, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, FallbackLengthStream)"_ns, + [&]() { return called; })); +} diff --git a/xpcom/tests/gtest/TestJSHolderMap.cpp b/xpcom/tests/gtest/TestJSHolderMap.cpp new file mode 100644 index 0000000000..4b05459499 --- /dev/null +++ b/xpcom/tests/gtest/TestJSHolderMap.cpp @@ -0,0 +1,348 @@ +/* -*- 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 "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" + +#include "js/GCAPI.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +enum HolderKind { SingleZone, MultiZone }; + +class MyHolder final : public nsScriptObjectTracer { + public: + explicit MyHolder(HolderKind kind = SingleZone, size_t value = 0) + : nsScriptObjectTracer(FlagsForKind(kind)), value(value) {} + + const size_t value; + + NS_IMETHOD_(void) Root(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) Unlink(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) Unroot(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) DeleteCycleCollectable(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override { + MOZ_CRASH(); + } + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override { + MOZ_CRASH(); + } + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(MyHolder) + + private: + Flags FlagsForKind(HolderKind kind) { + return kind == MultiZone ? FlagMultiZoneJSHolder + : FlagMaybeSingleZoneJSHolder; + } +}; + +static size_t CountEntries(JSHolderMap& map) { + size_t count = 0; + for (JSHolderMap::Iter i(map); !i.Done(); i.Next()) { + MOZ_RELEASE_ASSERT(i->mHolder); + MOZ_RELEASE_ASSERT(i->mTracer); + count++; + } + return count; +} + +JS::Zone* DummyZone = reinterpret_cast<JS::Zone*>(1); + +JS::Zone* ZoneForKind(HolderKind kind) { + return kind == MultiZone ? nullptr : DummyZone; +} + +TEST(JSHolderMap, Empty) +{ + JSHolderMap map; + ASSERT_EQ(CountEntries(map), 0u); +} + +static void TestAddAndRemove(HolderKind kind) { + JSHolderMap map; + + MyHolder holder(kind); + nsScriptObjectTracer* tracer = &holder; + + ASSERT_FALSE(map.Has(&holder)); + ASSERT_EQ(map.Extract(&holder), nullptr); + + map.Put(&holder, tracer, ZoneForKind(kind)); + ASSERT_TRUE(map.Has(&holder)); + ASSERT_EQ(CountEntries(map), 1u); + ASSERT_EQ(map.Get(&holder), tracer); + + ASSERT_EQ(map.Extract(&holder), tracer); + ASSERT_EQ(map.Extract(&holder), nullptr); + ASSERT_FALSE(map.Has(&holder)); + ASSERT_EQ(CountEntries(map), 0u); +} + +TEST(JSHolderMap, AddAndRemove) +{ + TestAddAndRemove(SingleZone); + TestAddAndRemove(MultiZone); +} + +static void TestIterate(HolderKind kind) { + JSHolderMap map; + + MyHolder holder(kind, 0); + nsScriptObjectTracer* tracer = &holder; + + Maybe<JSHolderMap::Iter> iter; + + // Iterate an empty map. + iter.emplace(map); + ASSERT_TRUE(iter->Done()); + iter.reset(); + + // Iterate a map with one entry. + map.Put(&holder, tracer, ZoneForKind(kind)); + iter.emplace(map); + ASSERT_FALSE(iter->Done()); + ASSERT_EQ(iter->Get().mHolder, &holder); + iter->Next(); + ASSERT_TRUE(iter->Done()); + iter.reset(); + + // Iterate a map with 10 entries. + constexpr size_t count = 10; + Vector<UniquePtr<MyHolder>, 0, InfallibleAllocPolicy> holders; + bool seen[count] = {}; + for (size_t i = 1; i < count; i++) { + MOZ_ALWAYS_TRUE( + holders.emplaceBack(mozilla::MakeUnique<MyHolder>(kind, i))); + map.Put(holders.back().get(), tracer, ZoneForKind(kind)); + } + for (iter.emplace(map); !iter->Done(); iter->Next()) { + MyHolder* holder = static_cast<MyHolder*>(iter->Get().mHolder); + size_t value = holder->value; + ASSERT_TRUE(value < count); + ASSERT_FALSE(seen[value]); + seen[value] = true; + } + for (const auto& s : seen) { + ASSERT_TRUE(s); + } +} + +TEST(JSHolderMap, Iterate) +{ + TestIterate(SingleZone); + TestIterate(MultiZone); +} + +static void TestAddRemoveMany(HolderKind kind, size_t count) { + JSHolderMap map; + + Vector<UniquePtr<MyHolder>, 0, InfallibleAllocPolicy> holders; + for (size_t i = 0; i < count; i++) { + MOZ_ALWAYS_TRUE(holders.emplaceBack(mozilla::MakeUnique<MyHolder>(kind))); + } + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + map.Put(holder, holder, ZoneForKind(kind)); + } + + ASSERT_EQ(CountEntries(map), count); + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + ASSERT_EQ(map.Extract(holder), holder); + } + + ASSERT_EQ(CountEntries(map), 0u); +} + +TEST(JSHolderMap, TestAddRemoveMany) +{ + TestAddRemoveMany(SingleZone, 10000); + TestAddRemoveMany(MultiZone, 10000); +} + +static void TestRemoveWhileIterating(HolderKind kind, size_t count) { + JSHolderMap map; + Vector<UniquePtr<MyHolder>, 0, InfallibleAllocPolicy> holders; + Maybe<JSHolderMap::Iter> iter; + + for (size_t i = 0; i < count; i++) { + MOZ_ALWAYS_TRUE(holders.emplaceBack(MakeUnique<MyHolder>(kind))); + } + + // Iterate a map with one entry but remove it before we get to it. + MyHolder* holder = holders[0].get(); + map.Put(holder, holder, ZoneForKind(kind)); + iter.emplace(map); + ASSERT_FALSE(iter->Done()); + ASSERT_EQ(map.Extract(holder), holder); + iter->UpdateForRemovals(); + ASSERT_TRUE(iter->Done()); + + // Check UpdateForRemovals is safe to call on a done iterator. + iter->UpdateForRemovals(); + ASSERT_TRUE(iter->Done()); + iter.reset(); + + // Add many holders and remove them mid way through iteration. + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + map.Put(holder, holder, ZoneForKind(kind)); + } + + iter.emplace(map); + for (size_t i = 0; i < count / 2; i++) { + iter->Next(); + ASSERT_FALSE(iter->Done()); + } + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + ASSERT_EQ(map.Extract(holder), holder); + } + + iter->UpdateForRemovals(); + + ASSERT_TRUE(iter->Done()); + iter.reset(); + + ASSERT_EQ(CountEntries(map), 0u); +} + +TEST(JSHolderMap, TestRemoveWhileIterating) +{ + TestRemoveWhileIterating(SingleZone, 10000); + TestRemoveWhileIterating(MultiZone, 10000); +} + +class ObjectHolder final { + public: + ObjectHolder() { HoldJSObjects(this); } + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ObjectHolder) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(ObjectHolder) + + void SetObject(JSObject* aObject) { mObject = aObject; } + + void ClearObject() { mObject = nullptr; } + + JSObject* GetObject() const { return mObject; } + JSObject* GetObjectUnbarriered() const { return mObject.unbarrieredGet(); } + + bool ObjectIsGray() const { + JSObject* obj = mObject.unbarrieredGet(); + MOZ_RELEASE_ASSERT(obj); + return JS::GCThingIsMarkedGray(JS::GCCellPtr(obj)); + } + + private: + JS::Heap<JSObject*> mObject; + + ~ObjectHolder() { DropJSObjects(this); } +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(ObjectHolder) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ObjectHolder) + tmp->ClearObject(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ObjectHolder) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ObjectHolder) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mObject) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +// Test GC things stored in JS holders are marked as gray roots by the GC. +static void TestHoldersAreMarkedGray(JSContext* cx) { + RefPtr holder(new ObjectHolder); + + JSObject* obj = JS_NewPlainObject(cx); + ASSERT_TRUE(obj); + holder->SetObject(obj); + obj = nullptr; + + JS_GC(cx); + + ASSERT_TRUE(holder->ObjectIsGray()); +} + +// Test GC things stored in JS holders are updated by compacting GC. +static void TestHoldersAreMoved(JSContext* cx, bool singleZone) { + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_TRUE(obj); + + // Set a property so we can check we have the same object at the end. + const char* PropertyName = "answer"; + const int32_t PropertyValue = 42; + JS::RootedValue value(cx, JS::Int32Value(PropertyValue)); + ASSERT_TRUE(JS_SetProperty(cx, obj, PropertyName, value)); + + // Ensure the object is tenured. + JS_GC(cx); + + RefPtr<ObjectHolder> holder(new ObjectHolder); + holder->SetObject(obj); + + uintptr_t original = uintptr_t(obj.get()); + + if (singleZone) { + JS::PrepareZoneForGC(cx, js::GetContextZone(cx)); + } else { + JS::PrepareForFullGC(cx); + } + + JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::DEBUG_GC); + + // Shrinking DEBUG_GC should move all GC things. + ASSERT_NE(uintptr_t(holder->GetObject()), original); + + // Both root and holder should have been updated. + ASSERT_EQ(obj, holder->GetObject()); + + // Check it's the object we expect. + value.setUndefined(); + ASSERT_TRUE(JS_GetProperty(cx, obj, PropertyName, &value)); + ASSERT_EQ(value, JS::Int32Value(PropertyValue)); +} + +TEST(JSHolderMap, GCIntegration) +{ + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); + ASSERT_NE(ccjscx, nullptr); + JSContext* cx = ccjscx->Context(); + ASSERT_NE(cx, nullptr); + + static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + + JS::RealmOptions options; + // dummy + options.behaviors().setReduceTimerPrecisionCallerType( + JS::RTPCallerTypeToken{0}); + JS::RootedObject global(cx); + global = JS_NewGlobalObject(cx, &GlobalClass, nullptr, + JS::FireOnNewGlobalHook, options); + ASSERT_NE(global, nullptr); + + JSAutoRealm ar(cx, global); + + TestHoldersAreMarkedGray(cx); + TestHoldersAreMoved(cx, true); + TestHoldersAreMoved(cx, false); +} diff --git a/xpcom/tests/gtest/TestLogCommandLineHandler.cpp b/xpcom/tests/gtest/TestLogCommandLineHandler.cpp new file mode 100644 index 0000000000..ebec4854dd --- /dev/null +++ b/xpcom/tests/gtest/TestLogCommandLineHandler.cpp @@ -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/. */ + +#include "LogCommandLineHandler.h" + +#include <iterator> +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +template <class T, size_t N> +constexpr size_t array_size(T (&)[N]) { + return N; +} + +TEST(LogCommandLineHandler, Empty) +{ + bool callbackInvoked = false; + auto callback = [&](nsACString const& env) mutable { + callbackInvoked = true; + }; + + mozilla::LoggingHandleCommandLineArgs(0, nullptr, callback); + EXPECT_FALSE(callbackInvoked); + + char const* argv1[] = {""}; + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_FALSE(callbackInvoked); +} + +TEST(LogCommandLineHandler, MOZ_LOG_regular) +{ + nsTArray<nsCString> results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG", "module1:5,module2:4,sync,timestamp"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE( + "MOZ_LOG=module1:5,module2:4,sync,timestamp"_ns.Equals(results[0])); + + char const* argv2[] = {"", "-MOZ_LOG=modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv3[] = {"", "--MOZ_LOG", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv4[] = {"", "--MOZ_LOG=modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); +} + +TEST(LogCommandLineHandler, MOZ_LOG_and_FILE_regular) +{ + nsTArray<nsCString> results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG", "modules", "-MOZ_LOG_FILE", + "c:\\file/path"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=c:\\file/path"_ns.Equals(results[1])); + + char const* argv2[] = {"", "-MOZ_LOG=modules", "-MOZ_LOG_FILE=file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); + + char const* argv3[] = {"", "--MOZ_LOG", "modules", "--MOZ_LOG_FILE", "file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); + + char const* argv4[] = {"", "--MOZ_LOG=modules", "--MOZ_LOG_FILE=file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); + + char const* argv5[] = {"", "--MOZ_LOG", "modules", "-P", + "foo", "--MOZ_LOG_FILE", "file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv5), argv5, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); +} + +TEST(LogCommandLineHandler, MOZ_LOG_fuzzy) +{ + nsTArray<nsCString> results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv2[] = {"", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv3[] = {"", "-MOZ_LOG,modules", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv4[] = {"", "-MOZ_LOG", "-MOZ_LOG", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv5[] = {"", "-MOZ_LOG", "-diffent_command", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv5), argv5, callback); + EXPECT_TRUE(results.Length() == 0); +} + +TEST(LogCommandLineHandler, MOZ_LOG_overlapping) +{ + nsTArray<nsCString> results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG=modules1", "-MOZ_LOG=modules2"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules1"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG=modules2"_ns.Equals(results[1])); + + char const* argv2[] = {"", "-MOZ_LOG", "--MOZ_LOG", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv3[] = {"", "-MOZ_LOG_FILE", "-MOZ_LOG", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv4[] = {"", "-MOZ_LOG", "-MOZ_LOG_FILE", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 0); +} diff --git a/xpcom/tests/gtest/TestLogging.cpp b/xpcom/tests/gtest/TestLogging.cpp new file mode 100644 index 0000000000..0eb2b7a152 --- /dev/null +++ b/xpcom/tests/gtest/TestLogging.cpp @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" +#include "gtest/gtest.h" + +namespace mozilla::detail { +bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize, + uint16_t aLongLineSize); +} + +// These format strings result in 1024 byte lines on disk regardless +// of OS, which makes various file sizes OS-agnostic. +#ifdef XP_WIN +# define WHOLE_LINE "%01022d\n" +# define SHORT_LINE "%0510d\n" +#else +# define WHOLE_LINE "%01023d\n" +# define SHORT_LINE "%0511d\n" +#endif + +// Write the given number of 1k lines to the given file name. +void WriteTestLogFile(const char* name, uint32_t numLines) { + FILE* f = fopen(name, "w"); + ASSERT_NE(f, (FILE*)nullptr); + + for (uint32_t i = 0; i < numLines; i++) { + char buf[1024 + 1]; + SprintfLiteral(buf, WHOLE_LINE, i); + EXPECT_TRUE(fputs(buf, f) >= 0); + } + + uint64_t size = static_cast<uint64_t>(ftell(f)); + + // Close before asserting. + EXPECT_FALSE(fclose(f)); + + ASSERT_EQ(numLines * 1024, size); +} + +// Assert that the given file name has the expected size and that its +// first line is the expected line. +void AssertSizeAndFirstLine(const char* name, uint32_t expectedSize, + const char* expectedLine) { + FILE* f = fopen(name, "r"); + ASSERT_NE(f, (FILE*)nullptr); + + EXPECT_FALSE(fseek(f, 0, SEEK_END)); + uint64_t size = static_cast<uint64_t>(ftell(f)); + + EXPECT_FALSE(fseek(f, 0, SEEK_SET)); + + char line[1024 + 1]; + const char* result = fgets(line, sizeof(line), f); + + // Close before asserting. + EXPECT_FALSE(fclose(f)); + + ASSERT_NE(result, nullptr); + ASSERT_EQ(expectedSize, size); + ASSERT_STREQ(expectedLine, line); +} + +TEST(Logging, DoesNothingWhenNotNeededExact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 256); + + // Here the log file is exactly the allowed size. It shouldn't be limited. + ASSERT_TRUE( + mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024)); + + char expectedLine[1024 + 1]; + SprintfLiteral(expectedLine, WHOLE_LINE, 0); + + AssertSizeAndFirstLine(nameBuf, 256 * 1024, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, DoesNothingWhenNotNeededInexact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 200); + + // Here the log file is strictly less than the allowed size. It shouldn't be + // limited. + ASSERT_TRUE( + mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024)); + + char expectedLine[1024 + 1]; + SprintfLiteral(expectedLine, WHOLE_LINE, 0); + + AssertSizeAndFirstLine(nameBuf, 200 * 1024, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, LimitsToLessThanSize) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 300); + + ASSERT_TRUE( + mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024)); + + char expectedLine[1024 + 1]; + SprintfLiteral(expectedLine, WHOLE_LINE, 300 - 256); + + AssertSizeAndFirstLine(nameBuf, 256 * 1024, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, MayCutLongLinesExact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 300); + + char expectedLine[1024 + 1]; + + ASSERT_TRUE(mozilla::detail::LimitFileToLessThanSize( + nameBuf, (256 * 1024) - 512, 512)); + + SprintfLiteral(expectedLine, SHORT_LINE, 300 - 256); + + // The line to be cut ends "...044\n." We read 512 bytes (the + // buffer size), so we're left with 512 bytes, one of which is the + // newline. + AssertSizeAndFirstLine(nameBuf, 256 * 1024 - 512, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, MayCutLongLinesInexact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 300); + + char expectedLine[1024 + 1]; + + ASSERT_TRUE(mozilla::detail::LimitFileToLessThanSize( + nameBuf, (256 * 1024) - 512, 512)); + + SprintfLiteral(expectedLine, SHORT_LINE, 300 - 256); + + // We read 512 bytes (the buffer size), so we're left with 512 + // bytes, one of which is the newline. Notice that the limited size + // is smaller than the requested size. + AssertSizeAndFirstLine(nameBuf, 256 * 1024 - 512, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} diff --git a/xpcom/tests/gtest/TestMacNSURLEscaping.mm b/xpcom/tests/gtest/TestMacNSURLEscaping.mm new file mode 100644 index 0000000000..bbb0c8e2e9 --- /dev/null +++ b/xpcom/tests/gtest/TestMacNSURLEscaping.mm @@ -0,0 +1,140 @@ +#include "nsCocoaUtils.h" +#include "nsEscape.h" +#include "nsNetUtil.h" +#include "gtest/gtest.h" + +#include <CoreFoundation/CoreFoundation.h> + +// +// For the macOS File->Share menu, we must create an NSURL. However, NSURL is +// more strict than the browser about the encoding of URLs it accepts. +// Therefore additional encoding must be done on a URL before it is used to +// create an NSURL object. These tests aim to exercise the code used to +// perform additional encoding on a URL used to create NSURL objects. +// + +// Ensure nsCocoaUtils::ToNSURL() didn't change the URL. +// Create an NSURL with the provided string and then read the URL out of +// the NSURL and test it matches the provided string. +void ExpectUnchangedByNSURL(nsCString& aEncoded) { + NSURL* macURL = nsCocoaUtils::ToNSURL(NS_ConvertUTF8toUTF16(aEncoded)); + NSString* macURLString = [macURL absoluteString]; + + nsString geckoURLString; + nsCocoaUtils::GetStringForNSString(macURLString, geckoURLString); + EXPECT_STREQ(aEncoded.BeginReading(), + NS_ConvertUTF16toUTF8(geckoURLString).get()); +} + +// Test escaping of URLs to ensure that +// 1) We escape URLs in such a way that macOS's NSURL code accepts the URL as +// valid. +// 2) The encoding encoded the expected characters only. +// 2) NSURL not modify the URL. Check this by reading the URL back out of the +// NSURL object and comparing it. If the URL is changed by creating an +// NSURL, it may indicate the encoding is incorrect. +// +// It is not a requirement that NSURL not change the URL, but we don't +// expect that for these test cases. +TEST(NSURLEscaping, NSURLEscapingTests) +{ + // Per RFC2396, URI "unreserved" characters. These are allowed in URIs and + // can be escaped without changing the semantics of the URI, but the escaping + // should only be done if the URI is used in a context that requires it. + // + // "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + // + // These are the URI general "reserved" characters. Their reserved purpose + // is as delimters so they don't need to be escaped unless used in a URI + // component in a way that conflicts with the reserved purpose. i.e., + // whether or not they must be encoded depends on the component. + // + // ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," + // + // Characters considered excluded from URI use and should be escaped. + // "#" when not used to delimit the start of the fragment identifier. + // "%" when not used to escape a character. + // + // "<" | ">" | "#" | "%" | <"> | "{" | "}" | "|" | "\" | "^" | "[" | "]" | + // "`" + + // Pairs of URLs of the form (un-encoded, expected encoded result) to verify. + nsTArray<std::pair<nsCString, nsCString>> pairs{ + {// '#' in ref + "https://chat.mozilla.org/#/room/#macdev:mozilla.org"_ns, + "https://chat.mozilla.org/#/room/%23macdev:mozilla.org"_ns}, + { + // '"' in ref + "https://example.com/path#ref_with_#_and\""_ns, + "https://example.com/path#ref_with_%23_and%22"_ns, + }, + { + // '[]{}|' in ref + "https://example.com/path#ref_with_[foo]_{and}_|"_ns, + "https://example.com/path#ref_with_%5Bfoo%5D_%7Band%7D_%7C"_ns, + }, + { + // Unreserved characters in path, query, and ref + "https://example.com/path-_.!~&'(x)?x=y&x=z-_.!~&'(x)#ref-_.!~&'(x)"_ns, + "https://example.com/path-_.!~&'(x)?x=y&x=z-_.!~&%27(x)#ref-_.!~&'(x)"_ns, + }, + { + // All excluded characters in the ref. + "https://example.com/path#ref \"#<>[]\\^`{|}ref"_ns, + "https://example.com/path#ref%20%22%23%3C%3E%5B%5D%5C%5E%60%7B%7C%7Dref"_ns, + }, + /* + * Bug 1739533: + * This test fails because the '%' character needs to be escaped before + * the URI is passed to [NSURL URLWithString]. '<' brackets are + * already escaped by the browser to their "%xx" form so the encoding must + * be added in a way that ignores characters already % encoded "%xx". + { + // Unreserved characters in path, query, and ref + // https://example.com/path/with<more>/%/and"#frag_with_#_and" + "https://example.com/path/with<more>/%/and\"#frag_with_#_and\""_ns, + "https://example.com/path/with%3Cmore%3E/%25/and%22#frag_with_%23_and%22"_ns, + }, + */ + }; + + for (std::pair<nsCString, nsCString>& pair : pairs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, pair.first); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(pair.second.BeginReading(), escaped.BeginReading()); + ExpectUnchangedByNSURL(escaped); + } + + // A list of URLs that should not be changed by encoding. + nsTArray<nsCString> unchangedURLs{ + // '=' In the query + "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854"_ns, + "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854#ref"_ns, + "https://bugzilla.mozilla.org/allinref#show_bug.cgi?id=1737854ref"_ns, + "https://example.com/script?foo=bar#this_ref"_ns, + // Escaped character in the ref + "https://html.spec.whatwg.org/multipage/dom.html#the-document%27s-address"_ns, + // Misc query + "https://www.google.com/search?q=firefox+web+browser&client=firefox-b-1-d&ei=abc&ved=abc&abc=5&oq=firefox+web+browser&gs_lcp=abc&sclient=gws-wiz"_ns, + // Check for double encoding. % encoded octals should not be re-encoded. + "https://chat.mozilla.org/#/room/%23macdev%3Amozilla.org"_ns, + "https://searchfox.org/mozilla-central/search?q=symbol%3AE_%3CT_mozilla%3A%3AWebGLExtensionID%3E_EXT_color_buffer_half_float&path="_ns, + // Unreserved and reserved that don't need encoding in ref. + "https://example.com/path#ref!$&'(foo),:;=?@~"_ns, + // Unreserved and reserved that don't need encoding in path. + "https://example.com/path-_.!~&'(x)#ref"_ns, + // Unreserved and reserved that don't need encoding in path and ref. + "https://example.com/path-_.!~&'(x)#ref-_.!~&'(x)"_ns, + // Reserved in query. + "https://example.com/path?a=b&;=/&/=?&@=a+b,$"_ns, + }; + + for (nsCString& toEscape : unchangedURLs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, toEscape); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading()); + ExpectUnchangedByNSURL(escaped); + } +} diff --git a/xpcom/tests/gtest/TestMemoryPressure.cpp b/xpcom/tests/gtest/TestMemoryPressure.cpp new file mode 100644 index 0000000000..8435848aa4 --- /dev/null +++ b/xpcom/tests/gtest/TestMemoryPressure.cpp @@ -0,0 +1,199 @@ +/* -*- 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 <thread> +#include "gtest/gtest.h" + +#include "mozilla/Atomics.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsMemoryPressure.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +namespace { + +enum class MemoryPressureEventType : int { + LowMemory, + LowMemoryOngoing, + Stop, +}; + +class MemoryPressureObserver final : public nsIObserver { + nsCOMPtr<nsIObserverService> mObserverSvc; + Vector<MemoryPressureEventType> mEvents; + + ~MemoryPressureObserver() { + EXPECT_TRUE( + NS_SUCCEEDED(mObserverSvc->RemoveObserver(this, kTopicMemoryPressure))); + EXPECT_TRUE(NS_SUCCEEDED( + mObserverSvc->RemoveObserver(this, kTopicMemoryPressureStop))); + } + + public: + NS_DECL_ISUPPORTS + + MemoryPressureObserver() + : mObserverSvc(do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) { + EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver( + this, kTopicMemoryPressure, /* ownsWeak */ false))); + EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver( + this, kTopicMemoryPressureStop, /* ownsWeak */ false))); + } + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + Maybe<MemoryPressureEventType> event; + if (strcmp(aTopic, kTopicMemoryPressure) == 0) { + if (nsDependentString(aData) == kSubTopicLowMemoryNew) { + event = Some(MemoryPressureEventType::LowMemory); + } else if (nsDependentString(aData) == kSubTopicLowMemoryOngoing) { + event = Some(MemoryPressureEventType::LowMemoryOngoing); + } else { + fprintf(stderr, "Unexpected subtopic: %S\n", + reinterpret_cast<const wchar_t*>(aData)); + EXPECT_TRUE(false); + } + } else if (strcmp(aTopic, kTopicMemoryPressureStop) == 0) { + event = Some(MemoryPressureEventType::Stop); + } else { + fprintf(stderr, "Unexpected topic: %s\n", aTopic); + EXPECT_TRUE(false); + } + + if (event) { + Unused << mEvents.emplaceBack(event.value()); + } + return NS_OK; + } + + uint32_t GetCount() const { return mEvents.length(); } + void Reset() { mEvents.clear(); } + MemoryPressureEventType Top() const { return mEvents[0]; } + + bool ValidateTransitions() const { + if (mEvents.length() == 0) { + return true; + } + + for (size_t i = 1; i < mEvents.length(); ++i) { + MemoryPressureEventType eventFrom = mEvents[i - 1]; + MemoryPressureEventType eventTo = mEvents[i]; + if ((eventFrom == MemoryPressureEventType::LowMemory && + eventTo == MemoryPressureEventType::LowMemoryOngoing) || + (eventFrom == MemoryPressureEventType::LowMemoryOngoing && + eventTo == MemoryPressureEventType::LowMemoryOngoing) || + (eventFrom == MemoryPressureEventType::Stop && + eventTo == MemoryPressureEventType::LowMemory) || + (eventFrom == MemoryPressureEventType::LowMemoryOngoing && + eventTo == MemoryPressureEventType::Stop) || + (eventFrom == MemoryPressureEventType::LowMemory && + eventTo == MemoryPressureEventType::Stop)) { + // Only these transitions are valid. + continue; + } + + fprintf(stderr, "Invalid transition: %d -> %d\n", + static_cast<int>(eventFrom), static_cast<int>(eventTo)); + return false; + } + return true; + } +}; + +NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver) + +template <MemoryPressureState State> +void PressureSender(Atomic<bool>& aContinue) { + while (aContinue) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + NS_NotifyOfEventualMemoryPressure(State); + } +} + +template <MemoryPressureState State> +void PressureSenderQuick(Atomic<bool>& aContinue) { + while (aContinue) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + NS_NotifyOfMemoryPressure(State); + } +} + +} // anonymous namespace + +TEST(MemoryPressure, Singlethread) +{ + RefPtr observer(new MemoryPressureObserver); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 1"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemory); + + observer->Reset(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 2"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing); + + observer->Reset(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 3"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing); + + observer->Reset(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 4"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::Stop); +} + +TEST(MemoryPressure, Multithread) +{ + // Start |kNumThreads| threads each for the following thread type: + // - LowMemory via NS_NotifyOfEventualMemoryPressure + // - LowMemory via NS_NotifyOfMemoryPressure + // - LowMemoryOngoing via NS_NotifyOfEventualMemoryPressure + // - LowMemoryOngoing via NS_NotifyOfMemoryPressure + // and keep them running until |kNumEventsToValidate| memory-pressure events + // are received. + constexpr int kNumThreads = 5; + constexpr int kNumEventsToValidate = 200; + + Atomic<bool> shouldContinue(true); + Vector<std::thread> threads; + for (int i = 0; i < kNumThreads; ++i) { + Unused << threads.emplaceBack( + PressureSender<MemoryPressureState::LowMemory>, + std::ref(shouldContinue)); + Unused << threads.emplaceBack( + PressureSender<MemoryPressureState::NoPressure>, + std::ref(shouldContinue)); + Unused << threads.emplaceBack( + PressureSenderQuick<MemoryPressureState::LowMemory>, + std::ref(shouldContinue)); + Unused << threads.emplaceBack( + PressureSenderQuick<MemoryPressureState::NoPressure>, + std::ref(shouldContinue)); + } + + RefPtr observer(new MemoryPressureObserver); + + // We cannot sleep here because the main thread needs to keep running. + SpinEventLoopUntil( + "xpcom:TEST(MemoryPressure, Multithread)"_ns, + [&observer]() { return observer->GetCount() >= kNumEventsToValidate; }); + + shouldContinue = false; + for (auto& thread : threads) { + thread.join(); + } + + EXPECT_TRUE(observer->ValidateTransitions()); +} diff --git a/xpcom/tests/gtest/TestMoveString.cpp b/xpcom/tests/gtest/TestMoveString.cpp new file mode 100644 index 0000000000..cdfacdbbac --- /dev/null +++ b/xpcom/tests/gtest/TestMoveString.cpp @@ -0,0 +1,266 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> +#include "nsASCIIMask.h" +#include "nsString.h" +#include "nsStringBuffer.h" +#include "nsReadableUtils.h" +#include "nsCRTGlue.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Unused.h" +#include "gtest/gtest.h" + +namespace TestMoveString { + +#define NEW_VAL "**new value**" +#define OLD_VAL "old value" + +typedef mozilla::detail::StringDataFlags Df; + +static void SetAsOwned(nsACString& aStr, const char* aValue) { + size_t len = strlen(aValue); + char* data = new char[len + 1]; + memcpy(data, aValue, len + 1); + aStr.Adopt(data, len); + EXPECT_EQ(aStr.GetDataFlags(), Df::OWNED | Df::TERMINATED); + EXPECT_STREQ(aStr.BeginReading(), aValue); +} + +static void ExpectTruncated(const nsACString& aStr) { + EXPECT_EQ(aStr.Length(), uint32_t(0)); + EXPECT_STREQ(aStr.BeginReading(), ""); + EXPECT_EQ(aStr.GetDataFlags(), Df::TERMINATED); +} + +static void ExpectNew(const nsACString& aStr) { + EXPECT_EQ(aStr.Length(), strlen(NEW_VAL)); + EXPECT_TRUE(aStr.EqualsASCII(NEW_VAL)); +} + +TEST(MoveString, SharedIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, OwnedIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in; + SetAsOwned(in, NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::OWNED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, LiteralIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in; + in.AssignLiteral(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, AutoIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsAutoCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::INLINE | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + EXPECT_NE(out.get(), data); +} + +TEST(MoveString, DepIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsDependentCSubstring in(NEW_VAL "garbage after", strlen(NEW_VAL)); + EXPECT_EQ(in.GetDataFlags(), Df(0)); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); +} + +TEST(MoveString, VoidIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in = VoidCString(); + EXPECT_EQ(in.GetDataFlags(), Df::VOIDED | Df::TERMINATED); + + out.Assign(std::move(in)); + ExpectTruncated(in); + + EXPECT_EQ(out.Length(), 0u); + EXPECT_STREQ(out.get(), ""); + EXPECT_EQ(out.GetDataFlags(), Df::VOIDED | Df::TERMINATED); +} + +TEST(MoveString, SharedIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, OwnedIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in; + SetAsOwned(in, NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::OWNED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, LiteralIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in; + in.AssignLiteral(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, AutoIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsAutoCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::INLINE | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + EXPECT_NE(out.get(), data); +} + +TEST(MoveString, DepIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsDependentCSubstring in(NEW_VAL "garbage after", strlen(NEW_VAL)); + EXPECT_EQ(in.GetDataFlags(), Df(0)); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); +} + +TEST(MoveString, VoidIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in = VoidCString(); + EXPECT_EQ(in.GetDataFlags(), Df::VOIDED | Df::TERMINATED); + + out.Assign(std::move(in)); + ExpectTruncated(in); + + EXPECT_EQ(out.Length(), 0u); + EXPECT_STREQ(out.get(), ""); + EXPECT_EQ(out.GetDataFlags(), Df::VOIDED | Df::TERMINATED); +} + +#undef NEW_VAL +#undef OLD_VAL + +} // namespace TestMoveString diff --git a/xpcom/tests/gtest/TestMozPromise.cpp b/xpcom/tests/gtest/TestMozPromise.cpp new file mode 100644 index 0000000000..bb7273cc1f --- /dev/null +++ b/xpcom/tests/gtest/TestMozPromise.cpp @@ -0,0 +1,756 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VideoUtils.h" +#include "base/message_loop.h" +#include "gtest/gtest.h" +#include "mozilla/MozPromise.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsISupportsImpl.h" + +using namespace mozilla; + +typedef MozPromise<int, double, false> TestPromise; +typedef MozPromise<int, double, true /* exclusive */> TestPromiseExcl; +typedef TestPromise::ResolveOrRejectValue RRValue; + +class MOZ_STACK_CLASS AutoTaskQueue { + public: + AutoTaskQueue() + : mTaskQueue( + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestMozPromise AutoTaskQueue")) {} + + ~AutoTaskQueue() { mTaskQueue->AwaitShutdownAndIdle(); } + + TaskQueue* Queue() { return mTaskQueue; } + + private: + RefPtr<TaskQueue> mTaskQueue; +}; + +class DelayedResolveOrReject : public Runnable { + public: + DelayedResolveOrReject(TaskQueue* aTaskQueue, TestPromise::Private* aPromise, + const TestPromise::ResolveOrRejectValue& aValue, + int aIterations) + : mozilla::Runnable("DelayedResolveOrReject"), + mTaskQueue(aTaskQueue), + mPromise(aPromise), + mValue(aValue), + mIterations(aIterations) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (!mPromise) { + // Canceled. + return NS_OK; + } + + if (--mIterations == 0) { + mPromise->ResolveOrReject(mValue, __func__); + return NS_OK; + } + + nsCOMPtr<nsIRunnable> r = this; + return mTaskQueue->Dispatch(r.forget()); + } + + void Cancel() { mPromise = nullptr; } + + protected: + ~DelayedResolveOrReject() = default; + + private: + RefPtr<TaskQueue> mTaskQueue; + RefPtr<TestPromise::Private> mPromise; + TestPromise::ResolveOrRejectValue mValue; + int mIterations; +}; + +template <typename FunctionType> +void RunOnTaskQueue(TaskQueue* aQueue, FunctionType aFun) { + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("RunOnTaskQueue", aFun); + Unused << aQueue->Dispatch(r.forget()); +} + +// std::function can't come soon enough. :-( +#define DO_FAIL \ + []() { \ + EXPECT_TRUE(false); \ + return TestPromise::CreateAndReject(0, __func__); \ + } + +TEST(MozPromise, BasicResolve) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndResolve(42, __func__) + ->Then( + queue, __func__, + [queue](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + queue->BeginShutdown(); + }, + DO_FAIL); + }); +} + +TEST(MozPromise, BasicReject) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndReject(42.0, __func__) + ->Then(queue, __func__, DO_FAIL, [queue](int aRejectValue) -> void { + EXPECT_EQ(aRejectValue, 42.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, BasicResolveOrRejectResolved) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndResolve(42, __func__) + ->Then( + queue, __func__, + [queue](const TestPromise::ResolveOrRejectValue& aValue) -> void { + EXPECT_TRUE(aValue.IsResolve()); + EXPECT_FALSE(aValue.IsReject()); + EXPECT_FALSE(aValue.IsNothing()); + EXPECT_EQ(aValue.ResolveValue(), 42); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, BasicResolveOrRejectRejected) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndReject(42.0, __func__) + ->Then( + queue, __func__, + [queue](const TestPromise::ResolveOrRejectValue& aValue) -> void { + EXPECT_TRUE(aValue.IsReject()); + EXPECT_FALSE(aValue.IsResolve()); + EXPECT_FALSE(aValue.IsNothing()); + EXPECT_EQ(aValue.RejectValue(), 42.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, AsyncResolve) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + RefPtr<TestPromise::Private> p = new TestPromise::Private(__func__); + + // Kick off three racing tasks, and make sure we get the one that finishes + // earliest. + RefPtr<DelayedResolveOrReject> a = + new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(32), 10); + RefPtr<DelayedResolveOrReject> b = + new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(42), 5); + RefPtr<DelayedResolveOrReject> c = + new DelayedResolveOrReject(queue, p, RRValue::MakeReject(32.0), 7); + + nsCOMPtr<nsIRunnable> ref = a.get(); + Unused << queue->Dispatch(ref.forget()); + ref = b.get(); + Unused << queue->Dispatch(ref.forget()); + ref = c.get(); + Unused << queue->Dispatch(ref.forget()); + + p->Then( + queue, __func__, + [queue, a, b, c](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + a->Cancel(); + b->Cancel(); + c->Cancel(); + queue->BeginShutdown(); + }, + DO_FAIL); + }); +} + +TEST(MozPromise, CompletionPromises) +{ + bool invokedPass = false; + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue, &invokedPass]() -> void { + TestPromise::CreateAndResolve(40, __func__) + ->Then( + queue, __func__, + [](int aVal) -> RefPtr<TestPromise> { + return TestPromise::CreateAndResolve(aVal + 10, __func__); + }, + DO_FAIL) + ->Then( + queue, __func__, + [&invokedPass](int aVal) { + invokedPass = true; + return TestPromise::CreateAndResolve(aVal, __func__); + }, + DO_FAIL) + ->Then( + queue, __func__, + [queue](int aVal) -> RefPtr<TestPromise> { + RefPtr<TestPromise::Private> p = + new TestPromise::Private(__func__); + nsCOMPtr<nsIRunnable> resolver = new DelayedResolveOrReject( + queue, p, RRValue::MakeResolve(aVal - 8), 10); + Unused << queue->Dispatch(resolver.forget()); + return RefPtr<TestPromise>(p); + }, + DO_FAIL) + ->Then( + queue, __func__, + [](int aVal) -> RefPtr<TestPromise> { + return TestPromise::CreateAndReject(double(aVal - 42) + 42.0, + __func__); + }, + DO_FAIL) + ->Then(queue, __func__, DO_FAIL, + [queue, &invokedPass](double aVal) -> void { + EXPECT_EQ(aVal, 42.0); + EXPECT_TRUE(invokedPass); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, PromiseAllResolve) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray<RefPtr<TestPromise>> promises; + promises.AppendElement(TestPromise::CreateAndResolve(22, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(32, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(42, __func__)); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, + [queue](const CopyableTArray<int>& aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 3UL); + EXPECT_EQ(aResolveValues[0], 22); + EXPECT_EQ(aResolveValues[1], 32); + EXPECT_EQ(aResolveValues[2], 42); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +TEST(MozPromise, PromiseAllResolveAsync) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray<RefPtr<TestPromise>> promises; + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(22, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(32, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(42, __func__); + })); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, + [queue](const CopyableTArray<int>& aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 3UL); + EXPECT_EQ(aResolveValues[0], 22); + EXPECT_EQ(aResolveValues[1], 32); + EXPECT_EQ(aResolveValues[2], 42); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +TEST(MozPromise, PromiseAllReject) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray<RefPtr<TestPromise>> promises; + promises.AppendElement(TestPromise::CreateAndResolve(22, __func__)); + promises.AppendElement(TestPromise::CreateAndReject(32.0, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(42, __func__)); + // Ensure that more than one rejection doesn't cause a crash (bug #1207312) + promises.AppendElement(TestPromise::CreateAndReject(52.0, __func__)); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, []() { EXPECT_TRUE(false); }, + [queue](float aRejectValue) -> void { + EXPECT_EQ(aRejectValue, 32.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, PromiseAllRejectAsync) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray<RefPtr<TestPromise>> promises; + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(22, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(32.0, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(42, __func__); + })); + // Ensure that more than one rejection doesn't cause a crash (bug #1207312) + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(52.0, __func__); + })); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, []() { EXPECT_TRUE(false); }, + [queue](float aRejectValue) -> void { + EXPECT_EQ(aRejectValue, 32.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, PromiseAllSettled) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray<RefPtr<TestPromise>> promises; + promises.AppendElement(TestPromise::CreateAndResolve(22, __func__)); + promises.AppendElement(TestPromise::CreateAndReject(32.0, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(42, __func__)); + promises.AppendElement(TestPromise::CreateAndReject(52.0, __func__)); + + TestPromise::AllSettled(queue, promises) + ->Then( + queue, __func__, + [queue](const TestPromise::AllSettledPromiseType::ResolveValueType& + aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 4UL); + EXPECT_TRUE(aResolveValues[0].IsResolve()); + EXPECT_EQ(aResolveValues[0].ResolveValue(), 22); + EXPECT_FALSE(aResolveValues[1].IsResolve()); + EXPECT_EQ(aResolveValues[1].RejectValue(), 32.0); + EXPECT_TRUE(aResolveValues[2].IsResolve()); + EXPECT_EQ(aResolveValues[2].ResolveValue(), 42); + EXPECT_FALSE(aResolveValues[3].IsResolve()); + EXPECT_EQ(aResolveValues[3].RejectValue(), 52.0); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +TEST(MozPromise, PromiseAllSettledAsync) +{ + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray<RefPtr<TestPromise>> promises; + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(22, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(32.0, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(42, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(52.0, __func__); + })); + + TestPromise::AllSettled(queue, promises) + ->Then( + queue, __func__, + [queue](const TestPromise::AllSettledPromiseType::ResolveValueType& + aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 4UL); + EXPECT_TRUE(aResolveValues[0].IsResolve()); + EXPECT_EQ(aResolveValues[0].ResolveValue(), 22); + EXPECT_FALSE(aResolveValues[1].IsResolve()); + EXPECT_EQ(aResolveValues[1].RejectValue(), 32.0); + EXPECT_TRUE(aResolveValues[2].IsResolve()); + EXPECT_EQ(aResolveValues[2].ResolveValue(), 42); + EXPECT_FALSE(aResolveValues[3].IsResolve()); + EXPECT_EQ(aResolveValues[3].RejectValue(), 52.0); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +// Test we don't hit the assertions in MozPromise when exercising promise +// chaining upon task queue shutdown. +TEST(MozPromise, Chaining) +{ + // We declare this variable before |atq| to ensure + // the destructor is run after |holder.Disconnect()|. + MozPromiseRequestHolder<TestPromise> holder; + + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + + RunOnTaskQueue(queue, [queue, &holder]() { + auto p = TestPromise::CreateAndResolve(42, __func__); + const size_t kIterations = 100; + for (size_t i = 0; i < kIterations; ++i) { + p = p->Then( + queue, __func__, + [](int aVal) { + EXPECT_EQ(aVal, 42); + return TestPromise::CreateAndResolve(aVal, __func__); + }, + [](double aVal) { + return TestPromise::CreateAndReject(aVal, __func__); + }); + + if (i == kIterations / 2) { + p->Then( + queue, __func__, + [queue, &holder]() { + holder.Disconnect(); + queue->BeginShutdown(); + }, + DO_FAIL); + } + } + // We will hit the assertion if we don't disconnect the leaf Request + // in the promise chain. + p->Then( + queue, __func__, []() {}, []() {}) + ->Track(holder); + }); +} + +TEST(MozPromise, ResolveOrRejectValue) +{ + using MyPromise = MozPromise<UniquePtr<int>, bool, false>; + using RRValue = MyPromise::ResolveOrRejectValue; + + RRValue val; + EXPECT_TRUE(val.IsNothing()); + EXPECT_FALSE(val.IsResolve()); + EXPECT_FALSE(val.IsReject()); + + val.SetResolve(MakeUnique<int>(87)); + EXPECT_FALSE(val.IsNothing()); + EXPECT_TRUE(val.IsResolve()); + EXPECT_FALSE(val.IsReject()); + EXPECT_EQ(87, *val.ResolveValue()); + + // IsResolve() should remain true after std::move(). + UniquePtr<int> i = std::move(val.ResolveValue()); + EXPECT_EQ(87, *i); + EXPECT_TRUE(val.IsResolve()); + EXPECT_EQ(val.ResolveValue().get(), nullptr); +} + +TEST(MozPromise, MoveOnlyType) +{ + using MyPromise = MozPromise<UniquePtr<int>, bool, true>; + using RRValue = MyPromise::ResolveOrRejectValue; + + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + + MyPromise::CreateAndResolve(MakeUnique<int>(87), __func__) + ->Then( + queue, __func__, [](UniquePtr<int> aVal) { EXPECT_EQ(87, *aVal); }, + []() { EXPECT_TRUE(false); }); + + MyPromise::CreateAndResolve(MakeUnique<int>(87), __func__) + ->Then(queue, __func__, [queue](RRValue&& aVal) { + EXPECT_FALSE(aVal.IsNothing()); + EXPECT_TRUE(aVal.IsResolve()); + EXPECT_FALSE(aVal.IsReject()); + EXPECT_EQ(87, *aVal.ResolveValue()); + + // std::move() shouldn't change the resolve/reject state of aVal. + RRValue val = std::move(aVal); + EXPECT_TRUE(aVal.IsResolve()); + EXPECT_EQ(nullptr, aVal.ResolveValue().get()); + EXPECT_EQ(87, *val.ResolveValue()); + + queue->BeginShutdown(); + }); +} + +TEST(MozPromise, HeterogeneousChaining) +{ + using Promise1 = MozPromise<UniquePtr<char>, bool, true>; + using Promise2 = MozPromise<UniquePtr<int>, bool, true>; + using RRValue1 = Promise1::ResolveOrRejectValue; + using RRValue2 = Promise2::ResolveOrRejectValue; + + MozPromiseRequestHolder<Promise2> holder; + + AutoTaskQueue atq; + RefPtr<TaskQueue> queue = atq.Queue(); + + RunOnTaskQueue(queue, [queue, &holder]() { + Promise1::CreateAndResolve(MakeUnique<char>(0), __func__) + ->Then(queue, __func__, + [&holder]() { + holder.Disconnect(); + return Promise2::CreateAndResolve(MakeUnique<int>(0), + __func__); + }) + ->Then(queue, __func__, + []() { + // Shouldn't be called for we've disconnected the request. + EXPECT_FALSE(true); + }) + ->Track(holder); + }); + + Promise1::CreateAndResolve(MakeUnique<char>(87), __func__) + ->Then( + queue, __func__, + [](UniquePtr<char> aVal) { + EXPECT_EQ(87, *aVal); + return Promise2::CreateAndResolve(MakeUnique<int>(94), __func__); + }, + []() { + return Promise2::CreateAndResolve(MakeUnique<int>(95), __func__); + }) + ->Then( + queue, __func__, [](UniquePtr<int> aVal) { EXPECT_EQ(94, *aVal); }, + []() { EXPECT_FALSE(true); }); + + Promise1::CreateAndResolve(MakeUnique<char>(87), __func__) + ->Then(queue, __func__, + [](RRValue1&& aVal) { + EXPECT_EQ(87, *aVal.ResolveValue()); + return Promise2::CreateAndResolve(MakeUnique<int>(94), __func__); + }) + ->Then(queue, __func__, [queue](RRValue2&& aVal) { + EXPECT_EQ(94, *aVal.ResolveValue()); + queue->BeginShutdown(); + }); +} + +TEST(MozPromise, XPCOMEventTarget) +{ + TestPromise::CreateAndResolve(42, __func__) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); }, + DO_FAIL); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, MessageLoopEventTarget) +{ + TestPromise::CreateAndResolve(42, __func__) + ->Then( + MessageLoop::current()->SerialEventTarget(), __func__, + [](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); }, + DO_FAIL); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, ChainTo) +{ + RefPtr<TestPromise> promise1 = TestPromise::CreateAndResolve(42, __func__); + RefPtr<TestPromise::Private> promise2 = new TestPromise::Private(__func__); + promise2->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); }, + DO_FAIL); + + promise1->ChainTo(promise2.forget(), __func__); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, SynchronousTaskDispatch1) +{ + bool value = false; + RefPtr<TestPromiseExcl::Private> promise = + new TestPromiseExcl::Private(__func__); + promise->UseSynchronousTaskDispatch(__func__); + promise->Resolve(42, __func__); + EXPECT_EQ(value, false); + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + value = true; + }, + DO_FAIL); + EXPECT_EQ(value, true); +} + +TEST(MozPromise, SynchronousTaskDispatch2) +{ + bool value = false; + RefPtr<TestPromiseExcl::Private> promise = + new TestPromiseExcl::Private(__func__); + promise->UseSynchronousTaskDispatch(__func__); + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + value = true; + }, + DO_FAIL); + EXPECT_EQ(value, false); + promise->Resolve(42, __func__); + EXPECT_EQ(value, true); +} + +TEST(MozPromise, DirectTaskDispatch) +{ + bool value1 = false; + bool value2 = false; + + // For direct task dispatch to be working, we must be within a + // nested event loop. So the test itself must be dispatched within + // a task. + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() { + GetCurrentSerialEventTarget()->Dispatch( + NS_NewRunnableFunction("test", [&]() { + EXPECT_EQ(value1, true); + value2 = true; + })); + + RefPtr<TestPromise::Private> promise = new TestPromise::Private(__func__); + promise->UseDirectTaskDispatch(__func__); + promise->Resolve(42, __func__); + EXPECT_EQ(value1, false); + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + EXPECT_EQ(value2, false); + value1 = true; + }, + DO_FAIL); + EXPECT_EQ(value1, false); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, ChainedDirectTaskDispatch) +{ + bool value1 = false; + bool value2 = false; + + // For direct task dispatch to be working, we must be within a + // nested event loop. So the test itself must be dispatched within + // a task. + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() { + GetCurrentSerialEventTarget()->Dispatch( + NS_NewRunnableFunction("test", [&]() { + EXPECT_EQ(value1, true); + value2 = true; + })); + + RefPtr<TestPromise::Private> promise1 = new TestPromise::Private(__func__); + promise1->UseDirectTaskDispatch(__func__); + promise1->Resolve(42, __func__); + EXPECT_EQ(value1, false); + promise1 + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> RefPtr<TestPromise> { + EXPECT_EQ(aResolveValue, 42); + EXPECT_EQ(value2, false); + RefPtr<TestPromise::Private> promise2 = + new TestPromise::Private(__func__); + promise2->UseDirectTaskDispatch(__func__); + promise2->Resolve(43, __func__); + return promise2; + }, + DO_FAIL) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 43); + EXPECT_EQ(value2, false); + value1 = true; + }, + DO_FAIL); + EXPECT_EQ(value1, false); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, ChainToDirectTaskDispatch) +{ + bool value1 = false; + bool value2 = false; + + // For direct task dispatch to be working, we must be within a + // nested event loop. So the test itself must be dispatched within + // a task. + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() { + GetCurrentSerialEventTarget()->Dispatch( + NS_NewRunnableFunction("test", [&]() { + EXPECT_EQ(value1, true); + value2 = true; + })); + + RefPtr<TestPromise::Private> promise1 = new TestPromise::Private(__func__); + promise1->UseDirectTaskDispatch(__func__); + + RefPtr<TestPromise::Private> promise2 = new TestPromise::Private(__func__); + promise2->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + EXPECT_EQ(value2, false); + value1 = true; + }, + DO_FAIL); + + promise1->ChainTo(promise2.forget(), __func__); + EXPECT_EQ(value1, false); + promise1->Resolve(42, __func__); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +#undef DO_FAIL diff --git a/xpcom/tests/gtest/TestMruCache.cpp b/xpcom/tests/gtest/TestMruCache.cpp new file mode 100644 index 0000000000..8cf97408d1 --- /dev/null +++ b/xpcom/tests/gtest/TestMruCache.cpp @@ -0,0 +1,395 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/MruCache.h" +#include "nsString.h" + +using namespace mozilla; + +// A few MruCache implementations to use during testing. +struct IntMap : public MruCache<int, int, IntMap> { + static HashNumber Hash(const KeyType& aKey) { return aKey - 1; } + static bool Match(const KeyType& aKey, const ValueType& aVal) { + return aKey == aVal; + } +}; + +struct UintPtrMap : public MruCache<uintptr_t, int*, UintPtrMap> { + static HashNumber Hash(const KeyType& aKey) { return aKey - 1; } + static bool Match(const KeyType& aKey, const ValueType& aVal) { + return aKey == (KeyType)aVal; + } +}; + +struct StringStruct { + nsCString mKey; + nsCString mOther; +}; + +struct StringStructMap + : public MruCache<nsCString, StringStruct, StringStructMap> { + static HashNumber Hash(const KeyType& aKey) { + return *aKey.BeginReading() - 1; + } + static bool Match(const KeyType& aKey, const ValueType& aVal) { + return aKey == aVal.mKey; + } +}; + +// Helper for emulating convertable holders such as RefPtr. +template <typename T> +struct Convertable { + T mItem; + operator T() const { return mItem; } +}; + +// Helper to create a StringStructMap key. +static nsCString MakeStringKey(char aKey) { + nsCString key; + key.Append(aKey); + return key; +} + +TEST(MruCache, TestNullChecker) +{ + using mozilla::detail::EmptyChecker; + + { + int test = 0; + EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test)); + + test = 42; + EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test)); + } + + { + const char* test = "abc"; + EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test)); + + test = nullptr; + EXPECT_FALSE(EmptyChecker<decltype(test)>::IsNotEmpty(test)); + } + + { + int foo = 42; + int* test = &foo; + EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test)); + + test = nullptr; + EXPECT_FALSE(EmptyChecker<decltype(test)>::IsNotEmpty(test)); + } +} + +TEST(MruCache, TestEmptyCache) +{ + { + // Test a basic empty cache. + IntMap mru; + + // Make sure the default values are set. + for (int i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Shouldn't be found. + EXPECT_FALSE(p); + } + } + + { + // Test an empty cache with pointer values. + UintPtrMap mru; + + // Make sure the default values are set. + for (uintptr_t i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Shouldn't be found. + EXPECT_FALSE(p); + } + } + + { + // Test an empty cache with more complex structure. + StringStructMap mru; + + // Make sure the default values are set. + for (char i = 1; i < 32; i++) { + const nsCString key = MakeStringKey(i); + auto p = mru.Lookup(key); + + // Shouldn't be found. + EXPECT_FALSE(p); + } + } +} + +TEST(MruCache, TestPut) +{ + IntMap mru; + + // Fill it up. + for (int i = 1; i < 32; i++) { + mru.Put(i, i); + } + + // Now check each value. + for (int i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Should be found. + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), i); + } +} + +TEST(MruCache, TestPutConvertable) +{ + UintPtrMap mru; + + // Fill it up. + for (uintptr_t i = 1; i < 32; i++) { + Convertable<int*> val{(int*)i}; + mru.Put(i, val); + } + + // Now check each value. + for (uintptr_t i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Should be found. + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), (int*)i); + } +} + +TEST(MruCache, TestOverwriting) +{ + // Test overwrting + IntMap mru; + + // 1-31 should be overwritten by 32-63 + for (int i = 1; i < 63; i++) { + mru.Put(i, i); + } + + // Look them up. + for (int i = 32; i < 63; i++) { + auto p = mru.Lookup(i); + + // Should be found. + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), i); + } +} + +TEST(MruCache, TestRemove) +{ + { + IntMap mru; + + // Fill it up. + for (int i = 1; i < 32; i++) { + mru.Put(i, i); + } + + // Now remove each value. + for (int i = 1; i < 32; i++) { + // Should be present. + auto p = mru.Lookup(i); + EXPECT_TRUE(p); + + mru.Remove(i); + + // Should no longer match. + p = mru.Lookup(i); + EXPECT_FALSE(p); + } + } + + { + UintPtrMap mru; + + // Fill it up. + for (uintptr_t i = 1; i < 32; i++) { + mru.Put(i, (int*)i); + } + + // Now remove each value. + for (uintptr_t i = 1; i < 32; i++) { + // Should be present. + auto p = mru.Lookup(i); + EXPECT_TRUE(p); + + mru.Remove(i); + + // Should no longer match. + p = mru.Lookup(i); + EXPECT_FALSE(p); + } + } + + { + StringStructMap mru; + + // Fill it up. + for (char i = 1; i < 32; i++) { + const nsCString key = MakeStringKey(i); + mru.Put(key, StringStruct{key, "foo"_ns}); + } + + // Now remove each value. + for (char i = 1; i < 32; i++) { + const nsCString key = MakeStringKey(i); + + // Should be present. + auto p = mru.Lookup(key); + EXPECT_TRUE(p); + + mru.Remove(key); + + // Should no longer match. + p = mru.Lookup(key); + EXPECT_FALSE(p); + } + } +} + +TEST(MruCache, TestClear) +{ + IntMap mru; + + // Fill it up. + for (int i = 1; i < 32; i++) { + mru.Put(i, i); + } + + // Empty it. + mru.Clear(); + + // Now check each value. + for (int i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Should not be found. + EXPECT_FALSE(p); + } +} + +TEST(MruCache, TestLookupMissingAndSet) +{ + IntMap mru; + + // Value not found. + auto p = mru.Lookup(1); + EXPECT_FALSE(p); + + // Set it. + p.Set(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); + + // Look it up again. + p = mru.Lookup(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); + + // Test w/ a convertable value. + p = mru.Lookup(2); + EXPECT_FALSE(p); + + // Set it. + Convertable<int> val{2}; + p.Set(val); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 2); + + // Look it up again. + p = mru.Lookup(2); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 2); +} + +TEST(MruCache, TestLookupAndOverwrite) +{ + IntMap mru; + + // Set 1. + mru.Put(1, 1); + + // Lookup a key that maps the 1's entry. + auto p = mru.Lookup(32); + EXPECT_FALSE(p); // not a match + + // Now overwrite the entry. + p.Set(32); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 32); + + // 1 should be gone now. + p = mru.Lookup(1); + EXPECT_FALSE(p); + + // 32 should be found. + p = mru.Lookup(32); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 32); +} + +TEST(MruCache, TestLookupAndRemove) +{ + IntMap mru; + + // Set 1. + mru.Put(1, 1); + + auto p = mru.Lookup(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); + + // Now remove it. + p.Remove(); + EXPECT_FALSE(p); + + p = mru.Lookup(1); + EXPECT_FALSE(p); +} + +TEST(MruCache, TestLookupNotMatchedAndRemove) +{ + IntMap mru; + + // Set 1. + mru.Put(1, 1); + + // Lookup a key that matches 1's entry. + auto p = mru.Lookup(32); + EXPECT_FALSE(p); + + // Now attempt to remove it. + p.Remove(); + + // Make sure 1 is still there. + p = mru.Lookup(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); +} + +TEST(MruCache, TestLookupAndSetWithMove) +{ + StringStructMap mru; + + const nsCString key = MakeStringKey((char)1); + StringStruct val{key, "foo"_ns}; + + auto p = mru.Lookup(key); + EXPECT_FALSE(p); + p.Set(std::move(val)); + + EXPECT_TRUE(p.Data().mKey == key); + EXPECT_TRUE(p.Data().mOther == "foo"_ns); +} diff --git a/xpcom/tests/gtest/TestMultiplexInputStream.cpp b/xpcom/tests/gtest/TestMultiplexInputStream.cpp new file mode 100644 index 0000000000..7c422b068b --- /dev/null +++ b/xpcom/tests/gtest/TestMultiplexInputStream.cpp @@ -0,0 +1,958 @@ +/* -*- 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 "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/ipc/DataPipe.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIAsyncInputStream.h" +#include "nsComponentManagerUtils.h" +#include "nsIAsyncOutputStream.h" +#include "nsIInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsIPipe.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "nsIThread.h" +#include "Helpers.h" + +using mozilla::GetCurrentSerialEventTarget; +using mozilla::SpinEventLoopUntil; + +TEST(MultiplexInputStream, Seek_SET) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr<nsIInputStream> inputStream1; + nsCOMPtr<nsIInputStream> inputStream2; + nsCOMPtr<nsIInputStream> inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_NS_SUCCEEDED(rv); + + int64_t tell; + uint64_t length; + uint32_t count; + char readBuf[4096]; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // Seek forward in first input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 1); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1, + length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 1); + + // Check read is correct + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4, + length); + ASSERT_EQ(0, strncmp(readBuf, "ell", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 4); + + // Seek to start of third input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, + buf1.Length() + buf2.Length()); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf3.Length(), length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length())); + + // Check read is correct + rv = stream->Read(readBuf, 5, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)5, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf3.Length() - 5, length); + ASSERT_EQ(0, strncmp(readBuf, "Foo b", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + 5)); + + // Seek back to start of second stream (covers bug 1272371) + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, buf1.Length()); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf2.Length() + buf3.Length(), length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length())); + + // Check read is correct + rv = stream->Read(readBuf, 6, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)6, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf2.Length() - 6 + buf3.Length(), length); + ASSERT_EQ(0, strncmp(readBuf, "The qu", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + 6)); +} + +TEST(MultiplexInputStream, Seek_CUR) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr<nsIInputStream> inputStream1; + nsCOMPtr<nsIInputStream> inputStream2; + nsCOMPtr<nsIInputStream> inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_NS_SUCCEEDED(rv); + + int64_t tell; + uint64_t length; + uint32_t count; + char readBuf[4096]; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // Seek forward in first input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 1); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1, + length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 1); + + // Check read is correct + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4, + length); + ASSERT_EQ(0, strncmp(readBuf, "ell", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 4); + + // Let's go to the second stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 11); + ASSERT_NS_SUCCEEDED(rv); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 15); + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(readBuf, "qui", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 18); + + // Let's go back to the first stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, -9); + ASSERT_NS_SUCCEEDED(rv); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 9); + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(readBuf, "ldT", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 12); +} + +TEST(MultiplexInputStream, Seek_END) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr<nsIInputStream> inputStream1; + nsCOMPtr<nsIInputStream> inputStream2; + nsCOMPtr<nsIInputStream> inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_NS_SUCCEEDED(rv); + + int64_t tell; + uint64_t length; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // SEEK_END wants <=0 values + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 1); + ASSERT_NS_FAILED(rv); + + // Let's go to the end. + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)0, length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + buf3.Length())); + + // -1 + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, -1); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)1, length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + buf3.Length() - 1)); + + // Almost at the beginning + tell = 1; + tell -= buf1.Length(); + tell -= buf2.Length(); + tell -= buf3.Length(); + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, tell); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(length, buf1.Length() + buf2.Length() + buf3.Length() - 1); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 1); +} + +static already_AddRefed<nsIInputStream> CreateStreamHelper() { + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCString buf1; + buf1.AssignLiteral("Hello"); + + nsCOMPtr<nsIInputStream> inputStream1 = new testing::AsyncStringStream(buf1); + multiplexStream->AppendStream(inputStream1); + + nsCString buf2; + buf2.AssignLiteral(" "); + + nsCOMPtr<nsIInputStream> inputStream2 = new testing::AsyncStringStream(buf2); + multiplexStream->AppendStream(inputStream2); + + nsCString buf3; + buf3.AssignLiteral("World!"); + + nsCOMPtr<nsIInputStream> inputStream3 = new testing::AsyncStringStream(buf3); + multiplexStream->AppendStream(inputStream3); + + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + return stream.forget(); +} + +// AsyncWait - without EventTarget +TEST(MultiplexInputStream, AsyncWait_withoutEventTarget) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, nullptr)); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - with EventTarget +TEST(MultiplexInputStream, AsyncWait_withEventTarget) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, thread)); + + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, AsyncWait_withEventTarget)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - without EventTarget - closureOnly +TEST(MultiplexInputStream, AsyncWait_withoutEventTarget_closureOnly) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + nullptr)); + ASSERT_FALSE(cb->Called()); + + ais->CloseWithStatus(NS_ERROR_FAILURE); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - with EventTarget - closureOnly +TEST(MultiplexInputStream, AsyncWait_withEventTarget_closureOnly) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + thread)); + + ASSERT_FALSE(cb->Called()); + ais->CloseWithStatus(NS_ERROR_FAILURE); + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, AsyncWait_withEventTarget_closureOnly)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +class ClosedStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + ClosedStream() = default; + + NS_IMETHOD + Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + StreamStatus() override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return NS_OK; } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return NS_OK; + } + + private: + ~ClosedStream() = default; +}; + +NS_IMPL_ISUPPORTS(ClosedStream, nsIInputStream) + +class AsyncStream final : public nsIAsyncInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit AsyncStream(int64_t aSize) : mState(eBlocked), mSize(aSize) {} + + void Unblock() { mState = eUnblocked; } + + NS_IMETHOD + Available(uint64_t* aLength) override { + *aLength = mState == eBlocked ? 0 : mSize; + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK; + } + + NS_IMETHOD + StreamStatus() override { + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK; + } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { + mState = eClosed; + return NS_OK; + } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return NS_OK; + } + + NS_IMETHOD + AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + CloseWithStatus(nsresult aStatus) override { return NS_OK; } + + private: + ~AsyncStream() = default; + + enum { eBlocked, eUnblocked, eClosed } mState; + + uint64_t mSize; +}; + +NS_IMPL_ISUPPORTS(AsyncStream, nsIInputStream, nsIAsyncInputStream) + +class BlockingStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + BlockingStream() = default; + + NS_IMETHOD + Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + StreamStatus() override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + // We are actually empty. + *aReadCount = 0; + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return NS_OK; } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = false; + return NS_OK; + } + + private: + ~BlockingStream() = default; +}; + +NS_IMPL_ISUPPORTS(BlockingStream, nsIInputStream) + +TEST(MultiplexInputStream, Available) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCOMPtr<nsIInputStream> s = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!s); + + nsCOMPtr<nsIAsyncInputStream> as = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!as); + + uint64_t length; + + // The stream returns NS_BASE_STREAM_CLOSED if there are no substreams. + nsresult rv = s->Available(&length); + ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv); + + rv = multiplexStream->AppendStream(new ClosedStream()); + ASSERT_EQ(NS_OK, rv); + + uint64_t asyncSize = 2; + RefPtr<AsyncStream> asyncStream = new AsyncStream(2); + rv = multiplexStream->AppendStream(asyncStream); + ASSERT_EQ(NS_OK, rv); + + nsCString buffer; + buffer.Assign("World!!!"); + + nsCOMPtr<nsIInputStream> stringStream; + rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), buffer); + ASSERT_EQ(NS_OK, rv); + + rv = multiplexStream->AppendStream(stringStream); + ASSERT_EQ(NS_OK, rv); + + // Now we are async. + as = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!as); + + // Available should skip the closed stream and return 0 because the + // asyncStream returns 0 and it's async. + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ((uint64_t)0, length); + + asyncStream->Unblock(); + + // Now we should return only the size of the async stream because we don't + // know when this is completed. + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(asyncSize, length); + + asyncStream->Close(); + + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(buffer.Length(), length); +} + +class NonBufferableStringStream final : public nsIInputStream { + nsCOMPtr<nsIInputStream> mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonBufferableStringStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + private: + ~NonBufferableStringStream() = default; +}; + +NS_IMPL_ISUPPORTS(NonBufferableStringStream, nsIInputStream) + +TEST(MultiplexInputStream, Bufferable) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCOMPtr<nsIInputStream> s = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!s); + + nsCString buf1; + buf1.AssignLiteral("Hello "); + nsCOMPtr<nsIInputStream> inputStream1; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + + nsCString buf2; + buf2.AssignLiteral("world"); + nsCOMPtr<nsIInputStream> inputStream2 = new NonBufferableStringStream(buf2); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(!!stream); + + char buf3[1024]; + uint32_t size = 0; + rv = stream->ReadSegments(NS_CopySegmentToBuffer, buf3, sizeof(buf3), &size); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_EQ(size, buf1.Length() + buf2.Length()); + ASSERT_TRUE(!strncmp(buf3, "Hello world", size)); +} + +TEST(MultiplexInputStream, QILengthInputStream) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + // nsMultiplexInputStream doesn't expose nsIInputStreamLength if there are + // no nsIInputStreamLength sub streams. + { + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!fsis); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!afsis); + } + + // nsMultiplexInputStream exposes nsIInputStreamLength if there is one or + // more nsIInputStreamLength sub streams. + { + RefPtr<testing::LengthInputStream> inputStream = + new testing::LengthInputStream(buf, true, false); + + nsresult rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!afsis); + } + + // nsMultiplexInputStream exposes nsIAsyncInputStreamLength if there is one + // or more nsIAsyncInputStreamLength sub streams. + { + RefPtr<testing::LengthInputStream> inputStream = + new testing::LengthInputStream(buf, true, true); + + nsresult rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + } +} + +TEST(MultiplexInputStream, LengthInputStream) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + // First stream is a a simple one. + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + // A LengthInputStream, non-async. + RefPtr<testing::LengthInputStream> lengthStream = + new testing::LengthInputStream(buf, true, false); + + rv = multiplexStream->AppendStream(lengthStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + // Size is the sum of the 2 streams. + int64_t length; + rv = fsis->Length(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(int64_t(buf.Length() * 2), length); + + // An async LengthInputStream. + RefPtr<testing::LengthInputStream> asyncLengthStream = + new testing::LengthInputStream(buf, true, true, + NS_BASE_STREAM_WOULD_BLOCK); + + rv = multiplexStream->AppendStream(asyncLengthStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + + // Now it would block. + rv = fsis->Length(&length); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + // Let's read the size async. + RefPtr<testing::LengthCallback> callback = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, LengthInputStream) 1"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(int64_t(buf.Length() * 3), callback->Size()); + + // Now a negative stream + lengthStream = new testing::LengthInputStream(buf, true, false, NS_OK, true); + + rv = multiplexStream->AppendStream(lengthStream); + ASSERT_NS_SUCCEEDED(rv); + + rv = fsis->Length(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(-1, length); + + // Another async LengthInputStream. + asyncLengthStream = new testing::LengthInputStream( + buf, true, true, NS_BASE_STREAM_WOULD_BLOCK); + + rv = multiplexStream->AppendStream(asyncLengthStream); + ASSERT_NS_SUCCEEDED(rv); + + afsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + + // Let's read the size async. + RefPtr<testing::LengthCallback> callback1 = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback1, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + RefPtr<testing::LengthCallback> callback2 = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback2, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, LengthInputStream) 2"_ns, + [&]() { return callback2->Called(); })); + ASSERT_FALSE(callback1->Called()); + ASSERT_TRUE(callback2->Called()); +} + +void TestMultiplexStreamReadWhileWaiting(nsIAsyncInputStream* pipeIn, + nsIAsyncOutputStream* pipeOut) { + // We had an issue where a stream which was read while a message was in-flight + // to report the stream was ready, meaning that the stream reported 0 bytes + // available when checked in the MultiplexInputStream's callback, and was + // skipped over. + + nsCOMPtr<nsIThread> mainThread = NS_GetCurrentThread(); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_NS_SUCCEEDED(multiplexStream->AppendStream(pipeIn)); + + nsCOMPtr<nsIInputStream> stringStream; + ASSERT_TRUE(NS_SUCCEEDED( + NS_NewCStringInputStream(getter_AddRefs(stringStream), "xxxx\0"_ns))); + ASSERT_NS_SUCCEEDED(multiplexStream->AppendStream(stringStream)); + + nsCOMPtr<nsIAsyncInputStream> asyncMultiplex = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(asyncMultiplex); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + ASSERT_NS_SUCCEEDED(asyncMultiplex->AsyncWait(cb, 0, 0, mainThread)); + EXPECT_FALSE(cb->Called()); + + NS_ProcessPendingEvents(mainThread); + EXPECT_FALSE(cb->Called()); + + uint64_t available; + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); + + // Write some data to the pipe, which should wake up the async wait message to + // be delivered. + char toWrite[] = "1234"; + uint32_t written; + ASSERT_NS_SUCCEEDED(pipeOut->Write(toWrite, sizeof(toWrite), &written)); + EXPECT_EQ(written, sizeof(toWrite)); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, sizeof(toWrite)); + + // Read that data from the stream + char toRead[sizeof(toWrite)]; + uint32_t read; + ASSERT_TRUE( + NS_SUCCEEDED(asyncMultiplex->Read(toRead, sizeof(toRead), &read))); + EXPECT_EQ(read, sizeof(toRead)); + EXPECT_STREQ(toRead, toWrite); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); + + // The multiplex stream will have detected the read and prevented the callback + // from having been called yet. + NS_ProcessPendingEvents(mainThread); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); + + // Write more data and close, then make sure we can read everything else in + // the stream. + char toWrite2[] = "56789"; + ASSERT_TRUE( + NS_SUCCEEDED(pipeOut->Write(toWrite2, sizeof(toWrite2), &written))); + EXPECT_EQ(written, sizeof(toWrite2)); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, sizeof(toWrite2)); + + ASSERT_NS_SUCCEEDED(pipeOut->Close()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + // XXX: Theoretically if the multiplex stream could detect it, we could report + // `sizeof(toWrite2) + 4` because the stream is complete, but there's no way + // for the multiplex stream to know. + EXPECT_EQ(available, sizeof(toWrite2)); + + NS_ProcessPendingEvents(mainThread); + EXPECT_TRUE(cb->Called()); + + // Read that final bit of data and make sure we read it. + char toRead2[sizeof(toWrite2)]; + ASSERT_TRUE( + NS_SUCCEEDED(asyncMultiplex->Read(toRead2, sizeof(toRead2), &read))); + EXPECT_EQ(read, sizeof(toRead2)); + EXPECT_STREQ(toRead2, toWrite2); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 5u); + + // Read the extra data as well. + char extraRead[5]; + ASSERT_TRUE( + NS_SUCCEEDED(asyncMultiplex->Read(extraRead, sizeof(extraRead), &read))); + EXPECT_EQ(read, sizeof(extraRead)); + EXPECT_STREQ(extraRead, "xxxx"); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); +} + +TEST(MultiplexInputStream, ReadWhileWaiting_nsPipe) +{ + nsCOMPtr<nsIAsyncInputStream> pipeIn; + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true); + TestMultiplexStreamReadWhileWaiting(pipeIn, pipeOut); +} + +TEST(MultiplexInputStream, ReadWhileWaiting_DataPipe) +{ + RefPtr<mozilla::ipc::DataPipeReceiver> pipeIn; + RefPtr<mozilla::ipc::DataPipeSender> pipeOut; + ASSERT_TRUE(NS_SUCCEEDED(mozilla::ipc::NewDataPipe( + mozilla::ipc::kDefaultDataPipeCapacity, getter_AddRefs(pipeOut), + getter_AddRefs(pipeIn)))); + TestMultiplexStreamReadWhileWaiting(pipeIn, pipeOut); +} diff --git a/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp new file mode 100644 index 0000000000..2294794ad2 --- /dev/null +++ b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp @@ -0,0 +1,167 @@ +/* -*- 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/ArrayUtils.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +TEST(NSPRLogModulesParser, Empty) +{ + bool callbackInvoked = false; + auto callback = [&](const char*, mozilla::LogLevel, int32_t) mutable { + callbackInvoked = true; + }; + + mozilla::NSPRLogModulesParser(nullptr, callback); + EXPECT_FALSE(callbackInvoked); + + mozilla::NSPRLogModulesParser("", callback); + EXPECT_FALSE(callbackInvoked); +} + +TEST(NSPRLogModulesParser, DefaultLevel) +{ + bool callbackInvoked = false; + auto callback = [&](const char* aName, mozilla::LogLevel aLevel, int32_t) { + EXPECT_STREQ("Foo", aName); + EXPECT_EQ(mozilla::LogLevel::Error, aLevel); + callbackInvoked = true; + }; + + mozilla::NSPRLogModulesParser("Foo", callback); + EXPECT_TRUE(callbackInvoked); + + callbackInvoked = false; + mozilla::NSPRLogModulesParser("Foo:", callback); + EXPECT_TRUE(callbackInvoked); +} + +TEST(NSPRLogModulesParser, LevelSpecified) +{ + std::pair<const char*, mozilla::LogLevel> expected[] = { + {"Foo:0", mozilla::LogLevel::Disabled}, + {"Foo:1", mozilla::LogLevel::Error}, + {"Foo:2", mozilla::LogLevel::Warning}, + {"Foo:3", mozilla::LogLevel::Info}, + {"Foo:4", mozilla::LogLevel::Debug}, + {"Foo:5", mozilla::LogLevel::Verbose}, + {"Foo:25", mozilla::LogLevel::Verbose}, // too high + {"Foo:-12", mozilla::LogLevel::Disabled} // too low + }; + + auto* currTest = expected; + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(expected); i++) { + bool callbackInvoked = false; + mozilla::NSPRLogModulesParser( + currTest->first, + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) { + EXPECT_STREQ("Foo", aName); + EXPECT_EQ(currTest->second, aLevel); + callbackInvoked = true; + }); + EXPECT_TRUE(callbackInvoked); + currTest++; + } +} + +TEST(NSPRLogModulesParser, Multiple) +{ + std::pair<const char*, mozilla::LogLevel> expected[] = { + {"timestamp", mozilla::LogLevel::Error}, + {"Foo", mozilla::LogLevel::Info}, + {"Bar", mozilla::LogLevel::Error}, + {"Baz", mozilla::LogLevel::Warning}, + {"Qux", mozilla::LogLevel::Verbose}, + }; + + const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected); + + auto* currTest = expected; + + size_t count = 0; + mozilla::NSPRLogModulesParser( + "timestamp,Foo:3, Bar,Baz:2, Qux:5", + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) mutable { + ASSERT_LT(count, kExpectedCount); + EXPECT_STREQ(currTest->first, aName); + EXPECT_EQ(currTest->second, aLevel); + currTest++; + count++; + }); + + EXPECT_EQ(kExpectedCount, count); +} + +TEST(NSPRLogModulesParser, Characters) +{ + std::pair<const char*, mozilla::LogLevel> expected[] = { + {"valid.name", mozilla::LogLevel::Verbose}, + {"valid_name", mozilla::LogLevel::Debug}, + {"invalid", mozilla::LogLevel::Error}, + }; + + const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected); + + auto* currTest = expected; + + size_t count = 0; + mozilla::NSPRLogModulesParser( + "valid.name:5,valid_name:4,invalid/name:3,aborts-everything:2", + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) mutable { + ASSERT_LT(count, kExpectedCount); + EXPECT_STREQ(currTest->first, aName); + EXPECT_EQ(currTest->second, aLevel); + currTest++; + count++; + }); + + EXPECT_EQ(kExpectedCount, count); +} + +TEST(NSPRLogModulesParser, RawArg) +{ + bool callbackInvoked = false; + auto callback = [&](const char* aName, mozilla::LogLevel aLevel, + int32_t aRawValue) { + EXPECT_STREQ("Foo", aName); + EXPECT_EQ(mozilla::LogLevel::Verbose, aLevel); + EXPECT_EQ(1000, aRawValue); + callbackInvoked = true; + }; + + mozilla::NSPRLogModulesParser("Foo:1000", callback); + EXPECT_TRUE(callbackInvoked); +} + +TEST(NSPRLogModulesParser, RustModules) +{ + std::pair<const char*, mozilla::LogLevel> expected[] = { + {"timestamp", mozilla::LogLevel::Error}, + {"crate::mod::file1", mozilla::LogLevel::Error}, + {"crate::mod::file2", mozilla::LogLevel::Verbose}, + {"crate::mod::*", mozilla::LogLevel::Info}, + }; + + const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected); + + auto* currTest = expected; + + size_t count = 0; + mozilla::NSPRLogModulesParser( + "timestamp,crate::mod::file1, crate::mod::file2:5, crate::mod::*:3", + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) mutable { + ASSERT_LT(count, kExpectedCount); + EXPECT_STREQ(currTest->first, aName); + EXPECT_EQ(currTest->second, aLevel); + currTest++; + count++; + }); + + EXPECT_EQ(kExpectedCount, count); +} diff --git a/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp b/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp new file mode 100644 index 0000000000..8301adf6c8 --- /dev/null +++ b/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp @@ -0,0 +1,379 @@ +#include "gtest/gtest.h" + +#include "mozilla/NonBlockingAsyncInputStream.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIAsyncInputStream.h" +#include "nsIThread.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "Helpers.h" + +using mozilla::NonBlockingAsyncInputStream; +using mozilla::SpinEventLoopUntil; + +TEST(TestNonBlockingAsyncInputStream, Simple) +{ + nsCString data; + data.Assign("Hello world!"); + + // It should not be async. + bool nonBlocking = false; + nsCOMPtr<nsIAsyncInputStream> async; + + { + // Let's create a test string inputStream + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + async = do_QueryInterface(stream); + ASSERT_EQ(nullptr, async); + + // It must be non-blocking + ASSERT_EQ(NS_OK, stream->IsNonBlocking(&nonBlocking)); + ASSERT_TRUE(nonBlocking); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Still non-blocking + ASSERT_EQ(NS_OK, async->IsNonBlocking(&nonBlocking)); + ASSERT_TRUE(nonBlocking); + + // Testing ::Available() + uint64_t length; + ASSERT_EQ(NS_OK, async->Available(&length)); + ASSERT_EQ(data.Length(), length); + + // Read works fine. + char buffer[1024]; + uint32_t read = 0; + ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read)); + ASSERT_EQ(data.Length(), read); + ASSERT_TRUE(data.Equals(nsCString(buffer, read))); +} + +class ReadSegmentsData { + public: + ReadSegmentsData(nsIInputStream* aStream, char* aBuffer) + : mStream(aStream), mBuffer(aBuffer) {} + + nsIInputStream* mStream; + char* mBuffer; +}; + +static nsresult ReadSegmentsFunction(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + ReadSegmentsData* data = static_cast<ReadSegmentsData*>(aClosure); + if (aInStr != data->mStream) return NS_ERROR_FAILURE; + memcpy(&data->mBuffer[aOffset], aBuffer, aCount); + *aCountWritten = aCount; + return NS_OK; +} + +TEST(TestNonBlockingAsyncInputStream, ReadSegments) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr<nsIAsyncInputStream> async; + { + // Let's create a test string inputStream + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + + // Read works fine. + char buffer[1024]; + uint32_t read = 0; + ReadSegmentsData closure(async, buffer); + ASSERT_EQ(NS_OK, async->ReadSegments(ReadSegmentsFunction, &closure, + sizeof(buffer), &read)); + ASSERT_EQ(data.Length(), read); + ASSERT_TRUE(data.Equals(nsCString(buffer, read))); +} + +TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr<nsIAsyncInputStream> async; + { + // Let's create a test string inputStream + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Testing ::Available() + uint64_t length; + ASSERT_EQ(NS_OK, async->Available(&length)); + ASSERT_EQ(data.Length(), length); + + // Testing ::AsyncWait - without EventTarget + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, nullptr)); + ASSERT_TRUE(cb->Called()); + + // Testing ::AsyncWait - with EventTarget + cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, thread)); + ASSERT_FALSE(cb->Called()); + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); + + // Read works fine. + char buffer[1024]; + uint32_t read = 0; + ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read)); + ASSERT_EQ(data.Length(), read); + ASSERT_TRUE(data.Equals(nsCString(buffer, read))); +} + +TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withoutEventTarget) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr<nsIAsyncInputStream> async; + { + // Let's create a test string inputStream + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Testing ::AsyncWait - no eventTarget + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, + 0, nullptr)); + + ASSERT_FALSE(cb->Called()); + ASSERT_EQ(NS_OK, async->Close()); + ASSERT_TRUE(cb->Called()); +} + +TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr<nsIAsyncInputStream> async; + { + // Let's create a test string inputStream + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Testing ::AsyncWait - with EventTarget + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, + 0, thread)); + + ASSERT_FALSE(cb->Called()); + ASSERT_EQ(NS_OK, async->Close()); + ASSERT_FALSE(cb->Called()); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +TEST(TestNonBlockingAsyncInputStream, Helper) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr<nsIAsyncInputStream> async; + { + // Let's create a test string inputStream + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // This should return the same object because async is already non-blocking + // and async. + nsCOMPtr<nsIAsyncInputStream> result; + nsCOMPtr<nsIAsyncInputStream> asyncTmp = async; + ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream(asyncTmp.forget(), + getter_AddRefs(result))); + ASSERT_EQ(async, result); + + // This will use NonBlockingAsyncInputStream wrapper. + { + nsCOMPtr<nsIInputStream> stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream( + stream.forget(), getter_AddRefs(result))); + } + ASSERT_TRUE(async != result); + ASSERT_TRUE(async); +} + +class QIInputStream final : public nsIInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream { + public: + NS_DECL_ISUPPORTS + + QIInputStream(bool aNonBlockingError, bool aCloneable, bool aIPCSerializable, + bool aSeekable) + : mNonBlockingError(aNonBlockingError), + mCloneable(aCloneable), + mIPCSerializable(aIPCSerializable), + mSeekable(aSeekable) {} + + // nsIInputStream + NS_IMETHOD Close() override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD Available(uint64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD StreamStatus() override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD Read(char*, uint32_t, uint32_t*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD ReadSegments(nsWriteSegmentFun, void*, uint32_t, + uint32_t*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return mNonBlockingError ? NS_ERROR_FAILURE : NS_OK; + } + + // nsICloneableInputStream + NS_IMETHOD GetCloneable(bool*) override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD Clone(nsIInputStream**) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // nsIIPCSerializableInputStream + void SerializedComplexity(uint32_t, uint32_t*, uint32_t*, + uint32_t*) override {} + void Serialize(mozilla::ipc::InputStreamParams&, uint32_t, + uint32_t*) override {} + bool Deserialize(const mozilla::ipc::InputStreamParams&) override { + return false; + } + + // nsISeekableStream + NS_IMETHOD Seek(int32_t, int64_t) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD SetEOF() override { return NS_ERROR_NOT_IMPLEMENTED; } + + // nsITellableStream + NS_IMETHOD Tell(int64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; } + + private: + ~QIInputStream() = default; + + bool mNonBlockingError; + bool mCloneable; + bool mIPCSerializable; + bool mSeekable; +}; + +NS_IMPL_ADDREF(QIInputStream); +NS_IMPL_RELEASE(QIInputStream); + +NS_INTERFACE_MAP_BEGIN(QIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, mCloneable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mIPCSerializable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, mSeekable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +TEST(TestNonBlockingAsyncInputStream, QI) +{ + // Let's test ::Create() returning error. + + nsCOMPtr<nsIAsyncInputStream> async; + { + nsCOMPtr<nsIInputStream> stream = new QIInputStream(true, true, true, true); + + ASSERT_EQ(NS_ERROR_FAILURE, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + + // Let's test the QIs + for (int i = 0; i < 8; ++i) { + bool shouldBeCloneable = !!(i & 0x01); + bool shouldBeSerializable = !!(i & 0x02); + bool shouldBeSeekable = !!(i & 0x04); + + nsCOMPtr<nsICloneableInputStream> cloneable; + nsCOMPtr<nsIIPCSerializableInputStream> ipcSerializable; + nsCOMPtr<nsISeekableStream> seekable; + + { + nsCOMPtr<nsIInputStream> stream = new QIInputStream( + false, shouldBeCloneable, shouldBeSerializable, shouldBeSeekable); + + cloneable = do_QueryInterface(stream); + ASSERT_EQ(shouldBeCloneable, !!cloneable); + + ipcSerializable = do_QueryInterface(stream); + ASSERT_EQ(shouldBeSerializable, !!ipcSerializable); + + seekable = do_QueryInterface(stream); + ASSERT_EQ(shouldBeSeekable, !!seekable); + + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + + // The returned async stream should be cloneable only if the underlying + // stream is. + cloneable = do_QueryInterface(async); + ASSERT_EQ(shouldBeCloneable, !!cloneable); + + // The returned async stream should be serializable only if the underlying + // stream is. + ipcSerializable = do_QueryInterface(async); + ASSERT_EQ(shouldBeSerializable, !!ipcSerializable); + + // The returned async stream should be seekable only if the underlying + // stream is. + seekable = do_QueryInterface(async); + ASSERT_EQ(shouldBeSeekable, !!seekable); + } +} diff --git a/xpcom/tests/gtest/TestNsDeque.cpp b/xpcom/tests/gtest/TestNsDeque.cpp new file mode 100644 index 0000000000..af37246647 --- /dev/null +++ b/xpcom/tests/gtest/TestNsDeque.cpp @@ -0,0 +1,594 @@ +/* -*- 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 "gtest/gtest.h" +#include "nsDeque.h" +#include "nsCRT.h" +#include "mozilla/RefPtr.h" +#include <stdio.h> +#include <functional> +#include <type_traits> +#include <utility> + +/************************************************************** + Now define the token deallocator class... + **************************************************************/ +namespace TestNsDeque { + +template <typename T> +class _Dealloc : public nsDequeFunctor<T> { + virtual void operator()(T* aObject) {} +}; + +static bool VerifyContents(const nsDeque<int>& aDeque, const int* aContents, + size_t aLength) { + for (size_t i = 0; i < aLength; ++i) { + if (*aDeque.ObjectAt(i) != aContents[i]) { + return false; + } + } + return true; +} + +class Deallocator : public nsDequeFunctor<int> { + virtual void operator()(int* aObject) { + if (aObject) { + // Set value to -1, to use in test function. + *(aObject) = -1; + } + } +}; + +class ForEachAdder : public nsDequeFunctor<int> { + virtual void operator()(int* aObject) { + if (aObject) { + sum += *(int*)aObject; + } + } + + private: + int sum = 0; + + public: + int GetSum() { return sum; } +}; + +static uint32_t sFreeCount = 0; + +class RefCountedClass { + public: + RefCountedClass() : mRefCnt(0), mVal(0) {} + + ~RefCountedClass() { ++sFreeCount; } + + NS_METHOD_(MozExternalRefCountType) AddRef() { + mRefCnt++; + return mRefCnt; + } + NS_METHOD_(MozExternalRefCountType) Release() { + NS_ASSERTION(mRefCnt > 0, ""); + mRefCnt--; + if (mRefCnt == 0) { + delete this; + } + return 0; + } + + inline uint32_t GetRefCount() const { return mRefCnt; } + + inline void SetVal(int aVal) { mVal = aVal; } + + inline int GetVal() const { return mVal; } + + private: + uint32_t mRefCnt; + int mVal; +}; + +class ForEachRefPtr : public nsDequeFunctor<RefCountedClass> { + virtual void operator()(RefCountedClass* aObject) { + if (aObject) { + aObject->SetVal(mVal); + } + } + + private: + int mVal = 0; + + public: + explicit ForEachRefPtr(int aVal) : mVal(aVal) {} +}; + +} // namespace TestNsDeque + +using namespace TestNsDeque; + +TEST(NsDeque, OriginalTest) +{ + const size_t size = 200; + int ints[size]; + size_t i = 0; + int temp; + nsDeque<int> theDeque(new _Dealloc<int>); // construct a simple one... + + // ints = [0...199] + for (i = 0; i < size; i++) { // initialize'em + ints[i] = static_cast<int>(i); + } + // queue = [0...69] + for (i = 0; i < 70; i++) { + theDeque.Push(&ints[i]); + temp = *theDeque.Peek(); + EXPECT_EQ(static_cast<int>(i), temp) << "Verify end after push #1"; + EXPECT_EQ(i + 1, theDeque.GetSize()) << "Verify size after push #1"; + } + + EXPECT_EQ(70u, theDeque.GetSize()) << "Verify overall size after pushes #1"; + + // queue = [0...14] + for (i = 1; i <= 55; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(70 - static_cast<int>(i), temp) << "Verify end after pop # 1"; + EXPECT_EQ(70u - i, theDeque.GetSize()) << "Verify size after pop # 1"; + } + EXPECT_EQ(15u, theDeque.GetSize()) << "Verify overall size after pops"; + + // queue = [0...14,0...54] + for (i = 0; i < 55; i++) { + theDeque.Push(&ints[i]); + temp = *theDeque.Peek(); + EXPECT_EQ(static_cast<int>(i), temp) << "Verify end after push #2"; + EXPECT_EQ(i + 15u + 1, theDeque.GetSize()) << "Verify size after push # 2"; + } + EXPECT_EQ(70u, theDeque.GetSize()) + << "Verify size after end of all pushes #2"; + + // queue = [0...14,0...19] + for (i = 1; i <= 35; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(55 - static_cast<int>(i), temp) << "Verify end after pop # 2"; + EXPECT_EQ(70u - i, theDeque.GetSize()) << "Verify size after pop #2"; + } + EXPECT_EQ(35u, theDeque.GetSize()) + << "Verify overall size after end of all pops #2"; + + // queue = [0...14,0...19,0...34] + for (i = 0; i < 35; i++) { + theDeque.Push(&ints[i]); + temp = *theDeque.Peek(); + EXPECT_EQ(static_cast<int>(i), temp) << "Verify end after push # 3"; + EXPECT_EQ(35u + 1u + i, theDeque.GetSize()) << "Verify size after push #3"; + } + + // queue = [0...14,0...19] + for (i = 0; i < 35; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(34 - static_cast<int>(i), temp) << "Verify end after pop # 3"; + } + + // queue = [0...14] + for (i = 0; i < 20; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(19 - static_cast<int>(i), temp) << "Verify end after pop # 4"; + } + + // queue = [] + for (i = 0; i < 15; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(14 - static_cast<int>(i), temp) << "Verify end after pop # 5"; + } + + EXPECT_EQ(0u, theDeque.GetSize()) << "Deque should finish empty."; +} + +TEST(NsDeque, OriginalFlaw) +{ + int ints[200]; + int i = 0; + int temp; + nsDeque<int> d(new _Dealloc<int>); + /** + * Test 1. Origin near end, semi full, call Peek(). + * you start, mCapacity is 8 + */ + for (i = 0; i < 30; i++) ints[i] = i; + + for (i = 0; i < 6; i++) { + d.Push(&ints[i]); + temp = *d.Peek(); + EXPECT_EQ(i, temp) << "OriginalFlaw push #1"; + } + EXPECT_EQ(6u, d.GetSize()) << "OriginalFlaw size check #1"; + + for (i = 0; i < 4; i++) { + temp = *d.PopFront(); + EXPECT_EQ(i, temp) << "PopFront test"; + } + // d = [4,5] + EXPECT_EQ(2u, d.GetSize()) << "OriginalFlaw size check #2"; + + for (i = 0; i < 4; i++) { + d.Push(&ints[6 + i]); + } + + // d = [4...9] + for (i = 4; i <= 9; i++) { + temp = *d.PopFront(); + EXPECT_EQ(i, temp) << "OriginalFlaw empty check"; + } +} + +TEST(NsDeque, TestObjectAt) +{ + nsDeque<int> d; + const int count = 10; + int ints[count]; + for (int i = 0; i < count; i++) { + ints[i] = i; + } + + for (int i = 0; i < 6; i++) { + d.Push(&ints[i]); + } + // d = [0...5] + d.PopFront(); + d.PopFront(); + + // d = [2..5] + for (size_t i = 2; i <= 5; i++) { + int t = *d.ObjectAt(i - 2); + EXPECT_EQ(static_cast<int>(i), t) << "Verify ObjectAt()"; + } +} + +TEST(NsDeque, TestPushFront) +{ + // PushFront has some interesting corner cases, primarily we're interested in + // whether: + // - wrapping around works properly + // - growing works properly + + nsDeque<int> d; + + const int kPoolSize = 10; + const size_t kMaxSizeBeforeGrowth = 8; + + int pool[kPoolSize]; + for (int i = 0; i < kPoolSize; i++) { + pool[i] = i; + } + + for (size_t i = 0; i < kMaxSizeBeforeGrowth; i++) { + d.PushFront(pool + i); + } + + EXPECT_EQ(kMaxSizeBeforeGrowth, d.GetSize()) << "verify size"; + + static const int t1[] = {7, 6, 5, 4, 3, 2, 1, 0}; + EXPECT_TRUE(VerifyContents(d, t1, kMaxSizeBeforeGrowth)) + << "verify pushfront 1"; + + // Now push one more so it grows + d.PushFront(pool + kMaxSizeBeforeGrowth); + EXPECT_EQ(kMaxSizeBeforeGrowth + 1, d.GetSize()) << "verify size"; + + static const int t2[] = {8, 7, 6, 5, 4, 3, 2, 1, 0}; + EXPECT_TRUE(VerifyContents(d, t2, kMaxSizeBeforeGrowth + 1)) + << "verify pushfront 2"; + + // And one more so that it wraps again + d.PushFront(pool + kMaxSizeBeforeGrowth + 1); + EXPECT_EQ(kMaxSizeBeforeGrowth + 2, d.GetSize()) << "verify size"; + + static const int t3[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + EXPECT_TRUE(VerifyContents(d, t3, kMaxSizeBeforeGrowth + 2)) + << "verify pushfront 3"; +} + +template <typename T> +static void CheckIfQueueEmpty(nsDeque<T>& d) { + EXPECT_EQ(0u, d.GetSize()) << "Size should be 0"; + EXPECT_EQ(nullptr, d.Pop()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.PopFront()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.Peek()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.PeekFront()) + << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.ObjectAt(0u)) + << "Invalid operation should return nullptr"; +} + +TEST(NsDeque, TestEmpty) +{ + // Make sure nsDeque gives sane results if it's empty. + nsDeque<void> d; + size_t numberOfEntries = 8; + + CheckIfQueueEmpty(d); + + // Fill it up and drain it. + for (size_t i = 0; i < numberOfEntries; i++) { + d.Push((void*)0xAA); + } + + EXPECT_EQ(numberOfEntries, d.GetSize()); + + for (size_t i = 0; i < numberOfEntries; i++) { + (void)d.Pop(); + } + + // Now check it again. + CheckIfQueueEmpty(d); +} + +TEST(NsDeque, TestEraseMethod) +{ + nsDeque<void> d; + const size_t numberOfEntries = 8; + + // Fill it up before calling Erase + for (size_t i = 0; i < numberOfEntries; i++) { + d.Push((void*)0xAA); + } + + // Call Erase + d.Erase(); + + // Now check it again. + CheckIfQueueEmpty(d); +} + +TEST(NsDeque, TestEraseShouldCallDeallocator) +{ + nsDeque<int> d(new Deallocator()); + const size_t NumTestValues = 8; + + int* testArray[NumTestValues]; + for (size_t i = 0; i < NumTestValues; i++) { + testArray[i] = new int(); + *(testArray[i]) = i; + d.Push(testArray[i]); + } + + d.Erase(); + + // Now check it again. + CheckIfQueueEmpty(d); + + for (size_t i = 0; i < NumTestValues; i++) { + EXPECT_EQ(-1, *(testArray[i])) + << "Erase should call deallocator: " << *(testArray[i]); + } +} + +TEST(NsDeque, TestForEach) +{ + nsDeque<int> d(new Deallocator()); + const size_t NumTestValues = 8; + int sum = 0; + + int* testArray[NumTestValues]; + for (size_t i = 0; i < NumTestValues; i++) { + testArray[i] = new int(); + *(testArray[i]) = i; + sum += i; + d.Push(testArray[i]); + } + + ForEachAdder adder; + d.ForEach(adder); + EXPECT_EQ(sum, adder.GetSum()) << "For each should iterate over values"; + + d.Erase(); +} + +TEST(NsDeque, TestConstRangeFor) +{ + nsDeque<int> d(new Deallocator()); + + const size_t NumTestValues = 3; + for (size_t i = 0; i < NumTestValues; ++i) { + d.Push(new int(i + 1)); + } + + static_assert( + std::is_same_v<nsDeque<int>::ConstDequeIterator, + decltype(std::declval<const nsDeque<int>&>().begin())>, + "(const nsDeque).begin() should return ConstDequeIterator"); + static_assert( + std::is_same_v<nsDeque<int>::ConstDequeIterator, + decltype(std::declval<const nsDeque<int>&>().end())>, + "(const nsDeque).end() should return ConstDequeIterator"); + + int sum = 0; + for (int* ob : const_cast<const nsDeque<int>&>(d)) { + sum += *ob; + } + EXPECT_EQ(1 + 2 + 3, sum) << "Const-range-for should iterate over values"; +} + +TEST(NsDeque, TestRangeFor) +{ + const size_t NumTestValues = 3; + struct Test { + size_t runAfterLoopCount; + std::function<void(nsDeque<int>&)> function; + int expectedSum; + const char* description; + }; + // Note: All tests start with a deque containing 3 pointers to ints 1, 2, 3. + Test tests[] = { + {0, [](nsDeque<int>& d) {}, 1 + 2 + 3, "no changes"}, + + {1, [](nsDeque<int>& d) { d.Pop(); }, 1 + 2, "Pop after 1st loop"}, + {2, [](nsDeque<int>& d) { d.Pop(); }, 1 + 2, "Pop after 2nd loop"}, + {3, [](nsDeque<int>& d) { d.Pop(); }, 1 + 2 + 3, "Pop after 3rd loop"}, + + {1, [](nsDeque<int>& d) { d.PopFront(); }, 1 + 3, + "PopFront after 1st loop"}, + {2, [](nsDeque<int>& d) { d.PopFront(); }, 1 + 2, + "PopFront after 2nd loop"}, + {3, [](nsDeque<int>& d) { d.PopFront(); }, 1 + 2 + 3, + "PopFront after 3rd loop"}, + + {1, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4, + "Push after 1st loop"}, + {2, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4, + "Push after 2nd loop"}, + {3, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4, + "Push after 3rd loop"}, + {4, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3, + "Push after would-be-4th loop"}, + + {1, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 1 + 2 + 3, + "PushFront after 1st loop"}, + {2, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 2 + 2 + 3, + "PushFront after 2nd loop"}, + {3, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 2 + 3 + 3, + "PushFront after 3rd loop"}, + {4, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 2 + 3, + "PushFront after would-be-4th loop"}, + + {1, [](nsDeque<int>& d) { d.Erase(); }, 1, "Erase after 1st loop"}, + {2, [](nsDeque<int>& d) { d.Erase(); }, 1 + 2, "Erase after 2nd loop"}, + {3, [](nsDeque<int>& d) { d.Erase(); }, 1 + 2 + 3, + "Erase after 3rd loop"}, + + {1, + [](nsDeque<int>& d) { + d.Erase(); + d.Push(new int(4)); + }, + 1, "Erase after 1st loop, Push 4"}, + {1, + [](nsDeque<int>& d) { + d.Erase(); + d.Push(new int(4)); + d.Push(new int(5)); + }, + 1 + 5, "Erase after 1st loop, Push 4,5"}, + {2, + [](nsDeque<int>& d) { + d.Erase(); + d.Push(new int(4)); + }, + 1 + 2, "Erase after 2nd loop, Push 4"}, + {2, + [](nsDeque<int>& d) { + d.Erase(); + d.Push(new int(4)); + d.Push(new int(5)); + }, + 1 + 2, "Erase after 2nd loop, Push 4,5"}, + {2, + [](nsDeque<int>& d) { + d.Erase(); + d.Push(new int(4)); + d.Push(new int(5)); + d.Push(new int(6)); + }, + 1 + 2 + 6, "Erase after 2nd loop, Push 4,5,6"}}; + + for (const Test& test : tests) { + nsDeque<int> d(new Deallocator()); + + for (size_t i = 0; i < NumTestValues; ++i) { + d.Push(new int(i + 1)); + } + + static_assert( + std::is_same_v<nsDeque<int>::ConstIterator, decltype(d.begin())>, + "(non-const nsDeque).begin() should return ConstIterator"); + static_assert( + std::is_same_v<nsDeque<int>::ConstIterator, decltype(d.end())>, + "(non-const nsDeque).end() should return ConstIterator"); + + int sum = 0; + size_t loopCount = 0; + for (int* ob : d) { + sum += *ob; + if (++loopCount == test.runAfterLoopCount) { + test.function(d); + } + } + EXPECT_EQ(test.expectedSum, sum) + << "Range-for should iterate over values in test '" << test.description + << "'"; + } +} + +TEST(NsDeque, RefPtrDeque) +{ + sFreeCount = 0; + nsRefPtrDeque<RefCountedClass> deque; + RefPtr<RefCountedClass> ptr1 = new RefCountedClass(); + EXPECT_EQ(1u, ptr1->GetRefCount()); + + deque.Push(ptr1); + EXPECT_EQ(2u, ptr1->GetRefCount()); + + { + auto* peekPtr1 = deque.Peek(); + EXPECT_TRUE(peekPtr1); + EXPECT_EQ(2u, ptr1->GetRefCount()); + EXPECT_EQ(1u, deque.GetSize()); + } + + { + RefPtr<RefCountedClass> ptr2 = new RefCountedClass(); + deque.PushFront(ptr2.forget()); + EXPECT_TRUE(deque.PeekFront()); + ptr2 = deque.PopFront(); + EXPECT_EQ(ptr1, deque.PeekFront()); + } + EXPECT_EQ(1u, sFreeCount); + + { + RefPtr<RefCountedClass> popPtr1 = deque.Pop(); + EXPECT_TRUE(popPtr1); + EXPECT_EQ(2u, ptr1->GetRefCount()); + EXPECT_EQ(0u, deque.GetSize()); + } + + EXPECT_EQ(1u, ptr1->GetRefCount()); + deque.Erase(); + EXPECT_EQ(0u, deque.GetSize()); + ptr1 = nullptr; + EXPECT_EQ(2u, sFreeCount); +} + +TEST(NsDeque, RefPtrDequeTestIterator) +{ + sFreeCount = 0; + nsRefPtrDeque<RefCountedClass> deque; + const uint32_t cnt = 10; + for (uint32_t i = 0; i < cnt; ++i) { + RefPtr<RefCountedClass> ptr = new RefCountedClass(); + deque.Push(ptr.forget()); + EXPECT_TRUE(deque.Peek()); + } + EXPECT_EQ(cnt, deque.GetSize()); + + int val = 100; + ForEachRefPtr functor(val); + deque.ForEach(functor); + + uint32_t pos = 0; + for (nsRefPtrDeque<RefCountedClass>::ConstIterator it = deque.begin(); + it != deque.end(); ++it) { + RefPtr<RefCountedClass> cur = *it; + EXPECT_TRUE(cur); + EXPECT_EQ(cur, deque.ObjectAt(pos++)); + EXPECT_EQ(val, cur->GetVal()); + } + + EXPECT_EQ(deque.ObjectAt(0), deque.PeekFront()); + EXPECT_EQ(deque.ObjectAt(cnt - 1), deque.Peek()); + + deque.Erase(); + + EXPECT_EQ(0u, deque.GetSize()); + EXPECT_EQ(cnt, sFreeCount); +} diff --git a/xpcom/tests/gtest/TestNsRefPtr.cpp b/xpcom/tests/gtest/TestNsRefPtr.cpp new file mode 100644 index 0000000000..cd89f2e86f --- /dev/null +++ b/xpcom/tests/gtest/TestNsRefPtr.cpp @@ -0,0 +1,444 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsQueryObject.h" +#include "mozilla/Unused.h" + +#include "gtest/gtest.h" + +namespace TestNsRefPtr { + +#define NS_FOO_IID \ + { \ + 0x6f7652e0, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class Foo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_FOO_IID) + + public: + Foo(); + // virtual dtor because Bar uses our Release() + virtual ~Foo(); + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override; + NS_IMETHOD_(MozExternalRefCountType) Release() override; + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + void MemberFunction(int, int*, int&); + virtual void VirtualMemberFunction(int, int*, int&); + virtual void VirtualConstMemberFunction(int, int*, int&) const; + + void NonconstMethod() {} + void ConstMethod() const {} + + int refcount_; + + static int total_constructions_; + static int total_destructions_; + static int total_addrefs_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(Foo, NS_FOO_IID) + +int Foo::total_constructions_; +int Foo::total_destructions_; +int Foo::total_addrefs_; +int Foo::total_queries_; + +Foo::Foo() : refcount_(0) { ++total_constructions_; } + +Foo::~Foo() { ++total_destructions_; } + +MozExternalRefCountType Foo::AddRef() { + ++refcount_; + ++total_addrefs_; + return refcount_; +} + +MozExternalRefCountType Foo::Release() { + int newcount = --refcount_; + if (newcount == 0) { + delete this; + } + + return newcount; +} + +nsresult Foo::QueryInterface(const nsIID& aIID, void** aResult) { + ++total_queries_; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(Foo))) + rawPtr = this; + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast<nsISupports*>(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +void Foo::MemberFunction(int aArg1, int* aArgPtr, int& aArgRef) {} + +void Foo::VirtualMemberFunction(int aArg1, int* aArgPtr, int& aArgRef) {} + +void Foo::VirtualConstMemberFunction(int aArg1, int* aArgPtr, + int& aArgRef) const {} + +static nsresult CreateFoo(void** result) +// a typical factory function (that calls AddRef) +{ + auto* foop = new Foo; + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +static void set_a_Foo(RefPtr<Foo>* result) { + assert(result); + + RefPtr<Foo> foop(do_QueryObject(new Foo)); + *result = foop; +} + +static RefPtr<Foo> return_a_Foo() { + RefPtr<Foo> foop(do_QueryObject(new Foo)); + return foop; +} + +#define NS_BAR_IID \ + { \ + 0x6f7652e1, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class Bar : public Foo { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_BAR_IID) + + public: + Bar(); + ~Bar() override; + + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + void VirtualMemberFunction(int, int*, int&) override; + void VirtualConstMemberFunction(int, int*, int&) const override; + + static int total_constructions_; + static int total_destructions_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(Bar, NS_BAR_IID) + +int Bar::total_constructions_; +int Bar::total_destructions_; +int Bar::total_queries_; + +Bar::Bar() { ++total_constructions_; } + +Bar::~Bar() { ++total_destructions_; } + +nsresult Bar::QueryInterface(const nsID& aIID, void** aResult) { + ++total_queries_; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(Bar))) + rawPtr = this; + else if (aIID.Equals(NS_GET_IID(Foo))) + rawPtr = static_cast<Foo*>(this); + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast<nsISupports*>(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +void Bar::VirtualMemberFunction(int aArg1, int* aArgPtr, int& aArgRef) {} +void Bar::VirtualConstMemberFunction(int aArg1, int* aArgPtr, + int& aArgRef) const {} + +} // namespace TestNsRefPtr + +using namespace TestNsRefPtr; + +TEST(nsRefPtr, AddRefAndRelease) +{ + Foo::total_constructions_ = 0; + Foo::total_destructions_ = 0; + + { + RefPtr<Foo> foop(do_QueryObject(new Foo)); + ASSERT_EQ(Foo::total_constructions_, 1); + ASSERT_EQ(Foo::total_destructions_, 0); + ASSERT_EQ(foop->refcount_, 1); + + foop = do_QueryObject(new Foo); + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 1); + + // [Shouldn't compile] Is it a compile time error to try to |AddRef| by + // hand? + // foop->AddRef(); + + // [Shouldn't compile] Is it a compile time error to try to |Release| be + // hand? + // foop->Release(); + + // [Shouldn't compile] Is it a compile time error to try to |delete| an + // |nsCOMPtr|? + // delete foop; + + static_cast<Foo*>(foop)->AddRef(); + ASSERT_EQ(foop->refcount_, 2); + + static_cast<Foo*>(foop)->Release(); + ASSERT_EQ(foop->refcount_, 1); + } + + ASSERT_EQ(Foo::total_destructions_, 2); + + { + RefPtr<Foo> fooP(do_QueryObject(new Foo)); + ASSERT_EQ(Foo::total_constructions_, 3); + ASSERT_EQ(Foo::total_destructions_, 2); + ASSERT_EQ(fooP->refcount_, 1); + + Foo::total_addrefs_ = 0; + RefPtr<Foo> fooP2 = std::move(fooP); + mozilla::Unused << fooP2; + ASSERT_EQ(Foo::total_addrefs_, 0); + } +} + +TEST(nsRefPtr, VirtualDestructor) +{ + Bar::total_destructions_ = 0; + + { + RefPtr<Foo> foop(do_QueryObject(new Bar)); + mozilla::Unused << foop; + } + + ASSERT_EQ(Bar::total_destructions_, 1); +} + +TEST(nsRefPtr, Equality) +{ + Foo::total_constructions_ = 0; + Foo::total_destructions_ = 0; + + { + RefPtr<Foo> foo1p(do_QueryObject(new Foo)); + RefPtr<Foo> foo2p(do_QueryObject(new Foo)); + + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 0); + + ASSERT_NE(foo1p, foo2p); + + ASSERT_NE(foo1p, nullptr); + ASSERT_NE(nullptr, foo1p); + ASSERT_FALSE(foo1p == nullptr); + ASSERT_FALSE(nullptr == foo1p); + + ASSERT_NE(foo1p, foo2p.get()); + + foo1p = foo2p; + + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 1); + ASSERT_EQ(foo1p, foo2p); + + ASSERT_EQ(foo2p, foo2p.get()); + + ASSERT_EQ(RefPtr<Foo>(foo2p.get()), foo2p); + + ASSERT_TRUE(foo1p); + } + + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 2); +} + +TEST(nsRefPtr, AddRefHelpers) +{ + Foo::total_addrefs_ = 0; + + { + auto* raw_foo1p = new Foo; + raw_foo1p->AddRef(); + + auto* raw_foo2p = new Foo; + raw_foo2p->AddRef(); + + ASSERT_EQ(Foo::total_addrefs_, 2); + + RefPtr<Foo> foo1p(dont_AddRef(raw_foo1p)); + + ASSERT_EQ(Foo::total_addrefs_, 2); + + RefPtr<Foo> foo2p; + foo2p = dont_AddRef(raw_foo2p); + + ASSERT_EQ(Foo::total_addrefs_, 2); + } + + { + // Test that various assignment helpers compile. + RefPtr<Foo> foop; + CreateFoo(RefPtrGetterAddRefs<Foo>(foop)); + CreateFoo(getter_AddRefs(foop)); + set_a_Foo(address_of(foop)); + foop = return_a_Foo(); + } +} + +TEST(nsRefPtr, QueryInterface) +{ + Foo::total_queries_ = 0; + Bar::total_queries_ = 0; + + { + RefPtr<Foo> fooP; + fooP = do_QueryObject(new Foo); + ASSERT_EQ(Foo::total_queries_, 1); + } + + { + RefPtr<Foo> fooP; + fooP = do_QueryObject(new Foo); + ASSERT_EQ(Foo::total_queries_, 2); + + RefPtr<Foo> foo2P; + foo2P = fooP; + ASSERT_EQ(Foo::total_queries_, 2); + } + + { + RefPtr<Bar> barP(do_QueryObject(new Bar)); + ASSERT_EQ(Bar::total_queries_, 1); + + RefPtr<Foo> fooP(do_QueryObject(barP)); + ASSERT_TRUE(fooP); + ASSERT_EQ(Foo::total_queries_, 2); + ASSERT_EQ(Bar::total_queries_, 2); + } +} + +// ------------------------------------------------------------------------- +// TODO(ER): The following tests should be moved to MFBT. + +#define NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(_class) \ + public: \ + NS_METHOD_(MozExternalRefCountType) AddRef(void) const { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_RELEASE_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + nsrefcnt count = ++mRefCnt; \ + return (nsrefcnt)count; \ + } \ + NS_METHOD_(MozExternalRefCountType) Release(void) const { \ + MOZ_RELEASE_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + nsrefcnt count = --mRefCnt; \ + if (count == 0) { \ + delete (this); \ + return 0; \ + } \ + return count; \ + } \ + \ + protected: \ + mutable ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + \ + public: + +class ObjectForConstPtr { + private: + // Reference-counted classes cannot have public destructors. + ~ObjectForConstPtr() = default; + + public: + NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(ObjectForConstPtr) + void ConstMemberFunction(int aArg1, int* aArgPtr, int& aArgRef) const {} +}; +#undef NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING + +namespace TestNsRefPtr { +static void AnFooPtrPtrContext(Foo**) {} +static void AVoidPtrPtrContext(void**) {} +} // namespace TestNsRefPtr + +TEST(nsRefPtr, RefPtrCompilationTests) +{ + { + RefPtr<Foo> fooP; + + AnFooPtrPtrContext(getter_AddRefs(fooP)); + AVoidPtrPtrContext(getter_AddRefs(fooP)); + } + + { + RefPtr<Foo> fooP(new Foo); + RefPtr<const Foo> constFooP = fooP; + constFooP->ConstMethod(); + + // [Shouldn't compile] Is it a compile time error to call a non-const method + // on an |RefPtr<const T>|? + // constFooP->NonconstMethod(); + + // [Shouldn't compile] Is it a compile time error to construct an |RefPtr<T> + // from an |RefPtr<const T>|? + // RefPtr<Foo> otherFooP(constFooP); + } + + { + RefPtr<Foo> foop = new Foo; + RefPtr<Foo> foop2 = new Bar; + RefPtr<const ObjectForConstPtr> foop3 = new ObjectForConstPtr; + int test = 1; + void (Foo::*fPtr)(int, int*, int&) = &Foo::MemberFunction; + void (Foo::*fVPtr)(int, int*, int&) = &Foo::VirtualMemberFunction; + void (Foo::*fVCPtr)(int, int*, int&) const = + &Foo::VirtualConstMemberFunction; + void (ObjectForConstPtr::*fCPtr2)(int, int*, int&) const = + &ObjectForConstPtr::ConstMemberFunction; + + (foop->*fPtr)(test, &test, test); + (foop2->*fVPtr)(test, &test, test); + (foop2->*fVCPtr)(test, &test, test); + (foop3->*fCPtr2)(test, &test, test); + } + + // Looks like everything ran. + ASSERT_TRUE(true); +} diff --git a/xpcom/tests/gtest/TestObserverArray.cpp b/xpcom/tests/gtest/TestObserverArray.cpp new file mode 100644 index 0000000000..8a4744d06b --- /dev/null +++ b/xpcom/tests/gtest/TestObserverArray.cpp @@ -0,0 +1,573 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=4:et:sw=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 "nsTObserverArray.h" +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/UniquePtr.h" + +using namespace mozilla; + +typedef nsTObserverArray<int> IntArray; + +#define DO_TEST(_type, _exp, _code) \ + do { \ + ++testNum; \ + count = 0; \ + IntArray::_type iter(arr); \ + while (iter.HasMore() && count != ArrayLength(_exp)) { \ + _code int next = iter.GetNext(); \ + int expected = _exp[count++]; \ + ASSERT_EQ(next, expected) \ + << "During test " << testNum << " at position " << count - 1; \ + } \ + ASSERT_FALSE(iter.HasMore()) \ + << "During test " << testNum << ", iterator ran over"; \ + ASSERT_EQ(count, ArrayLength(_exp)) \ + << "During test " << testNum << ", iterator finished too early"; \ + } while (0) + +// XXX Split this up into independent test cases +TEST(ObserverArray, Tests) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t count; + int testNum = 0; + + // Basic sanity + static int test1Expected[] = {3, 4}; + DO_TEST(ForwardIterator, test1Expected, {/* nothing */}); + + // Appends + static int test2Expected[] = {3, 4, 2}; + DO_TEST(ForwardIterator, test2Expected, + if (count == 1) arr.AppendElement(2);); + DO_TEST(ForwardIterator, test2Expected, {/* nothing */}); + + DO_TEST(EndLimitedIterator, test2Expected, + if (count == 1) arr.AppendElement(5);); + + static int test5Expected[] = {3, 4, 2, 5}; + DO_TEST(ForwardIterator, test5Expected, {/* nothing */}); + + // Removals + DO_TEST(ForwardIterator, test5Expected, + if (count == 1) arr.RemoveElementAt(0);); + + static int test7Expected[] = {4, 2, 5}; + DO_TEST(ForwardIterator, test7Expected, {/* nothing */}); + + static int test8Expected[] = {4, 5}; + DO_TEST(ForwardIterator, test8Expected, + if (count == 1) arr.RemoveElementAt(1);); + DO_TEST(ForwardIterator, test8Expected, {/* nothing */}); + + arr.AppendElement(2); + arr.AppendElementUnlessExists(6); + static int test10Expected[] = {4, 5, 2, 6}; + DO_TEST(ForwardIterator, test10Expected, {/* nothing */}); + + arr.AppendElementUnlessExists(5); + DO_TEST(ForwardIterator, test10Expected, {/* nothing */}); + + static int test12Expected[] = {4, 5, 6}; + DO_TEST(ForwardIterator, test12Expected, + if (count == 1) arr.RemoveElementAt(2);); + DO_TEST(ForwardIterator, test12Expected, {/* nothing */}); + + // Removals + Appends + static int test14Expected[] = {4, 6, 7}; + DO_TEST( + ForwardIterator, test14Expected, if (count == 1) { + arr.RemoveElementAt(1); + arr.AppendElement(7); + }); + DO_TEST(ForwardIterator, test14Expected, {/* nothing */}); + + arr.AppendElement(2); + static int test16Expected[] = {4, 6, 7, 2}; + DO_TEST(ForwardIterator, test16Expected, {/* nothing */}); + + static int test17Expected[] = {4, 7, 2}; + DO_TEST( + EndLimitedIterator, test17Expected, if (count == 1) { + arr.RemoveElementAt(1); + arr.AppendElement(8); + }); + + static int test18Expected[] = {4, 7, 2, 8}; + DO_TEST(ForwardIterator, test18Expected, {/* nothing */}); + + // Prepends + arr.PrependElementUnlessExists(3); + static int test19Expected[] = {3, 4, 7, 2, 8}; + DO_TEST(ForwardIterator, test19Expected, {/* nothing */}); + + arr.PrependElementUnlessExists(7); + DO_TEST(ForwardIterator, test19Expected, {/* nothing */}); + + DO_TEST( + ForwardIterator, test19Expected, + if (count == 1) { arr.PrependElementUnlessExists(9); }); + + static int test22Expected[] = {9, 3, 4, 7, 2, 8}; + DO_TEST(ForwardIterator, test22Expected, {}); + + // BackwardIterator + static int test23Expected[] = {8, 2, 7, 4, 3, 9}; + DO_TEST(BackwardIterator, test23Expected, ); + + // Removals + static int test24Expected[] = {8, 2, 7, 4, 9}; + DO_TEST(BackwardIterator, test24Expected, + if (count == 1) arr.RemoveElementAt(1);); + + // Appends + DO_TEST(BackwardIterator, test24Expected, + if (count == 1) arr.AppendElement(1);); + + static int test26Expected[] = {1, 8, 2, 7, 4, 9}; + DO_TEST(BackwardIterator, test26Expected, ); + + // Prepends + static int test27Expected[] = {1, 8, 2, 7, 4, 9, 3}; + DO_TEST(BackwardIterator, test27Expected, + if (count == 1) arr.PrependElementUnlessExists(3);); + + // Removal using Iterator + DO_TEST(BackwardIterator, test27Expected, + // when this code runs, |GetNext()| has only been called once, so + // this actually removes the very first element + if (count == 1) iter.Remove();); + + static int test28Expected[] = {8, 2, 7, 4, 9, 3}; + DO_TEST(BackwardIterator, test28Expected, ); + + /** + * Note: _code is executed before the call to GetNext(), it can therefore not + * test the case of prepending when the BackwardIterator already returned the + * first element. + * In that case BackwardIterator does not traverse the newly prepended Element + */ +} + +TEST(ObserverArray, ForwardIterator_Remove) +{ + static const int expected[] = {3, 4}; + + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t count = 0; + for (auto iter = IntArray::ForwardIterator{arr}; iter.HasMore();) { + const int next = iter.GetNext(); + iter.Remove(); + + ASSERT_EQ(expected[count++], next); + } + ASSERT_EQ(2u, count); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_NonEmpty) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_RemoveCurrent) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + sum += element; + ++iterations; + arr.RemoveElementAt(0); + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_Append) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + if (!iterations) { + arr.AppendElement(5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(3u, iterations); + EXPECT_EQ(12, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_Prepend) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + if (!iterations) { + arr.InsertElementAt(0, 5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_Empty) +{ + IntArray arr; + + size_t iterations = 0; + for (int element : arr.ForwardRange()) { + (void)element; + ++iterations; + } + + EXPECT_EQ(0u, iterations); +} + +TEST(ObserverArray, RangeBasedFor_Reference_Forward_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray<UniquePtr<int>> arr; + arr.AppendElement(MakeUnique<int>(3)); + arr.AppendElement(MakeUnique<int>(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr<int>& element : arr.ForwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_NonConstReference_Forward_NonEmpty) +{ + nsTObserverArray<UniquePtr<int>> arr; + arr.AppendElement(MakeUnique<int>(3)); + arr.AppendElement(MakeUnique<int>(4)); + + size_t iterations = 0; + int sum = 0; + for (UniquePtr<int>& element : arr.ForwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_NonEmpty) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_RemoveCurrent) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + sum += element; + ++iterations; + arr.RemoveElementAt(arr.Length() - 1); + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_Append) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + if (!iterations) { + arr.AppendElement(5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_Prepend) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + if (!iterations) { + arr.InsertElementAt(0, 5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(3u, iterations); + EXPECT_EQ(12, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_Empty) +{ + IntArray arr; + + size_t iterations = 0; + for (int element : arr.BackwardRange()) { + (void)element; + ++iterations; + } + + EXPECT_EQ(0u, iterations); +} + +TEST(ObserverArray, RangeBasedFor_Reference_Backward_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray<UniquePtr<int>> arr; + arr.AppendElement(MakeUnique<int>(3)); + arr.AppendElement(MakeUnique<int>(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr<int>& element : arr.BackwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_NonConstReference_Backward_NonEmpty) +{ + nsTObserverArray<UniquePtr<int>> arr; + arr.AppendElement(MakeUnique<int>(3)); + arr.AppendElement(MakeUnique<int>(4)); + + size_t iterations = 0; + int sum = 0; + for (UniquePtr<int>& element : arr.BackwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_NonEmpty) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_RemoveCurrent) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + sum += element; + ++iterations; + arr.RemoveElementAt(0); + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Append) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + if (!iterations) { + arr.AppendElement(5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Prepend) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + if (!iterations) { + arr.InsertElementAt(0, 5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Empty) +{ + IntArray arr; + + size_t iterations = 0; + for (int element : arr.EndLimitedRange()) { + (void)element; + ++iterations; + } + + EXPECT_EQ(0u, iterations); +} + +TEST(ObserverArray, RangeBasedFor_Reference_EndLimited_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray<UniquePtr<int>> arr; + arr.AppendElement(MakeUnique<int>(3)); + arr.AppendElement(MakeUnique<int>(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr<int>& element : arr.EndLimitedRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_NonConstReference_EndLimited_NonEmpty) +{ + nsTObserverArray<UniquePtr<int>> arr; + arr.AppendElement(MakeUnique<int>(3)); + arr.AppendElement(MakeUnique<int>(4)); + + size_t iterations = 0; + int sum = 0; + for (UniquePtr<int>& element : arr.EndLimitedRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Reference_NonObserving_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray<UniquePtr<int>> arr; + arr.AppendElement(MakeUnique<int>(3)); + arr.AppendElement(MakeUnique<int>(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr<int>& element : arr.NonObservingRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +// TODO add tests for EndLimitedIterator diff --git a/xpcom/tests/gtest/TestObserverService.cpp b/xpcom/tests/gtest/TestObserverService.cpp new file mode 100644 index 0000000000..4126815f1f --- /dev/null +++ b/xpcom/tests/gtest/TestObserverService.cpp @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsISimpleEnumerator.h" +#include "nsComponentManagerUtils.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsWeakReference.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/RefPtr.h" + +#include "gtest/gtest.h" + +static void testResult(nsresult rv) { + EXPECT_TRUE(NS_SUCCEEDED(rv)) << "0x" << std::hex << (int)rv; +} + +class TestObserver final : public nsIObserver, public nsSupportsWeakReference { + public: + explicit TestObserver(const nsAString& name) + : mName(name), mObservations(0) {} + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsString mName; + int mObservations; + static int sTotalObservations; + + nsString mExpectedData; + + private: + ~TestObserver() = default; +}; + +NS_IMPL_ISUPPORTS(TestObserver, nsIObserver, nsISupportsWeakReference) + +int TestObserver::sTotalObservations; + +NS_IMETHODIMP +TestObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + mObservations++; + sTotalObservations++; + + if (!mExpectedData.IsEmpty()) { + EXPECT_TRUE(mExpectedData.Equals(someData)); + } + + return NS_OK; +} + +static nsISupports* ToSupports(TestObserver* aObs) { + return static_cast<nsIObserver*>(aObs); +} + +static void TestExpectedCount(nsIObserverService* svc, const char* topic, + size_t expected) { + nsCOMPtr<nsISimpleEnumerator> e; + nsresult rv = svc->EnumerateObservers(topic, getter_AddRefs(e)); + testResult(rv); + EXPECT_TRUE(e); + + bool hasMore = false; + rv = e->HasMoreElements(&hasMore); + testResult(rv); + + if (expected == 0) { + EXPECT_FALSE(hasMore); + return; + } + + size_t count = 0; + while (hasMore) { + count++; + + // Grab the element. + nsCOMPtr<nsISupports> supports; + e->GetNext(getter_AddRefs(supports)); + ASSERT_TRUE(supports); + + // Move on. + rv = e->HasMoreElements(&hasMore); + testResult(rv); + } + + EXPECT_EQ(count, expected); +} + +TEST(ObserverService, Creation) +{ + nsresult rv; + nsCOMPtr<nsIObserverService> svc = + do_CreateInstance("@mozilla.org/observer-service;1", &rv); + + ASSERT_EQ(rv, NS_OK); + ASSERT_TRUE(svc); +} + +TEST(ObserverService, AddObserver) +{ + nsCOMPtr<nsIObserverService> svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + // Add a strong ref. + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + nsresult rv = svc->AddObserver(a, "Foo", false); + testResult(rv); + + // Add a few weak ref. + RefPtr<TestObserver> b = new TestObserver(u"B"_ns); + rv = svc->AddObserver(b, "Bar", true); + testResult(rv); +} + +TEST(ObserverService, RemoveObserver) +{ + nsCOMPtr<nsIObserverService> svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + RefPtr<TestObserver> b = new TestObserver(u"B"_ns); + RefPtr<TestObserver> c = new TestObserver(u"C"_ns); + + svc->AddObserver(a, "Foo", false); + svc->AddObserver(b, "Foo", true); + + // Remove from non-existent topic. + nsresult rv = svc->RemoveObserver(a, "Bar"); + ASSERT_NS_FAILED(rv); + + // Remove a. + testResult(svc->RemoveObserver(a, "Foo")); + + // Remove b. + testResult(svc->RemoveObserver(b, "Foo")); + + // Attempt to remove c. + rv = svc->RemoveObserver(c, "Foo"); + ASSERT_NS_FAILED(rv); +} + +TEST(ObserverService, EnumerateEmpty) +{ + nsCOMPtr<nsIObserverService> svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + // Try with no observers. + TestExpectedCount(svc, "A", 0); + + // Now add an observer and enumerate an unobserved topic. + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", false)); + + TestExpectedCount(svc, "A", 0); +} + +TEST(ObserverService, Enumerate) +{ + nsCOMPtr<nsIObserverService> svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + const size_t kFooCount = 10; + for (size_t i = 0; i < kFooCount; i++) { + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", false)); + } + + const size_t kBarCount = kFooCount / 2; + for (size_t i = 0; i < kBarCount; i++) { + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Bar", false)); + } + + // Enumerate "Foo". + TestExpectedCount(svc, "Foo", kFooCount); + + // Enumerate "Bar". + TestExpectedCount(svc, "Bar", kBarCount); +} + +TEST(ObserverService, EnumerateWeakRefs) +{ + nsCOMPtr<nsIObserverService> svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + const size_t kFooCount = 10; + for (size_t i = 0; i < kFooCount; i++) { + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", true)); + } + + // All refs are out of scope, expect enumeration to be empty. + TestExpectedCount(svc, "Foo", 0); + + // Now test a mixture. + for (size_t i = 0; i < kFooCount; i++) { + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + RefPtr<TestObserver> b = new TestObserver(u"B"_ns); + + // Register a as weak for "Foo". + testResult(svc->AddObserver(a, "Foo", true)); + + // Register b as strong for "Foo". + testResult(svc->AddObserver(b, "Foo", false)); + } + + // Expect the b instances to stick around. + TestExpectedCount(svc, "Foo", kFooCount); + + // Now add a couple weak refs, but don't go out of scope. + RefPtr<TestObserver> a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", true)); + RefPtr<TestObserver> b = new TestObserver(u"B"_ns); + testResult(svc->AddObserver(b, "Foo", true)); + + // Expect all the observers from before and the two new ones. + TestExpectedCount(svc, "Foo", kFooCount + 2); +} + +TEST(ObserverService, TestNotify) +{ + nsCString topicA; + topicA.Assign("topic-A"); + nsCString topicB; + topicB.Assign("topic-B"); + + nsCOMPtr<nsIObserverService> svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + RefPtr<TestObserver> aObserver = new TestObserver(u"Observer-A"_ns); + RefPtr<TestObserver> bObserver = new TestObserver(u"Observer-B"_ns); + + // Add two observers for topicA. + testResult(svc->AddObserver(aObserver, topicA.get(), false)); + testResult(svc->AddObserver(bObserver, topicA.get(), false)); + + // Add one observer for topicB. + testResult(svc->AddObserver(bObserver, topicB.get(), false)); + + // Notify topicA. + const char16_t* dataA = u"Testing Notify(observer-A, topic-A)"; + aObserver->mExpectedData = dataA; + bObserver->mExpectedData = dataA; + nsresult rv = + svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 1); + + // Notify topicB. + const char16_t* dataB = u"Testing Notify(observer-B, topic-B)"; + bObserver->mExpectedData = dataB; + rv = svc->NotifyObservers(ToSupports(bObserver), topicB.get(), dataB); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 2); + + // Remove one of the topicA observers, make sure it's not notified. + testResult(svc->RemoveObserver(aObserver, topicA.get())); + + // Notify topicA, only bObserver is expected to be notified. + bObserver->mExpectedData = dataA; + rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 3); + + // Remove the other topicA observer, make sure none are notified. + testResult(svc->RemoveObserver(bObserver, topicA.get())); + rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 3); +} diff --git a/xpcom/tests/gtest/TestOwningNonNull.cpp b/xpcom/tests/gtest/TestOwningNonNull.cpp new file mode 100644 index 0000000000..5f82c7b37b --- /dev/null +++ b/xpcom/tests/gtest/TestOwningNonNull.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/OwningNonNull.h" +#include "mozilla/RefCounted.h" +#include "mozilla/RefPtr.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +struct OwnedRefCounted : public RefCounted<OwnedRefCounted> { + MOZ_DECLARE_REFCOUNTED_TYPENAME(OwnedRefCounted) + + OwnedRefCounted() = default; +}; + +TEST(OwningNonNull, Move) +{ + auto refptr = MakeRefPtr<OwnedRefCounted>(); + OwningNonNull<OwnedRefCounted> owning(std::move(refptr)); + EXPECT_FALSE(!!refptr); +} diff --git a/xpcom/tests/gtest/TestPLDHash.cpp b/xpcom/tests/gtest/TestPLDHash.cpp new file mode 100644 index 0000000000..d302e72595 --- /dev/null +++ b/xpcom/tests/gtest/TestPLDHash.cpp @@ -0,0 +1,407 @@ +/* -*- 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 "PLDHashTable.h" +#include "gtest/gtest.h" +#include "mozilla/gtest/MozHelpers.h" + +// This test mostly focuses on edge cases. But more coverage of normal +// operations wouldn't be a bad thing. + +#ifdef XP_UNIX +# include <unistd.h> +# include <sys/types.h> +# include <sys/wait.h> +#endif + +// We can test that certain operations cause expected aborts by forking +// and then checking that the child aborted in the expected way (i.e. via +// MOZ_CRASH). We skip this for the following configurations. +// - On Windows, because it doesn't have fork(). +// - On non-DEBUG builds, because the crashes cause the crash reporter to pop +// up when running this test locally, which is surprising and annoying. +// - On ASAN builds, because ASAN alters the way a MOZ_CRASHing process +// terminates, which makes it harder to test if the right thing has occurred. +static void TestCrashyOperation(const char* label, void (*aCrashyOperation)()) { +#if defined(XP_UNIX) && defined(DEBUG) && !defined(MOZ_ASAN) + // We're about to trigger a crash. When it happens don't pause to allow GDB + // to be attached. + SAVE_GDB_SLEEP_LOCAL(); + + int pid = fork(); + ASSERT_NE(pid, -1); + + if (pid == 0) { + // Disable the crashreporter -- writing a crash dump in the child will + // prevent the parent from writing a subsequent dump. Crashes here are + // expected, so we don't want their stacks to show up in the log anyway. + mozilla::gtest::DisableCrashReporter(); + + // Child: perform the crashy operation. + FILE* stderr_dup = fdopen(dup(fileno(stderr)), "w"); + // We don't want MOZ_CRASH from the crashy operation to print out its + // error message and stack-trace, which would be confusing and irrelevant. + fclose(stderr); + aCrashyOperation(); + fprintf(stderr_dup, "TestCrashyOperation %s: didn't crash?!\n", label); + ASSERT_TRUE(false); // shouldn't reach here + } + + // Parent: check that child crashed as expected. + int status; + ASSERT_NE(waitpid(pid, &status, 0), -1); + + // The path taken here depends on the platform and configuration. + ASSERT_TRUE(WIFEXITED(status) || WTERMSIG(status)); + if (WIFEXITED(status)) { + // This occurs if the ah_crap_handler() is run, i.e. we caught the crash. + // It returns the number of the caught signal. + int signum = WEXITSTATUS(status); + if (signum != SIGSEGV && signum != SIGBUS) { + fprintf(stderr, "TestCrashyOperation %s: 'exited' failure: %d\n", label, + signum); + ASSERT_TRUE(false); + } + } else if (WIFSIGNALED(status)) { + // This one occurs if we didn't catch the crash. The exit code is the + // number of the terminating signal. + int signum = WTERMSIG(status); + if (signum != SIGSEGV && signum != SIGBUS) { + fprintf(stderr, "TestCrashyOperation %s: 'signaled' failure: %d\n", label, + signum); + ASSERT_TRUE(false); + } + } + + RESTORE_GDB_SLEEP_LOCAL(); +#endif +} + +static void InitCapacityOk_InitialLengthTooBig() { + PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), + PLDHashTable::kMaxInitialLength + 1); +} + +static void InitCapacityOk_InitialEntryStoreTooBig() { + // Try the smallest disallowed power-of-two entry store size, which is 2^32 + // bytes (which overflows to 0). (Note that the 2^23 *length* gets converted + // to a 2^24 *capacity*.) + PLDHashTable t(PLDHashTable::StubOps(), (uint32_t)1 << 8, (uint32_t)1 << 23); +} + +static void InitCapacityOk_EntrySizeTooBig() { + // Try the smallest disallowed entry size, which is 256 bytes. + PLDHashTable t(PLDHashTable::StubOps(), 256); +} + +TEST(PLDHashTableTest, InitCapacityOk) +{ + // Try the largest allowed capacity. With kMaxCapacity==1<<26, this + // would allocate (if we added an element) 0.5GB of entry store on 32-bit + // platforms and 1GB on 64-bit platforms. + PLDHashTable t1(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), + PLDHashTable::kMaxInitialLength); + + // Try the largest allowed power-of-two entry store size, which is 2^31 bytes + // (Note that the 2^23 *length* gets converted to a 2^24 *capacity*.) + PLDHashTable t2(PLDHashTable::StubOps(), (uint32_t)1 << 7, (uint32_t)1 << 23); + + // Try a too-large capacity (which aborts). + TestCrashyOperation("length too big", InitCapacityOk_InitialLengthTooBig); + + // Try a large capacity combined with a large entry size that when multiplied + // overflow (causing abort). + TestCrashyOperation("entry store too big", + InitCapacityOk_InitialEntryStoreTooBig); + + // Try the largest allowed entry size. + PLDHashTable t3(PLDHashTable::StubOps(), 255); + + // Try an overly large entry size. + TestCrashyOperation("entry size too big", InitCapacityOk_EntrySizeTooBig); + + // Ideally we'd also try a large-but-ok capacity that almost but doesn't + // quite overflow, but that would result in allocating slightly less than 4 + // GiB of entry storage. That would be very likely to fail on 32-bit + // platforms, so such a test wouldn't be reliable. +} + +TEST(PLDHashTableTest, LazyStorage) +{ + PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub)); + + // PLDHashTable allocates entry storage lazily. Check that all the non-add + // operations work appropriately when the table is empty and the storage + // hasn't yet been allocated. + + ASSERT_EQ(t.Capacity(), 0u); + ASSERT_EQ(t.EntrySize(), sizeof(PLDHashEntryStub)); + ASSERT_EQ(t.EntryCount(), 0u); + ASSERT_EQ(t.Generation(), 0u); + + ASSERT_TRUE(!t.Search((const void*)1)); + + // No result to check here, but call it to make sure it doesn't crash. + t.Remove((const void*)2); + + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + ASSERT_TRUE(false); // shouldn't hit this on an empty table + } + + ASSERT_EQ(t.ShallowSizeOfExcludingThis(moz_malloc_size_of), 0u); +} + +// A trivial hash function is good enough here. It's also super-fast for the +// GrowToMaxCapacity test because we insert the integers 0.., which means it's +// collision-free. +static PLDHashNumber TrivialHash(const void* key) { + return (PLDHashNumber)(size_t)key; +} + +static void TrivialInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) { + auto entry = static_cast<PLDHashEntryStub*>(aEntry); + entry->key = aKey; +} + +static const PLDHashTableOps trivialOps = { + TrivialHash, PLDHashTable::MatchEntryStub, PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, TrivialInitEntry}; + +TEST(PLDHashTableTest, MoveSemantics) +{ + PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); + t1.Add((const void*)88); + PLDHashTable t2(&trivialOps, sizeof(PLDHashEntryStub)); + t2.Add((const void*)99); + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-move" +#endif + t1 = std::move(t1); // self-move +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + t1 = std::move(t2); // empty overwritten with empty + + PLDHashTable t3(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t4(&trivialOps, sizeof(PLDHashEntryStub)); + t3.Add((const void*)88); + + t3 = std::move(t4); // non-empty overwritten with empty + + PLDHashTable t5(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t6(&trivialOps, sizeof(PLDHashEntryStub)); + t6.Add((const void*)88); + + t5 = std::move(t6); // empty overwritten with non-empty + + PLDHashTable t7(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t8(std::move(t7)); // new table constructed with uninited + + PLDHashTable t9(&trivialOps, sizeof(PLDHashEntryStub)); + t9.Add((const void*)88); + PLDHashTable t10(std::move(t9)); // new table constructed with inited +} + +TEST(PLDHashTableTest, Clear) +{ + PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); + + t1.Clear(); + ASSERT_EQ(t1.EntryCount(), 0u); + + t1.ClearAndPrepareForLength(100); + ASSERT_EQ(t1.EntryCount(), 0u); + + t1.Add((const void*)77); + t1.Add((const void*)88); + t1.Add((const void*)99); + ASSERT_EQ(t1.EntryCount(), 3u); + + t1.Clear(); + ASSERT_EQ(t1.EntryCount(), 0u); + + t1.Add((const void*)55); + t1.Add((const void*)66); + t1.Add((const void*)77); + t1.Add((const void*)88); + t1.Add((const void*)99); + ASSERT_EQ(t1.EntryCount(), 5u); + + t1.ClearAndPrepareForLength(8192); + ASSERT_EQ(t1.EntryCount(), 0u); +} + +TEST(PLDHashTableTest, Iterator) +{ + PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub)); + + // Explicitly test the move constructor. We do this because, due to copy + // elision, compilers might optimize away move constructor calls for normal + // iterator use. + { + PLDHashTable::Iterator iter1(&t); + PLDHashTable::Iterator iter2(std::move(iter1)); + } + + // Iterate through the empty table. + for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) { + (void)iter.Get(); + ASSERT_TRUE(false); // shouldn't hit this + } + + // Add three entries. + t.Add((const void*)77); + t.Add((const void*)88); + t.Add((const void*)99); + + // Check the iterator goes through each entry once. + bool saw77 = false, saw88 = false, saw99 = false; + int n = 0; + for (auto iter(t.Iter()); !iter.Done(); iter.Next()) { + auto entry = static_cast<PLDHashEntryStub*>(iter.Get()); + if (entry->key == (const void*)77) { + saw77 = true; + } + if (entry->key == (const void*)88) { + saw88 = true; + } + if (entry->key == (const void*)99) { + saw99 = true; + } + n++; + } + ASSERT_TRUE(saw77 && saw88 && saw99 && n == 3); + + t.Clear(); + + // First, we insert 64 items, which results in a capacity of 128, and a load + // factor of 50%. + for (intptr_t i = 0; i < 64; i++) { + t.Add((const void*)i); + } + ASSERT_EQ(t.EntryCount(), 64u); + ASSERT_EQ(t.Capacity(), 128u); + + // The first removing iterator does no removing; capacity and entry count are + // unchanged. + for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) { + (void)iter.Get(); + } + ASSERT_EQ(t.EntryCount(), 64u); + ASSERT_EQ(t.Capacity(), 128u); + + // The second removing iterator removes 16 items. This reduces the load + // factor to 37.5% (48 / 128), which isn't low enough to shrink the table. + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PLDHashEntryStub*>(iter.Get()); + if ((intptr_t)(entry->key) % 4 == 0) { + iter.Remove(); + } + } + ASSERT_EQ(t.EntryCount(), 48u); + ASSERT_EQ(t.Capacity(), 128u); + + // The third removing iterator removes another 16 items. This reduces + // the load factor to 25% (32 / 128), so the table is shrunk. + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PLDHashEntryStub*>(iter.Get()); + if ((intptr_t)(entry->key) % 2 == 0) { + iter.Remove(); + } + } + ASSERT_EQ(t.EntryCount(), 32u); + ASSERT_EQ(t.Capacity(), 64u); + + // The fourth removing iterator removes all remaining items. This reduces + // the capacity to the minimum. + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + iter.Remove(); + } + ASSERT_EQ(t.EntryCount(), 0u); + ASSERT_EQ(t.Capacity(), unsigned(PLDHashTable::kMinCapacity)); +} + +TEST(PLDHashTableTest, WithEntryHandle) +{ + PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub)); + + PLDHashEntryHdr* entry1 = + t.WithEntryHandle((const void*)88, [](auto entryHandle) { + EXPECT_FALSE(entryHandle); + + bool initEntryCalled = false; + PLDHashEntryHdr* entry = + entryHandle.OrInsert([&initEntryCalled](PLDHashEntryHdr* entry) { + EXPECT_TRUE(entry); + TrivialInitEntry(entry, (const void*)88); + initEntryCalled = true; + }); + EXPECT_TRUE(initEntryCalled); + EXPECT_EQ(entryHandle.Entry(), entry); + + return entry; + }); + ASSERT_TRUE(entry1); + ASSERT_EQ(t.EntryCount(), 1u); + + PLDHashEntryHdr* entry2 = + t.WithEntryHandle((const void*)88, [](auto entryHandle) { + EXPECT_TRUE(entryHandle); + + bool initEntryCalled = false; + PLDHashEntryHdr* entry = + entryHandle.OrInsert([&initEntryCalled](PLDHashEntryHdr* entry) { + EXPECT_TRUE(entry); + TrivialInitEntry(entry, (const void*)88); + initEntryCalled = true; + }); + EXPECT_FALSE(initEntryCalled); + EXPECT_EQ(entryHandle.Entry(), entry); + + return entry; + }); + ASSERT_TRUE(entry2); + ASSERT_EQ(t.EntryCount(), 1u); + + ASSERT_EQ(entry1, entry2); +} + +// This test involves resizing a table repeatedly up to 512 MiB in size. On +// 32-bit platforms (Win32, Android) it sometimes OOMs, causing the test to +// fail. (See bug 931062 and bug 1267227.) Therefore, we only run it on 64-bit +// platforms where OOM is much less likely. +// +// Also, it's slow, and so should always be last. +#ifdef HAVE_64BIT_BUILD +TEST(PLDHashTableTest, GrowToMaxCapacity) +{ + // This is infallible. + PLDHashTable* t = + new PLDHashTable(&trivialOps, sizeof(PLDHashEntryStub), 128); + + // Keep inserting elements until failure occurs because the table is full. + size_t numInserted = 0; + while (true) { + if (!t->Add((const void*)numInserted, mozilla::fallible)) { + break; + } + numInserted++; + } + + // We stop when the element count is 96.875% of PLDHashTable::kMaxCapacity + // (see MaxLoadOnGrowthFailure()). + if (numInserted != + PLDHashTable::kMaxCapacity - (PLDHashTable::kMaxCapacity >> 5)) { + delete t; + ASSERT_TRUE(false); + } + + delete t; +} +#endif diff --git a/xpcom/tests/gtest/TestPipes.cpp b/xpcom/tests/gtest/TestPipes.cpp new file mode 100644 index 0000000000..a4f0ebc7a5 --- /dev/null +++ b/xpcom/tests/gtest/TestPipes.cpp @@ -0,0 +1,1031 @@ +/* -*- 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 <algorithm> +#include "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Printf.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBufferedStreams.h" +#include "nsIClassInfo.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIPipe.h" +#include "nsITellableStream.h" +#include "nsIThread.h" +#include "nsIRunnable.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "prinrval.h" + +using namespace mozilla; + +#define ITERATIONS 33333 +char kTestPattern[] = "My hovercraft is full of eels.\n"; + +bool gTrace = false; + +static nsresult WriteAll(nsIOutputStream* os, const char* buf, uint32_t bufLen, + uint32_t* lenWritten) { + const char* p = buf; + *lenWritten = 0; + while (bufLen) { + uint32_t n; + nsresult rv = os->Write(p, bufLen, &n); + if (NS_FAILED(rv)) return rv; + p += n; + bufLen -= n; + *lenWritten += n; + } + return NS_OK; +} + +class nsReceiver final : public Runnable { + public: + NS_IMETHOD Run() override { + nsresult rv; + char buf[101]; + uint32_t count; + PRIntervalTime start = PR_IntervalNow(); + while (true) { + rv = mIn->Read(buf, 100, &count); + if (NS_FAILED(rv)) { + printf("read failed\n"); + break; + } + if (count == 0) { + // printf("EOF count = %d\n", mCount); + break; + } + + if (gTrace) { + buf[count] = '\0'; + printf("read: %s\n", buf); + } + mCount += count; + } + PRIntervalTime end = PR_IntervalNow(); + printf("read %d bytes, time = %dms\n", mCount, + PR_IntervalToMilliseconds(end - start)); + return rv; + } + + explicit nsReceiver(nsIInputStream* in) + : Runnable("nsReceiver"), mIn(in), mCount(0) {} + + uint32_t GetBytesRead() { return mCount; } + + private: + ~nsReceiver() = default; + + protected: + nsCOMPtr<nsIInputStream> mIn; + uint32_t mCount; +}; + +static nsresult TestPipe(nsIInputStream* in, nsIOutputStream* out) { + RefPtr<nsReceiver> receiver = new nsReceiver(in); + nsresult rv; + + nsCOMPtr<nsIThread> thread; + rv = NS_NewNamedThread("TestPipe", getter_AddRefs(thread), receiver); + if (NS_FAILED(rv)) return rv; + + uint32_t total = 0; + PRIntervalTime start = PR_IntervalNow(); + for (uint32_t i = 0; i < ITERATIONS; i++) { + uint32_t writeCount; + SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf.get()); + rv = WriteAll(out, buf.get(), len, &writeCount); + if (gTrace) { + printf("wrote: "); + for (uint32_t j = 0; j < writeCount; j++) { + putc(buf.get()[j], stdout); + } + printf("\n"); + } + if (NS_FAILED(rv)) return rv; + total += writeCount; + } + rv = out->Close(); + if (NS_FAILED(rv)) return rv; + + PRIntervalTime end = PR_IntervalNow(); + + thread->Shutdown(); + + printf("wrote %d bytes, time = %dms\n", total, + PR_IntervalToMilliseconds(end - start)); + EXPECT_EQ(receiver->GetBytesRead(), total); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsShortReader final : public Runnable { + public: + NS_IMETHOD Run() override { + nsresult rv; + char buf[101]; + uint32_t count; + uint32_t total = 0; + while (true) { + // if (gTrace) + // printf("calling Read\n"); + rv = mIn->Read(buf, 100, &count); + if (NS_FAILED(rv)) { + printf("read failed\n"); + break; + } + if (count == 0) { + break; + } + + if (gTrace) { + // For next |printf()| call and possible others elsewhere. + buf[count] = '\0'; + + printf("read %d bytes: %s\n", count, buf); + } + + Received(count); + total += count; + } + printf("read %d bytes\n", total); + return rv; + } + + explicit nsShortReader(nsIInputStream* in) + : Runnable("nsShortReader"), mIn(in), mReceived(0) { + mMon = new ReentrantMonitor("nsShortReader"); + } + + void Received(uint32_t count) { + ReentrantMonitorAutoEnter mon(*mMon); + mReceived += count; + mon.Notify(); + } + + uint32_t WaitForReceipt(const uint32_t aWriteCount) { + ReentrantMonitorAutoEnter mon(*mMon); + uint32_t result = mReceived; + + while (result < aWriteCount) { + mon.Wait(); + + EXPECT_TRUE(mReceived > result); + result = mReceived; + } + + mReceived = 0; + return result; + } + + private: + ~nsShortReader() = default; + + protected: + nsCOMPtr<nsIInputStream> mIn; + uint32_t mReceived; + ReentrantMonitor* mMon; +}; + +static nsresult TestShortWrites(nsIInputStream* in, nsIOutputStream* out) { + RefPtr<nsShortReader> receiver = new nsShortReader(in); + nsresult rv; + + nsCOMPtr<nsIThread> thread; + rv = NS_NewNamedThread("TestShortWrites", getter_AddRefs(thread), receiver); + if (NS_FAILED(rv)) return rv; + + uint32_t total = 0; + for (uint32_t i = 0; i < ITERATIONS; i++) { + uint32_t writeCount; + SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf.get()); + len = len * rand() / RAND_MAX; + len = std::min(1u, len); + rv = WriteAll(out, buf.get(), len, &writeCount); + if (NS_FAILED(rv)) return rv; + EXPECT_EQ(writeCount, len); + total += writeCount; + + if (gTrace) printf("wrote %d bytes: %s\n", writeCount, buf.get()); + // printf("calling Flush\n"); + out->Flush(); + // printf("calling WaitForReceipt\n"); + +#ifdef DEBUG + const uint32_t received = receiver->WaitForReceipt(writeCount); + EXPECT_EQ(received, writeCount); +#endif + } + rv = out->Close(); + if (NS_FAILED(rv)) return rv; + + thread->Shutdown(); + + printf("wrote %d bytes\n", total); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsPump final : public Runnable { + public: + NS_IMETHOD Run() override { + nsresult rv; + uint32_t count; + while (true) { + rv = mOut->WriteFrom(mIn, ~0U, &count); + if (NS_FAILED(rv)) { + printf("Write failed\n"); + break; + } + if (count == 0) { + printf("EOF count = %d\n", mCount); + break; + } + + if (gTrace) { + printf("Wrote: %d\n", count); + } + mCount += count; + } + mOut->Close(); + return rv; + } + + nsPump(nsIInputStream* in, nsIOutputStream* out) + : Runnable("nsPump"), mIn(in), mOut(out), mCount(0) {} + + private: + ~nsPump() = default; + + protected: + nsCOMPtr<nsIInputStream> mIn; + nsCOMPtr<nsIOutputStream> mOut; + uint32_t mCount; +}; + +TEST(Pipes, ChainedPipes) +{ + nsresult rv; + if (gTrace) { + printf("TestChainedPipes\n"); + } + + nsCOMPtr<nsIInputStream> in1; + nsCOMPtr<nsIOutputStream> out1; + NS_NewPipe(getter_AddRefs(in1), getter_AddRefs(out1), 20, 1999); + + nsCOMPtr<nsIInputStream> in2; + nsCOMPtr<nsIOutputStream> out2; + NS_NewPipe(getter_AddRefs(in2), getter_AddRefs(out2), 200, 401); + + RefPtr<nsPump> pump = new nsPump(in1, out2); + if (pump == nullptr) return; + + nsCOMPtr<nsIThread> thread; + rv = NS_NewNamedThread("ChainedPipePump", getter_AddRefs(thread), pump); + if (NS_FAILED(rv)) return; + + RefPtr<nsReceiver> receiver = new nsReceiver(in2); + if (receiver == nullptr) return; + + nsCOMPtr<nsIThread> receiverThread; + rv = NS_NewNamedThread("ChainedPipeRecv", getter_AddRefs(receiverThread), + receiver); + if (NS_FAILED(rv)) return; + + uint32_t total = 0; + for (uint32_t i = 0; i < ITERATIONS; i++) { + uint32_t writeCount; + SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf.get()); + len = len * rand() / RAND_MAX; + len = std::max(1u, len); + rv = WriteAll(out1, buf.get(), len, &writeCount); + if (NS_FAILED(rv)) return; + EXPECT_EQ(writeCount, len); + total += writeCount; + + if (gTrace) printf("wrote %d bytes: %s\n", writeCount, buf.get()); + } + if (gTrace) { + printf("wrote total of %d bytes\n", total); + } + rv = out1->Close(); + if (NS_FAILED(rv)) return; + + thread->Shutdown(); + receiverThread->Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// + +static void RunTests(uint32_t segSize, uint32_t segCount) { + nsresult rv; + nsCOMPtr<nsIInputStream> in; + nsCOMPtr<nsIOutputStream> out; + uint32_t bufSize = segSize * segCount; + if (gTrace) { + printf("Testing New Pipes: segment size %d buffer size %d\n", segSize, + bufSize); + printf("Testing long writes...\n"); + } + NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize); + rv = TestPipe(in, out); + EXPECT_NS_SUCCEEDED(rv); + + if (gTrace) { + printf("Testing short writes...\n"); + } + NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize); + rv = TestShortWrites(in, out); + EXPECT_NS_SUCCEEDED(rv); +} + +TEST(Pipes, Main) +{ + RunTests(16, 1); + RunTests(4096, 16); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +static const uint32_t DEFAULT_SEGMENT_SIZE = 4 * 1024; + +// An alternate pipe testing routing that uses NS_ConsumeStream() instead of +// manual read loop. +static void TestPipe2(uint32_t aNumBytes, + uint32_t aSegmentSize = DEFAULT_SEGMENT_SIZE) { + nsCOMPtr<nsIInputStream> reader; + nsCOMPtr<nsIOutputStream> writer; + + uint32_t maxSize = std::max(aNumBytes, aSegmentSize); + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize, + maxSize); + + nsTArray<char> inputData; + testing::CreateData(aNumBytes, inputData); + testing::WriteAllAndClose(writer, inputData); + testing::ConsumeAndValidateStream(reader, inputData); +} + +} // namespace + +TEST(Pipes, Blocking_32k) +{ TestPipe2(32 * 1024); } + +TEST(Pipes, Blocking_64k) +{ TestPipe2(64 * 1024); } + +TEST(Pipes, Blocking_128k) +{ TestPipe2(128 * 1024); } + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +// Utility routine to validate pipe clone before. There are many knobs. +// +// aTotalBytes Total number of bytes to write to the pipe. +// aNumWrites How many separate write calls should be made. Bytes +// are evenly distributed over these write calls. +// aNumInitialClones How many clones of the pipe input stream should be +// made before writing begins. +// aNumToCloseAfterWrite How many streams should be closed after each write. +// One stream is always kept open. This verifies that +// closing one stream does not effect other open +// streams. +// aNumToCloneAfterWrite How many clones to create after each write. Occurs +// after closing any streams. This tests cloning +// active streams on a pipe that is being written to. +// aNumStreamToReadPerWrite How many streams to read fully after each write. +// This tests reading cloned streams at different rates +// while the pipe is being written to. +static void TestPipeClone(uint32_t aTotalBytes, uint32_t aNumWrites, + uint32_t aNumInitialClones, + uint32_t aNumToCloseAfterWrite, + uint32_t aNumToCloneAfterWrite, + uint32_t aNumStreamsToReadPerWrite, + uint32_t aSegmentSize = DEFAULT_SEGMENT_SIZE) { + nsCOMPtr<nsIInputStream> reader; + nsCOMPtr<nsIOutputStream> writer; + + uint32_t maxSize = std::max(aTotalBytes, aSegmentSize); + + // Use async input streams so we can NS_ConsumeStream() the current data + // while the pipe is still being written to. + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize, + maxSize, true, false); // non-blocking - reader, writer + + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(reader); + ASSERT_TRUE(cloneable); + ASSERT_TRUE(cloneable->GetCloneable()); + + nsTArray<nsCString> outputDataList; + + nsTArray<nsCOMPtr<nsIInputStream>> streamList; + + // first stream is our original reader from the pipe + streamList.AppendElement(reader); + outputDataList.AppendElement(); + + // Clone the initial input stream the specified number of times + // before performing any writes. + nsresult rv; + for (uint32_t i = 0; i < aNumInitialClones; ++i) { + nsCOMPtr<nsIInputStream>* clone = streamList.AppendElement(); + rv = cloneable->Clone(getter_AddRefs(*clone)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(*clone); + + outputDataList.AppendElement(); + } + + nsTArray<char> inputData; + testing::CreateData(aTotalBytes, inputData); + + const uint32_t bytesPerWrite = ((aTotalBytes - 1) / aNumWrites) + 1; + uint32_t offset = 0; + uint32_t remaining = aTotalBytes; + uint32_t nextStreamToRead = 0; + + while (remaining) { + uint32_t numToWrite = std::min(bytesPerWrite, remaining); + testing::Write(writer, inputData, offset, numToWrite); + offset += numToWrite; + remaining -= numToWrite; + + // Close the specified number of streams. This allows us to + // test that one closed clone does not break other open clones. + for (uint32_t i = 0; i < aNumToCloseAfterWrite && streamList.Length() > 1; + ++i) { + uint32_t lastIndex = streamList.Length() - 1; + streamList[lastIndex]->Close(); + streamList.RemoveElementAt(lastIndex); + outputDataList.RemoveElementAt(lastIndex); + + if (nextStreamToRead >= streamList.Length()) { + nextStreamToRead = 0; + } + } + + // Create the specified number of clones. This lets us verify + // that we can create clones in the middle of pipe reading and + // writing. + for (uint32_t i = 0; i < aNumToCloneAfterWrite; ++i) { + nsCOMPtr<nsIInputStream>* clone = streamList.AppendElement(); + rv = cloneable->Clone(getter_AddRefs(*clone)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(*clone); + + // Initialize the new output data to make whats been read to data for + // the original stream. First stream is always the original stream. + nsCString* outputData = outputDataList.AppendElement(); + *outputData = outputDataList[0]; + } + + // Read the specified number of streams. This lets us verify that we + // can read from the clones at different rates while the pipe is being + // written to. + for (uint32_t i = 0; i < aNumStreamsToReadPerWrite; ++i) { + nsCOMPtr<nsIInputStream>& stream = streamList[nextStreamToRead]; + nsCString& outputData = outputDataList[nextStreamToRead]; + + // Can't use ConsumeAndValidateStream() here because we're not + // guaranteed the exact amount read. It should just be at least + // as many as numToWrite. + nsAutoCString tmpOutputData; + rv = NS_ConsumeStream(stream, UINT32_MAX, tmpOutputData); + ASSERT_TRUE(rv == NS_BASE_STREAM_WOULD_BLOCK || NS_SUCCEEDED(rv)); + ASSERT_GE(tmpOutputData.Length(), numToWrite); + + outputData += tmpOutputData; + + nextStreamToRead += 1; + if (nextStreamToRead >= streamList.Length()) { + // Note: When we wrap around on the streams being read, its possible + // we will trigger a segment to be deleted from the pipe. It + // would be nice to validate this here, but we don't have any + // QI'able interface that would let us check easily. + + nextStreamToRead = 0; + } + } + } + + rv = writer->Close(); + ASSERT_NS_SUCCEEDED(rv); + + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + // Finally, read the remaining bytes from each stream. This may be + // different amounts of data depending on how much reading we did while + // writing. Verify that the end result matches the input data. + for (uint32_t i = 0; i < streamList.Length(); ++i) { + nsCOMPtr<nsIInputStream>& stream = streamList[i]; + nsCString& outputData = outputDataList[i]; + + nsAutoCString tmpOutputData; + rv = NS_ConsumeStream(stream, UINT32_MAX, tmpOutputData); + ASSERT_TRUE(rv == NS_BASE_STREAM_WOULD_BLOCK || NS_SUCCEEDED(rv)); + stream->Close(); + + // Append to total amount read from the stream + outputData += tmpOutputData; + + ASSERT_EQ(inputString.Length(), outputData.Length()); + ASSERT_TRUE(inputString.Equals(outputData)); + } +} + +} // namespace + +TEST(Pipes, Clone_BeforeWrite_ReadAtEnd) +{ + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 3, // num initial clones + 0, // num streams to close after each write + 0, // num clones to add after each write + 0); // num streams to read after each write +} + +TEST(Pipes, Clone_BeforeWrite_ReadDuringWrite) +{ + // Since this reads all streams on every write, it should trigger the + // pipe cursor roll back optimization. Currently we can only verify + // this with logging. + + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 3, // num initial clones + 0, // num streams to close after each write + 0, // num clones to add after each write + 4); // num streams to read after each write +} + +TEST(Pipes, Clone_DuringWrite_ReadAtEnd) +{ + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 0, // num initial clones + 0, // num streams to close after each write + 1, // num clones to add after each write + 0); // num streams to read after each write +} + +TEST(Pipes, Clone_DuringWrite_ReadDuringWrite) +{ + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 0, // num initial clones + 0, // num streams to close after each write + 1, // num clones to add after each write + 1); // num streams to read after each write +} + +TEST(Pipes, Clone_DuringWrite_ReadDuringWrite_CloseDuringWrite) +{ + // Since this reads streams faster than we clone new ones, it should + // trigger pipe segment deletion periodically. Currently we can + // only verify this with logging. + + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 1, // num initial clones + 1, // num streams to close after each write + 2, // num clones to add after each write + 3); // num streams to read after each write +} + +TEST(Pipes, Write_AsyncWait) +{ + nsCOMPtr<nsIAsyncInputStream> reader; + nsCOMPtr<nsIAsyncOutputStream> writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsTArray<char> inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + nsresult rv = + writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + RefPtr<testing::OutputStreamCallback> cb = + new testing::OutputStreamCallback(); + + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + testing::ConsumeAndValidateStream(reader, inputData); + + ASSERT_TRUE(cb->Called()); +} + +TEST(Pipes, Write_AsyncWait_Clone) +{ + nsCOMPtr<nsIAsyncInputStream> reader; + nsCOMPtr<nsIAsyncOutputStream> writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsCOMPtr<nsIInputStream> clone; + nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray<char> inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // This attempts to write data beyond the original pipe size limit. It + // should fail since neither side of the clone has been read yet. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + RefPtr<testing::OutputStreamCallback> cb = + new testing::OutputStreamCallback(); + + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + // Consume data on the original stream, but the clone still has not been read. + testing::ConsumeAndValidateStream(reader, inputData); + + // A clone that is not being read should not stall the other input stream + // reader. Therefore the writer callback should trigger when the fastest + // reader drains the other input stream. + ASSERT_TRUE(cb->Called()); + + // Attempt to write data. This will buffer data beyond the pipe size limit in + // order for the clone stream to still work. This is allowed because the + // other input stream has drained its buffered segments and is ready for more + // data. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // Again, this should fail since the origin stream has not been read again. + // The pipe size should still restrict how far ahead we can buffer even + // when there is a cloned stream not being read. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + // The write should again be blocked since we have written data and the + // main reader is at its maximum advance buffer. + ASSERT_FALSE(cb->Called()); + + nsTArray<char> expectedCloneData; + expectedCloneData.AppendElements(inputData); + expectedCloneData.AppendElements(inputData); + + // We should now be able to consume the entire backlog of buffered data on + // the cloned stream. + testing::ConsumeAndValidateStream(clone, expectedCloneData); + + // Draining the clone side should also trigger the AsyncWait() writer + // callback + ASSERT_TRUE(cb->Called()); + + // Finally, we should be able to consume the remaining data on the original + // reader. + testing::ConsumeAndValidateStream(reader, inputData); +} + +TEST(Pipes, Write_AsyncWait_Clone_CloseOriginal) +{ + nsCOMPtr<nsIAsyncInputStream> reader; + nsCOMPtr<nsIAsyncOutputStream> writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsCOMPtr<nsIInputStream> clone; + nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray<char> inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // This attempts to write data beyond the original pipe size limit. It + // should fail since neither side of the clone has been read yet. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + RefPtr<testing::OutputStreamCallback> cb = + new testing::OutputStreamCallback(); + + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + // Consume data on the original stream, but the clone still has not been read. + testing::ConsumeAndValidateStream(reader, inputData); + + // A clone that is not being read should not stall the other input stream + // reader. Therefore the writer callback should trigger when the fastest + // reader drains the other input stream. + ASSERT_TRUE(cb->Called()); + + // Attempt to write data. This will buffer data beyond the pipe size limit in + // order for the clone stream to still work. This is allowed because the + // other input stream has drained its buffered segments and is ready for more + // data. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // Again, this should fail since the origin stream has not been read again. + // The pipe size should still restrict how far ahead we can buffer even + // when there is a cloned stream not being read. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + // The write should again be blocked since we have written data and the + // main reader is at its maximum advance buffer. + ASSERT_FALSE(cb->Called()); + + // Close the original reader input stream. This was the fastest reader, + // so we should have a single stream that is buffered beyond our nominal + // limit. + reader->Close(); + + // Because the clone stream is still buffered the writable callback should + // not be fired. + ASSERT_FALSE(cb->Called()); + + // And we should not be able to perform a write. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + // Create another clone stream. Now we have two streams that exceed our + // maximum size limit + nsCOMPtr<nsIInputStream> clone2; + rv = NS_CloneInputStream(clone, getter_AddRefs(clone2)); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray<char> expectedCloneData; + expectedCloneData.AppendElements(inputData); + expectedCloneData.AppendElements(inputData); + + // We should now be able to consume the entire backlog of buffered data on + // the cloned stream. + testing::ConsumeAndValidateStream(clone, expectedCloneData); + + // The pipe should now be writable because we have two open streams, one of + // which is completely drained. + ASSERT_TRUE(cb->Called()); + + // Write again to reach our limit again. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // The stream is again non-writeable. + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_FALSE(cb->Called()); + + // Close the empty stream. This is different from our previous close since + // before we were closing a stream with some data still buffered. + clone->Close(); + + // The pipe should not be writable. The second clone is still fully buffered + // over our limit. + ASSERT_FALSE(cb->Called()); + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + // Finally consume all of the buffered data on the second clone. + expectedCloneData.AppendElements(inputData); + testing::ConsumeAndValidateStream(clone2, expectedCloneData); + + // Draining the final clone should make the pipe writable again. + ASSERT_TRUE(cb->Called()); +} + +TEST(Pipes, Read_AsyncWait) +{ + nsCOMPtr<nsIAsyncInputStream> reader; + nsCOMPtr<nsIAsyncOutputStream> writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsTArray<char> inputData; + testing::CreateData(segmentSize, inputData); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + nsresult rv = reader->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_TRUE(cb->Called()); + + testing::ConsumeAndValidateStream(reader, inputData); +} + +TEST(Pipes, Read_AsyncWait_Clone) +{ + nsCOMPtr<nsIAsyncInputStream> reader; + nsCOMPtr<nsIAsyncOutputStream> writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsCOMPtr<nsIInputStream> clone; + nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIAsyncInputStream> asyncClone = do_QueryInterface(clone); + ASSERT_TRUE(asyncClone); + + nsTArray<char> inputData; + testing::CreateData(segmentSize, inputData); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + RefPtr<testing::InputStreamCallback> cb2 = new testing::InputStreamCallback(); + + rv = reader->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + rv = asyncClone->AsyncWait(cb2, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb2->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_TRUE(cb->Called()); + ASSERT_TRUE(cb2->Called()); + + testing::ConsumeAndValidateStream(reader, inputData); +} + +namespace { + +nsresult CloseDuringReadFunc(nsIInputStream* aReader, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCountOut) { + MOZ_RELEASE_ASSERT(aReader); + MOZ_RELEASE_ASSERT(aClosure); + MOZ_RELEASE_ASSERT(aFromSegment); + MOZ_RELEASE_ASSERT(aWriteCountOut); + MOZ_RELEASE_ASSERT(aToOffset == 0); + + // This is insanity and you probably should not do this under normal + // conditions. We want to simulate the case where the pipe is closed + // (possibly from other end on another thread) simultaneously with the + // read. This is the easiest way to do trigger this case in a synchronous + // gtest. + MOZ_ALWAYS_SUCCEEDS(aReader->Close()); + + nsTArray<char>* buffer = static_cast<nsTArray<char>*>(aClosure); + buffer->AppendElements(aFromSegment, aCount); + + *aWriteCountOut = aCount; + + return NS_OK; +} + +void TestCloseDuringRead(uint32_t aSegmentSize, uint32_t aDataSize) { + nsCOMPtr<nsIInputStream> reader; + nsCOMPtr<nsIOutputStream> writer; + + const uint32_t maxSize = aSegmentSize; + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize, + maxSize); + + nsTArray<char> inputData; + + testing::CreateData(aDataSize, inputData); + + uint32_t numWritten = 0; + nsresult rv = + writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray<char> outputData; + + uint32_t numRead = 0; + rv = reader->ReadSegments(CloseDuringReadFunc, &outputData, + inputData.Length(), &numRead); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(inputData.Length(), numRead); + + ASSERT_EQ(inputData, outputData); + + uint64_t available; + rv = reader->Available(&available); + ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv); +} + +} // namespace + +TEST(Pipes, Close_During_Read_Partial_Segment) +{ TestCloseDuringRead(1024, 512); } + +TEST(Pipes, Close_During_Read_Full_Segment) +{ TestCloseDuringRead(1024, 1024); } + +TEST(Pipes, Interfaces) +{ + nsCOMPtr<nsIInputStream> reader; + nsCOMPtr<nsIOutputStream> writer; + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer)); + + nsCOMPtr<nsIAsyncInputStream> readerType1 = do_QueryInterface(reader); + ASSERT_TRUE(readerType1); + + nsCOMPtr<nsITellableStream> readerType2 = do_QueryInterface(reader); + ASSERT_TRUE(readerType2); + + nsCOMPtr<nsISearchableInputStream> readerType3 = do_QueryInterface(reader); + ASSERT_TRUE(readerType3); + + nsCOMPtr<nsICloneableInputStream> readerType4 = do_QueryInterface(reader); + ASSERT_TRUE(readerType4); + + nsCOMPtr<nsIClassInfo> readerType5 = do_QueryInterface(reader); + ASSERT_TRUE(readerType5); + + nsCOMPtr<nsIBufferedInputStream> readerType6 = do_QueryInterface(reader); + ASSERT_TRUE(readerType6); +} diff --git a/xpcom/tests/gtest/TestPriorityQueue.cpp b/xpcom/tests/gtest/TestPriorityQueue.cpp new file mode 100644 index 0000000000..c5f59072da --- /dev/null +++ b/xpcom/tests/gtest/TestPriorityQueue.cpp @@ -0,0 +1,73 @@ +/* -*- 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 "nsTPriorityQueue.h" +#include <stdio.h> +#include <stdlib.h> +#include "gtest/gtest.h" + +template <class T, class Compare> +void CheckPopSequence(const nsTPriorityQueue<T, Compare>& aQueue, + const T* aExpectedSequence, + const uint32_t aSequenceLength) { + nsTPriorityQueue<T, Compare> copy = aQueue.Clone(); + + for (uint32_t i = 0; i < aSequenceLength; i++) { + EXPECT_FALSE(copy.IsEmpty()); + + T pop = copy.Pop(); + EXPECT_EQ(pop, aExpectedSequence[i]); + } + + EXPECT_TRUE(copy.IsEmpty()); +} + +template <class A> +class MaxCompare { + public: + bool LessThan(const A& a, const A& b) { return a > b; } +}; + +TEST(PriorityQueue, Main) +{ + nsTPriorityQueue<int> queue; + + EXPECT_TRUE(queue.IsEmpty()); + + queue.Push(8); + queue.Push(6); + queue.Push(4); + queue.Push(2); + queue.Push(10); + queue.Push(6); + EXPECT_EQ(queue.Top(), 2); + EXPECT_EQ(queue.Length(), 6u); + EXPECT_FALSE(queue.IsEmpty()); + int expected[] = {2, 4, 6, 6, 8, 10}; + CheckPopSequence(queue, expected, sizeof(expected) / sizeof(expected[0])); + + // copy ctor is tested by using CheckPopSequence, but check default assignment + // operator + nsTPriorityQueue<int> queue2; + queue2 = queue.Clone(); + CheckPopSequence(queue2, expected, sizeof(expected) / sizeof(expected[0])); + + queue.Clear(); + EXPECT_TRUE(queue.IsEmpty()); + + // try same sequence with a max heap + nsTPriorityQueue<int, MaxCompare<int> > max_queue; + max_queue.Push(8); + max_queue.Push(6); + max_queue.Push(4); + max_queue.Push(2); + max_queue.Push(10); + max_queue.Push(6); + EXPECT_EQ(max_queue.Top(), 10); + int expected_max[] = {10, 8, 6, 6, 4, 2}; + CheckPopSequence(max_queue, expected_max, + sizeof(expected_max) / sizeof(expected_max[0])); +} diff --git a/xpcom/tests/gtest/TestQueue.cpp b/xpcom/tests/gtest/TestQueue.cpp new file mode 100644 index 0000000000..e6d8c07dd2 --- /dev/null +++ b/xpcom/tests/gtest/TestQueue.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 "mozilla/Queue.h" +#include "gtest/gtest.h" +#include <array> + +using namespace mozilla; + +namespace TestQueue { + +struct Movable { + Movable() : mDestructionCounter(nullptr) {} + explicit Movable(uint32_t* aDestructionCounter) + : mDestructionCounter(aDestructionCounter) {} + + ~Movable() { + if (mDestructionCounter) { + (*mDestructionCounter)++; + } + } + + Movable(Movable&& aOther) : mDestructionCounter(aOther.mDestructionCounter) { + aOther.mDestructionCounter = nullptr; + } + + uint32_t* mDestructionCounter; +}; + +template <size_t N, size_t ItemsPerPage> +void PushMovables(Queue<Movable, ItemsPerPage>& aQueue, + std::array<uint32_t, N>& aDestructionCounters) { + auto oldDestructionCounters = aDestructionCounters; + auto oldCount = aQueue.Count(); + for (uint32_t i = 0; i < N; ++i) { + aQueue.Push(Movable(&aDestructionCounters[i])); + } + for (uint32_t i = 0; i < N; ++i) { + EXPECT_EQ(aDestructionCounters[i], oldDestructionCounters[i]); + } + EXPECT_EQ(aQueue.Count(), oldCount + N); + EXPECT_FALSE(aQueue.IsEmpty()); +} + +template <size_t N> +void ExpectCounts(const std::array<uint32_t, N>& aDestructionCounters, + uint32_t aExpected) { + for (const auto& counters : aDestructionCounters) { + EXPECT_EQ(counters, aExpected); + } +} + +TEST(Queue, Clear) +{ + std::array<uint32_t, 32> counts{0}; + + Queue<Movable, 8> queue; + PushMovables(queue, counts); + queue.Clear(); + ExpectCounts(counts, 1); + EXPECT_EQ(queue.Count(), 0u); + EXPECT_TRUE(queue.IsEmpty()); +} + +TEST(Queue, Destroy) +{ + std::array<uint32_t, 32> counts{0}; + + { + Queue<Movable, 8> queue; + PushMovables(queue, counts); + } + ExpectCounts(counts, 1u); +} + +TEST(Queue, MoveConstruct) +{ + std::array<uint32_t, 32> counts{0}; + + { + Queue<Movable, 8> queue; + PushMovables(queue, counts); + + Queue<Movable, 8> queue2(std::move(queue)); + EXPECT_EQ(queue2.Count(), 32u); + EXPECT_FALSE(queue2.IsEmpty()); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_EQ(queue.Count(), 0u); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_TRUE(queue.IsEmpty()); + ExpectCounts(counts, 0u); + } + ExpectCounts(counts, 1u); +} + +TEST(Queue, MoveAssign) +{ + std::array<uint32_t, 32> counts{0}; + std::array<uint32_t, 32> counts2{0}; + + { + Queue<Movable, 8> queue; + PushMovables(queue, counts); + + { + Queue<Movable, 8> queue2; + PushMovables(queue2, counts2); + + queue = std::move(queue2); + ExpectCounts(counts, 1); + ExpectCounts(counts2, 0); + EXPECT_EQ(queue.Count(), 32u); + EXPECT_FALSE(queue.IsEmpty()); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_EQ(queue2.Count(), 0u); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_TRUE(queue2.IsEmpty()); + } + ExpectCounts(counts, 1); + ExpectCounts(counts2, 0); + EXPECT_EQ(queue.Count(), 32u); + EXPECT_FALSE(queue.IsEmpty()); + } + ExpectCounts(counts, 1); + ExpectCounts(counts2, 1); +} + +TEST(Queue, PopOrder) +{ + std::array<uint32_t, 32> counts{0}; + Queue<Movable, 8> queue; + PushMovables(queue, counts); + + for (auto& count : counts) { + EXPECT_EQ(count, 0u); + { + Movable popped = queue.Pop(); + EXPECT_EQ(popped.mDestructionCounter, &count); + EXPECT_EQ(count, 0u); + } + EXPECT_EQ(count, 1u); + } + EXPECT_TRUE(queue.IsEmpty()); + EXPECT_EQ(queue.Count(), 0u); +} + +void DoPushPopSequence(Queue<uint32_t, 8>& aQueue, uint32_t& aInSerial, + uint32_t& aOutSerial, uint32_t aPush, uint32_t aPop) { + auto initialCount = aQueue.Count(); + for (uint32_t i = 0; i < aPush; ++i) { + aQueue.Push(aInSerial++); + } + EXPECT_EQ(aQueue.Count(), initialCount + aPush); + for (uint32_t i = 0; i < aPop; ++i) { + uint32_t popped = aQueue.Pop(); + EXPECT_EQ(popped, aOutSerial++); + } + EXPECT_EQ(aQueue.Count(), initialCount + aPush - aPop); +} + +void PushPopPushPop(uint32_t aPush1, uint32_t aPop1, uint32_t aPush2, + uint32_t aPop2) { + Queue<uint32_t, 8> queue; + uint32_t inSerial = 0; + uint32_t outSerial = 0; + DoPushPopSequence(queue, inSerial, outSerial, aPush1, aPop1); + DoPushPopSequence(queue, inSerial, outSerial, aPush2, aPop2); +} + +TEST(Queue, PushPopSequence) +{ + for (uint32_t push1 = 0; push1 < 16; ++push1) { + for (uint32_t pop1 = 0; pop1 < push1; ++pop1) { + for (uint32_t push2 = 0; push2 < 16; ++push2) { + for (uint32_t pop2 = 0; pop2 < push1 - pop1 + push2; ++pop2) { + PushPopPushPop(push1, pop1, push2, pop2); + } + } + } + } +} + +} // namespace TestQueue diff --git a/xpcom/tests/gtest/TestRWLock.cpp b/xpcom/tests/gtest/TestRWLock.cpp new file mode 100644 index 0000000000..eee392f709 --- /dev/null +++ b/xpcom/tests/gtest/TestRWLock.cpp @@ -0,0 +1,214 @@ +/* -*- 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/Atomics.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/RWLock.h" +#include "mozilla/SyncRunnable.h" +#include "nsIThread.h" +#include "gtest/gtest.h" + +using mozilla::AutoReadLock; +using mozilla::AutoTryReadLock; +using mozilla::AutoTryWriteLock; +using mozilla::AutoWriteLock; +using mozilla::RWLock; + +static const size_t sNumThreads = 4; +static const size_t sOuterIterations = 100; +static const size_t sInnerIterations = 100; +static const size_t sWriteLockIteration = 10; + +// Based on example code from _Programming with POSIX Threads_. Not an actual +// test of correctness, but more of a "does this work at all" sort of test. + +class RWLockRunnable : public mozilla::Runnable { + public: + RWLockRunnable(RWLock* aRWLock, mozilla::Atomic<size_t>* aSharedData) + : mozilla::Runnable("RWLockRunnable"), + mRWLock(aRWLock), + mSharedData(aSharedData) {} + + NS_DECL_NSIRUNNABLE + + private: + ~RWLockRunnable() = default; + + RWLock* mRWLock; + mozilla::Atomic<size_t>* mSharedData; +}; + +NS_IMETHODIMP +RWLockRunnable::Run() { + for (size_t i = 0; i < sOuterIterations; ++i) { + if (i % sWriteLockIteration == 0) { + mozilla::AutoWriteLock lock(*mRWLock); + + ++(*mSharedData); + } else { + mozilla::AutoReadLock lock(*mRWLock); + + // Loop and try to force other threads to run, but check that our + // shared data isn't being modified by them. + size_t initialValue = *mSharedData; + for (size_t j = 0; j < sInnerIterations; ++j) { + EXPECT_EQ(initialValue, *mSharedData); + + // This is a magic yield call. + PR_Sleep(PR_INTERVAL_NO_WAIT); + } + } + } + + return NS_OK; +} + +TEST(RWLock, SmokeTest) +{ + nsCOMPtr<nsIThread> threads[sNumThreads]; + RWLock rwlock MOZ_UNANNOTATED("test lock"); + mozilla::Atomic<size_t> data(0); + + for (size_t i = 0; i < sNumThreads; ++i) { + nsCOMPtr<nsIRunnable> event = new RWLockRunnable(&rwlock, &data); + NS_NewNamedThread("RWLockTester", getter_AddRefs(threads[i]), event); + } + + // Wait for all the threads to finish. + for (size_t i = 0; i < sNumThreads; ++i) { + nsresult rv = threads[i]->Shutdown(); + EXPECT_NS_SUCCEEDED(rv); + } + + EXPECT_EQ(data, (sOuterIterations / sWriteLockIteration) * sNumThreads); +} + +template <typename Function> +static std::invoke_result_t<Function> RunOnBackgroundThread( + Function&& aFunction) { + using Result = std::invoke_result_t<Function>; + nsCOMPtr<nsISerialEventTarget> thread; + MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue( + "TestRWLock Background Thread", getter_AddRefs(thread))); + mozilla::Maybe<Result> tryResult; + RefPtr<nsIRunnable> runnable = + NS_NewRunnableFunction(__func__, [&] { tryResult.emplace(aFunction()); }); + MOZ_ALWAYS_SUCCEEDS( + mozilla::SyncRunnable::DispatchToThread(thread.get(), runnable)); + return *tryResult; +} + +TEST(RWLock, AutoTryReadLock) +{ + RWLock l1 MOZ_UNANNOTATED("autotryreadlock"); + { + AutoTryReadLock autol1(l1); + + EXPECT_TRUE(autol1); + + AutoTryReadLock autol2(l1); + EXPECT_TRUE(autol2); + + EXPECT_TRUE(RunOnBackgroundThread([&] { + AutoTryReadLock lock(l1); + return !!lock; + })); + + EXPECT_TRUE(autol1); + EXPECT_TRUE(autol2); + + { + RWLock l2 MOZ_UNANNOTATED("autotryreadlock2"); + AutoTryReadLock autol3(l2); + + EXPECT_TRUE(autol3); + } + + EXPECT_TRUE(autol1); + EXPECT_TRUE(autol2); + } + + { + AutoWriteLock autol4(l1); + MOZ_ASSERT(l1.LockedForWritingByCurrentThread()); + + AutoTryReadLock autol5(l1); + EXPECT_FALSE(autol5); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryReadLock lock(l1); + return !!lock; + })); + } + + AutoTryReadLock autol6(l1); + EXPECT_TRUE(autol6); + + EXPECT_TRUE(RunOnBackgroundThread([&] { + AutoTryReadLock lock(l1); + return !!lock; + })); +} + +TEST(RWLock, AutoTryWriteLock) +{ + RWLock l1 MOZ_UNANNOTATED("autotrywritelock"); + { + AutoTryWriteLock autol1(l1); + + EXPECT_TRUE(autol1); + + AutoTryReadLock autol2(l1); + EXPECT_FALSE(autol2); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryWriteLock lock(l1); + return !!lock; + })); + + EXPECT_TRUE(autol1); + EXPECT_FALSE(autol2); + + { + RWLock l2 MOZ_UNANNOTATED("autotrywritelock2"); + AutoTryWriteLock autol3(l2); + + EXPECT_TRUE(autol3); + } + + EXPECT_TRUE(autol1); + EXPECT_FALSE(autol2); + } + + { + AutoReadLock autol4(l1); + + AutoTryWriteLock autol5(l1); + EXPECT_FALSE(autol5); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryWriteLock lock(l1); + return !!lock; + })); + } + + { + AutoWriteLock autol6(l1); + MOZ_ASSERT(l1.LockedForWritingByCurrentThread()); + + AutoTryWriteLock autol7(l1); + EXPECT_FALSE(autol7); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryWriteLock lock(l1); + return !!lock; + })); + } + + AutoTryWriteLock autol8(l1); + EXPECT_TRUE(autol8); +} diff --git a/xpcom/tests/gtest/TestRacingServiceManager.cpp b/xpcom/tests/gtest/TestRacingServiceManager.cpp new file mode 100644 index 0000000000..ad6c08d97b --- /dev/null +++ b/xpcom/tests/gtest/TestRacingServiceManager.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIFactory.h" +#include "nsXULAppAPI.h" +#include "nsIThread.h" + +#include "nsComponentManager.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCIDInternal.h" +#include "pratom.h" +#include "prmon.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/gtest/MozAssertions.h" + +#include "mozilla/ReentrantMonitor.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +/* f93f6bdc-88af-42d7-9d64-1b43c649a3e5 */ +#define FACTORY_CID1 \ + { \ + 0xf93f6bdc, 0x88af, 0x42d7, { \ + 0x9d, 0x64, 0x1b, 0x43, 0xc6, 0x49, 0xa3, 0xe5 \ + } \ + } +NS_DEFINE_CID(kFactoryCID1, FACTORY_CID1); + +/* ef38ad65-6595-49f0-8048-e819f81d15e2 */ +#define FACTORY_CID2 \ + { \ + 0xef38ad65, 0x6595, 0x49f0, { \ + 0x80, 0x48, 0xe8, 0x19, 0xf8, 0x1d, 0x15, 0xe2 \ + } \ + } +NS_DEFINE_CID(kFactoryCID2, FACTORY_CID2); + +#define FACTORY_CONTRACTID "TestRacingThreadManager/factory;1" + +namespace TestRacingServiceManager { +int32_t gComponent1Count = 0; +int32_t gComponent2Count = 0; + +ReentrantMonitor* gReentrantMonitor = nullptr; + +bool gCreateInstanceCalled = false; +bool gMainThreadWaiting = false; + +class AutoCreateAndDestroyReentrantMonitor { + public: + explicit AutoCreateAndDestroyReentrantMonitor( + ReentrantMonitor** aReentrantMonitorPtr) + : mReentrantMonitorPtr(aReentrantMonitorPtr) { + *aReentrantMonitorPtr = + new ReentrantMonitor("TestRacingServiceManager::AutoMon"); + MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!"); + } + + ~AutoCreateAndDestroyReentrantMonitor() { + if (*mReentrantMonitorPtr) { + delete *mReentrantMonitorPtr; + *mReentrantMonitorPtr = nullptr; + } + } + + private: + ReentrantMonitor** mReentrantMonitorPtr; +}; + +class Factory final : public nsIFactory { + ~Factory() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Factory() : mFirstComponentCreated(false) {} + + NS_IMETHOD CreateInstance(const nsIID& aIID, void** aResult) override; + + bool mFirstComponentCreated; +}; + +NS_IMPL_ISUPPORTS(Factory, nsIFactory) + +class Component1 final : public nsISupports { + ~Component1() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Component1() { + // This is the real test - make sure that only one instance is ever created. + int32_t count = PR_AtomicIncrement(&gComponent1Count); + MOZ_RELEASE_ASSERT(count == 1, "Too many components created!"); + } +}; + +NS_IMPL_ADDREF(Component1) +NS_IMPL_RELEASE(Component1) + +NS_INTERFACE_MAP_BEGIN(Component1) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +class Component2 final : public nsISupports { + ~Component2() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Component2() { + // This is the real test - make sure that only one instance is ever created. + int32_t count = PR_AtomicIncrement(&gComponent2Count); + EXPECT_EQ(count, int32_t(1)) << "Too many components created!"; + } +}; + +NS_IMPL_ADDREF(Component2) +NS_IMPL_RELEASE(Component2) + +NS_INTERFACE_MAP_BEGIN(Component2) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +Factory::CreateInstance(const nsIID& aIID, void** aResult) { + // Make sure that the second thread beat the main thread to the getService + // call. + MOZ_RELEASE_ASSERT(!NS_IsMainThread(), "Wrong thread!"); + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + gCreateInstanceCalled = true; + mon.Notify(); + + mon.Wait(PR_MillisecondsToInterval(3000)); + } + + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsISupports> instance; + + if (!mFirstComponentCreated) { + instance = new Component1(); + } else { + instance = new Component2(); + } + NS_ENSURE_TRUE(instance, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = instance->QueryInterface(aIID, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +class TestRunnable : public Runnable { + public: + NS_DECL_NSIRUNNABLE + + TestRunnable() + : mozilla::Runnable("TestRacingServiceManager::TestRunnable"), + mFirstRunnableDone(false) {} + + bool mFirstRunnableDone; +}; + +NS_IMETHODIMP +TestRunnable::Run() { + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + while (!gMainThreadWaiting) { + mon.Wait(); + } + } + + nsresult rv; + nsCOMPtr<nsISupports> component; + + if (!mFirstRunnableDone) { + component = do_GetService(kFactoryCID1, &rv); + } else { + component = do_GetService(FACTORY_CONTRACTID, &rv); + } + EXPECT_TRUE(NS_SUCCEEDED(rv)) << "GetService failed!"; + + return NS_OK; +} + +static Factory* gFactory; + +TEST(RacingServiceManager, Test) +{ + nsresult rv; + + gFactory = new Factory(); + NS_ADDREF(gFactory); + + nsComponentManagerImpl::gComponentManager->RegisterFactory( + kFactoryCID2, "factory1", FACTORY_CONTRACTID, gFactory); + nsComponentManagerImpl::gComponentManager->RegisterFactory( + kFactoryCID1, "factory2", nullptr, gFactory); + + AutoCreateAndDestroyReentrantMonitor mon1(&gReentrantMonitor); + + RefPtr<TestRunnable> runnable = new TestRunnable(); + ASSERT_TRUE(runnable); + + // Run the classID test + nsCOMPtr<nsIThread> newThread; + rv = NS_NewNamedThread("RacingServMan", getter_AddRefs(newThread), runnable); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon2(*gReentrantMonitor); + + gMainThreadWaiting = true; + mon2.Notify(); + + while (!gCreateInstanceCalled) { + mon2.Wait(); + } + } + + nsCOMPtr<nsISupports> component(do_GetService(kFactoryCID1, &rv)); + ASSERT_NS_SUCCEEDED(rv); + + // Reset for the contractID test + gMainThreadWaiting = gCreateInstanceCalled = false; + gFactory->mFirstComponentCreated = runnable->mFirstRunnableDone = true; + component = nullptr; + + rv = newThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon3(*gReentrantMonitor); + + gMainThreadWaiting = true; + mon3.Notify(); + + while (!gCreateInstanceCalled) { + mon3.Wait(); + } + } + + component = do_GetService(FACTORY_CONTRACTID, &rv); + ASSERT_NS_SUCCEEDED(rv); + + NS_RELEASE(gFactory); +} + +} // namespace TestRacingServiceManager diff --git a/xpcom/tests/gtest/TestRecursiveMutex.cpp b/xpcom/tests/gtest/TestRecursiveMutex.cpp new file mode 100644 index 0000000000..57fb6fc038 --- /dev/null +++ b/xpcom/tests/gtest/TestRecursiveMutex.cpp @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsThreadUtils.h" +#include "mozilla/RecursiveMutex.h" +#include "gtest/gtest.h" + +using mozilla::RecursiveMutex; +using mozilla::RecursiveMutexAutoLock; + +// Basic test to make sure the underlying implementation of RecursiveMutex is, +// well, actually recursively acquirable. + +TEST(RecursiveMutex, SmokeTest) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + RecursiveMutex mutex("testing mutex"); + + RecursiveMutexAutoLock lock1(mutex); + RecursiveMutexAutoLock lock2(mutex); + + //...and done. +} diff --git a/xpcom/tests/gtest/TestRustRegex.cpp b/xpcom/tests/gtest/TestRustRegex.cpp new file mode 100644 index 0000000000..38f7119b6b --- /dev/null +++ b/xpcom/tests/gtest/TestRustRegex.cpp @@ -0,0 +1,181 @@ +/* -*- 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 "gtest/gtest.h" +#include "mozilla/RustRegex.h" + +// This file is adapted from the test.c file in the `rure` crate, but modified +// to use gtest and the `RustRegex` wrapper. + +namespace mozilla { + +TEST(TestRustRegex, IsMatch) +{ + RustRegex re("\\p{So}$"); + ASSERT_TRUE(re.IsValid()); + ASSERT_TRUE(re.IsMatch("snowman: \xE2\x98\x83")); +} + +TEST(TestRustRegex, ShortestMatch) +{ + RustRegex re("a+"); + ASSERT_TRUE(re.IsValid()); + + Maybe<size_t> match = re.ShortestMatch("aaaaa"); + ASSERT_TRUE(match); + EXPECT_EQ(*match, 1u); +} + +TEST(TestRustRegex, Find) +{ + RustRegex re("\\p{So}$"); + ASSERT_TRUE(re.IsValid()); + + auto match = re.Find("snowman: \xE2\x98\x83"); + ASSERT_TRUE(match); + EXPECT_EQ(match->start, 9u); + EXPECT_EQ(match->end, 12u); +} + +TEST(TestRustRegex, Captures) +{ + RustRegex re(".(.*(?P<snowman>\\p{So}))$"); + ASSERT_TRUE(re); + + auto captures = re.FindCaptures("snowman: \xE2\x98\x83"); + ASSERT_TRUE(captures); + EXPECT_EQ(captures.Length(), 3u); + EXPECT_EQ(re.CaptureNameIndex("snowman"), 2); + + auto match = captures[2]; + ASSERT_TRUE(match); + EXPECT_EQ(match->start, 9u); + EXPECT_EQ(match->end, 12u); +} + +TEST(TestRustRegex, Iter) +{ + RustRegex re("\\w+(\\w)"); + ASSERT_TRUE(re); + + auto it = re.IterMatches("abc xyz"); + ASSERT_TRUE(it); + + auto match = it.Next(); + ASSERT_TRUE(match); + EXPECT_EQ(match->start, 0u); + EXPECT_EQ(match->end, 3u); + + auto captures = it.NextCaptures(); + ASSERT_TRUE(captures); + + auto capture = captures[1]; + ASSERT_TRUE(capture); + EXPECT_EQ(capture->start, 6u); + EXPECT_EQ(capture->end, 7u); +} + +TEST(TestRustRegex, IterCaptureNames) +{ + RustRegex re("(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})"); + ASSERT_TRUE(re); + + auto it = re.IterCaptureNames(); + Maybe<const char*> result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, ""); + + result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, "year"); + + result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, "month"); + + result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, "day"); + + result = it.Next(); + ASSERT_TRUE(result.isNothing()); +} + +/* + * This tests whether we can set the flags correctly. In this case, we disable + * all flags, which includes disabling Unicode mode. When we disable Unicode + * mode, we can match arbitrary possibly invalid UTF-8 bytes, such as \xFF. + * (When Unicode mode is enabled, \xFF won't match .) + */ +TEST(TestRustRegex, Flags) +{ + { + RustRegex re("."); + ASSERT_TRUE(re); + ASSERT_FALSE(re.IsMatch("\xFF")); + } + { + RustRegex re(".", RustRegexOptions().Unicode(false)); + ASSERT_TRUE(re); + ASSERT_TRUE(re.IsMatch("\xFF")); + } +} + +TEST(TestRustRegex, CompileErrorSizeLimit) +{ + RustRegex re("\\w{100}", RustRegexOptions().SizeLimit(0)); + EXPECT_FALSE(re); +} + +TEST(TestRustRegex, SetMatches) +{ + RustRegexSet set(nsTArray<std::string_view>{"foo", "barfoo", "\\w+", "\\d+", + "foobar", "bar"}); + + ASSERT_TRUE(set); + EXPECT_EQ(set.Length(), 6u); + EXPECT_TRUE(set.IsMatch("foobar")); + EXPECT_FALSE(set.IsMatch("")); + + auto matches = set.Matches("foobar"); + EXPECT_TRUE(matches.matchedAny); + EXPECT_EQ(matches.matches.Length(), 6u); + + nsTArray<bool> expectedMatches{true, false, true, false, true, true}; + EXPECT_EQ(matches.matches, expectedMatches); +} + +TEST(TestRustRegex, SetMatchStart) +{ + RustRegexSet re(nsTArray<std::string_view>{"foo", "bar", "fooo"}); + EXPECT_TRUE(re); + EXPECT_EQ(re.Length(), 3u); + + EXPECT_FALSE(re.IsMatch("foobiasdr", 2)); + + { + auto matches = re.Matches("fooobar"); + EXPECT_TRUE(matches.matchedAny); + nsTArray<bool> expectedMatches{true, true, true}; + EXPECT_EQ(matches.matches, expectedMatches); + } + + { + auto matches = re.Matches("fooobar", 1); + EXPECT_TRUE(matches.matchedAny); + nsTArray<bool> expectedMatches{false, true, false}; + EXPECT_EQ(matches.matches, expectedMatches); + } +} + +TEST(TestRustRegex, RegexSetOptions) +{ + RustRegexSet re(nsTArray<std::string_view>{"\\w{100}"}, + RustRegexOptions().SizeLimit(0)); + EXPECT_FALSE(re); +} + +} // namespace mozilla diff --git a/xpcom/tests/gtest/TestSTLWrappers.cpp b/xpcom/tests/gtest/TestSTLWrappers.cpp new file mode 100644 index 0000000000..31b658f764 --- /dev/null +++ b/xpcom/tests/gtest/TestSTLWrappers.cpp @@ -0,0 +1,65 @@ +#include <stdio.h> + +#include <algorithm> +#ifndef mozilla_algorithm_h +# error "failed to wrap <algorithm>" +#endif + +#include <vector> +#ifndef mozilla_vector_h +# error "failed to wrap <vector>" +#endif + +// gcc errors out if we |try ... catch| with -fno-exceptions, but we +// can still test on windows +#ifdef _MSC_VER +// C4530 will be generated whenever try...catch is used without +// enabling exceptions. We know we don't enbale exceptions. +# pragma warning(disable : 4530) +# define TRY try +# define CATCH(e) catch (e) +#else +# define TRY +# define CATCH(e) if (0) +#endif + +#include "gtest/gtest.h" + +#include "mozilla/gtest/MozHelpers.h" + +void ShouldAbort() { + ZERO_GDB_SLEEP(); + + mozilla::gtest::DisableCrashReporter(); + + std::vector<int> v; + + TRY { + // v.at(1) on empty v should abort; NOT throw an exception + + (void)v.at(1); + } + CATCH(const std::out_of_range&) { + fputs("TEST-FAIL | TestSTLWrappers.cpp | caught an exception?\n", stderr); + return; + } + + fputs("TEST-FAIL | TestSTLWrappers.cpp | didn't abort()?\n", stderr); +} + +#if defined(XP_WIN) || (defined(XP_MACOSX) && !defined(MOZ_DEBUG)) +TEST(STLWrapper, DISABLED_ShouldAbortDeathTest) +#else +TEST(STLWrapper, ShouldAbortDeathTest) +#endif +{ + ASSERT_DEATH_IF_SUPPORTED(ShouldAbort(), +#ifdef __GLIBCXX__ + // Only libstdc++ will print this message. + "terminate called after throwing an instance of " + "'std::out_of_range'|vector::_M_range_check" +#else + "" +#endif + ); +} diff --git a/xpcom/tests/gtest/TestSegmentedBuffer.cpp b/xpcom/tests/gtest/TestSegmentedBuffer.cpp new file mode 100644 index 0000000000..136e35d489 --- /dev/null +++ b/xpcom/tests/gtest/TestSegmentedBuffer.cpp @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "gtest/gtest.h" +#include "../../io/nsSegmentedBuffer.h" +#include "nsIEventTarget.h" + +using namespace mozilla; + +TEST(SegmentedBuffer, AppendAndDelete) +{ + auto buf = MakeUnique<nsSegmentedBuffer>(); + buf->Init(4); + char* seg; + bool empty; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(empty) << "DeleteFirstSegment failed"; +} diff --git a/xpcom/tests/gtest/TestSlicedInputStream.cpp b/xpcom/tests/gtest/TestSlicedInputStream.cpp new file mode 100644 index 0000000000..a2c1a077e4 --- /dev/null +++ b/xpcom/tests/gtest/TestSlicedInputStream.cpp @@ -0,0 +1,665 @@ +#include "gtest/gtest.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/SlicedInputStream.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "Helpers.h" + +using namespace mozilla; + +// This helper class is used to call OnInputStreamReady with the right stream +// as argument. +class InputStreamCallback final : public nsIInputStreamCallback { + nsCOMPtr<nsIAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + InputStreamCallback(nsIAsyncInputStream* aStream, + nsIInputStreamCallback* aCallback) + : mStream(aStream), mCallback(aCallback) {} + + NS_IMETHOD + OnInputStreamReady(nsIAsyncInputStream* aStream) override { + return mCallback->OnInputStreamReady(mStream); + } + + private: + ~InputStreamCallback() = default; +}; + +NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback) + +/* We want to ensure that sliced streams work with both seekable and + * non-seekable input streams. As our string streams are seekable, we need to + * provide a string stream that doesn't permit seeking, so we can test the + * logic that emulates seeking in sliced input streams. + */ +class NonSeekableStringStream final : public nsIAsyncInputStream { + nsCOMPtr<nsIInputStream> mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonSeekableStringStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + explicit NonSeekableStringStream(nsIInputStream* aStream) + : mStream(aStream) {} + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + NS_IMETHOD + CloseWithStatus(nsresult aStatus) override { + nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mStream); + if (!async) { + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + } + + return async->CloseWithStatus(aStatus); + } + + NS_IMETHOD + AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override { + nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mStream); + if (!async) { + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + } + + RefPtr<InputStreamCallback> callback = + new InputStreamCallback(this, aCallback); + + return async->AsyncWait(callback, aFlags, aRequestedCount, aEventTarget); + } + + private: + ~NonSeekableStringStream() = default; +}; + +NS_IMPL_ISUPPORTS(NonSeekableStringStream, nsIInputStream, nsIAsyncInputStream) + +// Helper function for creating a seekable nsIInputStream + a SlicedInputStream. +static SlicedInputStream* CreateSeekableStreams(uint32_t aSize, uint64_t aStart, + uint64_t aLength, + nsCString& aBuffer) { + aBuffer.SetLength(aSize); + for (uint32_t i = 0; i < aSize; ++i) { + aBuffer.BeginWriting()[i] = i % 10; + } + + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), aBuffer); + return new SlicedInputStream(stream.forget(), aStart, aLength); +} + +// Helper function for creating a non-seekable nsIInputStream + a +// SlicedInputStream. +static SlicedInputStream* CreateNonSeekableStreams(uint32_t aSize, + uint64_t aStart, + uint64_t aLength, + nsCString& aBuffer) { + aBuffer.SetLength(aSize); + for (uint32_t i = 0; i < aSize; ++i) { + aBuffer.BeginWriting()[i] = i % 10; + } + + RefPtr<NonSeekableStringStream> stream = new NonSeekableStringStream(aBuffer); + return new SlicedInputStream(stream.forget(), aStart, aLength); +} + +// Same start, same length. +TEST(TestSlicedInputStream, Simple) +{ + const size_t kBufSize = 4096; + + nsCString buf; + RefPtr<SlicedInputStream> sis = + CreateSeekableStreams(kBufSize, 0, kBufSize, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + char buf2[kBufSize]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get()).Equals(nsCString(buf2))); +} + +// Simple sliced stream - seekable +TEST(TestSlicedInputStream, Sliced) +{ + const size_t kBufSize = 4096; + + nsCString buf; + RefPtr<SlicedInputStream> sis = CreateSeekableStreams(kBufSize, 10, 100, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)100, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)100, count); + ASSERT_TRUE(nsCString(buf.get() + 10, count).Equals(nsCString(buf2, count))); +} + +// Simple sliced stream - non seekable +TEST(TestSlicedInputStream, SlicedNoSeek) +{ + const size_t kBufSize = 4096; + + nsCString buf; + RefPtr<SlicedInputStream> sis = + CreateNonSeekableStreams(kBufSize, 10, 100, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)100, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)100, count); + ASSERT_TRUE(nsCString(buf.get() + 10, count).Equals(nsCString(buf2, count))); +} + +// Big inputStream - seekable +TEST(TestSlicedInputStream, BigSliced) +{ + const size_t kBufSize = 4096 * 40; + + nsCString buf; + RefPtr<SlicedInputStream> sis = + CreateSeekableStreams(kBufSize, 4096 * 5, 4096 * 10, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)4096 * 10, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)4096 * 10, count); + ASSERT_TRUE( + nsCString(buf.get() + 4096 * 5, count).Equals(nsCString(buf2, count))); +} + +// Big inputStream - non seekable +TEST(TestSlicedInputStream, BigSlicedNoSeek) +{ + const size_t kBufSize = 4096 * 40; + + nsCString buf; + RefPtr<SlicedInputStream> sis = + CreateNonSeekableStreams(kBufSize, 4096 * 5, 4096 * 10, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)4096 * 10, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)4096 * 10, count); + ASSERT_TRUE( + nsCString(buf.get() + 4096 * 5, count).Equals(nsCString(buf2, count))); +} + +// Available size. +TEST(TestSlicedInputStream, Available) +{ + nsCString buf; + RefPtr<SlicedInputStream> sis = + CreateNonSeekableStreams(500000, 4, 400000, buf); + + uint64_t toRead = 400000; + for (uint32_t i = 0; i < 400; ++i) { + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ(toRead, length); + + char buf2[1000]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)1000, count); + ASSERT_TRUE(nsCString(buf.get() + 4 + (1000 * i), count) + .Equals(nsCString(buf2, count))); + + toRead -= count; + } + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)0, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)0, count); +} + +// What if start is > then the size of the buffer? +TEST(TestSlicedInputStream, StartBiggerThan) +{ + nsCString buf; + RefPtr<SlicedInputStream> sis = CreateNonSeekableStreams(500, 4000, 1, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)0, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)0, count); +} + +// What if the length is > than the size of the buffer? +TEST(TestSlicedInputStream, LengthBiggerThan) +{ + nsCString buf; + RefPtr<SlicedInputStream> sis = CreateNonSeekableStreams(500, 0, 500000, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)500, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)500, count); +} + +// What if the length is 0? +TEST(TestSlicedInputStream, Length0) +{ + nsCString buf; + RefPtr<SlicedInputStream> sis = CreateNonSeekableStreams(500, 0, 0, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)0, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)0, count); +} + +// Seek test NS_SEEK_SET +TEST(TestSlicedInputStream, Seek_SET) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr<SlicedInputStream> sis; + { + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + sis = new SlicedInputStream(stream.forget(), 1, buf.Length()); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_SET, 1)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)buf.Length() - 2, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)buf.Length() - 2, count); + ASSERT_EQ(0, strncmp(buf2, "llo world", count)); +} + +// Seek test NS_SEEK_CUR +TEST(TestSlicedInputStream, Seek_CUR) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr<SlicedInputStream> sis; + { + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + sis = new SlicedInputStream(stream.forget(), 1, buf.Length()); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_CUR, 1)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)buf.Length() - 2, length); + + char buf2[3]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(buf2, "llo", count)); + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_CUR, 1)); + + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(buf2, "wor", count)); +} + +// Seek test NS_SEEK_END - length > real one +TEST(TestSlicedInputStream, Seek_END_Bigger) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr<SlicedInputStream> sis; + { + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + sis = new SlicedInputStream(stream.forget(), 2, buf.Length()); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_END, -5)); + + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(stream); + ASSERT_EQ(NS_OK, seekStream->Seek(nsISeekableStream::NS_SEEK_END, -5)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)5, length); + + ASSERT_EQ(NS_OK, stream->Available(&length)); + ASSERT_EQ((uint64_t)5, length); + + char buf2[5]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)5, count); + ASSERT_EQ(0, strncmp(buf2, "world", count)); + + ASSERT_EQ(NS_OK, stream->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)5, count); + ASSERT_EQ(0, strncmp(buf2, "world", count)); +} + +// Seek test NS_SEEK_END - length < real one +TEST(TestSlicedInputStream, Seek_END_Lower) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr<SlicedInputStream> sis; + { + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + sis = new SlicedInputStream(stream.forget(), 2, 6); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_END, -3)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)3, length); + + char buf2[5]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(buf2, " wo", count)); +} + +// Check the nsIAsyncInputStream interface +TEST(TestSlicedInputStream, NoAsyncInputStream) +{ + const size_t kBufSize = 4096; + + nsCString buf; + nsCOMPtr<nsIInputStream> sis = + CreateSeekableStreams(kBufSize, 0, kBufSize, buf); + + // If the stream is not asyncInputStream, also SIS is not. + nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(sis); + ASSERT_TRUE(!async); +} + +TEST(TestSlicedInputStream, AsyncInputStream) +{ + nsCOMPtr<nsIAsyncInputStream> reader; + nsCOMPtr<nsIAsyncOutputStream> writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsTArray<char> inputData; + testing::CreateData(segmentSize, inputData); + + // We have to wrap the reader because it implements only a partial + // nsISeekableStream interface. When ::Seek() is called, it does a MOZ_CRASH. + nsCOMPtr<nsIInputStream> sis; + { + RefPtr<NonSeekableStringStream> wrapper = + new NonSeekableStringStream(reader); + + sis = new SlicedInputStream(wrapper.forget(), 500, 500); + } + + nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(sis); + ASSERT_TRUE(!!async); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + nsresult rv = async->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_TRUE(cb->Called()); + + inputData.RemoveElementsAt(0, 500); + inputData.RemoveElementsAt(500, 24); + + testing::ConsumeAndValidateStream(async, inputData); +} + +TEST(TestSlicedInputStream, QIInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + for (int i = 0; i < 4; i++) { + nsCOMPtr<nsIInputStream> sis; + { + RefPtr<testing::LengthInputStream> stream = + new testing::LengthInputStream(buf, i % 2, i > 1); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + { + nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(sis); + ASSERT_EQ(!!(i % 2), !!qi); + } + + { + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis); + ASSERT_EQ(i > 1, !!qi); + } + } +} + +TEST(TestSlicedInputStream, InputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> sis; + { + RefPtr<testing::LengthInputStream> stream = + new testing::LengthInputStream(buf, true, false); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(5, size); +} + +TEST(TestSlicedInputStream, NegativeInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> sis; + { + RefPtr<testing::LengthInputStream> stream = + new testing::LengthInputStream(buf, true, false, NS_OK, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(-1, size); +} + +TEST(TestSlicedInputStream, AsyncInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> sis; + { + RefPtr<testing::LengthInputStream> stream = + new testing::LengthInputStream(buf, false, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + RefPtr<testing::LengthCallback> callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestSlicedInputStream, AsyncInputStreamLength)"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(5, callback->Size()); +} + +TEST(TestSlicedInputStream, NegativeAsyncInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> sis; + { + RefPtr<testing::LengthInputStream> stream = + new testing::LengthInputStream(buf, false, true, NS_OK, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + RefPtr<testing::LengthCallback> callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestSlicedInputStream, NegativeAsyncInputStreamLength)"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(-1, callback->Size()); +} + +TEST(TestSlicedInputStream, AbortLengthCallback) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> sis; + { + RefPtr<testing::LengthInputStream> stream = + new testing::LengthInputStream(buf, false, true, NS_OK, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + RefPtr<testing::LengthCallback> callback1 = new testing::LengthCallback(); + nsresult rv = qi->AsyncLengthWait(callback1, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + RefPtr<testing::LengthCallback> callback2 = new testing::LengthCallback(); + rv = qi->AsyncLengthWait(callback2, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestSlicedInputStream, AbortLengthCallback)"_ns, + [&]() { return callback2->Called(); })); + ASSERT_TRUE(!callback1->Called()); + ASSERT_EQ(-1, callback2->Size()); +} diff --git a/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp b/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp new file mode 100644 index 0000000000..10e2b71a69 --- /dev/null +++ b/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp @@ -0,0 +1,368 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/SmallArrayLRUCache.h" + +#include <algorithm> +#include <cstring> +#include <utility> + +using Key = unsigned; + +struct Value { + Value() : m(unsigned(-1)) {} + explicit Value(unsigned a) : m(a) {} + + bool operator==(const Value& aOther) const { return m == aOther.m; } + bool operator!=(const Value& aOther) const { return m != aOther.m; } + + unsigned m; +}; + +constexpr static unsigned CacheSize = 8; + +using TestCache = mozilla::SmallArrayLRUCache<Key, Value, CacheSize>; + +// This struct embeds a given object type between two "guard" objects, to check +// if anything is written out of bounds. +template <typename T> +struct Boxed { + constexpr static size_t GuardSize = std::max(sizeof(T), size_t(256)); + + // A Guard is a character array with a pre-set content that can be checked for + // unwanted changes. + struct Guard { + char mGuard[GuardSize]; + explicit Guard(char aValue) { memset(&mGuard, aValue, GuardSize); } + void Check(char aValue) { + for (const char& c : mGuard) { + ASSERT_EQ(c, aValue); + } + } + }; + + Guard mGuardBefore; + T mObject; + Guard mGuardAfter; + + template <typename... Ts> + explicit Boxed(Ts&&... aTs) + : mGuardBefore(0x5a), + mObject(std::forward<Ts>(aTs)...), + mGuardAfter(0xa5) { + Check(); + } + + ~Boxed() { Check(); } + + T& Object() { return mObject; } + const T& Object() const { return mObject; } + + void Check() { + mGuardBefore.Check(0x5a); + mGuardAfter.Check(0xa5); + } +}; + +TEST(SmallArrayLRUCache, FetchOrAdd_KeysFitInCache) +{ + // We're going to add-or-fetch between 1 and CacheSize keys, so they all fit + // in the cache. + for (Key keys = 1; keys <= CacheSize; ++keys) { + Boxed<TestCache> boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i, [&]() { + valueFunctionCalled = true; + return Value{i}; + }); + ASSERT_EQ(v, Value{i}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Fetching any key should never call the value function. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, Add_FetchOrAdd_KeysFitInCache) +{ + // We're going to add between 1 and CacheSize keys, so they all fit in the + // cache. + for (Key keys = 1; keys <= CacheSize; ++keys) { + Boxed<TestCache> boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + cache.Add(i, Value{i}); + boxedCache.Check(); + } + + // Fetching any key should never call the value function. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, FetchOrAdd_KeysDoNotFitInCache) +{ + // We're going to add-or-fetch strictly more than CacheSize keys, so they + // cannot fit in the cache, only the last `CacheSize` ones are kept. + for (Key keys = CacheSize + 1; keys <= CacheSize * 2; ++keys) { + Boxed<TestCache> boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i, [&]() { + valueFunctionCalled = true; + return Value{i}; + }); + ASSERT_EQ(v, Value{i}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Fetching keys from 0 should always call the function value: + // - 0 is the oldest key, it must have been pushed out when `CacheSize` + // was added. + // - Once we've fetched 0, it's pushed out the old (smallest) key. + // Etc. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, Add_FetchOrAdd_KeysDoNotFitInCache) +{ + // We're going to add strictly more than CacheSize keys, so they cannot fit in + // the cache, only the last `CacheSize` ones are kept. + for (Key keys = CacheSize + 1; keys <= CacheSize * 2; ++keys) { + Boxed<TestCache> boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + cache.Add(i, Value{i}); + boxedCache.Check(); + } + + // Fetching keys from 0 should always call the function value: + // - 0 is the oldest key, it must have been pushed out when `CacheSize` + // was added. + // - Once we've fetched 0, it's pushed out the old (smallest) key. + // Etc. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, Clear) +{ + Boxed<TestCache> boxedCache; + TestCache& cache = boxedCache.Object(); + + // First fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Second fetch will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + + cache.Clear(); + + // After Clear(), first fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Next fetch will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } +} + +TEST(SmallArrayLRUCache, Shutdown) +{ + Boxed<TestCache> boxedCache; + TestCache& cache = boxedCache.Object(); + + // First fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Second fetch will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + + cache.Shutdown(); + + // After Shutdown(), any fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + cache.Add(42, Value{4242}); + boxedCache.Check(); + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } +} diff --git a/xpcom/tests/gtest/TestSnappyStreams.cpp b/xpcom/tests/gtest/TestSnappyStreams.cpp new file mode 100644 index 0000000000..b7e7e7cf73 --- /dev/null +++ b/xpcom/tests/gtest/TestSnappyStreams.cpp @@ -0,0 +1,162 @@ +/* -*- 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 <algorithm> +#include "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/SnappyCompressOutputStream.h" +#include "mozilla/SnappyUncompressInputStream.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsTArray.h" + +namespace { + +using mozilla::SnappyCompressOutputStream; +using mozilla::SnappyUncompressInputStream; + +static already_AddRefed<nsIOutputStream> CompressPipe( + nsIInputStream** aReaderOut) { + nsCOMPtr<nsIOutputStream> pipeWriter; + NS_NewPipe(aReaderOut, getter_AddRefs(pipeWriter)); + + nsCOMPtr<nsIOutputStream> compress = + new SnappyCompressOutputStream(pipeWriter); + return compress.forget(); +} + +// Verify the given number of bytes compresses to a smaller number of bytes. +static void TestCompress(uint32_t aNumBytes) { + // Don't permit this test on small data sizes as snappy can slightly + // bloat very small content. + ASSERT_GT(aNumBytes, 1024u); + + nsCOMPtr<nsIInputStream> pipeReader; + nsCOMPtr<nsIOutputStream> compress = CompressPipe(getter_AddRefs(pipeReader)); + ASSERT_TRUE(compress); + + nsTArray<char> inputData; + testing::CreateData(aNumBytes, inputData); + + testing::WriteAllAndClose(compress, inputData); + + nsAutoCString outputData; + nsresult rv = NS_ConsumeStream(pipeReader, UINT32_MAX, outputData); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_LT(outputData.Length(), inputData.Length()); +} + +// Verify that the given number of bytes can be compressed and uncompressed +// successfully. +static void TestCompressUncompress(uint32_t aNumBytes) { + nsCOMPtr<nsIInputStream> pipeReader; + nsCOMPtr<nsIOutputStream> compress = CompressPipe(getter_AddRefs(pipeReader)); + ASSERT_TRUE(compress); + + nsCOMPtr<nsIInputStream> uncompress = + new SnappyUncompressInputStream(pipeReader); + + nsTArray<char> inputData; + testing::CreateData(aNumBytes, inputData); + + testing::WriteAllAndClose(compress, inputData); + + nsAutoCString outputData; + nsresult rv = NS_ConsumeStream(uncompress, UINT32_MAX, outputData); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_EQ(inputData.Length(), outputData.Length()); + for (uint32_t i = 0; i < inputData.Length(); ++i) { + EXPECT_EQ(inputData[i], outputData.get()[i]) << "Byte " << i; + } +} + +static void TestUncompressCorrupt(const char* aCorruptData, + uint32_t aCorruptLength) { + nsCOMPtr<nsIInputStream> source; + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(source), mozilla::Span(aCorruptData, aCorruptLength), + NS_ASSIGNMENT_DEPEND); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIInputStream> uncompress = new SnappyUncompressInputStream(source); + + nsAutoCString outputData; + rv = NS_ConsumeStream(uncompress, UINT32_MAX, outputData); + ASSERT_EQ(NS_ERROR_CORRUPTED_CONTENT, rv); +} + +} // namespace + +TEST(SnappyStream, Compress_32k) +{ TestCompress(32 * 1024); } + +TEST(SnappyStream, Compress_64k) +{ TestCompress(64 * 1024); } + +TEST(SnappyStream, Compress_128k) +{ TestCompress(128 * 1024); } + +TEST(SnappyStream, CompressUncompress_0) +{ TestCompressUncompress(0); } + +TEST(SnappyStream, CompressUncompress_1) +{ TestCompressUncompress(1); } + +TEST(SnappyStream, CompressUncompress_32) +{ TestCompressUncompress(32); } + +TEST(SnappyStream, CompressUncompress_1k) +{ TestCompressUncompress(1024); } + +TEST(SnappyStream, CompressUncompress_32k) +{ TestCompressUncompress(32 * 1024); } + +TEST(SnappyStream, CompressUncompress_64k) +{ TestCompressUncompress(64 * 1024); } + +TEST(SnappyStream, CompressUncompress_128k) +{ TestCompressUncompress(128 * 1024); } + +// Test buffers that are not exactly power-of-2 in length to try to +// exercise more edge cases. The number 13 is arbitrary. + +TEST(SnappyStream, CompressUncompress_256k_less_13) +{ TestCompressUncompress((256 * 1024) - 13); } + +TEST(SnappyStream, CompressUncompress_256k) +{ TestCompressUncompress(256 * 1024); } + +TEST(SnappyStream, CompressUncompress_256k_plus_13) +{ TestCompressUncompress((256 * 1024) + 13); } + +TEST(SnappyStream, UncompressCorruptStreamIdentifier) +{ + static const char data[] = "This is not a valid compressed stream"; + TestUncompressCorrupt(data, strlen(data)); +} + +TEST(SnappyStream, UncompressCorruptCompressedDataLength) +{ + static const char data[] = + "\xff\x06\x00\x00sNaPpY" // stream identifier + "\x00\x99\x00\x00This is not a valid compressed stream"; + static const uint32_t dataLength = (sizeof(data) / sizeof(const char)) - 1; + TestUncompressCorrupt(data, dataLength); +} + +TEST(SnappyStream, UncompressCorruptCompressedDataContent) +{ + static const char data[] = + "\xff\x06\x00\x00sNaPpY" // stream identifier + "\x00\x25\x00\x00This is not a valid compressed stream"; + static const uint32_t dataLength = (sizeof(data) / sizeof(const char)) - 1; + TestUncompressCorrupt(data, dataLength); +} diff --git a/xpcom/tests/gtest/TestStateMirroring.cpp b/xpcom/tests/gtest/TestStateMirroring.cpp new file mode 100644 index 0000000000..e96ecb9a18 --- /dev/null +++ b/xpcom/tests/gtest/TestStateMirroring.cpp @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#include "gtest/gtest.h" +#include "mozilla/gtest/WaitFor.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/StateMirroring.h" +#include "mozilla/SynchronizedEventQueue.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsISupportsImpl.h" +#include "nsThreadUtils.h" +#include "VideoUtils.h" + +namespace TestStateMirroring { + +using namespace mozilla; + +class StateMirroringTest : public ::testing::Test { + public: + using ValueType = int; + using Promise = MozPromise<ValueType, bool, /*IsExclusive =*/true>; + + StateMirroringTest() + : mTarget( + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestStateMirroring", + /*aSupportsTailDispatch =*/true)), + mCanonical(AbstractThread::GetCurrent(), 0, "TestCanonical"), + mMirror(mTarget, 0, "TestMirror") {} + + void TearDown() override { + mTarget->BeginShutdown(); + mTarget->AwaitShutdownAndIdle(); + + // Make sure to run any events just dispatched from mTarget to main thread + // before continuing on to the next test. + NS_ProcessPendingEvents(nullptr); + } + + RefPtr<Promise> ReadMirrorAsync() { + return InvokeAsync(mTarget, __func__, [&] { + return Promise::CreateAndResolve(mMirror, "ReadMirrorAsync::Resolve"); + }); + } + + protected: + const RefPtr<TaskQueue> mTarget; + Canonical<int> mCanonical; + Mirror<int> mMirror; +}; + +TEST_F(StateMirroringTest, MirrorInitiatedEventOrdering) { + // Note that this requires the tail dispatcher, which is not available until + // we are processing events. + ASSERT_FALSE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable()); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchAndSpinEventLoopUntilComplete( + "NeedTailDispatcher"_ns, GetCurrentSerialEventTarget(), + NS_NewRunnableFunction(__func__, [&] { + ASSERT_TRUE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable()); + + RefPtr<Promise> mirrorPromise; + + // This will ensure the tail dispatcher fires to dispatch the group + // runnable holding the Mirror::Connect task, before we continue the + // test. + MOZ_ALWAYS_SUCCEEDS(NS_DispatchAndSpinEventLoopUntilComplete( + "NeedTailDispatcher"_ns, GetCurrentSerialEventTarget(), + NS_NewRunnableFunction(__func__, [&] { + // Show that mirroring does not take effect until async init is + // done. + + MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(NS_NewRunnableFunction( + __func__, [&] { mMirror.Connect(&mCanonical); }))); + mCanonical = 1; + mirrorPromise = ReadMirrorAsync(); + }))); + + // Make sure Mirror::Connect has run on mTarget. + mTarget->AwaitIdle(); + + // Make sure Canonical::AddMirror has run on this thread. + NS_ProcessPendingEvents(nullptr); + + // Prior to establishing the connection, a value has not been mirrored. + EXPECT_EQ(WaitFor(mirrorPromise).unwrap(), 0); + + // Once a connection has been establised, event ordering is as expected. + mCanonical = 2; + EXPECT_EQ(WaitFor(ReadMirrorAsync()).unwrap(), 2); + + MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(NS_NewRunnableFunction( + __func__, [&] { mMirror.DisconnectIfConnected(); }))); + }))); +} + +TEST_F(StateMirroringTest, CanonicalInitiatedEventOrdering) { + // Note that this requires the tail dispatcher, which is not available until + // we are processing events. + ASSERT_FALSE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable()); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchAndSpinEventLoopUntilComplete( + "NeedTailDispatcher"_ns, GetCurrentSerialEventTarget(), + NS_NewRunnableFunction(__func__, [&] { + ASSERT_TRUE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable()); + + mCanonical.ConnectMirror(&mMirror); + + // Event ordering is as expected immediately. + mCanonical = 1; + EXPECT_EQ(WaitFor(ReadMirrorAsync()).unwrap(), 1); + + mCanonical.DisconnectAll(); + }))); +} + +} // namespace TestStateMirroring diff --git a/xpcom/tests/gtest/TestStateWatching.cpp b/xpcom/tests/gtest/TestStateWatching.cpp new file mode 100644 index 0000000000..e4b48fb2b3 --- /dev/null +++ b/xpcom/tests/gtest/TestStateWatching.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/StateWatching.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsISupportsImpl.h" +#include "VideoUtils.h" + +namespace TestStateWatching { + +using namespace mozilla; + +struct Foo { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Foo) + void Notify() { mNotified = true; } + bool mNotified = false; + + private: + ~Foo() = default; +}; + +TEST(WatchManager, Shutdown) +{ + RefPtr<TaskQueue> queue = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestWatchManager Shutdown"); + + RefPtr<Foo> p = new Foo; + WatchManager<Foo> manager(p, queue); + Watchable<bool> notifier(false, "notifier"); + + Unused << queue->Dispatch(NS_NewRunnableFunction( + "TestStateWatching::WatchManager_Shutdown_Test::TestBody", [&]() { + manager.Watch(notifier, &Foo::Notify); + notifier = true; // Trigger the call to Foo::Notify(). + manager.Shutdown(); // Shutdown() should cancel the call. + })); + + queue->BeginShutdown(); + queue->AwaitShutdownAndIdle(); + EXPECT_FALSE(p->mNotified); +} + +} // namespace TestStateWatching diff --git a/xpcom/tests/gtest/TestStorageStream.cpp b/xpcom/tests/gtest/TestStorageStream.cpp new file mode 100644 index 0000000000..f92cb986ba --- /dev/null +++ b/xpcom/tests/gtest/TestStorageStream.cpp @@ -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/. */ + +#include <stdlib.h> +#include "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsCOMPtr.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIStorageStream.h" +#include "nsTArray.h" + +namespace { + +void WriteData(nsIOutputStream* aOut, nsTArray<char>& aData, uint32_t aNumBytes, + nsACString& aDataWritten) { + uint32_t n; + nsresult rv = aOut->Write(aData.Elements(), aNumBytes, &n); + EXPECT_NS_SUCCEEDED(rv); + aDataWritten.Append(aData.Elements(), aNumBytes); +} + +} // namespace + +TEST(StorageStreams, Main) +{ + // generate some test data we will write in 4k chunks to the stream + nsTArray<char> kData; + testing::CreateData(4096, kData); + + // track how much data was written so we can compare at the end + nsAutoCString dataWritten; + + nsresult rv; + nsCOMPtr<nsIStorageStream> stor; + + rv = NS_NewStorageStream(kData.Length(), UINT32_MAX, getter_AddRefs(stor)); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIOutputStream> out; + rv = stor->GetOutputStream(0, getter_AddRefs(out)); + EXPECT_NS_SUCCEEDED(rv); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + + rv = out->Close(); + EXPECT_NS_SUCCEEDED(rv); + out = nullptr; + + nsCOMPtr<nsIInputStream> in; + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(in); + ASSERT_TRUE(cloneable != nullptr); + ASSERT_TRUE(cloneable->GetCloneable()); + + nsCOMPtr<nsIInputStream> clone; + rv = cloneable->Clone(getter_AddRefs(clone)); + + testing::ConsumeAndValidateStream(in, dataWritten); + testing::ConsumeAndValidateStream(clone, dataWritten); + in = nullptr; + clone = nullptr; + + // now, write 3 more full 4k segments + 11 bytes, starting at 8192 + // total written equals 20491 bytes + + rv = stor->GetOutputStream(dataWritten.Length(), getter_AddRefs(out)); + EXPECT_NS_SUCCEEDED(rv); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, 11, dataWritten); + + rv = out->Close(); + EXPECT_NS_SUCCEEDED(rv); + out = nullptr; + + // now, read all + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_NS_SUCCEEDED(rv); + + testing::ConsumeAndValidateStream(in, dataWritten); + in = nullptr; +} + +TEST(StorageStreams, EarlyInputStream) +{ + // generate some test data we will write in 4k chunks to the stream + nsTArray<char> kData; + testing::CreateData(4096, kData); + + // track how much data was written so we can compare at the end + nsAutoCString dataWritten; + + nsresult rv; + nsCOMPtr<nsIStorageStream> stor; + + rv = NS_NewStorageStream(kData.Length(), UINT32_MAX, getter_AddRefs(stor)); + EXPECT_NS_SUCCEEDED(rv); + + // Get input stream before writing data into the output stream + nsCOMPtr<nsIInputStream> in; + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_NS_SUCCEEDED(rv); + + // Write data to output stream + nsCOMPtr<nsIOutputStream> out; + rv = stor->GetOutputStream(0, getter_AddRefs(out)); + EXPECT_NS_SUCCEEDED(rv); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + + rv = out->Close(); + EXPECT_NS_SUCCEEDED(rv); + out = nullptr; + + // Should be able to consume input stream + testing::ConsumeAndValidateStream(in, dataWritten); + in = nullptr; +} diff --git a/xpcom/tests/gtest/TestStringStream.cpp b/xpcom/tests/gtest/TestStringStream.cpp new file mode 100644 index 0000000000..8314c3e74c --- /dev/null +++ b/xpcom/tests/gtest/TestStringStream.cpp @@ -0,0 +1,100 @@ +/* -*- 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 "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsICloneableInputStream.h" +#include "nsStringStream.h" +#include "nsTArray.h" +#include "nsIInputStream.h" +#include "nsCOMPtr.h" +#include "nsStreamUtils.h" +#include "mozilla/Span.h" +#include "nsISeekableStream.h" + +namespace { + +static void TestStringStream(uint32_t aNumBytes) { + nsTArray<char> inputData; + testing::CreateData(aNumBytes, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString); + ASSERT_NS_SUCCEEDED(rv); + + testing::ConsumeAndValidateStream(stream, inputString); +} + +static void TestStringStreamClone(uint32_t aNumBytes) { + nsTArray<char> inputData; + testing::CreateData(aNumBytes, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable != nullptr); + ASSERT_TRUE(cloneable->GetCloneable()); + + nsCOMPtr<nsIInputStream> clone; + rv = cloneable->Clone(getter_AddRefs(clone)); + + testing::ConsumeAndValidateStream(stream, inputString); + + // Release the stream to verify that the clone's string survives correctly. + stream = nullptr; + + testing::ConsumeAndValidateStream(clone, inputString); +} + +} // namespace + +TEST(StringStream, Simple_4k) +{ TestStringStream(1024 * 4); } + +TEST(StringStream, Clone_4k) +{ TestStringStreamClone(1024 * 4); } + +static nsresult CloseStreamThenRead(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + // Closing the stream will free the data + nsresult rv = aInStr->Close(); + if (NS_FAILED(rv)) { + return rv; + } + // This will likely be allocated in the same slot as what we have in aBuffer + char* newAlloc = moz_xstrdup("abcd"); + + char* toBuf = static_cast<char*>(aClosure); + memcpy(&toBuf[aOffset], aBuffer, aCount); + *aCountWritten = aCount; + free(newAlloc); + return NS_OK; +} + +TEST(StringStream, CancelInReadSegments) +{ + char* buffer = moz_xstrdup("test"); + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(stream), mozilla::Span(buffer, 5), NS_ASSIGNMENT_ADOPT); + ASSERT_NS_SUCCEEDED(rv); + + char buf[100]; + uint32_t count = 0; + uint64_t available = 0; + rv = stream->Available(&available); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->ReadSegments(CloseStreamThenRead, buf, available, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(count == 5); + ASSERT_TRUE(!strcmp(buf, "test")); +} diff --git a/xpcom/tests/gtest/TestStrings.cpp b/xpcom/tests/gtest/TestStrings.cpp new file mode 100644 index 0000000000..7e0f986d29 --- /dev/null +++ b/xpcom/tests/gtest/TestStrings.cpp @@ -0,0 +1,2801 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> +#include "nsASCIIMask.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsStringBuffer.h" +#include "nsReadableUtils.h" +#include "nsCRTGlue.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/Utf8.h" +#include "nsTArray.h" +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH +#include "gtest/BlackBox.h" +#include "nsBidiUtils.h" +#include "js/String.h" + +#define CONVERSION_ITERATIONS 50000 + +#define CONVERSION_BENCH(name, func, src, dstType) \ + MOZ_GTEST_BENCH_F(Strings, name, [this] { \ + for (int i = 0; i < CONVERSION_ITERATIONS; i++) { \ + dstType dst; \ + func(*BlackBox(&src), *BlackBox(&dst)); \ + } \ + }); + +// Disable the C++ 2a warning. See bug #1509926 +#if defined(__clang__) && (__clang_major__ >= 6) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wc++2a-compat" +#endif + +namespace TestStrings { + +using mozilla::BlackBox; +using mozilla::fallible; +using mozilla::IsAscii; +using mozilla::IsUtf8; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; +using mozilla::Span; + +#define TestExample1 \ + "Sed ut perspiciatis unde omnis iste natus error sit voluptatem " \ + "accusantium doloremque laudantium,\n totam rem aperiam, eaque ipsa quae " \ + "ab illo inventore veritatis et quasi\r architecto beatae vitae dicta sunt " \ + "explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur\n aut " \ + "odit aut fugit, sed quia consequuntur magni dolores eos qui ratione " \ + "voluptatem sequi nesciunt. Neque porro quisquam est, qui\r\n\r dolorem " \ + "ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non " \ + "numquam eius modi tempora incidunt ut labore et dolore magnam aliquam " \ + "quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem " \ + "ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi " \ + "consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate " \ + "velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum " \ + "fugiat quo voluptas nulla pariatur?" + +#define TestExample2 \ + "At vero eos et accusamus et iusto odio dignissimos ducimus\n\n qui " \ + "blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et " \ + "quas molestias excepturi sint occaecati cupiditate non provident, " \ + "similique sunt in culpa qui officia deserunt\r\r \n mollitia animi, id " \ + "est laborum et dolorum fuga. Et harum quidem rerum facilis est et " \ + "expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi " \ + "optio cumque nihil impedit quo minus id quod maxime placeat facere " \ + "possimus, omnis voluptas assumenda est, omnis dolor repellendus. " \ + "Temporibus autem quibusdam et aut officiis debitis aut rerum " \ + "necessitatibus saepe eveniet ut et voluptates repudiandae sint et " \ + "molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente " \ + "delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut " \ + "perferendis doloribus asperiores repellat." + +#define TestExample3 \ + " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac tellus " \ + "eget velit viverra viverra id sit amet neque. Sed id consectetur mi, " \ + "vestibulum aliquet arcu. Curabitur sagittis accumsan convallis. Sed eu " \ + "condimentum ipsum, a laoreet tortor. Orci varius natoque penatibus et " \ + "magnis dis \r\r\n\n parturient montes, nascetur ridiculus mus. Sed non " \ + "tellus nec ante sodales placerat a nec risus. Cras vel bibendum sapien, " \ + "nec ullamcorper felis. Pellentesque congue eget nisi sit amet vehicula. " \ + "Morbi pulvinar turpis justo, in commodo dolor vulputate id. Curabitur in " \ + "dui urna. Vestibulum placerat dui in sem congue, ut faucibus nibh rutrum. " \ + "Duis mattis turpis facilisis ullamcorper tincidunt. Vestibulum pharetra " \ + "tortor at enim sagittis, dapibus consectetur ex blandit. Curabitur ac " \ + "fringilla quam. In ornare lectus ut ipsum mattis venenatis. Etiam in " \ + "mollis lectus, sed luctus risus.\nCras dapibus\f\t \n finibus justo sit " \ + "amet dictum. Aliquam non elit diam. Fusce magna nulla, bibendum in massa " \ + "a, commodo finibus lectus. Sed rutrum a augue id imperdiet. Aliquam " \ + "sagittis sodales felis, a tristique ligula. Aliquam erat volutpat. " \ + "Pellentesque habitant morbi tristique senectus et netus et malesuada " \ + "fames ac turpis egestas. Duis volutpat interdum lorem et congue. " \ + "Phasellus porttitor posuere justo eget euismod. Nam a condimentum turpis, " \ + "sit amet gravida lacus. Vestibulum dolor diam, lobortis ac metus et, " \ + "convallis dapibus tellus. Ut nec metus in velit malesuada tincidunt et " \ + "eget justo. Curabitur ut libero bibendum, porttitor diam vitae, aliquet " \ + "justo. " + +#define TestExample4 \ + " Donec feugiat volutpat massa. Cras ornare lacinia porta. Fusce in " \ + "feugiat nunc. Praesent non felis varius diam feugiat ultrices ultricies a " \ + "risus. Donec maximus nisi nisl, non consectetur nulla eleifend in. Nulla " \ + "in massa interdum, eleifend orci a, vestibulum est. Mauris aliquet, massa " \ + "et convallis mollis, felis augue vestibulum augue, in lobortis metus eros " \ + "a quam. Nam ac diam ornare, vestibulum elit sit amet, " \ + "consectetur ante. Praesent massa mauris, pulvinar sit amet sapien vel, " \ + "tempus gravida neque. Praesent id quam sit amet est maximus molestie eget " \ + "at turpis. Nunc sit amet orci id arcu dapibus fermentum non eu " \ + "erat.\f\tSuspendisse commodo nunc sem, eu congue eros condimentum vel. " \ + "Nullam sit amet posuere arcu. Nulla facilisi. Mauris dapibus iaculis " \ + "massa sed gravida. Nullam vitae urna at tortor feugiat auctor ut sit amet " \ + "dolor. Proin rutrum at nunc et faucibus. Quisque suscipit id nibh a " \ + "aliquet. Pellentesque habitant morbi tristique senectus et netus et " \ + "malesuada fames ac turpis egestas. Aliquam a dapibus erat, id imperdiet " \ + "mauris. Nulla blandit libero non magna dapibus tristique. Integer " \ + "hendrerit imperdiet lorem, quis facilisis lacus semper ut. Vestibulum " \ + "ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia " \ + "Curae Nullam dignissim elit in congue ultricies. Quisque erat odio, " \ + "maximus mollis laoreet id, iaculis at turpis. " + +#define TestExample5 \ + "Donec id risus urna. Nunc consequat lacinia urna id bibendum. Nulla " \ + "faucibus faucibus enim. Cras ex risus, ultrices id semper vitae, luctus " \ + "ut nulla. Sed vehicula tellus sed purus imperdiet efficitur. Suspendisse " \ + "feugiat\n\n\n imperdiet odio, sed porta lorem feugiat nec. Curabitur " \ + "laoreet massa venenatis\r\n risus ornare\r\n, vitae feugiat tortor " \ + "accumsan. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " \ + "Maecenas id scelerisque mauris, eget facilisis erat. Ut nec pulvinar " \ + "risus, sed iaculis ante. Mauris tincidunt, risus et pretium elementum, " \ + "leo nisi consectetur ligula, tincidunt suscipit erat velit eget libero. " \ + "Sed ac est tempus, consequat dolor mattis, mattis mi. " + +// Originally ReadVPXFile in TestVPXDecoding.cpp +static void ReadFile(const char* aPath, nsACString& aBuffer) { + FILE* f = fopen(aPath, "rb"); + ASSERT_NE(f, (FILE*)nullptr); + + int r = fseek(f, 0, SEEK_END); + ASSERT_EQ(r, 0); + + long size = ftell(f); + ASSERT_NE(size, -1); + aBuffer.SetLength(size); + + r = fseek(f, 0, SEEK_SET); + ASSERT_EQ(r, 0); + + size_t got = fread(aBuffer.BeginWriting(), 1, size, f); + ASSERT_EQ(got, size_t(size)); + + r = fclose(f); + ASSERT_EQ(r, 0); +} + +class Strings : public ::testing::Test { + protected: + void SetUp() override { + // Intentionally AssignASCII and not AssignLiteral + // to simulate the usual heap case. + mExample1Utf8.AssignASCII(TestExample1); + mExample2Utf8.AssignASCII(TestExample2); + mExample3Utf8.AssignASCII(TestExample3); + mExample4Utf8.AssignASCII(TestExample4); + mExample5Utf8.AssignASCII(TestExample5); + + // Use span to make the resulting string as ordinary as possible + mAsciiOneUtf8.Append(Span(mExample3Utf8).To(1)); + mAsciiThreeUtf8.Append(Span(mExample3Utf8).To(3)); + mAsciiFifteenUtf8.Append(Span(mExample3Utf8).To(15)); + mAsciiHundredUtf8.Append(Span(mExample3Utf8).To(100)); + mAsciiThousandUtf8.Append(Span(mExample3Utf8).To(1000)); + + ReadFile("ar.txt", mArUtf8); + ReadFile("de.txt", mDeUtf8); + ReadFile("de-edit.txt", mDeEditUtf8); + ReadFile("ru.txt", mRuUtf8); + ReadFile("th.txt", mThUtf8); + ReadFile("ko.txt", mKoUtf8); + ReadFile("ja.txt", mJaUtf8); + ReadFile("tr.txt", mTrUtf8); + ReadFile("vi.txt", mViUtf8); + + CopyASCIItoUTF16(mExample1Utf8, mExample1Utf16); + CopyASCIItoUTF16(mExample2Utf8, mExample2Utf16); + CopyASCIItoUTF16(mExample3Utf8, mExample3Utf16); + CopyASCIItoUTF16(mExample4Utf8, mExample4Utf16); + CopyASCIItoUTF16(mExample5Utf8, mExample5Utf16); + + CopyASCIItoUTF16(mAsciiOneUtf8, mAsciiOneUtf16); + CopyASCIItoUTF16(mAsciiFifteenUtf8, mAsciiFifteenUtf16); + CopyASCIItoUTF16(mAsciiHundredUtf8, mAsciiHundredUtf16); + CopyASCIItoUTF16(mAsciiThousandUtf8, mAsciiThousandUtf16); + + CopyUTF8toUTF16(mArUtf8, mArUtf16); + CopyUTF8toUTF16(mDeUtf8, mDeUtf16); + CopyUTF8toUTF16(mDeEditUtf8, mDeEditUtf16); + CopyUTF8toUTF16(mRuUtf8, mRuUtf16); + CopyUTF8toUTF16(mThUtf8, mThUtf16); + CopyUTF8toUTF16(mJaUtf8, mJaUtf16); + CopyUTF8toUTF16(mKoUtf8, mKoUtf16); + CopyUTF8toUTF16(mTrUtf8, mTrUtf16); + CopyUTF8toUTF16(mViUtf8, mViUtf16); + + LossyCopyUTF16toASCII(mDeEditUtf16, mDeEditLatin1); + + // Use span to make the resulting string as ordinary as possible + mArOneUtf16.Append(Span(mArUtf16).To(1)); + mDeOneUtf16.Append(Span(mDeUtf16).To(1)); + mDeEditOneUtf16.Append(Span(mDeEditUtf16).To(1)); + mRuOneUtf16.Append(Span(mRuUtf16).To(1)); + mThOneUtf16.Append(Span(mThUtf16).To(1)); + mJaOneUtf16.Append(Span(mJaUtf16).To(1)); + mKoOneUtf16.Append(Span(mKoUtf16).To(1)); + mTrOneUtf16.Append(Span(mTrUtf16).To(1)); + mViOneUtf16.Append(Span(mViUtf16).To(1)); + + mDeEditOneLatin1.Append(Span(mDeEditLatin1).To(1)); + + mArThreeUtf16.Append(Span(mArUtf16).To(3)); + mDeThreeUtf16.Append(Span(mDeUtf16).To(3)); + mDeEditThreeUtf16.Append(Span(mDeEditUtf16).To(3)); + mRuThreeUtf16.Append(Span(mRuUtf16).To(3)); + mThThreeUtf16.Append(Span(mThUtf16).To(3)); + mJaThreeUtf16.Append(Span(mJaUtf16).To(3)); + mKoThreeUtf16.Append(Span(mKoUtf16).To(3)); + mTrThreeUtf16.Append(Span(mTrUtf16).To(3)); + mViThreeUtf16.Append(Span(mViUtf16).To(3)); + + mDeEditThreeLatin1.Append(Span(mDeEditLatin1).To(3)); + + mArFifteenUtf16.Append(Span(mArUtf16).To(15)); + mDeFifteenUtf16.Append(Span(mDeUtf16).To(15)); + mDeEditFifteenUtf16.Append(Span(mDeEditUtf16).To(15)); + mRuFifteenUtf16.Append(Span(mRuUtf16).To(15)); + mThFifteenUtf16.Append(Span(mThUtf16).To(15)); + mJaFifteenUtf16.Append(Span(mJaUtf16).To(15)); + mKoFifteenUtf16.Append(Span(mKoUtf16).To(15)); + mTrFifteenUtf16.Append(Span(mTrUtf16).To(15)); + mViFifteenUtf16.Append(Span(mViUtf16).To(15)); + + mDeEditFifteenLatin1.Append(Span(mDeEditLatin1).To(15)); + + mArHundredUtf16.Append(Span(mArUtf16).To(100)); + mDeHundredUtf16.Append(Span(mDeUtf16).To(100)); + mDeEditHundredUtf16.Append(Span(mDeEditUtf16).To(100)); + mRuHundredUtf16.Append(Span(mRuUtf16).To(100)); + mThHundredUtf16.Append(Span(mThUtf16).To(100)); + mJaHundredUtf16.Append(Span(mJaUtf16).To(100)); + mKoHundredUtf16.Append(Span(mKoUtf16).To(100)); + mTrHundredUtf16.Append(Span(mTrUtf16).To(100)); + mViHundredUtf16.Append(Span(mViUtf16).To(100)); + + mDeEditHundredLatin1.Append(Span(mDeEditLatin1).To(100)); + + mArThousandUtf16.Append(Span(mArUtf16).To(1000)); + mDeThousandUtf16.Append(Span(mDeUtf16).To(1000)); + mDeEditThousandUtf16.Append(Span(mDeEditUtf16).To(1000)); + mRuThousandUtf16.Append(Span(mRuUtf16).To(1000)); + mThThousandUtf16.Append(Span(mThUtf16).To(1000)); + mJaThousandUtf16.Append(Span(mJaUtf16).To(1000)); + mKoThousandUtf16.Append(Span(mKoUtf16).To(1000)); + mTrThousandUtf16.Append(Span(mTrUtf16).To(1000)); + mViThousandUtf16.Append(Span(mViUtf16).To(1000)); + + mDeEditThousandLatin1.Append(Span(mDeEditLatin1).To(1000)); + + CopyUTF16toUTF8(mArOneUtf16, mArOneUtf8); + CopyUTF16toUTF8(mDeOneUtf16, mDeOneUtf8); + CopyUTF16toUTF8(mDeEditOneUtf16, mDeEditOneUtf8); + CopyUTF16toUTF8(mRuOneUtf16, mRuOneUtf8); + CopyUTF16toUTF8(mThOneUtf16, mThOneUtf8); + CopyUTF16toUTF8(mJaOneUtf16, mJaOneUtf8); + CopyUTF16toUTF8(mKoOneUtf16, mKoOneUtf8); + CopyUTF16toUTF8(mTrOneUtf16, mTrOneUtf8); + CopyUTF16toUTF8(mViOneUtf16, mViOneUtf8); + + CopyUTF16toUTF8(mArThreeUtf16, mArThreeUtf8); + CopyUTF16toUTF8(mDeThreeUtf16, mDeThreeUtf8); + CopyUTF16toUTF8(mDeEditThreeUtf16, mDeEditThreeUtf8); + CopyUTF16toUTF8(mRuThreeUtf16, mRuThreeUtf8); + CopyUTF16toUTF8(mThThreeUtf16, mThThreeUtf8); + CopyUTF16toUTF8(mJaThreeUtf16, mJaThreeUtf8); + CopyUTF16toUTF8(mKoThreeUtf16, mKoThreeUtf8); + CopyUTF16toUTF8(mTrThreeUtf16, mTrThreeUtf8); + CopyUTF16toUTF8(mViThreeUtf16, mViThreeUtf8); + + CopyUTF16toUTF8(mArFifteenUtf16, mArFifteenUtf8); + CopyUTF16toUTF8(mDeFifteenUtf16, mDeFifteenUtf8); + CopyUTF16toUTF8(mDeEditFifteenUtf16, mDeEditFifteenUtf8); + CopyUTF16toUTF8(mRuFifteenUtf16, mRuFifteenUtf8); + CopyUTF16toUTF8(mThFifteenUtf16, mThFifteenUtf8); + CopyUTF16toUTF8(mJaFifteenUtf16, mJaFifteenUtf8); + CopyUTF16toUTF8(mKoFifteenUtf16, mKoFifteenUtf8); + CopyUTF16toUTF8(mTrFifteenUtf16, mTrFifteenUtf8); + CopyUTF16toUTF8(mViFifteenUtf16, mViFifteenUtf8); + + CopyUTF16toUTF8(mArHundredUtf16, mArHundredUtf8); + CopyUTF16toUTF8(mDeHundredUtf16, mDeHundredUtf8); + CopyUTF16toUTF8(mDeEditHundredUtf16, mDeEditHundredUtf8); + CopyUTF16toUTF8(mRuHundredUtf16, mRuHundredUtf8); + CopyUTF16toUTF8(mThHundredUtf16, mThHundredUtf8); + CopyUTF16toUTF8(mJaHundredUtf16, mJaHundredUtf8); + CopyUTF16toUTF8(mKoHundredUtf16, mKoHundredUtf8); + CopyUTF16toUTF8(mTrHundredUtf16, mTrHundredUtf8); + CopyUTF16toUTF8(mViHundredUtf16, mViHundredUtf8); + + CopyUTF16toUTF8(mArThousandUtf16, mArThousandUtf8); + CopyUTF16toUTF8(mDeThousandUtf16, mDeThousandUtf8); + CopyUTF16toUTF8(mDeEditThousandUtf16, mDeEditThousandUtf8); + CopyUTF16toUTF8(mRuThousandUtf16, mRuThousandUtf8); + CopyUTF16toUTF8(mThThousandUtf16, mThThousandUtf8); + CopyUTF16toUTF8(mJaThousandUtf16, mJaThousandUtf8); + CopyUTF16toUTF8(mKoThousandUtf16, mKoThousandUtf8); + CopyUTF16toUTF8(mTrThousandUtf16, mTrThousandUtf8); + CopyUTF16toUTF8(mViThousandUtf16, mViThousandUtf8); + } + + public: + nsCString mAsciiOneUtf8; + nsCString mAsciiThreeUtf8; + nsCString mAsciiFifteenUtf8; + nsCString mAsciiHundredUtf8; + nsCString mAsciiThousandUtf8; + nsCString mExample1Utf8; + nsCString mExample2Utf8; + nsCString mExample3Utf8; + nsCString mExample4Utf8; + nsCString mExample5Utf8; + nsCString mArUtf8; + nsCString mDeUtf8; + nsCString mDeEditUtf8; + nsCString mRuUtf8; + nsCString mThUtf8; + nsCString mJaUtf8; + nsCString mKoUtf8; + nsCString mTrUtf8; + nsCString mViUtf8; + + nsString mAsciiOneUtf16; + nsString mAsciiThreeUtf16; + nsString mAsciiFifteenUtf16; + nsString mAsciiHundredUtf16; + nsString mAsciiThousandUtf16; + nsString mExample1Utf16; + nsString mExample2Utf16; + nsString mExample3Utf16; + nsString mExample4Utf16; + nsString mExample5Utf16; + nsString mArUtf16; + nsString mDeUtf16; + nsString mDeEditUtf16; + nsString mRuUtf16; + nsString mThUtf16; + nsString mJaUtf16; + nsString mKoUtf16; + nsString mTrUtf16; + nsString mViUtf16; + + nsCString mDeEditLatin1; + + nsString mArOneUtf16; + nsString mDeOneUtf16; + nsString mDeEditOneUtf16; + nsString mRuOneUtf16; + nsString mThOneUtf16; + nsString mJaOneUtf16; + nsString mKoOneUtf16; + nsString mTrOneUtf16; + nsString mViOneUtf16; + + nsCString mDeEditOneLatin1; + + nsCString mArOneUtf8; + nsCString mDeOneUtf8; + nsCString mDeEditOneUtf8; + nsCString mRuOneUtf8; + nsCString mThOneUtf8; + nsCString mJaOneUtf8; + nsCString mKoOneUtf8; + nsCString mTrOneUtf8; + nsCString mViOneUtf8; + + nsString mArThreeUtf16; + nsString mDeThreeUtf16; + nsString mDeEditThreeUtf16; + nsString mRuThreeUtf16; + nsString mThThreeUtf16; + nsString mJaThreeUtf16; + nsString mKoThreeUtf16; + nsString mTrThreeUtf16; + nsString mViThreeUtf16; + + nsCString mDeEditThreeLatin1; + + nsCString mArThreeUtf8; + nsCString mDeThreeUtf8; + nsCString mDeEditThreeUtf8; + nsCString mRuThreeUtf8; + nsCString mThThreeUtf8; + nsCString mJaThreeUtf8; + nsCString mKoThreeUtf8; + nsCString mTrThreeUtf8; + nsCString mViThreeUtf8; + + nsString mArFifteenUtf16; + nsString mDeFifteenUtf16; + nsString mDeEditFifteenUtf16; + nsString mRuFifteenUtf16; + nsString mThFifteenUtf16; + nsString mJaFifteenUtf16; + nsString mKoFifteenUtf16; + nsString mTrFifteenUtf16; + nsString mViFifteenUtf16; + + nsCString mDeEditFifteenLatin1; + + nsCString mArFifteenUtf8; + nsCString mDeFifteenUtf8; + nsCString mDeEditFifteenUtf8; + nsCString mRuFifteenUtf8; + nsCString mThFifteenUtf8; + nsCString mJaFifteenUtf8; + nsCString mKoFifteenUtf8; + nsCString mTrFifteenUtf8; + nsCString mViFifteenUtf8; + + nsString mArHundredUtf16; + nsString mDeHundredUtf16; + nsString mDeEditHundredUtf16; + nsString mRuHundredUtf16; + nsString mThHundredUtf16; + nsString mJaHundredUtf16; + nsString mKoHundredUtf16; + nsString mTrHundredUtf16; + nsString mViHundredUtf16; + + nsCString mDeEditHundredLatin1; + + nsCString mArHundredUtf8; + nsCString mDeHundredUtf8; + nsCString mDeEditHundredUtf8; + nsCString mRuHundredUtf8; + nsCString mThHundredUtf8; + nsCString mJaHundredUtf8; + nsCString mKoHundredUtf8; + nsCString mTrHundredUtf8; + nsCString mViHundredUtf8; + + nsString mArThousandUtf16; + nsString mDeThousandUtf16; + nsString mDeEditThousandUtf16; + nsString mRuThousandUtf16; + nsString mThThousandUtf16; + nsString mJaThousandUtf16; + nsString mKoThousandUtf16; + nsString mTrThousandUtf16; + nsString mViThousandUtf16; + + nsCString mDeEditThousandLatin1; + + nsCString mArThousandUtf8; + nsCString mDeThousandUtf8; + nsCString mDeEditThousandUtf8; + nsCString mRuThousandUtf8; + nsCString mThThousandUtf8; + nsCString mJaThousandUtf8; + nsCString mKoThousandUtf8; + nsCString mTrThousandUtf8; + nsCString mViThousandUtf8; +}; + +static void test_assign_helper(const nsACString& in, nsACString& _retval) { + _retval = in; +} + +// Simple helper struct to test if conditionally enabled string functions are +// working. +template <typename T> +struct EnableTest { + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + bool IsChar16() { + return true; + } + + template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>> + bool IsChar16(int dummy = 42) { + return false; + } + + template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> + bool IsChar() { + return false; + } + + template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>> + bool IsChar(int dummy = 42) { + return true; + } +}; + +TEST_F(Strings, IsChar) { + EnableTest<char> charTest; + EXPECT_TRUE(charTest.IsChar()); + EXPECT_FALSE(charTest.IsChar16()); + + EnableTest<char16_t> char16Test; + EXPECT_TRUE(char16Test.IsChar16()); + EXPECT_FALSE(char16Test.IsChar()); + +#ifdef COMPILATION_FAILURE_TEST + nsAutoCString a_ctest; + nsAutoString a_test; + + a_ctest.AssignLiteral("hello"); + // This should cause a compilation failure. + a_ctest.AssignLiteral(u"hello"); + a_test.AssignLiteral(u"hello"); + a_test.AssignLiteral("hello"); +#endif +} + +TEST_F(Strings, DependentStrings) { + // A few tests that make sure copying nsTDependentStrings behaves properly. + using DataFlags = mozilla::detail::StringDataFlags; + + { + // Test copy ctor. + nsDependentCString tmp("foo"); + auto data = tmp.Data(); + nsDependentCString foo(tmp); + // Neither string should be using a shared buffer. + EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED); + EXPECT_FALSE(foo.GetDataFlags() & DataFlags::REFCOUNTED); + // Both strings should be pointing to the original buffer. + EXPECT_EQ(data, tmp.Data()); + EXPECT_EQ(data, foo.Data()); + } + { + // Test move ctor. + nsDependentCString tmp("foo"); + auto data = tmp.Data(); + nsDependentCString foo(std::move(tmp)); + // Neither string should be using a shared buffer. + EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED); + EXPECT_FALSE(foo.GetDataFlags() & DataFlags::REFCOUNTED); + // First string should be reset, the second should be pointing to the + // original buffer. + EXPECT_NE(data, tmp.Data()); + EXPECT_EQ(data, foo.Data()); + EXPECT_TRUE(tmp.IsEmpty()); + } + { + // Test copying to a nsCString. + nsDependentCString tmp("foo"); + auto data = tmp.Data(); + nsCString foo(tmp); + // Original string should not be shared, copy should be shared. + EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED); + EXPECT_TRUE(foo.GetDataFlags() & DataFlags::REFCOUNTED); + // First string should remain the same, the second should be pointing to + // a new buffer. + EXPECT_EQ(data, tmp.Data()); + EXPECT_NE(data, foo.Data()); + } +} + +TEST_F(Strings, assign) { + nsCString result; + test_assign_helper("a"_ns + "b"_ns, result); + EXPECT_STREQ(result.get(), "ab"); +} + +TEST_F(Strings, assign_c) { + nsCString c; + c.Assign('c'); + EXPECT_STREQ(c.get(), "c"); +} + +TEST_F(Strings, test1) { + constexpr auto empty = u""_ns; + const nsAString& aStr = empty; + + nsAutoString buf(aStr); + + int32_t n = buf.FindChar(','); + EXPECT_EQ(n, kNotFound); + + n = buf.Length(); + + buf.Cut(0, n + 1); + n = buf.FindChar(','); + + EXPECT_EQ(n, kNotFound); +} + +TEST_F(Strings, test2) { + nsCString data("hello world"); + const nsACString& aStr = data; + + nsCString temp(aStr); + temp.Cut(0, 6); + + EXPECT_STREQ(temp.get(), "world"); +} + +TEST_F(Strings, find) { + nsCString src("<!DOCTYPE blah blah blah>"); + + int32_t i = src.Find("DOCTYPE", 2); + EXPECT_EQ(i, 2); + + i = src.Find("DOCTYPE"); + EXPECT_EQ(i, 2); +} + +TEST_F(Strings, lower_case_find) { + nsCString src("<!DOCTYPE blah blah blah>"); + + int32_t i = src.LowerCaseFindASCII("doctype", 2); + EXPECT_EQ(i, 2); + + i = src.LowerCaseFindASCII("doctype"); + EXPECT_EQ(i, 2); +} + +TEST_F(Strings, rfind) { + const char text[] = "<!DOCTYPE blah bLaH bLaH>"; + nsCString src(text); + int32_t i; + + i = src.RFind("bLaH"); + EXPECT_EQ(i, 20); + + i = src.RFind("blah"); + EXPECT_EQ(i, 10); + + i = src.RFind("BLAH"); + EXPECT_EQ(i, kNotFound); +} + +TEST_F(Strings, rfind_2) { + const char text[] = "<!DOCTYPE blah blah blah>"; + nsCString src(text); + int32_t i = src.RFind("TYPE"); + EXPECT_EQ(i, 5); +} + +TEST_F(Strings, rfind_3) { + const char text[] = "urn:mozilla:locale:en-US:necko"; + nsAutoCString value(text); + int32_t i = value.RFind(":"); + EXPECT_EQ(i, 24); +} + +TEST_F(Strings, rfind_4) { + nsCString value("a.msf"); + int32_t i = value.RFind(".msf"); + EXPECT_EQ(i, 1); +} + +TEST_F(Strings, findinreadable) { + const char text[] = + "jar:jar:file:///c:/software/mozilla/mozilla_2006_02_21.jar!/browser/" + "chrome/classic.jar!/"; + nsAutoCString value(text); + + nsACString::const_iterator begin, end; + value.BeginReading(begin); + value.EndReading(end); + nsACString::const_iterator delim_begin(begin), delim_end(end); + + // Search for last !/ at the end of the string + EXPECT_TRUE(FindInReadable("!/"_ns, delim_begin, delim_end)); + char* r = ToNewCString(Substring(delim_begin, delim_end)); + // Should match the first "!/" but not the last + EXPECT_NE(delim_end, end); + EXPECT_STREQ(r, "!/"); + free(r); + + delim_begin = begin; + delim_end = end; + + // Search for first jar: + EXPECT_TRUE(FindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_EQ(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Search for jar: in a Substring + delim_begin = begin; + delim_begin++; + delim_end = end; + EXPECT_TRUE(FindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_NE(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Should not find a match + EXPECT_FALSE(FindInReadable("gecko"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not beyond Substring) + delim_begin = begin; + for (int i = 0; i < 6; i++) delim_begin++; + delim_end = end; + EXPECT_FALSE(FindInReadable("jar:"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not beyond Substring) + delim_begin = begin; + delim_end = end; + for (int i = 0; i < 7; i++) delim_end--; + EXPECT_FALSE(FindInReadable("classic"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); +} + +TEST_F(Strings, rfindinreadable) { + const char text[] = + "jar:jar:file:///c:/software/mozilla/mozilla_2006_02_21.jar!/browser/" + "chrome/classic.jar!/"; + nsAutoCString value(text); + + nsACString::const_iterator begin, end; + value.BeginReading(begin); + value.EndReading(end); + nsACString::const_iterator delim_begin(begin), delim_end(end); + + // Search for last !/ at the end of the string + EXPECT_TRUE(RFindInReadable("!/"_ns, delim_begin, delim_end)); + char* r = ToNewCString(Substring(delim_begin, delim_end)); + // Should match the last "!/" + EXPECT_EQ(delim_end, end); + EXPECT_STREQ(r, "!/"); + free(r); + + delim_begin = begin; + delim_end = end; + + // Search for last jar: but not the first one... + EXPECT_TRUE(RFindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_NE(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Search for jar: in a Substring + delim_begin = begin; + delim_end = begin; + for (int i = 0; i < 6; i++) delim_end++; + EXPECT_TRUE(RFindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_EQ(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Should not find a match + delim_begin = begin; + delim_end = end; + EXPECT_FALSE(RFindInReadable("gecko"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not before Substring) + delim_begin = begin; + for (int i = 0; i < 6; i++) delim_begin++; + delim_end = end; + EXPECT_FALSE(RFindInReadable("jar:"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not beyond Substring) + delim_begin = begin; + delim_end = end; + for (int i = 0; i < 7; i++) delim_end--; + EXPECT_FALSE(RFindInReadable("classic"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); +} + +TEST_F(Strings, distance) { + const char text[] = "abc-xyz"; + nsCString s(text); + nsCString::const_iterator begin, end; + s.BeginReading(begin); + s.EndReading(end); + size_t d = Distance(begin, end); + EXPECT_EQ(d, sizeof(text) - 1); +} + +TEST_F(Strings, length) { + const char text[] = "abc-xyz"; + nsCString s(text); + size_t d = s.Length(); + EXPECT_EQ(d, sizeof(text) - 1); +} + +TEST_F(Strings, trim) { + const char text[] = " a\t $ "; + const char set[] = " \t$"; + + nsCString s(text); + s.Trim(set); + EXPECT_STREQ(s.get(), "a"); + + s.AssignLiteral("\t \t\t \t"); + s.Trim(set); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral(" "); + s.Trim(set); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral(" "); + s.Trim(set, false, true); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral(" "); + s.Trim(set, true, false); + EXPECT_STREQ(s.get(), ""); +} + +TEST_F(Strings, replace_substr) { + const char text[] = "abc-ppp-qqq-ppp-xyz"; + nsCString s(text); + s.ReplaceSubstring("ppp", "www"); + EXPECT_STREQ(s.get(), "abc-www-qqq-www-xyz"); + + s.AssignLiteral("foobar"); + s.ReplaceSubstring("foo", "bar"); + s.ReplaceSubstring("bar", ""); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral("foofoofoo"); + s.ReplaceSubstring("foo", "foo"); + EXPECT_STREQ(s.get(), "foofoofoo"); + + s.AssignLiteral("foofoofoo"); + s.ReplaceSubstring("of", "fo"); + EXPECT_STREQ(s.get(), "fofoofooo"); +} + +TEST_F(Strings, replace_substr_2) { + const char* newName = "user"; + nsString acctName; + acctName.AssignLiteral("forums.foo.com"); + nsAutoString newAcctName, oldVal, newVal; + CopyASCIItoUTF16(mozilla::MakeStringSpan(newName), newVal); + newAcctName.Assign(acctName); + + // here, oldVal is empty. we are testing that this function + // does not hang. see bug 235355. + newAcctName.ReplaceSubstring(oldVal, newVal); + + // we expect that newAcctName will be unchanged. + EXPECT_TRUE(newAcctName.Equals(acctName)); +} + +TEST_F(Strings, replace_substr_3) { + nsCString s; + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "X"); + EXPECT_STREQ(s.get(), "abXbXbc"); + + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "XYZ"); + EXPECT_STREQ(s.get(), "abXYZbXYZbc"); + + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "XY"); + EXPECT_STREQ(s.get(), "abXYbXYbc"); + + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "XYZ!"); + EXPECT_STREQ(s.get(), "abXYZ!bXYZ!bc"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "X"); + EXPECT_STREQ(s.get(), "aXaXaX"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ!"); + EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XY"); + EXPECT_STREQ(s.get(), "aXYaXYaXY"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZABC"); + EXPECT_STREQ(s.get(), "aXYZABCaXYZABCaXYZABC"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ"); + EXPECT_STREQ(s.get(), "aXYZaXYZaXYZ"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ!"); + EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "X"); + EXPECT_STREQ(s.get(), "XcdXcdXcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XYZABC"); + EXPECT_STREQ(s.get(), "XYZABCcdXYZABCcdXYZABCcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XY"); + EXPECT_STREQ(s.get(), "XYcdXYcdXYcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XYZ!"); + EXPECT_STREQ(s.get(), "XYZ!cdXYZ!cdXYZ!cd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("notfound", "X"); + EXPECT_STREQ(s.get(), "abcdabcdabcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("notfound", "longlongstring"); + EXPECT_STREQ(s.get(), "abcdabcdabcd"); +} + +TEST_F(Strings, strip_ws) { + const char* texts[] = {"", " a $ ", "Some\fother\t thing\r\n", + "And \f\t\r\n even\nmore\r \f"}; + const char* results[] = {"", "a$", "Someotherthing", "Andevenmore"}; + for (size_t i = 0; i < sizeof(texts) / sizeof(texts[0]); i++) { + nsCString s(texts[i]); + s.StripWhitespace(); + EXPECT_STREQ(s.get(), results[i]); + } +} + +TEST_F(Strings, equals_ic) { + nsCString s; + EXPECT_FALSE(s.LowerCaseEqualsLiteral("view-source")); +} + +TEST_F(Strings, concat) { + nsCString bar("bar"); + const nsACString& barRef = bar; + + const nsPromiseFlatCString& result = + PromiseFlatCString("foo"_ns + ","_ns + barRef); + EXPECT_STREQ(result.get(), "foo,bar"); +} + +TEST_F(Strings, concat_2) { + nsCString fieldTextStr("xyz"); + nsCString text("text"); + const nsACString& aText = text; + + nsAutoCString result(fieldTextStr + aText); + + EXPECT_STREQ(result.get(), "xyztext"); +} + +TEST_F(Strings, concat_3) { + nsCString result; + nsCString ab("ab"), c("c"); + + result = ab + result + c; + EXPECT_STREQ(result.get(), "abc"); +} + +TEST_F(Strings, empty_assign) { + nsCString a; + a.AssignLiteral(""); + + a.AppendLiteral(""); + + nsCString b; + b.SetCapacity(0); +} + +TEST_F(Strings, set_length) { + const char kText[] = "Default Plugin"; + nsCString buf; + buf.SetCapacity(sizeof(kText) - 1); + buf.Assign(kText); + buf.SetLength(sizeof(kText) - 1); + EXPECT_STREQ(buf.get(), kText); +} + +TEST_F(Strings, substring) { + nsCString super("hello world"), sub("hello"); + + // this tests that |super| starts with |sub|, + + EXPECT_TRUE(sub.Equals(StringHead(super, sub.Length()))); + + // and verifies that |sub| does not start with |super|. + + EXPECT_FALSE(super.Equals(StringHead(sub, super.Length()))); +} + +#define test_append_expect(str, int, suffix, expect) \ + str.Truncate(); \ + str.AppendInt(suffix = int); \ + EXPECT_TRUE(str.EqualsLiteral(expect)); + +#define test_appends_expect(int, suffix, expect) \ + test_append_expect(str, int, suffix, expect) \ + test_append_expect(cstr, int, suffix, expect) + +#define test_appendbase(str, prefix, int, suffix, base) \ + str.Truncate(); \ + str.AppendInt(suffix = prefix##int##suffix, base); \ + EXPECT_TRUE(str.EqualsLiteral(#int)); + +#define test_appendbases(prefix, int, suffix, base) \ + test_appendbase(str, prefix, int, suffix, base) \ + test_appendbase(cstr, prefix, int, suffix, base) + +TEST_F(Strings, appendint) { + nsString str; + nsCString cstr; + int32_t L; + uint32_t UL; + int64_t LL; + uint64_t ULL; + test_appends_expect(INT32_MAX, L, "2147483647"); + test_appends_expect(INT32_MIN, L, "-2147483648"); + test_appends_expect(UINT32_MAX, UL, "4294967295"); + test_appends_expect(INT64_MAX, LL, "9223372036854775807"); + test_appends_expect(INT64_MIN, LL, "-9223372036854775808"); + test_appends_expect(UINT64_MAX, ULL, "18446744073709551615"); + test_appendbases(0, 17777777777, L, 8); + test_appendbases(0, 20000000000, L, 8); + test_appendbases(0, 37777777777, UL, 8); + test_appendbases(0, 777777777777777777777, LL, 8); + test_appendbases(0, 1000000000000000000000, LL, 8); + test_appendbases(0, 1777777777777777777777, ULL, 8); + test_appendbases(0x, 7fffffff, L, 16); + test_appendbases(0x, 80000000, L, 16); + test_appendbases(0x, ffffffff, UL, 16); + test_appendbases(0x, 7fffffffffffffff, LL, 16); + test_appendbases(0x, 8000000000000000, LL, 16); + test_appendbases(0x, ffffffffffffffff, ULL, 16); +} + +TEST_F(Strings, appendint64) { + nsCString str; + + int64_t max = INT64_MAX; + static const char max_expected[] = "9223372036854775807"; + int64_t min = INT64_MIN; + static const char min_expected[] = "-9223372036854775808"; + static const char min_expected_oct[] = "1000000000000000000000"; + int64_t maxint_plus1 = 1LL << 32; + static const char maxint_plus1_expected[] = "4294967296"; + static const char maxint_plus1_expected_x[] = "100000000"; + + str.AppendInt(max); + + EXPECT_TRUE(str.Equals(max_expected)); + + str.Truncate(); + str.AppendInt(min); + EXPECT_TRUE(str.Equals(min_expected)); + str.Truncate(); + str.AppendInt(min, 8); + EXPECT_TRUE(str.Equals(min_expected_oct)); + + str.Truncate(); + str.AppendInt(maxint_plus1); + EXPECT_TRUE(str.Equals(maxint_plus1_expected)); + str.Truncate(); + str.AppendInt(maxint_plus1, 16); + EXPECT_TRUE(str.Equals(maxint_plus1_expected_x)); +} + +TEST_F(Strings, inttotstring) { + EXPECT_EQ("42"_ns, IntToCString(42)); + EXPECT_EQ(u"42"_ns, IntToString(42)); + + EXPECT_EQ("2a"_ns, IntToCString(42, 16)); + EXPECT_EQ(u"2a"_ns, IntToString(42, 16)); +} + +TEST_F(Strings, appendfloat) { + nsCString str; + double bigdouble = 11223344556.66; + static const char double_expected[] = "11223344556.66"; + static const char float_expected[] = "0.01"; + + // AppendFloat is used to append doubles, therefore the precision must be + // large enough (see bug 327719) + str.AppendFloat(bigdouble); + EXPECT_TRUE(str.Equals(double_expected)); + + str.Truncate(); + // AppendFloat is used to append floats (bug 327719 #27) + str.AppendFloat(0.1f * 0.1f); + EXPECT_TRUE(str.Equals(float_expected)); +} + +TEST_F(Strings, findcharinset) { + nsCString buf("hello, how are you?"); + + int32_t index = buf.FindCharInSet(",?", 5); + EXPECT_EQ(index, 5); + + index = buf.FindCharInSet("helo", 0); + EXPECT_EQ(index, 0); + + index = buf.FindCharInSet("z?", 6); + EXPECT_EQ(index, (int32_t)buf.Length() - 1); +} + +TEST_F(Strings, rfindcharinset) { + nsCString buf("hello, how are you?"); + + int32_t index = buf.RFindCharInSet(",?", 5); + EXPECT_EQ(index, 5); + + index = buf.RFindCharInSet("helo", 0); + EXPECT_EQ(index, 0); + + index = buf.RFindCharInSet("z?", 6); + EXPECT_EQ(index, kNotFound); + + index = buf.RFindCharInSet("l", 5); + EXPECT_EQ(index, 3); + + buf.AssignLiteral("abcdefghijkabc"); + + index = buf.RFindCharInSet("ab"); + EXPECT_EQ(index, 12); + + index = buf.RFindCharInSet("ab", 11); + EXPECT_EQ(index, 11); + + index = buf.RFindCharInSet("ab", 10); + EXPECT_EQ(index, 1); + + index = buf.RFindCharInSet("ab", 0); + EXPECT_EQ(index, 0); + + index = buf.RFindCharInSet("cd", 1); + EXPECT_EQ(index, kNotFound); + + index = buf.RFindCharInSet("h"); + EXPECT_EQ(index, 7); +} + +TEST_F(Strings, stringbuffer) { + const char kData[] = "hello world"; + + RefPtr<nsStringBuffer> buf; + + buf = nsStringBuffer::Alloc(sizeof(kData)); + EXPECT_TRUE(!!buf); + + buf = nsStringBuffer::Alloc(sizeof(kData)); + EXPECT_TRUE(!!buf); + char* data = (char*)buf->Data(); + memcpy(data, kData, sizeof(kData)); + + nsCString str; + buf->ToString(sizeof(kData) - 1, str); + + nsStringBuffer* buf2; + buf2 = nsStringBuffer::FromString(str); + + EXPECT_EQ(buf, buf2); +} + +TEST_F(Strings, voided) { + const char kData[] = "hello world"; + + nsCString str; + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.SetIsVoid(true); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.Assign(kData); + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(!str.IsEmpty()); + EXPECT_STREQ(str.get(), kData); + + str.SetIsVoid(true); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.SetIsVoid(false); + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.Assign(kData); + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(!str.IsEmpty()); + EXPECT_STREQ(str.get(), kData); + + str.Adopt(nullptr); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); +} + +TEST_F(Strings, voided_autostr) { + const char kData[] = "hello world"; + + nsAutoCString str; + EXPECT_FALSE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + + str.Assign(kData); + EXPECT_STREQ(str.get(), kData); + + str.SetIsVoid(true); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + + str.Assign(kData); + EXPECT_FALSE(str.IsVoid()); + EXPECT_FALSE(str.IsEmpty()); + EXPECT_STREQ(str.get(), kData); +} + +TEST_F(Strings, voided_assignment) { + nsCString a, b; + b.SetIsVoid(true); + a = b; + EXPECT_TRUE(a.IsVoid()); + EXPECT_EQ(a.get(), b.get()); +} + +TEST_F(Strings, empty_assignment) { + nsCString a, b; + a = b; + EXPECT_EQ(a.get(), b.get()); +} + +struct ToIntegerTest { + const char* str; + uint32_t radix; + int32_t result; + nsresult rv; +}; + +static const ToIntegerTest kToIntegerTests[] = { + {"123", 10, 123, NS_OK}, + {"7b", 16, 123, NS_OK}, + {"90194313659", 10, 0, NS_ERROR_ILLEGAL_VALUE}, + {nullptr, 0, 0, NS_OK}}; + +TEST_F(Strings, string_tointeger) { + nsresult rv; + for (const ToIntegerTest* t = kToIntegerTests; t->str; ++t) { + int32_t result = nsAutoCString(t->str).ToInteger(&rv, t->radix); + EXPECT_EQ(rv, t->rv); + EXPECT_EQ(result, t->result); + result = nsAutoCString(t->str).ToInteger(&rv, t->radix); + EXPECT_EQ(rv, t->rv); + EXPECT_EQ(result, t->result); + } +} + +static void test_parse_string_helper(const char* str, char separator, int len, + const char* s1, const char* s2) { + nsCString data(str); + nsTArray<nsCString> results; + ParseString(data, separator, results); + EXPECT_EQ(int(results.Length()), len); + const char* strings[] = {s1, s2}; + for (int i = 0; i < len; ++i) { + EXPECT_TRUE(results[i].Equals(strings[i])); + } +} + +static void test_parse_string_helper0(const char* str, char separator) { + test_parse_string_helper(str, separator, 0, nullptr, nullptr); +} + +static void test_parse_string_helper1(const char* str, char separator, + const char* s1) { + test_parse_string_helper(str, separator, 1, s1, nullptr); +} + +static void test_parse_string_helper2(const char* str, char separator, + const char* s1, const char* s2) { + test_parse_string_helper(str, separator, 2, s1, s2); +} + +TEST(String, parse_string) +{ + test_parse_string_helper1("foo, bar", '_', "foo, bar"); + test_parse_string_helper2("foo, bar", ',', "foo", " bar"); + test_parse_string_helper2("foo, bar ", ' ', "foo,", "bar"); + test_parse_string_helper2("foo,bar", 'o', "f", ",bar"); + test_parse_string_helper0("", '_'); + test_parse_string_helper0(" ", ' '); + test_parse_string_helper1(" foo", ' ', "foo"); + test_parse_string_helper1(" foo", ' ', "foo"); +} + +static void test_strip_chars_helper(const char16_t* str, const char16_t* strip, + const nsAString& result) { + nsAutoString data(str); + data.StripChars(strip); + EXPECT_TRUE(data.Equals(result)); +} + +TEST(String, strip_chars) +{ + test_strip_chars_helper(u"foo \r \nbar", u" \n\r", u"foobar"_ns); + test_strip_chars_helper(u"\r\nfoo\r\n", u" \n\r", u"foo"_ns); + test_strip_chars_helper(u"foo", u" \n\r", u"foo"_ns); + test_strip_chars_helper(u"foo", u"fo", u""_ns); + test_strip_chars_helper(u"foo", u"foo", u""_ns); + test_strip_chars_helper(u" foo", u" ", u"foo"_ns); +} + +TEST_F(Strings, append_with_capacity) { + nsAutoString s; + const char16_t* origPtr = s.BeginReading(); + s.SetCapacity(8000); + const char16_t* ptr = s.BeginReading(); + EXPECT_NE(origPtr, ptr); + for (int i = 0; i < 100; i++) { + s.Append(u'a'); + EXPECT_EQ(s.BeginReading(), ptr); + EXPECT_EQ(s.Length(), uint32_t(i + 1)); + } +} + +TEST_F(Strings, append_string_with_capacity) { + nsAutoString aa; + aa.Append(u'a'); + aa.Append(u'a'); + nsAutoString s; + const char16_t* origPtr = s.BeginReading(); + s.SetCapacity(8000); + const char16_t* ptr = s.BeginReading(); + EXPECT_NE(origPtr, ptr); + nsAutoString empty; + s.Append(empty); + EXPECT_EQ(s.BeginReading(), ptr); + for (int i = 0; i < 100; i++) { + s.Append(aa); + EXPECT_EQ(s.BeginReading(), ptr); + EXPECT_EQ(s.Length(), uint32_t(2 * (i + 1))); + } +} + +TEST_F(Strings, append_literal_with_capacity) { + nsAutoString s; + const char16_t* origPtr = s.BeginReading(); + s.SetCapacity(8000); + const char16_t* ptr = s.BeginReading(); + EXPECT_NE(origPtr, ptr); + s.AppendLiteral(u""); + EXPECT_EQ(s.BeginReading(), ptr); + for (int i = 0; i < 100; i++) { + s.AppendLiteral(u"aa"); + EXPECT_EQ(s.BeginReading(), ptr); + EXPECT_EQ(s.Length(), uint32_t(2 * (i + 1))); + } +} + +// The following test is intentionally not memory +// checking-clean. +#if !(defined(MOZ_HAVE_MEM_CHECKS) || defined(MOZ_MSAN)) +TEST_F(Strings, legacy_set_length_semantics) { + const char* foobar = "foobar"; + nsCString s; + s.SetCapacity(2048); + memcpy(s.BeginWriting(), foobar, strlen(foobar)); + s.SetLength(strlen(foobar)); + EXPECT_FALSE(s.EqualsASCII(foobar)); +} +#endif + +TEST_F(Strings, bulk_write) { + nsCString s; + const char* ptrTwoThousand; + { + auto handleOrErr = s.BulkWrite(500, 0, true); + EXPECT_TRUE(handleOrErr.isOk()); + + auto handle = handleOrErr.unwrap(); + + auto span = handle.AsSpan(); + for (auto&& c : span) { + c = 'a'; + } + mozilla::Unused << handle.RestartBulkWrite(2000, 500, false); + span = handle.AsSpan().From(500); + for (auto&& c : span) { + c = 'b'; + } + ptrTwoThousand = handle.Elements(); + handle.Finish(1000, true); + } + EXPECT_EQ(s.Length(), 1000U); + EXPECT_NE(s.BeginReading(), ptrTwoThousand); + EXPECT_EQ(s.BeginReading()[1000], '\0'); + for (uint32_t i = 0; i < 500; i++) { + EXPECT_EQ(s[i], 'a'); + } + for (uint32_t i = 500; i < 1000; i++) { + EXPECT_EQ(s[i], 'b'); + } +} + +TEST_F(Strings, bulk_write_fail) { + nsCString s; + { + auto handleOrErr = s.BulkWrite(500, 0, true); + EXPECT_TRUE(handleOrErr.isOk()); + } + EXPECT_EQ(s.Length(), 3U); + EXPECT_TRUE(s.Equals(u8"\uFFFD")); +} + +TEST_F(Strings, huge_capacity) { + nsString a, b, c, d, e, f, g, h, i, j, k, l, m, n, o; + nsCString n1, o1; + + // Ignore the result if the address space is less than 64-bit because + // some of the allocations above will exhaust the address space. + if (sizeof(void*) >= 8) { + EXPECT_TRUE(a.SetCapacity(1, fallible)); + EXPECT_FALSE(a.SetCapacity(uint32_t(-1) / 2, fallible)); + a.Truncate(); // free the allocated memory + + EXPECT_TRUE(b.SetCapacity(1, fallible)); + EXPECT_FALSE(b.SetCapacity(uint32_t(-1) / 2 - 1, fallible)); + b.Truncate(); + + EXPECT_TRUE(c.SetCapacity(1, fallible)); + EXPECT_FALSE(c.SetCapacity(uint32_t(-1) / 2, fallible)); + c.Truncate(); + + EXPECT_FALSE(d.SetCapacity(uint32_t(-1) / 2 - 1, fallible)); + EXPECT_FALSE(d.SetCapacity(uint32_t(-1) / 2, fallible)); + d.Truncate(); + + EXPECT_FALSE(e.SetCapacity(uint32_t(-1) / 4, fallible)); + EXPECT_FALSE(e.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + e.Truncate(); + + EXPECT_FALSE(f.SetCapacity(uint32_t(-1) / 2, fallible)); + f.Truncate(); + + EXPECT_FALSE(g.SetCapacity(uint32_t(-1) / 4 + 1000, fallible)); + EXPECT_FALSE(g.SetCapacity(uint32_t(-1) / 4 + 1001, fallible)); + g.Truncate(); + + EXPECT_FALSE(h.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + EXPECT_FALSE(h.SetCapacity(uint32_t(-1) / 2, fallible)); + h.Truncate(); + + EXPECT_TRUE(i.SetCapacity(1, fallible)); + EXPECT_TRUE(i.SetCapacity(uint32_t(-1) / 4 - 1000, fallible)); + EXPECT_FALSE(i.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + i.Truncate(); + + EXPECT_TRUE(j.SetCapacity(uint32_t(-1) / 4 - 1000, fallible)); + EXPECT_FALSE(j.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + j.Truncate(); + +// Disabled due to intermittent failures. +// https://bugzilla.mozilla.org/show_bug.cgi?id=1493458 +#if 0 + EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/8 - 1000, fallible)); + EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/4 - 1001, fallible)); + EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/4 - 998, fallible)); + EXPECT_FALSE(k.SetCapacity(uint32_t(-1)/4 + 1, fallible)); + k.Truncate(); +#endif + + EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8, fallible)); + EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8 + 1, fallible)); + EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8 + 2, fallible)); + l.Truncate(); + + EXPECT_TRUE(m.SetCapacity(uint32_t(-1) / 8 + 1000, fallible)); + EXPECT_TRUE(m.SetCapacity(uint32_t(-1) / 8 + 1001, fallible)); + m.Truncate(); + + EXPECT_TRUE(n.SetCapacity(uint32_t(-1) / 8 + 1, fallible)); + EXPECT_FALSE(n.SetCapacity(uint32_t(-1) / 4, fallible)); + n.Truncate(); + + n.Truncate(); + EXPECT_TRUE(n.SetCapacity((uint32_t(-1) / 2) / 2 - 1, fallible)); + n.Truncate(); + EXPECT_FALSE(n.SetCapacity((uint32_t(-1) / 2) / 2, fallible)); + n.Truncate(); + n1.Truncate(); + EXPECT_TRUE(n1.SetCapacity((uint32_t(-1) / 2) / 1 - 1, fallible)); + n1.Truncate(); + EXPECT_FALSE(n1.SetCapacity((uint32_t(-1) / 2) / 1, fallible)); + n1.Truncate(); + + // The longest possible JS string should fit within both a `nsString` and + // nsCString. + EXPECT_TRUE(o.SetCapacity(JS::MaxStringLength, fallible)); + o.Truncate(); + EXPECT_TRUE(o1.SetCapacity(JS::MaxStringLength, fallible)); + o1.Truncate(); + } +} + +static void test_tofloat_helper(const nsString& aStr, + mozilla::Maybe<float> aExpected) { + nsresult result; + float value = aStr.ToFloat(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, tofloat) { + test_tofloat_helper(u"42"_ns, Some(42.f)); + test_tofloat_helper(u"42.0"_ns, Some(42.f)); + test_tofloat_helper(u"-42"_ns, Some(-42.f)); + test_tofloat_helper(u"+42"_ns, Some(42)); + test_tofloat_helper(u"13.37"_ns, Some(13.37f)); + test_tofloat_helper(u"1.23456789"_ns, Some(1.23456789f)); + test_tofloat_helper(u"1.98765432123456"_ns, Some(1.98765432123456f)); + test_tofloat_helper(u"0"_ns, Some(0.f)); + test_tofloat_helper(u"1.e5"_ns, Some(100000)); + test_tofloat_helper(u""_ns, Nothing()); + test_tofloat_helper(u"42foo"_ns, Nothing()); + test_tofloat_helper(u"foo"_ns, Nothing()); + test_tofloat_helper(u"1.5e-"_ns, Nothing()); + + // Leading spaces are ignored + test_tofloat_helper(u" \t5"_ns, Some(5.f)); + + // Values which are too large generate an error + test_tofloat_helper(u"3.402823e38"_ns, Some(3.402823e+38)); + test_tofloat_helper(u"1e39"_ns, Nothing()); + test_tofloat_helper(u"-3.402823e38"_ns, Some(-3.402823e+38)); + test_tofloat_helper(u"-1e39"_ns, Nothing()); + + // Values which are too small round to zero + test_tofloat_helper(u"1.4013e-45"_ns, Some(1.4013e-45f)); + test_tofloat_helper(u"1e-46"_ns, Some(0.f)); + test_tofloat_helper(u"-1.4013e-45"_ns, Some(-1.4013e-45f)); + test_tofloat_helper(u"-1e-46"_ns, Some(-0.f)); +} + +static void test_tofloat_allow_trailing_chars_helper(const nsString& aStr, + Maybe<float> aExpected) { + nsresult result; + float value = aStr.ToFloatAllowTrailingChars(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, ToFloatAllowTrailingChars) { + test_tofloat_allow_trailing_chars_helper(u""_ns, Nothing()); + test_tofloat_allow_trailing_chars_helper(u"foo"_ns, Nothing()); + test_tofloat_allow_trailing_chars_helper(u"42foo"_ns, Some(42.f)); + test_tofloat_allow_trailing_chars_helper(u"42-5"_ns, Some(42.f)); + test_tofloat_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37f)); + test_tofloat_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5f)); +} + +static void test_todouble_helper(const nsString& aStr, + Maybe<double> aExpected) { + nsresult result; + double value = aStr.ToDouble(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, todouble) { + test_todouble_helper(u"42"_ns, Some(42)); + test_todouble_helper(u"42.0"_ns, Some(42)); + test_todouble_helper(u"-42"_ns, Some(-42)); + test_todouble_helper(u"+42"_ns, Some(42)); + test_todouble_helper(u"13.37"_ns, Some(13.37)); + test_todouble_helper(u"1.23456789"_ns, Some(1.23456789)); + test_todouble_helper(u"1.98765432123456"_ns, Some(1.98765432123456)); + test_todouble_helper(u"123456789.98765432123456"_ns, + Some(123456789.98765432123456)); + test_todouble_helper(u"0"_ns, Some(0)); + test_todouble_helper(u"1.e5"_ns, Some(100000)); + test_todouble_helper(u""_ns, Nothing()); + test_todouble_helper(u"42foo"_ns, Nothing()); + test_todouble_helper(u"foo"_ns, Nothing()); + test_todouble_helper(u"1.5e-"_ns, Nothing()); + + // Leading spaces are ignored + test_todouble_helper(u" \t5"_ns, Some(5.)); + + // Values which are too large generate an error + test_todouble_helper(u"1.797693e+308"_ns, Some(1.797693e+308)); + test_todouble_helper(u"1e309"_ns, Nothing()); + test_todouble_helper(u"-1.797693e+308"_ns, Some(-1.797693e+308)); + test_todouble_helper(u"-1e309"_ns, Nothing()); + + // Values which are too small round to zero + test_todouble_helper(u"4.940656e-324"_ns, Some(4.940656e-324)); + test_todouble_helper(u"1e-325"_ns, Some(0.)); + test_todouble_helper(u"-4.940656e-324"_ns, Some(-4.940656e-324)); + test_todouble_helper(u"-1e-325"_ns, Some(-0.)); +} + +static void test_todouble_allow_trailing_chars_helper(const nsString& aStr, + Maybe<double> aExpected) { + nsresult result; + double value = aStr.ToDoubleAllowTrailingChars(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, ToDoubleAllowTrailingChars) { + test_todouble_allow_trailing_chars_helper(u""_ns, Nothing()); + test_todouble_allow_trailing_chars_helper(u"foo"_ns, Nothing()); + test_todouble_allow_trailing_chars_helper(u"42foo"_ns, Some(42)); + test_todouble_allow_trailing_chars_helper(u"42-5"_ns, Some(42)); + test_todouble_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37)); + test_todouble_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5)); +} + +TEST_F(Strings, Split) { + nsCString one("one"), two("one;two"), three("one--three"), empty(""), + delimStart("-two"), delimEnd("one-"); + + nsString wide(u"hello world"); + + size_t counter = 0; + for (const nsACString& token : one.Split(',')) { + EXPECT_TRUE(token.EqualsLiteral("one")); + counter++; + } + EXPECT_EQ(counter, (size_t)1); + + counter = 0; + for (const nsACString& token : two.Split(';')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("one")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("two")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); + + counter = 0; + for (const nsACString& token : three.Split('-')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("one")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("")); + } else if (counter == 2) { + EXPECT_TRUE(token.EqualsLiteral("three")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)3); + + counter = 0; + for (const nsACString& token : empty.Split(',')) { + mozilla::Unused << token; + counter++; + } + EXPECT_EQ(counter, (size_t)0); + + counter = 0; + for (const nsACString& token : delimStart.Split('-')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("two")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); + + counter = 0; + for (const nsACString& token : delimEnd.Split('-')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("one")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); + + counter = 0; + for (const nsAString& token : wide.Split(' ')) { + if (counter == 0) { + EXPECT_TRUE(token.Equals(u"hello"_ns)); + } else if (counter == 1) { + EXPECT_TRUE(token.Equals(u"world"_ns)); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); +} + +TEST_F(Strings, Join) { + // Join a sequence of strings. + { + // 8-bit strings + EXPECT_EQ(""_ns, StringJoin(","_ns, std::array<nsCString, 0>{})); + EXPECT_EQ("foo"_ns, StringJoin(","_ns, std::array{"foo"_ns})); + EXPECT_EQ("foo,bar"_ns, StringJoin(","_ns, std::array{"foo"_ns, "bar"_ns})); + + // 16-bit strings + EXPECT_EQ(u""_ns, StringJoin(u","_ns, std::array<nsString, 0>{})); + EXPECT_EQ(u"foo"_ns, StringJoin(u","_ns, std::array{u"foo"_ns})); + EXPECT_EQ(u"foo,bar"_ns, + StringJoin(u","_ns, std::array{u"foo"_ns, u"bar"_ns})); + } + + // Join a sequence of strings, appending. + { + // 8-bit string + { + nsAutoCString dst{"prefix:"_ns}; + StringJoinAppend(dst, ","_ns, std::array{"foo"_ns, "bar"_ns}); + EXPECT_EQ("prefix:foo,bar"_ns, dst); + } + + // 16-bit string + { + nsAutoString dst{u"prefix:"_ns}; + StringJoinAppend(dst, u","_ns, std::array{u"foo"_ns, u"bar"_ns}); + EXPECT_EQ(u"prefix:foo,bar"_ns, dst); + } + } +} + +TEST_F(Strings, JoinWithAppendingTransformation) { + const auto toCString = [](nsACString& dst, int val) { dst.AppendInt(val); }; + const auto toString = [](nsAString& dst, int val) { dst.AppendInt(val); }; + + // Join a sequence of elements transformed to a string. + { + // 8-bit strings + EXPECT_EQ(""_ns, StringJoin(","_ns, std::array<int, 0>{}, toCString)); + EXPECT_EQ("7"_ns, StringJoin(","_ns, std::array{7}, toCString)); + EXPECT_EQ("7,42"_ns, StringJoin(","_ns, std::array{7, 42}, toCString)); + + // 16-bit strings + EXPECT_EQ(u""_ns, StringJoin(u","_ns, std::array<int, 0>{}, toString)); + EXPECT_EQ(u"7"_ns, StringJoin(u","_ns, std::array{7}, toString)); + EXPECT_EQ(u"7,42"_ns, StringJoin(u","_ns, std::array{7, 42}, toString)); + } + + // Join a sequence of elements transformed to a string, appending. + { + // 8-bit string + { + nsAutoCString dst{"prefix:"_ns}; + StringJoinAppend(dst, ","_ns, std::array{7, 42}, toCString); + EXPECT_EQ("prefix:7,42"_ns, dst); + } + + // 16-bit string + { + nsAutoString dst{u"prefix:"_ns}; + StringJoinAppend(dst, u","_ns, std::array{7, 42}, toString); + EXPECT_EQ(u"prefix:7,42"_ns, dst); + } + } +} + +constexpr bool TestSomeChars(char c) { + return c == 'a' || c == 'c' || c == 'e' || c == '7' || c == 'G' || c == 'Z' || + c == '\b' || c == '?'; +} +TEST_F(Strings, ASCIIMask) { + const ASCIIMaskArray& maskCRLF = mozilla::ASCIIMask::MaskCRLF(); + EXPECT_TRUE(maskCRLF['\n'] && mozilla::ASCIIMask::IsMasked(maskCRLF, '\n')); + EXPECT_TRUE(maskCRLF['\r'] && mozilla::ASCIIMask::IsMasked(maskCRLF, '\r')); + EXPECT_FALSE(maskCRLF['g'] || mozilla::ASCIIMask::IsMasked(maskCRLF, 'g')); + EXPECT_FALSE(maskCRLF[' '] || mozilla::ASCIIMask::IsMasked(maskCRLF, ' ')); + EXPECT_FALSE(maskCRLF['\0'] || mozilla::ASCIIMask::IsMasked(maskCRLF, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); + + const ASCIIMaskArray& mask0to9 = mozilla::ASCIIMask::Mask0to9(); + EXPECT_TRUE(mask0to9['9'] && mozilla::ASCIIMask::IsMasked(mask0to9, '9')); + EXPECT_TRUE(mask0to9['0'] && mozilla::ASCIIMask::IsMasked(mask0to9, '0')); + EXPECT_TRUE(mask0to9['4'] && mozilla::ASCIIMask::IsMasked(mask0to9, '4')); + EXPECT_FALSE(mask0to9['g'] || mozilla::ASCIIMask::IsMasked(mask0to9, 'g')); + EXPECT_FALSE(mask0to9[' '] || mozilla::ASCIIMask::IsMasked(mask0to9, ' ')); + EXPECT_FALSE(mask0to9['\n'] || mozilla::ASCIIMask::IsMasked(mask0to9, '\n')); + EXPECT_FALSE(mask0to9['\0'] || mozilla::ASCIIMask::IsMasked(mask0to9, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); + + const ASCIIMaskArray& maskWS = mozilla::ASCIIMask::MaskWhitespace(); + EXPECT_TRUE(maskWS[' '] && mozilla::ASCIIMask::IsMasked(maskWS, ' ')); + EXPECT_TRUE(maskWS['\t'] && mozilla::ASCIIMask::IsMasked(maskWS, '\t')); + EXPECT_FALSE(maskWS['8'] || mozilla::ASCIIMask::IsMasked(maskWS, '8')); + EXPECT_FALSE(maskWS['\0'] || mozilla::ASCIIMask::IsMasked(maskWS, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); + + constexpr ASCIIMaskArray maskSome = mozilla::CreateASCIIMask(TestSomeChars); + EXPECT_TRUE(maskSome['a'] && mozilla::ASCIIMask::IsMasked(maskSome, 'a')); + EXPECT_TRUE(maskSome['c'] && mozilla::ASCIIMask::IsMasked(maskSome, 'c')); + EXPECT_TRUE(maskSome['e'] && mozilla::ASCIIMask::IsMasked(maskSome, 'e')); + EXPECT_TRUE(maskSome['7'] && mozilla::ASCIIMask::IsMasked(maskSome, '7')); + EXPECT_TRUE(maskSome['G'] && mozilla::ASCIIMask::IsMasked(maskSome, 'G')); + EXPECT_TRUE(maskSome['Z'] && mozilla::ASCIIMask::IsMasked(maskSome, 'Z')); + EXPECT_TRUE(maskSome['\b'] && mozilla::ASCIIMask::IsMasked(maskSome, '\b')); + EXPECT_TRUE(maskSome['?'] && mozilla::ASCIIMask::IsMasked(maskSome, '?')); + EXPECT_FALSE(maskSome['8'] || mozilla::ASCIIMask::IsMasked(maskSome, '8')); + EXPECT_FALSE(maskSome['\0'] || mozilla::ASCIIMask::IsMasked(maskSome, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); +} + +template <typename T> +void CompressWhitespaceHelper() { + T s; + s.AssignLiteral("abcabcabc"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abcabcabc")); + + s.AssignLiteral(" \n\rabcabcabc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abcabcabc")); + + s.AssignLiteral(" \n\rabc abc abc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc")); + + s.AssignLiteral(" \n\rabc\r \nabc\n \rabc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(false, true); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(true, false); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc ")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(false, false); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc ")); + + s.AssignLiteral(" \r\n "); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(" \r\n \t"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral("\n \r\n \t"); + s.CompressWhitespace(false, false); + EXPECT_TRUE(s.EqualsLiteral(" ")); + + s.AssignLiteral("\n \r\n \t"); + s.CompressWhitespace(false, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral("\n \r\n \t"); + s.CompressWhitespace(true, false); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(false, false); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(false, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(true, false); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("")); +} + +TEST_F(Strings, CompressWhitespace) { CompressWhitespaceHelper<nsCString>(); } + +TEST_F(Strings, CompressWhitespaceW) { + CompressWhitespaceHelper<nsString>(); + + nsString str, result; + str.AssignLiteral(u"\u263A is\r\n ;-)"); + result.AssignLiteral(u"\u263A is ;-)"); + str.CompressWhitespace(true, true); + EXPECT_TRUE(str == result); +} + +template <typename T> +void StripCRLFHelper() { + T s; + s.AssignLiteral("abcabcabc"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral("abcabcabc")); + + s.AssignLiteral(" \n\rabcabcabc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abcabcabc")); + + s.AssignLiteral(" \n\rabc abc abc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r \nabc\n \rabc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \r\n "); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" ")); + + s.AssignLiteral(" \r\n \t"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" \t")); + + s.AssignLiteral("\n \r\n \t"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" \t")); + + s.AssignLiteral(""); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral("")); +} + +TEST_F(Strings, StripCRLF) { StripCRLFHelper<nsCString>(); } + +TEST_F(Strings, StripCRLFW) { + StripCRLFHelper<nsString>(); + + nsString str, result; + str.AssignLiteral(u"\u263A is\r\n ;-)"); + result.AssignLiteral(u"\u263A is ;-)"); + str.StripCRLF(); + EXPECT_TRUE(str == result); +} + +TEST_F(Strings, utf8_to_latin1_sharing) { + nsCString s; + s.Append('a'); + s.Append('b'); + s.Append('c'); + nsCString t; + LossyAppendUTF8toLatin1(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); + LossyCopyUTF8toLatin1(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); +} + +TEST_F(Strings, latin1_to_utf8_sharing) { + nsCString s; + s.Append('a'); + s.Append('b'); + s.Append('c'); + nsCString t; + AppendLatin1toUTF8(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); + CopyLatin1toUTF8(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); +} + +TEST_F(Strings, utf8_to_latin1) { + nsCString s; + s.AssignLiteral("\xC3\xA4"); + nsCString t; + LossyCopyUTF8toLatin1(s, t); + // EqualsLiteral requires ASCII + EXPECT_TRUE(t.Equals("\xE4")); +} + +TEST_F(Strings, latin1_to_utf8) { + nsCString s; + s.AssignLiteral("\xE4"); + nsCString t; + CopyLatin1toUTF8(s, t); + // EqualsLiteral requires ASCII + EXPECT_TRUE(t.Equals("\xC3\xA4")); +} + +TEST_F(Strings, ConvertToSpan) { + nsString string; + + // from const string + { + const auto& constStringRef = string; + + auto span = Span{constStringRef}; + static_assert(std::is_same_v<decltype(span), Span<const char16_t>>); + } + + // from non-const string + { + auto span = Span{string}; + static_assert(std::is_same_v<decltype(span), Span<const char16_t>>); + } + + // get mutable data + { + auto span = string.GetMutableData(); + static_assert(std::is_same_v<decltype(span), Span<char16_t>>); + } + + nsCString cstring; + + // from const string + { + const auto& constCStringRef = cstring; + + auto span = Span{constCStringRef}; + static_assert(std::is_same_v<decltype(span), Span<const char>>); + } + + // from non-const string + { + auto span = Span{cstring}; + static_assert(std::is_same_v<decltype(span), Span<const char>>); + } + + // get mutable data + { + auto span = cstring.GetMutableData(); + static_assert(std::is_same_v<decltype(span), Span<char>>); + } +} + +template <typename T> +void InsertSpanHelper() { + T str1, str2; + str1.AssignLiteral("hello world"); + str2.AssignLiteral("span "); + + T expect; + expect.AssignLiteral("hello span world"); + + Span span(str2); + str1.Insert(span, 6); + EXPECT_TRUE(str1.Equals(expect)); +} + +TEST_F(Strings, InsertSpan) { InsertSpanHelper<nsCString>(); } +TEST_F(Strings, InsertSpanW) { InsertSpanHelper<nsString>(); } + +TEST_F(Strings, TokenizedRangeEmpty) { + // 8-bit strings + { + for (const auto& token : nsCCharSeparatedTokenizer(""_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } + + // 16-bit strings + { + for (const auto& token : nsCharSeparatedTokenizer(u""_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } +} + +TEST_F(Strings, TokenizedRangeWhitespaceOnly) { + // 8-bit strings + { + for (const auto& token : nsCCharSeparatedTokenizer(" "_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } + + // 16-bit strings + { + for (const auto& token : nsCharSeparatedTokenizer(u" "_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } +} + +TEST_F(Strings, TokenizedRangeNonEmpty) { + // 8-bit strings + { + nsTArray<nsCString> res; + for (const auto& token : + nsCCharSeparatedTokenizer("foo,bar"_ns, ',').ToRange()) { + res.EmplaceBack(token); + } + + EXPECT_EQ(res, (nsTArray<nsCString>{"foo"_ns, "bar"_ns})); + } + + // 16-bit strings + { + nsTArray<nsString> res; + for (const auto& token : + nsCharSeparatedTokenizer(u"foo,bar"_ns, ',').ToRange()) { + res.EmplaceBack(token); + } + + EXPECT_EQ(res, (nsTArray<nsString>{u"foo"_ns, u"bar"_ns})); + } +} + +// Macros for reducing verbosity of printf tests. +#define create_printf_strings(format, ...) \ + nsCString appendPrintfString; \ + appendPrintfString.AppendPrintf(format, __VA_ARGS__); \ + const nsCString appendVprintfString( \ + getAppendVprintfString(format, __VA_ARGS__)); \ + const nsPrintfCString printfString(format, __VA_ARGS__); \ + const nsVprintfCString vprintfString{getVprintfCString(format, __VA_ARGS__)}; + +// We don't check every possible combination as we assume equality is +// transitive. +#define verify_printf_strings(expected) \ + EXPECT_TRUE(appendPrintfString.EqualsASCII(expected)) \ + << "appendPrintfString != expected:" << appendPrintfString.get() \ + << " != " << (expected); \ + EXPECT_TRUE(appendPrintfString.Equals(appendVprintfString)) \ + << "appendPrintfString != appendVprintfString:" \ + << appendPrintfString.get() << " != " << appendVprintfString; \ + EXPECT_TRUE(appendPrintfString.Equals(printfString)) \ + << "appendPrintfString != printfString:" << appendPrintfString.get() \ + << " != " << printfString; \ + EXPECT_TRUE(appendPrintfString.Equals(vprintfString)) \ + << "appendPrintfString != vprintfString:" << appendPrintfString.get() \ + << " != " << vprintfString; + +TEST_F(Strings, printf) { + auto getAppendVprintfString = [](const char* aFormat, ...) { + // Helper to get a string with contents set via AppendVprint. + nsCString cString; + va_list ap; + va_start(ap, aFormat); + cString.AppendVprintf(aFormat, ap); + va_end(ap); + return cString; + }; + + auto getVprintfCString = [](const char* aFormat, ...) { + // Helper to get a nsVprintfCString. + va_list ap; + va_start(ap, aFormat); + const nsVprintfCString vprintfString(aFormat, ap); + va_end(ap); + return vprintfString; + }; + + { + const char* format = "Characters %c %%"; + const char* expectedOutput = "Characters B %"; + create_printf_strings(format, 'B'); + verify_printf_strings(expectedOutput); + } + { + const char* format = "Strings %s %s"; + const char* expectedOutput = "Strings foo bar"; + create_printf_strings(format, "foo", "bar"); + verify_printf_strings(expectedOutput); + } + { + const int signedThree = 3; + const unsigned int unsignedTen = 10; + const char* format = "Integers %i %.3d %.2u %o %x %X"; + const char* expectedOutput = "Integers 3 003 10 12 a A"; + create_printf_strings(format, signedThree, signedThree, unsignedTen, + unsignedTen, unsignedTen, unsignedTen); + verify_printf_strings(expectedOutput); + } + { + const char* format = "Floats %f %.0f %e %.2E"; + const char* expectedOutput = "Floats 1.500000 2 1.500000e+00 1.50E+00"; + create_printf_strings(format, 1.5, 1.5, 1.5, 1.5); + verify_printf_strings(expectedOutput); + } + { + const char* expectedOutput = "Just a string"; + const char* format = "%s"; + create_printf_strings(format, "Just a string"); + verify_printf_strings(expectedOutput); + } + { + const char* anotherString = "another string"; + const char* format = "Just a string and %s"; + const char* expectedOutput = "Just a string and another string"; + create_printf_strings(format, anotherString); + verify_printf_strings(expectedOutput); + } + { + // This case tickles an unexpected overload resolution in MSVC where a + // va_list overload will be selected if available. See bug 1673670 and + // 1673917 for more detail. + char anotherString[] = "another string"; + const char* format = "Just a string and %s"; + const char* expectedOutput = "Just a string and another string"; + // Calling with a non-const pointer triggers selection of va_list overload + // in MSVC at time of writing + create_printf_strings(format, (char*)anotherString); + verify_printf_strings(expectedOutput); + } +} + +// We don't need these macros following the printf test. +#undef verify_printf_strings +#undef create_printf_strings + +// Note the five calls in the loop, so divide by 100k +MOZ_GTEST_BENCH_F(Strings, PerfStripWhitespace, [this] { + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripWhitespace(); + test2.StripWhitespace(); + test3.StripWhitespace(); + test4.StripWhitespace(); + test5.StripWhitespace(); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfStripCharsWhitespace, [this] { + // This is the unoptimized (original) version of + // StripWhitespace using StripChars. + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripChars("\f\t\r\n "); + test2.StripChars("\f\t\r\n "); + test3.StripChars("\f\t\r\n "); + test4.StripChars("\f\t\r\n "); + test5.StripChars("\f\t\r\n "); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfCompressWhitespace, [this] { + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.CompressWhitespace(); + test2.CompressWhitespace(); + test3.CompressWhitespace(); + test4.CompressWhitespace(); + test5.CompressWhitespace(); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfStripCRLF, [this] { + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripCRLF(); + test2.StripCRLF(); + test3.StripCRLF(); + test4.StripCRLF(); + test5.StripCRLF(); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfStripCharsCRLF, [this] { + // This is the unoptimized (original) version of + // stripping \r\n using StripChars. + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripChars("\r\n"); + test2.StripChars("\r\n"); + test3.StripChars("\r\n"); + test4.StripChars("\r\n"); + test5.StripChars("\r\n"); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8One, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsUtf8(*BlackBox(&mAsciiOneUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Fifteen, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsUtf8(*BlackBox(&mAsciiFifteenUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Hundred, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsUtf8(*BlackBox(&mAsciiHundredUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Example3, [this] { + for (int i = 0; i < 100000; i++) { + bool b = IsUtf8(*BlackBox(&mExample3Utf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCII8One, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsAscii(*BlackBox(&mAsciiOneUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIFifteen, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsAscii(*BlackBox(&mAsciiFifteenUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIHundred, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsAscii(*BlackBox(&mAsciiHundredUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIExample3, [this] { + for (int i = 0; i < 100000; i++) { + bool b = IsAscii(*BlackBox(&mExample3Utf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsExample3, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mExample3Utf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsDE, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mDeUtf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsRU, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mRuUtf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsTH, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mThUtf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsJA, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mJaUtf16)); + BlackBox(&b); + } +}); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIOne, LossyCopyUTF16toASCII, + mAsciiOneUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIThree, LossyCopyUTF16toASCII, + mAsciiThreeUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIFifteen, LossyCopyUTF16toASCII, + mAsciiFifteenUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIHundred, LossyCopyUTF16toASCII, + mAsciiHundredUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIThousand, LossyCopyUTF16toASCII, + mAsciiThousandUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEOne, LossyCopyUTF16toASCII, mDeEditOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEThree, LossyCopyUTF16toASCII, + mDeEditThreeUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEFifteen, LossyCopyUTF16toASCII, + mDeEditFifteenUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEHundred, LossyCopyUTF16toASCII, + mDeEditHundredUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEThousand, LossyCopyUTF16toASCII, + mDeEditThousandUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiOne, CopyASCIItoUTF16, mAsciiOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiThree, CopyASCIItoUTF16, mAsciiThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiFifteen, CopyASCIItoUTF16, + mAsciiFifteenUtf8, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiHundred, CopyASCIItoUTF16, + mAsciiHundredUtf8, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiThousand, CopyASCIItoUTF16, + mAsciiThousandUtf8, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEOne, CopyASCIItoUTF16, mDeEditOneLatin1, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEThree, CopyASCIItoUTF16, mDeEditThreeLatin1, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEFifteen, CopyASCIItoUTF16, + mDeEditFifteenLatin1, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEHundred, CopyASCIItoUTF16, + mDeEditHundredLatin1, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEThousand, CopyASCIItoUTF16, + mDeEditThousandLatin1, nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiOne, CopyUTF16toUTF8, mAsciiOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiThree, CopyUTF16toUTF8, mAsciiThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiFifteen, CopyUTF16toUTF8, + mAsciiFifteenUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiHundred, CopyUTF16toUTF8, + mAsciiHundredUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiThousand, CopyUTF16toUTF8, + mAsciiThousandUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiOne, CopyUTF8toUTF16, mAsciiOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiThree, CopyUTF8toUTF16, mAsciiThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiFifteen, CopyUTF8toUTF16, + mAsciiFifteenUtf8, nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiHundred, CopyUTF8toUTF16, + mAsciiHundredUtf8, nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiThousand, CopyUTF8toUTF16, + mAsciiThousandUtf8, nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8AROne, CopyUTF16toUTF8, mArOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARThree, CopyUTF16toUTF8, mArThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARFifteen, CopyUTF16toUTF8, mArFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARHundred, CopyUTF16toUTF8, mArHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARThousand, CopyUTF16toUTF8, mArThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16AROne, CopyUTF8toUTF16, mArOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARThree, CopyUTF8toUTF16, mArThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARFifteen, CopyUTF8toUTF16, mArFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARHundred, CopyUTF8toUTF16, mArHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARThousand, CopyUTF8toUTF16, mArThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEOne, CopyUTF16toUTF8, mDeOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEThree, CopyUTF16toUTF8, mDeThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEFifteen, CopyUTF16toUTF8, mDeFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEHundred, CopyUTF16toUTF8, mDeHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEThousand, CopyUTF16toUTF8, mDeThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEOne, CopyUTF8toUTF16, mDeOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEThree, CopyUTF8toUTF16, mDeThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEFifteen, CopyUTF8toUTF16, mDeFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEHundred, CopyUTF8toUTF16, mDeHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEThousand, CopyUTF8toUTF16, mDeThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUOne, CopyUTF16toUTF8, mRuOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUThree, CopyUTF16toUTF8, mRuThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUFifteen, CopyUTF16toUTF8, mRuFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUHundred, CopyUTF16toUTF8, mRuHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUThousand, CopyUTF16toUTF8, mRuThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUOne, CopyUTF8toUTF16, mRuOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUThree, CopyUTF8toUTF16, mRuThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUFifteen, CopyUTF8toUTF16, mRuFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUHundred, CopyUTF8toUTF16, mRuHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUThousand, CopyUTF8toUTF16, mRuThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8THOne, CopyUTF16toUTF8, mThOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THThree, CopyUTF16toUTF8, mThThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THFifteen, CopyUTF16toUTF8, mThFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THHundred, CopyUTF16toUTF8, mThHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THThousand, CopyUTF16toUTF8, mThThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16THOne, CopyUTF8toUTF16, mThOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THThree, CopyUTF8toUTF16, mThThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THFifteen, CopyUTF8toUTF16, mThFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THHundred, CopyUTF8toUTF16, mThHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THThousand, CopyUTF8toUTF16, mThThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAOne, CopyUTF16toUTF8, mJaOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAThree, CopyUTF16toUTF8, mJaThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAFifteen, CopyUTF16toUTF8, mJaFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAHundred, CopyUTF16toUTF8, mJaHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAThousand, CopyUTF16toUTF8, mJaThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAOne, CopyUTF8toUTF16, mJaOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAThree, CopyUTF8toUTF16, mJaThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAFifteen, CopyUTF8toUTF16, mJaFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAHundred, CopyUTF8toUTF16, mJaHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAThousand, CopyUTF8toUTF16, mJaThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOOne, CopyUTF16toUTF8, mKoOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOThree, CopyUTF16toUTF8, mKoThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOFifteen, CopyUTF16toUTF8, mKoFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOHundred, CopyUTF16toUTF8, mKoHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOThousand, CopyUTF16toUTF8, mKoThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOOne, CopyUTF8toUTF16, mKoOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOThree, CopyUTF8toUTF16, mKoThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOFifteen, CopyUTF8toUTF16, mKoFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOHundred, CopyUTF8toUTF16, mKoHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOThousand, CopyUTF8toUTF16, mKoThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8TROne, CopyUTF16toUTF8, mTrOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRThree, CopyUTF16toUTF8, mTrThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRFifteen, CopyUTF16toUTF8, mTrFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRHundred, CopyUTF16toUTF8, mTrHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRThousand, CopyUTF16toUTF8, mTrThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16TROne, CopyUTF8toUTF16, mTrOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRThree, CopyUTF8toUTF16, mTrThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRFifteen, CopyUTF8toUTF16, mTrFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRHundred, CopyUTF8toUTF16, mTrHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRThousand, CopyUTF8toUTF16, mTrThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIOne, CopyUTF16toUTF8, mViOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIThree, CopyUTF16toUTF8, mViThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIFifteen, CopyUTF16toUTF8, mViFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIHundred, CopyUTF16toUTF8, mViHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIThousand, CopyUTF16toUTF8, mViThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIOne, CopyUTF8toUTF16, mViOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIThree, CopyUTF8toUTF16, mViThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIFifteen, CopyUTF8toUTF16, mViFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIHundred, CopyUTF8toUTF16, mViHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIThousand, CopyUTF8toUTF16, mViThousandUtf8, + nsAutoString); + +// Tests for usability of nsTLiteralString in constant expressions. +static_assert(u""_ns.IsEmpty()); + +constexpr auto testStringA = u"a"_ns; +static_assert(!testStringA.IsEmpty()); +static_assert(!testStringA.IsVoid()); +static_assert(testStringA.IsLiteral()); +static_assert(testStringA.IsTerminated()); +static_assert(testStringA.GetDataFlags() == + (nsLiteralString::DataFlags::LITERAL | + nsLiteralString::DataFlags::TERMINATED)); +static_assert(*static_cast<const char16_t*>(testStringA.Data()) == 'a'); +static_assert(1 == testStringA.Length()); +static_assert(testStringA.CharAt(0) == 'a'); +static_assert(testStringA[0] == 'a'); +static_assert(*testStringA.BeginReading() == 'a'); +static_assert(*testStringA.EndReading() == 0); +static_assert(testStringA.EndReading() - testStringA.BeginReading() == 1); + +} // namespace TestStrings + +#if defined(__clang__) && (__clang_major__ >= 6) +# pragma clang diagnostic pop +#endif diff --git a/xpcom/tests/gtest/TestSubstringTuple.cpp b/xpcom/tests/gtest/TestSubstringTuple.cpp new file mode 100644 index 0000000000..6dfd6bc154 --- /dev/null +++ b/xpcom/tests/gtest/TestSubstringTuple.cpp @@ -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/. */ + +#include "nsLiteralString.h" +#include "nsTSubstringTuple.h" +#include "gtest/gtest.h" + +namespace TestSubstringTuple { + +static const auto kFooLiteral = u"foo"_ns; + +static const auto kFoo = nsCString("foo"); +static const auto kBar = nsCString("bar"); +static const auto kBaz = nsCString("baz"); + +// The test must be done in a macro to ensure that tuple is always a temporary. +#define DO_SUBSTRING_TUPLE_TEST(tuple, dependentString, expectedLength, \ + expectedDependency) \ + const auto length = (tuple).Length(); \ + const auto isDependentOn = (tuple).IsDependentOn( \ + dependentString.BeginReading(), dependentString.EndReading()); \ + \ + EXPECT_EQ((expectedLength), length); \ + EXPECT_EQ((expectedDependency), isDependentOn); \ + \ + const auto [combinedIsDependentOn, combinedLength] = \ + (tuple).IsDependentOnWithLength(dependentString.BeginReading(), \ + dependentString.EndReading()); \ + \ + EXPECT_EQ(length, combinedLength); \ + EXPECT_EQ(isDependentOn, combinedIsDependentOn); + +TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_Literal_ZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(u""_ns + u""_ns, kFooLiteral, 0u, false); } + +TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_Literal_NonZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(u"bar"_ns + u"baz"_ns, kFooLiteral, 6u, false); } + +TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_NonZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz, kFoo, 6u, false); } + +TEST(SubstringTuple, + IsDependentOnAndLength_NonDependent_NonZeroLength_ThreeParts) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz + kBar, kFoo, 9u, false); } + +TEST(SubstringTuple, IsDependentOnAndLength_Dependent_NonZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz, kBar, 6u, true); } + +TEST(SubstringTuple, IsDependentOnAndLength_Dependent_NonZeroLength_ThreeParts) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz + kFoo, kBar, 9u, true); } + +} // namespace TestSubstringTuple diff --git a/xpcom/tests/gtest/TestSynchronization.cpp b/xpcom/tests/gtest/TestSynchronization.cpp new file mode 100644 index 0000000000..c4a1f5c99e --- /dev/null +++ b/xpcom/tests/gtest/TestSynchronization.cpp @@ -0,0 +1,324 @@ +/* -*- 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/CondVar.h" +#include "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +static PRThread* spawn(void (*run)(void*), void* arg) { + return PR_CreateThread(PR_SYSTEM_THREAD, run, arg, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); +} + +//----------------------------------------------------------------------------- +// Sanity check: tests that can be done on a single thread +// +TEST(Synchronization, Sanity) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + Mutex lock("sanity::lock"); + lock.Lock(); + lock.AssertCurrentThreadOwns(); + lock.Unlock(); + + { + MutexAutoLock autolock(lock); + lock.AssertCurrentThreadOwns(); + } + + lock.Lock(); + lock.AssertCurrentThreadOwns(); + { MutexAutoUnlock autounlock(lock); } + lock.AssertCurrentThreadOwns(); + lock.Unlock(); + + ReentrantMonitor mon("sanity::monitor"); + mon.Enter(); + mon.AssertCurrentThreadIn(); + mon.Enter(); + mon.AssertCurrentThreadIn(); + mon.Exit(); + mon.AssertCurrentThreadIn(); + mon.Exit(); + + { + ReentrantMonitorAutoEnter automon(mon); + mon.AssertCurrentThreadIn(); + } +} + +//----------------------------------------------------------------------------- +// Mutex contention tests +// +static Mutex* gLock1; + +static void MutexContention_thread(void* /*arg*/) { + for (int i = 0; i < 100000; ++i) { + gLock1->Lock(); + gLock1->AssertCurrentThreadOwns(); + gLock1->Unlock(); + } +} + +TEST(Synchronization, MutexContention) +{ + gLock1 = new Mutex("lock1"); + // PURPOSELY not checking for OOM. YAY! + + PRThread* t1 = spawn(MutexContention_thread, nullptr); + PRThread* t2 = spawn(MutexContention_thread, nullptr); + PRThread* t3 = spawn(MutexContention_thread, nullptr); + + PR_JoinThread(t1); + PR_JoinThread(t2); + PR_JoinThread(t3); + + delete gLock1; +} + +//----------------------------------------------------------------------------- +// Monitor tests +// +static Monitor* gMon1; + +static void MonitorContention_thread(void* /*arg*/) { + for (int i = 0; i < 100000; ++i) { + gMon1->Lock(); + gMon1->AssertCurrentThreadOwns(); + gMon1->Unlock(); + } +} + +TEST(Synchronization, MonitorContention) +{ + gMon1 = new Monitor("mon1"); + + PRThread* t1 = spawn(MonitorContention_thread, nullptr); + PRThread* t2 = spawn(MonitorContention_thread, nullptr); + PRThread* t3 = spawn(MonitorContention_thread, nullptr); + + PR_JoinThread(t1); + PR_JoinThread(t2); + PR_JoinThread(t3); + + delete gMon1; +} + +static ReentrantMonitor* gMon2; + +static void MonitorContention2_thread(void* /*arg*/) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + for (int i = 0; i < 100000; ++i) { + gMon2->Enter(); + gMon2->AssertCurrentThreadIn(); + { + gMon2->Enter(); + gMon2->AssertCurrentThreadIn(); + gMon2->Exit(); + } + gMon2->AssertCurrentThreadIn(); + gMon2->Exit(); + } +} + +TEST(Synchronization, MonitorContention2) +{ + gMon2 = new ReentrantMonitor("mon1"); + + PRThread* t1 = spawn(MonitorContention2_thread, nullptr); + PRThread* t2 = spawn(MonitorContention2_thread, nullptr); + PRThread* t3 = spawn(MonitorContention2_thread, nullptr); + + PR_JoinThread(t1); + PR_JoinThread(t2); + PR_JoinThread(t3); + + delete gMon2; +} + +static ReentrantMonitor* gMon3; +static int32_t gMonFirst; + +static void MonitorSyncSanity_thread(void* /*arg*/) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + gMon3->Enter(); + gMon3->AssertCurrentThreadIn(); + if (gMonFirst) { + gMonFirst = 0; + gMon3->Wait(); + gMon3->Enter(); + } else { + gMon3->Notify(); + gMon3->Enter(); + } + gMon3->AssertCurrentThreadIn(); + gMon3->Exit(); + gMon3->AssertCurrentThreadIn(); + gMon3->Exit(); +} + +TEST(Synchronization, MonitorSyncSanity) +{ + gMon3 = new ReentrantMonitor("monitor::syncsanity"); + + for (int32_t i = 0; i < 10000; ++i) { + gMonFirst = 1; + PRThread* ping = spawn(MonitorSyncSanity_thread, nullptr); + PRThread* pong = spawn(MonitorSyncSanity_thread, nullptr); + PR_JoinThread(ping); + PR_JoinThread(pong); + } + + delete gMon3; +} + +//----------------------------------------------------------------------------- +// Condvar tests +// +static Mutex* gCvlock1; +static CondVar* gCv1; +static int32_t gCvFirst; + +static void CondVarSanity_thread(void* /*arg*/) { + gCvlock1->Lock(); + gCvlock1->AssertCurrentThreadOwns(); + if (gCvFirst) { + gCvFirst = 0; + gCv1->Wait(); + } else { + gCv1->Notify(); + } + gCvlock1->AssertCurrentThreadOwns(); + gCvlock1->Unlock(); +} + +TEST(Synchronization, CondVarSanity) +{ + gCvlock1 = new Mutex("cvlock1"); + gCv1 = new CondVar(*gCvlock1, "cvlock1"); + + for (int32_t i = 0; i < 10000; ++i) { + gCvFirst = 1; + PRThread* ping = spawn(CondVarSanity_thread, nullptr); + PRThread* pong = spawn(CondVarSanity_thread, nullptr); + PR_JoinThread(ping); + PR_JoinThread(pong); + } + + delete gCv1; + delete gCvlock1; +} + +//----------------------------------------------------------------------------- +// AutoLock tests +// +TEST(Synchronization, AutoLock) +{ + Mutex l1 MOZ_UNANNOTATED("autolock"); + MutexAutoLock autol1(l1); + + l1.AssertCurrentThreadOwns(); + + { + Mutex l2 MOZ_UNANNOTATED("autolock2"); + MutexAutoLock autol2(l2); + + l1.AssertCurrentThreadOwns(); + l2.AssertCurrentThreadOwns(); + } + + l1.AssertCurrentThreadOwns(); +} + +//----------------------------------------------------------------------------- +// AutoTryLock tests +// +// The thread owns assertions make mutex analysis throw spurious warnings +TEST(Synchronization, AutoTryLock) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + Mutex l1 MOZ_UNANNOTATED("autotrylock"); + MutexAutoTryLock autol1(l1); + + EXPECT_TRUE(autol1); + l1.AssertCurrentThreadOwns(); + + MutexAutoTryLock autol2(l1); + + EXPECT_TRUE(autol1); + EXPECT_FALSE(autol2); + l1.AssertCurrentThreadOwns(); + + { + Mutex l2 MOZ_UNANNOTATED("autotrylock2"); + MutexAutoTryLock autol3(l2); + + EXPECT_TRUE(autol3); + l1.AssertCurrentThreadOwns(); + l2.AssertCurrentThreadOwns(); + } + + l1.AssertCurrentThreadOwns(); +} + +//----------------------------------------------------------------------------- +// AutoUnlock tests +// +TEST(Synchronization, AutoUnlock) +{ + Mutex l1 MOZ_UNANNOTATED("autounlock"); + Mutex l2 MOZ_UNANNOTATED("autounlock2"); + + l1.Lock(); + l1.AssertCurrentThreadOwns(); + + { + MutexAutoUnlock autol1(l1); + { + l2.Lock(); + l2.AssertCurrentThreadOwns(); + + MutexAutoUnlock autol2(l2); + } + l2.AssertCurrentThreadOwns(); + l2.Unlock(); + } + l1.AssertCurrentThreadOwns(); + + l1.Unlock(); +} + +//----------------------------------------------------------------------------- +// AutoMonitor tests +// +TEST(Synchronization, AutoMonitor) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + ReentrantMonitor m1("automonitor"); + ReentrantMonitor m2("automonitor2"); + + m1.Enter(); + m1.AssertCurrentThreadIn(); + { + ReentrantMonitorAutoEnter autom1(m1); + m1.AssertCurrentThreadIn(); + + m2.Enter(); + m2.AssertCurrentThreadIn(); + { + ReentrantMonitorAutoEnter autom2(m2); + m1.AssertCurrentThreadIn(); + m2.AssertCurrentThreadIn(); + } + m2.AssertCurrentThreadIn(); + m2.Exit(); + + m1.AssertCurrentThreadIn(); + } + m1.AssertCurrentThreadIn(); + m1.Exit(); +} diff --git a/xpcom/tests/gtest/TestTArray.cpp b/xpcom/tests/gtest/TestTArray.cpp new file mode 100644 index 0000000000..54aea92dff --- /dev/null +++ b/xpcom/tests/gtest/TestTArray.cpp @@ -0,0 +1,1042 @@ +/* -*- 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 "nsTArray.h" +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/RefPtr.h" +#include "nsTHashMap.h" + +using namespace mozilla; + +namespace TestTArray { + +struct Copyable { + Copyable() : mDestructionCounter(nullptr) {} + + ~Copyable() { + if (mDestructionCounter) { + (*mDestructionCounter)++; + } + } + + Copyable(const Copyable&) = default; + Copyable& operator=(const Copyable&) = default; + + uint32_t* mDestructionCounter; +}; + +struct Movable { + Movable() : mDestructionCounter(nullptr) {} + + ~Movable() { + if (mDestructionCounter) { + (*mDestructionCounter)++; + } + } + + Movable(Movable&& aOther) : mDestructionCounter(aOther.mDestructionCounter) { + aOther.mDestructionCounter = nullptr; + } + + uint32_t* mDestructionCounter; +}; + +} // namespace TestTArray + +template <> +struct nsTArray_RelocationStrategy<TestTArray::Copyable> { + using Type = nsTArray_RelocateUsingMoveConstructor<TestTArray::Copyable>; +}; + +template <> +struct nsTArray_RelocationStrategy<TestTArray::Movable> { + using Type = nsTArray_RelocateUsingMoveConstructor<TestTArray::Movable>; +}; + +namespace TestTArray { + +constexpr int dummyArrayData[] = {4, 1, 2, 8}; + +static const nsTArray<int>& DummyArray() { + static nsTArray<int> sArray; + if (sArray.IsEmpty()) { + sArray.AppendElements(dummyArrayData, ArrayLength(dummyArrayData)); + } + return sArray; +} + +// This returns an invalid nsTArray with a huge length in order to test that +// fallible operations actually fail. +#ifdef DEBUG +static const nsTArray<int>& FakeHugeArray() { + static nsTArray<int> sArray; + if (sArray.IsEmpty()) { + sArray.AppendElement(); + ((nsTArrayHeader*)sArray.DebugGetHeader())->mLength = UINT32_MAX; + } + return sArray; +} +#endif + +TEST(TArray, int_AppendElements_PlainArray) +{ + nsTArray<int> array; + + int* ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData)); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + + ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData)); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); +} + +TEST(TArray, int_AppendElements_PlainArray_Fallible) +{ + nsTArray<int> array; + + int* ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData), + fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + + ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData), + fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); +} + +TEST(TArray, int_AppendElements_TArray_Copy) +{ + nsTArray<int> array; + + const nsTArray<int> temp(DummyArray().Clone()); + int* ptr = array.AppendElements(temp); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_FALSE(temp.IsEmpty()); + + ptr = array.AppendElements(temp); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_FALSE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_TArray_Copy_Fallible) +{ + nsTArray<int> array; + + const nsTArray<int> temp(DummyArray().Clone()); + int* ptr = array.AppendElements(temp, fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_FALSE(temp.IsEmpty()); + + ptr = array.AppendElements(temp, fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_FALSE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_TArray_Rvalue) +{ + nsTArray<int> array; + + nsTArray<int> temp(DummyArray().Clone()); + int* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyArray().Clone(); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_TArray_Rvalue_Fallible) +{ + nsTArray<int> array; + + nsTArray<int> temp(DummyArray().Clone()); + int* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyArray().Clone(); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_FallibleArray_Rvalue) +{ + nsTArray<int> array; + + FallibleTArray<int> temp; + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + int* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_FallibleArray_Rvalue_Fallible) +{ + nsTArray<int> array; + + FallibleTArray<int> temp; + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + int* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, AppendElementsSpan) +{ + nsTArray<int> array; + + nsTArray<int> temp(DummyArray().Clone()); + Span<int> span = temp; + array.AppendElements(span); + ASSERT_EQ(DummyArray(), array); + + Span<const int> constSpan = temp; + array.AppendElements(constSpan); + nsTArray<int> expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); +} + +TEST(TArray, int_AppendElement_NoElementArg) +{ + nsTArray<int> array; + array.AppendElement(); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, int_AppendElement_NoElementArg_Fallible) +{ + nsTArray<int> array; + ASSERT_NE(nullptr, array.AppendElement(fallible)); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, int_AppendElement_NoElementArg_Address) +{ + nsTArray<int> array; + *array.AppendElement() = 42; + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +TEST(TArray, int_AppendElement_NoElementArg_Fallible_Address) +{ + nsTArray<int> array; + *array.AppendElement(fallible) = 42; + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +TEST(TArray, int_AppendElement_ElementArg) +{ + nsTArray<int> array; + array.AppendElement(42); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +TEST(TArray, int_AppendElement_ElementArg_Fallible) +{ + nsTArray<int> array; + ASSERT_NE(nullptr, array.AppendElement(42, fallible)); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +constexpr size_t dummyMovableArrayLength = 4; +uint32_t dummyMovableArrayDestructorCounter; + +static nsTArray<Movable> DummyMovableArray() { + nsTArray<Movable> res; + res.SetLength(dummyMovableArrayLength); + for (size_t i = 0; i < dummyMovableArrayLength; ++i) { + res[i].mDestructionCounter = &dummyMovableArrayDestructorCounter; + } + return res; +} + +TEST(TArray, Movable_AppendElements_TArray_Rvalue) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray<Movable> array; + + nsTArray<Movable> temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElements_TArray_Rvalue_Fallible) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray<Movable> array; + + nsTArray<Movable> temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElements_FallibleArray_Rvalue) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray<Movable> array; + + FallibleTArray<Movable> temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElements_FallibleArray_Rvalue_Fallible) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray<Movable> array; + + FallibleTArray<Movable> temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_NoElementArg) +{ + nsTArray<Movable> array; + array.AppendElement(); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, Movable_AppendElement_NoElementArg_Fallible) +{ + nsTArray<Movable> array; + ASSERT_NE(nullptr, array.AppendElement(fallible)); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, Movable_AppendElement_NoElementArg_Address) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray<Movable> array; + array.AppendElement()->mDestructionCounter = + &dummyMovableArrayDestructorCounter; + + ASSERT_EQ(1u, array.Length()); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_NoElementArg_Fallible_Address) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray<Movable> array; + array.AppendElement(fallible)->mDestructionCounter = + &dummyMovableArrayDestructorCounter; + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(&dummyMovableArrayDestructorCounter, + array[0].mDestructionCounter); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_ElementArg) +{ + dummyMovableArrayDestructorCounter = 0; + Movable movable; + movable.mDestructionCounter = &dummyMovableArrayDestructorCounter; + { + nsTArray<Movable> array; + array.AppendElement(std::move(movable)); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(&dummyMovableArrayDestructorCounter, + array[0].mDestructionCounter); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_ElementArg_Fallible) +{ + dummyMovableArrayDestructorCounter = 0; + Movable movable; + movable.mDestructionCounter = &dummyMovableArrayDestructorCounter; + { + nsTArray<Movable> array; + ASSERT_NE(nullptr, array.AppendElement(std::move(movable), fallible)); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(&dummyMovableArrayDestructorCounter, + array[0].mDestructionCounter); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, int_Assign) +{ + nsTArray<int> array; + array.Assign(DummyArray()); + ASSERT_EQ(DummyArray(), array); + + ASSERT_TRUE(array.Assign(DummyArray(), fallible)); + ASSERT_EQ(DummyArray(), array); + +#ifdef DEBUG + ASSERT_FALSE(array.Assign(FakeHugeArray(), fallible)); +#endif + + nsTArray<int> array2; + array2.Assign(std::move(array)); + ASSERT_TRUE(array.IsEmpty()); + ASSERT_EQ(DummyArray(), array2); +} + +TEST(TArray, int_Assign_FromEmpty_ToNonEmpty) +{ + nsTArray<int> array; + array.AppendElement(42); + + const nsTArray<int> empty; + array.Assign(empty); + + ASSERT_TRUE(array.IsEmpty()); +} + +TEST(TArray, int_Assign_FromEmpty_ToNonEmpty_Fallible) +{ + nsTArray<int> array; + array.AppendElement(42); + + const nsTArray<int> empty; + ASSERT_TRUE(array.Assign(empty, fallible)); + + ASSERT_TRUE(array.IsEmpty()); +} + +TEST(TArray, int_AssignmentOperatorSelfAssignment) +{ + CopyableTArray<int> array; + array = DummyArray(); + + array = *&array; + ASSERT_EQ(DummyArray(), array); + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-move" +#endif + array = std::move(array); // self-move + ASSERT_EQ(DummyArray(), array); +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +} + +TEST(TArray, Movable_CopyOverlappingForwards) +{ + const size_t rangeLength = 8; + const size_t initialLength = 2 * rangeLength; + uint32_t destructionCounters[initialLength]; + nsTArray<Movable> array; + array.AppendElements(initialLength); + + for (uint32_t i = 0; i < initialLength; ++i) { + destructionCounters[i] = 0; + } + for (uint32_t i = 0; i < initialLength; ++i) { + array[i].mDestructionCounter = &destructionCounters[i]; + } + + const size_t removedLength = rangeLength / 2; + array.RemoveElementsAt(0, removedLength); + + for (uint32_t i = 0; i < removedLength; ++i) { + ASSERT_EQ(destructionCounters[i], 1u); + } + for (uint32_t i = removedLength; i < initialLength; ++i) { + ASSERT_EQ(destructionCounters[i], 0u); + } +} + +// The code to copy overlapping regions had a bug in that it wouldn't correctly +// destroy all over the source elements being copied. +TEST(TArray, Copyable_CopyOverlappingBackwards) +{ + const size_t rangeLength = 8; + const size_t initialLength = 2 * rangeLength; + uint32_t destructionCounters[initialLength]; + nsTArray<Copyable> array; + array.SetCapacity(3 * rangeLength); + array.AppendElements(initialLength); + // To tickle the bug, we need to copy a source region: + // + // ..XXXXX.. + // + // such that it overlaps the destination region: + // + // ....XXXXX + // + // so we are forced to copy back-to-front to ensure correct behavior. + // The easiest way to do that is to call InsertElementsAt, which will force + // the desired kind of shift. + for (uint32_t i = 0; i < initialLength; ++i) { + destructionCounters[i] = 0; + } + for (uint32_t i = 0; i < initialLength; ++i) { + array[i].mDestructionCounter = &destructionCounters[i]; + } + + array.InsertElementsAt(0, rangeLength); + + for (uint32_t i = 0; i < initialLength; ++i) { + ASSERT_EQ(destructionCounters[i], 1u); + } +} + +namespace { + +class E { + public: + E() : mA(-1), mB(-2) { constructCount++; } + E(int a, int b) : mA(a), mB(b) { constructCount++; } + E(E&& aRhs) : mA(aRhs.mA), mB(aRhs.mB) { + aRhs.mA = 0; + aRhs.mB = 0; + moveCount++; + } + + E& operator=(E&& aRhs) { + mA = aRhs.mA; + aRhs.mA = 0; + mB = aRhs.mB; + aRhs.mB = 0; + moveCount++; + return *this; + } + + int a() const { return mA; } + int b() const { return mB; } + + E(const E&) = delete; + E& operator=(const E&) = delete; + + static size_t constructCount; + static size_t moveCount; + + private: + int mA; + int mB; +}; + +size_t E::constructCount = 0; +size_t E::moveCount = 0; + +} // namespace + +TEST(TArray, Emplace) +{ + nsTArray<E> array; + array.SetCapacity(20); + + ASSERT_EQ(array.Length(), 0u); + + for (int i = 0; i < 10; i++) { + E s(i, i * i); + array.AppendElement(std::move(s)); + } + + ASSERT_EQ(array.Length(), 10u); + ASSERT_EQ(E::constructCount, 10u); + ASSERT_EQ(E::moveCount, 10u); + + for (int i = 10; i < 20; i++) { + array.EmplaceBack(i, i * i); + } + + ASSERT_EQ(array.Length(), 20u); + ASSERT_EQ(E::constructCount, 20u); + ASSERT_EQ(E::moveCount, 10u); + + for (int i = 0; i < 20; i++) { + ASSERT_EQ(array[i].a(), i); + ASSERT_EQ(array[i].b(), i * i); + } + + array.EmplaceBack(); + + ASSERT_EQ(array.Length(), 21u); + ASSERT_EQ(E::constructCount, 21u); + ASSERT_EQ(E::moveCount, 10u); + + ASSERT_EQ(array[20].a(), -1); + ASSERT_EQ(array[20].b(), -2); +} + +TEST(TArray, UnorderedRemoveElements) +{ + // When removing an element from the end of the array, it can be removed in + // place, by destroying it and decrementing the length. + // + // [ 1, 2, 3 ] => [ 1, 2 ] + // ^ + { + nsTArray<int> array{1, 2, 3}; + array.UnorderedRemoveElementAt(2); + + nsTArray<int> goal{1, 2}; + ASSERT_EQ(array, goal); + } + + // When removing any other single element, it is removed by swapping it with + // the last element, and then decrementing the length as before. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ] + // ^ + { + nsTArray<int> array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementAt(1); + + nsTArray<int> goal{1, 6, 3, 4, 5}; + ASSERT_EQ(array, goal); + } + + // This method also supports efficiently removing a range of elements. If they + // are at the end, then they can all be removed like in the one element case. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ] + // ^--------^ + { + nsTArray<int> array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementsAt(2, 4); + + nsTArray<int> goal{1, 2}; + ASSERT_EQ(array, goal); + } + + // If more elements are removed than exist after the removed section, the + // remaining elements will be shifted down like in a normal removal. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ] + // ^--------^ + { + nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8}; + array.UnorderedRemoveElementsAt(2, 4); + + nsTArray<int> goal{1, 2, 7, 8}; + ASSERT_EQ(array, goal); + } + + // And if fewer elements are removed than exist after the removed section, + // elements will be moved from the end of the array to fill the vacated space. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ] + // ^--^ + { + nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8}; + array.UnorderedRemoveElementsAt(1, 2); + + nsTArray<int> goal{1, 7, 8, 4, 5, 6}; + ASSERT_EQ(array, goal); + } + + // We should do the right thing if we drain the entire array. + { + nsTArray<int> array{1, 2, 3, 4, 5}; + array.UnorderedRemoveElementsAt(0, 5); + + nsTArray<int> goal{}; + ASSERT_EQ(array, goal); + } + + { + nsTArray<int> array{1}; + array.UnorderedRemoveElementAt(0); + + nsTArray<int> goal{}; + ASSERT_EQ(array, goal); + } + + // We should do the right thing if we remove the same number of elements that + // we have remaining. + { + nsTArray<int> array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementsAt(2, 2); + + nsTArray<int> goal{1, 2, 5, 6}; + ASSERT_EQ(array, goal); + } + + { + nsTArray<int> array{1, 2, 3}; + array.UnorderedRemoveElementAt(1); + + nsTArray<int> goal{1, 3}; + ASSERT_EQ(array, goal); + } + + // We should be able to remove elements from the front without issue. + { + nsTArray<int> array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementsAt(0, 2); + + nsTArray<int> goal{5, 6, 3, 4}; + ASSERT_EQ(array, goal); + } + + { + nsTArray<int> array{1, 2, 3, 4}; + array.UnorderedRemoveElementAt(0); + + nsTArray<int> goal{4, 2, 3}; + ASSERT_EQ(array, goal); + } +} + +TEST(TArray, RemoveFromEnd) +{ + { + nsTArray<int> array{1, 2, 3, 4}; + ASSERT_EQ(array.PopLastElement(), 4); + array.RemoveLastElement(); + ASSERT_EQ(array.PopLastElement(), 2); + array.RemoveLastElement(); + ASSERT_TRUE(array.IsEmpty()); + } +} + +TEST(TArray, ConvertIteratorToConstIterator) +{ + nsTArray<int> array{1, 2, 3, 4}; + + nsTArray<int>::const_iterator it = array.begin(); + ASSERT_EQ(array.cbegin(), it); +} + +TEST(TArray, RemoveElementAt_ByIterator) +{ + nsTArray<int> array{1, 2, 3, 4}; + const auto it = std::find(array.begin(), array.end(), 3); + const auto itAfter = array.RemoveElementAt(it); + + // Based on the implementation of the iterator, we could compare it and + // itAfter, but we should not rely on such implementation details. + + ASSERT_EQ(2, std::distance(array.cbegin(), itAfter)); + const nsTArray<int> expected{1, 2, 4}; + ASSERT_EQ(expected, array); +} + +TEST(TArray, RemoveElementsRange_ByIterator) +{ + nsTArray<int> array{1, 2, 3, 4}; + const auto it = std::find(array.begin(), array.end(), 3); + const auto itAfter = array.RemoveElementsRange(it, array.end()); + + // Based on the implementation of the iterator, we could compare it and + // itAfter, but we should not rely on such implementation details. + + ASSERT_EQ(2, std::distance(array.cbegin(), itAfter)); + const nsTArray<int> expected{1, 2}; + ASSERT_EQ(expected, array); +} + +TEST(TArray, RemoveLastElements_None) +{ + const nsTArray<int> original{1, 2, 3, 4}; + nsTArray<int> array = original.Clone(); + array.RemoveLastElements(0); + + ASSERT_EQ(original, array); +} + +TEST(TArray, RemoveLastElements_Empty_None) +{ + nsTArray<int> array; + array.RemoveLastElements(0); + + ASSERT_EQ(0u, array.Length()); +} + +TEST(TArray, RemoveLastElements_All) +{ + nsTArray<int> array{1, 2, 3, 4}; + array.RemoveLastElements(4); + + ASSERT_EQ(0u, array.Length()); +} + +TEST(TArray, RemoveLastElements_One) +{ + nsTArray<int> array{1, 2, 3, 4}; + array.RemoveLastElements(1); + + ASSERT_EQ((nsTArray<int>{1, 2, 3}), array); +} + +static_assert(std::is_copy_assignable<decltype(MakeBackInserter( + std::declval<nsTArray<int>&>()))>::value, + "output iteraror must be copy-assignable"); +static_assert(std::is_copy_constructible<decltype(MakeBackInserter( + std::declval<nsTArray<int>&>()))>::value, + "output iterator must be copy-constructible"); + +TEST(TArray, MakeBackInserter) +{ + const std::vector<int> src{1, 2, 3, 4}; + nsTArray<int> dst; + + std::copy(src.begin(), src.end(), MakeBackInserter(dst)); + + const nsTArray<int> expected{1, 2, 3, 4}; + ASSERT_EQ(expected, dst); +} + +TEST(TArray, MakeBackInserter_Move) +{ + uint32_t destructionCounter = 0; + + { + std::vector<Movable> src(1); + src[0].mDestructionCounter = &destructionCounter; + + nsTArray<Movable> dst; + + std::copy(std::make_move_iterator(src.begin()), + std::make_move_iterator(src.end()), MakeBackInserter(dst)); + + ASSERT_EQ(1u, dst.Length()); + ASSERT_EQ(0u, destructionCounter); + } + + ASSERT_EQ(1u, destructionCounter); +} + +TEST(TArray, ConvertToSpan) +{ + nsTArray<int> arr = {1, 2, 3, 4, 5}; + + // from const + { + const auto& constArrRef = arr; + + auto span = Span{constArrRef}; + static_assert(std::is_same_v<decltype(span), Span<const int>>); + } + + // from non-const + { + auto span = Span{arr}; + static_assert(std::is_same_v<decltype(span), Span<int>>); + } +} + +// This should compile: +struct RefCounted; + +class Foo { + ~Foo(); // Intentionally out of line + + nsTArray<RefPtr<RefCounted>> mArray; + + const RefCounted* GetFirst() const { return mArray.SafeElementAt(0); } +}; + +TEST(TArray, StableSort) +{ + const nsTArray<std::pair<int, int>> expected = { + std::pair(1, 9), std::pair(1, 8), std::pair(1, 7), std::pair(2, 0), + std::pair(3, 0)}; + nsTArray<std::pair<int, int>> array = {std::pair(1, 9), std::pair(2, 0), + std::pair(1, 8), std::pair(3, 0), + std::pair(1, 7)}; + + array.StableSort([](std::pair<int, int> left, std::pair<int, int> right) { + return left.first - right.first; + }); + + EXPECT_EQ(expected, array); +} + +TEST(TArray, ToArray) +{ + const auto src = std::array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + nsTArray<int> keys = ToArray(src); + keys.Sort(); + + EXPECT_EQ((nsTArray<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +// Test this to make sure this properly uses ADL. +TEST(TArray, ToArray_HashMap) +{ + nsTHashMap<uint32_t, uint64_t> src; + + for (uint32_t i = 0; i < 10; ++i) { + src.InsertOrUpdate(i, i); + } + + nsTArray<uint32_t> keys = ToArray(src.Keys()); + keys.Sort(); + + EXPECT_EQ((nsTArray<uint32_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +TEST(TArray, ToTArray) +{ + const auto src = std::array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + auto keys = ToTArray<AutoTArray<uint64_t, 10>>(src); + keys.Sort(); + + static_assert(std::is_same_v<decltype(keys), AutoTArray<uint64_t, 10>>); + + EXPECT_EQ((nsTArray<uint64_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +TEST(TArray, RemoveElementsBy) +{ + // Removing elements returns the correct number of removed elements. + { + nsTArray<int> array{8, 1, 1, 3, 3, 5, 2, 3}; + auto removed = array.RemoveElementsBy([](int i) { return i == 3; }); + EXPECT_EQ(removed, 3u); + + nsTArray<int> goal{8, 1, 1, 5, 2}; + EXPECT_EQ(array, goal); + } + + // The check is called in order. + { + int index = 0; + nsTArray<int> array{0, 1, 2, 3, 4, 5}; + auto removed = array.RemoveElementsBy([&](int i) { + EXPECT_EQ(index, i); + index++; + return i == 3; + }); + EXPECT_EQ(removed, 1u); + + nsTArray<int> goal{0, 1, 2, 4, 5}; + EXPECT_EQ(array, goal); + } + + // Removing nothing works + { + nsTArray<int> array{0, 1, 2, 3, 4}; + auto removed = array.RemoveElementsBy([](int) { return false; }); + EXPECT_EQ(removed, 0u); + + nsTArray<int> goal{0, 1, 2, 3, 4}; + EXPECT_EQ(array, goal); + } + + // Removing everything works + { + nsTArray<int> array{0, 1, 2, 3, 4}; + auto removed = array.RemoveElementsBy([](int) { return true; }); + EXPECT_EQ(removed, 5u); + + nsTArray<int> goal{}; + EXPECT_EQ(array, goal); + } +} + +} // namespace TestTArray diff --git a/xpcom/tests/gtest/TestTArray2.cpp b/xpcom/tests/gtest/TestTArray2.cpp new file mode 100644 index 0000000000..2ccf7df4fa --- /dev/null +++ b/xpcom/tests/gtest/TestTArray2.cpp @@ -0,0 +1,1524 @@ +/* -*- 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 "mozilla/ArrayUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/TimeStamp.h" + +#include <stdlib.h> +#include <stdio.h> +#include <iostream> +#include "nsTArray.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "nsIFile.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using namespace mozilla; + +namespace TestTArray { + +// Define this so we can use test_basic_array in test_comptr_array +template <class T> +inline bool operator<(const nsCOMPtr<T>& lhs, const nsCOMPtr<T>& rhs) { + return lhs.get() < rhs.get(); +} + +//---- + +template <class ElementType> +static bool test_basic_array(ElementType* data, size_t dataLen, + const ElementType& extra) { + CopyableTArray<ElementType> ary; + const nsTArray<ElementType>& cary = ary; + + ary.AppendElements(data, dataLen); + if (ary.Length() != dataLen) { + return false; + } + if (!(ary == ary)) { + return false; + } + size_t i; + for (i = 0; i < ary.Length(); ++i) { + if (ary[i] != data[i]) return false; + } + for (i = 0; i < ary.Length(); ++i) { + if (ary.SafeElementAt(i, extra) != data[i]) return false; + } + if (ary.SafeElementAt(ary.Length(), extra) != extra || + ary.SafeElementAt(ary.Length() * 10, extra) != extra) + return false; + // ensure sort results in ascending order + ary.Sort(); + size_t j = 0, k = ary.IndexOfFirstElementGt(extra); + if (k != 0 && ary[k - 1] == extra) return false; + for (i = 0; i < ary.Length(); ++i) { + k = ary.IndexOfFirstElementGt(ary[i]); + if (k == 0 || ary[k - 1] != ary[i]) return false; + if (k < j) return false; + j = k; + } + for (i = ary.Length(); --i;) { + if (ary[i] < ary[i - 1]) return false; + if (ary[i] == ary[i - 1]) ary.RemoveElementAt(i); + } + if (!(ary == ary)) { + return false; + } + for (i = 0; i < ary.Length(); ++i) { + if (ary.BinaryIndexOf(ary[i]) != i) return false; + } + if (ary.BinaryIndexOf(extra) != ary.NoIndex) return false; + size_t oldLen = ary.Length(); + ary.RemoveElement(data[dataLen / 2]); + if (ary.Length() != (oldLen - 1)) return false; + if (!(ary == ary)) return false; + + if (ary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](size_t) { return true; }, []() { return false; })) + return false; + // On a non-const array, ApplyIf's first lambda may use either const or non- + // const element types. + if (ary.ApplyIf( + extra, [](ElementType&) { return true; }, []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](const ElementType&) { return true; }, + []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](size_t, ElementType&) { return true; }, + []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](size_t, const ElementType&) { return true; }, + []() { return false; })) + return false; + + if (cary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + if (cary.ApplyIf( + extra, [](size_t) { return true; }, []() { return false; })) + // On a const array, ApplyIf's first lambda must only use const element + // types. + if (cary.ApplyIf( + extra, [](const ElementType&) { return true; }, + []() { return false; })) + if (cary.ApplyIf( + extra, [](size_t, const ElementType&) { return true; }, + []() { return false; })) + return false; + + size_t index = ary.Length() / 2; + ary.InsertElementAt(index, extra); + if (!(ary == ary)) return false; + if (ary[index] != extra) return false; + if (ary.IndexOf(extra) == ary.NoIndex) return false; + if (ary.LastIndexOf(extra) == ary.NoIndex) return false; + // ensure proper searching + if (ary.IndexOf(extra) > ary.LastIndexOf(extra)) return false; + if (ary.IndexOf(extra, index) != ary.LastIndexOf(extra, index)) return false; + if (!ary.ApplyIf( + extra, + [&](size_t i, const ElementType& e) { + return i == index && e == extra; + }, + []() { return false; })) + return false; + if (!cary.ApplyIf( + extra, + [&](size_t i, const ElementType& e) { + return i == index && e == extra; + }, + []() { return false; })) + return false; + + nsTArray<ElementType> copy(ary.Clone()); + if (!(ary == copy)) return false; + for (i = 0; i < copy.Length(); ++i) { + if (ary[i] != copy[i]) return false; + } + ary.AppendElements(copy); + size_t cap = ary.Capacity(); + ary.RemoveElementsAt(copy.Length(), copy.Length()); + ary.Compact(); + if (ary.Capacity() == cap) return false; + + ary.Clear(); + if (ary.IndexOf(extra) != ary.NoIndex) return false; + if (ary.LastIndexOf(extra) != ary.NoIndex) return false; + if (ary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + return false; + if (cary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + return false; + + ary.Clear(); + if (!ary.IsEmpty()) return false; + if (!(ary == nsTArray<ElementType>())) return false; + if (ary == copy) return false; + if (ary.SafeElementAt(0, extra) != extra || + ary.SafeElementAt(10, extra) != extra) + return false; + + ary = copy; + if (!(ary == copy)) return false; + for (i = 0; i < copy.Length(); ++i) { + if (ary[i] != copy[i]) return false; + } + + ary.InsertElementsAt(0, copy); + if (ary == copy) return false; + ary.RemoveElementsAt(0, copy.Length()); + for (i = 0; i < copy.Length(); ++i) { + if (ary[i] != copy[i]) return false; + } + + // These shouldn't crash! + nsTArray<ElementType> empty; + ary.AppendElements(reinterpret_cast<ElementType*>(0), 0); + ary.AppendElements(empty); + + // See bug 324981 + ary.RemoveElement(extra); + ary.RemoveElement(extra); + + return true; +} + +TEST(TArray, test_int_array) +{ + int data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), int(14))); +} + +TEST(TArray, test_int64_array) +{ + int64_t data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), int64_t(14))); +} + +TEST(TArray, test_char_array) +{ + char data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), char(14))); +} + +TEST(TArray, test_uint32_array) +{ + uint32_t data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), uint32_t(14))); +} + +//---- + +class Object { + public: + Object() : mNum(0) {} + Object(const char* str, uint32_t num) : mStr(str), mNum(num) {} + Object(const Object& other) = default; + ~Object() = default; + + Object& operator=(const Object& other) = default; + + bool operator==(const Object& other) const { + return mStr == other.mStr && mNum == other.mNum; + } + + bool operator<(const Object& other) const { + // sort based on mStr only + return Compare(mStr, other.mStr) < 0; + } + + const char* Str() const { return mStr.get(); } + uint32_t Num() const { return mNum; } + + private: + nsCString mStr; + uint32_t mNum; +}; + +TEST(TArray, test_object_array) +{ + nsTArray<Object> objArray; + const char kdata[] = "hello world"; + size_t i; + for (i = 0; i < ArrayLength(kdata); ++i) { + char x[] = {kdata[i], '\0'}; + objArray.AppendElement(Object(x, i)); + } + for (i = 0; i < ArrayLength(kdata); ++i) { + ASSERT_EQ(objArray[i].Str()[0], kdata[i]); + ASSERT_EQ(objArray[i].Num(), i); + } + objArray.Sort(); + const char ksorted[] = "\0 dehllloorw"; + for (i = 0; i < ArrayLength(kdata) - 1; ++i) { + ASSERT_EQ(objArray[i].Str()[0], ksorted[i]); + } +} + +class Countable { + static int sCount; + + public: + Countable() { sCount++; } + + Countable(const Countable& aOther) { sCount++; } + + static int Count() { return sCount; } +}; + +class Moveable { + static int sCount; + + public: + Moveable() { sCount++; } + + Moveable(const Moveable& aOther) { sCount++; } + + Moveable(Moveable&& aOther) { + // Do not increment sCount + } + + static int Count() { return sCount; } +}; + +class MoveOnly_RelocateUsingMemutils { + public: + MoveOnly_RelocateUsingMemutils() = default; + + MoveOnly_RelocateUsingMemutils(const MoveOnly_RelocateUsingMemutils&) = + delete; + MoveOnly_RelocateUsingMemutils(MoveOnly_RelocateUsingMemutils&&) = default; + + MoveOnly_RelocateUsingMemutils& operator=( + const MoveOnly_RelocateUsingMemutils&) = delete; + MoveOnly_RelocateUsingMemutils& operator=(MoveOnly_RelocateUsingMemutils&&) = + default; +}; + +static_assert( + std::is_move_constructible_v<nsTArray<MoveOnly_RelocateUsingMemutils>>); +static_assert( + std::is_move_assignable_v<nsTArray<MoveOnly_RelocateUsingMemutils>>); +static_assert( + !std::is_copy_constructible_v<nsTArray<MoveOnly_RelocateUsingMemutils>>); +static_assert( + !std::is_copy_assignable_v<nsTArray<MoveOnly_RelocateUsingMemutils>>); + +class MoveOnly_RelocateUsingMoveConstructor { + public: + MoveOnly_RelocateUsingMoveConstructor() = default; + + MoveOnly_RelocateUsingMoveConstructor( + const MoveOnly_RelocateUsingMoveConstructor&) = delete; + MoveOnly_RelocateUsingMoveConstructor( + MoveOnly_RelocateUsingMoveConstructor&&) = default; + + MoveOnly_RelocateUsingMoveConstructor& operator=( + const MoveOnly_RelocateUsingMoveConstructor&) = delete; + MoveOnly_RelocateUsingMoveConstructor& operator=( + MoveOnly_RelocateUsingMoveConstructor&&) = default; +}; +} // namespace TestTArray + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + TestTArray::MoveOnly_RelocateUsingMoveConstructor) + +namespace TestTArray { +static_assert(std::is_move_constructible_v< + nsTArray<MoveOnly_RelocateUsingMoveConstructor>>); +static_assert( + std::is_move_assignable_v<nsTArray<MoveOnly_RelocateUsingMoveConstructor>>); +static_assert(!std::is_copy_constructible_v< + nsTArray<MoveOnly_RelocateUsingMoveConstructor>>); +static_assert(!std::is_copy_assignable_v< + nsTArray<MoveOnly_RelocateUsingMoveConstructor>>); +} // namespace TestTArray + +namespace TestTArray { + +/* static */ +int Countable::sCount = 0; +/* static */ +int Moveable::sCount = 0; + +static nsTArray<int> returns_by_value() { + nsTArray<int> result; + return result; +} + +TEST(TArray, test_return_by_value) +{ + nsTArray<int> result = returns_by_value(); + ASSERT_TRUE(true); // This is just a compilation test. +} + +TEST(TArray, test_move_array) +{ + nsTArray<Countable> countableArray; + uint32_t i; + for (i = 0; i < 4; ++i) { + countableArray.AppendElement(Countable()); + } + + ASSERT_EQ(Countable::Count(), 8); + + const nsTArray<Countable>& constRefCountableArray = countableArray; + + ASSERT_EQ(Countable::Count(), 8); + + nsTArray<Countable> copyCountableArray(constRefCountableArray.Clone()); + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray<Countable>&& moveRefCountableArray = std::move(countableArray); + moveRefCountableArray.Length(); // Make compilers happy. + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray<Countable> movedCountableArray(std::move(countableArray)); + + ASSERT_EQ(Countable::Count(), 12); + + // Test ctor + FallibleTArray<Countable> differentAllocatorCountableArray( + std::move(copyCountableArray)); + // operator= + copyCountableArray = std::move(differentAllocatorCountableArray); + differentAllocatorCountableArray = std::move(copyCountableArray); + // And the other ctor + nsTArray<Countable> copyCountableArray2( + std::move(differentAllocatorCountableArray)); + // with auto + AutoTArray<Countable, 3> autoCountableArray(std::move(copyCountableArray2)); + // operator= + copyCountableArray2 = std::move(autoCountableArray); + // Mix with FallibleTArray + FallibleTArray<Countable> differentAllocatorCountableArray2( + std::move(copyCountableArray2)); + AutoTArray<Countable, 4> autoCountableArray2( + std::move(differentAllocatorCountableArray2)); + differentAllocatorCountableArray2 = std::move(autoCountableArray2); + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray<Moveable> moveableArray; + for (i = 0; i < 4; ++i) { + moveableArray.AppendElement(Moveable()); + } + + ASSERT_EQ(Moveable::Count(), 4); + + const nsTArray<Moveable>& constRefMoveableArray = moveableArray; + + ASSERT_EQ(Moveable::Count(), 4); + + nsTArray<Moveable> copyMoveableArray(constRefMoveableArray.Clone()); + + ASSERT_EQ(Moveable::Count(), 8); + + nsTArray<Moveable>&& moveRefMoveableArray = std::move(moveableArray); + moveRefMoveableArray.Length(); // Make compilers happy. + + ASSERT_EQ(Moveable::Count(), 8); + + nsTArray<Moveable> movedMoveableArray(std::move(moveableArray)); + + ASSERT_EQ(Moveable::Count(), 8); + + // Test ctor + FallibleTArray<Moveable> differentAllocatorMoveableArray( + std::move(copyMoveableArray)); + // operator= + copyMoveableArray = std::move(differentAllocatorMoveableArray); + differentAllocatorMoveableArray = std::move(copyMoveableArray); + // And the other ctor + nsTArray<Moveable> copyMoveableArray2( + std::move(differentAllocatorMoveableArray)); + // with auto + AutoTArray<Moveable, 3> autoMoveableArray(std::move(copyMoveableArray2)); + // operator= + copyMoveableArray2 = std::move(autoMoveableArray); + // Mix with FallibleTArray + FallibleTArray<Moveable> differentAllocatorMoveableArray2( + std::move(copyMoveableArray2)); + AutoTArray<Moveable, 4> autoMoveableArray2( + std::move(differentAllocatorMoveableArray2)); + differentAllocatorMoveableArray2 = std::move(autoMoveableArray2); + + ASSERT_EQ(Moveable::Count(), 8); + + AutoTArray<Moveable, 8> moveableAutoArray; + for (uint32_t i = 0; i < 4; ++i) { + moveableAutoArray.AppendElement(Moveable()); + } + + ASSERT_EQ(Moveable::Count(), 12); + + const AutoTArray<Moveable, 8>& constRefMoveableAutoArray = moveableAutoArray; + + ASSERT_EQ(Moveable::Count(), 12); + + CopyableAutoTArray<Moveable, 8> copyMoveableAutoArray( + constRefMoveableAutoArray); + + ASSERT_EQ(Moveable::Count(), 16); + + AutoTArray<Moveable, 8> movedMoveableAutoArray(std::move(moveableAutoArray)); + + ASSERT_EQ(Moveable::Count(), 16); +} + +template <typename TypeParam> +class TArray_MoveOnlyTest : public ::testing::Test {}; + +TYPED_TEST_SUITE_P(TArray_MoveOnlyTest); + +static constexpr size_t kMoveOnlyTestArrayLength = 4; + +template <typename ArrayType> +static auto MakeMoveOnlyArray() { + ArrayType moveOnlyArray; + for (size_t i = 0; i < kMoveOnlyTestArrayLength; ++i) { + EXPECT_TRUE(moveOnlyArray.AppendElement(typename ArrayType::value_type(), + fallible)); + } + return moveOnlyArray; +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + nsTArray<TypeParam> movedMoveOnlyArray(std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + nsTArray<TypeParam> movedMoveOnlyArray; + movedMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveReAssign) { + nsTArray<TypeParam> movedMoveOnlyArray; + movedMoveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + // Re-assign, to check that move-assign does not only work on an empty array. + movedMoveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + + ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_to_FallibleTArray_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + FallibleTArray<TypeParam> differentAllocatorMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_to_FallibleTArray_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + FallibleTArray<TypeParam> differentAllocatorMoveOnlyArray; + differentAllocatorMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, FallibleTArray_to_nsTArray_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>(); + nsTArray<TypeParam> differentAllocatorMoveOnlyArray(std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, FallibleTArray_to_nsTArray_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>(); + nsTArray<TypeParam> differentAllocatorMoveOnlyArray; + differentAllocatorMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, AutoTArray_AutoStorage_MoveConstruct) { + auto moveOnlyArray = + MakeMoveOnlyArray<AutoTArray<TypeParam, kMoveOnlyTestArrayLength>>(); + AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, AutoTArray_AutoStorage_MoveAssign) { + auto moveOnlyArray = + MakeMoveOnlyArray<AutoTArray<TypeParam, kMoveOnlyTestArrayLength>>(); + AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_AutoStorage_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_AutoStorage_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_HeapStorage_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + AutoTArray<TypeParam, kMoveOnlyTestArrayLength - 1> autoMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_HeapStorage_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>(); + AutoTArray<TypeParam, kMoveOnlyTestArrayLength - 1> autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + FallibleTArray_to_AutoTArray_HeapStorage_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>(); + AutoTArray<TypeParam, 4> autoMoveOnlyArray(std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + FallibleTArray_to_AutoTArray_HeapStorage_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>(); + AutoTArray<TypeParam, 4> autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +REGISTER_TYPED_TEST_SUITE_P( + TArray_MoveOnlyTest, nsTArray_MoveConstruct, nsTArray_MoveAssign, + nsTArray_MoveReAssign, nsTArray_to_FallibleTArray_MoveConstruct, + nsTArray_to_FallibleTArray_MoveAssign, + FallibleTArray_to_nsTArray_MoveConstruct, + FallibleTArray_to_nsTArray_MoveAssign, AutoTArray_AutoStorage_MoveConstruct, + AutoTArray_AutoStorage_MoveAssign, + nsTArray_to_AutoTArray_AutoStorage_MoveConstruct, + nsTArray_to_AutoTArray_AutoStorage_MoveAssign, + nsTArray_to_AutoTArray_HeapStorage_MoveConstruct, + nsTArray_to_AutoTArray_HeapStorage_MoveAssign, + FallibleTArray_to_AutoTArray_HeapStorage_MoveConstruct, + FallibleTArray_to_AutoTArray_HeapStorage_MoveAssign); + +using BothMoveOnlyTypes = + ::testing::Types<MoveOnly_RelocateUsingMemutils, + MoveOnly_RelocateUsingMoveConstructor>; +INSTANTIATE_TYPED_TEST_SUITE_P(InstantiationOf, TArray_MoveOnlyTest, + BothMoveOnlyTypes); + +//---- + +TEST(TArray, test_string_array) +{ + nsTArray<nsCString> strArray; + const char kdata[] = "hello world"; + size_t i; + for (i = 0; i < ArrayLength(kdata); ++i) { + nsCString str; + str.Assign(kdata[i]); + strArray.AppendElement(str); + } + for (i = 0; i < ArrayLength(kdata); ++i) { + ASSERT_EQ(strArray[i].CharAt(0), kdata[i]); + } + + const char kextra[] = "foo bar"; + size_t oldLen = strArray.Length(); + strArray.AppendElement(kextra); + strArray.RemoveElement(kextra); + ASSERT_EQ(oldLen, strArray.Length()); + + ASSERT_EQ(strArray.IndexOf("e"), size_t(1)); + ASSERT_TRUE(strArray.ApplyIf( + "e", [](size_t i, nsCString& s) { return i == 1 && s == "e"; }, + []() { return false; })); + + strArray.Sort(); + const char ksorted[] = "\0 dehllloorw"; + for (i = ArrayLength(kdata); i--;) { + ASSERT_EQ(strArray[i].CharAt(0), ksorted[i]); + if (i > 0 && strArray[i] == strArray[i - 1]) strArray.RemoveElementAt(i); + } + for (i = 0; i < strArray.Length(); ++i) { + ASSERT_EQ(strArray.BinaryIndexOf(strArray[i]), i); + } + auto no_index = strArray.NoIndex; // Fixes gtest compilation error + ASSERT_EQ(strArray.BinaryIndexOf(""_ns), no_index); + + nsCString rawArray[MOZ_ARRAY_LENGTH(kdata) - 1]; + for (i = 0; i < ArrayLength(rawArray); ++i) + rawArray[i].Assign(kdata + i); // substrings of kdata + + ASSERT_TRUE( + test_basic_array(rawArray, ArrayLength(rawArray), nsCString("foopy"))); +} + +//---- + +typedef nsCOMPtr<nsIFile> FilePointer; + +class nsFileNameComparator { + public: + bool Equals(const FilePointer& a, const char* b) const { + nsAutoCString name; + a->GetNativeLeafName(name); + return name.Equals(b); + } +}; + +TEST(TArray, test_comptr_array) +{ + FilePointer tmpDir; + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir)); + ASSERT_TRUE(tmpDir); + const char* kNames[] = {"foo.txt", "bar.html", "baz.gif"}; + nsTArray<FilePointer> fileArray; + size_t i; + for (i = 0; i < ArrayLength(kNames); ++i) { + FilePointer f; + tmpDir->Clone(getter_AddRefs(f)); + ASSERT_TRUE(f); + ASSERT_NS_SUCCEEDED(f->AppendNative(nsDependentCString(kNames[i]))); + fileArray.AppendElement(f); + } + + ASSERT_EQ(fileArray.IndexOf(kNames[1], 0, nsFileNameComparator()), size_t(1)); + ASSERT_TRUE(fileArray.ApplyIf( + kNames[1], 0, nsFileNameComparator(), [](size_t i) { return i == 1; }, + []() { return false; })); + + // It's unclear what 'operator<' means for nsCOMPtr, but whatever... + ASSERT_TRUE( + test_basic_array(fileArray.Elements(), fileArray.Length(), tmpDir)); +} + +//---- + +class RefcountedObject { + public: + RefcountedObject() : rc(0) { val = std::rand(); } + void AddRef() { + MOZ_DIAGNOSTIC_ASSERT(rcchangeallowed); + ++rc; + } + void Release() { + MOZ_DIAGNOSTIC_ASSERT(rcchangeallowed); + if (--rc == 0) delete this; + } + ~RefcountedObject() = default; + + int32_t GetVal() const { return val; } + + static void AllowRCChange() { rcchangeallowed = true; } + static void ForbidRCChange() { rcchangeallowed = false; } + + bool operator<(const RefcountedObject& b) const { + return this->GetVal() < b.GetVal(); + }; + + bool operator==(const RefcountedObject& b) const { + return this->GetVal() == b.GetVal(); + }; + + private: + int rc; + int32_t val; + static bool rcchangeallowed; +}; +bool RefcountedObject::rcchangeallowed = true; + +class ObjectComparatorRaw { + public: + bool Equals(RefcountedObject* const& a, RefcountedObject* const& b) const { + return a->GetVal() == b->GetVal(); + } + + bool LessThan(RefcountedObject* const& a, RefcountedObject* const& b) const { + return a->GetVal() < b->GetVal(); + } +}; + +class ObjectComparatorRefPtr { + public: + bool Equals(RefPtr<RefcountedObject> const& a, + RefPtr<RefcountedObject> const& b) const { + return a->GetVal() == b->GetVal(); + } + + bool LessThan(RefPtr<RefcountedObject> const& a, + RefPtr<RefcountedObject> const& b) const { + return a->GetVal() < b->GetVal(); + } +}; + +TEST(TArray, test_refptr_array) +{ + nsTArray<RefPtr<RefcountedObject>> objArray; + + RefcountedObject* a = new RefcountedObject(); + a->AddRef(); + RefcountedObject* b = new RefcountedObject(); + b->AddRef(); + RefcountedObject* c = new RefcountedObject(); + c->AddRef(); + + objArray.AppendElement(a); + objArray.AppendElement(b); + objArray.AppendElement(c); + + ASSERT_EQ(objArray.IndexOf(b), size_t(1)); + ASSERT_TRUE(objArray.ApplyIf( + b, + [&](size_t i, RefPtr<RefcountedObject>& r) { return i == 1 && r == b; }, + []() { return false; })); + + a->Release(); + b->Release(); + c->Release(); +} + +TEST(TArray, test_sort_refptr) +{ + int numobjects = 1111111; + std::vector<RefPtr<RefcountedObject>> myobjects; + for (int i = 0; i < numobjects; i++) { + auto* obj = new RefcountedObject(); + myobjects.push_back(obj); + } + + { + nsTArray<RefPtr<RefcountedObject>> objArray(numobjects); + std::vector<RefPtr<RefcountedObject>> plainRefPtrArray(numobjects, nullptr); + + for (int i = 0; i < numobjects; i++) { + objArray.AppendElement(myobjects[i]); + plainRefPtrArray[i] = myobjects[i]; + } + + ASSERT_EQ(objArray.IndexOf(myobjects[1]), size_t(1)); + ASSERT_TRUE(objArray.ApplyIf( + myobjects[1], + [&](size_t i, RefPtr<RefcountedObject>& r) { + return i == 1 && r == myobjects[1]; + }, + []() { return false; })); + + // Do not expect that sorting affects the reference counters of elements. + RefcountedObject::ForbidRCChange(); + + // Sort objArray with explicit, pointee value based comparator + objArray.Sort(ObjectComparatorRefPtr()); + for (int i = 0; i < numobjects - 1; i++) { + ASSERT_TRUE(objArray[i]->GetVal() <= objArray[i + 1]->GetVal()); + } + + // std::sort plainRefPtrArray + auto comp = ObjectComparatorRefPtr(); + std::sort(plainRefPtrArray.begin(), plainRefPtrArray.end(), + [&comp](auto const& left, auto const& right) { + return comp.LessThan(left, right); + }); + + // We expect the order to be the same. + for (int i = 0; i < numobjects; i++) { + ASSERT_TRUE(objArray[i]->GetVal() == plainRefPtrArray[i]->GetVal()); + } + + RefcountedObject::AllowRCChange(); + // Destroy the arrays + } + + for (int i = 0; i < numobjects; i++) { + myobjects.pop_back(); + } +} + +//---- + +TEST(TArray, test_ptrarray) +{ + nsTArray<uint32_t*> ary; + ASSERT_EQ(ary.SafeElementAt(0), nullptr); + ASSERT_EQ(ary.SafeElementAt(1000), nullptr); + + uint32_t a = 10; + ary.AppendElement(&a); + ASSERT_EQ(*ary[0], a); + ASSERT_EQ(*ary.SafeElementAt(0), a); + + nsTArray<const uint32_t*> cary; + ASSERT_EQ(cary.SafeElementAt(0), nullptr); + ASSERT_EQ(cary.SafeElementAt(1000), nullptr); + + const uint32_t b = 14; + cary.AppendElement(&a); + cary.AppendElement(&b); + ASSERT_EQ(*cary[0], a); + ASSERT_EQ(*cary[1], b); + ASSERT_EQ(*cary.SafeElementAt(0), a); + ASSERT_EQ(*cary.SafeElementAt(1), b); +} + +//---- + +// This test relies too heavily on the existence of DebugGetHeader to be +// useful in non-debug builds. +#ifdef DEBUG +TEST(TArray, test_autoarray) +{ + uint32_t data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + AutoTArray<uint32_t, MOZ_ARRAY_LENGTH(data)> array; + + void* hdr = array.DebugGetHeader(); + ASSERT_NE(hdr, nsTArray<uint32_t>().DebugGetHeader()); + ASSERT_NE(hdr, + (AutoTArray<uint32_t, MOZ_ARRAY_LENGTH(data)>().DebugGetHeader())); + + array.AppendElement(1u); + ASSERT_EQ(hdr, array.DebugGetHeader()); + + array.RemoveElement(1u); + array.AppendElements(data, ArrayLength(data)); + ASSERT_EQ(hdr, array.DebugGetHeader()); + + array.AppendElement(2u); + ASSERT_NE(hdr, array.DebugGetHeader()); + + array.Clear(); + array.Compact(); + ASSERT_EQ(hdr, array.DebugGetHeader()); + array.AppendElements(data, ArrayLength(data)); + ASSERT_EQ(hdr, array.DebugGetHeader()); + + nsTArray<uint32_t> array2; + void* emptyHdr = array2.DebugGetHeader(); + array.SwapElements(array2); + ASSERT_NE(emptyHdr, array.DebugGetHeader()); + ASSERT_NE(hdr, array2.DebugGetHeader()); + size_t i; + for (i = 0; i < ArrayLength(data); ++i) { + ASSERT_EQ(array2[i], data[i]); + } + ASSERT_TRUE(array.IsEmpty()); + + array.Compact(); + array.AppendElements(data, ArrayLength(data)); + uint32_t data3[] = {5, 7, 11}; + AutoTArray<uint32_t, MOZ_ARRAY_LENGTH(data3)> array3; + array3.AppendElements(data3, ArrayLength(data3)); + array.SwapElements(array3); + for (i = 0; i < ArrayLength(data); ++i) { + ASSERT_EQ(array3[i], data[i]); + } + for (i = 0; i < ArrayLength(data3); ++i) { + ASSERT_EQ(array[i], data3[i]); + } +} +#endif + +//---- + +// IndexOf used to potentially scan beyond the end of the array. Test for +// this incorrect behavior by adding a value (5), removing it, then seeing +// if IndexOf finds it. +TEST(TArray, test_indexof) +{ + nsTArray<int> array; + array.AppendElement(0); + // add and remove the 5 + array.AppendElement(5); + array.RemoveElementAt(1); + // we should not find the 5! + auto no_index = array.NoIndex; // Fixes gtest compilation error. + ASSERT_EQ(array.IndexOf(5, 1), no_index); + ASSERT_FALSE(array.ApplyIf( + 5, 1, []() { return true; }, []() { return false; })); +} + +//---- + +template <class Array> +static bool is_heap(const Array& ary, size_t len) { + size_t index = 1; + while (index < len) { + if (ary[index] > ary[(index - 1) >> 1]) return false; + index++; + } + return true; +} + +//---- + +// An array |arr| is using its auto buffer if |&arr < arr.Elements()| and +// |arr.Elements() - &arr| is small. + +#define IS_USING_AUTO(arr) \ + ((uintptr_t) & (arr) < (uintptr_t)(arr).Elements() && \ + ((ptrdiff_t)(arr).Elements() - (ptrdiff_t) & (arr)) <= 16) + +#define CHECK_IS_USING_AUTO(arr) \ + do { \ + ASSERT_TRUE(IS_USING_AUTO(arr)); \ + } while (0) + +#define CHECK_NOT_USING_AUTO(arr) \ + do { \ + ASSERT_FALSE(IS_USING_AUTO(arr)); \ + } while (0) + +#define CHECK_USES_SHARED_EMPTY_HDR(arr) \ + do { \ + nsTArray<int> _empty; \ + ASSERT_EQ(_empty.Elements(), (arr).Elements()); \ + } while (0) + +#define CHECK_EQ_INT(actual, expected) \ + do { \ + ASSERT_EQ((actual), (expected)); \ + } while (0) + +#define CHECK_ARRAY(arr, data) \ + do { \ + CHECK_EQ_INT((arr).Length(), (size_t)ArrayLength(data)); \ + for (size_t _i = 0; _i < ArrayLength(data); _i++) { \ + CHECK_EQ_INT((arr)[_i], (data)[_i]); \ + } \ + } while (0) + +TEST(TArray, test_swap) +{ + // Test nsTArray::SwapElements. Unfortunately there are many cases. + int data1[] = {8, 6, 7, 5}; + int data2[] = {3, 0, 9}; + + // Swap two auto arrays. + { + AutoTArray<int, 8> a; + AutoTArray<int, 6> b; + + a.AppendElements(data1, ArrayLength(data1)); + b.AppendElements(data2, ArrayLength(data2)); + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + CHECK_ARRAY(a, data2); + CHECK_ARRAY(b, data1); + } + + // Swap two auto arrays -- one whose data lives on the heap, the other whose + // data lives on the stack -- which each fits into the other's auto storage. + { + AutoTArray<int, 3> a; + AutoTArray<int, 3> b; + + a.AppendElements(data1, ArrayLength(data1)); + a.RemoveElementAt(3); + b.AppendElements(data2, ArrayLength(data2)); + + // Here and elsewhere, we assert that if we start with an auto array + // capable of storing N elements, we store N+1 elements into the array, and + // then we remove one element, that array is still not using its auto + // buffer. + // + // This isn't at all required by the TArray API. It would be fine if, when + // we shrink back to N elements, the TArray frees its heap storage and goes + // back to using its stack storage. But we assert here as a check that the + // test does what we expect. If the TArray implementation changes, just + // change the failing assertions. + CHECK_NOT_USING_AUTO(a); + + // This check had better not change, though. + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(b); + CHECK_ARRAY(a, data2); + int expectedB[] = {8, 6, 7}; + CHECK_ARRAY(b, expectedB); + } + + // Swap two auto arrays which are using heap storage such that one fits into + // the other's auto storage, but the other needs to stay on the heap. + { + AutoTArray<int, 3> a; + AutoTArray<int, 2> b; + a.AppendElements(data1, ArrayLength(data1)); + a.RemoveElementAt(3); + + b.AppendElements(data2, ArrayLength(data2)); + b.RemoveElementAt(2); + + CHECK_NOT_USING_AUTO(a); + CHECK_NOT_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_NOT_USING_AUTO(b); + + int expected1[] = {3, 0}; + int expected2[] = {8, 6, 7}; + + CHECK_ARRAY(a, expected1); + CHECK_ARRAY(b, expected2); + } + + // Swap two arrays, neither of which fits into the other's auto-storage. + { + AutoTArray<int, 1> a; + AutoTArray<int, 3> b; + + a.AppendElements(data1, ArrayLength(data1)); + b.AppendElements(data2, ArrayLength(data2)); + + a.SwapElements(b); + + CHECK_ARRAY(a, data2); + CHECK_ARRAY(b, data1); + } + + // Swap an empty nsTArray with a non-empty AutoTArray. + { + nsTArray<int> a; + AutoTArray<int, 3> b; + + b.AppendElements(data2, ArrayLength(data2)); + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_ARRAY(a, data2); + CHECK_EQ_INT(b.Length(), size_t(0)); + CHECK_IS_USING_AUTO(b); + } + + // Swap two big auto arrays. + { + const unsigned size = 8192; + AutoTArray<unsigned, size> a; + AutoTArray<unsigned, size> b; + + for (unsigned i = 0; i < size; i++) { + a.AppendElement(i); + b.AppendElement(i + 1); + } + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + + CHECK_EQ_INT(a.Length(), size_t(size)); + CHECK_EQ_INT(b.Length(), size_t(size)); + + for (unsigned i = 0; i < size; i++) { + CHECK_EQ_INT(a[i], i + 1); + CHECK_EQ_INT(b[i], i); + } + } + + // Swap two arrays and make sure that their capacities don't increase + // unnecessarily. + { + nsTArray<int> a; + nsTArray<int> b; + b.AppendElements(data2, ArrayLength(data2)); + + CHECK_EQ_INT(a.Capacity(), size_t(0)); + size_t bCapacity = b.Capacity(); + + a.SwapElements(b); + + // Make sure that we didn't increase the capacity of either array. + CHECK_ARRAY(a, data2); + CHECK_EQ_INT(b.Length(), size_t(0)); + CHECK_EQ_INT(b.Capacity(), size_t(0)); + CHECK_EQ_INT(a.Capacity(), bCapacity); + } + + // Swap an auto array with a TArray, then clear the auto array and make sure + // it doesn't forget the fact that it has an auto buffer. + { + nsTArray<int> a; + AutoTArray<int, 3> b; + + a.AppendElements(data1, ArrayLength(data1)); + + a.SwapElements(b); + + CHECK_EQ_INT(a.Length(), size_t(0)); + CHECK_ARRAY(b, data1); + + b.Clear(); + + CHECK_USES_SHARED_EMPTY_HDR(a); + CHECK_IS_USING_AUTO(b); + } + + // Same thing as the previous test, but with more auto arrays. + { + AutoTArray<int, 16> a; + AutoTArray<int, 3> b; + + a.AppendElements(data1, ArrayLength(data1)); + + a.SwapElements(b); + + CHECK_EQ_INT(a.Length(), size_t(0)); + CHECK_ARRAY(b, data1); + + b.Clear(); + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + } + + // Swap an empty nsTArray and an empty AutoTArray. + { + AutoTArray<int, 8> a; + nsTArray<int> b; + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_NOT_USING_AUTO(b); + CHECK_EQ_INT(a.Length(), size_t(0)); + CHECK_EQ_INT(b.Length(), size_t(0)); + } + + // Swap empty auto array with non-empty AutoTArray using malloc'ed storage. + // I promise, all these tests have a point. + { + AutoTArray<int, 2> a; + AutoTArray<int, 1> b; + + a.AppendElements(data1, ArrayLength(data1)); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_NOT_USING_AUTO(b); + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } + + // Test fallible SwapElements of nsTArray. + { + nsTArray<int> a; + nsTArray<int> b; + + a.AppendElements(data1, ArrayLength(data1)); + + ASSERT_TRUE(a.SwapElements(b, fallible)); + + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } + + // Test fallible SwapElements of FallibleTArray. + { + FallibleTArray<int> a; + FallibleTArray<int> b; + + ASSERT_TRUE(a.AppendElements(data1, ArrayLength(data1), fallible)); + + ASSERT_TRUE(a.SwapElements(b, fallible)); + + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } + + // Test fallible SwapElements of FallibleTArray with large AutoTArray. + { + FallibleTArray<int> a; + AutoTArray<int, 8192> b; + + ASSERT_TRUE(a.AppendElements(data1, ArrayLength(data1), fallible)); + + ASSERT_TRUE(a.SwapElements(b, fallible)); + + CHECK_IS_USING_AUTO(b); + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } +} + +// Bug 1171296: Disabled on andoid due to crashes. +#if !defined(ANDROID) +TEST(TArray, test_fallible) +{ + // Test that FallibleTArray works properly; that is, it never OOMs, but + // instead eventually returns false. + // + // This test is only meaningful on 32-bit systems. On a 64-bit system, we + // might never OOM. + if (sizeof(void*) > 4) { + ASSERT_TRUE(true); + return; + } + + // Allocate a bunch of 128MB arrays. Larger allocations will fail on some + // platforms without actually hitting OOM. + // + // 36 * 128MB > 4GB, so we should definitely OOM by the 36th array. + const unsigned numArrays = 36; + FallibleTArray<char> arrays[numArrays]; + bool oomed = false; + for (size_t i = 0; i < numArrays; i++) { + // SetCapacity allocates the requested capacity + a header, and we want to + // avoid allocating more than 128MB overall because of the size padding it + // will cause, which depends on allocator behavior, so use 128MB - an + // arbitrary size larger than the array header, so that chances are good + // that allocations will always be 128MB. + bool success = arrays[i].SetCapacity(128 * 1024 * 1024 - 1024, fallible); + if (!success) { + // We got our OOM. Check that it didn't come too early. + oomed = true; +# ifdef XP_WIN + // 32-bit Windows sometimes OOMs on the 6th, 7th, or 8th. To keep the + // test green, choose the lower of those: the important thing here is + // that some allocations fail and some succeed. We're not too + // concerned about how many iterations it takes. + const size_t kOOMIterations = 6; +# else + const size_t kOOMIterations = 8; +# endif + ASSERT_GE(i, kOOMIterations) + << "Got OOM on iteration " << i << ". Too early!"; + } + } + + ASSERT_TRUE(oomed) + << "Didn't OOM or crash? nsTArray::SetCapacity" + "must be lying."; +} +#endif + +TEST(TArray, test_conversion_operator) +{ + FallibleTArray<int> f; + const FallibleTArray<int> fconst; + + nsTArray<int> t; + const nsTArray<int> tconst; + AutoTArray<int, 8> tauto; + const AutoTArray<int, 8> tautoconst; + +#define CHECK_ARRAY_CAST(type) \ + do { \ + const type<int>& z1 = f; \ + ASSERT_EQ((void*)&z1, (void*)&f); \ + const type<int>& z2 = fconst; \ + ASSERT_EQ((void*)&z2, (void*)&fconst); \ + const type<int>& z9 = t; \ + ASSERT_EQ((void*)&z9, (void*)&t); \ + const type<int>& z10 = tconst; \ + ASSERT_EQ((void*)&z10, (void*)&tconst); \ + const type<int>& z11 = tauto; \ + ASSERT_EQ((void*)&z11, (void*)&tauto); \ + const type<int>& z12 = tautoconst; \ + ASSERT_EQ((void*)&z12, (void*)&tautoconst); \ + } while (0) + + CHECK_ARRAY_CAST(FallibleTArray); + CHECK_ARRAY_CAST(nsTArray); + +#undef CHECK_ARRAY_CAST +} + +template <class T> +struct BufAccessor : public T { + void* GetHdr() { return T::mHdr; } +}; + +TEST(TArray, test_SetLengthAndRetainStorage_no_ctor) +{ + // 1050 because sizeof(int)*1050 is more than a page typically. + const int N = 1050; + FallibleTArray<int> f; + + nsTArray<int> t; + AutoTArray<int, N> tauto; + +#define LPAREN ( +#define RPAREN ) +#define FOR_EACH(pre, post) \ + do { \ + pre f post; \ + pre t post; \ + pre tauto post; \ + } while (0) + + // Setup test arrays. + FOR_EACH(; Unused <<, .SetLength(N, fallible)); + for (int n = 0; n < N; ++n) { + FOR_EACH(;, [n] = n); + } + + void* initial_Hdrs[] = { + static_cast<BufAccessor<FallibleTArray<int>>&>(f).GetHdr(), + static_cast<BufAccessor<nsTArray<int>>&>(t).GetHdr(), + static_cast<BufAccessor<AutoTArray<int, N>>&>(tauto).GetHdr(), nullptr}; + + // SetLengthAndRetainStorage(n), should NOT overwrite memory when T hasn't + // a default constructor. + FOR_EACH(;, .SetLengthAndRetainStorage(8)); + FOR_EACH(;, .SetLengthAndRetainStorage(12)); + for (int n = 0; n < 12; ++n) { + ASSERT_EQ(f[n], n); + ASSERT_EQ(t[n], n); + ASSERT_EQ(tauto[n], n); + } + FOR_EACH(;, .SetLengthAndRetainStorage(0)); + FOR_EACH(;, .SetLengthAndRetainStorage(N)); + for (int n = 0; n < N; ++n) { + ASSERT_EQ(f[n], n); + ASSERT_EQ(t[n], n); + ASSERT_EQ(tauto[n], n); + } + + void* current_Hdrs[] = { + static_cast<BufAccessor<FallibleTArray<int>>&>(f).GetHdr(), + static_cast<BufAccessor<nsTArray<int>>&>(t).GetHdr(), + static_cast<BufAccessor<AutoTArray<int, N>>&>(tauto).GetHdr(), nullptr}; + + // SetLengthAndRetainStorage(n) should NOT have reallocated the internal + // memory. + ASSERT_EQ(sizeof(initial_Hdrs), sizeof(current_Hdrs)); + for (size_t n = 0; n < sizeof(current_Hdrs) / sizeof(current_Hdrs[0]); ++n) { + ASSERT_EQ(current_Hdrs[n], initial_Hdrs[n]); + } + +#undef FOR_EACH +#undef LPAREN +#undef RPAREN +} + +template <typename Comparator> +bool TestCompareMethods(const Comparator& aComp) { + nsTArray<int> ary({57, 4, 16, 17, 3, 5, 96, 12}); + + ary.Sort(aComp); + + const int sorted[] = {3, 4, 5, 12, 16, 17, 57, 96}; + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sorted); i++) { + if (sorted[i] != ary[i]) { + return false; + } + } + + if (!ary.ContainsSorted(5, aComp)) { + return false; + } + if (ary.ContainsSorted(42, aComp)) { + return false; + } + + if (ary.BinaryIndexOf(16, aComp) != 4) { + return false; + } + + return true; +} + +struct IntComparator { + bool Equals(int aLeft, int aRight) const { return aLeft == aRight; } + + bool LessThan(int aLeft, int aRight) const { return aLeft < aRight; } +}; + +TEST(TArray, test_comparator_objects) +{ + ASSERT_TRUE(TestCompareMethods(IntComparator())); + ASSERT_TRUE( + TestCompareMethods([](int aLeft, int aRight) { return aLeft - aRight; })); +} + +struct Big { + uint64_t size[40] = {}; +}; + +TEST(TArray, test_AutoTArray_SwapElements) +{ + AutoTArray<Big, 40> oneArray; + AutoTArray<Big, 40> another; + + for (size_t i = 0; i < 8; ++i) { + oneArray.AppendElement(Big()); + } + oneArray[0].size[10] = 1; + for (size_t i = 0; i < 9; ++i) { + another.AppendElement(Big()); + } + oneArray.SwapElements(another); + + ASSERT_EQ(oneArray.Length(), 9u); + ASSERT_EQ(another.Length(), 8u); + + ASSERT_EQ(oneArray[0].size[10], 0u); + ASSERT_EQ(another[0].size[10], 1u); +} + +} // namespace TestTArray diff --git a/xpcom/tests/gtest/TestTaskQueue.cpp b/xpcom/tests/gtest/TestTaskQueue.cpp new file mode 100644 index 0000000000..bc0e78b608 --- /dev/null +++ b/xpcom/tests/gtest/TestTaskQueue.cpp @@ -0,0 +1,215 @@ +/* -*- 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 <memory> +#include "gtest/gtest.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsITargetShutdownTask.h" +#include "VideoUtils.h" + +namespace TestTaskQueue { + +using namespace mozilla; + +TEST(TaskQueue, EventOrder) +{ + RefPtr<TaskQueue> tq1 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue tq1", true); + RefPtr<TaskQueue> tq2 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue tq2", true); + RefPtr<TaskQueue> tq3 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue tq3", true); + + bool errored = false; + int counter = 0; + int sync = 0; + Monitor monitor MOZ_UNANNOTATED("TaskQueue::EventOrder::monitor"); + + // We expect task1 happens before task3. + for (int i = 0; i < 10000; ++i) { + Unused << tq1->Dispatch( + NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { + Unused << tq2->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + []() { // task0 + })); + Unused << tq3->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { // task1 + EXPECT_EQ(1, ++counter); + errored = counter != 1; + MonitorAutoLock mon(monitor); + ++sync; + mon.Notify(); + })); + Unused << tq2->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { // task2 + Unused << tq3->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { // task3 + EXPECT_EQ(0, --counter); + errored = counter != 0; + MonitorAutoLock mon(monitor); + ++sync; + mon.Notify(); + })); + })); + }), + AbstractThread::TailDispatch); + + // Ensure task1 and task3 are done before next loop. + MonitorAutoLock mon(monitor); + while (sync != 2) { + mon.Wait(); + } + sync = 0; + + if (errored) { + break; + } + } + + tq1->BeginShutdown(); + tq1->AwaitShutdownAndIdle(); + tq2->BeginShutdown(); + tq2->AwaitShutdownAndIdle(); + tq3->BeginShutdown(); + tq3->AwaitShutdownAndIdle(); +} + +TEST(TaskQueue, GetCurrentSerialEventTarget) +{ + RefPtr<TaskQueue> tq1 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue GetCurrentSerialEventTarget", false); + Unused << tq1->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TestCurrentSerialEventTarget::TestBody", [tq1]() { + nsCOMPtr<nsISerialEventTarget> thread = GetCurrentSerialEventTarget(); + EXPECT_EQ(thread, tq1); + })); + tq1->BeginShutdown(); + tq1->AwaitShutdownAndIdle(); +} + +namespace { + +class TestShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit TestShutdownTask(std::function<void()> aCallback) + : mCallback(std::move(aCallback)) {} + + void TargetShutdown() override { + if (mCallback) { + mCallback(); + } + } + + private: + ~TestShutdownTask() = default; + std::function<void()> mCallback; +}; + +NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask) + +} // namespace + +TEST(TaskQueue, ShutdownTask) +{ + auto shutdownTaskRun = std::make_shared<bool>(); + auto runnableFromShutdownRun = std::make_shared<bool>(); + + RefPtr<TaskQueue> tq = TaskQueue::Create( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue"); + + nsCOMPtr<nsITargetShutdownTask> shutdownTask = new TestShutdownTask([=] { + EXPECT_TRUE(tq->IsOnCurrentThread()); + + ASSERT_FALSE(*shutdownTaskRun); + *shutdownTaskRun = true; + + nsCOMPtr<nsITargetShutdownTask> dummyTask = new TestShutdownTask([] {}); + nsresult rv = tq->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + MOZ_ALWAYS_SUCCEEDS( + tq->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] { + EXPECT_TRUE(tq->IsOnCurrentThread()); + + nsCOMPtr<nsITargetShutdownTask> dummyTask = + new TestShutdownTask([] {}); + nsresult rv = tq->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + ASSERT_FALSE(*runnableFromShutdownRun); + *runnableFromShutdownRun = true; + }))); + }); + MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + RefPtr<mozilla::SyncRunnable> syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + tq->BeginShutdown(); + tq->AwaitShutdownAndIdle(); + + ASSERT_TRUE(*shutdownTaskRun); + ASSERT_TRUE(*runnableFromShutdownRun); +} + +TEST(TaskQueue, UnregisteredShutdownTask) +{ + RefPtr<TaskQueue> tq = TaskQueue::Create( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue"); + + nsCOMPtr<nsITargetShutdownTask> shutdownTask = + new TestShutdownTask([=] { MOZ_CRASH("should not be run"); }); + + MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask)); + + RefPtr<mozilla::SyncRunnable> syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq)); + + MOZ_ALWAYS_SUCCEEDS(tq->UnregisterShutdownTask(shutdownTask)); + + tq->BeginShutdown(); + tq->AwaitShutdownAndIdle(); +} + +TEST(AbstractThread, GetCurrentSerialEventTarget) +{ + RefPtr<AbstractThread> mainThread = AbstractThread::GetCurrent(); + EXPECT_EQ(mainThread, AbstractThread::MainThread()); + Unused << mainThread->Dispatch(NS_NewRunnableFunction( + "TestAbstractThread::TestCurrentSerialEventTarget::TestBody", + [mainThread]() { + nsCOMPtr<nsISerialEventTarget> thread = GetCurrentSerialEventTarget(); + EXPECT_EQ(thread, mainThread); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +} // namespace TestTaskQueue diff --git a/xpcom/tests/gtest/TestTextFormatter.cpp b/xpcom/tests/gtest/TestTextFormatter.cpp new file mode 100644 index 0000000000..9e3d99a056 --- /dev/null +++ b/xpcom/tests/gtest/TestTextFormatter.cpp @@ -0,0 +1,237 @@ +/* -*- 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/. */ + +#include "nsTextFormatter.h" +#include "nsString.h" +#include "gtest/gtest.h" + +TEST(TextFormatter, Tests) +{ + nsAutoString fmt(u"%3$s %4$S %1$d %2$d %2$d %3$s"_ns); + char utf8[] = "Hello"; + char16_t ucs2[] = {'W', 'o', 'r', 'l', 'd', + 0x4e00, 0xAc00, 0xFF45, 0x0103, 0x00}; + int d = 3; + + char16_t buf[256]; + nsTextFormatter::snprintf(buf, 256, fmt.get(), d, 333, utf8, ucs2); + nsAutoString out(buf); + + const char16_t* uout = out.get(); + const char16_t expected[] = { + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, + 0x4E00, 0xAC00, 0xFF45, 0x0103, 0x20, 0x33, 0x20, 0x33, 0x33, 0x33, 0x20, + 0x33, 0x33, 0x33, 0x20, 0x48, 0x65, 0x6C, 0x6C, 0x6F}; + + for (uint32_t i = 0; i < out.Length(); i++) { + ASSERT_EQ(uout[i], expected[i]); + } + + // Test that an unrecognized escape is passed through. + nsString out2; + nsTextFormatter::ssprintf(out2, u"%1m!", 23); + EXPECT_STREQ("%1m!", NS_ConvertUTF16toUTF8(out2).get()); + + // Treat NULL the same in both %s cases. + nsTextFormatter::ssprintf(out2, u"%s %S", (char*)nullptr, (char16_t*)nullptr); + EXPECT_STREQ("(null) (null)", NS_ConvertUTF16toUTF8(out2).get()); + + nsTextFormatter::ssprintf(out2, u"%lld", INT64_MIN); + EXPECT_STREQ("-9223372036854775808", NS_ConvertUTF16toUTF8(out2).get()); + + // Regression test for bug 1401821. + nsTextFormatter::ssprintf(out2, u"%*.f", 0, 23.2); + EXPECT_STREQ("23", NS_ConvertUTF16toUTF8(out2).get()); +} + +/* + * Check misordered parameters + */ + +TEST(TextFormatterOrdering, orders) +{ + nsString out; + + // plain list + nsTextFormatter::ssprintf(out, u"%S %S %S", u"1", u"2", u"3"); + EXPECT_STREQ("1 2 3", NS_ConvertUTF16toUTF8(out).get()); + + // ordered list + nsTextFormatter::ssprintf(out, u"%2$S %3$S %1$S", u"1", u"2", u"3"); + EXPECT_STREQ("2 3 1", NS_ConvertUTF16toUTF8(out).get()); + + // Mixed ordered list and non-ordered does not work. This shouldn't + // crash (hence the calls to ssprintf) but should fail for for + // snprintf. + nsTextFormatter::ssprintf(out, u"%2S %S %1$S", u"1", u"2", u"3"); + nsTextFormatter::ssprintf(out, u"%S %2$S", u"1", u"2"); + char16_t buffer[1024]; // plenty big + EXPECT_EQ(nsTextFormatter::snprintf(buffer, sizeof(buffer), u"%2S %S %1$S", + u"1", u"2", u"3"), + uint32_t(-1)); + EXPECT_EQ( + nsTextFormatter::snprintf(buffer, sizeof(buffer), u"%S %2$S", u"1", u"2"), + uint32_t(-1)); + + // Referencing an extra param returns empty strings in release. +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u" %2$S ", u"1"); + EXPECT_STREQ(" ", NS_ConvertUTF16toUTF8(out).get()); +#endif + + // Double referencing existing argument works + nsTextFormatter::ssprintf(out, u"%1$S %1$S", u"1"); + EXPECT_STREQ("1 1", NS_ConvertUTF16toUTF8(out).get()); + + // Dropping trailing argument works + nsTextFormatter::ssprintf(out, u" %1$S ", u"1", u"2"); + EXPECT_STREQ(" 1 ", NS_ConvertUTF16toUTF8(out).get()); + + // Dropping leading arguments works + nsTextFormatter::ssprintf(out, u" %2$S ", u"1", u"2"); + EXPECT_STREQ(" 2 ", NS_ConvertUTF16toUTF8(out).get()); + + // Dropping middle arguments works + nsTextFormatter::ssprintf(out, u" %3$S %1$S ", u"1", u"2", u"3"); + EXPECT_STREQ(" 3 1 ", NS_ConvertUTF16toUTF8(out).get()); +} + +/* + * Tests to validate that horrible things don't happen if the passed-in + * variable and the formatter don't match. + */ +TEST(TextFormatterTestMismatch, format_d) +{ + nsString out; + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%d", int(-1)); + EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%d", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%d", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%d", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%d", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_u) +{ + nsString out; + nsTextFormatter::ssprintf(out, u"%u", int(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%u", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%u", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%u", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%u", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_x) +{ + nsString out; + nsTextFormatter::ssprintf(out, u"%x", int32_t(-1)); + EXPECT_STREQ("ffffffff", NS_ConvertUTF16toUTF8(out).get()); + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%x", uint32_t(-1)); + EXPECT_STREQ("ffffffff", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%x", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%x", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%x", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_s) +{ + nsString out; +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%s", int(-1)); + EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%s", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%s", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); +#endif + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%s", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%s", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_S) +{ + nsString out; +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%S", int32_t(-1)); + EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%S", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%S", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%S", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%S", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +} + +TEST(TextFormatterTestMismatch, format_c) +{ + nsString out; + nsTextFormatter::ssprintf(out, u"%c", int32_t(-1)); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ((uint16_t)-1, out.CharAt(0)); // not useful for humans :-/ + nsTextFormatter::ssprintf(out, u"%c", uint32_t(-1)); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ((uint16_t)-1, out.CharAt(0)); // not useful for humans :-/ +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%c", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%c", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%c", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif + + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%c", 'c'); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ(u'c', out.CharAt(0)); + nsTextFormatter::ssprintf(out, u"%c", u'c'); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ(u'c', out.CharAt(0)); +} + +TEST(TextFormatterTestResults, Tests) +{ + char16_t buf[10]; + + EXPECT_EQ( + nsTextFormatter::snprintf(buf, 10, u"%s", "more than 10 characters"), 9u); + EXPECT_EQ(buf[9], '\0'); + EXPECT_STREQ("more than", NS_ConvertUTF16toUTF8(&buf[0]).get()); + + nsString out; + nsTextFormatter::ssprintf(out, u"%s", "more than 10 characters"); + // The \0 isn't written here. + EXPECT_EQ(out.Length(), 23u); +} diff --git a/xpcom/tests/gtest/TestThreadManager.cpp b/xpcom/tests/gtest/TestThreadManager.cpp new file mode 100644 index 0000000000..41279e104c --- /dev/null +++ b/xpcom/tests/gtest/TestThreadManager.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIThreadManager.h" +#include "nsCOMPtr.h" +#include "nsIThread.h" +#include "nsXPCOM.h" +#include "nsThreadUtils.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Atomics.h" +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using mozilla::Atomic; +using mozilla::Runnable; + +class WaitCondition final : public nsINestedEventLoopCondition { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + WaitCondition(Atomic<uint32_t>& aCounter, uint32_t aMaxCount) + : mCounter(aCounter), mMaxCount(aMaxCount) {} + + NS_IMETHODIMP IsDone(bool* aDone) override { + *aDone = (mCounter == mMaxCount); + return NS_OK; + } + + private: + ~WaitCondition() = default; + + Atomic<uint32_t>& mCounter; + const uint32_t mMaxCount; +}; + +NS_IMPL_ISUPPORTS(WaitCondition, nsINestedEventLoopCondition) + +class SpinRunnable final : public Runnable { + public: + explicit SpinRunnable(nsINestedEventLoopCondition* aCondition) + : Runnable("SpinRunnable"), mCondition(aCondition), mResult(NS_OK) {} + + NS_IMETHODIMP Run() { + nsCOMPtr<nsIThreadManager> threadMan = + do_GetService("@mozilla.org/thread-manager;1"); + + mResult = threadMan->SpinEventLoopUntil( + "xpcom:TestThreadManager.cpp:SpinRunnable->Run()"_ns, mCondition); + return NS_OK; + } + + nsresult SpinLoopResult() { return mResult; } + + private: + ~SpinRunnable() = default; + + nsCOMPtr<nsINestedEventLoopCondition> mCondition; + Atomic<nsresult> mResult; +}; + +class CountRunnable final : public Runnable { + public: + explicit CountRunnable(Atomic<uint32_t>& aCounter) + : Runnable("CountRunnable"), mCounter(aCounter) {} + + NS_IMETHODIMP Run() { + mCounter++; + return NS_OK; + } + + private: + Atomic<uint32_t>& mCounter; +}; + +TEST(ThreadManager, SpinEventLoopUntilSuccess) +{ + const uint32_t kRunnablesToDispatch = 100; + nsresult rv; + mozilla::Atomic<uint32_t> count(0); + + nsCOMPtr<nsINestedEventLoopCondition> condition = + new WaitCondition(count, kRunnablesToDispatch); + RefPtr<SpinRunnable> spinner = new SpinRunnable(condition); + nsCOMPtr<nsIThread> thread; + rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIRunnable> counter = new CountRunnable(count); + for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) { + rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + } + + rv = thread->Shutdown(); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_NS_SUCCEEDED(spinner->SpinLoopResult()); +} + +class ErrorCondition final : public nsINestedEventLoopCondition { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + ErrorCondition(Atomic<uint32_t>& aCounter, uint32_t aMaxCount) + : mCounter(aCounter), mMaxCount(aMaxCount) {} + + NS_IMETHODIMP IsDone(bool* aDone) override { + if (mCounter == mMaxCount) { + return NS_ERROR_ILLEGAL_VALUE; + } + return NS_OK; + } + + private: + ~ErrorCondition() = default; + + Atomic<uint32_t>& mCounter; + const uint32_t mMaxCount; +}; + +NS_IMPL_ISUPPORTS(ErrorCondition, nsINestedEventLoopCondition) + +TEST(ThreadManager, SpinEventLoopUntilError) +{ + const uint32_t kRunnablesToDispatch = 100; + nsresult rv; + mozilla::Atomic<uint32_t> count(0); + + nsCOMPtr<nsINestedEventLoopCondition> condition = + new ErrorCondition(count, kRunnablesToDispatch); + RefPtr<SpinRunnable> spinner = new SpinRunnable(condition); + nsCOMPtr<nsIThread> thread; + rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIRunnable> counter = new CountRunnable(count); + for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) { + rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + } + + rv = thread->Shutdown(); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_NS_FAILED(spinner->SpinLoopResult()); +} diff --git a/xpcom/tests/gtest/TestThreadPool.cpp b/xpcom/tests/gtest/TestThreadPool.cpp new file mode 100644 index 0000000000..0dd0f51537 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadPool.cpp @@ -0,0 +1,211 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "nsThreadPool.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/Monitor.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +class TestTask final : public Runnable { + public: + TestTask(int i, Atomic<int>& aCounter) + : Runnable("TestThreadPool::Task"), mIndex(i), mCounter(aCounter) {} + + NS_IMETHOD Run() override { + printf("###(%d) running from thread: %p\n", mIndex, + (void*)PR_GetCurrentThread()); + int r = (int)((float)rand() * 200 / float(RAND_MAX)); + PR_Sleep(PR_MillisecondsToInterval(r)); + printf("###(%d) exiting from thread: %p\n", mIndex, + (void*)PR_GetCurrentThread()); + ++mCounter; + return NS_OK; + } + + private: + ~TestTask() = default; + + int mIndex; + Atomic<int>& mCounter; +}; + +TEST(ThreadPool, Main) +{ + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + + Atomic<int> count(0); + + for (int i = 0; i < 100; ++i) { + nsCOMPtr<nsIRunnable> task = new TestTask(i, count); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + pool->Shutdown(); + EXPECT_EQ(count, 100); +} + +TEST(ThreadPool, Parallelism) +{ + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + + // Dispatch and sleep to ensure we have an idle thread + nsCOMPtr<nsIRunnable> r0 = new Runnable("TestRunnable"); + NS_DispatchAndSpinEventLoopUntilComplete("ThreadPool::Parallelism"_ns, pool, + do_AddRef(r0)); + PR_Sleep(PR_SecondsToInterval(2)); + + class Runnable1 : public Runnable { + public: + Runnable1(Monitor& aMonitor, bool& aDone) + : mozilla::Runnable("Runnable1"), mMonitor(aMonitor), mDone(aDone) {} + + NS_IMETHOD Run() override { + MonitorAutoLock mon(mMonitor); + if (!mDone) { + // Wait for a reasonable timeout since we don't want to block gtests + // forever should any regression happen. + mon.Wait(TimeDuration::FromSeconds(300)); + } + EXPECT_TRUE(mDone); + return NS_OK; + } + + private: + Monitor& mMonitor; + bool& mDone; + }; + + class Runnable2 : public Runnable { + public: + Runnable2(Monitor& aMonitor, bool& aDone) + : mozilla::Runnable("Runnable2"), mMonitor(aMonitor), mDone(aDone) {} + + NS_IMETHOD Run() override { + MonitorAutoLock mon(mMonitor); + mDone = true; + mon.NotifyAll(); + return NS_OK; + } + + private: + Monitor& mMonitor; + bool& mDone; + }; + + // Dispatch 2 events in a row. Since we are still within the thread limit, + // We should wake up the idle thread and spawn a new thread so these 2 events + // can run in parallel. We will time out if r1 and r2 run in sequence for r1 + // won't finish until r2 finishes. + Monitor mon MOZ_UNANNOTATED("ThreadPool::Parallelism"); + bool done = false; + nsCOMPtr<nsIRunnable> r1 = new Runnable1(mon, done); + nsCOMPtr<nsIRunnable> r2 = new Runnable2(mon, done); + pool->Dispatch(r1, NS_DISPATCH_NORMAL); + pool->Dispatch(r2, NS_DISPATCH_NORMAL); + + pool->Shutdown(); +} + +TEST(ThreadPool, ShutdownWithTimeout) +{ + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + + Atomic<int> allThreadsCount(0); + for (int i = 0; i < 4; ++i) { + nsCOMPtr<nsIRunnable> task = new TestTask(i, allThreadsCount); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + // Wait for a max of 350 ms. All threads should be done by then. + pool->ShutdownWithTimeout(350); + EXPECT_EQ(allThreadsCount, 4); + + Atomic<int> infiniteLoopCount(0); + Atomic<bool> shutdownInfiniteLoop(false); + Atomic<bool> shutdownAck(false); + pool = new nsThreadPool(); + for (int i = 0; i < 3; ++i) { + nsCOMPtr<nsIRunnable> task = new TestTask(i, infiniteLoopCount); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + pool->Dispatch(NS_NewRunnableFunction( + "infinite-loop", + [&shutdownInfiniteLoop, &shutdownAck]() { + printf("### running from thread that never ends: %p\n", + (void*)PR_GetCurrentThread()); + while (!shutdownInfiniteLoop) { + PR_Sleep(PR_MillisecondsToInterval(100)); + } + shutdownAck = true; + }), + NS_DISPATCH_NORMAL); + + pool->ShutdownWithTimeout(1000); + EXPECT_EQ(infiniteLoopCount, 3); + + shutdownInfiniteLoop = true; + while (!shutdownAck) { + /* nothing */ + } +} + +TEST(ThreadPool, ShutdownWithTimeoutThenSleep) +{ + Atomic<int> count(0); + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + + for (int i = 0; i < 3; ++i) { + nsCOMPtr<nsIRunnable> task = new TestTask(i, count); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + pool->Dispatch( + NS_NewRunnableFunction( + "sleep-for-400-ms", + [&count]() { + printf("### running from thread that sleeps for 400ms: %p\n", + (void*)PR_GetCurrentThread()); + PR_Sleep(PR_MillisecondsToInterval(400)); + ++count; + printf("### thread awoke from long sleep: %p\n", + (void*)PR_GetCurrentThread()); + }), + NS_DISPATCH_NORMAL); + + // Wait for a max of 350 ms. The thread should still be sleeping, and will + // be leaked. + pool->ShutdownWithTimeout(350); + // We can't be exact here; the thread we're running on might have gotten + // suspended and the sleeping thread, above, might have finished. + EXPECT_GE(count, 3); + + // Sleep for a bit, and wait for the last thread to finish up. + PR_Sleep(PR_MillisecondsToInterval(200)); + + // Process events so the shutdown ack is received + NS_ProcessPendingEvents(NS_GetCurrentThread()); + + EXPECT_EQ(count, 4); +} diff --git a/xpcom/tests/gtest/TestThreadPoolListener.cpp b/xpcom/tests/gtest/TestThreadPoolListener.cpp new file mode 100644 index 0000000000..2ca4fa26f1 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadPoolListener.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIThread.h" + +#include "nsComponentManagerUtils.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCIDInternal.h" +#include "pratom.h" +#include "prinrval.h" +#include "prmon.h" +#include "prthread.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/gtest/MozAssertions.h" + +#include "mozilla/ReentrantMonitor.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +#define NUMBER_OF_THREADS 4 + +// One hour... because test boxes can be slow! +#define IDLE_THREAD_TIMEOUT 3600000 + +namespace TestThreadPoolListener { +static nsIThread** gCreatedThreadList = nullptr; +static nsIThread** gShutDownThreadList = nullptr; + +static ReentrantMonitor* gReentrantMonitor = nullptr; + +static bool gAllRunnablesPosted = false; +static bool gAllThreadsCreated = false; +static bool gAllThreadsShutDown = false; + +class Listener final : public nsIThreadPoolListener { + ~Listener() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITHREADPOOLLISTENER +}; + +NS_IMPL_ISUPPORTS(Listener, nsIThreadPoolListener) + +NS_IMETHODIMP +Listener::OnThreadCreated() { + nsCOMPtr<nsIThread> current(do_GetCurrentThread()); + EXPECT_TRUE(current) << "Couldn't get current thread!"; + + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + while (!gAllRunnablesPosted) { + mon.Wait(); + } + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsIThread* thread = gCreatedThreadList[i]; + EXPECT_NE(thread, current) << "Saw the same thread twice!"; + + if (!thread) { + gCreatedThreadList[i] = current; + if (i == (NUMBER_OF_THREADS - 1)) { + gAllThreadsCreated = true; + mon.NotifyAll(); + } + return NS_OK; + } + } + + EXPECT_TRUE(false) << "Too many threads!"; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Listener::OnThreadShuttingDown() { + nsCOMPtr<nsIThread> current(do_GetCurrentThread()); + EXPECT_TRUE(current) << "Couldn't get current thread!"; + + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsIThread* thread = gShutDownThreadList[i]; + EXPECT_NE(thread, current) << "Saw the same thread twice!"; + + if (!thread) { + gShutDownThreadList[i] = current; + if (i == (NUMBER_OF_THREADS - 1)) { + gAllThreadsShutDown = true; + mon.NotifyAll(); + } + return NS_OK; + } + } + + EXPECT_TRUE(false) << "Too many threads!"; + return NS_ERROR_FAILURE; +} + +class AutoCreateAndDestroyReentrantMonitor { + public: + explicit AutoCreateAndDestroyReentrantMonitor( + ReentrantMonitor** aReentrantMonitorPtr) + : mReentrantMonitorPtr(aReentrantMonitorPtr) { + *aReentrantMonitorPtr = + new ReentrantMonitor("TestThreadPoolListener::AutoMon"); + MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!"); + } + + ~AutoCreateAndDestroyReentrantMonitor() { + delete *mReentrantMonitorPtr; + *mReentrantMonitorPtr = nullptr; + } + + private: + ReentrantMonitor** mReentrantMonitorPtr; +}; + +TEST(ThreadPoolListener, Test) +{ + nsIThread* createdThreadList[NUMBER_OF_THREADS] = {nullptr}; + gCreatedThreadList = createdThreadList; + + nsIThread* shutDownThreadList[NUMBER_OF_THREADS] = {nullptr}; + gShutDownThreadList = shutDownThreadList; + + AutoCreateAndDestroyReentrantMonitor newMon(&gReentrantMonitor); + ASSERT_TRUE(gReentrantMonitor); + + nsresult rv; + + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + + rv = pool->SetThreadLimit(NUMBER_OF_THREADS); + ASSERT_NS_SUCCEEDED(rv); + + rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS); + ASSERT_NS_SUCCEEDED(rv); + + rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIThreadPoolListener> listener = new Listener(); + ASSERT_TRUE(listener); + + rv = pool->SetListener(listener); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsCOMPtr<nsIRunnable> runnable = new Runnable("TestRunnable"); + ASSERT_TRUE(runnable); + + rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + } + + gAllRunnablesPosted = true; + mon.NotifyAll(); + } + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + while (!gAllThreadsCreated) { + mon.Wait(); + } + } + + rv = pool->Shutdown(); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + while (!gAllThreadsShutDown) { + mon.Wait(); + } + } + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsIThread* created = gCreatedThreadList[i]; + ASSERT_TRUE(created); + + bool match = false; + for (uint32_t j = 0; j < NUMBER_OF_THREADS; j++) { + nsIThread* destroyed = gShutDownThreadList[j]; + ASSERT_TRUE(destroyed); + + if (destroyed == created) { + match = true; + break; + } + } + + ASSERT_TRUE(match); + } +} + +} // namespace TestThreadPoolListener diff --git a/xpcom/tests/gtest/TestThreadUtils.cpp b/xpcom/tests/gtest/TestThreadUtils.cpp new file mode 100644 index 0000000000..2b9ff97192 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadUtils.cpp @@ -0,0 +1,2226 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <type_traits> + +#include "nsComponentManagerUtils.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "mozilla/IdleTaskRunner.h" +#include "mozilla/RefCounted.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/UniquePtr.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +enum { + TEST_CALL_VOID_ARG_VOID_RETURN, + TEST_CALL_VOID_ARG_VOID_RETURN_CONST, + TEST_CALL_VOID_ARG_NONVOID_RETURN, + TEST_CALL_NONVOID_ARG_VOID_RETURN, + TEST_CALL_NONVOID_ARG_NONVOID_RETURN, + TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT, + TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT, +#ifdef HAVE_STDCALL + TEST_STDCALL_VOID_ARG_VOID_RETURN, + TEST_STDCALL_VOID_ARG_NONVOID_RETURN, + TEST_STDCALL_NONVOID_ARG_VOID_RETURN, + TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN, + TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT, +#endif + TEST_CALL_NEWTHREAD_SUICIDAL, + MAX_TESTS +}; + +bool gRunnableExecuted[MAX_TESTS]; + +class nsFoo : public nsISupports { + NS_DECL_ISUPPORTS + nsresult DoFoo(bool* aBool) { + *aBool = true; + return NS_OK; + } + + private: + virtual ~nsFoo() = default; +}; + +NS_IMPL_ISUPPORTS0(nsFoo) + +class TestSuicide : public mozilla::Runnable { + public: + TestSuicide() : mozilla::Runnable("TestSuicide") {} + NS_IMETHOD Run() override { + // Runs first time on thread "Suicide", then dies on MainThread + if (!NS_IsMainThread()) { + mThread = do_GetCurrentThread(); + NS_DispatchToMainThread(this); + return NS_OK; + } + MOZ_RELEASE_ASSERT(mThread); + mThread->Shutdown(); + gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL] = true; + return NS_OK; + } + + private: + nsCOMPtr<nsIThread> mThread; +}; + +class nsBar : public nsISupports { + virtual ~nsBar() = default; + + public: + NS_DECL_ISUPPORTS + void DoBar1(void) { + gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN] = true; + } + void DoBar1Const(void) const { + gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN_CONST] = true; + } + nsresult DoBar2(void) { + gRunnableExecuted[TEST_CALL_VOID_ARG_NONVOID_RETURN] = true; + return NS_OK; + } + void DoBar3(nsFoo* aFoo) { + aFoo->DoFoo(&gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN]); + } + nsresult DoBar4(nsFoo* aFoo) { + return aFoo->DoFoo( + &gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN]); + } + void DoBar5(nsFoo* aFoo) { + if (aFoo) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + } + nsresult DoBar6(char* aFoo) { + if (strlen(aFoo)) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT] = true; + return NS_OK; + } +#ifdef HAVE_STDCALL + void __stdcall DoBar1std(void) { + gRunnableExecuted[TEST_STDCALL_VOID_ARG_VOID_RETURN] = true; + } + nsresult __stdcall DoBar2std(void) { + gRunnableExecuted[TEST_STDCALL_VOID_ARG_NONVOID_RETURN] = true; + return NS_OK; + } + void __stdcall DoBar3std(nsFoo* aFoo) { + aFoo->DoFoo(&gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN]); + } + nsresult __stdcall DoBar4std(nsFoo* aFoo) { + return aFoo->DoFoo( + &gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN]); + } + void __stdcall DoBar5std(nsFoo* aFoo) { + if (aFoo) + gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + } + nsresult __stdcall DoBar6std(char* aFoo) { + if (strlen(aFoo)) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + return NS_OK; + } +#endif +}; + +NS_IMPL_ISUPPORTS0(nsBar) + +struct TestCopyWithNoMove { + explicit TestCopyWithNoMove(int* aCopyCounter) : mCopyCounter(aCopyCounter) {} + TestCopyWithNoMove(const TestCopyWithNoMove& a) + : mCopyCounter(a.mCopyCounter) { + *mCopyCounter += 1; + }; + // No 'move' declaration, allows passing object by rvalue copy. + // Destructor nulls member variable... + ~TestCopyWithNoMove() { mCopyCounter = nullptr; } + // ... so we can check that the object is called when still alive. + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); } + int* mCopyCounter; +}; +struct TestCopyWithDeletedMove { + explicit TestCopyWithDeletedMove(int* aCopyCounter) + : mCopyCounter(aCopyCounter) {} + TestCopyWithDeletedMove(const TestCopyWithDeletedMove& a) + : mCopyCounter(a.mCopyCounter) { + *mCopyCounter += 1; + }; + // Deleted move prevents passing by rvalue (even if copy would work) + TestCopyWithDeletedMove(TestCopyWithDeletedMove&&) = delete; + ~TestCopyWithDeletedMove() { mCopyCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); } + int* mCopyCounter; +}; +struct TestMove { + explicit TestMove(int* aMoveCounter) : mMoveCounter(aMoveCounter) {} + TestMove(const TestMove&) = delete; + TestMove(TestMove&& a) : mMoveCounter(a.mMoveCounter) { + a.mMoveCounter = nullptr; + *mMoveCounter += 1; + } + ~TestMove() { mMoveCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mMoveCounter); } + int* mMoveCounter; +}; +struct TestCopyMove { + TestCopyMove(int* aCopyCounter, int* aMoveCounter) + : mCopyCounter(aCopyCounter), mMoveCounter(aMoveCounter) {} + TestCopyMove(const TestCopyMove& a) + : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { + *mCopyCounter += 1; + }; + TestCopyMove(TestCopyMove&& a) + : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { + a.mMoveCounter = nullptr; + *mMoveCounter += 1; + } + ~TestCopyMove() { + mCopyCounter = nullptr; + mMoveCounter = nullptr; + } + void operator()() { + MOZ_RELEASE_ASSERT(mCopyCounter); + MOZ_RELEASE_ASSERT(mMoveCounter); + } + int* mCopyCounter; + int* mMoveCounter; +}; + +struct TestRefCounted : RefCounted<TestRefCounted> { + MOZ_DECLARE_REFCOUNTED_TYPENAME(TestRefCounted); +}; + +static void Expect(const char* aContext, int aCounter, int aMaxExpected) { + EXPECT_LE(aCounter, aMaxExpected) << aContext; +} + +static void ExpectRunnableName(Runnable* aRunnable, const char* aExpectedName) { +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + nsAutoCString name; + EXPECT_TRUE(NS_SUCCEEDED(aRunnable->GetName(name))) << "Runnable::GetName()"; + EXPECT_TRUE(name.EqualsASCII(aExpectedName)) << "Verify Runnable name"; +#endif +} + +struct BasicRunnableFactory { + static constexpr bool SupportsCopyWithDeletedMove = true; + + template <typename Function> + static auto Create(const char* aName, Function&& aFunc) { + return NS_NewRunnableFunction(aName, std::forward<Function>(aFunc)); + } +}; + +struct CancelableRunnableFactory { + static constexpr bool SupportsCopyWithDeletedMove = false; + + template <typename Function> + static auto Create(const char* aName, Function&& aFunc) { + return NS_NewCancelableRunnableFunction(aName, + std::forward<Function>(aFunc)); + } +}; + +template <typename RunnableFactory> +static void TestRunnableFactory(bool aNamed) { + // Test RunnableFactory with copyable-only function object. + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) + : RunnableFactory::Create( + "TestNewRunnableFunction", tracker); + // Original 'tracker' is destroyed here. + } + // Verify that the runnable contains a non-destroyed function object. + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and no move) function, " + "copies", + copyCounter, 1); + } + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + // Passing as rvalue, but using copy. + // (TestCopyWithDeletedMove wouldn't allow this.) + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", + TestCopyWithNoMove(©Counter)) + : RunnableFactory::Create("TestNewRunnableFunction", + TestCopyWithNoMove(©Counter)); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and no move) function " + "rvalue, copies", + copyCounter, 1); + } + if constexpr (RunnableFactory::SupportsCopyWithDeletedMove) { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) + : RunnableFactory::Create( + "TestNewRunnableFunction", tracker); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and deleted move) " + "function, copies", + copyCounter, 1); + } + + // Test RunnableFactory with movable-only function object. + { + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestMove tracker(&moveCounter); + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", std::move(tracker)) + : RunnableFactory::Create("TestNewRunnableFunction", + std::move(tracker)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with movable-only function, moves", moveCounter, 1); + } + { + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", TestMove(&moveCounter)) + : RunnableFactory::Create("TestNewRunnableFunction", + TestMove(&moveCounter)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with movable-only function rvalue, moves", + moveCounter, 1); + } + + // Test RunnableFactory with copyable&movable function object. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", std::move(tracker)) + : RunnableFactory::Create("TestNewRunnableFunction", + std::move(tracker)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with copyable&movable function, copies", + copyCounter, 0); + Expect("RunnableFactory with copyable&movable function, moves", moveCounter, + 1); + } + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + trackedRunnable = + aNamed ? RunnableFactory::Create( + "unused", TestCopyMove(©Counter, &moveCounter)) + : RunnableFactory::Create( + "TestNewRunnableFunction", + TestCopyMove(©Counter, &moveCounter)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with copyable&movable function rvalue, copies", + copyCounter, 0); + Expect("RunnableFactory with copyable&movable function rvalue, moves", + moveCounter, 1); + } + + // Test RunnableFactory with copyable-only lambda capture. + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + // Expect 2 copies (here -> local lambda -> runnable lambda). + trackedRunnable = + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and no move) capture, " + "copies", + copyCounter, 2); + } + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + // Expect 2 copies (here -> local lambda -> runnable lambda). + trackedRunnable = + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and deleted move) capture, " + "copies", + copyCounter, 2); + } + + // Note: Not possible to use move-only captures. + // (Until we can use C++14 generalized lambda captures) + + // Test RunnableFactory with copyable&movable lambda capture. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); + // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable + // lambda). + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with copyable&movable capture, copies", copyCounter, + 1); + Expect("RunnableFactory with copyable&movable capture, moves", moveCounter, + 1); + } +} + +TEST(ThreadUtils, NewRunnableFunction) +{ TestRunnableFactory<BasicRunnableFactory>(/*aNamed*/ false); } + +TEST(ThreadUtils, NewNamedRunnableFunction) +{ + // The named overload shall behave identical to the non-named counterpart. + TestRunnableFactory<BasicRunnableFactory>(/*aNamed*/ true); + + // Test naming. + { + const char* expectedName = "NamedRunnable"; + RefPtr<Runnable> NamedRunnable = + NS_NewRunnableFunction(expectedName, [] {}); + ExpectRunnableName(NamedRunnable, expectedName); + } +} + +TEST(ThreadUtils, NewCancelableRunnableFunction) +{ TestRunnableFactory<CancelableRunnableFactory>(/*aNamed*/ false); } + +TEST(ThreadUtils, NewNamedCancelableRunnableFunction) +{ + // The named overload shall behave identical to the non-named counterpart. + TestRunnableFactory<CancelableRunnableFactory>(/*aNamed*/ true); + + // Test naming. + { + const char* expectedName = "NamedRunnable"; + RefPtr<Runnable> NamedRunnable = + NS_NewCancelableRunnableFunction(expectedName, [] {}); + ExpectRunnableName(NamedRunnable, expectedName); + } + + // Test release on cancelation. + { + auto foo = MakeRefPtr<TestRefCounted>(); + bool ran = false; + + RefPtr<CancelableRunnable> func = + NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; }); + + EXPECT_EQ(foo->refCount(), 2u); + func->Cancel(); + + EXPECT_EQ(foo->refCount(), 1u); + EXPECT_FALSE(ran); + } + + // Test no-op after cancelation. + { + auto foo = MakeRefPtr<TestRefCounted>(); + bool ran = false; + + RefPtr<CancelableRunnable> func = + NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; }); + + EXPECT_EQ(foo->refCount(), 2u); + func->Cancel(); + func->Run(); + + EXPECT_FALSE(ran); + } +} + +static void TestNewRunnableMethod(bool aNamed) { + memset(gRunnableExecuted, false, MAX_TESTS * sizeof(bool)); + // Scope the smart ptrs so that the runnables need to hold on to whatever they + // need + { + RefPtr<nsFoo> foo = new nsFoo(); + RefPtr<nsBar> bar = new nsBar(); + RefPtr<const nsBar> constBar = bar; + + // This pointer will be freed at the end of the block + // Do not dereference this pointer in the runnable method! + RefPtr<nsFoo> rawFoo = new nsFoo(); + + // Read only string. Dereferencing in runnable method to check this works. + char* message = (char*)"Test message"; + + { + auto bar = MakeRefPtr<nsBar>(); + + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", std::move(bar), &nsBar::DoBar1) + : NewRunnableMethod("nsBar::DoBar1", std::move(bar), + &nsBar::DoBar1)); + } + + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1) + : NewRunnableMethod("nsBar::DoBar1", bar, &nsBar::DoBar1)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", constBar, &nsBar::DoBar1Const) + : NewRunnableMethod("nsBar::DoBar1Const", constBar, + &nsBar::DoBar1Const)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2) + : NewRunnableMethod("nsBar::DoBar2", bar, &nsBar::DoBar2)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar, &nsBar::DoBar3, + foo) + : NewRunnableMethod<RefPtr<nsFoo>>("nsBar::DoBar3", bar, + &nsBar::DoBar3, foo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar, &nsBar::DoBar4, + foo) + : NewRunnableMethod<RefPtr<nsFoo>>("nsBar::DoBar4", bar, + &nsBar::DoBar4, foo)); + NS_DispatchToMainThread( + aNamed + ? NewRunnableMethod<nsFoo*>("unused", bar, &nsBar::DoBar5, rawFoo) + : NewRunnableMethod<nsFoo*>("nsBar::DoBar5", bar, &nsBar::DoBar5, + rawFoo)); + NS_DispatchToMainThread( + aNamed + ? NewRunnableMethod<char*>("unused", bar, &nsBar::DoBar6, message) + : NewRunnableMethod<char*>("nsBar::DoBar6", bar, &nsBar::DoBar6, + message)); +#ifdef HAVE_STDCALL + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1std) + : NewRunnableMethod(bar, &nsBar::DoBar1std)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2std) + : NewRunnableMethod(bar, &nsBar::DoBar2std)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar, + &nsBar::DoBar3std, foo) + : NewRunnableMethod<RefPtr<nsFoo>>(bar, &nsBar::DoBar3std, foo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar, + &nsBar::DoBar4std, foo) + : NewRunnableMethod<RefPtr<nsFoo>>(bar, &nsBar::DoBar4std, foo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod<nsFoo*>("unused", bar, &nsBar::DoBar5std, + rawFoo) + : NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5std, rawFoo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod<char*>("unused", bar, &nsBar::DoBar6std, + message) + : NewRunnableMethod<char*>(bar, &nsBar::DoBar6std, message)); +#endif + } + + // Spin the event loop + NS_ProcessPendingEvents(nullptr); + + // Now test a suicidal event in NS_New(Named)Thread + nsCOMPtr<nsIThread> thread; + NS_NewNamedThread("SuicideThread", getter_AddRefs(thread), new TestSuicide()); + ASSERT_TRUE(thread); + + while (!gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL]) { + NS_ProcessPendingEvents(nullptr); + } + + for (uint32_t i = 0; i < MAX_TESTS; i++) { + EXPECT_TRUE(gRunnableExecuted[i]) << "Error in test " << i; + } +} + +TEST(ThreadUtils, RunnableMethod) +{ TestNewRunnableMethod(/* aNamed */ false); } + +TEST(ThreadUtils, NamedRunnableMethod) +{ + // The named overloads shall behave identical to the non-named counterparts. + TestNewRunnableMethod(/* aNamed */ true); + + // Test naming. + { + RefPtr<nsFoo> foo = new nsFoo(); + const char* expectedName = "NamedRunnable"; + bool unused; + RefPtr<Runnable> NamedRunnable = + NewRunnableMethod<bool*>(expectedName, foo, &nsFoo::DoFoo, &unused); + ExpectRunnableName(NamedRunnable, expectedName); + } +} + +class IdleObjectWithoutSetDeadline final { + public: + NS_INLINE_DECL_REFCOUNTING(IdleObjectWithoutSetDeadline) + IdleObjectWithoutSetDeadline() : mRunnableExecuted(false) {} + void Method() { mRunnableExecuted = true; } + bool mRunnableExecuted; + + private: + ~IdleObjectWithoutSetDeadline() = default; +}; + +class IdleObjectParentWithSetDeadline { + public: + IdleObjectParentWithSetDeadline() : mSetDeadlineCalled(false) {} + void SetDeadline(TimeStamp aDeadline) { mSetDeadlineCalled = true; } + bool mSetDeadlineCalled; +}; + +class IdleObjectInheritedSetDeadline final + : public IdleObjectParentWithSetDeadline { + public: + NS_INLINE_DECL_REFCOUNTING(IdleObjectInheritedSetDeadline) + IdleObjectInheritedSetDeadline() : mRunnableExecuted(false) {} + void Method() { mRunnableExecuted = true; } + bool mRunnableExecuted; + + private: + ~IdleObjectInheritedSetDeadline() = default; +}; + +class IdleObject final { + public: + NS_INLINE_DECL_REFCOUNTING(IdleObject) + IdleObject() { + for (uint32_t index = 0; index < ArrayLength(mRunnableExecuted); ++index) { + mRunnableExecuted[index] = false; + mSetIdleDeadlineCalled = false; + } + } + void SetDeadline(TimeStamp aTimeStamp) { mSetIdleDeadlineCalled = true; } + + void CheckExecutedMethods(const char* aKey, uint32_t aNumExecuted) { + uint32_t index; + for (index = 0; index < aNumExecuted; ++index) { + ASSERT_TRUE(mRunnableExecuted[index]) + << aKey << ": Method" << index << " should've executed"; + } + + for (; index < ArrayLength(mRunnableExecuted); ++index) { + ASSERT_FALSE(mRunnableExecuted[index]) + << aKey << ": Method" << index << " shouldn't have executed"; + } + } + + void Method0() { + CheckExecutedMethods("Method0", 0); + mRunnableExecuted[0] = true; + mSetIdleDeadlineCalled = false; + } + + void Method1() { + CheckExecutedMethods("Method1", 1); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[1] = true; + mSetIdleDeadlineCalled = false; + } + + void Method2() { + CheckExecutedMethods("Method2", 2); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[2] = true; + mSetIdleDeadlineCalled = false; + NS_DispatchToCurrentThread( + NewRunnableMethod("IdleObject::Method3", this, &IdleObject::Method3)); + } + + void Method3() { + CheckExecutedMethods("Method3", 3); + + NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), Method4, this, 10, + nsITimer::TYPE_ONE_SHOT, "IdleObject::Method3"); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethodWithTimer("IdleObject::Method5", this, + &IdleObject::Method5), + 50, EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewRunnableMethod("IdleObject::Method6", this, &IdleObject::Method6), + 100, EventQueuePriority::Idle); + + PR_Sleep(PR_MillisecondsToInterval(200)); + mRunnableExecuted[3] = true; + mSetIdleDeadlineCalled = false; + } + + static void Method4(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleObject> self = static_cast<IdleObject*>(aClosure); + self->CheckExecutedMethods("Method4", 4); + self->mRunnableExecuted[4] = true; + self->mSetIdleDeadlineCalled = false; + } + + void Method5() { + CheckExecutedMethods("Method5", 5); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[5] = true; + mSetIdleDeadlineCalled = false; + } + + void Method6() { + CheckExecutedMethods("Method6", 6); + mRunnableExecuted[6] = true; + mSetIdleDeadlineCalled = false; + } + + void Method7() { + CheckExecutedMethods("Method7", 7); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[7] = true; + mSetIdleDeadlineCalled = false; + } + + private: + nsCOMPtr<nsITimer> mTimer; + bool mRunnableExecuted[8]; + bool mSetIdleDeadlineCalled; + ~IdleObject() = default; +}; + +// Disable test due to frequent failures +#if 0 +// because test fails on multiple platforms +TEST(ThreadUtils, IdleRunnableMethod) +{ + { + RefPtr<IdleObject> idle = new IdleObject(); + RefPtr<IdleObjectWithoutSetDeadline> idleNoSetDeadline = + new IdleObjectWithoutSetDeadline(); + RefPtr<IdleObjectInheritedSetDeadline> idleInheritedSetDeadline = + new IdleObjectInheritedSetDeadline(); + + NS_DispatchToCurrentThread( + NewRunnableMethod("IdleObject::Method0", idle, &IdleObject::Method0)); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObject::Method1", idle, + &IdleObject::Method1), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethodWithTimer("IdleObject::Method2", idle, + &IdleObject::Method2), + 60000, EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObject::Method7", idle, + &IdleObject::Method7), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod<const char*, uint32_t>( + "IdleObject::CheckExecutedMethods", idle, + &IdleObject::CheckExecutedMethods, "final", 8), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObjectWithoutSetDeadline::Method", + idleNoSetDeadline, + &IdleObjectWithoutSetDeadline::Method), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObjectInheritedSetDeadline::Method", + idleInheritedSetDeadline, + &IdleObjectInheritedSetDeadline::Method), + EventQueuePriority::Idle); + + NS_ProcessPendingEvents(nullptr); + + ASSERT_TRUE(idleNoSetDeadline->mRunnableExecuted); + ASSERT_TRUE(idleInheritedSetDeadline->mRunnableExecuted); + ASSERT_TRUE(idleInheritedSetDeadline->mSetDeadlineCalled); + } +} +#endif + +TEST(ThreadUtils, IdleTaskRunner) +{ + using namespace mozilla; + + // Repeating. + int cnt1 = 0; + RefPtr<IdleTaskRunner> runner1 = IdleTaskRunner::Create( + [&cnt1](TimeStamp) { + cnt1++; + return true; + }, + "runner1", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), true, nullptr); + + // Non-repeating but callback always return false so it's still repeating. + int cnt2 = 0; + RefPtr<IdleTaskRunner> runner2 = IdleTaskRunner::Create( + [&cnt2](TimeStamp) { + cnt2++; + return false; + }, + "runner2", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), false, nullptr); + + // Repeating until cnt3 >= 2 by returning 'true' in MayStopProcessing + // callback. The strategy is to stop repeating as early as possible so that we + // are more probable to catch the bug if it didn't stop as expected. + int cnt3 = 0; + RefPtr<IdleTaskRunner> runner3 = IdleTaskRunner::Create( + [&cnt3](TimeStamp) { + cnt3++; + return true; + }, + "runner3", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), true, [&cnt3] { return cnt3 >= 2; }); + + // Non-repeating can callback return true so the callback will + // be only run once. + int cnt4 = 0; + RefPtr<IdleTaskRunner> runner4 = IdleTaskRunner::Create( + [&cnt4](TimeStamp) { + cnt4++; + return true; + }, + "runner4", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), false, nullptr); + + // Firstly we wait until the two repeating tasks reach their limits. + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt1"_ns, + [&]() { return cnt1 >= 100; })); + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt2"_ns, + [&]() { return cnt2 >= 100; })); + + // At any point ==> 0 <= cnt3 <= 2 since MayStopProcessing() would return + // true when cnt3 >= 2. + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt3"_ns, [&]() { + if (cnt3 > 2) { + EXPECT_TRUE(false) << "MaybeContinueProcess() doesn't work."; + return true; // Stop on failure. + } + return cnt3 == 2; // Stop finish if we have reached its max value. + })); + + // At any point ==> 0 <= cnt4 <= 1 since this is a non-repeating + // idle runner. + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt4"_ns, [&]() { + // At any point: 0 <= cnt4 <= 1 + if (cnt4 > 1) { + EXPECT_TRUE(false) << "The 'mRepeating' flag doesn't work."; + return true; // Stop on failure. + } + return cnt4 == 1; + })); + + // The repeating timers require an explicit Cancel() call. + runner1->Cancel(); + runner2->Cancel(); +} + +// {9e70a320-be02-11d1-8031-006008159b5a} +#define NS_IFOO_IID \ + { \ + 0x9e70a320, 0xbe02, 0x11d1, { \ + 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \ + } \ + } + +TEST(ThreadUtils, TypeTraits) +{ + static_assert(!mozilla::IsRefcountedSmartPointer<int>::value, + "IsRefcountedSmartPointer<int> should be false"); + static_assert(mozilla::IsRefcountedSmartPointer<RefPtr<int>>::value, + "IsRefcountedSmartPointer<RefPtr<...>> should be true"); + static_assert(mozilla::IsRefcountedSmartPointer<const RefPtr<int>>::value, + "IsRefcountedSmartPointer<const RefPtr<...>> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer<volatile RefPtr<int>>::value, + "IsRefcountedSmartPointer<volatile RefPtr<...>> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer<const volatile RefPtr<int>>::value, + "IsRefcountedSmartPointer<const volatile RefPtr<...>> should be true"); + static_assert(mozilla::IsRefcountedSmartPointer<nsCOMPtr<int>>::value, + "IsRefcountedSmartPointer<nsCOMPtr<...>> should be true"); + static_assert(mozilla::IsRefcountedSmartPointer<const nsCOMPtr<int>>::value, + "IsRefcountedSmartPointer<const nsCOMPtr<...>> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer<volatile nsCOMPtr<int>>::value, + "IsRefcountedSmartPointer<volatile nsCOMPtr<...>> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer<const volatile nsCOMPtr<int>>::value, + "IsRefcountedSmartPointer<const volatile nsCOMPtr<...>> should be true"); + + static_assert(std::is_same_v<int, mozilla::RemoveSmartPointer<int>::Type>, + "RemoveSmartPointer<int>::Type should be int"); + static_assert(std::is_same_v<int*, mozilla::RemoveSmartPointer<int*>::Type>, + "RemoveSmartPointer<int*>::Type should be int*"); + static_assert( + std::is_same_v<UniquePtr<int>, + mozilla::RemoveSmartPointer<UniquePtr<int>>::Type>, + "RemoveSmartPointer<UniquePtr<int>>::Type should be UniquePtr<int>"); + static_assert( + std::is_same_v<int, mozilla::RemoveSmartPointer<RefPtr<int>>::Type>, + "RemoveSmartPointer<RefPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, mozilla::RemoveSmartPointer<const RefPtr<int>>::Type>, + "RemoveSmartPointer<const RefPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, + mozilla::RemoveSmartPointer<volatile RefPtr<int>>::Type>, + "RemoveSmartPointer<volatile RefPtr<int>>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveSmartPointer<const volatile RefPtr<int>>::Type>, + "RemoveSmartPointer<const volatile RefPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, mozilla::RemoveSmartPointer<nsCOMPtr<int>>::Type>, + "RemoveSmartPointer<nsCOMPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, + mozilla::RemoveSmartPointer<const nsCOMPtr<int>>::Type>, + "RemoveSmartPointer<const nsCOMPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, + mozilla::RemoveSmartPointer<volatile nsCOMPtr<int>>::Type>, + "RemoveSmartPointer<volatile nsCOMPtr<int>>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveSmartPointer<const volatile nsCOMPtr<int>>::Type>, + "RemoveSmartPointer<const volatile nsCOMPtr<int>>::Type should be int"); + + static_assert( + std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<int>::Type>, + "RemoveRawOrSmartPointer<int>::Type should be int"); + static_assert( + std::is_same_v<UniquePtr<int>, + mozilla::RemoveRawOrSmartPointer<UniquePtr<int>>::Type>, + "RemoveRawOrSmartPointer<UniquePtr<int>>::Type should be UniquePtr<int>"); + static_assert( + std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<int*>::Type>, + "RemoveRawOrSmartPointer<int*>::Type should be int"); + static_assert( + std::is_same_v<const int, + mozilla::RemoveRawOrSmartPointer<const int*>::Type>, + "RemoveRawOrSmartPointer<const int*>::Type should be const int"); + static_assert( + std::is_same_v<volatile int, + mozilla::RemoveRawOrSmartPointer<volatile int*>::Type>, + "RemoveRawOrSmartPointer<volatile int*>::Type should be volatile int"); + static_assert( + std::is_same_v<const volatile int, mozilla::RemoveRawOrSmartPointer< + const volatile int*>::Type>, + "RemoveRawOrSmartPointer<const volatile int*>::Type should be const " + "volatile int"); + static_assert( + std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<RefPtr<int>>::Type>, + "RemoveRawOrSmartPointer<RefPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, + mozilla::RemoveRawOrSmartPointer<const RefPtr<int>>::Type>, + "RemoveRawOrSmartPointer<const RefPtr<int>>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveRawOrSmartPointer<volatile RefPtr<int>>::Type>, + "RemoveRawOrSmartPointer<volatile RefPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, mozilla::RemoveRawOrSmartPointer< + const volatile RefPtr<int>>::Type>, + "RemoveRawOrSmartPointer<const volatile RefPtr<int>>::Type should be " + "int"); + static_assert( + std::is_same_v<int, + mozilla::RemoveRawOrSmartPointer<nsCOMPtr<int>>::Type>, + "RemoveRawOrSmartPointer<nsCOMPtr<int>>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveRawOrSmartPointer<const nsCOMPtr<int>>::Type>, + "RemoveRawOrSmartPointer<const nsCOMPtr<int>>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveRawOrSmartPointer<volatile nsCOMPtr<int>>::Type>, + "RemoveRawOrSmartPointer<volatile nsCOMPtr<int>>::Type should be int"); + static_assert( + std::is_same_v<int, mozilla::RemoveRawOrSmartPointer< + const volatile nsCOMPtr<int>>::Type>, + "RemoveRawOrSmartPointer<const volatile nsCOMPtr<int>>::Type should be " + "int"); +} + +namespace TestThreadUtils { + +static bool gDebug = false; +static int gAlive, gZombies; +static int gAllConstructions, gConstructions, gCopyConstructions, + gMoveConstructions, gDestructions, gAssignments, gMoves; +struct Spy { + static void ClearActions() { + gAllConstructions = gConstructions = gCopyConstructions = + gMoveConstructions = gDestructions = gAssignments = gMoves = 0; + } + static void ClearAll() { + ClearActions(); + gAlive = 0; + } + + explicit Spy(int aID) : mID(aID) { + ++gAlive; + ++gAllConstructions; + ++gConstructions; + if (gDebug) { + printf("Spy[%d@%p]()\n", mID, this); + } + } + Spy(const Spy& o) : mID(o.mID) { + ++gAlive; + ++gAllConstructions; + ++gCopyConstructions; + if (gDebug) { + printf("Spy[%d@%p](&[%d@%p])\n", mID, this, o.mID, &o); + } + } + Spy(Spy&& o) : mID(o.mID) { + o.mID = -o.mID; + ++gZombies; + ++gAllConstructions; + ++gMoveConstructions; + if (gDebug) { + printf("Spy[%d@%p](&&[%d->%d@%p])\n", mID, this, -o.mID, o.mID, &o); + } + } + ~Spy() { + if (mID >= 0) { + --gAlive; + } else { + --gZombies; + } + ++gDestructions; + if (gDebug) { + printf("~Spy[%d@%p]()\n", mID, this); + } + mID = 0; + } + Spy& operator=(const Spy& o) { + ++gAssignments; + if (gDebug) { + printf("Spy[%d->%d@%p] = &[%d@%p]\n", mID, o.mID, this, o.mID, &o); + } + mID = o.mID; + return *this; + }; + Spy& operator=(Spy&& o) { + --gAlive; + ++gZombies; + ++gMoves; + if (gDebug) { + printf("Spy[%d->%d@%p] = &&[%d->%d@%p]\n", mID, o.mID, this, o.mID, + -o.mID, &o); + } + mID = o.mID; + o.mID = -o.mID; + return *this; + }; + + int mID; // ID given at construction, or negation if was moved from; 0 when + // destroyed. +}; + +struct ISpyWithISupports : public nsISupports { + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + NS_IMETHOD_(nsrefcnt) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; +NS_DEFINE_STATIC_IID_ACCESSOR(ISpyWithISupports, NS_IFOO_IID) +struct SpyWithISupports : public ISpyWithISupports, public Spy { + private: + virtual ~SpyWithISupports() = default; + + public: + explicit SpyWithISupports(int aID) : Spy(aID){}; + NS_DECL_ISUPPORTS + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return mID; } +}; +NS_IMPL_ISUPPORTS(SpyWithISupports, ISpyWithISupports) + +class IThreadUtilsObject : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + NS_IMETHOD_(nsrefcnt) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IThreadUtilsObject, NS_IFOO_IID) + +struct ThreadUtilsObjectNonRefCountedBase { + virtual void MethodFromNonRefCountedBase() {} +}; + +struct ThreadUtilsObject : public IThreadUtilsObject, + public ThreadUtilsObjectNonRefCountedBase { + // nsISupports implementation + NS_DECL_ISUPPORTS + + // IThreadUtilsObject implementation + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return 0; } + + int mCount; // Number of calls + arguments processed. + int mA0, mA1, mA2, mA3; + Spy mSpy; + const Spy* mSpyPtr; + ThreadUtilsObject() + : mCount(0), mA0(0), mA1(0), mA2(0), mA3(0), mSpy(1), mSpyPtr(nullptr) {} + + private: + virtual ~ThreadUtilsObject() = default; + + public: + void Test0() { mCount += 1; } + void Test1i(int a0) { + mCount += 2; + mA0 = a0; + } + void Test2i(int a0, int a1) { + mCount += 3; + mA0 = a0; + mA1 = a1; + } + void Test3i(int a0, int a1, int a2) { + mCount += 4; + mA0 = a0; + mA1 = a1; + mA2 = a2; + } + void Test4i(int a0, int a1, int a2, int a3) { + mCount += 5; + mA0 = a0; + mA1 = a1; + mA2 = a2; + mA3 = a3; + } + void Test1pi(int* ap) { + mCount += 2; + mA0 = ap ? *ap : -1; + } + void Test1pci(const int* ap) { + mCount += 2; + mA0 = ap ? *ap : -1; + } + void Test1ri(int& ar) { + mCount += 2; + mA0 = ar; + } + void Test1rri(int&& arr) { + mCount += 2; + mA0 = arr; + } + void Test1upi(mozilla::UniquePtr<int> aup) { + mCount += 2; + mA0 = aup ? *aup : -1; + } + void Test1rupi(mozilla::UniquePtr<int>& aup) { + mCount += 2; + mA0 = aup ? *aup : -1; + } + void Test1rrupi(mozilla::UniquePtr<int>&& aup) { + mCount += 2; + mA0 = aup ? *aup : -1; + } + + void Test1s(Spy) { mCount += 2; } + void Test1ps(Spy*) { mCount += 2; } + void Test1rs(Spy&) { mCount += 2; } + void Test1rrs(Spy&&) { mCount += 2; } + void Test1ups(mozilla::UniquePtr<Spy>) { mCount += 2; } + void Test1rups(mozilla::UniquePtr<Spy>&) { mCount += 2; } + void Test1rrups(mozilla::UniquePtr<Spy>&&) { mCount += 2; } + + // Possible parameter passing styles: + void TestByValue(Spy s) { + if (gDebug) { + printf("TestByValue(Spy[%d@%p])\n", s.mID, &s); + } + mSpy = s; + }; + void TestByConstLRef(const Spy& s) { + if (gDebug) { + printf("TestByConstLRef(Spy[%d@%p]&)\n", s.mID, &s); + } + mSpy = s; + }; + void TestByRRef(Spy&& s) { + if (gDebug) { + printf("TestByRRef(Spy[%d@%p]&&)\n", s.mID, &s); + } + mSpy = std::move(s); + }; + void TestByLRef(Spy& s) { + if (gDebug) { + printf("TestByLRef(Spy[%d@%p]&)\n", s.mID, &s); + } + mSpy = s; + mSpyPtr = &s; + }; + void TestByPointer(Spy* p) { + if (p) { + if (gDebug) { + printf("TestByPointer(&Spy[%d@%p])\n", p->mID, p); + } + mSpy = *p; + } else { + if (gDebug) { + printf("TestByPointer(nullptr)\n"); + } + } + mSpyPtr = p; + }; + void TestByPointerToConst(const Spy* p) { + if (p) { + if (gDebug) { + printf("TestByPointerToConst(&Spy[%d@%p])\n", p->mID, p); + } + mSpy = *p; + } else { + if (gDebug) { + printf("TestByPointerToConst(nullptr)\n"); + } + } + mSpyPtr = p; + }; +}; + +NS_IMPL_ISUPPORTS(ThreadUtilsObject, IThreadUtilsObject) + +class ThreadUtilsRefCountedFinal final { + public: + ThreadUtilsRefCountedFinal() : m_refCount(0) {} + ~ThreadUtilsRefCountedFinal() = default; + // 'AddRef' and 'Release' methods with different return types, to verify + // that the return type doesn't influence storage selection. + long AddRef(void) { return ++m_refCount; } + void Release(void) { --m_refCount; } + + private: + long m_refCount; +}; + +class ThreadUtilsRefCountedBase { + public: + ThreadUtilsRefCountedBase() : m_refCount(0) {} + virtual ~ThreadUtilsRefCountedBase() = default; + // 'AddRef' and 'Release' methods with different return types, to verify + // that the return type doesn't influence storage selection. + virtual void AddRef(void) { ++m_refCount; } + virtual MozExternalRefCountType Release(void) { return --m_refCount; } + + private: + MozExternalRefCountType m_refCount; +}; + +class ThreadUtilsRefCountedDerived : public ThreadUtilsRefCountedBase {}; + +class ThreadUtilsNonRefCounted {}; + +} // namespace TestThreadUtils + +TEST(ThreadUtils, main) +{ + using namespace TestThreadUtils; + + static_assert(!IsParameterStorageClass<int>::value, + "'int' should not be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreCopyPassByValue<int>>::value, + "StoreCopyPassByValue<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreCopyPassByConstLRef<int>>::value, + "StoreCopyPassByConstLRef<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreCopyPassByLRef<int>>::value, + "StoreCopyPassByLRef<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreCopyPassByRRef<int>>::value, + "StoreCopyPassByRRef<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreRefPassByLRef<int>>::value, + "StoreRefPassByLRef<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreConstRefPassByConstLRef<int>>::value, + "StoreConstRefPassByConstLRef<int> should be recognized as Storage " + "Class"); + static_assert( + IsParameterStorageClass<StoreRefPtrPassByPtr<int>>::value, + "StoreRefPtrPassByPtr<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StorePtrPassByPtr<int>>::value, + "StorePtrPassByPtr<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreConstPtrPassByConstPtr<int>>::value, + "StoreConstPtrPassByConstPtr<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreCopyPassByConstPtr<int>>::value, + "StoreCopyPassByConstPtr<int> should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass<StoreCopyPassByPtr<int>>::value, + "StoreCopyPassByPtr<int> should be recognized as Storage Class"); + + RefPtr<ThreadUtilsObject> rpt(new ThreadUtilsObject); + int count = 0; + + // Test legacy functions. + + nsCOMPtr<nsIRunnable> r1 = + NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt, + &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + r1 = NewRunnableMethod<int>("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, + &ThreadUtilsObject::Test1i, 11); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(11, rpt->mA0); + + // Test calling a method from a non-ref-counted base. + + r1 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObjectNonRefCountedBase::" + "MethodFromNonRefCountedBase", + rpt, &ThreadUtilsObject::MethodFromNonRefCountedBase); + r1->Run(); + EXPECT_EQ(count, rpt->mCount); + + // Test variadic function with simple POD arguments. + + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt, + &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + static_assert(std::is_same_v<::detail::ParameterStorage<int>::Type, + StoreCopyPassByConstLRef<int>>, + "detail::ParameterStorage<int>::Type should be " + "StoreCopyPassByConstLRef<int>"); + static_assert(std::is_same_v< + ::detail::ParameterStorage<StoreCopyPassByValue<int>>::Type, + StoreCopyPassByValue<int>>, + "detail::ParameterStorage<StoreCopyPassByValue<int>>::Type " + "should be StoreCopyPassByValue<int>"); + + r1 = NewRunnableMethod<int>("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, + &ThreadUtilsObject::Test1i, 12); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(12, rpt->mA0); + + r1 = NewRunnableMethod<int, int>("TestThreadUtils::ThreadUtilsObject::Test2i", + rpt, &ThreadUtilsObject::Test2i, 21, 22); + r1->Run(); + EXPECT_EQ(count += 3, rpt->mCount); + EXPECT_EQ(21, rpt->mA0); + EXPECT_EQ(22, rpt->mA1); + + r1 = NewRunnableMethod<int, int, int>( + "TestThreadUtils::ThreadUtilsObject::Test3i", rpt, + &ThreadUtilsObject::Test3i, 31, 32, 33); + r1->Run(); + EXPECT_EQ(count += 4, rpt->mCount); + EXPECT_EQ(31, rpt->mA0); + EXPECT_EQ(32, rpt->mA1); + EXPECT_EQ(33, rpt->mA2); + + r1 = NewRunnableMethod<int, int, int, int>( + "TestThreadUtils::ThreadUtilsObject::Test4i", rpt, + &ThreadUtilsObject::Test4i, 41, 42, 43, 44); + r1->Run(); + EXPECT_EQ(count += 5, rpt->mCount); + EXPECT_EQ(41, rpt->mA0); + EXPECT_EQ(42, rpt->mA1); + EXPECT_EQ(43, rpt->mA2); + EXPECT_EQ(44, rpt->mA3); + + // More interesting types of arguments. + + // Passing a short to make sure forwarding works with an inexact type match. + short int si = 11; + r1 = NewRunnableMethod<int>("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, + &ThreadUtilsObject::Test1i, si); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(si, rpt->mA0); + + // Raw pointer, possible cv-qualified. + static_assert( + std::is_same_v<::detail::ParameterStorage<int*>::Type, + StorePtrPassByPtr<int>>, + "detail::ParameterStorage<int*>::Type should be StorePtrPassByPtr<int>"); + static_assert(std::is_same_v<::detail::ParameterStorage<int* const>::Type, + StorePtrPassByPtr<int>>, + "detail::ParameterStorage<int* const>::Type should be " + "StorePtrPassByPtr<int>"); + static_assert(std::is_same_v<::detail::ParameterStorage<int* volatile>::Type, + StorePtrPassByPtr<int>>, + "detail::ParameterStorage<int* volatile>::Type should be " + "StorePtrPassByPtr<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int* const volatile>::Type, + StorePtrPassByPtr<int>>, + "detail::ParameterStorage<int* const volatile>::Type should be " + "StorePtrPassByPtr<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int*>::Type::stored_type, int*>, + "detail::ParameterStorage<int*>::Type::stored_type should be int*"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int*>::Type::passed_type, int*>, + "detail::ParameterStorage<int*>::Type::passed_type should be int*"); + { + int i = 12; + r1 = NewRunnableMethod<int*>("TestThreadUtils::ThreadUtilsObject::Test1pi", + rpt, &ThreadUtilsObject::Test1pi, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const. + static_assert(std::is_same_v<::detail::ParameterStorage<const int*>::Type, + StoreConstPtrPassByConstPtr<int>>, + "detail::ParameterStorage<const int*>::Type should be " + "StoreConstPtrPassByConstPtr<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<const int* const>::Type, + StoreConstPtrPassByConstPtr<int>>, + "detail::ParameterStorage<const int* const>::Type should be " + "StoreConstPtrPassByConstPtr<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<const int* volatile>::Type, + StoreConstPtrPassByConstPtr<int>>, + "detail::ParameterStorage<const int* volatile>::Type should be " + "StoreConstPtrPassByConstPtr<int>"); + static_assert(std::is_same_v< + ::detail::ParameterStorage<const int* const volatile>::Type, + StoreConstPtrPassByConstPtr<int>>, + "detail::ParameterStorage<const int* const volatile>::Type " + "should be StoreConstPtrPassByConstPtr<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<const int*>::Type::stored_type, + const int*>, + "detail::ParameterStorage<const int*>::Type::stored_type should be const " + "int*"); + static_assert( + std::is_same_v<::detail::ParameterStorage<const int*>::Type::passed_type, + const int*>, + "detail::ParameterStorage<const int*>::Type::passed_type should be const " + "int*"); + { + int i = 1201; + r1 = NewRunnableMethod<const int*>( + "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt, + &ThreadUtilsObject::Test1pci, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to copy. + static_assert(std::is_same_v<StoreCopyPassByPtr<int>::stored_type, int>, + "StoreCopyPassByPtr<int>::stored_type should be int"); + static_assert(std::is_same_v<StoreCopyPassByPtr<int>::passed_type, int*>, + "StoreCopyPassByPtr<int>::passed_type should be int*"); + { + int i = 1202; + r1 = NewRunnableMethod<StoreCopyPassByPtr<int>>( + "TestThreadUtils::ThreadUtilsObject::Test1pi", rpt, + &ThreadUtilsObject::Test1pi, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const copy. + static_assert(std::is_same_v<StoreCopyPassByConstPtr<int>::stored_type, int>, + "StoreCopyPassByConstPtr<int>::stored_type should be int"); + static_assert( + std::is_same_v<StoreCopyPassByConstPtr<int>::passed_type, const int*>, + "StoreCopyPassByConstPtr<int>::passed_type should be const int*"); + { + int i = 1203; + r1 = NewRunnableMethod<StoreCopyPassByConstPtr<int>>( + "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt, + &ThreadUtilsObject::Test1pci, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // nsRefPtr to pointer. + static_assert( + std::is_same_v<::detail::ParameterStorage< + StoreRefPtrPassByPtr<SpyWithISupports>>::Type, + StoreRefPtrPassByPtr<SpyWithISupports>>, + "ParameterStorage<StoreRefPtrPassByPtr<SpyWithISupports>>::Type should " + "be StoreRefPtrPassByPtr<SpyWithISupports>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<SpyWithISupports*>::Type, + StoreRefPtrPassByPtr<SpyWithISupports>>, + "ParameterStorage<SpyWithISupports*>::Type should be " + "StoreRefPtrPassByPtr<SpyWithISupports>"); + static_assert( + std::is_same_v<StoreRefPtrPassByPtr<SpyWithISupports>::stored_type, + RefPtr<SpyWithISupports>>, + "StoreRefPtrPassByPtr<SpyWithISupports>::stored_type should be " + "RefPtr<SpyWithISupports>"); + static_assert( + std::is_same_v<StoreRefPtrPassByPtr<SpyWithISupports>::passed_type, + SpyWithISupports*>, + "StoreRefPtrPassByPtr<SpyWithISupports>::passed_type should be " + "SpyWithISupports*"); + // (more nsRefPtr tests below) + + // nsRefPtr for ref-countable classes that do not derive from ISupports. + static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedFinal>::value, + "ThreadUtilsRefCountedFinal has AddRef() and Release()"); + static_assert( + std::is_same_v< + ::detail::ParameterStorage<ThreadUtilsRefCountedFinal*>::Type, + StoreRefPtrPassByPtr<ThreadUtilsRefCountedFinal>>, + "ParameterStorage<ThreadUtilsRefCountedFinal*>::Type should be " + "StoreRefPtrPassByPtr<ThreadUtilsRefCountedFinal>"); + static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedBase>::value, + "ThreadUtilsRefCountedBase has AddRef() and Release()"); + static_assert( + std::is_same_v< + ::detail::ParameterStorage<ThreadUtilsRefCountedBase*>::Type, + StoreRefPtrPassByPtr<ThreadUtilsRefCountedBase>>, + "ParameterStorage<ThreadUtilsRefCountedBase*>::Type should be " + "StoreRefPtrPassByPtr<ThreadUtilsRefCountedBase>"); + static_assert( + ::detail::HasRefCountMethods<ThreadUtilsRefCountedDerived>::value, + "ThreadUtilsRefCountedDerived has AddRef() and Release()"); + static_assert( + std::is_same_v< + ::detail::ParameterStorage<ThreadUtilsRefCountedDerived*>::Type, + StoreRefPtrPassByPtr<ThreadUtilsRefCountedDerived>>, + "ParameterStorage<ThreadUtilsRefCountedDerived*>::Type should be " + "StoreRefPtrPassByPtr<ThreadUtilsRefCountedDerived>"); + + static_assert(!::detail::HasRefCountMethods<ThreadUtilsNonRefCounted>::value, + "ThreadUtilsNonRefCounted doesn't have AddRef() and Release()"); + static_assert(!std::is_same_v< + ::detail::ParameterStorage<ThreadUtilsNonRefCounted*>::Type, + StoreRefPtrPassByPtr<ThreadUtilsNonRefCounted>>, + "ParameterStorage<ThreadUtilsNonRefCounted*>::Type should NOT " + "be StoreRefPtrPassByPtr<ThreadUtilsNonRefCounted>"); + + // Lvalue reference. + static_assert( + std::is_same_v<::detail::ParameterStorage<int&>::Type, + StoreRefPassByLRef<int>>, + "ParameterStorage<int&>::Type should be StoreRefPassByLRef<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int&>::Type::stored_type, + StoreRefPassByLRef<int>::stored_type>, + "ParameterStorage<int&>::Type::stored_type should be " + "StoreRefPassByLRef<int>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int&>::Type::stored_type, int&>, + "ParameterStorage<int&>::Type::stored_type should be int&"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int&>::Type::passed_type, int&>, + "ParameterStorage<int&>::Type::passed_type should be int&"); + { + int i = 13; + r1 = NewRunnableMethod<int&>("TestThreadUtils::ThreadUtilsObject::Test1ri", + rpt, &ThreadUtilsObject::Test1ri, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Rvalue reference -- Actually storing a copy and then moving it. + static_assert( + std::is_same_v<::detail::ParameterStorage<int&&>::Type, + StoreCopyPassByRRef<int>>, + "ParameterStorage<int&&>::Type should be StoreCopyPassByRRef<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int&&>::Type::stored_type, + StoreCopyPassByRRef<int>::stored_type>, + "ParameterStorage<int&&>::Type::stored_type should be " + "StoreCopyPassByRRef<int>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int&&>::Type::stored_type, int>, + "ParameterStorage<int&&>::Type::stored_type should be int"); + static_assert( + std::is_same_v<::detail::ParameterStorage<int&&>::Type::passed_type, + int&&>, + "ParameterStorage<int&&>::Type::passed_type should be int&&"); + { + int i = 14; + r1 = NewRunnableMethod<int&&>( + "TestThreadUtils::ThreadUtilsObject::Test1rri", rpt, + &ThreadUtilsObject::Test1rri, std::move(i)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(14, rpt->mA0); + + // Null unique pointer, by semi-implicit store&move with "T&&" syntax. + static_assert(std::is_same_v< + ::detail::ParameterStorage<mozilla::UniquePtr<int>&&>::Type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>>, + "ParameterStorage<UniquePtr<int>&&>::Type should be " + "StoreCopyPassByRRef<UniquePtr<int>>"); + static_assert( + std::is_same_v<::detail::ParameterStorage< + mozilla::UniquePtr<int>&&>::Type::stored_type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>, + "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be " + "StoreCopyPassByRRef<UniquePtr<int>>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage< + mozilla::UniquePtr<int>&&>::Type::stored_type, + mozilla::UniquePtr<int>>, + "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be " + "UniquePtr<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage< + mozilla::UniquePtr<int>&&>::Type::passed_type, + mozilla::UniquePtr<int>&&>, + "ParameterStorage<UniquePtr<int>&&>::Type::passed_type should be " + "UniquePtr<int>&&"); + { + mozilla::UniquePtr<int> upi; + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + rpt->mA0 = 0; + + // Null unique pointer, by explicit store&move with "StoreCopyPassByRRef<T>" + // syntax. + static_assert( + std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef< + mozilla::UniquePtr<int>>>::Type::stored_type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_" + "type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef< + mozilla::UniquePtr<int>>>::Type::stored_type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_" + "type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef< + mozilla::UniquePtr<int>>>::Type::stored_type, + mozilla::UniquePtr<int>>, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_" + "type should be UniquePtr<int>"); + static_assert( + std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef< + mozilla::UniquePtr<int>>>::Type::passed_type, + mozilla::UniquePtr<int>&&>, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::passed_" + "type should be UniquePtr<int>&&"); + { + mozilla::UniquePtr<int> upi; + r1 = NewRunnableMethod<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + + // Unique pointer as xvalue. + { + mozilla::UniquePtr<int> upi = mozilla::MakeUnique<int>(1); + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + { + mozilla::UniquePtr<int> upi = mozilla::MakeUnique<int>(1); + r1 = NewRunnableMethod<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + // Unique pointer as prvalue. + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, mozilla::MakeUnique<int>(2)); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(2, rpt->mA0); + + // Unique pointer as lvalue to lref. + { + mozilla::UniquePtr<int> upi; + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&>( + "TestThreadUtils::ThreadUtilsObject::Test1rupi", rpt, + &ThreadUtilsObject::Test1rupi, upi); + // Passed as lref, so Run() must be called while local upi is still alive! + r1->Run(); + } + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + + // Verify copy/move assumptions. + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from lvalue, pass by value\n", __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r2; + { // Block around Spy lifetime. + if (gDebug) { + printf("%d - Spy s(10)\n", __LINE__); + } + Spy s(10); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r2 = " + "NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, s)\n", + __LINE__); + } + r2 = NewRunnableMethod<StoreCopyPassByValue<Spy>>( + "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt, + &ThreadUtilsObject::TestByValue, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with Spy s(10)\n", __LINE__); + } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r2->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(10, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from prvalue, pass by value\n", __LINE__); + } + { + if (gDebug) { + printf( + "%d - r3 = " + "NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, " + "Spy(11))\n", + __LINE__); + } + nsCOMPtr<nsIRunnable> r3 = NewRunnableMethod<StoreCopyPassByValue<Spy>>( + "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt, + &ThreadUtilsObject::TestByValue, Spy(11)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r3->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(11, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + { // Store copy from xvalue, pass by value. + nsCOMPtr<nsIRunnable> r4; + { + Spy s(12); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + r4 = NewRunnableMethod<StoreCopyPassByValue<Spy>>( + "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt, + &ThreadUtilsObject::TestByValue, std::move(s)); + EXPECT_LE(1, gMoveConstructions); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gZombies); + Spy::ClearActions(); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(0, gZombies); + Spy::ClearActions(); + r4->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(12, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + // Won't test xvalues anymore, prvalues are enough to verify all rvalues. + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from lvalue, pass by const lvalue ref\n", + __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r5; + { // Block around Spy lifetime. + if (gDebug) { + printf("%d - Spy s(20)\n", __LINE__); + } + Spy s(20); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r5 = " + "NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef," + " s)\n", + __LINE__); + } + r5 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>( + "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt, + &ThreadUtilsObject::TestByConstLRef, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with Spy s(20)\n", __LINE__); + } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r5->Run(); + EXPECT_EQ(0, gCopyConstructions); // No copies in call. + EXPECT_EQ(20, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from prvalue, pass by const lvalue ref\n", + __LINE__); + } + { + if (gDebug) { + printf( + "%d - r6 = " + "NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef, " + "Spy(21))\n", + __LINE__); + } + nsCOMPtr<nsIRunnable> r6 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>( + "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt, + &ThreadUtilsObject::TestByConstLRef, Spy(21)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r6->Run(); + EXPECT_EQ(0, gCopyConstructions); // No copies in call. + EXPECT_EQ(21, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from lvalue, pass by rvalue ref\n", __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r7; + { // Block around Spy lifetime. + if (gDebug) { + printf("%d - Spy s(30)\n", __LINE__); + } + Spy s(30); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r7 = " + "NewRunnableMethod<StoreCopyPassByRRef<Spy>>(&TestByRRef, s)\n", + __LINE__); + } + r7 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>( + "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt, + &ThreadUtilsObject::TestByRRef, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with Spy s(30)\n", __LINE__); + } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r7->Run(); + EXPECT_LE(1, gMoves); // Move in call. + EXPECT_EQ(30, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. + EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from prvalue, pass by rvalue ref\n", + __LINE__); + } + { + if (gDebug) { + printf( + "%d - r8 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(&TestByRRef, " + "Spy(31))\n", + __LINE__); + } + nsCOMPtr<nsIRunnable> r8 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>( + "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt, + &ThreadUtilsObject::TestByRRef, Spy(31)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r8->Run(); + EXPECT_LE(1, gMoves); // Move in call. + EXPECT_EQ(31, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. + EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store lvalue ref, pass lvalue ref\n", __LINE__); + } + { + if (gDebug) { + printf("%d - Spy s(40)\n", __LINE__); + } + Spy s(40); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - r9 = NewRunnableMethod<Spy&>(&TestByLRef, s)\n", __LINE__); + } + nsCOMPtr<nsIRunnable> r9 = NewRunnableMethod<Spy&>( + "TestThreadUtils::ThreadUtilsObject::TestByLRef", rpt, + &ThreadUtilsObject::TestByLRef, s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r9->Run(); + EXPECT_LE(1, gAssignments); // Assignment from reference in call. + EXPECT_EQ(40, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store nsRefPtr, pass by pointer\n", __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r10; + SpyWithISupports* ptr = 0; + { // Block around RefPtr<Spy> lifetime. + if (gDebug) { + printf("%d - RefPtr<SpyWithISupports> s(new SpyWithISupports(45))\n", + __LINE__); + } + RefPtr<SpyWithISupports> s(new SpyWithISupports(45)); + ptr = s.get(); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r10 = " + "NewRunnableMethod<StoreRefPtrPassByPtr<Spy>>(&TestByRRef, " + "s.get())\n", + __LINE__); + } + r10 = NewRunnableMethod<StoreRefPtrPassByPtr<SpyWithISupports>>( + "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt, + &ThreadUtilsObject::TestByPointer, s.get()); + EXPECT_LE(0, gAllConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with RefPtr<Spy> s\n", __LINE__); + } + } + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r10->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(45, rpt->mSpy.mID); + EXPECT_EQ(ptr, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store pointer to lvalue, pass by pointer\n", __LINE__); + } + { + if (gDebug) { + printf("%d - Spy s(55)\n", __LINE__); + } + Spy s(55); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - r11 = NewRunnableMethod<Spy*>(&TestByPointer, s)\n", + __LINE__); + } + nsCOMPtr<nsIRunnable> r11 = NewRunnableMethod<Spy*>( + "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt, + &ThreadUtilsObject::TestByPointer, &s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r11->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(55, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store pointer to const lvalue, pass by pointer\n", + __LINE__); + } + { + if (gDebug) { + printf("%d - Spy s(60)\n", __LINE__); + } + Spy s(60); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - r12 = NewRunnableMethod<Spy*>(&TestByPointer, s)\n", + __LINE__); + } + nsCOMPtr<nsIRunnable> r12 = NewRunnableMethod<const Spy*>( + "TestThreadUtils::ThreadUtilsObject::TestByPointerToConst", rpt, + &ThreadUtilsObject::TestByPointerToConst, &s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r12->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(60, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); +} diff --git a/xpcom/tests/gtest/TestThreads.cpp b/xpcom/tests/gtest/TestThreads.cpp new file mode 100644 index 0000000000..c4b1217031 --- /dev/null +++ b/xpcom/tests/gtest/TestThreads.cpp @@ -0,0 +1,415 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> +#include <memory> +#include "nspr.h" +#include "nsCOMPtr.h" +#include "nsITargetShutdownTask.h" +#include "nsIThread.h" +#include "nsXPCOM.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Monitor.h" +#include "mozilla/SyncRunnable.h" +#include "gtest/gtest.h" + +#ifdef XP_WIN +# include <windef.h> +# include <winuser.h> +#endif + +using namespace mozilla; + +class nsRunner final : public Runnable { + ~nsRunner() = default; + + public: + NS_IMETHOD Run() override { + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread)); + EXPECT_NS_SUCCEEDED(rv); + printf("running %d on thread %p\n", mNum, (void*)thread.get()); + + // if we don't do something slow, we'll never see the other + // worker threads run + PR_Sleep(PR_MillisecondsToInterval(100)); + + return rv; + } + + explicit nsRunner(int num) : Runnable("nsRunner"), mNum(num) {} + + protected: + int mNum; +}; + +TEST(Threads, Main) +{ + nsresult rv; + + nsCOMPtr<nsIRunnable> event = new nsRunner(0); + EXPECT_TRUE(event); + + nsCOMPtr<nsIThread> runner; + rv = NS_NewNamedThread("TestThreadsMain", getter_AddRefs(runner), event); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIThread> thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + EXPECT_NS_SUCCEEDED(rv); + + rv = runner->Shutdown(); // wait for the runner to die before quitting + EXPECT_NS_SUCCEEDED(rv); + + PR_Sleep( + PR_MillisecondsToInterval(100)); // hopefully the runner will quit here +} + +class nsStressRunner final : public Runnable { + public: + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + PR_Sleep(1); + if (!PR_AtomicDecrement(&gNum)) { + printf(" last thread was %d\n", mNum); + } + return NS_OK; + } + + explicit nsStressRunner(int num) + : Runnable("nsStressRunner"), mNum(num), mWasRun(false) { + PR_AtomicIncrement(&gNum); + } + + static int32_t GetGlobalCount() { return gNum; } + + private: + ~nsStressRunner() { EXPECT_TRUE(mWasRun); } + + protected: + static int32_t gNum; + int32_t mNum; + bool mWasRun; +}; + +int32_t nsStressRunner::gNum = 0; + +TEST(Threads, Stress) +{ +#if defined(XP_WIN) && defined(MOZ_ASAN) // Easily hits OOM + const int loops = 250; +#else + const int loops = 1000; +#endif + + const int threads = 50; + + for (int i = 0; i < loops; i++) { + printf("Loop %d of %d\n", i + 1, loops); + + int k; + nsIThread** array = new nsIThread*[threads]; + + EXPECT_EQ(nsStressRunner::GetGlobalCount(), 0); + + for (k = 0; k < threads; k++) { + nsCOMPtr<nsIThread> t; + nsresult rv = NS_NewNamedThread("StressRunner", getter_AddRefs(t), + new nsStressRunner(k)); + EXPECT_NS_SUCCEEDED(rv); + NS_ADDREF(array[k] = t); + } + + for (k = threads - 1; k >= 0; k--) { + array[k]->Shutdown(); + NS_RELEASE(array[k]); + } + delete[] array; + } +} + +mozilla::Monitor* gAsyncShutdownReadyMonitor; +mozilla::Monitor* gBeginAsyncShutdownMonitor; + +class AsyncShutdownPreparer : public Runnable { + public: + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + + mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor); + lock.Notify(); + + return NS_OK; + } + + explicit AsyncShutdownPreparer() + : Runnable("AsyncShutdownPreparer"), mWasRun(false) {} + + private: + virtual ~AsyncShutdownPreparer() { EXPECT_TRUE(mWasRun); } + + protected: + bool mWasRun; +}; + +class AsyncShutdownWaiter : public Runnable { + public: + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + + nsCOMPtr<nsIThread> t; + nsresult rv; + + { + mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor); + + rv = NS_NewNamedThread("AsyncShutdownPr", getter_AddRefs(t), + new AsyncShutdownPreparer()); + EXPECT_NS_SUCCEEDED(rv); + + lock.Wait(); + } + + rv = t->AsyncShutdown(); + EXPECT_NS_SUCCEEDED(rv); + + return NS_OK; + } + + explicit AsyncShutdownWaiter() + : Runnable("AsyncShutdownWaiter"), mWasRun(false) {} + + private: + virtual ~AsyncShutdownWaiter() { EXPECT_TRUE(mWasRun); } + + protected: + bool mWasRun; +}; + +class SameThreadSentinel : public Runnable { + public: + NS_IMETHOD Run() override { + mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor); + lock.Notify(); + return NS_OK; + } + + SameThreadSentinel() : Runnable("SameThreadSentinel") {} + + private: + virtual ~SameThreadSentinel() = default; +}; + +TEST(Threads, AsyncShutdown) +{ + gAsyncShutdownReadyMonitor = new mozilla::Monitor("gAsyncShutdownReady"); + gBeginAsyncShutdownMonitor = new mozilla::Monitor("gBeginAsyncShutdown"); + + nsCOMPtr<nsIThread> t; + nsresult rv; + + { + mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor); + + rv = NS_NewNamedThread("AsyncShutdownWt", getter_AddRefs(t), + new AsyncShutdownWaiter()); + EXPECT_NS_SUCCEEDED(rv); + + lock.Wait(); + } + + NS_DispatchToCurrentThread(new SameThreadSentinel()); + rv = t->Shutdown(); + EXPECT_NS_SUCCEEDED(rv); + + delete gAsyncShutdownReadyMonitor; + delete gBeginAsyncShutdownMonitor; +} + +static void threadProc(void* arg) { + // printf(" running thread %d\n", (int) arg); + PR_Sleep(1); + EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(PR_GetCurrentThread())); +} + +TEST(Threads, StressNSPR) +{ +#if defined(XP_WIN) && defined(MOZ_ASAN) // Easily hits OOM + const int loops = 250; +#else + const int loops = 1000; +#endif + + const int threads = 50; + + for (int i = 0; i < loops; i++) { + printf("Loop %d of %d\n", i + 1, loops); + + intptr_t k; + PRThread** array = new PRThread*[threads]; + + for (k = 0; k < threads; k++) { + array[k] = PR_CreateThread(PR_USER_THREAD, threadProc, (void*)k, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + EXPECT_TRUE(array[k]); + } + + for (k = 0; k < threads; k++) { + EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(array[k])); + } + + for (k = threads - 1; k >= 0; k--) { + PR_JoinThread(array[k]); + } + delete[] array; + } +} + +TEST(Threads, GetCurrentSerialEventTarget) +{ + nsCOMPtr<nsIThread> thread; + nsresult rv = + NS_NewNamedThread("Testing Thread", getter_AddRefs(thread), + NS_NewRunnableFunction("Testing Thread::check", []() { + nsCOMPtr<nsISerialEventTarget> serialEventTarget = + GetCurrentSerialEventTarget(); + nsCOMPtr<nsIThread> thread = NS_GetCurrentThread(); + EXPECT_EQ(thread.get(), serialEventTarget.get()); + })); + MOZ_ALWAYS_SUCCEEDS(rv); + thread->Shutdown(); +} + +namespace { + +class TestShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit TestShutdownTask(std::function<void()> aCallback) + : mCallback(std::move(aCallback)) {} + + void TargetShutdown() override { + if (mCallback) { + mCallback(); + } + } + + private: + ~TestShutdownTask() = default; + std::function<void()> mCallback; +}; + +NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask) + +} // namespace + +TEST(Threads, ShutdownTask) +{ + auto shutdownTaskRun = std::make_shared<bool>(); + auto runnableFromShutdownRun = std::make_shared<bool>(); + + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread)); + MOZ_ALWAYS_SUCCEEDS(rv); + + nsCOMPtr<nsITargetShutdownTask> shutdownTask = new TestShutdownTask([=] { + EXPECT_TRUE(thread->IsOnCurrentThread()); + + ASSERT_FALSE(*shutdownTaskRun); + *shutdownTaskRun = true; + + nsCOMPtr<nsITargetShutdownTask> dummyTask = new TestShutdownTask([] {}); + nsresult rv = thread->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + MOZ_ALWAYS_SUCCEEDS( + thread->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] { + EXPECT_TRUE(thread->IsOnCurrentThread()); + + nsCOMPtr<nsITargetShutdownTask> dummyTask = + new TestShutdownTask([] {}); + nsresult rv = thread->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + ASSERT_FALSE(*runnableFromShutdownRun); + *runnableFromShutdownRun = true; + }))); + }); + MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + RefPtr<mozilla::SyncRunnable> syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + thread->Shutdown(); + + ASSERT_TRUE(*shutdownTaskRun); + ASSERT_TRUE(*runnableFromShutdownRun); +} + +TEST(Threads, UnregisteredShutdownTask) +{ + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread)); + MOZ_ALWAYS_SUCCEEDS(rv); + + nsCOMPtr<nsITargetShutdownTask> shutdownTask = + new TestShutdownTask([=] { MOZ_CRASH("should not be run"); }); + + MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask)); + + RefPtr<mozilla::SyncRunnable> syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread)); + + MOZ_ALWAYS_SUCCEEDS(thread->UnregisterShutdownTask(shutdownTask)); + + thread->Shutdown(); +} + +#if (defined(XP_WIN) || !defined(DEBUG)) && !defined(XP_MACOSX) +TEST(Threads, OptionsIsUiThread) +{ + // On Windows, test that the isUiThread flag results in a GUI thread. + // In non-Windows non-debug builds, test that the isUiThread flag is ignored. + + nsCOMPtr<nsIThread> thread; + nsIThreadManager::ThreadCreationOptions options; + options.isUiThread = true; + MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread( + "Testing Thread", getter_AddRefs(thread), nullptr, options)); + + bool isGuiThread = false; + auto syncRunnable = + MakeRefPtr<SyncRunnable>(NS_NewRunnableFunction(__func__, [&] { +# ifdef XP_WIN + isGuiThread = ::IsGUIThread(false); +# endif + })); + MOZ_ALWAYS_SUCCEEDS(syncRunnable->DispatchToThread(thread)); + + bool expectGuiThread = false; +# ifdef XP_WIN + expectGuiThread = true; +# endif + EXPECT_EQ(expectGuiThread, isGuiThread); + + thread->Shutdown(); +} +#endif diff --git a/xpcom/tests/gtest/TestThreads_mac.mm b/xpcom/tests/gtest/TestThreads_mac.mm new file mode 100644 index 0000000000..cb6e1c83cc --- /dev/null +++ b/xpcom/tests/gtest/TestThreads_mac.mm @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#include <Foundation/Foundation.h> + +#include "gtest/gtest.h" +#include "mozilla/SyncRunnable.h" +#include "nsIThread.h" +#include "nsIThreadManager.h" + +using namespace mozilla; + +// Tests whether an nsThread ran a block in its NSRunLoop. +// ThreadCreationOptions.isUiThread gets set to aIsUiThread. +// Returns true if a block ran on the NSRunLoop. +bool UiThreadRunsRunLoop(bool aIsUiThread) { + nsCOMPtr<nsIThread> thread; + nsIThreadManager::ThreadCreationOptions options; + options.isUiThread = aIsUiThread; + MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread( + "Testing Thread", getter_AddRefs(thread), nullptr, options)); + + __block bool blockRanInRunLoop = false; + { + // We scope this so `loop` doesn't live past `thread-Shutdown()` since this + // file is compiled without ARC. + NSRunLoop* loop = nil; + auto syncRunnable = + MakeRefPtr<SyncRunnable>(NS_NewRunnableFunction(__func__, [&] { + // If the thread doesn't already have an NSRunLoop, one will be + // created. + loop = NSRunLoop.currentRunLoop; + })); + MOZ_ALWAYS_SUCCEEDS(syncRunnable->DispatchToThread(thread)); + + [loop performBlock:^void() { + blockRanInRunLoop = true; + }]; + } + + thread->Shutdown(); + return blockRanInRunLoop; +} + +TEST(ThreadsMac, OptionsIsUiThread) +{ + const bool isUiThread = true; + const bool isNoUiThread = false; + + EXPECT_TRUE(UiThreadRunsRunLoop(isUiThread)); + EXPECT_FALSE(UiThreadRunsRunLoop(isNoUiThread)); +} diff --git a/xpcom/tests/gtest/TestThrottledEventQueue.cpp b/xpcom/tests/gtest/TestThrottledEventQueue.cpp new file mode 100644 index 0000000000..64f34ff14f --- /dev/null +++ b/xpcom/tests/gtest/TestThrottledEventQueue.cpp @@ -0,0 +1,613 @@ +/* -*- 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 <functional> +#include <queue> +#include <string> +#include <utility> + +#include "MainThreadUtils.h" +#include "gtest/gtest.h" +#include "mozilla/Attributes.h" +#include "mozilla/CondVar.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThrottledEventQueue.h" +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsIRunnable.h" +#include "nsISerialEventTarget.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "prinrval.h" + +using mozilla::CondVar; +using mozilla::MakeRefPtr; +using mozilla::Mutex; +using mozilla::MutexAutoLock; +using mozilla::ThrottledEventQueue; +using std::function; +using std::string; + +namespace TestThrottledEventQueue { + +// A simple queue of runnables, to serve as the base target of +// ThrottledEventQueues in tests. +// +// This is much simpler than mozilla::TaskQueue, and so better for unit tests. +// It's about the same as mozilla::EventQueue, but that doesn't implement +// nsIEventTarget, so it can't be the base target of a ThrottledEventQueue. +struct RunnableQueue : nsISerialEventTarget { + std::queue<nsCOMPtr<nsIRunnable>> runnables; + + bool IsEmpty() { return runnables.empty(); } + size_t Length() { return runnables.size(); } + + [[nodiscard]] nsresult Run() { + while (!runnables.empty()) { + auto runnable = std::move(runnables.front()); + runnables.pop(); + nsresult rv = runnable->Run(); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; + } + + // nsIEventTarget methods + + [[nodiscard]] NS_IMETHODIMP Dispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags) override { + MOZ_ALWAYS_TRUE(aFlags == nsIEventTarget::DISPATCH_NORMAL); + runnables.push(aRunnable); + return NS_OK; + } + + [[nodiscard]] NS_IMETHODIMP DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) override { + RefPtr<nsIRunnable> r = aRunnable; + return Dispatch(r.forget(), aFlags); + } + + NS_IMETHOD_(bool) + IsOnCurrentThreadInfallible(void) override { return NS_IsMainThread(); } + + [[nodiscard]] NS_IMETHOD IsOnCurrentThread(bool* retval) override { + *retval = IsOnCurrentThreadInfallible(); + return NS_OK; + } + + [[nodiscard]] NS_IMETHODIMP DelayedDispatch( + already_AddRefed<nsIRunnable> aEvent, uint32_t aDelay) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // nsISupports methods + + NS_DECL_THREADSAFE_ISUPPORTS + + private: + virtual ~RunnableQueue() = default; +}; + +NS_IMPL_ISUPPORTS(RunnableQueue, nsIEventTarget, nsISerialEventTarget) + +static void Enqueue(nsIEventTarget* target, function<void()>&& aCallable) { + nsresult rv = target->Dispatch( + NS_NewRunnableFunction("TEQ GTest", std::move(aCallable))); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); +} + +} // namespace TestThrottledEventQueue + +using namespace TestThrottledEventQueue; + +TEST(ThrottledEventQueue, RunnableQueue) +{ + string log; + + RefPtr<RunnableQueue> queue = MakeRefPtr<RunnableQueue>(); + Enqueue(queue, [&]() { log += 'a'; }); + Enqueue(queue, [&]() { log += 'b'; }); + Enqueue(queue, [&]() { log += 'c'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(queue->Run()); + ASSERT_EQ(log, "abc"); +} + +TEST(ThrottledEventQueue, SimpleDispatch) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 1"); + + Enqueue(throttled, [&]() { log += 'a'; }); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, MixedDispatch) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 2"); + + // A ThrottledEventQueue limits its impact on the base target by only queuing + // its next event on the base once the prior event has been run. What it + // actually queues on the base is a sort of proxy event called an + // "executor": the base running the executor draws an event from the + // ThrottledEventQueue and runs that. If the ThrottledEventQueue has further + // events, it re-queues the executor on the base, effectively "going to the + // back of the line". + + // Queue an event on the ThrottledEventQueue. This also queues the "executor" + // event on the base. + Enqueue(throttled, [&]() { log += 'a'; }); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 1U); + + // Add a second event to the throttled queue. The executor is already queued. + Enqueue(throttled, [&]() { log += 'b'; }); + ASSERT_EQ(throttled->Length(), 2U); + ASSERT_EQ(base->Length(), 1U); + + // Add an event directly to the base, after the executor. + Enqueue(base, [&]() { log += 'c'; }); + ASSERT_EQ(throttled->Length(), 2U); + ASSERT_EQ(base->Length(), 2U); + + // Run the base target. This runs: + // - the executor, which runs the first event from the ThrottledEventQueue, + // and re-enqueues itself + // - the event queued directly on the base + // - the executor again, which runs the second event from the + // ThrottledEventQueue. + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "acb"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, EnqueueFromRun) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 3"); + + // When an event from the throttled queue dispatches a new event directly to + // the base target, it is queued after the executor, so the next event from + // the throttled queue will run before it. + Enqueue(base, [&]() { log += 'a'; }); + Enqueue(throttled, [&]() { + log += 'b'; + Enqueue(base, [&]() { log += 'c'; }); + }); + Enqueue(throttled, [&]() { log += 'd'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "abdc"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, RunFromRun) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 4"); + + // Running the event queue from within an event (i.e., a nested event loop) + // does not stall the ThrottledEventQueue. + Enqueue(throttled, [&]() { + log += '('; + // This should run subsequent events from throttled. + ASSERT_NS_SUCCEEDED(base->Run()); + log += ')'; + }); + + Enqueue(throttled, [&]() { log += 'a'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "(a)"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, DropWhileRunning) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + + // If we drop the event queue while it still has events, they still run. + { + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 5"); + Enqueue(throttled, [&]() { log += 'a'; }); + } + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); +} + +TEST(ThrottledEventQueue, AwaitIdle) +{ + Mutex mutex MOZ_UNANNOTATED("TEQ AwaitIdle"); + CondVar cond(mutex, "TEQ AwaitIdle"); + + string dequeue_await; // mutex + bool threadFinished = false; // mutex & cond + bool runnableFinished = false; // main thread only + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 6"); + + // Put an event in the queue so the AwaitIdle might block. + Enqueue(throttled, [&]() { runnableFinished = true; }); + + // Create a separate thread that waits for the queue to become idle, and + // then takes observable action. + nsCOMPtr<nsIRunnable> await = NS_NewRunnableFunction("TEQ AwaitIdle", [&]() { + throttled->AwaitIdle(); + MutexAutoLock lock(mutex); + dequeue_await += " await"; + threadFinished = true; + cond.Notify(); + }); + + nsCOMPtr<nsIThread> thread; + nsresult rv = + NS_NewNamedThread("TEQ AwaitIdle", getter_AddRefs(thread), await); + ASSERT_NS_SUCCEEDED(rv); + + // We can't guarantee that the thread has reached the AwaitIdle call, but we + // can get pretty close. Either way, it shouldn't affect the behavior of the + // test. + PR_Sleep(PR_MillisecondsToInterval(100)); + + // Drain the queue. + { + MutexAutoLock lock(mutex); + ASSERT_EQ(dequeue_await, ""); + dequeue_await += "dequeue"; + ASSERT_FALSE(threadFinished); + } + ASSERT_FALSE(runnableFinished); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_TRUE(runnableFinished); + + // Wait for the thread to finish. + { + MutexAutoLock lock(mutex); + while (!threadFinished) cond.Wait(); + ASSERT_EQ(dequeue_await, "dequeue await"); + } + + ASSERT_NS_SUCCEEDED(thread->Shutdown()); +} + +TEST(ThrottledEventQueue, AwaitIdleMixed) +{ + // Create a separate thread that waits for the queue to become idle, and + // then takes observable action. + nsCOMPtr<nsIThread> thread; + ASSERT_TRUE(NS_SUCCEEDED( + NS_NewNamedThread("AwaitIdleMixed", getter_AddRefs(thread)))); + + Mutex mutex MOZ_UNANNOTATED("AwaitIdleMixed"); + CondVar cond(mutex, "AwaitIdleMixed"); + + // The following are protected by mutex and cond, above. + string log; + bool threadStarted = false; + bool threadFinished = false; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 7"); + + Enqueue(throttled, [&]() { + MutexAutoLock lock(mutex); + log += 'a'; + }); + + Enqueue(throttled, [&]() { + MutexAutoLock lock(mutex); + log += 'b'; + }); + + nsCOMPtr<nsIRunnable> await = NS_NewRunnableFunction("AwaitIdleMixed", [&]() { + { + MutexAutoLock lock(mutex); + + // Note that we are about to begin awaiting. When the main thread sees + // this notification, it will begin draining the queue. + log += '('; + threadStarted = true; + cond.Notify(); + } + + // Wait for the main thread to drain the TEQ. + throttled->AwaitIdle(); + + { + MutexAutoLock lock(mutex); + + // Note that we have finished awaiting. + log += ')'; + threadFinished = true; + cond.Notify(); + } + }); + + { + MutexAutoLock lock(mutex); + ASSERT_EQ(log, ""); + } + + ASSERT_NS_SUCCEEDED(thread->Dispatch(await.forget())); + + // Wait for the thread to be ready to await. We can't be sure it will actually + // be blocking before we get around to draining the event queue, but that's + // the nature of the API; this test should work even if we drain the queue + // before it awaits. + { + MutexAutoLock lock(mutex); + while (!threadStarted) cond.Wait(); + ASSERT_EQ(log, "("); + } + + // Let the queue drain. + ASSERT_NS_SUCCEEDED(base->Run()); + + { + MutexAutoLock lock(mutex); + // The first runnable must always finish before AwaitIdle returns. But the + // TEQ notifies the condition variable as soon as it dequeues the last + // runnable, without waiting for that runnable to complete. So the thread + // and the last runnable could run in either order. Or, we might beat the + // thread to the mutex. + // + // (The only combination excluded here is "(a)": the 'b' runnable should + // definitely have run.) + ASSERT_TRUE(log == "(ab" || log == "(a)b" || log == "(ab)"); + while (!threadFinished) cond.Wait(); + ASSERT_TRUE(log == "(a)b" || log == "(ab)"); + } + + ASSERT_NS_SUCCEEDED(thread->Shutdown()); +} + +TEST(ThrottledEventQueue, SimplePauseResume) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 8"); + + ASSERT_FALSE(throttled->IsPaused()); + + Enqueue(throttled, [&]() { log += 'a'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + ASSERT_TRUE(throttled->IsPaused()); + + Enqueue(throttled, [&]() { log += 'b'; }); + + ASSERT_EQ(log, "a"); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_FALSE(throttled->IsPaused()); + + ASSERT_EQ(log, "a"); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "ab"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, MixedPauseResume) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 9"); + + ASSERT_FALSE(throttled->IsPaused()); + + Enqueue(base, [&]() { log += 'A'; }); + Enqueue(throttled, [&]() { + log += 'b'; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(throttled->SetIsPaused(true))); + }); + Enqueue(throttled, [&]() { log += 'c'; }); + Enqueue(base, [&]() { log += 'D'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + // Since the 'b' event paused the throttled queue, 'c' should not have run. + // but 'D' was enqueued directly on the base, and should have run. + ASSERT_EQ(log, "AbD"); + ASSERT_TRUE(base->IsEmpty()); + ASSERT_FALSE(throttled->IsEmpty()); + ASSERT_TRUE(throttled->IsPaused()); + + Enqueue(base, [&]() { log += 'E'; }); + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + Enqueue(base, [&]() { log += 'F'; }); + ASSERT_FALSE(throttled->IsPaused()); + + ASSERT_NS_SUCCEEDED(base->Run()); + // Since we've unpaused, 'c' should be able to run now. The executor should + // have been enqueued between 'E' and 'F'. + ASSERT_EQ(log, "AbDEcF"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, AwaitIdlePaused) +{ + Mutex mutex MOZ_UNANNOTATED("AwaitIdlePaused"); + CondVar cond(mutex, "AwaitIdlePaused"); + + string dequeue_await; // mutex + bool threadFinished = false; // mutex & cond + bool runnableFinished = false; // main thread only + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 10"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + + // Put an event in the queue so the AwaitIdle might block. Since throttled is + // paused, this should not enqueue an executor in the base target. + Enqueue(throttled, [&]() { runnableFinished = true; }); + ASSERT_TRUE(base->IsEmpty()); + + // Create a separate thread that waits for the queue to become idle, and + // then takes observable action. + nsCOMPtr<nsIRunnable> await = + NS_NewRunnableFunction("AwaitIdlePaused", [&]() { + throttled->AwaitIdle(); + MutexAutoLock lock(mutex); + dequeue_await += " await"; + threadFinished = true; + cond.Notify(); + }); + + nsCOMPtr<nsIThread> thread; + nsresult rv = + NS_NewNamedThread("AwaitIdlePaused", getter_AddRefs(thread), await); + ASSERT_NS_SUCCEEDED(rv); + + // We can't guarantee that the thread has reached the AwaitIdle call, but we + // can get pretty close. Either way, it shouldn't affect the behavior of the + // test. + PR_Sleep(PR_MillisecondsToInterval(100)); + + // The AwaitIdle call should be blocked, even though there is no executor, + // because throttled is paused. + { + MutexAutoLock lock(mutex); + ASSERT_EQ(dequeue_await, ""); + dequeue_await += "dequeue"; + ASSERT_FALSE(threadFinished); + } + + // A paused TEQ contributes no events to its base target. (This is covered by + // other tests...) + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_TRUE(base->IsEmpty()); + ASSERT_FALSE(throttled->IsEmpty()); + + // Resume and drain the queue. + ASSERT_FALSE(runnableFinished); + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); + ASSERT_TRUE(runnableFinished); + + // Wait for the thread to finish. + { + MutexAutoLock lock(mutex); + while (!threadFinished) cond.Wait(); + ASSERT_EQ(dequeue_await, "dequeue await"); + } + + ASSERT_NS_SUCCEEDED(thread->Shutdown()); +} + +TEST(ThrottledEventQueue, ExecutorTransitions) +{ + string log; + + auto base = MakeRefPtr<RunnableQueue>(); + RefPtr<ThrottledEventQueue> throttled = + ThrottledEventQueue::Create(base, "test queue 11"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + + // Since we're paused, queueing an event on throttled shouldn't queue the + // executor on the base target. + Enqueue(throttled, [&]() { log += 'a'; }); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 0U); + + // Resuming throttled should create the executor, since throttled is not + // empty. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 1U); + + // Pausing can't remove the executor from the base target since we've already + // queued it there, but it can ensure that it doesn't do anything. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, ""); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 0U); + + // As before, resuming must create the executor, since throttled is not empty. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 1U); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + ASSERT_EQ(throttled->Length(), 0U); + ASSERT_EQ(base->Length(), 0U); + + // Since throttled is empty, pausing and resuming now should not enqueue an + // executor. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_EQ(throttled->Length(), 0U); + ASSERT_EQ(base->Length(), 0U); +} diff --git a/xpcom/tests/gtest/TestTimeStamp.cpp b/xpcom/tests/gtest/TestTimeStamp.cpp new file mode 100644 index 0000000000..acd8ff575c --- /dev/null +++ b/xpcom/tests/gtest/TestTimeStamp.cpp @@ -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/. */ + +#include "mozilla/TimeStamp.h" + +#include "prinrval.h" +#include "prthread.h" + +#include "gtest/gtest.h" + +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +TEST(TimeStamp, Main) +{ + TimeDuration td; + EXPECT_TRUE(td.ToSeconds() == 0.0); + EXPECT_TRUE(TimeDuration::FromSeconds(5).ToSeconds() == 5.0); + EXPECT_TRUE(TimeDuration::FromMilliseconds(5000).ToSeconds() == 5.0); + EXPECT_TRUE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(2)); + EXPECT_FALSE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(2) > TimeDuration::FromSeconds(1)); + EXPECT_FALSE(TimeDuration::FromSeconds(1) > TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(2)); + EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(1)); + EXPECT_FALSE(TimeDuration::FromSeconds(2) <= TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(2) >= TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(1)); + EXPECT_FALSE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(2)); + + TimeStamp ts; + EXPECT_TRUE(ts.IsNull()); + + ts = TimeStamp::Now(); + EXPECT_TRUE(!ts.IsNull()); + EXPECT_TRUE((ts - ts).ToSeconds() == 0.0); + + PR_Sleep(PR_SecondsToInterval(2)); + + TimeStamp ts2(TimeStamp::Now()); + EXPECT_TRUE(ts2 > ts); + EXPECT_FALSE(ts > ts); + EXPECT_TRUE(ts < ts2); + EXPECT_FALSE(ts < ts); + EXPECT_TRUE(ts <= ts2); + EXPECT_TRUE(ts <= ts); + EXPECT_FALSE(ts2 <= ts); + EXPECT_TRUE(ts2 >= ts); + EXPECT_TRUE(ts2 >= ts); + EXPECT_FALSE(ts >= ts2); + + // We can't be sure exactly how long PR_Sleep slept for. It should have + // slept for at least one second. We might have slept a lot longer due + // to process scheduling, but hopefully not more than 10 seconds. + td = ts2 - ts; + EXPECT_TRUE(td.ToSeconds() > 1.0); + EXPECT_TRUE(td.ToSeconds() < 20.0); + td = ts - ts2; + EXPECT_TRUE(td.ToSeconds() < -1.0); + EXPECT_TRUE(td.ToSeconds() > -20.0); + + double resolution = TimeDuration::Resolution().ToSecondsSigDigits(); + printf(" (platform timer resolution is ~%g s)\n", resolution); + EXPECT_TRUE(1e-10 < resolution); + // Don't upper-bound sanity check ... although NSPR reports 1ms + // resolution, it might be lying, so we shouldn't compare with it +} diff --git a/xpcom/tests/gtest/TestTimers.cpp b/xpcom/tests/gtest/TestTimers.cpp new file mode 100644 index 0000000000..ea8792e273 --- /dev/null +++ b/xpcom/tests/gtest/TestTimers.cpp @@ -0,0 +1,928 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIThread.h" +#include "nsITimer.h" + +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +#include "prinrval.h" +#include "prmon.h" +#include "prthread.h" +#include "mozilla/Attributes.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Services.h" + +#include "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPrefs_timer.h" + +#include <list> +#include <vector> + +#include "gtest/gtest.h" + +using namespace mozilla; + +typedef nsresult (*TestFuncPtr)(); + +class AutoTestThread { + public: + AutoTestThread() { + nsCOMPtr<nsIThread> newThread; + nsresult rv = + NS_NewNamedThread("AutoTestThread", getter_AddRefs(newThread)); + if (NS_FAILED(rv)) return; + + newThread.swap(mThread); + } + + ~AutoTestThread() { mThread->Shutdown(); } + + operator nsIThread*() const { return mThread; } + + nsIThread* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { + return mThread; + } + + private: + nsCOMPtr<nsIThread> mThread; +}; + +class AutoCreateAndDestroyReentrantMonitor { + public: + AutoCreateAndDestroyReentrantMonitor() { + mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon"); + MOZ_RELEASE_ASSERT(mReentrantMonitor, "Out of memory!"); + } + + ~AutoCreateAndDestroyReentrantMonitor() { delete mReentrantMonitor; } + + operator ReentrantMonitor*() const { return mReentrantMonitor; } + + private: + ReentrantMonitor* mReentrantMonitor; +}; + +class TimerCallback final : public nsITimerCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + TimerCallback(nsIThread** aThreadPtr, ReentrantMonitor* aReentrantMonitor) + : mThreadPtr(aThreadPtr), mReentrantMonitor(aReentrantMonitor) {} + + NS_IMETHOD Notify(nsITimer* aTimer) override { + MOZ_RELEASE_ASSERT(mThreadPtr, "Callback was not supposed to be called!"); + nsCOMPtr<nsIThread> current(do_GetCurrentThread()); + + ReentrantMonitorAutoEnter mon(*mReentrantMonitor); + + MOZ_RELEASE_ASSERT(!*mThreadPtr, "Timer called back more than once!"); + *mThreadPtr = current; + + mon.Notify(); + + return NS_OK; + } + + private: + ~TimerCallback() = default; + + nsIThread** mThreadPtr; + ReentrantMonitor* mReentrantMonitor; +}; + +NS_IMPL_ISUPPORTS(TimerCallback, nsITimerCallback) + +class TimerHelper { + public: + explicit TimerHelper(nsIEventTarget* aTarget) + : mStart(TimeStamp::Now()), + mTimer(NS_NewTimer(aTarget)), + mMonitor(__func__), + mTarget(aTarget) {} + + ~TimerHelper() { Cancel(); } + + static void ClosureCallback(nsITimer*, void* aClosure) { + reinterpret_cast<TimerHelper*>(aClosure)->Notify(); + } + + // We do not use nsITimerCallback, because that results in a circular + // reference. One of the properties we want from TimerHelper is for the + // timer to be canceled when it goes out of scope. + void Notify() { + MonitorAutoLock lock(mMonitor); + EXPECT_TRUE(mTarget->IsOnCurrentThread()); + TimeDuration elapsed = TimeStamp::Now() - mStart; + mStart = TimeStamp::Now(); + mLastDelay = Some(elapsed.ToMilliseconds()); + if (mBlockTime) { + PR_Sleep(mBlockTime); + } + mMonitor.Notify(); + } + + nsresult SetTimer(uint32_t aDelay, uint8_t aType) { + Cancel(); + MonitorAutoLock lock(mMonitor); + mStart = TimeStamp::Now(); + return mTimer->InitWithNamedFuncCallback( + ClosureCallback, this, aDelay, aType, "TimerHelper::ClosureCallback"); + } + + Maybe<uint32_t> Wait(uint32_t aLimitMs) { + return WaitAndBlockCallback(aLimitMs, 0); + } + + // Waits for callback, and if it occurs within the limit, causes the callback + // to block for the specified time. Useful for testing cases where the + // callback takes a long time to return. + Maybe<uint32_t> WaitAndBlockCallback(uint32_t aLimitMs, uint32_t aBlockTime) { + MonitorAutoLock lock(mMonitor); + mBlockTime = aBlockTime; + TimeStamp start = TimeStamp::Now(); + while (!mLastDelay.isSome()) { + mMonitor.Wait(TimeDuration::FromMilliseconds(aLimitMs)); + TimeDuration elapsed = TimeStamp::Now() - start; + uint32_t elapsedMs = static_cast<uint32_t>(elapsed.ToMilliseconds()); + if (elapsedMs >= aLimitMs) { + break; + } + aLimitMs -= elapsedMs; + start = TimeStamp::Now(); + } + mBlockTime = 0; + return std::move(mLastDelay); + } + + void Cancel() { + NS_DispatchAndSpinEventLoopUntilComplete( + "~TimerHelper timer cancel"_ns, mTarget, + NS_NewRunnableFunction("~TimerHelper timer cancel", [this] { + MonitorAutoLock lock(mMonitor); + mTimer->Cancel(); + })); + } + + private: + TimeStamp mStart; + RefPtr<nsITimer> mTimer; + mutable Monitor mMonitor MOZ_UNANNOTATED; + uint32_t mBlockTime = 0; + Maybe<uint32_t> mLastDelay; + RefPtr<nsIEventTarget> mTarget; +}; + +class SimpleTimerTest : public ::testing::Test { + public: + std::unique_ptr<TimerHelper> MakeTimer(uint32_t aDelay, uint8_t aType) { + std::unique_ptr<TimerHelper> timer(new TimerHelper(mThread)); + timer->SetTimer(aDelay, aType); + return timer; + } + + void PauseTimerThread() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(nullptr, "sleep_notification", nullptr); + } + + void ResumeTimerThread() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(nullptr, "wake_notification", nullptr); + } + + protected: + AutoTestThread mThread; +}; + +#ifdef XP_MACOSX +// For some reason, our OS X testers fire timed condition waits _extremely_ +// late (as much as 200ms). +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1726915 +const unsigned kSlowdownFactor = 50; +#elif XP_WIN +// Windows also needs some extra leniency, but not nearly as much as our OS X +// testers +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1729035 +const unsigned kSlowdownFactor = 5; +#else +const unsigned kSlowdownFactor = 1; +#endif + +TEST_F(SimpleTimerTest, OneShot) { + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); + auto res = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(res.isSome()); + ASSERT_LT(*res, 110U * kSlowdownFactor); + ASSERT_GT(*res, 95U * kSlowdownFactor); +} + +TEST_F(SimpleTimerTest, TimerWithStoppedTarget) { + mThread->Shutdown(); + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); + auto res = timer->Wait(110 * kSlowdownFactor); + ASSERT_FALSE(res.isSome()); +} + +TEST_F(SimpleTimerTest, SlackRepeating) { + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK); + auto delay = + timer->WaitAndBlockCallback(110 * kSlowdownFactor, 50 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + // REPEATING_SLACK timers re-schedule with the full duration when the timer + // callback completes + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 160U * kSlowdownFactor); + ASSERT_GT(*delay, 145U * kSlowdownFactor); +} + +TEST_F(SimpleTimerTest, RepeatingPrecise) { + auto timer = MakeTimer(100 * kSlowdownFactor, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + auto delay = + timer->WaitAndBlockCallback(110 * kSlowdownFactor, 50 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + // Delays smaller than the timer's period do not effect the period. + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + // Delays larger than the timer's period should result in the skipping of + // firings, but the cadence should remain the same. + delay = + timer->WaitAndBlockCallback(110 * kSlowdownFactor, 150 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 210U * kSlowdownFactor); + ASSERT_GT(*delay, 195U * kSlowdownFactor); +} + +// gtest on 32bit Win7 debug build is unstable and somehow this test +// makes it even worse. +#if !defined(XP_WIN) || !defined(DEBUG) || defined(HAVE_64BIT_BUILD) + +class FindExpirationTimeState final { + public: + // We'll offset the timers 10 seconds into the future to assure that they + // won't fire + const uint32_t kTimerOffset = 10 * 1000; + // And we'll set the timers spaced by 5 seconds. + const uint32_t kTimerInterval = 5 * 1000; + // We'll use 20 timers + const uint32_t kNumTimers = 20; + + TimeStamp mBefore; + TimeStamp mMiddle; + + std::list<nsCOMPtr<nsITimer>> mTimers; + + ~FindExpirationTimeState() { + while (!mTimers.empty()) { + nsCOMPtr<nsITimer> t = mTimers.front().get(); + mTimers.pop_front(); + t->Cancel(); + } + } + + // Create timers, with aNumLowPriority low priority timers first in the queue + void InitTimers(uint32_t aNumLowPriority, uint32_t aType) { + // aType is just for readability. + MOZ_ASSERT(aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + InitTimers(aNumLowPriority, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, nullptr); + } + + // Create timers, with aNumDifferentTarget timers with target aTarget first in + // the queue + void InitTimers(uint32_t aNumDifferentTarget, nsIEventTarget* aTarget) { + InitTimers(aNumDifferentTarget, nsITimer::TYPE_ONE_SHOT, aTarget); + } + + void InitTimers(uint32_t aNumDifferingTimers, uint32_t aType, + nsIEventTarget* aTarget) { + do { + TimeStamp clearUntil = + TimeStamp::Now() + TimeDuration::FromMilliseconds( + kTimerOffset + kNumTimers * kTimerInterval); + + // NS_GetTimerDeadlineHintOnCurrentThread returns clearUntil if there are + // no pending timers before clearUntil. + // Passing 0 ensures that we examine the next timer to fire, regardless + // of its thread target. This is important, because lots of the checks + // we perform in the test get confused by timers targeted at other + // threads. + TimeStamp t = NS_GetTimerDeadlineHintOnCurrentThread(clearUntil, 0); + if (t >= clearUntil) { + break; + } + + // Clear whatever random timers there might be pending. + uint32_t waitTime = 10; + if (t > TimeStamp::Now()) { + waitTime = uint32_t((t - TimeStamp::Now()).ToMilliseconds()); + } + PR_Sleep(PR_MillisecondsToInterval(waitTime)); + } while (true); + + mBefore = TimeStamp::Now(); + // To avoid getting exactly the same time for a timer and mMiddle, subtract + // 50 ms. + mMiddle = mBefore + + TimeDuration::FromMilliseconds(kTimerOffset + + kTimerInterval * kNumTimers / 2) - + TimeDuration::FromMilliseconds(50); + for (uint32_t i = 0; i < kNumTimers; ++i) { + nsCOMPtr<nsITimer> timer = NS_NewTimer(); + ASSERT_TRUE(timer); + + if (i < aNumDifferingTimers) { + if (aTarget) { + timer->SetTarget(aTarget); + } + + timer->InitWithNamedFuncCallback( + &UnusedCallbackFunc, nullptr, kTimerOffset + kTimerInterval * i, + aType, "FindExpirationTimeState::InitTimers"); + } else { + timer->InitWithNamedFuncCallback( + &UnusedCallbackFunc, nullptr, kTimerOffset + kTimerInterval * i, + nsITimer::TYPE_ONE_SHOT, "FindExpirationTimeState::InitTimers"); + } + mTimers.push_front(timer.get()); + } + } + + static void UnusedCallbackFunc(nsITimer* aTimer, void* aClosure) { + FAIL() << "Timer shouldn't fire."; + } +}; + +TEST_F(SimpleTimerTest, FindExpirationTime) { + { + FindExpirationTimeState state; + // 0 low priority timers + state.InitTimers(0, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + } + + { + FindExpirationTimeState state; + // 5 low priority timers + state.InitTimers(5, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + } + + { + FindExpirationTimeState state; + // 15 low priority timers + state.InitTimers(15, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + } + + { + AutoTestThread testThread; + FindExpirationTimeState state; + // 5 other targets + state.InitTimers(5, static_cast<nsIEventTarget*>(testThread)); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + } + + { + AutoTestThread testThread; + FindExpirationTimeState state; + // 15 other targets + state.InitTimers(15, static_cast<nsIEventTarget*>(testThread)); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + } +} + +#endif + +// Do these _after_ FindExpirationTime; apparently pausing the timer thread +// schedules minute-long timers, which FindExpirationTime waits out before +// starting. +TEST_F(SimpleTimerTest, SleepWakeOneShot) { + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return; + } + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); + PauseTimerThread(); + auto delay = timer->Wait(200 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); +} + +TEST_F(SimpleTimerTest, SleepWakeRepeatingSlack) { + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return; + } + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK); + PauseTimerThread(); + auto delay = timer->Wait(200 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread slept for ~200ms, longer than the duration of the timer, so + // it should fire pretty much immediately. + delay = timer->Wait(10 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 210 * kSlowdownFactor); + ASSERT_GT(*delay, 199 * kSlowdownFactor); + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + PauseTimerThread(); + delay = timer->Wait(50 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread only slept for ~50 ms, shorter than the duration of the + // timer, so there should be no effect on the timing. + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); +} + +TEST_F(SimpleTimerTest, SleepWakeRepeatingPrecise) { + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return; + } + auto timer = MakeTimer(100 * kSlowdownFactor, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + PauseTimerThread(); + auto delay = timer->Wait(350 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread slept longer than the duration of the timer, so it should + // fire pretty much immediately. + delay = timer->Wait(10 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 360U * kSlowdownFactor); + ASSERT_GT(*delay, 349U * kSlowdownFactor); + + // After that, we should get back on our original cadence + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 60U * kSlowdownFactor); + ASSERT_GT(*delay, 45U * kSlowdownFactor); + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + PauseTimerThread(); + delay = timer->Wait(50 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread only slept for ~50 ms, shorter than the duration of the + // timer, so there should be no effect on the timing. + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); +} + +#define FUZZ_MAX_TIMEOUT 9 +class FuzzTestThreadState final : public nsITimerCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit FuzzTestThreadState(nsIThread* thread) + : mThread(thread), mStopped(false) {} + + class StartRunnable final : public mozilla::Runnable { + public: + explicit StartRunnable(FuzzTestThreadState* threadState) + : mozilla::Runnable("FuzzTestThreadState::StartRunnable"), + mThreadState(threadState) {} + + NS_IMETHOD Run() override { + mThreadState->ScheduleOrCancelTimers(); + return NS_OK; + } + + private: + RefPtr<FuzzTestThreadState> mThreadState; + }; + + void Start() { + nsCOMPtr<nsIRunnable> runnable = new StartRunnable(this); + nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch StartRunnable."); + } + + void Stop() { mStopped = true; } + + NS_IMETHOD Notify(nsITimer* aTimer) override { + bool onCorrectThread; + nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check."); + MOZ_RELEASE_ASSERT(onCorrectThread, "Notify invoked on wrong thread."); + + uint32_t delay; + rv = aTimer->GetDelay(&delay); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "GetDelay failed."); + + MOZ_RELEASE_ASSERT(delay <= FUZZ_MAX_TIMEOUT, + "Delay was an invalid value for this test."); + + uint32_t type; + rv = aTimer->GetType(&type); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type."); + MOZ_RELEASE_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + + if (type == nsITimer::TYPE_ONE_SHOT) { + MOZ_RELEASE_ASSERT(!mOneShotTimersByDelay[delay].empty(), + "Unexpected one-shot timer."); + + MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[delay].front().get() == aTimer, + "One-shot timers have been reordered."); + + mOneShotTimersByDelay[delay].pop_front(); + --mTimersOutstanding; + } else if (mStopped) { + CancelRepeatingTimer(aTimer); + } + + ScheduleOrCancelTimers(); + RescheduleSomeTimers(); + return NS_OK; + } + + bool HasTimersOutstanding() const { return !!mTimersOutstanding; } + + private: + ~FuzzTestThreadState() { + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[i].empty(), + "Timers remain at end of test."); + } + } + + uint32_t GetRandomType() const { + return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1); + } + + size_t CountOneShotTimers() const { + size_t count = 0; + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + count += mOneShotTimersByDelay[i].size(); + } + return count; + } + + void ScheduleOrCancelTimers() { + if (mStopped) { + return; + } + + const size_t numTimersDesired = (rand() % 100) + 100; + MOZ_RELEASE_ASSERT(numTimersDesired >= 100); + MOZ_RELEASE_ASSERT(numTimersDesired < 200); + int adjustment = numTimersDesired - mTimersOutstanding; + + while (adjustment > 0) { + CreateRandomTimer(); + --adjustment; + } + + while (adjustment < 0) { + CancelRandomTimer(); + ++adjustment; + } + + MOZ_RELEASE_ASSERT(numTimersDesired == mTimersOutstanding); + } + + void RescheduleSomeTimers() { + if (mStopped) { + return; + } + + static const size_t kNumRescheduled = 40; + + // Reschedule some timers with a Cancel first. + for (size_t i = 0; i < kNumRescheduled; ++i) { + InitRandomTimer(CancelRandomTimer().get()); + } + // Reschedule some timers without a Cancel first. + for (size_t i = 0; i < kNumRescheduled; ++i) { + InitRandomTimer(RemoveRandomTimer().get()); + } + } + + void CreateRandomTimer() { + nsCOMPtr<nsITimer> timer = + NS_NewTimer(static_cast<nsIEventTarget*>(mThread.get())); + MOZ_RELEASE_ASSERT(timer, "Failed to create timer."); + + InitRandomTimer(timer.get()); + } + + nsCOMPtr<nsITimer> CancelRandomTimer() { + nsCOMPtr<nsITimer> timer(RemoveRandomTimer()); + timer->Cancel(); + return timer; + } + + nsCOMPtr<nsITimer> RemoveRandomTimer() { + MOZ_RELEASE_ASSERT(mTimersOutstanding); + + if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers()) || + mRepeatingTimers.empty()) { + uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1); + while (mOneShotTimersByDelay[delayToRemove].empty()) { + // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1 + delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1); + } + + uint32_t indexToRemove = + rand() % mOneShotTimersByDelay[delayToRemove].size(); + + for (auto it = mOneShotTimersByDelay[delayToRemove].begin(); + it != mOneShotTimersByDelay[delayToRemove].end(); ++it) { + if (indexToRemove) { + --indexToRemove; + continue; + } + + nsCOMPtr<nsITimer> removed = *it; + mOneShotTimersByDelay[delayToRemove].erase(it); + --mTimersOutstanding; + return removed; + } + } else { + size_t indexToRemove = rand() % mRepeatingTimers.size(); + nsCOMPtr<nsITimer> removed(mRepeatingTimers[indexToRemove]); + mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove); + --mTimersOutstanding; + return removed; + } + + MOZ_CRASH("Unable to remove a timer"); + } + + void InitRandomTimer(nsITimer* aTimer) { + // Between 0 and FUZZ_MAX_TIMEOUT + uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1); + uint32_t type = GetRandomType(); + nsresult rv = aTimer->InitWithCallback(this, delay, type); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set timer."); + + if (type == nsITimer::TYPE_ONE_SHOT) { + mOneShotTimersByDelay[delay].push_back(aTimer); + } else { + mRepeatingTimers.push_back(aTimer); + } + ++mTimersOutstanding; + } + + void CancelRepeatingTimer(nsITimer* aTimer) { + for (auto it = mRepeatingTimers.begin(); it != mRepeatingTimers.end(); + ++it) { + if (it->get() == aTimer) { + mRepeatingTimers.erase(it); + aTimer->Cancel(); + --mTimersOutstanding; + return; + } + } + } + + nsCOMPtr<nsIThread> mThread; + // Scheduled timers, indexed by delay between 0-9 ms, in lists + // with most recently scheduled last. + std::list<nsCOMPtr<nsITimer>> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1]; + std::vector<nsCOMPtr<nsITimer>> mRepeatingTimers; + Atomic<bool> mStopped; + Atomic<size_t> mTimersOutstanding; +}; + +NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback) + +TEST(Timers, FuzzTestTimers) +{ + static const size_t kNumThreads(10); + AutoTestThread threads[kNumThreads]; + RefPtr<FuzzTestThreadState> threadStates[kNumThreads]; + + for (size_t i = 0; i < kNumThreads; ++i) { + threadStates[i] = new FuzzTestThreadState(&*threads[i]); + threadStates[i]->Start(); + } + + PR_Sleep(PR_MillisecondsToInterval(20000)); + + for (size_t i = 0; i < kNumThreads; ++i) { + threadStates[i]->Stop(); + } + + // Wait at most 10 seconds for all outstanding timers to pop + PRIntervalTime start = PR_IntervalNow(); + for (auto& threadState : threadStates) { + while (threadState->HasTimersOutstanding()) { + uint32_t elapsedMs = PR_IntervalToMilliseconds(PR_IntervalNow() - start); + ASSERT_LE(elapsedMs, uint32_t(10000)) + << "Timed out waiting for all timers to pop"; + PR_Sleep(PR_MillisecondsToInterval(10)); + } + } +} + +TEST(Timers, ClosureCallback) +{ + AutoCreateAndDestroyReentrantMonitor newMon; + ASSERT_TRUE(newMon); + + AutoTestThread testThread; + ASSERT_TRUE(testThread); + + nsIThread* notifiedThread = nullptr; + + nsCOMPtr<nsITimer> timer; + nsresult rv = NS_NewTimerWithCallback( + getter_AddRefs(timer), + [&](nsITimer*) { + nsCOMPtr<nsIThread> current(do_GetCurrentThread()); + + ReentrantMonitorAutoEnter mon(*newMon); + ASSERT_FALSE(notifiedThread); + notifiedThread = current; + mon.Notify(); + }, + 50, nsITimer::TYPE_ONE_SHOT, "(test) Timers.ClosureCallback", testThread); + ASSERT_NS_SUCCEEDED(rv); + + ReentrantMonitorAutoEnter mon(*newMon); + while (!notifiedThread) { + mon.Wait(); + } + ASSERT_EQ(notifiedThread, testThread); +} + +static void SetTime(nsITimer* aTimer, void* aClosure) { + *static_cast<TimeStamp*>(aClosure) = TimeStamp::Now(); +} + +TEST(Timers, HighResFuncCallback) +{ + TimeStamp first; + TimeStamp second; + TimeStamp third; + nsCOMPtr<nsITimer> t1 = NS_NewTimer(GetCurrentSerialEventTarget()); + nsCOMPtr<nsITimer> t2 = NS_NewTimer(GetCurrentSerialEventTarget()); + nsCOMPtr<nsITimer> t3 = NS_NewTimer(GetCurrentSerialEventTarget()); + + // Reverse order, since if the timers are not high-res we'd end up + // out-of-order. + MOZ_ALWAYS_SUCCEEDS(t3->InitHighResolutionWithNamedFuncCallback( + &SetTime, &third, TimeDuration::FromMicroseconds(300), + nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::third")); + MOZ_ALWAYS_SUCCEEDS(t2->InitHighResolutionWithNamedFuncCallback( + &SetTime, &second, TimeDuration::FromMicroseconds(200), + nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::second")); + MOZ_ALWAYS_SUCCEEDS(t1->InitHighResolutionWithNamedFuncCallback( + &SetTime, &first, TimeDuration::FromMicroseconds(100), + nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::first")); + + SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( + "TestTimers::HighResFuncCallback"_ns, + [&] { return !first.IsNull() && !second.IsNull() && !third.IsNull(); }); +} diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp new file mode 100644 index 0000000000..1457b82fff --- /dev/null +++ b/xpcom/tests/gtest/TestTokenizer.cpp @@ -0,0 +1,1447 @@ +/* -*- 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/Tokenizer.h" +#include "mozilla/IncrementalTokenizer.h" +#include "mozilla/Unused.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +template <typename Char> +static bool IsOperator(Char const c) { + return c == '+' || c == '*'; +} + +static bool HttpHeaderCharacter(char const c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || (c == '_') || (c == '-'); +} + +TEST(Tokenizer, HTTPResponse) +{ + Tokenizer::Token t; + + // Real life test, HTTP response + + Tokenizer p( + nsLiteralCString("HTTP/1.0 304 Not modified\r\n" + "ETag: hallo\r\n" + "Content-Length: 16\r\n" + "\r\n" + "This is the body")); + + EXPECT_TRUE(p.CheckWord("HTTP")); + EXPECT_TRUE(p.CheckChar('/')); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 1); + EXPECT_TRUE(p.CheckChar('.')); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 0); + p.SkipWhites(); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 304); + p.SkipWhites(); + + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL) { + ; + } + EXPECT_FALSE(p.HasFailed()); + nsAutoCString h; + p.Claim(h); + EXPECT_TRUE(h == "Not modified"); + + p.Record(); + while (p.CheckChar(HttpHeaderCharacter)) { + ; + } + p.Claim(h, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(h == "ETag"); + p.SkipWhites(); + EXPECT_TRUE(p.CheckChar(':')); + p.SkipWhites(); + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL) { + ; + } + EXPECT_FALSE(p.HasFailed()); + p.Claim(h); + EXPECT_TRUE(h == "hallo"); + + p.Record(); + while (p.CheckChar(HttpHeaderCharacter)) { + ; + } + p.Claim(h, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(h == "Content-Length"); + p.SkipWhites(); + EXPECT_TRUE(p.CheckChar(':')); + p.SkipWhites(); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.AsInteger() == 16); + EXPECT_TRUE(p.CheckEOL()); + + EXPECT_TRUE(p.CheckEOL()); + + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOF) { + ; + } + nsAutoCString b; + p.Claim(b); + EXPECT_TRUE(b == "This is the body"); +} + +TEST(Tokenizer, Main) +{ + Tokenizer::Token t; + + // Synthetic code-specific test + + Tokenizer p("test123 ,15 \t*\r\n%xx,-15\r\r"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_WORD); + EXPECT_TRUE(t.AsString() == "test123"); + + Tokenizer::Token u; + EXPECT_FALSE(p.Check(u)); + + EXPECT_FALSE(p.CheckChar('!')); + + EXPECT_FALSE(p.Check(Tokenizer::Token::Number(123))); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_FALSE(p.CheckChar(IsOperator)); + + EXPECT_TRUE(p.CheckWhite()); + + p.SkipWhites(); + + EXPECT_FALSE(p.CheckWhite()); + + p.Rollback(); + + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + + p.Record(Tokenizer::EXCLUDE_LAST); + + EXPECT_TRUE(p.CheckChar(IsOperator)); + + p.Rollback(); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '*'); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '%'); + + nsAutoCString claim; + p.Claim(claim, Tokenizer::EXCLUDE_LAST); + EXPECT_TRUE(claim == "*\r\n"); + p.Claim(claim, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(claim == "*\r\n%"); + + p.Rollback(); + EXPECT_TRUE(p.CheckChar('%')); + + p.Record(Tokenizer::INCLUDE_LAST); + + EXPECT_FALSE(p.CheckWord("xy")); + + EXPECT_TRUE(p.CheckWord("xx")); + + p.Claim(claim, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(claim == "%xx"); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == ','); + + EXPECT_TRUE(p.CheckChar('-')); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.CheckEOF()); +} + +TEST(Tokenizer, Main16) +{ + Tokenizer16::Token t; + + // Synthetic code-specific test + + Tokenizer16 p(u"test123 ,15 \t*\r\n%xx,-15\r\r"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_WORD); + EXPECT_TRUE(t.AsString() == u"test123"_ns); + + Tokenizer16::Token u; + EXPECT_FALSE(p.Check(u)); + + EXPECT_FALSE(p.CheckChar('!')); + + EXPECT_FALSE(p.Check(Tokenizer16::Token::Number(123))); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(Tokenizer16::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer16::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_FALSE(p.CheckChar(IsOperator)); + + EXPECT_TRUE(p.CheckWhite()); + + p.SkipWhites(); + + EXPECT_FALSE(p.CheckWhite()); + + p.Rollback(); + + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + + p.Record(Tokenizer16::EXCLUDE_LAST); + + EXPECT_TRUE(p.CheckChar(IsOperator)); + + p.Rollback(); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '*'); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '%'); + + nsAutoString claim; + p.Claim(claim, Tokenizer16::EXCLUDE_LAST); + EXPECT_TRUE(claim == u"*\r\n"_ns); + p.Claim(claim, Tokenizer16::INCLUDE_LAST); + EXPECT_TRUE(claim == u"*\r\n%"_ns); + + p.Rollback(); + EXPECT_TRUE(p.CheckChar('%')); + + p.Record(Tokenizer16::INCLUDE_LAST); + + EXPECT_FALSE(p.CheckWord(u"xy"_ns)); + + EXPECT_TRUE(p.CheckWord(u"xx"_ns)); + + p.Claim(claim, Tokenizer16::INCLUDE_LAST); + EXPECT_TRUE(claim == u"%xx"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == ','); + + EXPECT_TRUE(p.CheckChar('-')); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.CheckEOF()); +} + +TEST(Tokenizer, SingleWord) +{ + // Single word with numbers in it test + + Tokenizer p("test123"_ns); + + EXPECT_TRUE(p.CheckWord("test123")); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, EndingAfterNumber) +{ + // An end handling after a number + + Tokenizer p("123"_ns); + + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(123))); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, BadInteger) +{ + Tokenizer::Token t; + + // A bad integer test + + Tokenizer p("189234891274981758617846178651647620587135"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_ERROR); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, CheckExpectedTokenValue) +{ + Tokenizer::Token t; + + // Check expected token value test + + Tokenizer p("blue velvet"_ns); + + EXPECT_FALSE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t)); + EXPECT_TRUE(t.AsString() == "blue"); + + EXPECT_FALSE(p.Check(Tokenizer::TOKEN_WORD, t)); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t)); + EXPECT_TRUE(t.AsString() == "velvet"); + + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.Next(t)); +} + +TEST(Tokenizer, HasFailed) +{ + Tokenizer::Token t; + + // HasFailed test + + Tokenizer p1("a b"_ns); + + while (p1.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR) { + ; + } + EXPECT_TRUE(p1.HasFailed()); + + Tokenizer p2("a b ?!c"_ns); + + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + EXPECT_TRUE(p2.CheckChar(HttpHeaderCharacter)); + EXPECT_FALSE(p2.HasFailed()); + p2.SkipWhites(); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + EXPECT_TRUE(p2.Next(t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_TRUE(p2.Next(t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + EXPECT_TRUE(p2.Check(Tokenizer::TOKEN_CHAR, t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_FALSE(p2.CheckChar('#')); + EXPECT_TRUE(p2.HasFailed()); + t = Tokenizer::Token::Char('!'); + EXPECT_TRUE(p2.Check(t)); + EXPECT_FALSE(p2.HasFailed()); + + while (p2.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR) { + ; + } + EXPECT_TRUE(p2.HasFailed()); +} + +TEST(Tokenizer, Construction) +{ + { + nsCString a("test"); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + nsAutoCString a("test"); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + static const char _a[] = "test"; + nsDependentCString a(_a); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + static const char* _a = "test"; + nsDependentCString a(_a); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + Tokenizer p1(nsDependentCString("test")); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + Tokenizer p1("test"_ns); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + Tokenizer p1("test"); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } +} + +TEST(Tokenizer, Customization) +{ + Tokenizer p1("test-custom*words and\tdefault-whites"_ns, nullptr, "-*"); + EXPECT_TRUE(p1.CheckWord("test-custom*words")); + EXPECT_TRUE(p1.CheckWhite()); + EXPECT_TRUE(p1.CheckWord("and")); + EXPECT_TRUE(p1.CheckWhite()); + EXPECT_TRUE(p1.CheckWord("default-whites")); + + Tokenizer p2("test, custom,whites"_ns, ", "); + EXPECT_TRUE(p2.CheckWord("test")); + EXPECT_TRUE(p2.CheckWhite()); + EXPECT_TRUE(p2.CheckWhite()); + EXPECT_TRUE(p2.CheckWord("custom")); + EXPECT_TRUE(p2.CheckWhite()); + EXPECT_TRUE(p2.CheckWord("whites")); + + Tokenizer p3("test, custom, whites-and#word-chars"_ns, ",", "-#"); + EXPECT_TRUE(p3.CheckWord("test")); + EXPECT_TRUE(p3.CheckWhite()); + EXPECT_FALSE(p3.CheckWhite()); + EXPECT_TRUE(p3.CheckChar(' ')); + EXPECT_TRUE(p3.CheckWord("custom")); + EXPECT_TRUE(p3.CheckWhite()); + EXPECT_FALSE(p3.CheckWhite()); + EXPECT_TRUE(p3.CheckChar(' ')); + EXPECT_TRUE(p3.CheckWord("whites-and#word-chars")); +} + +TEST(Tokenizer, ShortcutChecks) +{ + Tokenizer p("test1 test2,123"); + + nsAutoCString test1; + nsDependentCSubstring test2; + char comma; + uint32_t integer; + + EXPECT_TRUE(p.ReadWord(test1)); + EXPECT_TRUE(test1 == "test1"); + p.SkipWhites(); + EXPECT_TRUE(p.ReadWord(test2)); + EXPECT_TRUE(test2 == "test2"); + EXPECT_TRUE(p.ReadChar(&comma)); + EXPECT_TRUE(comma == ','); + EXPECT_TRUE(p.ReadInteger(&integer)); + EXPECT_TRUE(integer == 123); + EXPECT_TRUE(p.CheckEOF()); +} + +static bool ABChar(const char aChar) { return aChar == 'a' || aChar == 'b'; } + +TEST(Tokenizer, ReadCharClassified) +{ + Tokenizer p("abc"); + + char c; + EXPECT_TRUE(p.ReadChar(ABChar, &c)); + EXPECT_TRUE(c == 'a'); + EXPECT_TRUE(p.ReadChar(ABChar, &c)); + EXPECT_TRUE(c == 'b'); + EXPECT_FALSE(p.ReadChar(ABChar, &c)); + nsDependentCSubstring w; + EXPECT_TRUE(p.ReadWord(w)); + EXPECT_TRUE(w == "c"); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, ClaimSubstring) +{ + Tokenizer p(" abc "); + + EXPECT_TRUE(p.CheckWhite()); + + p.Record(); + EXPECT_TRUE(p.CheckWord("abc")); + nsDependentCSubstring v; + p.Claim(v, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(v == "abc"); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, Fragment) +{ + const char str[] = "ab;cd:10 "; + Tokenizer p(str); + nsDependentCSubstring f; + + Tokenizer::Token t1, t2; + + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD); + EXPECT_TRUE(t1.Fragment() == "ab"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[0]); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2)); + EXPECT_TRUE(t2.Fragment() == "ab"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[0]); + + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t1.Fragment() == ";"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[2]); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2)); + EXPECT_TRUE(t2.Fragment() == ";"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[2]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2)); + EXPECT_TRUE(t2.Fragment() == "cd"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[3]); + + p.Rollback(); + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD); + EXPECT_TRUE(t1.Fragment() == "cd"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[3]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2)); + EXPECT_TRUE(t2.Fragment() == ":"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[5]); + + p.Rollback(); + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t1.Fragment() == ":"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[5]); + + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t1.Fragment() == "10"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[6]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WS, t2)); + EXPECT_TRUE(t2.Fragment() == " "); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[8]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_EOF, t1)); + EXPECT_TRUE(t1.Fragment() == ""); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[9]); +} + +TEST(Tokenizer, SkipWhites) +{ + Tokenizer p("Text1 \nText2 \nText3\n Text4\n "); + + EXPECT_TRUE(p.CheckWord("Text1")); + p.SkipWhites(); + EXPECT_TRUE(p.CheckEOL()); + + EXPECT_TRUE(p.CheckWord("Text2")); + p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE); + + EXPECT_TRUE(p.CheckWord("Text3")); + p.SkipWhites(); + EXPECT_TRUE(p.CheckEOL()); + p.SkipWhites(); + + EXPECT_TRUE(p.CheckWord("Text4")); + p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, SkipCustomWhites) +{ + Tokenizer p("Text1 \n\r\t.Text2 \n\r\t.", " \n\r\t."); + + EXPECT_TRUE(p.CheckWord("Text1")); + p.SkipWhites(); + EXPECT_TRUE(p.CheckWord("Text2")); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, IntegerReading) +{ +#define INT_6_BITS 64U +#define INT_30_BITS 1073741824UL +#define INT_32_BITS 4294967295UL +#define INT_50_BITS 1125899906842624ULL +#define STR_INT_MORE_THAN_64_BITS "922337203685477580899" + + { + Tokenizer p(MOZ_STRINGIFY(INT_6_BITS)); + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + EXPECT_TRUE(p.ReadInteger(&u8)); + EXPECT_TRUE(u8 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u16)); + EXPECT_TRUE(u16 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u32)); + EXPECT_TRUE(u32 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u64)); + EXPECT_TRUE(u64 == INT_6_BITS); + + p.Rollback(); + + int8_t s8; + int16_t s16; + int32_t s32; + int64_t s64; + EXPECT_TRUE(p.ReadInteger(&s8)); + EXPECT_TRUE(s8 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s16)); + EXPECT_TRUE(s16 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s32)); + EXPECT_TRUE(s32 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s64)); + EXPECT_TRUE(s64 == INT_6_BITS); + + EXPECT_TRUE(p.CheckWord("U")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(MOZ_STRINGIFY(INT_30_BITS)); + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + EXPECT_FALSE(p.ReadInteger(&u8)); + EXPECT_FALSE(p.ReadInteger(&u16)); + EXPECT_TRUE(p.ReadInteger(&u32)); + EXPECT_TRUE(u32 == INT_30_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u64)); + EXPECT_TRUE(u64 == INT_30_BITS); + + p.Rollback(); + + int8_t s8; + int16_t s16; + int32_t s32; + int64_t s64; + EXPECT_FALSE(p.ReadInteger(&s8)); + EXPECT_FALSE(p.ReadInteger(&s16)); + EXPECT_TRUE(p.ReadInteger(&s32)); + EXPECT_TRUE(s32 == INT_30_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s64)); + EXPECT_TRUE(s64 == INT_30_BITS); + EXPECT_TRUE(p.CheckWord("UL")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(MOZ_STRINGIFY(INT_32_BITS)); + uint32_t u32; + int32_t s32; + EXPECT_FALSE(p.ReadInteger(&s32)); + EXPECT_TRUE(p.ReadInteger(&u32)); + EXPECT_TRUE(u32 == INT_32_BITS); + EXPECT_TRUE(p.CheckWord("UL")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(MOZ_STRINGIFY(INT_50_BITS)); + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + EXPECT_FALSE(p.ReadInteger(&u8)); + EXPECT_FALSE(p.ReadInteger(&u16)); + EXPECT_FALSE(p.ReadInteger(&u32)); + EXPECT_TRUE(p.ReadInteger(&u64)); + EXPECT_TRUE(u64 == INT_50_BITS); + EXPECT_TRUE(p.CheckWord("ULL")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(STR_INT_MORE_THAN_64_BITS); + int64_t i; + EXPECT_FALSE(p.ReadInteger(&i)); + uint64_t u; + EXPECT_FALSE(p.ReadInteger(&u)); + EXPECT_FALSE(p.CheckEOF()); + } +} + +TEST(Tokenizer, ReadUntil) +{ + Tokenizer p("Hello;test 4,"); + nsDependentCSubstring f; + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(';'), f)); + EXPECT_TRUE(f == "Hello"); + p.Rollback(); + + EXPECT_TRUE( + p.ReadUntil(Tokenizer::Token::Char(';'), f, Tokenizer::INCLUDE_LAST)); + EXPECT_TRUE(f == "Hello;"); + p.Rollback(); + + EXPECT_FALSE(p.ReadUntil(Tokenizer::Token::Char('!'), f)); + EXPECT_TRUE(f == "Hello;test 4,"); + p.Rollback(); + + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word("test"_ns), f)); + EXPECT_TRUE(f == "Hello;"); + p.Rollback(); + + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word("test"_ns), f, + Tokenizer::INCLUDE_LAST)); + EXPECT_TRUE(f == "Hello;test"); + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(','), f)); + EXPECT_TRUE(f == " 4"); +} + +TEST(Tokenizer, SkipUntil) +{ + { + Tokenizer p("test1,test2,,,test3"); + + p.SkipUntil(Tokenizer::Token::Char(',')); + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckWord("test2")); + + p.SkipUntil(Tokenizer::Token::Char(',')); // must not move + EXPECT_TRUE(p.CheckChar(',')); // check the first comma of the ',,,' string + + p.Rollback(); // moves cursor back to the first comma of the ',,,' string + + p.SkipUntil( + Tokenizer::Token::Char(',')); // must not move, we are on the ',' char + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckWord("test3")); + p.Rollback(); + + p.SkipUntil(Tokenizer::Token::Char(',')); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p("test0,test1,test2"); + + p.SkipUntil(Tokenizer::Token::Char(',')); + EXPECT_TRUE(p.CheckChar(',')); + + p.SkipUntil(Tokenizer::Token::Char(',')); + p.Rollback(); + + EXPECT_TRUE(p.CheckWord("test1")); + EXPECT_TRUE(p.CheckChar(',')); + + p.SkipUntil(Tokenizer::Token::Char(',')); + p.Rollback(); + + EXPECT_TRUE(p.CheckWord("test2")); + EXPECT_TRUE(p.CheckEOF()); + } +} + +TEST(Tokenizer, Custom) +{ + Tokenizer p( + "aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2"); + + Tokenizer::Token c1 = + p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE); + Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE); + + // It's expected to NOT FIND the custom token if it's not on an edge + // between other recognizable tokens. + EXPECT_TRUE(p.CheckWord("aaaaaacustom")); + EXPECT_TRUE(p.CheckChar('-')); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1))); + EXPECT_TRUE(p.CheckEOL()); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(c1)); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(c1)); + EXPECT_TRUE(p.CheckChar(',')); + + p.EnableCustomToken(c1, false); + EXPECT_TRUE(p.CheckWord("Custom")); + EXPECT_TRUE(p.CheckChar('-')); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1))); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(0))); + EXPECT_TRUE(p.Check(c2)); + EXPECT_TRUE(p.CheckWord("xxxx")); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.CheckWord("CUSTOM")); + EXPECT_TRUE(p.CheckChar('-')); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(2))); + + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, CustomRaw) +{ + Tokenizer p( + "aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2"); + + Tokenizer::Token c1 = + p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE); + Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE); + + // In this mode it's expected to find all custom tokens among any kind of + // input. + p.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + Tokenizer::Token t; + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral("aaaaaa")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral("\r,")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral(",")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral(",")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral(",00")); + + EXPECT_TRUE(p.Check(c2)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral("xxxx,CUSTOM-2")); + + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, Incremental) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(Token::Word("test1"_ns))); + break; + case 2: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::Word("test2"_ns))); + break; + case 4: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 5: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 7: + EXPECT_TRUE(t.Equals(Token::Word("test3"_ns))); + break; + case 8: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + constexpr auto input = "test1,test2,,,test3"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + for (; cur < end; ++cur) { + i.FeedInput(nsDependentCSubstring(cur, 1)); + } + + EXPECT_TRUE(test == 6); + i.FinishInput(); + EXPECT_TRUE(test == 8); +} + +TEST(Tokenizer, IncrementalRollback) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(Token::Word("test1"_ns))); + break; + case 2: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::Word("test2"_ns))); + i.Rollback(); // so that we get the token again + break; + case 4: + EXPECT_TRUE(t.Equals(Token::Word("test2"_ns))); + break; + case 5: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 7: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 8: + EXPECT_TRUE(t.Equals(Token::Word("test3"_ns))); + break; + case 9: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + constexpr auto input = "test1,test2,,,test3"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + for (; cur < end; ++cur) { + i.FeedInput(nsDependentCSubstring(cur, 1)); + } + + EXPECT_TRUE(test == 7); + i.FinishInput(); + EXPECT_TRUE(test == 9); +} + +TEST(Tokenizer, IncrementalNeedMoreInput) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + Token t2; + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(Token::Word("a"_ns))); + break; + case 2: + case 3: + case 4: + case 5: + EXPECT_TRUE(t.Equals(Token::Whitespace())); + if (i.Next(t2)) { + EXPECT_TRUE(test == 5); + EXPECT_TRUE(t2.Equals(Token::Word("bb"_ns))); + } else { + EXPECT_TRUE(test < 5); + i.NeedMoreInput(); + } + break; + case 6: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 7: + EXPECT_TRUE(t.Equals(Token::Word("c"_ns))); + return NS_ERROR_FAILURE; + default: + EXPECT_TRUE(false); + break; + } + + return NS_OK; + }); + + constexpr auto input = "a bb,c"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + + nsresult rv; + for (; cur < end; ++cur) { + rv = i.FeedInput(nsDependentCSubstring(cur, 1)); + if (NS_FAILED(rv)) { + break; + } + } + + EXPECT_TRUE(rv == NS_OK); + EXPECT_TRUE(test == 6); + + rv = i.FinishInput(); + EXPECT_TRUE(rv == NS_ERROR_FAILURE); + EXPECT_TRUE(test == 7); +} + +TEST(Tokenizer, IncrementalCustom) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(custom)); + break; + case 2: + EXPECT_TRUE(t.Equals(Token::Word("bla"_ns))); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }, + nullptr, "-"); + + custom = i.AddCustomToken("some-test", Tokenizer::CASE_SENSITIVE); + i.FeedInput("some-"_ns); + EXPECT_TRUE(test == 0); + i.FeedInput("tes"_ns); + EXPECT_TRUE(test == 0); + i.FeedInput("tbla"_ns); + EXPECT_TRUE(test == 1); + i.FinishInput(); + EXPECT_TRUE(test == 3); +} + +TEST(Tokenizer, IncrementalCustomRaw) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Fragment().EqualsLiteral("test1,")); + break; + case 2: + EXPECT_TRUE(t.Equals(custom)); + break; + case 3: + EXPECT_TRUE(t.Fragment().EqualsLiteral("!,,test3")); + i.Rollback(); + i.SetTokenizingMode(Tokenizer::Mode::FULL); + break; + case 4: + EXPECT_TRUE(t.Equals(Token::Char('!'))); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + break; + case 5: + EXPECT_TRUE(t.Fragment().EqualsLiteral(",,test3")); + break; + case 6: + EXPECT_TRUE(t.Equals(custom)); + break; + case 7: + EXPECT_TRUE(t.Fragment().EqualsLiteral("tes")); + break; + case 8: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + custom = i.AddCustomToken("test2", Tokenizer::CASE_SENSITIVE); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + constexpr auto input = "test1,test2!,,test3test2tes"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + for (; cur < end; ++cur) { + i.FeedInput(nsDependentCSubstring(cur, 1)); + } + + EXPECT_TRUE(test == 6); + i.FinishInput(); + EXPECT_TRUE(test == 8); +} + +TEST(Tokenizer, IncrementalCustomRemove) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(custom)); + i.RemoveCustomToken(custom); + break; + case 2: + EXPECT_FALSE(t.Equals(custom)); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + custom = i.AddCustomToken("custom1", Tokenizer::CASE_SENSITIVE); + + constexpr auto input = "custom1custom1"_ns; + i.FeedInput(input); + EXPECT_TRUE(test == 1); + i.FinishInput(); + EXPECT_TRUE(test == 3); +} + +TEST(Tokenizer, IncrementalBuffering1) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + nsDependentCSubstring observedFragment; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Fragment().EqualsLiteral("012")); + break; + case 2: + EXPECT_TRUE(t.Fragment().EqualsLiteral("3456789")); + break; + case 3: + EXPECT_TRUE(t.Equals(custom)); + break; + case 4: + EXPECT_TRUE(t.Fragment().EqualsLiteral("qwe")); + break; + case 5: + EXPECT_TRUE(t.Fragment().EqualsLiteral("rt")); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + observedFragment.Rebind(t.Fragment().BeginReading(), + t.Fragment().Length()); + return NS_OK; + }, + nullptr, nullptr, 3); + + custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE); + // This externally unused token is added only to check the internal algorithm + // does work correctly as expected when there are two different length tokens. + Unused << i.AddCustomToken("bb", Tokenizer::CASE_SENSITIVE); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + i.FeedInput("01234"_ns); + EXPECT_TRUE(test == 1); + EXPECT_TRUE(observedFragment.EqualsLiteral("012")); + + i.FeedInput("5"_ns); + EXPECT_TRUE(test == 1); + i.FeedInput("6789aa"_ns); + EXPECT_TRUE(test == 2); + EXPECT_TRUE(observedFragment.EqualsLiteral("3456789")); + + i.FeedInput("aqwert"_ns); + EXPECT_TRUE(test == 4); + EXPECT_TRUE(observedFragment.EqualsLiteral("qwe")); + + i.FinishInput(); + EXPECT_TRUE(test == 6); +} + +TEST(Tokenizer, IncrementalBuffering2) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Fragment().EqualsLiteral("01")); + break; + case 2: + EXPECT_TRUE(t.Fragment().EqualsLiteral("234567")); + break; + case 3: + EXPECT_TRUE(t.Fragment().EqualsLiteral("89")); + break; + case 4: + EXPECT_TRUE(t.Equals(custom)); + break; + case 5: + EXPECT_TRUE(t.Fragment().EqualsLiteral("qwert")); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + return NS_OK; + }, + nullptr, nullptr, 3); + + custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE); + // This externally unused token is added only to check the internal algorithm + // does work correctly as expected when there are two different length tokens. + Unused << i.AddCustomToken("bbbbb", Tokenizer::CASE_SENSITIVE); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + i.FeedInput("01234"_ns); + EXPECT_TRUE(test == 0); + i.FeedInput("5"_ns); + EXPECT_TRUE(test == 1); + i.FeedInput("6789aa"_ns); + EXPECT_TRUE(test == 2); + i.FeedInput("aqwert"_ns); + EXPECT_TRUE(test == 4); + i.FinishInput(); + EXPECT_TRUE(test == 6); +} + +TEST(Tokenizer, RecordAndReadUntil) +{ + Tokenizer t("aaaa,bbbb"); + t.SkipWhites(); + nsDependentCSubstring subject; + + EXPECT_TRUE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject)); + EXPECT_FALSE(t.CheckChar(',')); + EXPECT_TRUE(subject.Length() == 4); + EXPECT_TRUE(subject == "aaaa"); + + EXPECT_FALSE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject)); + EXPECT_TRUE(subject.Length() == 4); + EXPECT_TRUE(subject == "bbbb"); + + EXPECT_FALSE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject)); + EXPECT_TRUE(subject.Length() == 0); + + EXPECT_TRUE(t.CheckEOF()); +} + +TEST(Tokenizer, ReadIntegers) +{ + // Make sure that adding dash (the 'minus' sign) as an additional char + // doesn't break reading negative numbers. + Tokenizer t("100,-100,200,-200,4294967295,-4294967295,-2147483647", nullptr, + "-"); + + uint32_t unsigned_value32; + int32_t signed_value32; + int64_t signed_value64; + + // "100," + EXPECT_TRUE(t.ReadInteger(&unsigned_value32)); + EXPECT_TRUE(unsigned_value32 == 100); + EXPECT_TRUE(t.CheckChar(',')); + + // "-100," + EXPECT_FALSE(t.ReadInteger(&unsigned_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == -100); + EXPECT_TRUE(t.CheckChar(',')); + + // "200," + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == 200); + EXPECT_TRUE(t.CheckChar(',')); + + // "-200," + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == -200); + EXPECT_TRUE(t.CheckChar(',')); + + // "4294967295," + EXPECT_FALSE(t.ReadSignedInteger(&signed_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadInteger(&unsigned_value32)); + EXPECT_TRUE(unsigned_value32 == 4294967295UL); + EXPECT_TRUE(t.CheckChar(',')); + + // "-4294967295," + EXPECT_FALSE(t.ReadSignedInteger(&signed_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_FALSE(t.ReadInteger(&unsigned_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadSignedInteger(&signed_value64)); + EXPECT_TRUE(signed_value64 == -4294967295LL); + EXPECT_TRUE(t.CheckChar(',')); + + // "-2147483647" + EXPECT_FALSE(t.ReadInteger(&unsigned_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == -2147483647L); + EXPECT_TRUE(t.CheckEOF()); +} + +TEST(Tokenizer, CheckPhrase) +{ + Tokenizer t("foo bar baz"); + + EXPECT_TRUE(t.CheckPhrase("foo ")); + + EXPECT_FALSE(t.CheckPhrase("barr")); + EXPECT_FALSE(t.CheckPhrase("BAR BAZ")); + EXPECT_FALSE(t.CheckPhrase("bar baz ")); + EXPECT_FALSE(t.CheckPhrase("b")); + EXPECT_FALSE(t.CheckPhrase("ba")); + EXPECT_FALSE(t.CheckPhrase("??")); + + EXPECT_TRUE(t.CheckPhrase("bar baz")); + + t.Rollback(); + EXPECT_TRUE(t.CheckPhrase("bar")); + EXPECT_TRUE(t.CheckPhrase(" baz")); + + t.Rollback(); + EXPECT_FALSE(t.CheckPhrase("\tbaz")); + EXPECT_TRUE(t.CheckPhrase(" baz")); + EXPECT_TRUE(t.CheckEOF()); +} diff --git a/xpcom/tests/gtest/TestUTF.cpp b/xpcom/tests/gtest/TestUTF.cpp new file mode 100644 index 0000000000..cb574aa855 --- /dev/null +++ b/xpcom/tests/gtest/TestUTF.cpp @@ -0,0 +1,264 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> +#include "nsString.h" +#include "nsStringBuffer.h" +#include "nsReadableUtils.h" +#include "UTFStrings.h" +#include "nsUnicharUtils.h" +#include "mozilla/HashFunctions.h" +#include "nsUTF8Utils.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +namespace TestUTF { + +TEST(UTF, Valid) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + nsDependentCString str8(ValidStrings[i].m8); + nsDependentString str16(ValidStrings[i].m16); + + EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8)); + + EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16)); + + nsCString tmp8("string "); + AppendUTF16toUTF8(str16, tmp8); + EXPECT_TRUE(tmp8.Equals("string "_ns + str8)); + + nsString tmp16(u"string "_ns); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Invalid16) +{ + for (unsigned int i = 0; i < ArrayLength(Invalid16Strings); ++i) { + nsDependentString str16(Invalid16Strings[i].m16); + nsDependentCString str8(Invalid16Strings[i].m8); + + EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8)); + + nsCString tmp8("string "); + AppendUTF16toUTF8(str16, tmp8); + EXPECT_TRUE(tmp8.Equals("string "_ns + str8)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Invalid8) +{ + for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) { + nsDependentString str16(Invalid8Strings[i].m16); + nsDependentCString str8(Invalid8Strings[i].m8); + + EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16)); + + nsString tmp16(u"string "_ns); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Malformed8) +{ + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsDependentString str16(Malformed8Strings[i].m16); + nsDependentCString str8(Malformed8Strings[i].m8); + + EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16)); + + nsString tmp16(u"string "_ns); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Hash16) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + nsDependentCString str8(ValidStrings[i].m8); + bool err; + EXPECT_EQ(HashString(ValidStrings[i].m16), + HashUTF8AsUTF16(str8.get(), str8.Length(), &err)); + EXPECT_FALSE(err); + } + + for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) { + nsDependentCString str8(Invalid8Strings[i].m8); + bool err; + EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u); + EXPECT_TRUE(err); + } + + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsDependentCString str8(Malformed8Strings[i].m8); + bool err; + EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u); + EXPECT_TRUE(err); + } +} + +/** + * This tests the handling of a non-ascii character at various locations in a + * UTF-16 string that is being converted to UTF-8. + */ +static void NonASCII16_helper(const size_t aStrSize) { + const size_t kTestSize = aStrSize; + const size_t kMaxASCII = 0x80; + const char16_t kUTF16Char = 0xC9; + const char kUTF8Surrogates[] = {char(0xC3), char(0x89)}; + + // Generate a string containing only ASCII characters. + nsString asciiString; + asciiString.SetLength(kTestSize); + nsCString asciiCString; + asciiCString.SetLength(kTestSize); + + auto str_buff = asciiString.BeginWriting(); + auto cstr_buff = asciiCString.BeginWriting(); + for (size_t i = 0; i < kTestSize; i++) { + str_buff[i] = i % kMaxASCII; + cstr_buff[i] = i % kMaxASCII; + } + + // Now go through and test conversion when exactly one character will + // result in a multibyte sequence. + for (size_t i = 0; i < kTestSize; i++) { + // Setup the UTF-16 string. + nsString unicodeString(asciiString); + auto buff = unicodeString.BeginWriting(); + buff[i] = kUTF16Char; + + // Do the conversion, make sure the length increased by 1. + nsCString dest; + AppendUTF16toUTF8(unicodeString, dest); + EXPECT_EQ(dest.Length(), unicodeString.Length() + 1); + + // Build up the expected UTF-8 string. + nsCString expected; + + // First add the leading ASCII chars. + expected.Append(asciiCString.BeginReading(), i); + + // Now append the UTF-8 pair we expect the UTF-16 unicode char to + // be converted to. + for (auto& c : kUTF8Surrogates) { + expected.Append(c); + } + + // And finish with the trailing ASCII chars. + expected.Append(asciiCString.BeginReading() + i + 1, kTestSize - i - 1); + + EXPECT_STREQ(dest.BeginReading(), expected.BeginReading()); + } +} + +TEST(UTF, NonASCII16) +{ + // Test with various string sizes to catch any special casing. + NonASCII16_helper(1); + NonASCII16_helper(8); + NonASCII16_helper(16); + NonASCII16_helper(32); + NonASCII16_helper(512); +} + +TEST(UTF, UTF8CharEnumerator) +{ + const char* p = + "\x61\xC0\xC2\xC2\x80\xE0\x80\x80\xE0\xA0\x80\xE1\x80\x80\xED\xBF\xBF\xED" + "\x9F\xBF\xEE\x80\x80\xEE\x80\xFF\xF0\x90\x80\x80\xF0\x80\x80\x80\xF1\x80" + "\x80\x80\xF4\x8F\xBF\xF4\x8F\xBF\xBF\xF4\xBF\xBF\xBF"; + const char* end = p + 49; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0061U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0080U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0800U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x1000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xD7FFU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xE000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x10000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x40000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x10FFFFU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xC2\xB6"; + end = p + 1; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xE2\x98\x83"; + end = p + 2; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xF0\x9F\x92\xA9"; + end = p + 2; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xF0\x9F\x92\xA9"; + end = p + 3; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); +} + +TEST(UTF, UTF16CharEnumerator) +{ + const char16_t* p = u"\u0061\U0001F4A9"; + const char16_t* end = p + 3; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x0061U); + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x1F4A9U); + EXPECT_EQ(p, end); + const char16_t loneHigh = 0xD83D; + p = &loneHigh; + end = p + 1; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + const char16_t loneLow = 0xDCA9; + p = &loneLow; + end = p + 1; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + const char16_t loneHighStr[] = {0xD83D, 0x0061}; + p = loneHighStr; + end = p + 2; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x0061U); + EXPECT_EQ(p, end); +} + +} // namespace TestUTF diff --git a/xpcom/tests/gtest/TestVariant.cpp b/xpcom/tests/gtest/TestVariant.cpp new file mode 100644 index 0000000000..8aec8840c6 --- /dev/null +++ b/xpcom/tests/gtest/TestVariant.cpp @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#include <math.h> +#include "nsVariant.h" +#include "gtest/gtest.h" + +TEST(Variant, DoubleNaN) +{ + nsDiscriminatedUnion du; + du.SetFromDouble(NAN); + + uint8_t ui8; + EXPECT_EQ(du.ConvertToInt8(&ui8), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + int16_t i16; + EXPECT_EQ(du.ConvertToInt16(&i16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + int32_t i32; + EXPECT_EQ(du.ConvertToInt32(&i32), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + int64_t i64; + EXPECT_EQ(du.ConvertToInt64(&i64), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + EXPECT_EQ(du.ConvertToUint8(&ui8), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + uint16_t ui16; + EXPECT_EQ(du.ConvertToUint16(&ui16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + uint32_t ui32; + EXPECT_EQ(du.ConvertToUint32(&ui32), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + uint64_t ui64; + EXPECT_EQ(du.ConvertToUint64(&ui64), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + float f = 0.0f; + EXPECT_EQ(du.ConvertToFloat(&f), NS_OK); + EXPECT_TRUE(isnan(f)); + + double d = 0.0; + EXPECT_EQ(du.ConvertToDouble(&d), NS_OK); + EXPECT_TRUE(isnan(d)); + + bool b = true; + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + char c; + EXPECT_EQ(du.ConvertToChar(&c), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + char16_t c16; + EXPECT_EQ(du.ConvertToWChar(&c16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + nsID id = {}; + EXPECT_EQ(du.ConvertToID(&id), NS_ERROR_CANNOT_CONVERT_DATA); + + nsAutoString string; + EXPECT_EQ(du.ConvertToAString(string), NS_OK); + EXPECT_EQ(string, u"NaN"_ns); + + nsAutoCString utf8string; + EXPECT_EQ(du.ConvertToAUTF8String(utf8string), NS_OK); + EXPECT_EQ(utf8string, "NaN"_ns); + + nsAutoCString autocstring; + EXPECT_EQ(du.ConvertToACString(autocstring), NS_OK); + EXPECT_EQ(autocstring, "NaN"_ns); + + char* chars = nullptr; + EXPECT_EQ(du.ConvertToString(&chars), NS_OK); + EXPECT_STREQ(chars, "NaN"); + free(chars); + + char16_t* wchars = nullptr; + EXPECT_EQ(du.ConvertToWString(&wchars), NS_OK); + { + // gtest doesn't seem to support EXPECT_STREQ on char16_t, so convert + // to Gecko strings to do the comparison. + nsDependentString wstring(wchars); + EXPECT_EQ(wstring, u"NaN"_ns); + } + free(wchars); + + chars = nullptr; + uint32_t size = 0; + EXPECT_EQ(du.ConvertToStringWithSize(&size, &chars), NS_OK); + EXPECT_STREQ(chars, "NaN"); + EXPECT_EQ(size, 3u); + free(chars); + + wchars = nullptr; + size = 0; + EXPECT_EQ(du.ConvertToWStringWithSize(&size, &wchars), NS_OK); + { + // gtest doesn't seem to support EXPECT_STREQ on char16_t, so convert + // to Gecko strings to do the comparison. + nsDependentString wstring(wchars); + EXPECT_EQ(wstring, u"NaN"_ns); + EXPECT_EQ(size, 3u); + } + free(wchars); + + nsISupports* isupports; + EXPECT_EQ(du.ConvertToISupports(&isupports), NS_ERROR_CANNOT_CONVERT_DATA); + + nsIID* idp; + void* iface; + EXPECT_EQ(du.ConvertToInterface(&idp, &iface), NS_ERROR_CANNOT_CONVERT_DATA); + + uint16_t type; + nsIID iid; + uint32_t count; + void* array; + EXPECT_EQ(du.ConvertToArray(&type, &iid, &count, &array), + NS_ERROR_CANNOT_CONVERT_DATA); +} + +TEST(Variant, Bool) +{ + nsDiscriminatedUnion du; + bool b = false; + + du.SetFromInt64(12); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_TRUE(b); + + b = true; + du.SetFromInt64(0); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + b = true; + du.SetFromDouble(1.25); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_TRUE(b); + + b = true; + du.SetFromDouble(0.0); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + b = true; + du.SetFromDouble(-0.0); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + // This is also checked in the previous test, but I'm including it + // here for completeness. + b = true; + du.SetFromDouble(NAN); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); +} diff --git a/xpcom/tests/gtest/UTFStrings.h b/xpcom/tests/gtest/UTFStrings.h new file mode 100644 index 0000000000..9613da32a1 --- /dev/null +++ b/xpcom/tests/gtest/UTFStrings.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 utfstrings_h__ +#define utfstrings_h__ + +struct UTFStringsStringPair { + char16_t m16[16]; + char m8[16]; +}; + +static const UTFStringsStringPair ValidStrings[] = { + {{'a', 'b', 'c', 'd'}, {'a', 'b', 'c', 'd'}}, + {{'1', '2', '3', '4'}, {'1', '2', '3', '4'}}, + {{0x7F, 'A', 0x80, 'B', 0x101, 0x200}, + {0x7F, 'A', char(0xC2), char(0x80), 'B', char(0xC4), char(0x81), + char(0xC8), char(0x80)}}, + {{0x7FF, 0x800, 0x1000}, + {char(0xDF), char(0xBF), char(0xE0), char(0xA0), char(0x80), char(0xE1), + char(0x80), char(0x80)}}, + {{0xD7FF, 0xE000, 0xF00F, 'A', 0xFFF0}, + {char(0xED), char(0x9F), char(0xBF), char(0xEE), char(0x80), char(0x80), + char(0xEF), char(0x80), char(0x8F), 'A', char(0xEF), char(0xBF), + char(0xB0)}}, + {{0xFFF7, 0xFFFC, 0xFFFD, 0xFFFD}, + {char(0xEF), char(0xBF), char(0xB7), char(0xEF), char(0xBF), char(0xBC), + char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD)}}, + {{0xD800, 0xDC00, 0xD800, 0xDCFF}, + {char(0xF0), char(0x90), char(0x80), char(0x80), char(0xF0), char(0x90), + char(0x83), char(0xBF)}}, + {{0xDBFF, 0xDFFF, 0xDBB7, 0xDCBA}, + {char(0xF4), char(0x8F), char(0xBF), char(0xBF), char(0xF3), char(0xBD), + char(0xB2), char(0xBA)}}, + {{0xFFFD, 0xFFFF}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBF)}}, + {{0xFFFD, 0xFFFE, 0xFFFF}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBE), + char(0xEF), char(0xBF), char(0xBF)}}, +}; + +static const UTFStringsStringPair Invalid16Strings[] = { + {{'a', 'b', 0xD800}, {'a', 'b', char(0xEF), char(0xBF), char(0xBD)}}, + {{0xD8FF, 'b'}, {char(0xEF), char(0xBF), char(0xBD), 'b'}}, + {{0xD821}, {char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC21}, {char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC00, 0xD800, 'b'}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), + 'b'}}, + {{'b', 0xDC00, 0xD800}, + {'b', char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), + char(0xBD)}}, + {{0xDC00, 0xD800}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC00, 0xD800, 0xDC00, 0xD800}, + {char(0xEF), char(0xBF), char(0xBD), char(0xF0), char(0x90), char(0x80), + char(0x80), char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC00, 0xD800, 0xD800, 0xDC00}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), + char(0xF0), char(0x90), char(0x80), char(0x80)}}, +}; + +static const UTFStringsStringPair Invalid8Strings[] = { + {{'a', 0xFFFD, 0xFFFD, 'b'}, {'a', char(0xC0), char(0x80), 'b'}}, + {{0xFFFD, 0xFFFD, 0x80}, {char(0xC1), char(0xBF), char(0xC2), char(0x80)}}, + {{0xFFFD, 0xFFFD}, {char(0xC1), char(0xBF)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 'x', 0x0800}, + {char(0xE0), char(0x80), char(0x80), 'x', char(0xE0), char(0xA0), + char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD}, + {char(0xF0), char(0x80), char(0x80), char(0x80), 'x', char(0xF0), + char(0x80), char(0x8F), char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD}, + {char(0xF4), char(0x90), char(0x80), char(0x80), char(0xF7), char(0xBF), + char(0xBF), char(0xBF)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xD800, 0xDC00, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD}, + {char(0xF0), char(0x8F), char(0xBF), char(0xBF), 'x', char(0xF0), + char(0x90), char(0x80), char(0x80), char(0xF0), char(0x8F), char(0xBF), + char(0xBF)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD}, + {char(0xF8), char(0x80), char(0x80), char(0x80), char(0x80), 'x', + char(0xF8), char(0x88), char(0x80), char(0x80), char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD}, + {char(0xFB), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xFC), + char(0xA0), char(0x80), char(0x80), char(0x80), char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD}, + {char(0xFC), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), + char(0xFD), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xBF)}}, +}; + +static const UTFStringsStringPair Malformed8Strings[] = { + {{0xFFFD}, {char(0x80)}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xC8), 'c'}}, + {{'a', 0xFFFD}, {'a', char(0xC8)}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xE8), 'c'}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xE8), char(0x80), 'c'}}, + {{'a', 0xFFFD}, {'a', char(0xE8), char(0x80)}}, + {{0xFFFD, 0x7F, 0xFFFD}, {char(0xE8), 0x7F, char(0x80)}}, + {{'a', 0xFFFD, 0xFFFD}, {'a', char(0xE8), char(0xE8), char(0x80)}}, + {{'a', 0xFFFD}, {'a', char(0xF4)}}, + {{'a', 0xFFFD, 'c', 'c'}, + {'a', char(0xF4), char(0x80), char(0x80), 'c', 'c'}}, + {{'a', 0xFFFD, 'x', 0xFFFD}, + {'a', char(0xF4), char(0x80), 'x', char(0x80)}}, + {{0xDBC0, 0xDC00, 0xFFFD}, + {char(0xF4), char(0x80), char(0x80), char(0x80), char(0x80)}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xFA), 'c'}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0x7F, 0xFFFD, 'c'}, + {'a', char(0xFA), char(0x80), char(0x80), 0x7F, char(0x80), 'c'}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'c'}, + {'a', char(0xFA), char(0x80), char(0x80), char(0x80), char(0x80), + char(0x80), 'c'}}, + {{'a', 0xFFFD}, {'a', char(0xFD)}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'c'}, + {'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), 'c'}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD}, + {'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), + char(0x80), char(0x80)}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0x40, 0xFFFD, 0xFFFD, 'c'}, + {'a', char(0xFD), char(0x80), char(0x80), 0x40, char(0x80), char(0x80), + 'c'}}, +}; + +#endif diff --git a/xpcom/tests/gtest/dafsa_test_1.dat b/xpcom/tests/gtest/dafsa_test_1.dat new file mode 100644 index 0000000000..603813dbb4 --- /dev/null +++ b/xpcom/tests/gtest/dafsa_test_1.dat @@ -0,0 +1,6 @@ +%% +foo.bar.baz, 1 +a.test.string, 0 +a.test.string2, 2 +aaaa, 4 +%% diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build new file mode 100644 index 0000000000..57ec43a371 --- /dev/null +++ b/xpcom/tests/gtest/moz.build @@ -0,0 +1,181 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + "Helpers.cpp", + "TestArenaAllocator.cpp", + "TestArrayAlgorithm.cpp", + "TestAtoms.cpp", + "TestAutoRefCnt.cpp", + "TestBase64.cpp", + "TestCallTemplates.cpp", + "TestCloneInputStream.cpp", + "TestCOMPtrEq.cpp", + "TestCRT.cpp", + "TestDafsa.cpp", + "TestDelayedRunnable.cpp", + "TestEncoding.cpp", + "TestEscape.cpp", + "TestEventPriorities.cpp", + "TestEventTargetQI.cpp", + "TestFile.cpp", + "TestGCPostBarriers.cpp", + "TestID.cpp", + "TestIDUtils.cpp", + "TestInputStreamLengthHelper.cpp", + "TestJSHolderMap.cpp", + "TestLogCommandLineHandler.cpp", + "TestLogging.cpp", + "TestMemoryPressure.cpp", + "TestMoveString.cpp", + "TestMozPromise.cpp", + "TestMruCache.cpp", + "TestMultiplexInputStream.cpp", + "TestNonBlockingAsyncInputStream.cpp", + "TestNsDeque.cpp", + "TestNSPRLogModulesParser.cpp", + "TestObserverArray.cpp", + "TestObserverService.cpp", + "TestOwningNonNull.cpp", + "TestPLDHash.cpp", + "TestPriorityQueue.cpp", + "TestQueue.cpp", + "TestRacingServiceManager.cpp", + "TestRecursiveMutex.cpp", + "TestRustRegex.cpp", + "TestRWLock.cpp", + "TestSegmentedBuffer.cpp", + "TestSlicedInputStream.cpp", + "TestSmallArrayLRUCache.cpp", + "TestSnappyStreams.cpp", + "TestStateMirroring.cpp", + "TestStateWatching.cpp", + "TestStorageStream.cpp", + "TestStrings.cpp", + "TestStringStream.cpp", + "TestSubstringTuple.cpp", + "TestSynchronization.cpp", + "TestTArray.cpp", + "TestTArray2.cpp", + "TestTaskQueue.cpp", + "TestTextFormatter.cpp", + "TestThreadManager.cpp", + "TestThreadPool.cpp", + "TestThreadPoolListener.cpp", + "TestThrottledEventQueue.cpp", + "TestTimeStamp.cpp", + "TestTokenizer.cpp", + "TestUTF.cpp", + "TestVariant.cpp", +] + +if CONFIG["OS_TARGET"] != "Android": + UNIFIED_SOURCES += [ + "TestPipes.cpp", + "TestThreads.cpp", + ] + +# skip the test on windows10-aarch64 due to perma-fail, bug 1422219 +if not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["TARGET_CPU"] == "aarch64"): + UNIFIED_SOURCES += ["TestThreadUtils.cpp"] + +# skip the test on OSX due to frequent failures (bug 1571186) +if CONFIG["OS_TARGET"] != "Darwin": + UNIFIED_SOURCES += ["TestExpirationTracker.cpp"] + +# skip the test on windows10-aarch64 and Android, aarch64 due to bug 1545670 +if CONFIG["OS_TARGET"] != "Android" and not ( + CONFIG["OS_TARGET"] == "WINNT" and CONFIG["TARGET_CPU"] == "aarch64" +): + UNIFIED_SOURCES += ["TestTimers.cpp"] + + +if ( + CONFIG["MOZ_DEBUG"] + and CONFIG["OS_ARCH"] not in ("WINNT") + and CONFIG["OS_TARGET"] != "Android" +): + # FIXME bug 523392: TestDeadlockDetector doesn't like Windows + # Bug 1054249: Doesn't work on Android + UNIFIED_SOURCES += [ + "TestDeadlockDetector.cpp", + "TestDeadlockDetectorScalability.cpp", + ] + +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "TestAvailableMemoryWatcherWin.cpp", + "TestFileNTFSSpecialPaths.cpp", + "TestFilePreferencesWin.cpp", + "TestHandleWatcher.cpp", + ] +else: + UNIFIED_SOURCES += [ + "TestFilePreferencesUnix.cpp", + ] + +if CONFIG["OS_TARGET"] == "Darwin": + UNIFIED_SOURCES += [ + "TestAvailableMemoryWatcherMac.cpp", + "TestMacNSURLEscaping.mm", + "TestThreads_mac.mm", + ] + +if CONFIG["OS_TARGET"] == "Linux": + UNIFIED_SOURCES += [ + "TestAvailableMemoryWatcherLinux.cpp", + ] + +if ( + CONFIG["WRAP_STL_INCLUDES"] + and CONFIG["CC_TYPE"] != "clang-cl" + and CONFIG["OS_TARGET"] != "Android" +): + UNIFIED_SOURCES += [ + "TestSTLWrappers.cpp", + ] + +# Compile TestAllocReplacement separately so Windows headers don't pollute +# the global namespace for other files. +if CONFIG["MOZ_MEMORY"]: + SOURCES += [ + "TestAllocReplacement.cpp", + ] + +SOURCES += [ + "TestCOMArray.cpp", + "TestCOMPtr.cpp", # Redefines IFoo and IBar + "TestHashtables.cpp", # Redefines IFoo + "TestNsRefPtr.cpp", # Redefines Foo +] + +LOCAL_INCLUDES += [ + "../../base", + "/toolkit/components/telemetry/tests/gtest", + "/xpcom/components", +] + +GeneratedFile( + "dafsa_test_1.inc", + script="../../ds/tools/make_dafsa.py", + inputs=["dafsa_test_1.dat"], +) + +TEST_HARNESS_FILES.gtest += [ + "wikipedia/ar.txt", + "wikipedia/de-edit.txt", + "wikipedia/de.txt", + "wikipedia/ja.txt", + "wikipedia/ko.txt", + "wikipedia/ru.txt", + "wikipedia/th.txt", + "wikipedia/tr.txt", + "wikipedia/vi.txt", +] + +FINAL_LIBRARY = "xul-gtest" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/xpcom/tests/gtest/wikipedia/README.txt b/xpcom/tests/gtest/wikipedia/README.txt new file mode 100644 index 0000000000..bd17b17c85 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/README.txt @@ -0,0 +1,13 @@ +The content of the .txt files in this directory originate from Wikipedia and +is licensed the Creative Commons Attribution-ShareAlike 3.0 Unported license +<https://creativecommons.org/licenses/by-sa/3.0/legalcode>. + +The content comes from the following revisions: +ar: https://ar.wikipedia.org/w/index.php?title=%D8%A7%D9%84%D9%85%D8%B1%D9%8A%D8%AE&oldid=21144485 +de: https://de.wikipedia.org/w/index.php?title=Mars_(Planet)&oldid=158965843 +ja: https://ja.wikipedia.org/w/index.php?title=%E7%81%AB%E6%98%9F&oldid=61095795 +ko: https://ko.wikipedia.org/w/index.php?title=%ED%99%94%EC%84%B1&oldid=17394891 +ru: https://ru.wikipedia.org/w/index.php?title=%D0%9C%D0%B0%D1%80%D1%81&oldid=81533008 +th: https://th.wikipedia.org/w/index.php?title=%E0%B8%94%E0%B8%B2%E0%B8%A7%E0%B8%AD%E0%B8%B1%E0%B8%87%E0%B8%84%E0%B8%B2%E0%B8%A3&oldid=6511628 +tr: https://tr.wikipedia.org/w/index.php?title=Mars&oldid=17608551 +vi: https://vi.wikipedia.org/w/index.php?title=Sao_H%E1%BB%8Fa&oldid=24192627 diff --git a/xpcom/tests/gtest/wikipedia/ar.txt b/xpcom/tests/gtest/wikipedia/ar.txt new file mode 100644 index 0000000000..0dba6a8a9a --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ar.txt @@ -0,0 +1,70 @@ +المÙرÙّيخ (Mars مارس) هو الكوكب الرابع ÙÙŠ البعد عن الشمس ÙÙŠ النظام الشمسي وهو الجار الخارجي للأرض ويصن٠كوكبا صخريا، من مجموعة الكواكب الأرضية (الشبيهة بالأرض). + +اطلق عليه بالعربية المريخ نسبةً إلى كلمة أمرخ أي ذو البقع الØمراء، Ùيقال ثور أَمرخ أي به بقع Øمراء، وهو باللاتينية مارس الذي اتخذه الرومان آله للØرب، وهو يلقب ÙÙŠ الوقت الØالي بالكوكب الأØمر بسبب لونه المائل إلى الØمره، بÙعل نسبة غبار أكسيد الØديد الثلاثي العالية على سطØÙ‡ ÙˆÙÙŠ جوه. + +يبلغ قطر المريخ Øوالي 6800 كلم وهو بذلك مساو لنص٠قطر الأرض وثاني أصغر كواكب النظام الشمسي بعد عطارد. تقدّر مساØته بربع مساØØ© الأرض. يدور المريخ Øول الشمس ÙÙŠ مدار يبعد عنها بمعدل 228 مليون كلم تقريبا، أي 1.5 مرات من المساÙØ© الÙاصلة بين مدار الأرض والشمس. + +له قمران، يسمّى الأول ديموس أي الرعب باللغة اليونانية والثاني Ùوبوس أي الخوÙ. + +يعتقد العلماء أن كوكب المريخ اØتوى الماء قبل 3.8 مليار سنة، مما يجعل Ùرضية وجود Øياة عليه متداولة نظريا على الأقل. به جبال أعلى من مثيلاتها الأرضية ووديان ممتدة. وبه أكبر بركان ÙÙŠ المجموعة الشمسية يطلق عليه اسم أوليمبس مونز تيمنا بجبل الأولمب. + +تبلغ درجة Øرارته العليا 27 درجة مئوية ودرجة Øرارته الصغرى -133 درجة مئوية. ويتكون غلاÙÙ‡ الجوي من ثاني أكسيد الكربون والنيتروجين والأرغون وبخار الماء وغازات أخرى. رمز المريخ الÙلكي هو ♂.يجذب الانتباه بلونه الأØمر + +قد يكون المريخ ÙˆÙقا لدراسة عالمين أمريكيين مجرد كوكب جنين لم يستطع أن يتم نموه، بعد أن نجا من الأصطدامات الكثيرة بين الأجرام السماوية التي شهدها النظام الشمسي ÙÙŠ بداية تكوينه والتي أدت لتضخم أغلب الكواكب الأخرى. وهذا ÙŠÙسر صغر Øجم المريخ مقارنة بالأرض أو بالزهرة. خلص العالمان إلى هذه النتيجة بعد دراسة استقصائية لنواتج الاضمØلال المشعة ÙÙŠ النيازك.[1] + +يستضي٠المريخ Øالياً 5 مركبات Ùضائية لا تزال تعمل، ثلاث ÙÙŠ مدار Øول الكوكب وهم مارس أوديسي ومارس إكسبريس ومارس ريكونيسانس أوربيتر، واثنتان على Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ وهما كيوريوسيتي روÙر وأبورتيونيتي، كما أن هناك مركبات Ùضائية لم تعد تعمل سواء كانت مهمتها ناجØØ© أم لا مثل مركبة Ùينيكس لاندر التي أنهت مهمتها عام 2008.[2] + +مقارنة بكوكب الأرض، للمريخ ربع مساØØ© Ø³Ø·Ø Ø§Ù„Ø£Ø±Ø¶ وبكتلة تعادل عÙشر كتلة الأرض. هواء المريخ لا يتمتع بنÙس كثاÙØ© هواء الأرض إذ يبلغ الضغط الجوي على Ø³Ø·Ø Ø§Ù„Ù…Ø±ÙŠØ® 0.75% من معدّل الضغط الجوي على الأرض، لذا نرى ان المجسّات الآلية التي قامت وكالة الÙضاء الأمريكية بإرسالها لكوكب المريخ، تÙغلّ٠بكÙرة٠هوائية لامتصاص الصدمة عند الارتطام Ø¨Ø³Ø·Ø ÙƒÙˆÙƒØ¨ المريخ. يتكون هواء المريخ من 95% ثنائي أكسيد الكربون، 3% نيتروجين، 1.6% ارجون، وجزء بسيط من الأكسجين والماء. ÙˆÙÙŠ العام 2000ØŒ توصّل الباØثون لنتائج توØÙŠ بوجود Øياة على كوكب المريخ بعد معاينة قطع من نيزك عثر عليه ÙÙŠ القارة المتجمدة الجنوبية وتم تØديد أصله من كوكب المريخ نتيجة مقارنة تكوينه المعدني وتكوين الصخور التي تمت معاينتها من المركبات Ùيكينغ 1 Ùˆ2ØŒ Øيث استدلّ الباØثون على وجود Ø£ØاÙير مجهرية ÙÙŠ النيزك. ولكن تبقى الÙرضية آنÙØ© الذكر مثاراً للجدل دون التوصل إلى نتيجة أكيدة بوجود Øياة ÙÙŠ الماضي على كوكب المريخ. + +ويعتبر المريخ كوكب صخري ومعظم سطØÙ‡ Ø£Øمر إلا بعض البقع الأغمق لوناً بسبب تربته وصخوره والغلا٠الجوي لكوكب المريخ قليل الكثاÙØ© ويتكون أساساً من ثاني أكسيد الكربون وكميات قليلة من بخار الماء والضغط الجوي على المريخ منخÙض جدًا ويصل إلى 0.01 من الضغط الجوي للأرض وجو المريخ ابرد من الأرض والسنة على المريخ 687 يوماً ارضياً. + +التركيب الداخلي[عدل] + +Øدث للمريخ تماماً ما Øدث للأرض من تمايز أو تباين والمقصود بالتمايز هنا العملية التي ينتج عنها اختلا٠ÙÙŠ كثاÙØ© ومكونات كل طبقة من طبقات الكوكب بØيث يكون قلب أو لب الكوكب عالي الكثاÙØ© وما Ùوقه أقل منه ÙÙŠ الكثاÙØ©. النموذج الØالي لكوكب المريخ ينطوي على التالي: القلب يمتد لمساÙØ© يبلغ نص٠قطرها 1794 ± 65 كيلومتر وهي تتكون أساساً من الØديد والنيكل والكبريت بنسبة 16-17%. هذا القلب المكون من كبريتات الØديد سائل جزئياً، وتركيزه ضع٠تركيز باقي المواد الأخ٠الموجودة ÙÙŠ القلب. ÙŠØاط هذا القلب بدثار من السليكات والتي تكون العديد من المظاهر التكتونية والبركانية على الكوكب إلا أنها الآن تبدو كامنة. بجانب السيليكون والأكسجين، Ùإن أكثر العناصر انتشاراً ÙÙŠ قشرة كوكب المريخ هي الØديد والألومنيوم والماغنسيوم والألومنيوم والكالسيوم والبوتاسيوم. يبلغ متوسط سماكة قشرة كوكب المريخ 50 كيلومتر وأقصى ارتÙاع 125 كيلومتر، ÙÙŠ Øين أن قشرة الأرض تبلغ سماكتها 40 كم، وهذا السÙمك بالنسبة Ù„Øجم الأرض يعادل ثلث سماكة قشرة كوكب المريخ بالنسبة إلى Øجمه. من المخطط له أن تقوم مركبة الÙضاء إن سايت بتØليل أكثر دقة لكوكب المريخ أثناء مهمتها عليه ÙÙŠ عام 2016 باستخدام جهاز مقياس الزلازل لتØدد نموذج للتركيب الداخلي للكوكب بصورة Ø£Ùضل. +التربة[عدل] + +أظهرت البيانات التي وصلت من مسبار الÙضاء Ùينيكس أن تربة المريخ قلوية قليلاً وتØتوي على مواد مثل الماغنسيوم والصوديوم والبوتاسيوم والكلورين، هذه المغذيات موجودة ÙÙŠ الØدائق على الأرض، وهي ضرورية لنمو النباتات. وأظهرت التجارب التي أجراها مسبار الÙضاء أن تربة المريخ لها تركيز هيدروجيني 8.3 وربما تØتوي على آثار Ù„Ù…Ù„Ø Ø§Ù„Ø¨ÙŠØ±ÙƒÙ„ÙˆØ±ÙŠÙƒ. قال سام كوناÙيس كبير الخبراء المختصين بمختبر كيمياء الموائع الموجود على Ùينيكس للصØÙيين "وجدنا أساسا ما تبدو أنها الخصائص أو العناصر المغذية التي تدعم إمكانية الØياة سواء ÙÙŠ الماضي أو الØاضر أو المستقبل.[3] +المياه[عدل] + + Crystal Clear app kdict.png مقالة Ù…Ùصلة: المياه علي كوكب المريخ + +توجد المياه علي Ø³Ø·Ø Ø§Ù„Ù…Ø±ÙŠØ® غالبا ÙÙŠ صورة جليد ويمثل الغطائين الجليديين ÙÙŠ القطب الشمالي والجنوبي للكوكب معظم الجليد الموجود علي Ø§Ù„Ø³Ø·Ø ÙŠÙˆØ¬Ø¯ أيضا بعض الجليد ÙÙŠ صخور القشرة المريخية. كما توجد نسبة ضئيلة من بخار الماء ÙÙŠ الغلا٠الجوي للكوكب. لكن لاتوجد مياه سائلة علي Ø³Ø·Ø Ø§Ù„Ù…Ø±ÙŠØ® إطلاقا. يرجع وجود الماء ÙÙŠ صورة جليدية الي الظرو٠المناخية للمريخ Øيث درجات الØرارة المنخÙضة جدا والتي تؤدي الي تجمد المياه الÙوري. مع ذلك Ùقد أكدت الدراسات ان الوضع علي Ø³Ø·Ø Ø§Ù„Ù…Ø±ÙŠØ® كان مختلÙا كثيرا عما هو عليه الآن ولربما كان يشبه كوكب الأرض Øيث كانت توجد المياة السائلة[4] ÙÙŠ مساØات كبيرة من Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ مشكله Ù…Øيطات مثل الموجودة الآن علي Ø³Ø·Ø Ø§Ù„Ø£Ø±Ø¶. + +توجد الكثير من الدلائل المباشرة وغير المباشرة علي هذه النظرية منها التØليلات الطيÙية Ù„Ø³Ø·Ø ØªØ±Ø¨Ø© المريخ وأيضا الغطائين القطبيين الجليديين وأيضاً وجود الكثير من المعادن ÙÙŠ قشرة المريخ والتي ارتبط وجودها علي Ø³Ø·Ø Ø§Ù„Ø£Ø±Ø¶ بوجود المياه. منها أكسيد الØديد Hematite وأكسيد الكبريت Sulfate والجوثايت goethite ومركبات السيليكا phyllosilicate.[5] لقد ساعدت كثيرا مركبات ورØلات الÙضاء غير المأهولة الي المريخ ÙÙŠ دراسة Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ وتØليل تربته وغلاÙÙ‡ الجوي. ومن أكثر المركبات التي ساعدت علي ذلك مركبة مارس ريكونيسانس أوربيتر علي تصوير Ø³Ø·Ø Ø§Ù„Ù…Ø±ÙŠØ® بدقة عالية وتØليل Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ بÙضل وجود الكاميرا عالية الجودة HiRISE كما كشÙت عن Ùوهات البراكين المتآكلة ومجاري النهار الجاÙØ© والأنهار الجليدية. + +كما كشÙت الدراسات الطيÙية بأشعة غاما عن وجود الجليد تØت Ø³Ø·Ø ØªØ±Ø¨Ø© المريخ. أيضا، كشÙت الدراسات بالرادار عن وجود الجليد النقي ÙÙŠ التشكيلات التي يعتقد أنها كانت أنهار جليدية قديمة.المركبة الÙضائية Ùينيكس التي هبطت قرب القطب الشمالي ورأت الجليد وهو يذوب الجليد، وشهدت تساقط الثلوج، ورأت Øتي قطرات من الماء السائل. +Arabic-final.jpg +الطبوغراÙيا[عدل] + + Crystal Clear app kdict.png مقالة Ù…Ùصلة: نيازك المريخ + +طبوغراÙية كوكب المريخ جديرة بالاهتمام، ÙÙÙŠ Øين يتكون الجزء الشمالي من الكوكب من سهول الØمم البركانية، وتقع البراكين العملاقة على هضبة تارسيس وأشهرها على الإطلاق أوليمبس مون وهو بدون شك أكبر بركان ÙÙŠ المجموعة الشمسية، نجد ان الجزء الجنوبي من كوكب المريخ يتمتّع بمرتÙعات شاهقة ويبدو على المرتÙعات آثار النيازك والشّهب التي ارتطمت على تلك المرتÙعات. يغطي سهول كوكب المريخ الغبار والرمل الغني بأكسيد الØديد ذو اللون الأØمر. تغطّي بعض مناطق المريخ Ø£Øيانا طبقة رقيقة من جليد الماء. ÙÙŠ Øين تغطي القطبين طبقات سميكة من جليد مكون من ثاني أكسيد الكربون والماء المتجمّد. تجدرالإشارة أن أعلى قمّة جبلية ÙÙŠ النظام الشمسي هي قمّة جبل "اوليمبوس" والتي يصل ارتÙاعها إلى 25 كم. أمّا بالنسبة للأخاديد، Ùيمتاز الكوكب الأØمر بوجود أكبر أخدود ÙÙŠ النظام الشمسي، ويمتد الأخدود "وادي مارينر" إلى مساÙØ© 4000 كم، وبعمق يصل إلى 7 كم. +الغلا٠الجوي[عدل] + +لقد كانت أول الأخبار عن جو المريخ من سلسلة رØلات مارينر، Øيث تم التأكيد على أن للكوكب غلا٠الجوي رقيق جداً يصل إلى 0.01 بالنسبة لغلا٠الأرض الجوي. يتأل٠هذا الجو الرقيق من CO2 ÙÙŠ أغلبه Øيث تصل نسبته إلى 95% من مكوناته. ثم تم تØليل مكونات الجو بواسطة المركبة Ùايكينغ 1 لنصل إلى خلاصته عن تركيب الجو وهي كما ÙÙŠ الجدول: +المادة النسبة % + +والضغط الجوي على Ø³Ø·Ø Ù‡Ø°Ø§ الكوكب يقارب 1/100 من الضغط الجوي على Ø³Ø·Ø Ø§Ù„Ø£Ø±Ø¶ عند مستوى Ø³Ø·Ø Ø§Ù„Ø¨Øر. وقد تم تلمس كمية ضئيلة جداً من الأوزون يصل تركيزها إلى 0.03 جزئ /مليون جزيء.ولكن هذا التركيز لا ÙŠØمي من الأشعة Ùوق البنÙسجية الضارة. ونلاØظ من الجدول أن نسبة بخار الماء ÙÙŠ الجو ضئيلة جداً مما يجعل الجو جاÙاً. ولكن بسبب برودة Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ Ùإن كمية بخار الماء الضئيلة هذه تكÙÙŠ لإشباعه. ومع استمرارية انخÙاض درجة الØرارة دون درجة الندى تبدأ الغازات وخاصة CO2 بالتكاث٠والتجمد والسقوط على Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨. وتم رصد عواص٠مØلية على Ø§Ù„Ø³Ø·Ø ÙˆÙ‡ÙŠ عبارة عن هبوب Ø±ÙŠØ§Ø Ù‚ÙˆÙŠØ© تتØرك بسرعة وتكون غيوم غبارية وزوابع تدور على Ø§Ù„Ø³Ø·Ø ÙˆØªÙ†Ù‚Ù„ التربة من مكان إلى آخر. وهذه Ø§Ù„Ø±ÙŠØ§Ø Ø§Ù„ØªÙŠ تعص٠على الكوكب لها كما على الأرض دورة Ø±ÙŠØ§Ø ÙŠÙˆÙ…ÙŠØ© ودورة موسمية. ولها تأثير كبير ÙÙŠ عمليات الØت والتجوية على Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨. ولأن كثاÙØ© الجو 2% من كثاÙØ© جو الأرض يجب أن تكون قوة Ø§Ù„Ø±ÙŠØ§Ø Ø£ÙƒØ¨Ø± بØوالي 7 إلى 8 مرات من قوة Ø§Ù„Ø±ÙŠØ§Ø Ø§Ù„Ø£Ø±Ø¶ÙŠØ© Øتى تستطيع أن تثير وتØمل الغبار وتكون زوابع. ÙØ§Ù„Ø±ÙŠØ§Ø Ø§Ù„Ø£Ø±Ø¶ÙŠØ© بسرعة 24 كلم بالساعة تثير هذه العواص٠أما على المريخ ÙÙ†Øتاج إلى Ø±ÙŠØ§Ø Ø¨Ø³Ø±Ø¹Ø© 180 كلم بالساعة لتقوم بمثل هذه العواصÙ. ودعيت هذه التأثير بتأثير عÙولس Eo`lian effect نسبة إلى آله Ø§Ù„Ø±ÙŠØ Ø¹Ùولس E`olus. ومن الأدلة الواضØØ© على تأثر عÙولس Ù„Øركة Ø§Ù„Ø±ÙŠØ§Ø Ù‡Ùˆ الكثبان الرملية. Øيث تØمل Ø§Ù„Ø±ÙŠØ§Ø Ø§Ù„Ø±Ù…Ø§Ù„ من مكان وتلقيها ÙÙŠ مكان آخر. Ùنجد لها امتداداً واضØاً على Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨. وعندما تثور كمية من الغبار Ùإن العاصÙØ© تØاÙظ على بقائها بتØويل الطاقة الشمسية إلى طاقة Øركية ريØية، Øيث تمتص الطاقة من الإشعاع الشمسي وتسخن الجو وتزيد من سرعة الرياØ. Ùيل٠الكوكب دثار مصÙر من الزوابع. ولعدم وجود ماء يغسل الغبار من الجو Ùإنه يبقى عالقاً لعدة أسابيع قبل أن يستقر على Ø§Ù„Ø³Ø·Ø Ø«Ø§Ù†ÙŠØ©. ومن الغريب أن هذه Ø§Ù„Ø±ÙŠØ§Ø ØªØ¹ØµÙ Ø¨Ù‡Ø¯ÙˆØ¡ ومن دون أصوات Ùلا ينطبق عليها أصوات العواص٠الهادرة الأرضية.[6] +مدار الكوكب ودورانه[عدل] + +المريخ هو رابع الكواكب بعداً عن الشمس. وأول كوكب له مدار خارج مدار الأرض ويبعد عن الشمس Øوالي 228 مليون كلم بالمتوسط. شذوذية مركزيته e = 0.093 وهي كبيرة نسبياً، مما يدل على أن مداره إهليلجي بشكل ÙˆØ§Ø¶Ø Øيث يكون وهو ÙÙŠ الØضيض على بعد 206 مليون كلم عن الشمس وعند وصوله إلى الأوج ÙŠØµØ¨Ø Ø¹Ù„Ù‰ بعد 249 مليون كلم عن الشمس. Ùنرى Ùرقاً واضØاً ÙÙŠ البعدين وهذا يؤدي إلى تباين كمية أشعة الشمس الساقطة على سطØÙ‡ بنسبة تصل إلى 45% بين الأوج والØضيض، أي بÙارق 30 Ù’ س وما يتبع ذلك من تغيرات ÙÙŠ مناخ الكوكب بين الموقعين. ودرجة الØرارة ØªØªØ±Ø§ÙˆØ Ø¹Ù„Ù‰ Ø§Ù„Ø³Ø·Ø Ø¨ÙŠÙ† الشتاء والصي٠-144 ْس إلى 27 ْس أما ÙÙŠ المتوسط Ùإن درجة الØرارة تقدر بØوالي –23 Ù’ إلى -55 Ù’ س. + +ويقطع الكوكب هذا المدار ÙÙŠ زمن يعادل 687 يوم أرضي، وأثناء دورانه ÙÙŠ مداره هذا تØدث له عدد من الظواهر منها الاقتران.[6] +قمرا المريخ[عدل] +كوكب المريخ + +تم اكتشا٠قمري المريخ ÙÙŠ العام 1877 على يد "آسا٠هول" وتمّت تسميتهم تيمّناً بمراÙقي الآله اليوناني "آريس". يدور كل من القمر "Ùوبوس" والقمر "ديموس" Øول الكوكب الأØمر، وخلال Ùترة الدوران، تقابل Ù†Ùس الجهة من القمر الكوكب الأØمر تماما مثلما يعرض القمر Ù†Ùس الجانب لكوكب الأرض. +القمر Ùوبوس[عدل] + +Ùوبوس قطعة صخرية صغيرة غير منتظمة الشكل لا يزيد طولها عن 21 كم (13 ميلا) ويتم دورته Øول المريخ كل 7.7 ساعات. يبدو القمر هرم نوعا ما. وتغشاه Ùوهات صدم متÙاوتة القدم. ويلاØظ عليه وجود Øزوز striations وسلاسل من Ùوهات صغيرة. يطلق أكبرها اسم ستيكني stickney الذي يقارب قطره 10 كم (6 أميال). يقوم القمر Ùوبوس بالدوران Øول المريخ اسرع من دوران المريخ Øول Ù†Ùسه، مما يؤدي بقطر دوران القمر Ùوبوس Øول المريخ للتناقص يوماً بعد يوم إلى أن ينتهي به الأمر إلى التÙتت ومن ثم الارتطام بكوكب المريخ. +القمر ديموس[عدل] + +ديموس هو Ø£Øد الأقمار التابعة لكوكب المريخ إلى جانب القمر Ùوبوس وهو عبارة عن قطعة صخرية صغيرة غير منتظمة الشكل لا يزيد طولها عن 12 كم (7 ميلا) ويتم دورته Øول المريخ خلال 1.3 يوم. ولبعده عن الكوكب الأØمر، Ùإن قطر مدار القمر آخذ بالزيادة. ويبدو ديموس على شكل هرمي نوعاً ما. وتغشاه Ùوهات صدم متÙاوتة القدم. +استكشا٠المريخ[عدل] +Ø³Ø·Ø ÙƒÙˆÙƒØ¨ المريخ + + مقال تÙصيلي: تاريخ رصد المريخ + +هناك ما يقرب من 44 Ù…Øاولة[7] إرسال مركبات Ùضائية للكوكب الأØمر من Ù‚Ùبل الولايات المتØدة، الاتØاد السوÙيتي، أوروبا، واليابان. قرابة ثلثين المركبات الÙضائية Ùشلت ÙÙŠ مهمّتها أما على الأرض، أو خلال رØلتها أو خلال هبوطها على Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ الأØمر. من Ø£Ù†Ø¬Ø Ø§Ù„Ù…Øاولات إلى كوكب المريخ تلك التي سمّيت بـ "مارينر"ØŒ "برنامج الÙيكنج"ØŒ "سورÙيور"ØŒ "باثÙيندر"ØŒ Ùˆ"أوديسي". قامت المركبة "سورÙيور" بالتقاط صور Ù„Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ØŒ الأمر الذي أعطى العلماء تصوراً بوجود ماء، إمّا على Ø§Ù„Ø³Ø·Ø Ø£Ùˆ تØت Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ بقليل. وبالنسبة للمركبة "أوديسي"ØŒ Ùقد قامت بإرسال معلومات إلى العلماء على الأرض والتي مكّنت العلماء من الاستنتاج من وجود ماء متجمّد تØت Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ ÙÙŠ المنطقة الواقعة عند 60 درجة جنوب القطب الجنوبي للكوكب. + +ÙÙŠ العام 2003ØŒ قامت وكالة الÙضاء الأوروبية بإرسال مركبة مدارية وسيارة تعمل عن طريق التØكم عن بعد، وقامت الأولى بتأكيد المعلومة المتعلقة بوجود ماء جليد وغاز ثاني أكسيد الكربون المتجمد ÙÙŠ منطقة القطب الجنوبي لكوكب المريخ. تجدر الإشارة إلى أن أول من توصل إلى تلك المعلومة هي وكالة الÙضاء الأمريكية وان المركبة الأوروبية قامت بتأكيد المعلومة. باءت Ù…Øاولات الوكالة الأوروبية بالÙشل ÙÙŠ Ù…Øاولة الاتصال بالسيارة المصاØبة للمركبة الÙضائية وأعلنت الوكالة رسمياً Ùقدانها للسيارة الآلية ÙÙŠ Ùبراير من من Ù†Ùس العام. Ù„Øقت وكالة الÙضاء الأمريكية الرّكب بإرسالها مركبتين Ùضائيتين وكان Ùرق الوقت بين المركبة الأولى والثانية، 3 أسابيع، وتمكن السيارات الآلية الأمريكية من إرسال صور مذهلة Ù„Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ وقامت السيارات بإرسال معلومات إلى العلماء على الأرض تÙيد، بل تؤكّد على تواجد الماء على Ø³Ø·Ø Ø§Ù„ÙƒÙˆÙƒØ¨ الأØمر ÙÙŠ الماضي. ÙÙŠ الشكل أدناه خريطة Ù„Ø³Ø·Ø Ø§Ù„Ù…Ø±ÙŠØ® تظهر أماكن تواجد أهم المركبات الأمريكية على سطØÙ‡. diff --git a/xpcom/tests/gtest/wikipedia/de-edit.txt b/xpcom/tests/gtest/wikipedia/de-edit.txt new file mode 100644 index 0000000000..2fbcd2d74d --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/de-edit.txt @@ -0,0 +1,487 @@ +gezählt, der vierte Planet im Sonnensystem und der äußere Nachbar der Erde. Er zählt zu den erdähnlichen (terrestrischen) Planeten. + +Sein Durchmesser ist mit knapp 6800 Kilometer etwa halb so groß wie der der Erde, sein Volumen beträgt gut ein Siebtel des Erdevolumens. Damit ist der Mars nach dem Merkur der zweitkleinste Planet des Sonnensystems, hat jedoch eine ausgeprägte Geologie und die höchsten Vulkane des Sonnensystems. Mit einer durchschnittlichen Entfernung von 228 Millionen Kilometern ist er rund 1,5-mal so weit von der Sonne entfernt wie die Erde. + +Die Masse des Mars beträgt etwa ein Zehntel der Erdmasse. Die Fallbeschleunigung auf seiner Oberfläche beträgt 3,69 m/s², dies entspricht etwa 38 % der irdischen. Mit einer Dichte von 3,9 g/cm³ weist der Mars den geringsten Wert der terrestrischen Planeten auf. Deshalb ist die Schwerkraft auf ihm sogar geringfügig niedriger als auf dem kleineren, jedoch dichteren Merkur. + +Der Mars wird oft auch als der Rote Planet bezeichnet. Diese Färbung geht auf Eisenoxid-Staub (Rost) zurück, der sich auf der Oberfläche und in der dünnen CO2-Atmosphäre verteilt hat. Seine orange- bis blutrote Farbe und seine Helligkeitsschwankungen sind auch verantwortlich für seine Namensgebung nach dem römischen Kriegsgott Mars.[3] + +In größeren Fernrohren deutlich sichtbar sind die zwei Polkappen und mehrere dunkle Ebenen, die sich im Frühjahr etwas verfärben. Fotos von Raumsonden zeigen eine teilweise mit Kratern bedeckte Oberfläche und starke Spuren früherer Tektonik (tiefe Canyons und fünf über 20 km hohe Vulkane). Marsroboter haben schon mehrere Gebiete geologisch untersucht. + +Der Mars besitzt zwei kleine, unregelmäßig geformte Monde, die 1877 entdeckt wurden: Phobos und Deimos (griechisch für Furcht und Schrecken). + +Das astronomische Symbol des Mars ist . + +Umlauf und Rotation +Umlaufbahn + +Der Mars bewegt sich in einem Abstand von 206,62 bis 249,23 Millionen Kilometern (1,38 AE bis 1,67 AE) in knapp 687 Tagen (etwa 1,9 Jahre) auf einer elliptischen Umlaufbahn um die Sonne. Die Bahnebene ist 1,85° gegen die Erdbahnebene geneigt. + +Seine Bahngeschwindigkeit schwankt mit dem Sonnenabstand zwischen 26,50 km/s und 21,97 km/s und beträgt im Mittel 24,13 km/s. Die Bahnexzentrizität beträgt 0,0935. Nach der Umlaufbahn des Merkurs ist das die zweitgrößte Abweichung von der Kreisform unter allen Planetenbahnen des Sonnensystems. + +Jedoch hatte der Mars in der Vergangenheit eine weniger exzentrische Umlaufbahn. Vor 1,35 Millionen Jahren betrug die Exzentrizität nur etwa 0,002, weniger als die der Erde heute.[4] Die Periode der Exzentrizität des Mars beträgt etwa 96.000 Jahre, die der Erde etwa 100.000 Jahre.[5] Mars hat jedoch noch einen längeren Zyklus der Exzentrizität mit einer Periode von 2,2 Millionen Jahren, der den mit der Periode von 96.000 Jahren überlagert. In den letzten 35.000 Jahren wurde die Umlaufbahn aufgrund der gravitativen Kräfte der anderen Planeten geringfügig exzentrischer. Der minimale Abstand zwischen Erde und Mars wird in den nächsten 25.000 Jahren noch ein wenig geringer werden.[6] + +Es gibt vier bekannte Asteroiden, die sich mit dem Mars die gleiche Umlaufbahn teilen (Mars-Trojaner). Sie befinden sich auf den Lagrangepunkten L4 und L5, das heißt, sie eilen dem Planeten um 60° voraus oder folgen ihm um 60° nach. +Rotation + +Der Mars rotiert in 24 Stunden und 37,4 Minuten um die eigene Achse (Siderischer Tag). In Bezug auf die Sonne ergibt sich daraus ein Marstag (auch Sol genannt) von 24:39:35. Die Äquatorebene des Planeten ist um 25,19° gegen seine Bahnebene geneigt (Erde 23,44°), somit gibt es Jahreszeiten ähnlich wie auf der Erde. Sie dauern jedoch fast doppelt so lang, weil das siderisches Marsjahr 687 Erdtage hat. Da die Bahn des Mars aber eine deutlich größere Exzentrizität aufweist, als die der Erde, und Mars-Nord tendenziell in Richtung der großen Bahn-Ellipsenachse weist, sind die Jahreszeiten unterschiedlich lang. In den letzten 300.000 Jahren variierte die Rotationsachse zwischen 22° und 26°. Zuvor lag sie mehrmals auch über 40°, wodurch starke Klimaschwankungen auftraten, es Vereisungen auch in der Äquatorregion gab und so die starken Bodenerosionen zu erklären sind. + +Der Nordpol des Mars weist zum nördlichen Teil des Sternbilds Schwan, womit sich die Richtung um etwa 40° von jener der Erdachse unterscheidet. Der marsianische Polarstern ist Deneb (mit leichter Abweichung der Achse Richtung Alpha Cephei).[7] + +Die Rotationsachse führt eine Präzessionsbewegung aus, deren Periode 170.000 Jahre beträgt (7× langsamer als die Erde). Aus diesem Wert, der mit Hilfe der Pathfinder-Mission festgestellt wurde, können die Wissenschaftler auf die Massenkonzentration im Inneren des Planeten schließen.[8] +Atmosphäre und Klima +Ãœber dem Marshorizont ist die Atmosphäre als dunstiger Schleier erkennbar. Links ist der einem Smiley ähnelnde Krater Galle zu sehen. Viking, 1976 + +Der Mars besitzt eine sehr dünne Atmosphäre. Dadurch ist der Atmosphärendruck sehr niedrig, und Wasser kann nicht in flüssiger Form auf der Marsoberfläche existieren, ausgenommen kurzzeitig in den tiefstgelegenen Gebieten. + +Da die dünne Marsatmosphäre nur wenig Sonnenwärme speichern kann, sind die Temperaturunterschiede auf der Oberfläche sehr groß. Die Temperaturen erreichen in Äquatornähe etwa 20 °C am Tag und sinken bis auf 85 °C in der Nacht. Die mittlere Temperatur des Planeten liegt bei etwa 55 °C. +Atmosphäre + Hauptartikel: Atmosphäre des Mars + +Die Marsatmosphäre besteht zu 95,3 % aus Kohlenstoffdioxid. Dazu kommen noch 2,7 % Stickstoff, 1,6 % Argon, geringe Anteile an Sauerstoff (1300 ppm) und Kohlenstoffmonoxid (800 ppm) sowie Spuren von Wasserdampf (210 ppm) und anderen Verbindungen oder Elementen. + +Die Atmosphäre ist ziemlich staubig. Sie enthält Teilchen mit etwa 1,5 µm im Durchmesser, die den Himmel über dem Mars in einem blassen gelb- bis orange-braunen Farbton erscheinen lassen. + +Der atmosphärische Druck beträgt auf der Oberfläche des Mars im Schnitt nur 6,36 hPa (Hektopascal). Im Vergleich zu durchschnittlich 1013 hPa auf der Erde sind dies nur 0,63 %, was dem Luftdruck der Erdatmosphäre in 35 Kilometern Höhe entspricht. Die Atmosphäre wurde wahrscheinlich im Laufe der Zeit vom Sonnenwind abgetragen und in den Weltraum mitgerissen. Dies wurde durch die geringe Schwerkraft des Planeten und sein schwaches Magnetfeld begünstigt, das kaum Schutz vor den hochenergetischen Teilchen der Sonne bietet. +Klima und Wetter +Eiswolken über Mars, aufgenommen von Mars Pathfinder + +Abhängig von den Jahreszeiten und der Intensität der Sonneneinstrahlung finden in der Atmosphäre dynamische Vorgänge statt. Die vereisten Polkappen sublimieren im Sommer teilweise, und kondensierter Wasserdampf bildet ausgedehnte Zirruswolken. Die Polkappen selbst bestehen aus Kohlendioxideis und Wassereis. + +2008 entdeckte man mit Hilfe der Raumsonde Mars Express Wolken aus gefrorenem Kohlendioxid. Sie befinden sich in bis zu 80 Kilometern Höhe und haben eine horizontale Ausdehnung von bis zu 100 km. Sie absorbieren bis zu 40 % des einstrahlenden Sonnenlichts und können damit die Temperatur der Oberfläche um bis zu 10 °C verringern.[9] + +Mit Hilfe des Lasers LIDAR der Raumsonde Phoenix wurde 2009 entdeckt, dass in der zweiten Nachthälfte fünfzig Tage nach der Sonnenwende winzige Eiskristalle aus dünnen Zirruswolken auf den Marsboden fielen.[10] +Jahreszeiten +Staubsturm in der Syria-Region, fotografiert von Mars Global Surveyor im Mai 2003 + +Hätte Mars eine erdähnliche Umlaufbahn, würden die Jahreszeiten aufgrund der Achsenneigung ähnlich denen der Erde sein. Jedoch führt die vergleichsweise große Exzentrizität seines Orbits zu einer beträchtlichen Auswirkung auf die Jahreszeiten. Der Mars befindet sich während des Sommers in der Südhalbkugel und des Winters in der nördlichen Hemisphäre nahe dem Perihel seiner Bahn. Nahe dem Aphel ist in der südlichen Hemisphäre Winter und in der nördlichen Sommer. + +Das hat zur Folge, dass die Jahreszeiten in der südlichen Hemisphäre viel deutlicher ausgeprägt sind als in der nördlichen, wo das Klima ausgeglichener ist, als es sonst der Fall wäre. Die Sommertemperaturen im Süden können bis zu 30 °C höher sein als die vergleichbaren Temperaturen im Sommer des Nordens.[11] Die Jahreszeiten sind aufgrund der Exzentrizität der Umlaufbahn des Mars unterschiedlich lang. Auf der Nordhalbkugel dauert der Frühling 199,6, der Sommer 181,7, der Herbst 145,6 und der Winter 160,1 irdische Tage.[12] +Wind und Stürme + +Aufgrund der starken Tag-Nacht-Temperaturschwankungen der Oberfläche gibt es tägliche Morgen- und Abendwinde.[13] + +Während des Marsfrühjahrs können in den ausgedehnten flachen Ebenen heftige Staubstürme auftreten, die mitunter große Teile der Marsoberfläche verhüllen. Die Aufnahmen von Marssonden zeigen auch Windhosen, die über die Marsebenen ziehen und auf dem Boden dunkle Spuren hinterlassen. Stürme auf dem Mars haben wegen der sehr dünnen Atmosphäre eine wesentlich geringere Kraft als Stürme auf der Erde. Selbst bei hohen Windgeschwindigkeiten werden nur kleine Partikel (Staub) aufgeweht.[14] Allerdings verbleibt aufgewehter Staub auf dem Mars wesentlich länger in der Atmosphäre als auf der Erde, da es keine Niederschläge gibt, die die Luft reinigen, und zudem die Gravitation geringer ist. + +Staubstürme treten gewöhnlich während des Perihels auf, da der Planet zu diesem Zeitpunkt 40 Prozent mehr Sonnenlicht empfängt als während des Aphels. Während des Aphels bilden sich in der Atmosphäre Wolken aus Wassereis, die ihrerseits mit den Staubpartikeln interagieren und so die Temperatur auf dem Planeten beeinflussen.[15] Die Windgeschwindigkeiten in der oberen Atmosphäre können bis zu 650 km/h erreichen, auf dem Boden immerhin fast 400 km/h.[16] +Gewitter + +Bei heftigen Staubstürmen scheint es auch zu Gewittern zu kommen. Im Juni 2006 untersuchten Forscher mit einem Radioteleskop den Mars und stellten im Mikrowellenbereich Strahlungsausbrüche fest, wie sie bei Blitzen auftreten. In der Region, in der man die Strahlungsimpulse beobachtet hat, herrschte zu der Zeit ein heftiger Staubsturm mit hohen Staubwolken. Sowohl der beobachtete Staubsturm wie auch das Spektrum der Strahlungsimpulse deuten auf ein Staubgewitter mit Blitzen bzw. großen Entladungen hin.[17][18] +Oberfläche +Typisches Felsengestein auf der Marsoberfläche (aufgenommen von Mars Pathfinder) + +Die Oberfläche des Mars beträgt etwa ein Viertel der Erdoberfläche. Sie entspricht mit 144 Mio. km2 ungefähr der Gesamtoberfläche aller Kontinente der Erde (149 Mio. km2). + +Die rote Färbung seiner Oberfläche verdankt der Planet dem Eisenoxid-Staub, der sich auf der Oberfläche und in der Atmosphäre verteilt hat. Somit ist der Rote Planet ein rostiger Planet. + +Seine beiden Hemisphären sind sehr verschieden. Die Südhalbkugel stellt ein riesiges Hochland dar, das durchschnittlich 23 km über dem globalen Nullniveau liegt und ausgedehnte Schildvulkane aufweist. Die vielen Einschlagkrater belegen sein hohes Alter von fast 4 Milliarden Jahren. Dem steht die geologisch junge, fast kraterlose nördliche Tiefebene gegenüber. Sie liegt 35 km unter dem Nullniveau und hat ihre ursprüngliche Struktur durch noch ungeklärte geologische Prozesse verloren. Auslöser war möglicherweise eine gewaltige Kollision in der Frühzeit des Planeten. +Gesteine + Hauptartikel: Marsgestein + +An den Landestellen der Marssonden sind Gesteinsbrocken, sandige Böden und Dünen sichtbar. Die Marsgesteine weisen an der Oberfläche eine blasenartige Struktur auf und ähneln in ihrer Zusammensetzung irdischen Basalten, was bereits vor Jahrzehnten aus den auf der Erde (Antarktis) gefundenen Marsmeteoriten erschlossen wurde. Die roten Böden sind offensichtlich durch die Verwitterung von eisenhaltigen, vulkanischen Basalten entstanden. + +Die Pathfinder-Sonde fand 1997 außer verschiedensten Basalten auch quarzreichere Tiefengesteine ähnlich dem südamerikanischen Andesit, ferner das aus der Tiefe stammende Olivin und runde Kiesel aus Konglomeraten. Weitverbreitet ist metamorpher Regolith (ähnlich wie am Mond) und äolische Sedimente. Vereinzelt verwehter Sand aus schwefelhaltigen Staubteilchen. +Areografie + +Die kartografische Darstellung und Beschreibung der Marsoberfläche ist die Areografie, von Ares (, griechisch für Mars) und grafein (, griechisch für beschreiben). Die Geologie des Mars wird mitunter dementsprechend als Areologie bezeichnet. + +Zur Festlegung von Positionen auf der Marsoberfläche dienen areografische Koordinaten, die definiert sind wie geografische Breite und Länge. +Topografische Hemisphären +Topografische Karte des Mars. Die blauen Regionen befinden sich unterhalb des festgelegten Nullniveaus, die roten oberhalb + +Auffallend ist die Dichotomie, die Zweiteilung, des Mars. Die nördliche und die südliche Hemisphäre unterscheiden sich deutlich, wobei man von den Tiefebenen des Nordens und den Hochländern des Südens sprechen kann. Der mittlere Großkreis, der die topografischen Hemisphären voneinander trennt, ist rund 40° gegen den Äquator geneigt. Der Massenmittelpunkt des Mars ist gegenüber dem geometrischen Mittelpunkt um etwa drei Kilometer in Richtung der nördlichen Tiefebenen versetzt. + +Auf der nördlichen Halbkugel sind flache sand- und staubbedeckte Ebenen vorherrschend, die Namen wie Utopia Planitia oder Amazonis Planitia erhielten. Dunkle Oberflächenmerkmale, die in Teleskopen sichtbar sind, wurden einst für Meere gehalten und erhielten Namen wie Mare Erythraeum, Mare Sirenum oder Aurorae Sinus. Diese Namen werden heute nicht mehr verwendet. Die ausgedehnteste dunkle Struktur, die von der Erde aus gesehen werden kann, ist Syrtis Major, die große Syrte. + +Die südliche Halbkugel ist durchschnittlich sechs Kilometer höher als die nördliche und besteht aus geologisch älteren Formationen. Die Südhalbkugel ist zudem stärker verkratert, wie zum Beispiel in der Hochlandregion Arabia Terra. Unter den zahlreichen Einschlagkratern der Südhalbkugel befindet sich auch der größte Marskrater, Hellas Planitia, die Hellas-Tiefebene. Das Becken misst im Durchmesser bis zu 2100 km. In seinem Innern maß Mars Global Surveyor 8180 m unter Nullniveau unter dem Durchschnittsniveau des Mars den tiefsten Punkt auf dem Planeten. Der zweitgrößte Einschlagkrater des Mars, Chryse Planitia, liegt im Randbereich der nördlichen Tiefländer. +Ãœbersichtskarte des Mars mit den größten Regionen + +Die deutlichen Unterschiede der Topografie können durch innere Prozesse oder aber ein Impaktereignis verursacht worden sein. In letzterem Fall könnte in der Frühzeit der Marsentstehung ein größerer Himmelskörper, etwa ein Asteroid, auf der Nordhalbkugel eingeschlagen sein und die silikatische Kruste durchschlagen haben. Aus dem Innern könnte Lava ausgetreten sein und das Einschlagbecken ausgefüllt haben. + +Wie sich gezeigt hat, hat die Marskruste unter den nördlichen Tiefebenen eine Dicke von etwa 40 km, die im Gegensatz zum stufenartigen Ãœbergang an der Oberfläche nur langsam auf 70 km bis zum Südpol hin zunimmt. Dies könnte ein Indiz für innere Ursachen der Zweiteilung sein. +Oberflächenstrukturen +Gräben +In der Bildmitte liegt das System der Mariner-Täler. Ganz links die Tharsis-Vulkane (Bildmosaik von Viking 1 Orbiter, 1980) + +Südlich am Äquator und fast parallel zu ihm verlaufen die Valles Marineris (die Mariner-Täler), das größte bekannte Grabensystem des Sonnensystems. Es erstreckt sich über 4000 km und ist bis zu 700 km breit und bis zu 7 km tief. Es handelt sich um einen gewaltigen tektonischen Bruch. In seinem westlichen Teil, dem Noctis Labyrinthus, verästelt er sich zu einem chaotisch anmutenden Gewirr zahlreicher Schluchten und Täler, die bis zu 20 km breit und bis zu 5 km tief sind. + +Noctis Labyrinthus liegt auf der östlichen Flanke des Tharsis-Rückens, einer gewaltigen Wulst der Mars-Lithosphäre quer über dem Äquator mit einer Ausdehnung von etwa 4000 mal 3000 Kilometern und einer Höhe von bis zu rund 10 Kilometern über dem nördlichen Tiefland. Die Aufwölbung ist entlang einer offenbar zentralen Bruchlinie von drei sehr hohen, erloschenen Schildvulkanen besetzt: Ascraeus Mons, Pavonis Mons und Arsia Mons. Der Tharsis-Rücken und die Mariner-Täler dürften in ursächlichem Zusammenhang stehen. Wahrscheinlich drückten vulkanische Kräfte die Oberfläche des Planeten in dieser Region empor, wobei die Kruste im Bereich des Grabensystems aufgerissen wurde. Eine Vermutung besagt, dass diese vulkanische Tätigkeit durch ein Impaktereignis ausgelöst wurde, dessen Einschlagstelle das Hellas-Becken auf der gegenüberliegenden Seite des Mars sei. 2007 wurden im Nordosten von Arsia Mons sieben tiefere Schächte mit 100 bis 250 Metern Durchmesser entdeckt. +Vulkane +Olympus Mons, der mit 26 km höchste Berg im Sonnensystem +Die komplexe Caldera des Olympus Mons + +Dem Hellas-Becken exakt gegenüber befindet sich der Vulkanriese Alba Patera. Er ragt unmittelbar am Nordrand des Tharsis-Rückens rund 6 km über das umgebende Tiefland und ist mit einem Basisdurchmesser von über 1200 km der flächengrößte Vulkan im Sonnensystem. Patera ist die Bezeichnung für unregelmäßig begrenzte Vulkane mit flachem Relief. Alba Patera ist anscheinend einmal durch einen Kollaps in sich zusammengefallen. + +Unmittelbar westlich neben dem Tharsis-Rücken und südwestlich von Alba Patera ragt der höchste Vulkan, Olympus Mons, 26,4 km über die Umgebung des nördlichen Tieflands. Mit einer Gipfelhöhe von etwa 21,3 km über dem mittleren Null-Niveau ist er die höchste bekannte Erhebung im Sonnensystem. + +Ein weiteres, wenn auch weniger ausgedehntes vulkanisches Gebiet ist die Elysium-Region nördlich des Äquators mit den Schildvulkanen Elysium Mons, Hecates Tholus und Albor Tholus. +Stromtäler +Kasei Vallis, das größte Stromtal des Mars + +Auf der Marsoberfläche verlaufen Stromtäler, die mehrere hundert Kilometer lang und mehrere Kilometer breit sein können. Die heutigen Trockentäler beginnen ziemlich abrupt und haben keine Zuflüsse. Die meisten entspringen an den Enden der Mariner-Täler und laufen nördlich im Chryse-Becken zusammen. In den Tälern erheben sich mitunter stromlinienförmige Inseln. Sie weisen auf eine vergangene Flutperiode hin, bei der über einen geologisch relativ kurzen Zeitraum große Mengen Wasser geflossen sein müssen. Es könnte sich um Wassereis gehandelt haben, das sich unter der Marsoberfläche befand, danach durch vulkanische Prozesse geschmolzen wurde und dann abgeflossen ist. + +Darüber hinaus finden sich an Abhängen und Kraterrändern Spuren von Erosionen, die möglicherweise ebenfalls durch flüssiges Wasser verursacht wurden. + +2006 proklamierte die NASA einen einzigartigen Fund: Auf einigen NASA-Fotografien, die im Abstand von sieben Jahren vom Mars gemacht wurden, lassen sich Veränderungen auf der Marsoberfläche erkennen, die eine gewisse Ähnlichkeit mit Veränderungen durch fließendes Wasser haben. Innerhalb der NASA wird nun diskutiert, ob es neben Wassereis auch flüssiges Wasser geben könnte.[19] +Delta-Strukturen + +In alten Marslandschaften, z. B. im Eberswalde-Krater auf der Südhalbkugel oder in der äquatornahen Hochebene Xanthe Terra, finden sich typische Ablagerungen einstiger Flussdeltas. +Tharsis-Tholus-Streifen, aufgenommen mit der Hirise-Kamera des Mars Reconnaissance Orbiters. Der Streifen ist links in der Mitte zu sehen. Rechts sind die Ausläufer von Tharsis Tholus. + +Seit längerem vermutet man, dass die tief eingeschnittenen Täler in Xanthe Terra einst durch Flüsse geformt wurden. Wenn ein solcher Fluss in ein größeres Becken, beispielsweise einen Krater, mündete, lagerte er erodiertes Gesteinsmaterial als Sedimente ab. Die Art der Ablagerung hängt dabei von der Natur dieses Beckens ab: Ist es mit dem Wasser eines Sees gefüllt, so bildet sich ein Delta. Ist das Becken jedoch trocken, so verliert der Fluss an Geschwindigkeit und versickert langsam. Es bildet sich ein sogenannter Schwemmkegel, der sich deutlich vom Delta unterscheidet. + +Jüngste Analysen von Sedimentkörpern auf Basis von Orbiter-Fotos weisen an zahlreichen Stellen in Xanthe Terra auf Deltas hin Flüsse und Seen waren in der Marsfrühzeit also recht verbreitet.[20] +Dark Slope Streaks + +Dunkle Streifen an Hängen sind auf dem Mars häufig zu sehen. Sie treten an steilen Hängen von Kratern, Mulden und Tälern auf und werden mit zunehmendem Alter heller. Manchmal beginnen sie in einem kleinen punktförmigen Bereich und werden dann zunehmend breiter. Man beobachtete, dass sie sich um Hindernisse, wie Mulden, weiterbewegen. + +Es wird angenommen, dass die Farbe von dunklen darunterliegenden Schichten stammt, die durch Lawinen von hellem Staub freigelegt werden. Es wurden jedoch auch andere Hypothesen aufgestellt, wie Wasser oder sogar der Wuchs von Organismen. Das Interessanteste an diesen dunklen Streifen (engl. dark slope streaks) ist, dass sie sich auch heute noch bilden.[21] +Chaotische Gebiete + +Auf dem Mars gibt es zahlreiche Regionen mit einer Häufung von unterschiedlich großen Gesteinsbrocken und tafelbergähnlichen Erhebungen. Sie werden auch chaotische Gebiete genannt. Ariadnes Colles ist mit einer Fläche von etwa 29.000 km² so ein Gebiet. Es liegt im Terra Sirenum, einem südlichen Hochland des Mars. Dabei haben die Blöcke Ausmaße von einem bis zu zehn Kilometern Ausdehnung. Die größeren Blöcke ähneln Tafelbergen mit Erhebungen von bis zu 300 Metern. + +Es treten hierbei riefenartige Strukturen und Runzelrücken (engl. wrinkle ridges) auf. Die Ursachen dafür sind vulkanisch-tektonische Bewegungen.[22] +Gesteinsschichten und Ablagerungen +Salzlager + +Mit Hilfe der Sonde Mars Odyssey wies die NASA ein umfangreiches Salzlager in den Hochebenen der Südhalbkugel des Mars nach. Vermutlich entstanden diese Ablagerungen durch Oberflächenwasser vor etwa 3,5 bis 3,9 Milliarden Jahren.[23] +Carbonatvorkommen + +Mit Hilfe der Compact Reconnaissance Imaging Spectrometer for Mars (CRISM) an Bord der NASA-Sonde Mars Reconnaissance Orbiter konnten Wissenschaftler Carbonat-Verbindungen in Gesteinsschichten rund um das knapp 1500 Kilometer große Isidis-Einschlagbecken nachweisen. Demnach wäre das vor mehr als 3,6 Milliarden Jahren existierende Wasser hier nicht sauer, sondern eher alkalisch oder neutral gewesen. + +Carbonatgestein entsteht, wenn Wasser und Kohlendioxid mit Kalzium, Eisen oder Magnesium in vulkanischem Gestein reagiert. Bei diesem Vorgang wird Kohlendioxid aus der Atmosphäre in dem Gestein eingelagert. Dies könnte bedeuten, dass der Mars früher eine dichte kohlendioxidreiche Atmosphäre hatte, wodurch ein wärmeres Klima möglich wurde, in dem es auch flüssiges Wasser gab.[24] + +Mit Hilfe von Daten des MRO wurden 2010 Gesteine entdeckt, die durch kosmische Einschläge aus der Tiefe an die Oberfläche befördert worden waren. Anhand ihrer spezifischen spektroskopischen Fingerabdrücke konnte festgestellt werden, dass sie hydrothermal (unter Einwirkung von Wasser) verändert wurden. Neben diesen Karbonat-Mineralen wurden auch Silikate nachgewiesen, die vermutlich auf die gleiche Weise entstanden sind. Dieser neue Fund beweise, dass es sich dabei nicht um örtlich begrenzte Vorkommen handele, sondern dass Karbonate in einer sehr großen Region des frühen Mars entstanden seien.[25] +Hämatitkügelchen +Hämatitkügelchen auf dem Felsen Berry Bowl + +Die Marssonde Opportunity fand im Gebiet des Meridiani Planum millimetergroße Kügelchen des Eisenminerals Hämatit. Diese könnten sich vor Milliarden Jahren unter Einwirkung von Wasser abgelagert haben. Darüber hinaus wurden Minerale gefunden, die aus Schwefel-, Eisen- oder Bromverbindungen aufgebaut sind, wie zum Beispiel Jarosit. Auf der entgegengesetzten Hemisphäre[26] des Mars fand die Sonde Spirit in den Columbia Hills das Mineral Goethit, das ausschließlich unter dem Einfluss von Wasser gebildet werden kann. +Kieselsäure + +Forscher entdeckten 2010 mit Hilfe von MRO Ablagerungen auf einem Vulkankegel, die von Wasser verursacht wurden. Sie konnten das Mineral als Kieselsäurehydrat identifizieren, das nur in Verbindung mit Wasser entstanden sein kann. Die Wissenschaftler nehmen an, dass, falls es auf dem Mars Leben gegeben hat, es sich dort in der hydrothermalen Umgebung am längsten hätte halten können.[27] +Polkappen +Die Nordpolregion, aufgenommen von Mars Global Surveyor + Hauptartikel: Polkappen des Mars + +Der Mars besitzt zwei auffällige Polkappen, die zum größten Teil aus gefrorenem Kohlendioxid (Trockeneis) sowie einem geringen Anteil an Wassereis zusammengesetzt sind. Die nördliche Polkappe hat während des nördlichen Marssommers einen Durchmesser von rund 1000 Kilometern. Ihre Dicke wird auf 5 km geschätzt. Die südliche Polkappe ist mit 350 km Durchmesser und einer Dicke von 1½ km weniger ausgedehnt. Die Polarkappen zeigen spiralförmige Einschnitte, deren Entstehung bislang nicht geklärt ist. + +Wenn im Sommer die jeweiligen Polkappen teilweise abschmelzen, werden darunter geschichtete Ablagerungen sichtbar, die möglicherweise abwechselnd aus Staub und Eis zusammengesetzt sind. Im Marswinter nimmt der Durchmesser der dann jeweils der Sonne abgewandten Polkappe durch ausfrierendes Kohlendioxid wieder zu. + +Da ein größerer, stabilisierender Mond fehlt, taumelt der Mars mit einer Periode von etwa 5 Millionen Jahren. Die Polarregionen werden daher immer wieder so stark erwärmt, dass das Wassereis schmilzt. Durch das abfließende Wasser entstehen die Riemen und Streifen an den Polkappen. +Wasservorkommen + +Der Mars erscheint heute als trockener Wüstenplanet. Die bislang vorliegenden Ergebnisse der Marsmissionen lassen jedoch den Schluss zu, dass die Marsatmosphäre in der Vergangenheit (vor Milliarden Jahren) wesentlich dichter war und auf der Oberfläche des Planeten reichlich flüssiges Wasser vorhanden war. +Eisvorkommen an den Polen +Die Südpolregion, aufgenommen von Viking Orbiter + +Durch Radarmessungen mit der Sonde Mars Express wurden in der Südpolarregion, dem Planum Australe, Ablagerungsschichten mit eingelagertem Wassereis entdeckt, die weit größer und tiefreichender als die hauptsächlich aus Kohlendioxideis bestehende Südpolkappe sind. Die Wassereisschichten bedecken eine Fläche, die fast der Größe Europas entspricht, und reichen in eine Tiefe von bis zu 3,7 Kilometern. Das in ihnen gespeicherte Wasservolumen wird auf bis zu 1,6 Millionen Kubikkilometer geschätzt circa zwei Drittel des irdischen Grönlandeispanzers was laut der Europäischen Weltraumorganisation (ESA) ausreichen würde, die Marsoberfläche mit einer etwa 11 Meter dicken Wasserschicht zu bedecken.[28] +Weitere Eisvorkommen +Beobachtete Veränderungen könnten Anzeichen für fließendes Wasser innerhalb der letzten Jahre sein.[19] + +Die schon lange gehegte Vermutung, dass sich unter der Oberfläche des Mars Wassereis befinden könnte, erwies sich 2005 durch Entdeckungen der ESA-Sonde Mars Express als richtig. + +Geologen gehen von wiederkehrenden Vereisungsperioden auf dem Mars aus, ähnlich irdischen Eiszeiten. Dabei sollen Gletscher bis in subtropische Breiten vorgestoßen sein. Die Forscher schließen dies aus Orbiter-Fotos, die Spuren einstiger Gletscher in diesen äquatornahen Gebieten zeigen. Zusätzlich stützen auch Radarmessungen aus der Umlaufbahn die Existenz beträchtlicher Mengen an Bodeneis in ebendiesen Gebieten. Diese Bodeneisvorkommen werden als Reste solcher Mars-Eiszeiten gedeutet.[29] + +Auf der Europäischen Planetologenkonferenz EPSC im September 2008 in Münster wurden hochauflösende Bilder des Mars Reconnaissance Orbiters der NASA vorgestellt, die jüngste Einschlagkrater zeigen. Wegen der sehr dünnen Atmosphäre stürzen die Meteoriten praktisch ohne Verglühen auf die Marsoberfläche. Die fünf neuen Krater, die nur drei bis sechs Meter Durchmesser und eine Tiefe von 30 bis 60 cm aufweisen, wurden in mittleren nördlichen Breiten gefunden. Sie zeigen an ihrem Boden ein gleißend weißes Material. Wenige Monate später waren die weißen Flecken durch Sublimation verschwunden. Damit erhärten sich die Hinweise, dass auch weit außerhalb der Polgebiete Wassereis dicht unter der Marsoberfläche begraben ist.[30][31] +Flüssiges Wasser + +Unter der Kryosphäre des Mars werden große Mengen flüssigen Wassers vermutet. Nahe oder an der Oberfläche ist es für flüssiges Wasser zu kalt, und Eis würde langsam verdunsten, da der Partialdruck von Wasser in der Marsatmosphäre zu gering ist. + +Es gibt jedoch Hinweise, dass die Raumsonde Phoenix Wassertropfen auf der Oberfläche entdeckt habe. Dabei könnten Perchlorate als Frostschutz wirken. Diese Salze haben die Eigenschaft, Wasser anzuziehen. Dies kann auch Wasserdampf aus der Atmosphäre sein. Bei ausreichender Konzentration der Salze könnte Wasser sogar bis 70 °C flüssig bleiben. Durch eine Durchmischung mit Perchloraten könnte Wasser auch unter der Oberfläche in flüssigem Zustand vorhanden sein.[32] 2010 fanden Forscher der Uni Münster Belege dafür, dass zumindest im Frühjahr und in Kratern wie dem Russell-Krater flüssiges Wasser auf der Marsoberfläche existiert. Auf Fotos, die vom Mars Reconnaissance Orbiter aufgenommen wurden, entdeckten sie an steilen Hängen Erosionsrinnen, die sich zwischen November 2006 und Mai 2009 verlängert hatten. Dass die Rinnen nach unten dünner werden, deuten die Forscher als Versickern,[33] andere als Verdunsten.[34] + +Eine alternative Erklärung für die Erosionsrinnen schlugen Wissenschaftler der NASA 2010 vor: Kohlendioxid, das sich im marsianischen Winter bei unter -100 °C aus der Atmosphäre an den Berghängen als Trockeneis ansammelt, bei Erwärmung des Planeten als sublimiertes Gas die Hänge hinab"fließt" und dabei Staub erodiert.[35][36] + +Mit dem abbildenden Spektrometer (CRISM) des Mars Reconnaissance Orbiters konnten Spektren von aktiven (jahreszeitlich dunkleren) Rinnen gewonnen werden, deren Auswertung, 2015 veröffentlicht,[37] Magnesiumperchlorat, Magnesiumchlorat und Natriumperchlorat ergaben. +Siehe auch: Extraterrestrischer Ozean +Innerer Aufbau +Illustration des vermuteten Marsaufbaus + +Ãœber den inneren Aufbau des Mars ist nur wenig bekannt, da bislang nur begrenzt seismische Messungen vorgenommen werden konnten. + +Sein Inneres gliedert sich ähnlich dem Schalenaufbau der Erde in eine Kruste, einen Gesteinsmantel und einen Kern, der überwiegend aus Eisen und zu etwa 14 bis 17 Prozent aus Schwefel besteht. Der Kern beinhaltet etwa doppelt so viele leichte Elemente wie der Erdkern. Deshalb ist die Dichte des Kerns niedriger, als es bei einem reinen Eisenkern der Fall wäre.[38] + +Laut neueren experimentellen Simulationen der Bedingungen in der Ãœbergangszone zwischen Mantel und Kern (Messungen des Mars Global Surveyor ergaben eine Temperatur von 1500 Grad Celsius und einen Druck von 23 Gigapascal) hat der Kern des Mars im Unterschied zu dem der Erde keinen inneren festen Bereich, sondern ist vollständig flüssig.[39] Dies belegt auch die Analyse der Bahndaten des Mars Global Surveyor. Dabei konnte nachgewiesen werden, dass der Mars einen flüssigen Kern mit einem Radius zwischen 1520 und 1840 km besitzt und damit eine höhere Temperatur hat, als zuvor angenommen wurde. + +Der Kern ist von einem Mantel aus Silicaten umgeben, der viele der tektonischen und vulkanischen Merkmale des Planeten formte, nun aber inaktiv zu sein scheint. Die durchschnittliche Dicke der Planetenkruste beträgt etwa 50 km, mit einem Maximum von 125 km.[38] Im Vergleich dazu ist die Erdkruste mit einer Dicke von durchschnittlich 40 km nur etwa ein Drittel so dick, wenn man die relative Größe der beiden Planeten berücksichtigt. +Magnetfeld +Magnetisierung des Mars Rot und Blau kennzeichnen entgegengesetzte Richtungen des Magnetfelds, ein Drittel der Südhalbkugel + +Anders als die Erde und der Merkur besitzt der Mars kein globales Magnetfeld mehr, seit er es ca. 500 Millionen Jahre nach seiner Entstehung verlor. Vermutlich erlosch es, als der Zerfall radioaktiver Elemente nicht mehr genügend Wärmeenergie produzierte, um im flüssigen Kern Konvektionsströmungen anzutreiben. Weil der Mars keinen festen inneren Kern besitzt, konnte er den Dynamo-Effekt nicht auf die gleiche Art aufbauen wie die Erde. + +Dennoch ergaben Messungen einzelne und sehr schwache lokale Magnetfelder. Die Messung des Magnetfeldes wird erschwert durch die Magnetisierung der Kruste mit Feldstärken von bis zu 220 Nanotesla und durch externe Magnetfelder mit Stärken zwischen wenigen Nanotesla und bis zu 100 Nanotesla, die durch die Wechselwirkung des Sonnenwindes mit der Marsatmosphäre entstehen und zeitlich sehr stark variieren. Nach den Analysen der Daten des Mars Global Surveyor konnte die Stärke des Magnetfeldes trotzdem sehr genau bestimmt werden sie liegt bei weniger als 0,5 Nanotesla gegenüber 30 bis 60 Mikrotesla des Erdmagnetfeldes. + +Messungen von Magnetfeldlinien durch Mars Global Surveyor ergaben, dass Teile der planetaren Kruste durch das einstige Magnetfeld stark magnetisiert sind, aber mit unterschiedlicher Orientierung, wobei gleichgerichtete Bänder von etwa 1000 km Länge und 150 km Breite auftreten. Ihre Größe und Verteilung erinnert an die streifenförmigen Magnetanomalien auf den Ozeanböden der Erde. Durch sie wurde die Theorie der Plattentektonik gestützt, weshalb 1991 auch eine ähnliche Theorie für den Mars entwickelt wurde. Magnetische Beobachtungen auf dem Mars sind jedoch noch nicht detailliert genug, um sichere Schlussfolgerungen zu erlauben oder gar die Theorie zu bestätigen. + +Möglicherweise werden bei der mit der Zeit zwangsläufigen Abkühlung des Marskerns durch die damit einsetzende Auskristallisation des Eisens und die freigesetzte Kristallisationswärme wieder Konvektionen einsetzen, die ausreichen, dass der Planet in ein paar Milliarden Jahren wieder über ein globales Magnetfeld in alter Stärke verfügt.[39] +Monde +Die Umlaufbahnen von Phobos und Deimos +Phobos (oben) und Deimos (unten) im Größenvergleich + +Zwei kleine Monde, Phobos und Deimos (griech. Furcht und Schrecken), umkreisen den Mars. Sie wurden 1877 von dem US-amerikanischen Astronomen Asaph Hall entdeckt und nach den in der Ilias überlieferten beiden Begleitern, die den Wagen des Kriegsgottes Ares (lat. Mars) ziehen, benannt. + +Phobos (Durchmesser 26,8 × 22,4 × 18,4 km) und Deimos (Durchmesser 15,0 × 12,2 × 10,4 km) sind zwei unregelmäßig geformte Felsbrocken. Möglicherweise handelt es sich um Asteroiden, die vom Mars eingefangen wurden. Phobos große Halbachse beträgt 9.376 km, diejenige von Deimos 23.459 km. Phobos ist damit kaum mehr als 6.000 km von der Oberfläche des Mars entfernt, der Abstand ist geringer als der Durchmesser des Planeten. + +Die periodischen Umlaufbewegungen der beiden Monde befinden sich mit der Größe von 0,31891 (Phobos) und 1,262 Tagen (Deimos) zueinander in einer 1:4-Bahnresonanz. + +Die Umlaufzeit von Phobos ist kürzer als die Rotationszeit von Mars. Der Mond kommt dem Planeten durch die Gezeitenwechselwirkung auf einer Spiralbahn langsam immer näher und wird schließlich auf diesen stürzen oder durch die Gezeitenkräfte auseinandergerissen werden, so dass er für kurze Zeit zu einem Marsring wird. Für ihn berechneten DLR-Forscher, basierend auf neueren Daten der europäischen Raumsonde Mars Express, dass dies in ca. 50 Millionen Jahren geschehen wird. Deimos wird dagegen in einer noch ferneren Zukunft dem Mars entfliehen. Er driftet durch die Gezeitenwechselwirkung langsam nach außen, wie alle Monde, die langsamer (und nicht retrograd) um einen Planeten kreisen, als dieser rotiert. + +Ihre Existenz war schon lange vorher mehrmals literarisch beschrieben worden, zuletzt von Voltaire, der in seiner 1750 erschienenen Geschichte Micromégas über zwei Marsmonde schreibt. Es ist wahrscheinlich, dass Voltaire diese Idee von Jonathan Swift übernahm, dessen Buch Gullivers Reisen 1726 erschienen war. Darin wird im dritten Teil beschrieben, die Astronomen des Landes Laputa hätten ebenfalls zwei kleinere Sterne oder Satelliten entdeckt, die um den Mars kreisen, wovon der innere vom Zentrum des Hauptplaneten genau drei seiner Durchmesser entfernt ist und der äußere fünf. Es wird vermutet, dass Swift von einer Fehlinterpretation Johannes Keplers gehört hatte. Der hatte das Anagramm, das Galileo Galilei 1609 an ihn schickte, um ihm die Entdeckung der Phasen der Venus mitzuteilen, als die Entdeckung zweier Marsmonde aufgefasst. +Entstehungsgeschichte +Datei:Mars.ogvMediendatei abspielen +Animation, welche die Topographie des Mars zeigt. Olympus Mons Mariner-Täler Mars Südpol Hellas-Becken Mars Nordpol + +Anhand der astrogeologischen Formationenvielfalt und der Verteilung von Einschlagskratern kann ein Großteil der Geschichte des Planeten abgeleitet werden. Der Mars entstand, wie die übrigen Planeten des Sonnensystems, vor etwa 4,5 Milliarden Jahren durch Zusammenballung kleinerer Körper, sogenannter Planetesimale, innerhalb der protoplanetaren Scheibe zu einem Protoplaneten. Vor 4 Milliarden Jahren bildete der im Innern noch glutflüssige planetare Körper eine feste Gesteinskruste aus, die einem heftigen Bombardement von Asteroiden und Kometen ausgesetzt war. +Noachische Periode + +Die ältesten der heute noch vorhandenen Formationen, wie das Hellas-Becken, und die verkraterten Hochländer, wie Noachis Terra, wurden vor 3,8 bis 3,5 Milliarden Jahren, in der so genannten Noachischen Periode, gebildet. In dieser Periode setzte die Zweiteilung der Marsoberfläche ein, wobei die nördlichen Tiefländer gebildet wurden. Durch starke vulkanische Eruptionen wurden weite Teile des Planeten von Ablagerungen aus vulkanischer Lava und Asche bedeckt. Diese wurden an vielen Stellen durch Wind und Wasser wieder abgetragen und ließen ein Netzwerk von Tälern zurück. +Hesperianische Periode + +Das geologische Mittelalter des Mars wird als Hesperianische Periode bezeichnet. Sie umfasst den Zeitraum von vor 3,5 bis 1,8 Milliarden Jahren. In dieser Periode ergossen sich riesige Lavamengen aus ausgedehnten Spalten in der Marskruste und bildeten weite Ebenen, wie Hesperia Planum. Es entstanden auch die ältesten Vulkane der Tharsis- und der Elysium-Region, wobei die Gesteinskruste stark verformt wurde und sich das Grabensystem der Mariner-Täler öffnete. Es bildeten sich die gewaltigen Stromtäler, in denen große Wassermengen flossen und sich stellenweise aufstauten. + +Es entwickelte sich auf dem Mars ein Wasserkreislauf. Im Unterschied zur Erde gab es jedoch keinen Wetterzyklus mit Verdunstung, Wolkenbildung und anschließendem Niederschlag. Das Wasser versickerte im Untergrund und wurde später durch hydrothermale Prozesse wieder an die Oberfläche getrieben. Da jedoch der Planet immer weiter abkühlte, endete dieser Prozess vor etwa 1,5 Milliarden Jahren, und es hielten sich nur noch Gletscher an der Oberfläche. Zeichen dieser Aktivität sind vor kurzem entdeckte Moränen am Olympus Mons.[40] +Amazonische Periode + +Das jüngste geologische Zeitalter des Mars wird als Amazonische Periode bezeichnet und begann vor 1,8 Milliarden Jahren. In dieser Phase entstanden die jüngeren Vulkane der Tharsis- und der Elysium-Region, aus denen große Lavamassen flossen. So bildeten sich weite Ebenen aus wie zum Beispiel Amazonis Planitia. + +2008 fanden Forscher Hinweise auf Geysire auf dem Mars, die vor einigen Millionen Jahren aktiv gewesen sein dürften. Dabei hätten sie Fontänen von kohlensäurehaltigem Wasser einige Kilometer weit in die Höhe geschossen. Darauf deuten auch die Formen von Ablagerungen hin, die britische Forscher in der Nähe zweier ausgedehnter Grabensysteme entdeckten. Wahrscheinlich wurden diese Eruptionen durch Blasen aus Kohlendioxid ausgelöst. Dadurch wurde das Wasser aus einer Tiefe von bis zu vier Kilometern durch Spalten im Marsboden an die Oberfläche gedrückt. Die Fontänen müssen dabei mit einem so großen Druck herausgepresst worden sein, dass das schlammige Wasser erst in einer Entfernung von mehreren Kilometern von der Austrittsstelle wieder auf den Boden regnete oder, bedingt durch die tiefen Temperaturen, als Hagel niederging.[41] + +Gegenwärtig wird die Oberfläche des Mars hauptsächlich durch Winderosion und Hangrutschung geformt. +Erforschung + +Aufgrund seiner großen Helligkeit war der Mars schon im frühen Altertum als Planet bekannt. Wegen seiner langen Planetenschleifen (die alle 2 Jahre in der Opposition auftreten) galten seine Bewegungen den Ägyptern als unvorhersehbar. Den Babyloniern gelang es zwar, sie näherungsweise vorauszusagen, sie schrieben die Bahnanomalien aber den Launen und der Gewalttätigkeit des Gottes Nergal zu. +Vor dem Raumfahrtzeitalter +Marsoberfläche nach Schiaparelli (1888) +Mars auf einer astronomischen Zeichnung des 19. Jahrhunderts (Trouvelot, 1881) + + Tycho Brahe (15461601) vermaß die Planetenpositionen des Mars mit bis dahin nicht gekannter Genauigkeit und ermöglichte es so Johannes Kepler (15711630), die elliptische Bahn des Planeten zu berechnen und die drei Keplerschen Gesetze abzuleiten. + Christiaan Huygens entdeckte 1659 eine dunkle, dreieckige Zone (Syrtis Major) auf der Marsoberfläche. Aus deren Positionsveränderungen errechnete er die Eigenrotation des Mars zu 24,5 Stunden (heutiger Wert: 24,623 Stunden). + Giovanni Domenico Cassini beschrieb 1666 die weißen Polkappen des Mars. + Wilhelm Herschel bestimmte 1784 die Neigung der Rotationsachse gegenüber der Umlaufbahn mit 25° (heutiger Wert 25,19°). + Wilhelm Beer fertigte 1830 die erste Marskarte an, Angelo Secchi 1863 schon in Farbe. + Richard Proctor veröffentlichte 1869 eine detaillierte Marskarte, die er aus Zeichnungen von William Rutter Dawes erstellte. + Giovanni Schiaparelli nahm 1877 auf der Marsoberfläche zarte Linienstrukturen wahr, die er Canali (italienisch für Rinnen oder Gräben) nannte und in eine detaillierte Karte eintrug. Er machte zunächst keine Angaben über den Ursprung der Canali (die er für breiter als 100 km schätzte), doch wurden sie in englischen Medien fälschlich als Channel (Kanäle) übersetzt und bald als Werk intelligenter Marsbewohner interpretiert. Auf älteren Marskarten erhielten viele dieser Linien auch Namen. Während einige Astronomen Schiaparellis Beobachtungen bestätigten, wurde die Existenz der Canali von anderen angezweifelt und als Ergebnis optischer Täuschungen bezeichnet. Erst der Vorbeiflug der amerikanischen Mariner-Sonden beendete die Spekulationen, denn Fotos der Marsoberfläche zeigten keine so breiten Rinnen. Drei Canali entsprechen aber den riesigen Canyons Valles Marineris, andere zeichnen Geländestufen und Schattenlinien nach, einige auch längere Kraterketten. + + Hauptartikel: Marskanäle + + Asaph Hall entdeckt bei der günstigen Opposition 1877 die beiden Marsmonde Phobos und Deimos. + Percival Lowell gründet 1894 das Lowell-Observatorium in Arizona, um die Marskanäle, ihre jahreszeitlichen Verfärbungen und allfällige Lebensspuren zu erforschen. Spektroskopisch findet man biologische Moleküle, die sich allerdings später als terrestrisch erweisen. In der Atmosphäre werden Spektrallinien von Sauerstoff entdeckt, dessen Volumsanteil aber überschätzt wird. + Eugène Antoniadi bestätigte zunächst die Marskanäle, kam aber 1909 am Riesenteleskop Meudon zum Schluss, sie würden nur in kleineren Fernrohren als solche erscheinen. In seinen detaillierten Marskarten die bis zu den ersten Marssonden kaum mehr übertroffen wurden zeichnete er sie als Folge diffuser Flecken ein. + Gerard Kuiper wies in den 1950ern Kohlendioxid in der Marsatmosphäre nach und glaubte bis zu den ersten Marssonden an die mögliche Existenz von Moosen oder Flechten. + +Im Raumfahrtzeitalter +Die erste Nahaufnahme vom Mars, aufgenommen von Mariner 4 + +Viele unbemannte Raumsonden wurden schon zum Mars entsandt, von denen einige sehr erfolgreich waren. Etwa die Hälfte der Missionen endete in einem Misserfolg, die meisten davon waren sowjetische Sonden. Im Unterschied zur Erkundung des Erdmondes gibt es bis heute keine Gesteinsproben, die vom Mars geholt wurden, so dass Marsmeteoriten die einzige Möglichkeit sind, Material vom Mars in irdischen Laboratorien zu erforschen. Bislang hat es auch noch keine bemannte Marsmission gegeben ein aktueller Ansatz dafür ist Mars One. + Hauptartikel: Chronologie der Marsmissionen +1960er Jahre +Darstellung auf einer ungarischen Sondermarke von 1964 + +Die beiden sowjetischen Sonden Marsnik 1 und 2 wurden im Oktober 1960 gestartet, um am Mars vorbeizufliegen, erreichten aber noch nicht einmal die Erdumlaufbahn. 1962 versagten drei weitere sowjetische Sonden (Sputnik 22, Mars 1 und Sputnik 24), zwei von ihnen blieben im Erdorbit, die dritte verlor auf dem Weg zum Mars den Kontakt mit der Erde. Auch ein weiterer Versuch im Jahre 1964 schlug fehl. + +Zwischen 1962 und 1973 wurden zehn Mariner-Raumsonden vom Jet Propulsion Laboratory der NASA entwickelt und gebaut, um das innere Sonnensystem zu erforschen. Es waren relativ kleine Sonden, die meistens nicht einmal eine halbe Tonne wogen. + +Mariner 3 und Mariner 4 waren identische Raumsonden, die am Mars vorbeifliegen sollten. Mariner 3 wurde am 5. November 1964 gestartet, aber die Transportverkleidung löste sich nicht richtig, und die Sonde erreichte den Mars nicht. + +Drei Wochen später, am 28. November 1964, wurde Mariner 4 erfolgreich auf eine achtmonatige Reise zum Roten Planeten geschickt. Am 15. Juli 1965 flog die Sonde am Mars vorbei und lieferte die ersten Nahaufnahmen insgesamt 22 Fotos des Planeten. Die Bilder zeigten mondähnliche Krater, von denen einige mit Reif bedeckt zu sein scheinen. + +1969 folgten Mariner 6 und Mariner 7 und lieferten insgesamt 200 Fotos. +1970er Jahre + +1971 missglückte der Start von Mariner 8, dafür erhielt die NASA im gleichen Jahr von Mariner 9 mehrere tausend Bilder. + +Ebenfalls 1971 landete mit der sowjetischen Mars 3 die erste Sonde weich auf dem Mars, nachdem Mars 2 wenige Tage zuvor gescheitert war. Der Funkkontakt brach jedoch 20 Sekunden nach der Landung ab. Mögliche Ursache war ein gerade tobender globaler Staubsturm, der den Lander umgeworfen haben könnte. +Bild von Viking 1. Der große Felsen links von der Mitte ist etwa zwei Meter breit. Er wurde Big Joe getauft. + +In den 1970er-Jahren landeten die Viking-Sonden erfolgreich auf dem Mars und lieferten die ersten Farbbilder sowie Daten von Bodenproben: Viking 1 schaffte am 20. Juli 1976 als erste US-amerikanische Sonde eine weiche Landung. Die Sowjetunion versuchte noch weitere Landungen auf dem Mars, scheiterte jedoch. +1980er Jahre + +Die einzigen Raumsonden, die in den 1980er Jahren zum Mars flogen, waren die beiden sowjetischen Fobos-Sonden. Sie wurden 1988 von Baikonur aus gestartet und sollten den Mars und seinen Mond Phobos untersuchen. Dafür waren sie im Rahmen einer internationalen Kooperation neben sowjetischen auch mit zahlreichen westlichen Instrumenten bestückt. Der Kontakt zu Fobos 1 brach jedoch schon auf dem Weg zum Mars wegen eines falschen Steuerbefehls ab. Fobos 2 erreichte eine Marsumlaufbahn und einige Daten und Bilder vom Mars wurden zur Erde übertragen. Danach wurde die Sonde zu Phobos gelenkt. Jedoch brach kurz vor dem Rendezvous auch der Kontakt zu Fobos 2 ab. +1990er Jahre + +1992 wurde die US-Sonde Mars Observer gestartet. Sie ging 1993 kurz vor dem Einschwenken in die Umlaufbahn verloren. + +Am 16. November 1996 startete Mars 96, die erste russische Raumsonde seit dem Zusammenbruch der Sowjetunion. Doch versagte die Proton-Trägerrakete, so dass Mars 96 wieder in die Erdatmosphäre eintrat und verglühte. +Der Marsrover Sojourner + +Besonderes Aufsehen erregte 1997 der Mars Pathfinder, bei dem zum ersten Mal ein kleines Marsmobil, der Rover Sojourner, eingesetzt wurde. Er landete publikumswirksam am 4. Juli, dem amerikanischen Unabhängigkeitstag, und lieferte viele Aufnahmen von der Umgebung der Landestelle, die von der NASA zum ersten Mal sofort im Internet veröffentlicht wurden. + +Eine weitere erfolgreiche Mission war 1997 die des Mars Global Surveyor, bei der die Marsoberfläche in einer hohen Auflösung kartografiert wurde. Am 2. November 2006 fünf Tage vor dem 10-jährigen Jubiläum seines Starts brach der Kontakt mit dem Satelliten ab. + +Das Scheitern der Marssonden Mars Climate Orbiter, der wegen eines Programmierfehlers in der Navigation verlorenging, und Mars Polar Lander, der wahrscheinlich wegen eines fehlerhaften Sensors bei der Landung aus größerer Höhe abstürzte, stellte 1999 einen herben Rückschlag für die Marsforschung dar. + +Auch die 1998 gestartete japanische Raumsonde Nozomi konnte den Mars nicht erreichen. +2000er Jahre + +Seit dem 24. Oktober 2001 umkreist außer dem Global Surveyor noch 2001 Mars Odyssey den roten Planeten, der spezielle Instrumente zur Fernerkundung von Wasservorkommen an Bord hat. + +Von den bis 2002 insgesamt 33 Missionen zum Mars waren nur acht erfolgreich, allesamt US-amerikanisch. + +Am 2. Juni 2003 startete im Rahmen der ersten europäischen Marsmission die ESA-Raumsonde Mars Express mit dem Landegerät Beagle 2 erfolgreich zum Mars. Zwar landete Beagle 2 am 25. Dezember 2003 auf der Marsoberfläche, allerdings konnte der Funkkontakt niemals aufgebaut werden. 2014 wurde er auf Bildern des MRO entdeckt. Der Orbiter Mars Express arbeitet jedoch erfolgreich in der Marsumlaufbahn und konnte unter anderem viele Aufnahmen von Formationen machen, von denen man annimmt, dass sie ausgetrocknete oder ausgefrorene Flusstäler seien. Er kartiert den Planeten u. a. mittels Radar und einer Stereokamera im sichtbaren Licht, sowie spektroskopisch auch in Infrarot. Am 30. November 2005 fand die Sonde unter der Ebene Chryse Planitia ein Eisfeld mit 250 km Durchmesser. +Marsrover Opportunity (MER-B) + +Am 10. Juni 2003 wurde die US-amerikanische Marssonde Spirit (MER-A) zum Mars gestartet. An Bord befand sich ein Rover, der nach der Landung drei Monate lang Gesteinsproben entnehmen und nach Spuren von früher vorhandenem Wasser suchen sollte. Die Landung erfolgte am 4. Januar 2004 im Krater Gusev, in den das Ma'adim Vallis mündet. Im April 2009 fuhr sich der Rover in einer Sandanhäufung fest und konnte seit dem 22. März 2010 auch nicht mehr kontaktiert werden (Stand: März 2011). + +Am 8. Juli 2003 wurde die baugleiche Sonde Opportunity (MER-B) mit einer Delta-II-Rakete gestartet. Sie landete am 25. Januar 2004 in der Tiefebene Meridiani Planum nahe dem Marsäquator, fast genau gegenüber von Spirit.[26] Die vom Rover gesammelten Beweise, dass der Mars einst warm und feucht war, wurden im Jahresrückblick der Fachzeitschrift Science mit der Wahl zum Durchbruch des Jahres 2004 gewürdigt. Opportunity ist noch immer aktiv (Stand: April 2015). +vergrößern und Informationen zum Bild anzeigen +Das Panoramabild, von dem hier nur ein Ausschnitt zu sehen ist, wurde aus hunderten Einzelbildern montiert, die Opportunity vom 6. Oktober bis 6. November 2006 aufgenommen hat. Es zeigt annähernd in Echtfarben den Victoria-Krater vom Cap Verde + +Am 12. August 2005 wurde die US-Sonde Mars Reconnaissance Orbiter mit einer Atlas-V-Rakete auf die Reise geschickt und erreichte am 10. März 2006 den Mars. Sie soll ihn mit hochauflösenden Kameras kartografieren und auch nach geeigneten Landestellen für spätere Rover-Missionen suchen. Außerdem soll sie zur Hochgeschwindigkeits-Kommunikation zwischen zukünftigen Raumsonden auf der Marsoberfläche und der Erde dienen. +Sonnenuntergang auf dem Mars beim Krater Gusev (Spirit am 19. Mai 2005) + +2007 fotografierte Mars Reconnaissance sieben fast kreisrunde schwarze und strukturlosen Flecken, die im Nordosten des Marsvulkans Arsia Mons liegen.[42] Der größte, genannt Jeanne, hat einen Durchmesser von etwa 150 Meter. Eine Schrägaufnahme der sonnenbeschienenen Seitenwand im August 2007 zeigte, dass es sich um einen mindestens 78 Meter tiefen senkrechten Schacht handeln muss. Diese Strukturen sind sehr wahrscheinlich vulkanischer Natur und durch den Einbruch einer nicht mehr tragfähigen Deckschicht entstanden.[43] + +Am 26. Dezember 2007 machte die High Resolution Stereo Camera des Mars Express Aufnahmen von Eumenides Dorsum, einem Bergrücken westlich der Tharsis-Region. Die Aufnahmen zeigen kilometerlange lineare Strukturen, die von Kanälen unterbrochen sind. Es handelt sich um durch Winderosion entstandene Yardangs (Windhöcker bzw. Sandwälle). + +Mit der Sonde Mars Odyssey wies die NASA im März 2008 eine umfangreiche Salzlagerstätte in den Hochebenen der Südhalbkugel nach. Die Wissenschaftler des JPL in Pasadena meinen, sie habe sich vor 3,5 bis 3,9 Milliarden Jahren gebildet. Vermutlich entstanden die Salze durch mineralienreiches Grundwasser, das an die Oberfläche gelangte und dort verdunstete. Die Bilder von Mars Odyssey zeigen kanalähnliche Strukturen, die in den Salzbecken enden.[23] Insgesamt wurden über 200 Gebiete mit Salzvorkommen ausgemacht, die zwischen 1 und 25 km² groß sind. Die Entdeckung deutet darauf hin, dass der Mars vor langer Zeit ein wärmeres und deutlich feuchteres Klima hatte.[44]. Solche Klimaschwankungen dürften durch aperiodische Änderungen der Rotationsachse entstehen, deren Neigung (derzeit 25°) zwischen 14 und 50° variiert.[45] +Die Orte der sieben erfolgreichen Marslandungen + +Am 26. Mai 2008 landete die Sonde Phoenix im nördlichen Polargebiet des Planeten. Sie suchte dort bis November 2008 im Boden nach Wassereis und habitablen Zonen, also für primitive Organismen bewohnbare Umgebungen. Ihr Roboterarm konnte Proben aus etwa 50 cm Tiefe holen, um sie dann in einem Minilabor zu analysieren. Phoenix entdeckte bei einer Grabung weiße Klümpchen, die nach einigen Tagen verschwanden. Man vermutete, dass es sich dabei um Wassereis handelt,[46] was am 31. Juli bestätigt wurde beim Erhitzen einer Gesteinsprobe trat Wasserdampf aus.[47] Mit dem nasschemischen Labor MECA, das die wasserlöslichen Ionen im Marsboden bestimmte, konnten erhebliche Mengen an Perchloraten detektiert werden. Auf der Erde kommen Perchlorate in den ariden Wüstengebieten vor. Natriumperchlorat wird durch Oxidation von Natriumchlorid in der Atmosphäre erzeugt und dann mit dem Staub abgelagert. + + +2010er Jahre +Curiosity auf dem Mars + +Am 26. November 2011 um 15:02 UTC startete die Rover-Mission Mars Science Laboratory (Curiosity) der NASA mit einer Atlas V(541) von Cape Canaveral und landete am 6. August 2012 auf dem Mars. Der Rover kann weite Strecken zurücklegen und umfassende Untersuchungen eines großen Umkreises durchführen. Wichtigstes Projektziel sind geologische Analysen des Marsbodens. + +Am 18. November 2013 startete eine weitere NASA-Sonde zum Mars. Die Mission mit dem Projektnamen Mars Atmosphere and Volatile Evolution (MAVEN) soll das Rätsel der verlorenen Atmosphäre aufklären.[48] Der Orbiter umkreist den Planeten seit dem 22. September 2014 und soll sich in fünf Tiefflügen annähern. Weiters wurde am 5. November 2013 eine indische Marsmission gestartet. Sie soll ebenfalls die Atmosphäre sowie verschiedene Oberflächenphänomene untersuchen.[49] + +ExoMars Rover ist ein europäischer Rover, dessen Start für 2020 geplant ist. Er soll speziell nach Spuren von Leben suchen. Die Finanzierung dieser Mission ist allerdings noch ungewiss. +Geplante Missionen + +Weitere Pläne der NASA und ESA zur Marserforschung enthalten unter anderem das Aussetzen von kleineren Flugzeugen in der Atmosphäre und nach 2020 die Rückführung von Marsproben zur Erde (Mission Mars Sample Return). +vergrößern und Informationen zum Bild anzeigen +Panoramabild der Marsoberfläche, aufgenommen von der Sonde Pathfinder + +Im Januar 2004 kündigte der US-amerikanische Präsident George W. Bush Anstrengungen der USA für eine bemannte Marsmission an. Im Rahmen des Raumfahrtprogramms Constellation plante die NASA diese Flüge für die Zeit nach 2020. Der ehemalige NASA-Direktor Michael Griffin nannte die Zeit bis 2037. Constellation wurde aber durch die Nachfolgeregierung unter Barack Obama aus Kostengründen gestrichen.[50] + +Auch das langfristig angelegte europäische Raumfahrtprogramm Aurora strebt insbesondere die Landung eines Menschen auf dem Mars an und plant sie für das Jahr 2033. + +Darüber hinaus existieren im Rahmen von Visionen einer Marskolonisation Vorstellungen, den Mars durch Terraforming in weiter Zukunft in einen für den Menschen lebensfreundlicheren Planeten umzuwandeln. Robert Zubrin, Edwin Aldrin und andere namhafte Stimmen in den Vereinigten Staaten von Amerika treten mittlerweile dafür ein, auf dem Mars unter dem Motto Mars to Stay schon in naher Zukunft die erste menschliche Siedlung zu begründen: Das sei möglich und sinnvoll, weil es wesentlich weniger Aufwand erfordere, die ersten Marsfahrer dauerhaft auf dem Planeten siedeln zu lassen, als sie sogleich wieder zur Erde zurückzuholen. +Möglichkeit von Leben + Hauptartikel: Leben auf dem Mars + +Die Ökosphäre (oder habitable Zone) des Sonnensystems reicht von 0,95 bis 1,37 AE Abstand zur Sonne. Im Sonnensystem befindet sich nur die Erde innerhalb dieses Gürtels um die Sonne, der Mars liegt knapp außerhalb. + +Höheres oder gar intelligentes Leben gibt es auf dem Mars nicht, Wissenschaftler halten jedoch primitive Lebensformen (Mikroben) tiefer im Boden, um vor UV-Strahlen geschützt zu sein, für denkbar.[51] Tatsächlich haben die in der Antarktis im Inneren von Gesteinen lebenden Pilzarten Cryomyces antarcticus und Cryomyces minteri simulierte Mars-Umweltbedingungen relativ gut überstanden: Nach 18 Monaten auf der Internationalen Raumstation[52] enthielten knapp 10 % der Proben noch fortpflanzungsfähige Zellen.[53] Auch die Flechte Xanthoria elegans hat die simulierten Marsbedingungen während des Experiments überlebt. +Vermutungen vor dem Raumzeitalter +Marsoberfläche nach Oswald Lohse (1888). Auf der Karte ist das Kanalsystem Schiaparellis nicht eingezeichnet. Die von Lohse gewählten Namen für die Seen und Ozeane sind heute nicht mehr gebräuchlich + +Der Gedanke an die Möglichkeit von Leben auf dem Mars beflügelte oft die Fantasie der Menschen. Im 18. Jahrhundert beobachtete man, dass die dunklen Flecken auf der Marsoberfläche ihre Farbe änderten und wuchsen oder schrumpften. Man hielt sie für ausgedehnte Vegetationszonen, deren Ausdehnung sich mit den Jahreszeiten änderte. + +Durch Schiaparellis Entdeckung der Marskanäle wurden die Spekulationen um intelligentes Leben auf dem Mars angefacht. + +So entstanden zahlreiche Legenden um vermeintliche Zivilisationen auf dem Mars. Die Diskussionen um die Marsmenschen hielten etwa ein Jahrhundert an. Der US-Amerikaner Percival Lowell, einer der heftigsten Verfechter der Marskanäle-Theorie, gründete sogar eine eigene Sternwarte, um die Marsbewohner zu erforschen. Für ihn waren die Kanäle das Produkt außerirdischer Ingenieure, die geschaffen wurden, um die Marszivilisation vor einer großen Trockenheit zu retten. Lowell beschrieb seine Vorstellungen der Marswelt in zahlreichen Publikationen, die weite Verbreitung fanden. + +Obwohl nicht alle Astronomen die Kanäle sehen konnten und keine Fotos existierten, hielt sich die Theorie, begleitet von einer heftigen Debatte. Die Vorstellung von außerirdischem Leben übt bis heute eine Faszination auf die Menschen aus, die mit wissenschaftlichem Interesse alleine oft nicht erklärt werden kann. Erst die Ergebnisse der unbemannten Marsmissionen beendeten den Streit um die Kanäle. + +Untersuchungen durch Viking + +Als im Juli 1976 der Orbiter 1 der Viking-Mission Bilder der Cydonia-Region machte und diese zur Erde schickte, wurde der Mars in der Öffentlichkeit wieder zum Gesprächsthema. Eine der Aufnahmen zeigte eine Formation auf der Marsoberfläche, die einem menschlichen Gesicht ähnelte, das gen Himmel blickt. In der unmittelbaren Nähe wurden außerdem Strukturen entdeckt, die Pyramiden auf der Erde ähneln, sowie rechteckige Strukturen (von den Wissenschaftlern Inka-Stadt getauft). Erst die Mission Mars Global Surveyor der NASA brachte im April 1998 für viele die Ernüchterung: Alle entdeckten Strukturen waren das Ergebnis natürlicher Erosion. Durch neue Bilder mit wesentlich höherer Auflösung wurde deutlich, dass auf dem Mars keine künstlichen Strukturen außerirdischer Intelligenz ersichtlich sind. +Das Marsgesicht in der Cydonia-Region; Aufnahme des Orbiters von Viking 1, 1976 + +Viking 1 und 2 hatten unter anderem die Aufgabe, der Frage nach dem Leben auf dem Mars nachzugehen. Dabei wurden ein chemisches und drei biologische Experimente durchgeführt. In dem chemischen Experiment wurde versucht, organische Substanzen im Marsboden nachzuweisen. Dazu wurde eine am MIT entwickelte GC/MS-Einheit (Kopplung eines Gaschromatographen mit einem Massenspektrometer) benutzt. Es konnten allerdings keine auf Kohlenstoff aufbauenden organischen Substanzen nachgewiesen werden. + +Das erste biologische Experiment beruhte auf Stoffwechselaktivitäten von Organismen. Eine Bodenprobe wurde mit einer Nährlösung benetzt und entstehende Gase registriert. Der Marsboden reagierte auf das Experiment mit Abgabe großer Mengen Sauerstoff. Im zweiten Experiment wurde eine Nährlösung mit radioaktiven Kohlenstoffatomen versehen und auf eine Probe gegeben. Als Ergebnis eines Stoffwechsels hätten sie unter den ausgeschiedenen Gasen nachgewiesen werden müssen. Tatsächlich wurden radioaktive Kohlenstoffatome nachgewiesen. Das dritte Experiment war ein Photosynthese-Experiment. Radioaktiv markiertes Kohlendioxid wurde dem Marsboden zugesetzt. Dieses Kohlendioxid hätte assimiliert werden und später nachgewiesen werden müssen. Auch dieses Ergebnis war positiv. Obwohl die Ergebnisse der biologischen Experimente positiv waren, gaben sie aufgrund des negativen Ergebnisses des GC/MS-Versuchs keinen schlüssigen Beweis für die Existenz oder Nichtexistenz von Leben auf dem Mars. +1990er und 2000er Jahre +Marsgesicht, Aufnahme von Mars Global Surveyor, 2001 + +Im Jahr 1996 fanden David S. McKay und seine Mitarbeiter Strukturen im Marsmeteoriten ALH 84001, die sie als Spuren von fossilen Bakterien deuteten. Das in diesem Meteoriten gefundene, kettenartig angeordnete Magnetit ähnelt morphologisch dem bakteriellen Magnetit aus Magnetospirillum magnetotacticum. Allerdings wird die Beweiskraft der gefundenen Strukturen von vielen Wissenschaftlern angezweifelt, da diese auch auf rein chemischem Wege entstehen konnten. + +Am 23. Januar 2004 entdeckte die europäische Marssonde Mars Express am Südpol des Mars große Mengen gefrorenen Wassers, Ende Juli 2005 auch in einem nahe dem Nordpol gelegenen Krater. + +Ende März 2004 wurde bekannt, dass Forscher der NASA und der ESA unabhängig voneinander Methan in der Marsatmosphäre nachgewiesen haben. Ob das Methan geologischen Ursprungs ist oder etwa durch den Stoffwechsel von Mikroorganismen gebildet wurde, sollen weitere Untersuchungen zeigen. + +Ebenfalls Anfang 2004 entdeckte die Marssonde Opportunity Gesteine, die in offenstehendem Wasser abgelagert worden sein müssen und viele regelmäßig verteilte kugelige, bis 1 cm große Hämatit-Konkretionen enthalten. Solche Konkretionen kommen auch auf der Erde vor. Unter irdischen Bedingungen ist es wahrscheinlich, dass bei ihrer Entstehung Bakterien beteiligt sind. Ob dies auch für den Mars gilt, könnten nur Laboruntersuchungen auf der Erde zeigen. + +Weitere Mikrostrukturen, welche die Rover Spirit und Opportunity 2004 entdeckt hatten und in denen ein Teil der interessierten Öffentlichkeit Hinweise auf Leben hatte sehen wollen, erwiesen sich bei näherer Untersuchung als abiotisch oder künstlich, so zum Beispiel Schleifspuren auf durch die Instrumente bearbeiteten Gesteinsoberflächen oder Filamente, die sich als Textilfasern der Lande-Airbags herausstellten. + +Forschungsergebnisse auf der Erde bestätigen, dass es Leben auch in extremen Bedingungen geben kann. Bei Bohrungen im grönländischen Eis entdeckten Forscher der University of California, Berkeley im Jahre 2005 in drei Kilometern Tiefe eine auffallende Menge Methan. Dieses Gas produzierten methanogene Bakterien, die trotz unwirtlicher Lebensbedingungen wie Kälte, Dunkelheit und Nährstoffmangel im Eis überleben. Dabei erhalten sie sich nur mühsam am Leben sie reparieren Erbgutschäden, vermehren jedoch nicht nennenswert ihre Population. Methanogene Mikroben sind eine Untergruppe der Archaebakterien, die sich auf Extremstandorte spezialisiert haben. So fanden sich im Jahr 2002 Mikroben in einer 15.000 Jahre alten heißen Quelle in Idaho. Die Bakterien zählen, wie schon der Name besagt, zu den ältesten Mikroorganismen der Erde. Die Wissenschaftler schätzen das Alter der in Grönland entdeckten Bakterienkolonie auf 100.000 Jahre und vermuten, dass das in der Atmosphäre des Roten Planeten nachgewiesene Methan nicht nur von chemischen Prozessen, sondern auch von solchen Mikroben stammen könnte. +Aktuelle Forschung + +Mit dem Mars Science Laboratory wird versucht, neue Aufschlüsse über mögliches Leben auf dem Mars zu liefern. Es ist fraglich, ob der Mars-Rover tief genug bohren kann, um Leben oder zumindest Lebensreste zu finden. Aber eine Isotopenanalyse des Methans kann bereits weitere Aufschlüsse geben. Leben, wie es auf der Erde bekannt ist, bevorzugt leichtere Wasserstoffisotope. +Beobachtung +Stellung zur Erde und Bahneigenschaften +Planetenschleife des Mars im Sternbild Wassermann im Jahr 2003 + +Aufgrund der Bahneigenschaften der Planeten überholt die Erde den Mars durchschnittlich alle 779 Tage auf ihrer inneren Bahn. Diesen Zeitraum, der zwischen 764 und 811 Tagen schwankt, nennt man synodische Periode. Befinden sich Sonne, Erde und Mars in dieser Anordnung auf einer Linie, so steht der Mars von der Erde aus gesehen in Opposition zur Sonne. Zu diesem Zeitpunkt ist Mars besonders gut zu beobachten, er steht dann als rötlicher Stern auffallend hell am Nachthimmel. Beobachtet man den Mars regelmäßig, kann man feststellen, dass er vor und nach einer Opposition am Himmel eine Schleifenbewegung vollführt. Diese Planetenschleife (Oppositionsschleife) ergibt sich aus den Sichtwinkeln, die Mars bietet, während er von der Erde überholt wird. +Marsoppositionen von 2003 bis 2018, relative Bewegung des Mars zur Erde, mit der Erde im Zentrum; Ansicht auf die Ekliptikebene + +Da die Planeten sich nicht auf idealen Kreisbahnen, sondern auf mehr oder weniger stark ausgeprägten elliptischen Bahnen bewegen, haben Erde und Mars zum Zeitpunkt der Oppositionen unterschiedliche Entfernungen zueinander. Diese können zwischen 55,6 und 101,3 Millionen Kilometern bzw. 0,37 und 0,68 AE betragen. Bei einer geringen Oppositionsentfernung spricht man von einer Perihelopposition, bei einer großen von einer Aphelopposition. +Schwankung des minimalen Abstands ErdeMars bei Oppositionen. Die tatsächlichen Werte sind an den einzelnen Punkten ablesbar. Die verbindende Kurve veranschaulicht die mathematische Gesetzmäßigkeit. + +Die alle 15 bis 17 Jahre stattfindenden Periheloppositionen bieten die besten Gelegenheiten, den Mars von der Erde aus mittels Teleskop zu beobachten. Der Planet hat dann einen scheinbaren Durchmesser von bis zu 25,8 Bogensekunden. Bei einer Aphelopposition ist er mit 14,1 Bogensekunden nur etwa halb so groß. Besonders erdnahe Oppositionen fanden im Abstand von jeweils 79 Jahren, zum Beispiel in den Jahren 1766, 1845, 1924 und 2003 statt. Am 28. August 2003 betrug der Abstand ErdeMars 55,76 Millionen Kilometer. Dies war die geringste Distanz seit etwa 60.000 Jahren.[54][55] Erst im Jahre 2287 wird der Mars der Erde noch näher kommen, der Abstand beträgt dann 55,69 Millionen Kilometer. + +Im Teleskop erscheint der Mars zunächst als rötliches Scheibchen. Bei stärkerer Vergrößerung können die Polkappen und dunkle Oberflächenmerkmale wie die Große Syrte ausgemacht werden. Treten auf dem Mars größere Staubstürme auf, verblassen die Merkmale, da die Oberfläche von einer rötlichen Dunstschicht eingehüllt wird, die sich mitunter über Wochen halten kann. Durch den Einsatz von CCD-Kameras sind mittlerweile auch Amateurastronomen in der Lage, detailreiche Aufnahmen der Marsoberfläche zu erzielen, wie sie vor etwa zehn Jahren nur von den leistungsfähigsten Großteleskopen erstellt werden konnten. + +Ereignisse (Jahreszeitenbeginn gilt für die Nordhalbkugel):[56][57][58] +Ereignis Frühlingsbeginn Aphel Sommerbeginn Herbstbeginn Perihel Winterbeginn +Datum 18. Juni 2015 20. November 2015 15. Februar 2014 17. August 2014 12. Dezember 2014 11. Januar 2015 +Nächste +Termine 5. Mai 2017 7. Oktober 2017 3. Januar 2016 4. Juli 2016 29. Oktober 2016 28. November 2016 +Ereignis Konjunktion Opposition +Datum 14. Juni 2015 8. April 2014 +Nächste +Termine 27. Juli 2017 22. Mai 2016 +Sichtbarkeiten + Hauptartikel: Marspositionen + +Wegen der Exzentrizität der Marsbahn kann der erdnächste Punkt bis zu einer Woche vor oder nach der Opposition erreicht werden, und die scheinbare Helligkeit während der Opposition sowie der Erdabstand und der scheinbare Durchmesser während der Erdnähe können recht unterschiedlich ausfallen. + +Eine Opposition findet etwa alle zwei Jahre (779,94 Tage) statt. Dabei kann bei einer Perihelopposition die maximale scheinbare Helligkeit bis zu 2,91m erreichen. Zu diesem Zeitpunkt sind nur die Sonne, der Erdmond, die Venus und in seltenen Fällen Jupiter (bis zu 2,94m) noch heller. Bei Konjunktion hingegen erscheint Mars nur mehr mit einer Helligkeit von +1,8m.[1] +Kulturgeschichte +Beschäftigung mit dem Mars von der Antike bis in die Neuzeit +Allegorische Darstellung des Mars als Herrscher der Tierkreiszeichen Widder und Skorpion, von Hans Sebald Beham, 16. Jahrhundert + +Der Mars bewegte die Menschheit von alters her besonders. Im alten Ägypten wurde Mars als Horus der Rote bezeichnet. Da der Planet sich während seiner Oppositionsschleife (Planetenschleife) zeitweise rückläufig bewegt, sprachen die Ägypter davon, dass Mars rückwärts wandere. Der Name der ägyptischen Hauptstadt Kairo leitet sich von Al Qahira ab, dem altarabischen Namen für den Planeten Mars. + +Im indischen Sanskrit wird der Mars als Mangal (verheißungsvoll), Angaraka (Glühende Kohle) und Kuja (der Blonde) bezeichnet. Er repräsentiert kraftvolle Aktion, Vertrauen und Zuversicht. + +Aufgrund seiner roten Färbung wurde der Mars in verschiedenen Kulturen mit den Gottheiten des Krieges in Verbindung gebracht. Die Babylonier sahen in ihm Nergal, den Gott der Unterwelt, des Todes und des Krieges. Für die Griechen und Römer der Antike repräsentierte er deren Kriegsgötter Ares beziehungsweise Mars. In der nordischen Mythologie steht er für Tyr, den Gott des Rechts und des Krieges. Die Azteken nannten ihn Huitzilopochtli, der Zerstörer von Menschen und Städten. Für die Chinesen war er Huoxing (chin. Huxng, ), Stern des Feuers. + +In der Astrologie ist Mars unter anderem das Symbol der Triebkraft. Es wird dem Element Feuer, dem Planetenmetall Eisen, den Tierkreiszeichen Widder und Skorpion sowie dem 1. Haus zugeordnet. +Rezeption in Literatur, Film, Videospiele und Musik + +Der Mars und seine fiktiven Bewohner sind auch Thema zahlreicher Romane und Verfilmungen. + +Ein Beispiel des 18. Jahrhunderts ist Carl Ignaz Geigers Roman Reise eines Erdbewohners in den Mars von 1790. + +1880 veröffentlichte Percy Greg seinen Roman Across the Zodiac, in dem er eine Reise in einem Raumschiff namens Astronaut zum Mars beschrieb. + +Die klassische Figur des kleinen grünen Männchens mit Antennen auf dem Kopf erschien erstmals 1913 in einem Comic und ist seitdem Klischee. + +Als der Astronom Percival Lowell Ende des 19. Jahrhunderts die Vorstellung entwickelte, die mit dem Fernrohr wahrnehmbaren Marskanäle seien künstlich angelegte Wasserkanäle, wurde diese Idee in der Science-Fiction-Literatur aufgegriffen und weitergesponnen. Dort wurde der Mars häufig als eine sterbende Welt vorgestellt, in deren kalten Wüstenregionen alte und weit entwickelte Zivilisationen ums Ãœberleben kämpften. + +Kurd Laßwitz brachte 1897 seinen sehr umfangreichen Roman Auf zwei Planeten über einen Besuch bei den Marsbewohnern heraus. +Angriff der Marsianer in Krieg der Welten von H. G. Wells. Buchillustration der französischen Ausgabe von Alvim Corréa von 1906 + +In H. G. Wells bekanntem Roman Krieg der Welten, der 1898 erschien, verlassen die Marsianer ihre Heimatwelt, um die lebensfreundlichere Erde zu erobern. Die Menschheit, die den hochtechnisierten kriegerischen Marsianern hoffnungslos unterlegen ist, entgeht ihrer Auslöschung nur dadurch, dass die Invasoren von für Menschen harmlosen, irdischen Mikroben dahingerafft werden. Orson Welles verwendete den Stoff im Jahre 1938 in einem Hörspiel, wobei er die Marsianer in New Jersey landen ließ. Das Hörspiel wurde im Stil einer realistischen Reportage ausgestrahlt. Hörer, die sich später einschalteten, hielten die Invasion der Marsianer für Realität. + +Wells Romanvorlage wurde 1952 verfilmt, wobei die Handlung wiederum in die USA der Gegenwart verlegt wurde. Der Film erhielt für die damals bahnbrechenden Spezialeffekte einen Oscar. + +1923 brachte Tolstoi seinen Roman Aelita heraus, der von der Liebe eines sowjetischen Ingenieurs zur Marsprinzessin und dem Untergang der Zivilisation auf dem Planeten handelt. Dieses Werk wurde 1924 verfilmt. + +Im Jahr 1978 entstand der Film Unternehmen Capricorn. Er griff das Thema der Verschwörungstheorien zur Mondlandung auf, indem er es in sehr zugespitzter Form auf eine im Filmstudio vorgetäuschte Marsexpedition übertrug. + +Der 1996 entstandene Film Mars Attacks! setzt sich ironisch mit dem Thema Marsinvasion auseinander, wobei den Marsianern amerikanische Schnulzenmusik aus den 1950er Jahren zum Verhängnis wird. + +Unter der Regie von Brian De Palma wurden im Jahr 2000 mit dem Film Mission to Mars die Spekulationen um das Marsgesicht der Cydonia-Region als hinterlassenes Bauwerk dramatisch weitgehend thematisiert. + +Steven Spielbergs 2005 entstandenes Remake von Krieg der Welten nahm noch einmal das Thema auf und zeigte die Invasion von Außerirdischen auf der Erde aus der Sicht eines Familienvaters aus den USA. + +Weitere bekannte Science-Fiction-Filme, die auf dem Mars handeln, sind Red Planet (2000) und Die totale Erinnerung Total Recall (1990). + +Edgar Rice Burroughs, der Autor von Tarzan, schrieb von 1917 bis 1943 die elfbändige Saga John Carter vom Mars, in der sich der irdische Held in marsianische Prinzessinnen verliebt und gegen Luftpiraten, grünhäutige Unholde, weiße Riesenaffen und andere Untiere kämpft. + +Die Mars-Chroniken (1950), eine stimmungsvolle Sammlung von Erzählungen des Schriftstellers Ray Bradbury, sind ebenfalls auf dem Mars angesiedelt. + +Große Beachtung erhielt die Marstrilogie, eine von Kim Stanley Robinson von 1993 bis 1996 verfasste Romanserie über die Besiedelung des Mars. Der besondere Ansatz dieser Geschichten liegt in der vorwiegend technischen Schilderung unter vollständigem Verzicht phantastischer Elemente. +Die Route von Mark Watney in einer nachgestellten topographischen Kartierung des DLR-Instituts für Planetenforschung + +Der wohl prominenteste Auftritt des Mars in der Musik dürfte der erste Satz von Gustav Holsts Orchestersuite Die Planeten (19141916) sein, deren erster Satz Mars, the Bringer of War mit seinem drohend-martialischen Charakter die mythologische Gestalt Mars eindrucksvoll porträtiert. + +Bestsellerautor Andreas Eschbach verfasste von 2001 bis 2008 die Pentalogie Das Marsprojekt. + +2011 veröffentlichte Andy Weir den Science-Fiction-Roman Der Marsianer, in dem ein Astronaut nach einem Unfall auf dem Mars zurückgelassen wird und fortan um sein Ãœberleben kämpfen muss. Mit Der Marsianer Rettet Mark Watney erschien 2015 eine Verfilmung dieses Bestsellers. + +Helga Abret und Lucian Boa geben in ihrem Buch Das Jahrhundert der Marsianer (1984) einen literarischen Ãœberblick über Erzählungen und Romane über den Mars und seine fiktiven Bewohner. Von der Beschreibung einer ekstatischen Reise zum Mars (Itinerarium exstaticum coeleste, 1656) des Jesuitenpaters Athanasius Kircher bis hin zu Science-Fiction-Erzählungen des 20. Jahrhunderts reicht die Bandbreite der kommentierten Werke, mit denen die Autoren aufzuzeigen versuchen, dass sich aus dem Zusammenwirken von Naturwissenschaften, Astronomie und Literatur ein moderner Mythos[59] entwickelte. + diff --git a/xpcom/tests/gtest/wikipedia/de.txt b/xpcom/tests/gtest/wikipedia/de.txt new file mode 100644 index 0000000000..486c676110 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/de.txt @@ -0,0 +1,487 @@ +Der Mars ist, von der Sonne aus gezählt, der vierte Planet im Sonnensystem und der äußere Nachbar der Erde. Er zählt zu den erdähnlichen (terrestrischen) Planeten. + +Sein Durchmesser ist mit knapp 6800 Kilometer etwa halb so groß wie der der Erde, sein Volumen beträgt gut ein Siebtel des Erdevolumens. Damit ist der Mars nach dem Merkur der zweitkleinste Planet des Sonnensystems, hat jedoch eine ausgeprägte Geologie und die höchsten Vulkane des Sonnensystems. Mit einer durchschnittlichen Entfernung von 228 Millionen Kilometern ist er rund 1,5-mal so weit von der Sonne entfernt wie die Erde. + +Die Masse des Mars beträgt etwa ein Zehntel der Erdmasse. Die Fallbeschleunigung auf seiner Oberfläche beträgt 3,69 m/s², dies entspricht etwa 38 % der irdischen. Mit einer Dichte von 3,9 g/cm³ weist der Mars den geringsten Wert der terrestrischen Planeten auf. Deshalb ist die Schwerkraft auf ihm sogar geringfügig niedriger als auf dem kleineren, jedoch dichteren Merkur. + +Der Mars wird oft auch als der Rote Planet bezeichnet. Diese Färbung geht auf Eisenoxid-Staub (Rost) zurück, der sich auf der Oberfläche und in der dünnen CO2-Atmosphäre verteilt hat. Seine orange- bis blutrote Farbe und seine Helligkeitsschwankungen sind auch verantwortlich für seine Namensgebung nach dem römischen Kriegsgott Mars.[3] + +In größeren Fernrohren deutlich sichtbar sind die zwei Polkappen und mehrere dunkle Ebenen, die sich im Frühjahr etwas verfärben. Fotos von Raumsonden zeigen eine teilweise mit Kratern bedeckte Oberfläche und starke Spuren früherer Tektonik (tiefe Canyons und fünf über 20 km hohe Vulkane). Marsroboter haben schon mehrere Gebiete geologisch untersucht. + +Der Mars besitzt zwei kleine, unregelmäßig geformte Monde, die 1877 entdeckt wurden: Phobos und Deimos (griechisch für Furcht und Schrecken). + +Das astronomische Symbol des Mars ist ♂. + +Umlauf und Rotation +Umlaufbahn + +Der Mars bewegt sich in einem Abstand von 206,62 bis 249,23 Millionen Kilometern (1,38 AE bis 1,67 AE) in knapp 687 Tagen (etwa 1,9 Jahre) auf einer elliptischen Umlaufbahn um die Sonne. Die Bahnebene ist 1,85° gegen die Erdbahnebene geneigt. + +Seine Bahngeschwindigkeit schwankt mit dem Sonnenabstand zwischen 26,50 km/s und 21,97 km/s und beträgt im Mittel 24,13 km/s. Die Bahnexzentrizität beträgt 0,0935. Nach der Umlaufbahn des Merkurs ist das die zweitgrößte Abweichung von der Kreisform unter allen Planetenbahnen des Sonnensystems. + +Jedoch hatte der Mars in der Vergangenheit eine weniger exzentrische Umlaufbahn. Vor 1,35 Millionen Jahren betrug die Exzentrizität nur etwa 0,002, weniger als die der Erde heute.[4] Die Periode der Exzentrizität des Mars beträgt etwa 96.000 Jahre, die der Erde etwa 100.000 Jahre.[5] Mars hat jedoch noch einen längeren Zyklus der Exzentrizität mit einer Periode von 2,2 Millionen Jahren, der den mit der Periode von 96.000 Jahren überlagert. In den letzten 35.000 Jahren wurde die Umlaufbahn aufgrund der gravitativen Kräfte der anderen Planeten geringfügig exzentrischer. Der minimale Abstand zwischen Erde und Mars wird in den nächsten 25.000 Jahren noch ein wenig geringer werden.[6] + +Es gibt vier bekannte Asteroiden, die sich mit dem Mars die gleiche Umlaufbahn teilen (Mars-Trojaner). Sie befinden sich auf den Lagrangepunkten L4 und L5, das heißt, sie eilen dem Planeten um 60° voraus oder folgen ihm um 60° nach. +Rotation + +Der Mars rotiert in 24 Stunden und 37,4 Minuten um die eigene Achse (Siderischer Tag). In Bezug auf die Sonne ergibt sich daraus ein Marstag (auch Sol genannt) von 24:39:35. Die Äquatorebene des Planeten ist um 25,19° gegen seine Bahnebene geneigt (Erde 23,44°), somit gibt es Jahreszeiten ähnlich wie auf der Erde. Sie dauern jedoch fast doppelt so lang, weil das siderisches Marsjahr 687 Erdtage hat. Da die Bahn des Mars aber eine deutlich größere Exzentrizität aufweist, als die der Erde, und Mars-Nord tendenziell in Richtung der großen Bahn-Ellipsenachse weist, sind die Jahreszeiten unterschiedlich lang. In den letzten 300.000 Jahren variierte die Rotationsachse zwischen 22° und 26°. Zuvor lag sie mehrmals auch über 40°, wodurch starke Klimaschwankungen auftraten, es Vereisungen auch in der Äquatorregion gab und so die starken Bodenerosionen zu erklären sind. + +Der Nordpol des Mars weist zum nördlichen Teil des Sternbilds Schwan, womit sich die Richtung um etwa 40° von jener der Erdachse unterscheidet. Der marsianische Polarstern ist Deneb (mit leichter Abweichung der Achse Richtung Alpha Cephei).[7] + +Die Rotationsachse führt eine Präzessionsbewegung aus, deren Periode 170.000 Jahre beträgt (7× langsamer als die Erde). Aus diesem Wert, der mit Hilfe der Pathfinder-Mission festgestellt wurde, können die Wissenschaftler auf die Massenkonzentration im Inneren des Planeten schließen.[8] +Atmosphäre und Klima +Ãœber dem Marshorizont ist die Atmosphäre als dunstiger Schleier erkennbar. Links ist der einem Smiley ähnelnde Krater Galle zu sehen. Viking, 1976 + +Der Mars besitzt eine sehr dünne Atmosphäre. Dadurch ist der Atmosphärendruck sehr niedrig, und Wasser kann nicht in flüssiger Form auf der Marsoberfläche existieren, ausgenommen kurzzeitig in den tiefstgelegenen Gebieten. + +Da die dünne Marsatmosphäre nur wenig Sonnenwärme speichern kann, sind die Temperaturunterschiede auf der Oberfläche sehr groß. Die Temperaturen erreichen in Äquatornähe etwa 20 °C am Tag und sinken bis auf −85 °C in der Nacht. Die mittlere Temperatur des Planeten liegt bei etwa −55 °C. +Atmosphäre +→ Hauptartikel: Atmosphäre des Mars + +Die Marsatmosphäre besteht zu 95,3 % aus Kohlenstoffdioxid. Dazu kommen noch 2,7 % Stickstoff, 1,6 % Argon, geringe Anteile an Sauerstoff (1300 ppm) und Kohlenstoffmonoxid (800 ppm) sowie Spuren von Wasserdampf (210 ppm) und anderen Verbindungen oder Elementen. + +Die Atmosphäre ist ziemlich staubig. Sie enthält Teilchen mit etwa 1,5 µm im Durchmesser, die den Himmel über dem Mars in einem blassen gelb- bis orange-braunen Farbton erscheinen lassen. + +Der atmosphärische Druck beträgt auf der Oberfläche des Mars im Schnitt nur 6,36 hPa (Hektopascal). Im Vergleich zu durchschnittlich 1013 hPa auf der Erde sind dies nur 0,63 %, was dem Luftdruck der Erdatmosphäre in 35 Kilometern Höhe entspricht. Die Atmosphäre wurde wahrscheinlich im Laufe der Zeit vom Sonnenwind abgetragen und in den Weltraum mitgerissen. Dies wurde durch die geringe Schwerkraft des Planeten und sein schwaches Magnetfeld begünstigt, das kaum Schutz vor den hochenergetischen Teilchen der Sonne bietet. +Klima und Wetter +Eiswolken über Mars, aufgenommen von Mars Pathfinder + +Abhängig von den Jahreszeiten und der Intensität der Sonneneinstrahlung finden in der Atmosphäre dynamische Vorgänge statt. Die vereisten Polkappen sublimieren im Sommer teilweise, und kondensierter Wasserdampf bildet ausgedehnte Zirruswolken. Die Polkappen selbst bestehen aus Kohlendioxideis und Wassereis. + +2008 entdeckte man mit Hilfe der Raumsonde Mars Express Wolken aus gefrorenem Kohlendioxid. Sie befinden sich in bis zu 80 Kilometern Höhe und haben eine horizontale Ausdehnung von bis zu 100 km. Sie absorbieren bis zu 40 % des einstrahlenden Sonnenlichts und können damit die Temperatur der Oberfläche um bis zu 10 °C verringern.[9] + +Mit Hilfe des Lasers LIDAR der Raumsonde Phoenix wurde 2009 entdeckt, dass in der zweiten Nachthälfte fünfzig Tage nach der Sonnenwende winzige Eiskristalle aus dünnen Zirruswolken auf den Marsboden fielen.[10] +Jahreszeiten +Staubsturm in der Syria-Region, fotografiert von Mars Global Surveyor im Mai 2003 + +Hätte Mars eine erdähnliche Umlaufbahn, würden die Jahreszeiten aufgrund der Achsenneigung ähnlich denen der Erde sein. Jedoch führt die vergleichsweise große Exzentrizität seines Orbits zu einer beträchtlichen Auswirkung auf die Jahreszeiten. Der Mars befindet sich während des Sommers in der Südhalbkugel und des Winters in der nördlichen Hemisphäre nahe dem Perihel seiner Bahn. Nahe dem Aphel ist in der südlichen Hemisphäre Winter und in der nördlichen Sommer. + +Das hat zur Folge, dass die Jahreszeiten in der südlichen Hemisphäre viel deutlicher ausgeprägt sind als in der nördlichen, wo das Klima ausgeglichener ist, als es sonst der Fall wäre. Die Sommertemperaturen im Süden können bis zu 30 °C höher sein als die vergleichbaren Temperaturen im Sommer des Nordens.[11] Die Jahreszeiten sind aufgrund der Exzentrizität der Umlaufbahn des Mars unterschiedlich lang. Auf der Nordhalbkugel dauert der Frühling 199,6, der Sommer 181,7, der Herbst 145,6 und der Winter 160,1 irdische Tage.[12] +Wind und Stürme + +Aufgrund der starken Tag-Nacht-Temperaturschwankungen der Oberfläche gibt es tägliche Morgen- und Abendwinde.[13] + +Während des Marsfrühjahrs können in den ausgedehnten flachen Ebenen heftige Staubstürme auftreten, die mitunter große Teile der Marsoberfläche verhüllen. Die Aufnahmen von Marssonden zeigen auch Windhosen, die über die Marsebenen ziehen und auf dem Boden dunkle Spuren hinterlassen. Stürme auf dem Mars haben wegen der sehr dünnen Atmosphäre eine wesentlich geringere Kraft als Stürme auf der Erde. Selbst bei hohen Windgeschwindigkeiten werden nur kleine Partikel (Staub) aufgeweht.[14] Allerdings verbleibt aufgewehter Staub auf dem Mars wesentlich länger in der Atmosphäre als auf der Erde, da es keine Niederschläge gibt, die die Luft reinigen, und zudem die Gravitation geringer ist. + +Staubstürme treten gewöhnlich während des Perihels auf, da der Planet zu diesem Zeitpunkt 40 Prozent mehr Sonnenlicht empfängt als während des Aphels. Während des Aphels bilden sich in der Atmosphäre Wolken aus Wassereis, die ihrerseits mit den Staubpartikeln interagieren und so die Temperatur auf dem Planeten beeinflussen.[15] Die Windgeschwindigkeiten in der oberen Atmosphäre können bis zu 650 km/h erreichen, auf dem Boden immerhin fast 400 km/h.[16] +Gewitter + +Bei heftigen Staubstürmen scheint es auch zu Gewittern zu kommen. Im Juni 2006 untersuchten Forscher mit einem Radioteleskop den Mars und stellten im Mikrowellenbereich Strahlungsausbrüche fest, wie sie bei Blitzen auftreten. In der Region, in der man die Strahlungsimpulse beobachtet hat, herrschte zu der Zeit ein heftiger Staubsturm mit hohen Staubwolken. Sowohl der beobachtete Staubsturm wie auch das Spektrum der Strahlungsimpulse deuten auf ein Staubgewitter mit Blitzen bzw. großen Entladungen hin.[17][18] +Oberfläche +Typisches Felsengestein auf der Marsoberfläche (aufgenommen von Mars Pathfinder) + +Die Oberfläche des Mars beträgt etwa ein Viertel der Erdoberfläche. Sie entspricht mit 144 Mio. km2 ungefähr der Gesamtoberfläche aller Kontinente der Erde (149 Mio. km2). + +Die rote Färbung seiner Oberfläche verdankt der Planet dem Eisenoxid-Staub, der sich auf der Oberfläche und in der Atmosphäre verteilt hat. Somit ist der Rote Planet ein „rostiger Planet“. + +Seine beiden Hemisphären sind sehr verschieden. Die Südhalbkugel stellt ein riesiges Hochland dar, das durchschnittlich 2–3 km über dem globalen Nullniveau liegt und ausgedehnte Schildvulkane aufweist. Die vielen Einschlagkrater belegen sein hohes Alter von fast 4 Milliarden Jahren. Dem steht die geologisch junge, fast kraterlose nördliche Tiefebene gegenüber. Sie liegt 3–5 km unter dem Nullniveau und hat ihre ursprüngliche Struktur durch noch ungeklärte geologische Prozesse verloren. Auslöser war möglicherweise eine gewaltige Kollision in der Frühzeit des Planeten. +Gesteine +→ Hauptartikel: Marsgestein + +An den Landestellen der Marssonden sind Gesteinsbrocken, sandige Böden und Dünen sichtbar. Die Marsgesteine weisen an der Oberfläche eine blasenartige Struktur auf und ähneln in ihrer Zusammensetzung irdischen Basalten, was bereits vor Jahrzehnten aus den auf der Erde (Antarktis) gefundenen Marsmeteoriten erschlossen wurde. Die roten Böden sind offensichtlich durch die Verwitterung von eisenhaltigen, vulkanischen Basalten entstanden. + +Die Pathfinder-Sonde fand 1997 außer verschiedensten Basalten auch quarzreichere Tiefengesteine ähnlich dem südamerikanischen Andesit, ferner das aus der Tiefe stammende Olivin und runde Kiesel aus Konglomeraten. Weitverbreitet ist metamorpher Regolith (ähnlich wie am Mond) und äolische Sedimente. Vereinzelt verwehter Sand aus schwefelhaltigen Staubteilchen. +Areografie + +Die kartografische Darstellung und Beschreibung der Marsoberfläche ist die Areografie, von Ares (ΆÏης, griechisch für Mars) und grafein (γÏάφειν, griechisch für beschreiben). Die „Geologie“ des Mars wird mitunter dementsprechend als Areologie bezeichnet. + +Zur Festlegung von Positionen auf der Marsoberfläche dienen areografische Koordinaten, die definiert sind wie geografische Breite und Länge. +Topografische Hemisphären +Topografische Karte des Mars. Die blauen Regionen befinden sich unterhalb des festgelegten Nullniveaus, die roten oberhalb + +Auffallend ist die Dichotomie, die „Zweiteilung“, des Mars. Die nördliche und die südliche Hemisphäre unterscheiden sich deutlich, wobei man von den Tiefebenen des Nordens und den Hochländern des Südens sprechen kann. Der mittlere Großkreis, der die topografischen Hemisphären voneinander trennt, ist rund 40° gegen den Äquator geneigt. Der Massenmittelpunkt des Mars ist gegenüber dem geometrischen Mittelpunkt um etwa drei Kilometer in Richtung der nördlichen Tiefebenen versetzt. + +Auf der nördlichen Halbkugel sind flache sand- und staubbedeckte Ebenen vorherrschend, die Namen wie Utopia Planitia oder Amazonis Planitia erhielten. Dunkle Oberflächenmerkmale, die in Teleskopen sichtbar sind, wurden einst für Meere gehalten und erhielten Namen wie Mare Erythraeum, Mare Sirenum oder Aurorae Sinus. Diese Namen werden heute nicht mehr verwendet. Die ausgedehnteste dunkle Struktur, die von der Erde aus gesehen werden kann, ist Syrtis Major, die „große Syrte“. + +Die südliche Halbkugel ist durchschnittlich sechs Kilometer höher als die nördliche und besteht aus geologisch älteren Formationen. Die Südhalbkugel ist zudem stärker verkratert, wie zum Beispiel in der Hochlandregion Arabia Terra. Unter den zahlreichen Einschlagkratern der Südhalbkugel befindet sich auch der größte Marskrater, Hellas Planitia, die Hellas-Tiefebene. Das Becken misst im Durchmesser bis zu 2100 km. In seinem Innern maß Mars Global Surveyor 8180 m unter Nullniveau – unter dem Durchschnittsniveau des Mars – den tiefsten Punkt auf dem Planeten. Der zweitgrößte Einschlagkrater des Mars, Chryse Planitia, liegt im Randbereich der nördlichen Tiefländer. +Ãœbersichtskarte des Mars mit den größten Regionen + +Die deutlichen Unterschiede der Topografie können durch innere Prozesse oder aber ein Impaktereignis verursacht worden sein. In letzterem Fall könnte in der Frühzeit der Marsentstehung ein größerer Himmelskörper, etwa ein Asteroid, auf der Nordhalbkugel eingeschlagen sein und die silikatische Kruste durchschlagen haben. Aus dem Innern könnte Lava ausgetreten sein und das Einschlagbecken ausgefüllt haben. + +Wie sich gezeigt hat, hat die Marskruste unter den nördlichen Tiefebenen eine Dicke von etwa 40 km, die im Gegensatz zum stufenartigen Ãœbergang an der Oberfläche nur langsam auf 70 km bis zum Südpol hin zunimmt. Dies könnte ein Indiz für innere Ursachen der Zweiteilung sein. +Oberflächenstrukturen +Gräben +In der Bildmitte liegt das System der Mariner-Täler. Ganz links die Tharsis-Vulkane (Bildmosaik von Viking 1 Orbiter, 1980) + +Südlich am Äquator und fast parallel zu ihm verlaufen die Valles Marineris (die Mariner-Täler), das größte bekannte Grabensystem des Sonnensystems. Es erstreckt sich über 4000 km und ist bis zu 700 km breit und bis zu 7 km tief. Es handelt sich um einen gewaltigen tektonischen Bruch. In seinem westlichen Teil, dem Noctis Labyrinthus, verästelt er sich zu einem chaotisch anmutenden Gewirr zahlreicher Schluchten und Täler, die bis zu 20 km breit und bis zu 5 km tief sind. + +Noctis Labyrinthus liegt auf der östlichen Flanke des Tharsis-Rückens, einer gewaltigen Wulst der Mars-Lithosphäre quer über dem Äquator mit einer Ausdehnung von etwa 4000 mal 3000 Kilometern und einer Höhe von bis zu rund 10 Kilometern über dem nördlichen Tiefland. Die Aufwölbung ist entlang einer offenbar zentralen Bruchlinie von drei sehr hohen, erloschenen Schildvulkanen besetzt: Ascraeus Mons, Pavonis Mons und Arsia Mons. Der Tharsis-Rücken und die Mariner-Täler dürften in ursächlichem Zusammenhang stehen. Wahrscheinlich drückten vulkanische Kräfte die Oberfläche des Planeten in dieser Region empor, wobei die Kruste im Bereich des Grabensystems aufgerissen wurde. Eine Vermutung besagt, dass diese vulkanische Tätigkeit durch ein Impaktereignis ausgelöst wurde, dessen Einschlagstelle das Hellas-Becken auf der gegenüberliegenden Seite des Mars sei. 2007 wurden im Nordosten von Arsia Mons sieben tiefere Schächte mit 100 bis 250 Metern Durchmesser entdeckt. +Vulkane +Olympus Mons, der mit 26 km höchste Berg im Sonnensystem +Die komplexe Caldera des Olympus Mons + +Dem Hellas-Becken exakt gegenüber befindet sich der Vulkanriese Alba Patera. Er ragt unmittelbar am Nordrand des Tharsis-Rückens rund 6 km über das umgebende Tiefland und ist mit einem Basisdurchmesser von über 1200 km der flächengrößte Vulkan im Sonnensystem. Patera ist die Bezeichnung für unregelmäßig begrenzte Vulkane mit flachem Relief. Alba Patera ist anscheinend einmal durch einen Kollaps in sich zusammengefallen. + +Unmittelbar westlich neben dem Tharsis-Rücken und südwestlich von Alba Patera ragt der höchste Vulkan, Olympus Mons, 26,4 km über die Umgebung des nördlichen Tieflands. Mit einer Gipfelhöhe von etwa 21,3 km über dem mittleren Null-Niveau ist er die höchste bekannte Erhebung im Sonnensystem. + +Ein weiteres, wenn auch weniger ausgedehntes vulkanisches Gebiet ist die Elysium-Region nördlich des Äquators mit den Schildvulkanen Elysium Mons, Hecates Tholus und Albor Tholus. +Stromtäler +Kasei Vallis, das größte Stromtal des Mars + +Auf der Marsoberfläche verlaufen Stromtäler, die mehrere hundert Kilometer lang und mehrere Kilometer breit sein können. Die heutigen Trockentäler beginnen ziemlich abrupt und haben keine Zuflüsse. Die meisten entspringen an den Enden der Mariner-Täler und laufen nördlich im Chryse-Becken zusammen. In den Tälern erheben sich mitunter stromlinienförmige Inseln. Sie weisen auf eine vergangene Flutperiode hin, bei der über einen geologisch relativ kurzen Zeitraum große Mengen Wasser geflossen sein müssen. Es könnte sich um Wassereis gehandelt haben, das sich unter der Marsoberfläche befand, danach durch vulkanische Prozesse geschmolzen wurde und dann abgeflossen ist. + +Darüber hinaus finden sich an Abhängen und Kraterrändern Spuren von Erosionen, die möglicherweise ebenfalls durch flüssiges Wasser verursacht wurden. + +2006 proklamierte die NASA einen einzigartigen Fund: Auf einigen NASA-Fotografien, die im Abstand von sieben Jahren vom Mars gemacht wurden, lassen sich Veränderungen auf der Marsoberfläche erkennen, die eine gewisse Ähnlichkeit mit Veränderungen durch fließendes Wasser haben. Innerhalb der NASA wird nun diskutiert, ob es neben Wassereis auch flüssiges Wasser geben könnte.[19] +Delta-Strukturen + +In alten Marslandschaften, z. B. im Eberswalde-Krater auf der Südhalbkugel oder in der äquatornahen Hochebene Xanthe Terra, finden sich typische Ablagerungen einstiger Flussdeltas. +Tharsis-Tholus-Streifen, aufgenommen mit der Hirise-Kamera des Mars Reconnaissance Orbiters. Der Streifen ist links in der Mitte zu sehen. Rechts sind die Ausläufer von Tharsis Tholus. + +Seit längerem vermutet man, dass die tief eingeschnittenen Täler in Xanthe Terra einst durch Flüsse geformt wurden. Wenn ein solcher Fluss in ein größeres Becken, beispielsweise einen Krater, mündete, lagerte er erodiertes Gesteinsmaterial als Sedimente ab. Die Art der Ablagerung hängt dabei von der Natur dieses Beckens ab: Ist es mit dem Wasser eines Sees gefüllt, so bildet sich ein Delta. Ist das Becken jedoch trocken, so verliert der Fluss an Geschwindigkeit und versickert langsam. Es bildet sich ein sogenannter Schwemmkegel, der sich deutlich vom Delta unterscheidet. + +Jüngste Analysen von Sedimentkörpern auf Basis von Orbiter-Fotos weisen an zahlreichen Stellen in Xanthe Terra auf Deltas hin – Flüsse und Seen waren in der Marsfrühzeit also recht verbreitet.[20] +Dark Slope Streaks + +Dunkle Streifen an Hängen sind auf dem Mars häufig zu sehen. Sie treten an steilen Hängen von Kratern, Mulden und Tälern auf und werden mit zunehmendem Alter heller. Manchmal beginnen sie in einem kleinen punktförmigen Bereich und werden dann zunehmend breiter. Man beobachtete, dass sie sich um Hindernisse, wie Mulden, weiterbewegen. + +Es wird angenommen, dass die Farbe von dunklen darunterliegenden Schichten stammt, die durch Lawinen von hellem Staub freigelegt werden. Es wurden jedoch auch andere Hypothesen aufgestellt, wie Wasser oder sogar der Wuchs von Organismen. Das Interessanteste an diesen dunklen Streifen (engl. dark slope streaks) ist, dass sie sich auch heute noch bilden.[21] +Chaotische Gebiete + +Auf dem Mars gibt es zahlreiche Regionen mit einer Häufung von unterschiedlich großen Gesteinsbrocken und tafelbergähnlichen Erhebungen. Sie werden auch „chaotische Gebiete“ genannt. Ariadnes Colles ist mit einer Fläche von etwa 29.000 km² so ein Gebiet. Es liegt im Terra Sirenum, einem südlichen Hochland des Mars. Dabei haben die Blöcke Ausmaße von einem bis zu zehn Kilometern Ausdehnung. Die größeren Blöcke ähneln Tafelbergen mit Erhebungen von bis zu 300 Metern. + +Es treten hierbei riefenartige Strukturen und „Runzelrücken“ (engl. wrinkle ridges) auf. Die Ursachen dafür sind vulkanisch-tektonische Bewegungen.[22] +Gesteinsschichten und Ablagerungen +Salzlager + +Mit Hilfe der Sonde Mars Odyssey wies die NASA ein umfangreiches Salzlager in den Hochebenen der Südhalbkugel des Mars nach. Vermutlich entstanden diese Ablagerungen durch Oberflächenwasser vor etwa 3,5 bis 3,9 Milliarden Jahren.[23] +Carbonatvorkommen + +Mit Hilfe der Compact Reconnaissance Imaging Spectrometer for Mars (CRISM) an Bord der NASA-Sonde Mars Reconnaissance Orbiter konnten Wissenschaftler Carbonat-Verbindungen in Gesteinsschichten rund um das knapp 1500 Kilometer große Isidis-Einschlagbecken nachweisen. Demnach wäre das vor mehr als 3,6 Milliarden Jahren existierende Wasser hier nicht sauer, sondern eher alkalisch oder neutral gewesen. + +Carbonatgestein entsteht, wenn Wasser und Kohlendioxid mit Kalzium, Eisen oder Magnesium in vulkanischem Gestein reagiert. Bei diesem Vorgang wird Kohlendioxid aus der Atmosphäre in dem Gestein eingelagert. Dies könnte bedeuten, dass der Mars früher eine dichte kohlendioxidreiche Atmosphäre hatte, wodurch ein wärmeres Klima möglich wurde, in dem es auch flüssiges Wasser gab.[24] + +Mit Hilfe von Daten des MRO wurden 2010 Gesteine entdeckt, die durch kosmische Einschläge aus der Tiefe an die Oberfläche befördert worden waren. Anhand ihrer spezifischen spektroskopischen Fingerabdrücke konnte festgestellt werden, dass sie hydrothermal (unter Einwirkung von Wasser) verändert wurden. Neben diesen Karbonat-Mineralen wurden auch Silikate nachgewiesen, die vermutlich auf die gleiche Weise entstanden sind. Dieser neue Fund beweise, dass es sich dabei nicht um örtlich begrenzte Vorkommen handele, sondern dass Karbonate in einer sehr großen Region des frühen Mars entstanden seien.[25] +Hämatitkügelchen +Hämatitkügelchen auf dem Felsen „Berry Bowl“ + +Die Marssonde Opportunity fand im Gebiet des Meridiani Planum millimetergroße Kügelchen des Eisenminerals Hämatit. Diese könnten sich vor Milliarden Jahren unter Einwirkung von Wasser abgelagert haben. Darüber hinaus wurden Minerale gefunden, die aus Schwefel-, Eisen- oder Bromverbindungen aufgebaut sind, wie zum Beispiel Jarosit. Auf der entgegengesetzten Hemisphäre[26] des Mars fand die Sonde Spirit in den „Columbia Hills“ das Mineral Goethit, das ausschließlich unter dem Einfluss von Wasser gebildet werden kann. +Kieselsäure + +Forscher entdeckten 2010 mit Hilfe von MRO Ablagerungen auf einem Vulkankegel, die von Wasser verursacht wurden. Sie konnten das Mineral als Kieselsäurehydrat identifizieren, das nur in Verbindung mit Wasser entstanden sein kann. Die Wissenschaftler nehmen an, dass, falls es auf dem Mars Leben gegeben hat, es sich dort in der hydrothermalen Umgebung am längsten hätte halten können.[27] +Polkappen +Die Nordpolregion, aufgenommen von Mars Global Surveyor +→ Hauptartikel: Polkappen des Mars + +Der Mars besitzt zwei auffällige Polkappen, die zum größten Teil aus gefrorenem Kohlendioxid (Trockeneis) sowie einem geringen Anteil an Wassereis zusammengesetzt sind. Die nördliche Polkappe hat während des nördlichen Marssommers einen Durchmesser von rund 1000 Kilometern. Ihre Dicke wird auf 5 km geschätzt. Die südliche Polkappe ist mit 350 km Durchmesser und einer Dicke von 1½ km weniger ausgedehnt. Die Polarkappen zeigen spiralförmige Einschnitte, deren Entstehung bislang nicht geklärt ist. + +Wenn im Sommer die jeweiligen Polkappen teilweise abschmelzen, werden darunter geschichtete Ablagerungen sichtbar, die möglicherweise abwechselnd aus Staub und Eis zusammengesetzt sind. Im Marswinter nimmt der Durchmesser der dann jeweils der Sonne abgewandten Polkappe durch ausfrierendes Kohlendioxid wieder zu. + +Da ein größerer, stabilisierender Mond fehlt, taumelt der Mars mit einer Periode von etwa 5 Millionen Jahren. Die Polarregionen werden daher immer wieder so stark erwärmt, dass das Wassereis schmilzt. Durch das abfließende Wasser entstehen die Riemen und Streifen an den Polkappen. +Wasservorkommen + +Der Mars erscheint heute als trockener Wüstenplanet. Die bislang vorliegenden Ergebnisse der Marsmissionen lassen jedoch den Schluss zu, dass die Marsatmosphäre in der Vergangenheit (vor Milliarden Jahren) wesentlich dichter war und auf der Oberfläche des Planeten reichlich flüssiges Wasser vorhanden war. +Eisvorkommen an den Polen +Die Südpolregion, aufgenommen von Viking Orbiter + +Durch Radarmessungen mit der Sonde Mars Express wurden in der Südpolarregion, dem Planum Australe, Ablagerungsschichten mit eingelagertem Wassereis entdeckt, die weit größer und tiefreichender als die hauptsächlich aus Kohlendioxideis bestehende Südpolkappe sind. Die Wassereisschichten bedecken eine Fläche, die fast der Größe Europas entspricht, und reichen in eine Tiefe von bis zu 3,7 Kilometern. Das in ihnen gespeicherte Wasservolumen wird auf bis zu 1,6 Millionen Kubikkilometer geschätzt – circa zwei Drittel des irdischen Grönlandeispanzers – was laut der Europäischen Weltraumorganisation (ESA) ausreichen würde, die Marsoberfläche mit einer etwa 11 Meter dicken Wasserschicht zu bedecken.[28] +Weitere Eisvorkommen +Beobachtete Veränderungen könnten Anzeichen für fließendes Wasser innerhalb der letzten Jahre sein.[19] + +Die schon lange gehegte Vermutung, dass sich unter der Oberfläche des Mars Wassereis befinden könnte, erwies sich 2005 durch Entdeckungen der ESA-Sonde Mars Express als richtig. + +Geologen gehen von wiederkehrenden Vereisungsperioden auf dem Mars aus, ähnlich irdischen Eiszeiten. Dabei sollen Gletscher bis in subtropische Breiten vorgestoßen sein. Die Forscher schließen dies aus Orbiter-Fotos, die Spuren einstiger Gletscher in diesen äquatornahen Gebieten zeigen. Zusätzlich stützen auch Radarmessungen aus der Umlaufbahn die Existenz beträchtlicher Mengen an Bodeneis in ebendiesen Gebieten. Diese Bodeneisvorkommen werden als Reste solcher „Mars-Eiszeiten“ gedeutet.[29] + +Auf der Europäischen Planetologenkonferenz EPSC im September 2008 in Münster wurden hochauflösende Bilder des Mars Reconnaissance Orbiters der NASA vorgestellt, die jüngste Einschlagkrater zeigen. Wegen der sehr dünnen Atmosphäre stürzen die Meteoriten praktisch ohne Verglühen auf die Marsoberfläche. Die fünf neuen Krater, die nur drei bis sechs Meter Durchmesser und eine Tiefe von 30 bis 60 cm aufweisen, wurden in mittleren nördlichen Breiten gefunden. Sie zeigen an ihrem Boden ein gleißend weißes Material. Wenige Monate später waren die weißen Flecken durch Sublimation verschwunden. Damit erhärten sich die Hinweise, dass auch weit außerhalb der Polgebiete Wassereis dicht unter der Marsoberfläche begraben ist.[30][31] +Flüssiges Wasser + +Unter der Kryosphäre des Mars werden große Mengen flüssigen Wassers vermutet. Nahe oder an der Oberfläche ist es für flüssiges Wasser zu kalt, und Eis würde langsam verdunsten, da der Partialdruck von Wasser in der Marsatmosphäre zu gering ist. + +Es gibt jedoch Hinweise, dass die Raumsonde Phoenix Wassertropfen auf der Oberfläche entdeckt habe. Dabei könnten Perchlorate als Frostschutz wirken. Diese Salze haben die Eigenschaft, Wasser anzuziehen. Dies kann auch Wasserdampf aus der Atmosphäre sein. Bei ausreichender Konzentration der Salze könnte Wasser sogar bis −70 °C flüssig bleiben. Durch eine Durchmischung mit Perchloraten könnte Wasser auch unter der Oberfläche in flüssigem Zustand vorhanden sein.[32] 2010 fanden Forscher der Uni Münster Belege dafür, dass zumindest im Frühjahr und in Kratern wie dem Russell-Krater flüssiges Wasser auf der Marsoberfläche existiert. Auf Fotos, die vom Mars Reconnaissance Orbiter aufgenommen wurden, entdeckten sie an steilen Hängen Erosionsrinnen, die sich zwischen November 2006 und Mai 2009 verlängert hatten. Dass die Rinnen nach unten dünner werden, deuten die Forscher als Versickern,[33] andere als Verdunsten.[34] + +Eine alternative Erklärung für die Erosionsrinnen schlugen Wissenschaftler der NASA 2010 vor: Kohlendioxid, das sich im marsianischen Winter bei unter -100 °C aus der Atmosphäre an den Berghängen als Trockeneis ansammelt, bei Erwärmung des Planeten als sublimiertes Gas die Hänge hinab"fließt" und dabei Staub erodiert.[35][36] + +Mit dem abbildenden Spektrometer (CRISM) des Mars Reconnaissance Orbiters konnten Spektren von aktiven (jahreszeitlich dunkleren) Rinnen gewonnen werden, deren Auswertung, 2015 veröffentlicht,[37] Magnesiumperchlorat, Magnesiumchlorat und Natriumperchlorat ergaben. +Siehe auch: Extraterrestrischer Ozean +Innerer Aufbau +Illustration des vermuteten Marsaufbaus + +Ãœber den inneren Aufbau des Mars ist nur wenig bekannt, da bislang nur begrenzt seismische Messungen vorgenommen werden konnten. + +Sein Inneres gliedert sich ähnlich dem Schalenaufbau der Erde in eine Kruste, einen Gesteinsmantel und einen Kern, der überwiegend aus Eisen und zu etwa 14 bis 17 Prozent aus Schwefel besteht. Der Kern beinhaltet etwa doppelt so viele leichte Elemente wie der Erdkern. Deshalb ist die Dichte des Kerns niedriger, als es bei einem reinen Eisenkern der Fall wäre.[38] + +Laut neueren experimentellen Simulationen der Bedingungen in der Ãœbergangszone zwischen Mantel und Kern (Messungen des Mars Global Surveyor ergaben eine Temperatur von 1500 Grad Celsius und einen Druck von 23 Gigapascal) hat der Kern des Mars im Unterschied zu dem der Erde keinen inneren festen Bereich, sondern ist vollständig flüssig.[39] Dies belegt auch die Analyse der Bahndaten des Mars Global Surveyor. Dabei konnte nachgewiesen werden, dass der Mars einen flüssigen Kern mit einem Radius zwischen 1520 und 1840 km besitzt und damit eine höhere Temperatur hat, als zuvor angenommen wurde. + +Der Kern ist von einem Mantel aus Silicaten umgeben, der viele der tektonischen und vulkanischen Merkmale des Planeten formte, nun aber inaktiv zu sein scheint. Die durchschnittliche Dicke der Planetenkruste beträgt etwa 50 km, mit einem Maximum von 125 km.[38] Im Vergleich dazu ist die Erdkruste mit einer Dicke von durchschnittlich 40 km nur etwa ein Drittel so dick, wenn man die relative Größe der beiden Planeten berücksichtigt. +Magnetfeld +Magnetisierung des Mars – Rot und Blau kennzeichnen entgegengesetzte Richtungen des Magnetfelds, ein Drittel der Südhalbkugel + +Anders als die Erde und der Merkur besitzt der Mars kein globales Magnetfeld mehr, seit er es ca. 500 Millionen Jahre nach seiner Entstehung verlor. Vermutlich erlosch es, als der Zerfall radioaktiver Elemente nicht mehr genügend Wärmeenergie produzierte, um im flüssigen Kern Konvektionsströmungen anzutreiben. Weil der Mars keinen festen inneren Kern besitzt, konnte er den Dynamo-Effekt nicht auf die gleiche Art aufbauen wie die Erde. + +Dennoch ergaben Messungen einzelne und sehr schwache lokale Magnetfelder. Die Messung des Magnetfeldes wird erschwert durch die Magnetisierung der Kruste mit Feldstärken von bis zu 220 Nanotesla und durch externe Magnetfelder mit Stärken zwischen wenigen Nanotesla und bis zu 100 Nanotesla, die durch die Wechselwirkung des Sonnenwindes mit der Marsatmosphäre entstehen und zeitlich sehr stark variieren. Nach den Analysen der Daten des Mars Global Surveyor konnte die Stärke des Magnetfeldes trotzdem sehr genau bestimmt werden – sie liegt bei weniger als 0,5 Nanotesla gegenüber 30 bis 60 Mikrotesla des Erdmagnetfeldes. + +Messungen von Magnetfeldlinien durch Mars Global Surveyor ergaben, dass Teile der planetaren Kruste durch das einstige Magnetfeld stark magnetisiert sind, aber mit unterschiedlicher Orientierung, wobei gleichgerichtete Bänder von etwa 1000 km Länge und 150 km Breite auftreten. Ihre Größe und Verteilung erinnert an die streifenförmigen Magnetanomalien auf den Ozeanböden der Erde. Durch sie wurde die Theorie der Plattentektonik gestützt, weshalb 1991 auch eine ähnliche Theorie für den Mars entwickelt wurde. Magnetische Beobachtungen auf dem Mars sind jedoch noch nicht detailliert genug, um sichere Schlussfolgerungen zu erlauben oder gar die Theorie zu bestätigen. + +Möglicherweise werden bei der mit der Zeit zwangsläufigen Abkühlung des Marskerns durch die damit einsetzende Auskristallisation des Eisens und die freigesetzte Kristallisationswärme wieder Konvektionen einsetzen, die ausreichen, dass der Planet in ein paar Milliarden Jahren wieder über ein globales Magnetfeld in alter Stärke verfügt.[39] +Monde +Die Umlaufbahnen von Phobos und Deimos +Phobos (oben) und Deimos (unten) im Größenvergleich + +Zwei kleine Monde, Phobos und Deimos (griech. Furcht und Schrecken), umkreisen den Mars. Sie wurden 1877 von dem US-amerikanischen Astronomen Asaph Hall entdeckt und nach den in der Ilias überlieferten beiden Begleitern, die den Wagen des Kriegsgottes Ares (lat. Mars) ziehen, benannt. + +Phobos (Durchmesser 26,8 × 22,4 × 18,4 km) und Deimos (Durchmesser 15,0 × 12,2 × 10,4 km) sind zwei unregelmäßig geformte Felsbrocken. Möglicherweise handelt es sich um Asteroiden, die vom Mars eingefangen wurden. Phobos’ große Halbachse beträgt 9.376 km, diejenige von Deimos 23.459 km. Phobos ist damit kaum mehr als 6.000 km von der Oberfläche des Mars entfernt, der Abstand ist geringer als der Durchmesser des Planeten. + +Die periodischen Umlaufbewegungen der beiden Monde befinden sich mit der Größe von 0,31891 (Phobos) und 1,262 Tagen (Deimos) zueinander in einer 1:4-Bahnresonanz. + +Die Umlaufzeit von Phobos ist kürzer als die Rotationszeit von Mars. Der Mond kommt dem Planeten durch die Gezeitenwechselwirkung auf einer Spiralbahn langsam immer näher und wird schließlich auf diesen stürzen oder durch die Gezeitenkräfte auseinandergerissen werden, so dass er für kurze Zeit zu einem Marsring wird. Für ihn berechneten DLR-Forscher, basierend auf neueren Daten der europäischen Raumsonde Mars Express, dass dies in ca. 50 Millionen Jahren geschehen wird. Deimos wird dagegen in einer noch ferneren Zukunft dem Mars entfliehen. Er driftet durch die Gezeitenwechselwirkung langsam nach außen, wie alle Monde, die langsamer (und nicht retrograd) um einen Planeten kreisen, als dieser rotiert. + +Ihre Existenz war schon lange vorher mehrmals literarisch beschrieben worden, zuletzt von Voltaire, der in seiner 1750 erschienenen Geschichte Micromégas über zwei Marsmonde schreibt. Es ist wahrscheinlich, dass Voltaire diese Idee von Jonathan Swift übernahm, dessen Buch Gullivers Reisen 1726 erschienen war. Darin wird im dritten Teil beschrieben, die Astronomen des Landes Laputa hätten „ebenfalls zwei kleinere Sterne oder Satelliten entdeckt, die um den Mars kreisen, wovon der innere vom Zentrum des Hauptplaneten genau drei seiner Durchmesser entfernt ist und der äußere fünf.“ Es wird vermutet, dass Swift von einer Fehlinterpretation Johannes Keplers gehört hatte. Der hatte das Anagramm, das Galileo Galilei 1609 an ihn schickte, um ihm die Entdeckung der Phasen der Venus mitzuteilen, als die Entdeckung zweier Marsmonde aufgefasst. +Entstehungsgeschichte +Datei:Mars.ogvMediendatei abspielen +Animation, welche die Topographie des Mars zeigt. Olympus Mons → Mariner-Täler → Mars Südpol → Hellas-Becken → Mars Nordpol + +Anhand der astrogeologischen Formationenvielfalt und der Verteilung von Einschlagskratern kann ein Großteil der Geschichte des Planeten abgeleitet werden. Der Mars entstand, wie die übrigen Planeten des Sonnensystems, vor etwa 4,5 Milliarden Jahren durch Zusammenballung kleinerer Körper, sogenannter Planetesimale, innerhalb der protoplanetaren Scheibe zu einem Protoplaneten. Vor 4 Milliarden Jahren bildete der im Innern noch glutflüssige planetare Körper eine feste Gesteinskruste aus, die einem heftigen Bombardement von Asteroiden und Kometen ausgesetzt war. +Noachische Periode + +Die ältesten der heute noch vorhandenen Formationen, wie das Hellas-Becken, und die verkraterten Hochländer, wie Noachis Terra, wurden vor 3,8 bis 3,5 Milliarden Jahren, in der so genannten Noachischen Periode, gebildet. In dieser Periode setzte die Zweiteilung der Marsoberfläche ein, wobei die nördlichen Tiefländer gebildet wurden. Durch starke vulkanische Eruptionen wurden weite Teile des Planeten von Ablagerungen aus vulkanischer Lava und Asche bedeckt. Diese wurden an vielen Stellen durch Wind und Wasser wieder abgetragen und ließen ein Netzwerk von Tälern zurück. +Hesperianische Periode + +Das geologische „Mittelalter“ des Mars wird als Hesperianische Periode bezeichnet. Sie umfasst den Zeitraum von vor 3,5 bis 1,8 Milliarden Jahren. In dieser Periode ergossen sich riesige Lavamengen aus ausgedehnten Spalten in der Marskruste und bildeten weite Ebenen, wie Hesperia Planum. Es entstanden auch die ältesten Vulkane der Tharsis- und der Elysium-Region, wobei die Gesteinskruste stark verformt wurde und sich das Grabensystem der Mariner-Täler öffnete. Es bildeten sich die gewaltigen Stromtäler, in denen große Wassermengen flossen und sich stellenweise aufstauten. + +Es entwickelte sich auf dem Mars ein Wasserkreislauf. Im Unterschied zur Erde gab es jedoch keinen Wetterzyklus mit Verdunstung, Wolkenbildung und anschließendem Niederschlag. Das Wasser versickerte im Untergrund und wurde später durch hydrothermale Prozesse wieder an die Oberfläche getrieben. Da jedoch der Planet immer weiter abkühlte, endete dieser Prozess vor etwa 1,5 Milliarden Jahren, und es hielten sich nur noch Gletscher an der Oberfläche. Zeichen dieser Aktivität sind vor kurzem entdeckte Moränen am Olympus Mons.[40] +Amazonische Periode + +Das jüngste geologische Zeitalter des Mars wird als Amazonische Periode bezeichnet und begann vor 1,8 Milliarden Jahren. In dieser Phase entstanden die jüngeren Vulkane der Tharsis- und der Elysium-Region, aus denen große Lavamassen flossen. So bildeten sich weite Ebenen aus wie zum Beispiel Amazonis Planitia. + +2008 fanden Forscher Hinweise auf Geysire auf dem Mars, die vor einigen Millionen Jahren aktiv gewesen sein dürften. Dabei hätten sie Fontänen von kohlensäurehaltigem Wasser einige Kilometer weit in die Höhe geschossen. Darauf deuten auch die Formen von Ablagerungen hin, die britische Forscher in der Nähe zweier ausgedehnter Grabensysteme entdeckten. Wahrscheinlich wurden diese Eruptionen durch Blasen aus Kohlendioxid ausgelöst. Dadurch wurde das Wasser aus einer Tiefe von bis zu vier Kilometern durch Spalten im Marsboden an die Oberfläche gedrückt. Die Fontänen müssen dabei mit einem so großen Druck herausgepresst worden sein, dass das schlammige Wasser erst in einer Entfernung von mehreren Kilometern von der Austrittsstelle wieder auf den Boden regnete oder, bedingt durch die tiefen Temperaturen, als Hagel niederging.[41] + +Gegenwärtig wird die Oberfläche des Mars hauptsächlich durch Winderosion und Hangrutschung geformt. +Erforschung + +Aufgrund seiner großen Helligkeit war der Mars schon im frühen Altertum als Planet bekannt. Wegen seiner langen Planetenschleifen (die alle 2 Jahre in der Opposition auftreten) galten seine Bewegungen den Ägyptern als unvorhersehbar. Den Babyloniern gelang es zwar, sie näherungsweise vorauszusagen, sie schrieben die Bahnanomalien aber den Launen und der Gewalttätigkeit des Gottes Nergal zu. +Vor dem Raumfahrtzeitalter +Marsoberfläche nach Schiaparelli (1888) +Mars auf einer astronomischen Zeichnung des 19. Jahrhunderts (Trouvelot, 1881) + + Tycho Brahe (1546–1601) vermaß die Planetenpositionen des Mars mit bis dahin nicht gekannter Genauigkeit und ermöglichte es so Johannes Kepler (1571–1630), die elliptische Bahn des Planeten zu berechnen und die drei Keplerschen Gesetze abzuleiten. + Christiaan Huygens entdeckte 1659 eine dunkle, dreieckige Zone (Syrtis Major) auf der Marsoberfläche. Aus deren Positionsveränderungen errechnete er die Eigenrotation des Mars zu 24,5 Stunden (heutiger Wert: 24,623 Stunden). + Giovanni Domenico Cassini beschrieb 1666 die weißen Polkappen des Mars. + Wilhelm Herschel bestimmte 1784 die Neigung der Rotationsachse gegenüber der Umlaufbahn mit 25° (heutiger Wert 25,19°). + Wilhelm Beer fertigte 1830 die erste Marskarte an, Angelo Secchi 1863 schon in Farbe. + Richard Proctor veröffentlichte 1869 eine detaillierte Marskarte, die er aus Zeichnungen von William Rutter Dawes erstellte. + Giovanni Schiaparelli nahm 1877 auf der Marsoberfläche zarte Linienstrukturen wahr, die er „Canali“ (italienisch für „Rinnen“ oder „Gräben“) nannte und in eine detaillierte Karte eintrug. Er machte zunächst keine Angaben über den Ursprung der Canali (die er für breiter als 100 km schätzte), doch wurden sie in englischen Medien fälschlich als „Channel“ (Kanäle) übersetzt und bald als Werk intelligenter Marsbewohner interpretiert. Auf älteren Marskarten erhielten viele dieser Linien auch Namen. Während einige Astronomen Schiaparellis Beobachtungen bestätigten, wurde die Existenz der Canali von anderen angezweifelt und als Ergebnis optischer Täuschungen bezeichnet. Erst der Vorbeiflug der amerikanischen Mariner-Sonden beendete die Spekulationen, denn Fotos der Marsoberfläche zeigten keine so breiten Rinnen. Drei Canali entsprechen aber den riesigen Canyons Valles Marineris, andere zeichnen Geländestufen und Schattenlinien nach, einige auch längere Kraterketten. + +→ Hauptartikel: Marskanäle + + Asaph Hall entdeckt bei der günstigen Opposition 1877 die beiden Marsmonde Phobos und Deimos. + Percival Lowell gründet 1894 das Lowell-Observatorium in Arizona, um die Marskanäle, ihre jahreszeitlichen Verfärbungen und allfällige Lebensspuren zu erforschen. Spektroskopisch findet man biologische Moleküle, die sich allerdings später als terrestrisch erweisen. In der Atmosphäre werden Spektrallinien von Sauerstoff entdeckt, dessen Volumsanteil aber überschätzt wird. + Eugène Antoniadi bestätigte zunächst die Marskanäle, kam aber 1909 am Riesenteleskop Meudon zum Schluss, sie würden nur in kleineren Fernrohren als solche erscheinen. In seinen detaillierten Marskarten – die bis zu den ersten Marssonden kaum mehr übertroffen wurden – zeichnete er sie als Folge diffuser Flecken ein. + Gerard Kuiper wies in den 1950ern Kohlendioxid in der Marsatmosphäre nach und glaubte bis zu den ersten Marssonden an die mögliche Existenz von Moosen oder Flechten. + +Im Raumfahrtzeitalter +Die erste Nahaufnahme vom Mars, aufgenommen von Mariner 4 + +Viele unbemannte Raumsonden wurden schon zum Mars entsandt, von denen einige sehr erfolgreich waren. Etwa die Hälfte der Missionen endete in einem Misserfolg, die meisten davon waren sowjetische Sonden. Im Unterschied zur Erkundung des Erdmondes gibt es bis heute keine Gesteinsproben, die vom Mars geholt wurden, so dass Marsmeteoriten die einzige Möglichkeit sind, Material vom Mars in irdischen Laboratorien zu erforschen. Bislang hat es auch noch keine bemannte Marsmission gegeben — ein aktueller Ansatz dafür ist Mars One. +→ Hauptartikel: Chronologie der Marsmissionen +1960er Jahre +Darstellung auf einer ungarischen Sondermarke von 1964 + +Die beiden sowjetischen Sonden Marsnik 1 und 2 wurden im Oktober 1960 gestartet, um am Mars vorbeizufliegen, erreichten aber noch nicht einmal die Erdumlaufbahn. 1962 versagten drei weitere sowjetische Sonden (Sputnik 22, Mars 1 und Sputnik 24), zwei von ihnen blieben im Erdorbit, die dritte verlor auf dem Weg zum Mars den Kontakt mit der Erde. Auch ein weiterer Versuch im Jahre 1964 schlug fehl. + +Zwischen 1962 und 1973 wurden zehn Mariner-Raumsonden vom Jet Propulsion Laboratory der NASA entwickelt und gebaut, um das innere Sonnensystem zu erforschen. Es waren relativ kleine Sonden, die meistens nicht einmal eine halbe Tonne wogen. + +Mariner 3 und Mariner 4 waren identische Raumsonden, die am Mars vorbeifliegen sollten. Mariner 3 wurde am 5. November 1964 gestartet, aber die Transportverkleidung löste sich nicht richtig, und die Sonde erreichte den Mars nicht. + +Drei Wochen später, am 28. November 1964, wurde Mariner 4 erfolgreich auf eine achtmonatige Reise zum Roten Planeten geschickt. Am 15. Juli 1965 flog die Sonde am Mars vorbei und lieferte die ersten Nahaufnahmen – insgesamt 22 Fotos – des Planeten. Die Bilder zeigten mondähnliche Krater, von denen einige mit Reif bedeckt zu sein scheinen. + +1969 folgten Mariner 6 und Mariner 7 und lieferten insgesamt 200 Fotos. +1970er Jahre + +1971 missglückte der Start von Mariner 8, dafür erhielt die NASA im gleichen Jahr von Mariner 9 mehrere tausend Bilder. + +Ebenfalls 1971 landete mit der sowjetischen Mars 3 die erste Sonde weich auf dem Mars, nachdem Mars 2 wenige Tage zuvor gescheitert war. Der Funkkontakt brach jedoch 20 Sekunden nach der Landung ab. Mögliche Ursache war ein gerade tobender globaler Staubsturm, der den Lander umgeworfen haben könnte. +Bild von Viking 1. Der große Felsen links von der Mitte ist etwa zwei Meter breit. Er wurde Big Joe getauft. + +In den 1970er-Jahren landeten die Viking-Sonden erfolgreich auf dem Mars und lieferten die ersten Farbbilder sowie Daten von Bodenproben: Viking 1 schaffte am 20. Juli 1976 als erste US-amerikanische Sonde eine weiche Landung. Die Sowjetunion versuchte noch weitere Landungen auf dem Mars, scheiterte jedoch. +1980er Jahre + +Die einzigen Raumsonden, die in den 1980er Jahren zum Mars flogen, waren die beiden sowjetischen Fobos-Sonden. Sie wurden 1988 von Baikonur aus gestartet und sollten den Mars und seinen Mond Phobos untersuchen. Dafür waren sie im Rahmen einer internationalen Kooperation neben sowjetischen auch mit zahlreichen westlichen Instrumenten bestückt. Der Kontakt zu Fobos 1 brach jedoch schon auf dem Weg zum Mars wegen eines falschen Steuerbefehls ab. Fobos 2 erreichte eine Marsumlaufbahn und einige Daten und Bilder vom Mars wurden zur Erde übertragen. Danach wurde die Sonde zu Phobos gelenkt. Jedoch brach kurz vor dem Rendezvous auch der Kontakt zu Fobos 2 ab. +1990er Jahre + +1992 wurde die US-Sonde Mars Observer gestartet. Sie ging 1993 kurz vor dem Einschwenken in die Umlaufbahn verloren. + +Am 16. November 1996 startete Mars 96, die erste russische Raumsonde seit dem Zusammenbruch der Sowjetunion. Doch versagte die Proton-Trägerrakete, so dass Mars 96 wieder in die Erdatmosphäre eintrat und verglühte. +Der Marsrover Sojourner + +Besonderes Aufsehen erregte 1997 der Mars Pathfinder, bei dem zum ersten Mal ein kleines Marsmobil, der Rover Sojourner, eingesetzt wurde. Er landete publikumswirksam am 4. Juli, dem amerikanischen Unabhängigkeitstag, und lieferte viele Aufnahmen von der Umgebung der Landestelle, die von der NASA zum ersten Mal sofort im Internet veröffentlicht wurden. + +Eine weitere erfolgreiche Mission war 1997 die des Mars Global Surveyor, bei der die Marsoberfläche in einer hohen Auflösung kartografiert wurde. Am 2. November 2006 – fünf Tage vor dem 10-jährigen Jubiläum seines Starts – brach der Kontakt mit dem Satelliten ab. + +Das Scheitern der Marssonden Mars Climate Orbiter, der wegen eines Programmierfehlers in der Navigation verlorenging, und Mars Polar Lander, der wahrscheinlich wegen eines fehlerhaften Sensors bei der Landung aus größerer Höhe abstürzte, stellte 1999 einen herben Rückschlag für die Marsforschung dar. + +Auch die 1998 gestartete japanische Raumsonde Nozomi konnte den Mars nicht erreichen. +2000er Jahre + +Seit dem 24. Oktober 2001 umkreist außer dem Global Surveyor noch 2001 Mars Odyssey den roten Planeten, der spezielle Instrumente zur Fernerkundung von Wasservorkommen an Bord hat. + +Von den bis 2002 insgesamt 33 Missionen zum Mars waren nur acht erfolgreich, allesamt US-amerikanisch. + +Am 2. Juni 2003 startete im Rahmen der ersten europäischen Marsmission die ESA-Raumsonde Mars Express mit dem Landegerät Beagle 2 erfolgreich zum Mars. Zwar landete Beagle 2 am 25. Dezember 2003 auf der Marsoberfläche, allerdings konnte der Funkkontakt niemals aufgebaut werden. 2014 wurde er auf Bildern des MRO entdeckt. Der Orbiter Mars Express arbeitet jedoch erfolgreich in der Marsumlaufbahn und konnte unter anderem viele Aufnahmen von Formationen machen, von denen man annimmt, dass sie ausgetrocknete oder ausgefrorene Flusstäler seien. Er kartiert den Planeten u. a. mittels Radar und einer Stereokamera im sichtbaren Licht, sowie spektroskopisch auch in Infrarot. Am 30. November 2005 fand die Sonde unter der Ebene Chryse Planitia ein Eisfeld mit 250 km Durchmesser. +Marsrover Opportunity (MER-B) + +Am 10. Juni 2003 wurde die US-amerikanische Marssonde Spirit (MER-A) zum Mars gestartet. An Bord befand sich ein Rover, der nach der Landung drei Monate lang Gesteinsproben entnehmen und nach Spuren von früher vorhandenem Wasser suchen sollte. Die Landung erfolgte am 4. Januar 2004 im Krater Gusev, in den das Ma'adim Vallis mündet. Im April 2009 fuhr sich der Rover in einer Sandanhäufung fest und konnte seit dem 22. März 2010 auch nicht mehr kontaktiert werden (Stand: März 2011). + +Am 8. Juli 2003 wurde die baugleiche Sonde Opportunity (MER-B) mit einer Delta-II-Rakete gestartet. Sie landete am 25. Januar 2004 in der Tiefebene Meridiani Planum nahe dem Marsäquator, fast genau gegenüber von Spirit.[26] Die vom Rover gesammelten Beweise, dass der Mars einst warm und feucht war, wurden im Jahresrückblick der Fachzeitschrift Science mit der Wahl zum „Durchbruch des Jahres 2004“ gewürdigt. Opportunity ist noch immer aktiv (Stand: April 2015). +vergrößern und Informationen zum Bild anzeigen +Das Panoramabild, von dem hier nur ein Ausschnitt zu sehen ist, wurde aus hunderten Einzelbildern montiert, die Opportunity vom 6. Oktober bis 6. November 2006 aufgenommen hat. Es zeigt annähernd in Echtfarben den Victoria-Krater vom Cap Verde + +Am 12. August 2005 wurde die US-Sonde Mars Reconnaissance Orbiter mit einer Atlas-V-Rakete auf die Reise geschickt und erreichte am 10. März 2006 den Mars. Sie soll ihn mit hochauflösenden Kameras kartografieren und auch nach geeigneten Landestellen für spätere Rover-Missionen suchen. Außerdem soll sie zur Hochgeschwindigkeits-Kommunikation zwischen zukünftigen Raumsonden auf der Marsoberfläche und der Erde dienen. +Sonnenuntergang auf dem Mars beim Krater Gusev (Spirit am 19. Mai 2005) + +2007 fotografierte Mars Reconnaissance sieben fast kreisrunde schwarze und strukturlosen Flecken, die im Nordosten des Marsvulkans Arsia Mons liegen.[42] Der größte, genannt Jeanne, hat einen Durchmesser von etwa 150 Meter. Eine Schrägaufnahme der sonnenbeschienenen Seitenwand im August 2007 zeigte, dass es sich um einen mindestens 78 Meter tiefen senkrechten Schacht handeln muss. Diese Strukturen sind sehr wahrscheinlich vulkanischer Natur und durch den Einbruch einer nicht mehr tragfähigen Deckschicht entstanden.[43] + +Am 26. Dezember 2007 machte die High Resolution Stereo Camera des Mars Express Aufnahmen von Eumenides Dorsum, einem Bergrücken westlich der Tharsis-Region. Die Aufnahmen zeigen kilometerlange lineare Strukturen, die von Kanälen unterbrochen sind. Es handelt sich um durch Winderosion entstandene Yardangs (Windhöcker bzw. Sandwälle). + +Mit der Sonde Mars Odyssey wies die NASA im März 2008 eine umfangreiche Salzlagerstätte in den Hochebenen der Südhalbkugel nach. Die Wissenschaftler des JPL in Pasadena meinen, sie habe sich vor 3,5 bis 3,9 Milliarden Jahren gebildet. Vermutlich entstanden die Salze durch mineralienreiches Grundwasser, das an die Oberfläche gelangte und dort verdunstete. Die Bilder von „Mars Odyssey“ zeigen kanalähnliche Strukturen, die in den Salzbecken enden.[23] Insgesamt wurden über 200 Gebiete mit Salzvorkommen ausgemacht, die zwischen 1 und 25 km² groß sind. Die Entdeckung deutet darauf hin, dass der Mars vor langer Zeit ein wärmeres und deutlich feuchteres Klima hatte.[44]. Solche Klimaschwankungen dürften durch aperiodische Änderungen der Rotationsachse entstehen, deren Neigung (derzeit 25°) zwischen 14 und 50° variiert.[45] +Die Orte der sieben erfolgreichen Marslandungen + +Am 26. Mai 2008 landete die Sonde Phoenix im nördlichen Polargebiet des Planeten. Sie suchte dort bis November 2008 im Boden nach Wassereis und „habitablen Zonen“, also für primitive Organismen bewohnbare Umgebungen. Ihr Roboterarm konnte Proben aus etwa 50 cm Tiefe holen, um sie dann in einem Minilabor zu analysieren. Phoenix entdeckte bei einer Grabung weiße Klümpchen, die nach einigen Tagen verschwanden. Man vermutete, dass es sich dabei um Wassereis handelt,[46] was am 31. Juli bestätigt wurde – beim Erhitzen einer Gesteinsprobe trat Wasserdampf aus.[47] Mit dem nasschemischen Labor MECA, das die wasserlöslichen Ionen im Marsboden bestimmte, konnten erhebliche Mengen an Perchloraten detektiert werden. Auf der Erde kommen Perchlorate in den ariden Wüstengebieten vor. Natriumperchlorat wird durch Oxidation von Natriumchlorid in der Atmosphäre erzeugt und dann mit dem Staub abgelagert. + + +2010er Jahre +Curiosity auf dem Mars + +Am 26. November 2011 um 15:02 UTC startete die Rover-Mission Mars Science Laboratory (Curiosity) der NASA mit einer Atlas V(541) von Cape Canaveral und landete am 6. August 2012 auf dem Mars. Der Rover kann weite Strecken zurücklegen und umfassende Untersuchungen eines großen Umkreises durchführen. Wichtigstes Projektziel sind geologische Analysen des Marsbodens. + +Am 18. November 2013 startete eine weitere NASA-Sonde zum Mars. Die Mission mit dem Projektnamen „Mars Atmosphere and Volatile Evolution“ (MAVEN) soll das Rätsel der verlorenen Atmosphäre aufklären.[48] Der Orbiter umkreist den Planeten seit dem 22. September 2014 und soll sich in fünf Tiefflügen annähern. Weiters wurde am 5. November 2013 eine indische Marsmission gestartet. Sie soll ebenfalls die Atmosphäre sowie verschiedene Oberflächenphänomene untersuchen.[49] + +ExoMars Rover ist ein europäischer Rover, dessen Start für 2020 geplant ist. Er soll speziell nach Spuren von Leben suchen. Die Finanzierung dieser Mission ist allerdings noch ungewiss. +Geplante Missionen + +Weitere Pläne der NASA und ESA zur Marserforschung enthalten unter anderem das Aussetzen von kleineren Flugzeugen in der Atmosphäre und – nach 2020 – die Rückführung von Marsproben zur Erde (Mission Mars Sample Return). +vergrößern und Informationen zum Bild anzeigen +Panoramabild der Marsoberfläche, aufgenommen von der Sonde Pathfinder + +Im Januar 2004 kündigte der US-amerikanische Präsident George W. Bush Anstrengungen der USA für eine bemannte Marsmission an. Im Rahmen des Raumfahrtprogramms Constellation plante die NASA diese Flüge für die Zeit nach 2020. Der ehemalige NASA-Direktor Michael Griffin nannte die Zeit bis 2037. Constellation wurde aber durch die Nachfolgeregierung unter Barack Obama aus Kostengründen gestrichen.[50] + +Auch das langfristig angelegte europäische Raumfahrtprogramm Aurora strebt insbesondere die Landung eines Menschen auf dem Mars an und plant sie für das Jahr 2033. + +Darüber hinaus existieren im Rahmen von Visionen einer Marskolonisation Vorstellungen, den Mars durch Terraforming in weiter Zukunft in einen für den Menschen lebensfreundlicheren Planeten umzuwandeln. Robert Zubrin, Edwin Aldrin und andere namhafte Stimmen in den Vereinigten Staaten von Amerika treten mittlerweile dafür ein, auf dem Mars unter dem Motto Mars to Stay schon in naher Zukunft die erste menschliche Siedlung zu begründen: Das sei möglich und sinnvoll, weil es wesentlich weniger Aufwand erfordere, die ersten Marsfahrer dauerhaft auf dem Planeten siedeln zu lassen, als sie sogleich wieder zur Erde zurückzuholen. +Möglichkeit von Leben +→ Hauptartikel: Leben auf dem Mars + +Die Ökosphäre (oder habitable Zone) des Sonnensystems reicht von 0,95 bis 1,37 AE Abstand zur Sonne. Im Sonnensystem befindet sich nur die Erde innerhalb dieses Gürtels um die Sonne, der Mars liegt knapp außerhalb. + +Höheres oder gar intelligentes Leben gibt es auf dem Mars nicht, Wissenschaftler halten jedoch primitive Lebensformen (Mikroben) tiefer im Boden, um vor UV-Strahlen geschützt zu sein, für denkbar.[51] Tatsächlich haben die in der Antarktis im Inneren von Gesteinen lebenden Pilzarten Cryomyces antarcticus und Cryomyces minteri simulierte Mars-Umweltbedingungen relativ gut überstanden: Nach 18 Monaten auf der Internationalen Raumstation[52] enthielten knapp 10 % der Proben noch fortpflanzungsfähige Zellen.[53] Auch die Flechte Xanthoria elegans hat die simulierten Marsbedingungen während des Experiments überlebt. +Vermutungen vor dem Raumzeitalter +Marsoberfläche nach Oswald Lohse (1888). Auf der Karte ist das Kanalsystem Schiaparellis nicht eingezeichnet. Die von Lohse gewählten Namen für die „Seen“ und „Ozeane“ sind heute nicht mehr gebräuchlich + +Der Gedanke an die Möglichkeit von Leben auf dem Mars beflügelte oft die Fantasie der Menschen. Im 18. Jahrhundert beobachtete man, dass die dunklen Flecken auf der Marsoberfläche ihre Farbe änderten und wuchsen oder schrumpften. Man hielt sie für ausgedehnte Vegetationszonen, deren Ausdehnung sich mit den Jahreszeiten änderte. + +Durch Schiaparellis „Entdeckung“ der Marskanäle wurden die Spekulationen um intelligentes Leben auf dem Mars angefacht. + +So entstanden zahlreiche Legenden um vermeintliche Zivilisationen auf dem Mars. Die Diskussionen um die „Marsmenschen“ hielten etwa ein Jahrhundert an. Der US-Amerikaner Percival Lowell, einer der heftigsten Verfechter der Marskanäle-Theorie, gründete sogar eine eigene Sternwarte, um die Marsbewohner zu erforschen. Für ihn waren die Kanäle das Produkt außerirdischer Ingenieure, die geschaffen wurden, um die Marszivilisation vor einer großen Trockenheit zu retten. Lowell beschrieb seine Vorstellungen der Marswelt in zahlreichen Publikationen, die weite Verbreitung fanden. + +Obwohl nicht alle Astronomen die Kanäle sehen konnten und keine Fotos existierten, hielt sich die Theorie, begleitet von einer heftigen Debatte. Die Vorstellung von außerirdischem Leben übt bis heute eine Faszination auf die Menschen aus, die mit wissenschaftlichem Interesse alleine oft nicht erklärt werden kann. Erst die Ergebnisse der unbemannten Marsmissionen beendeten den Streit um die Kanäle. + +Untersuchungen durch Viking + +Als im Juli 1976 der Orbiter 1 der Viking-Mission Bilder der Cydonia-Region machte und diese zur Erde schickte, wurde der Mars in der Öffentlichkeit wieder zum Gesprächsthema. Eine der Aufnahmen zeigte eine Formation auf der Marsoberfläche, die einem menschlichen Gesicht ähnelte, das gen Himmel blickt. In der unmittelbaren Nähe wurden außerdem Strukturen entdeckt, die Pyramiden auf der Erde ähneln, sowie rechteckige Strukturen (von den Wissenschaftlern „Inka-Stadt“ getauft). Erst die Mission Mars Global Surveyor der NASA brachte im April 1998 für viele die Ernüchterung: Alle entdeckten Strukturen waren das Ergebnis natürlicher Erosion. Durch neue Bilder mit wesentlich höherer Auflösung wurde deutlich, dass auf dem Mars keine künstlichen Strukturen außerirdischer Intelligenz ersichtlich sind. +Das Marsgesicht in der Cydonia-Region; Aufnahme des Orbiters von Viking 1, 1976 + +Viking 1 und 2 hatten unter anderem die Aufgabe, der Frage nach dem Leben auf dem Mars nachzugehen. Dabei wurden ein chemisches und drei biologische Experimente durchgeführt. In dem chemischen Experiment wurde versucht, organische Substanzen im Marsboden nachzuweisen. Dazu wurde eine am MIT entwickelte GC/MS-Einheit (Kopplung eines Gaschromatographen mit einem Massenspektrometer) benutzt. Es konnten allerdings keine auf Kohlenstoff aufbauenden organischen Substanzen nachgewiesen werden. + +Das erste biologische Experiment beruhte auf Stoffwechselaktivitäten von Organismen. Eine Bodenprobe wurde mit einer Nährlösung benetzt und entstehende Gase registriert. Der Marsboden reagierte auf das Experiment mit Abgabe großer Mengen Sauerstoff. Im zweiten Experiment wurde eine Nährlösung mit radioaktiven Kohlenstoffatomen versehen und auf eine Probe gegeben. Als Ergebnis eines Stoffwechsels hätten sie unter den ausgeschiedenen Gasen nachgewiesen werden müssen. Tatsächlich wurden radioaktive Kohlenstoffatome nachgewiesen. Das dritte Experiment war ein Photosynthese-Experiment. Radioaktiv markiertes Kohlendioxid wurde dem Marsboden zugesetzt. Dieses Kohlendioxid hätte assimiliert werden und später nachgewiesen werden müssen. Auch dieses Ergebnis war positiv. Obwohl die Ergebnisse der biologischen Experimente positiv waren, gaben sie aufgrund des negativen Ergebnisses des GC/MS-Versuchs keinen schlüssigen Beweis für die Existenz oder Nichtexistenz von Leben auf dem Mars. +1990er und 2000er Jahre +Marsgesicht, Aufnahme von Mars Global Surveyor, 2001 + +Im Jahr 1996 fanden David S. McKay und seine Mitarbeiter Strukturen im Marsmeteoriten ALH 84001, die sie als Spuren von fossilen Bakterien deuteten. Das in diesem Meteoriten gefundene, kettenartig angeordnete Magnetit ähnelt morphologisch dem bakteriellen Magnetit aus Magnetospirillum magnetotacticum. Allerdings wird die Beweiskraft der gefundenen Strukturen von vielen Wissenschaftlern angezweifelt, da diese auch auf rein chemischem Wege entstehen konnten. + +Am 23. Januar 2004 entdeckte die europäische Marssonde Mars Express am Südpol des Mars große Mengen gefrorenen Wassers, Ende Juli 2005 auch in einem nahe dem Nordpol gelegenen Krater. + +Ende März 2004 wurde bekannt, dass Forscher der NASA und der ESA unabhängig voneinander Methan in der Marsatmosphäre nachgewiesen haben. Ob das Methan geologischen Ursprungs ist oder etwa durch den Stoffwechsel von Mikroorganismen gebildet wurde, sollen weitere Untersuchungen zeigen. + +Ebenfalls Anfang 2004 entdeckte die Marssonde Opportunity Gesteine, die in offenstehendem Wasser abgelagert worden sein müssen und viele regelmäßig verteilte kugelige, bis 1 cm große Hämatit-Konkretionen enthalten. Solche Konkretionen kommen auch auf der Erde vor. Unter irdischen Bedingungen ist es wahrscheinlich, dass bei ihrer Entstehung Bakterien beteiligt sind. Ob dies auch für den Mars gilt, könnten nur Laboruntersuchungen auf der Erde zeigen. + +Weitere Mikrostrukturen, welche die Rover Spirit und Opportunity 2004 entdeckt hatten und in denen ein Teil der interessierten Öffentlichkeit Hinweise auf Leben hatte sehen wollen, erwiesen sich bei näherer Untersuchung als abiotisch oder künstlich, so zum Beispiel Schleifspuren auf durch die Instrumente bearbeiteten Gesteinsoberflächen oder Filamente, die sich als Textilfasern der Lande-Airbags herausstellten. + +Forschungsergebnisse auf der Erde bestätigen, dass es Leben auch in extremen Bedingungen geben kann. Bei Bohrungen im grönländischen Eis entdeckten Forscher der University of California, Berkeley im Jahre 2005 in drei Kilometern Tiefe eine auffallende Menge Methan. Dieses Gas produzierten methanogene Bakterien, die trotz unwirtlicher Lebensbedingungen wie Kälte, Dunkelheit und Nährstoffmangel im Eis überleben. Dabei erhalten sie sich nur mühsam am Leben – sie reparieren Erbgutschäden, vermehren jedoch nicht nennenswert ihre Population. Methanogene Mikroben sind eine Untergruppe der Archaebakterien, die sich auf Extremstandorte spezialisiert haben. So fanden sich im Jahr 2002 Mikroben in einer 15.000 Jahre alten heißen Quelle in Idaho. Die Bakterien zählen, wie schon der Name besagt, zu den ältesten Mikroorganismen der Erde. Die Wissenschaftler schätzen das Alter der in Grönland entdeckten Bakterienkolonie auf 100.000 Jahre und vermuten, dass das in der Atmosphäre des Roten Planeten nachgewiesene Methan nicht nur von chemischen Prozessen, sondern auch von solchen Mikroben stammen könnte. +Aktuelle Forschung + +Mit dem Mars Science Laboratory wird versucht, neue Aufschlüsse über mögliches Leben auf dem Mars zu liefern. Es ist fraglich, ob der Mars-Rover tief genug bohren kann, um Leben oder zumindest Lebensreste zu finden. Aber eine Isotopenanalyse des Methans kann bereits weitere Aufschlüsse geben. Leben, wie es auf der Erde bekannt ist, bevorzugt leichtere Wasserstoffisotope. +Beobachtung +Stellung zur Erde und Bahneigenschaften +Planetenschleife des Mars im Sternbild Wassermann im Jahr 2003 + +Aufgrund der Bahneigenschaften der Planeten „überholt“ die Erde den Mars durchschnittlich alle 779 Tage auf ihrer inneren Bahn. Diesen Zeitraum, der zwischen 764 und 811 Tagen schwankt, nennt man synodische Periode. Befinden sich Sonne, Erde und Mars in dieser Anordnung auf einer Linie, so steht der Mars von der Erde aus gesehen in Opposition zur Sonne. Zu diesem Zeitpunkt ist Mars besonders gut zu beobachten, er steht dann als rötlicher „Stern“ auffallend hell am Nachthimmel. Beobachtet man den Mars regelmäßig, kann man feststellen, dass er vor und nach einer Opposition am Himmel eine Schleifenbewegung vollführt. Diese Planetenschleife (Oppositionsschleife) ergibt sich aus den Sichtwinkeln, die Mars bietet, während er von der Erde überholt wird. +Marsoppositionen von 2003 bis 2018, relative Bewegung des Mars zur Erde, mit der Erde im Zentrum; Ansicht auf die Ekliptikebene + +Da die Planeten sich nicht auf idealen Kreisbahnen, sondern auf mehr oder weniger stark ausgeprägten elliptischen Bahnen bewegen, haben Erde und Mars zum Zeitpunkt der Oppositionen unterschiedliche Entfernungen zueinander. Diese können zwischen 55,6 und 101,3 Millionen Kilometern bzw. 0,37 und 0,68 AE betragen. Bei einer geringen Oppositionsentfernung spricht man von einer Perihelopposition, bei einer großen von einer Aphelopposition. +Schwankung des minimalen Abstands Erde–Mars bei Oppositionen. Die tatsächlichen Werte sind an den einzelnen Punkten ablesbar. Die verbindende Kurve veranschaulicht die mathematische Gesetzmäßigkeit. + +Die alle 15 bis 17 Jahre stattfindenden Periheloppositionen bieten die besten Gelegenheiten, den Mars von der Erde aus mittels Teleskop zu beobachten. Der Planet hat dann einen scheinbaren Durchmesser von bis zu 25,8 Bogensekunden. Bei einer Aphelopposition ist er mit 14,1 Bogensekunden nur etwa halb so groß. Besonders erdnahe Oppositionen fanden im Abstand von jeweils 79 Jahren, zum Beispiel in den Jahren 1766, 1845, 1924 und 2003 statt. Am 28. August 2003 betrug der Abstand Erde–Mars 55,76 Millionen Kilometer. Dies war die geringste Distanz seit etwa 60.000 Jahren.[54][55] Erst im Jahre 2287 wird der Mars der Erde noch näher kommen, der Abstand beträgt dann 55,69 Millionen Kilometer. + +Im Teleskop erscheint der Mars zunächst als rötliches Scheibchen. Bei stärkerer Vergrößerung können die Polkappen und dunkle Oberflächenmerkmale wie die Große Syrte ausgemacht werden. Treten auf dem Mars größere Staubstürme auf, verblassen die Merkmale, da die Oberfläche von einer rötlichen Dunstschicht eingehüllt wird, die sich mitunter über Wochen halten kann. Durch den Einsatz von CCD-Kameras sind mittlerweile auch Amateurastronomen in der Lage, detailreiche Aufnahmen der Marsoberfläche zu erzielen, wie sie vor etwa zehn Jahren nur von den leistungsfähigsten Großteleskopen erstellt werden konnten. + +Ereignisse (Jahreszeitenbeginn gilt für die Nordhalbkugel):[56][57][58] +Ereignis Frühlingsbeginn Aphel Sommerbeginn Herbstbeginn Perihel Winterbeginn +Datum 18. Juni 2015 20. November 2015 15. Februar 2014 17. August 2014 12. Dezember 2014 11. Januar 2015 +Nächste +Termine 5. Mai 2017 7. Oktober 2017 3. Januar 2016 4. Juli 2016 29. Oktober 2016 28. November 2016 +Ereignis Konjunktion Opposition +Datum 14. Juni 2015 8. April 2014 +Nächste +Termine 27. Juli 2017 22. Mai 2016 +Sichtbarkeiten +→ Hauptartikel: Marspositionen + +Wegen der Exzentrizität der Marsbahn kann der erdnächste Punkt bis zu einer Woche vor oder nach der Opposition erreicht werden, und die scheinbare Helligkeit während der Opposition sowie der Erdabstand und der scheinbare Durchmesser während der Erdnähe können recht unterschiedlich ausfallen. + +Eine Opposition findet etwa alle zwei Jahre (779,94 Tage) statt. Dabei kann bei einer Perihelopposition die maximale scheinbare Helligkeit bis zu −2,91m erreichen. Zu diesem Zeitpunkt sind nur die Sonne, der Erdmond, die Venus und in seltenen Fällen Jupiter (bis zu −2,94m) noch heller. Bei Konjunktion hingegen erscheint Mars nur mehr mit einer Helligkeit von +1,8m.[1] +Kulturgeschichte +Beschäftigung mit dem Mars von der Antike bis in die Neuzeit +Allegorische Darstellung des Mars als Herrscher der Tierkreiszeichen Widder und Skorpion, von Hans Sebald Beham, 16. Jahrhundert + +Der Mars bewegte die Menschheit von alters her besonders. Im alten Ägypten wurde Mars als „Horus der Rote“ bezeichnet. Da der Planet sich während seiner Oppositionsschleife (Planetenschleife) zeitweise rückläufig bewegt, sprachen die Ägypter davon, dass Mars rückwärts wandere. Der Name der ägyptischen Hauptstadt „Kairo“ leitet sich von „Al Qahira“ ab, dem altarabischen Namen für den Planeten Mars. + +Im indischen Sanskrit wird der Mars als „Mangal“ (verheißungsvoll), „Angaraka“ (Glühende Kohle) und „Kuja“ (der Blonde) bezeichnet. Er repräsentiert kraftvolle Aktion, Vertrauen und Zuversicht. + +Aufgrund seiner roten Färbung wurde der Mars in verschiedenen Kulturen mit den Gottheiten des Krieges in Verbindung gebracht. Die Babylonier sahen in ihm Nergal, den Gott der Unterwelt, des Todes und des Krieges. Für die Griechen und Römer der Antike repräsentierte er deren Kriegsgötter Ares beziehungsweise Mars. In der nordischen Mythologie steht er für Tyr, den Gott des Rechts und des Krieges. Die Azteken nannten ihn Huitzilopochtli, der Zerstörer von Menschen und Städten. Für die Chinesen war er Huoxing (chin. HuÅxÄ«ng, ç«æ˜Ÿ), Stern des Feuers. + +In der Astrologie ist Mars unter anderem das Symbol der Triebkraft. Es wird dem Element Feuer, dem Planetenmetall Eisen, den Tierkreiszeichen Widder und Skorpion sowie dem 1. Haus zugeordnet. +Rezeption in Literatur, Film, Videospiele und Musik + +Der Mars und seine fiktiven Bewohner sind auch Thema zahlreicher Romane und Verfilmungen. + +Ein Beispiel des 18. Jahrhunderts ist Carl Ignaz Geigers Roman Reise eines Erdbewohners in den Mars von 1790. + +1880 veröffentlichte Percy Greg seinen Roman Across the Zodiac, in dem er eine Reise in einem Raumschiff namens Astronaut zum Mars beschrieb. + +Die klassische Figur des kleinen grünen Männchens mit Antennen auf dem Kopf erschien erstmals 1913 in einem Comic und ist seitdem Klischee. + +Als der Astronom Percival Lowell Ende des 19. Jahrhunderts die Vorstellung entwickelte, die mit dem Fernrohr wahrnehmbaren Marskanäle seien künstlich angelegte Wasserkanäle, wurde diese Idee in der Science-Fiction-Literatur aufgegriffen und weitergesponnen. Dort wurde der Mars häufig als eine sterbende Welt vorgestellt, in deren kalten Wüstenregionen alte und weit entwickelte Zivilisationen ums Ãœberleben kämpften. + +Kurd Laßwitz brachte 1897 seinen sehr umfangreichen Roman Auf zwei Planeten über einen Besuch bei den Marsbewohnern heraus. +Angriff der Marsianer in Krieg der Welten von H. G. Wells. Buchillustration der französischen Ausgabe von Alvim Corréa von 1906 + +In H. G. Wells’ bekanntem Roman Krieg der Welten, der 1898 erschien, verlassen die Marsianer ihre Heimatwelt, um die lebensfreundlichere Erde zu erobern. Die Menschheit, die den hochtechnisierten kriegerischen Marsianern hoffnungslos unterlegen ist, entgeht ihrer Auslöschung nur dadurch, dass die Invasoren von für Menschen harmlosen, irdischen Mikroben dahingerafft werden. Orson Welles verwendete den Stoff im Jahre 1938 in einem Hörspiel, wobei er die Marsianer in New Jersey landen ließ. Das Hörspiel wurde im Stil einer realistischen Reportage ausgestrahlt. Hörer, die sich später einschalteten, hielten die Invasion der Marsianer für Realität. + +Wells’ Romanvorlage wurde 1952 verfilmt, wobei die Handlung wiederum in die USA der Gegenwart verlegt wurde. Der Film erhielt für die damals bahnbrechenden Spezialeffekte einen Oscar. + +1923 brachte Tolstoi seinen Roman Aelita heraus, der von der Liebe eines sowjetischen Ingenieurs zur Marsprinzessin und dem Untergang der Zivilisation auf dem Planeten handelt. Dieses Werk wurde 1924 verfilmt. + +Im Jahr 1978 entstand der Film Unternehmen Capricorn. Er griff das Thema der Verschwörungstheorien zur Mondlandung auf, indem er es in sehr zugespitzter Form auf eine im Filmstudio vorgetäuschte Marsexpedition übertrug. + +Der 1996 entstandene Film Mars Attacks! setzt sich ironisch mit dem Thema Marsinvasion auseinander, wobei den Marsianern amerikanische Schnulzenmusik aus den 1950er Jahren zum Verhängnis wird. + +Unter der Regie von Brian De Palma wurden im Jahr 2000 mit dem Film Mission to Mars die Spekulationen um das Marsgesicht der Cydonia-Region als hinterlassenes Bauwerk dramatisch weitgehend thematisiert. + +Steven Spielbergs 2005 entstandenes Remake von Krieg der Welten nahm noch einmal das Thema auf und zeigte die Invasion von Außerirdischen auf der Erde aus der Sicht eines Familienvaters aus den USA. + +Weitere bekannte Science-Fiction-Filme, die auf dem Mars handeln, sind Red Planet (2000) und Die totale Erinnerung – Total Recall (1990). + +Edgar Rice Burroughs, der Autor von Tarzan, schrieb von 1917 bis 1943 die elfbändige Saga John Carter vom Mars, in der sich der irdische Held in marsianische Prinzessinnen verliebt und gegen Luftpiraten, grünhäutige Unholde, weiße Riesenaffen und andere Untiere kämpft. + +Die Mars-Chroniken (1950), eine stimmungsvolle Sammlung von Erzählungen des Schriftstellers Ray Bradbury, sind ebenfalls auf dem Mars angesiedelt. + +Große Beachtung erhielt die Marstrilogie, eine von Kim Stanley Robinson von 1993 bis 1996 verfasste Romanserie über die Besiedelung des Mars. Der besondere Ansatz dieser Geschichten liegt in der vorwiegend technischen Schilderung unter vollständigem Verzicht phantastischer Elemente. +Die Route von Mark Watney in einer nachgestellten topographischen Kartierung des DLR-Instituts für Planetenforschung + +Der wohl prominenteste Auftritt des Mars in der Musik dürfte der erste Satz von Gustav Holsts Orchestersuite Die Planeten (1914–1916) sein, deren erster Satz Mars, the Bringer of War mit seinem drohend-martialischen Charakter die mythologische Gestalt Mars eindrucksvoll porträtiert. + +Bestsellerautor Andreas Eschbach verfasste von 2001 bis 2008 die Pentalogie Das Marsprojekt. + +2011 veröffentlichte Andy Weir den Science-Fiction-Roman Der Marsianer, in dem ein Astronaut nach einem Unfall auf dem Mars zurückgelassen wird und fortan um sein Ãœberleben kämpfen muss. Mit Der Marsianer – Rettet Mark Watney erschien 2015 eine Verfilmung dieses Bestsellers. + +Helga Abret und Lucian Boa geben in ihrem Buch Das Jahrhundert der Marsianer (1984) einen literarischen Ãœberblick über Erzählungen und Romane über den Mars und seine fiktiven Bewohner. Von der Beschreibung einer „ekstatischen Reise“ zum Mars (Itinerarium exstaticum coeleste, 1656) des Jesuitenpaters Athanasius Kircher bis hin zu Science-Fiction-Erzählungen des 20. Jahrhunderts reicht die Bandbreite der kommentierten Werke, mit denen die Autoren aufzuzeigen versuchen, dass „sich aus dem Zusammenwirken von Naturwissenschaften, Astronomie und Literatur ein moderner Mythos“[59] entwickelte. + diff --git a/xpcom/tests/gtest/wikipedia/ja.txt b/xpcom/tests/gtest/wikipedia/ja.txt new file mode 100644 index 0000000000..f5ad44dc5f --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ja.txt @@ -0,0 +1,151 @@ + + +ç«æ˜Ÿï¼ˆã‹ã›ã„ã€ãƒ©ãƒ†ãƒ³èªž: Mars マールスã€è‹±èªž: マーズã€ã‚®ãƒªã‚·ã‚¢èªž: ΆÏης アレース)ã¯ã€å¤ªé™½ç³»ã®å¤ªé™½ã«è¿‘ã„æ–¹ã‹ã‚‰4番目ã®æƒ‘星ã§ã‚る。地çƒåž‹æƒ‘星ã«åˆ†é¡žã•ã‚Œã€åœ°çƒã®å¤–å´ã®è»Œé“を公転ã—ã¦ã„る。 + +英語åœã§ã¯ã€ãã®è¡¨é¢ã®è‰²ã‹ã‚‰ã€Red Planet(レッド・プラãƒãƒƒãƒˆã€ã€Œèµ¤ã„惑星ã€ã®æ„)ã¨ã„ã†é€šç§°ãŒã‚る。 + +物ç†çš„性質[編集] +地çƒã¨ç«æ˜Ÿã®å¤§ãã•æ¯”較。 + +ç«æ˜Ÿã¯åœ°çƒåž‹æƒ‘星ã«åˆ†é¡žã•ã‚Œã‚‹ã€ã„ã‚ゆる硬ã„岩石ã®åœ°è¡¨ã‚’æŒã£ãŸæƒ‘星ã§ã‚る。ç«æ˜ŸãŒèµ¤ã見ãˆã‚‹ã®ã¯ã€ãã®è¡¨é¢ã«åœ°çƒã®ã‚ˆã†ãªæ°´ã®æµ·ãŒç„¡ãã€åœ°è¡¨ã«é…¸åŒ–鉄(赤ã•ã³ï¼‰ãŒå¤§é‡ã«å«ã¾ã‚Œã¦ã„ã‚‹ãŸã‚ã§ã‚る。直径ã¯åœ°çƒã®åŠåˆ†ã»ã©ã§ã€è³ªé‡ã¯åœ°çƒã®ç´„ 1/10 ã«éŽãŽãªã„ãŸã‚ã€ç«æ˜Ÿã®åœ°è¡¨ã§ã®é‡åŠ›ã®å¼·ã•ã¯åœ°çƒã®40%ã»ã©ã—ã‹ãªã„。ç«æ˜Ÿã®è¡¨é¢ç©ã¯ã€åœ°çƒã®è¡¨é¢ç©ã®ç´„ 1/4ã§ã‚ã‚‹ãŒã€ã“ã‚Œã¯åœ°çƒã®é™¸åœ°ã®é¢ç©ï¼ˆç´„1.5å„„km2)ã¨ã»ã¼ç‰ã—ã„。ç«æ˜Ÿã®è‡ªè»¢å‘¨æœŸã¯åœ°çƒã®ãã‚Œã¨éžå¸¸ã«è¿‘ãã€ç«æ˜Ÿã®1日(1ç«æ˜Ÿå¤ªé™½æ—¥ã€1 sol)ã¯ã€24時間39分35.244秒ã§ã‚る。ã¾ãŸåœ°çƒã¨åŒã˜ã‚ˆã†ã«å¤ªé™½ã«å¯¾ã—ã¦è‡ªè»¢è»¸ã‚’傾ã‘ãŸã¾ã¾å…¬è»¢ã—ã¦ã„ã‚‹ã®ã§ã€ç«æ˜Ÿã«ã¯å£ç¯€ãŒå˜åœ¨ã™ã‚‹ã€‚ +質é‡[編集] + +地çƒã‚„金星ã¨æ¯”ã¹ã¦ç«æ˜Ÿã®è³ªé‡ã¯å°ã•ã„[2]。太陽系ã®æƒ‘星移動ã®ãƒ¢ãƒ‡ãƒ«ã§ã‚るグランド・タックモデルã«ã‚ˆã‚‹ã¨ã€æœ¨æ˜Ÿã¯ç«æ˜Ÿå½¢æˆå‰ã«ä¸€åº¦ç«æ˜Ÿè»Œé“程度ã¾ã§å¤ªé™½ã«è¿‘ã¥ãã€å¾Œã«ç¾åœ¨ã®è»Œé“ã«è½ã¡ç€ã„ãŸã¨ã—ã¦ã„ã‚‹[2]。ãã®éš›ã€ç«æ˜Ÿã®æ§‹æˆã«ä½¿ç”¨ã•ã‚ŒãŸã§ã‚ã‚ã†è³ªé‡ã®å°å¤©ä½“ã‚’ã¯ã˜ã飛ã°ã—ã¦ã—ã¾ã£ãŸãŸã‚ã€ç«æ˜ŸãŒå分æˆé•·ã§ããªã‹ã£ãŸå¯èƒ½æ€§ã‚’示唆ã—ã¦ã„ã‚‹[2]。 +大気[編集] +詳細ã¯ã€Œç«æ˜Ÿã®å¤§æ°—ã€ã‚’å‚ç…§ +ç«æ˜Ÿï¼ˆã“ã®ä½Žè»Œé“写真ã®ä¸ã®åœ°å¹³ç·šã§è¦‹ãˆã‚‹ï¼‰ã®è–„ã„大気 + +ç«æ˜Ÿã®å¤§æ°—ã¯å¸Œè–„ã§ã€åœ°è¡¨ã§ã®å¤§æ°—圧ã¯ç´„750Paã¨åœ°çƒã§ã®å¹³å‡å€¤ã®ç´„0.75%ã«éŽãŽãªã„。逆ã«å¤§æ°—ã®åŽšã•ã‚’示ã™ã‚¹ã‚±ãƒ¼ãƒ«ãƒã‚¤ãƒˆã¯ç´„11kmã«é”ã—ã€ãŠã‚ˆã6kmã§ã‚る地çƒã‚ˆã‚Šã‚‚高ã„。ã“れらã¯ã„ãšã‚Œã‚‚ã€ç«æ˜Ÿã®é‡åŠ›ãŒåœ°çƒã‚ˆã‚Šã‚‚å¼±ã„ã“ã¨ã«èµ·å› ã—ã¦ã„る。大気ãŒå¸Œè–„ãªãŸã‚ã«ç†±ã‚’ä¿æŒã™ã‚‹ä½œç”¨ãŒå¼±ãã€è¡¨é¢æ¸©åº¦ã¯æœ€é«˜ã§ã‚‚ç´„20℃ã§ã‚る。大気ã®çµ„æˆã¯äºŒé…¸åŒ–ç‚ç´ ãŒ95%ã€çª’ç´ ãŒ3%ã€ã‚¢ãƒ«ã‚´ãƒ³ãŒ1.6%ã§ã€ä»–ã«é…¸ç´ や水蒸気ãªã©ã®å¾®é‡æˆåˆ†ã‚’å«ã‚€ã€‚ãŸã ã—ã€ç«æ˜Ÿã®å¤§æ°—ã®ä¸Šå±¤éƒ¨ã¯å¤ªé™½é¢¨ã®å½±éŸ¿ã‚’å—ã‘ã¦å®‡å®™ç©ºé–“ã¸ã¨æµå‡ºã—ã¦ã„ã‚‹ã“ã¨ãŒã€ã‚½ãƒ“エト連邦ã®ç„¡äººç«æ˜ŸæŽ¢æŸ»æ©Ÿã®ãƒ•ã‚©ãƒœã‚¹2å·ã«ã‚ˆã£ã¦è¦³æ¸¬ã•ã‚Œã¦ã„る。ã—ãŸãŒã£ã¦ä¸Šè¨˜ã®ç«æ˜Ÿã®å¤§æ°—圧や大気組æˆã¯ã€é•·ã„ç›®ã§è¦‹ã‚‹ã¨å¤‰åŒ–ã—ã¦ã„ã‚‹å¯èƒ½æ€§ã€ãã—ã¦ä»Šå¾Œã‚‚変化ã—ã¦ã‚†ãå¯èƒ½æ€§ãŒæŒ‡æ‘˜ã•ã‚Œã¦ã„る。 + +2003å¹´ã«åœ°çƒã‹ã‚‰ã®æœ›é é¡ã«ã‚ˆã‚‹è¦³æ¸¬ã§å¤§æ°—ã«ãƒ¡ã‚¿ãƒ³ãŒå«ã¾ã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ãŒæµ®ä¸Šã—ã€2004å¹´3月ã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機ã®èª¿æŸ»ã«ã‚ˆã‚‹å¤§æ°—ã®è§£æžã§ãƒ¡ã‚¿ãƒ³ã®å˜åœ¨ãŒç¢ºèªã•ã‚ŒãŸã€‚ç¾åœ¨è¦³æ¸¬ã•ã‚Œã¦ã„るメタンã®é‡ã®å¹³å‡å€¤ã¯ä½“ç©æ¯”ã§ç´„11±4 ppb ã§ã‚る。 + +ç«æ˜Ÿã®ç’°å¢ƒä¸‹ã§ã¯ä¸å®‰å®šãªæ°—体ã§ã‚るメタンã®å˜åœ¨ã¯ã€ç«æ˜Ÿã«ãƒ¡ã‚¿ãƒ³ã®ã‚¬ã‚¹æºãŒå˜åœ¨ã™ã‚‹ï¼ˆã¾ãŸã¯ã€å°‘ãªãã¨ã‚‚最近100年以内ã«ã¯å˜åœ¨ã—ã¦ã„ãŸï¼‰ã¨ã„ã†èˆˆå‘³æ·±ã„事実を示唆ã—ã¦ã„る。ガスã®ç”Ÿæˆæºã¨ã—ã¦ã¯ç«å±±æ´»å‹•ã‚„彗星ã®è¡çªã€ã‚ã‚‹ã„ã¯ãƒ¡ã‚¿ãƒ³èŒã®ã‚ˆã†ãªå¾®ç”Ÿç‰©ã®å½¢ã§ç”Ÿå‘½ãŒå˜åœ¨ã™ã‚‹ãªã©ã®å¯èƒ½æ€§ãŒè€ƒãˆã‚‰ã‚Œã¦ã„ã‚‹ãŒã€ã„ãšã‚Œã‚‚未確èªã§ã‚る。地çƒã®æµ·ã§ã¯ã€ç”Ÿç‰©ã«ã‚ˆã£ã¦ãƒ¡ã‚¿ãƒ³ãŒç”Ÿæˆã•ã‚Œã‚‹éš›ã«ã¯åŒæ™‚ã«ã‚¨ã‚¿ãƒ³ã‚‚生æˆã•ã‚Œã‚‹å‚¾å‘ãŒã‚る。一方ã€ç«å±±æ´»å‹•ã‹ã‚‰æ”¾å‡ºã•ã‚Œã‚‹ãƒ¡ã‚¿ãƒ³ã«ã¯äºŒé…¸åŒ–硫黄ãŒä»˜éšã™ã‚‹ã€‚メタンã¯ç«æ˜Ÿè¡¨é¢ã®æ‰€ã€…ã«å±€æ‰€çš„ã«å˜åœ¨ã—ã¦ã„るよã†ã«è¦‹ãˆã‚‹äº‹ã‹ã‚‰ã€ç™ºç”Ÿã—ãŸãƒ¡ã‚¿ãƒ³ã¯å¤§æ°—ä¸ã«ä¸€æ§˜ã«åˆ†å¸ƒã™ã‚‹ã‚ˆã‚Šã‚‚çŸæ™‚é–“ã§åˆ†è§£ã•ã‚Œã¦ã„ã‚‹ã“ã¨ãŒã†ã‹ãŒãˆã‚‹ã€‚ãれゆãˆã€ãŠãらãæŒç¶šçš„ã«å¤§æ°—ä¸ã«æ”¾å‡ºã•ã‚Œã¦ã„ã‚‹ã¨ã‚‚推測ã•ã‚Œã‚‹ã€‚発生æºã«é–¢ã™ã‚‹ä»®èª¬ã§ã©ã‚ŒãŒæœ€ã‚‚有力ã‹ã‚’推定ã™ã‚‹ãŸã‚ã«ã€ãƒ¡ã‚¿ãƒ³ã¨åŒæ™‚ã«æ”¾å‡ºã•ã‚Œã‚‹åˆ¥ã®æ°—体を検出ã™ã‚‹è¨ˆç”»ã‚‚ç¾åœ¨é€²ã‚られã¦ã„る。 + +ç«æ˜Ÿå¤§æ°—ã«ã¯å¤§ãã変化ã™ã‚‹é¢ã‚‚ã‚る。冬ã®æ•°ãƒ¶æœˆé–“ã«æ¥µåœ°æ–¹ã§å¤œãŒç¶šãã¨ã€åœ°è¡¨ã¯éžå¸¸ã«ä½Žæ¸©ã«ãªã‚Šã€å¤§æ°—全体ã®25%ã‚‚ãŒå‡å›ºã—ã¦åŽšã•æ•°ãƒ¡ãƒ¼ãƒˆãƒ«ã«é”ã™ã‚‹äºŒé…¸åŒ–ç‚ç´ ã®æ°·ï¼ˆãƒ‰ãƒ©ã‚¤ã‚¢ã‚¤ã‚¹ï¼‰ã®å±¤ã‚’ã¤ãる。やãŒã¦ã€æ¥µã«å†ã³æ—¥å…‰ãŒå½“ãŸã‚‹å£ç¯€ã«ãªã‚‹ã¨äºŒé…¸åŒ–ç‚ç´ ã®æ°·ã¯æ˜‡è¯ã—ã¦ã€æ¥µåœ°æ–¹ã«å¹ã付ã‘ã‚‹400km/hã«é”ã™ã‚‹å¼·ã„風ãŒç™ºç”Ÿã™ã‚‹ã€‚ã“れらã®å£ç¯€çš„活動ã«ã‚ˆã£ã¦å¤§é‡ã®å¡µã‚„水蒸気ãŒé‹ã°ã‚Œã€åœ°çƒã¨ä¼¼ãŸéœœã‚„大è¦æ¨¡ãªå·»é›²ãŒç”Ÿã˜ã‚‹ã€‚ã“ã®ã‚ˆã†ãªæ°´ã®æ°·ã‹ã‚‰ãªã‚‹é›²ã®å†™çœŸãŒ2004å¹´ã«ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚Œã¦ã„る(撮影画åƒï¼‰ã€‚ã¾ãŸã€å—極ã§äºŒé…¸åŒ–ç‚ç´ ãŒçˆ†ç™ºçš„ã«å™´å‡ºã—ãŸè·¡ãŒãƒžãƒ¼ã‚ºãƒ»ã‚ªãƒ‡ãƒƒã‚»ã‚¤ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚Œã¦ã„ã‚‹[3]。 + +ç«æ˜Ÿã¯çŸã„時間尺度ã§ã¯æ¸©æš–化ã—ã¦ã„ã‚‹ã“ã¨ã‚’示唆ã™ã‚‹è¨¼æ‹ も発見ã•ã‚Œã¦ã„ã‚‹[4]。ã—ã‹ã—21世紀åˆé ã®ç«æ˜Ÿã¯1970年代よりã¯å¯’冷ã§ã‚ã‚‹[5]。 +地質[編集] +スピリットãŒæŠ‰ã£ãŸåœ°è¡¨ã€‚明るã„ã‚·ãƒªã‚«ï¼ˆäºŒé…¸åŒ–ã‚±ã‚¤ç´ ï¼‰ãŒå‰ã出ã—ã«ãªã£ã¦ã„る。 + +ç«æ˜Ÿã®è¡¨é¢ã¯ä¸»ã¨ã—ã¦çŽ„æ¦å²©ã¨å®‰å±±å²©ã®å²©çŸ³ã‹ã‚‰ãªã£ã¦ã„る。ã„ãšã‚Œã‚‚地çƒä¸Šã§ã¯ãƒžã‚°ãƒžãŒåœ°è¡¨è¿‘ãã§å›ºã¾ã£ã¦ç”Ÿæˆã™ã‚‹å²©çŸ³ã§ã‚ã‚Šã€å«ã¾ã‚Œã‚‹äºŒé…¸åŒ–ã‚±ã‚¤ç´ (SiO2) ã®é‡ã§åŒºåˆ¥ã•ã‚Œã‚‹ã€‚ç«æ˜Ÿã§ã¯å¤šãã®å ´æ‰€ãŒåŽšã•æ•°ãƒ¡ãƒ¼ãƒˆãƒ«ã‚ã‚‹ã„ã¯ãれ以上ã®æ»‘石粉ã®ã‚ˆã†ãªç´°ã‹ã„塵ã§è¦†ã‚ã‚Œã¦ã„る。 + +マーズ・グãƒãƒ¼ãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼æŽ¢æŸ»æ©Ÿã«ã‚ˆã‚‹ç«æ˜Ÿã®ç£å ´ã®è¦³æ¸¬ã‹ã‚‰ã€ç«æ˜Ÿã®åœ°æ®»ãŒå‘ãã®å転を繰り返ã™ãƒãƒ³ãƒ‰çŠ¶ã«ç£åŒ–ã•ã‚Œã¦ã„ã‚‹ã“ã¨ãŒåˆ†ã‹ã£ã¦ã„る。ã“ã®ç£åŒ–ãƒãƒ³ãƒ‰ã¯å…¸åž‹çš„ã«ã¯å¹…160kmã€é•·ã•1,000kmã«ã‚ãŸã£ã¦ã„る。ã“ã®ã‚ˆã†ãªç£åŒ–ã®ãƒ‘ターンã¯åœ°çƒã®æµ·åº•ã«è¦‹ã‚‰ã‚Œã‚‹ã‚‚ã®ã¨ä¼¼ã¦ã„る。1999å¹´ã«ç™ºè¡¨ã•ã‚ŒãŸèˆˆå‘³æ·±ã„説ã«ã‚ˆã‚‹ã¨ã€ã“れらã®ãƒãƒ³ãƒ‰ã¯éŽåŽ»ã®ç«æ˜Ÿã®ãƒ—レートテクトニクス作用ã®è¨¼æ‹ ã‹ã‚‚ã—ã‚Œãªã„ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。ã—ã‹ã—ãã®ã‚ˆã†ãªãƒ—レート活動ãŒã‚ã£ãŸè¨¼æ‹ ã¯ã¾ã 確èªã•ã‚Œã¦ã„ãªã„[6]。2005å¹´10月ã«ç™ºè¡¨ã•ã‚ŒãŸæ–°ãŸãªç™ºè¦‹ã¯ä¸Šè¨˜ã®èª¬ã‚’支æŒã™ã‚‹ã‚‚ã®ã§ã€åœ°çƒã§ç™ºè¦‹ã•ã‚Œã¦ã„る海底拡大ã«ã‚ˆã‚‹ãƒ†ã‚¯ãƒˆãƒ‹ã‚¯ã‚¹æ´»å‹•ã¨åŒæ§˜ã®æ´»å‹•ãŒå¤ªå¤ã®ç«æ˜Ÿã«ã‚ã£ãŸã“ã¨ã‚’示ã—ã¦ã„ã‚‹[7]。もã—ã“れらãŒæ£ã—ã‘ã‚Œã°ã€ã“れらã®æ´»å‹•ã«ã‚ˆã£ã¦ç‚ç´ ã®è±Šå¯Œãªå²©çŸ³ãŒåœ°è¡¨ã«é‹ã°ã‚Œã‚‹ã“ã¨ã«ã‚ˆã£ã¦åœ°çƒã«è¿‘ã„大気ãŒç¶æŒã•ã‚Œã€ä¸€æ–¹ã§ç£å ´ã®å˜åœ¨ã«ã‚ˆã£ã¦ç«æ˜Ÿè¡¨é¢ãŒå®‡å®™æ”¾å°„ç·šã‹ã‚‰å®ˆã‚‰ã‚Œã‚‹ã“ã¨ã«ãªã£ãŸã‹ã‚‚ã—ã‚Œãªã„。ã¾ãŸã“れらã¨ã¯åˆ¥ã®ç†è«–的説明もæ案ã•ã‚Œã¦ã„る。 +オãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸç«æ˜Ÿã®å²©çŸ³ã®é¡•å¾®é¡å†™çœŸã€‚éŽåŽ»ã«æ°´ã®ä½œç”¨ã«ã‚ˆã£ã¦ä½œã‚‰ã‚ŒãŸã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。 + +オãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã‚‹ç™ºè¦‹ã®ä¸ã«ã€ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹å¹³åŽŸã§æŽ¡å–ã—ãŸå²©çŸ³ã‹ã‚‰å°ã•ãªçƒå½¢ã®èµ¤é‰„鉱(ヘマタイト)ãŒç™ºè¦‹ã•ã‚ŒãŸã€‚ã“ã®çƒä½“ã¯ç›´å¾„ã‚ãšã‹æ•°mmã—ã‹ãªãã€æ•°åå„„å¹´å‰ã«æ°´ã®å¤šã„環境ã®ä¸‹ã§å †ç©å²©ã¨ã—ã¦ä½œã‚‰ã‚ŒãŸã‚‚ã®ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。他ã«ã‚‚鉄ミョウãƒãƒ³çŸ³ãªã©ã€ç¡«é»„ã€é‰„ã€è‡ç´ ã‚’å«ã‚€é‰±ç‰©ãŒç™ºè¦‹ã•ã‚Œã¦ã„る。ã“れらをå«ã‚€å¤šãã®è¨¼æ‹ ã‹ã‚‰ã€å¦è¡“誌「サイエンス〠2004å¹´12月9æ—¥å·ã«ãŠã„ã¦50åã®ç ”究者ã‹ã‚‰ãªã‚‹ç ”究グループã¯ã€ã€Œç«æ˜Ÿè¡¨é¢ã®ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹å¹³åŽŸã§ã¯éŽåŽ»ã«æ¶²ä½“ã®æ°´ãŒæ–続的ã«å˜åœ¨ã—ã€åœ°è¡¨ã®ä¸‹ãŒæ°´ã§æº€ãŸã•ã‚Œã¦ã„ãŸæ™‚代ãŒä½•å›žã‹ã‚ã£ãŸã€‚液体ã®æ°´ã¯ç”Ÿå‘½ã«ã¨ã£ã¦éµã¨ãªã‚‹å¿…è¦æ¡ä»¶ã§ã‚ã‚‹ãŸã‚ã€æˆ‘々ã¯ç«æ˜Ÿã®æ´å²ã®ä¸ã§ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹ã§ã¯ç”Ÿå‘½ã®å˜åœ¨å¯èƒ½ãªç’°å¢ƒãŒä½•åº¦ã‹ä½œã‚‰ã‚Œã¦ã„ãŸã¨æŽ¨æ¸¬ã—ã¦ã„ã‚‹ã€ã¨çµè«–ã—ã¦ã„る。メリディアニã®å対å´ã®ç«æ˜Ÿè¡¨é¢ã§ã¯ã€ã‚³ãƒãƒ³ãƒ“ア・ヒルズã«ãŠã„ã¦ã‚¹ãƒ”リットãŒé‡é‰„鉱を発見ã—ã¦ã„る。ã“ã‚Œã¯ï¼ˆèµ¤é‰„鉱ã¨ã¯ç•°ãªã‚Šï¼‰æ°´ãŒå˜åœ¨ã™ã‚‹ç’°å¢ƒã§ã€Œã®ã¿ã€ä½œã‚‰ã‚Œã‚‹é‰±ç‰©ã§ã‚る。スピリットã¯ä»–ã«ã‚‚æ°´ã®å˜åœ¨ã‚’示ã™è¨¼æ‹ を発見ã—ã¦ã„る。 + +マーズ・グãƒãƒ¼ãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ãŒ2006å¹´ã«æ’®å½±ã—ãŸå†™çœŸã‹ã‚‰ã€ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼å†…å£ã®æ–œé¢ã‚’液体ãŒæµã‚ŒãŸç—•è·¡ãŒè¦‹ã¤ã‹ã£ãŸãŒã€1999å¹´ã«åŒã˜å ´æ‰€ã‚’撮影ã—ãŸå†™çœŸã«ã¯å†™ã£ã¦ãŠã‚‰ãšã€ãれ以é™ã«ã§ããŸã‚‚ã®ã¨æ€ã‚れる。 + +1996å¹´ã€ç«æ˜Ÿèµ·æºã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る隕石「ALH84001ã€ã‚’調査ã—ã¦ã„ãŸç ”究者ãŒã€ç«æ˜Ÿã®ç”Ÿå‘½ã«ã‚ˆã£ã¦æ®‹ã•ã‚ŒãŸã¨æ€ã‚れる微å°åŒ–石ãŒã“ã®éš•çŸ³ã«å«ã¾ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’å ±å‘Šã—ãŸã€‚2005å¹´ç¾åœ¨ã€ã“ã®è§£é‡ˆã«ã¤ã„ã¦ã¯ã„ã¾ã ã«è°è«–ãŒã‚ã‚Šã€åˆæ„ã¯å¾—られã¦ã„ãªã„。 +地形[編集] +ç«æ˜Ÿã®åœ°å½¢å›³ã€‚特徴的ãªåœ°å½¢ã¨ã—ã¦ã€è¥¿éƒ¨ã®ã‚¿ãƒ«ã‚·ã‚¹ç«å±±ç¾¤ï¼ˆã‚ªãƒªãƒ³ãƒã‚¹å±±ã‚’å«ã‚€ï¼‰ã€ã‚¿ãƒ«ã‚·ã‚¹ã®æ±ã«ã‚るマリãƒãƒªã‚¹å³¡è°·ã€å—åŠçƒã®ãƒ˜ãƒ©ã‚¹ç›†åœ°ãªã©ãŒã‚ã‚‹ +「ç«æ˜Ÿã®åœ°å½¢ä¸€è¦§ã€ã‚‚å‚ç…§ + +ç«æ˜Ÿã®åœ°å½¢ã¯å¤§ãã二通りã«åˆ†ã‹ã‚Œã¦ãŠã‚Šã€ç‰¹å¾´çš„ã§ã‚る。北åŠçƒã¯æº¶å²©æµã«ã‚ˆã£ã¦å¹³ã‚‰ã«å‡ã•ã‚ŒãŸå¹³åŽŸï¼ˆåŒ—部平原ã®æˆå› ã¨ã—ã¦ã¯å¤§é‡ã®æ°´ã«ã‚ˆã‚‹ä¾µé£Ÿèª¬ã‚‚ã‚る)ãŒåºƒãŒã£ã¦ãŠã‚Šã€ä¸€æ–¹ã€å—åŠçƒã¯å¤ªå¤ã®éš•çŸ³è¡çªã«ã‚ˆã‚‹çªªåœ°ã‚„クレーターãŒå˜åœ¨ã™ã‚‹é«˜åœ°ãŒå¤šã„。地çƒã‹ã‚‰è¦‹ãŸç«æ˜Ÿè¡¨é¢ã‚‚ã“ã®ãŸã‚ã«äºŒç¨®é¡žã®åœ°åŸŸã«åˆ†ã‘られã€ä¸¡è€…ã¯å…‰ã®å射率ã§ã‚るアルベドãŒç•°ãªã£ã¦ã„る。明るã見ãˆã‚‹å¹³åŽŸã¯èµ¤ã„酸化鉄を多ãå«ã‚€å¡µã¨ç ‚ã«è¦†ã‚ã‚Œã¦ãŠã‚Šã€ã‹ã¤ã¦ã¯ç«æ˜Ÿã®å¤§é™¸ã¨è¦‹ç«‹ã¦ã‚‰ã‚Œã¦ã‚¢ãƒ©ãƒ“ア大陸 (Arabia Terra) やアマゾニス平原 (Amazonis Planitia) ãªã©ã¨å‘½åã•ã‚Œã¦ã„る。暗ã„模様ã¯æµ·ã¨è€ƒãˆã‚‰ã‚Œã€ã‚¨ãƒªãƒˆãƒªã‚¢æµ· (Mare Erythraeum)ã€ã‚·ãƒ¬ãƒ¼ãƒŒã‚¹ï¼ˆã‚»ã‚¤ãƒ¬ãƒ¼ãƒ³ãŸã¡ï¼‰ã®æµ· (Mare Sirenum)ã€ã‚ªãƒ¼ãƒãƒ©æ¹¾ (Aurorae Sinus) ãªã©ã¨åã¥ã‘られã¦ã„る。地çƒã‹ã‚‰è¦‹ãˆã‚‹æœ€ã‚‚大ããªæš—ã„模様ã¯å¤§ã‚·ãƒ«ãƒã‚¹ (Syrtis Major) ã§ã‚る。 +北極地ã®åˆå¤æ¥µå† + +ç«æ˜Ÿã«ã¯æ°´ã¨äºŒé…¸åŒ–ç‚ç´ ã®æ°·ã‹ã‚‰ãªã‚‹æ¥µå† ãŒã‚ã‚Šã€ç«æ˜Ÿã®å£ç¯€ã«ã‚ˆã£ã¦å¤‰åŒ–ã™ã‚‹ã€‚二酸化ç‚ç´ ã®æ°·ã¯å¤ã«ã¯æ˜‡è¯ã—ã¦å²©çŸ³ã‹ã‚‰ãªã‚‹è¡¨é¢ãŒç¾ã‚Œã€å†¬ã«ã¯å†ã³æ°·ãŒã§ãる。楯状ç«å±±ã§ã‚るオリンãƒã‚¹å±±ã¯æ¨™é«˜27kmã®å¤ªé™½ç³»æœ€é«˜ã®å±±ã§ã‚ã‚‹[8]。ã“ã®å±±ã¯ã‚¿ãƒ«ã‚·ã‚¹é«˜åœ°ã¨å‘¼ã°ã‚Œã‚‹åºƒå¤§ãªé«˜åœ°ã«ã‚ã‚Šã€ã“ã®åœ°æ–¹ã«ã¯ã„ãã¤ã‹ã®å¤§ããªç«å±±ãŒã‚る。ç«æ˜Ÿã«ã¯å¤ªé™½ç³»æœ€å¤§ã®å³¡è°·ã§ã‚るマリãƒãƒªã‚¹å³¡è°·ã‚‚å˜åœ¨ã™ã‚‹ã€‚ã“ã®å³¡è°·ã¯å…¨é•·4,000kmã€æ·±ã•7kmã«é”ã™ã‚‹ã€‚ç«æ˜Ÿã«ã¯å¤šãã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‚‚å˜åœ¨ã™ã‚‹ã€‚最大ã®ã‚‚ã®ã¯ãƒ˜ãƒ©ã‚¹ç›†åœ°ã§ã€æ˜Žã‚‹ã„赤色ã®ç ‚ã§è¦†ã‚ã‚Œã¦ã„る。 + +ç«æ˜Ÿã®æœ€é«˜åœ°ç‚¹ã¨æœ€ä½Žåœ°ç‚¹ã®æ¨™é«˜å·®ã¯ç´„31kmã§ã‚る。オリンãƒã‚¹å±±ã®å±±é ‚ 27km ãŒæœ€ã‚‚高ãã€ãƒ˜ãƒ©ã‚¹ç›†åœ°ã®åº•éƒ¨ã€æ¨™é«˜åŸºæº–é¢ã®ç´„ 4km 下ãŒæœ€ã‚‚低ã„。ã“ã‚Œã¨æ¯”ã¹ã¦åœ°çƒã®æœ€é«˜ç‚¹ã¨æœ€ä½Žç‚¹ï¼ˆã‚¨ãƒ™ãƒ¬ã‚¹ãƒˆã¨ãƒžãƒªã‚¢ãƒŠæµ·æºï¼‰ã®å·®ã¯19.7kmã«éŽãŽãªã„。両惑星ã®åŠå¾„ã®å·®ã‚’考ãˆã‚‹ã¨ã€ç«æ˜ŸãŒåœ°çƒã‚ˆã‚Šã‚‚ãŠã‚ˆã3å€ã‚‚凸凹ã§ã‚ã‚‹ã“ã¨ã‚’示ã—ã¦ã„る。 + +21世紀åˆé ç¾åœ¨ã§ã¯ã€å›½éš›å¤©æ–‡å¦é€£åˆ (IAU) ã®æƒ‘星系命åワーã‚ンググループãŒç«æ˜Ÿè¡¨é¢ã®åœ°å½¢åã®å‘½åを担当ã—ã¦ã„る。 +座標ã®åŸºæº–[編集] + +ç«æ˜Ÿã«ã¯æµ·ãŒãªã„ã®ã§æµ·æŠœã¨ã„ã†å®šç¾©ã¯ä½¿ãˆãªã„。従ã£ã¦é«˜åº¦0ã®é¢ã€ã™ãªã‚ã¡å¹³å‡é‡åŠ›é¢ã‚’é¸ã¶å¿…è¦ãŒã‚る。ç«æ˜Ÿã®åŸºæº–測地系ã¯4階4次ã®çƒé¢èª¿å’Œé–¢æ•°é‡åŠ›å ´ã§å®šç¾©ã•ã‚Œã€é«˜åº¦0ã¯æ¸©åº¦273.16Kã§ã®å¤§æ°—圧ãŒ610.5Pa(地çƒã®ç´„0.6%)ã¨ãªã‚‹é¢ã¨ã—ã¦å®šç¾©ã•ã‚Œã¦ã„る。ã“ã®åœ§åŠ›ã¨æ¸©åº¦ã¯æ°´ã®ä¸‰é‡ç‚¹ã«å¯¾å¿œã—ã¦ã„る。 + +ç«æ˜Ÿã®èµ¤é“ã¯ãã®è‡ªè»¢ã‹ã‚‰å®šç¾©ã•ã‚Œã¦ã„ã‚‹ãŒã€åŸºæº–ååˆç·šã®ä½ç½®ã¯åœ°çƒã®å ´åˆã¨åŒæ§˜ã«ä»»æ„ã®ç‚¹ãŒé¸ã°ã‚Œã€å¾Œä¸–ã®è¦³æ¸¬è€…ã«ã‚ˆã£ã¦å—ã‘入れられã¦ã„ã£ãŸã€‚ドイツã®å¤©æ–‡å¦è€…ヴィルヘルム・ベーアã¨ãƒ¨ãƒãƒ³ãƒ»ãƒã‚¤ãƒ³ãƒªãƒƒãƒ’・メドラーã¯1830å¹´ã‹ã‚‰32å¹´ã«ã‹ã‘ã¦æœ€åˆã®ç«æ˜Ÿã®ä½“系的ãªåœ°å›³ã‚’作æˆã—ãŸéš›ã«ã€ã‚ã‚‹å°ã•ãªå††å½¢ã®æ¨¡æ§˜ã‚’基準点ã¨ã—ãŸã€‚彼らã®é¸æŠžã—ãŸåŸºæº–点ã¯1877å¹´ã«ã€ã‚¤ã‚¿ãƒªã‚¢ã®å¤©æ–‡å¦è€…ジョヴァンニ・スã‚アパレッリãŒæœ‰åãªç«æ˜Ÿå›³ã®ä½œæˆã‚’始ã‚ãŸéš›ã«åŸºæº–ååˆç·šã¨ã—ã¦æŽ¡ç”¨ã•ã‚ŒãŸã€‚1972å¹´ã«æŽ¢æŸ»æ©ŸãƒžãƒªãƒŠãƒ¼9å·ãŒç«æ˜Ÿã®åºƒç¯„囲ã®ç”»åƒã‚’撮影ã—ãŸå¾Œã€ååˆç·šã®æ¹¾ã®ãƒ™ãƒ¼ã‚¢ã¨ãƒ¡ãƒ‰ãƒ©ãƒ¼ã®ååˆç·šä¸Šã«ã‚ã‚‹å°ã•ãªã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ï¼ˆå¾Œã«ã‚¨ã‚¢ãƒªãƒ¼0ã¨å‘¼ã°ã‚Œã‚‹ï¼‰ãŒã‚¢ãƒ¡ãƒªã‚«ã€RAND社ã®ãƒ¡ãƒ«ãƒˆãƒ³ãƒ»ãƒ‡ãƒ¼ãƒ´ã‚£ã‚¹ã«ã‚ˆã£ã¦ã€æƒ‘星撮影時ã®åˆ¶å¾¡ç‚¹ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚’決ã‚ã‚‹éš›ã«ã‚ˆã‚Šæ£ç¢ºãªçµŒåº¦0.0度ã®å®šç¾©ã¨ã—ã¦æŽ¡ç”¨ã•ã‚ŒãŸã€‚ +「é‹æ²³ã€[編集] + +ç«æ˜Ÿã«ã¯ã‹ã¤ã¦ç”Ÿå‘½ãŒå˜åœ¨ã—ãŸã¨ã„ã†è€ƒãˆã®ãŸã‚ã«ã€ç«æ˜Ÿã¯äººé¡žã®æƒ³åƒã®ä¸–ç•Œã®ä¸ã§é‡è¦ãªä½ç½®ã‚’å ã‚ã¦ã„る。ã“ã†ã„ã£ãŸè€ƒãˆã¯ä¸»ã«19世紀ã«å¤šãã®äººã€…ã«ã‚ˆã£ã¦è¡Œã‚ã‚Œã€ç‰¹ã«ãƒ‘ーシヴァル・ãƒãƒ¼ã‚¦ã‚§ãƒ«ã‚„ジョヴァンニ・スã‚アパレッリã«ã‚ˆã‚‹ç«æ˜Ÿè¦³æ¸¬ã‹ã‚‰ç”Ÿã¾ã‚Œã€ä¸€èˆ¬ã«çŸ¥ã‚‰ã‚Œã‚‹ã‚ˆã†ã«ãªã£ãŸã€ã‚¹ã‚アパレッリã¯è¦³æ¸¬ã•ã‚ŒãŸæ¨¡æ§˜ã‚’イタリア語: canali(æºï¼‰ã¨ã„ã†èªžã§è¨˜è¿°ã—ãŸã€‚ã“ã‚ŒãŒè‹±èªž: canal(é‹æ²³ï¼‰ã¨èª¤è¨³ã•ã‚Œã€ã“ã“ã‹ã‚‰ã€Œç«æ˜Ÿã®é‹æ²³ã€ã¨ã„ã†èª¬ãŒå§‹ã¾ã£ãŸ[9]。ã“れらã®ç«æ˜Ÿè¡¨é¢ã®æ¨¡æ§˜ã¯äººå·¥çš„ãªç›´ç·šçŠ¶ã®æ¨¡æ§˜ã®ã‚ˆã†ã«è¦‹ãˆãŸãŸã‚ã«é‹æ²³ã§ã‚ã‚‹ã¨ä¸»å¼µã•ã‚Œã€ã¾ãŸã‚ã‚‹é ˜åŸŸã®æ˜Žã‚‹ã•ãŒå£ç¯€ã«ã‚ˆã£ã¦å¤‰åŒ–ã™ã‚‹ã®ã¯æ¤ç‰©ã®æˆé•·ã«ã‚ˆã‚‹ã‚‚ã®ã ã¨è€ƒãˆã‚‰ã‚ŒãŸã€‚ã“れらã®è€ƒãˆã‹ã‚‰ç«æ˜Ÿäººã«é–¢é€£ã—ãŸå¤šãã®è©±ãŒç”Ÿã¾ã‚ŒãŸã€‚ã—ã‹ã—今日ã§ã¯è‰²ã®å¤‰åŒ–ã¯å¡µã®åµã®ãŸã‚ã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。 +ç«æ˜Ÿã®è¡›æ˜Ÿ[編集] +詳細ã¯ã€Œç«æ˜Ÿã®è¡›æ˜Ÿã€ã‚’å‚ç…§ + +ç«æ˜Ÿã«ã¯ãƒ•ã‚©ãƒœã‚¹ã¨ãƒ€ã‚¤ãƒ¢ã‚¹ã®2ã¤ã®è¡›æ˜ŸãŒå˜åœ¨ã™ã‚‹ã€‚ã¨ã‚‚ã«1877å¹´ã«ã‚¢ã‚µãƒ•ãƒ»ãƒ›ãƒ¼ãƒ«ã«ã‚ˆã£ã¦ç™ºè¦‹ã•ã‚Œã€ã‚®ãƒªã‚·ã‚¢ç¥žè©±ã§è»ç¥žã‚¢ãƒ¬ãƒ¼ã‚¹ã®æˆ¦ã„ã«åŒè¡Œã—ãŸæ¯åã®ãƒ•ã‚©ãƒœã‚¹ï¼ˆã€Œç‹¼ç‹½ã€ã®æ„)ã€ãƒ€ã‚¤ãƒ¢ã‚¹ï¼ˆã€Œæ怖ã€ã®æ„)ã‹ã‚‰å付ã‘られãŸã€‚アレースã¯ãƒãƒ¼ãƒžç¥žè©±ã§ã¯æˆ¦äº‰ã®ç¥žãƒžãƒ«ã‚¹ã¨ã—ã¦çŸ¥ã‚‰ã‚Œã¦ã„る。 +ç«æ˜ŸæŽ¢æŸ»[編集] +ヴァイã‚ング1å·ã®ç€é™¸åœ°ç‚¹ +詳細ã¯ã€Œç«æ˜ŸæŽ¢æŸ»ã€ã€ã€Œç«æ˜ŸæŽ¢æŸ»æ©Ÿã€ã€ãŠã‚ˆã³ã€Œç«æ˜Ÿã«ã‚る人工物ã®ä¸€è¦§ã€ã‚’å‚ç…§ + +ç«æ˜Ÿã®åœ°è¡¨ã‚„気候ã€åœ°å½¢ã‚’ç ”ç©¶ã™ã‚‹ãŸã‚ã«ã€ã‚½é€£ã€ã‚¢ãƒ¡ãƒªã‚«ã€ãƒ¨ãƒ¼ãƒãƒƒãƒ‘ã€æ—¥æœ¬ã«ã‚ˆã£ã¦ä»Šã¾ã§ã«è»Œé“探査機ã€ç€é™¸æ©Ÿã€ãƒãƒ¼ãƒãƒ¼ãªã©ã®å¤šãã®æŽ¢æŸ»æ©ŸãŒç«æ˜Ÿã«é€ã‚Šè¾¼ã¾ã‚ŒãŸã€‚ç«æ˜Ÿã‚’目指ã—ãŸæŽ¢æŸ»æ©Ÿã®ã†ã¡ã€ç´„ 2/3 ãŒãƒŸãƒƒã‚·ãƒ§ãƒ³å®Œäº†å‰ã«ã€ã¾ãŸã¯ãƒŸãƒƒã‚·ãƒ§ãƒ³é–‹å§‹ç›´å¾Œã«ä½•ã‚‰ã‹ã®å¤±æ•—ã‚’èµ·ã“ã—ã¦ã„る。ã“ã®é«˜ã„失敗率ã®ä¸€éƒ¨ã¯æŠ€è¡“上ã®å•é¡Œã«ã‚ˆã‚‹ã‚‚ã®ã¨è€ƒãˆã‚‰ã‚Œã‚‹ãŒã€ç‰¹ã«è€ƒãˆã‚‰ã‚Œã‚‹åŽŸå› ãŒãªã„ã¾ã¾å¤±æ•—ã—ãŸã‚Šäº¤ä¿¡ãŒé€”絶ãˆãŸã‚Šã—ãŸã‚‚ã®ã‚‚多ãã€ç ”究者ã®ä¸ã«ã¯å†—談åŠåˆ†ã«åœ°çƒ-ç«æ˜Ÿé–“ã®ã€ŒãƒãƒŸãƒ¥ãƒ¼ãƒ€ãƒˆãƒ©ã‚¤ã‚¢ãƒ³ã‚°ãƒ«ã€ã¨å‘¼ã‚“ã ã‚Šã€ç«æ˜ŸæŽ¢æŸ»æ©Ÿã‚’食ã¹ã¦æš®ã‚‰ã—ã¦ã„る宇宙悪霊ãŒã„ã‚‹ã¨è¨€ã£ãŸã‚Šã€ç«æ˜Ÿã®å‘ªã„ã¨è¨€ã†äººã‚‚ã„る。 + +最もæˆåŠŸã—ãŸãƒŸãƒƒã‚·ãƒ§ãƒ³ã¨ã—ã¦ã¯ã€ã‚½é€£ã®ç«æ˜ŸæŽ¢æŸ»æ©Ÿè¨ˆç”»ã‚„アメリカã®ãƒžãƒªãƒŠãƒ¼è¨ˆç”»ã€ãƒã‚¤ã‚ング計画ã€ãƒžãƒ¼ã‚ºãƒ»ã‚°ãƒãƒ¼ãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ã€ãƒžãƒ¼ã‚ºãƒ»ãƒ‘スファインダーã€2001マーズ・オデッセイãªã©ãŒã‚る。グãƒãƒ¼ãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ã¯å³¡è°·ã‚„土石æµã®å†™çœŸã‚’撮影ã—ã€å¸¯æ°´å±¤ã¨åŒæ§˜ã®æ¶²ä½“ã®æ°´ãŒæµã‚Œã‚‹æ°´æºãŒç«æ˜Ÿã®åœ°è¡¨ã¾ãŸã¯åœ°è¡¨è¿‘ãã«å˜åœ¨ã™ã‚‹å¯èƒ½æ€§ã‚’示唆ã—ãŸã€‚2001マーズ・オデッセイã¯ã€ç«æ˜Ÿã®å—ç·¯60度以å—ã®å—極地方ã®åœ°ä¸‹ç´„3m以内ã®è¡¨åœŸã«ã¯å¤§é‡ã®æ°´ã®æ°·ãŒå †ç©ã—ã¦ã„ã‚‹ã“ã¨ã‚’明らã‹ã«ã—ãŸã€‚ + +2003å¹´ã€æ¬§å·žå®‡å®™æ©Ÿé–¢ (ESA) ã¯ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス・オービタã¨ç€é™¸æ©Ÿãƒ“ーグル2ã‹ã‚‰ãªã‚‹ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機を打ã¡ä¸Šã’ãŸã€‚マーズ・エクスプレス・オービタã¯ç«æ˜Ÿã®å—極ã«æ°´ã¨äºŒé…¸åŒ–ç‚ç´ ã®æ°·ãŒå˜åœ¨ã™ã‚‹ã“ã¨ã‚’確èªã—ãŸã€‚NASA ã¯ãれ以å‰ã«åŒ—極ã«ã¤ã„ã¦ã€åŒæ§˜ã®æ°·ãŒå˜åœ¨ã™ã‚‹ã“ã¨ã‚’確èªã—ã¦ã„ãŸã€‚ビーグル2ã¨ã®äº¤ä¿¡ã«ã¯å¤±æ•—ã—ã€2004å¹´2月åˆæ—¬ã«ãƒ“ーグル2ãŒå¤±ã‚ã‚ŒãŸã“ã¨ãŒå®£è¨€ã•ã‚ŒãŸã€‚ +スピリットã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸã‚³ãƒãƒ³ãƒ“ア・ヒルズã®ãƒ‘ノラマ画åƒã€‚アメリカã«ã‚るカホã‚ア墳丘ã¨ã„ã†å…ˆä½æ°‘éºè·¡ã«ã¡ãªã‚“㧠Cahokia panorama ã¨å‘¼ã°ã‚Œã¦ã„ã‚‹ + +åŒã˜2003å¹´ã« NASA ã¯ã‚¹ãƒ”リット (MER-A)ã€ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ (MER-B) ã¨å‘½åã•ã‚ŒãŸ2æ©Ÿã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—ãƒãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãƒ»ãƒãƒ¼ãƒãƒ¼ã‚’打ã¡ä¸Šã’ãŸã€‚2æ©Ÿã¨ã‚‚2004å¹´1月ã«ç„¡äº‹ã«ç€é™¸ã—ã€å…¨ã¦ã®æŽ¢æŸ»ç›®æ¨™ã‚’調査ã—ãŸã€‚当åˆè¨ˆç”»ã•ã‚ŒãŸãƒŸãƒƒã‚·ãƒ§ãƒ³ã¯90日間ã ã£ãŸãŒã€ãƒŸãƒƒã‚·ãƒ§ãƒ³ã¯æ•°å›žå»¶é•·ã•ã‚Œã€ã„ãã¤ã‹ã®æ©Ÿæ¢°çš„トラブルã¯èµ·ããŸã‚‚ã®ã®ã€2007å¹´ç¾åœ¨ã‚‚ãªãŠç§‘å¦çš„æˆæžœã‚’地çƒã«é€ã‚Šç¶šã‘ã¦ã„る。最大ã®ç§‘å¦çš„æˆæžœã¯ã€ä¸¡æ–¹ã®ç€é™¸åœ°ç‚¹ã§éŽåŽ»ã®ã‚る時期ã«æ¶²ä½“ã®æ°´ãŒå˜åœ¨ã—ãŸè¨¼æ‹ を発見ã—ãŸã“ã¨ã§ã‚る。ã¾ãŸã€ç«æ˜Ÿã®åœ°ä¸Šã§æ’®å½±ã•ã‚ŒãŸæ—‹é¢¨ (dust devil) ãŒç«æ˜Ÿã®åœ°è¡¨ã‚’å‹•ã„ã¦ã„ã様åãŒã‚¹ãƒ”リットã«ã‚ˆã£ã¦æ¤œå‡ºã•ã‚ŒãŸã€‚ã“ã®æ—‹é¢¨ã¯ãƒžãƒ¼ã‚ºãƒ»ãƒ‘スファインダーã§åˆã‚ã¦æ’®å½±ã•ã‚Œã¦ã„ãŸã€‚ +スピリットã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸç«æ˜Ÿã®æ—‹é¢¨ + +2012å¹´ã«ãƒžãƒ¼ã‚ºãƒ»ã‚µã‚¤ã‚¨ãƒ³ã‚¹ãƒ»ãƒ©ãƒœãƒ©ãƒˆãƒªãƒ¼ãŒç«æ˜Ÿã«åˆ°ç€ã—ã€ã‚ュリオシティーç€é™¸ã®éŽç¨‹ã‚’撮影ã—ãŸ720p10fpsã®é«˜ç²¾ç´°ãªå‹•ç”»ãŒåœ°çƒã«é€ã‚‰ã‚ŒãŸã€‚ ã‚ュリオシティーã«ã¯éŽåŽ»ç«æ˜Ÿã«æŠ•å…¥ã•ã‚ŒãŸæŽ¢æŸ»æ©Ÿã®ä¸ã§ã¯æœ€é«˜ã®è§£åƒåº¦ (1600×1200) ã®ã‚«ãƒ¡ãƒ©ãŒæ載ã•ã‚Œã¦ãŠã‚Šã€æ¬¡ã€…ã«é«˜ç²¾ç´°ãªãƒ‘ノラマ画åƒãŒé€ã‚‰ã‚Œã¦ã„る。 +有人ç«æ˜ŸæŽ¢æŸ»[編集] +詳細ã¯ã€Œæœ‰äººç«æ˜ŸæŽ¢æŸ»ã€ãŠã‚ˆã³ã€Œç«æ˜Ÿã®æ¤æ°‘ã€ã‚’å‚ç…§ +有人ç«æ˜ŸæŽ¢æŸ»ã®æƒ³åƒå›³ã€‚ + +ヴェルナー・フォン・ブラウンをã¯ã˜ã‚ã€å¤šãã®äººã€…ãŒæœ‰äººæœˆæŽ¢æŸ»ã®æ¬¡ã®ã‚¹ãƒ†ãƒƒãƒ—ã¯ã€æœ‰äººç«æ˜ŸæŽ¢æŸ»ã§ã‚ã‚‹ã¨è€ƒãˆã¦ããŸã€‚有人探査ã®è³›åŒè€…ã¯ã€äººé–“ã¯ç„¡äººæŽ¢æŸ»æ©Ÿã‚ˆã‚Šã‚‚幾分優れã¦ãŠã‚Šã€æœ‰äººæŽ¢æŸ»ã‚’進ã‚ã‚‹ã¹ãã ã¨ä¸»å¼µã—ã¦ã„る。 + +アメリカåˆè¡†å›½ã®ãƒ–ãƒƒã‚·ãƒ¥å¤§çµ±é ˜ï¼ˆçˆ¶ï¼‰ã¯1989å¹´ã«æœˆãŠã‚ˆã³ç«æ˜Ÿã®æœ‰äººæŽ¢æŸ»æ§‹æƒ³ã‚’明らã‹ã«ã—ãŸãŒã€å¤šé¡ã®äºˆç®—ã‚’å¿…è¦ã¨ã™ã‚‹ãŸã‚ã«æ–念ã•ã‚ŒãŸã€‚ã¾ãŸã€ãƒ–ãƒƒã‚·ãƒ¥å¤§çµ±é ˜ï¼ˆæ¯å)も2004å¹´1月14æ—¥ã«ã€Œå®‡å®™æŽ¢æŸ»ã®å°†æ¥ã€ã¨é¡Œã—ãŸæ–°ãŸãªè¨ˆç”»ã‚’発表ã—ãŸã€‚ã“ã‚Œã«ã‚ˆã‚‹ã¨ã€ã‚¢ãƒ¡ãƒªã‚«ã¯2015å¹´ã¾ã§ã«ã‚‚ã†ä¸€åº¦æœˆã«æœ‰äººæŽ¢æŸ»æ©Ÿã‚’é€ã‚Šã€ãã®å¾Œæœ‰äººã§ã®ç«æ˜ŸæŽ¢æŸ»ã®å¯èƒ½æ€§ã‚’探るã“ã¨ã¨ãªã£ã¦ã„ãŸï¼ˆã‚³ãƒ³ã‚¹ãƒ†ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³è¨ˆç”»ï¼‰ã€‚ã¾ãŸã€ãƒã‚·ã‚¢ã‚‚å°†æ¥çš„ã«æœ‰äººç«æ˜ŸæŽ¢æŸ»ã‚’è¡Œã†ã“ã¨ã‚’予定ã—ã¦ãŠã‚Šã€æŠ€è¡“的・経済的ã«åˆ¤æ–ã—ã¦2025å¹´ã¾ã§ã«ã¯å®Ÿç¾å¯èƒ½ã§ã‚ã‚‹ã¨ã—ã¦ã„る。更ã«ESAã‚‚ã€2030å¹´ã¾ã§ã«äººé–“ã‚’ç«æ˜Ÿã«é€ã‚‹ã€Œã‚ªãƒ¼ãƒãƒ©ãƒ»ãƒ—ãƒã‚°ãƒ©ãƒ ã€ã¨å‘¼ã°ã‚Œã‚‹é•·æœŸè¨ˆç”»ã‚’æŒã£ã¦ã„る。 + +特ã«ãƒãƒƒã‚¯ã¨ãªã‚‹ã®ã¯ã€ç«æ˜Ÿã¸ã®å¾€å¾©ã¨æ»žåœ¨æœŸé–“ã®åˆè¨ˆã§1å¹´å¼·ã‹ã‚‰3å¹´å¼±ã¨ã„ã†ã€æœˆæŽ¢æŸ»ã¨ã¯æ¯”較ã«ãªã‚‰ãªã„長期間ã®ãƒŸãƒƒã‚·ãƒ§ãƒ³ã§ã‚ã‚‹ã“ã¨ã¨ã€é‹ã°ãªã‘ã‚Œã°ãªã‚‰ãªã„物資ã®é‡ã§ã‚る。ã“ã®ãŸã‚ã€ç«æ˜Ÿã®å¤§æ°—ã‹ã‚‰å¸°é‚„ç”¨ç‡ƒæ–™ã‚’è£½é€ ã™ã‚‹ç„¡äººå·¥å ´ã‚’先行ã—ã¦é€ã‚Šè¾¼ã¿ã€æœ‰äººå®‡å®™èˆ¹ã¯å¾€è·¯åˆ†ã®ã¿ã®ç‡ƒæ–™ã§ç«æ˜Ÿã«åˆ°é”ã—ã€æŽ¢æŸ»å¾Œã«ç„¡äººå·¥å ´ã§è£½é€ ã•ã‚Œã¦ã„ãŸç‡ƒæ–™ã§å¸°é‚„ã™ã‚‹ã¨ã„ã†ãƒ—ラン「マーズ・ダイレクトã€ãªã©ã‚‚æ案ã•ã‚Œã¦ã„る。 + +2010å¹´ã€ã‚ªãƒãƒžå¤§çµ±é ˜ã¯ã‚³ãƒ³ã‚¹ãƒ†ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³è¨ˆç”»ã®ä¸æ¢ã‚’表明ã—ãŸãŒã€åŒæ™‚ã«äºˆç®—ã‚’æ–°åž‹ã®ãƒã‚±ãƒƒãƒˆã‚¨ãƒ³ã‚¸ãƒ³é–‹ç™ºãªã©ã®å°†æ¥æ€§ã®é«˜ã„新技術開発ã«æŒ¯ã‚Šå‘ã‘ã‚‹ã¨ã—ã¦ãŠã‚Šã€ã‚ˆã‚ŠçŸæœŸé–“ã§ç«æ˜Ÿã«åˆ°é”ã§ãる航行手段ãŒå®Ÿç”¨åŒ–ã•ã‚Œã‚‹äº‹ãŒæœŸå¾…ã•ã‚Œã‚‹ã€‚ã¾ãŸã€åŒè¨ˆç”»ã®ä»£ã‚ã‚Šã«ã‚ªãƒãƒžå¤§çµ±é ˜ã¯ã€2030年代åŠã°ã‚’目標ã«ã—ãŸæ–°ãŸãªæœ‰äººç«æ˜ŸæŽ¢æŸ»è¨ˆç”»ã‚‚発表ã—ã¦ã„る。 +ç«æ˜ŸæŽ¢æŸ»æ‰¹åˆ¤[編集] + +ç«æ˜ŸæŽ¢æŸ»ã¯è¿‘å¹´æ ¹å¼·ã実施ã•ã‚Œã¦ã„ã‚‹ãŒã€å‰è¿°ã®ã‚ˆã†ã«æŽ¢æŸ»è¨ˆç”»ã®ç´„2/3ãŒå¤±æ•—ã«çµ‚ã‚る上ã«ã€èŽ«å¤§ãªäºˆç®—ãŒã‹ã‹ã‚‹ã¨ã—ã¦æ‰¹åˆ¤ã™ã‚‹å£°ã‚‚大ãã„。「ç«æ˜Ÿã«æ°´ãŒã‹ã¤ã¦ã‚ã£ãŸã€‚ãã‚ŒãŒã©ã†ã—ãŸã€‚我々ã®ç”Ÿæ´»ã«é–¢ä¿‚ã‚ã‚‹ã®ã‹? 予算を地çƒã®ãŸã‚ã«ä½¿ã†ã¹ãã ã€ã¨ã„ã†ã‚ˆã†ãªã‚‚ã®ã§ã‚る。実際ã«ã¯ï¼ˆã‚¢ãƒ¡ãƒªã‚«åˆè¡†å›½ã‚’例ã«å–ã‚Œã°ï¼‰å›½é˜²è²»ã®1/20以下ã®NASAã®äºˆç®—ã®ã€æ›´ã«ã”ã一部ãŒç«æ˜ŸæŽ¢æŸ»ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ã‚‹ã«éŽãŽãªã„ã®ã ãŒã€ã“ã†ã—ãŸå£°ã‚’無視ã™ã‚‹ã“ã¨ã‚‚出æ¥ãšã€æŽ¢æŸ»è¨ˆç”»ã®ä½Žã‚³ã‚¹ãƒˆåŒ–ãŒé€²ã‚られã¦ã„る。 +ç«æ˜Ÿã®è¦³æ¸¬[編集] +天çƒä¸Šã®ç«æ˜Ÿã®å‹•ã。 + +16世紀デンマークã®å¤©æ–‡å¦è€…ティコ・ブラーエã¯ã€åœ°çƒã‚’ä¸å¿ƒã«å¤ªé™½ï¼ˆç«æ˜Ÿãªã©æƒ‘星ã¯å¤ªé™½ã®å‘¨ã‚Šã‚’廻る)ãŒå»»ã‚‹å¤‰å‰‡çš„ãªå¤©å‹•èª¬ã‚’ã¨ã£ã¦ã„ãŸãŒã€è‚‰çœ¼ã«ã‚ˆã‚‹ã‚‚ã®ã§ã¯æœ€ã‚‚精密ã«ç«æ˜Ÿã®è»Œé“を観測ã—ãŸã€‚ティコ(慣習ã¨ã—ã¦å§“ã§ãªãåを通称ã¨ã™ã‚‹ï¼‰ã®åŠ©æ‰‹ã§ã‚ã£ãŸãƒ¨ãƒãƒã‚¹ãƒ»ã‚±ãƒ—ラーã¯å¸«ã®æ»å¾Œã€è¦³æ¸¬ãƒ‡ãƒ¼ã‚¿ã‚’解æžã™ã‚‹ã“ã¨ã§æƒ‘星ã®è»Œé“ãŒå††ã§ã¯ãªã楕円ã§ã‚ã‚‹ã“ã¨ã€ã•ã‚‰ã«ç«æ˜Ÿã®è»Œé“ã‹ã‚‰ä»–ã®æƒ‘星ã®è»Œé“も楕円ã§ã‚りケプラーã®æ³•å‰‡ã«å¾“ã†ã¨ã„ã†åœ°å‹•èª¬ã‚’主張ã—ãŸã€‚公転速度ãŒé€Ÿã観測ã—ã‚„ã™ã„ç«æ˜Ÿã®è»Œé“離心率ãŒå†¥çŽ‹æ˜Ÿã‚„水星ã«æ¬¡ã„ã§å¤§ãã„0.0934ã§ã‚ã£ãŸã“ã¨ã‚‚幸é‹ã§ã‚ã£ãŸã€‚ + +1877å¹´ã®ç«æ˜Ÿå¤§æŽ¥è¿‘ã¨ã‚¹ã‚アパレッリã®ç™ºè¡¨ã«å§‹ã¾ã£ãŸç«æ˜Ÿé‹æ²³èª¬ã«é‡å¤§ãªç–‘å•ã‚’投ã’ã‹ã‘ãŸã®ãŒã€ã‚¨ãƒƒã‚¸ãƒ¯ãƒ¼ã‚¹ãƒ»ã‚«ã‚¤ãƒ‘ーベルトã®æ唱者ã®ä¸€äººã§ã‚るカイパーã§ã‚る。1947å¹´ã€ç«æ˜Ÿã‚’赤外線帯ã§è¦³æ¸¬ã—ã€å¤§æ°—ã®æˆåˆ†ãŒäºŒé…¸åŒ–ç‚ç´ ã§ã‚ã‚‹ã¨ä¸»å¼µã—ãŸã€‚地çƒå¤§æ°—ã®é‡è¦ãªæˆåˆ†ã§ã‚ã‚‹çª’ç´ ã€é…¸ç´ ã€æ°´è’¸æ°—ã®ç—•è·¡ã¯è¦‹å½“ãŸã‚‰ãšã€æ–‡æ˜Žã‚’æŒã¤ç«æ˜Ÿäººã®å˜åœ¨ã¯ã»ã¼å¦å®šã•ã‚ŒãŸã€‚ +ãƒãƒƒãƒ–ル宇宙望é é¡ãŒå†™ã—ãŸç«æ˜Ÿã€‚ + +地çƒã¯780日(2å¹´ã¨7週間ã¨1日)ã”ã¨ã«ç«æ˜Ÿã‚’追ã„越ã—ã€ãã®ã¨ãã®è·é›¢ã¯ç´„8000万km(約4光分)ã¾ã§æŽ¥è¿‘ã™ã‚‹ã€‚ã—ã‹ã—ã€ç«æ˜Ÿè»Œé“ãŒæ¥•å††ã§ã‚ã‚‹ãŸã‚ã«æœ€æŽ¥è¿‘時ã®è·é›¢ã¯å¤‰åŒ–ã™ã‚‹ã€‚ç«æ˜Ÿã®è¿‘日点付近ã§æŽ¥è¿‘ã™ã‚Œã°æŽ¥è¿‘è·é›¢ã¯5600万km程度ã¨ãªã‚‹ãŒã€é 日点付近ã§æŽ¥è¿‘ã™ã‚Œã°1å„„km程度ã¨2å€è¿‘ãè·é›¢ãŒç•°ãªã‚‹ã€‚肉眼ã§è¦³æ¸¬ã—ã¦ã„ã‚‹ã¨ã€ç«æ˜Ÿã¯é€šå¸¸ã€ä»–ã®æ˜Ÿã¨ã¯ã£ãã‚Šç•°ãªã‚‹é»„色ã‚ã‚‹ã„ã¯ã‚ªãƒ¬ãƒ³ã‚¸è‰²ã‚„赤ã£ã½ã„色ã«è¦‹ãˆã€è»Œé“を公転ã™ã‚‹ã«ã¤ã‚Œã¦åœ°çƒã‹ã‚‰è¦‹ã‚‹ä»–ã®ã©ã®æƒ‘星よりも大ãã明るã•ãŒå¤‰åŒ–ã™ã‚‹ã€‚ã“ã‚Œã¯ã€ç«æ˜ŸãŒåœ°çƒã‹ã‚‰æœ€ã‚‚離れる時ã«ã¯æœ€ã‚‚è¿‘ã¥ã„ãŸæ™‚ã®7å€ä»¥ä¸Šã‚‚è·é›¢ãŒé›¢ã‚Œã‚‹ãŸã‚ã§ã‚る。ãªãŠã€å¤ªé™½ã¨åŒã˜æ–¹å‘ã«ã‚ã‚‹åˆå‰å¾Œã®æ•°ãƒ¶æœˆé–“ã¯å¤ªé™½ã®å…‰ã§è¦‹ãˆãªããªã‚‹ã“ã¨ã‚‚ã‚る。最も観測ã«é©ã—ãŸæ™‚期ã¯32å¹´ã”ã¨ã«2回ã€15å¹´ã¨17å¹´ã‚’ãŠã„ã¦äº¤äº’ã«ã‚„ã£ã¦ãã¦ã€Œå¤§æŽ¥è¿‘ã€ã¨å‘¼ã°ã‚Œã‚‹ã€‚ã“ã®æ™‚期ã¯å¸¸ã«7月終ã‚ã‚Šã‹ã‚‰9月終ã‚ã‚Šã®é–“ã«ãªã‚‹ã€‚ã“ã®æ™‚期ã«ç«æ˜Ÿã‚’望é é¡ã§è¦‹ã‚‹ã¨è¡¨é¢ã®æ§˜ã€…ãªæ§˜åを詳細ã«è¦‹ã‚‹ã“ã¨ãŒã§ãる。低å€çŽ‡ã§ã‚‚見ãˆã‚‹ç‰¹ã«ç›®ç«‹ã¤ç‰¹å¾´ã¯æ¥µå† ã§ã‚る。 + +2003å¹´8月27æ—¥9時51分13秒(世界時)ã«ç«æ˜Ÿã¯éŽåŽ»60,000å¹´ã§æœ€ã‚‚è¿‘ãã€55,758,006 kmã¾ã§åœ°çƒã«æŽ¥è¿‘ã—ãŸï¼ˆæƒ‘星光行差補æ£ãªã—ã§ã®å€¤ï¼‰ã€‚ã“ã®å¤§æŽ¥è¿‘ã¯ç«æ˜Ÿã®è¿‘日点通éŽã®3日後ãŒç«æ˜Ÿã®è¡ã®ç¿Œæ—¥ã¨é‡ãªã£ãŸãŸã‚ã«ç”Ÿã˜ãŸã‚‚ã®ã§ã€åœ°çƒã‹ã‚‰ç«æ˜Ÿã‚’特ã«è¦‹ã‚„ã™ããªã£ãŸã€‚ã“れ以å‰ã«æœ€ã‚‚è¿‘ã接近ã—ãŸã®ã¯ç´€å…ƒå‰57617å¹´9月12æ—¥ã¨è¨ˆç®—ã•ã‚Œã¦ã„ã‚‹[10]。太陽系ã®é‡åŠ›è¨ˆç®—ã®è©³ç´°ãªè§£æžã‹ã‚‰ã€2287å¹´ã«ã¯2003年よりも近ã„接近ãŒèµ·ã“ã‚‹ã¨è¨ˆç®—ã•ã‚Œã¦ã„る。ã—ã‹ã—æ£ç¢ºã«è¦‹ã¦ã„ãã¨ã€ã“ã®è¨˜éŒ²çš„ãªå¤§æŽ¥è¿‘ã¯284å¹´ã”ã¨ã«4回起ãã¦ã„る別ã®å¤§æŽ¥è¿‘よりもã”ãã‚ãšã‹ã«è¿‘ã„ã ã‘ã§ã‚ã‚‹ã“ã¨ãŒåˆ†ã‹ã‚‹ã€‚例ãˆã°ã€2003å¹´8月27æ—¥ã®æœ€æŽ¥è¿‘è·é›¢ãŒ 0.37271AU ã§ã‚ã‚‹ã®ã«å¯¾ã—ã¦1924å¹´8月22æ—¥ã®æœ€æŽ¥è¿‘è·é›¢ã¯ 0.37284AU ã§ã‚ã‚Šã€2208å¹´8月24æ—¥ã®æŽ¥è¿‘㯠0.37278AU ã§ã‚る。 + +2084å¹´11月10æ—¥ã«ã¯ç«æ˜Ÿã‹ã‚‰è¦‹ã¦åœ°çƒã®å¤ªé™½é¢é€šéŽãŒèµ·ã“る。ã“ã®æ™‚ã«ã¯å¤ªé™½ã¨åœ°çƒã€ç«æ˜ŸãŒä¸€ç›´ç·šä¸Šã«ä¸¦ã¶ã€‚åŒæ§˜ã«ç«æ˜Ÿã‹ã‚‰è¦‹ãŸæ°´æ˜Ÿã‚„金星ã®å¤ªé™½é¢é€šéŽã‚‚èµ·ã“る。ç«æ˜Ÿã®è¡›æ˜Ÿã§ã‚るダイモスã¯ç«æ˜Ÿã‹ã‚‰è¦‹ãŸè§’直径ãŒå¤ªé™½ã®ãれよりå分ã«å°ã•ã„ãŸã‚ã€ãƒ€ã‚¤ãƒ¢ã‚¹ã«ã‚ˆã‚‹éƒ¨åˆ†æ—¥é£Ÿã‚‚太陽é¢é€šéŽã¨è¦‹ãªã›ã‚‹ã€‚ + +1590å¹´10月13æ—¥ã«ã¯éŽåŽ»å”¯ä¸€ã®é‡‘星ã«ã‚ˆã‚‹ç«æ˜Ÿé£ŸãŒèµ·ã“ã‚Š[11]ã€ãƒ‰ã‚¤ãƒ„ã®ãƒã‚¤ãƒ‡ãƒ«ãƒ™ãƒ«ã‚¯ã§ãƒ¡ã‚¹ãƒˆãƒªãƒ³ã«ã‚ˆã£ã¦è¦³æ¸¬ã•ã‚ŒãŸã€‚ +ç«æ˜Ÿèµ·æºã®éš•çŸ³[編集] +「ALH84001ã€éš•çŸ³ + +地çƒä¸Šã§ç™ºè¦‹ã•ã‚ŒãŸã‚‚ã®ã®ã†ã¡ã€ç¢ºå®Ÿã«éš•çŸ³ã§ã‚ã‚Šã€ã‹ã¤ç«æ˜Ÿã«èµ·æºã‚’æŒã¤ã¨æ€ã‚れる岩石ãŒã„ãã¤ã‹çŸ¥ã‚‰ã‚Œã¦ã„る。ã“れらã®éš•çŸ³ã®ã†ã¡2ã¤ã‹ã‚‰ã¯å¤ä»£ã®ç´°èŒã®æ´»å‹•ã®ç—•è·¡ã‹ã‚‚ã—ã‚Œãªã„特徴ãŒè¦‹ã¤ã‹ã£ã¦ã„る。1996å¹´8月6æ—¥ã€NASAã¯ç«æ˜Ÿèµ·æºã¨è€ƒãˆã‚‰ã‚Œã¦ã„る「ALH84001ã€éš•çŸ³ã®åˆ†æžã‹ã‚‰ã€å˜ç´°èƒžç”Ÿå‘½ä½“ã®åŒ–石ã®å¯èƒ½æ€§ãŒã‚る特徴ãŒç™ºè¦‹ã•ã‚ŒãŸã¨ç™ºè¡¨ã—ãŸã€‚ã—ã‹ã—ã“ã®è§£é‡ˆã«ã¯ã„ã¾ã ã«è°è«–ã®ä½™åœ°ãŒã‚る。 + +『Solar System Researchã€2004å¹´3æœˆå· (38, p.97) ã«æŽ²è¼‰ã•ã‚ŒãŸè«–æ–‡ã§ã¯ã€ã‚¤ã‚¨ãƒ¡ãƒ³ã§ç™ºè¦‹ã•ã‚ŒãŸã‚«ã‚¤ãƒ‰ã‚¥ãƒ³éš•çŸ³ãŒç«æ˜Ÿã®è¡›æ˜Ÿãƒ•ã‚©ãƒœã‚¹ã«èµ·æºã‚’æŒã¤å¯èƒ½æ€§ãŒã‚ã‚‹ã¨ç¤ºå”†ã—ã¦ã„る。 + +2004å¹´4月14æ—¥ã«NASAã¯ã€ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦èª¿æŸ»ã•ã‚ŒãŸ "Bounce" ã¨ã„ã†åå‰ã®å²©çŸ³ãŒã€1979å¹´ã«å—極ã§ç™ºè¦‹ã•ã‚ŒãŸéš•çŸ³ã€ŒEETA79001-Bã€ã¨ä¼¼ãŸçµ„æˆã‚’æŒã£ã¦ã„ã‚‹ã“ã¨ã‚’明らã‹ã«ã—ãŸ[12]。ã“ã®å²©çŸ³ã¯ã“ã®éš•çŸ³ã¨åŒã˜ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‹ã‚‰é£›æ•£ã—ãŸã‹ã€ã‚ã‚‹ã„ã¯ç«æ˜Ÿè¡¨é¢ã®åŒã˜åœ°åŸŸã«ã‚る別々ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‹ã‚‰é£›ã°ã•ã‚ŒãŸå¯èƒ½æ€§ãŒã‚る。 +æ°·ã®æ¹–[編集] + +2005å¹´7月29æ—¥ã€BBCã¯ç«æ˜Ÿã®åŒ—極地方ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã§æ°·ã®æ¹–ãŒç™ºè¦‹ã•ã‚ŒãŸã¨å ±ã˜ãŸ[13]。ESAã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機ã«æ載ã•ã‚ŒãŸé«˜è§£åƒåº¦ã‚¹ãƒ†ãƒ¬ã‚ªã‚«ãƒ¡ãƒ©ã§æ’®å½±ã•ã‚ŒãŸã“ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®ç”»åƒã«ã¯ã€åŒ—ç·¯70.5度ã€æ±çµŒ103度ã«ä½ç½®ã—ç«æ˜ŸåŒ—極域ã®å¤§åŠã‚’å ã‚るボレアリス平野ã«ã‚ã‚‹ç„¡åã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®åº•ã«å¹³ã‚‰ãªæ°·ãŒåºƒãŒã£ã¦ã„る様åãŒã¯ã£ãã‚Šã¨å†™ã£ã¦ã„る。ã“ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ç›´å¾„35kmã§æ·±ã•ç´„2kmã§ã‚る。 + +BBCã®å ±é“ã§ã¯ã‚„や誇張ã•ã‚Œã¦ã„ã‚‹ãŒã€å…ƒã€…ã®ESAã®ç™ºè¡¨ã§ã¯ã“ã‚ŒãŒæ¹–ã§ã‚ã‚‹ã¨ã¯ä¸»å¼µã—ã¦ã„ãªã„[14]。ç«æ˜Ÿã®æ•°å¤šãã®ä»–ã®å ´æ‰€ã«è¦‹ã‚‰ã‚Œã‚‹ã‚‚ã®ã¨åŒæ§˜ã«ã€ã“ã®å††æ¿çŠ¶ã®æ°·ã¯æš—ã低温ã®ç ‚丘ã®é ‚上(高度約200m)ã«è–„ã„層状ã®éœœãŒå‡çµã—ã¦ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®åº•ã«åºƒãŒã£ãŸã‚‚ã®ã§ã‚ã‚‹ã€‚å ±ã˜ã‚‰ã‚ŒãŸã“ã®æ°·ãŒç‰¹ã«çã—ã„ã®ã¯ã€éœœã®ã„ãらã‹ãŒä¸€å¹´ä¸æ®‹ã‚Šã†ã‚‹ã»ã©ã“ã®å ´æ‰€ãŒé«˜ç·¯åº¦ã«ã‚ã‚‹ã¨ã„ã†ç‚¹ã ã‘ã§ã‚る。赤é“付近ã¯æ—¥ä¸20℃を越ã™ã“ã¨ã‚‚ã‚ã‚Šã€é«˜ç·¯åº¦ã§ãªã‘ã‚Œã°æ°·ã¯å˜åœ¨ã§ããªã„[15]。ã¾ãŸã€æ¶²ä½“ã®æ°´ã‚‚ã€ç«æ˜Ÿã®å¤§æ°—ã¯å¸Œè–„ã€ã™ãªã‚ã¡å¤§æ°—ä¸ã®æ°´è’¸æ°—圧ãŒå°ã•ã„ãŸã‚ã€ç«æ˜Ÿè¡¨é¢ã®ã»ã¨ã‚“ã©ã®åœ°åŸŸã§ã¯ã™ã蒸発ã—ã¦ã—ã¾ã†ã®ã§å˜åœ¨ã§ããªã„。液体ã®æ°´ãŒå˜åœ¨ã§ãã‚‹ã®ã¯ãƒ˜ãƒ©ã‚¹ç›†åœ°ãªã©é™ã‚‰ã‚ŒãŸå ´æ‰€ã®ã¿ã§ã‚る。 +ç«æ˜Ÿã®ç”Ÿå‘½[編集] +詳細ã¯ã€Œç«æ˜Ÿã®ç”Ÿå‘½ã€ã‚’å‚ç…§ +å¡©æ°´ã®ã‚ˆã†ãªæ¶²ä½“ãŒæµã‚Œå‡ºãŸã¨ã¿ã‚‰ã‚Œã‚‹è·¡ã€‚ + +ç«æ˜Ÿã¯ã‹ã¤ã¦ã¯ç¾åœ¨ã‚ˆã‚Šã‚‚確実ã«ç”Ÿå‘½ã«é©ã—ãŸç’°å¢ƒã ã£ãŸã¨ã„ã†è¨¼æ‹ ãŒå˜åœ¨ã™ã‚‹ãŒã€ç«æ˜Ÿã«ã‹ã¤ã¦å®Ÿéš›ã«ç”Ÿå‘½ä½“ãŒç”Ÿå˜ã—ã¦ã„ãŸã‹ã©ã†ã‹ã¨ã„ã†ç–‘å•ã¯æœªè§£æ±ºã§ã‚る。ç«æ˜Ÿèµ·æºã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る岩石(特ã«ã€ŒALH84001ã€éš•çŸ³ï¼‰ã«éŽåŽ»ã®ç”Ÿå‘½æ´»å‹•ã®è¨¼æ‹ ãŒå«ã¾ã‚Œã¦ã„ã‚‹ã¨è€ƒãˆã¦ã„ã‚‹ç ”ç©¶è€…ã‚‚ã„ã‚‹ãŒã€ã“ã®ä¸»å¼µã«å¯¾ã—ã¦ã¯ç¾çŠ¶ã§ã¯åˆæ„ã¯å¾—られã¦ã„ãªã„。ã“ã®éš•çŸ³ã¯æ•°åå„„å¹´å‰ã«ç”Ÿã¾ã‚Œã¦ä»¥æ¥ã€æ¶²ä½“ã®æ°´ãŒå˜åœ¨ã§ãるよã†ãªæ¸©åº¦ã«ä¸€å®šæœŸé–“ã•ã‚‰ã•ã‚ŒãŸã“ã¨ã¯ãªã„ã“ã¨ã‚’示ã™ç ”究もã‚る。 + +ãƒã‚¤ã‚ング探査機ã«ã¯ãã‚Œãžã‚Œã®ç€é™¸åœ°ç‚¹ã§ç«æ˜Ÿã®åœŸå£Œã«å«ã¾ã‚Œã‚‹å¾®ç”Ÿç‰©ã‚’検出ã™ã‚‹ãŸã‚ã®å®Ÿé¨“装置ãŒæ載ã•ã‚Œã€é™½æ€§ã®çµæžœã‚’ã„ãã¤ã‹å¾—ãŸãŒã€å¾Œã«å¤šãã®ç§‘å¦è€…ã«ã‚ˆã£ã¦å¦å®šã•ã‚ŒãŸã€‚ã“ã®ä»¶ã«ã¤ã„ã¦ã¯ç¾åœ¨ã‚‚è°è«–ãŒç¶šã„ã¦ã„る。ã¾ãŸã€ç«æ˜Ÿã®å¤§æ°—ã«ãƒ¡ã‚¿ãƒ³ãŒã”ãå¾®é‡å˜åœ¨ã—ã¦ã„ã‚‹åŽŸå› ã«ã¤ã„ã¦ã€ç¾åœ¨ç”Ÿå‘½æ´»å‹•ãŒé€²è¡Œã—ã¦ã„ã‚‹ã¨ã„ã†èª¬ãŒä¸€ã¤ã®è§£é‡ˆã¨ã—ã¦æ案ã•ã‚Œã¦ã„ã‚‹ãŒã€ç”Ÿå‘½æ´»å‹•ã«ç”±æ¥ã—ãªã„別ã®èª¬ã®æ–¹ãŒã‚ˆã‚Šã‚‚ã£ã¨ã‚‚らã—ã„ã¨ä¸€èˆ¬ã«è€ƒãˆã‚‰ã‚Œã¦ã„る。 + +ç¾åœ¨ã®ç«æ˜Ÿã¯ã€ãƒãƒ“タブルゾーン内(生命å˜åœ¨ã®å¯èƒ½ãªå¤©ä½“ãŒã€å˜åœ¨ã§ãã‚‹é ˜åŸŸï¼‰ã«ã‚ã‚‹ã¨ã„ã†[16]。 + +å°†æ¥æ¤æ°‘地化ãŒè¡Œãªã‚れるã¨ã™ã‚Œã°ã€ç«æ˜Ÿã¯ï¼ˆå¤ªé™½ç³»ã«å±žã™ã‚‹åœ°çƒä»¥å¤–ã®æƒ‘星ã¨æ¯”較ã—ã¦ï¼‰ã‹ãªã‚Šç”Ÿå‘½ã®ç”Ÿå˜ã«é©ã—ãŸæ¡ä»¶ã«ã‚ã‚‹ãŸã‚ã€æœ‰åŠ›ãªé¸æŠžè‚¢ã¨ãªã‚‹ã¨æ€ã‚れる。 +人類ã¨ç«æ˜Ÿ[編集] +æ´å²ã¨ç¥žè©±[編集] + +ç«æ˜Ÿã®å称 (Marsï¼ãƒžãƒ¼ã‚º) ã¯ã€ãƒãƒ¼ãƒžç¥žè©±ã®ç¥žãƒžãƒ«ã‚¹ï¼ˆã‚®ãƒªã‚·ã‚¢ç¥žè©±ã®è»ç¥žã‚¢ãƒ¬ãƒ¼ã‚¹ï¼‰ã‹ã‚‰å付ã‘られãŸã€‚メソãƒã‚¿ãƒŸã‚¢ã®æ°‘ã¯èµ¤ã„惑星ã«æˆ¦ç«ã¨è¡€ã‚’連想ã—ã¦å½¼ã‚‰ã®æˆ¦ç¥žãƒãƒ«ã‚¬ãƒ«ã®åã‚’å† ã—ã¦ä»¥æ¥ã€ç«æ˜Ÿã«ã¯å„々ã®åœ°ã§ãã®åœ°ã®æˆ¦ç¥žã®åãŒã¤ã‘られã¦ã„る(他ã®æƒ‘星åã«ã¤ã„ã¦ã‚‚ã»ã¼åŒæ§˜ã®ç¶™æ‰¿ãŒèªã‚られる)。 +æ±æ´‹[編集] + +ç«æ˜Ÿã¯äº”行説ã«åŸºã¥ãオカルト的ãª[è¦å‡ºå…¸]呼ã³åã§ã‚ã£ã¦ï¼ˆäº”行説ã¯æ±æ´‹åŒ»å¦ã®åŸºç¤Žç†è«–ã§ã‚‚ã‚る)ã€å¦å•ä¸Šï¼ˆå¤©æ–‡å²æ–™ï¼‰ã§ã¯ç†’惑(ケイコクã€ã‚¨ã‚¤ã‚³ã‚¯ï¼‰ã¨ã„ã£ãŸã€‚「熒ã€ã¯ã—ã°ã—ã°åŒéŸ³ã®ã€Œèž¢ã€ã¨èª¤ã‚‰ã‚Œã‚‹ã€‚ã¾ãŸã€ã“ã®å ´åˆã®ã€Œæƒ‘ã€ã¯ã€Œãƒ¯ã‚¯ã€ã§ã¯ãªã「コクã€ã¨èªã‚€ã€‚営惑ã¨ã‚‚書ã。江戸時代ã«ã¯ã€Œãªã¤ã²ã¼ã—ã€ã¨è¨“ã˜ã‚‰ã‚ŒãŸã€‚ãã®ãŸã‚å¤æ—¥æ˜Ÿã¨ã„ã†å’Œåã‚‚ã‚る。 + +ç«æ˜ŸãŒã•ãり座ã®ã‚¢ãƒ³ã‚¿ãƒ¬ã‚¹ï¼ˆé»„é“ã®è¿‘ãã«ä½ç½®ã—ã¦ã„ã‚‹ãŸã‚)ã«æŽ¥è¿‘ã™ã‚‹ã“ã¨ã‚’熒惑守心(熒惑心を守る)ã¨ã„ã„ã€ä¸å‰ã®å‰å…†ã¨ã•ã‚ŒãŸã€‚「心ã€ã¨ã¯ã€ã‚¢ãƒ³ã‚¿ãƒ¬ã‚¹ãŒæ‰€å±žã™ã‚‹æ˜Ÿå®˜ï¼ˆä¸å›½ã®æ˜Ÿåº§ï¼‰å¿ƒå®¿ã®ã“ã¨ã€‚ +å 星術[編集] + +ç«æ˜Ÿã¯ä¸ƒæ›œãƒ»ä¹æ›œã®1ã¤ã§ã€10大天体ã®1ã¤ã§ã‚る。 + +西洋å 星術ã§ã¯ã€ç™½ç¾Šå®®ã®æ”¯é…星ã§ã€å¤©èŽå®®ã®å‰¯æ”¯é…星ã§ã€å‡¶æ˜Ÿã§ã‚る。ç©æ¥µæ€§ã‚’示ã—ã€é‹å‹•ã€äº‰ã„ã€å¤–科ã€å¹´ä¸‹ã®ç”·ã«å½“ã¦ã¯ã¾ã‚‹[17]。 +惑星記å·[編集] +Mars symbol.ant.png + +ç«æ˜Ÿã®æƒ‘星記å·ã¯ãƒžãƒ«ã‚¹ã‚’象徴ã™ã‚‹ç›¾ã¨æ§ã‚’図案化ã—ãŸã‚‚ã®ãŒã€å 星術・天文å¦ã‚’通ã—ã¦ç”¨ã„られる。ã“れを雌雄ã®è¡¨è¨˜ã«è»¢ç”¨ã—ãŸã®ã¯ã‚«ãƒ¼ãƒ«ãƒ»ãƒ•ã‚©ãƒ³ãƒ»ãƒªãƒ³ãƒã§ã‚ã‚Šã€ç”Ÿæ®–器ã®å›³æ¡ˆã§ã¯ãªã„。 +ç«æ˜Ÿã‚’扱ã£ãŸä½œå“[編集] +詳細ã¯ã€Œç«æ˜Ÿã‚’扱ã£ãŸä½œå“一覧ã€ã‚’å‚ç…§ diff --git a/xpcom/tests/gtest/wikipedia/ko.txt b/xpcom/tests/gtest/wikipedia/ko.txt new file mode 100644 index 0000000000..7c11333ad4 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ko.txt @@ -0,0 +1,110 @@ +화성(ç«æ˜Ÿ, Mars)ì€ íƒœì–‘ê³„ì˜ ë„¤ 번째 행성ì´ë‹¤. 붉ì€ìƒ‰ì„ ë 기 ë•Œë¬¸ì— ë™ì–‘권ì—서는 ë¶ˆì„ ëœ»í•˜ëŠ” í™”(ç«)를 ì¨ì„œ 화성 ë˜ëŠ” 형혹성(熒惑星)ì´ë¼ ë¶€ë¥´ê³ , 서양권ì—서는 로마 ì‹ í™”ì˜ ì „ìŸì˜ ì‹ ë§ˆë¥´ìŠ¤ì˜ ì´ë¦„ì„ ë”° Marsë¼ ë¶€ë¥¸ë‹¤. ì˜¤ëŠ˜ë‚ ì˜ì–´ì—ì„œ 3ì›”ì„ ëœ»í•˜ëŠ” Marchë„ ì—¬ê¸°ì„œ ìƒê²¼ë‹¤. + +매리너 4호가 1965ë…„ì— í™”ì„±ì„ ì²˜ìŒìœ¼ë¡œ ê·¼ì ‘ ë¹„í–‰ì„ í•˜ê¸° ì „ê¹Œì§€ 과학계 ì•ˆíŒŽì˜ ì‚¬ëžŒë“¤ì€ í™”ì„±ì— ëŒ€ëŸ‰ì˜ ë¬¼ì´ ì¡´ìž¬í•˜ë¦¬ë¼ê³ 기대하였다. ì´ëŸ¬í•œ ê¸°ëŒ€ì˜ ê·¼ê±°ëŠ” í™”ì„±ì˜ ê·¹ì§€ë°©ì—ì„œ ë°ê³ ì–´ë‘ìš´ 무늬가 주기ì 으로 변화한다는 사실ì´ì—ˆë‹¤. 60년대 중반 ì´ì „까지 ì‚¬ëžŒë“¤ì€ ë†ì—…ì„ ìœ„í•œ 관개수로가 í™”ì„±ì— ìžˆìœ¼ë¦¬ë¼ ê¸°ëŒ€í•˜ê¸°ê¹Œì§€ 했다. ì´ëŠ” 사실 20세기 ì´ˆÂ·ì¤‘ë°˜ì˜ ê³µìƒê³¼í•™ ìž‘ê°€ë“¤ì˜ ìƒìƒì— ì˜í–¥ë°›ì€ 것으로, 1950년대 ì´í›„ì˜ íƒì‚¬ì„ ì— ì˜í•œ 관측으로 화성 운하는 존재하지 않았ìŒì´ ë°í˜€ì¡Œë‹¤. + +물과 ìƒëª…ì²´ì˜ ë°œê²¬ì— ëŒ€í•œ 기대로 ë§Žì€ íƒì‚¬ì„ ë“¤ì— ë¯¸ìƒë¬¼ì„ 찾기 위한 ì„¼ì„œë“¤ì´ íƒ‘ìž¬ë˜ì–´ í™”ì„±ì— ë³´ë‚´ì¡Œë‹¤. 화성ì—서는 ë‹¤ëŸ‰ì˜ ì–¼ìŒì´ 발견ë˜ì—ˆê³ , ìƒëª…ì²´ê°€ ì¡´ìž¬í• ê°€ëŠ¥ì„±ì´ ì œê¸°ë˜ê³ 있다.[4] + +í™”ì„±ì˜ ìžì „ 주기와 ê³„ì ˆì˜ ë³€í™” 주기는 지구와 비슷하다. 화성ì—는 태양계ì—ì„œ 가장 ë†’ì€ ì‚°ì¸ ì˜¬ë¦¼í‘¸ìŠ¤ í™”ì‚°ì´ ìžˆìœ¼ë©°, ì—ì‹œ 태양계ì—ì„œ 가장 í° ê³„ê³¡ì¸ ë§¤ë¦¬ë„ˆìŠ¤ 협곡과 ê·¹ê´€ì„ ê°€ì§€ê³ ìžˆë‹¤. + +물리ì ì¸ íŠ¹ì„±[편집] +지구와 í™”ì„±ì˜ í¬ê¸° ë¹„êµ + +í™”ì„±ì€ ë¶‰ê²Œ 타는 듯한 ì™¸í˜•ì„ ê°€ì§€ê³ ìžˆë‹¤. í™”ì„±ì˜ í‘œë©´ì ì€ ì§€êµ¬ì˜ 4ë¶„ì˜ 1ë°–ì— ë˜ì§€ 않으며, 부피는 10ë¶„ì˜ 1ë°–ì— ë˜ì§€ 않는다. í™”ì„±ì€ ë‘ ê°œì˜ ìž‘ì€ ìœ„ì„±ì„ ê°€ì§€ê³ ìžˆë‹¤. í™”ì„±ì˜ ëŒ€ê¸°ê¶Œì€ ë§¤ìš° 얇으며, í‘œë©´ì˜ ê¸°ì••ì€ 7.5ë°€ë¦¬ë°”ë°–ì— ë˜ì§€ 않는다. 화성 í‘œë©´ì˜ 95%는 ì´ì‚°í™”탄소로 ë®ì—¬ 있으며, ì´ ë°–ì— 3%ì˜ ì§ˆì†Œ, 1.6%ì˜ ì•„ë¥´ê³¤ê³¼ í”ì ë§Œì´ ë‚¨ì•„ 있는 산소와 2015ë…„ NASAì—ì„œ 발견한 ì•¡ì²´ ìƒíƒœì˜ ë¬¼ì´ í¬í•¨ë˜ì–´ 있다. +지질[편집] + +궤ë„ì„ ì˜ ê´€ì¸¡ê³¼ 화성 기ì›ì˜ ìš´ì„ì— ëŒ€í•œ ë¶„ì„ ê²°ê³¼ì— ì˜í•˜ë©´, í™”ì„±ì˜ í‘œë©´ì€ ê¸°ë³¸ì 으로 현무암으로 ë˜ì–´ 있다. 화성 í‘œë©´ì˜ ì¼ë¶€ëŠ” ì§€êµ¬ì˜ ì•ˆì‚°ì•”ê³¼ ê°™ì´ ì¢€ ë” ì´ì‚°í™”규소가 í’부하다는 ì¦ê±°ê°€ 있으나 ì´ëŸ¬í•œ ê´€ì¸¡ì€ ê·œì‚°ì—¼ê³¼ ê°™ì€ ìœ ë¦¬ì˜ ì¡´ìž¬ë¥¼ 통해서 설명ë ìˆ˜ë„ ìžˆê¸° ë•Œë¬¸ì— ê²°ì •ì ì´ì§€ëŠ” 않다. í‘œë©´ì˜ ëŒ€ë¶€ë¶„ì€ ì‚°í™”ì² ì˜ ë¨¼ì§€ë¡œ ë®ì—¬ìžˆë‹¤. í™”ì„±ì˜ í‘œë©´ì— ì¼ì‹œì ì´ë‚˜ë§ˆ ë¬¼ì´ ì¡´ìž¬í–ˆë‹¤ëŠ” ê²°ì •ì ì¸ ì¦ê±°ê°€ 있다. 화성 표면ì—ì„œ ë°œê²¬ëœ ì•”ì—¼ì´ë‚˜ ì¹¨ì² ì„ê³¼ ê°™ì´ ëŒ€ì²´ë¡œ ë¬¼ì´ ì¡´ìž¬í• ë•Œ ìƒì„±ë˜ëŠ” ê´‘ë¬¼ì´ ë°œê²¬ë˜ì—ˆê¸° 때문ì´ë‹¤. + +ë¹„ë¡ í™”ì„± ìžì²´ì˜ ìžê¸°ìž¥ì€ 없지만, 과거 행성 í‘œë©´ì˜ ì¼ë¶€ëŠ” ìží™”ëœ ì ì´ ìžˆìŒì´ ê´€ì¸¡ì„ í†µí•´ ë°í˜€ì¡Œë‹¤. 화성ì—ì„œ ë°œê²¬ëœ ìží™”ì˜ í”ì (ê³ ì§€ìžê¸°)ì€ ì§€êµ¬ì˜ í•´ì–‘ì§€ê°ì—ì„œ 발견ë˜ëŠ” êµëŒ€í•˜ëŠ” ë ëª¨ì–‘ì˜ ê³ ì§€ìžê¸°ì™€ 비êµë˜ì–´ 왔다. 1999ë…„ì— ë°œí‘œë˜ê³ 2005ë…„ì— ë§ˆìŠ¤ 글로벌 ì„œë² ì´ì–´ë¡œë¶€í„°ì˜ 관측 ê²°ê³¼ì˜ ë„움으로 ìž¬ê²€í† ëœ ì´ë¡ ì— ë”°ë¥´ë©´, ì´ë“¤ 지ìžê¸°ì˜ ë ë“¤ì€ ê³¼ê±°ì— ìžˆì—ˆë˜ í™”ì„±ì˜ íŒêµ¬ì¡° 활ë™ì˜ ì¦ê±°ì¼ 수 있다. ê·¹ ì´ë™(polar wandering)ìœ¼ë¡œë„ í™”ì„±ì—ì„œ ë°œê²¬ëœ ê³ ì§€ìžê¸°ë¥¼ ì„¤ëª…í• ìˆ˜ 있었다. + +í™”ì„±ì˜ ë‚´ë¶€ë¥¼ 설명하는 ì´ë¡ ì— ë”°ë¥´ë©´, 화성 í•µì˜ ë°˜ì§€ë¦„ì€ ì•½ 1,480kmë¡œ 주로 ì² ê³¼ 15~17%ì˜ í™©ìœ¼ë¡œ ì´ë£¨ì–´ì ¸ 있다. í™©í™”ì² ì˜ í•µì€ ë¶€ë¶„ì 으로 용융ë˜ì–´ 있으며, ì§€êµ¬ì˜ í•µì— ë¹„í•˜ë©´ 가벼운 ì›ì†Œì˜ í•¨ëŸ‰ì´ ì•½ 2ë°°ì •ë„ ëœë‹¤. í•µì€ ê·œì‚°ì—¼ì§ˆ ë§¨í‹€ì— ë‘˜ëŸ¬ì‹¸ì—¬ 있다. ë§¨í‹€ì€ í™”ì„±ì—ì„œ ë³¼ 수 있는 ë§Žì€ íŒêµ¬ì¡° 활ë™ê³¼ 화산 활ë™ì„ ì¼ìœ¼ì¼œ 왔으나 현재는 ë” ì´ìƒ 활ë™í•˜ì§€ 않는다. 화성 지ê°ì˜ ë‘께는 약 50kmì´ê³ , ìµœëŒ“ê°’ì€ 125kmì •ë„ì´ë‹¤. + +í™”ì„±ì˜ ì§€ì§ˆ 시대는 세 시대로 구분ëœë‹¤. + +노아키안 시대는 노아키스 í…Œë¼ì˜ ì´ë¦„ì„ ë”°ì„œ 붙여진 ì´ë¦„ì´ë‹¤. í™”ì„±ì˜ í˜•ì„±ìœ¼ë¡œë¶€í„° 38ì–µ~35ì–µ ë…„ ì „ê¹Œì§€ì˜ ì‹œëŒ€ì´ë‹¤. 노아키안 ì‹œëŒ€ì˜ í‘œë©´ì€ ë§Žì€ ê±°ëŒ€í•œ í¬ë ˆì´í„°ë¡œ ë®ì—¬ 있다. 타르시스 벌지는 ì´ ì‹œëŒ€ì— í˜•ì„±ëœ ê²ƒìœ¼ë¡œ 여겨진다. ì´ ì‹œëŒ€ì˜ í›„ê¸°ì—는 ì—„ì²ë‚œ ì–‘ì˜ ì•¡ì²´ ë¬¼ì— ì˜í•œ í™ìˆ˜ê°€ ìžˆì—ˆë‹¤ê³ ìƒê°ëœë‹¤. + +헤스í¼ë¦¬ì•ˆ 시대는 헤스í¼ë¦¬ì•ˆ í‰ì›ìœ¼ë¡œë¶€í„° ì´ë¦„ì´ ë¶™ì—¬ì¡Œë‹¤. 35ì–µ ë…„ ì „ë¶€í„° 18ì–µ ë…„ ì „ê¹Œì§€ì˜ ì‹œëŒ€ì´ë‹¤. 헤스í¼ë¦¬ì•ˆ ì‹œëŒ€ì˜ í™”ì„±ì—서는 ë„“ì€ ìš©ì•”ëŒ€ì§€ê°€ 형성ë˜ì—ˆë‹¤. + +아마조니안 시대는 아마조니스 í‰ì›ì˜ ì´ë¦„ì„ ë”°ì„œ 붙여졌다. 18ì–µ ë…„ ì „ë¶€í„° í˜„ìž¬ì— ì´ë¥´ëŠ” 시대ì´ë‹¤. 아마조니안 지ì—ì€ í¬ë ˆì´í„°ê°€ ê±°ì˜ ì—†ìœ¼ë‚˜ ìƒë‹¹í•œ 변화가 있는 지형ì´ë‹¤. 올림푸스 í™”ì‚°ì´ ì´ ì‹œëŒ€ì— í˜•ì„±ë˜ì—ˆê³ , 다른 지ì—ì—ì„œ 용암류가 형성ë˜ì—ˆë‹¤. + +마스 ìµìŠ¤í”„ë ˆìŠ¤ ì˜¤ë¹„í„°ì˜ OMEGA 가시광-ì ì™¸ì„ ê´‘ë¬¼í•™ 매핑 스팩트로메터 ìžë£Œë¥¼ 기초로 ë˜ ë‹¤ë¥¸ 시대 êµ¬ë¶„ì´ ì œì‹œë˜ê³ 있다. +지형[편집] + +í™”ì„±ì˜ ì¢Œí‘œë¥¼ ì„¤ì •í•˜ê¸° 위하여서는 ìžì˜¤ì„ ê³¼ 0ì ê³ ë„ê°€ ì •í•´ì ¸ì•¼ 한다. 화성ì—는 바다가 없기 때문ì—, '해수면'ì´ ì—†ì–´ì„œ, 0ì ê³ ë„ë©´ì´ë‚˜ í‰ê· ì¤‘ë ¥ í‘œë©´ì´ ìž„ì˜ì˜ 지ì 으로 ì„ íƒë ìˆ˜ë°–ì— ì—†ë‹¤. ë˜í•œ ì ë„와는 달리 ê²½ë„ì˜ ê¸°ì¤€ì ì€ ìž„ì˜ë¡œ ì„ íƒì´ 가능하기 ë•Œë¬¸ì— ê³µí†µëœ ê·œì•½ì„ ì •í• í•„ìš”ê°€ 있다. 그리하여 ìž„ì˜ì 으로 사ì´ë„ˆìŠ¤ 메리디아니(Sinus Meridiani, ì ë„만('Equatorial Gulf')) ì•ˆì˜ ë¶„í™”êµ¬ê°€ 0ì ìžì˜¤ì„ ì„ ë‚˜íƒ€ë‚´ëŠ” 것으로 ì„ íƒë˜ì—ˆë‹¤. + +화성 ì§€í˜•ì˜ ëª‡ 가지 기본ì ì¸ íŠ¹ì§•ì€ ë‹¤ìŒê³¼ 같다. í™”ì„±ì€ ê·¹ ì§€ë°©ì´ ì–¸ 물과 ì´ì‚°í™”탄소를 í¬í•¨í•˜ëŠ” ì–¼ìŒ ì§€ëŒ€ë¡œ ë®ì—¬ 있다. ë˜í•œ 화성ì—는 ë°œë ˆìŠ¤ 매리너리스(Valles Marineris) ë˜ëŠ” í™”ì„±ì˜ í‰í„°ë¼ê³ 불리는 태양계ì—ì„œ 가장 í° [협곡 지대]ê°€ 있다. ì´ í˜‘ê³¡ 지대는 4000kmì˜ ê¸¸ì´ì— 깊ì´ëŠ” 7kmì— ì´ë¥¸ë‹¤. + +화성 ë¶ë°˜êµ¬ì™€ 남반구 ì§€í˜•ì˜ ë¹„ëŒ€ì¹ì„±ì€ 매우 ì¸ìƒì ì´ë‹¤. ë¶ìª½ ë¶€ë¶„ì€ ìš©ì•”ì¸µì´ í˜ëŸ¬ë‚´ë¦¼ìœ¼ë¡œ ì¸í•´ í‰í‰í•˜ê³ , ë‚¨ìª½ì€ ê³ ì§€ëŒ€ì— ì˜¤ëž˜ì „ì˜ ì¶©ê²©ìœ¼ë¡œ ì¸í•´ 구ë©ì´ 파ì´ê³ 분화구가 ìƒê²¨ë‚˜ 있다. 지구ì—ì„œ 본 í™”ì„±ì˜ í‘œë©´ì€ í™•ì‹¤ížˆ ë‘ ë¶€ë¶„ì˜ êµ¬ì—으로 나뉘어 있다. 먼지와 ì‚°í™”ì² ì´ ì„žì¸ ëª¨ëž˜ë¡œ ë’¤ë®ì¸ 좀 ë” ì°½ë°±í•œ ë¶€ë¶„ì€ í•œë•Œ 'ì•„ë¼ë¹„ì•„ì˜ ë•…'ì´ë¼ 불리며 í™”ì„±ì˜ ëŒ€ë¥™ìœ¼ë¡œ ì—¬ê²¨ì¡Œê³ , ì–´ë‘ìš´ ë¶€ë¶„ì€ ë°”ë‹¤ë¡œ 여겨졌다. 지구ì—ì„œ ë³´ì´ëŠ” 가장 ì–´ë‘ìš´ ë¶€ë¶„ì€ ì‹œë¥´í‹°ìŠ¤ ë©”ì´ì €(Syrtis Major)ì´ë‹¤. 화성ì—ì„œ 가장 í° ë¶„í™”êµ¬ëŠ” í—¬ë¼ìŠ¤ ì¶©ëŒ ë¶„ì§€(Hellas impact basin)ì¸ë°, 가벼운 ë¶‰ì€ ëª¨ëž˜ë¡œ ë®ì—¬ 있다. + +화성 표면 지ì—ì˜ ì´ë¦„ì„ ì§“ëŠ” ìž‘ì—…ì€ êµì œ 천문 ì—°ë§¹ì˜ '행성계 명명법 워킹 그룹'ì´ ë‹´ë‹¹í•˜ê³ ìžˆë‹¤.. +대기[편집] +ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„±ì˜ ëŒ€ê¸°ìž…ë‹ˆë‹¤. +í™”ì„±ì˜ ì„ì–‘. '스피릿'호 ì´¬ì˜ + +í™”ì„±ì˜ ëŒ€ê¸°ì••ì€ 0.7ì—ì„œ 0.9kPaë¡œ, ì§€êµ¬ì˜ ëŒ€ê¸° ë°€ë„와 비êµí•˜ë©´ 1/100 ì •ë„ë¡œ 매우 낮다. 대기가 ì 으므로 ê¸°ì••ì´ ë§¤ìš° ë‚®ê³ ë¬¼ì´ ìžˆë”ë¼ë„ 기압 ë•Œë¬¸ì— ë¹¨ë¦¬ ì¦ë°œí•˜ê²Œ ëœë‹¤. 과학ìžë“¤ì€ ê³¼ê±°ì˜ í™”ì„±ì€ ë¬¼ì´ í’ë¶€í•˜ê³ ëŒ€ê¸°ë„ ì§€ê¸ˆë³´ë‹¤ 컸으리ë¼ê³ 추측한다. ëŒ€ê¸°ì˜ ì£¼ì„±ë¶„ì¸ ì´ì‚°í™”탄소가 얼어 거대한 ê·¹ê´€ì„ í˜•ì„±í•˜ëŠ” ê³¼ì •ì´ ì–‘ê·¹ì—ì„œ êµëŒ€ë¡œ ì¼ì–´ë‚˜ê³ ì´ì‚°í™”탄소는 ëˆˆì¸µì„ í˜•ì„±í•˜ê³ ë´„ì´ ë˜ë©´ ì¦ë°œí•œë‹¤. +ìžê¸°ê¶Œ[편집] + +아주 ì˜¤ëž˜ì „ í™”ì„±ì€ íƒœì–‘í’ì„ ë§‰ì„ ìˆ˜ ìžˆì„ ë§Œí¼ ì¶©ë¶„ížˆ ê°•í•œ ìžê¸°ê¶Œì„ ê°€ì§€ê³ ìžˆì—ˆìœ¼ë¦¬ë¼ ì—¬ê²¨ì§„ë‹¤. 그러나 40ì–µ ë…„ ì „ í™”ì„±ì˜ ë‹¤ì´ë‚˜ëª¨ê°€ ë©ˆì¶”ê³ ë‚œ ë’¤ì—는 투ìžìœ¨ì´ ë†’ì€ ê´‘ë¬¼ì— ìž”ë¥˜ìžê¸°ê°€ 남아있는 ì •ë„ë°–ì—는 ìžê¸°ìž¥ì„ ê°€ì§€ê³ ìžˆì§€ 않다. ì‹œê°„ì´ ì§€ë‚¨ì— ë”°ë¼ ì´ëŸ° ê´‘ë¬¼ì€ í’í™”ë˜ì—ˆê¸° ë•Œë¬¸ì— í˜„ìž¬ëŠ” ë‚¨ë°˜êµ¬ì˜ ê³ ì§€ì˜ ì¼ë¶€ì—서만 ê³ ì§€ìžê¸°ë¥¼ ê´€ì¸¡í• ìˆ˜ 있다. 태양í’ì€ í™”ì„±ì˜ ì „ë¦¬ì¸µì— ì§ì ‘ 닿기 ë•Œë¬¸ì— í™”ì„±ì˜ ëŒ€ê¸°ëŠ” 조금씩 ë²—ê²¨ì ¸ ë‚˜ê°€ê³ ìžˆë‹¤ê³ ì—¬ê²¨ì§€ë‚˜ ê·¸ ì–‘ì€ ì•„ì§ í™•ì‹¤í•˜ì§€ 않다. 마스 글로벌 ì„œë² ì´ì–´ì™€ 마스 ìµìŠ¤í”„ë ˆìŠ¤ëŠ” í™”ì„±ì´ ì§€ë‚˜ê°„ ìžë¦¬ì— 남아있는 ì´ì˜¨í™”ëœ ëŒ€ê¸°ì˜ ìž…ìžë¥¼ íƒì§€í•˜ì˜€ë‹¤. +ê³µì „ê³¼ ìžì „[편집] + +í™”ì„±ì˜ ê¶¤ë„ ì´ì‹¬ë¥ ì€ ì•½ 9%ë¡œ ìƒëŒ€ì 으로 í° íŽ¸ì´ë‹¤. 태양계ì—ì„œ ì´ë³´ë‹¤ ë” ì´ì‹¬ë¥ ì´ í° ê¶¤ë„를 가지는 í–‰ì„±ì€ ìˆ˜ì„±ë°–ì— ì—†ë‹¤. íƒœì–‘ê¹Œì§€ì˜ í‰ê· 거리는 약 2ì–µ 2천만 km(1.5 천문단위)ì´ë©°, ê³µì „ 주기는 686.98ì¼ì´ë‹¤. í™”ì„±ì˜ íƒœì–‘ì¼(솔; sol)ì€ ì§€êµ¬ë³´ë‹¤ 약간 길어서 24시간 39분 35.244ì´ˆ ì •ë„ì´ë‹¤. + +í™”ì„±ì˜ ìžì „ì¶•ì€ 25.19ë„ë§Œí¼ ê¸°ìš¸ì–´ì ¸ 있어서 ì§€êµ¬ì˜ ê¸°ìš¸ê¸°ì™€ ê±°ì˜ ë¹„ìŠ·í•˜ë‹¤. ê·¸ ê²°ê³¼ 화성ì—서는 지구와 마찬가지로 ê³„ì ˆì´ ë‚˜íƒ€ë‚œë‹¤. 하지만 ê³µì „ ê°ì†ë„ê°€ ëŠë¦¬ê¸° ë•Œë¬¸ì— ê³„ì ˆì˜ ê¸¸ì´ëŠ” ì§€êµ¬ì— ë¹„í•´ 약 2ë°°ì •ë„ ëœë‹¤. +위성[편집] +ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„±ì˜ ìœ„ì„±ìž…ë‹ˆë‹¤. + +í¬ë³´ìŠ¤(Phobos)와 ë°ì´ëª¨ìŠ¤(Deimos)ê°€ í™”ì„±ì˜ ìœ„ì„±ì´ë‹¤. ì´ë“¤ì€ 늘 달 쪽으로 ê°™ì€ ë©´ì„ í–¥í•˜ê³ ìžˆë‹¤. í¬ë³´ìŠ¤ì˜ 화성 주위 궤ë„ê°€ 화성 ìžì²´ê°€ ë„는 ì†ë„보다 ë¹ ë¥´ë©° 아주 서서히 그러나 꾸준히 í™”ì„±ì— ê°€ê¹Œì›Œì§€ê³ ìžˆë‹¤. ì–¸ì ê°€ 미래ì—는 í¬ë³´ìŠ¤ê°€ 화성 í‘œë©´ì— ì¶©ëŒí•˜ê²Œ ë 것ì´ë¼ê³ 예측한다. ë°˜ë©´ì— ë°ì´ëª¨ìŠ¤ëŠ” 충분히 멀리 ë–¨ì–´ì ¸ ìžˆê³ ì„œì„œížˆ ë©€ì–´ì§€ê³ ìžˆë‹¤. + +ë‘ ìœ„ì„±ì€ ëª¨ë‘ 1877ë…„ 미êµì¸ ì²œë¬¸í•™ìž ì•„ì‚¬í”„ 홀(Asaph Hall)ì´ ë°œê²¬í–ˆê³ , 그리스 ì‹ í™”ì— ë‚˜ì˜¤ëŠ” ë§ˆë¥´ìŠ¤ì˜ ë‘ ì•„ë“¤ì˜ ì´ë¦„ì„ ë”° 명명ë˜ì—ˆë‹¤. +í™”ì„±ì˜ ìœ„ì„± ì´ë¦„ ì§ê²½ (km) 질량 (kg) í‰ê· ê¶¤ë„ ë°˜ì§€ë¦„ (km) ê³µì „ 주기 +í¬ë³´ìŠ¤ 22.2 (27 × 21.6 × 18.8) 1.08×1016 9378 7.66 시간 +ë°ì´ëª¨ìŠ¤ 12.6 (10 × 12 × 16) 2×1015 23,400 30.35 시간 +ìƒëª…ì²´[편집] + +여러 ì¦ê±°ë¡œë¶€í„° 미루어 ë³¼ ë•Œ í™”ì„±ì´ ê³¼ê±°ì—는 지금보다 ë” ìƒëª…ì´ ì‚´ê¸°ì— ì í•©í•œ 환경ì´ì—ˆë˜ 것으로 ì¶”ì •ë˜ì—ˆìœ¼ë‚˜, 지금까지는, ì‹¤ì œ í™”ì„±ì— ìƒëª…ì´ ì¡´ìž¬í•œ ì ì´ ìžˆëŠ”ê°€ 하는 ì§ˆë¬¸ì— ëŒ€í•´ì„œëŠ” ì•„ì§ í™•ì‹¤í•œ ë‹µì„ ì–»ì§€ ëª»í•˜ê³ ìžˆë‹¤. ë°”ì´í‚¹ íƒì‚¬ì„ ì€ 70년대 ì¤‘ë°˜ì— í™”ì„± 표면ì—ì„œ 미ìƒë¬¼ì„ íƒì§€í•˜ê¸° 위한 ì‹¤í—˜ì„ ìˆ˜í–‰í•˜ì—¬, 과학ìžë“¤ 사ì´ì—ì„œ ë§Žì€ ë…¼ìŸì´ ë˜ê³ 있다. 존슨 우주센터 연구소는 화성ì—ì„œ ë‚ ì•„ì™”ì„ ê²ƒìœ¼ë¡œ ì¶”ì •ë˜ëŠ” ìš´ì„ AL[5] 빨리 분해ë˜ê¸° ë•Œë¬¸ì— ì†ŒëŸ‰ì˜ ì´ë“¤ 분ìžëŠ” í™”ì„±ì— ìƒë¬¼ì´ 사는 ì¦ê±°ë¡œ 여겨질 수 있으나, ì´ë“¤ ì›ì†ŒëŠ” 화산ì´ë‚˜ 사문함화작용 ê°™ì€ ì§€ì§ˆí•™ì ìž‘ìš©ì— ì˜í•´ì„œë„ 공급ë 수 있다. + + í™”ì„±ì€ ìƒë¬¼ì´ ì‚´ê¸°ì— ë¶€ì í•©í•œ 특성 ì—ì‹œ ê°€ì§€ê³ ìžˆë‹¤. í™”ì„±ì˜ ìœ„ì¹˜ëŠ” íƒœì–‘ì˜ ê±°ì£¼ 가능 지대보다 ë°˜ ì²œë¬¸ë‹¨ìœ„ì •ë„ ë©€ë¦¬ ë–¨ì–´ì ¸ ìžˆê³ [6] ë¬¼ì€ ì–¼ì–´ 있다. + +ë¬¼ë¡ ê³¼ê±°ì— ë¬¼ì´ í˜ë €ë˜ ì ì´ ìžˆê¸°ëŠ” 하다. 화성ì—는 ë˜í•œ ìžê¸°ê¶Œì´ 없으며 대기가 í¬ë°•í•˜ë©°, ì§€ê° ì—´ë¥˜ëŸ‰ì€ ë§¤ìš° ì 으며, ì™¸ë¶€ì˜ ìš´ì„ ë˜ëŠ” ì†Œí–‰ì„±ë“¤ê³¼ì˜ ì¶©ëŒ~ ë˜ëŠ” 태양í’으로부터 보호받지 못한다. ë‚®ì€ ëŒ€ê¸°ì•• ë•Œë¬¸ì— ì–¼ìŒì€ ì•¡ì²´ìƒíƒœë¥¼ 거치지 ì•Šê³ ê³§ë°”ë¡œ 기화해버리며, 지질학ì 으로 ì‚¬ì‹¤ìƒ ì™„ì „ížˆ ì£½ì€ í–‰ì„±ìœ¼ë¡œ 본다. {화산 활ë™ì´ 없기 ë•Œë¬¸ì— í‘œë©´ê³¼ 행성 내부 사ì´ì˜ 화학 물질과 ê´‘ë¬¼ì˜ ìˆœí™˜ì´ ì¼ì–´ë‚˜ì§€ 않는다.} + + ë‹¤ë¥¸í•œíŽ¸ìœ¼ë¡ , ì•„ì§ ìƒëª…ì²´ê°€ ì¡´ìž¬í•˜ê³ ìžˆë‹¤ëŠ” ì£¼ìž¥ì˜ ê·¼ê±°ë¡œ, 대기ì—ì„œ ë©”íƒ„ì´ ê²€ì¶œì„ ë“ ë‹¤. + +그러나, ì´ëŠ” 지질활ë™ì´ 멈춘 í™”ì„±ì˜ í™˜ê²½ì—ì„œ ìžì—°ì 으로 ë°œìƒí• 수 없으며, ìƒëª…활ë™ì— ì˜í•´ì„œë§Œ 공급ë˜ë¯€ë¡œ, 안면ì„ì´ë‚˜ 화성 피ë¼ë¯¸ë“œì™€ ê°™ì€ ìŒëª¨ë¡ ì ì¸ ê°€ì„¤ë„ ìžˆìœ¼ë‚˜ 과학ì ì¸ ì˜ë¯¸ë¡œ 주목받지는 못하다. +화성 íƒì‚¬[편집] +ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„± íƒì‚¬ìž…니다. +ë¬´ì¸ íƒì‚¬ì„ [편집] +ë°”ì´í‚¹ 1호 ì°©ë¥™ì„ ì´ ì „ì†¡í•œ 사진 +1978ë…„ 2ì›” 11ì¼ Sol 556ì—ì„œ ì´¬ì˜ + +지금까지 ì¸ë¥˜ëŠ” ë‹¤ìˆ˜ì˜ ë¡œë´‡ íƒì‚¬ì„ ì„ í™”ì„±ì— ë³´ëƒˆê³ , 그중 ëª‡ëª‡ì€ ëŒ€ë‹¨í•œ 성과를 ê±°ë‘었지만, íƒì‚¬ì˜ ì‹¤íŒ¨ìœ¨ì€ ë§¤ìš° 높았다. 실패 사례 중 ëª‡ì€ ëª…ë°±í•œ ê¸°ìˆ ì ê²°í•¨ì— ë”°ë¥¸ 것ì´ì—ˆì§€ë§Œ, ë§Žì€ ê²½ìš° 연구ìžë“¤ì€ 확실한 실패 ì´ìœ 를 ì°¾ì„ ìˆ˜ 없었다. 그래서 ì´ëŸ° 사례는 지구-화성 "버뮤다 삼ê°ì§€ëŒ€" í˜¹ì€ í™”ì„±íƒì‚¬ì„ ì„ ë¨¹ê³ ì‚¬ëŠ” ì€í•˜ê·€ì‹ (Ghoul)ë¼ëŠ” ë†ë‹´ì„ 낳았다. 화성 로봇 íƒì‚¬ì˜ ì—사를 ì´í•´í•˜ê¸° 위해서는, 발사 시간대가 약 2ë…„ 남짓(í™”ì„±ì˜ ê³µì „ 주기)ì˜ ê¸°ê°„ì„ ì£¼ê¸°ë¡œ ë°œìƒí•œë‹¤ëŠ” ì‚¬ì‹¤ì„ ì•Œì•„ë‘어야 한다. + +1960ë…„ ì†Œë ¨ì€ ë‘ ê¸°ì˜ íƒì‚¬ì„ ì„ í™”ì„±ê¶¤ë„를 ì§€ë‚˜ì³ ëŒì•„오는 계íšìœ¼ë¡œ 발사하였으나, 지구궤ë„ì— ë„달하는 ë°ì— 실패한다. 1962ë…„ ì†Œë ¨ì€ ì„¸ 기를 ë” ì‹œë„하지만, 실패했다. ë‘ ê¸°ëŠ” 지구 궤ë„ì— ë¨¸ë¬¼ë €ê³ , 나머지 하나는 í™”ì„±ì„ ëŒì•„오는 ë™ì•ˆ ì§€êµ¬ì™€ì˜ êµì‹ ì´ ëŠì–´ì¡Œë‹¤. 1964ë…„ì— ë˜ í•œë²ˆì˜ ì‹œë„ê°€ 실패한다. + +1962ë…„ì—ì„œ 1973ë…„ 사ì´ì—, NASA(나사)ì˜ ì œíŠ¸ 추진 연구소(Jet Propulsion Laboratory)는 내태양계(inner solar system)를 íƒí—˜í• 10ê°œì˜ ë§¤ë¦¬ë„ˆ ìš°ì£¼ì„ ì„ ì„¤ê³„Â·ì œìž‘í•˜ì˜€ë‹¤. ì´ ìš°ì£¼ì„ ì€ ê¸ˆì„±, 화성, ìˆ˜ì„±ì„ ìµœì´ˆë¡œ íƒì‚¬í•˜ê¸° 위해서 만들어졌다. 매리너 ìš°ì£¼ì„ ì€ ë¹„êµì ìž‘ì€ ë¡œë´‡ íƒì‚¬ì„ 으로 ì•„í‹€ë¼ìŠ¤ ë¡œì¼“ì— ì‹¤ë ¤ 발사ë˜ì—ˆë‹¤. ê° ìš°ì£¼ì„ ì˜ ë¬´ê²ŒëŠ” 0.5í†¤ì„ ë„˜ì§€ 않았다. + +매리너 3호와 4호는 ë™ì¼í•œ 기체로, 최초로 í™”ì„±ì„ ì§€ë‚˜ì¹˜ë©° 관찰하ë„ë¡ ì„¤ê³„ë˜ì—ˆë‹¤. 매리너 3호는 1964ë…„ 11ì›” 5ì¼ ë°œì‚¬ë˜ì—ˆìœ¼ë‚˜, ìš°ì£¼ì„ ì˜ ìœ—ë¶€ë¶„ì„ ë®ì€ ëšœê»‘ì´ ì 당히 열리지 ì•Šì•˜ê³ , í™”ì„±ì— ë„달하지 못했다. 3주 후 1964ë…„ 11ì›” 28ì¼ ë§¤ë¦¬ë„ˆ 4호는 성공ì 으로 발사ë˜ì–´ 8ê°œì›”ì˜ í•í•´ë¥¼ 시작한다. + +매리너 4호는 1965ë…„ 6ì›” 14ì¼ í™”ì„±ì„ ì§€ë‚˜ë©°, 다른 í–‰ì„±ì˜ ê·¼ì ‘ ì‚¬ì§„ì„ ìµœì´ˆë¡œ ì°ì–´ëƒˆë‹¤. 오랜 기간 ë™ì•ˆ ìž‘ì€ í…Œì´í”„ ë ˆì½”ë”ì— ê¸°ë¡ëœ ê·¸ ì‚¬ì§„ë“¤ì€ ë‹¬ ëª¨ì–‘ì˜ ë¶„í™”êµ¬ë“¤ì„ ë³´ì—¬ 주었다. ê·¸ 분화구 들 중 ëª‡ëª‡ì€ ì„œë¦¬ê°€ ë®ì—¬ 추운 í™”ì„±ì˜ ë°¤ì„ ë³´ì—¬ì£¼ì—ˆë‹¤. + +NASA는 계ì†í•´ì„œ 매리너 계íšì„ 수행했다. ê·¸ë“¤ì€ ë‹¤ìŒ ë°œì‚¬ ì‹œê°„ëŒ€ì— ê·¼ì ‘ 비행 ì‹œí—˜ì„ ë˜ë‹¤ì‹œ 수행하였다. ì´ ë¹„í–‰ì„ ë“¤ì€ 1969ë…„ì— í™”ì„±ì— ë„달하였다. ì´ì— 관해서는 매리너 6호 와 7호를 참조하ë¼. ë‹¤ìŒ ë°œì‚¬ ë•Œ 매리너 계íšì€ ë‘ ëŒ€ì˜ ë¹„í–‰ì„ ì¤‘ í•œ 대를 잃는 ì‚¬ê³ ë¥¼ 겪었다. ì‚´ì•„ë‚¨ì€ ë§¤ë¦¬ë„ˆ 9호는 성공ì 으로 화성 궤ë„ì— ì§„ìž…í•˜ì˜€ë‹¤. 매리너 9호가 í™”ì„±ì— ë„ë‹¬í–ˆì„ ë•Œ, 그것과 ë‘ ëŒ€ì˜ ì†Œë ¨ ì¸ê³µìœ„ì„±ì€ í–‰ì„± ì „ì˜ì—ì— ê±¸ì³ ë¨¼ì§€ íí’ì´ ì¼ì–´ë‚˜ê³ 있는 ê²ƒì„ ë°œê²¬í•˜ì˜€ë‹¤. ê·¸ íí’ì´ ê°€ë¼ì•‰ëŠ” ê²ƒì„ ê¸°ë‹¤ë¦¬ëŠ” ë™ì•ˆ 화성 í‘œë©´ì˜ ì‚¬ì§„ì„ ì°ëŠ” ê²ƒì€ ë¶ˆê°€ëŠ¥í•˜ì˜€ìœ¼ë¯€ë¡œ, 매리너 9호는 í¬ë³´ìŠ¤ì˜ ì‚¬ì§„ì„ ì°ì—ˆë‹¤. íí’ì´ í™”ì„±ì˜ í‘œë©´ ì‚¬ì§„ì„ ì°ê¸°ì— ì¶©ë¶„í• ë§Œí¼ ê°€ë¼ì•‰ì•˜ì„ ë•Œ, ì „ì†¡ëœ ì‚¬ì§„ì€ ì´ì „ ìž„ë¬´ì˜ ê²°ê³¼ë¡œ 온 사진보다 ë” ë†’ì€ í’ˆì§ˆì„ ê°€ì§€ê³ ìžˆì—ˆë‹¤. ì´ ì‚¬ì§„ë“¤ì´ í™”ì„±ì— í•œë•Œ ì•¡ì²´ í˜•íƒœì˜ ë¬¼ì´ ìžˆì—ˆì„ëŠ”ì§€ë„ ëª¨ë¥¸ë‹¤ëŠ” ê²ƒì„ ì¦ê±°í•˜ëŠ” 첫 번째 사진ì´ì—ˆë‹¤. + +1976ë…„ì— ë‘ ëŒ€ì˜ ë°”ì´í‚¹ 호가 화성 궤ë„ì— ë“¤ì–´ê°€ ê°ê° 착륙 ëª¨ë“ˆì„ ë‚´ë ¤ 화성 í‘œë©´ì— ë‚´ë ¤ 앉았다. ì´ ìž„ë¬´ë¥¼ 통해 ì¸ë¥˜ëŠ” 첫 번째 컬러 사진과 ë”ìš± í™•ìž¥ëœ ê³¼í•™ì ì •ë³´ë¥¼ ì–»ì„ ìˆ˜ 있었다. + +소비ì—트 ì—°ë°©ì˜ í™”ì„± íƒì‚¬ 계íšì—ì„œ 발사한 ìš°ì£¼ì„ ë“¤ì€ ë°”ì´í‚¹ë³´ë‹¤ 몇 ë…„ ì¼ì° ìˆ˜ë§Žì€ ì°©ë¥™ì„ ì‹œë„했다. 그러나 매리너 계íšì´ ìˆ˜í–‰í–ˆë˜ ê²ƒë³´ë‹¤ 성공ì ì¸ ê²°ê³¼ë¥¼ 얻지는 못했다. + +마스 패스파ì¸ë”는 1997ë…„ 7ì›” 4ì¼ì— í™”ì„±ì— ì°©ë¥™í•˜ì—¬, ì†Œì €ë„ˆë¼ëŠ” 매우 ìž‘ì€ ì›ê²© ì¡°ì •ì²´ë¥¼ 움ì§ì—¬ 착륙 지ì ì£¼ìœ„ì˜ ëª‡ 미터를 ì—¬í–‰í•˜ê³ , í™”ì„±ì˜ í™˜ê²½ ì¡°ê±´ì„ íƒìƒ‰í•˜ê³ í‘œë©´ì˜ ëŒë“¤ì„ 수집해왔다. + +ë‹¤ìŒ íƒì‚¬ëŠ” 마스 글로벌 ì„œë² ì´ì–´(Mars Global Surveyor)ì— ì˜í•´ ì´ë£¨ì–´ì¡Œë‹¤. ì´ ìž„ë¬´ëŠ” 20ì—¬ ë…„ê°„ì˜ í™”ì„± íƒì‚¬ì—사ì—ì„œ 첫 번째로 성공ì ì¸ ê²ƒì´ì—ˆê³ , 1996ë…„ 11ì›” 7ì¼ì— 발사ë˜ì–´ 1997ë…„ 9ì›” 12ì¼ì— 화성 궤ë„ì— ë„달하였다. 1ë…„ ë°˜ ì •ë„ê°€ í른 후, íšŒì „ 궤ë„ê°€ 타ì›í˜•ì—ì„œ ì›í˜•ìœ¼ë¡œ ìžë¦¬ë¥¼ ìž¡ì•˜ê³ , ìš°ì£¼ì„ ì€ 1999ë…„ 3월부터 기초ì ì¸ ë§¤í•‘ ìž„ë¬´ì— ëŒìž…했다. ìš°ì£¼ì„ ì€ í™”ì„±ì„ í™”ì„±ë ¥ìœ¼ë¡œ 1ë…„, ì§€êµ¬ë ¥ìœ¼ë¡œëŠ” ê±°ì˜ 2ë…„ê°„ ì €ê³ ë„ì—ì„œ 관찰했다. 마스 글로벌 ì„œë² ì´ì–´í˜¸ëŠ” ìµœê·¼ì¸ 2001ë…„ 1ì›” 31ì¼ ê·¸ 기초ì ì¸ ìž„ë¬´ë¥¼ ì™„ë£Œí•˜ê³ í˜„ìž¬ëŠ” 2단계 임무를 ìˆ˜í–‰í•˜ê³ ìžˆë‹¤. + +ì´ íƒì‚¬ëŠ” 화성 표면, 대기권, ê·¸ë¦¬ê³ ë‚´ë¶€ì— ëŒ€í•œ ì „ì²´ì ì¸ ì—°êµ¬ë¥¼ ìˆ˜í–‰í•˜ê³ , 지난 íƒì‚¬ 계íšì—ì„œ ê±°ë‘¬ë“¤ì¸ ëª¨ë“ ê²°ê³¼ë¬¼ë³´ë‹¤ ë” ë§Žì€ ë°ì´í„°ë¥¼ ê°€ì ¸ì™”ë‹¤. ì´ ê°€ì¹˜ìžˆëŠ” ë°ì´í„°ë“¤ì€ 마스 글로벌 ì„œë² ì´ì–´: MOLA ì—ì„œ 찾아볼 수 있다. + +2008ë…„ 7ì›” 31ì¼ ë¯¸êµ êµë¦½í•ê³µìš°ì£¼êµì€ 화성íƒì‚¬ì„ 피닉스가 í™”ì„±ì— ë¬¼ì´ ì¡´ìž¬í•¨ì„ í™•ì¸í•˜ì˜€ë‹¤ê³ 발표했다. 피닉스는 2008ë…„ 11ì›” 10ì¼ ìž„ë¬´ê°€ 종료ë˜ì—ˆë‹¤. +ê´€ì¸¡ì˜ ì—사[편집] + +기ì›ì „ 1600ë…„ê²½ì— í™”ì„±ì— ëŒ€í•œ ê´€ì¸¡ì´ ì‹œìž‘ë˜ì—ˆë‹¤ê³ 여겨지며, í™”ì„±ì€ ë¶ˆê³¼ ê°™ì´ ë¶‰ê²Œ ë¹›ë‚˜ê³ ë‹¤ë¥¸ 천체와 달리 하늘ì—ì„œ ì´ìƒí•˜ê²Œ 움ì§ì¸ë‹¤ê³ ì•Œë ¤ì¡Œë‹¤. + + 바빌로니아ì¸ì€ ì´ë¯¸ 기ì›ì „ 400ë…„ê²½ì— ì²œë¬¸í˜„ìƒì„ 연구했었으며 ì¼ì‹, ì›”ì‹ê³¼ ê°™ì€ ì²œë¬¸í˜„ìƒì„ 예측하기 위해 ê³ ë„ë¡œ ë°œë‹¬ëœ ë°©ë²•ì„ ì‚¬ìš©í•˜ì˜€ë‹¤. ê·¸ë“¤ì€ ê·¸ë“¤ì˜ ë‹¬ë ¥ê³¼ 종êµì ì¸ ì´ìœ ì—ì„œ ê·¸ë“¤ì„ ì£¼ì˜ê¹Šê²Œ 연구하였다. 그러나 ê·¸ë“¤ì´ ëª©ê²©í•œ 현ìƒì— 대해서 깊게 분ì„한다거나 ì„¤ëª…í•˜ë ¤ê³ í•˜ì§€ëŠ” 않았다. 바빌로니아ì¸ë“¤ì€ í™”ì„±ì„ ë„¤ë¥´ê°ˆ(Nergal, ‘위대한 ì˜ì›…’ ë˜ëŠ” â€˜ì „ìŸì˜ 왕.’ ì›ëœ»ì€ ‘커다란 ì§‘ì˜ ì£¼ì¸â€™)ì´ë¼ ë¶ˆë €ë‹¤. + ì´ì§‘트ì¸ì€ ë³„ì´ â€œê³ ì •ëœâ€ ë“¯ì´ ë³´ì´ë©°, íƒœì–‘ì´ ê³ ì •ëœ ë³„ì— ëŒ€í•˜ì—¬ ìƒëŒ€ì 으로 ì´ë™í•œë‹¤ê³ ìƒê°í–ˆë‹¤. ë˜í•œ ê·¸ë“¤ì€ í•˜ëŠ˜ì˜ 5ê°œì˜ ë¹›ë‚˜ëŠ” 천체가 ê³ ì •ëœ ë³„ 사ì´ë¥¼ 움ì§ì¸ë‹¤ëŠ” ê²ƒì„ ì•Œì•˜ë‹¤. ì´ì§‘트ì¸ì€ í™”ì„±ì„ Har Decher(ë¶‰ì€ ê²ƒ) í˜¹ì€ '죽ìŒì˜ 별'ì´ë¼ê³ ë¶ˆë €ë‹¤. + 그리스ì¸ì€ í™”ì„±ì„ ì „ìŸì˜ ì‹ ì˜ ì´ë¦„ì„ ë”°ì„œ ì•„ë ˆìŠ¤(Ares)ë¼ê³ ë¶ˆë €ë‹¤. 로마ì—ì„œë„ ì´ ì´ë¦„ì„ ê·¸ëŒ€ë¡œ 번ì—하여 í™”ì„±ì„ ë§ˆë¥´ìŠ¤(Mars)ë¼ê³ ë¶ˆë €ë‹¤. í™”ì„±ì˜ ê¸°í˜¸ëŠ” ë§ˆë¥´ìŠ¤ì˜ ë°©íŒ¨ì™€ 칼로 여겨진다. + 조반니 스키아파ë 리(Giovanni Virginio Schiaparelli, 1835ë…„~1910ë…„)는 1877ë…„, 화성ì—ì„œ "cannali"ë¡œ ë³´ì´ëŠ” ê²ƒì´ ë°œê²¬ë˜ì—ˆë‹¤ê³ 발표했다. ì´ ë‹¨ì–´ëŠ” ì´íƒˆë¦¬ì•„ì–´ë¡œ "거대한 홈"ì„ ëœ»í•œë‹¤. ì´ê²ƒì´ ì œëŒ€ë¡œ 번ì—ë˜ì—ˆë‹¤ë©´ "channels"ê°€ ë˜ì–´ì•¼ 했다. 하지만 당시 수ì—즈 ìš´í•˜ë„ ê±´ì„¤ë˜ê³ ê´€ì‹¬ì´ ê°€ë˜ ì°¨ì— "운하(canals)"ë¡œ 번ì—ë˜ì—ˆë‹¤. ì´ê²ƒìœ¼ë¡œ 화성 íƒì‚¬ ì—´í’ì˜ ì—사가 ì‹œìž‘ëœ ê²ƒì´ë‹¤. + +※ ë™ì–‘ì˜ ê³ ëŒ€ê¸°ë¡ì—는 ë‚®ì— í™”ì„±ì„ ë³¸ ê²ƒì´ ìžˆìœ¼ë‚˜, ê²€ì¦ê²°ê³¼ ê¸ˆì„±ì˜ ì°©ì˜¤ì˜€ìœ¼ë©°, í™”ì„±ì„ ë‚®ì— ë§¨ 눈으로 본다는 ê²ƒì€ ì‚¬ì‹¤ìƒ ë¶ˆê°€ëŠ¥í•˜ë‹¤ diff --git a/xpcom/tests/gtest/wikipedia/ru.txt b/xpcom/tests/gtest/wikipedia/ru.txt new file mode 100644 index 0000000000..9467849e6f --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ru.txt @@ -0,0 +1,410 @@ +ÐœÐ°Ñ€Ñ â€” Ñ‡ÐµÑ‚Ð²Ñ‘Ñ€Ñ‚Ð°Ñ Ð¿Ð¾ удалённоÑти от Солнца и ÑÐµÐ´ÑŒÐ¼Ð°Ñ Ð¿Ð¾ размерам планета Солнечной ÑиÑтемы; маÑÑа планеты ÑоÑтавлÑет 10,7 % маÑÑÑ‹ Земли. Ðазвана в чеÑÑ‚ÑŒ МарÑа — древнеримÑкого бога войны, ÑоответÑтвующего древнегречеÑкому ÐреÑу. Иногда ÐœÐ°Ñ€Ñ Ð½Ð°Ð·Ñ‹Ð²Ð°ÑŽÑ‚ «краÑной планетой» из-за краÑноватого оттенка поверхноÑти, придаваемого ей окÑидом железа. + +ÐœÐ°Ñ€Ñ â€” планета земной группы Ñ Ñ€Ð°Ð·Ñ€ÐµÐ¶ÐµÐ½Ð½Ð¾Ð¹ атмоÑферой (давление у поверхноÑти в 160 раз меньше земного). ОÑобенноÑÑ‚Ñми поверхноÑтного рельефа МарÑа можно Ñчитать ударные кратеры наподобие лунных, а также вулканы, долины, пуÑтыни и полÑрные ледниковые шапки наподобие земных. + +У МарÑа еÑÑ‚ÑŒ два еÑтеÑтвенных Ñпутника — Ð¤Ð¾Ð±Ð¾Ñ Ð¸ Ð”ÐµÐ¹Ð¼Ð¾Ñ (в переводе Ñ Ð´Ñ€ÐµÐ²Ð½ÐµÐ³Ñ€ÐµÑ‡ÐµÑкого — «Ñтрах» и «ужаÑ», имена двух Ñыновей ÐреÑа, Ñопровождавших его в бою), которые отноÑительно малы (Ð¤Ð¾Ð±Ð¾Ñ â€” 26,8×22,4×18,4 км, Ð”ÐµÐ¹Ð¼Ð¾Ñ â€” 15×12,2×10,4 км)[6][7] и имеют неправильную форму. + +ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1960-Ñ… годов непоÑредÑтвенным иÑÑледованием МарÑа Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ÐМС занималиÑÑŒ СССР(программы «МарÑ» и «ФобоÑ»), СШР(программы «Маринер», «Викинг», «Mars Global Surveyor» и другие), ЕвропейÑкое коÑмичеÑкое агентÑтво (программа «МарÑ-ÑкÑпреÑÑ») и Ð˜Ð½Ð´Ð¸Ñ (программа «МангальÑн»). Ðа ÑегоднÑшний день, поÑле Земли, ÐœÐ°Ñ€Ñ â€” ÑÐ°Ð¼Ð°Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð½Ð°Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ð° Солнечной ÑиÑтемы. + +ОÑновные ÑведениÑ[править | править вики-текÑÑ‚] + +ÐœÐ°Ñ€Ñ â€” Ñ‡ÐµÑ‚Ð²Ñ‘Ñ€Ñ‚Ð°Ñ Ð¿Ð¾ удалённоÑти от Солнца (поÑле МеркуриÑ, Венеры и Земли) и ÑÐµÐ´ÑŒÐ¼Ð°Ñ Ð¿Ð¾ размерам (превоÑходит по маÑÑе и диаметру только Меркурий) планета Солнечной ÑиÑтемы[8]. МаÑÑа МарÑа ÑоÑтавлÑет 10,7 % маÑÑÑ‹ Земли (6,423·1023 кг против 5,9736·1024 кг Ð´Ð»Ñ Ð—ÐµÐ¼Ð»Ð¸), объём — 0,15 объёма Земли, а Ñредний линейный диаметр — 0,53 диаметра Земли (6800 км)[7]. + +Рельеф МарÑа обладает многими уникальными чертами. МарÑианÑкий потухший вулкан гора Олимп — ÑÐ°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð³Ð¾Ñ€Ð° на планетах Солнечной ÑиÑтемы[9] (ÑÐ°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð³Ð¾Ñ€Ð° в Солнечной ÑиÑтеме — на аÑтероиде ВеÑта[10]), а долины Маринер — Ñамый крупный извеÑтный каньон на планетах (Ñамый большой каньон в Ñолнечной ÑиÑтеме обнаружен на Ñпутнике Плутона — Хароне[11]). Помимо Ñтого, в июне 2008 года три Ñтатьи, опубликованные в журнале «Nature», предÑтавили доказательÑтва ÑущеÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² Ñеверном полушарии МарÑа Ñамого крупного извеÑтного ударного кратера в Солнечной ÑиÑтеме. Его длина — 10,6 Ñ‚Ñ‹Ñ. км, а ширина — 8,5 Ñ‚Ñ‹Ñ. км, что примерно в четыре раза больше, чем крупнейший ударный кратер, до того также обнаруженный на МарÑе, вблизи его южного полюÑа[12]. + +ÐœÐ°Ñ€Ñ Ð¸Ð¼ÐµÐµÑ‚ период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¸ Ñмену времён года, аналогичные земным, но его климат значительно холоднее и Ñуше земного. + +Вплоть до полёта к МарÑу автоматичеÑкой межпланетной Ñтанции «Маринер-4» в 1965 году многие иÑÑледователи полагали, что на его поверхноÑти еÑÑ‚ÑŒ вода в жидком ÑоÑтоÑнии. Ðто мнение было оÑновано на наблюдениÑÑ… за периодичеÑкими изменениÑми в Ñветлых и тёмных учаÑтках, оÑобенно в полÑрных широтах, которые были похожи на континенты и морÑ. Тёмные длинные линии на поверхноÑти МарÑа интерпретировалиÑÑŒ некоторыми наблюдателÑми как ирригационные каналы Ð´Ð»Ñ Ð¶Ð¸Ð´ÐºÐ¾Ð¹ воды. Позднее было доказано, что большинÑтво Ñтих тёмных линий ÑвлÑÑŽÑ‚ÑÑ Ð¾Ð¿Ñ‚Ð¸Ñ‡ÐµÑкой иллюзией[13]. +Великие противоÑтоÑÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа (раÑÑтоÑние до Земли менее 60 млн. км), 1830—2050 годы Дата РаÑÑÑ‚., +а. e. +19 ÑентÑÐ±Ñ€Ñ 1830 0,388 +18 авгуÑта 1845 0,373 +17 Ð¸ÑŽÐ»Ñ 1860 0,393 +5 ÑентÑÐ±Ñ€Ñ 1877 0,377 +4 авгуÑта 1892 0,378 +24 ÑентÑÐ±Ñ€Ñ 1909 0,392 +23 авгуÑта 1924 0,373 +23 Ð¸ÑŽÐ»Ñ 1939 0,390 +10 ÑентÑÐ±Ñ€Ñ 1956 0,379 +10 авгуÑта 1971 0,378 +22 ÑентÑÐ±Ñ€Ñ 1988 0,394 +28 авгуÑта 2003 0,373 +27 Ð¸ÑŽÐ»Ñ 2018 0,386 +15 ÑентÑÐ±Ñ€Ñ 2035 0,382 +14 авгуÑта 2050 0,374 + +Ðа Ñамом деле из-за низкого Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð²Ð¾Ð´Ð° не может ÑущеÑтвовать в жидком ÑоÑтоÑнии на большей чаÑти (около 70 %) поверхноÑти МарÑа[14]. Вода в ÑоÑтоÑнии льда была обнаружена в марÑианÑком грунте коÑмичеÑким аппаратом ÐÐСР«ФеникÑ»[15][16]. Ð’ то же Ð²Ñ€ÐµÐ¼Ñ Ñобранные марÑоходами «Спирит» и «Opportunity» геологичеÑкие данные позволÑÑŽÑ‚ предположить, что в далёком прошлом вода покрывала значительную чаÑÑ‚ÑŒ поверхноÑти МарÑа. ÐÐ°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð² течение поÑледнего деÑÑÑ‚Ð¸Ð»ÐµÑ‚Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ð¸Ð»Ð¸ обнаружить в некоторых меÑтах на поверхноÑти МарÑа Ñлабую гейзерную активноÑÑ‚ÑŒ[17]. По наблюдениÑм Ñ ÐºÐ¾ÑмичеÑкого аппарата «Mars Global Surveyor», некоторые чаÑти южной полÑрной шапки МарÑа поÑтепенно отÑтупают[18]. + +С Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 2009 по наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¸ÑÑледовательÑÐºÐ°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð¸Ñ€Ð¾Ð²ÐºÐ° на орбите МарÑа наÑчитывает три функционирующих коÑмичеÑких аппарата: Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей», «МарÑ-ÑкÑпреÑÑ» и «Mars Reconnaissance Orbiter». Ðто больше, чем около любой другой планеты, помимо Земли. + +ПоверхноÑÑ‚ÑŒ МарÑа в наÑтоÑщий момент иÑÑледуют два марÑохода: «Opportunity» и «Curiosity». Ðа поверхноÑти МарÑа также находÑÑ‚ÑÑ Ð½ÐµÑколько неактивных поÑадочных модулей и марÑоходов, завершивших иÑÑледованиÑ. + +ÐœÐ°Ñ€Ñ Ñ…Ð¾Ñ€Ð¾ÑˆÐ¾ виден Ñ Ð—ÐµÐ¼Ð»Ð¸ невооружённым глазом. Его Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° доÑтигает −2,91m (при макÑимальном Ñближении Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹), уÑÑ‚ÑƒÐ¿Ð°Ñ Ð¿Ð¾ ÑркоÑти лишь Юпитеру (и то далеко не вÑегда во Ð²Ñ€ÐµÐ¼Ñ Ð²ÐµÐ»Ð¸ÐºÐ¾Ð³Ð¾ противоÑтоÑниÑ) и Венере (но лишь утром или вечером). ПротивоÑтоÑние МарÑа можно наблюдать каждые два года. ПоÑледний раз такое Ñвление на Земле наблюдалоÑÑŒ Ñ 9 по 14 Ð°Ð¿Ñ€ÐµÐ»Ñ 2014 года[129 1]. Как правило, во Ð²Ñ€ÐµÐ¼Ñ Ð²ÐµÐ»Ð¸ÐºÐ¾Ð³Ð¾ противоÑтоÑÐ½Ð¸Ñ (то еÑÑ‚ÑŒ при Ñовпадении противоÑтоÑÐ½Ð¸Ñ Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹ и Ð¿Ñ€Ð¾Ñ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñом Ð¿ÐµÑ€Ð¸Ð³ÐµÐ»Ð¸Ñ Ñвоей орбиты) оранжевый ÐœÐ°Ñ€Ñ ÑвлÑетÑÑ Ñрчайшим объектом земного ночного неба (не ÑÑ‡Ð¸Ñ‚Ð°Ñ Ð›ÑƒÐ½Ñ‹), но Ñто проиÑходит лишь один раз в 15—17 лет в течение одной-двух недель. +Орбитальные характериÑтики[править | править вики-текÑÑ‚] + +Минимальное раÑÑтоÑние от МарÑа до Земли ÑоÑтавлÑет 55,76 млн. км[19] (когда Ð—ÐµÐ¼Ð»Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ñ‚Ð¾Ñ‡Ð½Ð¾ между Солнцем и МарÑом), макÑимальное — около 401 млн. км (когда Солнце находитÑÑ Ñ‚Ð¾Ñ‡Ð½Ð¾ между Землёй и МарÑом). +РаÑÑтоÑние между Землёй и МарÑом (в а. е.) во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð¾ÑтоÑний 2014—2061 гг. + +Среднее раÑÑтоÑние от МарÑа до Солнца ÑоÑтавлÑет 228 млн. км (1,52 а. e.), период Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð²Ð¾ÐºÑ€ÑƒÐ³ Солнца равен 687 земным Ñуткам[2]. Орбита МарÑа имеет довольно заметный ÑкÑцентриÑитет (0,0934), поÑтому раÑÑтоÑние до Солнца менÑетÑÑ Ð¾Ñ‚ 206,6 до 249,2 млн. км. Ðаклонение орбиты МарÑа к плоÑкоÑти Ñклиптики равно 1,85°[2]. + +ÐœÐ°Ñ€Ñ Ð±Ð»Ð¸Ð¶Ðµ вÑего к Земле во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð¾ÑтоÑниÑ, когда планета находитÑÑ Ð½Ð° небе в направлении, противоположном Солнцу. ПротивоÑтоÑÐ½Ð¸Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑÑŽÑ‚ÑÑ ÐºÐ°Ð¶Ð´Ñ‹Ðµ 26 меÑÑцев в разных точках орбиты МарÑа и Земли. Раз в 15—17 лет противоÑтоÑÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð´ÑÑ‚ÑÑ Ð½Ð° то времÑ, когда ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð±Ð»Ð¸Ð·Ð¸ Ñвоего перигелиÑ; в Ñтих традиционно называемых великими противоÑтоÑниÑÑ… раÑÑтоÑние до планеты минимально (менее 60 млн км), и ÐœÐ°Ñ€Ñ Ð´Ð¾Ñтигает наибольшего углового размера 25,1″ и ÑркоÑти −2,88m[20]. +ФизичеÑкие характериÑтики[править | править вики-текÑÑ‚] + +По линейному размеру ÐœÐ°Ñ€Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¸ вдвое меньше Земли — его Ñкваториальный Ñ€Ð°Ð´Ð¸ÑƒÑ Ñ€Ð°Ð²ÐµÐ½ 3396,9 км (53,2 % земного). Площадь поверхноÑти МарÑа примерно равна площади Ñуши на Земле[21]. + +ПолÑрный Ñ€Ð°Ð´Ð¸ÑƒÑ ÐœÐ°Ñ€Ñа примерно на 20 км меньше Ñкваториального, Ñ…Ð¾Ñ‚Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´ Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñƒ планеты больший, чем у Земли, что даёт повод предположить изменение ÑкороÑти Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа Ñо временем[22]. +Сравнение размеров Земли (Ñредний Ñ€Ð°Ð´Ð¸ÑƒÑ 6371 км) и МарÑа (Ñредний Ñ€Ð°Ð´Ð¸ÑƒÑ 3386,2 км) + +МаÑÑа планеты — 6,418·1023 кг (11 % маÑÑÑ‹ Земли). УÑкорение Ñвободного Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ Ð½Ð° Ñкваторе равно 3,711 м/Ѳ (0,378 земного); Ð¿ÐµÑ€Ð²Ð°Ñ ÐºÐ¾ÑмичеÑÐºÐ°Ñ ÑкороÑÑ‚ÑŒ ÑоÑтавлÑет 3,6 км/Ñ, Ð²Ñ‚Ð¾Ñ€Ð°Ñ â€” 5,027 км/Ñ. + +Период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ — 24 чаÑа 37 минут 22,7 Ñекунд (отноÑительно звёзд), длина Ñредних Ñолнечных Ñуток (называемых Ñолами) ÑоÑтавлÑет 24 чаÑа 39 минут 35,24409 Ñекунды, вÑего на 2,7 % длиннее земных Ñуток. МарÑианÑкий год ÑоÑтоит из 668,6 марÑианÑких Ñолнечных Ñуток. + +ÐœÐ°Ñ€Ñ Ð²Ñ€Ð°Ñ‰Ð°ÐµÑ‚ÑÑ Ð²Ð¾ÐºÑ€ÑƒÐ³ Ñвоей оÑи, наклонённой к перпендикулÑру плоÑкоÑти орбиты под углом 25,19°[2]. Ðаклон оÑи Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа обеÑпечивает Ñмену времён года. При Ñтом вытÑнутоÑÑ‚ÑŒ орбиты приводит к большим различиÑм в их продолжительноÑти — так, ÑÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð²ÐµÑна и лето, вмеÑте взÑтые, длÑÑ‚ÑÑ 371 Ñол, то еÑÑ‚ÑŒ заметно больше половины марÑианÑкого года. Ð’ то же Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð½Ð¸ приходÑÑ‚ÑÑ Ð½Ð° учаÑток орбиты МарÑа, удалённый от Солнца. ПоÑтому на МарÑе Ñеверное лето долгое и прохладное, а южное — короткое и отноÑительно тёплое. +ÐтмоÑфера и климат[править | править вики-текÑÑ‚] +ОÑновные Ñтатьи: ÐтмоÑфера МарÑа, Климат МарÑа +ÐтмоÑфера МарÑа, Ñнимок получен иÑкуÑÑтвенным Ñпутником «Викинг» в 1976. Слева виден «кратер-Ñмайлик» Галле + +Температура на планете колеблетÑÑ Ð¾Ñ‚ −153 °C[23] на полюÑе зимой и до более +20 °C[24] на Ñкваторе в полдень. СреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° ÑоÑтавлÑет −50 °C[23]. + +ÐтмоÑфера МарÑа, ÑоÑтоÑÑ‰Ð°Ñ Ð² оÑновном из углекиÑлого газа, очень разрежена. Давление у поверхноÑти МарÑа в 160 раз меньше земного — 6,1 мбар на Ñреднем уровне поверхноÑти. Из-за большого перепада выÑот на МарÑе давление у поверхноÑти Ñильно изменÑетÑÑ. ÐŸÑ€Ð¸Ð¼ÐµÑ€Ð½Ð°Ñ Ñ‚Ð¾Ð»Ñ‰Ð¸Ð½Ð° атмоÑферы — 110 км. + +По данным ÐÐСР(2004), атмоÑфера МарÑа ÑоÑтоит на 95,32 % из углекиÑлого газа; также в ней ÑодержитÑÑ 2,7 % азота, 1,6 % аргона, 0,13 % киÑлорода, 210 ppm водÑного пара, 0,08 % угарного газа, окÑид азота (NO) — 100 ppm, неон (Ne) — 2,5 ppm, полутÑÐ¶Ñ‘Ð»Ð°Ñ Ð²Ð¾Ð´Ð° водород-дейтерий-киÑлород (HDO) 0,85 ppm, криптон (Kr) 0,3 ppm, кÑенон (Xe) — 0,08 ppm[2] (ÑоÑтав приведён в объёмных долÑÑ…). + +По данным ÑпуÑкаемого аппарата ÐМС «Викинг» (1976), в марÑианÑкой атмоÑфере было определено около 1—2 % аргона, 2—3 % азота, а 95 % — углекиÑлый газ[25]. СоглаÑно данным ÐМС «МарÑ-2» и «МарÑ-3», нижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° ионоÑферы находитÑÑ Ð½Ð° выÑоте 80 км, макÑимум Ñлектронной концентрации 1,7×105 Ñлектронов/Ñм³ раÑположен на выÑоте 138 км, другие два макÑимума находÑÑ‚ÑÑ Ð½Ð° выÑотах 85 и 107 км[26]. + +РадиопроÑвечивание атмоÑферы на радиоволнах 8 и 32 Ñм, проведённое ÐМС «МарÑ-4» 10 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 1974 года, показало наличие ночной ионоÑферы МарÑа Ñ Ð³Ð»Ð°Ð²Ð½Ñ‹Ð¼ макÑимумом ионизации на выÑоте 110 км и концентрацией Ñлектронов 4,6×103 Ñлектронов/Ñм³, а также вторичными макÑимумами на выÑоте 65 и 185 км[26]. + +РазреженноÑÑ‚ÑŒ марÑианÑкой атмоÑферы и отÑутÑтвие магнитоÑферы ÑвлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð¾Ð¹ того, что уровень ионизирующей радиации на поверхноÑти МарÑа ÑущеÑтвенно выше, чем на поверхноÑти Земли. МощноÑÑ‚ÑŒ Ñквивалентной дозы на поверхноÑти МарÑа ÑоÑтавлÑет в Ñреднем 0,7 мЗв/Ñутки (изменÑÑÑÑŒ в завиÑимоÑти от Ñолнечной активноÑти и атмоÑферного Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² пределах от 0,35 до 1,15 мЗв/Ñутки)[27] и обуÑловлена главным образом коÑмичеÑким излучением; Ð´Ð»Ñ ÑравнениÑ, на Земле ÑÑ€ÐµÐ´Ð½ÐµÐ¼Ð¸Ñ€Ð¾Ð²Ð°Ñ ÑÐºÐ²Ð¸Ð²Ð°Ð»ÐµÐ½Ñ‚Ð½Ð°Ñ Ð´Ð¾Ð·Ð° Ð¾Ð±Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚ еÑтеÑтвенных иÑточников, Ð½Ð°ÐºÐ°Ð¿Ð»Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð·Ð° год, равна 2,4 мЗв, в том чиÑле от коÑмичеÑких лучей 0,4 мЗв[28]. Таким образом, за один-два Ð´Ð½Ñ ÐºÐ¾Ñмонавт на поверхноÑти МарÑа получит такую же Ñквивалентную дозу облучениÑ, какую на поверхноÑти Земли он получил бы за год. +ÐтмоÑферное давление[править | править вики-текÑÑ‚] + +По данным ÐÐСРна 2004 год, давление атмоÑферы на Ñреднем радиуÑе ÑоÑтавлÑет 636 Па (6,36 мбар). ПлотноÑÑ‚ÑŒ атмоÑферы у поверхноÑти — около 0,020 кг/м³, Ð¾Ð±Ñ‰Ð°Ñ Ð¼Ð°ÑÑа атмоÑферы МарÑа — около 2,5×1016 кг[2]. +Изменение атмоÑферного Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð° МарÑе в завиÑимоÑти от времени Ñуток, зафикÑированное поÑадочным модулем «Mars Pathfinder» в 1997 году + +Ð’ отличие от Земли, маÑÑа марÑианÑкой атмоÑферы Ñильно изменÑетÑÑ Ð² течение года в ÑвÑзи Ñ Ñ‚Ð°Ñнием и намерзанием полÑрных шапок, Ñодержащих углекиÑлый газ. Зимой 20—30 процентов вÑей атмоÑферы намораживаетÑÑ Ð½Ð° полÑрной шапке, ÑоÑтоÑщей из углекиÑлоты[29]. Сезонные перепады давлениÑ, по разным иÑточникам, ÑоÑтавлÑÑŽÑ‚ Ñледующие значениÑ: + + По данным ÐÐСР(2004): от 4,0 до 8,7 мбар на Ñреднем радиуÑе[2]; + По данным Encarta (2000): от 6 до 10 мбар[30]; + По данным Zubrin и Wagner (1996): от 7 до 10 мбар[31]; + По данным поÑадочного аппарата «Викинг-1»: от 6,9 до 9 мбар[2]; + По данным поÑадочного аппарата «Mars Pathfinder»: от 6,7 мбар[29]. + +Ð’ меÑте поÑадки зонда ÐМС «МарÑ-6» в районе ÐритрейÑкого Ð¼Ð¾Ñ€Ñ Ð±Ñ‹Ð»Ð¾ зафикÑировано давление у поверхноÑти 6,1 мбар, что на тот момент ÑчиталоÑÑŒ Ñредним давлением на планете, и от Ñтого ÑƒÑ€Ð¾Ð²Ð½Ñ Ð±Ñ‹Ð»Ð¾ уÑловлено отÑчитывать выÑоÌÑ‚Ñ‹ и глубиÌны на МарÑе. По данным Ñтого аппарата, полученным во Ð²Ñ€ÐµÐ¼Ñ ÑпуÑка, тропопауза находитÑÑ Ð½Ð° выÑоте примерно 30 км, где давление ÑоÑтавлÑет 5×10−7 г/Ñм³ (как на Земле на выÑоте 57 км)[32]. +Ð£Ð´Ð°Ñ€Ð½Ð°Ñ Ð²Ð¿Ð°Ð´Ð¸Ð½Ð° Ðллада — Ñамое глубокое меÑто МарÑа, где можно зафикÑировать Ñамое выÑокое атмоÑферное давление. + +ОблаÑÑ‚ÑŒ Ðллада наÑтолько глубока, что атмоÑферное давление доÑтигает примерно 12,4 мбар[14], что выше тройной точки воды (около 6,1 мбар)[33], поÑтому при доÑтаточно выÑокой температуре вода могла бы ÑущеÑтвовать там в жидком ÑоÑтоÑнии; при таком давлении, однако, вода закипает и превращаетÑÑ Ð² пар уже при +10 °C[14]. + +Ðа вершине выÑочайшей горы МарÑа, 27-километрового вулкана Олимп, давление может ÑоÑтавлÑÑ‚ÑŒ от 0,5 до 1 мбар[33]. + +До выÑадки на поверхноÑÑ‚ÑŒ МарÑа поÑадочных модулей давление было измерено за Ñчёт оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð´Ð¸Ð¾Ñигналов Ñ ÐМС «Маринер-4», «Маринер-6», «Маринер-7» и «Маринер-9» при их захождении за марÑианÑкий диÑк и выходе из-за марÑианÑкого диÑка — 6,5±2,0 мбар на Ñреднем уровне поверхноÑти, что в 160 раз меньше земного; такой же результат показали Ñпектральные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐМС «МарÑ-3». При Ñтом в раÑположенных ниже Ñреднего ÑƒÑ€Ð¾Ð²Ð½Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑÑ… (например, в марÑианÑкой Ðмазонии) давление, ÑоглаÑно Ñтим измерениÑм, доÑтигает 12 мбар[34]. + +ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1930-Ñ… годов, ÑоветÑкие аÑтрономы пыталиÑÑŒ определÑÑ‚ÑŒ давление атмоÑферы методами фотографичеÑкой фотометрии — по раÑпределению ÑркоÑти вдоль диаметра диÑка в разных диапазонах Ñветовых волн. ФранцузÑкие учёные Б. Лио и О. Ð”Ð¾Ð»ÑŒÑ„ÑŽÑ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ð»Ð¸ Ñ Ñтой целью Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ñризации раÑÑеÑнного атмоÑферой МарÑа Ñвета. Сводку оптичеÑких наблюдений опубликовал американÑкий аÑтроном Ж. де Вокулёр в 1951 году, и по ним получалоÑÑŒ давление 85\мбар, завышенное почти в 15 раз, поÑкольку не было отдельно учтено раÑÑеÑние Ñвета пылью, взвешенной в атмоÑфере МарÑа. Вклад пыли был припиÑан газовой атмоÑфере[35]. +Климат[править | править вики-текÑÑ‚] +Циклон возле Ñеверного полюÑа МарÑа, Ñнимки Ñ Ñ‚ÐµÐ»ÐµÑкопа «Хаббл» (27 Ð°Ð¿Ñ€ÐµÐ»Ñ 1999 года). + +Климат, как и на Земле, ноÑит Ñезонный характер. Угол наклона МарÑа к плоÑкоÑти орбиты почти равен земному и ÑоÑтавлÑет 25,1919°[5]; ÑоответÑтвенно, на МарÑе, так же как и на Земле, проиÑходит Ñмена времён года. ОÑобенноÑтью марÑианÑкого климата также ÑвлÑетÑÑ Ñ‚Ð¾, что ÑкÑцентриÑитет орбиты МарÑа значительно больше земного, и на климат также влиÑет раÑÑтоÑние до Солнца. Перигелий ÐœÐ°Ñ€Ñ Ð¿Ñ€Ð¾Ñ…Ð¾Ð´Ð¸Ñ‚ во Ð²Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð·Ð³Ð°Ñ€Ð° зимы в Ñеверном полушарии и лета в южном, афелий — во Ð²Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð·Ð³Ð°Ñ€Ð° зимы в южном полушарии и ÑоответÑтвенно лета в Ñеверном. Ð’ÑледÑтвие Ñтого климат Ñеверного и южного полушарий различаетÑÑ. Ð”Ð»Ñ Ñеверного Ð¿Ð¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ñ Ñ…Ð°Ñ€Ð°ÐºÑ‚ÐµÑ€Ð½Ñ‹ более мÑÐ³ÐºÐ°Ñ Ð·Ð¸Ð¼Ð° и прохладное лето; в южном полушарии зима более холоднаÑ, а лето более жаркое[36]. Ð’ холодное Ð²Ñ€ÐµÐ¼Ñ Ð³Ð¾Ð´Ð° даже вне полÑрных шапок на поверхноÑти может образовыватьÑÑ Ñветлый иней. Ðппарат «ФеникÑ» зафикÑировал Ñнегопад, однако Ñнежинки иÑпарÑлиÑÑŒ, не доÑÑ‚Ð¸Ð³Ð°Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти[37]. + +По ÑведениÑм ÐÐСР(2004 год), ÑреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° ÑоÑтавлÑет ~210 K (−63 °C). По данным поÑадочных аппаратов «Викинг», Ñуточный температурный диапазон ÑоÑтавлÑет от 184 K до 242 K (от −89 до −31 °C) («Викинг-1»), а ÑкороÑÑ‚ÑŒ ветра 2—7 м/Ñ (лето), 5—10 м/Ñ (оÑень), 17—30 м/Ñ (пылевой шторм)[2]. + +По данным поÑадочного зонда «МарÑ-6», ÑреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° тропоÑферы МарÑа ÑоÑтавлÑет 228 K, в тропоÑфере температура убывает в Ñреднем на 2,5 градуÑа на километр, а находÑщаÑÑÑ Ð²Ñ‹ÑˆÐµ тропопаузы (30 км) ÑтратоÑфера имеет почти поÑтоÑнную температуру 144 K[32]. + +ИÑÑледователи из Центра имени Карла Сагана в 2007—2008 годах пришли к выводу, что в поÑледние деÑÑÑ‚Ð¸Ð»ÐµÑ‚Ð¸Ñ Ð½Ð° МарÑе идёт процеÑÑ Ð¿Ð¾Ñ‚ÐµÐ¿Ð»ÐµÐ½Ð¸Ñ. СпециалиÑÑ‚Ñ‹ ÐÐСРподтвердили Ñту гипотезу на оÑнове анализа изменений альбедо разных чаÑтей планеты. Другие ÑпециалиÑÑ‚Ñ‹ Ñчитают, что такие выводы делать пока рано[38][39]. Ð’ мае 2016 года иÑÑледователи из Юго-Западного иÑÑледовательÑкого инÑтитута в Боулдере (Колорадо) опубликовали в журнале Science Ñтатью, в которой предъÑвили новые доказательÑтва идущего Ð¿Ð¾Ñ‚ÐµÐ¿Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð¸Ð¼Ð°Ñ‚Ð° (на оÑнове анализа данных Mars Reconnaissance Orbiter). По их мнению, Ñтот процеÑÑ Ð´Ð»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ð¹ и идёт, возможно, уже в течение 370 Ñ‚Ñ‹Ñ. лет.[40] + +СущеÑтвуют предположениÑ, что в прошлом атмоÑфера могла быть более плотной, а климат — тёплым и влажным, и на поверхноÑти МарÑа ÑущеÑтвовала Ð¶Ð¸Ð´ÐºÐ°Ñ Ð²Ð¾Ð´Ð° и шли дожди[41][42]. ДоказательÑтвом Ñтой гипотезы ÑвлÑетÑÑ Ð°Ð½Ð°Ð»Ð¸Ð· метеорита ALH 84001, показавший, что около 4 миллиардов лет назад температура МарÑа ÑоÑтавлÑла 18 ± 4 °C[43]. + +Главной оÑобенноÑтью общей циркулÑции атмоÑферы МарÑа ÑвлÑÑŽÑ‚ÑÑ Ñ„Ð°Ð·Ð¾Ð²Ñ‹Ðµ переходы углекиÑлого газа в полÑрных шапках, приводÑщие к значительным меридиональным потокам. ЧиÑленное моделирование общей циркулÑции атмоÑферы МарÑа[44] указывает на ÑущеÑтвенный годовой ход Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ Ð´Ð²ÑƒÐ¼Ñ Ð¼Ð¸Ð½Ð¸Ð¼ÑƒÐ¼Ð°Ð¼Ð¸ незадолго перед равноденÑтвиÑми, что подтверждаетÑÑ Ð¸ наблюдениÑми по программе «Викинг». Ðнализ данных о давлении[45] выÑвил годовой и полугодовой циклы. ИнтереÑно, что, как и на Земле, макÑимум полугодовых колебаний зональной ÑкороÑти ветра Ñовпадает Ñ Ñ€Ð°Ð²Ð½Ð¾Ð´ÐµÐ½ÑтвиÑми[46]. ЧиÑленное моделирование[44] выÑвлÑет также и ÑущеÑтвенный цикл индекÑа Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´Ð¾Ð¼ 4—6 Ñуток в периоды ÑолнцеÑтоÑний. «Викингом» обнаружено подобие цикла индекÑа на МарÑе Ñ Ð°Ð½Ð°Ð»Ð¾Ð³Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ колебаниÑми в атмоÑферах других планет. +Пылевые бури и пыльные вихри[править | править вики-текÑÑ‚] + +ВеÑеннее таÑние полÑрных шапок приводит к резкому повышению Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферы и перемещению больших маÑÑ Ð³Ð°Ð·Ð° в противоположное полушарие. СкороÑÑ‚ÑŒ дующих при Ñтом ветров ÑоÑтавлÑет 10—40 м/Ñ, иногда до 100 м/Ñ. Ветер поднимает Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти большое количеÑтво пыли, что приводит к пылевым бурÑм. Сильные пылевые бури практичеÑки полноÑтью Ñкрывают поверхноÑÑ‚ÑŒ планеты. Пылевые бури оказывают заметное воздейÑтвие на раÑпределение температуры в атмоÑфере МарÑа[47]. +Фотографии МарÑа, на которых видна Ð¿Ñ‹Ð»ÑŒÐ½Ð°Ñ Ð±ÑƒÑ€Ñ (июнь — ÑентÑбрь 2001). + +22 ÑентÑÐ±Ñ€Ñ 1971 года в Ñветлой облаÑти Noachis в южном полушарии началаÑÑŒ Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ. К 29 ÑентÑÐ±Ñ€Ñ Ð¾Ð½Ð° охватила двеÑти градуÑов по долготе от Ausonia до Thaumasia, а 30 ÑентÑÐ±Ñ€Ñ Ð·Ð°ÐºÑ€Ñ‹Ð»Ð° южную полÑрную шапку. Ð‘ÑƒÑ€Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶Ð°Ð»Ð° бушевать вплоть до Ð´ÐµÐºÐ°Ð±Ñ€Ñ 1971 года, когда на орбиту МарÑа прибыли ÑоветÑкие Ñтанции «МарÑ-2» и «МарÑ-3». «МарÑы» проводили Ñъёмку поверхноÑти, но пыль полноÑтью Ñкрывала рельеф — не видно было даже горы Олимп, возвышающейÑÑ Ð½Ð° 27 км. Ð’ одном из ÑеанÑов Ñъёмки была получена Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð³Ð¾ диÑка МарÑа Ñ Ñ‡Ñ‘Ñ‚ÐºÐ¾ выраженным тонким Ñлоем марÑианÑких облаков над пылью. Во Ð²Ñ€ÐµÐ¼Ñ Ñтих иÑÑледований в декабре 1971 года Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ Ð¿Ð¾Ð´Ð½Ñла в атмоÑферу Ñтолько пыли, что планета выглÑдела мутным краÑноватым диÑком. Только примерно к 10 ÑÐ½Ð²Ð°Ñ€Ñ 1972 года Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ Ð¿Ñ€ÐµÐºÑ€Ð°Ñ‚Ð¸Ð»Ð°ÑÑŒ, и ÐœÐ°Ñ€Ñ Ð¿Ñ€Ð¸Ð½Ñл обычный вид[48]. +Пыльные вихри, Ñфотографированные марÑоходом «Спирит» 15 Ð¼Ð°Ñ 2005 года. Цифры в левом нижнем углу отображают Ð²Ñ€ÐµÐ¼Ñ Ð² Ñекундах Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° первого кадра. + +ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1970-Ñ… годов, в рамках программы «Викинг», а также марÑоходом «Спирит» и другими аппаратами были зафикÑированы многочиÑленные пыльные вихри. Ðто воздушные завихрениÑ, возникающие у поверхноÑти планеты и поднимающие в воздух большое количеÑтво пеÑка и пыли. Вихри чаÑто наблюдаютÑÑ Ð¸ на Земле (в англоÑзычных Ñтранах их называют «пыльными демонами» — англ. dust devil), однако на МарÑе они могут доÑтигать гораздо больших размеров: в 10 раз выше и в 50 раз шире земных. Ð’ марте 2005 года такой вихрь очиÑтил Ñолнечные батареи у марÑохода «Спирит»[49][50]. +ПоверхноÑÑ‚ÑŒ[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ПоверхноÑÑ‚ÑŒ МарÑа +ОÑновные регионы[править | править вики-текÑÑ‚] +Иней на поверхноÑти МарÑа (Ñнимок марÑианÑкой Ñтанции «Викинг-2», 18 Ð¼Ð°Ñ 1979 года). + +УчаÑток кратера ГуÑева (мозаика Ñнимков марÑохода «Спирит»). + +ТопографичеÑÐºÐ°Ñ ÐºÐ°Ñ€Ñ‚Ð° МарÑа, по данным Mars Global Surveyor (1999). Ðулевой меридиан МарÑа принÑÑ‚ проходÑщим через кратер Ðйри-0. + +Две трети поверхноÑти МарÑа занимают Ñветлые облаÑти, получившие название материков, около трети — тёмные учаÑтки, называемые морÑми. ÐœÐ¾Ñ€Ñ ÑоÑредоточены главным образом в южном полушарии планеты, между 10 и 40° широты. Ð’ Ñеверном полушарии еÑÑ‚ÑŒ только два крупных Ð¼Ð¾Ñ€Ñ â€” ÐцидалийÑкое и Большой Сирт. + +Характер тёмных учаÑтков до Ñих пор оÑтаётÑÑ Ð¿Ñ€ÐµÐ´Ð¼ÐµÑ‚Ð¾Ð¼ Ñпоров. Они ÑохранÑÑŽÑ‚ÑÑ, неÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° то, что на МарÑе бушуют пылевые бури. Ð’ Ñвоё Ð²Ñ€ÐµÐ¼Ñ Ñто Ñлужило доводом в пользу предположениÑ, что тёмные учаÑтки покрыты раÑтительноÑтью. Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ð¾Ð»Ð°Ð³Ð°ÑŽÑ‚, что Ñто проÑто учаÑтки, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ…, в Ñилу их рельефа, легко выдуваетÑÑ Ð¿Ñ‹Ð»ÑŒ. КрупномаÑштабные Ñнимки показывают, что на Ñамом деле тёмные учаÑтки ÑоÑтоÑÑ‚ из групп тёмных Ð¿Ð¾Ð»Ð¾Ñ Ð¸ пÑтен, ÑвÑзанных Ñ ÐºÑ€Ð°Ñ‚ÐµÑ€Ð°Ð¼Ð¸, холмами и другими препÑÑ‚ÑтвиÑми на пути ветров. Сезонные и долговременные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ñ… размера и формы ÑвÑзаны, по-видимому, Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸ÐµÐ¼ ÑÐ¾Ð¾Ñ‚Ð½Ð¾ÑˆÐµÐ½Ð¸Ñ ÑƒÑ‡Ð°Ñтков поверхноÑти, покрытых Ñветлым и тёмным вещеÑтвом. + +ÐŸÐ¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ñ ÐœÐ°Ñ€Ñа довольно Ñильно различаютÑÑ Ð¿Ð¾ характеру поверхноÑти. Ð’ южном полушарии поверхноÑÑ‚ÑŒ находитÑÑ Ð½Ð° 1—2 км над Ñредним уровнем и гуÑто уÑеÑна кратерами. Ðта чаÑÑ‚ÑŒ МарÑа напоминает лунные материки. Ðа Ñевере Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ñ‡Ð°ÑÑ‚ÑŒ поверхноÑти находитÑÑ Ð½Ð¸Ð¶Ðµ Ñреднего уровнÑ, здеÑÑŒ мало кратеров, и оÑновную чаÑÑ‚ÑŒ занимают отноÑительно гладкие равнины, вероÑтно, образовавшиеÑÑ Ð² результате Ð·Ð°Ñ‚Ð¾Ð¿Ð»ÐµÐ½Ð¸Ñ Ð»Ð°Ð²Ð¾Ð¹ и Ñрозии. Такое различие полушарий оÑтаётÑÑ Ð¿Ñ€ÐµÐ´Ð¼ÐµÑ‚Ð¾Ð¼ диÑкуÑÑий. Граница между полушариÑми Ñледует примерно по большому кругу, наклонённому на 30° к Ñкватору. Граница ÑˆÐ¸Ñ€Ð¾ÐºÐ°Ñ Ð¸ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð¸ образует Ñклон в направлении на Ñевер. Вдоль неё вÑтречаютÑÑ Ñамые Ñродированные учаÑтки марÑианÑкой поверхноÑти. + +Выдвинуто две альтернативных гипотезы, объÑÑнÑющих аÑимметрию полушарий. СоглаÑно одной из них, на раннем геологичеÑком Ñтапе литоÑферные плиты «ÑъехалиÑь» (возможно, Ñлучайно) в одно полушарие, подобно континенту ÐŸÐ°Ð½Ð³ÐµÑ Ð½Ð° Земле, а затем «заÑтыли» в Ñтом положении. Ð”Ñ€ÑƒÐ³Ð°Ñ Ð³Ð¸Ð¿Ð¾Ñ‚ÐµÐ·Ð° предполагает Ñтолкновение МарÑа Ñ ÐºÐ¾ÑмичеÑким телом размером Ñ ÐŸÐ»ÑƒÑ‚Ð¾Ð½[51]. + +Большое количеÑтво кратеров в южном полушарии предполагает, что поверхноÑÑ‚ÑŒ здеÑÑŒ древнÑÑ â€” 3—4 млрд. лет. ВыделÑÑŽÑ‚ неÑколько типов кратеров: большие кратеры Ñ Ð¿Ð»Ð¾Ñким дном, более мелкие и молодые чашеобразные кратеры, похожие на лунные, кратеры, окружённые валом, и возвышенные кратеры. ПоÑледние два типа уникальны Ð´Ð»Ñ ÐœÐ°Ñ€Ñа — кратеры Ñ Ð²Ð°Ð»Ð¾Ð¼ образовалиÑÑŒ там, где по поверхноÑти текли жидкие выброÑÑ‹, а возвышенные кратеры образовалиÑÑŒ там, где покрывало выброÑов кратера защитило поверхноÑÑ‚ÑŒ от ветровой Ñрозии. Самой крупной деталью ударного проиÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ ÑвлÑетÑÑ Ñ€Ð°Ð²Ð½Ð¸Ð½Ð° Ðллада (примерно 2100 км в поперечнике[52]). + +Ð’ облаÑти хаотичеÑкого ландшафта вблизи границы полушарий поверхноÑÑ‚ÑŒ иÑпытала разломы и ÑÐ¶Ð°Ñ‚Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ñ… учаÑтков, за которыми иногда Ñледовала ÑÑ€Ð¾Ð·Ð¸Ñ (вÑледÑтвие оползней или катаÑтрофичеÑкого выÑÐ²Ð¾Ð±Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð·ÐµÐ¼Ð½Ñ‹Ñ… вод), а также затопление жидкой лавой. ХаотичеÑкие ландшафты чаÑто находÑÑ‚ÑÑ Ñƒ иÑтока больших каналов, прорезанных водой. Ðаиболее приемлемой гипотезой их ÑовмеÑтного Ð¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑвлÑетÑÑ Ð²Ð½ÐµÐ·Ð°Ð¿Ð½Ð¾Ðµ таÑние подповерхноÑтного льда. +Долины Маринер на МарÑе. + +Ð’ Ñеверном полушарии, помимо обширных вулканичеÑких равнин, находÑÑ‚ÑÑ Ð´Ð²Ðµ облаÑти крупных вулканов — ФарÑида и Ðлизий. ФарÑида — Ð¾Ð±ÑˆÐ¸Ñ€Ð½Ð°Ñ Ð²ÑƒÐ»ÐºÐ°Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ñ€Ð°Ð²Ð½Ð¸Ð½Ð° протÑжённоÑтью 2000 км, доÑÑ‚Ð¸Ð³Ð°ÑŽÑ‰Ð°Ñ Ð²Ñ‹Ñоты 10 км над Ñредним уровнем. Ðа ней находÑÑ‚ÑÑ Ñ‚Ñ€Ð¸ крупных щитовых вулкана — гора ÐÑ€ÑиÑ, гора Павлина и гора ÐÑкрийÑкаÑ. Ðа краю ФарÑиды находитÑÑ Ð²Ñ‹ÑÐ¾Ñ‡Ð°Ð¹ÑˆÐ°Ñ Ð½Ð° МарÑе и выÑÐ¾Ñ‡Ð°Ð¹ÑˆÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð² Солнечной ÑиÑтеме[9] гора Олимп. Олимп доÑтигает 27 км выÑоты по отношению к его оÑнованию[9] и 25 км по отношению к Ñреднему уровню поверхноÑти МарÑа, и охватывает площадь 550 км диаметром, окружённую обрывами, меÑтами доÑтигающими 7 км выÑоты. Объём Олимпа в 10 раз превышает объём крупнейшего вулкана Земли Мауна-Кеа. ЗдеÑÑŒ же раÑположено неÑколько менее крупных вулканов. Ðлизий — возвышенноÑÑ‚ÑŒ до шеÑти километров над Ñредним уровнем, Ñ Ñ‚Ñ€ÐµÐ¼Ñ Ð²ÑƒÐ»ÐºÐ°Ð½Ð°Ð¼Ð¸ — купол Гекаты, гора Ðлизий и купол Ðльбор. + +По другим данным, выÑота Олимпа ÑоÑтавлÑет 21 287 метров над нулевым уровнем и 18 километров над окружающей меÑтноÑтью, а диаметр оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ â€” примерно 600 км. ОÑнование охватывает площадь 282 600 км²[53]. Кальдера (углубление в центре вулкана) имеет ширину 70 км и глубину 3 км[54]. + +ВозвышенноÑÑ‚ÑŒ ФарÑида также переÑечена множеÑтвом тектоничеÑких разломов, чаÑто очень Ñложных и протÑжённых. Крупнейший из них — долины Маринер — Ñ‚ÑнетÑÑ Ð² широтном направлении почти на 4000 км (четверть окружноÑти планеты), доÑÑ‚Ð¸Ð³Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ñ‹ 600 и глубины 7—10 км[55][56]; по размерам Ñтот разлом Ñравним Ñ Ð’Ð¾ÑточноафриканÑким рифтом на Земле. Ðа его крутых Ñклонах проиÑходÑÑ‚ крупнейшие в Солнечной ÑиÑтеме оползни. Долины Маринер ÑвлÑÑŽÑ‚ÑÑ Ñамым большим извеÑтным каньоном в Солнечной ÑиÑтеме. Каньон, который был открыт коÑмичеÑким аппаратом «Маринер-9» в 1971 году, мог бы занÑÑ‚ÑŒ вÑÑŽ территорию СШÐ, от океана до океана. +Панорама ударного кратера Ð’Ð¸ÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð¸Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ около 800 метров, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити». Панорама ÑоÑтавлена из Ñнимков, которые были получены за три недели, в период Ñ 16 октÑÐ±Ñ€Ñ Ð¿Ð¾ 6 ноÑÐ±Ñ€Ñ 2006. +Панорама ударного кратера Ð’Ð¸ÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð¸Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ около 800 метров, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити». Панорама ÑоÑтавлена из Ñнимков, которые были получены за три недели, в период Ñ 16 октÑÐ±Ñ€Ñ Ð¿Ð¾ 6 ноÑÐ±Ñ€Ñ 2006. +Панорама поверхноÑти МарÑа в районе Husband Hill, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Спирит» 23-28 ноÑÐ±Ñ€Ñ 2005. +Панорама поверхноÑти МарÑа в районе Husband Hill, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Спирит» 23-28 ноÑÐ±Ñ€Ñ 2005. +Лёд и полÑрные шапки[править | править вики-текÑÑ‚] +Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¿Ð¾Ð»ÑÑ€Ð½Ð°Ñ ÑˆÐ°Ð¿ÐºÐ° в летний период, фото ÐœÐ°Ñ€Ñ Ð“Ð»Ð¾Ð±Ð°Ð» Сервейор. Длинный широкий разлом, раÑÑекающий шапку Ñлева — Каньон Северный. + +Внешний вид МарÑа Ñильно изменÑетÑÑ Ð² завиÑимоÑти от времени года. Прежде вÑего, броÑаютÑÑ Ð² глаза Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ñрных шапок. Они разраÑтаютÑÑ Ð¸ уменьшаютÑÑ, ÑÐ¾Ð·Ð´Ð°Ð²Ð°Ñ Ñезонные ÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð² атмоÑфере и на поверхноÑти МарÑа. ПолÑрные шапки в макÑимуме разраÑÑ‚Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ доÑтигать широты 50°. Диаметр поÑтоÑнной чаÑти Ñеверной полÑрной шапки ÑоÑтавлÑет 1000 км[57]. По мере того, как веÑной полÑÑ€Ð½Ð°Ñ ÑˆÐ°Ð¿ÐºÐ° в одном из полушарий отÑтупает, детали поверхноÑти планеты начинают темнеть. + +Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¸ Ð®Ð¶Ð½Ð°Ñ Ð¿Ð¾Ð»Ñрные шапки ÑоÑтоÑÑ‚ из двух ÑоÑтавлÑющих: Ñезонной — углекиÑлого газа[57] и вековой — водÑного льда[58]. По данным Ñо Ñпутника «МарÑ-ÑкÑпреÑÑ», толщина шапок может ÑоÑтавлÑÑ‚ÑŒ от 1 м до 3,7 км. Ðппарат Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» обнаружил на южной полÑрной шапке МарÑа дейÑтвующие гейзеры. Как Ñчитают ÑпециалиÑÑ‚Ñ‹ ÐÐСÐ, Ñтруи углекиÑлого газа Ñ Ð²ÐµÑенним потеплением вырываютÑÑ Ð²Ð²ÐµÑ€Ñ… на большую выÑоту, уноÑÑ Ñ Ñобой пыль и пеÑок[59][60]. + +Ð’ 1784 году аÑтроном У. Гершель обратил внимание на Ñезонные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð° полÑрных шапок, по аналогии Ñ Ñ‚Ð°Ñнием и намерзанием льдов в земных полÑрных облаÑÑ‚ÑÑ…[61]. Ð’ 1860-Ñ… годах французÑкий аÑтроном Ð. ЛÑи наблюдал волну Ð¿Ð¾Ñ‚ÐµÐ¼Ð½ÐµÐ½Ð¸Ñ Ð²Ð¾ÐºÑ€ÑƒÐ³ тающей веÑенней полÑрной шапки, что тогда было иÑтолковано как раÑтекание талых вод и развитие раÑтительноÑти. СпектрометричеÑкие измерениÑ, которые были проведены в начале XX века в обÑерватории Ловелла во ФлагÑтаффе Ð’. Слайфером, однако, не показали Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ Ð»Ð¸Ð½Ð¸Ð¸ хлорофилла — зелёного пигмента земных раÑтений[62]. + +По фотографиÑм «Маринера-7» удалоÑÑŒ определить, что полÑрные шапки имеют толщину в неÑколько метров, а Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð½Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° 115 K (−158 °C) подтвердила возможноÑÑ‚ÑŒ того, что она ÑоÑтоит из замёрзшей углекиÑлоты — «Ñухого льда»[63]. + +ВозвышенноÑÑ‚ÑŒ, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ð»Ð° название гор Митчелла, раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ð°Ñ Ð±Ð»Ð¸Ð· южного полюÑа МарÑа, при таÑнии полÑрной шапки выглÑдит как белый оÑтровок, поÑкольку в горах ледники тают позднее, в том чиÑле и на Земле[64]. + +Данные аппарата Mars Reconnaissance Orbiter позволили обнаружить под камениÑтыми оÑыпÑми у Ð¿Ð¾Ð´Ð½Ð¾Ð¶Ð¸Ñ Ð³Ð¾Ñ€ значительный Ñлой льда. Ледник толщиной в Ñотни метров занимает площадь в Ñ‚Ñ‹ÑÑчи квадратных километров, и его дальнейшее изучение ÑпоÑобно дать информацию об иÑтории марÑианÑкого климата[65][66]. +РуÑла «рек» и другие оÑобенноÑти[править | править вики-текÑÑ‚] +Дельта выÑохшей реки в кратере ÐберÑвальде (фото Mars Global Surveyor). +ÐœÐ¸ÐºÑ€Ð¾Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ ÐºÐ¾Ð½ÐºÑ€ÐµÑ†Ð¸Ð¸ гематита в марÑианÑком грунте, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити» 2 марта 2004 года (поле Ð·Ñ€ÐµÐ½Ð¸Ñ 1,3 Ñм), что ÑвидетельÑтвует о приÑутÑтвии в геологичеÑком прошлом воды в жидком ÑоÑтоÑнии[67]. +Так Ð½Ð°Ð·Ñ‹Ð²Ð°ÐµÐ¼Ð°Ñ Â«Ñ‡Ñ‘Ñ€Ð½Ð°Ñ Ð´Ñ‹Ñ€Ð°Â» (колодец) диаметром более 150 м на поверхноÑти МарÑа. Видна чаÑÑ‚ÑŒ боковой Ñтенки. Склон горы ÐÑ€ÑÐ¸Ñ (фото «МарÑианÑкого разведывательного Ñпутника»). +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ГидроÑфера МарÑа + +Ðа МарÑе имеетÑÑ Ð¼Ð½Ð¾Ð¶ÐµÑтво геологичеÑких образований, напоминающих водную Ñрозию, в чаÑтноÑти, выÑохшие руÑла рек. СоглаÑно одной из гипотез, Ñти руÑла могли ÑформироватьÑÑ Ð² результате кратковременных катаÑтрофичеÑких Ñобытий и не ÑвлÑÑŽÑ‚ÑÑ Ð´Ð¾ÐºÐ°Ð·Ð°Ñ‚ÐµÐ»ÑŒÑтвом длительного ÑущеÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€ÐµÑ‡Ð½Ð¾Ð¹ ÑиÑтемы. Однако поÑледние данные ÑвидетельÑтвуют о том, что реки текли в течение геологичеÑки значимых промежутков времени. Ð’ чаÑтноÑти, обнаружены инвертированные руÑла (то еÑÑ‚ÑŒ руÑла, приподнÑтые над окружающей меÑтноÑтью). Ðа Земле подобные Ð¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð¾Ñ€Ð¼Ð¸Ñ€ÑƒÑŽÑ‚ÑÑ Ð±Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ð´Ð»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¼Ñƒ накоплению плотных донных отложений Ñ Ð¿Ð¾Ñледующим выÑыханием и выветриванием окружающих пород. Кроме того, еÑÑ‚ÑŒ ÑвидетельÑтва ÑÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ñ€ÑƒÑел в дельте реки при поÑтепенном поднÑтии поверхноÑти[68]. + +Ð’ юго-западном полушарии, в кратере ÐберÑвальде обнаружена дельта реки площадью около 115 км²[69]. ÐÐ°Ð¼Ñ‹Ð²ÑˆÐ°Ñ Ð´ÐµÐ»ÑŒÑ‚Ñƒ река имела в длину более 60 км[70]. + +Данные марÑоходов ÐÐСР«Спирит» и «Оппортьюнити» ÑвидетельÑтвуют также о наличии воды в прошлом (найдены минералы, которые могли образоватьÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ в результате длительного воздейÑÑ‚Ð²Ð¸Ñ Ð²Ð¾Ð´Ñ‹). Ðппарат «ФеникÑ» обнаружил залежи льда непоÑредÑтвенно в грунте. + +Кроме того, обнаружены тёмные полоÑÑ‹ на Ñклонах холмов, ÑвидетельÑтвующие о поÑвлении жидкой Ñолёной воды на поверхноÑти в наше времÑ. Они поÑвлÑÑŽÑ‚ÑÑ Ð²Ñкоре поÑле наÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð»ÐµÑ‚Ð½ÐµÐ³Ð¾ периода и иÑчезают к зиме, «обтекают» различные препÑÑ‚ÑтвиÑ, ÑливаютÑÑ Ð¸ раÑходÑÑ‚ÑÑ. «Сложно предÑтавить, что подобные Ñтруктуры могли ÑформироватьÑÑ Ð½Ðµ из потоков жидкоÑти, а из чего-то иного», — заÑвил Ñотрудник ÐÐСРРичард Зурек[71]. Дальнейший Ñпектральный анализ показал приÑутÑтвие в указанных облаÑÑ‚ÑÑ… перхлоратов — Ñолей, ÑпоÑобных обеÑпечить ÑущеÑтвование жидкой воды в уÑловиÑÑ… марÑианÑкого давлениÑ[72][73]. + +28 ÑентÑÐ±Ñ€Ñ 2012 года на МарÑе обнаружены Ñледы переÑохшего водного потока. Об Ñтом объÑвили ÑпециалиÑÑ‚Ñ‹ американÑкого коÑмичеÑкого агентÑтва ÐÐСРпоÑле Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ð¹, полученных Ñ Ð¼Ð°Ñ€Ñохода «КьюриоÑити», на тот момент работавшего на планете лишь Ñемь недель. Речь идёт о фотографиÑÑ… камней, которые, по мнению учёных, Ñвно подвергалиÑÑŒ воздейÑтвию воды[74]. + +Ðа вулканичеÑкой возвышенноÑти ФарÑида обнаружено неÑколько необычных глубоких колодцев. Ð¡ÑƒÐ´Ñ Ð¿Ð¾ Ñнимку аппарата «МарÑианÑкий разведывательный Ñпутник», Ñделанному в 2007 году, один из них имеет диаметр 150 метров, а оÑÐ²ÐµÑ‰Ñ‘Ð½Ð½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ Ñтенки уходит в глубину не менее чем на 178 метров. Ð’Ñ‹Ñказана гипотеза о вулканичеÑком проиÑхождении Ñтих образований[75]. + +Ðа МарÑе имеетÑÑ Ð½ÐµÐ¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¹ регион — Лабиринт Ðочи, предÑтавлÑющий Ñобой ÑиÑтему переÑекающихÑÑ ÐºÐ°Ð½ÑŒÐ¾Ð½Ð¾Ð²[76]. Их образование не было ÑвÑзано Ñ Ð²Ð¾Ð´Ð½Ð¾Ð¹ Ñрозией, и вероÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð° поÑÐ²Ð»ÐµÐ½Ð¸Ñ â€” тектоничеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ[77][78]. Когда ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð±Ð»Ð¸Ð·Ð¸ перигелиÑ, над лабиринтом Ðочи и долинами Маринера поÑвлÑÑŽÑ‚ÑÑ Ð²Ñ‹Ñокие (40—50 км) облака. ВоÑточный ветер вытÑгивает их вдоль Ñкватора и ÑноÑит к западу, где они поÑтепенно размываютÑÑ. Их длина доÑтигает неÑкольких Ñотен (до Ñ‚Ñ‹ÑÑчи) километров, а ширина — неÑкольких деÑÑтков. СоÑтоÑÑ‚ они, ÑÑƒÐ´Ñ Ð¿Ð¾ уÑловиÑм в Ñтих ÑлоÑÑ… атмоÑферы, тоже из водÑного льда. Они довольно гуÑтые и отбраÑывают на поверхноÑÑ‚ÑŒ хорошо заметные тени. Их поÑвление объÑÑнÑÑŽÑ‚ тем, что неровноÑти рельефа вноÑÑÑ‚ Ð²Ð¾Ð·Ð¼ÑƒÑ‰ÐµÐ½Ð¸Ñ Ð² воздушные потоки, направлÑÑ Ð¸Ñ… вверх. Там они охлаждаютÑÑ, а ÑодержащийÑÑ Ð² них водÑной пар конденÑируетÑÑ[79]. +Грунт[править | править вики-текÑÑ‚] +Ð¤Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкого грунта в меÑте поÑадки аппарата «ФеникÑ». + +Ðлементный ÑоÑтав поверхноÑтного ÑÐ»Ð¾Ñ Ð³Ñ€ÑƒÐ½Ñ‚Ð°, определённый по данным поÑадочных аппаратов, неодинаков в разных меÑтах. ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑоÑтавлÑÑŽÑ‰Ð°Ñ Ð¿Ð¾Ñ‡Ð²Ñ‹ — кремнезём (20—25 %), Ñодержащий примеÑÑŒ гидратов окÑидов железа (до 15 %), придающих почве краÑноватый цвет. ИмеютÑÑ Ð·Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ðµ примеÑи Ñоединений Ñеры, кальциÑ, алюминиÑ, магниÑ, Ð½Ð°Ñ‚Ñ€Ð¸Ñ (единицы процентов Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾)[80][81]. + +СоглаÑно данным зонда ÐÐСР«ФеникÑ» (поÑадка на ÐœÐ°Ñ€Ñ 25 Ð¼Ð°Ñ 2008 года), Ñоотношение pH и некоторые другие параметры марÑианÑких почв близки к земным, и на них теоретичеÑки можно было бы выращивать раÑтениÑ[82][83]. «ФактичеÑки мы обнаружили, что почва на МарÑе отвечает требованиÑм, а также Ñодержит необходимые Ñлементы Ð´Ð»Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¸ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸ как в прошлом, так и в наÑтоÑщем и будущем», Ñообщил ведущий иÑÑледователь-химик проекта СÑм КунейвÑ[84]. Также, по его Ñловам, данный щелочной тип грунта (pH = 7,7) многие могут вÑтретить на «Ñвоём заднем дворе», и он вполне пригоден Ð´Ð»Ñ Ð²Ñ‹Ñ€Ð°Ñ‰Ð¸Ð²Ð°Ð½Ð¸Ñ Ñпаржи[85]. + +Ð’ меÑте поÑадки аппарата в грунте имеетÑÑ Ñ‚Ð°ÐºÐ¶Ðµ значительное количеÑтво водÑного льда[86]. Орбитальный зонд Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» также обнаружил, что под поверхноÑтью краÑной планеты еÑÑ‚ÑŒ залежи водÑного льда[87]. Позже Ñто предположение было подтверждено и другими аппаратами, но окончательно Ð²Ð¾Ð¿Ñ€Ð¾Ñ Ð¾ наличии воды на МарÑе был решён в 2008 году, когда зонд «ФеникÑ», Ñевший вблизи Ñеверного полюÑа планеты, получил воду из марÑианÑкого грунта[15][88]. + +Данные, полученные марÑоходом Curiosity и обнародованные в ÑентÑбре 2013 года, показали, что Ñодержание воды под поверхноÑтью МарÑа гораздо выше, чем ÑчиталоÑÑŒ ранее. Ð’ породе, из которой брал образцы марÑоход, её Ñодержание может доÑтигать 2 % по веÑу[89]. +Ð“ÐµÐ¾Ð»Ð¾Ð³Ð¸Ñ Ð¸ внутреннее Ñтроение[править | править вики-текÑÑ‚] + +Ð’ прошлом на МарÑе, как и на Земле, проиÑходило движение литоÑферных плит. Ðто подтверждаетÑÑ Ð¾ÑобенноÑÑ‚Ñми магнитного Ð¿Ð¾Ð»Ñ ÐœÐ°Ñ€Ñа, меÑтами раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… вулканов, например, в провинции ФарÑида, а также формой долины Маринер[90]. Современное положение дел, когда вулканы могут ÑущеÑтвовать гораздо более длительное времÑ, чем на Земле, и доÑтигать гигантÑких размеров, говорит о том, что ÑÐµÐ¹Ñ‡Ð°Ñ Ð´Ð°Ð½Ð½Ð¾Ðµ движение Ñкорее отÑутÑтвует. Ð’ пользу Ñтого говорит тот факт, что щитовые вулканы раÑтут в результате повторных извержений из одного и того же жерла в течение длительного времени. Ðа Земле из-за Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ‚Ð¾Ñферных плит вулканичеÑкие точки поÑтоÑнно менÑли Ñвоё положение, что ограничивало роÑÑ‚ щитовых вулканов и, возможно, не позволÑло доÑтичь им такой выÑоты, как на МарÑе. С другой Ñтороны, разница в макÑимальной выÑоте вулканов может объÑÑнÑÑ‚ÑŒÑÑ Ñ‚ÐµÐ¼, что из-за меньшей Ñилы Ñ‚ÑжеÑти на МарÑе возможно поÑтроение более выÑоких Ñтруктур, которые не обрушилиÑÑŒ бы под ÑобÑтвенным веÑом[91]. Возможно, на планете имеетÑÑ ÑÐ»Ð°Ð±Ð°Ñ Ñ‚ÐµÐºÑ‚Ð¾Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ, приводÑÑ‰Ð°Ñ Ðº образованию наблюдаемых Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ пологих каньонов[92][93]. +Сравнение ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и других планет земной группы + +Современные модели внутреннего ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа предполагают, что ÐœÐ°Ñ€Ñ ÑоÑтоит из коры Ñо Ñредней толщиной 50 км (макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð¾Ñ†ÐµÐ½ÐºÐ° — не более 125 км)[94], Ñиликатной мантии и Ñдра радиуÑом, по разным оценкам, от 1480[94] до 1800 км[95]. ПлотноÑÑ‚ÑŒ в центре планеты должна доÑтигать 8,5 г/Ñм³. Ядро чаÑтично жидкое и ÑоÑтоит в оÑновном из железа Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑью 14—18 % (по маÑÑе) Ñеры[95], причём Ñодержание лёгких Ñлементов вдвое выше, чем в Ñдре Земли. СоглаÑно Ñовременным оценкам, формирование Ñдра Ñовпало Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´Ð¾Ð¼ раннего вулканизма и продолжалоÑÑŒ около миллиарда лет. Примерно то же Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð½Ñло чаÑтичное плавление мантийных Ñиликатов[91]. Из-за меньшей Ñилы Ñ‚ÑжеÑти на МарÑе диапазон давлений в мантии МарÑа гораздо меньше, чем на Земле, а значит, в ней меньше фазовых переходов. ПредполагаетÑÑ, что фазовый переход оливина в шпинелевую модификацию начинаетÑÑ Ð½Ð° довольно больших глубинах — 800 км (400 км на Земле). Характер рельефа и другие признаки позволÑÑŽÑ‚ предположить наличие аÑтеноÑферы, ÑоÑтоÑщей из зон чаÑтично раÑплавленного вещеÑтва[96]. Ð”Ð»Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… районов МарÑа ÑоÑтавлена Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð³ÐµÐ¾Ð»Ð¾Ð³Ð¸Ñ‡ÐµÑÐºÐ°Ñ ÐºÐ°Ñ€Ñ‚Ð°[97]. + +СоглаÑно наблюдениÑм Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ и анализу коллекции марÑианÑких метеоритов, поверхноÑÑ‚ÑŒ МарÑа ÑоÑтоит главным образом из базальта. ЕÑÑ‚ÑŒ некоторые оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐ´Ð¿Ð¾Ð»Ð°Ð³Ð°Ñ‚ÑŒ, что на чаÑти марÑианÑкой поверхноÑти материал ÑвлÑетÑÑ Ð±Ð¾Ð»ÐµÐµ кварцеÑодержащим, чем обычный базальт, и может быть подобен андезитным камнÑм на Земле. Однако Ñти же Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð¶Ð½Ð¾ толковать в пользу Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ ÐºÐ²Ð°Ñ€Ñ†ÐµÐ²Ð¾Ð³Ð¾ Ñтекла. Ð—Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ более глубокого ÑÐ»Ð¾Ñ ÑоÑтоит из зерниÑтой пыли окÑида железа[98][99]. +Магнитное поле[править | править вики-текÑÑ‚] + +У МарÑа было зафикÑировано Ñлабое магнитное поле. + +СоглаÑно показаниÑм магнетометров Ñтанций «МарÑ-2» и «МарÑ-3», напрÑжённоÑÑ‚ÑŒ магнитного Ð¿Ð¾Ð»Ñ Ð½Ð° Ñкваторе ÑоÑтавлÑет около 60 гамм, на полюÑе — 120 гамм, что в 500 раз Ñлабее земного. По данным ÐМС «МарÑ-5», напрÑжённоÑÑ‚ÑŒ магнитного Ð¿Ð¾Ð»Ñ Ð½Ð° Ñкваторе ÑоÑтавлÑла 64 гаммы, а магнитный момент планетарного Ð´Ð¸Ð¿Ð¾Ð»Ñ â€” 2,4×1022 ÑÑ€Ñтед·Ñм²[100]. +Магнитное поле МарÑа + +Магнитное поле МарÑа крайне неуÑтойчиво, в различных точках планеты его напрÑжённоÑÑ‚ÑŒ может отличатьÑÑ Ð¾Ñ‚ 1,5 до 2 раз, а магнитные полюÑа не Ñовпадают Ñ Ñ„Ð¸Ð·Ð¸Ñ‡ÐµÑкими. Ðто говорит о том, что железное Ñдро МарÑа находитÑÑ Ð² Ñравнительной неподвижноÑти по отношению к его коре, то еÑÑ‚ÑŒ механизм планетарного динамо, ответÑтвенный за магнитное поле Земли, на МарÑе не работает. Ð¥Ð¾Ñ‚Ñ Ð½Ð° МарÑе не имеетÑÑ ÑƒÑтойчивого вÑепланетного магнитного полÑ[101], Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð»Ð¸, что чаÑти планетной коры намагничены и что наблюдалаÑÑŒ Ñмена магнитных полюÑов Ñтих чаÑтей в прошлом. ÐамагниченноÑÑ‚ÑŒ данных чаÑтей оказалаÑÑŒ похожей на полоÑовые магнитные аномалии в мировом океане[102]. + +По одной теории, опубликованной в 1999 году и перепроверенной в 2005 году (Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ беÑпилотной Ñтанции Â«ÐœÐ°Ñ€Ñ Ð“Ð»Ð¾Ð±Ð°Ð» Сервейор»), Ñти полоÑÑ‹ демонÑтрируют тектонику плит 4 миллиарда лет назад — до того, как динамо-машина планеты прекратила выполнÑÑ‚ÑŒ Ñвою функцию, что поÑлужило причиной резкого оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ð¼Ð°Ð³Ð½Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ полÑ[103]. Причины такого резкого оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ð½ÐµÑÑны. СущеÑтвует предположение, что функционирование динамо-машины 4 млрд. лет назад объÑÑнÑетÑÑ Ð½Ð°Ð»Ð¸Ñ‡Ð¸ÐµÐ¼ аÑтероида, который вращалÑÑ Ð½Ð° раÑÑтоÑнии 50—75 Ñ‚Ñ‹ÑÑч километров вокруг МарÑа и вызывал неÑтабильноÑÑ‚ÑŒ в его Ñдре. Затем аÑтероид ÑнизилÑÑ Ð´Ð¾ предела Роша и разрушилÑÑ[104]. Тем не менее, Ñто объÑÑнение Ñамо Ñодержит неÑÑные моменты и оÑпариваетÑÑ Ð² научном ÑообщеÑтве[105]. +Ð“Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð¾Ð·Ð°Ð¸ÐºÐ° из 102 Ñнимков, полученных иÑкуÑÑтвенным Ñпутником МарÑа «Викинг-1» 22 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 1980 +ГеологичеÑÐºÐ°Ñ Ð¸ÑториÑ[править | править вики-текÑÑ‚] + +СоглаÑно одной из гипотез, в далёком прошлом в результате ÑÑ‚Ð¾Ð»ÐºÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ Ñ ÐºÑ€ÑƒÐ¿Ð½Ñ‹Ð¼ небеÑным телом произошла оÑтановка Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñдра[106], а также Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¾Ñновного объёма атмоÑферы. ÐŸÐ¾Ñ‚ÐµÑ€Ñ Ð»ÐµÐ³ÐºÐ¸Ñ… атомов и молекул из атмоÑферы — ÑледÑтвие Ñлабого притÑÐ¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа. СчитаетÑÑ, что Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¼Ð°Ð³Ð½Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»Ð° около 4 млрд. лет назад. Ð’ÑледÑтвие ÑлабоÑти магнитного Ð¿Ð¾Ð»Ñ Ñолнечный ветер практичеÑки беÑпрепÑÑ‚Ñтвенно проникает в атмоÑферу МарÑа, и многие из фотохимичеÑких реакций под дейÑтвием Ñолнечной радиации, которые на Земле проиÑходÑÑ‚ в ионоÑфере и выше, на МарÑе могут наблюдатьÑÑ Ð¿Ñ€Ð°ÐºÑ‚Ð¸Ñ‡ÐµÑки у Ñамой его поверхноÑти. + +ГеологичеÑÐºÐ°Ñ Ð¸ÑÑ‚Ð¾Ñ€Ð¸Ñ ÐœÐ°Ñ€Ñа заключает в ÑÐµÐ±Ñ Ñ‚Ñ€Ð¸ нижеÑледующие Ñпохи[107][108]: + +ÐойÑÐºÐ°Ñ Ñра[109] (названа в чеÑÑ‚ÑŒ «Ðоевой земли», района МарÑа): формирование наиболее Ñтарой ÑохранившейÑÑ Ð´Ð¾ наших дней поверхноÑти МарÑа. ПродолжалаÑÑŒ в период 4,5—3,5 млрд. лет назад. Ð’ Ñту Ñпоху поверхноÑÑ‚ÑŒ была изрубцована многочиÑленными ударными кратерами. Плато провинции ФарÑида было, вероÑтно, Ñформировано в Ñтот период Ñ Ð¸Ð½Ñ‚ÐµÐ½Ñивным обтеканием водой позднее. + +ГеÑперийÑÐºÐ°Ñ Ñра: от 3,5 млрд. лет назад до 2,9—3,3 млрд. лет назад. Ðта Ñпоха отмечена образованием огромных лавовых полей. + +ÐмазонийÑÐºÐ°Ñ Ñра (названа в чеÑÑ‚ÑŒ «ÐмазонÑкой равнины» на МарÑе): 2,9—3,3 млрд. лет назад до наших дней. Районы, образовавшиеÑÑ Ð² Ñту Ñпоху, имеют очень мало метеоритных кратеров, но во вÑём оÑтальном они полноÑтью различаютÑÑ. Гора Олимп Ñформирована в Ñтот период. Ð’ Ñто Ð²Ñ€ÐµÐ¼Ñ Ð² других чаÑÑ‚ÑÑ… МарÑа разливалиÑÑŒ лавовые потоки. +Спутники[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: Спутники МарÑа +См. также: ТроÑнÑкие аÑтероиды МарÑа + + ФобоÑ, ÑнÑтый 23 марта 2008 года Ñпутником Mars Reconnaissance Orbiter + + ДеймоÑ, ÑнÑтый 21 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 2009 года Ñпутником Mars Reconnaissance Orbiter + + Прохождение ФобоÑа по диÑку Солнца. Снимки «Оппортьюнити» + +ЕÑтеÑтвенными Ñпутниками МарÑа ÑвлÑÑŽÑ‚ÑÑ Ð¤Ð¾Ð±Ð¾Ñ Ð¸ ДеймоÑ. Оба они открыты американÑким аÑтрономом ÐÑафом Холлом в 1877 году. Ð¤Ð¾Ð±Ð¾Ñ Ð¸ Ð”ÐµÐ¹Ð¼Ð¾Ñ Ð¸Ð¼ÐµÑŽÑ‚ неправильную форму и очень маленькие размеры. По одной из гипотез, они могут предÑтавлÑÑ‚ÑŒ Ñобой захваченные гравитационным полем МарÑа аÑтероиды наподобие (5261) Ðврика из ТроÑнÑкой группы аÑтероидов. Спутники названы в чеÑÑ‚ÑŒ перÑонажей, Ñопровождающих бога ÐреÑа (то еÑÑ‚ÑŒ МарÑа), — ФобоÑа и ДеймоÑа, олицетворÑющих Ñтрах и ужаÑ, которые помогали богу войны в битвах[110]. + +Оба Ñпутника вращаютÑÑ Ð²Ð¾ÐºÑ€ÑƒÐ³ Ñвоих оÑей Ñ Ñ‚ÐµÐ¼ же периодом, что и вокруг МарÑа, поÑтому вÑегда повёрнуты к планете одной и той же Ñтороной (Ñто вызвано Ñффектом приливного захвата и характерно Ð´Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ð½Ñтва Ñпутников планет в Солнечной ÑиÑтеме, в том чиÑле Ð´Ð»Ñ Ð›ÑƒÐ½Ñ‹). Приливное воздейÑтвие МарÑа поÑтепенно замедлÑет движение ФобоÑа, и, в конце концов, приведёт к падению Ñпутника на ÐœÐ°Ñ€Ñ (при Ñохранении текущей тенденции), или к его раÑпаду[111]. Ðапротив, Ð”ÐµÐ¹Ð¼Ð¾Ñ ÑƒÐ´Ð°Ð»ÑетÑÑ Ð¾Ñ‚ МарÑа. + +Орбитальный период ФобоÑа меньше, чем период Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа, поÑтому Ð´Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° поверхноÑти планеты Ð¤Ð¾Ð±Ð¾Ñ (в отличие от ДеймоÑа и вообще от вÑех извеÑтных еÑтеÑтвенных Ñпутников планет Солнечной ÑиÑтемы, кроме Метиды и ÐдраÑтеи) воÑходит на западе и заходит на воÑтоке[111]. + +Оба Ñпутника имеют форму, приближающуюÑÑ Ðº трёхоÑному ÑллипÑоиду, Ð¤Ð¾Ð±Ð¾Ñ (26,8×22,4×18,4 км)[6] неÑколько крупнее ДеймоÑа (15×12,2×11 км)[112]. ПоверхноÑÑ‚ÑŒ ДеймоÑа выглÑдит гораздо более гладкой за Ñчёт того, что большинÑтво кратеров покрыто тонкозерниÑтым вещеÑтвом. Очевидно, на ФобоÑе, более близком к планете и более маÑÑивном, вещеÑтво, выброшенное при ударах метеоритов, либо наноÑило повторные удары по поверхноÑти, либо падало на МарÑ, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº на ДеймоÑе оно долгое Ð²Ñ€ÐµÐ¼Ñ Ð¾ÑтавалоÑÑŒ на орбите вокруг Ñпутника, поÑтепенно оÑаждаÑÑÑŒ и ÑÐºÑ€Ñ‹Ð²Ð°Ñ Ð½ÐµÑ€Ð¾Ð²Ð½Ð¾Ñти рельефа. +Жизнь[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: Жизнь на МарÑе +ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ð¾Ð¿Ñ€Ð¾Ñа[править | править вики-текÑÑ‚] + +ПопулÑÑ€Ð½Ð°Ñ Ð¸Ð´ÐµÑ, что ÐœÐ°Ñ€Ñ Ð½Ð°Ñелён разумными марÑианами, широко раÑпроÑтранилаÑÑŒ в конце XIX века. + +ÐÐ°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¡ÐºÐ¸Ð°Ð¿Ð°Ñ€ÐµÐ»Ð»Ð¸ так называемых каналов, в Ñочетании Ñ ÐºÐ½Ð¸Ð³Ð¾Ð¹ ПерÑÐ¸Ð²Ð°Ð»Ñ Ð›Ð¾ÑƒÑлла по той же теме Ñделали популÑрной идею о планете, климат которой ÑтановилÑÑ Ð²ÑÑ‘ Ñуше, холоднее, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑƒÐ¼Ð¸Ñ€Ð°Ð»Ð° и на которой ÑущеÑтвовала древнÑÑ Ñ†Ð¸Ð²Ð¸Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ, выполнÑÑŽÑ‰Ð°Ñ Ð¸Ñ€Ñ€Ð¸Ð³Ð°Ñ†Ð¸Ð¾Ð½Ð½Ñ‹Ðµ работы[113]. + + Карта МарÑа Скиапарелли, 1888 г. + + МарÑианÑкие каналы, зариÑованные аÑтрономом П. ЛоуÑллом, 1898. + +Другие многочиÑленные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¸ объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð·Ð²ÐµÑтных лиц породили вокруг Ñтой темы так называемую «МарÑианÑкую лихорадку» (англ. Mars Fever)[114]. Ð’ 1899 году во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферных радиопомех Ñ Ð¸Ñпользованием приёмников в КолорадÑкой обÑерватории, изобретатель Ðикола ТеÑла наблюдал повторÑющийÑÑ Ñигнал. Он выÑказал догадку, что Ñто может быть радиоÑигнал Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… планет, например, МарÑа. Ð’ интервью 1901 года ТеÑла Ñказал, что ему пришла в голову мыÑль о том, что помехи могут быть вызваны иÑкуÑÑтвенно. Ð¥Ð¾Ñ‚Ñ Ð¾Ð½ не Ñмог раÑшифровать их значение, Ð´Ð»Ñ Ð½ÐµÐ³Ð¾ было невозможным то, что они возникли Ñовершенно Ñлучайно. По его мнению, Ñто было приветÑтвие одной планеты другой[115]. + +Гипотеза ТеÑлы вызвала горÑчую поддержку извеÑтного британÑкого учёного-физика УильÑма ТомÑона (лорда Кельвина), который, поÑетив СШРв 1902 году, Ñказал, что, по его мнению, ТеÑла поймал Ñигнал марÑиан, поÑланный в СШÐ[116]. Однако ещё до Ð¾Ñ‚Ð±Ñ‹Ñ‚Ð¸Ñ Ð¸Ð· Ðмерики Кельвин Ñтал решительно отрицать Ñто заÑвление: «Ðа Ñамом деле Ñ Ñказал, что жители МарÑа, еÑли они ÑущеÑтвуют, неÑомненно могут видеть Ðью-Йорк, в чаÑтноÑти, Ñвет от ÑлектричеÑтва»[117]. +ФактичеÑкие данные[править | править вики-текÑÑ‚] + +Ðаучные гипотезы о ÑущеÑтвовании в прошлом жизни на МарÑе приÑутÑтвуют давно. По результатам наблюдений Ñ Ð—ÐµÐ¼Ð»Ð¸ и данным коÑмичеÑкого аппарата «МарÑ-ÑкÑпреÑÑ» в атмоÑфере МарÑа обнаружен метан. Позднее, в 2014 году, марÑоход ÐÐСРCuriosity зафикÑировал вÑплеÑк ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¼ÐµÑ‚Ð°Ð½Ð° в атмоÑфере МарÑа и обнаружил органичеÑкие молекулы в образцах, извлечённых в ходе Ð±ÑƒÑ€ÐµÐ½Ð¸Ñ Ñкалы Камберленд.[118] +РаÑпределение метана в атмоÑфере МарÑа в летний период в Ñеверном полушарии. + +Ð’ уÑловиÑÑ… МарÑа Ñтот газ довольно быÑтро разлагаетÑÑ, поÑтому должен ÑущеÑтвовать поÑтоÑнный иÑточник его пополнениÑ. Таким иÑточником может быть либо геологичеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ (но дейÑтвующие вулканы на МарÑе не обнаружены), либо жизнедеÑтельноÑÑ‚ÑŒ бактерий. ИнтереÑно, что в некоторых метеоритах марÑианÑкого проиÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ñ‹ образованиÑ, по форме напоминающие клетки, Ñ…Ð¾Ñ‚Ñ Ð¾Ð½Ð¸ и уÑтупают мельчайшим земным организмам по размерам[118][119]. Одним из таких метеоритов ÑвлÑетÑÑ ALH 84001, найденный в Ðнтарктиде в 1984 году. +ALH84001 под микроÑкопом. + +Важные Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñделаны марÑоходом «Curiosity». Ð’ декабре 2012 года были получены данные о наличии на МарÑе органичеÑких вещеÑтв, а также перхлоратов. Те же иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð»Ð¸ наличие водÑного пара в нагретых образцах грунта[120]. ИнтереÑным фактом ÑвлÑетÑÑ Ñ‚Ð¾, что «Curiosity» на МарÑе приземлилÑÑ Ð½Ð° дно выÑохшего озера[121]. + +Ðнализ наблюдений говорит, что планета ранее имела значительно более благоприÑтные Ð´Ð»Ñ Ð¶Ð¸Ð·Ð½Ð¸ уÑловиÑ, нежели теперь. СоглаÑно программе «Викинг», оÑущеÑтвлённой в Ñередине 1970-Ñ… годов, была проведена ÑÐµÑ€Ð¸Ñ ÑкÑпериментов Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð¼Ð¸ÐºÑ€Ð¾Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð¼Ð¾Ð² в марÑианÑкой почве. Она дала положительные результаты: например, временное увеличение Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ CO2 при помещении чаÑтиц почвы в воду и питательную Ñреду. Однако затем данное ÑвидетельÑтво жизни на МарÑе было оÑпорено учёными команды «Викингов»[122]. Ðто привело к их продолжительным Ñпорам Ñ ÑƒÑ‡Ñ‘Ð½Ñ‹Ð¼ из NASA Гильбертом Левиным, который утверждал, что «Викинг» обнаружил жизнь. ПоÑле переоценки данных «Викинга» в Ñвете Ñовременных научных знаний об ÑкÑтремофилах было уÑтановлено, что проведённые ÑкÑперименты были недоÑтаточно Ñовершенны Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ñтих форм жизни. Более того, Ñти теÑÑ‚Ñ‹ могли убить организмы, даже еÑли поÑледние ÑодержалиÑÑŒ в пробах[123]. ТеÑÑ‚Ñ‹, проведённые в рамках программы «ФеникÑ», показали, что почва имеет очень щелочной pH и Ñодержит магний, натрий, калий и хлориды[124]. Питательных вещеÑтв в почве доÑтаточно Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸, однако жизненные формы должны иметь защиту от интенÑивного ультрафиолетового Ñвета[125]. + +Ðа ÑегоднÑшний день уÑловием Ð´Ð»Ñ Ñ€Ð°Ð·Ð²Ð¸Ñ‚Ð¸Ñ Ð¸ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸ на планете ÑчитаетÑÑ Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ðµ жидкой воды на её поверхноÑти, а также нахождение орбиты планеты в так называемой зоне обитаемоÑти, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð² Солнечной ÑиÑтеме начинаетÑÑ Ð·Ð° орбитой Венеры и заканчиваетÑÑ Ð±Ð¾Ð»ÑŒÑˆÐ¾Ð¹ полуоÑью орбиты МарÑа[126]. Вблизи Ð¿ÐµÑ€Ð¸Ð³ÐµÐ»Ð¸Ñ ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð½ÑƒÑ‚Ñ€Ð¸ Ñтой зоны, однако Ñ‚Ð¾Ð½ÐºÐ°Ñ Ð°Ñ‚Ð¼Ð¾Ñфера Ñ Ð½Ð¸Ð·ÐºÐ¸Ð¼ давлением препÑÑ‚Ñтвует поÑвлению жидкой воды на длительный период. Ðедавние ÑвидетельÑтва говорÑÑ‚ о том, что Ð»ÑŽÐ±Ð°Ñ Ð²Ð¾Ð´Ð° на поверхноÑти МарÑа ÑвлÑетÑÑ Ñлишком Ñолёной и киÑлотной Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¿Ð¾ÑтоÑнной земноподобной жизни[127]. + +ОтÑутÑтвие магнитоÑферы и крайне Ñ€Ð°Ð·Ñ€ÐµÐ¶Ñ‘Ð½Ð½Ð°Ñ Ð°Ñ‚Ð¼Ð¾Ñфера МарÑа также ÑвлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¾Ð¹ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸. Ðа поверхноÑти планеты идёт очень Ñлабое перемещение тепловых потоков, она плохо изолирована от бомбардировки чаÑтицами Ñолнечного ветра; помимо Ñтого, при нагревании вода мгновенно иÑпарÑетÑÑ, Ð¼Ð¸Ð½ÑƒÑ Ð¶Ð¸Ð´ÐºÐ¾Ðµ ÑоÑтоÑние из-за низкого давлениÑ. Кроме того, ÐœÐ°Ñ€Ñ Ñ‚Ð°ÐºÐ¶Ðµ находитÑÑ Ð½Ð° пороге Ñ‚. н. «геологичеÑкой Ñмерти». Окончание вулканичеÑкой активноÑти, по вÑей видимоÑти, оÑтановило круговорот минералов и химичеÑких Ñлементов между поверхноÑтью и внутренней чаÑтью планеты[128]. +Терраформированный ÐœÐ°Ñ€Ñ Ð² предÑтавлении художника. + +БлизоÑÑ‚ÑŒ МарÑа и отноÑительное его ÑходÑтво Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹ породило Ñ€Ñд фантаÑтичеÑких проектов Ñ‚ÐµÑ€Ñ€Ð°Ñ„Ð¾Ñ€Ð¼Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ колонизации МарÑа землÑнами в будущем. + +МарÑоход Curiosity обнаружил Ñразу два иÑточника органичеÑких молекул на поверхноÑти МарÑа. Помимо кратковременного ÑƒÐ²ÐµÐ»Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð»Ð¸ метана в атмоÑфере, аппарат зафикÑировал наличие углеродных Ñоединений в порошкообразном образце, оÑтавшемÑÑ Ð¾Ñ‚ Ð±ÑƒÑ€ÐµÐ½Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкой Ñкалы. Первое открытие позволил Ñделать инÑтрумент SAM на борту марÑохода. За 20 меÑÑцев он 12 раз измерил ÑоÑтав марÑианÑкой атмоÑферы. Ð’ двух ÑлучаÑÑ… — в конце 2013 года и начале 2014 — Curiosity удалоÑÑŒ обнаружить деÑÑтикратное увеличение Ñредней доли метана. Ðтот вÑплеÑк, по мнению членов научной команды марÑохода, ÑвидетельÑтвует об обнаружении локального иÑточника метана. Имеет ли он биологичеÑкое или же иное проиÑхождение, ÑпециалиÑÑ‚Ñ‹ утверждать затруднÑÑŽÑ‚ÑÑ Ð²ÑледÑтвие нехватки данных Ð´Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ñ†ÐµÐ½Ð½Ð¾Ð³Ð¾ анализа. +ÐÑтрономичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти МарÑа[править | править вики-текÑÑ‚] + +ПоÑле поÑадок автоматичеÑких аппаратов на поверхноÑÑ‚ÑŒ МарÑа поÑвилаÑÑŒ возможноÑÑ‚ÑŒ веÑти аÑтрономичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð½ÐµÐ¿Ð¾ÑредÑтвенно Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти планеты. Ð’ÑледÑтвие аÑтрономичеÑкого Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа в Солнечной ÑиÑтеме, характериÑтик атмоÑферы, периода Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и его Ñпутников картина ночного неба МарÑа (и аÑтрономичеÑких Ñвлений, наблюдаемых Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹) отличаетÑÑ Ð¾Ñ‚ земной и во многом предÑтавлÑетÑÑ Ð½ÐµÐ¾Ð±Ñ‹Ñ‡Ð½Ð¾Ð¹ и интереÑной. +ÐебеÑÐ½Ð°Ñ Ñфера[править | править вики-текÑÑ‚] + +Северный Ð¿Ð¾Ð»ÑŽÑ Ð½Ð° МарÑе, вÑледÑтвие наклона оÑи планеты, находитÑÑ Ð² Ñозвездии Ð›ÐµÐ±ÐµÐ´Ñ (Ñкваториальные координаты: прÑмое воÑхождение 21ч 10м 42Ñ, Ñклонение +52° 53.0′ и не отмечен Ñркой звездой: Ð±Ð»Ð¸Ð¶Ð°Ð¹ÑˆÐ°Ñ Ðº полюÑу — туÑÐºÐ»Ð°Ñ Ð·Ð²ÐµÐ·Ð´Ð° шеÑтой величины BD +52 2880 (другие её Ð¾Ð±Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ â€” HR 8106, HD 201834, SAO 33185). Южный Ð¿Ð¾Ð»ÑŽÑ Ð¼Ð¸Ñ€Ð° (координаты 9ч 10м 42Ñ Ð¸ −52° 53,0) находитÑÑ Ð² паре градуÑов от звезды Каппа ПаруÑов (Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° 2,5) — её, в принципе, можно Ñчитать Южной ПолÑрной звездой МарÑа. + +Вид неба похож на наблюдаемый Ñ Ð—ÐµÐ¼Ð»Ð¸, Ñ Ð¾Ð´Ð½Ð¸Ð¼ отличием: при наблюдении годичного Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð¡Ð¾Ð»Ð½Ñ†Ð° по ÑозвездиÑм Зодиака оно (как и другие планеты, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð—ÐµÐ¼Ð»ÑŽ), Ð²Ñ‹Ð¹Ð´Ñ Ð¸Ð· воÑточной чаÑти ÑÐ¾Ð·Ð²ÐµÐ·Ð´Ð¸Ñ Ð Ñ‹Ð±, будет проходить в течение 6 дней через Ñеверную чаÑÑ‚ÑŒ ÑÐ¾Ð·Ð²ÐµÐ·Ð´Ð¸Ñ ÐšÐ¸Ñ‚Ð° перед тем, как Ñнова вÑтупить в западную чаÑÑ‚ÑŒ Рыб. + +Во Ð²Ñ€ÐµÐ¼Ñ Ð²Ð¾Ñхода и захода Солнца марÑианÑкое небо в зените имеет краÑновато-розовый цвет[129], а в непоÑредÑтвенной близоÑти к диÑку Солнца — от голубого до фиолетового, что Ñовершенно противоположно картине земных зорь. +Закат на МарÑе 19 Ð¼Ð°Ñ 2005 года. Снимок марÑохода «Спирит», который находилÑÑ Ð² кратере ГуÑева. +Закат на МарÑе 19 Ð¼Ð°Ñ 2005 года. Снимок марÑохода «Спирит», который находилÑÑ Ð² кратере ГуÑева. + +Ð’ полдень небо МарÑа жёлто-оранжевое. Причина таких отличий от цветовой гаммы земного неба — ÑвойÑтва тонкой, разреженной, Ñодержащей взвешенную пыль атмоÑферы МарÑа. Ðа МарÑе Ñ€ÑлеевÑкое раÑÑеÑние лучей (которое на Земле и ÑвлÑетÑÑ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð¾Ð¹ голубого цвета неба) играет незначительную роль, Ñффект его Ñлаб, но проÑвлÑетÑÑ Ð² виде голубого ÑÐ²ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ воÑходе\закате Солнца, когда Ñвет проходит более толÑтый Ñлой воздуха. Предположительно, жёлто-Ð¾Ñ€Ð°Ð½Ð¶ÐµÐ²Ð°Ñ Ð¾ÐºÑ€Ð°Ñка неба также вызываетÑÑ Ð¿Ñ€Ð¸ÑутÑтвием 1 % магнетита в чаÑтицах пыли, поÑтоÑнно взвешенной в марÑианÑкой атмоÑфере и поднимаемой Ñезонными пылевыми бурÑми. Сумерки начинаютÑÑ Ð·Ð°Ð´Ð¾Ð»Ð³Ð¾ до воÑхода Солнца и длÑÑ‚ÑÑ Ð´Ð¾Ð»Ð³Ð¾ поÑле его захода. Иногда цвет марÑианÑкого неба приобретает фиолетовый оттенок в результате раÑÑеÑÐ½Ð¸Ñ Ñвета на микрочаÑтицах водÑного льда в облаках (поÑледнее — довольно редкое Ñвление)[129]. +Солнце и планеты[править | править вики-текÑÑ‚] + +Угловой размер Солнца, наблюдаемый Ñ ÐœÐ°Ñ€Ñа, меньше видимого Ñ Ð—ÐµÐ¼Ð»Ð¸ и ÑоÑтавлÑет 2â„3 от поÑледнего. Меркурий Ñ ÐœÐ°Ñ€Ñа будет практичеÑки недоÑтупен Ð´Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ð¹ невооружённым глазом из-за чрезвычайной близоÑти к Солнцу. Самой Ñркой планетой на небе МарÑа ÑвлÑетÑÑ Ð’ÐµÐ½ÐµÑ€Ð°, на втором меÑте — Юпитер (его четыре крупнейших Ñпутника чаÑÑ‚ÑŒ времени можно наблюдать без телеÑкопа), на третьем — ЗемлÑ[130]. + +Ð—ÐµÐ¼Ð»Ñ Ð¿Ð¾ отношению к МарÑу ÑвлÑетÑÑ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÐµÐ¹ планетой, так же, как Венера Ð´Ð»Ñ Ð—ÐµÐ¼Ð»Ð¸. СоответÑтвенно, Ñ ÐœÐ°Ñ€Ñа Ð—ÐµÐ¼Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°ÐµÑ‚ÑÑ ÐºÐ°Ðº утреннÑÑ Ð¸Ð»Ð¸ вечернÑÑ Ð·Ð²ÐµÐ·Ð´Ð°, воÑходÑÑ‰Ð°Ñ Ð¿ÐµÑ€ÐµÐ´ раÑÑветом или Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð½Ð° вечернем небе поÑле захода Солнца. + +МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑÐ»Ð¾Ð½Ð³Ð°Ñ†Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸ на небе МарÑа ÑоÑтавлÑет 38 градуÑов. Ð”Ð»Ñ Ð½ÐµÐ²Ð¾Ð¾Ñ€ÑƒÐ¶Ñ‘Ð½Ð½Ð¾Ð³Ð¾ глаза Ð—ÐµÐ¼Ð»Ñ Ð±ÑƒÐ´ÐµÑ‚ видна как ÑÑ€ÐºÐ°Ñ (макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° около −2,5m) Ð·ÐµÐ»ÐµÐ½Ð¾Ð²Ð°Ñ‚Ð°Ñ Ð·Ð²ÐµÐ·Ð´Ð°, Ñ€Ñдом Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ будет легко различима Ð¶ÐµÐ»Ñ‚Ð¾Ð²Ð°Ñ‚Ð°Ñ Ð¸ более туÑÐºÐ»Ð°Ñ (около +0,9m) звёздочка Луны[131]. Ð’ телеÑкоп оба объекта будут видны Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼Ð¸ фазами. Обращение Луны вокруг Земли будет наблюдатьÑÑ Ñ ÐœÐ°Ñ€Ñа Ñледующим образом: на макÑимальном угловом удалении Луны от Земли невооружённый глаз легко разделит Луну и Землю: через неделю «звёздочки» Луны и Земли ÑольютÑÑ Ð² неразделимую глазом единую звезду, ещё через неделю Луна будет Ñнова видна на макÑимальном раÑÑтоÑнии, но уже Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¹ Ñтороны от Земли. ПериодичеÑки наблюдатель на МарÑе Ñможет видеть проход (транзит) Луны по диÑку Земли либо, наоборот, покрытие Луны диÑком Земли. МакÑимальное видимое удаление Луны от Земли (и их Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ ÑркоÑÑ‚ÑŒ) при наблюдении Ñ ÐœÐ°Ñ€Ñа будет значительно изменÑÑ‚ÑŒÑÑ Ð² завиÑимоÑти от взаимного Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸ и МарÑа, и, ÑоответÑтвенно, раÑÑтоÑÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ планетами. Ð’ Ñпохи противоÑтоÑний оно ÑоÑтавит около 17 минут дуги (около половины углового диаметра Солнца и Луны при наблюдении Ñ Ð—ÐµÐ¼Ð»Ð¸), на макÑимальном удалении Земли и МарÑа — 3,5 минуты дуги. ЗемлÑ, как и другие планеты, будет наблюдатьÑÑ Ð² полоÑе Ñозвездий Зодиака. ÐÑтроном на МарÑе также Ñможет наблюдать прохождение Земли по диÑку Солнца; ближайшее такое Ñвление произойдёт 10 ноÑÐ±Ñ€Ñ 2084 года[132]. +ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ИÑÑледование МарÑа +ИÑÑледование МарÑа клаÑÑичеÑкими методами аÑтрономии[править | править вики-текÑÑ‚] +Ð˜Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа Ñ Ñ€Ð°Ð·Ð½Ð¾Ð¹ Ñтепенью детализации в разные годы. + +Первые Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа проводилиÑÑŒ до Ð¸Ð·Ð¾Ð±Ñ€ÐµÑ‚ÐµÐ½Ð¸Ñ Ñ‚ÐµÐ»ÐµÑкопа. Ðто были позиционные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ñ Ñ†ÐµÐ»ÑŒÑŽ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹ планеты по отношению к звёздам. СущеÑтвование МарÑа как блуждающего объекта в ночном небе было пиÑьменно заÑвидетельÑтвовано древнеегипетÑкими аÑтрономами в 1534 году до н. Ñ. Ими же было уÑтановлено ретроградное (попÑтное) движение планеты и раÑÑчитана Ñ‚Ñ€Ð°ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¼ÐµÑте Ñ Ñ‚Ð¾Ñ‡ÐºÐ¾Ð¹, где планета менÑет Ñвоё движение отноÑительно Земли Ñ Ð¿Ñ€Ñмого на попÑтное[133]. + +Ð’ вавилонÑкой планетарной теории были впервые получены временные Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ð°Ñ€Ð½Ð¾Ð³Ð¾ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и уточнено положение планеты на ночном небе[134][135]. ПользуÑÑÑŒ данными египтÑн и вавилонÑн, древнегречеÑкие (ÑллиниÑтичеÑкие) филоÑофы и аÑтрономы разработали подробную геоцентричеÑкую модель Ð´Ð»Ñ Ð¾Ð±ÑŠÑÑÐ½ÐµÐ½Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚. СпуÑÑ‚Ñ Ð½ÐµÑколько веков индийÑкими и иÑламÑкими аÑтрономами был оценен размер МарÑа и раÑÑтоÑние до него от Земли. Ð’ XVI веке Ðиколай Коперник предложил гелиоцентричеÑкую модель Ð´Ð»Ñ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ Ð¡Ð¾Ð»Ð½ÐµÑ‡Ð½Ð¾Ð¹ ÑиÑтемы Ñ ÐºÑ€ÑƒÐ³Ð¾Ð²Ñ‹Ð¼Ð¸ планетарными орбитами. Его результаты были переÑмотрены Иоганном Кеплером, который ввёл более точную ÑллиптичеÑкую орбиту МарÑа, Ñовпадающую Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°ÐµÐ¼Ð¾Ð¹. + +ГолландÑкий аÑтроном ХриÑтиан Ð“ÑŽÐ¹Ð³ÐµÐ½Ñ Ð¿ÐµÑ€Ð²Ñ‹Ð¼ ÑоÑтавил карту поверхноÑти МарÑа, отражающую множеÑтво деталей. 28 ноÑÐ±Ñ€Ñ 1659 года он Ñделал неÑколько риÑунков МарÑа, на которых были отображены различные темные облаÑти, позже ÑопоÑтавленные Ñ Ð¿Ð»Ð°Ñ‚Ð¾ Большой Сирт[136]. + +Предположительно первые наблюдениÑ, уÑтановившие ÑущеÑтвование у МарÑа ледÑной шапки на южном полюÑе, были Ñделаны итальÑнÑким аÑтрономом Джованни Доменико КаÑÑини в 1666 году. Ð’ том же году он при наблюдениÑÑ… МарÑа делал зариÑовки видимых деталей поверхноÑти и выÑÑнил, что через 36 или 37 дней Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´ÐµÑ‚Ð°Ð»ÐµÐ¹ поверхноÑти повторÑÑŽÑ‚ÑÑ, а затем вычиÑлил период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ â€” 24 ч. 40 м. (Ñтот результат отличаетÑÑ Ð¾Ñ‚ правильного Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÐ½ÐµÐµ чем на 3 минуты)[136]. + +Ð’ 1672 году ХриÑтиан Ð“ÑŽÐ¹Ð³ÐµÐ½Ñ Ð·Ð°Ð¼ÐµÑ‚Ð¸Ð» нечёткую белую шапочку и на Ñеверном полюÑе[137] + +Ð’ 1888 году Джованни Скиапарелли дал первые имена отдельным деталÑм поверхноÑти[138]: Ð¼Ð¾Ñ€Ñ Ðфродиты, ÐритрейÑкое, ÐдриатичеÑкое, КиммерийÑкое; озёра Солнца, Лунное и ФеникÑ. + +РаÑцвет телеÑкопичеÑких наблюдений МарÑа пришёлÑÑ Ð½Ð° конец XIX — Ñередину XX века. Во многом он обуÑловлен общеÑтвенным интереÑом и извеÑтными научными Ñпорами вокруг наблюдавшихÑÑ Ð¼Ð°Ñ€ÑианÑких каналов. Среди аÑтрономов докоÑмичеÑкой Ñры, проводивших телеÑкопичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа в Ñтот период, наиболее извеÑтны Скиапарелли, ПерÑиваль Ловелл, Слайфер, Ðнтониади, Барнард, Жарри-Делож, Л. Ðдди, Тихов, Вокулёр. Именно ими были заложены оÑновы ареографии и ÑоÑтавлены первые подробные карты поверхноÑти МарÑа — Ñ…Ð¾Ñ‚Ñ Ð¾Ð½Ð¸ и оказалиÑÑŒ практичеÑки полноÑтью неверными поÑле полётов к МарÑу автоматичеÑких зондов. +ИÑÑледование МарÑа коÑмичеÑкими аппаратами[править | править вики-текÑÑ‚] +Изучение Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ орбитальных телеÑкопов[править | править вики-текÑÑ‚] +КоÑмичеÑкий телеÑкоп «Хаббл». + +Ð”Ð»Ñ ÑиÑтематичеÑкого иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа были иÑпользованы[139] возможноÑти коÑмичеÑкого телеÑкопа «Хаббл» (КТХ или HST — Hubble Space Telescope), при Ñтом были получены фотографии МарÑа Ñ Ñамым выÑоким разрешением из когда-либо Ñделанных на Земле[140]. КТХ может Ñоздать Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ð¹, что позволÑет промоделировать погодные ÑиÑтемы. Ðаземные телеÑкопы, оÑнащенные ПЗС, могут Ñделать Ñ„Ð¾Ñ‚Ð¾Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа выÑокой чёткоÑти, что позволÑет в противоÑтоÑнии регулÑрно проводить мониторинг планетной погоды[141]. + +РентгеновÑкое излучение Ñ ÐœÐ°Ñ€Ñа, впервые обнаруженное аÑтрономами в 2001 году Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ коÑмичеÑкой рентгеновÑкой обÑерватории «Чандра», ÑоÑтоит из двух компонентов. ÐŸÐµÑ€Ð²Ð°Ñ ÑоÑтавлÑÑŽÑ‰Ð°Ñ ÑвÑзана Ñ Ñ€Ð°ÑÑеиванием в верхней атмоÑфере МарÑа рентгеновÑких лучей Солнца, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº Ð²Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ñ€Ð¾Ð¸Ñходит от взаимодейÑÑ‚Ð²Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ ионами Ñ Ð¾Ð±Ð¼ÐµÐ½Ð¾Ð¼ зарÑдами[142]. +ИÑÑледование МарÑа межпланетными ÑтанциÑми[править | править вики-текÑÑ‚] + +С 1960-Ñ… годов к МарÑу Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾Ð³Ð¾ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ и Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти были направлены неÑколько автоматичеÑких межпланетных Ñтанций (ÐМС). Кроме того, продолжалоÑÑŒ диÑтанционное зондирование МарÑа Ñ Ð—ÐµÐ¼Ð»Ð¸ в большей чаÑти Ñлектромагнитного Ñпектра Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ наземных и орбитальных телеÑкопов, например, в инфракраÑном Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÑоÑтава поверхноÑти[143], в ультрафиолетовом и Ñубмиллиметровом диапазонах — Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÑоÑтава атмоÑферы[144][145], в радиодиапазоне — Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ ÑкороÑти ветра[146]. +СоветÑкие иÑÑледованиÑ[править | править вики-текÑÑ‚] +Одна из первых цветных фотографий МарÑа, полученных Ñ ÐМС «МарÑ-3». + +СоветÑкие иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа включали в ÑÐµÐ±Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñƒ «МарÑ», в рамках которой Ñ 1962 по 1973 год были запущены автоматичеÑкие межпланетные Ñтанции четырёх поколений Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ ÐœÐ°Ñ€Ñ Ð¸ околопланетного проÑтранÑтва. Первые ÐМС («МарÑ-1», «Зонд-2») иÑÑледовали также и межпланетное проÑтранÑтво. + +КоÑмичеÑкие аппараты четвёртого Ð¿Ð¾ÐºÐ¾Ð»ÐµÐ½Ð¸Ñ (ÑÐµÑ€Ð¸Ñ Ðœ-71 — «МарÑ-2», «МарÑ-3», запущены в 1971 году) ÑоÑтоÑли из орбитальной Ñтанции — иÑкуÑÑтвенного Ñпутника МарÑа и ÑпуÑкаемого аппарата Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой марÑианÑкой Ñтанцией, комплектовавшейÑÑ Ð¼Ð°Ñ€Ñоходом «ПрОП-М». КоÑмичеÑкие аппараты Ñерии Ðœ-73С «МарÑ-4» и «МарÑ-5» должны были выйти на орбиту вокруг МарÑа и обеÑпечивать ÑвÑзь Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкими марÑианÑкими ÑтанциÑми, которые неÑли ÐМС Ñерии Ðœ-73П «МарÑ-6» и «МарÑ-7»; Ñти четыре ÐМС были запущены в 1973 году. + +Из-за неудач ÑпуÑкаемых аппаратов Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ñ‚ÐµÑ…Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° вÑей программы «МарÑ» — проведение иÑÑледований на поверхноÑти планеты Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ автоматичеÑкой марÑианÑкой Ñтанции — не была решена. Тем не менее, многие научные задачи, такие, как получение фотографий поверхноÑти МарÑа и различные Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферы, магнитоÑферы, ÑоÑтава почвы ÑвлÑлиÑÑŒ передовыми Ð´Ð»Ñ Ñвоего времени[147]. Ð’ рамках программы была оÑущеÑтвлена Ð¿ÐµÑ€Ð²Ð°Ñ Ð¼ÑÐ³ÐºÐ°Ñ Ð¿Ð¾Ñадка ÑпуÑкаемого аппарата на поверхноÑÑ‚ÑŒ МарÑа («МарÑ-3», 2 Ð´ÐµÐºÐ°Ð±Ñ€Ñ 1971 года) и Ð¿ÐµÑ€Ð²Ð°Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ° передачи Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти. + +СССРоÑущеÑтвил также программу «ФобоÑ» — две автоматичеÑкие межпланетные Ñтанции, предназначенные Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа и его Ñпутника ФобоÑа. + +ÐŸÐµÑ€Ð²Ð°Ñ ÐМС «ФобоÑ-1» была запущена 7 июлÑ, а втораÑ, «ФобоÑ-2» — 12 Ð¸ÑŽÐ»Ñ 1988 года[148]. ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° — доÑтавка на поверхноÑÑ‚ÑŒ ФобоÑа ÑпуÑкаемых аппаратов (ПрОП-Ф и ДÐС) Ð´Ð»Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñпутника МарÑа — оÑталаÑÑŒ невыполненной. Однако, неÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° потерю ÑвÑзи Ñ Ð¾Ð±Ð¾Ð¸Ð¼Ð¸ КÐ, иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа, ФобоÑа и околомарÑианÑкого проÑтранÑтва, выполненные в течение 57 дней на Ñтапе орбитального Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Â«Ð¤Ð¾Ð±Ð¾Ñа-2» вокруг МарÑа, позволили получить новые научные результаты о тепловых характериÑтиках ФобоÑа, плазменном окружении МарÑа, взаимодейÑтвии его Ñ Ñолнечным ветром. +ÐмериканÑкие иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² XX веке[править | править вики-текÑÑ‚] +Ð¤Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ñ€Ð°Ð¹Ð¾Ð½Ð° КидониÑ, ÑÐ´ÐµÐ»Ð°Ð½Ð½Ð°Ñ Ñтанцией «Викинг-1» в 1976 году. + +Ð’ 1964 году в СШРбыл оÑущеÑтвлён первый удачный запуÑк к МарÑу в рамках программы «Маринер». «Маринер-4» оÑущеÑтвил первое иÑÑледование Ñ Ð¿Ñ€Ð¾Ð»Ñ‘Ñ‚Ð½Ð¾Ð¹ траектории и Ñделал первые Ñнимки поверхноÑти[149]. «Маринер-6» и «Маринер-7», запущенные в 1969 году, произвели Ñ Ð¿Ñ€Ð¾Ð»Ñ‘Ñ‚Ð½Ð¾Ð¹ траектории первое иÑÑледование ÑоÑтава атмоÑферы Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸ÐµÐ¼ ÑпектроÑкопичеÑких методик и определение температуры поверхноÑти по измерениÑм инфракраÑного излучениÑ. Ð’ 1971 году «Маринер-9» Ñтал первым иÑкуÑÑтвенным Ñпутником МарÑа и оÑущеÑтвил первое картографирование поверхноÑти. + +Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° СШР— «Викинг» — включала запуÑк в 1975 году двух идентичных коÑмичеÑких аппаратов — «Викинг-1» и «Викинг-2», которые провели иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ Ð¾ÐºÐ¾Ð»Ð¾Ð¼Ð°Ñ€ÑианÑкой орбиты и на поверхноÑти МарÑа, в чаÑтноÑти, поиÑк жизни в пробах грунта. Каждый «Викинг» ÑоÑтоÑл из орбитальной Ñтанции — иÑкуÑÑтвенного Ñпутника МарÑа — и ÑпуÑкаемого аппарата Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой марÑианÑкой Ñтанцией. ÐвтоматичеÑкие марÑианÑкие Ñтанции «Викингов» — первые коÑмичеÑкие аппараты, уÑпешно работавшие на поверхноÑти МарÑа и передавшие фотографии Ñ Ð¼ÐµÑта поÑадки. Жизнь не удалоÑÑŒ обнаружить. + +Mars Pathfinder — поÑадочный аппарат ÐÐСÐ, работавший на поверхноÑти в 1996—1997 годах. +Карта МарÑа +ОпиÑание Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + +Спирит Спирит + +Mars rover msrds simulation.jpg Оппортьюнити + +МарÑопроходец Соджорнер + +Viking Lander model.jpg + +Викинг-1 + +Viking Lander model.jpg Викинг-2 + +Ð¤ÐµÐ½Ð¸ÐºÑ Ð¤ÐµÐ½Ð¸ÐºÑ + +Mars3 lander vsm.jpg МарÑ-3 + +КьюриоÑити КьюриоÑити + +Maquette EDM salon du Bourget 2013 DSC 0192.JPG + +Скиапарелли +Ð’ наше времÑ[править | править вики-текÑÑ‚] + + Соединённые Штаты Ðмерики Mars Global Surveyor — орбитальный аппарат ÐÐСÐ, оÑущеÑтвлÑвший картографирование поверхноÑти в 1999—2007 годах. + Соединённые Штаты Ðмерики «ФеникÑ» — поÑадочный аппарат ÐÐСÐ, работавший на поверхноÑти в 2008 году. + Соединённые Штаты Ðмерики «Спирит» — марÑоход, работавший на поверхноÑти в 2004—2010 годах. + +Ðа наÑтоÑщий момент (2016 год) на орбитах иÑкуÑÑтвенных Ñпутников МарÑа находÑÑ‚ÑÑ Ð½ÐµÑколько работающих ÐМС: + + Соединённые Штаты Ðмерики Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» (Ñ 24 октÑÐ±Ñ€Ñ 2001 года), + Европа «МарÑ-ÑкÑпреÑÑ» (Ñ 25 Ð´ÐµÐºÐ°Ð±Ñ€Ñ 2003 года), + Соединённые Штаты Ðмерики «МарÑианÑкий разведывательный Ñпутник» (Ñ 10 марта 2006 года), + Соединённые Штаты Ðмерики «MAVEN» (Ñ 21/22 ÑентÑÐ±Ñ€Ñ 2014 года)[150][151], + Ð˜Ð½Ð´Ð¸Ñ Â«Mangalyaan» (c 24 ÑентÑÐ±Ñ€Ñ 2014 года)[152]. + Европа Â«Ð¢Ñ€ÐµÐ¹Ñ Ð“Ð°Ñ ÐžÑ€Ð±Ð¸Ñ‚ÐµÑ€Â» (Ñ 19 октÑÐ±Ñ€Ñ 2016 г.) + +Ðа поверхноÑти планеты работают марÑоходы: + + Соединённые Штаты Ðмерики «Оппортьюнити» (Ñ 25 ÑÐ½Ð²Ð°Ñ€Ñ 2004 года), + Соединённые Штаты Ðмерики «КьюриоÑити» (Mars Science Laboratory) (Ñ 6 авгуÑта 2012 года). + +Кроме того, к МарÑу летит + + РоÑÑÐ¸Ñ Ð•Ð²Ñ€Ð¾Ð¿Ð° ДеÑантный модуль Ñ Ð¿Ð¾Ñадочной платформой «ÐкзомарÑ» (Ñ 14 марта 2016 года). + +Ð’ культуре[править | править вики-текÑÑ‚] +ИллюÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкого треножника из французÑкого Ð¸Ð·Ð´Ð°Ð½Ð¸Ñ Â«Ð’Ð¾Ð¹Ð½Ñ‹ миров» 1906 года. +Кадр из фильма Я. Протазанова «ÐÑлита» (1924). +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ÐœÐ°Ñ€Ñ Ð² культуре + +К Ñозданию фантаÑтичеÑких произведений о МарÑе пиÑателей подталкивали начавшиеÑÑ Ð² конце XIX века диÑкуÑÑии учёных о возможноÑти того, что на поверхноÑти МарÑа ÑущеÑтвует не проÑто жизнь, а Ñ€Ð°Ð·Ð²Ð¸Ñ‚Ð°Ñ Ñ†Ð¸Ð²Ð¸Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ[153]. Ð’ Ñто Ð²Ñ€ÐµÐ¼Ñ Ð±Ñ‹Ð» Ñоздан, например, знаменитый роман Г. УÑллÑа «Война миров», в котором марÑиане пыталиÑÑŒ покинуть Ñвою умирающую планету Ð´Ð»Ñ Ð·Ð°Ð²Ð¾ÐµÐ²Ð°Ð½Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸. Ð’ 1938 году в СШРрадиоверÑÐ¸Ñ Ñтого Ð¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð±Ñ‹Ð»Ð° предÑтавлена в виде новоÑтной радиопередачи, что поÑлужило причиной маÑÑовой паники, когда многие Ñлушатели по ошибке принÑли Ñтот «репортаж» за правду[154]. Ð’ 1966 году пиÑатели Ðркадий и Ð‘Ð¾Ñ€Ð¸Ñ Ð¡Ñ‚Ñ€ÑƒÐ³Ð°Ñ†ÐºÐ¸Ðµ напиÑали ÑатиричеÑкое «продолжение» данного Ð¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´ названием «Второе нашеÑтвие марÑиан». + +Ð’ 1917—1964 годах вышло одиннадцать книг о БарÑуме. Так называлаÑÑŒ планета ÐœÐ°Ñ€Ñ Ð² фантаÑтичеÑком мире, Ñозданном Ðдгаром РайÑом Берроузом. Ð’ его произведениÑÑ… планета была предÑтавлена как умирающаÑ, жители которой находÑÑ‚ÑÑ Ð² непрерывной войне вÑех Ñо вÑеми за Ñкудные природные реÑурÑÑ‹. Ð’ 1938 году К. Ð›ÑŒÑŽÐ¸Ñ Ð½Ð°Ð¿Ð¸Ñал роман «За пределы безмолвной планеты». + +Ð’ чиÑле важных произведений о МарÑе также Ñтоит отметить вышедший в 1950 году роман Ð ÑÑ Ð‘Ñ€Ñдбери «МарÑианÑкие хроники», ÑоÑтоÑщий из отдельных Ñлабо ÑвÑзанных между Ñобой новелл, а также Ñ€Ñд примыкающих к Ñтому циклу раÑÑказов; роман повеÑтвует об Ñтапах оÑÐ²Ð¾ÐµÐ½Ð¸Ñ Ñ‡ÐµÐ»Ð¾Ð²ÐµÐºÐ¾Ð¼ МарÑа и контактах Ñ Ð³Ð¸Ð±Ð½ÑƒÑ‰ÐµÐ¹ древней марÑианÑкой цивилизацией. + +Ð’ вымышленной вÑеленной Warhammer 40,000 ÐœÐ°Ñ€Ñ ÑвлÑетÑÑ Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹ цитаделью Adeptus Mechanicus, первым из миров-кузниц. Фабрики МарÑа, покрывающие вÑÑŽ поверхноÑÑ‚ÑŒ планеты, круглоÑуточно выпуÑкают оружие и боевую технику Ð´Ð»Ñ Ð±ÑƒÑˆÑƒÑŽÑ‰ÐµÐ¹ в Галактике войны. + +Примечательно, что Джонатан Свифт упомÑнул о Ñпутниках МарÑа за 150 лет до того, как они были реально открыты, в 19-й чаÑти Ñвоего романа «ПутешеÑÑ‚Ð²Ð¸Ñ Ð“ÑƒÐ»Ð»Ð¸Ð²ÐµÑ€Ð°Â»[155]. diff --git a/xpcom/tests/gtest/wikipedia/th.txt b/xpcom/tests/gtest/wikipedia/th.txt new file mode 100644 index 0000000000..3341946a13 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/th.txt @@ -0,0 +1,412 @@ +ดาวà¸à¸±à¸‡à¸„าร (à¸à¸±à¸‡à¸à¸¤à¸©: Mars) เป็นดาวเคราะห์ลำดับที่สี่จาà¸à¸”วงà¸à¸²à¸—ิตย์ เป็นดาวเคราะห์เล็à¸à¸—ี่สุดà¸à¸±à¸™à¸”ับที่สà¸à¸‡à¹ƒà¸™à¸£à¸°à¸šà¸šà¸ªà¸¸à¸£à¸´à¸¢à¸°à¸£à¸à¸‡à¸ˆà¸²à¸à¸”าวพุธ ในภาษาà¸à¸±à¸‡à¸à¸¤à¸©à¹„ด้ชื่à¸à¸•à¸²à¸¡à¹€à¸—พเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามขà¸à¸‡à¹‚รมัน มัà¸à¹„ด้รับขนานนาม "ดาวà¹à¸”ง" เพราะมีà¸à¸à¸à¹„ซด์ขà¸à¸‡à¹€à¸«à¸¥à¹‡à¸à¸”าษดื่นบนพื้นผิวทำให้มีสีà¸à¸à¸à¹à¸”งเรื่à¸[15] ดาวà¸à¸±à¸‡à¸„ารเป็นดาวเคราะห์หินที่มีบรรยาà¸à¸²à¸¨à¹€à¸šà¸²à¸šà¸²à¸‡ มีลัà¸à¸©à¸“ะพื้นผิวคล้ายคลึงà¸à¸±à¸šà¸—ั้งหลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”วงจันทร์ à¹à¸¥à¸°à¸ ูเขาไฟ หุบเขา ทะเลทราย ตลà¸à¸”จนพิดน้ำà¹à¸‚็งขั้วดาวที่ปราà¸à¸à¸šà¸™à¹‚ลภคาบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸à¸šà¸•à¸±à¸§à¹€à¸à¸‡à¹à¸¥à¸°à¸§à¸±à¸à¸ˆà¸±à¸à¸£à¸¤à¸”ูà¸à¸²à¸¥à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารà¸à¹‡à¸¡à¸µà¸„วามคล้ายคลึงà¸à¸±à¸šà¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸„วามเà¸à¸µà¸¢à¸‡à¸à¹ˆà¸à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”ฤดูà¸à¸²à¸¥à¸•à¹ˆà¸²à¸‡ ๆ ดาวà¸à¸±à¸‡à¸„ารเป็นที่ตั้งขà¸à¸‡à¹‚à¸à¸¥à¸´à¸¡à¸›à¸±à¸ªà¸¡à¸à¸™à¸ªà¹Œ ภูเขาไฟใหà¸à¹ˆà¸—ี่สุดบนดาวà¸à¸±à¸‡à¸„ารà¹à¸¥à¸°à¸ªà¸¹à¸‡à¸ªà¸¸à¸”à¸à¸±à¸™à¸”ับสà¸à¸‡à¹ƒà¸™à¸£à¸°à¸šà¸šà¸ªà¸¸à¸£à¸´à¸¢à¸°à¹€à¸—่าที่มีà¸à¸²à¸£à¸„้นพบ à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸—ี่ตั้งขà¸à¸‡à¹€à¸§à¸¥à¸ªà¹Œà¸¡à¸²à¸£à¸´à¹€à¸™à¸£à¸´à¸ª à¹à¸„นยà¸à¸™à¸‚นาดใหà¸à¹ˆà¸à¸±à¸™à¸”ับต้น ๆ ในระบบสุริยะ à¹à¸à¹ˆà¸‡à¸šà¸à¹€à¸£à¸µà¸¢à¸¥à¸´à¸ªà¸—ี่ราบเรียบในซีà¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวปà¸à¸„ลุมà¸à¸§à¹ˆà¸²à¸£à¹‰à¸à¸¢à¸¥à¸° 40 ขà¸à¸‡à¸žà¸·à¹‰à¸™à¸—ี่ทั้งหมดà¹à¸¥à¸°à¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸¥à¸±à¸à¸©à¸“ะà¸à¸²à¸£à¸–ูà¸à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸Šà¸™à¸„รั้งใหà¸à¹ˆ[16][17] ดาวà¸à¸±à¸‡à¸„ารมีดาวบริวารสà¸à¸‡à¸”วง คืภโฟบà¸à¸ªà¹à¸¥à¸°à¸”ีมà¸à¸ªà¸‹à¸¶à¹ˆà¸‡à¸•à¹ˆà¸²à¸‡à¸à¹‡à¸¡à¸µà¸‚นาดเล็à¸à¹à¸¥à¸°à¸¡à¸µà¸£à¸¹à¸›à¸£à¹ˆà¸²à¸‡à¸šà¸´à¸”เบี้ยว ทั้งคู่à¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸”าวเคราะห์น้à¸à¸¢à¸—ี่ถูà¸à¸ˆà¸±à¸šà¹„ว้[18][19] คล้ายà¸à¸±à¸šà¸—รà¸à¸¢à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร เช่น 5261 ยูเรà¸à¸² + +à¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²à¸à¸²à¸£à¸šà¸´à¸™à¸œà¹ˆà¸²à¸™à¸”าวà¸à¸±à¸‡à¸„ารที่สำเร็จครั้งà¹à¸£à¸à¸‚à¸à¸‡ มาริเนà¸à¸£à¹Œ 4 เมื่ภ1965 หลายคนคาดว่ามีน้ำในรูปขà¸à¸‡à¹€à¸«à¸¥à¸§à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวà¸à¸±à¸‡à¸„าร à¹à¸™à¸§à¸„ิดนี้à¸à¸²à¸¨à¸±à¸¢à¸œà¸¥à¸•à¹ˆà¸²à¸‡à¹€à¸›à¹‡à¸™à¸„าบที่สังเà¸à¸•à¹„ด้ขà¸à¸‡à¸£à¸à¸¢à¸¡à¸·à¸”à¹à¸¥à¸°à¸£à¸à¸¢à¸ªà¸§à¹ˆà¸²à¸‡ โดยเฉพาะในละติจูดขั้วดาวซึ่งดูเป็นทะเลà¹à¸¥à¸°à¸—วีป บางคนà¹à¸›à¸¥à¸„วามรà¸à¸¢à¸¡à¸·à¸”ริ้วลายขนานเป็นร่à¸à¸‡à¸—ดน้ำสำหรับน้ำในรูปขà¸à¸‡à¹€à¸«à¸¥à¸§ ภายหลัง มีà¸à¸²à¸£à¸à¸˜à¸´à¸šà¸²à¸¢à¸§à¹ˆà¸²à¸ ูมิประเทศเส้นตรงเหล่านั้นเป็นภาพลวงตา à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸—างธรณีวิทยาที่ภารà¸à¸´à¸ˆà¹„ร้คนบังคับรวบรวมชี้ว่า ครั้งหนึ่งดาวà¸à¸±à¸‡à¸„ารเคยมีน้ำปริมาณมาà¸à¸›à¸à¸„ลุมบนพื้นผิว ณ ช่วงใดช่วงหนึ่งในระยะต้น ๆ ขà¸à¸‡à¸à¸²à¸¢à¸¸[20] ในปี 2005 เรดาร์เผยว่ามีน้ำà¹à¸‚็งน้ำ (water ice) ปริมาณมาà¸à¸‚ั้วทั้งสà¸à¸‡à¸‚à¸à¸‡à¸”าว[21] à¹à¸¥à¸°à¸—ี่ละติจูดà¸à¸¥à¸²à¸‡[22][23] ยานสำรวจภาคพื้นดาวà¸à¸±à¸‡à¸„ารสปิริต พบตัวà¸à¸¢à¹ˆà¸²à¸‡à¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸à¸šà¹€à¸„มีที่มีโมเลà¸à¸¸à¸¥à¸™à¹‰à¸³à¹€à¸¡à¸·à¹ˆà¸à¹€à¸”ืà¸à¸™à¸¡à¸µà¸™à¸²à¸„ม 2007 ส่วนลงจà¸à¸”ฟีนิà¸à¸‹à¹Œ พบตัวà¸à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸³à¹à¸‚็งน้ำโดยตรงในดินส่วนตื้นขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเมื่à¸à¸§à¸±à¸™à¸—ี่ 31 à¸à¸£à¸à¸Žà¸²à¸„ม 2008[24] + +มียานà¸à¸§à¸à¸²à¸¨à¸—ี่à¸à¸³à¸¥à¸±à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸‡à¸²à¸™à¸à¸¢à¸¹à¹ˆà¹€à¸ˆà¹‡à¸”ลำ ห้าลำà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸§à¸‡à¹‚คจร ได้à¹à¸à¹ˆ 2001 มาร์สโà¸à¸”ิสซี มาร์สเà¸à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª มาร์สรีคà¸à¸™à¹€à¸™à¸ªà¹€à¸‹à¸™à¸‹à¹Œà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œ เมเว็น à¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¸¡à¸´à¸Šà¸Šà¸±à¸™ à¹à¸¥à¸°à¸ªà¸à¸‡à¸¥à¸³à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ ได้à¹à¸à¹ˆ ยานสำรวจภาคพื้นดาวà¸à¸±à¸‡à¸„ารà¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตี à¹à¸¥à¸°à¸¢à¸²à¸™à¸¡à¸²à¸£à¹Œà¸ªà¹„ซà¹à¸à¸™à¸‹à¹Œà¹à¸¥à¸šà¸à¸£à¸²à¸—à¸à¸£à¸µà¸„ิวริà¸à¸à¸‹à¸´à¸•à¸µ à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹‚ดย มาร์สรีคà¸à¸™à¹€à¸™à¸ªà¹€à¸‹à¸™à¸‹à¹Œà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œ เปิดเผยว่ามีความเป็นไปได้ที่จะมีน้ำไหลในช่วงเดืà¸à¸™à¸—ี่ร้à¸à¸™à¸—ี่สุดบนดาวà¸à¸±à¸‡à¸„าร[25] ในปี 2013 ยานคิวริà¸à¸à¸‹à¸´à¸•à¸µ ขà¸à¸‡à¸™à¸²à¸‹à¸²à¸„้นพบว่าดินขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีน้ำเป็นà¸à¸‡à¸„์ประà¸à¸à¸šà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸£à¹‰à¸à¸¢à¸¥à¸° 1.5 ถึง 3 โดยมวล à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸™à¹‰à¸³à¸™à¸±à¹‰à¸™à¸ˆà¸°à¸•à¸´à¸”à¸à¸¢à¸¹à¹ˆà¸à¸±à¸šà¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸à¸šà¸à¸·à¹ˆà¸™ ทำให้ไม่สามารถเข้าถึงได้โดยà¸à¸´à¸ªà¸£à¸°[26] + +à¸à¸³à¸¥à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¸ªà¸·à¸šà¸„้นเพื่à¸à¸›à¸£à¸°à¹€à¸¡à¸´à¸™à¸¨à¸±à¸à¸¢à¸ าพความสามารถà¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¹„ด้ในà¸à¸”ีตขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ตลà¸à¸”จนความเป็นไปได้ที่จะมีสิ่งมีชีวิตหลงเหลืà¸à¸à¸¢à¸¹à¹ˆ มีà¸à¸²à¸£à¸ªà¸·à¸šà¸„้นบริเวณนั้นโดยส่วนลงจà¸à¸” ไวà¸à¸´à¸‡ โรเวà¸à¸£à¹Œ สปิริต à¹à¸¥à¸°à¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตี ส่วนลงจà¸à¸”ฟีนิà¸à¸‹à¹Œ à¹à¸¥à¸°à¹‚รเวà¸à¸£à¹Œ คิวริà¸à¸à¸‹à¸´à¸•à¸µ[27][28] มีà¸à¸²à¸£à¸§à¸²à¸‡à¹à¸œà¸™à¸ ารà¸à¸´à¸ˆà¸—างชีวดาราศาสตร์ไว้à¹à¸¥à¹‰à¸§ ซึ่งรวม มาร์ส 2020 à¹à¸¥à¸°à¹€à¸à¹‡à¸à¹‚ซมาร์สโรเวà¸à¸£à¹Œ [29][30] + +ดาวà¸à¸±à¸‡à¸„ารสามารถมà¸à¸‡à¹€à¸«à¹‡à¸™à¹„ด้ด้วยตาเปล่าจาà¸à¹‚ลà¸à¹‚ดยง่ายซึ่งจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¹€à¸›à¹‡à¸™à¸ªà¸µà¸à¸à¸à¹à¸”ง มีความส่à¸à¸‡à¸ªà¸§à¹ˆà¸²à¸‡à¸›à¸£à¸²à¸à¸à¹„ด้ถึง −2.91[6] ซึ่งเป็นรà¸à¸‡à¹€à¸žà¸µà¸¢à¸‡à¸”าวพฤหัสบดี ดาวศุà¸à¸£à¹Œ ดวงจันทร์ à¹à¸¥à¸°à¸”วงà¸à¸²à¸—ิตย์ à¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ภาคพื้นดินโดยทั่วไปมีขีดจำà¸à¸±à¸”à¸à¸²à¸£à¸¡à¸à¸‡à¹€à¸«à¹‡à¸™à¸£à¸²à¸¢à¸¥à¸°à¹€à¸à¸µà¸¢à¸”ขà¸à¸‡à¸ ูมิประเทศขนาดประมาณ 300 à¸à¸´à¹‚ลเมตรเมื่à¸à¹‚ลà¸à¹à¸¥à¸°à¸”าวà¸à¸±à¸‡à¸„ารเข้าใà¸à¸¥à¹‰à¸à¸±à¸™à¸¡à¸²à¸à¸—ี่สุดà¸à¸±à¸™à¹€à¸›à¹‡à¸™à¸œà¸¥à¸ˆà¸²à¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚à¸à¸‡à¹‚ลà¸[31] + +ลัà¸à¸©à¸“ะทางà¸à¸²à¸¢à¸ าพ[à¹à¸à¹‰] +โลà¸à¹€à¸—ียบà¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„ารโลà¸à¹€à¸—ียบà¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„าร +ไฟล์:Mars.ogvPlay media +ภาพเคลื่à¸à¸™à¹„หว (00:40) à¹à¸ªà¸”งภูมิประเทศสำคัภ+ไฟล์:GMM-3 Mars Gravity.webmPlay media +วีดีโภ(01:28) à¹à¸ªà¸”งให้เห็นสนามà¹à¸£à¸‡à¹‚น้มถ่วงขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร. + +ดาวà¸à¸±à¸‡à¸„ารมีขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ครึ่งหนึ่งขà¸à¸‡à¹‚ลภà¹à¸¥à¸°à¸¡à¸µà¸žà¸·à¹‰à¸™à¸—ี่ผิวน้à¸à¸¢à¸à¸§à¹ˆà¸²à¸žà¸·à¹‰à¸™à¸—ี่ผิวดินทั้งหมดขà¸à¸‡à¹‚ลà¸à¸£à¸§à¸¡à¸à¸±à¸™à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸à¸¢[6] ดาวà¸à¸±à¸‡à¸„ารมีความหนาà¹à¸™à¹ˆà¸™à¸™à¹‰à¸à¸¢à¸à¸§à¹ˆà¸²à¹‚ลภมีปริมาตรประมาณร้à¸à¸¢à¸¥à¸° 15 ขà¸à¸‡à¹‚ลภà¹à¸¥à¸°à¸¡à¸µà¸¡à¸§à¸¥à¸›à¸£à¸°à¸¡à¸²à¸“ร้à¸à¸¢à¸¥à¸° 11 ขà¸à¸‡à¸¡à¸§à¸¥à¸‚à¸à¸‡à¹‚ลภถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารจะมีขนาดใหà¸à¹ˆà¸à¸§à¹ˆà¸²à¹à¸¥à¸°à¸¡à¸µà¸¡à¸§à¸¥à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸”าวพุธà¸à¹‡à¸•à¸²à¸¡ à¹à¸•à¹ˆà¸”าวพุธมีความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸² เป็นผลให้à¹à¸£à¸‡à¹‚น้มถ่วงบริเวณพื้นผิวดาวเคราะห์ทั้งสà¸à¸‡à¸™à¸±à¹‰à¸™à¹à¸—บจะเท่าà¸à¸±à¸™ โดยดาวà¸à¸±à¸‡à¸„ารมีà¹à¸£à¸‡à¸”ึงโน้มถ่วงสูงà¸à¸§à¹ˆà¸²à¹€à¸žà¸µà¸¢à¸‡à¹„ม่ถึงร้à¸à¸¢à¸¥à¸°à¸«à¸™à¸¶à¹ˆà¸‡ ลัà¸à¸©à¸“ะปราà¸à¸à¸ªà¸µà¹à¸”งปนส้มขà¸à¸‡à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวà¸à¸±à¸‡à¸„ารมีสาเหตุมาจาà¸à¹„à¸à¹€à¸à¸´à¸£à¹Œà¸™(III) à¸à¸à¸à¹„ซด์ รู้จัà¸à¸à¸±à¸™à¹ƒà¸™à¸Šà¸·à¹ˆà¸à¸ªà¸²à¸¡à¸±à¸à¸„ืà¸à¸®à¸µà¸¡à¸²à¹„ทต์หรืà¸à¸ªà¸™à¸´à¸¡à¹€à¸«à¸¥à¹‡à¸[32] à¸à¸²à¸ˆà¸¡à¸à¸‡à¹€à¸«à¹‡à¸™à¸„ล้ายà¸à¸±à¸šà¸šà¸±à¸•à¹€à¸•à¸à¸£à¹Œà¸ªà¸à¸à¸•à¸Šà¹Œ[33] à¹à¸¥à¸°à¸ªà¸µà¸à¸·à¹ˆà¸™ ๆ ที่ปราà¸à¸à¸—ั่วไปตามพื้นผิวนั้นมีได้ทั้งสีทà¸à¸‡ สีน้ำตาล สีน้ำตาลà¸à¹ˆà¸à¸™ หรืà¸à¸ªà¸µà¸à¸à¸à¹€à¸‚ียวขึ้นà¸à¸¢à¸¹à¹ˆà¸à¸±à¸šà¹à¸£à¹ˆà¸à¸‡à¸„์ประà¸à¸à¸š[33] +โครงสร้างภายใน[à¹à¸à¹‰] + +เช่นเดียวà¸à¸±à¸™à¸à¸±à¸šà¹‚ลภดาวà¸à¸±à¸‡à¸„ารมีà¸à¸²à¸£à¹à¸¢à¸à¸Šà¸±à¹‰à¸™à¸à¸‡à¸„์ประà¸à¸à¸šà¸à¸à¸à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¹à¸à¹ˆà¸™à¹‚ลหะความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸‹à¸¶à¹ˆà¸‡à¸–ูà¸à¸«à¹ˆà¸à¸«à¸¸à¹‰à¸¡à¸à¸¢à¸¹à¹ˆà¸ ายใต้ส่วนประà¸à¸à¸šà¸à¸·à¹ˆà¸™ ๆ ที่มีความหนาà¹à¸™à¹ˆà¸™à¸™à¹‰à¸à¸¢à¸à¸§à¹ˆà¸²[34] à¹à¸šà¸šà¸ˆà¸³à¸¥à¸à¸‡à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸‚à¸à¸‡à¹‚ครงสร้างภายในà¹à¸ªà¸”งรัศมีà¸à¸²à¸“าบริเวณขà¸à¸‡à¹à¸à¹ˆà¸™à¸”าวà¸à¸¢à¸¹à¹ˆà¸—ี่ประมาณ 1,794±65 à¸à¸´à¹‚ลเมตร (1,115±40 ไมล์) มีà¸à¸‡à¸„์ประà¸à¸à¸šà¸«à¸¥à¸±à¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸™à¸´à¸à¹€à¸à¸´à¸¥ โดยมีà¸à¸³à¸¡à¸°à¸–ันรวมà¸à¸¢à¸¹à¹ˆà¸”้วยประมาณร้à¸à¸¢à¸¥à¸° 16-17[35] คาดว่าà¹à¸à¹ˆà¸™à¹„à¸à¹€à¸à¸´à¸£à¹Œà¸™(II) ซัลไฟด์นั้นมีธาตุเบาเป็นà¸à¸‡à¸„์ประà¸à¸à¸šà¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹à¸à¹ˆà¸™à¸‚à¸à¸‡à¹‚ลà¸à¸–ึงสà¸à¸‡à¹€à¸—่า[36] à¹à¸à¹ˆà¸™à¸”าวล้à¸à¸¡à¸£à¸à¸šà¹„ปด้วยเนื้à¸à¸”าวซิลิเà¸à¸•à¸‹à¸¶à¹ˆà¸‡à¸›à¸£à¸°à¸à¸à¸šà¸‚ึ้นเป็นโครงสร้างทางธรณีสัณà¸à¸²à¸™à¹à¸¥à¸°à¸ ูเขาไฟต่าง ๆ บนดาวเคราะห์ซึ่งในปัจจุบันเหมืà¸à¸™à¸ˆà¸°à¸ªà¸‡à¸šà¸™à¸´à¹ˆà¸‡ นà¸à¸à¹€à¸«à¸™à¸·à¸à¸ˆà¸²à¸à¸‹à¸´à¸¥à¸´à¸à¸à¸™à¹à¸¥à¸°à¸à¸à¸à¸‹à¸´à¹€à¸ˆà¸™ ธาตุที่มีมาà¸à¸—ี่สุดในเปลืà¸à¸à¸œà¸´à¸§à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารได้à¹à¸à¹ˆ เหล็ภà¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ à¸à¸°à¸¥à¸¹à¸¡à¸´à¹€à¸™à¸µà¸¢à¸¡ à¹à¸„ลเซียม à¹à¸¥à¸°à¹‚พà¹à¸—สเซียม ความหนาเฉลี่ยขà¸à¸‡à¹€à¸›à¸¥à¸·à¸à¸à¸”าวà¸à¸¢à¸¹à¹ˆà¸—ี่ประมาณ 50 à¸à¸´à¹‚ลเมตร (31 ไมล์) มีความหนาสูงสุดที่ประมาณ 125 à¸à¸´à¹‚ลเมตร (78 ไมล์)[36] เปลืà¸à¸à¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸¡à¸µà¸„วามหนาเฉลี่ย 40 à¸à¸´à¹‚ลเมตร (25 ไมล์) ถืà¸à¸§à¹ˆà¸²à¸¡à¸µà¸„วามหนาเพียงหนึ่งในสามขà¸à¸‡à¹€à¸›à¸¥à¸·à¸à¸à¸”าวà¸à¸±à¸‡à¸„ารเมื่à¸à¹€à¸›à¸£à¸µà¸¢à¸šà¸ªà¸±à¸¡à¸žà¸±à¸—ธ์à¸à¸±à¸šà¸‚นาดขà¸à¸‡à¸”าวเคราะห์ทั้งคู่ ยานส่วนลงจà¸à¸”à¸à¸´à¸™à¹„ซต์ตามà¹à¸œà¸™à¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¹ƒà¸™à¸›à¸µ 2016 (พ.ศ. 2559) จะมีà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸„รื่à¸à¸‡à¸¡à¸·à¸à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”ความไหวสะเทืà¸à¸™à¹€à¸žà¸·à¹ˆà¸à¹ƒà¸«à¹‰à¹„ด้à¹à¸šà¸šà¸ˆà¸³à¸¥à¸à¸‡à¹‚ครงสร้างภายในดาวที่ชัดเจนมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้น[37] +ธรณีวิทยาพื้นผิว[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ธรณีวิทยาดาวà¸à¸±à¸‡à¸„าร + +ดาวà¸à¸±à¸‡à¸„ารเป็นดาวเคราะห์หินประà¸à¸à¸šà¸‚ึ้นจาà¸à¹à¸£à¹ˆà¸Šà¸™à¸´à¸”ต่าง ๆ ที่มีซิลิà¸à¸à¸™ à¸à¸à¸à¸‹à¸´à¹€à¸ˆà¸™ โลหะ ตลà¸à¸”จนธาตุà¸à¸·à¹ˆà¸™ ๆ à¸à¸µà¸à¸«à¸¥à¸²à¸¢à¸Šà¸™à¸´à¸”เป็นà¸à¸‡à¸„์ประà¸à¸à¸šà¸£à¸§à¸¡à¸à¸±à¸™à¹€à¸‚้าเป็นหิน พื้นผิวขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีหินบะซà¸à¸¥à¸•à¹Œà¸Šà¸™à¸´à¸”โทเลà¸à¸´à¸—ิà¸à¹€à¸›à¹‡à¸™à¸à¸‡à¸„์ประà¸à¸à¸šà¸«à¸¥à¸±à¸[38] à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸«à¸¥à¸²à¸¢à¸ªà¹ˆà¸§à¸™à¹€à¸›à¹‡à¸™à¸«à¸´à¸™à¸Šà¸™à¸´à¸”ที่มีซิลิà¸à¸²à¸ªà¸¹à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸«à¸´à¸™à¸šà¸°à¸‹à¸à¸¥à¸•à¹Œà¸—ั่วไปà¹à¸¥à¸°à¸à¸²à¸ˆà¸¡à¸µà¸„วามคล้ายคลึงà¸à¸±à¸šà¸«à¸´à¸™à¹à¸à¸™à¸”ีไซต์บนโลà¸à¸«à¸£à¸·à¸à¹à¸à¹‰à¸§à¸‹à¸´à¸¥à¸´à¹€à¸à¸• ภูมิภาคที่มีà¸à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸ªà¸°à¸—้à¸à¸™à¸•à¹ˆà¸³à¹à¸ªà¸”งà¸à¸²à¸£à¸¡à¸µà¹€à¸Ÿà¸¥à¸”์สปาร์à¸à¸¥à¸¸à¹ˆà¸¡à¹€à¸žà¸¥à¸ˆà¸´à¹‚à¸à¹€à¸„ลสหนาà¹à¸™à¹ˆà¸™ ในขณะที่ภูมิภาคที่มีà¸à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸ªà¸°à¸—้à¸à¸™à¸•à¹ˆà¸³à¸—างตà¸à¸™à¹€à¸«à¸™à¸·à¸à¹€à¸œà¸¢à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸à¸²à¸£à¸¡à¸µà¹à¸œà¹ˆà¸™à¸‹à¸´à¸¥à¸´à¹€à¸à¸•à¹à¸¥à¸°à¹à¸à¹‰à¸§à¸Šà¸™à¸´à¸”ที่มีซิลิà¸à¸à¸™à¸ªà¸¹à¸‡à¸”้วยความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²à¸›à¸à¸•à¸´ ในหลายส่วนขà¸à¸‡à¸ ูมิภาคที่ราบสูงตà¸à¸™à¹ƒà¸•à¹‰à¸•à¸£à¸§à¸ˆà¸žà¸šà¹„พรà¸à¸à¸‹à¸µà¸™à¸Šà¸™à¸´à¸”à¹à¸„ลเซียมสูงรวมà¸à¸¢à¸¹à¹ˆà¹€à¸›à¹‡à¸™à¸›à¸£à¸´à¸¡à¸²à¸“มาภนà¸à¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¸žà¸šà¸®à¸µà¸¡à¸²à¹„ทต์à¹à¸¥à¸°à¹‚à¸à¸¥à¸´à¸§à¸µà¸™à¸«à¸™à¸²à¹à¸™à¹ˆà¸™à¹ƒà¸™à¸ ูมิภาคจำเพาะบางà¹à¸«à¹ˆà¸‡[39] พื้นที่ผิวส่วนใหà¸à¹ˆà¸–ูà¸à¸›à¸à¸„ลุมด้วยชั้นหนาขà¸à¸‡à¹€à¸¡à¹‡à¸”à¸à¸¸à¹ˆà¸™à¹„à¸à¹€à¸à¸´à¸£à¹Œà¸™(III) à¸à¸à¸à¹„ซด์ละเà¸à¸µà¸¢à¸”[40][41] +à¹à¸œà¸™à¸—ี่ธรณีวิทยาขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร (USGS; 14 à¸à¸£à¸à¸Žà¸²à¸„ม 2014) (à¹à¸œà¸™à¸—ี่เต็ม / วิดีโà¸)[42][43][44] + +ถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารจะไม่มีหลัà¸à¸à¸²à¸™à¸‚à¸à¸‡à¹‚ครงสร้างสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸£à¸°à¸”ับครà¸à¸šà¸„ลุมทั่วทั้งดาวในปัจจุบัน[45] à¹à¸•à¹ˆà¸œà¸¥à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹à¸ªà¸”งให้ทราบว่าหลายส่วนขà¸à¸‡à¹€à¸›à¸¥à¸·à¸à¸à¸”าวถูà¸à¸à¸£à¸°à¸—ำด้วยà¸à¸³à¸™à¸²à¸ˆà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸à¸²à¸£à¸žà¸¥à¸´à¸à¸œà¸±à¸™à¸ªà¸¥à¸±à¸šà¸‚ั้วขà¸à¸‡à¸ªà¸™à¸²à¸¡à¹„ดโพลเคยปราà¸à¸à¸¡à¸²à¹à¸¥à¹‰à¸§à¹ƒà¸™à¸à¸”ีต เพราะในทางบรรพวิทยาà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸ à¹à¸£à¹ˆà¸—ี่มีความไวต่à¸à¹à¸£à¸‡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸™à¸±à¹‰à¸™à¸¢à¹ˆà¸à¸¡à¹à¸ªà¸”งคุณสมบัติเช่นเดียวà¸à¸±à¸™à¸à¸±à¸šà¹à¸–บสลับที่พบบนพื้นมหาสมุทรขà¸à¸‡à¹‚ลภทฤษฎีหนึ่งที่มีà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œà¹ƒà¸™à¸›à¸µ 1999 (พ.ศ. 2542) à¹à¸¥à¸°à¸¡à¸µà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸ªà¸à¸šà¸à¸µà¸à¸„รั้งในเดืà¸à¸™à¸•à¸¸à¸¥à¸²à¸„ม ปี 2005 (พ.ศ. 2548) (โดยà¸à¸²à¸¨à¸±à¸¢à¸‚้à¸à¸¡à¸¹à¸¥à¸ˆà¸²à¸à¸¡à¸²à¸£à¹Œà¸ªà¹‚à¸à¸¥à¸šà¸à¸¥à¹€à¸‹à¸à¸£à¹Œà¹€à¸§à¹€à¸¢à¸à¸£à¹Œ) ชี้ว่าà¹à¸™à¸§à¹à¸–บต่าง ๆ ที่เà¸à¸´à¸”ขึ้นà¹à¸ªà¸”งถึงà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸à¸²à¸£à¹à¸›à¸£à¸ªà¸±à¸“à¸à¸²à¸™à¹à¸œà¹ˆà¸™à¸˜à¸£à¸“ีภาคบนดาวà¸à¸±à¸‡à¸„ารเมื่à¸à¹€à¸§à¸¥à¸²à¸à¸§à¹ˆà¸²à¸ªà¸µà¹ˆà¸žà¸±à¸™à¸¥à¹‰à¸²à¸™à¸›à¸µà¸à¹ˆà¸à¸™ à¸à¹ˆà¸à¸™à¸—ี่ไดนาโมขà¸à¸‡à¸”าวเคราะห์จะหยุดลงเป็นผลให้สนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸‚à¸à¸‡à¸”าวจางหายไป[46] + +ในช่วงà¸à¸²à¸£à¸à¹ˆà¸à¸à¸³à¹€à¸™à¸´à¸”ระบบสุริยะ ดาวà¸à¸±à¸‡à¸„ารได้ถืà¸à¸à¸³à¹€à¸™à¸´à¸”ขึ้นจาà¸à¸œà¸¥à¸‚à¸à¸‡à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸ªà¸¸à¹ˆà¸¡à¸‚à¸à¸‡à¸¡à¸§à¸¥à¸—ี่พà¸à¸à¸žà¸¹à¸™à¸‚ึ้นà¹à¸¢à¸à¸à¸à¸à¸ˆà¸²à¸à¸ˆà¸²à¸™à¸”าวเคราะห์à¸à¹ˆà¸à¸™à¹€à¸à¸´à¸”ที่โคจรรà¸à¸šà¸”วงà¸à¸²à¸—ิตย์ ดาวà¸à¸±à¸‡à¸„ารจึงมีคุณลัà¸à¸©à¸“ะทางเคมีที่จำเพาะพิเศษหลายประà¸à¸²à¸£à¸•à¸²à¸¡à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¹ƒà¸™à¸£à¸°à¸šà¸šà¸ªà¸¸à¸£à¸´à¸¢à¸° ธาตุต่าง ๆ ที่มีจุดเดืà¸à¸”ค่à¸à¸™à¸‚้างต่ำตัวà¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™à¸„ลà¸à¸£à¸µà¸™ ฟà¸à¸ªà¸Ÿà¸à¸£à¸±à¸ª à¹à¸¥à¸°à¸à¸³à¸¡à¸°à¸–ัน จะพบเป็นปà¸à¸•à¸´à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารในระดับที่มาà¸à¸à¸§à¹ˆà¸²à¹‚ลภเป็นไปได้ว่าธาตุเหล่านี้ถูà¸à¸‚ับà¸à¸à¸à¸¡à¸²à¸ˆà¸²à¸à¸šà¸£à¸´à¹€à¸§à¸“ใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์โดยลมสุริยะà¸à¸±à¸™à¸—รงพลังในช่วงต้นขà¸à¸‡à¸à¸²à¸¢à¸¸à¸‚ัย[47] + +หลังà¸à¸²à¸£à¸à¹ˆà¸à¸à¸³à¹€à¸™à¸´à¸”ดาวเคราะห์à¹à¸¥à¹‰à¸§ ทั้งหมดล้วนตà¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸¢à¸·à¹ˆà¸à¸‚à¸à¸‡ "à¸à¸²à¸£à¸£à¸°à¸”มชนหนัà¸à¸„รั้งสุดท้าย" à¸à¸§à¹ˆà¸²à¸£à¹‰à¸à¸¢à¸¥à¸° 60 ขà¸à¸‡à¸žà¸·à¹‰à¸™à¸—ี่ผิวดาวà¸à¸±à¸‡à¸„ารà¹à¸ªà¸”งบันทึà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์à¸à¸²à¸£à¸£à¸°à¸”มชนจาà¸à¸¢à¸¸à¸„นั้น[48][49][50] ในขณะที่เป็นไปได้ว่าพื้นที่ผิวส่วนที่เหลืà¸à¸à¸µà¸à¸¡à¸²à¸à¸¡à¸²à¸¢à¸§à¸²à¸‡à¸•à¸±à¸§à¸à¸¢à¸¹à¹ˆà¸ ายใต้à¹à¸à¹ˆà¸‡à¸‚นาดมโหฬารซึ่งà¸à¹‡à¹€à¸à¸´à¸”ขึ้นจาà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ดังà¸à¸¥à¹ˆà¸²à¸§ มีหลัà¸à¸à¸²à¸™à¸‚à¸à¸‡à¹à¸à¹ˆà¸‡à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดมหึมาในบริเวณซีà¸à¹‚ลà¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารซึ่งà¹à¸œà¹ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸£à¸²à¸§ 8,500 à¸à¸´à¹‚ลเมตร à¹à¸¥à¸°à¸¢à¸²à¸§à¸£à¹ˆà¸§à¸¡ 10,600 à¸à¸´à¹‚ลเมตร (5,300 x 6,600 ไมล์) หรืà¸à¸¡à¸µà¸‚นาดใหà¸à¹ˆà¹€à¸›à¹‡à¸™à¸ªà¸µà¹ˆà¹€à¸—่าขà¸à¸‡à¹à¸à¹ˆà¸‡à¹„à¸à¸•à¹Œà¹€à¸„็น-ขั้วใต้ขà¸à¸‡à¸”วงจันทร์ ทำให้เป็นà¹à¸à¹ˆà¸‡à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸—ี่มีขนาดใหà¸à¹ˆà¸—ี่สุดเท่าที่มีà¸à¸²à¸£à¸„้นพบ[16][17] ทฤษฎีนี้เสนà¸à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยวัตถุขนาดเท่าดาวพลูโตเมื่à¸à¸›à¸£à¸°à¸¡à¸²à¸“สี่พันล้านปีà¸à¹ˆà¸à¸™ à¹à¸¥à¸°à¸„าดว่าเหตุà¸à¸²à¸£à¸“์นี้เà¸à¸‡à¹€à¸›à¹‡à¸™à¸ªà¸²à¹€à¸«à¸•à¸¸à¸—ำให้ดาวà¸à¸±à¸‡à¸„ารมีซีà¸à¸”าวà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™à¹€à¸›à¹‡à¸™à¸ªà¸à¸‡à¸¥à¸±à¸à¸©à¸“ะà¸à¸¢à¹ˆà¸²à¸‡à¸Šà¸±à¸”เจน เà¸à¸´à¸”à¹à¸à¹ˆà¸‡à¸šà¸à¹€à¸£à¸µà¸¢à¸¥à¸´à¸ªà¸à¸±à¸™à¸£à¸²à¸šà¹€à¸£à¸µà¸¢à¸šà¸›à¸à¸„ลุมพื้นที่à¸à¸§à¹ˆà¸²à¸£à¹‰à¸à¸¢à¸¥à¸° 40 ทางซีà¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวเคราะห์[51][52] +ภาพรังสรรค์โดยศิลปินà¹à¸ªà¸”งภาพขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารว่าน่าจะเป็นà¸à¸¢à¹ˆà¸²à¸‡à¹„รเมื่à¸à¸ªà¸µà¹ˆà¸žà¸±à¸™à¸¥à¹‰à¸²à¸™à¸›à¸µà¸à¹ˆà¸à¸™[53] + +ประวัติศาสตร์ธรณีวิทยาขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารสามารถà¹à¸šà¹ˆà¸‡à¸à¸à¸à¹„ด้เป็นหลายช่วงเวลา à¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¸«à¸¥à¸±à¸à¹à¸¥à¹‰à¸§à¸ªà¸²à¸¡à¸²à¸£à¸–à¹à¸šà¹ˆà¸‡à¹„ด้เป็นสามยุคด้วยà¸à¸±à¸™[54][55] + + ยุคโนà¸à¸²à¹€à¸„ียน (ตั้งชื่à¸à¸•à¸²à¸¡ โนà¸à¸²à¸„ิสเทร์รา หรืà¸à¹à¸œà¹ˆà¸™à¸”ินขà¸à¸‡à¹‚นà¸à¸²à¸«à¹Œ): เป็นช่วงà¸à¸³à¹€à¸™à¸´à¸”พื้นผิวดาวà¸à¸±à¸‡à¸„ารที่เà¸à¹ˆà¸²à¹à¸à¹ˆà¸—ี่สุดเท่าที่ปราà¸à¸ à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¸›à¸£à¸°à¸¡à¸²à¸“ 4.5 พันล้านปีà¸à¹ˆà¸à¸™à¸ˆà¸™à¸–ึง 3.5 พันล้านปีที่ผ่านมา พื้นผิวยุคโนà¸à¸²à¹€à¸„ียนเต็มไปด้วยริ้วรà¸à¸¢à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดใหà¸à¹ˆà¸„รั้งà¹à¸¥à¹‰à¸§à¸„รั้งเล่า ส่วนโป่งธาร์ซิส ที่ราบสูงภูเขาไฟที่คาดว่าเà¸à¸´à¸”ขึ้นในระหว่างยุคนี้พร้à¸à¸¡à¸”้วยà¸à¸²à¸£à¸—่วมท้นà¸à¸¢à¹ˆà¸²à¸‡à¸à¸§à¹‰à¸²à¸‡à¸‚วางขà¸à¸‡à¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¸›à¸¥à¸²à¸¢à¸¢à¸¸à¸„ + ยุคเฮสเพียเรียน (ตั้งขื่à¸à¸•à¸²à¸¡ เฮสเพียเรียนเพลนัม หรืà¸à¸—ี่ราบสูงตะวันตà¸): ราว 3.5 พันล้านปีà¸à¹ˆà¸à¸™ จนถึงช่วงเวลาประมาณ 2.9 - 3.3 พันล้านปีที่ผ่านมา เป็นยุคที่มีรà¸à¸¢à¸›à¸£à¸²à¸à¸à¸Šà¸±à¸”เจนขà¸à¸‡à¸à¸²à¸£à¹€à¸à¸´à¸”ที่ราบลาวาขนาดใหà¸à¹ˆ + ยุคà¹à¸à¸¡à¸°à¹‚ซเนียน (ตั้งขื่à¸à¸•à¸²à¸¡ à¹à¸à¸¡à¸°à¹‚ซนิสเพลนิเชีย หรืà¸à¸—ี่ราบà¹à¸à¸¡à¸°à¸‹à¸à¸™): นับตั้งà¹à¸•à¹ˆ 2.9 - 3.3 พันล้านปีà¸à¹ˆà¸à¸™à¸ˆà¸™à¸–ึงปัจจุบัน พิ้นผิวยุคนี้มีหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸™à¹‰à¸à¸¢à¹à¸•à¹ˆà¸„่à¸à¸™à¸‚้างหลาà¸à¸«à¸¥à¸²à¸¢ ภูเขาไฟโà¸à¸¥à¸´à¸¡à¸›à¸±à¸ªà¹€à¸à¸´à¸”ขึ้นในยุคนี้ร่วมไปà¸à¸±à¸šà¸à¸²à¸£à¹„หลขà¸à¸‡à¸¥à¸²à¸§à¸²à¸à¸µà¸à¸«à¸¥à¸²à¸¢à¸—ี่บนดาวà¸à¸±à¸‡à¸„าร + +à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸—างธรณีวิทยาบางà¸à¸¢à¹ˆà¸²à¸‡à¸¢à¸±à¸‡à¸„งเà¸à¸´à¸”ขึ้นบนดาวà¸à¸±à¸‡à¸„าร ที่หุบเขาà¸à¸°à¸˜à¸²à¸šà¸²à¸ªà¸à¸²à¸¡à¸µà¸£à¹ˆà¸à¸‡à¸£à¸à¸¢à¸à¸²à¸£à¹„หลขà¸à¸‡à¸¥à¸²à¸§à¸²à¹ƒà¸™à¸¥à¸±à¸à¸©à¸“ะเป็นà¹à¸œà¹ˆà¸™à¸à¸²à¸¢à¸¸à¸à¸§à¹ˆà¸² 200 ล้านปี ปราà¸à¸à¸£à¹ˆà¸à¸‡à¸£à¸à¸¢à¸à¸²à¸£à¹„หลขà¸à¸‡à¸™à¹‰à¸³à¹ƒà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸—่ามà¸à¸¥à¸²à¸‡à¸£à¸à¸¢à¹€à¸¥à¸·à¹ˆà¸à¸™à¸‹à¸¶à¹ˆà¸‡à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸£à¹ˆà¸à¸‡à¹à¸¢à¸à¹€à¸‹à¸à¸£à¹Œà¹€à¸šà¸à¸£à¸±à¸ªà¸”้วยà¸à¸²à¸¢à¸¸à¸™à¹‰à¸à¸¢à¸à¸§à¹ˆà¸² 20 ล้านปี บ่งชี้ว่าเป็นà¸à¸²à¸£à¸žà¸¥à¸¸à¹ˆà¸‡à¸‚ึ้นขà¸à¸‡à¸ ูเขาไฟเมื่à¸à¹„ม่นานมานี้เช่นà¸à¸±à¸™[56] วันที่ 19 à¸à¸¸à¸¡à¸ าพันธ์ 2008 (พ.ศ. 2551) ภาพจาà¸à¸¢à¸²à¸™à¸¡à¸²à¸£à¹Œà¸ªà¸£à¸µà¸„à¸à¸™à¹€à¸™à¸ªà¹€à¸‹à¸™à¸‹à¹Œà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¹à¸ªà¸”งให้เห็นหลัà¸à¸à¸²à¸™à¸‚à¸à¸‡à¸«à¸´à¸¡à¸°à¸—ี่พังทลายลงมาจาà¸à¸«à¸™à¹‰à¸²à¸œà¸²à¸„วามสูง 700 เมตร[57] +ดิน[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดินดาวà¸à¸±à¸‡à¸„าร +à¸à¸¸à¹ˆà¸™à¸—ี่มีซิลิà¸à¸²à¸›à¸£à¸´à¸¡à¸²à¸“สูง เผยให้เห็นโดยยานสำรวจดาวà¸à¸±à¸‡à¸„ารสปิริต + +ข้à¸à¸¡à¸¹à¸¥à¸ˆà¸²à¸à¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸à¸”ฟีนิà¸à¸‹à¹Œà¸—ี่ส่งà¸à¸¥à¸±à¸šà¸¡à¸²à¹à¸ªà¸”งว่าดินดาวà¸à¸±à¸‡à¸„ารมีความเป็นด่างเล็à¸à¸™à¹‰à¸à¸¢à¹à¸¥à¸°à¸›à¸£à¸°à¸à¸à¸šà¸”้วยธาตุต่าง ๆ à¸à¸²à¸—ิเช่น à¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ โซเดียม โพà¹à¸—สเซียม à¹à¸¥à¸°à¸„ลà¸à¸£à¸µà¸™ สารà¸à¸²à¸«à¸²à¸£à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸µà¹‰à¸ªà¸²à¸¡à¸²à¸£à¸–พบได้ทั่วไปในสวนบนโลà¸à¹à¸¥à¸°à¸•à¹ˆà¸²à¸‡à¸à¹‡à¸ˆà¸³à¹€à¸›à¹‡à¸™à¸•à¹ˆà¸à¸à¸²à¸£à¹€à¸ˆà¸£à¸´à¸à¹€à¸•à¸´à¸šà¹‚ตขà¸à¸‡à¸žà¸·à¸Š[58] à¸à¸²à¸£à¸—ดสà¸à¸šà¹‚ดนยานสำรวจเผยว่าดินดาวà¸à¸±à¸‡à¸„ารมีสมบัติเป็นด่างด้วยค่า พีเà¸à¸Šà¸—ี่ 7.7 à¹à¸¥à¸°à¸¡à¸µà¹€à¸à¸¥à¸·à¸à¹€à¸›à¸à¸£à¹Œà¸„ลà¸à¹€à¸£à¸•à¸à¸¢à¸¹à¹ˆà¸£à¸²à¸§à¸£à¹‰à¸à¸¢à¸¥à¸° 0.6[59][60][61][62] + +มีภูมิประเทศที่เป็นเส้นพาดขวางà¸à¸¢à¸¹à¹ˆà¸—ั่วไปบนดาวà¸à¸±à¸‡à¸„ารà¹à¸¥à¸°à¸—ี่เà¸à¸´à¸”ขึ้นใหม่ ๆ ปราà¸à¸à¸šà¹ˆà¸à¸¢à¸„รั้งในบริเวณส่วนลาดที่สูงชันขà¸à¸‡à¸«à¸¥à¸¸à¸¡à¸•à¸à¸à¸£à¸°à¸—บ ร่à¸à¸‡à¸¥à¸¶à¸ à¹à¸¥à¸°à¸«à¸¸à¸šà¹€à¸«à¸§ รà¸à¸¢à¹€à¸ªà¹‰à¸™à¸žà¸²à¸”จะมีสีคล้ำในช่วงà¹à¸£à¸à¹à¸¥à¹‰à¸§à¸„่à¸à¸¢ ๆ จางลงเมื่à¸à¹€à¸§à¸¥à¸²à¸œà¹ˆà¸²à¸™à¹„ป ในบางครั้งรà¸à¸¢à¹€à¸ªà¹‰à¸™à¹€à¸£à¸´à¹ˆà¸¡à¸•à¹‰à¸™à¹ƒà¸™à¸žà¸·à¹‰à¸™à¸—ี่เล็ภๆ à¸à¹ˆà¸à¸™à¸—ี่จะà¹à¸œà¹ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸à¸à¸à¹„ปได้เป็นหลายร้à¸à¸¢à¹€à¸¡à¸•à¸£ สามารถมà¸à¸‡à¹€à¸«à¹‡à¸™à¹„ด้ตามขà¸à¸šà¸‚à¸à¸‡à¸«à¸´à¸™à¸‚นาดใหà¸à¹ˆà¹à¸¥à¸°à¹€à¸„รื่à¸à¸‡à¸à¸µà¸”ขวางต่าง ๆ ตามเส้นทางà¸à¸µà¸à¸”้วย ทฤษฎีที่ได้รับà¸à¸²à¸£à¸¢à¸à¸¡à¸£à¸±à¸šà¹‚ดยทั่วไปà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸²à¸£à¸à¸¢à¹€à¸ªà¹‰à¸™à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸±à¹‰à¸™à¹€à¸›à¹‡à¸™à¸”ินชั้นล่างซึ่งมีสีคล้ำà¹à¸•à¹ˆà¸–ูà¸à¹€à¸›à¸´à¸”à¸à¸à¸à¸¡à¸²à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸±à¸‡à¸—ลายขà¸à¸‡à¸à¸¸à¹ˆà¸™à¸ªà¸µà¸ˆà¸²à¸‡à¸—างด้านบนหรืà¸à¹‚ดยพายุà¸à¸¸à¹ˆà¸™[63] มีà¸à¸²à¸£à¹€à¸ªà¸™à¸à¸„ำà¸à¸˜à¸´à¸šà¸²à¸¢à¹„ปà¸à¸µà¸à¸«à¸¥à¸²à¸¢à¹à¸™à¸§à¸—าง บางส่วนà¸à¸˜à¸´à¸šà¸²à¸¢à¸§à¹ˆà¸²à¹€à¸à¸µà¹ˆà¸¢à¸§à¸‚้à¸à¸‡à¸à¸±à¸šà¸™à¹‰à¸³à¸«à¸£à¸·à¸à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งว่าเป็นà¸à¸²à¸£à¹€à¸ˆà¸£à¸´à¸à¹€à¸•à¸´à¸šà¹‚ตขà¸à¸‡à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•[64][65] +à¸à¸¸à¸—à¸à¸§à¸´à¸—ยา[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: น้ำบนดาวà¸à¸±à¸‡à¸„าร +ภาพถ่ายà¸à¸³à¸¥à¸±à¸‡à¸‚ยายสูงถ่ายโดยยานà¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตี à¹à¸ªà¸”งà¸à¸²à¸£à¸žà¸à¸à¸•à¸±à¸§à¸‚à¸à¸‡à¸®à¸µà¸¡à¸²à¹„ทต์สีเทา ซึ่งบ่งชี้ว่าเคยมีน้ำในสถานะขà¸à¸‡à¹€à¸«à¸¥à¸§à¸›à¸£à¸²à¸à¸à¹ƒà¸™à¸à¸”ีต + +น้ำขà¸à¸‡à¹€à¸«à¸¥à¸§à¸™à¸±à¹‰à¸™à¹„ม่สามารถดำรงà¸à¸¢à¸¹à¹ˆà¹„ด้บนดาวà¸à¸±à¸‡à¸„ารเนื่à¸à¸‡à¸ˆà¸²à¸à¸„วามà¸à¸”à¸à¸²à¸à¸²à¸¨à¸—ี่ต่ำมาà¸à¹€à¸žà¸µà¸¢à¸‡à¹à¸„่หนึ่งในร้à¸à¸¢à¸‚à¸à¸‡à¹‚ลà¸[66] เว้นà¹à¸•à¹ˆà¸žà¸·à¹‰à¸™à¸—ี่ลุ่มต่ำบางบริเวณในช่วงเวลาเพียงสั้น ๆ[67][68] à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งที่ขั้วดาวทั้งคู่มีสภาพที่พà¸à¸ˆà¸°à¹ƒà¸«à¹‰à¸™à¹‰à¸³à¹ƒà¸™à¸›à¸£à¸´à¸¡à¸²à¸“มาภๆ ได้[69][70] เฉพาะปริมาตรขà¸à¸‡à¸™à¹‰à¸³à¹à¸‚็งขั้วใต้ขà¸à¸‡à¸”าวหาà¸à¸¥à¸°à¸¥à¸²à¸¢à¸¥à¸‡à¸à¹‡à¸ˆà¸°à¹ƒà¸«à¹‰à¸™à¹‰à¸³à¹€à¸žà¸µà¸¢à¸‡à¸žà¸à¸ªà¸³à¸«à¸£à¸±à¸šà¸›à¸à¸„ลุมพื้นผิวทั้งหมดขà¸à¸‡à¸”าวเคราะห์ได้ด้วยความลึภ11 เมตร (36 ฟุต)[71] ชั้นดินเยืà¸à¸à¹à¸‚็งคงตัวà¹à¸œà¹ˆà¸‚ยายจาà¸à¸‚ั้วดาวลงมาจนถึงประมาณละติจูดที่ 60 à¸à¸‡à¸¨à¸²[69] + +คาดว่าน้ำà¹à¸‚็งปริมาณมาà¸à¸–ูà¸à¸ˆà¸±à¸šà¹€à¸à¸²à¹„ว้ภายในไครโà¸à¸ªà¹€à¸Ÿà¸µà¸¢à¸£à¹Œà¸«à¸™à¸²à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ข้à¸à¸¡à¸¹à¸¥à¹€à¸£à¸”าร์จาภมาร์สเà¸à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª à¹à¸¥à¸° มาร์สรีคà¸à¸™à¹€à¸™à¸ªà¹€à¸‹à¸™à¸‹à¹Œà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œ เมื่à¸à¸à¸£à¸à¸Žà¸²à¸„ม 2005 (พ.ศ. 2548) à¹à¸ªà¸”งน้ำà¹à¸‚็งปริมาณมหาศาลที่ขั้วทั้งสà¸à¸‡à¸‚à¸à¸‡à¸”าว[21][72] à¹à¸¥à¸°à¹ƒà¸™à¹€à¸”ืà¸à¸™à¸žà¸¤à¸¨à¸ˆà¸´à¸à¸²à¸¢à¸™ 2008 (พ.ศ. 2551) พบในบริเวณละติจูดà¸à¸¥à¸²à¸‡[22] ยานส่วนลงจà¸à¸”ฟีนิà¸à¸‹à¹Œà¸žà¸šà¸•à¸±à¸§à¸à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸³à¹à¸‚็งโดยตรงในดินส่วนตื้นขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเมื่à¸à¸§à¸±à¸™à¸—ี่ 31 à¸à¸£à¸à¸Žà¸²à¸„ม 2008[24] + +ลัà¸à¸©à¸“ะทางธรณีสัณà¸à¸²à¸™à¸—ี่มà¸à¸‡à¹€à¸«à¹‡à¸™à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารบ่งชี้à¸à¸¢à¹ˆà¸²à¸‡à¸«à¸™à¸±à¸à¹à¸™à¹ˆà¸™à¸§à¹ˆà¸²à¸¡à¸µà¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¸›à¸£à¸²à¸à¸à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวเคราะห์ เส้นทางคดเคี้ยวขนาดใหà¸à¹ˆà¸—ี่โà¸à¸šà¸„ลุมพื้นดินที่ถูà¸à¸à¸±à¸”เซาะหรืà¸à¸Šà¹ˆà¸à¸‡à¸—างà¸à¸²à¸£à¹„หลà¸à¸à¸à¸™à¸±à¹‰à¸™à¸•à¸±à¸”ผ่านพื้นผิวโดยรà¸à¸šà¸à¸§à¹ˆà¸² 25 à¹à¸«à¹ˆà¸‡ คาดว่าร่à¸à¸‡à¸£à¸à¸¢à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸šà¸±à¸™à¸—ึà¸à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸¨à¸²à¸ªà¸•à¸£à¹Œà¸‚à¸à¸‡à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸à¸±à¸”เซาะระหว่างที่มีà¸à¸²à¸£à¸›à¸¥à¸”ปล่à¸à¸¢à¸™à¹‰à¸³à¸à¸¢à¹ˆà¸²à¸‡à¸–ล่มทลายà¸à¸à¸à¸¡à¸²à¸ˆà¸²à¸à¸Šà¸±à¹‰à¸™à¸«à¸´à¸™à¸à¸¸à¹‰à¸¡à¸™à¹‰à¸³à¹ƒà¸•à¹‰à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ à¸à¸¢à¹ˆà¸²à¸‡à¹„รà¸à¹‡à¸•à¸²à¸¡à¹‚ครงสร้างบางส่วนถูà¸à¸•à¸±à¹‰à¸‡à¸ªà¸¡à¸¡à¸•à¸´à¸à¸²à¸™à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™à¸œà¸¥à¸¡à¸²à¸ˆà¸²à¸à¸à¸²à¸£à¸à¸£à¸°à¸—ำขà¸à¸‡à¸˜à¸²à¸£à¸™à¹‰à¸³à¹à¸‚็งหรืà¸à¸¥à¸²à¸§à¸²[73][74] ตัวà¸à¸¢à¹ˆà¸²à¸‡à¸«à¸™à¸¶à¹ˆà¸‡à¸—ี่มีขนาดใหà¸à¹ˆà¸„ืภมาดดิมวัลลิส ซึ่งมีความยาว 700 à¸à¸´à¹‚ลเมตร (430 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸‚นาดใหà¸à¹ˆà¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¹à¸à¸£à¸™à¸”์à¹à¸„นยà¸à¸™à¸”้วยความà¸à¸§à¹‰à¸²à¸‡ 20 à¸à¸´à¹‚ลเมตร (12 ไมล์) à¹à¸¥à¸°à¸„วามลึภ2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) ในบางท้à¸à¸‡à¸—ี่ คาดว่าภูมิประเทศถูà¸à¸à¸±à¸”สร้างขึ้นมาโดยà¸à¸²à¸£à¹„หลขà¸à¸‡à¸™à¹‰à¸³à¸•à¸±à¹‰à¸‡à¹à¸•à¹ˆà¸Šà¹ˆà¸§à¸‡à¸•à¹‰à¸™ ๆ ขà¸à¸‡à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸¨à¸²à¸ªà¸•à¸£à¹Œà¸”าวà¸à¸±à¸‡à¸„าร[75] ช่à¸à¸‡à¸—างà¸à¸²à¸£à¹„หลเหล่านี้ที่มีà¸à¸²à¸¢à¸¸à¸™à¹‰à¸à¸¢à¸—ี่สุดคาดว่าเพิ่งจะเà¸à¸´à¸”ขึ้นเมื่à¸à¹€à¸§à¸¥à¸²à¹€à¸žà¸µà¸¢à¸‡à¹„ม่à¸à¸µà¹ˆà¸¥à¹‰à¸²à¸™à¸›à¸µà¸—ี่à¹à¸¥à¹‰à¸§[76] สำหรับที่à¸à¸·à¹ˆà¸™ ๆ โดยเฉพาะพื้นที่ที่เà¸à¹ˆà¸²à¹à¸à¹ˆà¸—ี่สุดบนผิวดาวà¸à¸±à¸‡à¸„าร โครงสร้างระดับเล็à¸à¸¢à¹ˆà¸à¸¢à¸•à¸¥à¸à¸”จนเครืà¸à¸‚่ายหุบเขาที่à¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸›à¹‡à¸™à¸à¸´à¹ˆà¸‡à¸à¹‰à¸²à¸™à¸ªà¸²à¸‚าล้วนà¹à¸œà¹ˆà¸‚ยายพาดขวางเป็นสัดส่วนà¸à¸¢à¹ˆà¸²à¸‡à¸¡à¸µà¸™à¸±à¸¢à¸ªà¸³à¸„ัà¸à¹ƒà¸™à¸ าคพื้นภูมิประเทศ รูปลัà¸à¸©à¸“ะขà¸à¸‡à¸«à¸¸à¸šà¹€à¸‚าเหล่านี้รวมทั้งà¸à¸²à¸£à¸à¸£à¸°à¸ˆà¸²à¸¢à¸•à¸±à¸§à¹à¸ªà¸”งนัยà¸à¸¢à¹ˆà¸²à¸‡à¹€à¸”่นชัดว่าถูà¸à¹€à¸‹à¸²à¸°à¸ªà¸£à¹‰à¸²à¸‡à¹‚ดยà¸à¸²à¸£à¹„หลบ่าซึ่งเป็นผลลัพธ์มาจาà¸à¸à¸™à¸«à¸£à¸·à¸à¸«à¸´à¸¡à¸°à¸—ี่ตà¸à¸¥à¸‡à¸¡à¸²à¹€à¸¡à¸·à¹ˆà¸à¸¢à¸¸à¸„à¹à¸£à¸à¸‚à¸à¸‡à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸¨à¸²à¸ªà¸•à¸£à¹Œà¸”าวà¸à¸±à¸‡à¸„าร à¸à¸²à¸£à¹„หลขà¸à¸‡à¸™à¹‰à¸³à¹ƒà¸•à¹‰à¸œà¸´à¸§à¸”ินà¹à¸¥à¸°à¸à¸²à¸£à¸œà¸¸à¸”เซาะขà¸à¸‡à¸™à¹‰à¸³à¸šà¸²à¸”าลà¸à¸²à¸ˆà¹à¸ªà¸”งบทบาทย่à¸à¸¢à¸ªà¸³à¸„ัà¸à¹ƒà¸™à¸«à¸¥à¸²à¸¢à¹€à¸„รืà¸à¸‚่าย à¹à¸•à¹ˆà¸«à¸¢à¸²à¸”น้ำฟ้าน่าจะเป็นสาเหตุหลัà¸à¸‚à¸à¸‡à¸£à¸´à¹‰à¸§à¸£à¹ˆà¸à¸‡à¹€à¸à¸·à¸à¸šà¸—ั้งหมดในà¹à¸•à¹ˆà¸¥à¸°à¸à¸£à¸“ี[77] + +ร่วมไปà¸à¸±à¸šà¸œà¸™à¸±à¸‡à¸‚à¸à¸‡à¸«à¸¥à¸¸à¸¡à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸«à¸£à¸·à¸à¸«à¸¸à¸šà¹€à¸‚าลึภมีลัà¸à¸©à¸“ะภูมิประเทศนับพันที่ปราà¸à¸à¸„ล้ายคลึงà¸à¸±à¸šà¹‚ตรà¸à¸«à¹‰à¸§à¸¢à¸šà¸™à¸žà¸·à¹‰à¸™à¸”ิน ห้วยต่าง ๆ นี้มัà¸à¸¡à¸µà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸žà¸·à¹‰à¸™à¸—ี่ราบสูงทางซีà¸à¹ƒà¸•à¹‰à¸‚à¸à¸‡à¸”าวà¹à¸¥à¸°à¹€à¸œà¸Šà¸´à¸à¸à¸±à¸šà¹€à¸ªà¹‰à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£ ทั้งหมดชี้ไปในà¹à¸™à¸§à¸‚ั้วดาวที่ละติจูด 30 à¸à¸‡à¸¨à¸² นัà¸à¸§à¸´à¸ˆà¸±à¸¢à¸ˆà¸³à¸™à¸§à¸™à¸«à¸™à¸¶à¹ˆà¸‡à¹€à¸ªà¸™à¸à¸§à¹ˆà¸²à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸à¹ˆà¸à¸à¸³à¹€à¸™à¸´à¸”เà¸à¸µà¹ˆà¸¢à¸§à¸‚้à¸à¸‡à¸à¸±à¸šà¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¸‹à¸¶à¹ˆà¸‡à¸à¸²à¸ˆà¸¡à¸²à¸ˆà¸²à¸à¸™à¹‰à¸³à¹à¸‚็งที่ละลาย[78][79] à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¸¡à¸µà¸à¸µà¸à¸«à¸¥à¸²à¸¢à¸„นà¹à¸¢à¹‰à¸‡à¸§à¹ˆà¸²à¸à¸¥à¹„à¸à¹ƒà¸™à¸à¸²à¸£à¹€à¸à¸´à¸”เà¸à¸µà¹ˆà¸¢à¸§à¸‚้à¸à¸‡à¸à¸±à¸šà¸„าร์บà¸à¸™à¹„ดà¸à¸à¸à¹„ซด์เยืà¸à¸à¹à¸‚็งหรืà¸à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸—ี่ขà¸à¸‡à¸à¸¸à¹ˆà¸™à¹à¸«à¹‰à¸‡[80][81] ไม่ปราà¸à¸à¸§à¹ˆà¸²à¸¡à¸µà¹‚ตรà¸à¸«à¹‰à¸§à¸¢à¸—ี่ถูà¸à¸à¸£à¹ˆà¸à¸™à¸—ำลายบางส่วนโดยà¸à¸²à¸£à¸œà¸¸à¸à¸£à¹ˆà¸à¸™à¸•à¸²à¸¡à¸ªà¸ าพà¸à¸²à¸à¸²à¸¨ à¹à¸¥à¸°à¸à¹‡à¸ªà¸±à¸‡à¹€à¸à¸•à¹„ม่พบในหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸—ั้งหลายที่มีความเด่นชัด จึงเป็นเครื่à¸à¸‡à¸Šà¸µà¹‰à¸§à¹ˆà¸²à¸ ูมิประเทศดังà¸à¸¥à¹ˆà¸²à¸§à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸¢à¸¸à¸™à¹‰à¸à¸¢à¹à¸¥à¸°à¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¹„ด้ว่ายังคงเà¸à¸´à¸”ขึ้นในปัจจุบัน[79] + +ลัà¸à¸©à¸“ะทางธรณีวิทยาà¸à¸·à¹ˆà¸™à¸à¸µà¸à¸«à¸¥à¸²à¸¢à¸›à¸£à¸°à¸à¸²à¸£ เช่น ดินดà¸à¸™à¸ªà¸²à¸¡à¹€à¸«à¸¥à¸µà¹ˆà¸¢à¸¡à¸›à¸²à¸à¹à¸¡à¹ˆà¸™à¹‰à¸³ à¹à¸¥à¸°à¸•à¸°à¸à¸à¸™à¸™à¹‰à¸³à¸žà¸²à¸£à¸¹à¸›à¸žà¸±à¸”ที่ถูà¸à¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¹„ว้ในหลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸•à¹ˆà¸²à¸‡ ๆ เป็นพยานหลัà¸à¸à¸²à¸™à¸—ี่เสริมให้ทราบว่ามีสภาพà¹à¸§à¸”ล้à¸à¸¡à¸à¸¸à¹ˆà¸™-ชื้น ณ บางช่วงเวลาหรืà¸à¸«à¸¥à¸²à¸¢à¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¹ƒà¸™à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸¨à¸²à¸ªà¸•à¸£à¹Œà¸¢à¸¸à¸„ต้นขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[82] สภาวะà¹à¸§à¸”ล้à¸à¸¡à¹€à¸Šà¹ˆà¸™à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸ªà¸´à¹ˆà¸‡à¸—ี่จำเป็นสำหรับà¸à¸²à¸£à¹€à¸à¸´à¸”มีà¸à¸¢à¹ˆà¸²à¸‡à¸à¸§à¹‰à¸²à¸‡à¸‚วางขà¸à¸‡à¸—ะเลสาบหลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸—ี่ข้ามผ่านเป็นสัดส่วนขนาดใหà¸à¹ˆà¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ à¹à¸¥à¸°à¸™à¸±à¸šà¸§à¹ˆà¸²à¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸à¸´à¸ªà¸£à¸°à¸—ั้งในทางà¹à¸£à¹ˆà¸§à¸´à¸—ยา ตะà¸à¸à¸™à¸§à¸´à¸—ยา à¹à¸¥à¸°à¸˜à¸£à¸“ีสัณà¸à¸²à¸™à¸§à¸´à¸—ยาà¸à¸µà¸à¸”้วย[83] +ส่วนประà¸à¸à¸šà¸‚à¸à¸‡à¸«à¸´à¸™à¸šà¸£à¸´à¹€à¸§à¸“ "เยลโลไนฟ์เบย์" - หินเวนมีà¹à¸„ลเซียมà¹à¸¥à¸°à¸à¸³à¸¡à¸°à¸–ันมาà¸à¸à¸§à¹ˆà¸²à¸”ินที่ถูà¸à¸žà¸²à¸¡à¸² - ผลจาà¸à¹€à¸à¸žà¸µà¹€à¸à¸à¸‹à¹Œà¹€à¸à¸ª - คิวริà¸à¸à¸‹à¸´à¸•à¸µ (มีนาคม 2013) + +หลัà¸à¸à¸²à¸™à¸™à¸à¸à¹€à¸«à¸™à¸·à¸à¸ˆà¸²à¸à¸™à¸µà¹‰à¸—ี่ยืนยันà¸à¸²à¸£à¸—ี่ครั้งหนึ่งเคยมีน้ำขà¸à¸‡à¹€à¸«à¸¥à¸§à¸›à¸£à¸²à¸à¸à¸šà¸™à¸œà¸´à¸§à¸”าวà¸à¸±à¸‡à¸„ารมาจาà¸à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¹à¸£à¹ˆà¸—ี่มีความจำเพาะ เช่น ฮีมาไทต์ à¹à¸¥à¸°à¹€à¸à¸à¹„ทต์ ซึ่งทั้งคู่บางครั้งจะà¸à¹ˆà¸à¸•à¸±à¸§à¹ƒà¸™à¸—ี่ที่มีน้ำ[84] ในปี 2004 (พ.ศ. 2547) ยานà¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตี ตรวจพบà¹à¸£à¹ˆà¸ˆà¸²à¹‚รไซต์ซึ่งà¸à¹ˆà¸à¸•à¸±à¸§à¸‚ึ้นเฉพาะเมื่à¸à¸¡à¸µà¸™à¹‰à¸³à¹ƒà¸™à¸ªà¸ าพเป็นà¸à¸£à¸” เป็นเครื่à¸à¸‡à¸žà¸´à¸ªà¸¹à¸ˆà¸™à¹Œà¸§à¹ˆà¸²à¸„รั้งหนึ่งเคยมีน้ำà¸à¸¢à¸¹à¹ˆà¸šà¸™à¸”าวà¸à¸±à¸‡à¸„าร[85] หลัà¸à¸à¸²à¸™à¹€à¸žà¸´à¹ˆà¸¡à¹€à¸•à¸´à¸¡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¹€à¸¡à¸·à¹ˆà¸à¹„ม่นานมานี้มาจาà¸à¸à¸²à¸£à¸„้นพบà¹à¸£à¹ˆà¸¢à¸´à¸›à¸‹à¸±à¸¡à¸šà¸™à¸žà¸·à¹‰à¸™à¸”ินโดยยานสำรวจà¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตีขà¸à¸‡à¸™à¸²à¸‹à¸² เมื่à¸à¸˜à¸±à¸™à¸§à¸²à¸„ม 2011 (พ.ศ. 2554)[86][87] นà¸à¸à¸ˆà¸²à¸à¸™à¸µà¹‰ ฟรานซิส à¹à¸¡à¸„คับบิน หัวหน้าà¸à¹ˆà¸²à¸¢à¸¨à¸¶à¸à¸©à¸² นัà¸à¸§à¸´à¸—ยาศาสตร์ดาวเคราะห์ที่มหาวิทยาลัยนิวเม็à¸à¸‹à¸´à¹‚à¸à¹ƒà¸™à¹à¸à¸¥à¸šà¸¹à¹€à¸„à¸à¸£à¹Œà¸„ี ตรวจสà¸à¸šà¸¥à¸±à¸à¸©à¸“ะไฮดรà¸à¸à¹„ซด์ในผลึà¸à¹à¸£à¹ˆà¸ˆà¸²à¸à¸”าวà¸à¸±à¸‡à¸„าร à¹à¸–ลงว่าน้ำในà¹à¸¡à¸™à¹€à¸—ิลส่วนบนขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีปริมาณเท่าà¸à¸±à¸šà¸«à¸£à¸·à¸à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸—ี่โลà¸à¸¡à¸µà¸à¸¢à¸¹à¹ˆà¸—ี่ระดับ 50 - 300 ส่วนในล้านส่วน ซึ่งมาà¸à¹€à¸žà¸µà¸¢à¸‡à¸žà¸à¸—ี่จะครà¸à¸šà¸„ลุมพื้นผิวทั้งหมดขà¸à¸‡à¸”าวได้ด้วยความลึภ200 ถึง 1,000 เมตร (660 ถึง 3,280 ฟุต)[88] + +เมื่à¸à¸§à¸±à¸™à¸—ี่ 18 มีนาคม 2013 (พ.ศ. 2556) นาซารายงานหลัà¸à¸à¸²à¸™à¸ˆà¸²à¸à¹€à¸„รื่à¸à¸‡à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”บนยานสำรวจคิวริà¸à¸à¸‹à¸´à¸•à¸µ ขà¸à¸‡à¹à¸£à¹ˆà¸—ี่เà¸à¸´à¸”ขึ้นโดยมีน้ำเป็นà¸à¸‡à¸„์ประà¸à¸à¸š à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™à¹„ฮเดรตขà¸à¸‡à¹à¸„ลเซียมซัลเฟต ในตัวà¸à¸¢à¹ˆà¸²à¸‡à¸«à¸´à¸™à¸«à¸¥à¸²à¸¢à¸Šà¸™à¸´à¸”รวมทั้งชิ้นส่วนที่à¹à¸•à¸à¸à¸à¸à¸¡à¸²à¸‚à¸à¸‡à¸«à¸´à¸™ "ทินทินา" à¹à¸¥à¸°à¸«à¸´à¸™ "ซัตตันà¸à¸´à¸™à¹€à¸¥à¸µà¸¢à¸£à¹Œ" เช่นเดียวà¸à¸±à¸šà¹€à¸§à¸™à¹à¸¥à¸°à¹‚นดูลในหินà¸à¸·à¹ˆà¸™ ๆ เช่นหิน "นà¸à¸£à¹Œ" à¹à¸¥à¸°à¸«à¸´à¸™ "เวà¸à¸™à¸´à¸à¹€à¸"[89][90][91] à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์โดยใช้เครื่à¸à¸‡à¸¡à¸·à¸à¸”ีเà¸à¹€à¸à¹‡à¸™à¸‚à¸à¸‡à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ าคพื้นให้หลัà¸à¸à¸²à¸™à¹€à¸£à¸·à¹ˆà¸à¸‡à¸™à¹‰à¸³à¹ƒà¸•à¹‰à¸œà¸´à¸§à¸”ินว่ามีปริมาณà¸à¸§à¹ˆà¸²à¸£à¹‰à¸à¸¢à¸¥à¸° 4 ลึà¸à¸¥à¸‡à¹„ปจนถึงระดับ 60 เซนติเมตร (24 นิ้ว) ในเส้นทางเคลื่à¸à¸™à¸œà¹ˆà¸²à¸™à¸‚à¸à¸‡à¸¢à¸²à¸™à¸ˆà¸²à¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸ˆà¸¸à¸”ลงจà¸à¸”à¹à¸šà¸£à¸”บูรี ไปจนถึงพื้นที่ เยลโลไนฟ์เบย์ ในบริเวณภูมิภาคเà¸à¸¥à¹€à¸™à¸ [89] + +นัà¸à¸§à¸´à¸ˆà¸±à¸¢à¸šà¸²à¸‡à¸ªà¹ˆà¸§à¸™à¹€à¸Šà¸·à¹ˆà¸à¸§à¹ˆà¸²à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆà¸‚à¸à¸‡à¸žà¸´à¹‰à¸™à¸—ี่ราบต่ำทางตà¸à¸™à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวเคยถูà¸à¸¡à¸«à¸²à¸ªà¸¡à¸¸à¸—รปà¸à¸„ลุมด้วยความลึà¸à¸«à¸¥à¸²à¸¢à¸£à¹‰à¸à¸¢à¹€à¸¡à¸•à¸£ ทั้งนี้ยังà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸à¸²à¸£à¹‚ต้à¹à¸¢à¹‰à¸‡[92] ในเดืà¸à¸™à¸¡à¸µà¸™à¸²à¸„ม 2015 (พ.ศ. 2558) นัà¸à¸§à¸´à¸—ยาศาสตร์ระบุว่ามหาสมุทรดังà¸à¸¥à¹ˆà¸²à¸§à¸à¸²à¸ˆà¸¡à¸µà¸‚นาดราวมหาสมุทรà¸à¸²à¸£à¹Œà¸à¸•à¸´à¸à¸‚à¸à¸‡à¹‚ลภà¸à¸²à¸£à¸§à¸´à¸™à¸´à¸ˆà¸‰à¸±à¸¢à¸™à¸µà¹‰à¹„ด้มาจาà¸à¸à¸²à¸£à¸›à¸£à¸°à¹€à¸¡à¸´à¸™à¸à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸™à¹‰à¸³à¹à¸¥à¸°à¸”ิวเทà¸à¹€à¸£à¸µà¸¢à¸¡à¹ƒà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเทียบà¸à¸±à¸™à¸à¸±à¸šà¸à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸—ี่พบบนโลภปริมาณดิวเทà¸à¹€à¸£à¸µà¸¢à¸¡à¸—ี่พบบนดาวà¸à¸±à¸‡à¸„ารมีมาà¸à¸à¸§à¹ˆà¸²à¸—ี่ดำรงà¸à¸¢à¸¹à¹ˆà¸šà¸™à¹‚ลà¸à¸–ึงà¹à¸›à¸”เท่า บ่งชี้ว่าดาวà¸à¸±à¸‡à¸„ารครั้งโบราณà¸à¸²à¸¥à¸¡à¸µà¸™à¹‰à¸³à¹€à¸›à¹‡à¸™à¸›à¸£à¸´à¸¡à¸²à¸“มาà¸à¸à¸¢à¹ˆà¸²à¸‡à¸¡à¸µà¸™à¸±à¸¢à¸ªà¸³à¸„ัภผลสำรวจจาà¸à¸¢à¸²à¸™à¸„ิวริà¸à¸à¸‹à¸´à¸•à¸µ มาพบในภายหลังว่ามีดิวเทà¸à¹€à¸£à¸µà¸¢à¸¡à¹ƒà¸™à¸à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸ªà¸¹à¸‡à¹ƒà¸™à¸«à¸¥à¸¸à¸¡à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¹€à¸à¸¥ à¸à¸¢à¹ˆà¸²à¸‡à¹„รà¸à¹‡à¸•à¸²à¸¡à¸„่าที่ได้ยังไม่สูงพà¸à¸—ี่จะสนับสนุนว่าเคยมีมหาสมุทรà¸à¸¢à¸¹à¹ˆ นัà¸à¸§à¸´à¸—ยาศาสตร์รายà¸à¸·à¹ˆà¸™ ๆ เตืà¸à¸™à¸§à¹ˆà¸²à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¹ƒà¸«à¸¡à¹ˆà¸™à¸µà¹‰à¸¢à¸±à¸‡à¹„ม่ได้รับà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™ à¹à¸¥à¸°à¸Šà¸µà¹‰à¸›à¸£à¸°à¹€à¸”็นว่าà¹à¸šà¸šà¸ˆà¸³à¸¥à¸à¸‡à¸ ูมิà¸à¸²à¸à¸²à¸¨à¸”าวà¸à¸±à¸‡à¸„ารยังไม่ได้à¹à¸ªà¸”งว่าดาวเคราะห์มีความà¸à¸šà¸à¸¸à¹ˆà¸™à¹€à¸žà¸µà¸¢à¸‡à¸žà¸à¹ƒà¸™à¸à¸”ีตที่ผ่านมาที่จะเà¸à¸·à¹‰à¸à¹ƒà¸«à¹‰à¸™à¹‰à¸³à¸„งà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸£à¸¹à¸›à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¹„ด้[93] +à¹à¸œà¹ˆà¸™à¸‚ั้วโลà¸[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: น้ำà¹à¸‚็งขั้วโลà¸à¸”าวà¸à¸±à¸‡à¸„าร +à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วเหนืà¸à¸Šà¹ˆà¸§à¸‡à¸•à¹‰à¸™à¸¤à¸”ูร้à¸à¸™ 1999 (พ.ศ. 2542) +à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วใต้ในช่วงฤดูร้à¸à¸™ 2000 (พ.ศ. 2543) + +ดาวà¸à¸±à¸‡à¸„ารมีà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งถาวรà¸à¸¢à¸¹à¹ˆà¸—ี่ขั้วทั้งสà¸à¸‡ เมื่à¸à¸–ึงฤดูหนาวขà¸à¸‡à¹à¸•à¹ˆà¸¥à¸°à¸‚ั้วพื้นที่โดยรà¸à¸šà¸à¹‡à¸ˆà¸°à¸•à¸à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸„วามมืดà¸à¸¢à¹ˆà¸²à¸‡à¸•à¹ˆà¸à¹€à¸™à¸·à¹ˆà¸à¸‡ à¸à¸²à¸£à¹€à¸¢à¸·à¸à¸à¹€à¸¢à¹‡à¸™à¸¥à¸‡à¸‚à¸à¸‡à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¹€à¸›à¹‡à¸™à¸ªà¸²à¹€à¸«à¸•à¸¸à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”à¸à¸²à¸£à¹€à¸¢à¸·à¸à¸à¹à¸‚็งสะสมขà¸à¸‡à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸à¸§à¹ˆà¸²à¸£à¹‰à¸à¸¢à¸¥à¸° 25 - 30 ลงมาเป็นà¹à¸œà¹ˆà¸™ CO2 เยืà¸à¸à¹à¸‚็ง (น้ำà¹à¸‚็งà¹à¸«à¹‰à¸‡)[94] เมื่à¸à¹à¸•à¹ˆà¸¥à¸°à¸‚ั้วà¸à¸¥à¸±à¸šà¸¡à¸²à¹„ด้รับà¹à¸ªà¸‡à¹à¸”ดà¸à¸µà¸à¸„รั้ง CO2 เยืà¸à¸à¹à¸‚็งà¸à¹‡à¸ˆà¸°à¸£à¸°à¹€à¸«à¸´à¸” เà¸à¸´à¸”เป็นลมขนาดมหึมาà¸à¸§à¸²à¸”ซัดไปทั่วบริเวณขั้วด้วยà¸à¸±à¸•à¸£à¸²à¹€à¸£à¹‡à¸§à¸–ึง 400 à¸à¸´à¹‚ลเมตร/ชั่วโมง (250 ไมล์/ชั่วโมง) ปราà¸à¸à¸à¸²à¸£à¸“์ตามฤดูà¸à¸²à¸¥à¸™à¸µà¹‰à¸Šà¹ˆà¸§à¸¢à¹€à¸„ลื่à¸à¸™à¸¢à¹‰à¸²à¸¢à¸à¸¸à¹ˆà¸™à¹à¸¥à¸°à¹„à¸à¸™à¹‰à¸³à¸›à¸£à¸´à¸¡à¸²à¸“มหาศาลให้ลà¸à¸¢à¸ªà¸¹à¸‡à¸‚ึ้นคล้ายà¸à¸±à¸šà¹€à¸¡à¸†à¹€à¸‹à¸à¸£à¹Œà¸£à¸±à¸ªà¹€à¸¢à¸·à¸à¸à¹à¸‚็งขนาดใหà¸à¹ˆà¸šà¸™à¹‚ลภยานสำรวจà¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตี ถ่ายภาพเมฆที่เป็นน้ำเยืà¸à¸à¹à¸‚็งนี้ได้ในปี 2004 (พ.ศ. 2547)[95] + +à¹à¸œà¹ˆà¸™à¸—ี่ขั้วโลà¸à¸—ั้งสà¸à¸‡à¸¡à¸µà¸à¸‡à¸„์ประà¸à¸à¸šà¸«à¸¥à¸±à¸à¸à¸§à¹ˆà¸²à¸£à¹‰à¸à¸¢à¸¥à¸° 70 เป็นน้ำเยืà¸à¸à¹à¸‚็ง สำหรับคาร์บà¸à¸™à¹„ดà¸à¸à¸à¹„ซด์เยืà¸à¸à¹à¸‚็งจะสะสมตัวเป็นชั้นที่บางà¸à¸§à¹ˆà¸²à¹€à¸¡à¸·à¹ˆà¸à¹€à¸—ียบà¸à¸±à¸™à¹‚ดยหนาประมาณหนึ่งเมตรบนà¹à¸œà¹ˆà¸™à¸‚ั้วเหนืà¸à¹€à¸‰à¸žà¸²à¸°à¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¸¤à¸”ูหนาวเท่านั้น ในขณะที่à¹à¸œà¹ˆà¸™à¸‚ั้วใต้เป็นà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸„งตัวปà¸à¸„ลุมด้วยความหนาประมาณà¹à¸›à¸”เมตร à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸„งตัวที่ปà¸à¸„ลุมยังขั้วใต้นี้เà¸à¸¥à¸·à¹ˆà¸à¸™à¸à¸¥à¹ˆà¸™à¹„ปด้วยหลุมตื้น ๆ พื้นเรียบขà¸à¸šà¹‚ค้งเว้าไม่à¹à¸™à¹ˆà¸™à¸à¸™à¸«à¸£à¸·à¸à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศà¹à¸šà¸šà¹€à¸™à¸¢à¹à¸‚็งสวิส ภาพถ่ายซ้ำยังสถานที่เดิมà¹à¸ªà¸”งให้เห็นà¸à¸²à¸£à¸‚ยายตัวขà¸à¸‡à¸£à¸à¸¢à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸µà¹‰à¹„ด้หลายเมตรต่à¸à¸›à¸µ บà¸à¸à¹ƒà¸«à¹‰à¸—ราบว่าà¹à¸œà¹ˆà¸™ CO2 คงตัวที่ปà¸à¸„ลุมขั้วใต้เบื้à¸à¸‡à¸šà¸™à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งจาà¸à¸™à¹‰à¸³à¸™à¸±à¹‰à¸™à¸¡à¸µà¸à¸²à¸£à¸ªà¸¥à¸²à¸¢à¸•à¸±à¸§à¹„ปตามเวลา[96] à¹à¸œà¹ˆà¸™à¸›à¸à¸„ลุมขั้วเหนืà¸à¸¡à¸µà¸‚นาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 1,000 à¸à¸´à¹‚ลเมตร (620 ไมล์) ระหว่างฤดูร้à¸à¸™à¸‚à¸à¸‡à¸‹à¸µà¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[97] à¹à¸¥à¸°à¸¡à¸µà¸›à¸£à¸´à¸¡à¸²à¸•à¸£à¸™à¹‰à¸³à¹à¸‚็งประมาณ 1.6 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร (380,000 ลูà¸à¸šà¸²à¸¨à¸à¹Œà¹„มล์) ซึ่งหาà¸à¸à¸£à¸°à¸ˆà¸²à¸¢à¸•à¸±à¸§à¸à¸¢à¹ˆà¸²à¸‡à¸ªà¸¡à¹ˆà¸³à¹€à¸ªà¸¡à¸à¸—ั่วทั้งà¹à¸œà¹ˆà¸™à¸à¹‡à¸ˆà¸°à¸¡à¸µà¸„วามหนาถึง 2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) [98] (เปรียบเทียบà¸à¸±à¸šà¸™à¹‰à¸³à¹à¸‚็งปริมาตร 2.85 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร (680,000 ลูà¸à¸šà¸²à¸¨à¸à¹Œà¹„มล์) ขà¸à¸‡à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¸à¸£à¸µà¸™à¹à¸¥à¸™à¸”์) à¹à¸œà¹ˆà¸™à¸Šà¸±à¹‰à¸§à¹ƒà¸•à¹‰à¸¡à¸µà¹€à¸ªà¹‰à¸™à¸œà¹ˆà¸²à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡ 350 à¸à¸´à¹‚ลเมตร (220 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามหนา 3 à¸à¸´à¹‚ลเมตร (1.9 ไมล์)[99] ปริมาตรรวมขà¸à¸‡à¸™à¹‰à¸³à¹à¸‚็งในà¹à¸œà¹ˆà¸™à¸‚ั้วใต้รวมทั้งที่เà¸à¹‡à¸šà¸ªà¸°à¸ªà¸¡à¹ƒà¸™à¸Šà¸±à¹‰à¸™à¸šà¸£à¸´à¹€à¸§à¸“ใà¸à¸¥à¹‰à¹€à¸„ียงประมาณว่ามีà¸à¸¢à¸¹à¹ˆà¸à¸§à¹ˆà¸² 1.6 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร[100] à¹à¸œà¹ˆà¸™à¸‚ั้วโลà¸à¸—ั้งคู่มีร่à¸à¸‡à¸£à¸¹à¸›à¹€à¸à¸¥à¸µà¸¢à¸§à¸›à¸£à¸²à¸à¸ ตามข้à¸à¸¡à¸¹à¸¥à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์จาà¸à¸Šà¸²à¹€à¸£à¸”หรืà¸à¹€à¸£à¸”าร์สำรวจส่วนตื้นขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารผ่านน้ำà¹à¸‚็ง à¹à¸ªà¸”งว่าร่à¸à¸‡à¸”ังà¸à¸¥à¹ˆà¸²à¸§à¹€à¸›à¹‡à¸™à¸œà¸¥à¸ˆà¸²à¸à¸¥à¸¡à¸žà¸±à¸”ลาดลงซี่งหมุนเป็นเà¸à¸¥à¸µà¸¢à¸§à¹€à¸™à¸·à¹ˆà¸à¸‡à¸ˆà¸²à¸à¸œà¸¥à¸à¸£à¸°à¸—บโคริโà¸à¸¥à¸´à¸ª[101][102] + +à¸à¸²à¸£à¹€à¸¢à¸·à¸à¸à¹à¸‚็งตามฤดูà¸à¸²à¸¥à¹ƒà¸™à¸šà¸²à¸‡à¸—้à¸à¸‡à¸—ี่ใà¸à¸¥à¹‰à¸à¸±à¸šà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วใต้ทำให้เà¸à¸´à¸”ชั้นใสขà¸à¸‡à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸«à¸™à¸²à¸›à¸£à¸°à¸¡à¸²à¸“หนึ่งเมตรเหนืà¸à¸žà¸·à¹‰à¸™à¸”ิน เมื่à¸à¸–ึงฤดูใบไม้ผลิ à¹à¸ªà¸‡à¸à¸²à¸—ิตย์ทำให้ใต้พื้นผิวà¸à¸¸à¹ˆà¸™à¸‚ึ้น ความดันจาภCO2 ระเหิดบริเวณข้างใต้à¹à¸œà¹ˆà¸™à¸ˆà¸°à¸”ัน ยภà¹à¸¥à¸°à¸ªà¸¸à¸”ท้ายทำให้à¹à¸œà¹ˆà¸™à¹à¸•à¸à¸à¸à¸ ซึ่งนำไปสู่à¸à¸²à¸£à¸›à¸°à¸—ุà¹à¸šà¸šà¹„à¸à¹€à¸‹à¸à¸£à¹Œà¸‚à¸à¸‡à¹à¸à¹Šà¸ª CO2 ผสมà¸à¸±à¸šà¸—รายบะซà¸à¸¥à¸•à¹Œà¸ªà¸µà¸„ล้ำหรืà¸à¸à¸¸à¹ˆà¸™ à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸™à¸µà¹‰à¹€à¸à¸´à¸”ขึ้นเร็ว สังเà¸à¸•à¸ˆà¸²à¸à¸à¸§à¸à¸²à¸¨à¹„ด้ในเวลาเพียงไม่à¸à¸µà¹ˆà¸§à¸±à¸™à¸«à¸£à¸·à¸à¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸«à¸¥à¸²à¸¢à¸ªà¸±à¸›à¸”าห์ถึงหลายเดืà¸à¸™ à¸à¸±à¸•à¸£à¸²à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸„่à¸à¸™à¸‚้างจะไม่ปà¸à¸•à¸´à¹ƒà¸™à¸—างธรณีวิทยาโดยเฉพาะà¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„าร à¹à¸à¹Šà¸ªà¸—ี่เคลื่à¸à¸™à¹„หลไปข้างใต้à¹à¸œà¹ˆà¸™à¸ˆà¸™à¸–ึงตำà¹à¸«à¸™à¹ˆà¸‡à¹„à¸à¹€à¸‹à¸à¸£à¹Œà¸ˆà¸°à¸à¸±à¸”สลัà¸à¸£à¸¹à¸›à¹à¸šà¸šà¸„ล้ายใยà¹à¸¡à¸‡à¸¡à¸¸à¸¡à¸à¸£à¸°à¸ˆà¸²à¸¢à¸à¸à¸à¹€à¸›à¹‡à¸™à¸£à¸±à¸¨à¸¡à¸µà¸•à¸²à¸¡à¸Šà¹ˆà¸à¸‡à¸—างที่ผ่านใต้น้ำà¹à¸‚็ง à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—ี่เà¸à¸´à¸”ขึ้นเหมืà¸à¸™à¸à¸±à¸šà¸ าคตรงข้ามขà¸à¸‡à¹‚ครงข่ายà¸à¸²à¸£à¸à¸±à¸”เซาะจาà¸à¸™à¹‰à¸³à¸—ี่ระบายลงหลุมที่ดึงจุà¸à¸à¸¸à¸”à¸à¸à¸à¹„ป[103][104][105][106] +ภูมิศาสตร์à¹à¸¥à¸°à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸à¸ ูมิประเทศพื้นผิว[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ภูมิศาสตร์ดาวà¸à¸±à¸‡à¸„าร +à¹à¸œà¸™à¸—ี่ภูมิประเทศจาà¸à¹‚มลา à¹à¸ªà¸”งพื้นที่มีระดับสูง (สีà¹à¸”งà¹à¸¥à¸°à¸ªà¸µà¸ªà¹‰à¸¡) เป็นพื้นที่ส่วนใหà¸à¹ˆà¹ƒà¸™à¸‹à¸µà¸à¹‚ลà¸à¹ƒà¸•à¹‰à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ที่ราบลุ่ม (สีฟ้า) ทางตà¸à¸™à¹€à¸«à¸™à¸·à¸ ที่ราบสูงภูเขาไฟà¸à¸³à¸«à¸™à¸”ขà¸à¸šà¹€à¸‚ตที่ราบทางเหนืà¸à¹ƒà¸™à¸šà¸²à¸‡à¸šà¸£à¸´à¹€à¸§à¸“ ในขณะที่พื้นที่สูงมีà¹à¸à¹ˆà¸‡à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดใหà¸à¹ˆà¸«à¸¥à¸²à¸¢à¹à¸«à¹ˆà¸‡ + +à¹à¸¡à¹‰à¸§à¹ˆà¸²à¹‚ยฮันน์ ไฮน์ริภฟà¸à¸™ เมดเลà¸à¸£à¹Œ à¹à¸¥à¸°à¸§à¸´à¸¥à¹€à¸®à¸¥à¹Œà¸¡ เบียร์จะเป็นที่จดจำà¸à¸¢à¹ˆà¸²à¸‡à¸”ียิ่งว่าเป็นผู้วาดà¹à¸œà¸™à¸—ี่ดวงจันทร์à¹à¸•à¹ˆà¸žà¸§à¸à¹€à¸‚าà¸à¹‡à¹€à¸›à¹‡à¸™ "นัà¸à¸§à¸²à¸”à¹à¸œà¸™à¸—ี่ดาวà¸à¸±à¸‡à¸„าร" à¸à¸±à¸™à¸”ับà¹à¸£à¸ พวà¸à¹€à¸‚าเริ่มโดยà¸à¸³à¸«à¸™à¸”ภูมิประเทศพื้นผิวดาวà¸à¸±à¸‡à¸„ารส่วนใหà¸à¹ˆà¹ƒà¸«à¹‰à¹€à¸›à¹‡à¸™à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸¡à¸±à¹ˆà¸™à¸„ง à¹à¸¥à¸°à¹‚ดยà¸à¸²à¸£à¸™à¸µà¹‰à¸ˆà¸¶à¸‡à¸ªà¸²à¸¡à¸²à¸£à¸–วัดคาบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸à¸šà¸•à¸±à¸§à¹€à¸à¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารได้à¸à¸¢à¹ˆà¸²à¸‡à¹à¸¡à¹ˆà¸™à¸¢à¸³à¸¡à¸²à¸à¸‚ึ้น ในปี 1840 (พ.ศ. 2383) เมดเลà¸à¸£à¹Œà¸£à¸§à¸šà¸£à¸§à¸¡à¸œà¸¥à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸•à¸¥à¸à¸”สิบปีขà¸à¸‡à¹€à¸‚าà¹à¸¥à¹‰à¸§à¸§à¸²à¸”à¹à¸œà¸™à¸—ี่ดาวà¸à¸±à¸‡à¸„ารขึ้นเป็นครั้งà¹à¸£à¸ à¹à¸—นที่จะมีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸à¹ƒà¸«à¹‰à¸à¸±à¸šà¸ˆà¸¸à¸”สังเà¸à¸•à¸•à¹ˆà¸²à¸‡ ๆ à¸à¸±à¸™à¸«à¸¥à¸²à¸¢à¸«à¸¥à¸²à¸à¸™à¸±à¹‰à¸™ เบียร์à¹à¸¥à¸°à¹€à¸¡à¸”เลà¸à¸£à¹Œà¸à¸¥à¸±à¸šà¹ƒà¸Šà¹‰à¸§à¸´à¸˜à¸µà¸‡à¹ˆà¸²à¸¢ ๆ โดยระบุด้วยตัวà¸à¸±à¸à¸©à¸£ เมà¸à¸£à¸´à¹€à¸”ียนเบย์ (ไซนัสเมà¸à¸£à¸´à¹€à¸”ียนี) ถูà¸à¹€à¸£à¸µà¸¢à¸à¹€à¸›à¹‡à¸™à¸ ูมิประเทศ "a"[107] + +ปัจจุบันนี้ภูมิประเทศบนดาวà¸à¸±à¸‡à¸„ารได้รับà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸à¸ˆà¸²à¸à¸«à¸¥à¸²à¸¢à¹à¸«à¸¥à¹ˆà¸‡à¸—ี่มา ภูมิประเทศที่เห็นโดดเด่นจะตั้งชื่à¸à¸•à¸²à¸¡à¹€à¸—ววิทยาคลาสสิภหลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸—ี่ใหà¸à¹ˆà¸à¸§à¹ˆà¸² 60 à¸à¸´à¹‚ลเมตรตั้งชื่à¸à¸•à¸²à¸¡à¸Šà¸·à¹ˆà¸à¸‚à¸à¸‡à¸™à¸±à¸à¸§à¸´à¸—ยาศาสตร์ นัà¸à¹€à¸‚ียน à¹à¸¥à¸°à¸šà¸¸à¸„คลà¸à¸·à¹ˆà¸™à¹ƒà¸”ที่มีบทบาทช่วยเหลืà¸à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¹ƒà¸™à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸”าวà¸à¸±à¸‡à¸„ารซึ่งได้ล่วงลับไปà¹à¸¥à¹‰à¸§ หลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸—ี่เล็à¸à¸à¸§à¹ˆà¸² 60 à¸à¸´à¹‚ลเมตรลงมา ตั้งชื่à¸à¸•à¸²à¸¡à¸Šà¸·à¹ˆà¸à¹€à¸¡à¸·à¸à¸‡à¸«à¸£à¸·à¸à¸«à¸¡à¸¹à¹ˆà¸šà¹‰à¸²à¸™à¸šà¸™à¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸ˆà¸°à¸•à¹‰à¸à¸‡à¸¡à¸µà¸›à¸£à¸°à¸Šà¸²à¸à¸£à¸™à¹‰à¸à¸¢à¸à¸§à¹ˆà¸² 100,000 คน หุบเขาขนาดใหà¸à¹ˆà¹„ด้ชื่à¸à¸¡à¸²à¸ˆà¸²à¸ คำ "ดาวà¸à¸±à¸‡à¸„าร" หรืภดาวฤà¸à¸©à¹Œ" ในภาษาต่าง ๆ นานา ส่วนหุบเขาขนาดเล็à¸à¸™à¸±à¹‰à¸™à¹„ด้ชื่à¸à¸ˆà¸²à¸à¸Šà¸·à¹ˆà¸à¸‚à¸à¸‡à¹à¸¡à¹ˆà¸™à¹‰à¸³[108] + +ภูมิประเทศที่มีความโดดเด่นขนาดใหà¸à¹ˆà¸¢à¸±à¸‡à¸„งมีชื่à¸à¹€à¸£à¸µà¸¢à¸à¹€à¸”ิมà¸à¸¢à¸¹à¹ˆà¸«à¸¥à¸²à¸¢à¸Šà¸·à¹ˆà¸ à¹à¸•à¹ˆà¸à¹‡à¸¡à¸±à¸à¸¡à¸µà¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹€à¸žà¸·à¹ˆà¸à¹ƒà¸«à¹‰à¸ªà¸°à¸—้à¸à¸™à¸à¸‡à¸„์ความรู้ใหม่เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸˜à¸£à¸£à¸¡à¸Šà¸²à¸•à¸´à¸‚à¸à¸‡à¸ ูมิประเทศนั้น ตัวà¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™ นิà¸à¸‹à¹Œà¹‚à¸à¸¥à¸´à¸¡à¸›à¸´à¸à¸² (หิมะà¹à¸«à¹ˆà¸‡à¹‚à¸à¸¥à¸´à¸¡à¸›à¸±à¸ª) à¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™ โà¸à¸¥à¸´à¸¡à¸›à¸±à¸ªà¸¡à¸à¸™à¸ªà¹Œ (ภูเขาโà¸à¸¥à¸´à¸¡à¸›à¸±à¸ª)[109] พื้นผิวดาวà¸à¸±à¸‡à¸„ารที่มà¸à¸‡à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¹‚ลà¸à¹à¸šà¹ˆà¸‡à¸à¸à¸à¹„ด้เป็นสà¸à¸‡à¸à¸¥à¸¸à¹ˆà¸¡à¸žà¸·à¹‰à¸™à¸—ี่จาà¸à¸„วามà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸‚à¸à¸‡à¸à¸²à¸£à¸ªà¸°à¸—้à¸à¸™à¹à¸ªà¸‡ ที่ราบสีจางที่ปà¸à¸„ลุมด้วยà¸à¸¸à¹ˆà¸™à¹à¸¥à¸°à¸—รายà¸à¸±à¸™à¸à¸¸à¸”มไปด้วยà¸à¸à¸à¹„ซด์ขà¸à¸‡à¹€à¸«à¸¥à¹‡à¸à¸‹à¸¶à¹ˆà¸‡à¸¡à¸µà¸ªà¸µà¹à¸”งนั้น ครั้งหนึ่งเคยคิดà¸à¸±à¸™à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™ "ทวีป" ขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร จึงมีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸à¸—ำนà¸à¸‡ à¸à¸°à¹€à¸£à¹€à¸šà¸µà¸¢à¹€à¸—ร์รา (à¹à¸œà¹ˆà¸™à¸”ินà¹à¸«à¹ˆà¸‡à¸à¸²à¸£à¸°à¹€à¸šà¸µà¸¢) หรืà¸à¸à¸¢à¹ˆà¸²à¸‡ à¹à¸à¸¡à¸°à¹‚ซนิสเพลนิเชีย (ที่ราบà¹à¸à¸¡à¸°à¸‹à¸à¸™) ภูมิประเทศคล้ำถูà¸à¸„ิดว่าเป็นทะเล ดังนั้นจึงตั้งชื่à¸à¸à¸¢à¹ˆà¸²à¸‡ à¹à¸¡à¸£à¹Œà¹€à¸à¸£à¸´à¹€à¸•à¸£à¸µà¸¢à¸¡ (ทะเลà¹à¸”ง) à¹à¸¡à¸£à¹Œà¹„ซเรนัม à¹à¸¥à¸°à¸à¸à¹‚รรีไซนัส ภูมิประเทศมืดคล้ำที่มีขนาดใหà¸à¹ˆà¸—ี่สุดที่มà¸à¸‡à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¹‚ลà¸à¸„ืภเซียทิสเมเจà¸à¸£à¹Œà¹€à¸žà¸¥à¸™à¸±à¸¡[110] à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งคงตัวทางขั้วเหนืà¸à¹„ด้ชื่à¸à¸§à¹ˆà¸² เพลนัมบà¸à¹€à¸£à¸µà¸¢à¸¡ ในขณะที่à¹à¸œà¹ˆà¸™à¸—างขั้วใต้เรียà¸à¸§à¹ˆà¸² เพลนัมà¸à¸à¸ªà¹€à¸—รล + +เส้นศูนย์สูตรขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารถูà¸à¸à¸³à¸«à¸™à¸”โดยà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸‚à¸à¸‡à¸”าว à¹à¸•à¹ˆà¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¹€à¸¡à¸£à¸´à¹€à¸”ียนà¹à¸£à¸à¹€à¸›à¹‡à¸™à¸ªà¸´à¹ˆà¸‡à¸—ี่ถูà¸à¸£à¸°à¸šà¸¸à¸‚ึ้นเà¸à¸‡ ดังเช่นตำà¹à¸«à¸™à¹ˆà¸‡à¸à¸£à¸µà¸™à¸´à¸Šà¸‚à¸à¸‡à¹‚ลภคืà¸à¸•à¹‰à¸à¸‡à¹€à¸¥à¸·à¸à¸à¸à¸³à¸«à¸™à¸”จุดชี้ขาดขึ้นมา เมดเลà¸à¸£à¹Œà¹à¸¥à¸°à¹€à¸šà¸µà¸¢à¸£à¹Œà¹„ด้เลืà¸à¸à¹€à¸ªà¹‰à¸™à¹€à¸¡à¸à¸£à¸´à¹€à¸”ียนในปี 1830 (พ.ศ. 2373) สำหรับà¹à¸œà¸™à¸—ี่à¹à¸£à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ต่à¸à¸¡à¸²à¸ ายหลังยานà¸à¸§à¸à¸²à¸¨à¸¡à¸²à¸£à¸´à¹€à¸™à¸à¸£à¹Œ 9 ได้ให้ภาพดาวà¸à¸±à¸‡à¸„ารมาà¸à¸¡à¸²à¸¢à¹ƒà¸™à¸›à¸µ 1972 (พ.ศ. 2515) หลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็à¸à¸‹à¸¶à¹ˆà¸‡à¹„ด้ชื่à¸à¸ ายหลังว่า à¹à¸à¸£à¸µ-0 ในบริเวณ ไซนัสเมà¸à¸£à¸´à¹€à¸”ียนี ("à¸à¹ˆà¸²à¸§à¸•à¸£à¸‡à¸à¸¥à¸²à¸‡" หรืภ"à¸à¹ˆà¸²à¸§à¹€à¸¡à¸à¸£à¸´à¹€à¸”ียน") ได้ถูà¸à¹€à¸¥à¸·à¸à¸à¹€à¸›à¹‡à¸™à¸ˆà¸¸à¸”นิยามลà¸à¸‡à¸ˆà¸´à¸ˆà¸¹à¸”ที่ 0.0 à¸à¸‡à¸¨à¸² เพื่à¸à¹ƒà¸«à¹‰à¸žà¹‰à¸à¸‡à¸•à¸£à¸‡à¸à¸±à¸™à¸à¸±à¸šà¹€à¸ªà¹‰à¸™à¸—ี่ได้à¸à¸³à¸«à¸™à¸”ไว้เดิม[111] + +เพราะดาวà¸à¸±à¸‡à¸„ารไม่มีมหาสมุทรดังนั้นจึงไม่มี "ระดับน้ำทะเล" พื้นผิวที่มีระดับà¸à¸²à¸£à¸¢à¸à¸•à¸±à¸§à¹€à¸›à¹‡à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ˆà¸¶à¸‡à¸–ูà¸à¹€à¸¥à¸·à¸à¸à¹ƒà¸Šà¹‰à¹€à¸›à¹‡à¸™à¸£à¸°à¸”ับà¸à¹‰à¸²à¸‡à¸à¸´à¸‡à¹à¸—นซึ่งเรียà¸à¸§à¹ˆà¸² à¹à¸à¸£à¸µà¸à¸à¸¢à¸”์ [112] ขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร เปรียบดังจีà¸à¸à¸¢à¸”์บนพื้นผิวโลภระดับความสูงที่มีค่าเท่าà¸à¸±à¸šà¸¨à¸¹à¸™à¸¢à¹Œà¸–ูà¸à¸à¸³à¸«à¸™à¸” ณ ความสูงที่มีความดันบรรยาà¸à¸²à¸¨à¹€à¸—่าà¸à¸±à¸š 610.5 ปาสà¸à¸²à¸¥ (6.105 มิลลิบาร์)[113] ค่าความดันนี้สà¸à¸”คล้à¸à¸‡à¸à¸±à¸šà¸ˆà¸¸à¸”สามสถานะขà¸à¸‡à¸™à¹‰à¸³à¹à¸¥à¸°à¸¡à¸µà¸„่าประมาณร้à¸à¸¢à¸¥à¸° 0.6 ขà¸à¸‡à¸„วามดันพื้นผิวที่ระดับน้ำทะเลบนโลภ(0.006 บรรยาà¸à¸²à¸¨)[114] ในทางปà¸à¸´à¸šà¸±à¸•à¸´ ณ ปัจจุบัน พื้นผิวนี้ถูà¸à¸à¸³à¸«à¸™à¸”โดยตรงจาà¸à¸”าวเทียมตรวจวัดความโน้มถ่วง +à¹à¸œà¸™à¸—ี่สี่มุมดาวà¸à¸±à¸‡à¸„าร[à¹à¸à¹‰] + +ภาพà¸à¸´à¸¡à¹€à¸¡à¸ˆà¹à¸¡à¸žà¸”ังต่à¸à¹„ปนี้ขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารà¹à¸šà¹ˆà¸‡à¸à¸à¸à¹€à¸›à¹‡à¸™à¹à¸œà¸™à¸—ี่สี่มุมจำนวน 30 ชิ้น à¸à¸³à¸«à¸™à¸”โดยà¸à¸‡à¸„์à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸˜à¸£à¸“ีวิทยาสหรัà¸à¸à¹€à¸¡à¸£à¸´à¸à¸²[115][116] à¹à¸œà¸™à¸—ี่à¹à¸•à¹ˆà¸¥à¸°à¸Šà¸´à¹‰à¸™à¸¡à¸µà¸à¸²à¸£à¸à¸³à¸à¸±à¸šà¸•à¸±à¸§à¹€à¸¥à¸‚พร้à¸à¸¡à¸à¸±à¸à¸©à¸£à¸™à¸³à¸«à¸™à¹‰à¸² "MC" ย่à¸à¸¡à¸²à¸ˆà¸²à¸ "Mars Chart" หรืà¸à¹à¸œà¸™à¸ าพดาวà¸à¸±à¸‡à¸„าร[117] ด้านบนคืà¸à¹à¸œà¸™à¸—ี่ตà¸à¸™à¹€à¸«à¸™à¸·à¸à¸ªà¸¸à¸” ตำà¹à¸«à¸™à¹ˆà¸‡ 0°N 180°W / 0°N 180°W à¸à¸¢à¸¹à¹ˆà¸—างซ้ายสุดเหนืà¸à¹€à¸ªà¹‰à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£ ภาพà¹à¸œà¸™à¸—ี่ได้มาจาà¸à¸¡à¸²à¸£à¹Œà¸ªà¹‚à¸à¸¥à¸šà¸à¸¥à¹€à¸‹à¸à¸£à¹Œà¹€à¸§à¹€à¸¢à¸à¸£à¹Œ +Mars Quad Map +เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ าพนี้ +0°N 180°W / 0°N 180°W +0°N 0°W / 0°N -0°E +90°N 0°W / 90°N -0°E +MC-01 + +à¹à¸¡à¸£à¹Œà¸šà¸à¹€à¸£à¸µà¸¢à¸¡ +MC-02 + +ไดà¸à¸°à¹€à¸„รีย +MC-03 + +à¸à¸²à¸£à¹Œà¸„าเดีย +MC-04 + +à¹à¸¡à¸£à¹Œà¹à¸à¸‹à¸´à¹€à¸”เลียม +MC-05 + +à¸à¸´à¸ªà¸¡à¸µà¹€à¸™à¸µà¸¢à¸ªà¸¥à¸²à¸„ัส +MC-06 + +เคเซียส +MC-07 + +ซีเบรเนีย +MC-08 + +à¹à¸à¸¡à¸°à¹‚ซนิส +MC-09 + +ธาร์ซิส +MC-10 + +ลูนีเพลัส +MC-11 + +à¸à¸à¸à¹€à¸‹à¸µà¸¢à¹€à¸žà¸¥à¸±à¸ª +MC-12 + +à¸à¸°à¹€à¸£à¹€à¸šà¸µà¸¢ +MC-13 + +เซียทิสเมเจà¸à¸£à¹Œ +MC-14 + +à¸à¸°à¹€à¸¡à¸™à¹€à¸˜à¸ª +MC-15 + +à¸à¸´à¸¥à¸µà¹€à¸‹à¸µà¸¢à¸¡ +MC-16 + +เมมโนเนีย +MC-17 + +ฟีนีซิส +MC-18 + +โคเพรตส์ +MC-19 + +มาร์à¸à¸²à¸£à¸´à¸•à¸´à¹€à¸Ÿà¸à¸£à¹Œ +MC-20 + +ซาบีà¸à¸±à¸ª +MC-21 + +ไà¸à¸à¸²à¸žà¸µà¹€à¸ˆà¸µà¸¢ +MC-22 + +ทีร์รีนัม +MC-23 + +à¸à¸µà¹‚à¸à¸¥à¸´à¸ª +MC-24 + +à¹à¸Ÿà¸˜à¸à¸™à¸•à¸´à¸ª +MC-25 + +ธà¸à¹€à¸¡à¹€à¸‹à¸µà¸¢ +MC-26 + +à¸à¸²à¸£à¹Œà¸ˆà¸µà¹€à¸£ +MC-27 + +โนà¸à¸²à¸„ิส +MC-28 + +เฮลลาส +MC-29 + +เà¸à¸£à¸´à¹€à¸”เนีย +MC-30 + +à¹à¸¡à¸£à¹Œà¸à¸à¸ªà¹€à¸—รล + + +ภูมิประเทศจาà¸à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™[à¹à¸à¹‰] +หลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸šà¸à¸™à¹€à¸™à¸§à¸´à¸¥à¸¥à¹Œà¹à¸¥à¸°à¸à¸²à¸™à¸Šà¹ˆà¸§à¸¢à¸¥à¸‡à¸ˆà¸à¸”ขà¸à¸‡à¸¢à¸²à¸™à¹‚รเวà¸à¸£à¹Œà¸ªà¸›à¸´à¸£à¸´à¸• + +ภูมิประเทศขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีà¸à¸²à¸£à¹à¸¢à¸à¸à¸à¸à¹€à¸›à¹‡à¸™à¸ªà¸à¸‡à¸¥à¸±à¸à¸©à¸“ะà¸à¸¢à¹ˆà¸²à¸‡à¹‚ดดเด่นคืภพื้นที่ราบà¹à¸šà¸™à¸ˆà¸²à¸à¸à¸²à¸£à¹„หลขà¸à¸‡à¸¥à¸²à¸§à¸²à¸—างซีà¸à¹€à¸«à¸™à¸·à¸à¸‹à¸¶à¹ˆà¸‡à¸œà¸´à¸”à¹à¸œà¸à¹€à¸”่นชัดจาà¸à¸—ี่ราบสูงà¸à¸±à¸™à¸à¸¸à¸”มไปด้วยหลุมเล็à¸à¸«à¸¥à¸¸à¸¡à¸™à¹‰à¸à¸¢à¸ˆà¸²à¸à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸¡à¸²à¹à¸•à¹ˆà¸„รั้งโบราณà¸à¸²à¸¥à¸—างซีà¸à¹ƒà¸•à¹‰ à¸à¸²à¸£à¸§à¸´à¸ˆà¸±à¸¢à¹ƒà¸™à¸›à¸µ 2008 (พ.ศ. 2551) à¹à¸ªà¸”งหลัà¸à¸à¸²à¸™à¹‚น้มเà¸à¸µà¸¢à¸‡à¹„ปยังทฤษฎีที่เสนà¸à¸‚ึ้นในปี 1980 (พ.ศ. 2523) ซึ่งà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² ราวสี่พันล้านปีà¸à¹ˆà¸à¸™ ซีà¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยวัตถุขนาดใหà¸à¹ˆà¸£à¸²à¸§à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¸ªà¸´à¸šà¸–ึงสà¸à¸‡à¹ƒà¸™à¸ªà¸²à¸¡à¸‚à¸à¸‡à¸”วงจันทร์ขà¸à¸‡à¹‚ลภถ้าทฤษฎีนี้เป็นจริงย่à¸à¸¡à¸—ำให้ซีà¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเป็นตำà¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¸«à¸¥à¸¸à¸¡à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸”้วยขนาดยาว 10,600 à¸à¸´à¹‚ลเมตร à¹à¸¥à¸°à¸à¸§à¹‰à¸²à¸‡ 8,500 à¸à¸´à¹‚ลเมตร (6,600 x 5,300 ไมล์) หรืà¸à¹‚ดยคร่าว ๆ à¹à¸¥à¹‰à¸§à¹€à¸—่าà¸à¸±à¸šà¸žà¸·à¹‰à¸™à¸—ี่ขà¸à¸‡à¸¢à¸¸à¹‚รป เà¸à¹€à¸Šà¸µà¸¢ à¹à¸¥à¸°à¸à¸à¸ªà¹€à¸•à¸£à¹€à¸¥à¸µà¸¢à¸—ั้งหมดรวมà¸à¸±à¸™ มีขนาดใหà¸à¹ˆà¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¹à¸à¹ˆà¸‡à¹„à¸à¸•à¹Œà¹€à¸„็น-ขั้วใต้ขà¸à¸‡à¸”วงจันทร์à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸«à¸¥à¸¸à¸¡à¸•à¸à¸à¸£à¸°à¸—บที่ใหà¸à¹ˆà¸—ี่สุดในระบบสุริยะ[16][17] +รà¸à¸¢à¸ˆà¸²à¸à¸”าวเคราะห์น้à¸à¸¢à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸”าวà¸à¸±à¸‡à¸„ารที่ใหม่มาภ3°20′N 219°23′E / 3.34°N 219.38°E - ซ้าย-à¸à¹ˆà¸à¸™/27 มีนาคม & ขวา-หลัง/28 มีนาคม 2012 (MRO)[118] + +ดาวà¸à¸±à¸‡à¸„ารมีรà¸à¸¢à¸•à¸³à¸«à¸™à¸´à¸‚à¸à¸‡à¸«à¸¥à¸¸à¸¡à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸¡à¸²à¸à¸¡à¸²à¸¢ เฉพาะที่มีเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸² 5 à¸à¸´à¹‚ลเมตร (3.1 ไมล์) ขึ้นไป พบว่ามีจำนวนรวมà¸à¸§à¹ˆà¸² 43,000 à¹à¸«à¹ˆà¸‡[119] หลุมใหà¸à¹ˆà¸—ี่สุดที่มีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¹à¸¥à¹‰à¸§à¸„ืà¸à¹à¸à¹ˆà¸‡à¸•à¸à¸à¸£à¸°à¸—บเฮลลาส ภูมิประเทศà¸à¸±à¸¥à¹€à¸šà¹‚ดจางมà¸à¸‡à¹€à¸«à¹‡à¸™à¹„ด้ชัดเจนจาà¸à¹‚ลà¸[120] จาà¸à¸à¸²à¸£à¸—ี่ดาวà¸à¸±à¸‡à¸„ารมีมวลน้à¸à¸¢ ความน่าจะเป็นที่จะถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸ˆà¸²à¸à¸§à¸±à¸•à¸–ุต่าง ๆ จึงà¸à¸¢à¸¹à¹ˆà¸£à¸²à¸§à¸„รึ่งหนึ่งขà¸à¸‡à¹‚ลภà¹à¸•à¹ˆà¸”้วยตำà¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารซึ่งใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¹à¸–บดาวเคราะห์น้à¸à¸¢ ฉะนั้นจึงมีโà¸à¸à¸²à¸ªà¸¡à¸²à¸à¸‚ึ้นที่จะโดนจู่โจมโดยวัตถุมาà¸à¸¡à¸²à¸¢à¸ˆà¸²à¸à¹à¸–บดังà¸à¸¥à¹ˆà¸²à¸§ ดาวà¸à¸±à¸‡à¸„ารยังคล้ายว่าจะถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยดาวหางคาบสั้นà¸à¸¢à¸¹à¹ˆà¸šà¹ˆà¸à¸¢à¸„รั้งà¸à¸µà¸à¸”้วย à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™à¸à¸¥à¸¸à¹ˆà¸¡à¸—ี่à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸§à¸‡à¹‚คจรขà¸à¸‡à¸”าวพฤหัสบดี[121] นà¸à¸à¹€à¸«à¸™à¸·à¸à¸ˆà¸²à¸à¸™à¸µà¹‰ หลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸—ี่พบบนดาวà¸à¸±à¸‡à¸„ารเมื่à¸à¹€à¸—ียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§à¸¢à¸±à¸‡à¸™à¹‰à¸à¸¢à¸à¸§à¹ˆà¸²à¸—ี่พบบนดวงจันทร์ค่à¸à¸™à¸‚้างมาภเพราะบรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารสามารถปà¸à¸›à¹‰à¸à¸‡à¸•à¹‰à¸²à¸™à¸—านต่à¸à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็à¸à¹„ด้ หลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸šà¸²à¸‡à¹à¸«à¹ˆà¸‡à¸¡à¸µà¸¥à¸±à¸à¸©à¸“ะทางสัณà¸à¸²à¸™à¸§à¸´à¸—ยาที่à¹à¸ªà¸”งว่าพื้นบริเวณนั้นเปียà¸à¸Šà¸·à¹‰à¸™à¸ ายหลังจาà¸à¸—ี่à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹à¸¥à¹‰à¸§[122] +ภูเขาไฟ[à¹à¸à¹‰] +ภาพโà¸à¸¥à¸´à¸¡à¸›à¸±à¸ªà¸¡à¸à¸™à¸ªà¹Œà¸ˆà¸²à¸à¸¢à¸²à¸™à¹„วà¸à¸´à¸‡à¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œ +ดูบทความหลัà¸à¸—ี่: ภูเขาไฟบนดาวà¸à¸±à¸‡à¸„าร + +ภูเขาไฟรูปโล่โà¸à¸¥à¸´à¸¡à¸›à¸±à¸ªà¸¡à¸à¸™à¸ªà¹Œ (เมาท์โà¸à¸¥à¸´à¸¡à¸›à¸±à¸ª) เป็นภูเขาไฟที่ดับà¹à¸¥à¹‰à¸§à¹ƒà¸™à¸šà¸£à¸´à¹€à¸§à¸“ธาร์ซิส พื้นที่ราบสูงà¸à¸§à¹‰à¸²à¸‡à¹ƒà¸«à¸à¹ˆà¸‹à¸¶à¹ˆà¸‡à¸¢à¸±à¸‡à¸¡à¸µà¸ ูเขาไฟขนาดใหà¸à¹ˆà¸à¸·à¹ˆà¸™à¸à¸µà¸à¸«à¸¥à¸²à¸¢à¸¥à¸¹à¸ โà¸à¸¥à¸´à¸¡à¸›à¸±à¸ªà¸¡à¸à¸™à¸ªà¹Œà¸¡à¸µà¸„วามสูงโดยประมาณà¸à¸§à¹ˆà¸²à¸ªà¸²à¸¡à¹€à¸—่าขà¸à¸‡à¸„วามสูงขà¸à¸‡à¹€à¸‚าเà¸à¹€à¸§à¸à¹€à¸£à¸ªà¸•à¹Œà¸‹à¸¶à¹ˆà¸‡à¹€à¸—ียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§à¸ªà¸¹à¸‡à¹€à¸žà¸µà¸¢à¸‡ 8.8 à¸à¸´à¹‚ลเมตร (5.5 ไมล์)[123] ทำให้ภูเขาไฟลูà¸à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¹€à¸‚าที่สูงที่สุดหรืà¸à¸ªà¸¹à¸‡à¹€à¸›à¹‡à¸™à¸à¸±à¸™à¸”ับสà¸à¸‡à¹ƒà¸™à¸£à¸°à¸šà¸šà¸ªà¸¸à¸£à¸´à¸¢à¸°à¸‚ึ้นà¸à¸¢à¸¹à¹ˆà¸à¸±à¸šà¸§à¸´à¸˜à¸µà¸à¸²à¸£à¸§à¸±à¸”ซึ่งà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™à¸à¸à¸à¹„ป ทำให้ได้ค่าตัวเลขตั้งà¹à¸•à¹ˆ 21 ถึง 27 à¸à¸´à¹‚ลเมตร (13 ถึง 17 ไมล์)[124][125] +ตำà¹à¸«à¸™à¹ˆà¸‡à¸˜à¸£à¸“ีภาค[à¹à¸à¹‰] +เวลส์มาริเนริส (2001 Mars Odyssey) + +เวลส์มาริเนริส (เป็นรูปละตินขà¸à¸‡ หุบเขามาริเนà¸à¸£à¹Œ หรืà¸à¸£à¸¹à¹‰à¸ˆà¸±à¸à¹ƒà¸™à¸Šà¸·à¹ˆà¸ à¸à¸°à¸à¸²à¸˜à¸²à¸”ีมà¸à¸™ ในà¹à¸œà¸™à¸—ี่คลà¸à¸‡à¹€à¸à¹ˆà¸²) เป็นหุบเขาขนาดใหà¸à¹ˆ มีความยาวร่วม 4,000 à¸à¸´à¹‚ลเมตร (2,500 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามลึà¸à¹„ด้มาà¸à¸–ึง 7 à¸à¸´à¹‚ลเมตร (4.3 ไมล์) ความยาวขà¸à¸‡à¹€à¸§à¸¥à¸ªà¹Œà¸¡à¸²à¸£à¸´à¹€à¸™à¸£à¸´à¸ªà¹€à¸—ียบเท่าà¸à¸±à¸šà¸„วามยาวขà¸à¸‡à¸—วีปยุโรปà¹à¸¥à¸°à¸—à¸à¸”ยาวà¸à¸´à¸™à¸£à¸°à¸¢à¸°à¸—างà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¸«à¹‰à¸²à¸‚à¸à¸‡à¹€à¸ªà¹‰à¸™à¸£à¸à¸šà¸§à¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร หาà¸à¹€à¸—ียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§ à¹à¸à¸£à¸™à¸”์à¹à¸„นยà¸à¸™à¸šà¸™à¹‚ลà¸à¸¡à¸µà¸„วามยาวเพียง 446 à¸à¸´à¹‚ลเมตร (277 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามลึà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸à¸·à¸à¸š 2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) เท่านั้น เวลส์มาริเนริสà¸à¸³à¹€à¸™à¸´à¸”ขึ้นจาà¸à¸à¸²à¸£à¸›à¸¹à¸”นูนขึ้นขà¸à¸‡à¸žà¸·à¹‰à¸™à¸—ี่ธาร์ซิสจนเป็นสาเหตุให้เปลืà¸à¸à¸”าวเคราะห์ในพื้นที่เวลส์มาริเนริสà¹à¸•à¸à¸—ลายà¸à¸à¸ มีà¸à¸²à¸£à¹€à¸ªà¸™à¸à¹ƒà¸™à¸›à¸µ 2012 (พ.ศ. 2555) ว่าเวลส์มาริเนริสไม่ได้เป็นเพียงà¹à¸„่à¸à¸£à¸²à¹€à¸šà¸™à¹à¸•à¹ˆà¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸‚à¸à¸šà¹€à¸‚ตระหว่างà¹à¸œà¹ˆà¸™à¹€à¸›à¸¥à¸·à¸à¸à¸”าวที่ปราà¸à¸à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸•à¸±à¸§à¹à¸šà¸šà¹€à¸¥à¸·à¹ˆà¸à¸™à¸œà¹ˆà¸²à¸™à¸à¸±à¸™à¸à¸§à¹ˆà¸² 150 à¸à¸´à¹‚ลเมตร (93 ไมล์) ทำให้ดาวà¸à¸±à¸‡à¸„ารเป็นดาวเคราะห์ที่à¸à¸²à¸ˆà¸ˆà¸°à¸¡à¸µà¸à¸²à¸£à¸§à¸²à¸‡à¸•à¸±à¸§à¸‚à¸à¸‡à¹à¸œà¹ˆà¸™à¸˜à¸£à¸“ีภาคเป็นสà¸à¸‡à¹à¸œà¹ˆà¸™[126][127] +หลุมโพรง[à¹à¸à¹‰] + +ภาพจาà¸à¹€à¸˜à¸¡à¸´à¸ªà¸«à¸£à¸·à¸à¸£à¸°à¸šà¸šà¸–่ายภาพจาà¸à¸à¸²à¸£à¸›à¸¥à¹ˆà¸à¸¢à¸„วามร้à¸à¸™à¸‹à¸¶à¹ˆà¸‡à¸à¸¢à¸¹à¹ˆà¸šà¸™à¸¢à¸²à¸™ 2001 มาร์สโà¸à¸”ิสซีขà¸à¸‡à¸™à¸²à¸‹à¸² ได้เผยให้เห็นถึงปาà¸à¸—างเข้าถ้ำที่เป็นไปได้เจ็ดà¹à¸«à¹ˆà¸‡à¸šà¸£à¸´à¹€à¸§à¸“ด้านข้างขà¸à¸‡à¸ ูเขาไฟà¸à¸²à¸£à¹Œà¹€à¸‹à¸µà¸¢à¸¡à¸à¸™à¸ªà¹Œ[128] มีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸à¸–้ำเหล่านี้ตามชื่à¸à¸‚à¸à¸‡à¸„นรัà¸à¹à¸•à¹ˆà¸¥à¸°à¸„นขà¸à¸‡à¸šà¸£à¸£à¸”าผู้คนพบซึ่งเรียà¸à¸£à¸§à¸¡ ๆ à¸à¸±à¸™à¸§à¹ˆà¸² "น้à¸à¸‡à¸ªà¸²à¸§à¸—ั้งเจ็ด"[129] ปาà¸à¸—างเข้าถ้ำมีความà¸à¸§à¹‰à¸²à¸‡à¸§à¸±à¸”ได้ตั้งà¹à¸•à¹ˆ 100 ไปจนถึง 252 เมตร (328 ถึง 827 ฟุต) à¹à¸¥à¸°à¹€à¸Šà¸·à¹ˆà¸à¸§à¹ˆà¸²à¸¡à¸µà¸„วามลึà¸à¸à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸à¸¢ 73 ถึง 96 เมตร (240 ถึง 315 ฟุต) เนื่à¸à¸‡à¸ˆà¸²à¸à¹à¸ªà¸‡à¹„ม่สามารถส่à¸à¸‡à¸¥à¸‡à¸–ึงพื้นขà¸à¸‡à¹€à¸à¸·à¸à¸šà¸—ุà¸à¸–้ำ จึงเป็นไปได้ว่าตัวถ้ำà¸à¸²à¸ˆà¸—à¸à¸”ยาวลึà¸à¹€à¸‚้าไปมาà¸à¸à¸§à¹ˆà¸²à¸„่าขั้นต่ำที่ประเมินไว้à¹à¸¥à¸°à¸à¸²à¸ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸à¸à¸à¹ƒà¸•à¹‰à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ มีเฉพาะ "เดนา" เท่านั้นที่เป็นข้à¸à¸¢à¸à¹€à¸§à¹‰à¸™à¹€à¸žà¸£à¸²à¸°à¸ªà¸²à¸¡à¸²à¸£à¸–มà¸à¸‡à¹€à¸«à¹‡à¸™à¸žà¸·à¹‰à¸™à¸–้ำà¹à¸¥à¸°à¸§à¸±à¸”ความลึà¸à¹„ด้เท่าà¸à¸±à¸š 130 เมตร (430 ฟุต) ภายในถ้ำโพรงเหล่านี้น่าจะเป็นบริเวณที่ปลà¸à¸”ภัยจาà¸à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็ภรังสีà¸à¸±à¸¥à¸•à¸£à¸²à¹„วโà¸à¹€à¸¥à¸• เปลวสุริยะ à¹à¸¥à¸°à¸à¸™à¸¸à¸ าคพลังงานสูงต่าง ๆ ที่à¸à¸£à¸°à¸«à¸™à¹ˆà¸³à¸Šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸‚à¸à¸‡à¸”าวเคราะห์[130] +บรรยาà¸à¸²à¸¨[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: บรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร +บรรยาà¸à¸²à¸¨à¸—ี่หลุดหนีไปจาà¸à¸”าวà¸à¸±à¸‡à¸„าร (คาร์บà¸à¸™ à¸à¸à¸à¸‹à¸´à¹€à¸ˆà¸™ à¹à¸¥à¸°à¹„ฮโดรเจน) โดยเมเว็นในรังสียูวี[131] + +ดาวà¸à¸±à¸‡à¸„ารสูà¸à¹€à¸ªà¸µà¸¢à¹à¸¡à¹‡à¸à¸™à¸µà¹‚ตสเฟียร์ไปเมื่à¸à¸ªà¸µà¹ˆà¸žà¸±à¸™à¸¥à¹‰à¸²à¸™à¸›à¸µà¸à¹ˆà¸à¸™[132] à¸à¸²à¸ˆà¹€à¸žà¸£à¸²à¸°à¸à¸²à¸£à¸Šà¸™à¸¡à¸²à¸à¸¡à¸²à¸¢à¸«à¸¥à¸²à¸¢à¸„รั้งโดยดาวเคราะห์น้à¸à¸¢[133] ทำให้ลมสุริยะมีปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸à¸£à¸°à¸—บโดยตรงà¸à¸±à¸šà¹„à¸à¹‚à¸à¹‚นสเฟียร์ขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ลดความหนาà¹à¸™à¹ˆà¸™à¸‚à¸à¸‡à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸¥à¸‡à¹„ปเรื่à¸à¸¢ ๆ โดยปà¸à¸à¹€à¸›à¸¥à¸·à¹‰à¸à¸‡à¸à¸°à¸•à¸à¸¡à¸ˆà¸²à¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸Šà¸±à¹‰à¸™à¸™à¸à¸à¹ƒà¸«à¹‰à¸«à¸¥à¸¸à¸”ลà¸à¸¢à¸à¸à¸à¹„ป ทั้งมาร์สโà¸à¸¥à¸šà¸à¸¥à¹€à¸‹à¸à¸£à¹Œà¹€à¸§à¹€à¸¢à¸à¸£à¹Œà¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ªà¸•à¹ˆà¸²à¸‡à¸à¹‡à¸•à¸£à¸§à¸ˆà¸žà¸šà¸à¸™à¸¸à¸ าคขà¸à¸‡à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่à¹à¸•à¸à¸•à¸±à¸§à¹€à¸›à¹‡à¸™à¸›à¸£à¸°à¸ˆà¸¸à¸¥à¸²à¸à¹€à¸›à¹‡à¸™à¸«à¸²à¸‡à¸¢à¸²à¸§à¹ƒà¸™à¸«à¹‰à¸§à¸‡à¸à¸§à¸à¸²à¸¨à¹€à¸šà¸·à¹‰à¸à¸‡à¸«à¸¥à¸±à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[132][134] à¹à¸¥à¸°à¸à¸²à¸£à¸ªà¸¹à¸à¹€à¸ªà¸µà¸¢à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¹„ปนี้à¸à¸³à¸¥à¸±à¸‡à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¹‚ดยยานเมเว็น เมื่à¸à¹€à¸—ียบà¸à¸±à¸šà¹‚ลà¸à¹à¸¥à¹‰à¸§à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเบาบางà¸à¸§à¹ˆà¸²à¸¡à¸²à¸ ความà¸à¸”à¸à¸²à¸à¸²à¸¨à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ ณ ปัจจุบันà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¸•à¸±à¹‰à¸‡à¹à¸•à¹ˆà¸™à¹‰à¸à¸¢à¸ªà¸¸à¸”ที่ 30 ปาสà¸à¸²à¸¥ (0.030 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) บนยà¸à¸”โà¸à¸¥à¸´à¸¡à¸›à¸±à¸ªà¸¡à¸à¸™à¸ªà¹Œ ไปจนถึง 1,155 ปาสà¸à¸²à¸¥ (1.155 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) ในเฮลลาสเพลนิเชีย โดยมีความà¸à¸”à¸à¸²à¸à¸²à¸¨à¹€à¸‰à¸¥à¸µà¹ˆà¸¢à¸—ี่ระดับพื้นผิวเท่าà¸à¸±à¸š 600 ปาสà¸à¸²à¸¥ (0.60 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥)[135] ความหนาà¹à¸™à¹ˆà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸ªà¸¹à¸‡à¸ªà¸¸à¸”บนดาวà¸à¸±à¸‡à¸„ารมีค่าเทียบเท่าà¸à¸±à¸šà¸„วามดัน ณ จุดที่สูง 35 à¸à¸´à¹‚ลเมตร (22 ไมล์)[136] เหนืà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¹‚ลภเป็นผลให้ความหนาà¹à¸™à¹ˆà¸™à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸„ิดเป็นเพียงร้à¸à¸¢à¸¥à¸° 0.6 ขà¸à¸‡à¹‚ลà¸à¹€à¸—่านั้น (101.3 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) มีมาตราความสูงขà¸à¸‡à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่ประมาณ 10.8 à¸à¸´à¹‚ลเมตร (6.7 ไมล์)[137] ซึ่งสูงà¸à¸§à¹ˆà¸²à¹‚ลภ(6 à¸à¸´à¹‚ลเมตร (3.7 ไมล์)) เพราะความโน้มถ่วงที่พื้นผิวดาวà¸à¸±à¸‡à¸„ารมีค่าเพียงร้à¸à¸¢à¸¥à¸° 38 ขà¸à¸‡à¹‚ลภรวมถึงผลชดเชยจาà¸à¸—ั้งà¸à¸²à¸£à¸¡à¸µà¸à¸¸à¸“หภูมิต่ำà¹à¸¥à¸°à¸™à¹‰à¸³à¸«à¸™à¸±à¸à¹‚มเลà¸à¸¸à¸¥à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²à¸„่าเฉลี่ยร้à¸à¸¢à¸¥à¸° 50 ขà¸à¸‡à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸”าวà¸à¸±à¸‡à¸„าร +บรรยาà¸à¸²à¸¨à¸—ี่เบาบางขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร มà¸à¸‡à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¸‚à¸à¸šà¸Ÿà¹‰à¸² + +บรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารประà¸à¸à¸šà¸”้วยคาร์บà¸à¸™à¹„ดà¸à¸à¸à¹„ซด์ร้à¸à¸¢à¸¥à¸° 96 à¸à¸²à¸£à¹Œà¸à¸à¸™à¸£à¹‰à¸à¸¢à¸¥à¸° 1.93 à¹à¸¥à¸°à¹„นโตรเจนร้à¸à¸¢à¸¥à¸° 1.89 ร่วมไปà¸à¸±à¸šà¸à¸à¸à¸‹à¸´à¹€à¸ˆà¸™à¹à¸¥à¸°à¸™à¹‰à¸³à¹ƒà¸™à¸›à¸£à¸´à¸¡à¸²à¸“เล็à¸à¸™à¹‰à¸à¸¢[6][138] บรรยาà¸à¸²à¸¨à¸¡à¸µà¸à¸¸à¹ˆà¸™à¸„่à¸à¸™à¸‚้างมาà¸à¹‚ดยเป็นà¸à¸™à¸¸à¸ าคขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 1.5 ไมโครเมตร ซึ่งทำให้ท้à¸à¸‡à¸Ÿà¹‰à¸²à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารดูเป็นสีน้ำตาลปนเหลืà¸à¸‡à¹€à¸¡à¸·à¹ˆà¸à¸¡à¸à¸‡à¸ˆà¸²à¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§[139] + +มีà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸¡à¸µà¹€à¸—นในบรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารโดยมีเศษส่วนโมลที่ประมาณ 30 ส่วนในพันล้านส่วน[14][140] พบปราà¸à¸à¹ƒà¸™à¸à¸²à¸£à¸žà¸¥à¸¹à¸¡à¸‚à¸à¸‡à¹à¸à¹Šà¸ªà¹à¸¥à¸°à¸ าวะà¸à¸²à¸£à¸“์à¹à¸ªà¸”งไปในทางว่ามีà¸à¸²à¸£à¸›à¸¥à¸”ปล่à¸à¸¢à¸¡à¸µà¹€à¸—นà¸à¸à¸à¸¡à¸²à¸ˆà¸²à¸à¹à¸–บท้à¸à¸‡à¸—ี่เฉพาะบางà¹à¸«à¹ˆà¸‡ ในช่วงà¸à¸¥à¸²à¸‡à¸¤à¸”ูร้à¸à¸™à¸‚à¸à¸‡à¸‹à¸µà¸à¹€à¸«à¸™à¸·à¸ à¸à¸²à¸£à¸žà¸¥à¸¹à¸¡à¸«à¸¥à¸±à¸à¸¡à¸µà¸›à¸£à¸´à¸¡à¸²à¸“มีเทนà¸à¸¢à¸¹à¹ˆà¸–ึง 19,000 เมตริà¸à¸•à¸±à¸™ คาดà¸à¸²à¸£à¸“์ว่าà¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”มีà¸à¸³à¸¥à¸±à¸‡à¸à¸²à¸£à¸›à¸¥à¸”ปล่à¸à¸¢à¸£à¸²à¸§ 0.6 à¸à¸´à¹‚ลà¸à¸£à¸±à¸¡à¸•à¹ˆà¸à¸§à¸´à¸™à¸²à¸—ี[141][142] ข้à¸à¸¡à¸¹à¸¥à¸—ี่พบชี้ว่าน่าจะมีบริเวณท้à¸à¸‡à¸—ี่ที่เป็นà¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”สà¸à¸‡à¹à¸«à¹ˆà¸‡ ศูนย์à¸à¸¥à¸²à¸‡à¹à¸«à¹ˆà¸‡à¹à¸£à¸à¸à¸¢à¸¹à¹ˆà¹ƒà¸à¸¥à¹‰ 30°N 260°W / 30°N 260°W à¹à¸¥à¸°à¹à¸«à¹ˆà¸‡à¸—ี่สà¸à¸‡à¹ƒà¸à¸¥à¹‰ 0°N 310°W / 0°N 310°W[141] ประมาณà¸à¸²à¸£à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารจะต้à¸à¸‡à¸¡à¸µà¸à¸²à¸£à¸œà¸¥à¸´à¸•à¸¡à¸µà¹€à¸—นปริมาณ 270 ตันต่à¸à¸›à¸µ[141][143] + +มีเทนสามารถà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸”าวà¸à¸±à¸‡à¸„ารได้เพียงเฉพาะช่วงเวลาจำà¸à¸±à¸”ระยะหนึ่งเท่านั้นà¸à¹ˆà¸à¸™à¸—ี่จะถูà¸à¸—ำลาย ประมาณว่ามีช่วงชีวิตยืนยาวได้ตั้งà¹à¸•à¹ˆ 0.6 ถึง 4 ปี[141][144] à¸à¸²à¸£à¸—ี่มีมีเทนดำรงà¸à¸¢à¸¹à¹ˆà¸—ั้ง ๆ ที่เป็นสารที่ช่วงชีวิตสั้นเช่นนี้จึงบ่งชี้ว่าจะต้à¸à¸‡à¸¡à¸µà¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¹à¸à¹Šà¸ªà¸”ังà¸à¸¥à¹ˆà¸²à¸§à¸—ี่ยังดำเนินà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™ ทั้งà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚à¸à¸‡à¸ ูเขาไฟ à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยดาวหาง à¹à¸¥à¸°à¸à¸²à¸£à¸¡à¸µà¸à¸¢à¸¹à¹ˆà¸‚à¸à¸‡à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸žà¸§à¸à¸ˆà¸¸à¸¥à¸Šà¸µà¸žà¸—ี่สร้างมีเทนล้วนเป็นà¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¸—ี่เป็นไปได้ นà¸à¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¡à¸µà¹€à¸—นยังสามารถผลิตขึ้นได้โดยà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—ี่ไม่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้à¸à¸‡à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸² เซà¸à¸£à¹Œà¹€à¸žà¸™à¸—ิไนเซชัน [b] (à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¹€à¸‹à¸à¸£à¹Œà¹€à¸žà¸™à¸—ีน) โดยà¸à¸²à¸¨à¸±à¸¢à¸™à¹‰à¸³ คาร์บà¸à¸™à¹„ดà¸à¸à¸à¹„ซด์ à¹à¸¥à¸°à¹à¸£à¹ˆà¹‚à¸à¸¥à¸´à¸§à¸µà¸™à¸‹à¸¶à¹ˆà¸‡à¸•à¹ˆà¸²à¸‡à¸à¹‡à¸žà¸šà¹„ด้ทั่วไปบนดาวà¸à¸±à¸‡à¸„าร[145] +à¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¹à¸¥à¸°à¸à¸±à¸à¹€à¸à¹‡à¸šà¸¡à¸µà¹€à¸—น (CH4) ที่มีศัà¸à¸¢à¸ าพบนดาวà¸à¸±à¸‡à¸„าร + +ยานสำรวจภาคพื้นคิวริà¸à¸à¸‹à¸´à¸•à¸µ ซึ่งลงจà¸à¸”บนดาวà¸à¸±à¸‡à¸„ารในเดืà¸à¸™à¸ªà¸´à¸‡à¸«à¸²à¸„ม 2012 (พ.ศ. 2555) นั้นมีความสามารถตรวจวัดเพื่à¸à¹à¸¢à¸à¹à¸¢à¸°à¸„วามà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸‚à¸à¸‡à¸¡à¸µà¹€à¸—นที่ได้จาà¸à¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”ที่ต่างà¸à¸±à¸™à¸à¸à¸à¸ˆà¸²à¸à¸à¸±à¸™à¹„ด้[146] à¹à¸•à¹ˆà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸à¸²à¸£à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ ารà¸à¸´à¸ˆà¸™à¸±à¹‰à¸™à¸ˆà¸°à¸Šà¸µà¹‰à¸‚าดได้จริง ๆ ว่าสิ่งมีชีวิตขนาดเล็à¸à¸ˆà¸´à¹‹à¸§à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารเป็นผู้ให้à¸à¸³à¹€à¸™à¸´à¸”มีเทน บรรดาสิ่งมีชีวิตเหล่านั้นà¸à¹‡à¹€à¸«à¸¡à¸·à¸à¸™à¸ˆà¸°à¸à¸¢à¸¹à¹ˆà¸•à¹ˆà¸³à¸¥à¸‡à¹„ปเบื้à¸à¸‡à¸¥à¹ˆà¸²à¸‡à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸™à¸à¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸šà¹€à¸‚ตที่ตัวยานจะเข้าถึง[147] à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”à¹à¸£à¸à¹‚ดยเครื่à¸à¸‡à¸§à¸±à¸”สเปà¸à¸•à¸£à¸±à¸¡à¹€à¸¥à¹€à¸‹à¸à¸£à¹Œà¹à¸šà¸šà¸›à¸£à¸±à¸šà¹„ด้à¹à¸ªà¸”งข้à¸à¸¡à¸¹à¸¥à¸§à¹ˆà¸²à¸¡à¸µà¸¡à¸µà¹€à¸—นต่ำà¸à¸§à¹ˆà¸² 5 ส่วนในพันล้านส่วน ณ จุดที่ทำà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”ในตำà¹à¸«à¸™à¹ˆà¸‡à¸¥à¸‡à¸ˆà¸à¸”[148][149][150][151] เมื่ภ19 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2013 (พ.ศ. 2556) นัà¸à¸§à¸´à¸—ยาศาสตร์นาซาได้เผยผลà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸„ืบหน้าจาà¸à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”โดยคิวริà¸à¸à¸‹à¸´à¸•à¸µ ว่า ตรวจไม่พบมีเทนในบรรยาà¸à¸²à¸¨à¹ƒà¸™à¸„่าà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸” 0.18±0.67 ส่วนในพันล้านส่วนปริมาตร สà¸à¸”คล้à¸à¸‡à¸à¸±à¸šà¸‚à¸à¸šà¹€à¸‚ตบนที่เฉพาะ 1.3 ส่วนในพันล้านส่วนปริมาตร (ขà¸à¸šà¹€à¸‚ตความเชื่à¸à¸¡à¸±à¹ˆà¸™à¸£à¹‰à¸à¸¢à¸¥à¸° 95) à¹à¸¥à¸°à¸ˆà¸²à¸à¸œà¸¥à¸¥à¸±à¸žà¸˜à¹Œà¸™à¸µà¹‰à¸—ำให้สรุปได้ว่าความเป็นไปได้ที่จะมีà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚à¸à¸‡à¸ˆà¸¸à¸¥à¸Šà¸µà¸žà¸—ี่สร้างมีเทนบนดาวà¸à¸±à¸‡à¸„ารในปัจจุบันนั้นลดลง[152][153][154] + +ยานมาร์สà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¸¡à¸´à¸Šà¸Šà¸±à¸™à¸‚à¸à¸‡à¸à¸´à¸™à¹€à¸”ียมีปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸„้นหามีเทนในบรรยาà¸à¸²à¸¨[155] ในขณะที่เà¸à¹‡à¸à¹‚ซมาร์สเทรซà¹à¸à¹Šà¸ªà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¸¡à¸µà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸‚ึ้นปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹ƒà¸™à¸›à¸µ 2016 (พ.ศ. 2559) เพื่à¸à¸¨à¸µà¸à¸©à¸²à¹ƒà¸«à¹‰à¹€à¸‚้าใจมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸¡à¸µà¹€à¸—นรวมไปถึงสารที่ได้จาà¸à¸à¸²à¸£à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¸‚à¸à¸‡à¸¡à¸µà¹€à¸—นด้วย เช่น ฟà¸à¸£à¹Œà¸¡à¸²à¸¥à¸”ีไฮด์ à¹à¸¥à¸°à¹€à¸¡à¸—านà¸à¸¥[156] + +ในวันที่ 16 ธันวาคม 2014 (พ.ศ. 2557) นาซารายงานว่ายานโรเวà¸à¸£à¹Œà¸„ิวริà¸à¸à¸‹à¸´à¸•à¸µ ตรวจพบปริมาณมีเทนในบรรยาà¸à¸²à¸¨à¸”าวà¸à¸±à¸‡à¸„ารเพิ่มสูงนับสิบเท่าในเฉพาะถิ่น ตัวà¸à¸¢à¹ˆà¸²à¸‡à¸—ี่ตรวจวัดได้ถืà¸à¸§à¹ˆà¸²à¸ªà¸¹à¸‡à¹€à¸›à¹‡à¸™à¸ªà¸´à¸šà¹€à¸—่าในรà¸à¸š 20 เดืà¸à¸™ à¹à¸ªà¸”งà¸à¸²à¸£à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นในปลายปี 2013 à¹à¸¥à¸°à¸•à¹‰à¸™à¸›à¸µ 2014 โดยมีค่าเฉลี่ยขà¸à¸‡à¸¡à¸µà¹€à¸—นเป็น 7 ส่วนในพันล้านส่วนในบรรยาà¸à¸²à¸¨ ซึ่งในเวลาà¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²à¸«à¸£à¸·à¸à¸«à¸¥à¸±à¸‡à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸„่าเฉลี่ยที่วัดได้à¸à¸¢à¸¹à¹ˆà¸›à¸£à¸°à¸¡à¸²à¸“หนึ่งในสิบขà¸à¸‡à¸„่าดังà¸à¸¥à¹ˆà¸²à¸§[157][158] + +มีà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¹à¸à¸¡à¹‚มเนียà¸à¸¢à¹ˆà¸²à¸‡à¸„ร่าว ๆ บนดาวà¸à¸±à¸‡à¸„ารà¹à¸¥à¹‰à¸§à¹€à¸Šà¹ˆà¸™à¸à¸±à¸™à¹‚ดยยานดาวเทียมมาร์สเà¸à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª à¹à¸•à¹ˆà¸”้วยความที่เป็นสารช่วงชีวิตค่à¸à¸™à¸‚้างสั้นจึงไม่เป็นที่à¹à¸™à¹ˆà¸Šà¸±à¸”ว่าถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸¡à¸²à¸ˆà¸²à¸à¸à¸°à¹„ร[159] à¹à¸à¸¡à¹‚มเนียนั้นไม่เสถียรในบรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร à¹à¸¥à¸°à¸ˆà¸°à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¹„ปในเวลาเพียงไม่à¸à¸µà¹ˆà¸Šà¸±à¹ˆà¸§à¹‚มง à¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”หนึ่งที่น่าจะเป็นไปได้คืà¸à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚à¸à¸‡à¸ ูเขาไฟ[159] +à¸à¸à¹‚รรา[à¹à¸à¹‰] + +ในปี 1994 (พ.ศ. 2537) ยานà¸à¸§à¸à¸²à¸¨à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ªà¸‚à¸à¸‡à¸à¸‡à¸„์à¸à¸²à¸£à¸à¸§à¸à¸²à¸¨à¸¢à¸¸à¹‚รปพบà¸à¸²à¸£à¹€à¸£à¸·à¸à¸‡à¹à¸ªà¸‡à¸à¸±à¸¥à¸•à¸£à¸²à¹„วโà¸à¹€à¸¥à¸•à¸ˆà¸²à¸ "ร่มà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸" ในซีà¸à¹ƒà¸•à¹‰à¸‚à¸à¸‡à¸”าว ดาวà¸à¸±à¸‡à¸„ารไม่มีสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸—ี่ครà¸à¸šà¸„ลุมทั้งดาวซึ่งจะนำทางà¸à¸™à¸¸à¸ าคมีประจุทั้งหลายให้เข้าสู่ชั้นบรรยาà¸à¸²à¸¨ ดาวà¸à¸±à¸‡à¸„ารมีสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸£à¸¹à¸›à¸£à¹ˆà¸¡à¸à¸¢à¸¹à¹ˆà¸«à¸¥à¸²à¸¢à¹à¸«à¹ˆà¸‡ ส่วนใหà¸à¹ˆà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸‹à¸µà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¹€à¸›à¹‡à¸™à¸‹à¸²à¸à¸«à¸¥à¸‡à¹€à¸«à¸¥à¸·à¸à¸‚à¸à¸‡à¸ªà¸™à¸²à¸¡à¸‹à¸¶à¹ˆà¸‡à¹€à¸„ยครà¸à¸šà¸„ลุมทั้งพิภพดาวà¹à¸•à¹ˆà¹€à¸ªà¸·à¹ˆà¸à¸¡à¸ªà¸¥à¸²à¸¢à¹„ปเมื่à¸à¸«à¸¥à¸²à¸¢à¸žà¸±à¸™à¸¥à¹‰à¸²à¸™à¸›à¸µà¸à¹ˆà¸à¸™ + +ในปลายเดืà¸à¸™à¸˜à¸±à¸™à¸§à¸²à¸„ม 2014 (พ.ศ. 2557) ยานà¸à¸§à¸à¸²à¸¨à¹€à¸¡à¹€à¸§à¹‡à¸™à¸‚à¸à¸‡à¸™à¸²à¸‹à¸²à¸•à¸£à¸§à¸ˆà¸žà¸šà¸«à¸¥à¸±à¸à¸à¸²à¸™à¸à¸²à¸£à¹à¸œà¹ˆà¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸›à¹‡à¸™à¸šà¸£à¸´à¹€à¸§à¸“à¸à¸§à¹‰à¸²à¸‡à¸‚à¸à¸‡à¸à¸à¹‚รราบนซีà¸à¹€à¸«à¸™à¸·à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร à¹à¸¥à¸°à¸—à¸à¸”ต่ำลงถึงละติจูดประมาณ 20-30 à¸à¸‡à¸¨à¸²à¹€à¸«à¸™à¸·à¸à¸ˆà¸²à¸à¹€à¸ªà¹‰à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£à¸”าวà¸à¸±à¸‡à¸„าร ในขณะที่à¸à¸à¹‚รราบนโลà¸à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸£à¸°à¸¢à¸°à¸ªà¸¹à¸‡ 100 ถึง 500 à¸à¸´à¹‚ลเมตรจาà¸à¸œà¸´à¸§à¸”าวเคราะห์ à¹à¸•à¹ˆà¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารà¸à¸™à¸¸à¸ าคที่à¸à¹ˆà¸à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”à¸à¸à¹‚รราทะลวงผ่านบรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวเข้ามาสร้างà¸à¸à¹‚รราขึ้นในระดับต่ำà¸à¸§à¹ˆà¸² 100 à¸à¸´à¹‚ลเมตรจาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ สนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹ƒà¸™à¸¥à¸¡à¸ªà¸¸à¸£à¸´à¸¢à¸°à¹‚à¸à¸šà¸„ลุมดาวà¸à¸±à¸‡à¸„าร เข้าสู่บรรยาà¸à¸²à¸¨ à¹à¸¥à¸°à¸à¸™à¸¸à¸ าคมีประจุตามเส้นà¹à¸£à¸‡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸‚à¸à¸‡à¸¥à¸¡à¸ªà¸¸à¸£à¸´à¸¢à¸°à¹€à¸‚้าสู่บรรยาà¸à¸²à¸¨à¸—ำให้à¸à¸à¹‚รราเà¸à¸´à¸”ขึ้นภายนà¸à¸à¸£à¹ˆà¸¡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸[160] + +วันที่ 18 มีนาคม 2015 (พ.ศ. 2558) นาซารายงานà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸à¸à¹‚รราที่ยังไม่เป็นที่เข้าใจà¹à¸™à¹ˆà¸Šà¸±à¸” à¹à¸¥à¸°à¹€à¸¡à¸†à¸à¸¸à¹ˆà¸™à¸—ี่ยังไมมีคำà¸à¸˜à¸´à¸šà¸²à¸¢à¸ ายในบรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[161] +ภูมิà¸à¸²à¸à¸²à¸¨[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ภูมิà¸à¸²à¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร +18 พฤศจิà¸à¸²à¸¢à¸™ 2012 +25 พฤศจิà¸à¸²à¸¢à¸™ 2012 +พายุà¸à¸¸à¹ˆà¸™à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„าร ยานà¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตีà¹à¸¥à¸°à¸¢à¸²à¸™à¸„ิวริà¸à¸à¸‹à¸´à¸•à¸µ มีเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¸à¸³à¸à¸±à¸š + +จาà¸à¸”าวเคราะห์ทั้งหมดในระบบสุริยะ ฤดูà¸à¸²à¸¥à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีความใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด เนื่à¸à¸‡à¸ˆà¸²à¸à¸„วามเà¸à¸µà¸¢à¸‡à¸‚à¸à¸‡à¹à¸à¸™à¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸‚à¸à¸‡à¸”าวทั้งสà¸à¸‡à¸—ี่คล้ายคลึงà¸à¸±à¸™ ระยะเวลาขà¸à¸‡à¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารมีความยาวประมาณสà¸à¸‡à¹€à¸—่าขà¸à¸‡à¸¤à¸”ูà¸à¸²à¸¥à¸šà¸™à¹‚ลภเพราะดาวà¸à¸±à¸‡à¸„ารมีระยะห่างจาà¸à¸”วงà¸à¸²à¸—ิตย์มาà¸à¸à¸§à¹ˆà¸² หนึ่งปีขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารจึงยาวนานร่วมสà¸à¸‡à¸›à¸µà¸‚à¸à¸‡à¹‚ลภà¸à¸¸à¸“หภูมิบนพื้นผิวดาวà¸à¸±à¸‡à¸„ารผันà¹à¸›à¸£à¸ˆà¸²à¸à¸„่าต่ำสุดที่ประมาณ -143 à¸à¸‡à¸¨à¸²à¹€à¸‹à¸¥à¹€à¸‹à¸µà¸¢à¸ª (-225 à¸à¸‡à¸¨à¸²à¸Ÿà¸²à¹€à¸£à¸™à¹„ฮต์) ที่บริเวณà¹à¸œà¹ˆà¸™à¸‚ั้วดาวในฤดูหนาว[8] จนถึงค่าสูงสุดที่ประมาณ 35 à¸à¸‡à¸¨à¸²à¹€à¸‹à¸¥à¹€à¸‹à¸µà¸¢à¸ª (95 à¸à¸‡à¸¨à¸²à¸Ÿà¸²à¹€à¸£à¸™à¹„ฮต์) ในฤดูร้à¸à¸™à¸šà¸£à¸´à¹€à¸§à¸“ศูนย์สูตร[9] à¸à¸²à¸£à¸¡à¸µà¸Šà¹ˆà¸§à¸‡à¸à¸¸à¸“หภูมิที่à¸à¸§à¹‰à¸²à¸‡à¸¡à¸²à¸à¹€à¸Šà¹ˆà¸™à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸œà¸¥à¸¡à¸²à¸ˆà¸²à¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่เบาบางจนไม่สามารถà¸à¸±à¸à¹€à¸à¹‡à¸šà¸„วามร้à¸à¸™à¸ˆà¸²à¸à¸”วงà¸à¸²à¸—ิตย์ได้มาà¸à¸™à¸±à¸ à¸à¸²à¸£à¸¡à¸µà¸„วามà¸à¸”à¸à¸²à¸à¸²à¸¨à¸—ี่ต่ำ à¹à¸¥à¸°à¸à¸²à¸£à¸—ี่มีค่าความเฉื่à¸à¸¢à¸„วามร้à¸à¸™à¸•à¹ˆà¸³à¸‚à¸à¸‡à¸”ินบนดาวà¸à¸±à¸‡à¸„าร[162] ระยะห่างจาà¸à¸”วงà¸à¸²à¸—ิตย์ถึงดาวà¸à¸±à¸‡à¸„ารคิดเป็น 1.52 เท่าเมื่à¸à¹€à¸—ียบà¸à¸±à¸šà¸£à¸°à¸¢à¸°à¸ˆà¸²à¸à¸”วงà¸à¸²à¸—ิตย์ถึงโลภทำให้ดาวà¸à¸±à¸‡à¸„ารได้รับà¹à¸ªà¸‡à¸ˆà¸²à¸à¸”วงà¸à¸²à¸—ิตย์เพียงร้à¸à¸¢à¸¥à¸° 43 ต่à¸à¸«à¸™à¹ˆà¸§à¸¢à¸žà¸·à¹‰à¸™à¸—ี่เมื่à¸à¹€à¸—ียบà¸à¸±à¸šà¹‚ลà¸[163] + +ถ้าหาà¸à¸”าวà¸à¸±à¸‡à¸„ารมีวงโคจรà¹à¸šà¸šà¹€à¸”ียวà¸à¸±à¸šà¹‚ลà¸à¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารà¸à¹‡à¸ˆà¸°à¹€à¸«à¸¡à¸·à¸à¸™à¹‚ลภà¹à¸•à¹ˆà¸à¸²à¸£à¸¡à¸µà¸„วามเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸‚à¸à¸‡à¸§à¸‡à¹‚คจรมาà¸à¸à¸§à¹ˆà¸²à¹€à¸¡à¸·à¹ˆà¸à¹€à¸›à¸£à¸µà¸¢à¸šà¸à¸±à¸™à¸™à¸µà¹‰à¹€à¸à¸‡à¸—ี่ส่งผลà¸à¸£à¸°à¸—บสำคัภดาวà¸à¸±à¸‡à¸„ารเข้าใà¸à¸¥à¹‰à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์ที่สุดเมื่à¸à¹€à¸›à¹‡à¸™à¸¤à¸”ูร้à¸à¸™à¹ƒà¸™à¸”าวซีà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸à¸à¹‡à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸¤à¸”ูหนาว à¹à¸¥à¸°à¹€à¸‚้าใà¸à¸¥à¹‰à¸ˆà¸¸à¸”ไà¸à¸¥à¸”วงà¸à¸²à¸—ิตย์ที่สุดเมื่à¸à¹€à¸›à¹‡à¸™à¸¤à¸”ูหนาวในดาวซีà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸à¸à¹‡à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸¤à¸”ูร้à¸à¸™ ผลที่ตามมาคืà¸à¸¤à¸”ูà¸à¸²à¸¥à¹ƒà¸™à¸”าวซีà¸à¹ƒà¸•à¹‰à¸ˆà¸°à¸£à¸¸à¸™à¹à¸£à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹à¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¹ƒà¸™à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸à¸ˆà¸°à¸à¹ˆà¸à¸™à¹€à¸šà¸²à¸à¸§à¹ˆà¸²à¸à¸µà¸à¸‹à¸µà¸à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¹à¸•à¹ˆà¸¥à¸°à¸à¸£à¸“ี à¸à¸¸à¸“หภูมิในฤดูร้à¸à¸™à¸‚à¸à¸‡à¸”าวซีà¸à¹ƒà¸•à¹‰à¸ªà¸²à¸¡à¸²à¸£à¸–à¸à¸¸à¹ˆà¸™à¹„ด้มาà¸à¸à¸§à¹ˆà¸²à¸à¸¸à¸“หภูมิในฤดูร้à¸à¸™à¸‚à¸à¸‡à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸à¹„ด้ถึง 30 เคลวิน (30 à¸à¸‡à¸¨à¸²à¹€à¸‹à¸¥à¹€à¸‹à¸µà¸¢à¸ª หรืภ54 à¸à¸‡à¸¨à¸²à¸Ÿà¸²à¹€à¸£à¸™à¹„ฮต์)[164] + +ดาวà¸à¸±à¸‡à¸„ารมีพายุà¸à¸¸à¹ˆà¸™à¸—ี่ใหà¸à¹ˆà¸—ี่สุดในระบบสุริยะ มีได้ตั้งà¹à¸•à¹ˆà¸žà¸²à¸¢à¸¸à¹ƒà¸™à¸žà¸·à¹‰à¸™à¸—ี่เล็ภๆ ไปจนถึงพายุขนาดมโหฬารที่ครà¸à¸šà¸„ลุมทั่วทั้งดาวเคราะห์ พายุเหล่านี้มัà¸à¸ˆà¸°à¹€à¸à¸´à¸”ขึ้นเมื่à¸à¸”าวà¸à¸±à¸‡à¸„ารเข้าใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์à¹à¸¥à¸°à¹à¸ªà¸”งให้เห็นà¸à¸²à¸£à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นขà¸à¸‡à¸à¸¸à¸“หภูมิบนดาว[165] +วงโคจรà¹à¸¥à¸°à¸à¸²à¸£à¸«à¸¡à¸¸à¸™[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: วงโคจรขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร +ดาวà¸à¸±à¸‡à¸„ารห่างจาà¸à¸”วงà¸à¸²à¸—ิตย์ประมาณ 230 ล้านà¸à¸´à¹‚ลเมตร (143 ล้านไมล์) คาบà¸à¸²à¸£à¹‚คจรเท่าà¸à¸±à¸š 687 วัน (โลà¸) à¹à¸ªà¸”งด้วยวงสีà¹à¸”ง สีน้ำเงินคืà¸à¸§à¸‡à¹‚คจรโลภ+ +ดาวà¸à¸±à¸‡à¸„ารไà¸à¸¥à¸ˆà¸²à¸à¸”วงà¸à¸²à¸—ิตย์ด้วยระยะทางเฉลี่ย 230 ล้านà¸à¸´à¹‚ลเมตรโดยประมาณ (143 ล้านไมล์, 1.5 หน่วยดาราศาสตร์) à¹à¸¥à¸°à¸¡à¸µà¸„าบà¸à¸²à¸£à¹‚คจรเท่าà¸à¸±à¸š 687 วันขà¸à¸‡à¹‚ลภหนึ่งวันสุริยะบนดาวà¸à¸±à¸‡à¸„ารยาวà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™à¸‚à¸à¸‡à¹‚ลà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸à¸¢à¸„ืà¸à¹€à¸—่าà¸à¸±à¸š 24 ชั่วโมง 39 นาที 35.244 วินาที หนึ่งปีขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเท่าà¸à¸±à¸š 1.8809 ปีขà¸à¸‡à¹‚ลภหรืภ1 ปี 320 วัน à¸à¸±à¸šà¸à¸µà¸ 18.2 ชั่วโมง[6] + +ดาวà¸à¸±à¸‡à¸„ารมีความเà¸à¸µà¸¢à¸‡à¸‚à¸à¸‡à¹à¸à¸™à¹€à¸—่าà¸à¸±à¸š 25.19 à¸à¸‡à¸¨à¸² สัมพัทธ์à¸à¸±à¸šà¸£à¸°à¸™à¸²à¸šà¸à¸²à¸£à¹‚คจรซึ่งคล้ายคลึงà¸à¸±à¸šà¸„วามเà¸à¸µà¸¢à¸‡à¸‚à¸à¸‡à¹à¸à¸™à¹‚ลà¸[6] เป็นผลให้ดาวà¸à¸±à¸‡à¸„ารมีฤดูà¸à¸²à¸¥à¸„ล้ายโลà¸à¹à¸¡à¹‰à¸§à¹ˆà¸²à¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูบนดาวà¸à¸±à¸‡à¸„ารจะยาวเà¸à¸·à¸à¸šà¸ªà¸à¸‡à¹€à¸—่าเพราะคาบà¸à¸²à¸£à¹‚คจรที่ยาวนานà¸à¸§à¹ˆà¸² ณ ปัจจุบัน ขั้วเหนืà¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีà¸à¸²à¸£à¸§à¸²à¸‡à¸•à¸±à¸§à¸Šà¸µà¹‰à¹„ปใà¸à¸¥à¹‰à¸à¸±à¸šà¸”าวฤà¸à¸©à¹Œà¹€à¸”เนบ[11] ดาวà¸à¸±à¸‡à¸„ารผ่านจุดไà¸à¸¥à¸”วงà¸à¸²à¸—ิตย์ที่สุดในเดืà¸à¸™à¸à¸¸à¸¡à¸ าพันธ์ 2012 (พ.ศ. 2555)[166][167] ผ่านจุดใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์ที่สุดในเดืà¸à¸™à¸¡à¸à¸£à¸²à¸„ม 2013 (พ.ศ. 2556)[166] จุดไà¸à¸¥à¸”วงà¸à¸²à¸—ิตย์ที่สุดถัดไปคืà¸à¸¡à¸à¸£à¸²à¸„ม 2014 (พ.ศ. 2557)[166] à¹à¸¥à¸°à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์ที่สุดถัดไปคืà¸à¸˜à¸±à¸™à¸§à¸²à¸„มปีเดียวà¸à¸±à¸™[166] + +ดาวà¸à¸±à¸‡à¸„ารมีความเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸‚à¸à¸‡à¸§à¸‡à¹‚คจรค่à¸à¸™à¸‚้างเด่นชัดที่ประมาณ 0.09 เมื่à¸à¹€à¸—ียบà¸à¸±à¸šà¸”าวเคราะห์à¸à¸·à¹ˆà¸™à¸à¸µà¸à¹€à¸ˆà¹‡à¸”ดวงในระบบสุริยะà¹à¸¥à¹‰à¸§ มีเพียงดาวพุธเท่านั้นที่มีความเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸‚à¸à¸‡à¸§à¸‡à¹‚คจรมาà¸à¸à¸§à¹ˆà¸² เป็นที่ทราบว่าในà¸à¸”ีตดาวà¸à¸±à¸‡à¸„ารมีวงโคจรที่à¸à¸¥à¸¡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹ƒà¸™à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸¡à¸²à¸ ที่ขณะหนึ่งเมื่ภ1.35 ล้านปีà¸à¹ˆà¸à¸™ ดาวà¸à¸±à¸‡à¸„ารมีความเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸—ี่ราว 0.002 ซึ่งน้à¸à¸¢à¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¹‚ลà¸à¹ƒà¸™à¸•à¸à¸™à¸™à¸µà¹‰[168] วัà¸à¸ˆà¸±à¸à¸£à¸„วามเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารà¸à¸¢à¸¹à¹ˆà¸—ี่ 96,000 ปีโลภเทียบà¸à¸±à¸šà¹‚ลà¸à¸—ี่วัà¸à¸ˆà¸±à¸à¸£à¹€à¸”ียวà¸à¸±à¸™à¸à¸¢à¸¹à¹ˆà¸—ี่ 100,000 ปี[169] ดาวà¸à¸±à¸‡à¸„ารยังมีวัà¸à¸ˆà¸±à¸à¸£à¸„วามเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸à¸µà¸à¹à¸šà¸šà¸«à¸™à¸¶à¹ˆà¸‡à¸—ี่à¸à¸´à¸™à¹€à¸§à¸¥à¸²à¸¢à¸²à¸§à¸™à¸²à¸™à¸à¸§à¹ˆà¸²à¸™à¸µà¹‰à¸”้วยคาบราว 2.2 ล้านปีโลภซึ่งมีความสำคัà¸à¸šà¸”บังà¸à¸£à¸²à¸Ÿà¸§à¸±à¸à¸ˆà¸±à¸à¸£ 96,000 ปี นับจาภ35,000 ปีที่ผ่านมา วงโคจรขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีความเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นทีละน้à¸à¸¢à¹€à¸žà¸£à¸²à¸°à¸œà¸¥à¸à¸£à¸°à¸—บเชิงโน้มถ่วงจาà¸à¸”าวเคราะห์ดวงà¸à¸·à¹ˆà¸™ ๆ ระยะที่ใà¸à¸¥à¹‰à¸—ี่สุดระหว่างโลà¸à¹à¸¥à¸°à¸”าวà¸à¸±à¸‡à¸„ารจะลดลงà¸à¸¢à¹ˆà¸²à¸‡à¸„่à¸à¸¢à¹€à¸›à¹‡à¸™à¸„่à¸à¸¢à¹„ปต่à¸à¹€à¸™à¸·à¹ˆà¸à¸‡à¸•à¸¥à¸à¸”ระยะเวลา 25,000 ปีข้างหน้า[170] +à¸à¸²à¸£à¸„้นหาสิ่งมีชีวิต[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: สิ่งมีชีวิตบนดาวà¸à¸±à¸‡à¸„าร à¹à¸¥à¸° à¸à¸²à¸£à¸—ดลà¸à¸‡à¸—างชีววิทยาโดยยานส่วนลงจà¸à¸”ไวà¸à¸´à¸‡ +ยานส่วนลงจà¸à¸”ไวà¸à¸´à¸‡ 1 - à¹à¸‚นสุ่มตัวà¸à¸¢à¹ˆà¸²à¸‡à¸ªà¸£à¹‰à¸²à¸‡à¸£à¹ˆà¸à¸‡à¸¥à¸¶à¸ ตัà¸à¸§à¸±à¸ªà¸”ุเพื่à¸à¸—ำà¸à¸²à¸£à¸—ดสà¸à¸š (คริสเพลนิเชีย) + +ตามความเข้าใจในปัจจุบันเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸„วามสามารถà¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¹„ด้ขà¸à¸‡à¸”าวเคราะห์ หรืà¸à¸„วามสามารถที่โลà¸à¹ƒà¸”โลà¸à¸«à¸™à¸¶à¹ˆà¸‡à¸¡à¸µà¸ าวะà¸à¸²à¸£à¸“์ทางสิ่งà¹à¸§à¸”ล้à¸à¸¡à¹€à¸ˆà¸£à¸´à¸à¸žà¸±à¸’นาขึ้นจนชีวิตà¸à¸¸à¸šà¸±à¸•à¸´à¸‚ึ้นได้ เช่นดาวเคราะห์ที่เà¸à¸·à¹‰à¸à¹ƒà¸«à¹‰à¸¡à¸µà¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¸à¸¢à¸¹à¹ˆà¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ เà¸à¸“ฑ์ที่ต้à¸à¸‡à¸à¸²à¸£à¹‚ดยมาà¸à¸„ืà¸à¸§à¸‡à¹‚คจรขà¸à¸‡à¸”าวเคราะห์นั้นต้à¸à¸‡à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¹€à¸‚ตà¸à¸²à¸¨à¸±à¸¢à¹„ด้ ซึ่งในà¸à¸£à¸“ีขà¸à¸‡à¸”วงà¸à¸²à¸—ิตย์คืà¸à¸•à¸±à¹‰à¸‡à¹à¸•à¹ˆà¹à¸–บพ้นจาà¸à¸”าวศุà¸à¸£à¹Œà¸à¸à¸à¹„ปจนถึงระยะประมาณà¸à¸¶à¹ˆà¸‡à¹à¸à¸™à¹€à¸à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[171] ระหว่างà¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์ที่สุด ดาวà¸à¸±à¸‡à¸„ารได้ล่วงเข้าไปในเขตนี้ à¹à¸•à¹ˆà¸”้วยความที่มีบรรยาà¸à¸²à¸¨à¹€à¸šà¸²à¸šà¸²à¸‡ ความà¸à¸”à¸à¸²à¸à¸²à¸¨à¸—ี่ต่ำเป็นà¸à¸¸à¸›à¸ªà¸£à¸£à¸„ไม่ให้น้ำขà¸à¸‡à¹€à¸«à¸¥à¸§à¸›à¸à¸„ลุมภูมิประเทศเป็นบริเวณà¸à¸§à¹‰à¸²à¸‡à¹„ด้ในช่วงระยะเวลาที่นานพภà¸à¸²à¸£à¹„หลขà¸à¸‡à¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¹ƒà¸™à¸à¸”ีตเป็นเครื่à¸à¸‡à¸žà¸´à¸ªà¸¹à¸ˆà¸™à¹Œà¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารมีศัà¸à¸¢à¸ าพสำหรับà¸à¸²à¸£à¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¸‚à¸à¸‡à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸• หลัà¸à¸à¸²à¸™à¸—ี่พบใหม่บางประà¸à¸²à¸£à¸Šà¸µà¹‰à¸§à¹ˆà¸²à¸™à¹‰à¸³à¸šà¸™à¸œà¸´à¸§à¸”าวà¸à¸±à¸‡à¸„ารนั้นà¸à¸²à¸ˆà¸ˆà¸°à¹€à¸„็มเà¸à¸´à¸™à¹„ปà¹à¸¥à¸°à¸¡à¸µà¸„วามเป็นà¸à¸£à¸”มาà¸à¹€à¸à¸´à¸™à¹„ปที่จะค้ำจุนสิ่งมีชีวิตบà¸à¹‚ดยทั่ว ๆ ไปได้[172] + +à¸à¸²à¸£à¸›à¸£à¸²à¸¨à¸ˆà¸²à¸à¸ªà¸™à¸²à¸¡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่เบาบางà¸à¸¢à¹ˆà¸²à¸‡à¸¢à¸´à¹ˆà¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเป็นปัà¸à¸«à¸²à¸—ี่ท้าทาย ดาวเคราะห์เà¸à¸‡à¸¡à¸µà¸à¸²à¸£à¸–่ายเทความร้à¸à¸™à¸œà¹ˆà¸²à¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸—ี่ต่ำ à¸à¸²à¸£à¸›à¹‰à¸à¸‡à¸à¸±à¸™à¸à¸±à¸™à¸¢à¹ˆà¸³à¹à¸¢à¹ˆà¸•à¹ˆà¸à¸à¸²à¸£à¸à¸£à¸°à¸«à¸™à¹ˆà¸³à¹‚จมตีขà¸à¸‡à¸¥à¸¡à¸ªà¸¸à¸£à¸´à¸¢à¸° à¹à¸¥à¸°à¸„วามà¸à¹ˆà¸à¸™à¸”้à¸à¸¢à¸‚à¸à¸‡à¸„วามดันบรรยาà¸à¸²à¸¨à¸ˆà¸™à¹„ม่à¸à¸²à¸ˆà¸à¸”น้ำลงมาให้à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸ªà¸ าพขà¸à¸‡à¹€à¸«à¸¥à¸§à¹€à¸žà¸£à¸²à¸°à¸™à¹‰à¸³à¹à¸‚็งจะระเหิดไปจนหมด à¸à¸¥à¹ˆà¸²à¸§à¹„ด้ว่าดาวà¸à¸±à¸‡à¸„ารนั้นจวนที่จะตายหรืà¸à¹„ม่à¸à¹‡à¸à¸²à¸ˆà¸ˆà¸°à¸•à¸²à¸¢à¹„ปà¹à¸¥à¹‰à¸§à¹ƒà¸™à¸—างธรณีวิทยา เพราะà¸à¸²à¸£à¸ˆà¸šà¸ªà¸´à¹‰à¸™à¸¥à¸‡à¸‚à¸à¸‡à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸ ูเขาไฟย่à¸à¸¡à¹€à¸›à¹‡à¸™à¸—ี่ประจัà¸à¸©à¹Œà¸§à¹ˆà¸²à¸à¸²à¸£à¹à¸›à¸£à¹ƒà¸Šà¹‰à¹ƒà¸«à¸¡à¹ˆà¸‚à¸à¸‡à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸•à¸¥à¸à¸”จนà¸à¸‡à¸„์ประà¸à¸à¸šà¹€à¸„มีต่าง ๆ ระหว่างพื้นผิวà¸à¸±à¸šà¸šà¸£à¸´à¹€à¸§à¸“ภายในดาวเคราะห์นั้นย่à¸à¸¡à¸•à¹‰à¸à¸‡à¸ˆà¸šà¸ªà¸´à¹‰à¸™à¹„ปด้วย[173] +หลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸à¸±à¸¥à¸à¸² - ตรวจพบà¸à¸²à¸£à¸—ับถมขà¸à¸‡à¸à¸¸à¸¥à¸à¸¡à¸“ี (จุดสีเขียว), ตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่เป็นไปได้ว่าจะมีสิ่งมีชีวิตโบราณถูà¸à¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¹„ว้[174] + +หลัà¸à¸à¸²à¸™à¸šà¹ˆà¸‡à¸šà¸à¸à¸§à¹ˆà¸²à¸„รั้งหนึ่งดาวà¸à¸±à¸‡à¸„ารมีความเป็นมิตรต่à¸à¸à¸²à¸£à¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹ƒà¸™à¸—ุà¸à¸§à¸±à¸™à¸™à¸µà¹‰à¸à¸¢à¹ˆà¸²à¸‡à¸¡à¸²à¸ à¹à¸•à¹ˆà¸ˆà¸°à¸¡à¸µà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸”ำรงสืบต่à¸à¸¡à¸²à¸šà¸™à¸”าวหรืà¸à¹„ม่นั้นยังไม่มีคำตà¸à¸šà¸—ี่à¹à¸™à¹ˆà¸Šà¸±à¸” ยานสำรวจไวà¸à¸´à¸‡à¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¸à¸¥à¸²à¸‡à¸—ศวรรษ 1970 (พ.ศ. 2513-) มีà¸à¸¸à¸›à¸à¸£à¸“์ที่à¸à¸à¸à¹à¸šà¸šà¸¡à¸²à¹€à¸žà¸·à¹ˆà¸à¸•à¸£à¸§à¸ˆà¸«à¸²à¸ˆà¸¸à¸¥à¸´à¸™à¸—รีย์ต่าง ๆ ในดินขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ณ บริเวณลงจà¸à¸”ขà¸à¸‡à¹à¸•à¹ˆà¸¥à¸°à¸¢à¸²à¸™à¹à¸¥à¸°à¸•à¹ˆà¸²à¸‡à¸à¹‡à¹„ด้ผลลัพธ์เป็นบวภรวมถึงà¸à¸²à¸£à¸œà¸¥à¸´à¸• CO2 เพิ่มขึ้นเป็นครั้งคราวเมื่à¸à¹„ด้สัมผัสà¸à¸±à¸šà¸™à¹‰à¸³à¹à¸¥à¸°à¸ªà¸²à¸£à¸à¸²à¸«à¸²à¸£ สัà¸à¸à¸²à¸“ขà¸à¸‡à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸µà¹‰à¸ ายหลังได้ถูà¸à¹‚ต้à¹à¸¢à¹‰à¸‡à¹‚ดยนัà¸à¸§à¸´à¸—ยาศาสตร์จำนวนหนึ่ง ผลที่ได้ยังคงเป็นที่à¸à¸ ิปรายถà¸à¹€à¸–ียงเรื่à¸à¸¢à¸¡à¸² โดยนัà¸à¸§à¸´à¸—ยาศาสตร์นาซา à¸à¸´à¸¥à¹€à¸šà¸´à¸£à¹Œà¸• เลวิน ยืนยันว่ายานไวà¸à¸´à¸‡à¸à¸²à¸ˆà¸•à¸£à¸§à¸ˆà¸žà¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸• à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ข้à¸à¸¡à¸¹à¸¥à¸ˆà¸²à¸à¹„วà¸à¸´à¸‡à¸‹à¹‰à¸³à¸à¸µà¸à¸„รั้งภายใต้à¸à¸‡à¸„์ความรู้ใหม่ในปัจจุบันเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸ªà¸¸à¸”ขั้วรูปà¹à¸šà¸šà¸•à¹ˆà¸²à¸‡ ๆ ชี้ว่า à¸à¸²à¸£à¸—ดสà¸à¸šà¹‚ดยไวà¸à¸´à¸‡à¹„ม่ได้ละเà¸à¸µà¸¢à¸”ซับซ้à¸à¸™à¹€à¸žà¸µà¸¢à¸‡à¸žà¸à¸—ี่จะตรวจหารูปà¹à¸šà¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹€à¸Šà¹ˆà¸™à¸™à¸µà¹‰ ตัวà¸à¸²à¸£à¸—ดสà¸à¸šà¹€à¸à¸‡à¸¢à¸±à¸‡à¸à¸²à¸ˆà¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งฆ่าสิ่งมีชีวิต (ตามสมมติà¸à¸²à¸™) เหล่านั้นไปเสียด้วยซ้ำ[175] ปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸—ดสà¸à¸šà¹‚ดยยานส่วนลงจà¸à¸”ฟีนิà¸à¸‹à¹Œà¹à¸ªà¸”งให้ทราบว่าดินมีค่าพีเà¸à¸Šà¹€à¸›à¹‡à¸™à¸”่าง ประà¸à¸à¸šà¸”้วยà¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ โซเดียม โพà¹à¸—สเซียม à¹à¸¥à¸°à¸„ลà¸à¹„รด์[176] ลำพังสารà¸à¸²à¸«à¸²à¸£à¹ƒà¸™à¸”ินà¸à¸²à¸ˆà¸ªà¸²à¸¡à¸²à¸£à¸–เà¸à¸·à¹‰à¸à¸«à¸™à¸¸à¸™à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹„ด้ à¹à¸•à¹ˆà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸¢à¸±à¸‡à¸„งต้à¸à¸‡à¹„ด้รับà¸à¸²à¸£à¸›à¹‰à¸à¸‡à¸à¸±à¸™à¸ˆà¸²à¸à¹à¸ªà¸‡à¸à¸±à¸¥à¸•à¸£à¸²à¹„วโà¸à¹€à¸¥à¸•à¸à¸±à¸™à¹à¸£à¸‡à¸à¸¥à¹‰à¸²[177] à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ล่าสุดเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸”าวà¸à¸±à¸‡à¸„าร EETA79001 พบ ClO4- 0.6 ส่วนในล้านส่วน ClO3- 1.4 ส่วนในล้านส่วน à¹à¸¥à¸° NO3- 16 ส่วนในล้านส่วน เà¸à¸·à¸à¸šà¸—ั้งหมดน่าจะมีที่มาจาà¸à¸”าวà¸à¸±à¸‡à¸„ารโดยตรง à¸à¸²à¸£à¸¡à¸µ ClO3- ชี้ว่าน่าจะมีสารประà¸à¸à¸šà¸à¸à¸à¸‹à¸´à¹€à¸ˆà¸™-คลà¸à¸£à¸µà¸™à¸—ี่สภาพà¸à¸à¸à¸‹à¸´à¹„ดซ์สูงชนิดà¸à¸·à¹ˆà¸™ à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™ ClO2- หรืภClO ด้วย ทั้งสà¸à¸‡à¸–ูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้นโดยปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²à¸à¸à¸à¸‹à¸´à¹€à¸”ชันขà¸à¸‡à¸„ลà¸à¸£à¸µà¸™à¹‚ดยรังสียูวี à¹à¸¥à¸°à¸à¸²à¸£à¹à¸•à¸à¸ªà¸¥à¸²à¸¢ ClO4- ด้วยรังสีเà¸à¸à¸‹à¹Œ ด้วยเหตุนี้รูปà¹à¸šà¸šà¸à¸´à¸™à¸—รีย์หรืà¸à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸—ี่ทนทายาดà¹à¸¥à¸°à¹„ด้รับà¸à¸²à¸£à¸›à¹‰à¸à¸‡à¸à¸±à¸™à¸à¸¢à¹ˆà¸²à¸‡à¸”ี (ใต้พื้นผิว) เท่านั้นที่à¸à¸²à¸ˆà¸à¸¢à¸¹à¹ˆà¸£à¸à¸”มาได้[178] + +นà¸à¸à¹€à¸«à¸™à¸·à¸à¸ˆà¸²à¸à¸™à¸µà¹‰ à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ใหม่จาà¸à¸‚้à¸à¸¡à¸¹à¸¥à¸‚à¸à¸‡à¸«à¹‰à¸à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹€à¸„มีเปียà¸à¸‚à¸à¸‡à¸¢à¸²à¸™à¸Ÿà¸µà¸™à¸´à¸à¸‹à¹Œ à¹à¸ªà¸”งให้เห็นว่า Ca(ClO4)2 ในดินตรงที่ยานฟีนิà¸à¸‹à¹Œà¸à¸¢à¸¹à¹ˆà¹„ม่ได้มีปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸à¸±à¸šà¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¹„ม่ว่าจะรูปà¹à¸šà¸šà¹ƒà¸” ๆ à¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸£à¸°à¸¢à¸°à¹€à¸§à¸¥à¸²à¸¢à¸²à¸§à¸™à¸²à¸™à¸–ึง 600 ล้านปี เพราะหาà¸à¸§à¹ˆà¸²à¸¡à¸µà¸™à¹‰à¸³ สาร Ca(ClO4)2 ซึ่งละลายได้ดีมาà¸à¹€à¸¡à¸·à¹ˆà¸à¹„ด้สัมผัสà¸à¸±à¸šà¸™à¹‰à¸³à¸‚à¸à¸‡à¹€à¸«à¸¥à¸§à¸¢à¹ˆà¸à¸¡à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹„ปเà¸à¸´à¸”เฉพาะ CaSO4 ขึ้น ผลที่ได้จึงเป็นเครื่à¸à¸‡à¸šà¹ˆà¸‡à¸šà¸à¸à¸–ึงà¸à¸²à¸£à¸¡à¸µà¸ªà¸ าพà¹à¸§à¸”ล้à¸à¸¡à¹à¸«à¹‰à¸‡à¹à¸¥à¹‰à¸‡à¸à¸¢à¹ˆà¸²à¸‡à¸ªà¸²à¸«à¸±à¸ªà¹‚ดยมีน้ำเล็à¸à¸™à¹‰à¸à¸¢à¸«à¸£à¸·à¸à¹„ม่มีเลยที่จะมาปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸”้วย[179] + +นัà¸à¸§à¸´à¸—ยาศาสตร์บางส่วนเสนà¸à¸§à¹ˆà¸²à¹€à¸¡à¹‡à¸”คาร์บà¸à¹€à¸™à¸•à¹€à¸¥à¹‡à¸ ๆ ที่พบในà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¹€à¸à¹à¸à¸¥à¹€à¸à¸Š 84001ซึ่งคาดว่ามาจาà¸à¸”าวà¸à¸±à¸‡à¸„ารนั้น à¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸‹à¸²à¸à¸ˆà¸¸à¸¥à¸Šà¸µà¸žà¸”ึà¸à¸”ำบรรพ์ที่หลงเหลืà¸à¸à¸¢à¸¹à¹ˆà¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารเมื่à¸à¸à¹‰à¸à¸™à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸£à¸°à¹€à¸šà¸´à¸”à¸à¸£à¸°à¹€à¸”็นà¸à¸à¸à¸¡à¸²à¸ˆà¸²à¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวà¸à¸±à¸‡à¸„ารโดยà¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚à¸à¸‡à¸”าวตà¸à¹€à¸¡à¸·à¹ˆà¸à¸£à¸²à¸§ 15 ล้านปีà¸à¹ˆà¸à¸™ ข้à¸à¹€à¸ªà¸™à¸à¸”ังà¸à¸¥à¹ˆà¸²à¸§à¸¢à¸±à¸‡à¸„งเป็นที่เคลืà¸à¸šà¹à¸„ลง à¹à¸¥à¸°à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¹€à¸ªà¸™à¸à¸§à¹ˆà¸²à¸£à¸¹à¸›à¹à¸šà¸šà¸—ี่เห็นà¸à¸²à¸ˆà¸¡à¸µà¸•à¹‰à¸™à¸à¸³à¹€à¸™à¸´à¸”à¹à¸šà¸šà¸à¸™à¸´à¸™à¸—รีย์ที่พิเศษà¸à¸à¸à¹„ปà¸à¹‡à¹„ด้[180] + +à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸—ั้งมีเทนà¹à¸¥à¸°à¸Ÿà¸à¸£à¹Œà¸¡à¸²à¸¥à¸”ีไฮด์ปริมาณเล็à¸à¸™à¹‰à¸à¸¢à¹‚ดยยานโคจรรà¸à¸šà¸”าวà¸à¸±à¸‡à¸„ารล้วนถูà¸à¸™à¸³à¹„ปà¸à¹‰à¸²à¸‡à¹€à¸›à¹‡à¸™à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸„วามเป็นไปได้ว่ามีสิ่งมีชีวิต เนื่à¸à¸‡à¸ˆà¸²à¸à¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸à¸šà¹€à¸„มีทั้งคู่จะà¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¹„ปà¸à¸¢à¹ˆà¸²à¸‡à¸£à¸§à¸”เร็วในบรรยาà¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[181][182] ในà¸à¸µà¸à¸—างหนึ่ง สารเหล่านี้à¸à¸²à¸ˆà¸¡à¸µà¸à¸²à¸£à¸œà¸¥à¸´à¸•à¸—ดà¹à¸—นโดยภูเขาไฟหรืà¸à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—างธรณีวิทยาà¸à¸·à¹ˆà¸™ เช่น à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¹€à¸‹à¸à¸£à¹Œà¹€à¸žà¸™à¸—ีน[145] + +à¸à¸¸à¸¥à¸à¸¡à¸“ีซึ่งเà¸à¸´à¸”ขึ้นภายหลังดาวตà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹ƒà¸™à¸à¸£à¸“ีขà¸à¸‡à¹‚ลà¸à¸™à¸±à¹‰à¸™à¸ªà¸²à¸¡à¸²à¸£à¸–เà¸à¹‡à¸šà¸£à¹ˆà¸à¸‡à¸£à¸à¸¢à¸‚à¸à¸‡à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹„ว้ได้ มีรายงานà¸à¸²à¸£à¸žà¸šà¸à¸¸à¸¥à¸à¸¡à¸“ีบนพื้นผิวขà¸à¸‡à¸«à¸¥à¸¸à¸¡à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„าร[183][184] ในทำนà¸à¸‡à¹€à¸”ียวà¸à¸±à¸™ à¹à¸à¹‰à¸§à¸—ี่พบในหลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารà¸à¹‡à¸à¸²à¸ˆà¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¸£à¹ˆà¸à¸‡à¸£à¸à¸‡à¸šà¸²à¸‡à¸à¸¢à¹ˆà¸²à¸‡à¸‚à¸à¸‡à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹„ว้หาà¸à¸ªà¸–านที่นั้นเคยมีสิ่งมีชีวิตà¸à¸¢à¸¹à¹ˆ[185][186][187] +ความสามารถà¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¹„ด้[à¹à¸à¹‰] +ดูเพิ่มเติมที่: ความสามารถà¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¹„ด้ขà¸à¸‡à¸”าวเคราะห์ + +ศูนย์à¸à¸²à¸£à¸šà¸´à¸™à¹à¸¥à¸°à¸à¸§à¸à¸²à¸¨à¹€à¸¢à¸à¸£à¸¡à¸±à¸™à¸„้นพบว่าไลเคนขà¸à¸‡à¹‚ลà¸à¸ªà¸²à¸¡à¸²à¸£à¸–à¸à¸¢à¸¹à¹ˆà¸£à¸à¸”ได้ในสภาพà¹à¸§à¸”ล้à¸à¸¡à¸”าวà¸à¸±à¸‡à¸„ารจำลà¸à¸‡ ทำให้à¸à¸²à¸£à¸¡à¸µà¸à¸¢à¸¹à¹ˆà¸‚à¸à¸‡à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารเป็นเรื่à¸à¸‡à¸™à¹ˆà¸²à¹€à¸Šà¸·à¹ˆà¸à¸–ืà¸à¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นตามที่นัà¸à¸§à¸´à¸ˆà¸±à¸¢ ทิลà¹à¸¡à¸™ สปà¸à¸«à¹Œà¸™ รายงาน[188] เงื่à¸à¸™à¹„ขด้านà¸à¸¸à¸“หภูมิ ความà¸à¸”à¸à¸²à¸à¸²à¸¨ à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸ à¹à¸¥à¸°à¹à¸ªà¸‡à¸ˆà¸³à¸¥à¸à¸‡à¸‚ึ้นโดยà¸à¸²à¸¨à¸±à¸¢à¸‚้à¸à¸¡à¸¹à¸¥à¸ˆà¸²à¸à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„าร[188] เครื่à¸à¸‡à¸¡à¸·à¸à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”สภาพà¹à¸§à¸”ล้à¸à¸¡à¸«à¸£à¸·à¸à¹€à¸£à¸¡à¸ªà¹Œà¸à¸à¸à¹à¸šà¸šà¸¡à¸²à¹€à¸žà¸·à¹ˆà¸à¸ªà¸·à¸šà¸„้นเบาะà¹à¸ªà¹ƒà¸«à¸¡à¹ˆ ๆ เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸„ุณลัà¸à¸©à¸“ะà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¹€à¸§à¸µà¸¢à¸™à¸—ั่วไปบนดาวà¸à¸±à¸‡à¸„าร ระบบสภาพà¸à¸²à¸à¸²à¸¨à¹ƒà¸™à¸£à¸°à¸”ับเล็ภวัà¸à¸ˆà¸±à¸à¸£à¸à¸¸à¸—à¸à¸§à¸´à¸—ยาท้à¸à¸‡à¸–ิ่น ศัà¸à¸¢à¸ าพในà¸à¸²à¸£à¸—ำลายล้างขà¸à¸‡à¸£à¸±à¸‡à¸ªà¸µà¸¢à¸¹à¸§à¸µ à¹à¸¥à¸°à¸„วามสามารถà¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¹„ด้ใต้พื้นผิวซึ่งวางà¸à¸¢à¸¹à¹ˆà¸šà¸™à¸›à¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸žà¸·à¹‰à¸™à¸”ินà¸à¸±à¸šà¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨[189][190] เครื่à¸à¸‡à¸¡à¸·à¸à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¸«à¸™à¸¶à¹ˆà¸‡à¸‚à¸à¸‡à¸¢à¸²à¸™ คิวริà¸à¸à¸‹à¸´à¸•à¸µ (มาร์สไซà¹à¸à¸™à¸‹à¹Œà¹à¸¥à¸šà¸à¸£à¸²à¸—à¸à¸£à¸µ (MSL) หรืภห้à¸à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸§à¸´à¸—ยาศาสตร์ดาวà¸à¸±à¸‡à¸„าร) ซึ่งลงจà¸à¸”บนดาวà¸à¸±à¸‡à¸„ารเมื่à¸à¹€à¸”ืà¸à¸™à¸ªà¸´à¸‡à¸«à¸²à¸„ม 2012 (พ.ศ. 2555) +à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆ[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„าร +ทัศนียภาพขà¸à¸‡à¸«à¸¥à¸¸à¸¡à¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸à¸¹à¹€à¸‹à¸Ÿ ตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่ยานสปิริตโรเวà¸à¸£à¹Œ สำรวจหินบะซà¸à¸¥à¸•à¹Œà¸ ูเขาไฟ + +นà¸à¸à¹€à¸«à¸™à¸·à¸à¸ˆà¸²à¸à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸ˆà¸²à¸à¹‚ลภส่วนหนึ่งขà¸à¸‡à¸‚้à¸à¸¡à¸¹à¸¥à¹ƒà¸«à¸¡à¹ˆ ๆ ขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารได้มาจาà¸à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¹€à¸ˆà¹‡à¸”ลำที่ยังà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸à¸²à¸£à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ ารà¸à¸´à¸ˆà¸—ั้งบนà¹à¸¥à¸°à¹‚คจรเหนืà¸à¸”าวà¸à¸±à¸‡à¸„าร ประà¸à¸à¸šà¸”้วยยานในวงโคจรห้าลำà¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ าคพื้นà¸à¸µà¸à¸ªà¸à¸‡à¸¥à¸³ ได้à¹à¸à¹ˆ 2001 มาร์สโà¸à¸”ิสซี [191] มาร์สเà¸à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª มาร์สรีคà¸à¸™à¹€à¸™à¸ªà¹€à¸‹à¸™à¸‹à¹Œà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œ เมเว็น มาร์สà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¸¡à¸´à¸Šà¸Šà¸±à¸™ à¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตี à¹à¸¥à¸°à¸„ิวริà¸à¸à¸‹à¸´à¸•à¸µ + +มีà¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸à¸§à¸à¸²à¸¨à¹„ร้คนบังคับหลายสิบลำทั้งที่โคจรรà¸à¸š ยานส่วนลงจà¸à¸” à¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ าคพื้นไปยังดาวà¸à¸±à¸‡à¸„ารโดยสหภาพโซเวียต สหรัà¸à¸à¹€à¸¡à¸£à¸´à¸à¸² ยุโรป à¹à¸¥à¸° à¸à¸´à¸™à¹€à¸”ีย เพื่à¸à¸¨à¸¶à¸à¸©à¸²à¸ªà¸ าพพื้นผิวขà¸à¸‡à¸”าว ภูมิà¸à¸²à¸à¸²à¸¨ à¹à¸¥à¸°à¸˜à¸£à¸“ีวิทยา สาธารณะชนทั่วไปสามารถขà¸à¸”ูรูปภาพดาวà¸à¸±à¸‡à¸„ารได้ผ่านทางโปรà¹à¸à¸£à¸¡à¹„ฮวิช + +ยานคิวริà¸à¸à¸‹à¸´à¸•à¸µ จาà¸à¸ ารà¸à¸´à¸ˆà¸¡à¸²à¸£à¹Œà¸ªà¹„ซà¹à¸à¸™à¸‹à¹Œà¹à¸¥à¸šà¸à¸£à¸²à¸—à¸à¸£à¸µà¸‹à¸¶à¹ˆà¸‡à¸ªà¹ˆà¸‡à¸‚ึ้นสู่à¸à¸§à¸à¸²à¸¨à¹€à¸¡à¸·à¹ˆà¸ 26 พฤศจิà¸à¸²à¸¢à¸™ 2011 (พ.ศ. 2554) à¹à¸¥à¸°à¹„ปถึงดาวà¸à¸±à¸‡à¸„ารวันที่ 6 สิงหาคม 2012 (พ.ศ. 2555) ตามเวลาสาà¸à¸¥ มีขนาดใหà¸à¹ˆà¹à¸¥à¸°à¸¥à¹‰à¸³à¸«à¸™à¹‰à¸²à¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ าคพื้นดาวà¸à¸±à¸‡à¸„ารรุ่นà¸à¹ˆà¸à¸™ โดยสามารถเคลื่à¸à¸™à¸—ี่ด้วยà¸à¸±à¸•à¸£à¸²à¹€à¸£à¹‡à¸§ 90 เมตร (300 ฟุต) ต่à¸à¸Šà¸±à¹ˆà¸§à¹‚มง[192] à¸à¸²à¸£à¸—ดลà¸à¸‡à¸›à¸£à¸°à¸à¸à¸šà¸”้วยà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸¥à¹€à¸‹à¸à¸£à¹Œà¸—ดสà¸à¸šà¸•à¸±à¸§à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸žà¸·à¹ˆà¸à¸«à¸²à¸à¸‡à¸„์ประà¸à¸à¸šà¸—างเคมี สามารถประเมินสรุปหินต่าง ๆ ที่พบว่ามีà¸à¸‡à¸„์ประà¸à¸à¸šà¸à¸¢à¹ˆà¸²à¸‡à¹„รได้ที่ระยะห่าง 7 เมตร (23 ฟุต)[193] วันที่ 10 à¸à¸¸à¸¡à¸ าพันธ์ 2013 (พ.ศ. 2556) ยานคิวริà¸à¸à¸‹à¸´à¸•à¸µ ได้มีà¸à¸²à¸£à¹€à¸à¹‡à¸šà¸•à¸±à¸§à¸à¸¢à¹ˆà¸²à¸‡à¸«à¸´à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸¶à¸à¸‹à¸¶à¹ˆà¸‡à¸–ืà¸à¹€à¸›à¹‡à¸™à¸à¸²à¸£à¹€à¸ˆà¸²à¸°à¸¨à¸¶à¸à¸©à¸²à¸•à¸±à¸§à¸à¸¢à¹ˆà¸²à¸‡à¸«à¸´à¸™à¸šà¸™à¸”าวเคราะห์ดวงà¸à¸·à¹ˆà¸™à¹€à¸›à¹‡à¸™à¸„รั้งà¹à¸£à¸à¹‚ดยà¸à¸²à¸£à¹€à¸ˆà¸²à¸°à¸”้วยสว่านบนยาน[194] + +วันที่ 24 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2014 (พ.ศ. 2557) ยานมาร์สà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¸¡à¸´à¸Šà¸Šà¸±à¸™ (มงคลยาน หรืภเà¸à¹‡à¸¡à¹‚à¸à¹€à¸à¹‡à¸¡) ซึ่งส่งขึ้นสู่à¸à¸§à¸à¸²à¸¨à¹‚ดยà¸à¸‡à¸„์à¸à¸²à¸£à¸§à¸´à¸ˆà¸±à¸¢à¸à¸§à¸à¸²à¸¨à¸à¸´à¸™à¹€à¸”ียได้เข้าสู่วงโคจรดาวà¸à¸±à¸‡à¸„าร โครงà¸à¸²à¸£à¹€à¸£à¸´à¹ˆà¸¡à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ˆà¸²à¸à¹‚ลà¸à¹€à¸¡à¸·à¹ˆà¸ 5 พฤศจิà¸à¸²à¸¢à¸™ 2013 โดยมีเป้าหมายเพื่à¸à¸¨à¸¶à¸à¸©à¸²à¸§à¸´à¹€à¸„ราะห์บรรยาà¸à¸²à¸¨à¹à¸¥à¸°à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ยานมาร์สà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¸¡à¸´à¸Šà¸Šà¸±à¸™à¹ƒà¸Šà¹‰à¸§à¸‡à¹‚คจรส่งเฮาห์à¹à¸¡à¸™à¸™à¹Œà¹€à¸žà¸·à¹ˆà¸à¸«à¸¥à¸¸à¸”à¸à¸à¸à¸ˆà¸²à¸à¸à¸´à¸—ธิพลโน้มถ่วงขà¸à¸‡à¹‚ลภà¹à¸¥à¸°à¹€à¸«à¸§à¸µà¹ˆà¸¢à¸‡à¹„ปสู่เส้นทางยาวไà¸à¸¥à¹€à¸à¹‰à¸²à¹€à¸”ืà¸à¸™à¸ªà¸¹à¹ˆà¸”าวà¸à¸±à¸‡à¸„าร ภารà¸à¸´à¸ˆà¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸ ารà¸à¸´à¸ˆà¹€à¸”ินทางสู่ดาวเคราะห์à¸à¸·à¹ˆà¸™à¹‚ดยเà¸à¹€à¸Šà¸µà¸¢à¸—ี่ประสบความสำเร็จเป็นครั้งà¹à¸£à¸[195] +à¸à¸™à¸²à¸„ต[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„าร § เส้นเวลาà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„าร + +มีà¹à¸œà¸™à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸à¸”à¸à¸´à¸™à¹„ซต์ในเดืà¸à¸™à¸¡à¸µà¸™à¸²à¸„ม 2016 (พ.ศ. 2559) ร่วมไปà¸à¸±à¸šà¸„ิวบ์à¹à¸‹à¸•à¸„ู่à¹à¸à¸”ซึ่งจะบินผ่านดาวà¸à¸±à¸‡à¸„ารà¹à¸¥à¸°à¸„à¸à¸¢à¸Šà¹ˆà¸§à¸¢à¹€à¸Šà¸·à¹ˆà¸à¸¡à¹‚ยงà¸à¸±à¸šà¸ าคพื้นดิน ยานส่วนลงจà¸à¸”à¹à¸¥à¸°à¸„ิวบ์à¹à¸‹à¸•à¸—ั้งคู่มีà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¹„ปถึงดาวà¸à¸±à¸‡à¸„ารในเดืà¸à¸™à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2016[196] + +à¸à¸‡à¸„์à¸à¸²à¸£à¸à¸§à¸à¸²à¸¨à¸¢à¸¸à¹‚รปโดยความร่วมมืà¸à¸à¸±à¸šà¸£à¸à¸ªà¸„à¸à¸ªà¸¡à¸à¸ªà¸ˆà¸°à¸¡à¸µà¸à¸²à¸£à¸ªà¹ˆà¸‡à¹€à¸à¹‡à¸à¹‚ซมาร์สเทรซà¹à¸à¹Šà¸ªà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œà¸à¸±à¸šà¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸à¸”สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ ในปี 2016 à¹à¸¥à¸°à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ าคพื้นเà¸à¹‡à¸à¹‚ซมาร์สในปี 2018 (พ.ศ. 2561) นาซามีà¹à¸œà¸™à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¡à¸²à¸£à¹Œà¸ª 2020 ยานสำรวจภาคพื้นชีววิทยาดาราศาสตร์ในปี 2020 (พ.ศ. 2563) + +ยานโคจรมาร์สโฮปขà¸à¸‡à¸ªà¸«à¸£à¸±à¸à¸à¸²à¸«à¸£à¸±à¸šà¹€à¸à¸¡à¸´à¹€à¸£à¸•à¸ªà¹Œà¸¡à¸µà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸ªà¹ˆà¸‡à¹ƒà¸™à¸›à¸µ 2020 ซึ่งจะไปถึงวงโคจรขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารในปี 2021 (พ.ศ. 2564) ยานจะทำà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ั่วทั้งหมดขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[197] + +มีà¸à¸²à¸£à¹€à¸ªà¸™à¸à¹à¸œà¸™à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¡à¸™à¸¸à¸©à¸¢à¹Œà¸ªà¸¹à¹ˆà¸”าวà¸à¸±à¸‡à¸„ารหลายต่à¸à¸«à¸¥à¸²à¸¢à¸„รั้งตลà¸à¸”ช่วงศตวรรษที่ 20 ต่à¸à¹€à¸™à¸·à¹ˆà¸à¸‡à¸¡à¸²à¸ˆà¸™à¸–ึงศตวรรษที่ 21 à¹à¸•à¹ˆà¸¢à¸±à¸‡à¹„ม่มีà¹à¸œà¸™à¹ƒà¸”ที่ดำเนินà¸à¸²à¸£à¸ˆà¸£à¸´à¸‡à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸£à¹‡à¸§à¸—ี่สุดà¸à¹ˆà¸à¸™à¸›à¸µ 2025 (พ.ศ. 2568) +ดาราศาสตร์บนดาวà¸à¸±à¸‡à¸„าร[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาราศาสตร์บนดาวà¸à¸±à¸‡à¸„าร +โฟบà¸à¸ªà¸œà¹ˆà¸²à¸™à¸«à¸™à¹‰à¸²à¸”วงà¸à¸²à¸—ิตย์ (à¸à¸à¸›à¸žà¸à¸£à¹Œà¸—ูนิตี, 10 มีนาคม 2004) +à¸à¸²à¸£à¹€à¸à¹‰à¸²à¸•à¸´à¸”ตามจุดมืดดวงà¸à¸²à¸—ิตย์จาà¸à¸”าวà¸à¸±à¸‡à¸„าร + +ด้วยà¸à¸²à¸£à¸—ี่มีทั้งยานà¸à¸§à¸à¸²à¸¨à¹ƒà¸™à¸§à¸‡à¹‚คจร ยานส่วนลงจà¸à¸” à¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ าคพื้นมาà¸à¸¡à¸²à¸¢à¸«à¸¥à¸²à¸¢à¸¥à¸³ ทำให้à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸”าราศาสตร์จาà¸à¸”าวà¸à¸±à¸‡à¸„ารในปัจจุบันเป็นเรื่à¸à¸‡à¸—ี่เป็นไปได้ à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวบริวารโฟบà¸à¸ªà¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸”้วยขนาดเชิงมุมประมาณหนึ่งในสามขà¸à¸‡à¸”วงจันทร์เต็มดวงที่มà¸à¸‡à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¹‚ลภà¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸”าวบริวารดีมà¸à¸ªà¹à¸¥à¹‰à¸§à¸à¸¥à¸±à¸šà¸›à¸£à¸²à¸à¸à¸„ล้ายà¸à¸±à¸šà¸”าวทั่วไปมาà¸à¸™à¹‰à¸à¸¢à¹à¸¥à¹‰à¸§à¹à¸•à¹ˆà¸à¸£à¸“ีà¹à¸¥à¸°à¸¡à¸à¸‡à¹€à¸«à¹‡à¸™à¸ªà¸§à¹ˆà¸²à¸‡à¸à¸§à¹ˆà¸²à¸”าวศุà¸à¸£à¹Œà¹€à¸¡à¸·à¹ˆà¸à¸¡à¸à¸‡à¸ˆà¸²à¸à¹‚ลà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸à¸¢[198] + +มีปราà¸à¸à¸à¸²à¸£à¸“์หลายà¸à¸¢à¹ˆà¸²à¸‡à¸—ี่รู้จัà¸à¸à¸±à¸™à¸šà¸™à¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸šà¸šà¸™à¸”าวà¸à¸±à¸‡à¸„าร เช่น ดาวตภà¹à¸¥à¸°à¸à¸à¹‚รรา[199] ปราà¸à¸à¸à¸²à¸£à¸“์โลà¸à¹€à¸„ลื่à¸à¸™à¸œà¹ˆà¸²à¸™à¸«à¸™à¹‰à¸²à¸”วงà¸à¸²à¸—ิตย์มà¸à¸‡à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¸”าวà¸à¸±à¸‡à¸„ารจะเà¸à¸´à¸”ขึ้นในวันที่ 10 พฤศจิà¸à¸²à¸¢à¸™ 2084 (พ.ศ. 2627)[200] นà¸à¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸œà¹ˆà¸²à¸™à¹‚ดยดาวพุธ à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸œà¹ˆà¸²à¸™à¹‚ดยดาวศุà¸à¸£à¹Œ ตลà¸à¸”จนดาวบริวารโฟบà¸à¸ªà¹à¸¥à¸°à¸”ีมà¸à¸ªà¸‹à¸¶à¹ˆà¸‡à¸¡à¸µà¸‚นาดเชิงมุมค่à¸à¸™à¸‚้างเล็à¸à¸—ำให้à¸à¸¢à¹ˆà¸²à¸‡à¸¡à¸²à¸à¸—ี่สุดเà¸à¸´à¸”เป็น "สุริยุปราคา" บางส่วนเมื่à¸à¸”าวทั้งสà¸à¸‡à¹€à¸„ลื่à¸à¸™à¸œà¹ˆà¸²à¸™ (ดู à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸œà¹ˆà¸²à¸™à¸‚à¸à¸‡à¸”ีมà¸à¸ªà¸ˆà¸²à¸à¸”าวà¸à¸±à¸‡à¸„าร)[201][202] + +วันที่ 19 ตุลาคม 2014 (พ.ศ. 2557) ดาวหางไซดิงสปริงผ่านเฉียดใà¸à¸¥à¹‰à¸”าวà¸à¸±à¸‡à¸„ารà¸à¸¢à¹ˆà¸²à¸‡à¸¡à¸²à¸ จนโคม่าà¸à¸²à¸ˆà¸„รà¸à¸šà¸„ลุมดาวà¸à¸±à¸‡à¸„าร[203][204][205][206][207][208] +à¸à¸²à¸£à¸Šà¸¡[à¹à¸à¹‰] +ภาพเคลื่à¸à¸™à¹„หวà¹à¸ªà¸”งà¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸–à¸à¸¢à¸«à¸¥à¸±à¸‡à¸›à¸£à¸²à¸à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารในปี 2003 เมื่à¸à¸¡à¸à¸‡à¸ˆà¸²à¸à¹‚ลภ+ +เนื่à¸à¸‡à¸ˆà¸²à¸à¸„วามเยื้à¸à¸‡à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸‚à¸à¸‡à¸§à¸‡à¹‚คจรดาวà¸à¸±à¸‡à¸„าร เมื่à¸à¸”าวà¸à¸±à¸‡à¸„ารà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามà¸à¸±à¸šà¸”วงà¸à¸²à¸—ิตย์จะมีความส่à¸à¸‡à¸ªà¸§à¹ˆà¸²à¸‡à¸›à¸£à¸²à¸à¸à¹„ด้ตั้งà¹à¸•à¹ˆ -2.91[6] จนถึง -1.4 ความสว่างน้à¸à¸¢à¸—ี่สุดขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารคืภ+1.6 เà¸à¸´à¸”ขึ้นเมื่à¸à¸”าวà¸à¸¢à¸¹à¹ˆà¸”้านเดียวà¸à¸±à¸™à¸à¸±à¸šà¸”วงà¸à¸²à¸—ิตย์[10] ดาวà¸à¸±à¸‡à¸„ารมัà¸à¸›à¸£à¸²à¸à¸à¸Šà¸±à¸”ว่ามีสีเหลืà¸à¸‡ สีส้ม หรืà¸à¸ªà¸µà¹à¸”ง à¹à¸•à¹ˆà¸ªà¸µà¸•à¸²à¸¡à¸ˆà¸£à¸´à¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารนั้นใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¸ªà¸µà¸‚à¸à¸‡à¸šà¸±à¸•à¹€à¸•à¸à¸£à¹Œà¸ªà¸à¸à¸•à¸Šà¹Œ สีà¹à¸”งที่มà¸à¸‡à¹€à¸«à¹‡à¸™à¸™à¸±à¹‰à¸™à¹€à¸›à¹‡à¸™à¹€à¸žà¸µà¸¢à¸‡à¸à¸¸à¹ˆà¸™à¹ƒà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚à¸à¸‡à¸”าวเคราะห์ ยานสำรวจภาคพื้นสปิริต ขà¸à¸‡à¸™à¸²à¸‹à¸²à¹„ด้ทำà¸à¸²à¸£à¸–่ายภาพภูมิทัศน์โคลนสีเขียวà¸à¸¡à¸™à¹‰à¸³à¸•à¸²à¸¥à¸£à¹ˆà¸§à¸¡à¸à¸±à¸šà¸«à¸´à¸™à¸ªà¸µà¸™à¹‰à¸³à¹€à¸‡à¸´à¸™à¸›à¸™à¹€à¸—าà¹à¸¥à¸°à¸«à¸¢à¹ˆà¸à¸¡à¸—รายสีà¹à¸”งจาง ๆ เà¸à¸²à¹„ว้[209] ขณะที่à¸à¸¢à¸¹à¹ˆà¸«à¹ˆà¸²à¸‡à¸à¸à¸à¹„ปจาà¸à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด จะมีระยะทางมาà¸à¸à¸§à¹ˆà¸²à¸•à¸à¸™à¸—ี่à¸à¸¢à¸¹à¹ˆà¹ƒà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดมาà¸à¸à¸§à¹ˆà¸²à¹€à¸ˆà¹‡à¸”เท่า เมื่à¸à¸–ึงตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่ไม่เหมาะสมสำหรับà¸à¸²à¸£à¸Šà¸¡ ดาวà¸à¸±à¸‡à¸„ารà¸à¹‡à¸ˆà¸°à¸–ูà¸à¸šà¸”บังโดยความเจิดจ้าขà¸à¸‡à¸”วงà¸à¸²à¸—ิตย์ได้เป็นเวลานานà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¹€à¸”ืà¸à¸™ สำหรับเวลาที่เหมาะสมที่สุดในà¸à¸²à¸£à¸Šà¸¡à¹€à¸à¸´à¸”ขึ้นทุภๆ ช่วง 15 - 17 ปี à¹à¸¥à¸°à¸¡à¸±à¸à¹€à¸à¸´à¸”ขึ้นระหว่างปลายเดืà¸à¸™à¸à¸£à¸à¸Žà¸²à¸„มถึงปลายเดืà¸à¸™à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ เป็นจุดที่สามารถมà¸à¸‡à¹€à¸«à¹‡à¸™à¸£à¸²à¸¢à¸¥à¸°à¹€à¸à¸µà¸¢à¸”พื้นผิวดาวà¸à¸±à¸‡à¸„ารได้ค่à¸à¸™à¸‚้างมาà¸à¸”้วยà¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ สำหรับส่วนที่สังเà¸à¸•à¹€à¸«à¹‡à¸™à¹„ด้ง่ายà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¹ƒà¸Šà¹‰à¸à¸¥à¹‰à¸à¸‡à¸à¸³à¸¥à¸±à¸‡à¸‚ยายต่ำคืà¸à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วดาว[210] + +เมื่à¸à¸”าวà¸à¸±à¸‡à¸„ารเข้ามายังตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามดวงà¸à¸²à¸—ิตย์ à¸à¹‡à¸ˆà¸°à¹€à¸£à¸´à¹ˆà¸¡à¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¹à¸«à¹ˆà¸‡à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸–à¸à¸¢à¸«à¸¥à¸±à¸‡ หมายความว่าดาวà¸à¸±à¸‡à¸„ารจะมà¸à¸‡à¹€à¸«à¹‡à¸™à¹€à¸ªà¸¡à¸·à¸à¸™à¹€à¸„ลื่à¸à¸™à¸—ี่ย้à¸à¸™à¸—างà¸à¸¥à¸±à¸šà¸«à¸¥à¸±à¸‡à¹ƒà¸™à¸¥à¸±à¸à¸©à¸“ะเป็นวงเมื่à¸à¹€à¸—ียบดาวฤà¸à¸©à¹Œà¸žà¸·à¹‰à¸™à¸«à¸¥à¸±à¸‡à¸•à¹ˆà¸²à¸‡ ๆ ระยะเวลาขà¸à¸‡à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸–à¸à¸¢à¸«à¸¥à¸±à¸‡à¸™à¸µà¹‰à¸¢à¸²à¸§à¹„ด้จนถึงราว 72 วัน à¹à¸¥à¸°à¸”าวà¸à¸±à¸‡à¸„ารจะมีความสว่างเพิ่มขึ้นสูงสุดท่ามà¸à¸¥à¸²à¸‡à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸—ี่ดังà¸à¸¥à¹ˆà¸²à¸§[211] +à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸—ี่สุด[à¹à¸à¹‰] +สัมพัทธ์[à¹à¸à¹‰] + +ณ จุดที่เส้นลà¸à¸‡à¸ˆà¸´à¸ˆà¸¹à¸”ขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡ 180 à¸à¸‡à¸¨à¸²à¸ˆà¸²à¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¸”วงà¸à¸²à¸—ิตย์เมื่à¸à¹‚ลà¸à¹€à¸›à¹‡à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸™à¸±à¹‰à¸™à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้าม ซึ่งเป็นเวลาที่ใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¸ˆà¸¸à¸”ที่เข้ามาใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด เวลาà¸à¸²à¸£à¹€à¸à¸´à¸”ขà¸à¸‡à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้าม สามารถห่างจาà¸à¸ˆà¸¸à¸”เข้ามาใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดได้มาà¸à¸–ึง 8.5 วัน ระยะทางเข้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดผันà¹à¸›à¸£à¹„ด้ตั้งà¹à¸•à¹ˆà¸›à¸£à¸°à¸¡à¸²à¸“ 54[212] ถึง 103 ล้านà¸à¸´à¹‚ลเมตรขึ้นà¸à¸¢à¸¹à¹ˆà¸à¸±à¸šà¸„วามรีขà¸à¸‡à¸§à¸‡à¹‚คจรดาวเคราะห์ ซึ่งเป็นสาเหตุทำให้ขนาดเชิงมุมผันà¹à¸›à¸£à¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™[213] ดาวà¸à¸±à¸‡à¸„ารà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามเมื่à¸à¸§à¸±à¸™à¸—ี่ 8 เมษายน 2014 (พ.ศ. 2557) ด้วยระยะทางประมาณ 93 ล้านà¸à¸´à¹‚ลเมตร[214] à¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามครั้งถัดไปขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารจะเà¸à¸´à¸”ขึ้นในวันที่ 22 พฤษภาคม 2016 (พ.ศ. 2559) ด้วยระยะทาง 76 ล้านà¸à¸´à¹‚ลเมตร[214] ระยะเวลาเฉลี่ยระหว่างà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารà¹à¸•à¹ˆà¸¥à¸°à¸„รั้งหรืà¸à¸„าบซินà¸à¸”ิà¸à¸„ืภ780 วัน โดยจำนวนวันที่เà¸à¸´à¸”จริงà¸à¸²à¸ˆà¸¢à¸²à¸§à¸™à¸²à¸™à¸ˆà¸²à¸ 764 ถึง 812 วัน[215] +ดาวà¸à¸±à¸‡à¸„ารในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามจาà¸à¸›à¸µ 2003-2018 มà¸à¸‡à¸ˆà¸²à¸à¸”้านบนขà¸à¸‡à¸ªà¸¸à¸£à¸´à¸¢à¸§à¸´à¸–ีโดยมีโลà¸à¸à¸¢à¸¹à¹ˆà¸•à¸£à¸‡à¸à¸¥à¸²à¸‡ +ค่าที่à¹à¸™à¹ˆà¸™à¸à¸™à¹ƒà¸à¸¥à¹‰à¹€à¸„ียงเวลาปัจจุบัน[à¹à¸à¹‰] + +ดาวà¸à¸±à¸‡à¸„ารเข้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดà¹à¸¥à¸°à¸¡à¸µà¸„วามสว่างปราà¸à¸à¸ªà¸¹à¸‡à¸—ี่สุดในรà¸à¸šà¹€à¸à¸·à¸à¸š 60,000 ปี ด้วยระยะทาง 55,758,006 à¸à¸´à¹‚ลเมตร (34,646,419 ไมล์, 0.37271925 หน่วยดาราศาสตร์) à¹à¸¥à¸°à¸¡à¸µà¸„วามส่à¸à¸‡à¸ªà¸§à¹ˆà¸²à¸‡ -2.88 เมื่à¸à¸§à¸±à¸™à¸—ี่ 27 สิงหาคม 2003 (พ.ศ. 2546) 9:51:13 ตามเวลาสาà¸à¸¥ à¸à¸²à¸£à¹€à¸à¸´à¸”ครั้งนี้ห่างจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารหนึ่งวัน à¹à¸¥à¸°à¸›à¸£à¸°à¸¡à¸²à¸“สามวันจาà¸à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์ที่สุด ทำให้มà¸à¸‡à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¹‚ลà¸à¹„ด้ง่ายเป็นพิเศษ à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸ªà¸¸à¸”à¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²à¸™à¸µà¹‰à¸„าดว่าเà¸à¸´à¸”ขึ้นในวันที่ 12 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 57,617 ปีà¸à¹ˆà¸à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š ครั้งต่à¸à¹„ปจะเà¸à¸´à¸”ขึ้นในปี 2287 (พ.ศ. 2830)[216] à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¹€à¸›à¹‡à¸™à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸à¸²à¸£à¸“์นี้จัดว่าใà¸à¸¥à¹‰à¸à¸§à¹ˆà¸²à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸—ี่สุดร่วมสมัยà¸à¸·à¹ˆà¸™à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸à¸¢ ตัวà¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™ ระยะใà¸à¸¥à¹‰à¸—ี่สุดเมื่ภ22 สิงหาคม 1924 (พ.ศ. 2467) ที่ 0.37285 หน่วยดาราศาสตร์ à¹à¸¥à¸°à¸£à¸°à¸¢à¸°à¹ƒà¸à¸¥à¹‰à¸—ี่สุดที่จะเà¸à¸´à¸”ขึ้นเมื่ภ24 สิงหาคม 2208 (พ.ศ. 2751) ที่ 0.37279 หน่วยดาราศาสตร์[169] +ประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸”าวà¸à¸±à¸‡à¸„าร + +จุดที่โดดเด่นในประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸”าวà¸à¸±à¸‡à¸„ารคืà¸à¹€à¸¡à¸·à¹ˆà¸à¸”าวà¸à¸±à¸‡à¸„ารà¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามใà¸à¸¥à¹‰à¸à¸±à¸šà¹‚ลà¸à¹à¸¥à¸°à¸—ำให้มà¸à¸‡à¹€à¸«à¹‡à¸™à¹„ด้ง่ายที่สุดซึ่งเà¸à¸´à¸”ขึ้นในทุà¸à¸ªà¸à¸‡à¸›à¸µ ที่เด่นชัดยิ่งขึ้นà¸à¸µà¸à¸„ืà¸à¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามขณะà¸à¸¢à¸¹à¹ˆà¹ƒà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์ที่สุดขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารซึ่งเà¸à¸´à¸”ขึ้นทุภๆ 15 - 17 ปี นั่นหมายถึงà¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นด้วยจนทำให้เห็นความà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸¢à¹ˆà¸²à¸‡à¸Šà¸±à¸”เจน +à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹ƒà¸™à¸¢à¸¸à¸„โบราณà¹à¸¥à¸°à¸¢à¸¸à¸„à¸à¸¥à¸²à¸‡[à¹à¸à¹‰] + +à¸à¸²à¸£à¸”ำรงà¸à¸¢à¸¹à¹ˆà¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารในà¸à¸²à¸™à¸°à¸§à¸±à¸•à¸–ุหนึ่งที่เคลื่à¸à¸™à¸œà¹ˆà¸²à¸™à¸—้à¸à¸‡à¸Ÿà¹‰à¸²à¸¢à¸²à¸¡iราตรีได้ถูà¸à¸šà¸±à¸™à¸—ึà¸à¹„ว้โดยนัà¸à¸”าราศาสตร์à¸à¸µà¸¢à¸´à¸›à¸•à¹Œà¹‚บราณ à¹à¸¥à¸°à¹€à¸¡à¸·à¹ˆà¸ 1534 ปีà¸à¹ˆà¸à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š พวà¸à¹€à¸‚าà¸à¹‡à¸„ุ้นเคยดีà¹à¸¥à¹‰à¸§à¸à¸±à¸šà¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸–à¸à¸¢à¸«à¸¥à¸±à¸‡à¸‚à¸à¸‡à¸”าวเคราะห์[217]ในยุคจัà¸à¸£à¸§à¸£à¸£à¸”ิบาบิโลเนียใหม่ นัà¸à¸”าราศาสตร์ชาวบาบิโลเนียได้มีà¸à¸²à¸£à¸šà¸±à¸™à¸—ึà¸à¸›à¸¹à¸¡à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¸”าวเคราะห์ต่าง ๆ ตลà¸à¸”จนพฤติà¸à¸£à¸£à¸¡à¸‚à¸à¸‡à¸”าวเคราะห์ที่สังเà¸à¸•à¹„ด้เà¸à¸²à¹„ว้à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸›à¹‡à¸™à¸£à¸°à¸šà¸šà¹à¸¥à¸°à¸ªà¸¡à¹ˆà¸³à¹€à¸ªà¸¡à¸ สำหรับดาวà¸à¸±à¸‡à¸„าร พวà¸à¹€à¸‚าทราบว่าดาวจะโคจรครบ 37 คาบซินà¸à¸”ิภหรืภ42 รà¸à¸šà¸ˆà¸±à¸à¸£à¸£à¸²à¸¨à¸µà¹ƒà¸™à¸—ุภๆ 79 ปี พวà¸à¹€à¸‚ายังได้คิดค้นระเบียบวิธีทางคณิตศาสตร์ขึ้นมาเพื่à¸à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”ความคลาดเคลื่à¸à¸™à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸à¸¢à¹ƒà¸™à¸à¸²à¸£à¸—ำนายตำà¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¸”าวเคราะห์ทั้งหลาย[218][219] + +ในศตวรรษที่สี่à¸à¹ˆà¸à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š à¸à¸²à¸£à¸´à¸ªà¹‚ตเติลตั้งข้à¸à¸ªà¸±à¸‡à¹€à¸à¸•à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารได้หายไปเบื้à¸à¸‡à¸«à¸¥à¸±à¸‡à¸”วงจันทร์ระหว่างà¸à¸²à¸£à¸–ูà¸à¸šà¸”บัง บ่งบà¸à¸à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารนั้นต้à¸à¸‡à¸à¸¢à¸¹à¹ˆà¸«à¹ˆà¸²à¸‡à¹„à¸à¸¥à¸à¸à¸à¹„ป[220] ทà¸à¹€à¸¥à¸¡à¸µ ชาวà¸à¸£à¸µà¸à¸—ี่à¸à¸²à¸¨à¸±à¸¢à¹ƒà¸™à¸à¸°à¹€à¸¥à¹‡à¸à¸‹à¸²à¸™à¹€à¸”รีย[221] ได้พยายามà¹à¸à¹‰à¹„ขปัà¸à¸«à¸²à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¹„หวในวงโคจรขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร à¹à¸šà¸šà¸ˆà¸³à¸¥à¸à¸‡à¸‚à¸à¸‡à¸—à¸à¹€à¸¥à¸¡à¸µà¹à¸¥à¸°à¸‡à¸²à¸™à¸—างดาราศาสตร์ที่เขารวบรวมขึ้น ปราà¸à¸à¸•à¹ˆà¸à¸¡à¸²à¹€à¸›à¹‡à¸™à¸Šà¸¸à¸”หนังสืà¸à¸«à¸¥à¸²à¸¢à¹€à¸¥à¹ˆà¸¡à¸£à¸¹à¹‰à¸ˆà¸±à¸à¸à¸±à¸™à¹ƒà¸™à¸Šà¸·à¹ˆà¸à¸à¸±à¸¥à¸¡à¸²à¹€à¸ˆà¸ªà¸•à¹Œ ซึ่งได้à¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™à¸•à¸³à¸£à¸²à¸à¸±à¸™à¸—รงà¸à¸´à¸—ธิพลต่à¸à¸”าราศาสตร์ตะวันตà¸à¸•à¸¥à¸à¸”สิบสี่ศตวรรษถัดมา[222] งานนิพนธ์จาà¸à¸ªà¸¡à¸±à¸¢à¸ˆà¸µà¸™à¹‚บราณยืนยันว่าดาวà¸à¸±à¸‡à¸„ารเป็นที่รู้จัà¸à¹‚ดยนัà¸à¸”าราศาสตร์ชาวจีนไม่ช้าไปà¸à¸§à¹ˆà¸²à¸¨à¸•à¸§à¸£à¸£à¸©à¸—ี่สี่à¸à¹ˆà¸à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š[223] ในคริสต์ศตวรรษที่ห้า สุริยสิทธันต์ ตำราทางดาราศาสตร์à¸à¸´à¸™à¹€à¸”ีย มีà¸à¸²à¸£à¸›à¸£à¸°à¸¡à¸²à¸“เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารไว้[c][224] ในวัฒนธรรมเà¸à¹€à¸Šà¸µà¸¢à¸•à¸°à¸§à¸±à¸™à¸à¸à¸ มัà¸à¹€à¸£à¸µà¸¢à¸à¸”าวà¸à¸±à¸‡à¸„ารตามประเพณีว่า "ดาวไฟ" (ç«æ˜Ÿ) โดยวางà¸à¸¢à¸¹à¹ˆà¸šà¸™à¸«à¸¥à¸±à¸à¸˜à¸²à¸•à¸¸à¸—ั้งห้า[225][226][227] + +ในช่วงคริสต์ศตวรรษที่สิบเจ็ด ไทโค บราเฮทำà¸à¸²à¸£à¸§à¸±à¸”พารัลà¹à¸¥à¸à¸‹à¹Œà¹ƒà¸™à¹à¸•à¹ˆà¸¥à¸°à¸§à¸±à¸™à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร ซึ่งต่à¸à¸¡à¸²à¹‚ยฮันเนส เคปเลà¸à¸£à¹Œà¹„ด้นำไปใช้คำนวณเบื้à¸à¸‡à¸•à¹‰à¸™à¸«à¸²à¸£à¸°à¸¢à¸°à¸—างสัมพัทธ์สู่ดาวเคราะห์[228] เมื่à¸à¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์เป็นที่à¹à¸žà¸£à¹ˆà¸«à¸¥à¸²à¸¢ ได้มีà¸à¸²à¸£à¸§à¸±à¸”ค่าพารัลà¹à¸¥à¸à¸‹à¹Œà¸£à¸²à¸¢à¸§à¸±à¸™à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารซ้ำà¸à¸µà¸à¸„รั้งเนื่à¸à¸‡à¹ƒà¸™à¸„วามพยายามที่จะหาระยะทางที่à¹à¸¡à¹ˆà¸™à¸¢à¸³à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¹‚ลà¸à¸à¸±à¸šà¸”วงà¸à¸²à¸—ิตย์ โจวันนี โดเมนีโภà¸à¸±à¸ªà¸‹à¸µà¸™à¸µà¹€à¸›à¹‡à¸™à¸œà¸¹à¹‰à¸”ำเนินà¸à¸²à¸£à¸”ังà¸à¸¥à¹ˆà¸²à¸§à¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลà¹à¸£à¸à¹ƒà¸™à¸›à¸µ 1672 (พ.ศ. 2215) à¸à¸²à¸£à¸§à¸±à¸”ค่าพารัลà¹à¸¥à¸à¸‹à¹Œà¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¹à¸£à¸ ๆ นั้นมีà¸à¸¸à¸›à¸ªà¸£à¸£à¸„สำคัà¸à¸ˆà¸²à¸à¸„ุณภาพขà¸à¸‡à¸•à¸±à¸§à¹€à¸„รื่à¸à¸‡à¸¡à¸·à¸à¹€à¸à¸‡[229] à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸›à¸£à¸²à¸à¸à¸à¸²à¸£à¸“์ดาวศุà¸à¸£à¹Œà¸šà¸”บังดาวà¸à¸±à¸‡à¸„ารเพียงครั้งเดียวเà¸à¸´à¸”ขึ้นในวันที่ 13 ตุลาคม 1590 (พ.ศ. 2133) โดยมิคาเà¸à¸¥ à¹à¸¡à¸ªà¸•à¹Œà¸¥à¸´à¸™à¸—ี่ไฮเดลà¹à¸šà¸£à¹Œà¸[230] ในปี 1610 (พ.ศ. 2153) à¸à¸²à¸¥à¸´à¹€à¸¥à¹‚ภà¸à¸²à¸¥à¸´à¹€à¸¥à¸à¸µà¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลà¹à¸£à¸à¸—ี่มà¸à¸‡à¸”ูดาวà¸à¸±à¸‡à¸„ารผ่านà¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์[231] บุคคลà¹à¸£à¸à¸—ี่วาดภาพดาวà¸à¸±à¸‡à¸„ารโดยà¹à¸ªà¸”งลัà¸à¸©à¸“ะภูมิประเทศต่าง ๆ ด้วยคืà¸à¸™à¸±à¸à¸”าราศาสตร์ชาวดัตช์ คริสตียาน เฮยเคินส์[232] +"คลà¸à¸‡" ดาวà¸à¸±à¸‡à¸„าร[à¹à¸à¹‰] +à¹à¸œà¸™à¸—ี่ดาวà¸à¸±à¸‡à¸„ารโดยโจวานนี สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ +ภาพร่างดาวà¸à¸±à¸‡à¸„ารจาà¸à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹‚ดยโลเวลล์ เวลาใดเวลาหนึ่งà¸à¹ˆà¸à¸™à¸›à¸µ 1914 (ขั้วใต้à¸à¸¢à¸¹à¹ˆà¸”้านบน) +à¹à¸œà¸™à¸—ี่ดาวà¸à¸±à¸‡à¸„ารจาà¸à¸à¸¥à¹‰à¸à¸‡à¸®à¸±à¸šà¹€à¸šà¸´à¸¥ เห็นใà¸à¸¥à¹‰à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามปี 1999 (ขั้วเหนืà¸à¸à¸¢à¸¹à¹ˆà¸”้านบน) +ดูบทความหลัà¸à¸—ี่: คลà¸à¸‡à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„าร + +เมื่à¸à¸–ึงคริสต์ศตวรรษที่สิบเà¸à¹‰à¸² à¸à¸³à¸¥à¸±à¸‡à¸‚ยายขà¸à¸‡à¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ได้เพิ่มมาà¸à¸‚ึ้นจนถึงระดับที่พà¸à¸ˆà¸³à¹à¸™à¸à¹à¸¢à¸à¹à¸¢à¸°à¸£à¸²à¸¢à¸¥à¸°à¹€à¸à¸µà¸¢à¸”ต่าง ๆ บนพื้นผิวได้ à¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามใà¸à¸¥à¹‰à¸”วงà¸à¸²à¸—ิตย์ที่สุดขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารเมื่à¸à¸§à¸±à¸™à¸—ี่ 5 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 1877 (พ.ศ. 2420) ปีนั้นเà¸à¸‡ โจวานนี สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ นัà¸à¸”าราศาสตร์ชาวà¸à¸´à¸•à¸²à¸¥à¸µ ใช้à¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ขนาด 22 เซนติเมตร (8.7 นิ้ว) ในมิลานได้สร้างà¹à¸œà¸™à¸—ี่ดาวà¸à¸±à¸‡à¸„ารที่มีรายละเà¸à¸µà¸¢à¸”ปลีà¸à¸¢à¹ˆà¸à¸¢à¸‚ึ้นเป็นฉบับà¹à¸£à¸ à¹à¸œà¸™à¸—ี่นี้มีเà¸à¸à¸¥à¸±à¸à¸©à¸“์โดดเด่นด้วยภูมิประเทศที่เขาเรียà¸à¸Šà¸·à¹ˆà¸à¸§à¹ˆà¸² คานาลี ซึ่งได้รับà¸à¸²à¸£à¹€à¸›à¸´à¸”เผยต่à¸à¸¡à¸²à¹ƒà¸™à¸ ายหลังว่าเป็นเพียงภาพลวงตา รà¸à¸¢à¹€à¸ªà¹‰à¸™à¸•à¸£à¸‡à¸¢à¸·à¸”ยาวบนพื้นผิวดาวà¸à¸±à¸‡à¸„ารที่ถูà¸à¸—ึà¸à¸—ัà¸à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸„านาลี เหล่านี้ โจวานนีได้ตั้งชื่à¸à¹ƒà¸«à¹‰à¸•à¸²à¸¡à¸à¸¢à¹ˆà¸²à¸‡à¸Šà¸·à¹ˆà¸à¹à¸¡à¹ˆà¸™à¹‰à¸³à¸—ี่มีชื่à¸à¹€à¸ªà¸µà¸¢à¸‡à¹€à¸›à¹‡à¸™à¸—ี่รู้จัà¸à¸šà¸™à¹‚ลภศัพท์ที่เขาใช้มีความหมายว่า "ทางน้ำ" หรืภ"ร่à¸à¸‡à¸™à¹‰à¸³" ซึ่งนิยมà¹à¸›à¸¥à¸à¸±à¸™à¸à¸¢à¹ˆà¸²à¸‡à¸œà¸´à¸” ๆ ในภาษาà¸à¸±à¸‡à¸à¸¤à¸©à¸§à¹ˆà¸² "คลà¸à¸‡"[233][234] + +จาà¸à¸à¸´à¸—ธิพลขà¸à¸‡à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸² เพà¸à¸£à¹Œà¸‹à¸´à¸§à¸±à¸¥ โลเวลล์ นัà¸à¸•à¸°à¸§à¸±à¸™à¸à¸à¸à¸¨à¸¶à¸à¸©à¸²à¹„ด้ตั้งหà¸à¸”ูดาวขึ้นโดยมีà¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ขนาด 30 à¹à¸¥à¸° 45 เซนติเมตร (12 à¹à¸¥à¸° 18 นิ้ว) หà¸à¸”ูดาวนี้ได้ใช้ในà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„ารระหว่างโà¸à¸à¸²à¸ªà¸à¸±à¸™à¸”ีที่ผ่านมาในปี 1894 (พ.ศ. 2437) ตลà¸à¸”จนà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามที่ดีลดหลั่นลงมาหลังจาà¸à¸™à¸±à¹‰à¸™ เขาตีพิมพ์หนังสืà¸à¸«à¸¥à¸²à¸¢à¹€à¸¥à¹ˆà¸¡à¹€à¸£à¸·à¹ˆà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารรวมไปถึงสิ่งมีชีวิตบนนั้นซึ่งส่งà¸à¸´à¸—ธิพลà¸à¸¢à¹ˆà¸²à¸‡à¹ƒà¸«à¸à¹ˆà¸«à¸¥à¸§à¸‡à¸•à¹ˆà¸à¸ªà¸²à¸˜à¸²à¸£à¸“ะ[235] ยังมีà¸à¸²à¸£à¸žà¸š คานาลี โดยนัà¸à¸”าราศาสตร์คนà¸à¸·à¹ˆà¸™ ๆ เช่น à¸à¸à¸‡à¸£à¸µ โฌเซฟ เพร์โรà¹à¸•à¸‡ à¹à¸¥à¸°à¸«à¸¥à¸¸à¸¢à¸ªà¹Œ ตà¸à¸¥à¸¥à¸‡ ที่เมืà¸à¸‡à¸™à¸´à¸ªà¹‚ดยใช้หนึ่งในà¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ที่ใหà¸à¹ˆà¸—ี่สุดในเวลานั้น[236][237] + +à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸•à¸²à¸¡à¸¤à¸”ูà¸à¸²à¸¥à¸à¸±à¸™à¸›à¸£à¸°à¸à¸à¸šà¸”้วยà¸à¸²à¸£à¸–à¸à¸¢à¸£à¹ˆà¸™à¸‚à¸à¸‡à¹à¸œà¹ˆà¸™à¸‚ั้วดาวà¹à¸¥à¸°à¸à¸²à¸£à¹€à¸à¸´à¸”พื้นที่มืดในช่วงฤดูร้à¸à¸™à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร เมื่à¸à¸›à¸£à¸°à¸ˆà¸§à¸šà¹€à¸‚้าà¸à¸±à¸šà¸„ลà¸à¸‡à¸¡à¸²à¸à¸¡à¸²à¸¢à¸ˆà¸¶à¸‡à¸™à¸³à¹„ปสู่à¸à¸²à¸£à¸„าดเดาเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„าร à¹à¸¥à¸°à¸„วามเชื่à¸à¸—ี่ยึดมั่นถืà¸à¸¡à¸±à¹ˆà¸™à¸à¸¢à¹ˆà¸²à¸‡à¸¢à¸²à¸§à¸™à¸²à¸™à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารมีผืนทะเลที่à¸à¸§à¹‰à¸²à¸‡à¹ƒà¸«à¸à¹ˆà¸à¸±à¸šà¸žà¸·à¸Šà¸™à¸²à¸™à¸²à¸žà¸±à¸™à¸˜à¸¸à¹Œ à¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ในขณะนั้นยังไม่มีà¸à¸³à¸¥à¸±à¸‡à¸‚ยายถึงขั้นที่สามารถให้หลัà¸à¸à¸²à¸™à¸¢à¸·à¸™à¸¢à¸±à¸™à¸à¸²à¸£à¸„าดเดาใด ๆ ได้ เมื่à¸à¹ƒà¸Šà¹‰à¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ขนาดใหà¸à¹ˆà¸‚ึ้นà¸à¹‡à¸ˆà¸°à¸ªà¸±à¸‡à¹€à¸à¸•à¹€à¸«à¹‡à¸™ คานาลี ตรงยาวที่ขนาดเล็à¸à¸¥à¸‡ ระหว่างà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹ƒà¸™à¸›à¸µ 1909 (พ.ศ. 2452) โดยใช้à¸à¸¥à¹‰à¸à¸‡à¹‚ทรทรรศน์ขนาด 84 เซนติเมตร (33 นิ้ว) à¹à¸Ÿà¸¥à¸¡à¸¡à¸²à¸£à¸´à¸¢à¸‡à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸šà¸£à¸¹à¸›à¹à¸šà¸šà¸—ี่ไม่เป็นระเบียบà¹à¸•à¹ˆà¹„ม่เห็นคานาลี [238] + +à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งบทความในทศวรรษ 1960 (พ.ศ. 2503-) ยังมีà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œà¹€à¸£à¸·à¹ˆà¸à¸‡à¸Šà¸µà¸§à¸§à¸´à¸—ยาบนดาวà¸à¸±à¸‡à¸„ารโดยผลัà¸à¹„สคำà¸à¸˜à¸´à¸šà¸²à¸¢à¹à¸™à¸§à¸—างà¸à¸·à¹ˆà¸™à¸à¸à¸à¹„ป คงไว้à¹à¸•à¹ˆà¸§à¹ˆà¸²à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸šà¸™à¸™à¸±à¹‰à¸™à¸™à¸±à¹ˆà¸™à¹€à¸à¸‡à¹€à¸›à¹‡à¸™à¹€à¸«à¸•à¸¸à¸‚à¸à¸‡à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¸•à¸²à¸¡à¸¤à¸”ูà¸à¸²à¸¥à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„าร ภาวะà¸à¸²à¸£à¸“์โดยละเà¸à¸µà¸¢à¸”ทั้งเมà¹à¸—บà¸à¸¥à¸´à¸‹à¸¶à¸¡à¹à¸¥à¸°à¸§à¸±à¸à¸ˆà¸±à¸à¸£à¸—างเคมีต่าง ๆ สำหรับระบบนิเวศที่ดำเนินได้จริงได้รับà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œ[239] +à¸à¸²à¸£à¹€à¸¢à¸·à¸à¸™à¹‚ดยยานà¸à¸§à¸à¸²à¸¨[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„าร + +ครั้นยานà¸à¸§à¸à¸²à¸¨à¹„ปเยืà¸à¸™à¸–ึงดาวà¸à¸±à¸‡à¸„ารระหว่างปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸¡à¸²à¸£à¸´à¹€à¸™à¸à¸£à¹Œà¸‚à¸à¸‡à¸™à¸²à¸‹à¸²à¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¸—ศวรรษ 1960 à¹à¸¥à¸° 70 à¹à¸™à¸§à¸„ิดเดิม ๆ à¸à¹‡à¸žà¸´à¸™à¸²à¸¨à¹„ปà¹à¸šà¸šà¹„ม่มีชิ้นดี นà¸à¸à¸ˆà¸²à¸à¸™à¸µà¹‰à¸œà¸¥à¸à¸²à¸£à¸—ดลà¸à¸‡à¸•à¸£à¸§à¸ˆà¸«à¸²à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹‚ดยยานไวà¸à¸´à¸‡à¹ƒà¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ ารà¸à¸´à¸ˆ ทำให้สมมติà¸à¸²à¸™à¸”าวเคราะห์มรณะที่ไม่น่าà¸à¸¢à¸¹à¹ˆà¸à¸¢à¹ˆà¸²à¸‡à¸¢à¸´à¹ˆà¸‡à¸à¹‡à¹„ด้มาเป็นที่ยà¸à¸¡à¸£à¸±à¸šà¸à¸¢à¹ˆà¸²à¸‡à¹à¸žà¸£à¹ˆà¸«à¸¥à¸²à¸¢[240] + +ข้à¸à¸¡à¸¹à¸¥à¸ˆà¸²à¸à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹‚ดยยานมาริเนà¸à¸£à¹Œ 9 à¹à¸¥à¸°à¹„วà¸à¸´à¸‡à¹„ด้นำมาใช้สร้างà¹à¸œà¸™à¸—ี่ดาวà¸à¸±à¸‡à¸„ารที่ดียิ่งขึ้น à¹à¸¥à¸°à¸¢à¸´à¹ˆà¸‡à¸”ียิ่งขึ้นà¸à¸¢à¹ˆà¸²à¸‡à¸à¹‰à¸²à¸§à¸à¸£à¸°à¹‚ดดด้วยปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹‚ดยมาร์สโà¸à¸¥à¸šà¸à¸¥à¹€à¸‹à¸à¸£à¹Œà¹€à¸§à¹€à¸¢à¸à¸£à¹Œà¸‹à¸¶à¹ˆà¸‡à¸ªà¹ˆà¸‡à¸‚ึ้นในปี 1996 (พ.ศ. 2539) à¹à¸¥à¸°à¸”ำเนินงานต่à¸à¹€à¸™à¸·à¹ˆà¸à¸‡à¸ˆà¸™à¸à¸£à¸°à¸—ั่งปลายปี 2006 (พ.ศ. 2549) ทำให้ได้à¹à¸œà¸™à¸—ี่à¹à¸ªà¸”งภูมิประเทศดาวà¸à¸±à¸‡à¸„ารที่ละเà¸à¸µà¸¢à¸”ลà¸à¸à¸„รบถ้วนสมบูรณ์à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸à¹‡à¹€à¸›à¹‡à¸™à¸—ี่รับทราบ[241] à¹à¸œà¸™à¸—ี่เหล่านี้สามารถเข้าถึงได้ทางà¸à¸à¸™à¹„ลน์ ตัวà¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™ à¸à¸¹à¹€à¸à¸´à¸¥à¸¡à¸²à¸£à¹Œà¸ª สำหรับมาร์สรีคà¸à¸™à¹€à¸™à¸ªà¹€à¸‹à¸™à¸‹à¹Œà¸à¸à¸£à¹Œà¸šà¸´à¹€à¸•à¸à¸£à¹Œ à¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª ยังทำà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸•à¹ˆà¸à¹€à¸™à¸·à¹ˆà¸à¸‡à¸”้วยเครื่à¸à¸‡à¹„ม้เครื่à¸à¸‡à¸¡à¸·à¸à¹ƒà¸«à¸¡à¹ˆ ๆ à¹à¸¥à¸°à¸Šà¹ˆà¸§à¸¢à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸¥à¸‡à¸ˆà¸à¸” นาซาได้เปิดให้เข้าใช้เครื่à¸à¸‡à¸¡à¸·à¸à¸—างà¸à¸à¸™à¹„ลน์คืภมาร์สเทร็ค ซึ่งให้ภาพปราà¸à¸à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารจาà¸à¸‚้à¸à¸¡à¸¹à¸¥à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸•à¸¥à¸à¸” 50 ปี à¹à¸¥à¸° เà¸à¹‡à¸à¸‹à¹Œà¸žà¸µà¹€à¸£à¸µà¸¢à¸™à¸‹à¹Œà¸„ิวริà¸à¸à¸‹à¸´à¸•à¸µ ซึ่งให้ภาพจำลà¸à¸‡à¸à¸²à¸£à¸—่à¸à¸‡à¹„ปบนดาวà¸à¸±à¸‡à¸„ารà¹à¸šà¸šà¸ªà¸²à¸¡à¸¡à¸´à¸•à¸´à¸žà¸£à¹‰à¸à¸¡à¸à¸±à¸šà¸¢à¸²à¸™à¸„ิวริà¸à¸à¸‹à¸´à¸•à¸µ[242] +ในวัฒนธรรม[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาวà¸à¸±à¸‡à¸„ารในวัฒนธรรม à¹à¸¥à¸° ดาวà¸à¸±à¸‡à¸„ารในบันเทิงคดี +Mars symbol.svg + +ดาวà¸à¸±à¸‡à¸„ารทางสาà¸à¸¥à¸™à¸´à¸¢à¸¡à¹„ด้ชื่à¸à¸•à¸²à¸¡à¹€à¸—พเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามขà¸à¸‡à¹‚รมัน ในต่างวัฒนธรรม ดาวà¸à¸±à¸‡à¸„ารเป็นตัวà¹à¸—นขà¸à¸‡à¸„วามเข้มà¹à¸‚็ง ความเป็นชาย à¹à¸¥à¸°à¸„วามเยาว์วัย มีสัà¸à¸¥à¸±à¸à¸©à¸“์เป็นรูปวงà¸à¸¥à¸¡à¸—ี่มีลูà¸à¸¨à¸£à¸Šà¸µà¹‰à¸à¸à¸à¸¡à¸²à¸ˆà¸²à¸à¸”้านขวาบน ซึ่งยังใช้เป็นสัà¸à¸¥à¸±à¸à¸©à¸“์à¹à¸—นเพศชายà¸à¸µà¸à¸”้วย + +จà¸à¸à¸„วามล้มเหลวหลายต่à¸à¸«à¸¥à¸²à¸¢à¸„รั้งขà¸à¸‡à¸¢à¸²à¸™-โครงà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„าร เป็นผลให้à¸à¸¥à¸¸à¹ˆà¸¡à¸§à¸±à¸’นธรรมนà¸à¸à¸à¸£à¸°à¹à¸ªà¸™à¸³à¹„ปเยาะเย้ยเสียดสีโดยà¸à¸¥à¹ˆà¸²à¸§à¹‚ทษตำหนิติเตียนว่าความล้มเหลวต่าง ๆ เป็นเพราะ "สามเหลี่ยมเบà¸à¸£à¹Œà¸¡à¸´à¸§à¸”า" ขà¸à¸‡à¹‚ลà¸-ดาวà¸à¸±à¸‡à¸„าร "คำสาปเทพà¸à¸±à¸‡à¸„าร" หรืà¸à¹„ม่à¸à¹‡ "ผีปà¸à¸šà¸¡à¸«à¸²à¸”าราจัà¸à¸£" ที่ได้เขมืà¸à¸šà¹€à¸à¸²à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸”าวà¸à¸±à¸‡à¸„ารไป[243] +"ชาวดาวà¸à¸±à¸‡à¸„าร" ผู้ทรงปัà¸à¸à¸²[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาวà¸à¸±à¸‡à¸„ารในบันเทิงคดี + +ความคิดตามสมัยนิยมที่ว่าดาวà¸à¸±à¸‡à¸„ารเต็มไปด้วยชาวดาวà¸à¸±à¸‡à¸„ารผู้ทรงปัà¸à¸à¸²à¹€à¸‰à¸¥à¸µà¸¢à¸§à¸‰à¸¥à¸²à¸”ลงหลัà¸à¸›à¸±à¸à¸à¸²à¸™à¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢ ได้ปะทุขึ้นในช่วงปลายคริสต์ศตวรรษที่ 19 à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸š "คานาลี" ขà¸à¸‡à¸ªà¹€à¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µà¹€à¸¡à¸·à¹ˆà¸à¸›à¸£à¸°à¸ªà¸²à¸™à¹€à¸‚้าà¸à¸±à¸šà¸«à¸™à¸±à¸‡à¸ªà¸·à¸à¸‚à¸à¸‡à¹€à¸žà¸à¸£à¹Œà¸‹à¸´à¸§à¸±à¸¥ โลเวลล์ในประเด็นดังà¸à¸¥à¹ˆà¸²à¸§ ได้ผลัà¸à¸”ันà¹à¸™à¸§à¸„ิดมาตรà¸à¸²à¸™à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„ารว่าเป็นดาวเคราะห์ที่à¹à¸«à¹‰à¸‡à¹à¸¥à¹‰à¸‡ หนาวเย็น ใà¸à¸¥à¹‰à¸”ับสูภร่วมไปà¸à¸±à¸šà¸à¸²à¸£à¸¡à¸µà¸à¸²à¸£à¸¢à¸˜à¸£à¸£à¸¡à¹‚บราณที่à¸à¹ˆà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‡à¸²à¸™à¸Šà¸¥à¸›à¸£à¸°à¸—านมาà¸à¸¡à¸²à¸¢à¹€à¸à¸²à¹„ว้[244] +โฆษณาสบู่ในปี 1893 (พ.ศ. 2436) บนมโนคตินิยมว่าดาวà¸à¸±à¸‡à¸„ารมีคนà¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢ + +ด้วยหลายà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹à¸¥à¸°à¸–้à¸à¸¢à¹à¸–ลงโดยบุคคลผู้มีความโดดเด่นในสังคมได้ทำให้เà¸à¸´à¸”สิ่งที่เรียà¸à¸§à¹ˆà¸² "โรคคลั่งดาวà¸à¸±à¸‡à¸„าร"[245] ในปี 1899 (พ.ศ. 2442) ขณะà¸à¸³à¸¥à¸±à¸‡à¸•à¸£à¸§à¸ˆà¸ªà¸à¸šà¸„ลื่นวิทยุในบรรยาà¸à¸²à¸¨à¸”้วยเครื่à¸à¸‡à¸£à¸±à¸šà¸ªà¸±à¸à¸à¸²à¸“ขà¸à¸‡à¹€à¸‚าในห้à¸à¸‡à¸—ดลà¸à¸‡à¹‚คโลราโดสปริงส์ นิโคลา เทสลา นัà¸à¸›à¸£à¸°à¸”ิษà¸à¹Œ ได้สังเà¸à¸•à¸žà¸šà¸ªà¸±à¸à¸à¸²à¸“ซ้ำ ๆ เขาสันนิษà¸à¸²à¸™à¹ƒà¸™à¸ ายหลังว่าà¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸à¸²à¸£à¸•à¸´à¸”ต่à¸à¸ªà¸·à¹ˆà¸à¸ªà¸²à¸£à¸—างวิทยุมาจาà¸à¸”าวเคราะห์ดวงà¸à¸·à¹ˆà¸™ ซึ่งเป็นไปได้ว่าคืà¸à¸”าวà¸à¸±à¸‡à¸„าร บทสัมภาษณ์ในปี 1901 (พ.ศ. 2444) เทสลาà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸²: + + มันเป็นบางครั้งภายหลังจาà¸à¸„วามคิดที่ได้ผุดวาบขึ้นมาในใจขà¸à¸‡à¸œà¸¡ à¸à¸²à¸£à¸£à¸šà¸à¸§à¸™à¸—ี่ผมสังเà¸à¸•à¸žà¸šà¸™à¸±à¹ˆà¸™à¸à¸²à¸ˆà¹€à¸›à¹‡à¸™à¹„ด้ว่าคืà¸à¸à¸²à¸£à¸„วบคุมทางปัà¸à¸à¸² à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸œà¸¡à¸ˆà¸°à¹„ม่สามารถไขรหัสความหมายเหล่านั้นได้ มันเป็นไปไม่ได้เลยสำหรับผมที่จะคิดว่าสิ่งเหล่านั้นทั้งหมดเป็นเพียงà¸à¸¸à¸šà¸±à¸•à¸´à¹€à¸«à¸•à¸¸ ความรู้สึà¸à¸—ี่ทวีขึ้นà¸à¸¢à¹ˆà¸²à¸‡à¸¡à¸±à¹ˆà¸™à¸„งในตัวผมà¸à¹‡à¸„ืà¸à¸œà¸¡à¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลà¹à¸£à¸à¸—ี่ได้ยินà¸à¸²à¸£à¸›à¸à¸´à¸ªà¸±à¸™à¸–ารขà¸à¸‡à¸”าวเคราะห์หนึ่งสู่ดาวเคราะห์à¸à¸·à¹ˆà¸™[246] + +ทฤษฎีขà¸à¸‡à¹€à¸—สลาได้รับà¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¹‚ดยลà¸à¸£à¹Œà¸”เคลวิน ผู้ซึ่งไปเยืà¸à¸™à¸ªà¸«à¸£à¸±à¸à¸à¹€à¸¡à¸£à¸´à¸à¸²à¹ƒà¸™à¸›à¸µ 1902 (พ.ศ. 2445) มีรายงานถึงคำพูดขà¸à¸‡à¹€à¸‚าว่าเขาคิดว่าเทสลาจับสัà¸à¸à¸²à¸“ขà¸à¸‡à¸Šà¸²à¸§à¸”าวà¸à¸±à¸‡à¸„ารที่ส่งมายังสหรัà¸à¸à¹€à¸¡à¸£à¸´à¸à¸²à¹„ว้ได้[247] เคลวินปà¸à¸´à¹€à¸ªà¸˜ "à¸à¸¢à¹ˆà¸²à¸‡à¸«à¸™à¸±à¸à¹à¸™à¹ˆà¸™" ในรายงานฉบับนี้ไม่นานà¸à¹ˆà¸à¸™à¸à¸²à¸£à¹€à¸”ินทางà¸à¸à¸à¸ˆà¸²à¸à¸à¹€à¸¡à¸£à¸´à¸à¸² เขาà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² "à¸à¸°à¹„รที่ผมพูดไปจริง ๆ à¸à¹‡à¸„ืภชนชาวดาวà¸à¸±à¸‡à¸„าร ถ้าพวà¸à¹€à¸‚ามีà¸à¸¢à¸¹à¹ˆ à¸à¹‡à¹„ม่ต้à¸à¸‡à¸ªà¸‡à¸ªà¸±à¸¢à¹€à¸¥à¸¢à¸§à¹ˆà¸²à¸„งเห็นนิวยà¸à¸£à¹Œà¸ เพราะไฟฟ้าจะเรืà¸à¸‡à¹à¸ªà¸‡à¸à¸à¸à¸¡à¸²à¸ˆà¸™à¹€à¸«à¹‡à¸™à¹„ด้ชัด"[248] + +ในบทความขà¸à¸‡à¸™à¸´à¸§à¸¢à¸à¸£à¹Œà¸à¹„ทมส์ ในปี 1901 เà¸à¹‡à¸”เวิร์ด ชาลส์ พิà¸à¹€à¸„à¸à¸£à¸´à¸‡ ผู้à¸à¸³à¸™à¸§à¸¢à¸à¸²à¸£à¸«à¸à¸”ูดาววิทยาลัยฮาร์วาร์ดà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² พวà¸à¹€à¸‚าได้รับโทรเลขจาà¸à¸«à¸à¸”ูดาวโลเวลล์ในรัà¸à¹à¸à¸£à¸´à¹‚ซนาที่ดูเหมืà¸à¸™à¸ˆà¸°à¸¢à¸·à¸™à¸¢à¸±à¸™à¸§à¹ˆà¸²à¸”าวà¸à¸±à¸‡à¸„ารได้พยายามติดต่à¸à¸ªà¸·à¹ˆà¸à¸ªà¸²à¸£à¸à¸±à¸šà¹‚ลà¸[249] + + ในต้นเดืà¸à¸™à¸˜à¸±à¸™à¸§à¸²à¸„มปี 1900 (พ.ศ. 2443) เราได้รับโทรเลขจาà¸à¸«à¸à¸”ูดาวโลเวลล์ในà¹à¸à¸£à¸´à¹‚ซนาว่าเห็นลำขà¸à¸‡à¹à¸ªà¸‡à¸‰à¸²à¸¢à¸ªà¹ˆà¸‡à¸à¸à¸à¸ˆà¸²à¸à¸”าวà¸à¸±à¸‡à¸„าร (หà¸à¸”ูดาวโลเวลล์มีความชำนาà¸à¹€à¸›à¹‡à¸™à¸žà¸´à¹€à¸¨à¸©à¹€à¸£à¸·à¹ˆà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร) เป็นเวลาเจ็ดสิบนาที ผมส่งต่à¸à¸‚้à¸à¹€à¸—็จจริงนี้ไปยังยุโรปà¹à¸¥à¸°à¸ªà¹ˆà¸‡à¸ªà¸³à¹€à¸™à¸²à¸ˆà¸±à¸”รูปà¹à¸šà¸šà¹ƒà¸«à¸¡à¹ˆà¸à¸µà¸à¸«à¸¥à¸²à¸¢à¸Šà¸¸à¸”ไปทั่วประเทศ ผู้สังเà¸à¸•à¸žà¸šà¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลที่ละเà¸à¸µà¸¢à¸”ถี่ถ้วน เชื่à¸à¸–ืà¸à¹„ด้ à¹à¸¥à¸°à¹€à¸‚าà¸à¹‡à¹„ม่มีเหตุผลà¸à¸°à¹„รที่จะสงสัยว่าà¹à¸ªà¸‡à¸™à¸±à¹ˆà¸™à¸¡à¸µà¸à¸¢à¸¹à¹ˆà¸ˆà¸£à¸´à¸‡ มันส่งมาจาà¸à¸ˆà¸¸à¸”ทางภูมิศาสตร์ที่รู้จัà¸à¸à¸±à¸™à¸”ีบนดาวà¸à¸±à¸‡à¸„าร นั่นà¹à¸«à¸¥à¸°à¸„ืà¸à¸—ั้งหมด ตà¸à¸™à¸™à¸µà¹‰à¹€à¸£à¸·à¹ˆà¸à¸‡à¹„ด้ไปทั่วโลà¸à¹à¸¥à¹‰à¸§ ในยุโรปà¸à¹‡à¸¡à¸µà¸à¸²à¸£à¸à¸¥à¹ˆà¸²à¸§à¸à¸±à¸™à¸§à¹ˆà¸²à¸‰à¸±à¸™à¸à¹‡à¸¡à¸µà¸à¸²à¸£à¸•à¸´à¸”ต่à¸à¸ªà¸·à¹ˆà¸à¸ªà¸²à¸£à¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„าร à¹à¸¥à¸°à¹€à¸£à¸·à¹ˆà¸à¸‡à¸žà¸´à¸ªà¸”ารเà¸à¸´à¸™à¸ˆà¸£à¸´à¸‡à¸ªà¸²à¸£à¸žà¸±à¸”à¸à¸¢à¹ˆà¸²à¸‡à¸à¹‡à¸žà¸¸à¹ˆà¸‡à¸žà¸£à¸§à¸” ไม่ว่าà¹à¸ªà¸‡à¸™à¸±à¹ˆà¸™à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸à¸°à¹„ร พวà¸à¹€à¸£à¸²à¹„ม่มีทางล่วงรู้ ไม่ว่านั่นจะทรงปัà¸à¸à¸²à¸«à¸£à¸·à¸à¹„ม่ ใครà¸à¹‡à¸•à¸à¸šà¹„ม่ได้ มันเป็นเรื่à¸à¸‡à¸—ี่à¸à¸˜à¸´à¸šà¸²à¸¢à¹„ม่ได้โดยà¹à¸—้[249] + +ต่à¸à¸¡à¸²à¸ ายหลังพิà¸à¹€à¸„à¸à¸£à¸´à¸‡à¹„ด้เสนà¸à¹ƒà¸«à¹‰à¸¡à¸µà¸à¸²à¸£à¸à¹ˆà¸à¸ªà¸£à¹‰à¸²à¸‡à¸Šà¸¸à¸”à¸à¸£à¸°à¸ˆà¸à¹€à¸‡à¸²à¸ˆà¸³à¸™à¸§à¸™à¸¡à¸²à¸à¹ƒà¸™à¸£à¸±à¸à¹€à¸—à¸à¸‹à¸±à¸ªà¹‚ดยมุ่งหมายเพื่à¸à¸ªà¹ˆà¸‡à¸ªà¸±à¸à¸à¸²à¸“ถึงชาวดาวà¸à¸±à¸‡à¸„าร[250] + +ในทศวรรษที่ผ่านมา à¹à¸œà¸™à¸—ี่พื้นผิวดาวà¸à¸±à¸‡à¸„ารความละเà¸à¸µà¸¢à¸”สูงได้สำเร็จสมบูรณ์โดยมาร์สโà¸à¸¥à¸šà¸à¸¥à¹€à¸‹à¸à¸£à¹Œà¹€à¸§à¹€à¸¢à¸à¸£à¹Œ เปิดเผยให้เห็นว่าไม่มีสิ่งประดิษà¸à¹Œà¹à¸›à¸¥à¸à¸›à¸¥à¸à¸¡à¹ƒà¸” ๆ เลยที่à¹à¸ªà¸”งว่ามีสิ่งมีชีวิตที่ "ทรงปัà¸à¸à¸²" à¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢ à¹à¸•à¹ˆà¸à¸²à¸£à¸™à¸¶à¸à¸à¸±à¸™à¹ƒà¸™à¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์เทียมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸—รงปัà¸à¸à¸²à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารยังดำเนินต่à¸à¹„ปจาà¸à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸±à¸à¸§à¸´à¸ˆà¸²à¸£à¸“์ เช่น ริชาร์ด ซี. ฮà¸à¸à¹à¸¥à¸™à¸”์ à¸à¸²à¸£à¹‚ต้à¹à¸¢à¹‰à¸‡à¹€à¸£à¸·à¹ˆà¸à¸‡ คานาลี ดั้งเดิม à¸à¸²à¸£à¸„าดà¸à¸±à¸™à¸šà¸²à¸‡à¹€à¸£à¸·à¹ˆà¸à¸‡à¸§à¸²à¸‡à¸à¸¢à¸¹à¹ˆà¸šà¸™à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศเล็ภๆ ที่เห็นรายละเà¸à¸µà¸¢à¸”ไม่ชัดà¹à¸•à¹ˆà¸™à¸¶à¸à¸„ิดเà¸à¸²à¸œà¹ˆà¸²à¸™à¸ าพที่ได้จาà¸à¸¢à¸²à¸™à¸à¸§à¸à¸²à¸¨ à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸Šà¹ˆà¸™ 'พีระมิด' à¹à¸¥à¸° 'ใบหน้าบนดาวà¸à¸±à¸‡à¸„าร' นัà¸à¸”าราศาสตร์ดาวเคราะห์ คาร์ล เซà¹à¸à¸™ เขียนไว้ว่า:: + + ดาวà¸à¸±à¸‡à¸„ารà¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™à¸ªà¸¡à¸£à¸ ูมิà¹à¸«à¹ˆà¸‡à¹€à¸—พนิยายชนิดหนึ่งที่พวà¸à¹€à¸£à¸²à¸Šà¸²à¸§à¹‚ลà¸à¹„ด้ฉายà¸à¸à¸à¸¡à¸²à¸‹à¸¶à¹ˆà¸‡à¸„วามหวังà¹à¸¥à¸°à¸„วามà¸à¸¥à¸±à¸§[234] + +ภาพประà¸à¸à¸šà¸¡à¸²à¸£à¹Œà¹€à¸Šà¸µà¸¢à¸™à¸ªà¸²à¸¡à¸‚าจาà¸à¸«à¸™à¸±à¸‡à¸ªà¸·à¸à¹€à¸”à¸à¸°à¸§à¸à¸£à¹Œà¸à¸à¸Ÿà¹€à¸”à¸à¸°à¹€à¸§à¸´à¸¥à¸”์ส ขà¸à¸‡ เà¸à¸Š. จี. เวลส์ ฉบับà¸à¸£à¸±à¹ˆà¸‡à¹€à¸¨à¸ª ปี 1906 (พ.ศ. 2449) + +à¸à¸²à¸£à¸žà¸£à¸£à¸“นาเรื่à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารในนิยายได้รับà¸à¸²à¸£à¸à¸£à¸°à¸•à¸¸à¹‰à¸™à¹€à¸ªà¸£à¸´à¸¡à¸”้วยโทนสีà¹à¸”งเร้าà¸à¸²à¸£à¸¡à¸“์ ผนวà¸à¸à¸±à¸šà¸à¸²à¸£à¸„าดเดาตามà¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์ในสมัยคริสต์ศตวรรษที่สิบเà¸à¹‰à¸²à¸§à¹ˆà¸²à¸ าวะà¸à¸²à¸£à¸“์ต่าง ๆ บนพื้นผิวดาวจะต้à¸à¸‡à¹€à¸à¸·à¹‰à¸à¸«à¸™à¸¸à¸™à¹„ม่เฉพาะชีวิตเท่านั้นà¹à¸•à¹ˆà¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸—รงปัà¸à¸à¸²à¸à¸µà¸à¸”้วย[251] นำไปสู่à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸ªà¸£à¸£à¸„์งานในà¸à¸²à¸™à¸šà¸—ดำเนินเรื่à¸à¸‡à¸‚à¸à¸‡à¸™à¸´à¸¢à¸²à¸¢à¸§à¸´à¸—ยาศาสตร์จำนวนมาภหนึ่งในนั้นคืà¸à¹€à¸£à¸·à¹ˆà¸à¸‡ เดà¸à¸°à¸§à¸à¸£à¹Œà¸à¸à¸Ÿà¹€à¸”à¸à¸°à¹€à¸§à¸´à¸¥à¸”์ส ขà¸à¸‡ เà¸à¸Š. จี. เวลส์ ซึ่งตีพิมพ์ในปี 1898 (พ.ศ. 2441) มีเนื้à¸à¸«à¸²à¸§à¹ˆà¸²à¸Šà¸²à¸§à¸”าวà¸à¸±à¸‡à¸„ารพยายามหลบหนีà¸à¸à¸à¸ˆà¸²à¸à¸”าวเคราะห์ใà¸à¸¥à¹‰à¸•à¸²à¸¢à¸‚à¸à¸‡à¸žà¸§à¸à¹€à¸‚าโดยà¸à¸²à¸£à¸¡à¸²à¸£à¸¸à¸à¸£à¸²à¸™à¹‚ลภต่à¸à¸¡à¸²à¸ ายหลังได้มีà¸à¸²à¸£à¸—ำเดà¸à¸°à¸§à¸à¸£à¹Œà¸à¸à¸Ÿà¹€à¸”à¸à¸°à¹€à¸§à¸´à¸¥à¸”์ส ฉบับวิทยุในà¸à¹€à¸¡à¸£à¸´à¸à¸² à¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸ªà¸µà¸¢à¸‡à¹€à¸¡à¸·à¹ˆà¸à¸§à¸±à¸™à¸—ี่ 30 ตุลาคม 1938 (พ.ศ. 2481) โดยà¸à¸à¸£à¹Œà¸ªà¸±à¸™ เวลส์ซึ่งà¹à¸ªà¸”งในรูปà¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™à¸‚่าวà¹à¸šà¸šà¸ªà¸” à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸—ี่ลืà¸à¸à¸£à¸°à¸‰à¹ˆà¸à¸™à¸‚ึ้นมาทันทีเพราะไปทำให้สาธารณชนเà¸à¸´à¸”à¸à¸²à¸£à¸•à¸·à¹ˆà¸™à¸•à¸£à¸°à¸«à¸™à¸à¹€à¸¡à¸·à¹ˆà¸à¸œà¸¹à¹‰à¸Ÿà¸±à¸‡à¸ˆà¸³à¸™à¸§à¸™à¸¡à¸²à¸à¹€à¸‚้าใจผิดไปว่าสิ่งที่พวà¸à¹€à¸‚าได้ยินเป็นเรื่à¸à¸‡à¸ˆà¸£à¸´à¸‡[252] + +งานที่มีà¸à¸´à¸—ธิพลประà¸à¸à¸šà¸”้วย เดà¸à¸°à¸¡à¸²à¸£à¹Œà¹€à¸Šà¸µà¸¢à¸™à¸„รà¸à¸™à¸´à¹€à¸„ิลส์ ขà¸à¸‡ เรย์ à¹à¸šà¸£à¸”บูรี ซึ่งมีเนื้à¸à¸«à¸²à¸§à¹ˆà¸²à¸™à¸±à¸à¸ªà¸³à¸£à¸§à¸ˆà¸¡à¸™à¸¸à¸©à¸¢à¹Œà¹„ด้ทำลายà¸à¸²à¸£à¸¢à¸˜à¸£à¸£à¸¡à¸Šà¸²à¸§à¸”าวà¸à¸±à¸‡à¸„ารโดยบังเà¸à¸´à¸ นิยายชุด บาร์ซูม ขà¸à¸‡à¹€à¸à¹‡à¸”à¸à¸²à¸£à¹Œ ไรซ์ เบà¸à¸£à¹Œà¹‚รห์ นวนิยายเรื่à¸à¸‡à¹€à¸à¸²à¸—์à¸à¸à¸Ÿà¹€à¸”à¸à¸°à¹„ซเลนต์à¹à¸žà¸¥à¹€à¸™à¹‡à¸• ขà¸à¸‡à¸‹à¸µ. เà¸à¸ª. ลิวà¸à¸´à¸ª ในปี 1938[253] à¹à¸¥à¸°à¸à¸µà¸à¸«à¸¥à¸²à¸¢à¸Šà¸´à¹‰à¸™à¸‡à¸²à¸™à¸‚à¸à¸‡à¹‚รเบิร์ต เà¸. ไฮน์ไลน์à¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²à¸Šà¹ˆà¸§à¸‡à¸à¸¥à¸²à¸‡à¸„ริสต์ทศวรรษหà¸à¸ªà¸´à¸š[254] + +โจนาธาน สวิฟท์ได้มีà¸à¸²à¸£à¸à¹‰à¸²à¸‡à¸à¸´à¸‡à¸–ึงดวงจันทร์บริวารขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารซึ่งเป็นเวลาà¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²à¸à¸²à¸£à¸„้นพบจริงโดยเà¸à¹€à¸ªà¸Ÿ ฮà¸à¸¥à¸¥à¹Œà¸à¸§à¹ˆà¸² 150 ปี โดยบรรยายรายละเà¸à¸µà¸¢à¸”ลัà¸à¸©à¸“ะวงโคจรขà¸à¸‡à¸”าวเหล่านั้นได้ใà¸à¸¥à¹‰à¹€à¸„ียงเป็นเหตุเป็นผลในบทที่ 19 ในนวนิยายขà¸à¸‡à¹€à¸‚าเรื่à¸à¸‡ à¸à¸±à¸¥à¸¥à¸´à¹€à¸§à¸à¸£à¹Œà¹à¸—รฟเวลส์[255] + +มาร์วินเดà¸à¸°à¸¡à¸²à¸£à¹Œà¹€à¸Šà¸µà¸¢à¸™ เป็นตัวà¸à¸²à¸£à¹Œà¸•à¸¹à¸™à¸¥à¸±à¸à¸©à¸“ะชาวดาวà¸à¸±à¸‡à¸„ารที่เฉลียวฉลาด เริ่มปราà¸à¸à¹ƒà¸™à¹‚ทรทัศน์เมื่à¸à¸›à¸µ 1948 (พ.ศ. 2491) ในà¸à¸²à¸™à¸°à¸•à¸±à¸§à¸¥à¸°à¸„รหนึ่งในà¸à¸²à¸£à¹Œà¸•à¸¹à¸™à¸ าพเคลื่à¸à¸™à¹„หวเรื่à¸à¸‡à¸¥à¸¹à¸™à¸µà¸—ูนส์ขà¸à¸‡à¸§à¸à¸£à¹Œà¹€à¸™à¸à¸£à¹Œà¸šà¸£à¸²à¹€à¸˜à¸à¸£à¹Œà¸ª à¹à¸¥à¸°à¸¢à¸±à¸‡à¸”ำเนินต่à¸à¸¡à¸²à¹ƒà¸™à¸à¸²à¸™à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¸«à¸™à¸¶à¹ˆà¸‡à¸‚à¸à¸‡à¸§à¸±à¸’นธรรมนิยมจนปัจจุบัน[256] + +หลังจาà¸à¸¢à¸²à¸™à¸à¸§à¸à¸²à¸¨à¸¡à¸²à¸£à¸´à¹€à¸™à¸à¸£à¹Œà¹à¸¥à¸°à¹„วà¸à¸´à¸‡à¹„ด้ส่งภาพดาวà¸à¸±à¸‡à¸„ารตามสภาพที่เป็นจริงมาà¸à¸¡à¸²à¸¢à¸à¸¥à¸±à¸šà¸¡à¸² ว่าเป็นโลà¸à¸—ี่à¹à¸¥à¹‰à¸‡à¸£à¹‰à¸²à¸‡ ไร้ซึ่งชีวิตà¸à¸¢à¹ˆà¸²à¸‡à¸Šà¸±à¸”à¹à¸ˆà¹‰à¸‡ à¹à¸¥à¸°à¸›à¸£à¸²à¸¨à¸ˆà¸²à¸à¸„ลà¸à¸‡à¹ƒà¸” ๆ à¹à¸™à¸§à¸„ิดดั้งเดิมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„ารà¸à¹‡à¸–ูà¸à¹‚ละทิ้ง นำมาสู่สมัยนิยมà¹à¸«à¹ˆà¸‡à¹€à¸£à¸·à¹ˆà¸à¸‡à¸£à¸²à¸§à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸™à¸´à¸„มà¸à¸¢à¸¹à¹ˆà¸à¸²à¸¨à¸±à¸¢à¸‚à¸à¸‡à¸¡à¸™à¸¸à¸©à¸¢à¹Œà¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารà¹à¸šà¸šà¸ªà¸à¸”คล้à¸à¸‡à¹€à¸—ี่ยงตรงตามจริง เรื่à¸à¸‡à¸—ี่เป็นที่รู้จัà¸à¸à¸±à¸™à¸”ีที่สุดเรื่à¸à¸‡à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¸¥à¸±à¸à¸©à¸“ะนี้คืภมาร์สไตรโลจี ขà¸à¸‡à¸„ิม สà¹à¸•à¸™à¸¥à¸µà¸¢à¹Œ โรบินสัน à¸à¸¢à¹ˆà¸²à¸‡à¹„รà¸à¹‡à¸•à¸²à¸¡ à¸à¸²à¸£à¸„าดเดาà¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์เทียมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¹ƒà¸šà¸«à¸™à¹‰à¸²à¸šà¸™à¸”าวà¸à¸±à¸‡à¸„ารตลà¸à¸”จนจุดลึà¸à¸¥à¸±à¸šà¸™à¹ˆà¸²à¸žà¸´à¸¨à¸§à¸‡à¸à¸·à¹ˆà¸™ ๆ ซึ่งยานสำรวจà¸à¸§à¸à¸²à¸¨à¸ˆà¸±à¸šà¸ าพได้ว่าเป็นร่à¸à¸‡à¸£à¸à¸¢à¸‚à¸à¸‡à¸à¸²à¸£à¸¢à¸˜à¸£à¸£à¸¡à¹‚บราณ ยังเป็นà¹à¸™à¸§à¸—างยà¸à¸”นิยมในบันเทิงคดีà¹à¸™à¸§à¸§à¸´à¸—ยาศาสตร์มาà¸à¸¢à¹ˆà¸²à¸‡à¸•à¹ˆà¸à¹€à¸™à¸·à¹ˆà¸à¸‡ โดยเฉพาะà¸à¸¢à¹ˆà¸²à¸‡à¸¢à¸´à¹ˆà¸‡à¹ƒà¸™à¸ าพยนตร์[257] +ดาวบริวาร[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาวบริวารขà¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร, โฟบà¸à¸ª à¹à¸¥à¸° ดีมà¸à¸ª +ภาพ ไฮไรส์ ปรับระดับสีขà¸à¸‡à¹‚ฟบà¸à¸ªà¹à¸ªà¸”งชุดร่à¸à¸‡à¸—ี่ขนานà¸à¸±à¸™à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆ à¹à¸¥à¸°à¹‚ซ่หลุมà¸à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸à¸±à¸šà¸«à¸¥à¸¸à¸¡à¸ªà¸•à¸´à¸à¸™à¸µà¸¢à¹Œà¸—างด้านขวา +ภาพไฮไรส์ ปรับระดับสีขà¸à¸‡à¸”ีมà¸à¸ª (ไม่ตามสัดส่วนà¸à¸±à¸šà¸£à¸¹à¸›à¸šà¸™) à¹à¸ªà¸”งผืนเรโà¸à¸¥à¸´à¸˜à¸£à¸²à¸šà¹€à¸£à¸µà¸¢à¸šà¸›à¸à¸„ลุมดาว + +ดาวà¸à¸±à¸‡à¸„ารมีดาวบริวารค่à¸à¸™à¸‚้างเล็à¸à¸ªà¸à¸‡à¸”วง ได้à¹à¸à¹ˆ โฟบà¸à¸ª (เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 22 à¸à¸´à¹‚ลเมตร (14 ไมล์)) à¹à¸¥à¸° ดีมà¸à¸ª (เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 12 à¸à¸´à¹‚ลเมตร (7.5 ไมล์)) โดยมีวงโคจรใà¸à¸¥à¹‰à¸à¸±à¸šà¸”าวเคราะห์à¹à¸¡à¹ˆ ทฤษฎีที่à¸à¸˜à¸´à¸šà¸²à¸¢à¸§à¹ˆà¸²à¸—ั้งคู่เป็นดาวเคราะห์น้à¸à¸¢à¸—ี่ถูà¸à¸ˆà¸±à¸šà¹€à¸à¸²à¹„ว้เป็นที่นิยมมายาวนาน à¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸³à¹€à¸™à¸´à¸”ที่มานั้นยังคลุมเครืà¸[258] ดาวบริวารทั้งสà¸à¸‡à¸–ูà¸à¸„้นพบในปี 1877 (พ.ศ. 2420) โดยเà¸à¹€à¸ªà¸Ÿ ฮà¸à¸¥à¸¥à¹Œ ตั้งชื่à¸à¸•à¸²à¸¡à¹‚ฟบà¸à¸ª (ตระหนà¸/à¸à¸¥à¸±à¸§) à¹à¸¥à¸°à¸”ีมà¸à¸ª (สยà¸à¸‡/น่าขนลุà¸) ซึ่งเป็นเทพในตำนานà¸à¸£à¸µà¸ ร่วมไปà¸à¸±à¸šà¹€à¸—พà¹à¸à¸£à¸µà¸ª เทพเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามบิดาขà¸à¸‡à¸žà¸§à¸à¹€à¸‚า ชื่à¸à¸”าวà¸à¸±à¸‡à¸„ารว่า "มาร์ส" นั้นคืà¸à¸Šà¸·à¹ˆà¸à¹€à¸—พà¹à¸à¸£à¸µà¸ªà¸•à¸²à¸¡à¹à¸šà¸šà¹‚รมัน[259][260] ในà¸à¸£à¸µà¸à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™ ดาวà¸à¸±à¸‡à¸„ารยังคงใช้ชื่à¸à¸•à¸²à¸¡à¸à¸¢à¹ˆà¸²à¸‡à¹‚บราณว่า Ares (Aris: ΆÏης)[261] + +จาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวà¸à¸±à¸‡à¸„าร à¸à¸²à¸£à¹€à¸„ลื่à¸à¸™à¸—ี่ขà¸à¸‡à¹‚ฟบà¸à¸ªà¹à¸¥à¸°à¸”ีมà¸à¸ªà¸ˆà¸°à¸›à¸£à¸²à¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸à¸à¹„ปจาà¸à¸”วงจันทร์ โฟบà¸à¸ªà¸ˆà¸°à¸‚ึ้นทางทิศตะวันตภตà¸à¸—างทิศตะวันà¸à¸à¸ à¹à¸¥à¸°à¸à¸¥à¸±à¸šà¸¡à¸²à¸‚ึ้นà¸à¸µà¸à¸„รั้งในเวลาเพียง 11 ชั่วโมง ส่วนดีมà¸à¸ªà¸‹à¸¶à¹ˆà¸‡à¸à¸¢à¸¹à¹ˆà¸™à¸à¸à¸§à¸‡à¹‚คจรพ้à¸à¸‡à¸„าบพà¸à¸”ี ระยะคาบà¸à¸²à¸£à¹‚คจรขà¸à¸‡à¸”าวจึงไม่ตรงพà¸à¸”ีà¸à¸±à¸šà¸„าบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸à¸šà¸•à¸±à¸§à¹€à¸à¸‡à¸‚à¸à¸‡à¸”าวเคราะห์à¹à¸¡à¹ˆ ดาวจะไม่ลà¸à¸¢à¸„้างฟ้าในตำà¹à¸«à¸™à¹ˆà¸‡à¹€à¸”ิมà¹à¸•à¹ˆà¸ˆà¸°à¸‚ึ้นตามปà¸à¸•à¸´à¸—างทิศตะวันà¸à¸à¸à¸à¸¢à¹ˆà¸²à¸‡à¸Šà¹‰à¸² ๆ à¹à¸¡à¹‰à¸”ีมà¸à¸ªà¸ˆà¸°à¸¡à¸µà¸„าบà¸à¸²à¸£à¹‚คจรราว 30 ชั่วโมง à¹à¸•à¹ˆà¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸²à¸–ึง 2.7 วันระหว่างà¸à¸²à¸£à¸‚ึ้นจนตà¸à¸¥à¸±à¸šà¸Ÿà¹‰à¸²à¹„ปสำหรับผู้สังเà¸à¸•à¸—ี่ศูนย์สูตร ซึ่งà¸à¹‡à¸ˆà¸°à¸¥à¸±à¸šà¹„ปà¸à¸¢à¹ˆà¸²à¸‡à¸Šà¹‰à¸² ๆ คล้à¸à¸¢à¸«à¸¥à¸±à¸‡à¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸à¸šà¸•à¸±à¸§à¹€à¸à¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„าร[262] +วงโคจรขà¸à¸‡à¹‚ฟบà¸à¸ªà¹à¸¥à¸°à¸”ีมà¸à¸ª (ตามสัดส่วน) + +เนื่à¸à¸‡à¸ˆà¸²à¸à¸§à¸‡à¹‚คจรขà¸à¸‡à¹‚ฟบà¸à¸ªà¸•à¹ˆà¸³à¸à¸§à¹ˆà¸²à¸£à¸°à¸”ับความสูงพ้à¸à¸‡à¸„าบ à¹à¸£à¸‡à¹„ทดัลจาà¸à¸”าวà¸à¸±à¸‡à¸„ารจึงดึงวงโคจรขà¸à¸‡à¸”าวให้ต่ำลงไปเรื่à¸à¸¢ ๆ ทีละน้à¸à¸¢ à¸à¸µà¸à¸›à¸£à¸°à¸¡à¸²à¸“ 50 ล้านปีข้างหน้า เป็นไปได้ว่าโฟบà¸à¸ªà¸à¸²à¸ˆà¸žà¸¸à¹ˆà¸‡à¹€à¸‚้าชนà¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„ารหรืà¸à¹„ม่à¸à¹‡à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¸à¸à¸à¸à¸¥à¸²à¸¢à¹€à¸›à¹‡à¸™à¹‚ครงสร้างวงà¹à¸«à¸§à¸™à¸£à¸à¸šà¸”าวเคราะห์[262] + +à¸à¸³à¹€à¸™à¸´à¸”ขà¸à¸‡à¸”าวบริวารทั้งสà¸à¸‡à¸™à¸±à¹‰à¸™à¸¢à¸±à¸‡à¹„ม่เป็นที่เข้าใจดีนัภà¸à¸²à¸£à¸¡à¸µà¸à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸ªà¸°à¸—้à¸à¸™à¸•à¹ˆà¸³à¹à¸¥à¸°à¸¡à¸µà¸à¸‡à¸„์ประà¸à¸à¸šà¹à¸šà¸šà¸«à¸´à¸™à¸„à¸à¸™à¹„ดรต์à¸à¸¥à¸¸à¹ˆà¸¡à¸„าร์บà¸à¹€à¸™à¹€à¸Šà¸µà¸¢à¸ªà¸—ำให้มีความคล้ายคลึงà¸à¸±à¸šà¸”าวเคราะห์น้à¸à¸¢à¸‹à¸¶à¹ˆà¸‡à¸Šà¹ˆà¸§à¸¢à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸—ฤษฎีà¸à¸²à¸£à¸ˆà¸±à¸šà¸¢à¸¶à¸” วงโคจรที่ไม่เสถียรขà¸à¸‡à¹‚ฟบà¸à¸ªà¹€à¸«à¸¡à¸·à¸à¸™à¸ˆà¸°à¸Šà¸µà¹‰à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™à¸à¸²à¸£à¸ˆà¸±à¸šà¹€à¸à¸²à¹„ว้ที่ค่à¸à¸™à¸‚้างใหม่ à¹à¸•à¹ˆà¸—ั้งคู่มีวงโคจรที่à¸à¸¥à¸¡à¹ƒà¸à¸¥à¹‰à¸à¸±à¸šà¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£à¸‹à¸¶à¹ˆà¸‡à¸ˆà¸±à¸”ว่าไม่ปà¸à¸•à¸´à¸ªà¸³à¸«à¸£à¸±à¸šà¸§à¸±à¸•à¸–ุที่ถูà¸à¸ˆà¸±à¸šà¹„ว้ได้à¹à¸¥à¸°à¸¢à¸±à¸‡à¸•à¹‰à¸à¸‡à¸à¸²à¸£à¸žà¸¥à¸§à¸±à¸•à¸à¸²à¸£à¸¢à¸¶à¸”จับที่สลับซับซ้à¸à¸™ à¸à¸²à¸£à¸ˆà¸±à¸šà¸•à¸±à¸§à¸žà¸à¸à¸žà¸¹à¸™à¸‚ึ้นตั้งà¹à¸•à¹ˆà¸Šà¹ˆà¸§à¸‡à¸•à¹‰à¸™à¸‚à¸à¸‡à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸¨à¸²à¸ªà¸•à¸£à¹Œà¸”าวà¸à¸±à¸‡à¸„ารยังเป็นà¸à¸£à¸“ีที่ถืà¸à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™à¹„ปได้ ถ้าหาà¸à¸§à¹ˆà¸²à¸ˆà¸°à¹„ม่นับรวมลัà¸à¸©à¸“ะà¸à¸‡à¸„์ประà¸à¸à¸šà¸‚à¸à¸‡à¸—ั้งคู่ที่คล้ายคลึงà¸à¸±à¸šà¸”าวเคราะห์น้à¸à¸¢à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸—ี่จะเหมืà¸à¸™à¸à¸±à¸šà¸”าวà¸à¸±à¸‡à¸„ารซึ่งยังต้à¸à¸‡à¸£à¸à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™ + +ความเป็นไปได้ในรูปà¹à¸šà¸šà¸—ี่สามคืà¸à¸à¸²à¸£à¸¡à¸µà¸§à¸±à¸•à¸–ุที่สามเข้ามาเà¸à¸µà¹ˆà¸¢à¸§à¸‚้à¸à¸‡à¸«à¸£à¸·à¸à¹€à¸›à¹‡à¸™à¸Šà¸™à¸´à¸”หนึ่งขà¸à¸‡à¸à¸²à¸£à¹à¸•à¸à¸à¸£à¸°à¸ˆà¸²à¸¢à¸à¸à¸à¸¡à¸²à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™[263] หลัà¸à¸à¸²à¸™à¸«à¸¥à¸²à¸¢à¸›à¸£à¸°à¸à¸²à¸£à¸—ี่ได้มาค่à¸à¸™à¸‚้างใหม่พบว่าโครงสร้างภายในขà¸à¸‡à¹‚ฟบà¸à¸ªà¸¡à¸µà¸„วามพรุนสูง[264] à¹à¸¥à¸°à¸Šà¸µà¹‰à¸§à¹ˆà¸²à¸à¸‡à¸„์ประà¸à¸à¸šà¸ ายในส่วนใหà¸à¹ˆà¹€à¸›à¹‡à¸™à¸Ÿà¸´à¸¥à¹‚ลซิลิเà¸à¸•à¹à¸¥à¸°à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸à¸·à¹ˆà¸™ ๆ ที่ทราบว่ามีบนดาวà¸à¸±à¸‡à¸„าร[265] ทำให้ประเด็นà¸à¸²à¸£à¸à¸³à¹€à¸™à¸´à¸”ขà¸à¸‡à¹‚ฟบà¸à¸ªà¸§à¹ˆà¸²à¸¡à¸²à¸ˆà¸²à¸à¹€à¸¨à¸©à¸§à¸±à¸•à¸–ุที่à¸à¸£à¸°à¸ˆà¸²à¸¢à¸à¸à¸à¸¡à¸²à¸ ายหลังà¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารà¹à¸¥à¹‰à¸§à¹„ด้มารวมà¸à¸±à¸™à¹ƒà¸™à¸§à¸‡à¹‚คจรรà¸à¸šà¸”าวà¹à¸¡à¹ˆà¸™à¸±à¹‰à¸™à¸™à¹ˆà¸²à¹€à¸Šà¸·à¹ˆà¸à¸–ืà¸à¸¡à¸²à¸à¸‚ึ้น[266] คล้ายà¸à¸±à¸™à¸à¸±à¸šà¸—ฤษฎีà¸à¸£à¸°à¹à¸ªà¸«à¸¥à¸±à¸à¹€à¸£à¸·à¹ˆà¸à¸‡à¸à¸²à¸£à¸à¸³à¹€à¸™à¸´à¸”ดวงจันทร์ขà¸à¸‡à¹‚ลภà¸à¸¢à¹ˆà¸²à¸‡à¹„รà¸à¹‡à¸•à¸²à¸¡ ค่าสเปà¸à¸•à¸£à¸±à¸¡à¸‚à¸à¸‡à¹à¸ªà¸‡à¸—ี่มà¸à¸‡à¹€à¸«à¹‡à¸™à¹„ด้ถึงช่วงใà¸à¸¥à¹‰à¸à¸´à¸™à¸Ÿà¸£à¸²à¹€à¸£à¸” (VNIR) ขà¸à¸‡à¸”าวบริวารทั้งสà¸à¸‡à¸‚à¸à¸‡à¸”าวà¸à¸±à¸‡à¸„ารมีความคล้ายคลึงà¸à¸±à¸šà¸—ี่วัดได้จาà¸à¹à¸–บดาวเคราะห์น้à¸à¸¢à¸”้านนà¸à¸ à¹à¸¥à¸°à¸¡à¸µà¸£à¸²à¸¢à¸‡à¸²à¸™à¸§à¹ˆà¸²à¸ªà¹€à¸›à¸à¸•à¸£à¸±à¸¡à¸£à¸±à¸‡à¸ªà¸µà¸à¸´à¸™à¸Ÿà¸£à¸²à¹€à¸£à¸”ขà¸à¸‡à¹‚ฟบà¸à¸ªà¹„ม่สà¸à¸”คล้à¸à¸‡à¸à¸±à¸šà¸„à¸à¸™à¹„ดรต์ไม่ว่าจะà¸à¸¥à¸¸à¹ˆà¸¡à¹ƒà¸”[265] + +ดาวà¸à¸±à¸‡à¸„ารà¸à¸²à¸ˆà¸ˆà¸°à¸¡à¸µà¸”าวบริวารà¸à¸·à¹ˆà¸™à¸™à¸à¸à¹€à¸«à¸™à¸·à¸à¸ˆà¸²à¸à¸™à¸µà¹‰à¹à¸•à¹ˆà¸¡à¸µà¸‚นาดเล็à¸à¸”้วยเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸£à¸²à¸§ 50 - 100 เมตร (160 ถึง 330 ฟุต) à¹à¸¥à¸°à¸„าดว่ามีวงà¹à¸«à¸§à¸™à¸à¸¸à¹ˆà¸™à¸à¸¢à¸¹à¹ˆà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¹‚ฟบà¸à¸ªà¸à¸±à¸šà¸”ีมà¸à¸ª[19] diff --git a/xpcom/tests/gtest/wikipedia/tr.txt b/xpcom/tests/gtest/wikipedia/tr.txt new file mode 100644 index 0000000000..7c83510231 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/tr.txt @@ -0,0 +1,245 @@ +Latince Mars veya Arapça Merih (Türkçe: Bakırsokum[1] ya da Sakıt[2]), GüneÅŸ Sistemi'nin GüneÅŸ'ten itibâren dördüncü gezegeni. Roma mitolojisindeki savaÅŸ tanrısı Mars'a ithâfen adlandırılmıştır. Yüzeyindeki yaygın demiroksitten dolayı kızılımsı bir görünüme sahip olduÄŸu için Kızıl Gezegen de denir. + +Ä°nce bir atmosferi olan Mars gerek Ay'daki gibi meteor kraterlerini, gerekse Dünya'daki gibi volkan, vadi, çöl ve kutup bölgelerini içeren çehresiyle bir yerbenzeri gezegendir. Ayrıca dönme periyodu ve mevsim dönemleri Dünya’nınkine çok benzer. 2 adet uydusu bulunmaktadır. + +Mars’taki Olimpos Dağı (Olympus Mons) adı verilen daÄŸ GüneÅŸ Sistemi’nde bilinen en yüksek daÄŸ ve Marineris Vadisi (Valles Marineris) adı verilen kanyon en büyük kanyondur. Ayrıca Haziran 2008’de Nature dergisinde yayımlanan üç makalede açıklandığı gibi, Mars’ın kuzey yarımküresinde 10.600 km. uzunluÄŸunda ve 8.500 km. geniÅŸliÄŸindeki dev bir meteor kraterinin varlığı saptanmıştır. Bu krater, bugüne kadar keÅŸfedilmiÅŸ en büyük meteor kraterinin (Ay'ın güney kutbu kısmındaki Atkien Havzası) dört misli büyüklüğündedir.[3][4] + +Mars, Dünya hariç tutulursa, halen GüneÅŸ Sistemi’ndeki gezegenler içinde sıvı su ve yaÅŸam içermesi en muhtemel gezegen olarak görülmektedir.[5] Mars Express ve Mars Reconnaissance Orbiter keÅŸif projelerinin radar verileri gerek kutuplarda (Temmuz 2005)[6] gerekse orta bölgelerde (Kasım 2008)[7] geniÅŸ miktarlarda su buzlarının var olduÄŸunu ortaya koymuÅŸ bulunmaktadır. 31 Temmuz 2008’de Phoenix Mars Lander adlı robotik uzay gemisi Mars toprağının sığ bölgelerindeki su buzlarından örnekler almayı baÅŸarmıştır.[8] + +Günümüzde, Mars, yörüngelerine oturmuÅŸ üç uzay gemisine evsahipliÄŸi yapmaktadır: Mars Odyssey, Mars Express ve Mars Reconnaissance Orbiter. Mars, Dünya hariç tutulursa, GüneÅŸ Sistemi’ndeki herhangi bir sıradan gezegenden ibaret deÄŸildir. Yüzeyi pek çok uzay aracına evsahipliÄŸi yapmıştır. Bu uzay araçlarıyla elde edilen jeolojik veriler ÅŸunu ortaya koymuÅŸtur ki, Mars önceden su konusunda geniÅŸ bir çeÅŸitliliÄŸe sahipti; hatta geçen on yıllık süre sırasında gayzer (kaynaç) türü su fışkırma veya akıntıları meydana gelmiÅŸti.[9] NASA’nın Mars Global Surveyor projesi kapsamında sürdürülen incelemeler Mars’ın güney kutbu buz bölgesinin geri çekilmiÅŸ olduÄŸunu ortaya koymuÅŸtur.[10] Bilim insanları, 2006'da Mars yörüngesine oturtulan "Mars Reconnaissance Orbiter" (Mars Yörünge KaÅŸifi) uydusundan alınan veriler sonucu, Mars'ta sıcak aylarda tuzlu su akıntılarının oluÅŸtuÄŸunu bildirmiÅŸlerdir.[11] + +Mars’ın 1877 yılında astronom Asaph Hall tarafından keÅŸfedilen Phobos ve Deimos adları verilmiÅŸ, düzensiz biçimli iki küçük uydusu vardır. Mars Dünya’dan çıplak gözle görülebilmektedir. "Görünür kadir"i −2,9’a[12] ulaşır ki bu, çıplak gözle çoÄŸu zaman Jüpiter Mars’tan daha parlak görünmesine karşın, ancak Venüs, Ay ve Güneş’çe aşılabilen bir parlaklıktır. + +Fiziksel özellikler[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’ın yarıçapı Dünya’nınkinin yaklaşık yarısı kadardır. YoÄŸunluÄŸu Dünya’nınkinden daha az olup, hacmi Dünya’nın hacminin % 15’i, kütlesi ise Dünya’nınkinin % 11’i kadardır. Mars’ın Merkür’den daha büyük ve daha ağır olmasına karşılık, Merkür ondan daha yoÄŸundur. Bu yüzden Merkürün yüzeyindeki yerçekimi Mars’ınkinden daha fazladır. Mars, boyutu, kütlesi ve yüzeyindeki yerçekimi bakımından Dünya ile Ay arasında yer alır. Mars yüzeyinin kızıl-turuncu görünümü hematit ya da pas adıyla tanınan demiroksitten (Fe2O3) kaynaklanır.[13] +Jeoloji ("arkeoloji")[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Dört "yerbenzeri gezegen"in[14] boyutlarının mukayesesi: Soldan saÄŸa doÄŸru Merkür, Venüs, Dünya ve Mars. +Mars’ın üstteki topoÄŸrafik haritasında daha ziyade volkanik platolar (kırmızı) ve çarpışma havzaları (mavi) hakim görünmektedir. +Mars Pathfinder tarafından çekilmiÅŸ Mars’ın dağınık kaya oluÅŸumlu bir yüzey fotoÄŸrafı + +Uydu gözlemleri ile Mars meteorlarının incelenmesi Mars yüzeyinin esas olarak bazalttan oluÅŸtuÄŸunu göstermektedir. Bazı kanıtlar Mars yüzeyinin bir kısmının tipik bazalttan ziyade, yeryüzündeki andezit kayalarının benzeri olabilecek zengin silisyum oluÅŸumlarından meydana geldiÄŸini göstermektedir; fakat gözlemlerdeki veriler bunların silisli cam olduÄŸu ÅŸeklinde de yorumlanabilir. Her ne kadar Mars’ın asli manyetik alanı yoksa da, gözlemler gezegen kabuÄŸunun parçalarının vaktiyle iki kutuplu bir manyetik alanın etkisinde bulunmuÅŸ olduÄŸunu göstermektedir. Minerallerde gözlemlenen bu paleomanyetizm[15] yeryüzünün okyanus diplerinde bulunan tabakalarındakilere çok benzer özelliklere sahiptir. 1999’da ortaya atılan ve 2005’te Mars Global Surveyor verileriyle yeniden gözden geçirilen bir teoriye göre bu tabakalar, Mars’ta 4 milyar yıl önce, manyetik kutuplaÅŸmanın yani manyetik alanın henüz etkin olduÄŸu dönemde mevcut olan tektonik plakaların kanıtıdır.[16] + +Gezegenin iç yapısına iliÅŸkin güncel modellere göre, gezegen, esas olarak demir ve % 14-17 civarında sülfürden oluÅŸan, yarıçapı yaklaşık 1480 km. olan bir çekirdek bölgesi içerir. Bu demir sülfür (FeS) bileÅŸiÄŸi kısmen akışkandır. Çekirdek, günümüzde etkin olmadığı görülen, gezegendeki birçok tektonik ve volkanik oluÅŸumlardan oluÅŸmuÅŸ bir silikat mantosuyla çevrilidir. Gezegenin kabuÄŸunun ortalama kalınlığı 50 km. olup, azami kalınlığı 120 km. civarındadır.[17] Dünya’nın ortalama kalınlığı 40 km. olan kabuÄŸu, her iki gezegenin boyutları gözönüne alındığında Mars’ınkine göre üç misli daha ince kalır. + +Mars’ın temel jeolojik devirleri ÅŸunlardır: + + Nuh Devri: Devre bu ad, Mars’ın güney yarımküresindeki bir bölgenin Nuh’un Toprağı (Noachis Terra) olarak adlandırılması nedeniyle verilmiÅŸtir. Mars’ın en eski yüzey oluÅŸumuna iliÅŸkin devirdir, 3,8 milyar yıl öncesi ile 3,5 milyar yıl öncesi arasındaki dönemi kapsar. Nuh Devri yüzeyleri birçok büyük çarpma kraterleriyle oyulmuÅŸ haldedir. Tharsis volkanik plato bölgesinin bu devirdeki büyük bir sıvı su baskınıyla oluÅŸtuÄŸu sanılmaktadır. + Hesperian devri: 3,5 milyar yıl öncesi ile 1,8 milyar yıl öncesi arasındaki dönemi kapsar. Bu devir, geniÅŸ lav ovalarının oluÅŸumu ile nitelenir. + Amazon Devri: 1,8 milyar yıl öncesi ile günümüze kadarki dönemi kapsar. Amazon Devri bölgeleri, meteor çarpmalarıyla açılmış kraterleri pek içermez ve tamamen deÄŸiÅŸiktir. Ãœnlü Olimpos Dağı bu dönemdeki lav akıntılarıyla oluÅŸmuÅŸtur. + +19 Åžubat 2008’de Mars’ta muhteÅŸem bir çığ meydana geldi. Mars Reconnaissance Orbiter uzay gemisinin kamerasınca filme kaydedilen görüntülerde 700 m. yükseklikteki bir uçurumun tepesinden kopan buz bloklarının ardında toz bulutları bırakarak yuvarlanışları görülüyordu.[18] Son incelemeler ilk kez 1980’lerde ortaya atılmış bir teoriyi desteklemektedir: Bu teoriye göre 4 milyar önce Mars’a Plüton gezegeni boyutlarındaki bir meteor çarpmıştır. Gezegenin kuzey kutup bölgesini kapsadığı gibi, yaklaşık % 40’ını kapsayan Borealis basin adı verilen garip havzanın bu çarpmayla oluÅŸtuÄŸu sanılmaktadır.[19][20] +Toprak[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Haziran 2008’de Phoenix uzay gemisi tarafından gönderilen veriler Mars toprağının hafifçe alkalin olduÄŸunu ve hepsi de organik maddenin geliÅŸmesi için elzem olan magnezyum, sodyum, potasyum ve klorür içerdiÄŸini ortaya koydu. Bilim insanları Mars’ın kuzey kutbuna yakın toprağın kuÅŸkonmaz gibi bitkilerin yetiÅŸtirilebileceÄŸi bir bahçe oluÅŸturulması için elveriÅŸli olduÄŸu sonucuna vardı.[21] AÄŸustos 2008’de Phoenix uzay gemisi Dünya suyu ile Mars toprağının karıştırılması gibi basit kimya deneylerine baÅŸladı ve önceden Mars toprağı konusunda ortaya atılmış birçok teoriyi doÄŸrulayan bir keÅŸifte bulundu: Mars toprağında perklorat tuzlarının izlerini keÅŸfetti. Perklorat tuzlarının varlığı Mars toprağının daha da ilginç bulunmasını saÄŸlamıştı[22] Fakat perklorat tuzlarının varlığının Mars’a taşınan Dünya toprağından, çeÅŸitli örneklerden veya aletlerden kaynaklanmış olma olasılığı da vardı; bu yüzden, kaynağın Mars toprağı olup olmadığından iyice emin olunması için bu konuda daha fazla deneyler yapılması gerekmektedir.[23] +2005 yılı Kasım ayı sonunda Mars Exploration Rover A Spirit 'in Husband Hill'in zirvesinden inerken çektiÄŸi Marstan bir panoramik fotoÄŸraf. +Hidroloji[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Cerberus Fossae adı verilen yüzey yarıkları +Opportunity adlı uzay keÅŸif aracı (astromobil) tarafından çekilmiÅŸ, Mars yüzeyinde geçmiÅŸte sıvı su bulunduÄŸunu gösteren mikroskobik kaya oluÅŸumlarının fotoÄŸrafı +10 Eylül 2005'te Mars Global Surveyor sonda aracı tarafından alınmış bu fotoÄŸraf (saÄŸda) 30 AÄŸustos 1999'daki fotoÄŸrafta (solda) mevcut olmayan su buzuna benzer beyazımsı bir çökeltinin meydana geldiÄŸini, yani geçici de olsa, yüzeyde sıvı su akışının varlığını ortaya koymaktadır.[24][25] + +1965’te Mariner-4’le gerçekleÅŸtirilen ilk Mars alçak uçuÅŸuna kadar, gezegenin yüzeyinde sıvı su olup olmadığı çok tartışılmıştı. Bu tartışma özellikle kutup bölgelerindeki periyodik olarak deÄŸiÅŸim gösteren, deniz ve kıtaları andıran açık ve koyu renkli lekelerin gözlemlenmiÅŸ olmasından kaynaklanıyordu. Koyu renkli çizgiler bazı gözlemciler tarafından uzun zaman sıvı su içeren sulama kanalları olarak yorumlanmıştı. Bu düz çizgi oluÅŸumları sonraki dönemlerde gözlemlenemediÄŸinden optik illüzyonlar olarak yorumlandı. Kısa dönemlerde alçak irtifalarda olabilecek oluÅŸumlar hariç tutulursa, günümüzdeki atmosferik basınç altında Mars yüzeyinde sıvı su mevcut olamaz, ancak geçici sıvı su akışları olabilir.[24][25][26][27] Buna karşılık özellikle iki kutup bölgesinde geniÅŸ su buzları mevcuttur.[28] Mart 2007’de NASA, güney kutbu bölgesindeki su buzlarının erimeleri halinde suların gezegenin tüm yüzeyini kaplayacağını ve oluÅŸacak bu okyanusun derinliÄŸinin 11 m. olacağının hesaplandığını açıkladı.[29] Ayrıca gezegende kutuptan 60° enlemine kadar bir buz permafrost[30] mantosu uzanır.[28] Mars’ta kalın kriyosfer[31] tabakasının altında, büyük miktarlarda, sıkışık halde tutulmuÅŸ (yüzeye çıkamayan) su rezervlerinin bulunduÄŸu sanılmaktadır. Mars Express ve Mars Reconnaissance Orbiter’dan gelen radar verileri her iki kutupta (Temmuz 2005)[6] ve orta enlemlerde (Kasım 2008)[7] büyük miktarlarda su buzlarının bulunduÄŸunu ortaya koymuÅŸtur. Phoenix Mars Lander ise 31 Temmuz 2008’de Mars toprağındaki su buzlarından örnek parçalar almayı baÅŸarmıştır.[32] + +Mars tarihinin nispeten erken bir döneminde Valles Marineris Vadisi (4000 km.) oluÅŸtuÄŸunda su kanallarının oluÅŸmasına neden olan, serbest kalmış yeraltı sularının yol açtığı büyük bir sıvı su baskınının meydana geldiÄŸi sanılmaktadır. Bu su baskının biraz daha küçüğü de daha sonra Cerberus Fossae denilen büyük yüzey yarıklarının açıldığı dönemde, yani yaklaşık 5 milyon yıl önce meydana gelmiÅŸtir ki, Cerberus Palus bölgesindeki Elysium Planitia’da halen görülebilen donmuÅŸ denizin bu olayın bir sonucu olduÄŸu sanılmaktadır.[33] Bununla birlikte bölgenin buz akıntılarını[34] andıran lav akıntıları gölcüklerinin oluÅŸabileceÄŸi bir morfolojiye de sahip olduÄŸu gözden uzak tutulmamalıdır. Kısa zaman önce Mars Global Surveyor’daki Mars Orbiter’ın yüksek çözünürlüğe sahip kamerasıyla çekilen fotoÄŸraflar Mars yüzeyindeki sıvı suyun tarihi hakkında daha ayrıntılı bilgiler saÄŸlamıştır. Ä°lginçtir ki, bu verilerde Mars’ta dev kanalların, aÄŸacın dallanmasına benzeyen aÄŸ biçimli geniÅŸ yolların bulunmasına karşın su akışlarını gösteren daha küçük ölçekli damar ve oluÅŸumlara rastlanamamıştır. Bunun üzerine hava koÅŸullarının bu küçük izleri zamanla yok etmiÅŸ olabilecekleri (erozyon) düşünüldü. Mars Global Surveyor uzay gemisiyle edinilen yüksek çözünürlüklü veriler, kraterlerde ve kanyonların duvarları boyunca yüzlerce yarık bulunduÄŸunu ortaya koymuÅŸtur. AraÅŸtırmalar bu oluÅŸumların genç yaÅŸta olduÄŸunu göstermektedir. Dikkat çeken bir yarığın altı yıl arayla çekilen iki fotoÄŸrafı karşılaÅŸtırıldığında yarıkta yeni tortul çökeltilerinin biriktiÄŸi farkedilmiÅŸtir. NASA’nın Mars KeÅŸif Programı yetkili uzmanlarından Michael Meyer bu tür renkli tortul çökelti oluÅŸumlarına ancak güçlü bir sıvı su akışının yol açabileceÄŸi görüşündedir. + +"Mars Reconnaissance Orbiter" (Mars Yörünge KaÅŸifi) uydusundan alınan veriler sonucu, Mars'ta sıcak aylarda tuzlu su akıntılarının oluÅŸtuÄŸu belirlenmiÅŸtir.[11][35] + +Ä°ster yağıştan (yaÄŸmurdan), ister yeraltı su kaynaklarından, ister baÅŸka bir kaynaktan kaynaklansın, sonuç olarak Mars’ta su mevcuttur.[36] Öte yandan söz konusu çökelti oluÅŸumlarına donmuÅŸ karbondioksidin veya gezegen yüzeyindeki toz akımlarının neden olduÄŸunu ileri süren senaryolar da ortaya atılmıştır.[37][38] Mars yüzeyinde geçmiÅŸte sıvı suyun bulunduÄŸunun bir baÅŸka kanıtı da yüzeyde saptanan minerallerden gelmektedir: Hematit, goetit gibi mineraller genellikle suyun varlığını iÅŸaret eden minerallerdir (goetit serin topraklardaki yegane demir oksittir).[39] +CoÄŸrafya[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Globo de Marte - Valles Marineris.gifGlobo de Marte - Elysium Planitia.gifGlobo de Marte - Syrtis Major.gif + +Ay’ın haritasının yapılmasında ilk çalışmalarda bulunanlardan biri olan Johann Heinrich Mädler on yıl süren gözlemlerinden sonra, 1840’ta da ilk Mars haritasını çizdi. Ä°lk areografi uzmanları olan Mädler ve kendisiyle Ay haritasının yapımında da çalışmış arkadaşı Wilhelm Beer, Mars haritasındaki iÅŸaretlemelerde, isimler vererek belirlemek yerine, sade bir ÅŸekilde, harfler kullanmayı tercih ettiler.[40] +Mars’ın ve GüneÅŸ Sistemi’nin en yüksek dağı olan, 27.000 m. yükseklikteki Olimpos Dağı'nın (Olympus Mons) Mars’ın yörüngesinden çekilmiÅŸ fotoÄŸrafı + +Mars’taki coÄŸrafi oluÅŸumlara Dünya coÄŸrafyasından veya tarihsel ve mitolojik isimler verilmiÅŸtir. Mars’ın ekvatoru doÄŸal olarak kendi çevresinde dönmesiyle belirlenmiÅŸtir, baÅŸlangıç meridyeni ise Dünya’daki Greenwich meridyeni gibi keyfi olarak, 1830’da ilk Mars haritalarının yapımı çalışmasında Mädler and Beer tarafından belirlenmiÅŸtir. 1972’de Mariner 9 uzay aracının Mars’le ilgili yeterince veri toplamasından itibaren, Sinus Meridiani’deki (Meridian Bay), sonradan Airy-0 olarak adlandırılan küçük bir krater, eski belirlemeyle uyuÅŸacak tarzda 0.0° boylamı olarak seçildi (Beer ve Mädler tarafından “a†harfi ile iÅŸaretlenen boylam). + +Mars’ta deniz olmadığından Olimpos Dağı’nın yüksekliÄŸi “ortalama çekim yüzeyi†(Ä°ng. mean gravity surface) esas alınarak hesaplanmış ve yüksekliÄŸi 27 km. olarak saptanmıştır. (Bir baÅŸka deyiÅŸle, Mars’ta irtifalar atmosfer basıncının 610,5 Pa (6.105 mbar) olduÄŸu seviye esas alınarak hesaplanır. Bu da Dünya’daki deniz seviyesinde mevcut basıncın yaklaşık ‰ 6’sıdır.)[41] +Gezegen fotoÄŸrafının tam ortasındaki devasa kanal, Valles Marineris kanyon oluÅŸumunu göstermektedir. +Mars’taki 7 maÄŸaranın giriÅŸlerinin THEMIS tarafından çekilen fotoÄŸrafı: A-Dena, B-Chloe, C-Wendy, D-Annie, E-Abby (solda) ve Nikki F-Jeanne. + +Mars topoÄŸrafyası ilginç bir ikilem göstermesiyle dikkat çeker. Kuzey yarımkürenin lav akıntılarıyla düzleÅŸmiÅŸ ovalar içermesine karşın, güney yarımküre eski çarpışmalarla çukurlar ve kraterlerle oyulmuÅŸ haldeki bir daÄŸlık arazidir. 2008’de yapılan araÅŸtırma ve incelemeler 1980’de ortaya atılmış, Mars’ın kuzey yarımküresine dört milyar yıl önce Ay’ın boyutunun %6,6’sı büyüklükteki bir cismin çarpmış olduÄŸunu ileri süren teoriyi kanıtlar görünmektedir. Bu görüş doÄŸru olduÄŸu takdirde Mars’ın kuzey yarımküresinde 10.600 km. uzunluÄŸunda ve 8.500 km. geniÅŸliÄŸinde bir krater alanının açılmış olması gerekirdi ki, bu, Avrupa, Asya ve Avustralya toprakları bütününe denk bir alandır.[42][43] Mars’ın yüzeyi Dünya’dan görünüşle, farklı albedo’su olan iki tür alana ayrılır. Kızılımsı demiroksit içeren tuz ve kumla kaplı soluk ovalar geçmiÅŸte Mars kıtaları olarak yorumlanmış ve bunlara Arabistan Ãœlkesi (Arabia Terra), Amazon Ovası (Amazonis Planitia) gibi adlar verilmiÅŸtir. Koyu renkli oluÅŸumlar ise denizler olarak yorumlanmış ve bunlara Mare Erythraeum, Mare Sirenum ve Aurorae Sinus adları verilmiÅŸtir. Dünya’dan görünüşe göre en koyu renkli coÄŸrafi oluÅŸum Syrtis Major’dur.[44] +Mars'taki Victoria Krateri'nin bir görüntüsü. + +Everest’in üç misli yüksekliÄŸindeki Olimpos Dağı birçok büyük volkan içeren daÄŸlık Tharsis bölgesindeki, yumuÅŸak eÄŸimli bir sönmüş volkandır. Mars aynı zamanda çarpma kraterlerinin gözlemlendiÄŸi bir gezegendir; yarıçapı 5 km. ve daha büyük olabilen bu krater oluÅŸumlarının toplam sayısı 43.000 olarak belirlenmiÅŸtir.[45] En büyükleri hafif bir albedo oluÅŸumuna sahip, Dünya’dan kolayca görülebilen Hellas çarpma havzasıdır (Hellas Planitia).[46] Hacmi açısından, bir kozmik cismin Dünya’ya oranla daha küçük olan Mars’a çarpma olasılığı, Dünya’ya çarpma olasılığının yarısı kadardır. Bununla birlikte Mars’ın asteroit kuÅŸağına daha yakın olması, bu kuÅŸaktan gelen cisimlerle çarpışma olasılığını çok fazla arttırmaktadır. Mars aynı zamanda kısa periyotlu (yörüngeleri Jüpiter’e uzanan) kuyruklu yıldızların çarpmalarına (veya süpürmelerine) da maruz kalmaktadır. Bununla birlikte Ay’ın yüzeyi ile kıyaslandığında, atmosferi kendisine küçük meteorlara karşı koruma saÄŸladığından Mars yüzeyinde daha az krater görülür. Bazı kraterler meteor düştüğünde yerin nemli olduÄŸunu gösteren bir morfolojiye sahiptir. + +Valles Marineris adlı ünlü büyük kanyon 4.000 km uzunluÄŸunda ve 200 km geniÅŸliÄŸinde olup, 7 km'ye varan bir derinliÄŸe sahiptir. Yani uzunluÄŸu Avrupa’nın uzunluÄŸuna eÅŸ olup, gezegenin çevresinin beÅŸte biridir. Büyüklüğünün devasa boyutlarının anlaşılması amacıyla Dünya’daki Büyük Kanyon'un boyutları göz önüne getirilebilir. (Büyük Kanyon 446 km uzunluÄŸunda ve yaklaşık 2 km derinliÄŸindedir.) Bir baÅŸka geniÅŸ kanyon olan Ma'adim Vallis 700 km uzunluÄŸunda, 20 km geniÅŸliÄŸinde ve yer yer 2 km derinliÄŸindedir. Bu kanyonun geçmiÅŸte bir sıvı su baskınıyla oluÅŸtuÄŸu sanılmaktadır.[47] 2001 Mars Odyssey robotik uzay gemisindeki kısa adı THEMIS (Thermal Emission Imaging System) olan kamera sayesinde Arsia Mons volkanının yamaçlarında 7 muhtemel maÄŸara giriÅŸi saptanmıştır.[48] Bunlar günümüzde “yedi kızkardeÅŸler†adıyla bilinmektedirler.[49] MaÄŸara giriÅŸlerinin geniÅŸliklerinin 100 m ile 252 m arasında deÄŸiÅŸtiÄŸi sanılmakta ve ışık genellikle maÄŸaraların dibine kadar giremediÄŸinden bu maÄŸaraların yeraltında sanılandan daha derin ve geniÅŸ bir halde uzandıkları düşünülmektedir. Bunlar içinden tek istisna dibi görünen Dena adlı maÄŸaradır. Mars’ın kuzey kutbu dairesine Planum Boreum ve güney kutbu dairesine Planum Australe adı verilmiÅŸtir. +Atmosfer[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars gezegeninde en bol bulunan gazlar – (Curiosity rover, Ekim 2012). +Kuzey yarımkürenin yaz döneminde Mars atmosferinde saptanan metan gazı izleri-NASA +Mars’ın yörüngeden çekilmiÅŸ, ufukta görülebilen ince atmosferi. +Mars Pathfinder tarafından çekilmiÅŸ, Mars semalarındaki buz bulutlarının fotoÄŸrafı + +. + +Mars manyetosferini 4 milyar yıl önce kaybetmiÅŸtir. Böylece GüneÅŸ rüzgârları Mars’ın iyonosfer tabakasıyla doÄŸrudan etkileÅŸime girerek atmosferi ince halde tutmaktadır. Mars Global Surveyor ve Mars Express’in her ikisi de, iyonize atmosfer parçacıklarının uzaya sürüklendiklerini saptamışlardır.[50][51] Mars atmosferi günümüzde nispeten incedir. Yüzeydeki atmosfer basıncı gezegenin en yüksek kısmında saptanan 30 Pa (0.03 kPa) ile en derin kısmında saptanan 1,155 Pa (1.155 kPa) arasında deÄŸiÅŸmektedir. Yani ortalama yüzey basıncı 600 Pa’dır (0.6 kPa) ki, bu da Dünya yüzeyinden 35 km. yükseklikte rastlanan basınca eÅŸtir. Bir baÅŸka deyiÅŸle Dünya yüzey basıncının %1’inden daha düşük bir deÄŸerdir. Mars’taki düşük yerçekiminden dolayı da atmosferinin "ölçek irtifa"sı (Ä°ng. scale height)[52] Dünya’nınkinden (6 km.) daha yüksek olup, 11 km.’dir. Mars yüzeyinde yerçekimi Dünya yüzeyindeki yerçekiminin %38’i kadardır. + +Mars atmosferi % 95 karbondioksit, % 3 nitrojen, % 1,6 argondan oluÅŸmakla birlikte, oksijen ve su izleri de taşımaktadır.[53] 1,5 µm yarıçapındaki toz parçacıklarını içeren atmosferi tümüyle tozludur ki, bu, Mars yüzeyinden bakıldığında Mars gökyüzünün soluk bir turuncu-kahverengimsi renkte (Ä°ng. tawny) görülmesine neden olmaktadır.[54] + +Birçok araÅŸtırmacı Mars atmosferinde hacim itibariyle 30 ppb oranında metanın varlığını saptamışlardır.[55][56] Metan morötesi ışınlarla bozunan ve Mars’ınki gibi bir atmosferde[57] yaklaşık 340 yılda bozunacak kararsız bir gaz olduÄŸundan, bu, gezegende güncel veya kısa zaman öncesine dek mevcut bir gaz kaynağının varlığını göstermektedir. Buna da ancak volkanik etkinlik, kuyruklu yıldız çarpmaları ve metanojenik mikroorganizma türleri neden olabilir. Bununla birlikte kısa zaman önce metanın biyolojik olmayan bir süreçle de üretilebileceÄŸi görüşü ortaya atılmıştır.[58] + +Kutup bölgelerinde kışın sürekli bir karanlık ve yüzeyde dondurucu bir soÄŸuk hakim olur, bu da atmosferin % 25–30 civarındaki kısmının yoÄŸunlaÅŸmasına ve karbondioksitin “kuru buz†(Ä°ng. dry ice)[59] denilen halde katılaÅŸmasına yol açar.[60] Kutuplar kış mevsimi geçip yeniden GüneÅŸ ışıklarına maruz kalmaya baÅŸladığında, buzlaÅŸmış karbondioksit, hızı saatte 400 km.’ye ulaÅŸan müthiÅŸ rüzgarlar yaratarak uçmaya baÅŸlar. Bu mevsimlik deÄŸiÅŸimler, büyük miktarlarda toz ve su buharı taşırlar ve Dünya’dakine benzer kırağı ve "sirüs bulutları"nın (saçakbulut) oluÅŸmasına neden olurlar. Su-buzu bulutlarının fotoÄŸrafı Opportunity tarafından 2004’te çekilmiÅŸtir.[61] +Ä°klim[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’ın Eylül 2001'deki toz fırtınasından önceki (solda) ve toz fırtınası sırasındaki (saÄŸda) görünümlerinin karşılaÅŸtırılması + +Gezegenler içinde mevsimleri Dünya’nınkilere en çok benzeyen gezegen, kendi çevresinde dönme ekseninin yörüngeye eÄŸikliÄŸinin Dünya’nınkine benzer olması nedeniyle, Mars’tır. Bununla birlikte Mars mevsimlerinin süreleri gezegenin Güneş’e daha uzak olması nedeniyle Dünya’nınkilerin iki mislidir ve “Mars yılıâ€nın süresi de iki Dünya yılı süresi kadardır. Mars’ın yüzey sıcaklıkları kutup kışı sırasındaki −140 °C (133 K) ile yaz sırasındaki 20 °C (293 K) arasında deÄŸiÅŸir.[62] Sıcaklık farklarının büyük olması, ince atmosferinin GüneÅŸ ısısını yeterince depolayamaması, atmosfer basıncının düşük olması ve toprağın ısı kapasitesinin (Ä°ng. thermal inertia) düşük olması gibi nedenlerden ileri gelir.[63] + +Mars Dünya’nınki gibi bir yörüngeye sahip olsaydı "eksen eÄŸikliÄŸi"nin de benzeÅŸmesi sayesinde, mevsimleri de Dünya’nınkilere daha benzer olacaktı. Bununla birlikte Mars yörüngesinin geniÅŸ eksantrikliÄŸi ilginç bir sonuç saÄŸlamaktadır. Mars, güney yarımkürede yaz, kuzey yarımkürede kış olduÄŸu zaman günberiye yakındır, güney yarımkürede kış, kuzey yarımkürede yaz olduÄŸu zaman da günöteye yakındır. Bunun sonucunda da güney yarımkürede mevsimlerin daha aşırı farklar göstermesine karşın kuzey yarımkürede mevsimler olması gerekenden daha yumuÅŸak geçerler. Böylece güneyde 30 °C ‘yi (303 K) bulan yaz sıcaklıkları kuzeydeki yaz sıcaklıklarına kıyasla biraz daha fazladır.[64] +Mars’ın kuzey kutbu buz bölgesi + +Mars aynı zamanda GüneÅŸ Sistemi’ndeki en büyük “toz fırtınalarıâ€na[65] sahne olan gezegendir. Bu toz fırtınaları mahalli bir bölgedeki küçük fırtınalar biçiminde olabildiÄŸi gibi, tüm gezegeni kaplar büyüklükteki dev fırtınalar biçiminde de olabilmektedir. Bunlar özellikle Mars Güneş’e en yakın konumuna geldiÄŸinde ve küresel sıcaklığın arttığı hallerde oluÅŸmaya eÄŸilimlidirler.[66] + +Kutup dairelerinin her ikisi de esas olarak su buzundan oluÅŸmaktadırlar. Ayrıca yüzeylerinde “kuru buz†da mevcuttur. KatılaÅŸan karbondioksit olan “kuru buz†(Ä°ng. dry ice) kuzey kutup dairesinde yalnızca kışın yaklaşık bir metre kalınlıkta bir ince tabaka oluÅŸturacak ÅŸekilde birikir; güney kutup dairesine ise bu tabaka kalıcıdır ve kalınlığı 8 m.’yi bulur.[67] Kuzey kutup dairesinin yarıçapı kuzey yarımkürenin yazı sırasında 1000 km. olup yaklaşık 1.6 milyon {\displaystyle km^{3}} buz içerir. (Grönland buz kitlesinin hacmi 2,85 milyon {\displaystyle km^{3}}’tür.) Bu buz tabakasının kalınlığı 2 km.’ye ulaşır. Güney kutbu dairesinin yarıçapı ise 350 km. olup, buradaki buz kalınlığı 3 km.’dir.[68] Buradaki buz kitlesinin hacminin de kuzeydeki kadar olduÄŸu sanılmaktadır.[69] Her iki kutup dairesinde de diferansiyel güneÅŸ ısısından kaynaklandığı sanılan, buzların uçması ve su buharının yoÄŸunlaÅŸması olaylarıyla etkileÅŸim içinde bulunan spiral oluÅŸumlar gözlemlenmiÅŸtir.[70][71] Her iki kutup dairesi de Mars mevsimlerinin ısı dalgalanmalarına baÄŸlı olarak küçülüp büyürler. +Evrim[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’la ilgili son keÅŸifler gezegenin tarihi boyunca çeÅŸitli belirleyici anlar yaÅŸamış olduÄŸunu ortaya koymuÅŸtur. ÖrneÄŸin sıvı su izleri gezegenin atmosferinin vaktiyle bugünkünden daha kalın olduÄŸunu, Kuzey Havzası izleri de çok büyük kütleli bir cisimle büyük bir çarpışma geçirmiÅŸ olduÄŸunu ortaya koymaktadır. Gezegenin evrimiyle ilgili muhtemel açıklamalar ÅŸunlardır: + + GeçmiÅŸte büyük bir uydu iç kısmın üzerindeki gelgit etkisiyle kalıcı bir manyetik alanın oluÅŸmasını saÄŸlamış olabilir. Bu alan Mars atmosferini güneÅŸ rüzgarlarından korumuÅŸ ve yüzeyde sıvı su hareketlerinin meydana gelmesine olanak saÄŸlamış olmalıdır. + Bu çarpışma bir yarımküresinin kabuÄŸunun kalkmasına ve atmosfer tabakasının tahrip olmasına yol açmış olmalıdır. Mars’a geçmiÅŸte kuzey kutbu bölgesinden çarpan bu büyük cisim muhtemelen yörüngesi gelgit gücünün etkisiye bozulmuÅŸ bir uydusu olabilir. Düşen uydunun artık gelgit etkisi olmadığından manyetik alan zayıflamış ve yüzeye çarpan güneÅŸ rüzgarları atmosferin yeniden oluÅŸmasını engellemiÅŸ olmalıdır. + Gezegende belirli bir kararlılığı saÄŸlayan uydunun yokluÄŸu beÅŸ milyon yıllık dengenin yalpalaması ya da bozulması demekti. Dengedeki bu bozulma, kutup bölgelerinin düzenli olarak ısınmasına, buzların bir parça erimesiyle sıvı suların oluÅŸmasına ve dolayısıyla kutup dairesinde çizgilerin meydana gelmesine neden oldu. + +Yörünge ve kendi çevresinde dönüş[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars ve yörüngesi (kırmızı) ile asteroit kuÅŸağındaki cüce gezegen Ceres’in (sarı) mukayesesi (kuzey tutulum kutbundan görünüşle). Tutulumun güney yörünge parçaları koyu renkle gösterilmiÅŸtir. Günberi (q) ve günöte (Q) en yakın geçiÅŸ tarihleriyle belirtilmiÅŸtir. + +Mars’ın Güneş’ten ortalama uzaklığı yaklaşık 230.000.000 km. (1,5 AU), yörünge süresi ise 687 Dünya günüdür. Mars günü Dünya gününden biraz daha uzun olup, tam olarak 24 saat, 39 dakika ve 35,244 saniyedir. Bir Mars yılı 1.8809 Dünya yılıdır, yani Dünya zaman birimiyle tam olarak 1 yıl, 320 gün ve 18,2 saattir. + +Mars’ın eksen eÄŸikliÄŸi Dünya’nın eksen eÄŸikliÄŸine çok yakın olup, 25,19 derecedir. Dolayısıyla Mars’ta de Dünya’dakini andıran mevsimler meydana gelir. Fakat Mars mevsimlerinin süreleri Mars’ın yörünge süresinin uzunluÄŸundan dolayı, Dünya mevsimlerinin sürelerinin iki katıdır. Mars Mayıs 2008’de günöteye Nisan 2009’de günberiye geçmiÅŸtir. Bir sonraki günöte tarihi haziran 2010’dur. + +Mars’ın nispi olarak söylenebilecek yörünge eksantrikliÄŸi (eksenel kaçıklık, dışmerkezlik) 0,09'dur; GüneÅŸ Sistemi’nde yalnızca Merkür bundan daha büyük bir eksantrikliÄŸe sahiptir. Bununla birlikte Mars’ın geçmiÅŸte bugünkünden daha dairesel bir yörünge çizdiÄŸi bilinmektedir. 1,35 milyon Dünya yılı öncesinde Mars’ın eksantrikliÄŸi yaklaşık 0,002 idi, yani Dünya’nın bugünkü eksantrikliÄŸinden de daha azdı.[72] Mars’ın eksantriklik devresi 96.000 Dünya yılıdır.[73] Bununla birlikte Mars’ın 2,2 milyon yıllık bir eksantriklik devresi daha vardır. Son 35.000 yılda Mars’ın yörüngesinin eksantrikliÄŸi diÄŸer gezegenlerin çekimsel etkileri dolayısıyla artmıştır. Mars ve Dünya’nın birbirlerine en yaklaÅŸtıkları zamanlarda aralarında bulunan mesafe gelecek 25.000 yılda biraz daha azalacaktır.[74] +DoÄŸal uyduları[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Phobos deimos diff.jpg +Ä°sim Çap +(km) Kütle +(kg) Ortalama yörünge +yarıçapı (km) Yörünge süresi +(saat) +Phobos 22,2 (27 × 21.6 × 18.8) 1,08×1016 9 378 7,66 +Deimos 12,6 (10 × 12 × 16) 2×1015 23 400 30.35 + +Mars’ın düzensiz biçimli, iki küçük doÄŸal uydusu vardır. Kendilerine eski Yunan mitolojisindeki savaÅŸ ilahı Ares’e (Romalılar’da Mars) yardım eden çocuklarının adlarından esinlenerek Phobos ve Deimos adları verilmiÅŸ, gezegene çok yakın yörüngeler izleyen bu uydular muhtemelen bir Mars "Trojan asteroiti"[75] olan 5261 Eureka gibi, gezegenin çekim alanına kapılarak uydu haline gelmiÅŸ asteroitlerdir.[76] Fakat hava tabakası olmayan Mars’ın bu iki uyduya nasıl ve ne zaman sahip olduÄŸu tam olarak anlaşılmış deÄŸildir. Ãœstelik bu büyüklükteki asteroitler çok nadirdir, özellikle ikili olanları. Bu büyüklükteki asteroitlere asteroit kuÅŸağının dışında rastlanması durumu daha da garip kılmaktadır.[77] + +Her iki uydu da 1877’de Asaph Hall tarafından keÅŸfedilmiÅŸtir. Phobos ve Deimos’un hareketleri Mars yüzeyinden bizim ‘ay’ımızın Dünya’dan görünüşüne kıyasla çok farklı olarak görünür. Phobos 11 saatte bir, batıdan doÄŸar. Deimos ise, dolanım süresi 30 saat olmakla birlikte, 2,7 günde bir doÄŸar.[78] Her iki uydu da ekvatora yakın dairesel yörüngeler izlerler. Phobos ‘un yörüngesi Mars’tan kaynaklanan gelgit etkileri nedeniyle giderek küçülmektedir. Bu yüzden Phobos yaklaşık 50 milyon yıl içinde Mars’a çarpacaktır.[78] +YaÅŸam[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +ALH84001 adı verilen Mars meteoru, bakteri düzeyinde yaÅŸam belirtileri olduÄŸu ileri sürülen mikroskobik oluÅŸumlar göstermektedir. +Mars Global Surveyor (MGS) tarafından çekilmiÅŸ fotoÄŸrafta görülen, mahiyeti anlaşılamamış “koyu kumul lekeleriâ€. +“Koyu kumul lekeleriâ€nin Mars Global Surveyor tarafından çekilen yüksek çözünürlüğe sahip fotoÄŸrafında lekelerin yakın plandan görünümü. + +Evrende yaÅŸamın Dünya’daki koÅŸullara benzer koÅŸullar altında ortaya çıkabileceÄŸi varsayımından hareketle, günümüzde bir gezegenin yaÅŸanabilirlik (Ä°ng. planetary habitability)[79] ölçüsü, yani bir gezegende yaÅŸamın geliÅŸebilme ve sürebilmesinin ölçüsü yüzeyinde su bulunup bulunmamasıyla yakından ilgili görülmektedir. Bu da bir güneÅŸ sistemindeki gezegenin güneÅŸine uzaklığının gereken uygun uzaklıkta olup olmamasına baÄŸlıdır. Mars’ın yörüngesinin Dünya’nın yer aldığı bu uygun kuÅŸağın yarım astronomik birim kadar daha uzağında olması, ince bir atmosfere sahip bu gezegenin yüzeyinde suyun donmasına neden olmaktadır. Bununla birlikte gezegenin geçmiÅŸindeki sıvı su akışları Mars’ın yaÅŸanabilirlik potansiyeli taşıdığını ortaya koymaktadır. Verilere göre, Mars yüzeyindeki sular yaÅŸam için gerekenden çok daha tuzlu ve çok daha asitlidir.[80] + +Gezegenin manyetosferinin olmayışı ve son derece ince bir atmosfere sahip oluÅŸu büyük bir handikaptır. Yüzeyindeki ısı tranferi (Ä°ng. heat transfer)[81] pek büyük deÄŸildir, meteorlara ve güneÅŸ rüzgarlarına karşı savunması hemen hemen yok gibidir ve suyu sıvı halde tutacak atmosfer basıncı yetersizdir (dolayısıyla su gaz haline geçer). Verilere göre gezegen geçmiÅŸte günümüzdeki haline kıyasla daha yaÅŸanabilir haldeydi. Bütün bu olumsuzluklara raÄŸmen Mars’ta organizmaların olmadığı ya da hiç yaÅŸamamış olduÄŸu söylenemez. Nitekim 1970’lerdeki Viking Programı sırasında Mars toprağındaki mikroorganizmaların saptanması amacıyla Mars’tan getirilen örneklerde bazı pozitif görünen sonuçlar elde edildi. Fakat bu sonuçlar birçok bilim insanının katıldığı bir tartışmaya yol açtı ve kesin bir sonuca ulaşılamadı. Buna karşılık Viking Programı’yla edinilen verilerden yararlanan profesör Gilbert Levin,[82] Rafaël Navarro-González[83] ve Ronalds Paepe yeni bir taksonomik sistem hazırladılar ve bu sistemde Mars’taki yaÅŸam türü Gillevinia straata[84] adı altında ele alındı.[85][86][87] + +Sonraki yıllarda Phoenix Mars Lander tarafından yürütülen deneyler Mars toprağında sodyum, potasyum ve klorür içeren bir alkali bulunduÄŸunu gösterdi.[88] Bu besleyici toprak yaÅŸamı taşımaya gayet elveriÅŸliydi, fakat unutulmaması gereken bir sorun daha vardı: YaÅŸamın yoÄŸun morötesi ışınlardan korunabilmesi. + +Nihayet Johnson Uzay Merkezi Laboratuvarı’nda[89] Mars kökenli ALH84001 meteoru üzerinde organik bileÅŸimler saptandı; varılan sonuca göre bunlar Mars üzerindeki ilk yaÅŸam türleriydi.[90][91][92][93] Öte yandan Mars yörüngesindeki uzay gemileri kısa zaman önce düşük miktarlarda metan ve formaldehit saptadılar ki, bunlar da yaÅŸamın varlığını ima eden iÅŸaretler olarak yorumlandılar; zira bu kimyasal bileÅŸimler Mars atmosferinde hızla çözünmektedirler.[94][95] + +Mars’ta biyolojik kökenli oldukları ileri sürülen oluÅŸumlardan en tanınmışları “koyu kumul lekeleri†adıyla bilinen oluÅŸumlardır.[96] Ä°lk kez Mars Global Surveyor tarafından 1998-1999 yıllarında gönderilen fotoÄŸraflarla keÅŸfedilen “koyu kumul lekeleri†Mars’ın özellikle güney kutup bölgesinde (60°-80°enlemleri arasında) görülebilen, buz tabakasının üzerinde veya altında beliren, mahiyeti henüz anlaşılamamış oluÅŸumlardır. Mars ilkbaharının baÅŸlarında belirmekte ve kış baÅŸlarında yok olmaktadırlar. Bunların kış boyunca buz tabakasının altında kalan fotosentetik koloniler, yani fotosentez yapan ve yakın çevrelerini ısıtan mikroorganizmalar oldukları ileri sürülmektedir.[97][98][99][100][101][102] + +28 Eylül 2015'te Mars'ta sıvı halde tuzlu su bulunduÄŸu açıklanmıştır. Tuzlu suyun bulunması ile birlikte, bilim adamları Mars'ta yaÅŸam bulma olasılığının da arttığını ifade etmiÅŸlerdir.[103] +KeÅŸif[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’a günümüze dek, gezegenin yüzeyini, iklimini ve jeolojisini incelemek üzere, ABD, Avrupa ülkeleri, Japonya ve SSCB tarafından düzinelerce uzay gemisi (Ä°ng. spacecraft), uydu/yörünge aracı (Ä°ng. orbiter), iniÅŸ aracı/uzay gemisi (Ä°ng. lander) ve sonda/uzay keÅŸif aracı (Ä°ng. rover) gibi çeÅŸitli uzay araçları gönderilmiÅŸtir. Fakat bu uzay gemisi gönderme denemelerinin yaklaşık üçte ikisi araçlar ya görevlerini tamamlayamadan ya da görevlerine daha baÅŸlayamadan bilinen veya bilinmeyen nedenlerle baÅŸarısızlıkla sonuçlanmıştır. +Tamamlanmış keÅŸif projeleri[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Phoenix'in temsilî iniÅŸi + +Görevini tamamlama konusunda ilk baÅŸarı 1964’te NASA tarafından gönderilen Mariner-4’ten gelmiÅŸtir. Yüzeye ilk baÅŸarılı iniÅŸler ise SSCB’nin Mars Probe Projesi kapsamında 1971’de fırlattığı Mars-2 ve Mars-3 tarafından gerçekleÅŸtirilmiÅŸ, fakat her iki araçla irtibat, iniÅŸlerinden kısa bir süre sonra kesilmiÅŸtir. Sonraki yıllarda NASA Viking Projesi'ni baÅŸlattı ve 1975’te her biri birer "iniÅŸ aracı" taşıyan iki "uydu aracı" fırlatıldı. Her iki araç 1976’da baÅŸarıyla iniÅŸ yaptılar. Gezegende Viking-1 altı yıl, Viking-2 ise üç yıl kaldı. Bunlar Mars’ın ilk renkli fotoÄŸraflarını gönderdiler[104]; gezegenin yüzeyinin haritasının çıkarılması amacıyla gönderdikleri fotoÄŸraflara günümüzde bile zaman zaman baÅŸvurulmaktadır. + +Sovyetler 1988’de Mars’a gezegeni ve doÄŸal uydularını incelemek üzere Phobos-1 ve Phobos-2 adlı sonda araçları gönderdiler. Phobos-1’le irtibat Mars yolundayken kesilmiÅŸ olmasına karşın, Phobos-2 fotoÄŸraflar göndermede baÅŸarılı oldu. Fakat Phobos-2 de tam Phobos adlı doÄŸal uydunun yüzeyine iki iniÅŸ aracını salmak üzereyken baÅŸarısızlığa uÄŸradı. + +Mars Observer uydusunun 1992’deki baÅŸarısızlığından sonra NASA tarafından 1996’da Mars Global Surveyor fırlatıldı. Görevinde tümüyle baÅŸarılı oldu. Harita çıkarma görevini 2001’de tamamladı. Kasım 2006’da üçüncü uzatılmış görevi sırasında sonda aracıyla irtibat kesildi, uzayda 10 yıl çalışır halde kalmayı baÅŸardı. NASA Surveyor’ın fırlatılmasından bir ay sonra da Mars Pathfinder’ı fırlattı. Bu, robotik bir keÅŸif aracı olan Sojourner’ı taşıyordu. 1997 yazında Mars’taki Ares Vallis bölgesine iniÅŸ yaptı. Bu proje de baÅŸarıyla sonuçlandı.[105] + +Mars’la ilgili son tamamlanmış görevde 4 AÄŸustos 2007’de fırlatılan iniÅŸ yeteneÄŸine sahip Phoenix uzay gemisi kullanılmıştır. Araç 25 Mayıs 2007’de Mars’ın kuzey kutbu bölgesine iniÅŸ yaptı.[106] 2,5 m.’ye uzanan robot koluyla Mars toprağını bir metre kazabilecek kapasitede olup, mikroskobik bir kamerayla donatılmıştı. Bu mikroskobik kamera insan saçının binde biri kadar inceliÄŸi ayırt edebilecek bir hassasiyete sahipti. 15 Haziran 2008’de indiÄŸi yerde su buzlarını keÅŸfetti.[107][108] Görevini 10 Kasım 2008’de tamamladı. + +Rusya ve Çin’in ortak projesi olan Phobos-Grunt projesiyle Mars'ın uydusu Phobos’tan örnekler toplanarak analiz için Dünya'ya geri getirilmesi planlanıyordu. Ancak bu sonda 9 Kasım 2011’de fırlatılmasının ardından bozulmuÅŸ, 15 Ocak 2012'de dünyaya düşerek imha olmuÅŸtur. +Sürdürülen keÅŸif projeleri[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Spirit adlı sonda aracı + + 2001’de NASA Eylül 2010’a kadar görevini sürdürmesi planlanan ve halen görevini baÅŸarıyla yerine getiren Mars Odyssey uydu aracını fırlattı.[109] Aracın Gamma Işını Spektrometresi,[110] regolit[111] incelemesi sırasında ilginç miktarlarda hidrojen tespit etti.[112] + 2003’te Avrupa Uzay Ajansı (ESA), Mars Express Projesi kapsamında Mars Express Orbiter adlı uydu aracı ile Beagle-2 adlı iniÅŸ aracını fırlattı. Beagle-2 iniÅŸ sırasında baÅŸarısızlığa uÄŸradı ve Åžubat 2004’te kaybolduÄŸuna dair bir açıklama yapıldı. + ESA’nın Beagle-2’yi fırlattığı yıl NASA Spirit ve Opportunity adlarında iki keÅŸif sondasını fırlattı. Her ikisi de görevini baÅŸarıyla yerine getirdi. Bu çalışmalarla Mars’ta en azından geçmiÅŸte sıvı suyun bulunduÄŸu kesinlik kazanmıştır. 2004 yılında Mars'ın kuzey kutbuna inen ve görev süresi 6 yıl olarak belirtilen Opportunity, bilim adamlarını ÅŸaşırtan bir performansla halen bütün fonksiyonlarını en üst düzeyde kullanarak Mars görevine devam etmektedir.[113] + 2004’te Planetary Fourier Spectrometer ekibi Mars atmosferinde metan saptadıklarını açıkladı. + NASA 12 AÄŸustos 2005’te Mars Reconnaissance Orbiter sonda aracını fırlattı, 10 Mart 2006’da yörüngeye oturan araç iki yıl boyunca bilimsel incelemelerde bulundu. Araç aynı zamanda gelecekteki projeler için en uygun iniÅŸ platformlarını bulmak üzere Mars toprağı ve iklimini incelemekle görevlidir. + ESA Haziran 2006’da Mars’ta “kutup ışıkları†olayını saptadıklarını açıkladı.[114] Vesta ve Ceres’i incelemek üzere gönderilen Dawn uzay gemisinin Åžubat 2009’da Mars’a ulaÅŸtı. + Mars Bilim Laboratuvarı adlı keÅŸif sondası (curiosity) "merak" Kasım 2011'de fırlatıldı. Sonda 2012 AÄŸustos'unda Mars'ın Gale kraterine yumuÅŸak iniÅŸ yaptı. Yapması planlanan deneyler arasında kayalar üzerinde kimyasal lazer yardımıyla uzaktan (azami 13 m.) çalışarak örnekler toplaması da bulunmaktadır. + +Gelecekte uygulanacak projeler[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + + ESA 2013’te Mars toprağındaki organik molekülleri araÅŸtırmak üzere ExoMars adlı sonda aracını fırlatmayı planlamıştır.[115] + 15 Eylül 2008’de NASA Mars atmosferi hakkında bilgi edinilmesini saÄŸlamak üzere 2013’te MAVEN adı verilen robot programını uygulamaya koyacağını açıklamıştır.[116] + Fin-Rus ortak projesi olan MetNet projesinde, gezegenin atmosferik, fiziksel ve meteorolojik yapısını yeterince inceleyebilmek için yaygın bir gözlem ağı kurmak üzere on kadar küçük taşıtın Mars yüzeyine gönderilmesi planlanmıştır.[117] Öncü görevinde bulunacak ilk iniÅŸ araçlarının 2009’da ya da 2011’de fırlatılması planlanmıştır.[118] + NASA ve Lockheed Martin ÅŸirketi önce Ay’a (2020’de), ardından Mars’a insan taşıması planlanan Orion adı verilen uzay gemisi için çalışmalara baÅŸladılar. 28 Eylül 2007’de NASA baÅŸkanı Michael D. Griffin NASA tarafından Mars’a yollanacak ilk insanın 2037’de Mars’ta olacağını açıkladı.[119] + Nasa, Marsa insan göndermek için ADEPT adında 1704 derece ısıya dayanıklı materyal geliÅŸtiriyor. Bu materyalin Mars atmosferine giriÅŸ için ve Mars'ta kurulacak seranın yapımında kullanılacağı açıklandı.[120] + ESA Mars’a insan göndermelerinin 2030 ve 2035 yılları arasında yer alacağını umduklarını açıkladı.[121] + Mars One adlı proje ile de insan bulunduran araçlarla koloni kurulması ön görülüyor.[122] + +Mars’ta astronomi gözlemleri[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’tan Güneş’in batışı manzarası. FotoÄŸraf Gusev Krateri’nden Spirit adlı uzay keÅŸif sonda aracınca 19 Mayıs 2005’te çekilmiÅŸtir. + +ÇeÅŸitli uydu araçlarının, iniÅŸ araçlarının ve sonda araçlarının Mars’taki varlıkları sayesinde günümüzde astronomi araÅŸtırmaları Mars gökyüzünden de yapılabilmektedir. Bunun pek çok yönden avantajları bulunmaktadır. Mars’ın doÄŸal uydularından Phobos doÄŸal olarak Mars’tan daha iyi gözlemlenebilmektedir (dolunayın üçte biri açısal çapta görülür). DiÄŸer doÄŸal uydu olan Deimos ise Mars yüzeyinden az çok bir yıldızı andırır tarzda görünür; Dünya’dan Venüs’ün görünüşüne kıyasla, Venüs’ten hafifçe daha parlaktır.[123] + +Öte yandan Dünya’da iyi bilinen, kutup ışıkları, meteorlar gibi birçok fenomen artık Mars yüzeyinde de gözlemlenebilmektedir.[124] Dünya’nın Mars ile GüneÅŸ arasına girecek ve GüneÅŸ üzerinde bir leke oluÅŸturacak ÅŸekilde GüneÅŸ önünden geçiÅŸi Mars’tan 10 Kasım 2084’te izlenebilecektir. Mars’tan aynı ÅŸekilde Merkür ve Venüs’ün transit geçiÅŸi (bir kütlenin baÅŸka bir kütlenin önünden geçmesinin gözlemlenebildiÄŸi geçiÅŸ) ve Deimos’un kısmi güneÅŸ tutulmaları izlenebilir. +Görünüş ve "karşı konum"lar[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’ın 2003 yılında küçük bir teleskoptan görünüşü esas alınarak hazırlanmış dönme hareketi. +Mars’ın 2003’te Dünya’dan görünüşle hazırlanan görünürdeki “geri devimâ€i(tersinir hareketi) +Dünya'yı merkez alan, tutulum üzerinden 2003-2018 Mars "karşı konum"larının görünüşü + +Çıplak gözle bakıldığında Mars genellikle, farklı olarak sarı, turuncu ya da kırmızımsı renklerde görünür. Parlaklığı da, yörüngesindeki yolculuÄŸu sırasındaki konumlarına baÄŸlı olarak, Dünya’dan görünen diÄŸer gezegenlerden daha fazla deÄŸiÅŸiklik gösterir. Görünürdeki kadiri “ kavuÅŸum konumuâ€ndaki +1,8’den “günberi karşı konumuâ€ndaki −2,9 aralığında deÄŸiÅŸir.[12] Kimi konumlarında güneÅŸin güçlü ışığından dolayı görünmez hale gelir. 32 yılda iki kez – Temmuz ve Eylül sonunda – en uygun konuma gelir. Mars teleskopla bakıldığında bolca yüzey ayrıntısı sunan bir gezegendir, özellikle kutuplardaki buzul bölgeleri elveriÅŸsiz koÅŸullarda bile belirgin olarak görülürler.[125] + +Mars’ın Dünya’ya en yakın olduÄŸu konum karşı konum olarak bilinir. “KavuÅŸum dönümü†(Ä°ng. synodic period) olarak bilinen iki “karşı konum†arasındaki süre Mars için 780 gündür. Yörünge eksantriklikleri nedeniyle bu sürede 8,5 güne varan oynamalar olabilir. Dünya’ya en yaklaÅŸtığı zamanlarda Dünya ile Mars arasındaki en kısa uzaklık, gezegenlerin eliptik yörüngelerine baÄŸlı olarak 55.000.000 km. ile 100.000.000 km. arasında deÄŸiÅŸir.[12] Önümüzdeki Mars “karşı konumâ€u 29 Ocak 2010’da meydana gelecektir. Mars “karşı konum†pozisyonuna yaklaÅŸtığında “geri devim “ (tersinir hareket, Ä°ng. retrograde motion) periyodu baÅŸlar. + +27 AÄŸustos 2003 günü, saat 9:51:13’de (UT) Mars son 60.000 yıl boyunca Dünya’ya en yaklaÅŸtığı konuma geldi. Bu konumunda Dünya ile arasındaki uzaklık 55.758.006 km. (0,372719 AU) idi. Bu olay Mars’ın karşı konumundan bir gün, günberisinden yaklaşık üç gün farkla meydana geldiÄŸinden Mars Dünya’dan kolaylıkla izlenebildi. Yapılan hesaplamalara göre, Mars’ın Dünya’ya bu denli yaklaÅŸmasının söz konusu olduÄŸu son tarih M.Ö. 57.617 yılıdır, bir sonraki tarih ise 2287 yılıdır. 24 AÄŸustos 2208’deki yakınlaÅŸmada ise iki gezegen arasındaki uzaklığın yalnızca 0.372254 AU olacağı hesaplanmıştır. +Gözlem tarihi[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’ın eski uygarlıklarla baÅŸlayan gözlem tarihinin özellikle, her iki yılda bir meydana gelen, gezegenin Dünya’ya yaklaÅŸtığı ve dolayısıyla görünürlüğünün arttığı “karşı konumâ€larına dayandığı görülür. Ayrıca tarihsel kayıtlarda her 15-17 yılda bir meydana gelen, farkedilebilen günberi “karşı konumâ€larına da yer verildiÄŸi görülmektedir, çünkü Mars günberiye yaklaÅŸtığında Dünya’ya da yaklaÅŸmaktadır. + +Batı tarihinde Mars gözlemlerine iliÅŸkin pek fazla kayıt olduÄŸu söylenemez. Aristo Mars gözlemlerini tarif eden ilk yazarlardan biri olmuÅŸtur. Mars’ın 3 Ekim 1590’da Venüs’çe “örtülmeâ€si (Ä°ng. Occultation) Heidelberg’te M. Möstlin tarafından kaydedilmiÅŸtir.[126] Nihayet 1609’da Mars Galile tarafından gözlemlendi. Bu aynı zamanda Mars’ın bir teleskop aracılığıyla yapılan ilk gözlemiydi. +Mars kanalları[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Giovanni Schiaparelli tarafından yapılan Mars haritası. + +19. yy.’da teleskobun yaygınlaÅŸması gök cisimlerinin tanımlanmasında belirli bir düzeye gelinmesini saÄŸladı. 5 Eylül 1877’de Mars’ın bir günberi karşı konumu meydana geldi. O yıl Ä°talyan astronom Giovanni Schiaparelli Milano’da ilk ayrıntılı Mars haritalarını çıkarmak üzere 22 cm.’lik bir teleskop kullandı. Gözlemlerinde Mars yüzeyinde kendisinin “kanallar†adını verdiÄŸi, günümüzde kimilerince “optik illüzyon†olarak açıklanan birtakım oluÅŸumlar saptadı ve bunları hazırladığı Mars haritalarına iÅŸaretledi. Mars yüzeyinde gözlemlediÄŸi bu uzun doÄŸrusal hatlara Dünya’daki ünlü nehirlerin adlarını verdi.[127][128] + +Bu gözlemlerden etkilenen ÅŸarkiyatçı Percival Lowell 300 mm. 450 mm.’lik teleskoplara sahip bir gözlemevi kurdu. Gözlemevi Mars’ın keÅŸfine ağırlık verdi. Mars’ın pozisyonları bakımından 1894 yılı son uygun fırsattı. Lowell Mars ve Mars’ta yaÅŸam üzerine kamuda büyük bir yankı uyandıran kitaplar yayımladı. “Kanallar†dönemin en büyük teleskoplarını kullanan Henri Joseph Perrotin ve Louis Thollon gibi baÅŸka astronomlarca da saptanmıştı. Sonraki yıllarda daha büyük teleskoplarla yapılan gözlemler sonucunda, boyları önceden belirtildiÄŸi kadar uzun olmamakla doÄŸrusal kanalların bulunduÄŸu doÄŸrulandı. +Lowell tarafından 1914’ten önce yapılan gözlemlere göre hazırlanmış Mars kanalları + +Fakat daha sonra, 1909’da Flammarion, 840 mm.’lik bir teleskopla yaptığı gözlemler sonucunda, düzensiz bazı izler gözlemlemekle birlikte sözü edilen kanallara rastlamadığını açıkladı.[129] 1960’lı yıllara gelindiÄŸinde farklı yaÅŸam biçimleri olan Marslılar hakkında çeÅŸitli senaryolar içeren bir sürü makale ve kitap yayımlanmış bulunuyordu.[130] NASA’nın Mariner Projesi kapsamında gönderdiÄŸi uzay gemisinin Mars’a ulaÅŸmasından sonra bu tür senaryolar azalmış ve ÅŸekil deÄŸiÅŸtirmiÅŸtir. ÖrneÄŸin bu kez, Marslılar’in bir baÅŸka boyutta ya da frekansta oldukları, gezegenlerine inilse de algılanamayacakları yönünde yeni senaryolar üretildi. +Kültürde[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’ın çeÅŸitli adları[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Bu gezegene Batı kültüründe Mars adının verilmesi, eski Yunan mitolojisinde savaÅŸ ilahı olan Ares’e Roma mitolojisinde tekabül eden ilahın adının Mars olmasından ileri gelir.[131] Mars’a çeÅŸitli dillerde verilmiÅŸ adlardan bazıları ÅŸunlardır: + + Babil mitolojisinde Mars, gezegenin kızılımsı görünüşünden olsa gerek, ateÅŸ ve yıkım ilahı Nergal’in adıyla ifade edilirdi.[132] + Eski Yunanlar Nergal’i Ares’e denk tuttukları zaman bu gezegene Areos aster (ἌÏεως ἀστἡÏ), yani “Ares’in yıldızı†adını verdiler.[133] Daha sonra Ares ile Mars ilahlarının özdeÅŸ kılınmasıyla gezegen Romalılar’da stella Martis, yani “Mars’ın yıldızı†ya da kısaca Mars adını aldı. Eski Yunanlarda Mars’ın bir baÅŸka adı “ateÅŸli, ateÅŸten †anlamına gelen Pyroeis (Î Ï…Ïόεις) idi.[134] + Mars’a Hindu mitolojisinde Mangala (मंगल),[135][136][137] Sanskrit dilinde ise, Koç Burcu ve Akrep Burcu iÅŸaretlerine sahip olan ve okült bilimleri öğreten savaÅŸ ilahından dolayı Angaraka denir.[138] + Mars eski Mısırlılar’da “kızıl Horusâ€[139] anlamında "Ḥr DÅ¡r";;;; adını almıştır. + Ä°branicede Mars’a “kızaran†anlamında Ma'adim (מ×די×) denir ki, Mars’taki en büyük kanyona da bu ad (Ma'adim Vallis) verilmiÅŸtir.[140] + Gezegenin Arapça’daki adı El-Mirrih’tir, oradan da Türkçe’ye Merih olarak geçmiÅŸtir. Eski Türkler’de Mars’a Sakit adı verilirdi. + Urdu ve Acem dillerinde de Merih (مریخ) olarak telaffuz edilir. Merih teriminin etimolojik kökeni bilinmemektedir. Eski Ä°ranlılar’da ise gezegene "iman ilahı"yla ilgili olarak Bahram (بهرام.) adı verilirdi.[141] + Çin, Japon, Kore ve Vietnam kültürlerinde Mars eski Çin mitolosindeki beÅŸ unsurdan ateÅŸle iliÅŸkilendirilerek “ateÅŸ yıldızı†( ç«æ˜Ÿ) adıyla geçer.[142] + +Mars symbol.svg + +Mars’ın sembolü astrolojik sembolünden yararlanılarak hazırlanmış bir sembol olup, bir daire ve küçük bir oktan oluÅŸur. Bu, aslında, savaÅŸ ilahı Mars’ın kalkan ve mızrağının stilize bir temsilidir. Biyolojide de eril cinsiyeti göstermede kullanılan bu sembol, simyada karakteristik rengi kırmızı olan Mars’ın hükmettiÄŸi demir elementini simgeler; Mars da kırmızımsı rengini demiroksite borçludur.[143][144] +“Zeki Marslılarâ€[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Cydonia bölgesinde insan suratına benzetilen, doÄŸal olmayabileceÄŸi iddia edilen oluÅŸum. Viking-1,1976 +Ä°nsan suratına benzetilen oluÅŸum yakınlarında doÄŸal olmayabileceÄŸi iddia edilen piramit benzeri oluÅŸumlar. + +Mars’ta zeki bir yaÅŸam olabileceÄŸi konusunda 19.yy.’da ve 20. yy.’da, özellikle Mars’ın modern uzay araçlarınca incelenmesinden önce çeÅŸitli iddialarda bulunulmuÅŸtur. Bu iddialardan bazıları şöyle özetlenebilir: + + 19. yy. sonlarındaki popüler görüşe göre Mars zeki Marslılar’ca meskundu. Schiaparelli’nin gözlemlediÄŸi kanallar ile Percival Lowell’ın kitapları insanlarda ÅŸu kavramın doÄŸmasına neden olmuÅŸtu: Mars soÄŸuk, çorak bir gezegen olmakla birlikte burada sulama çalışmaları yapan eski uygarlıklar mevcuttu.[145] + 1899 Colorado Springs Laboratuvarı’nda alıcılarını kullanarak atmosferdeki radyo gürültülerini incelemeye çalışan mucit Nikola Tesla, sonradan bir baÅŸka gezegenden, muhtemelen Mars’tan gelmekte olduÄŸunu iddia ettiÄŸi, tekrarlanan sinyaller saptadı. Bu düşüncesini 1901’deki bir konuÅŸmasında açıkladı.[146] Lord Kelvin, Tesla’nın Dünya-dışı yaÅŸamla ilgili varsayımlarını önceleri desteklemiÅŸse de, sonradan reddetmiÅŸtir.[147][148] + 1901’de ise Harvard College Gözlemevi müdürü Edward Charles Pickering, New York Times’taki bir makalede Arizona’daki Lowell Gözlemevi’nden Mars’tan irtibat kurma giriÅŸimlerinin olduÄŸunu doÄŸrulayıcı bir telgraf almış bulunduÄŸunu açıkladı.[149] + 20. yy.’ın son çeyreÄŸinde Mars’a giden uzay araçları Mars’ta “zeki yaÅŸam†ürünü olabilecek ikamet yapıları olmadığını ortaya koyduÄŸunda, bu kez, Marslılar konusunda yeni görüşler ileri sürüldü. Bunlardan bazılarına göre, Marslılar farklı bir boyutta yaÅŸamaktaydılar, kimilerine göre de Mars’ta saptanan insan suratı ve piramitler biçimindeki oluÅŸumlar doÄŸal oluÅŸumlar deÄŸildiler.[150][151][152][153] + +Bilimkurgu[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Bilimkurguda Mars kızıl renkte temsil edilmiÅŸ ve ilk zamanlardaki bilimsel spekülasyonlar doÄŸrultusunda zeki canlılarca meskun olarak canlandırılmıştı. Marslılar’a iliÅŸkin ilk bilimkurgu senaryoları içinde en tanınmışı H.G. Wells’in 1898’de yayımlanan, ölmekte olan gezegenlerinden kaçan Marslılar’ın Dünya’yı istila etmesini konu alan Dünyalar Savaşı'dır. Kitap, 1938’de radyoya, daha sonra sinemaya uyarlandı.[154] Mars ya da Marslılar’a iliÅŸkin diÄŸer bilimkurgu eserlerinin arasında ÅŸunlar sayılabilir: + + The Martian Chronicles (hikâye), Ray Bradbury. + Barsoom (hikâyeler serisi), Edgar Rice Burroughs. + Marvin the Martian (çizgi film), Warner Brothers. + Gulliver'in Gezileri, Jonathan Swift. + Mars Trilogy (hikâyeler serisi), Kim Stanley Robinson. + We Can Remember It for You Wholesale (hikâye), Philip K. Dick. (Bu hikâyeden uyarlanan Total Recall isimli film de bulunmaktadır.) + Babylon 5 (televizyon dizisi). + GerçeÄŸe ÇaÄŸrı (sinema filmi). + Marslı (bilim kurgu romanı), Andy Weir. + diff --git a/xpcom/tests/gtest/wikipedia/vi.txt b/xpcom/tests/gtest/wikipedia/vi.txt new file mode 100644 index 0000000000..48270cc62d --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/vi.txt @@ -0,0 +1,333 @@ + + +Sao Há»a còn gá»i là : Há»a Tinh, (Tiếng Anh: Mars) là hà nh tinh thứ tÆ° tÃnh từ Mặt Trá»i trong Thái DÆ°Æ¡ng Hệ. Äôi khi hà nh tinh nà y còn được gá»i tên là Há»a Tinh. Nó thÆ°á»ng được gá»i vá»›i tên khác là "Hà nh tinh Äá»", do sắt ôxÃt có mặt rất nhiá»u trên bá» mặt hà nh tinh là m cho bá» mặt nó hiện lên vá»›i mà u đỠđặc trÆ°ng.[14] Sao Há»a là má»™t hà nh tinh đất đá vá»›i má»™t khà quyển má»ng, vá»›i những đặc Ä‘iểm trên bá» mặt có nét giống vá»›i cả các hố va chạm trên Mặt Trăng và các núi lá»a, thung lÅ©ng, sa mạc và chá»m băng ở cá»±c trên Trái Äất. Chu kỳ tá»± quay và sá»± tuần hoà n của các mùa trên Há»a Tinh khá giống vá»›i của Trái Äất do sá»± nghiêng của trục quay tạo ra. Trên Sao Há»a có ngá»n núi Olympus Mons, ngá»n núi cao nhất trong Hệ Mặt Trá»i, và hẻm núi Valles Marineris, hẻm núi dà i và rá»™ng nhất trong Thái DÆ°Æ¡ng Hệ. Lòng chảo Borealis bằng phẳng trên bán cầu bắc bao phủ tá»›i 40% diện tÃch bá» mặt hà nh tinh Ä‘á» và có thể là má»™t hố va chạm khổng lồ trong quá khứ.[15][16] + +Cho đến khi tà u Mariner 4 lần đầu tiên bay ngang qua Sao Há»a và o năm 1965, đã có nhiá»u suy Ä‘oán vá» sá»± có mặt của nÆ°á»›c lá»ng trên bá» mặt hà nh tinh nà y. Chúng dá»±a trên những quan sát vá» sá»± biến đổi chu kỳ vá» Ä‘á»™ sáng và tối của những nÆ¡i trên bá» mặt hà nh tinh, đặc biệt tại những vÄ© Ä‘á»™ vùng cá»±c, nÆ¡i có đặc Ä‘iểm của biển và lục địa; những Ä‘Æ°á»ng kẻ sá»c dà i và tối ban đầu được cho là những kênh tÆ°á»›i tiêu chứa nÆ°á»›c lá»ng. Những Ä‘Æ°á»ng sá»c thẳng nà y sau đó được giải thÃch nhÆ° là những ảo ảnh quang há»c, mặc dù các chứng cứ địa chất thu tháºp bởi các tà u thăm dò không gian cho thấy Sao Há»a có khả năng đã từng có nÆ°á»›c lá»ng bao phủ trên diện rá»™ng ở bá» mặt của nó.[17] Năm 2005, dữ liệu từ tÃn hiệu radar cho thấy sá»± có mặt của má»™t lượng lá»›n nÆ°á»›c đóng băng ở hai cá»±c,[18] và tại các vÅ©ng vÄ© Ä‘á»™ trung bình.[19][20] Robot tá»± hà nh Spirit đã lấy được mẫu các hợp chất hóa há»c chứa phân tá» nÆ°á»›c và o tháng 3 năm 2007. Tà u đổ bá»™ Phoenix đã trá»±c tiếp lấy được mẫu nÆ°á»›c đóng băng trong lá»›p đất nông trên bá» mặt và o ngà y 31 tháng 7 năm 2008.[21] + +Sao Há»a có hai vệ tinh, Phobos và Deimos, chúng là các vệ tinh nhá» và dị hình. Äây có thể là các tiểu hà nh tinh bị Há»a Tinh bắt được, tÆ°Æ¡ng tá»± nhÆ° 5261 Eureka-má»™t tiểu hà nh tinh Trojan của Há»a Tinh. Hiện nay có ba tà u quỹ đạo còn hoạt Ä‘á»™ng Ä‘ang bay quanh Sao Há»a: Mars Odyssey, Mars Express, và Mars Reconnaissance Orbiter. Trên bá» mặt nó có robot tá»± hà nh thám hiểm Sao Há»a (Mars Exploration Rover) Opportunity còn hoạt Ä‘á»™ng và cặp song sinh vá»›i nó robot tá»± hà nh Spirit đã ngừng hoạt Ä‘á»™ng, cùng vá»›i đó là những tà u đổ bá»™ và robot tá»± hà nh trong quá khứ-cả thà nh công lẫn không thà nh công. Tà u đổ bá»™ Phoenix đã hoà n thà nh phi vụ của nó và o năm 2008. Những quan sát bởi tà u quỹ đạo đã ngừng hoạt Ä‘á»™ng của NASA Mars Global Surveyor chỉ ra chứng cứ vá» sá»± dịch chuyển thu nhá» và mở rá»™ng của chá»m băng cá»±c bắc theo các mùa.[22] Tà u quỹ đạo Mars Reconnaissance Orbiter của NASA đã thu nháºn được các bức ảnh cho thấy khả năng có nÆ°á»›c chảy trong những tháng nóng nhất trên Há»a Tinh.[23] + +Sao Há»a có thể dá»… dà ng nhìn từ Trái Äất bằng mắt thÆ°á»ng. Cấp sao biểu kiến của nó đạt giá trị −3,0[7] chỉ đứng sau so vá»›i Sao Má»™c, Sao Kim, Mặt Trăng, và Mặt Trá»i. + +Äặc tÃnh váºt lý[sá»a | sá»a mã nguồn] +So sánh kÃch cỡ của Trái Äất và Sao Há»a. + +Bán kÃnh của Sao Há»a xấp xỉ bằng má»™t ná»a bán kÃnh của Trái Äất. Tá»· trá»ng của nó nhá» hÆ¡n của Trái Äất, vá»›i thể tÃch chỉ bằng 15% thể tÃch Trái Äất và khối lượng chỉ bằng 11%. Diện tÃch bá» mặt của hà nh tinh Ä‘á» chỉ hÆ¡i nhá» hÆ¡n tổng diện tÃch đất liá»n trên Trái Äất.[6] Trong khi Sao Há»a có Ä‘Æ°á»ng kÃnh và khối lượng lá»›n hÆ¡n Sao Thủy, nhÆ°ng Sao Thủy lại có tá»· trá»ng cao hÆ¡n. Äiá»u nà y là m cho hai hà nh tinh có giá trị gia tốc hấp dẫn tại bá» mặt gần bằng nhau-của Sao Há»a chỉ lá»›n hÆ¡n có 1%. Sao Há»a cÅ©ng là hà nh tinh có giá trị kÃch thÆ°á»›c, khối lượng và gia tốc hấp dẫn bá» mặt ở giữa khi so vá»›i Trái Äất và Mặt Trăng (Mặt Trăng có Ä‘Æ°á»ng kÃnh bằng má»™t ná»a của Sao Há»a, trong khi Trái Äất có Ä‘Æ°á»ng kÃnh gấp đôi Há»a Tinh; Trái Äất có khối lượng gấp chÃn lần khối lượng Sao Há»a trong khi Mặt Trăng có khối lượng chỉ bằng má»™t phần chÃn so vá»›i Há»a Tinh). Mà u sắc và ng cam của bá» mặt Sao Há»a là do lá»›p phủ chứa sắt(III) ôxÃt, thÆ°á»ng được gá»i là hematit, hay rỉ sét.[24] +Äịa chất[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Äịa chất trên Sao Há»a + +Minh há»a cấu trúc bên trong Sao Há»a + +Dá»±a trên những quan sát từ các tà u quỹ đạo và kết quả phân tÃch mẫu thiên thạch Sao Há»a, các nhà khoa há»c nháºn thấy bá» mặt Sao Há»a có thà nh phần chủ yếu từ đá bazan. Má»™t số chứng cứ cho thấy có nÆ¡i trên bá» mặt Sao Há»a già u silic hÆ¡n bazan, và có thể giống vá»›i đá andesit ở trên Trái Äất; những chứng cứ nà y cÅ©ng có thể được giải thÃch bởi sá»± có mặt của silic Ä‘iôxÃt (silica glass). Äa phần bá» mặt của hà nh tinh được bao phủ má»™t lá»›p bụi mịn, dà y của sắt(III) ôxÃt.[25][26] + +Mặc dù Há»a Tinh không còn thấy sá»± hoạt Ä‘á»™ng của má»™t từ trÆ°á»ng trên toà n cầu,[27] các quan sát cÅ©ng chỉ ra là nhiá»u phần trên lá»›p vá» hà nh tinh bị từ hóa, và sá»± đảo ngược cá»±c từ luân phiên đã xảy ra trong quá khứ. Những đặc Ä‘iểm cổ từ há»c đối vá»›i những khoáng chất dá»… bị từ hóa nà y có tÃnh chất rất giống vá»›i những dải vằn từ luân phiên nhau trên ná»n đại dÆ°Æ¡ng của Trái Äất. Má»™t lý thuyết được công bố năm 1999 và được tái kiểm tra và o tháng 10 năm 2005 (nhá» những dữ liệu từ tà u Mars Global Surveyor), theo đó những dải nà y thể hiện hoạt Ä‘á»™ng kiến tạo mảng trên Sao Há»a khoảng 4 tá»· năm trÆ°á»›c, trÆ°á»›c khi sá»± váºn Ä‘á»™ng dynamo của hà nh tinh bị suy giảm và dẫn đến sá»± mất hoà n toà n của từ trÆ°á»ng toà n cầu bao quanh hà nh tinh Ä‘á».[28] + +Những mô hình hiện tại vá» cấu trúc bên trong hà nh tinh cho rằng vùng lõi Há»a Tinh có bán kÃnh khoảng 1.480 km, vá»›i thà nh phần chủ yếu là sắt vá»›i khoảng 14–17% lÆ°u huỳnh. Lõi sắt sunfit nà y có trạng thái lá»ng má»™t phần, vá»›i sá»± táºp trung các nguyên tố nhẹ hÆ¡n cao gấp hai lần so vá»›i lõi của Trái Äất. Lõi được bao quanh bởi má»™t lá»›p phủ silicat, lá»›p nà y hình thà nh lên sá»± kiến tạo và đặc Ä‘iểm núi lá»a của hà nh tinh, nhÆ°ng hiện nay những hoạt Ä‘á»™ng nà y đã ngừng hẳn. Chiá»u dà y trung bình của lá»›p vá» Sao Há»a và o khoảng 50 km, vá»›i chiá»u dà y lá»›n nhất bằng 125 km.[29] Lá»›p vá» Trái Äất vá»›i chiá»u dà y trung bình 40 km, chỉ dà y bằng má»™t phần ba so vá»›i Sao Há»a khi so vá»›i tỉ lệ Ä‘Æ°á»ng kÃnh của hai hà nh tinh. + +Trong thá»i gian hình thà nh hệ Mặt Trá»i, Sao Há»a được tạo ra từ Ä‘Ä©a tiá»n hà nh tinh quay quanh Mặt Trá»i do kết quả của các quá trình ngẫu nhiên của sá»± váºn Ä‘á»™ng Ä‘Ä©a tiá»n hà nh tinh. Trên Há»a Tinh có nhiá»u đặc trÆ°ng hóa há»c khác biệt do vị trà của nó trong hệ Mặt Trá»i. Trên hà nh tinh nà y, các nguyên tố vá»›i Ä‘iểm sôi tÆ°Æ¡ng đối thấp nhÆ° clo, phốt pho và lÆ°u huỳnh có mặt nhiá»u hÆ¡n so vá»›i trên Trái Äất; các nguyên tố nà y có lẽ đã bị đẩy khá»i những vùng gần Mặt Trá»i bởi gió Mặt Trá»i trong giai Ä‘oạn hình thà nh Thái DÆ°Æ¡ng hệ.[30] + +Ngay sau khi hình thà nh lên các hà nh tinh, tất cả chúng Ä‘á»u chịu những đợt bắn phá lá»›n của các thiên thạch ("Late Heavy Bombardment"). Khoảng 60% bá» mặt Sao Há»a còn để lại chứng tÃch những đợt va chạm trong thá»i kỳ nà y.[31][32][33] Phần bá» mặt còn lại có lẽ thuá»™c vá» má»™t lòng chảo va chạm rá»™ng lá»›n hình thà nh trong thá»i gian đó—chứng tÃch của má»™t lòng chảo va chạm khổng lồ ở bán cầu bắc Sao Há»a, dà i khoảng 10.600 km và rá»™ng 8.500 km, hay gần bốn lần lá»›n hÆ¡n lòng chảo cá»±c nam Aitken của Mặt Trăng, lòng chảo lá»›n nhất từng được phát hiện.[15][16] Các nhà thiên văn cho rằng trong thá»i kỳ nà y Sao Há»a đã bị va chạm vá»›i má»™t thiên thể kÃch cỡ tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i Pluto cách nay bốn tá»· năm. Sá»± kiện nà y được cho là nguyên nhân gây nên sá»± khác biệt vỠđịa hình giữa bán cầu bắc và bán cầu nam của Há»a Tinh, tạo nên lòng chảo Borealis bao phủ 40% diện tÃch bá» mặt hà nh tinh.[34][35] + +Lịch sỠđịa chất của Sao Há»a có thể tách ra thà nh nhiá»u chu kỳ, nhÆ°ng bao gồm ba giai Ä‘oạn lá»›n sau:[36][37] + + Ká»· Noachis (đặt tên theo Noachis Terra): Giai Ä‘oạn hình thà nh những bá» mặt cổ nhất hiện còn tồn tại trên Sao Há»a, cách nay từ 4,5 tá»· năm đến 3,5 tá»· năm trÆ°á»›c. Bá» mặt hà nh tinh ở thá»i kỳ Noachis đã bị cà y xá»›i bởi rất nhiá»u cú va chạm lá»›n. Cao nguyên Tharsis, cao nguyên núi lá»a, được cho là đã hình thà nh trong thá»i kỳ nà y. Cuối thá»i kỳ nà y bá» mặt hà nh tinh bị bao phủ bởi những tráºn lụt lá»›n. + Ká»· Hesperia (đặt tên theo Hesperia Planum): 3,5 tá»· năm đến 2,9–3,3 tá»· năm trÆ°á»›c. Ká»· Hesperia đánh dấu bởi sá»± hình thà nh và mở rá»™ng của các đồng bằng nham thạch núi lá»a. + Ká»· Amazon (đặt tên theo Amazonis Planitia): thá»i gian từ 2,9–3,3 tá»· năm trÆ°á»›c cho đến ngà y nay. Bá» mặt hà nh tinh trong ká»· Amazon chịu má»™t số Ãt các hố va chạm thiên thạch, nhÆ°ng đặc tÃnh của các hố va chạm cÅ©ng khá Ä‘a dạng. Ngá»n núi Olympus Mons hình thà nh trong ká»· nà y, cùng vá»›i các dòng nham thạch ở khắp nÆ¡i trên Sao Há»a. + +Má»™t số hoạt Ä‘á»™ng địa chất vẫn còn diá»…n ra trên Há»a Tinh. Trong thung lÅ©ng Athabasca (Athabasca Valles) có những vỉa dung nham niên đại tá»›i 200 triệu năm (Mya). NÆ°á»›c chảy trong những địa hà o (graben) dá»c ở vùng Cerberus diá»…n ra ở thá»i Ä‘iểm 20 Mya, ám chỉ những sá»± xâm thá»±c của mac ma núi lá»a trong thá»i gian gần đây.[38] Ngà y 19 tháng 2 năm 2008, ảnh chụp từ tà u Mars Reconnaissance Orbiter cho thấy chứng cứ vá» vụ sạt lở đất đá từ má»™t vách núi cao 700 m.[39] +Äất[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Äất trên Sao Há»a + +Tà u đổ bá»™ Phoenix gá»i dữ liệu vá» cho thấy đất trên Sao Há»a có tÃnh kiá»m yếu và chứa các nguyên tố nhÆ° magiê, natri, kali và clo. Những dưỡng chất nà y được tìm thấy trong đất canh tác trên Trái Äất, và cần thiết cho sá»± phát triển của thá»±c váºt.[40] Các thà nghiệm thá»±c hiện bởi tà u đổ bá»™ cho thấy đất của Há»a Tinh có Ä‘á»™ bazÆ¡ pH bằng 8,3, và chứa dấu vết của muối peclorat.[41][42] +Khe đất hẹp (streak) ở vùng Tharsis Tholus, chụp bởi thiết bị Hirise. Nó nằm ở giữa bên trái bức ảnh nà y (hình mÅ©i kim). Tharsis Tholus nằm ở ngay bên phải ngoà i bức ảnh. + +Khe đất hẹp (streak) thÆ°á»ng xuất hiện trên khắp bá» mặt Sao Há»a và những cái má»›i thÆ°á»ng xuất hiện trên các sÆ°á»n dốc của các hố va chạm, trÅ©ng hẹp và thung lÅ©ng. Khe đất hẹp ban đầu có mà u tối và theo thá»i gian nó bị nhạt mà u dần. Thỉnh thoảng các rãnh nà y có mặt ở trong má»™t vùng nhá» hẹp sau đó chúng bắt đầu kéo dà i ra hà ng trăm mét. Khe rãnh hẹp cÅ©ng đã được quan sát thấy ở cạnh của các tảng đá lá»›n và những chÆ°á»›ng ngại váºt trên Ä‘Æ°á»ng lan rá»™ng của chúng. Các lý thuyết Ä‘a số cho rằng những khe rãnh hẹp có mà u tối do chúng nằm ở dÆ°á»›i những lá»›p đất sau đó bị lá»™ ra bá» mặt do tác Ä‘á»™ng của quá trình sạt nở đất của bụi sáng mà u hay các tráºn bão bụi.[43] Má»™t số cách giải thÃch khác lại trá»±c tiếp hÆ¡n khi cho rằng có sá»± tham gia của nÆ°á»›c hay tháºm chà là sá»± sinh trưởng của các tổ chức sống.[44][45] +Thủy văn[sá»a | sá»a mã nguồn] + + Bà i chi tiết: NÆ°á»›c trên Sao Há»a + +Ảnh vi mô chụp bởi robot Opportunity vá» dạng kết hạch mà u xám của khoáng váºt hematit, ám chỉ sá»± tồn tại trong quá khứ của nÆ°á»›c lá»ng +Khe xói má»›i hình thà nh trong hố ở vùng Centauri Montes + +NÆ°á»›c lá»ng không thể tồn tại trên bá» mặt Sao Há»a do áp suất khà quyển của nó hiện nay rất thấp, ngoại trừ những nÆ¡i có cao Ä‘á»™ thấp nhất trong má»™t chu kỳ ngắn.[46][47] Hai mÅ© băng ở các cá»±c dÆ°á»ng nhÆ° chứa má»™t lượng lá»›n nÆ°á»›c.[48][49] Thể tÃch của nÆ°á»›c băng ở mÅ© băng cá»±c nam nếu bị tan chảy có thể đủ bao phủ toà n bá»™ bá» mặt hà nh tinh Ä‘á» vá»›i Ä‘á»™ dà y 11 mét.[50] Lá»›p manti của tầng băng vÄ©nh cá»u mở rá»™ng từ vùng cá»±c đến vÄ© Ä‘á»™ khoảng 60°.[48] + +Má»™t lượng lá»›n nÆ°á»›c băng được cho là nằm ẩn dÆ°á»›i băng quyển dà y của Sao Há»a. Dữ liệu radar từ tà u Mars Express và Mars Reconnaissance Orbiter đã chỉ ra trữ lượng lá»›n nÆ°á»›c băng ở hai cá»±c (tháng 7 năm 2005)[18][51] và ở những vùng vÄ© Ä‘á»™ trung bình (tháng 11 năm 2008).[19] Tà u đổ bá»™ Phoenix đã trá»±c tiếp lấy được mẫu nÆ°á»›c băng trong lá»›p đất nông và o ngà y 31 tháng 7 năm 2008.[21] + +Äịa mạo trên Sao Há»a gợi ra má»™t cách mạnh mẽ rằng nÆ°á»›c lá»ng đã từng có thá»i Ä‘iểm tồn tại trên bá» mặt hà nh tinh nà y. Cụ thể, những mạng lÆ°á»›i thÆ°a khổng lồ phân tán trên bá» mặt, gá»i là thung lÅ©ng chảy thoát (outflow channels), xuất hiện ở 25 vị trà trên bá» mặt hà nh tinh. Äây được cho là dấu tÃch của sá»± xâm thá»±c diá»…n ra trong thá»i kỳ đại hồng thủy giải phóng nÆ°á»›c lÅ© từ tầng chứa nÆ°á»›c dÆ°á»›i bá» mặt, mặc dù má»™t và i đặc Ä‘iểm cấu trúc nà y được giả thuyết là do kết quả của tác Ä‘á»™ng từ băng hà hoặc dung nham núi lá»a.[52][53] Những con kênh trẻ nhất có thể hình thà nh trong thá»i gian gần đây vá»›i chỉ và i triệu năm tuổi.[54] Ở những nÆ¡i khác, đặc biệt là những vùng cổ nhất trên bá» mặt Há»a Tinh, ở tá»· lệ nhá» hÆ¡n, những mạng lÆ°á»›i thung lÅ©ng (networks of valleys) hình cây trải rá»™ng trên má»™t tá»· lệ diện tÃch lá»›n của cảnh quan địa hình. Những thung lÅ©ng đặc trÆ°ng nà y và sá»± phân bố của chúng hà m ý mạnh mẽ rằng chúng hình thà nh từ các dòng chảy mặt, kết quả của những tráºn mÆ°a hay tuyết rÆ¡i trong giai Ä‘oạn sá»›m của lịch sá» Sao Há»a. Sá»± váºn Ä‘á»™ng của dòng nÆ°á»›c ngầm và sá»± thoát của nó (groundwater sapping) có thể đóng má»™t vai trò phụ quan trá»ng trong má»™t số mạng lÆ°á»›i, nhÆ°ng có lẽ lượng mÆ°a là nguyên nhân gây ra những khe rãnh trong má»i trÆ°á»ng hợp.[55] + +CÅ©ng có hà ng nghìn đặc Ä‘iểm dá»c các hố va chạm và hẻm vá»±c giống vá»›i các khe xói (gully) trên Trái Äất. Những khe xói nà y có xu hÆ°á»›ng xuất hiện trên các cao nguyên ở bán cầu nam và gần xÃch đạo. Má»™t số nhà khoa há»c Ä‘á» xuất là quá trình hình thà nh của chúng đòi há»i có sá»± tham gia của nÆ°á»›c lá»ng, có lẽ từ sá»± tan chảy của băng,[56][57] mặc dù những ngÆ°á»i khác lại cho rằng cÆ¡ chế hình thà nh có sá»± tham gia của lá»›p băng cacbon Ä‘iôxÃt vÄ©nh cá»u hoặc do sá»± chuyển Ä‘á»™ng của bụi khô.[58][59] Không má»™t phần biến dạng nà o của các khe xói được hình thà nh bởi quá trình phong hóa và không có má»™t hố va chạm nà o xuất hiện trên những khe xói, Ä‘iá»u nà y chứng tá» những đặc Ä‘iểm nà y còn rất trẻ, tháºm chà các khe xói được hình thà nh chỉ trong những ngà y gần đây.[57] + +Những đặc trÆ°ng địa chất khác, nhÆ° châu thổ và quạt bồi tÃch (alluvial fans) tồn tại trong các miệng hố lá»›n, cÅ©ng là bằng chứng mạnh vá» những Ä‘iá»u kiện nóng hÆ¡n, ẩm Æ°á»›t hÆ¡n trên bá» mặt trong má»™t số thá»i Ä‘iểm ở giai Ä‘oạn lịch sá» ban đầu của Sao Há»a.[60] Những Ä‘iá»u kiện nà y cÅ©ng yêu cầu cần sá»± xuất hiện của những hồ nÆ°á»›c lá»›n phân bố trên diện rá»™ng của bá» mặt hà nh tinh, mà ở những hồ nà y cÅ©ng có những bằng chứng Ä‘á»™c láºp vá» khoáng váºt há»c, trầm tÃch há»c và địa mạo há»c.[61] Má»™t số nhà khoa há»c tháºm chà còn láºp luáºn rằng ở má»™t số thá»i Ä‘iểm trong quá khứ, nhiá»u đồng bằng thấp ở bán cầu bắc của hà nh tinh đã thá»±c sá»± bị bao phủ bởi những đại dÆ°Æ¡ng sâu hà ng trăm mét, mặc dù váºy vấn Ä‘á» nà y vẫn còn nhiá»u tranh luáºn.[62] + +CÅ©ng có thêm các dữ kiện khác vá» nÆ°á»›c lá»ng đã từng tồn tại trên bá» mặt Há»a Tinh nhá» việc xác định được những chất khoáng đặc biệt nhÆ° hematit và goethit, cả hai đôi khi được hình thà nh trong sá»± có mặt của nÆ°á»›c lá»ng.[63] Má»™t số chứng cứ trÆ°á»›c đây được cho là ám chỉ sá»± tồn tại của các đại dÆ°Æ¡ng và các con sông cổ đã bị phủ nháºn bởi việc nghiên cứu từ các bức ảnh Ä‘á»™ phân giải cao từ tà u Mars Reconnaissance Orbiter.[64] Năm 2004, robot Opportunity phát hiện khoáng chất jarosit. Khoáng chất nà y chỉ hình thà nh trong môi trÆ°á»ng nÆ°á»›c a xÃt, đây cÅ©ng là biểu hiện của việc nÆ°á»›c lá»ng đã từng tồn tại trên Sao Há»a.[65] +Chá»m băng ở các cá»±c[sá»a | sá»a mã nguồn] +Tà u Viking chụp chá»m băng cá»±c bắc Sao Há»a +Chá»m băng cá»±c nam chụp bởi Mars Global Surveyor năm 2000 + +Sao Há»a có hai chá»m băng vÄ©nh cá»u ở các cá»±c. Khi mùa đông trà n đến má»™t cá»±c, chá»m băng liên tục nằm trong bóng tối, bá» mặt bị đông lạnh và gây ra sá»± tÃch tụ của 25–30% bầu khà quyển thà nh những phiến băng khô CO2.[66] Khi vùng cá»±c chuyển sang mùa hè, các chá»m băng bị ánh sáng Mặt Trá»i chiếu liên tục, băng khô CO2 thăng hoa, dẫn đến những cÆ¡n gió khổng lồ quét qua vùng cá»±c vá»›i tốc Ä‘á»™ 400 km/h. Những hoạt Ä‘á»™ng theo mùa nà y đã váºn chuyển lượng lá»›n bụi và hÆ¡i nÆ°á»›c, tạo ra những đám mây ti lá»›n, băng giá giống nhÆ° trên Trái Äất. Những đám mây băng giá nà y đã được robot Opportunity chụp và o năm 2004.[67] + +Hai chá»m băng ở cá»±c chứa chủ yếu nÆ°á»›c đóng băng. Cacbon Ä‘iôxÃt đóng băng thà nh má»™t lá»›p tÆ°Æ¡ng đối má»ng dà y khoảng 1 mét trên bá» mặt chá»m băng cá»±c bắc chỉ trong thá»i gian mùa đông, trong khi chá»m băng cá»±c nam có lá»›p băng khô cacbon Ä‘iôxÃt vÄ©nh cá»u dà y tá»›i 8 mét.[68] Chá»m băng cá»±c bắc có Ä‘Æ°á»ng kÃnh khoảng 1.000 kilômét trong thá»i gian mùa hè ở bán cầu bắc Sao Há»a,[69] và chứa khoảng 1,6 triệu km khối băng, và nếu trải Ä‘á»u ra thì chá»m băng nà y dà y khoảng 2 km.[70] (Lá»›p phủ băng ở Greenland có thể tÃch khoảng 2,85 triệu km3.) Chá»m băng cá»±c nam có Ä‘Æ°á»ng kÃnh khoảng 350 km và dà y tá»›i 3 km.[71] Tổng thể tÃch của chá»m băng cá»±c nam cá»™ng vá»›i lượng băng tà ng trữ ở những lá»›p kế tiếp Æ°á»›c lượng và o khoảng 1,6 triệu km3.[72] Cả hai cá»±c có những rãnh băng hà hình xoắn ốc, mà các nhà khoa há»c cho là được hình thà nh từ sá»± nháºn được lượng nhiệt Mặt Trá»i khác nhau theo từng nÆ¡i, kết hợp vá»›i sá»± thăng hoa của băng và tÃch tụ của hÆ¡i nÆ°á»›c.[73][74] + +Sá»± đóng băng theo mùa ở má»™t số vùng gần chá»m băng cá»±c nam là m hình thà nh má»™t lá»›p băng khô (hoặc tấm) trong suốt dà y 1 mét trên bá» mặt. Khi mùa xuân đến, những vùng nà y ấm dần lên, áp suất được tạo ra ở dÆ°á»›i lá»›p băng khô do sá»± thăng hoa của CO2, đẩy lá»›p nà y căng lên và cuối cùng phá bung nó ra. Äiá»u nà y dẫn đến sá»± hình thà nh những mạch phun trên Sao Há»a ở cá»±c nam (Martian geyser) chứa há»—n hợp khà CO2 vá»›i bụi hoặc cát bazan Ä‘en. Quá trình nà y diá»…n ra nhanh chóng, được quan sát từ các tà u quỹ đạo trong không gian vá»›i tốc Ä‘á»™ thay đổi chỉ diá»…n ra trong và i ngà y, tuần hoặc tháng, má»™t tốc Ä‘á»™ rất nhanh so vá»›i các hiện tượng địa chất khác—đặc biệt đối vá»›i Sao Há»a. Ãnh sáng Mặt Trá»i xuyên qua lá»›p băng khô trong suốt, là m ấm lá»›p váºt liệu tối ở bên dÆ°á»›i, tạo ra áp suất đẩy khà lên tá»›i 161 km/h qua những vị trà băng má»ng. Bên dÆ°á»›i những phiến băng, khà cÅ©ng là m xói mòn ná»n đất, giáºt những hạt cát lá»ng lẻo và tạo ra những hình thù giống mạng nhện bên dÆ°á»›i lá»›p băng.[75][76][77][78] +Äịa lý[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Äịa lý trên Sao Há»a + +Cao nguyên núi lá»a (Ä‘á») và lòng chảo va chạm (xanh) chiếm phần lá»›n trong bản đồ địa hình của Sao Há»a + +Mặc dù được công nháºn nhiá»u là những ngÆ°á»i đã vẽ bản đồ Mặt Trăng, Johann Heinrich Mädler và Wilhelm Beer cÅ©ng là hai ngÆ°á»i đầu tiên vẽ bản đồ Sao Há»a. HỠđã nháºn ra rằng hầu hết đặc Ä‘iểm trên bá» mặt Sao Há»a là vÄ©nh cá»u và nhỠđó đã xác định được má»™t cách chÃnh xác chu kỳ tá»± quay của hà nh tinh nà y. Năm 1840, Mädler kết hợp 10 năm quan sát để vẽ ra tấm bản đồ địa hình đầu tiên trên Há»a Tinh. Tuy không đặt tên cho những vị trà đặc trÆ°ng, Beer và Mädler Ä‘Æ¡n giản chỉ gán chữ cho chúng; và dụ Vịnh Meridiani (Sinus Meridiani) được đặt tên vá»›i chữ "a."[79] + +Ngà y nay, các đặc Ä‘iểm trên Sao Há»a được đặt tên theo nhiá»u nguồn khác nhau. Những đặc Ä‘iểm theo suất phản chiếu quang há»c được đặt tên trong thần thoại. Các hố lá»›n hÆ¡n 60 km được đặt tên để tưởng nhá»› những nhà khoa há»c và văn chÆ°Æ¡ng và những ngÆ°á»i đã đóng góp cho việc nghiên cứu Há»a Tinh. Những hố nhá» hÆ¡n 60 km được đặt tên theo các thị trấn và ngôi là ng trên Trái Äất vá»›i dân số nhá» hÆ¡n 100.000 ngÆ°á»i. Những thung lÅ©ng lá»›n được đặt tên theo từ "Sao Há»a" và các ngôi sao trong nghÄ©a của các ngôn ngữ khác nhau, những thung lÅ©ng nhỠđược đặt tên theo các con sông.[80] + +Những đặc Ä‘iểm có suất phản chiếu hình há»c lá»›n (albedo) mang nhiá»u tên gá»i cÅ©, nhÆ°ng thÆ°á»ng được thay đổi để phản ánh những hiểu biết má»›i vá» bản chất của đặc Ä‘iểm. Và dụ, tên gá»i Nix Olympica (tuyết ở ngá»n Olympus) được đổi thà nh Olympus Mons (núi Olympus).[81] Bá» mặt Sao Há»a khi quan sát từ Trái Äất được chia ra thà nh loại vùng, vá»›i suất phản chiếu hình há»c khác nhau. Những đồng bằng nhạt mà u bao phủ bởi bụi và cát trong mà u Ä‘á» của sắt ôxÃt từng được cho là các 'lục địa' và đặt tên nhÆ° Arabia Terra (vùng đất Ả Ráºp) hay Amazonis Planitia (đồng bằng Amazon). Những vùng tối mà u được coi là các biển, nhÆ° Mare Erythraeum (biển Erythraeum), Mare Sirenum và Aurorae Sinus. Vùng tối lá»›n nhất khi nhìn từ Trái Äất là Syrtis Major Planum.[82] Chá»m băng vÄ©nh cá»u cá»±c bắc được đặt tên là Planum Boreum, và Planum Australe. + +XÃch đạo của Sao Há»a được xác định bởi sá»± tá»± quay của nó, nhÆ°ng vị trà của kinh tuyến gốc được quy Æ°á»›c cụ thể, nhÆ° kinh tuyến Greenwich của Trái Äất. Bằng cách lá»±a chá»n má»™t Ä‘iểm bất kỳ, năm 1830 Mädler và Beer đã chá»n lấy má»™t Ä‘Æ°á»ng trong bản đồ đầu tiên của há» vá» hà nh tinh Ä‘á». Sau khi tà u Mariner 9 cung cấp thêm những bức ảnh vá» bá» mặt Sao Há»a năm 1972, má»™t miệng hố nhá» (sau nà y gá»i là Airy-0), nằm trong Sinus Meridiani ("vịnh Kinh Tuyến"), được chá»n là m định nghÄ©a cho kinh Ä‘á»™ 0,0° để phù hợp vá»›i lá»±a chá»n ban đầu của hai ông.[83] + +Do Sao Há»a không có đại dÆ°Æ¡ng và vì váºy không có 'má»±c nÆ°á»›c biển', nên các nhà khoa há»c phải lá»±a chá»n má»™t bá» mặt có cao Ä‘á»™ bằng 0, tÆ°Æ¡ng tá»± nhÆ° má»±c nÆ°á»›c biển, là m bá» mặt tham chiếu; mặt nà y được gá»i là areoid [84] của Sao Há»a, tÆ°Æ¡ng tá»± nhÆ° geoid của Trái Äất. Cao Ä‘á»™ 0 được xác định tại Ä‘á»™ cao mà ở đó áp suất khà quyển Há»a Tinh bằng 610,5 Pa (6,105 mbar).[85] Ãp suất nà y tÆ°Æ¡ng ứng vá»›i Ä‘iểm ba trạng thái của nÆ°á»›c, và bằng khoảng 0,6% áp suất tại má»±c nÆ°á»›c biển trên Trái Äất (0,006 atm).[86] Ngà y nay, mặt geoid hay areoid được xác định má»™t cách chÃnh xác nhá» những vệ tinh khảo sát trÆ°á»ng hấp dẫn của Trái Äất và Sao Há»a. +Ảnh mà u gần đúng vá» miệng hố Victoria, chụp bởi robot tá»± hà nh Opportunity. Nó được chụp trong thá»i gian ba tuần từ 16 tháng 10 – 6 tháng 11, 2006. +Äịa hình va chạm[sá»a | sá»a mã nguồn] + +Äịa hình Sao Há»a có hai Ä‘iểm khác biệt rõ rệt: những vùng đồng bằng bắc bán cầu bằng phẳng do tác Ä‘á»™ng của dòng chảy dung nham ngược hẳn vá»›i vùng cao nguyên, những hố va chạm cổ ở bán cầu nam. Má»™t nghiên cứu năm 2008 cho thấy chứng cứ ủng há»™ lý thuyết Ä‘á» xuất năm 1980 rằng, khoảng bốn tá»· năm trÆ°á»›c, bán cầu bắc của Sao Há»a đã bị má»™t thiên thể kÃch cỡ má»™t phần mÆ°á»i đến má»™t phần ba Mặt Trăng đâm và o. Nếu Ä‘iá»u nà y đúng, bán cầu bắc Sao Há»a sẽ có má»™t hố va chạm vá»›i chiá»u dà i tá»›i 10.600 km và rá»™ng tá»›i 8.500 km, hay gần bằng diện tÃch của châu Âu, châu à và lục địa Australia cá»™ng lại, và hố va chạm nà y sẽ vượt qua lòng chảo cá»±c nam Aitken, được coi là lòng chảo va chạm lá»›n nhất trong hệ Mặt Trá»i hiện nay.[15][16] + +Bá» mặt Sao Há»a có rất nhiá»u hố va chạm: có khoảng 43.000 hố vá»›i Ä‘Æ°á»ng kÃnh lá»›n hÆ¡n hoặc bằng 5 km đã được phát hiện.[87] Hố lá»›n nhất được công nháºn là lòng chảo va chạm Hellas, vá»›i đặc trÆ°ng suất phản chiếu hình há»c có thể nhìn thấy rõ từ Trái Äất.[88] Do Sao Há»a có kÃch thÆ°á»›c và khối lượng nhá» hÆ¡n, nên xác suất để má»™t váºt thể va chạm và o Há»a Tinh bằng khoảng má»™t ná»a so vá»›i Trái Äất. Sao Há»a nằm gần và nh Ä‘ai tiểu hà nh tinh hÆ¡n, nên khả năng nó bị những váºt thể từ nÆ¡i nà y va chạm và o là cao hÆ¡n. Hà nh tinh Ä‘á» cÅ©ng bị các sao chổi chu kỳ ngắn va và o vá»›i khả năng lá»›n, do những sao chổi nà y nằm gần bên trong quỹ đạo của Sao Má»™c.[89] Mặc dù váºy, hố va chạm trên Sao Há»a vẫn Ãt hÆ¡n nhiá»u so vá»›i trên Mặt Trăng, do bầu khà quyển má»ng của nó cÅ©ng có tác dụng bảo vệ những thiên thạch nhá» chạm tá»›i bá» mặt. Má»™t số hố va chạm có hình thái gợi ra rằng chúng bị ẩm Æ°á»›t sau má»™t thá»i gian thiên thạch va chạm xuống bá» mặt.[90] +Những vùng kiến tạo[sá»a | sá»a mã nguồn] +Ảnh chụp núi lá»a Olympus Mons, núi cao nhất trong hệ Mặt Trá»i + +Núi lá»a hình khiên Olympus Mons có chiá»u cao tá»›i 27 km và là ngá»n núi cao nhất trong hệ Mặt Trá»i.[91] Nó là ngá»n núi lá»a đã tắt nằm trong vùng cao nguyên rá»™ng lá»›n Tharsis, vùng nà y cÅ©ng chứa má»™t và i ngá»n núi lá»a lá»›n khác. Olympus Mons cao gấp ba lần núi Everest, vá»›i chiá»u cao trên 8,8 km. CÅ©ng chú ý rằng, ngoà i những hoạt Ä‘á»™ng kiến tạo, kÃch thÆ°á»›c của hà nh tinh cÅ©ng giá»›i hạn cho chiá»u cao của những ngá»n núi trên bá» mặt của nó.[92] + +Hẻm vá»±c lá»›n Valles Marineris (tiếng Latin của thung lÅ©ng Mariner, hay còn gá»i là Agathadaemon trong những tấm bản đồ kênh Ä‘Ã o Sao Há»a cÅ©), có chiá»u dà i tá»›i 4.000 km và độ sâu khoảng 7 km. Chiá»u dà i của Valles Marineris tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i chiá»u dà i của châu Âu và chiếm tá»›i má»™t phần năm chu vi của Sao Há»a. Hẻm núi Grand Canyon trên Trái Äất có chiá»u dà i 446 km và sâu gần 2 km. Valles Marineris được hình thà nh là do sá»± trồi lên của vùng cao nguyên Tharsis là m cho lá»›p vá» hà nh tinh ở vùng Valles Marineris bị tách giãn và sụt xuống. Má»™t hẻm vá»±c lá»›n khác là Ma'adim Vallis (Ma'adim trong tiếng Hebrew là Sao Há»a). Nó dà i 700 km và bá» rá»™ng cÅ©ng lá»›n hÆ¡n Grand Canyon vá»›i chiá»u rá»™ng 20 km và độ sâu 2 km ở má»™t số vị trÃ. Trong quá khứ Ma'adim Vallis có thể đã bị ngáºp bởi nÆ°á»›c lÅ©.[93] +Hang Ä‘á»™ng[sá»a | sá»a mã nguồn] +Ảnh chụp từ thiết bị THEMIS vá» má»™t số hang trên bá» mặt Há»a Tinh. Những hang nà y được đặt tên không chÃnh thức là (A) Dena, (B) Chloe, (C) Wendy, (D) Annie, (E) Abby (trái) và Nikki, và (F) Jeanne. + +Ảnh chụp từ thiết bị THEMIS trên tà u Mars Odyssey của NASA cho thấy khả năng có tá»›i 7 cá»a hang Ä‘á»™ng trên sÆ°á»n núi lá»a Arsia Mons.[94] Những hang nà y được đặt tên tạm thá»i theo những ngÆ°á»i phát hiện ra nó đôi khi còn được gá»i là "bảy chị em."[95] Cá»a và o hang có bá» rá»™ng từ 100 m tá»›i 252 m và chiá»u sâu Ãt nhất từ 73 m tá»›i 96 m. Bởi vì ánh sáng không thể chiếu tá»›i đáy của hầu hết các hang, do váºy các nhà thiên văn cho rằng thá»±c tế chúng có chiá»u sâu lá»›n hÆ¡n và rá»™ng hÆ¡n ở trong hang so vá»›i giá trị Æ°á»›c lượng. Hang "Dena" là má»™t ngoại lệ; có thể quan sát thấy đáy của nó và vì váºy chiá»u sâu của nó bằng 130 m. Bên trong những hang nà y có thể giúp tránh khá»i tác Ä‘á»™ng từ những thiên thạch nhá», bức xạ cá»±c tÃm, gió Mặt Trá»i và những tia vÅ© trụ năng lượng cao bắn phá xuống hà nh tinh Ä‘á».[96] +Khà quyển[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Khà quyển Sao Há»a + +Bầu khà quyển má»ng manh của Há»a Tinh, nhìn từ chân trá»i trong bức ảnh chụp từ quỹ đạo thấp. + +Sao Há»a đã mất từ quyển của nó từ 4 tá»· năm trÆ°á»›c,[97] do váºy gió Mặt Trá»i tÆ°Æ¡ng tác trá»±c tiếp đến tầng Ä‘iện li của hà nh tinh, là m giảm máºt Ä‘á»™ khà quyển do dần dần tÆ°á»›c Ä‘i các nguyên tỠở lá»›p ngoà i cùng của bầu khà quyển. Cả hai tà u Mars Global Surveyor và Mars Express đã thu nháºn được những hạt bị ion hóa từ khà quyển khi chúng Ä‘ang thoát và o không gian.[97][98] So vá»›i Trái Äất, khà quyển của Sao Há»a khá loãng. Ãp suất khà quyển tại bá» mặt thay đổi từ 30 Pa (0,030 kPa) ở ngá»n Olympus Mons tá»›i 1.155 Pa (1,155 kPa) ở lòng chảo Hellas Planitia, và áp suất trung bình bằng 600 Pa (0,600 kPa).[99] Ãp suất lá»›n nhất trên Há»a Tinh bằng vá»›i áp suất ở những Ä‘iểm có Ä‘á»™ cao 35 km[100] trên bá» mặt Trái Äất. Con số nà y nhá» hÆ¡n 1% áp suất trung bình tại bá» mặt Trái Äất (101,3 kPa). Tỉ lệ Ä‘á»™ cao (scale height) của khà quyển Sao Há»a bằng 10,8 km,[101] lá»›n hÆ¡n của Trái Äất (bằng 6 km) bởi vì gia tốc hấp dẫn bá» mặt Sao Há»a chỉ bằng 38% của Trái Äất, và nhiệt Ä‘á»™ trung bình trong khà quyển Sao Há»a thấp hÆ¡n đồng thá»i khối lượng trung bình của các phân tá» cao hÆ¡n 50% so vá»›i trên Trái Äất. + +Bầu khà quyển Sao Há»a chứa 95% cacbon Ä‘iôxÃt, 3% nitÆ¡, 1,6% argon và chứa dấu vết của ôxy và hÆ¡i nÆ°á»›c.[6] Khà quyển khá là bụi bặm, chứa các hạt bụi Ä‘Æ°á»ng kÃnh khoảng 1,5 µm khiến cho bầu trá»i Sao Há»a có mà u và ng nâu khi đứng nhìn từ bá» mặt của nó.[102] +Bản đồ mêtan + +Mêtan đã được phát hiện trong khà quyển hà nh tinh Ä‘á» vá»›i tá»· lệ mol và o khoảng 30 ppb;[12][103] nó xuất hiện theo những luồng mở rá»™ng và ở những vị trà rá»i rạc khác nhau. Và o giữa mùa hè ở bán cầu bắc, luồng chÃnh chứa tá»›i 19.000 tấn mêtan, và các nhà thiên văn Æ°á»›c lượng cÆ°á»ng Ä‘á»™ ở nguồn và o khoảng 0,6 kilôgam trên giây.[104][105] Nghiên cứu cÅ©ng cho thấy có hai nguồn chÃnh phát ra mêtan, nguồn thứ nhất gần tá»a Ä‘á»™ 30° B, 260° T và nguồn hai gần tá»a Ä‘á»™ 0° B, 310° T.[104] Các nhà khoa há»c cÅ©ng Æ°á»›c lượng được Sao Há»a sản sinh ra khoảng 270 tấn mêtan trong má»™t năm.[104][106] + +Nghiên cứu cÅ©ng chỉ ra khoảng thá»i gian để lượng mêtan phân hủy có thể dà i bằng 4 năm hoặc ngắn bằng 0,6 năm Trái Äất.[104][107] Sá»± phân hủy nhanh chóng và lượng mêtan được bổ sung ám chỉ có những nguồn còn hoạt Ä‘á»™ng Ä‘ang giải phóng lượng khà nà y. Những hoạt Ä‘á»™ng núi lá»a, sao chổi rÆ¡i xuống, và khả năng có mặt của các dạng sống vi sinh váºt sản sinh ra mêtan. Mêtan cÅ©ng có thể sinh ra từ quá trình vô cÆ¡ nhÆ° sá»± serpentin hóa (serpentinization)[b] vá»›i sá»± tham gia của nÆ°á»›c, cacbon Ä‘iôxÃt và khoáng váºt olivin, nó tồn tại khá phổ biến trên Sao Há»a.[108] +Khà háºu[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Khà háºu Sao Há»a + +Trong số các hà nh tinh trong hệ Mặt Trá»i, các mùa trên Sao Há»a là gần giống vá»›i trên Trái Äất nhất, do sá»± gần bằng vá» Ä‘á»™ nghiêng của trục tá»± quay ở hai hà nh tinh. Äá»™ dà i các mùa trên Há»a Tinh bằng khoảng hai lần trên Trái Äất, do khoảng cách từ Sao Há»a đến Mặt Trá»i lá»›n hÆ¡n dẫn đến má»™t năm trên hà nh tinh nà y bằng khoảng hai năm Trái Äất. Nhiệt Ä‘á»™ Sao Há»a thay đổi từ nhiệt Ä‘á»™ rất thấp -87 °C trong thá»i gian mùa đông ở các cá»±c cho đến -5 °C và o mùa hè.[46] Biên Ä‘á»™ nhiệt Ä‘á»™ lá»›n nhÆ° váºy là vì bầu khà quyển quá má»ng không thể giữ lại được nhiệt lượng từ Mặt Trá»i, do áp suất khà quyển thấp, và do tỉ số thể tÃch nhiệt rung riêng (thermal inertia) của đất Sao Há»a thấp.[109] Hà nh tinh cÅ©ng nằm xa Mặt Trá»i gấp 1,52 lần so vá»›i Trái Äất, do váºy nó chỉ nháºn được khoảng 43% lượng ánh sáng so vá»›i Trái Äất.[110] + +Nếu Sao Há»a nằm và o quỹ đạo của Trái Äất, các mùa trên hà nh tinh nà y sẽ giống vá»›i trên địa cầu do Ä‘á»™ lá»›n góc nghiêng trục quay hai hà nh tinh giống nhau. Äá»™ lệch tâm quỹ đạo tÆ°Æ¡ng đối lá»›n của nó cÅ©ng có má»™t tác Ä‘á»™ng quan trá»ng. Khi Há»a Tinh gần cáºn Ä‘iểm quỹ đạo thì ở bán cầu bắc là mùa đông và bán cầu nam là mùa hè, khi nó gần viá»…n Ä‘iểm quỹ đạo thì ngược lại. Các mùa ở bán cầu nam diá»…n ra khắc nghiệt hÆ¡n so vá»›i bán cầu bắc. Nhiệt Ä‘á»™ trong mùa hè ở bán cầu nam có thể cao hÆ¡n tá»›i 30 °C (86 °F) so vá»›i mùa hè ở bán cầu bắc.[111] + +Sao Há»a cÅ©ng có những tráºn bão bụi lá»›n nhất trong hệ Mặt Trá»i. Chúng có thể biến đổi từ má»™t cÆ¡n bão trong má»™t vùng nhá» cho đến hình thà nh cÆ¡n bão khổng lồ bao phủ toà n bá»™ hà nh tinh. Những tráºn bão bụi thÆ°á»ng xuất hiện khi Sao Há»a nằm gần Mặt Trá»i và khi đó nhiệt Ä‘á»™ toà n cầu cÅ©ng tăng lên do tác Ä‘á»™ng của bão bụi.[112] +Ảnh chụp qua kÃnh thiên văn Hubble so sánh Sao Há»a trÆ°á»›c và sau tráºn bão bụi bao phủ toà n cầu. +Quỹ đạo và chu kỳ quay[sá»a | sá»a mã nguồn] + +Khoảng cách trung bình từ Sao Há»a đến Mặt Trá»i và o khoảng 230 triệu km (1,5 AU) và chu kỳ quỹ đạo của nó bằng 687 ngà y Trái Äất. Ngà y mặt trá»i (viết tắt sol) trên Sao Há»a hÆ¡i dà i hÆ¡n ngà y Trái Äất và bằng: 24 giá», 39 phút, và 35,244 giây. Má»™t năm Sao Há»a bằng 1,8809 năm Trái Äất; hay 1 năm, 320 ngà y, và 18,2 giá».[6] + +Äá»™ nghiêng trục quay bằng 25,19 Ä‘á»™ và gần bằng vá»›i Ä‘á»™ nghiêng trục quay của Trái Äất.[6] Kết quả là Sao Há»a có các mùa gần giống vá»›i Trái Äất mặc dù chúng có thá»i gian kéo dà i gần gấp đôi trong má»™t năm dà i hÆ¡n. Hiện tại hÆ°á»›ng của cá»±c bắc Há»a Tinh nằm gần vá»›i ngôi sao Deneb.[13] Sao Há»a đã vượt qua cáºn Ä‘iểm quỹ đạo và o tháng 3, 2011 và vượt qua viá»…n Ä‘iểm quỹ đạo và o tháng 2, 2012.[113] + +Sao Há»a có Ä‘á»™ lệch tâm quỹ đạo tÆ°Æ¡ng đối lá»›n và o khoảng 0,09; trong bảy hà nh tinh còn lại của hệ Mặt Trá»i, chỉ có Sao Thủy có Ä‘á»™ lệch tâm lá»›n hÆ¡n. Các nhà khoa há»c biết rằng trong quá khứ Sao Há»a có quỹ đạo tròn hÆ¡n so vá»›i bây giá». Cách đây khoảng 1,35 triệu năm Trái Äất, Sao Há»a có Ä‘á»™ lệch tâm gần bằng 0,002, nhá» hÆ¡n nhiá»u so vá»›i Trái Äất ngà y nay.[114] Chu kỳ Ä‘á»™ lệch tâm của Sao Há»a bằng 96.000 năm Trái Äất so vá»›i chu kỳ lệch tâm của Trái Äất bằng 100.000 năm.[115] Sao Há»a cÅ©ng đã từng có chu kỳ lệch tâm bằng 2,2 triệu năm Trái Äất. Trong vòng 35.000 năm trÆ°á»›c đây, quỹ đạo Sao Há»a trở lên elip hÆ¡n do ảnh hưởng hấp dẫn từ những hà nh tinh khác. Khoảng cách gần nhất giữa Trái Äất và Sao Há»a sẽ giảm nhẹ dần trong vòng 25.000 năm tá»›i.[116] +ThePlanets Orbits Ceres Mars PolarView.svg Ảnh bên trái so sánh quỹ đạo của Sao Há»a và hà nh tinh lùn Ceres nằm trong và nh Ä‘ai tiểu hà nh tinh, khi nhìn từ cá»±c bắc của hoà ng đạo, trong khi bức ảnh bên phải nhìn từ Ä‘iểm nút lên của quỹ đạo. Các Ä‘oạn của quỹ đạo nằm ở phÃa nam hoà ng đạo được vẽ bằng mà u tối. Cáºn Ä‘iểm quỹ đạo (q) và viá»…n Ä‘iểm quỹ đạo (Q) được đánh dấu vá»›i ngà y gần nhất thiên thể sẽ vượt qua. Quỹ đạo Sao Há»a có mà u Ä‘á», Ceres có mà u và ng. ThePlanets Orbits Ceres Mars.svg +Vệ tinh tá»± nhiên[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Vệ tinh tá»± nhiên của Sao Há»a, Phobos (vệ tinh), và Deimos (vệ tinh) + +Ảnh mà u chụp bởi Mars Reconnaissance Orbiter – HiRISE, ngà y 23 tháng 3, 2008 +Ảnh mà u Deimos chụp ngà y 21 tháng 2, 2009 cÅ©ng bởi tà u nà y (không theo tá»· lệ) + +Sao Há»a có hai vệ tinh tá»± nhiên tÆ°Æ¡ng đối nhá», Phobos và Deimos, chúng quay quanh trên những quỹ đạo khá gần hà nh tinh. Lý thuyết vá» tiểu hà nh tinh bị hà nh tinh Ä‘á» bắt giữ đã thu hút sá»± quan tâm từ lâu nhÆ°ng nguồn gốc của nó vẫn còn nhiá»u bà ẩn.[117] Nhà thiên văn há»c Asaph Hall đã phát hiện ra 2 vệ tinh nà y và o năm 1877, và ông đặt tên chúng theo tên các nhân váºt trong thần thoại Hy Lạp là Phobos (Ä‘au Ä‘á»›n/sợ hãi) và Deimos (kinh hoà ng/khiếp sợ), hai ngÆ°á»i con cùng tham gia những tráºn đánh của vị thần chiến tranh Ares. Ares trong thần thoại La Mã tên là Mars (mà ngÆ°á»i La Mã dùng tên của vị thần đó đặt tên cho Sao Há»a).[118][119] + +Nhìn từ bá» mặt Há»a Tinh, chuyển Ä‘á»™ng của Phobos và Deimos hiện lên rất khác lạ so vá»›i chuyển Ä‘á»™ng của Mặt Trăng. Phobos má»c lên ở phÃa tây, lặn ở phÃa đông, và lại má»c lên chỉ sau 11 giá». Deimos nằm ngay bên ngoà i quỹ đạo đồng bộ—tại đó chu kỳ quỹ đạo bằng vá»›i chu kỳ tá»± quay của hà nh tinh—nó má»c lên ở phÃa đông nhÆ°ng rất cháºm. Mặc dù chu kỳ quỹ đạo của nó bằng 30 giá», nó phải mất 2,7 ngà y để lặn ở phÃa tây khi nó cháºm dần Ä‘i vá» phÃa sau sá»± quay của Sao Há»a, và sau đó phải khá lâu nó má»›i má»c trở lại.[120] + +Bởi vì quỹ đạo của Phobos nằm bên trong quỹ đạo đồng bá»™, lá»±c thủy triá»u từ Há»a Tinh Ä‘ang dần dần hút vệ tinh nà y vá» phÃa nó. Trong khoảng 50 triệu năm nữa vệ tinh nà y sẽ đâm xuống bá» mặt Sao Há»a hoặc bị phá tan thà nh má»™t cái và nh bụi quay quanh hà nh tinh.[120] + +Nguồn gốc của hai vệ tinh nà y vẫn chÆ°a được hiểu đầy đủ. Äặc tÃnh suất phản chiếu hình há»c thấp và thà nh phần cấu tạo bằng "thiên thạch hạt chứa than" (carbonaceous chondrite) giống vá»›i tÃnh chất của các tiểu hà nh tinh là má»™t trong những bằng chứng ủng há»™ lý thuyết tiểu hà nh tinh bị bắt. Quỹ đạo không ổn định của Phobos dÆ°á»ng nhÆ° là má»™t chứng cứ khác cho thấy nó bị bắt trong thá»i gian khá gần ngà y nay. Tuy váºy, cả hai vệ tinh có quỹ đạo tròn, mặt phẳng quỹ đạo rất gần vá»›i mặt phẳng xÃch đạo hà nh tinh, lại là má»™t Ä‘iá»u không thông thÆ°á»ng cho các váºt thể bị bắt và nhÆ° thế đòi há»i quá trình Ä‘á»™ng lá»±c bắt giữ 2 vệ tinh nà y rất phức tạp. Sá»± bồi tụ trong buổi đầu lịch sá» hình thà nh Sao Há»a cÅ©ng là má»™t khả năng khác nhÆ°ng lý thuyết nà y lại không giải thÃch được thà nh phần cấu tạo của 2 vệ tinh giống vá»›i các tiểu hà nh tinh hÆ¡n là giống vá»›i thà nh phần của Há»a Tinh. + +Má»™t khả năng khác đó là sá»± tham gia của má»™t váºt thể thứ ba hoặc má»™t kiểu va chạm gây nhiá»…u loạn.[121] Những dữ liệu gần đây cho thấy khả năng vệ tinh Phobos có cấu trúc bên trong khá rá»—ng[122] và các nhà khoa há»c Ä‘á» xuất thà nh phần chÃnh của nó là khoáng phyllosilicat và những loại khoáng váºt khác đã có trên Sao Há»a,[123] và há» chỉ ra trá»±c tiếp rằng nguồn gốc của Phobos là từ những váºt liệu bắn ra từ má»™t thiên thể va chạm vá»›i Sao Há»a và sau đó tÃch tụ lại trên quỹ đạo quanh hà nh tinh nà y,[124] tÆ°Æ¡ng tá»± nhÆ° lý thuyết giải thÃch cho nguồn gốc Mặt Trăng. Trong khi phổ VNIR của các vệ tinh Sao Há»a giống vá»›i phổ của các tiểu hà nh tinh trong và nh Ä‘ai tiểu hà nh tinh, thì phổ hồng ngoại nhiệt (thermal infrared) của Phobos lại không hoà n toà n tÆ°Æ¡ng thÃch vá»›i phổ của bất kỳ lá»›p khoáng váºt chondrit.[123] +Tên ÄÆ°á»ng kÃnh +(km) Khối lượng +(kg) Bán trục +lá»›n (km) Chu kỳ +quỹ đạo (giá») Chu kỳ +trăng má»c +trung bình +(giá», ngà y) +Phobos 22,2 km (27×21,6×18,8) 1,08×1016 9 377 km 7,66 11,12 giá» +(0,463 ngà y) +Deimos 12,6 km (10×12×16) 2×1015 23 460 km 30,35 131 giá» +(5,44 ngà y) +Sá»± sống[sá»a | sá»a mã nguồn] + + Bà i chi tiết: NÆ°á»›c trên Sao Há»a và Sá»± sống trên Sao Há»a + +Những hiểu biết hiện tại vá» hà nh tinh ở được—khả năng má»™t thế giá»›i cho sá»± sống phát triển và duy trì—ưu tiên những hà nh tinh có nÆ°á»›c lá»ng tồn tại trên bá» mặt của chúng. Äiá»u nà y trÆ°á»›c tiên đòi há»i quỹ đạo hà nh tinh nằm trong vùng ở được, mà đối vá»›i Mặt Trá»i hiện nay là vùng mở rá»™ng ngà y bên ngoà i quỹ đạo Sao Kim đến bán trục lá»›n của Sao Há»a.[125] Trong thá»i gian Sao Há»a nằm gần cáºn Ä‘iểm quỹ đạo thì nó cÅ©ng nằm sâu bên trong vùng ở được, nhÆ°ng bầu khà quyển má»ng của hà nh tinh (và do đó áp suất khà quyển thấp) không đủ để cho nÆ°á»›c lá»ng tồn tại trên diện rá»™ng và trong thá»i gian dà i. Những dòng chảy trong quá khứ của nÆ°á»›c lá»ng có khả năng mang lại tÃnh ở được cho hà nh tinh Ä‘á». Má»™t số chứng cứ hiện nay cÅ©ng cho thấy nếu nÆ°á»›c lá»ng có tồn tại trên bá» mặt Sao Há»a thì nó sẽ quá mặn và có tÃnh a xÃt cao để có thể duy trì má»™t sá»± sống thông thÆ°á»ng.[126] + +Sao Há»a thiếu Ä‘i từ quyển và có má»™t bầu khà quyển cá»±c má»ng cÅ©ng là má»™t thách thức: sẽ có Ãt sá»± truyá»n nhiệt trên toà n bá» mặt hà nh tinh, đồng thá»i khà quyển cÅ©ng không thể ngăn được sá»± bắn phá của gió Mặt Trá»i và má»™t áp suất quá thấp để duy trì nÆ°á»›c dÆ°á»›i dạng lá»ng (thay và o đó nÆ°á»›c sẽ láºp tức thăng hoa thà nh dạng hÆ¡i). Sao Há»a cÅ©ng gần nhÆ°, hay có lẽ hoà n toà n không còn các hoạt Ä‘á»™ng địa chất; sá»± ngÆ°ng hoạt Ä‘á»™ng của các núi lá»a rõ rà ng là m ngừng sá»± tuần hoà n của các khoáng chất và hợp chất hóa há»c giữa bá» mặt và phần bên trong hà nh tinh.[127] + +Nhiá»u bằng chứng ủng há»™ cho Há»a Tinh trÆ°á»›c đây đã từng có những Ä‘iá»u kiện cho sá»± sống phát triển hÆ¡n so vá»›i ngà y nay, nhÆ°ng liệu các sinh váºt sống có từng tồn tại hay không vẫn còn là bà ẩn. Các tà u thăm dò Viking trong giữa tháºp niên 1970 đã thá»±c hiện những thà nghiệm được thiết kế nhằm xác định các vi sinh váºt trong đất Sao Há»a ở những vị trà chúng đổ bá»™ và đã cho kết quả khả quan, bao gồm sá»± tăng tạm thá»i của sản phẩm CO2 khi trá»™n những mẫu đất vá»›i nÆ°á»›c và khoáng chất. Dấu hiệu của sá»± sống nà y đã gây ra tranh cãi trong cá»™ng đồng các nhà khoa há»c, và vẫn còn là má»™t vấn Ä‘á» mở, trong đó nhà khoa há»c NASA Gilbert Levin cho rằng tà u Viking có thể đã tìm thấy sá»± sống. Má»™t cuá»™c phân tÃch lại những dữ liệu từ Viking, trong ánh sáng của hiểu biết hiện đại vá» dạng sống trong môi trÆ°á»ng cá»±c kỳ khắc nghiệt (extremophile forms), cho thấy các thà nghiệm trong chÆ°Æ¡ng trình Viking không đủ Ä‘á»™ phức tạp để xác định được những dạng sống nà y. Tháºm chà những thà nghiệm nà y có thể đã giết chết những dạng vi sinh váºt (giả thuyết là tồn tại).[128] Các thà nghiệm thá»±c hiện bởi tà u đổ bá»™ Phoenix đã chỉ ra đất ở vị trà đáp xuống có tÃnh kiá»m pH khá cao và nó chứa magiê, natri, kali và clo.[129] Những chất dinh dưỡng trong đất có thể giúp phát triển sá»± sống những sá»± sống vẫn cần phải được bảo vệ từ những ánh sáng cá»±c tÃm rất mạnh.[130] + +Tại phòng thà nghiệm Trung tâm không gian Johnson, má»™t số hình dạng thú vị đã được tìm thấy trong khối vẫn thạch ALH84001. Má»™t số nhà khoa há»c Ä‘á» xuất là những hình dạng nà y có khả năng là hóa thạch của những vi sinh váºt đã từng tồn tại trên Sao Há»a trÆ°á»›c khi vẫn thạch nà y bị bắn và o không gian bởi má»™t vụ chạm của thiên thạch vá»›i hà nh tinh Ä‘á» và gá»i nó Ä‘i trong chuyến hà nh trình khoảng 15 triệu năm tá»›i Trái Äất. Äá» xuất vá» nguồn gốc phi hữu cÆ¡ cho những hình dạng nà y cÅ©ng đã được nêu ra.[131] + +Những lượng nhá» mêtan và fomanđêhÃt xác định được gần đây bởi các tà u quỹ đạo Ä‘á»u được coi là những dấu hiệu cho sá»± sống, và những hợp chất hóa há»c nà y cÅ©ng nhanh chóng bị phân hủy trong bầu khà quyển của Há»a Tinh.[132][133] CÅ©ng có khả năng những hợp chất nà y được bổ sung bởi hoạt Ä‘á»™ng địa chất hay núi lá»a cÅ©ng nhÆ° sá»± serpentin hóa của khoáng chất (serpentinization).[108] + +Trong tÆ°Æ¡ng lai, có thể là nhiệt Ä‘á»™ bá» mặt Sao Há»a sẽ tăng từ từ, hÆ¡i nÆ°á»›c và CO2 hiện tại Ä‘ang đóng băng dÆ°á»›i regolith bá» mặt sẽ giải phóng và o khà quyển tạo nên hiệu ứng nhà kÃnh nung nóng hà nh tinh cho đến khi nó đạt những Ä‘iá»u kiện tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i Trái Äất ngà y nay, do đó cung cấp nÆ¡i trú chân tiá»m năng trong tÆ°Æ¡ng lai cho sinh váºt trên Trái Äất.[134] +Quá trình thám hiểm[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Thám hiểm Sao Há»a + +Tà u đổ bá»™ Viking 1 và o tháng 2, 1978. + +Hà ng tá tà u không gian, bao gồm tà u quỹ đạo, tà u đổ bá»™, và robot tá»± hà nh, đã được gá»i đến Sao Há»a bởi Liên Xô, Hoa Kỳ, châu Âu, và Nháºt Bản nhằm nghiên cứu bá» mặt, khà háºu và địa chất hà nh tinh Ä‘á». Äến năm 2008, chi phà cho váºn chuyển váºt liệu từ bá» mặt Trái Äất lên bá» mặt Sao Há»a có giá xấp xỉ 309.000US$ trên má»™t kilôgam.[135] + +Những tà u còn hoạt Ä‘á»™ng cho đến năm 2011 bao gồm Mars Reconnaissance Orbiter (từ 2006), Mars Express (từ 2003), 2001 Mars Odyssey (từ 2001), và trên bá» mặt là robot tá»± hà nh Opportunity (từ 2004). Những phi vụ kết thúc gần đây bao gồm Mars Global Surveyor (1997–2006) và Robot tá»± hà nh Spirit (2004–2010). + +Gần hai phần ba số tà u không gian được thiết kế đến Sao Há»a đã bị lá»—i trong giai Ä‘oạn phóng, hà nh trình hoặc trÆ°á»›c khi bắt đầu thá»±c hiện phi vụ hoặc không hoà n tất phi vụ của chúng, chủ yếu trong giai Ä‘oạn cuối thế ká»· 20. Sang thế ká»· 21, những thất bại trong các phi vụ đã được giảm bá»›t nhiá»u.[136] Những lá»—i trong các phi vụ chủ yếu là do vấn Ä‘á» kÄ© thuáºt, nhÆ° mất liên lạc hoặc sai lầm trong thiết kế, và thÆ°á»ng do hạn chế vá» tà i chÃnh và thiếu năng lá»±c trong các phi vụ.[136] Số thất bại nhiá»u nhÆ° váºy đã là m cho công chúng liên tưởng đến những Ä‘iá»u viá»…n tưởng nhÆ° "Tam giác Bermuda", "Lá»i nguyá»n" Sao Há»a, hoặc "ma cà rồng" trong thiên hà đã ăn những tà u không gian nà y.[136] Những thất bại gần đây bao gồm phi vụ Beagle 2 (2003), Mars Climate Orbiter (1999), và Mars 96 (1996). +Các phi vụ trong quá khứ[sá»a | sá»a mã nguồn] +Tà u Mars 3 trên con tem năm 1972. + +Chuyến bay ngang qua Sao Há»a thà nh công đầu tiên bởi tà u Mariner 4 của NASA và o ngà y 14–15 tháng 7, 1965. Ngà y 14 tháng 11, 1971 tà u Mariner 9 trở thà nh tà u không gian đầu tiên quay quanh má»™t hà nh tinh khác khi nó Ä‘i và o quỹ đạo quanh Sao Há»a.[137] Con tà u đầu tiên đổ bá»™ thà nh công xuống bá» mặt là hai tà u của Liên Xô: Mars 2 và o ngà y 27 tháng 11 và Mars 3 và o ngà y 2 tháng 12, 1971, nhÆ°ng cả hai đã bị mất tÃn hiệu liên lạc chỉ và i giây sau khi đổ bá»™ thà nh công. Năm 1975 NASA triển khai chÆ°Æ¡ng trình Viking bao gồm hai tà u quỹ đạo, má»—i tà u có má»™t thiết bị đổ bá»™; và cả hai đã đổ bá»™ thà nh công và o năm 1976. Tà u quỹ đạo Viking 1 còn hoạt Ä‘á»™ng tiếp được 6 năm, trong khi Viking 2 hoạt Ä‘á»™ng được 3 năm. Các thiết bị đổ bá»™ đã gá»i bức ảnh mà u toà n cảnh tại vị trà đổ bá»™ vá» Sao Há»a[138] và hai tà u quỹ đạo đã chụp ảnh bá» mặt hà nh tinh mà vẫn còn được sá» dụng cho tá»›i ngà y nay. + +Tà u thám hiểm của Liên Xô Phobos 1 và 2 được gá»i đến Sao Há»a năm 1988 nhằm nghiên cứu hà nh tinh và hai vệ tinh của nó. Phobos 1 bị mất liên lạc trong hà nh trình đến Sao Há»a còn Phobos 2 đã thà nh công khi chụp ảnh được Sao Há»a và vệ tinh Phobos nhÆ°ng đã không thà nh công khi gá»i thiết bị đổ bá»™ xuống bá» mặt Phobos.[139] + +Sau thất bại của tà u quỹ đạo Mars Observer và o năm 1992, tà u Mars Global Surveyor của NASA đã Ä‘i và o quỹ đạo hà nh tinh nà y năm 1997. Phi vụ nà y đã thà nh công và kết thúc nhiệm vụ chÃnh là vẽ bản đồ và o đầu năm 2001. Trong chÆ°Æ¡ng trình mở rá»™ng lần thứ 3, con tà u nà y đã bị mất liên lạc và o tháng 11 năm 2006, tổng cá»™ng nó đã hoạt Ä‘á»™ng tá»›i 10 năm trong không gian. Tà u quỹ đạo Mars Pathfinder của NASA, mang theo má»™t robot thám hiểm là Sojourner, đã đổ bá»™ xuống thung lÅ©ng Ares Vallis và o mùa hè năm 1997, và gá»i vá» nhiá»u bức ảnh giá trị.[140] +Robot Spirit đổ bá»™ lên Sao Há»a năm 2004 +Nhìn từ tà u đổ bá»™ Phoenix năm 2008 + +Tà u đổ bá»™ Phoenix đã hạ cánh xuống vùng cá»±c bắc Sao Há»a và o ngà y 25 tháng 5, 2008.[141] Cánh tay robot của nó được sá» dụng để Ä‘Ã o đất và sá»± có mặt của băng nÆ°á»›c đã được xác nháºn và o ngà y 20 tháng 6.[142][143][143] Phi vụ nà y kết thúc và o ngà y 10 tháng 11, 2008 sau khi liên lạc vá»›i tà u thất bại.[144] + +Tháng 11 năm 2011, phi vụ Fobos-Grunt và Huỳnh Há»a 1 được phóng lên trong chÆ°Æ¡ng trình hợp tác giữa Liên bang Nga và Trung Quốc. NhÆ°ng tà u Fobos-Grunt đã không khởi Ä‘á»™ng được Ä‘á»™ng cÆ¡ đẩy sau khi nó được phóng lên quỹ đạo quanh Trái Äất. Fobos-Grunt là phi vụ gá»i má»™t tà u quỹ đạo đến Sao Há»a đồng thá»i phóng má»™t thiết bị đổ bá»™ xuống vệ tinh Phobos nhằm thu tháºp mẫu đất đá sau đó gá»i vá» Trái Äất. Các nhà khoa há»c Nga đã không thể liên lạc được vá»›i tà u và khả năng con tà u sẽ rÆ¡i trở lại Trái Äất và o tháng 1 năm 2012. +Phi vụ hiện tại[sá»a | sá»a mã nguồn] + +Tà u Mars Odyssey của NASA Ä‘i và o quỹ đạo Há»a Tinh năm 2001.[145] Phổ kế tia gamma trên tà u Odyssey đã phát hiện má»™t lượng đáng kể hiÄ‘rô chỉ cách lá»›p phủ regolith ở bá» mặt có và i mét trên Sao Há»a. Lượng hiÄ‘rô nà y được chứa trong lá»›p băng tà ng trữ ở phÃa dÆ°á»›i.[146] + +Tà u quỹ đạo Mars Express của cÆ¡ quan không gian châu Âu (ESA) đến Sao Há»a năm 2003. Nó mang theo thiết bị đổ bá»™ Beagle 2 nhÆ°ng đã đổ bá»™ không thà nh công trong quá trình Ä‘i và o bầu khà quyển và được coi là mất hoà n toà n và o tháng 2 năm 2004.[147] Äầu năm 2004, Ä‘á»™i phân tÃch phổ kế Fourier hà nh tinh (Planetary Fourier Spectrometer team) đã thông báo rằng tà u quỹ đạo đã xác định được sá»± có mặt của mêtan trong bầu khà quyển Há»a Tinh. CÆ¡ quan ESA thông báo tà u của hỠđã quan sát được hiện tượng cá»±c quang trên Sao Há»a và o tháng 6 năm 2006.[148] + +Tháng 1 năm 2004, hai tà u giống nhau của NASA thuá»™c chÆ°Æ¡ng trình robot tá»± hà nh thám hiểm Sao Há»a là Spirit (MER-A) và Opportunity (MER-B) đã đáp thà nh công xuống bá» mặt hà nh tinh Ä‘á». Cả hai Ä‘á»u đã hoà n thà nh mục tiêu của chúng. Má»™t trong những kết quả khoa há»c quan trá»ng nhất đó là chứng cứ thu được vá» sá»± tồn tại của nÆ°á»›c lá»ng trong quá khứ ở cả hai địa Ä‘iểm đổ bá»™. Bão bụi (dust devils) và gió bão đã thÆ°á»ng xuyên là m sạch các tấm pin mặt trá»i ở 2 robot tá»± hà nh, do váºy hai robot có Ä‘iá»u kiện để mở rá»™ng thá»i gian tìm kiếm trên Há»a Tinh.[149] Tháng 3 năm 2010 robot Spirit đã ngừng hoạt Ä‘á»™ng sau má»™t thá»i gian bị mắc kẹt trong cát. + +Ngà y 10 tháng 3 năm 2006, tà u Mars Reconnaissance Orbiter (MRO) của NASA Ä‘i và o quỹ đạo hà nh tinh nà y để thá»±c hiện nhiệm vụ 2 năm khảo sát khoa há»c. Con tà u đã vẽ bản đổ địa hình và khà háºu Sao Há»a nhằm tìm những địa Ä‘iểm phù hợp cho các phi vụ đổ bá»™ trong tÆ°Æ¡ng lai. Ngà y 3 tháng 3 năm 2008, các nhà khoa há»c thông báo tà u MRO đã lần đầu tiên chụp được bức ảnh vá» má»™t chuá»—i các hoạt Ä‘á»™ng sạt lở đất đá gần cá»±c bắc hà nh tinh.[150] + +Tà u Dawn đã bay ngang qua Sao Há»a và o tháng 2 năm 2009 để nháºn thêm lá»±c đẩy hấp dẫn nhằm tăng tốc đến tiểu hà nh tinh Vesta và sau đó là hà nh tinh lùn Ceres.[151] + Wikimedia Commons có thÆ° viện hình ảnh và phÆ°Æ¡ng tiện truyá»n tải vá» Hình do Curiosity truyá»n vá» + +ChÆ°Æ¡ng trình Mars Science Laboratory, vá»›i robot tá»± hà nh mang tên Curiosity, được phóng lên ngà y 26 tháng 12 năm 2011. Robot tá»± hà nh nà y là má»™t phiên bản lá»›n hÆ¡n và hiện đại hÆ¡n so vá»›i hai robot tá»± hà nh trong chÆ°Æ¡ng trình Mars Exploration Rovers, vá»›i khả năng di chuyển tá»›i 90 m/h. Nó cÅ©ng được thiết kế vá»›i khả năng thá»±c hiện thà nghiệm vá»›i các mẫu đất đá lấy từ mÅ©i khoan ở cánh tay robot hoặc thu được thà nh phần đất đá từ việc chiếu tia laser có tầm xa tá»›i. Robot nà y cÅ©ng sẽ thá»±c hiện khả năng đổ bá»™ chÃnh xác trong vùng bán kÃnh khoảng 20 km nằm trong hố Gale nhá» lần đầu tiên sá» dụng thiết bị phản lá»±c có tên "Sky crane".[152] + +Năm 2008, NASA tà i trợ cho chÆ°Æ¡ng trình MAVEN, má»™t phi vụ gá»i tà u quỹ đạo được phóng lên năm 2013 nhằm nghiên cứu bầu khà quyển của Sao Há»a. Con tà u sẽ Ä‘i và o quỹ đạo hà nh tinh Ä‘á» và o năm 2014.[153] +Các phi vụ trong tÆ°Æ¡ng lai[sá»a | sá»a mã nguồn] + +Năm 2018 cÆ¡ quan ESA có kế hoạch phóng robot tá»± hà nh đầu tiên của há» lên hà nh tinh nà y; robot ExoMars có khả năng khoan sâu 2 m và o đất nhằm tìm kiếm các phân tá» hữu cÆ¡.[154] + +NASA sẽ gá»i robot đổ bá»™ InSight dá»±a trên thiết kế tà u đổ bá»™ Phoenix nhằm nghiên cứu cấu trúc sâu bên trong Sao Há»a và o năm 2016.[155] + +Năm 2020, má»™t robot tá»± hà nh có thiết kế tÆ°Æ¡ng tá»± nhÆ° Curiosity sẽ được phóng lên nhằm mục Ä‘Ãch tiếp tục nghiên cứu hà nh tinh nà y của cÆ¡ quan NASA.[156] + +ChÆ°Æ¡ng trình MetNet hợp tác giữa Phần Lan-Nga sẽ gá»i má»™t tà u quỹ đạo nhằm nghiên cứu cấu trúc khà quyển, khà tượng hà nh tinh đồng thá»i nó sẽ gá»i má»™t thiết bị nhá» xuống bá» mặt hà nh tinh.[157][158] +Kế hoạch Ä‘Æ°a ngÆ°á»i lên Sao Há»a[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Phi vụ Ä‘Æ°a ngÆ°á»i lên Sao Há»a + +CÆ¡ quan ESA hi vá»ng Ä‘Æ°a ngÆ°á»i đặt chân lên Sao Há»a trong khoảng thá»i gian 2030 và 2035.[159] Quá trình nà y sẽ tiếp bÆ°á»›c sau khi phóng những con tà u lá»›n má»™t cách thà nh công đến hà nh tinh, mà bắt đầu từ tà u ExoMars[160] và phi vụ hợp tác NASA-ESA nhằm gá»i vá» Trái Äất mẫu đất của Sao Há»a.[161] + +Quá trình thám hiểm có con ngÆ°á»i của Hoa Kỳ đã được định ra là má»™t mục tiêu lâu dà i trong chÆ°Æ¡ng trình Viá»…n cảnh thám hiểm không gian công bố năm 2004 bởi Tổng thống George W. Bush.[162] Vá»›i kế hoạch chế tạo tà u Orion nhằm Ä‘Æ°a ngÆ°á»i trở lại Mặt Trăng trong tháºp niên 2020 được coi là má»™t bÆ°á»›c cÆ¡ bản trong quá trình Ä‘Æ°a ngÆ°á»i lên Sao Há»a. Ngà y 28 tháng 9 năm 2007, ngÆ°á»i đứng đầu cÆ¡ quan NASA Michael D. Griffin phát biểu NASA hÆ°á»›ng mục tiêu Ä‘Æ°a ngÆ°á»i lên Sao Há»a và o năm 2037.[163] + +Mars Direct, má»™t chÆ°Æ¡ng trình thám hiểm Há»a Tinh có ngÆ°á»i lái vá»›i chi phà thấp được Ä‘á» xuất bởi Robert Zubrin, sáng láºp viên của Mars Society, sẽ sá» dụng lá»›p tên lá»a sức nâng lá»›n Saturn V, nhÆ° Space X Falcon X, hoặc Ares V, để bá» qua giai Ä‘oạn trên quỹ đạo quanh Trái Äất và nạp nhiên liệu trên Mặt Trăng.[164] + +MARS-500 là má»™t dá»± án hợp tác giữa Nga (Roskosmos, Viện Hà n lâm Khoa há»c Nga), Liên minh châu Âu (ESA) và Trung Quốc[165] mô phá»ng các Ä‘iá»u kiện y-sinh trên Sao Há»a nhằm nghiên cứu khả năng thÃch nghi của con ngÆ°á»i vá»›i hà nh trình dà i trên 500 ngà y-thá»i gian tối thiểu theo tÃnh toán để hoà n thà nh chuyến bay lên hà nh tinh Ä‘á» và quay vá». 3 mô-Ä‘un lắp đặt năm 2006, 2 mô-Ä‘un xây dá»±ng năm 2007 và 2008[166] là nÆ¡i để 6 tình nguyện viên đã sống và là m việc cô láºp trong 520 ngà y.[167] +Thiên văn trên Sao Há»a[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Thiên văn trên Sao Há»a + +Phobos Ä‘i qua Mặt Trá»i, chụp từ robot Opportunity và o ngà y 10 tháng 3, 2004. + +Vá»›i những tà u quỹ đạo, tà u đổ bá»™ và robot tá»± hà nh Ä‘ang hoạt Ä‘á»™ng trên Sao Há»a mà các nhà thiên văn há»c có thể nghiên cứu thiên văn há»c từ bầu trá»i Sao Há»a. Vệ tinh Phobos hiện lên có Ä‘Æ°á»ng kÃnh góc chỉ bằng má»™t phần ba so vá»›i lúc Trăng tròn trên Trái Äất, trong khi đó Deimos hiện lên nhÆ° má»™t ngôi sao, chỉ hÆ¡i sáng hÆ¡n Sao Kim má»™t chút khi nhìn Sao Kim từ Trái Äất.[168] + +CÅ©ng có nhiá»u hiện tượng từng được biết trên Trái Äất mà đã được quan sát trên Sao Há»a, nhÆ° thiên thạch rÆ¡i và cá»±c quang.[148] Sá»± kiện Trái Äất Ä‘i qua Ä‘Ä©a Mặt Trá»i khi quan sát từ Sao Há»a được tiên Ä‘oán sẽ xảy ra và o ngà y 10 tháng 11 năm 2084.[169] TÆ°Æ¡ng tá»±, sá»± kiện Sao Thủy và Sao Kim Ä‘i qua Ä‘Ä©a Mặt Trá»i khi nhìn từ Sao Há»a cÅ©ng được tiên Ä‘oán. Do Ä‘Æ°á»ng kÃnh góc của hai vệ tinh Phobos và Deimos quá nhá» cho nên sẽ chỉ có hiện tượng nháºt thá»±c má»™t phần (hay Ä‘i ngang qua) trên Sao Há»a.[170][171] +Quan sát Sao Há»a[sá»a | sá»a mã nguồn] +Chuyển Ä‘á»™ng nghịch hà nh biểu kiến của Sao Há»a và o năm 2003 khi nhìn từ Trái Äất + +Bởi vì quỹ đạo Sao Há»a có Ä‘á»™ lệch tâm đáng kể cho nên Ä‘á»™ sáng biểu kiến của nó ở vị trà xung đối vá»›i Mặt Trá»i có thể thay đổi trong khoảng −3,0 đến −1,4. Äá»™ sáng nhá» nhất của nó tÆ°Æ¡ng ứng vá»›i cấp sao +1,6 khi hà nh tinh ở vị trà giao há»™i vá»›i Mặt Trá»i.[7] Sao Há»a khi quan sát qua kÃnh thiên văn nhá» thÆ°á»ng hiện lên có mà u và ng, cam hay Ä‘á» nâu; trong khi mà u sắc thá»±c sá»± của Sao Há»a gần vá»›i mà u bÆ¡, và mà u Ä‘á» là do khà quyển Sao Há»a chứa rất nhiá»u bụi; bên dÆ°á»›i là bức ảnh mà robot Spirit chụp được trên Sao Há»a vá»›i mà u nâu-xanh nhạt, mà u bùn vá»›i những tảng đá xám-xanh và cát mà u Ä‘á» nhạt.[172] Khi hà nh tinh hÆ°á»›ng vá» phÃa gần Mặt Trá»i, nó sẽ rất khó quan sát trong má»™t và i tháng bởi ánh sáng mạnh của Mặt Trá»i. Ở những thá»i Ä‘iểm thÃch hợp—khoảng thá»i gian 15 hoặc 17 năm, và luôn luôn là giữa cuối tháng 7 cho đến tháng 9—có thể quan sát những chi tiết trên bá» mặt Sao Há»a qua kÃnh thiên văn nghiệp dÆ°. Tháºm chà đối vá»›i các kÃnh thiên văn Ä‘á»™ phóng đại nhá», vẫn có thể quan sát thấy các chá»m băng ở cá»±c.[173] + +Khi Sao Há»a tiến gần và o vị trà xung đối nó bắt đầu và o giai Ä‘oạn của chuyển Ä‘á»™ng nghịch hà nh biểu kiến khi quan sát từ Trái Äất, có nghÄ©a là nó dÆ°á»ng nhÆ° di chuyển ngược lại thà nh vòng tròn trên ná»n bầu trá»i. Khoảng thá»i gian diá»…n ra chuyển Ä‘á»™ng nghịch hà nh trong khoảng 72 ngà y và Sao Há»a đạt đến Ä‘á»™ sáng biểu kiến cá»±c đại và o giữa giai Ä‘oạn nà y.[174] +Ảnh chụp Mặt Trá»i lặn ở hố va chạm Gusev chụp bởi robot Spirit và o ngà y 19 tháng 5, 2005. +Những lần tiếp cáºn gần nhất[sá»a | sá»a mã nguồn] +Gần tÆ°Æ¡ng đối[sá»a | sá»a mã nguồn] + +Khi Sao Há»a ở gần vị trà xung đối vá»›i Mặt Trá»i thì đây là thá»i Ä‘iểm hà nh tinh nằm gần vá»›i Trái Äất nhất. Giai Ä‘oạn xung đối có thể kéo dà i trong khoảng 8½ ngà y xung quanh thá»i Ä‘iểm hai hà nh tinh nằm gần nhau. Khoảng cách lúc hai hà nh tinh tiếp cáºn gần nhau nhất có thể thay đổi trong khoảng từ 54[175] đến 103 triệu km do quỹ đạo của hai hà nh tinh có hình elip, và do đó cÅ©ng là m thay đổi Ä‘Æ°á»ng kÃnh góc của Sao Há»a khi nhìn từ Trái Äất.[176] Lần xung đối gần đây nhất (2011) diá»…n ra và o ngà y 29 tháng 1 năm 2010. Lần tiếp theo sẽ xảy ra và o ngà y 3 tháng 3 năm 2012 ở khoảng cách khoảng 100 triệu km.[177] Thá»i gian trung bình giữa hai lần xung đối, hay chu kỳ giao há»™i của hà nh tinh, là 780 ngà y nhÆ°ng số ngà y chÃnh xác giữa hai lần xung đối kế tiếp có thể thay đổi từ 764 đến 812 ngà y.[178] + +Khi Há»a Tinh và o thá»i kỳ xung đối nó cÅ©ng bắt đầu và o giai Ä‘oạn chuyển Ä‘á»™ng biểu kiến nghịch hà nh vá»›i thá»i gian khoảng 72 ngà y. +Lần tiếp cáºn gần nhất[sá»a | sá»a mã nguồn] +Vị trà xung đối của hà nh tinh Ä‘á» trong thá»i gian 2003–2018, khi nhìn trên mặt phẳng hoà ng đạo vá»›i Trái Äất ở chÃnh giữa. + +Sao Há»a nằm gần Trái Äất nhất trong vòng khoảng 60.000 năm qua là và o thá»i Ä‘iểm 9:51:13 UT ở khoảng cách 55.758.006 km (0,372719 AU), Ä‘á»™ sáng biểu kiến đạt −2,88. Thá»i Ä‘iểm nà y xảy ra khi Sao Há»a đã và o ở vị trà xung đối được má»™t ngà y và khoảng ba ngà y từ cáºn Ä‘iểm quỹ đạo là m cho Sao Há»a dá»… dà ng nhìn thấy từ Trái Äất. Lần cuối hà nh tinh Ä‘á» nằm gần nhất vá»›i Trái Äất được Æ°á»›c tÃnh đã diá»…n ra và o ngà y 12 tháng 9 năm 57.617 trÆ°á»›c Công nguyên, lần tiếp theo được Æ°á»›c tÃnh diá»…n ra và o năm 2287.[179] Ká»· lục tiếp cáºn gần nhất năm 2003 chỉ hÆ¡i bé hÆ¡n so vá»›i má»™t số lần tiếp cáºn gần nhất trong thá»i gian gần đây. Và dụ, khoảng cách nhá» nhất giữa hai hà nh tinh xảy ra và o ngà y 22 tháng 8 năm 1924 là 0,37285 AU, và và o ngà y 24 tháng 8 năm 2208 sẽ là 0,37279 AU.[115] + +Trong năm 2003, và những năm sau, đã có má»™t trò chÆ¡i khăm phát tán trên internet nói rằng năm 2003 Sao Há»a sẽ nằm gần Trái Äất nhất trong hà ng nghìn năm qua và nó sẽ hiện lên to nhÆ° Mặt Trăng trên bầu trá»i.[180] +Lịch sá» quan sát Sao Há»a[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Lịch sá» quan sát Sao Há»a + +Lịch sá» quan sát Sao Há»a được đánh dấu bởi những lần hà nh tinh nà y ở vị trà xung đối, khi nó nằm gần Trái Äất và vì váºy dá»… dà ng có thể quan sát bằng mắt thÆ°á»ng, và những lần xung đối xảy ra khoảng 2 năm má»™t lần. Những lần xảy ra xung đối nổi báºt hÆ¡n cả trong lịch sỠđó là khoảng thá»i gian cách nhau 15 đến 17 năm khi lần xung đối xảy ra trùng hoặc gần vá»›i cáºn Ä‘iểm quỹ đạo của Há»a Tinh, Ä‘iá»u nà y cà ng là m cho nó dá»… dà ng quan sát được từ Trái Äất. + +Sá»± tồn tại của Sao Há»a nhÆ° má»™t thiên thể Ä‘i lang thang trên bầu trá»i đêm đã được ghi lại bởi những nhà thiên văn há»c Ai Cáºp cổ đại và và o năm 1534 TCN hỠđã nháºn thấy được chuyển Ä‘á»™ng nghịch hà nh biểu kiến của hà nh tinh Ä‘á».[181] Trong lịch sá» của đế chế Babylon lần hai, các nhà thiên văn Babylon đã quan sát má»™t cách có hệ thống và ghi chép thÆ°á»ng xuyên vị trà của các hà nh tinh. Äối vá»›i Sao Há»a, há» biết rằng hà nh tinh nà y thá»±c hiện được 37 chu kỳ giao há»™i, hay Ä‘i được 42 vòng trên vòng hoà ng đạo, trong khoảng 79 năm Trái Äất. Há» cÅ©ng đã phát minh ra phÆ°Æ¡ng pháp số há»c nhằm hiệu chỉnh những Ä‘á»™ lệch nhá» trong việc tiên Ä‘oán vị trà của các hà nh tinh.[182][183] + +Trong thế ká»· thứ tÆ° trÆ°á»›c Công nguyên, Aristoteles đã phát hiện ra Sao Há»a biến mất đằng sau Mặt Trăng trong má»™t lần che khuất, và ông nháºn xét rằng hà nh tinh nà y phải nằm xa hÆ¡n Mặt Trăng.[184] Ptolemaeus, nhà thiên văn Hy Lạp cổ đại ở Alexandria,[185] đã cố gắng giải quyết vấn Ä‘á» chuyển Ä‘á»™ng quỹ đạo của Há»a Tinh. Mô hình của Ptolemaeus và táºp hợp những nghiên cứu của ông vá» thiên văn há»c đã được trình bà y trong bản thảo nhiá»u táºp mang tên Almagest, và nó đã trở thà nh ná»™i dung được phổ biến trong thiên văn há»c phÆ°Æ¡ng Tây trong gần mÆ°á»i bốn thế ká»· sau.[186] Các tÆ° liệu lịch sá» Trung Hoa cổ đại cho thấy Sao Há»a được các nhà thiên văn Trung Hoa cổ đại biết đến không muá»™n hÆ¡n thế ká»· thứ tÆ° trÆ°á»›c Công nguyên.[187] Ở thế ká»· thứ năm, trong tà i liệu ghi chép thiên văn của Ấn Äá»™ mang tên Surya Siddhanta đã ghi lại Æ°á»›c tÃnh Ä‘Æ°á»ng kÃnh Sao Há»a của những nhà thiên văn Ấn Äá»™.[188] + +Trong thế ká»· thứ mÆ°á»i bảy, Tycho Brahe đã Ä‘o thị sai ngà y của Sao Há»a và dữ liệu nà y được Johannes Kepler sá» dụng để tÃnh toán sÆ¡ bá»™ vá» khoảng cách tÆ°Æ¡ng đối đến hà nh tinh Ä‘á».[189] Khi kÃnh thiên văn được phát minh ra và trở lên phổ biến hÆ¡n, thị sai ngà y của Sao Há»a đã được Ä‘o lại cẩn tháºn trong ná»— lá»±c nhằm xác định khoảng cách Trái Äất-Mặt Trá»i. Ná»— lá»±c nà y lần đầu tiên được thá»±c hiện bởi Giovanni Domenico Cassini năm 1672. Những Ä‘o đạc thị sai trong thá»i kỳ nà y đã bị cản trở bởi chất lượng của dụng cụ quan sát.[190] Ngà y 13 tháng 10 năm 1590, sá»± kiện Sao Há»a bị Sao Kim che khuất đã được Michael Maestlin ở Heidelberg ghi nháºn.[191] Năm 1610, Galileo Galilei là ngÆ°á»i đầu tiên đã quan sát Sao Há»a qua má»™t kÃnh thiên văn.[192] NgÆ°á»i đầu tiên cố gắng vẽ ra tấm bản đồ Sao Há»a thể hiện những đặc Ä‘iểm trên bá» mặt của nó là nhà thiên văn há»c ngÆ°á»i Hà Lan Christiaan Huygens.[193] +"Kênh Ä‘Ã o" Sao Há»a[sá»a | sá»a mã nguồn] +Bản đồ Sao Há»a của Giovanni Schiaparelli. +Phác há»a bản đồ Sao Há»a bởi Lowell trÆ°á»›c năm 1914. +Bản đồ Sao Há»a chụp bởi kÃnh thiên văn không gian Hubble khi hà nh tinh ở gần vị trà xung đối năm 1999. + + Bà i chi tiết: Kênh Ä‘Ã o Sao Há»a + +Cho đến thế ká»· 19, Ä‘á»™ phóng đại của các kÃnh thiên văn đã đạt đến mức cần thiết cho việc phân giải các đặc Ä‘iểm trên bá» mặt hà nh tinh Ä‘á». Trong tháng 9 năm 1877, sá»± kiện Sao Há»a tiến đến vị trà xung đối đã được dá»± Ä‘oán xảy ra và o ngà y 5 tháng 9. Nhá» và o sá»± kiện nà y, nhà thiên văn ngÆ°á»i Italia Giovanni Schiaparelli sá» dụng kÃnh thiên văn 22 cm ở Milano nhằm quan sát hà nh tinh nà y để vẽ ra tấm bản đồ chi tiết đầu tiên vá» Sao Há»a mà ông thấy qua ống kÃnh. Trên bản đồ nà y có đánh dấu những đặc Ä‘iểm mà ông gá»i là canali, mặc dù sau đó được chỉ ra là những ảo ảnh quang há»c. Những canali được vẽ là những Ä‘Æ°á»ng thẳng trên bá» mặt Sao Há»a và ông đặt tên của chúng theo tên của những con sông nổi tiếng trên Trái Äất. Trong ngôn ngữ của ông, canali có nghÄ©a là "kênh Ä‘Ã o" hoặc "rãnh", và được dịch má»™t cách hiểu nhầm sang tiếng Anh là "canals" (kênh Ä‘Ã o).[194][195] + +Ảnh hưởng bởi những quan sát nà y, nhà Äông phÆ°Æ¡ng há»c Percival Lowell đã xây dá»±ng má»™t Ä‘Ã i quan sát mà sau nà y mang tên Ä‘Ã i quan sát Lowell vá»›i hai kÃnh thiên văn Ä‘Æ°á»ng kÃnh 300 và 450 mm. Äà i quan sát nà y được sá» dụng để quan sát Sao Há»a trong lần xung đối hiếm có và o năm 1894 và những lần xung đối thông thÆ°á»ng vá» sau. Lowell đã xuất bản má»™t và i cuốn sách vá» Há»a Tinh và đỠcáºp đến sá»± sống trên hà nh tinh nà y, chúng đã có những ảnh hưởng nhất định đối vá»›i công chúng vá» hà nh tinh nà y.[196] Äặc Ä‘iểm canali cÅ©ng đã được má»™t số nhà thiên văn há»c tìm thấy, nhÆ° Henri Joseph Perrotin và Louis Thollon ở Nice, nhá» sá» dụng má»™t trong những kÃnh thiên văn lá»›n nhất thá»i bấy giá».[197][198] + +Sá»± thay đổi theo mùa (bao gồm sá»± thu hẹp diện tÃch của các chá»m băng vùng cá»±c và những miá»n tối hình thà nh trong mùa hè trên Há»a Tinh) kết hợp vá»›i ý niệm vá» kênh Ä‘Ã o đã dẫn đến những phá»ng Ä‘oán vá» sá»± sống trên Sao Há»a, và nhiá»u ngÆ°á»i có niá»m tin lâu dà i rằng Sao Há»a có những vùng biển rá»™ng lá»›n và những cánh đồng bạt ngà n. Tuy nhiên những kÃnh thiên văn thá»i nà y không đủ Ä‘á»™ phân giải đủ lá»›n để chứng minh hay bác bá» những phá»ng Ä‘oán nà y. Khi những kÃnh thiên văn lá»›n hÆ¡n ra Ä‘á»i, những canali thẳng, ngắn hÆ¡n được quan sát rõ hÆ¡n. Khi Camille Flammarion thá»±c hiện quan sát năm 1909 vá»›i kÃnh Ä‘Æ°á»ng kÃnh 840 mm, những địa hình không đồng Ä‘á»u được nháºn ra nhÆ°ng không má»™t đặc Ä‘iểm canali được trông thấy.[199] + +Tháºm chà những bà i báo trong tháºp niên 1960 vá» sinh há»c vÅ© trụ trên Sao Há»a, nhiá»u tác giả đã giải thÃch theo khÃa cạnh sá»± sống cho những đặc Ä‘iểm thay đổi theo mùa trên hà nh tinh nà y. Những kịch bản cụ thể vá» quá trình trao đổi chất và chu trình hóa há»c cho những hệ sinh thái cÅ©ng đã được xuất bản.[200] + +Cho đến khi những tà u vÅ© trụ viếng thăm hà nh tinh nà y trong chÆ°Æ¡ng trình Mariner của NASA trong tháºp niên 1960 thì những bà ẩn nà y má»›i được sáng tá». Những chấp nháºn chung vá» má»™t hà nh tinh đã chết được khẳng định trong thà nghiệm nhằm xác định sá»± sống của tà u Viking và những ảnh chụp tại nÆ¡i nó đổ bá»™.[201] + +Má»™t và i bản đồ vá» Sao Há»a đã được láºp ra nhá» sá» dụng các dữ liệu thu được từ các phi vụ nà y, nhÆ°ng cho đến táºn phi vụ của tà u Mars Global Surveyor, phóng lên và o năm 1996 và ngừng hoạt Ä‘á»™ng năm 2006, đã mang lại những chi tiết đầy đủ nhất vá» bản đồ địa hình, từ trÆ°á»ng và sá»± phân bố khoáng chất trên bá» mặt.[202] Những bản đồ vá» Sao Há»a hiện nay đã được cung cấp trên má»™t số dịch vụ trá»±c tuyến, nhÆ° Google Mars. +Trong văn hóa[sá»a | sá»a mã nguồn] + + Bà i chi tiết: Sao Há»a trong văn hóa + +Sao Há»a trong ngôn ngữ phÆ°Æ¡ng Tây được mang tên của vị thần chiến tranh trong thần thoại. Từ há»a cÅ©ng là tên của má»™t trong năm yếu tố của ngÅ© hà nh trong triết há»c cổ Trung Hoa. Biểu tượng Sao Há»a, gồm má»™t vòng tròn vá»›i má»™t mÅ©i tên chỉ ra ngoà i, cÅ©ng là biểu tượng cho giống Ä‘á»±c. + +à tưởng cho rằng trên Sao Há»a có những sinh váºt có trà thông minh đã xuất hiện từ cuối thế ká»· 19. Quan sát các "canali" (kênh Ä‘Ã o) của Giovanni Schiaparelli kết hợp vá»›i cuốn sách của Percival Lowell vỠý tưởng nà y đã là m cÆ¡ sở cho những bà n luáºn vá» má»™t hà nh tinh Ä‘ang hạn hát, lạnh lẽo, má»™t thế giá»›i chết vá»›i ná»n văn minh trên đó Ä‘ang xây dá»±ng những hệ thống tÆ°á»›i tiêu.[203] + +Nhiá»u quan sát khác và những lá»i tuyên bố bởi những ngÆ°á»i có ảnh hưởng đã là m dấy lên cái gá»i là "CÆ¡n sốt Sao Há»a".[204] Năm 1899, khi Ä‘ang nghiên cứu Ä‘á»™ ồn vô tuyến trong khà quyển bằng cách sá» dụng máy thu ở phòng thà nghiệm Colorado Springs, nhà sáng chế Nikola Tesla đã nháºn ra sá»± lặp lại trong tÃn hiệu mà sau đó ông Ä‘oán có thể là tÃn hiệu liên lạc vô tuyến đến từ má»™t hà nh tinh khác, và khả năng là Sao Há»a. Năm 1901, trong má»™t cuá»™c phá»ng vấn, Tesla nói: + + Ở thá»i Ä‘iểm sau khi có má»™t ý nghÄ© lóe lên trong đầu tôi rằng những nhiá»…u loạn mà tôi đã thu được có thể là do sá»± Ä‘iá»u khiển từ má»™t ná»n văn minh. Mặc dù tôi không thể giải mã ý nghÄ©a của chúng, nhÆ°ng tôi không thể nghÄ© rằng đó chỉ hoà n toà n là sá»± ngẫu nhiên. Cảm giác tăng dần trong tôi rằng lần đầu tiên tôi đã nghe được lá»i chà o từ má»™t hà nh tinh khác.[205] + +à nghÄ© của Tesla nháºn được sá»± ủng há»™ từ Lord Kelvin, ông nà y khi viếng thăm Hoa Kỳ năm 1902, đã nói là ông nghÄ© rằng những tÃn hiệu mà Tesla thu được là do từ hà nh tinh Ä‘á» gá»i đến Hoa Kỳ.[206] Kelvin "nhấn mạnh" từ chối lá»i nói nà y ngay trÆ°á»›c khi ông rá»i Hoa Kỳ: "Cái mà tôi thá»±c sá»± nói rằng những cÆ° dân Sao Há»a, nếu có, sẽ không nghi ngá» khi há» có thể nhìn thấy New York, đặc biệt từ ánh sáng đèn Ä‘iện."[207] + +Trong má»™t bà i viết trên tá» New York Times năm 1901, Edward Charles Pickering, giám đốc Äà i quan sát Harvard College, Ä‘Æ°a tin hỠđã nháºn được má»™t Ä‘iện tÃn từ Äà i quan sát Lowell ở Arizona vá»›i ná»™i dung xác nháºn là dÆ°á»ng nhÆ° ná»n văn minh trên Sao Há»a Ä‘ang cố liên lạc vá»›i Trái Äất.[208] + + Äầu tháng 12 năm 1900, chúng tôi nháºn được bức Ä‘iện tÃn từ Äà i quan sát Lowell ở Arizona rằng má»™t luồng ánh sáng chiếu từ Sao Há»a (Ä‘Ã i quan sát Lowell luôn dà nh sá»± quan tâm đặc biệt đến Sao Há»a) kéo dà i trong khoảng 70 phút. Tôi đã gá»i những thông tin nà y sang châu Âu cÅ©ng nhÆ° bản sao của Ä‘iện tÃn đến khắp nÆ¡i trên đất nÆ°á»›c nà y. Những ngÆ°á»i quan sát đã rất cẩn tháºn, đáng tin và do váºy không có lý do gì để nghi ngá» vá» sá»± tồn tại của tia sáng. NgÆ°á»i ta cho rằng nó bắt nguồn từ má»™t vị trà địa lý nổi tiếng trên Sao Há»a. Tất cả là thế. Bây giá» câu chuyện đã lan ra trên toà n thế giá»›i. Ở châu Âu, ngÆ°á»i ta nói rằng tôi đã liên lạc vá»›i ngÆ°á»i Sao Há»a và đủ má»i thông tin cÆ°á»ng Ä‘iệu đã xuất hiện. Cho dù thứ ánh sáng đó là gì, chúng ta cÅ©ng không biết ý nghÄ©a của nó. Không ai có thể nói được đó là từ má»™t ná»n văn minh hay không phải. Nó tuyệt đối không thể giải thÃch được.[208] + +Pickering sau đó Ä‘á» xuất lắp đặt má»™t loạt tấm gÆ°Æ¡ng ở Texas nhằm thu các tÃn hiệu từ Sao Há»a.[209] + +Trong những tháºp ká»· gần đây, nhá» những tấm bản đồ Ä‘á»™ phân giải cao vá» bá» mặt Sao Há»a, đặc biệt từ tà u Mars Global Surveyor và Mars Reconnaissance Orbiter, cho thấy không há» có má»™t dấu hiệu của sá»± sống có trà tuệ trên hà nh tinh nà y, mặc dù những phá»ng Ä‘oán giả khoa há»c vá» sá»± sống có trà thông minh trên Sao Há»a vẫn xuất hiện từ những biên táºp viên nhÆ° Richard C. Hoagland. Nhá»› lại những tranh luáºn trÆ°á»›c đây vỠđặc Ä‘iểm canali, xuất hiện má»™t số suy Ä‘oán vá» những hình tượng kÃch cỡ nhá» trên má»™t số bức ảnh từ tà u không gian, nhÆ° 'kim tá»± tháp' và 'khuôn mặt trên Sao Há»a'. Nhà thiên văn há»c hà nh tinh Carl Sagan đã viết: + + Sao Há»a đã trở thà nh má»™t sân khấu cho những vở kịch thần thoại mà ở đó chúng ta chiếu lên những hi vá»ng và sợ hãi của chúng ta trên Trái Äất.[195] + +Minh há»a sinh váºt ba chân Há»a Tinh trong tác phẩm ấn bản tiếng Pháp xuất bản năm 1906, The War of the Worlds của nhà văn H.G. Wells. + +Các miêu tả Sao Há»a trong tiểu thuyết đã bị kÃch thÃch bởi mà u đỠđặc trÆ°ng của nó và bởi những suy Ä‘oán mang tÃnh khoa há»c ở thế ká»· 19 vá» các Ä‘iá»u kiện bá» mặt hà nh tinh không những duy trì cho sá»± sống mà còn tồn tại ná»n văn minh trên đó.[210] Äã có nhiá»u những tác phẩm khoa há»c viá»…n tưởng được ra Ä‘á»i, trong số đó có tác phẩm The War of the Worlds của H. G. Wells xuất bản năm 1898, vá»›i ná»™i dung vá» những sinh váºt Sao Há»a Ä‘ang cố gắng thoát khá»i hà nh tinh Ä‘ang chết dần và chúng xuống xâm lược Äịa cầu. Sau đó, ngà y 30 tháng 10 năm 1938, phát thanh viên Orson Welles đã dá»±a và o tác phẩm nà y và gây ra trò đùa trên Ä‘Ã i phát thanh là m cho nhiá»u thÃnh giả thiếu hiểu biết bị hiểu nhầm.[211] + +Những tác phẩm có tÃnh ảnh hưởng bao gồm The Martian Chronicles của Ray Bradbury, trong đó cuá»™c thám hiểm của con ngÆ°á»i đã trở thà nh má»™t tai nạn phá hủy ná»n văn minh Há»a Tinh, Barsoom của Edgar Rice Burroughs, tiểu thuyết Out of the Silent Planet của C. S. Lewis (1938),[212] và má»™t số câu chuyện của Robert A. Heinlein trong những năm 60.[213] + +Tác giả Jonathan Swift đã từng miêu tả vá» các Mặt Trăng của Sao Há»a, khoảng 150 năm trÆ°á»›c khi chúng được nhà thiên văn há»c Asaph Hall phát hiện ra. J.Swift đã miêu tả khá chÃnh xác và chi tiết vá» quỹ đạo của chúng trong chÆ°Æ¡ng 19 của tiểu thuyết Gulliver's Travels.[214] + +Má»™t nhân váºt truyện tranh thể hiện trà thông minh Sao Há»a, Marvin, đã xuất hiện trên truyá»n hình năm 1948 trong bá»™ phim hoạt hình Looney Tunes của hãng Warner Brothers, và nó vẫn còn tiếp tục xuất hiện trong văn hóa đại chúng phÆ°Æ¡ng Tây hiện nay.[215] + +Sau khi các tà u Mariner và Viking gá»i vá» các bức ảnh chụp Há»a Tinh, má»™t thế giá»›i không có sá»± sống và những kênh Ä‘Ã o, thì những quan niệm vá» ná»n văn minh Sao Há»a ngay láºp tức bị từ bá», và thay và o đó là những miêu tả vá» viá»…n cảnh con ngÆ°á»i sẽ đến khai phá hà nh tinh nà y, nổi tiếng nhất có lẽ là tác phẩm bá»™ ba Sao Há»a của Kim Stanley Robinson. Những suy Ä‘oán giả khoa há»c vá» Khuôn mặt trên Sao Há»a và những địa hình bà ẩn khác được chụp bởi các tà u quỹ đạo đã trở thà nh bối cảnh phổ biến cho những tác phẩm khoa há»c viá»…n tưởng, đặc biệt trong phim ảnh.[216] + +Bối cảnh con ngÆ°á»i trên Sao Há»a đấu tranh già nh Ä‘á»™c láºp khá»i Trái Äất cÅ©ng là má»™t ná»™i dung chÃnh trong tiểu thuyết của Greg Bear cÅ©ng nhÆ° bá»™ phim Total Recall (dá»±a trên câu chuyện ngắn của Philip K. Dick) và sê ri truyá»n hình Babylon 5. Má»™t số trò chÆ¡i cÅ©ng sá» dụng bối cảnh nà y, bao gồm Red Faction và Zone of the Enders. Sao Há»a (và vệ tinh của nó) cÅ©ng xuất hiện trong video game nhượng quyá»n thÆ°Æ¡ng mại Doom và Martian Gothic. diff --git a/xpcom/tests/moz.build b/xpcom/tests/moz.build new file mode 100644 index 0000000000..83105e7ddd --- /dev/null +++ b/xpcom/tests/moz.build @@ -0,0 +1,57 @@ +# -*- 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/. + +TEST_DIRS += [ + "gtest", +] + +if CONFIG["OS_ARCH"] == "WINNT": + TEST_DIRS += ["windows"] + +if CONFIG["OS_TARGET"] == "Linux": + CppUnitTests( + [ + "TestMemoryPressureWatcherLinux", + ] + ) + +EXPORTS.testing += [ + "TestHarness.h", +] + +test_progs = [ + "TestArguments", + "TestBlockingProcess", + "TestPRIntN", + "TestQuickReturn", + "TestUnicodeArguments", +] +SimplePrograms(test_progs) + +USE_LIBS += ["mozglue"] + +XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml"] + +if CONFIG["COMPILE_ENVIRONMENT"]: + TEST_HARNESS_FILES.xpcshell.xpcom.tests.unit += [ + "!%s%s" % (f, CONFIG["BIN_SUFFIX"]) for f in test_progs + ] + +XPIDL_MODULE = "xpcomtest" +XPIDL_SOURCES += [ + "NotXPCOMTest.idl", +] + +LOCAL_INCLUDES += [ + "../base", + "../ds", +] + +RESOURCE_FILES += [ + "test.properties", +] + +CRASHTEST_MANIFESTS += ["crashtests/crashtests.list"] diff --git a/xpcom/tests/resources.h b/xpcom/tests/resources.h new file mode 100644 index 0000000000..7ba590e475 --- /dev/null +++ b/xpcom/tests/resources.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef resources_h___ +#define resources_h___ + +#define TIMER_1SECOND 40000 +#define TIMER_5SECOND 40001 +#define TIMER_10SECOND 40002 + +#define TIMER_1REPEAT 40003 +#define TIMER_5REPEAT 40004 +#define TIMER_10REPEAT 40005 + +#define TIMER_CANCEL 40006 +#define TIMER_EXIT 40010 + +#endif /* resources_h___ */ diff --git a/xpcom/tests/test.properties b/xpcom/tests/test.properties new file mode 100644 index 0000000000..19cae97028 --- /dev/null +++ b/xpcom/tests/test.properties @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +1=1 + 2=2 +3 =3 + 4 =4 +5=5 +6= 6 +7=7 +8= 8 +# this is a comment +9=this is the first part of a continued line \ + and here is the 2nd part diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist b/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist new file mode 100644 index 0000000000..8388fa2a55 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>SmallApp</string> + <key>CFBundleIdentifier</key> + <string>com.yourcompany.SmallApp</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>SmallApp</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> +</dict> +</plist> diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp Binary files differnew file mode 100755 index 0000000000..c821003d34 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo b/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo new file mode 100644 index 0000000000..bd04210fb4 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL????
\ No newline at end of file diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 0000000000..5e45963c38 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib new file mode 100644 index 0000000000..59f8803c5d --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib @@ -0,0 +1,343 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.02"> + <data> + <int key="IBDocument.SystemTarget">0</int> + <string key="IBDocument.SystemVersion">9E17</string> + <string key="IBDocument.InterfaceBuilderVersion">644</string> + <string key="IBDocument.AppKitVersion">949.33</string> + <string key="IBDocument.HIToolboxVersion">352.00</string> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="29"/> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="1048"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="1021"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSCustomObject" id="1014"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1050"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSMenu" id="649796088"> + <string key="NSTitle">AMainMenu</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="694149608"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">NewApplication</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <object class="NSCustomResource" key="NSOnImage" id="35465992"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuCheckmark</string> + </object> + <object class="NSCustomResource" key="NSMixedImage" id="591987212"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuMixedState</string> + </object> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="110575045"> + <string key="NSTitle">NewApplication</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="632727374"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Quit NewApplication</string> + <string key="NSKeyEquiv">q</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + </object> + <string key="NSName">_NSAppleMenu</string> + </object> + </object> + <object class="NSMenuItem" id="379814623"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">File</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="952259628"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Edit</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="626404410"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Format</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="586577488"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">View</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="713487014"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Window</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="391199113"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Help</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + </object> + <string key="NSName">_NSMainMenu</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">terminate:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="632727374"/> + </object> + <int key="connectionID">369</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <object class="NSArray" key="object" id="1049"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="children" ref="1048"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1021"/> + <reference key="parent" ref="1049"/> + <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1014"/> + <reference key="parent" ref="1049"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1050"/> + <reference key="parent" ref="1049"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">29</int> + <reference key="object" ref="649796088"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="713487014"/> + <reference ref="694149608"/> + <reference ref="391199113"/> + <reference ref="952259628"/> + <reference ref="379814623"/> + <reference ref="586577488"/> + <reference ref="626404410"/> + </object> + <reference key="parent" ref="1049"/> + <string key="objectName">MainMenu</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">19</int> + <reference key="object" ref="713487014"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">56</int> + <reference key="object" ref="694149608"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="110575045"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">103</int> + <reference key="object" ref="391199113"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + <string key="objectName">1</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">217</int> + <reference key="object" ref="952259628"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">83</int> + <reference key="object" ref="379814623"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">57</int> + <reference key="object" ref="110575045"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="632727374"/> + </object> + <reference key="parent" ref="694149608"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">136</int> + <reference key="object" ref="632727374"/> + <reference key="parent" ref="110575045"/> + <string key="objectName">1111</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">295</int> + <reference key="object" ref="586577488"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">299</int> + <reference key="object" ref="626404410"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-1.IBPluginDependency</string> + <string>-2.IBPluginDependency</string> + <string>-3.IBPluginDependency</string> + <string>103.IBPluginDependency</string> + <string>103.ImportedFromIB2</string> + <string>136.IBPluginDependency</string> + <string>136.ImportedFromIB2</string> + <string>19.IBPluginDependency</string> + <string>19.ImportedFromIB2</string> + <string>217.IBPluginDependency</string> + <string>217.ImportedFromIB2</string> + <string>29.IBEditorWindowLastContentRect</string> + <string>29.IBPluginDependency</string> + <string>29.ImportedFromIB2</string> + <string>29.WindowOrigin</string> + <string>29.editorWindowContentRectSynchronizationRect</string> + <string>295.IBPluginDependency</string> + <string>299.IBPluginDependency</string> + <string>56.IBPluginDependency</string> + <string>56.ImportedFromIB2</string> + <string>57.IBEditorWindowLastContentRect</string> + <string>57.IBPluginDependency</string> + <string>57.ImportedFromIB2</string> + <string>57.editorWindowContentRectSynchronizationRect</string> + <string>83.IBPluginDependency</string> + <string>83.ImportedFromIB2</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <integer value="1" id="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{0, 975}, {478, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{74, 862}</string> + <string>{{6, 978}, {478, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{12, 952}, {218, 23}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{23, 794}, {245, 183}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">374</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"/> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.LastKnownRelativeProjectPath">../SmallApp.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + </data> +</archive> diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 0000000000..bb27d4a5d6 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/xpcom/tests/unit/data/bug121341-2.properties b/xpcom/tests/unit/data/bug121341-2.properties new file mode 100644 index 0000000000..f7885e4fca --- /dev/null +++ b/xpcom/tests/unit/data/bug121341-2.properties @@ -0,0 +1,9 @@ +# this file contains invalid UTF-8 sequence +# no property should be loaded + +1 = test + +# property with invalid UTF-8 sequence (0xa0) +2 = a b + +3 = test2 diff --git a/xpcom/tests/unit/data/bug121341.properties b/xpcom/tests/unit/data/bug121341.properties new file mode 100644 index 0000000000..b45fc9698c --- /dev/null +++ b/xpcom/tests/unit/data/bug121341.properties @@ -0,0 +1,68 @@ +# simple check +1=abc +# test whitespace trimming in key and value + 2 = xy +# test parsing of escaped values +3 = \u1234\t\r\n\uAB\ +\u1\n +# test multiline properties +4 = this is \ +multiline property +5 = this is \ + another multiline property +# property with DOS EOL
+6 = test\u0036
+# test multiline property with with DOS EOL +7 = yet another multi\
+ line propery
+# trimming should not trim escaped whitespaces +8 = \ttest5\u0020 +# another variant of #8 +9 = \ test6\t +# test UTF-8 encoded property/value +10aሴb = cì·¯d +# next property should test unicode escaping at the boundary of parsing buffer +# buffer size is expected to be 4096 so add comments to get to this offset +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +############################################################################### +11 = \uABCD diff --git a/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini new file mode 100644 index 0000000000..46b134b197 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini @@ -0,0 +1 @@ +ÿþ
\ No newline at end of file diff --git a/xpcom/tests/unit/data/iniparser01-utf8BOM.ini b/xpcom/tests/unit/data/iniparser01-utf8BOM.ini new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01-utf8BOM.ini @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/xpcom/tests/unit/data/iniparser01.ini b/xpcom/tests/unit/data/iniparser01.ini new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01.ini diff --git a/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..49cc8ef0e1 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser02-utf8BOM.ini b/xpcom/tests/unit/data/iniparser02-utf8BOM.ini new file mode 100644 index 0000000000..e02abfc9b0 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02-utf8BOM.ini @@ -0,0 +1 @@ + diff --git a/xpcom/tests/unit/data/iniparser02.ini b/xpcom/tests/unit/data/iniparser02.ini new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02.ini @@ -0,0 +1 @@ +
diff --git a/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..05255100a2 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser03-utf8BOM.ini b/xpcom/tests/unit/data/iniparser03-utf8BOM.ini new file mode 100644 index 0000000000..b76e44e194 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03-utf8BOM.ini @@ -0,0 +1 @@ +[] diff --git a/xpcom/tests/unit/data/iniparser03.ini b/xpcom/tests/unit/data/iniparser03.ini new file mode 100644 index 0000000000..60b0742537 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03.ini @@ -0,0 +1 @@ +[]
diff --git a/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..e95d971134 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser04-utf8BOM.ini b/xpcom/tests/unit/data/iniparser04-utf8BOM.ini new file mode 100644 index 0000000000..47ef32c0a9 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04-utf8BOM.ini @@ -0,0 +1 @@ +[section1] diff --git a/xpcom/tests/unit/data/iniparser04.ini b/xpcom/tests/unit/data/iniparser04.ini new file mode 100644 index 0000000000..23a50d155f --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04.ini @@ -0,0 +1 @@ +[section1]
diff --git a/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..a49491816c --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser05-utf8BOM.ini b/xpcom/tests/unit/data/iniparser05-utf8BOM.ini new file mode 100644 index 0000000000..eb33b5ccf1 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05-utf8BOM.ini @@ -0,0 +1 @@ +[section1]junk diff --git a/xpcom/tests/unit/data/iniparser05.ini b/xpcom/tests/unit/data/iniparser05.ini new file mode 100644 index 0000000000..ade1373377 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05.ini @@ -0,0 +1 @@ +[section1]junk
diff --git a/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..e9023ac7c9 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser06-utf8BOM.ini b/xpcom/tests/unit/data/iniparser06-utf8BOM.ini new file mode 100644 index 0000000000..073d841cf3 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] + diff --git a/xpcom/tests/unit/data/iniparser06.ini b/xpcom/tests/unit/data/iniparser06.ini new file mode 100644 index 0000000000..c24821e6ed --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06.ini @@ -0,0 +1,2 @@ +[section1]
+
diff --git a/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..d1c167e6e3 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser07-utf8BOM.ini b/xpcom/tests/unit/data/iniparser07-utf8BOM.ini new file mode 100644 index 0000000000..38176d9444 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1 diff --git a/xpcom/tests/unit/data/iniparser07.ini b/xpcom/tests/unit/data/iniparser07.ini new file mode 100644 index 0000000000..49816873b2 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07.ini @@ -0,0 +1,2 @@ +[section1]
+name1
diff --git a/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..e450566a0f --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser08-utf8BOM.ini b/xpcom/tests/unit/data/iniparser08-utf8BOM.ini new file mode 100644 index 0000000000..5fa7d2495c --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1= diff --git a/xpcom/tests/unit/data/iniparser08.ini b/xpcom/tests/unit/data/iniparser08.ini new file mode 100644 index 0000000000..cfa15c9ffe --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08.ini @@ -0,0 +1,2 @@ +[section1]
+name1=
diff --git a/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..ef1da39e27 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser09-utf8BOM.ini b/xpcom/tests/unit/data/iniparser09-utf8BOM.ini new file mode 100644 index 0000000000..e3edce4d49 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser09.ini b/xpcom/tests/unit/data/iniparser09.ini new file mode 100644 index 0000000000..1c87762ba4 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09.ini @@ -0,0 +1,2 @@ +[section1]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..e5e70b6612 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser10-utf8BOM.ini b/xpcom/tests/unit/data/iniparser10-utf8BOM.ini new file mode 100644 index 0000000000..bda15fcc7b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10-utf8BOM.ini @@ -0,0 +1,3 @@ + +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser10.ini b/xpcom/tests/unit/data/iniparser10.ini new file mode 100644 index 0000000000..037fd79303 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10.ini @@ -0,0 +1,3 @@ +
+[section1]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..932d4004bc --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser11-utf8BOM.ini b/xpcom/tests/unit/data/iniparser11-utf8BOM.ini new file mode 100644 index 0000000000..78caafaba5 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11-utf8BOM.ini @@ -0,0 +1,3 @@ +# comment +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser11.ini b/xpcom/tests/unit/data/iniparser11.ini new file mode 100644 index 0000000000..f8d573a284 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11.ini @@ -0,0 +1,3 @@ +# comment
+[section1]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..8a29127222 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser12-utf8BOM.ini b/xpcom/tests/unit/data/iniparser12-utf8BOM.ini new file mode 100644 index 0000000000..09ca62779d --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12-utf8BOM.ini @@ -0,0 +1,3 @@ +[section1] +# [sectionBAD] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser12.ini b/xpcom/tests/unit/data/iniparser12.ini new file mode 100644 index 0000000000..2727940c09 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12.ini @@ -0,0 +1,3 @@ +[section1]
+# [sectionBAD]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..ebd9a51d3e --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser13-utf8BOM.ini b/xpcom/tests/unit/data/iniparser13-utf8BOM.ini new file mode 100644 index 0000000000..8c9499b669 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13-utf8BOM.ini @@ -0,0 +1,3 @@ +[section1] +name1=value1 +# nameBAD=valueBAD diff --git a/xpcom/tests/unit/data/iniparser13.ini b/xpcom/tests/unit/data/iniparser13.ini new file mode 100644 index 0000000000..21d40b140c --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13.ini @@ -0,0 +1,3 @@ +[section1]
+name1=value1
+# nameBAD=valueBAD
diff --git a/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..bbc3413aa1 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser14-utf8BOM.ini b/xpcom/tests/unit/data/iniparser14-utf8BOM.ini new file mode 100644 index 0000000000..d109052c8d --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14-utf8BOM.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +name2=value2 +[section2] +name1=value1 +name2=foopy diff --git a/xpcom/tests/unit/data/iniparser14.ini b/xpcom/tests/unit/data/iniparser14.ini new file mode 100644 index 0000000000..744af4cb65 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14.ini @@ -0,0 +1,6 @@ +[section1]
+name1=value1
+name2=value2
+[section2]
+name1=value1
+name2=foopy
diff --git a/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..e60525dec6 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser15-utf8BOM.ini b/xpcom/tests/unit/data/iniparser15-utf8BOM.ini new file mode 100644 index 0000000000..172803f90b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15-utf8BOM.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +[section2] +name1=foopy +[section1] +name1=newValue1 diff --git a/xpcom/tests/unit/data/iniparser15.ini b/xpcom/tests/unit/data/iniparser15.ini new file mode 100644 index 0000000000..608a27d8fb --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15.ini @@ -0,0 +1,6 @@ +[section1]
+name1=value1
+[section2]
+name1=foopy
+[section1]
+name1=newValue1
diff --git a/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini Binary files differnew file mode 100644 index 0000000000..142b175902 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser16-utf8BOM.ini b/xpcom/tests/unit/data/iniparser16-utf8BOM.ini new file mode 100644 index 0000000000..bba1018dab --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16-utf8BOM.ini @@ -0,0 +1,13 @@ +#á¿»á¹Ò³Ï–·Ì˄ȡǨŅ©& +[☺♫] +#ѼΏá¹Ò³Ï– +♫=☻ +#·Ì˄ȡǨŅ© +♪=♥ +#‽ἧᵿΏá¹Ò³ +#ϖ·Ì˄ȡǨŅ©& +[☼] +♣=â™ +♦=♥ +#‽ἧᵿΏá¹Ò³ +#·Ì˄ȡǨŅ© diff --git a/xpcom/tests/unit/data/iniparser16.ini b/xpcom/tests/unit/data/iniparser16.ini new file mode 100644 index 0000000000..b94607d15d --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16.ini @@ -0,0 +1,13 @@ +#á¿»á¹Ò³Ï–·Ì˄ȡǨŅ©& +[☺♫] +#ѼΏá¹Ò³Ï– +♫=☻ +#·Ì˄ȡǨŅ© +♪=♥ +#‽ἧᵿΏá¹Ò³ +#ϖ·Ì˄ȡǨŅ©& +[☼] +♣=â™ +♦=♥ +#‽ἧᵿΏá¹Ò³ +#·Ì˄ȡǨŅ© diff --git a/xpcom/tests/unit/data/iniparser17.ini b/xpcom/tests/unit/data/iniparser17.ini new file mode 100644 index 0000000000..bc4815b8c7 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser17.ini @@ -0,0 +1,7 @@ +[section] +key= + +[] + +[empty] +=foo diff --git a/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict b/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict diff --git a/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo b/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo new file mode 100644 index 0000000000..b0bc8e0761 --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo @@ -0,0 +1 @@ +????????
\ No newline at end of file diff --git a/xpcom/tests/unit/data/presentation.key/index.apxl.gz b/xpcom/tests/unit/data/presentation.key/index.apxl.gz Binary files differnew file mode 100644 index 0000000000..26178d809e --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/index.apxl.gz diff --git a/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff Binary files differnew file mode 100644 index 0000000000..8b49316b4d --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff diff --git a/xpcom/tests/unit/data/process_directive.manifest b/xpcom/tests/unit/data/process_directive.manifest new file mode 100644 index 0000000000..3deb2ec444 --- /dev/null +++ b/xpcom/tests/unit/data/process_directive.manifest @@ -0,0 +1,2 @@ +category directives-test main-process @mozilla.org/supports-cstring;1 process=main +category directives-test content-process @mozilla.org/supports-cstring;1 process=content diff --git a/xpcom/tests/unit/head_xpcom.js b/xpcom/tests/unit/head_xpcom.js new file mode 100644 index 0000000000..e2ae79cb12 --- /dev/null +++ b/xpcom/tests/unit/head_xpcom.js @@ -0,0 +1,21 @@ +let CC = Components.Constructor; + +function get_test_program(prog) { + var progPath = do_get_cwd(); + progPath.append(prog); + progPath.leafName = progPath.leafName + mozinfo.bin_suffix; + return progPath; +} + +function set_process_running_environment() { + // Importing Services here messes up appInfo for some of the tests. + // eslint-disable-next-line mozilla/use-services + var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService( + Ci.nsIProperties + ); + var greBinDir = dirSvc.get("GreBinD", Ci.nsIFile); + Services.env.set("DYLD_LIBRARY_PATH", greBinDir.path); + // For Linux + Services.env.set("LD_LIBRARY_PATH", greBinDir.path); + // XXX: handle windows +} diff --git a/xpcom/tests/unit/test_bug121341.js b/xpcom/tests/unit/test_bug121341.js new file mode 100644 index 0000000000..2e66970092 --- /dev/null +++ b/xpcom/tests/unit/test_bug121341.js @@ -0,0 +1,64 @@ +const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function run_test() { + var dataFile = do_get_file("data/bug121341.properties"); + var channel = NetUtil.newChannel({ + uri: Services.io.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true, + }); + var inp = channel.open(); + + var properties = Cu.createPersistentProperties(); + properties.load(inp); + + var value; + + value = properties.getStringProperty("1"); + Assert.equal(value, "abc"); + + value = properties.getStringProperty("2"); + Assert.equal(value, "xy"); + + value = properties.getStringProperty("3"); + Assert.equal(value, "\u1234\t\r\n\u00AB\u0001\n"); + + value = properties.getStringProperty("4"); + Assert.equal(value, "this is multiline property"); + + value = properties.getStringProperty("5"); + Assert.equal(value, "this is another multiline property"); + + value = properties.getStringProperty("6"); + Assert.equal(value, "test\u0036"); + + value = properties.getStringProperty("7"); + Assert.equal(value, "yet another multiline propery"); + + value = properties.getStringProperty("8"); + Assert.equal(value, "\ttest5\u0020"); + + value = properties.getStringProperty("9"); + Assert.equal(value, " test6\t"); + + value = properties.getStringProperty("10a\u1234b"); + Assert.equal(value, "c\uCDEFd"); + + value = properties.getStringProperty("11"); + Assert.equal(value, "\uABCD"); + + dataFile = do_get_file("data/bug121341-2.properties"); + + var channel2 = NetUtil.newChannel({ + uri: Services.io.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true, + }); + inp = channel2.open(); + + var properties2 = Cu.createPersistentProperties(); + try { + properties2.load(inp); + do_throw("load() didn't fail"); + } catch (e) {} +} diff --git a/xpcom/tests/unit/test_bug1434856.js b/xpcom/tests/unit/test_bug1434856.js new file mode 100644 index 0000000000..a8dfa08079 --- /dev/null +++ b/xpcom/tests/unit/test_bug1434856.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + let complete = false; + + let runnable = { + internalQI: ChromeUtils.generateQI(["nsIRunnable"]), + // eslint-disable-next-line mozilla/use-chromeutils-generateqi + QueryInterface(iid) { + // Attempt to schedule another runnable. This simulates a GC/CC + // being scheduled while executing the JS QI. + Services.tm.dispatchToMainThread(() => false); + return this.internalQI(iid); + }, + + run() { + complete = true; + }, + }; + + Services.tm.dispatchToMainThread(runnable); + Services.tm.spinEventLoopUntil( + "Test(test_bug1434856.js:run_test)", + () => complete + ); +} diff --git a/xpcom/tests/unit/test_bug325418.js b/xpcom/tests/unit/test_bug325418.js new file mode 100644 index 0000000000..5840aacf74 --- /dev/null +++ b/xpcom/tests/unit/test_bug325418.js @@ -0,0 +1,72 @@ +// 5 seconds. +const kExpectedDelay1 = 5; +// 1 second. +const kExpectedDelay2 = 1; + +var gStartTime1; +var gStartTime2; +var timer; + +var observer1 = { + observe: function observeTC1(subject, topic, data) { + if (topic == "timer-callback") { + // Stop timer, so it doesn't repeat (if test runs slowly). + timer.cancel(); + + // Actual delay may not be exact, so convert to seconds and round. + Assert.equal( + Math.round((Date.now() - gStartTime1) / 1000), + kExpectedDelay1 + ); + + timer = null; + + info( + "1st timer triggered (before being cancelled). Should not have happened!" + ); + Assert.ok(false); + } + }, +}; + +var observer2 = { + observe: function observeTC2(subject, topic, data) { + if (topic == "timer-callback") { + // Stop timer, so it doesn't repeat (if test runs slowly). + timer.cancel(); + + // Actual delay may not be exact, so convert to seconds and round. + Assert.equal( + Math.round((Date.now() - gStartTime2) / 1000), + kExpectedDelay2 + ); + + timer = null; + + do_test_finished(); + } + }, +}; + +function run_test() { + do_test_pending(); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + + // Initialize the timer (with some delay), then cancel it. + gStartTime1 = Date.now(); + timer.init( + observer1, + kExpectedDelay1 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP + ); + timer.cancel(); + + // Re-initialize the timer (with a different delay). + gStartTime2 = Date.now(); + timer.init( + observer2, + kExpectedDelay2 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP + ); +} diff --git a/xpcom/tests/unit/test_bug332389.js b/xpcom/tests/unit/test_bug332389.js new file mode 100644 index 0000000000..605772f12a --- /dev/null +++ b/xpcom/tests/unit/test_bug332389.js @@ -0,0 +1,14 @@ +function run_test() { + var f = Services.dirsvc.get("CurProcD", Ci.nsIFile); + + var terminated = false; + for (var i = 0; i < 100; i++) { + if (f == null) { + terminated = true; + break; + } + f = f.parent; + } + + Assert.ok(terminated); +} diff --git a/xpcom/tests/unit/test_bug333505.js b/xpcom/tests/unit/test_bug333505.js new file mode 100644 index 0000000000..97016519da --- /dev/null +++ b/xpcom/tests/unit/test_bug333505.js @@ -0,0 +1,10 @@ +function run_test() { + var dirEntries = do_get_cwd().directoryEntries; + + while (dirEntries.hasMoreElements()) { + dirEntries.getNext(); + } + + // We ensure there is no crash + dirEntries.hasMoreElements(); +} diff --git a/xpcom/tests/unit/test_bug364285-1.js b/xpcom/tests/unit/test_bug364285-1.js new file mode 100644 index 0000000000..167e94e852 --- /dev/null +++ b/xpcom/tests/unit/test_bug364285-1.js @@ -0,0 +1,43 @@ +var nameArray = [ + "ascii", // ASCII + "fran\u00E7ais", // Latin-1 + "\u0420\u0443\u0441\u0441\u043A\u0438\u0439", // Cyrillic + "\u65E5\u672C\u8A9E", // Japanese + "\u4E2D\u6587", // Chinese + "\uD55C\uAD6D\uC5B4", // Korean + "\uD801\uDC0F\uD801\uDC2D\uD801\uDC3B\uD801\uDC2B", // Deseret +]; + +function getTempDir() { + return Services.dirsvc.get("TmpD", Ci.nsIFile); +} + +function create_file(fileName) { + var outFile = getTempDir(); + outFile.append(fileName); + outFile.createUnique(outFile.NORMAL_FILE_TYPE, 0o600); + + var stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + stream.init(outFile, 0x02 | 0x08 | 0x20, 0o600, 0); + stream.write("foo", 3); + stream.close(); + + Assert.equal(outFile.leafName.substr(0, fileName.length), fileName); + + return outFile; +} + +function test_create(fileName) { + var file1 = create_file(fileName); + var file2 = create_file(fileName); + file1.remove(false); + file2.remove(false); +} + +function run_test() { + for (var i = 0; i < nameArray.length; ++i) { + test_create(nameArray[i]); + } +} diff --git a/xpcom/tests/unit/test_bug374754.js b/xpcom/tests/unit/test_bug374754.js new file mode 100644 index 0000000000..0d20d90b2c --- /dev/null +++ b/xpcom/tests/unit/test_bug374754.js @@ -0,0 +1,65 @@ +var addedTopic = "xpcom-category-entry-added"; +var removedTopic = "xpcom-category-entry-removed"; +var testCategory = "bug-test-category"; +var testEntry = "@mozilla.org/bug-test-entry;1"; + +var testValue = "check validity"; +var result = ""; +var expected = "add remove add remove "; +var timer; + +var observer = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + observe(subject, topic, data) { + if (topic == "timer-callback") { + Assert.equal(result, expected); + + Services.obs.removeObserver(this, addedTopic); + Services.obs.removeObserver(this, removedTopic); + + do_test_finished(); + + timer = null; + } + + if ( + subject.QueryInterface(Ci.nsISupportsCString).data != testEntry || + data != testCategory + ) { + return; + } + + if (topic == addedTopic) { + result += "add "; + } else if (topic == removedTopic) { + result += "remove "; + } + }, +}; + +function run_test() { + do_test_pending(); + + Services.obs.addObserver(observer, addedTopic); + Services.obs.addObserver(observer, removedTopic); + + Services.catMan.addCategoryEntry( + testCategory, + testEntry, + testValue, + false, + true + ); + Services.catMan.addCategoryEntry( + testCategory, + testEntry, + testValue, + false, + true + ); + Services.catMan.deleteCategoryEntry(testCategory, testEntry, false); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init(observer, 0, timer.TYPE_ONE_SHOT); +} diff --git a/xpcom/tests/unit/test_bug476919.js b/xpcom/tests/unit/test_bug476919.js new file mode 100644 index 0000000000..5ba64758c7 --- /dev/null +++ b/xpcom/tests/unit/test_bug476919.js @@ -0,0 +1,25 @@ +/* global __LOCATION__ */ + +function run_test() { + var testDir = __LOCATION__.parent; + // create a test file, then symlink it, then check that we think it's a symlink + var targetFile = testDir.clone(); + targetFile.append("target.txt"); + if (!targetFile.exists()) { + targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + } + + var link = testDir.clone(); + link.append("link"); + if (link.exists()) { + link.remove(false); + } + + var ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + ln.initWithPath("/bin/ln"); + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(ln); + var args = ["-s", targetFile.path, link.path]; + process.run(true, args, args.length); + Assert.ok(link.isSymlink()); +} diff --git a/xpcom/tests/unit/test_bug478086.js b/xpcom/tests/unit/test_bug478086.js new file mode 100644 index 0000000000..6debb58fbc --- /dev/null +++ b/xpcom/tests/unit/test_bug478086.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + var nsIFile = Ci.nsIFile; + var root = Cc["@mozilla.org/file/local;1"].createInstance(nsIFile); + + // copied from http://mxr.mozilla.org/mozilla-central/source/image/test/unit/test_imgtools.js#135 + // nsIXULRuntime.OS doesn't seem to be available in xpcshell, so we'll use + // this as a kludgy way to figure out if we're running on Windows. + if (mozinfo.os == "win") { + root.initWithPath("\\\\."); + } else { + return; // XXX disabled, since this causes intermittent failures on Mac (bug 481369). + // root.initWithPath("/"); + } + var drives = root.directoryEntries; + Assert.ok(drives.hasMoreElements()); + while (drives.hasMoreElements()) { + var newPath = drives.nextFile.path; + Assert.equal(newPath.indexOf("\0"), -1); + } +} diff --git a/xpcom/tests/unit/test_bug745466.js b/xpcom/tests/unit/test_bug745466.js new file mode 100644 index 0000000000..a655bf45b4 --- /dev/null +++ b/xpcom/tests/unit/test_bug745466.js @@ -0,0 +1,7 @@ +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +function run_test() { + Assert.ok(FileUtils.File("~").equals(FileUtils.getDir("Home", []))); +} diff --git a/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js b/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js new file mode 100644 index 0000000000..1ca97ed6ec --- /dev/null +++ b/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js @@ -0,0 +1,265 @@ +let lastMessage; +const consoleListener = { + observe(message) { + dump(" >> new message: " + message.errorMessage + "\n"); + lastMessage = message; + }, +}; +Services.console.registerListener(consoleListener); + +// The Console Service notifies its listener after one event loop cycle. +// So wait for one tick after each action dispatching a message/error to the service. +function waitForATick() { + return new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); +} + +add_task(async function customScriptError() { + const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance( + Ci.nsIScriptError + ); + scriptError.init( + "foo", + "file.js", + null, + 1, + 2, + Ci.nsIScriptError.warningFlag, + "some javascript" + ); + Services.console.logMessage(scriptError); + + await waitForATick(); + + Assert.equal( + lastMessage, + scriptError, + "We receive the exact same nsIScriptError object" + ); + + Assert.equal(lastMessage.errorMessage, "foo"); + Assert.equal(lastMessage.sourceName, "file.js"); + Assert.equal(lastMessage.lineNumber, 1); + Assert.equal(lastMessage.columnNumber, 2); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.warningFlag); + Assert.equal(lastMessage.category, "some javascript"); + + Assert.equal( + lastMessage.stack, + undefined, + "Custom nsIScriptError object created from JS can't convey any stack" + ); +}); + +add_task(async function callFunctionAndLogExceptionWithChromeGlobal() { + try { + Services.console.callFunctionAndLogException(globalThis, function () { + throw new Error("custom exception"); + }); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.equal(lastMessage.errorMessage, "Error: custom exception"); + Assert.equal(lastMessage.sourceName, _TEST_FILE); + Assert.equal(lastMessage.lineNumber, 56); + Assert.equal(lastMessage.columnNumber, 13); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); + Assert.equal(lastMessage.category, "chrome javascript"); + Assert.ok(lastMessage.stack, "It has a stack"); + Assert.equal(lastMessage.stack.source, _TEST_FILE); + Assert.equal(lastMessage.stack.line, 56); + Assert.equal(lastMessage.stack.column, 13); + Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); + Assert.equal( + lastMessage.innerWindowID, + 0, + "The message isn't bound to any WindowGlobal" + ); +}); + +add_task(async function callFunctionAndLogExceptionWithContentGlobal() { + const window = createContentWindow(); + try { + Services.console.callFunctionAndLogException(window, function () { + throw new Error("another custom exception"); + }); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.equal(lastMessage.errorMessage, "Error: another custom exception"); + Assert.equal(lastMessage.sourceName, _TEST_FILE); + Assert.equal(lastMessage.lineNumber, 97); + Assert.equal(lastMessage.columnNumber, 13); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); + Assert.equal(lastMessage.category, "content javascript"); + Assert.ok(lastMessage.stack, "It has a stack"); + Assert.equal(lastMessage.stack.source, _TEST_FILE); + Assert.equal(lastMessage.stack.line, 97); + Assert.equal(lastMessage.stack.column, 13); + Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); + Assert.ok( + !!window.windowGlobalChild.innerWindowId, + "The window has a innerWindowId" + ); + Assert.equal( + lastMessage.innerWindowID, + window.windowGlobalChild.innerWindowId, + "The message is bound to the content window" + ); +}); + +add_task(async function callFunctionAndLogExceptionForContentScriptSandboxes() { + const { sandbox, window } = createContentScriptSandbox(); + Cu.evalInSandbox( + `function foo() { throw new Error("sandbox exception"); }`, + sandbox, + null, + "sandbox-file.js", + 1, + 0 + ); + try { + Services.console.callFunctionAndLogException(window, sandbox.foo); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes. + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.equal(lastMessage.errorMessage, "Error: sandbox exception"); + Assert.equal(lastMessage.sourceName, "sandbox-file.js"); + Assert.equal(lastMessage.lineNumber, 1); + Assert.equal(lastMessage.columnNumber, 24); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); + Assert.equal(lastMessage.category, "content javascript"); + Assert.ok(lastMessage.stack, "It has a stack"); + Assert.equal(lastMessage.stack.source, "sandbox-file.js"); + Assert.equal(lastMessage.stack.line, 1); + Assert.equal(lastMessage.stack.column, 24); + Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); + Assert.ok( + !!window.windowGlobalChild.innerWindowId, + "The sandbox's prototype is a window and has a innerWindowId" + ); + Assert.equal( + lastMessage.innerWindowID, + window.windowGlobalChild.innerWindowId, + "The message is bound to the sandbox's prototype WindowGlobal" + ); +}); + +add_task( + async function callFunctionAndLogExceptionForContentScriptSandboxesWrappedInChrome() { + const { sandbox, window } = createContentScriptSandbox(); + Cu.evalInSandbox( + `function foo() { throw new Error("sandbox exception"); }`, + sandbox, + null, + "sandbox-file.js", + 1, + 0 + ); + try { + Services.console.callFunctionAndLogException(window, function () { + sandbox.foo(); + }); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes. + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.ok( + !!window.windowGlobalChild.innerWindowId, + "The sandbox's prototype is a window and has a innerWindowId" + ); + Assert.equal( + lastMessage.innerWindowID, + window.windowGlobalChild.innerWindowId, + "The message is bound to the sandbox's prototype WindowGlobal" + ); + } +); + +add_task(function teardown() { + Services.console.unregisterListener(consoleListener); +}); + +// We are in xpcshell, so we can't have a real DOM Window as in Firefox +// but let's try to have a fake one. +function createContentWindow() { + const principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "http://example.com/" + ); + + const webnav = Services.appShell.createWindowlessBrowser(false); + + webnav.docShell.createAboutBlankDocumentViewer(principal, principal); + + return webnav.document.defaultView; +} + +// Create a Sandbox as in WebExtension content scripts +function createContentScriptSandbox() { + const window = createContentWindow(); + // The sandboxPrototype is the key here in order to + // make xpc::SandboxWindowOrNull ignore the sandbox + // and instead retrieve its prototype and link the error message + // to the window instead of the sandbox. + return { + sandbox: Cu.Sandbox(window, { sandboxPrototype: window }), + window, + }; +} diff --git a/xpcom/tests/unit/test_debugger_malloc_size_of.js b/xpcom/tests/unit/test_debugger_malloc_size_of.js new file mode 100644 index 0000000000..3141d8c2c3 --- /dev/null +++ b/xpcom/tests/unit/test_debugger_malloc_size_of.js @@ -0,0 +1,32 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 is just a sanity test that Gecko is giving SpiderMonkey a MallocSizeOf +// function for new JSRuntimes. There is more extensive testing around the +// expected byte sizes within SpiderMonkey's jit-tests, we just want to make +// sure that Gecko is providing SpiderMonkey with the callback it needs. + +const { byteSize } = Cu.getJSTestingFunctions(); + +function run_test() { + const objects = [ + {}, + { w: 1, x: 2, y: 3, z: 4, a: 5 }, + [], + Array(10).fill(null), + new RegExp("(2|two) problems", "g"), + new Date(), + new Uint8Array(64), + Promise.resolve(1), + function f() {}, + Object, + ]; + + for (let obj of objects) { + info(uneval(obj)); + ok(byteSize(obj), "We should get some (non-zero) byte size"); + } +} diff --git a/xpcom/tests/unit/test_file_createUnique.js b/xpcom/tests/unit/test_file_createUnique.js new file mode 100644 index 0000000000..510002bda2 --- /dev/null +++ b/xpcom/tests/unit/test_file_createUnique.js @@ -0,0 +1,29 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +function run_test() { + // Generate a leaf name that is 255 characters long. + var longLeafName = new Array(256).join("T"); + + // Generate the path for a file located in a directory with a long name. + var tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile); + tempFile.append(longLeafName); + tempFile.append("test.txt"); + + try { + tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + do_throw("Creating an item in a folder with a very long name should throw"); + } catch (e) { + if ( + !( + e instanceof Ci.nsIException && + e.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH + ) + ) { + throw e; + } + // We expect the function not to crash but to raise this exception. + } +} diff --git a/xpcom/tests/unit/test_file_equality.js b/xpcom/tests/unit/test_file_equality.js new file mode 100644 index 0000000000..74ea8046d8 --- /dev/null +++ b/xpcom/tests/unit/test_file_equality.js @@ -0,0 +1,37 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var LocalFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); + +function run_test() { + test_normalized_vs_non_normalized(); +} + +function test_normalized_vs_non_normalized() { + // get a directory that exists on all platforms + var tmp1 = Services.dirsvc.get("TmpD", Ci.nsIFile); + var exists = tmp1.exists(); + Assert.ok(exists); + if (!exists) { + return; + } + + // the test logic below assumes we're starting with a normalized path, but the + // default location on macos is a symbolic link, so resolve it before starting + tmp1.normalize(); + + // this has the same exact path as tmp1, it should equal tmp1 + var tmp2 = new LocalFile(tmp1.path); + Assert.ok(tmp1.equals(tmp2)); + + // this is a non-normalized version of tmp1, it should not equal tmp1 + tmp2.appendRelativePath("."); + Assert.ok(!tmp1.equals(tmp2)); + + // normalize and make sure they are equivalent again + tmp2.normalize(); + Assert.ok(tmp1.equals(tmp2)); +} diff --git a/xpcom/tests/unit/test_file_renameTo.js b/xpcom/tests/unit/test_file_renameTo.js new file mode 100644 index 0000000000..a6e8633773 --- /dev/null +++ b/xpcom/tests/unit/test_file_renameTo.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +function run_test() { + // Create the base directory. + let base = Services.dirsvc.get("TmpD", Ci.nsIFile); + base.append("renameTesting"); + if (base.exists()) { + base.remove(true); + } + base.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0777", 8)); + + // Create a sub directory under the base. + let subdir = base.clone(); + subdir.append("subdir"); + subdir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0777", 8)); + + // Create a file under the sub directory. + let tempFile = subdir.clone(); + tempFile.append("file0.txt"); + tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0777", 8)); + + // Test renameTo in the base directory + tempFile.renameTo(null, "file1.txt"); + Assert.ok(exists(subdir, "file1.txt")); + + // Test moving across directories + tempFile = subdir.clone(); + tempFile.append("file1.txt"); + tempFile.renameTo(base, ""); + Assert.ok(exists(base, "file1.txt")); + + // Test moving across directories and renaming at the same time + tempFile = base.clone(); + tempFile.append("file1.txt"); + tempFile.renameTo(subdir, "file2.txt"); + Assert.ok(exists(subdir, "file2.txt")); + + // Test moving a directory + subdir.renameTo(base, "renamed"); + Assert.ok(exists(base, "renamed")); + let renamed = base.clone(); + renamed.append("renamed"); + Assert.ok(exists(renamed, "file2.txt")); + + base.remove(true); +} + +function exists(parent, filename) { + let file = parent.clone(); + file.append(filename); + return file.exists(); +} diff --git a/xpcom/tests/unit/test_getTimers.js b/xpcom/tests/unit/test_getTimers.js new file mode 100644 index 0000000000..58a048a4bc --- /dev/null +++ b/xpcom/tests/unit/test_getTimers.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const timerManager = Cc["@mozilla.org/timer-manager;1"].getService( + Ci.nsITimerManager +); + +function newTimer(name, delay, type) { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback( + { + QueryInterface: ChromeUtils.generateQI(["nsITimerCallback", "nsINamed"]), + name, + notify: () => {}, + }, + delay, + type + ); + return timer; +} + +function getTimers() { + return timerManager.getTimers().filter(t => { + if (t.name == "BackgroundHangThread_timer") { + // BHR is Nightly-only, so just ignore it. + return false; + } + + if (AppConstants.platform == "win" && t.name == "nsAnonTempFileRemover") { + // On Windows there's a 3min timer added at startup to then add an + // idle observer that finally triggers removing leftover temp files. + // Ignore that too. + return false; + } + + return true; + }); +} + +function run_test() { + { + let timers = getTimers(); + for (let timer of timers) { + // Print info about unexpected startup timers to help debugging. + info(`${timer.name}: ${timer.delay}ms, ${timer.type}`); + } + Assert.equal( + timers.length, + 0, + "there should be no timer at xpcshell startup" + ); + } + + let timerData = [ + ["t1", 500, Ci.nsITimer.TYPE_ONE_SHOT], + ["t2", 1500, Ci.nsITimer.TYPE_REPEATING_SLACK], + ["t3", 2500, Ci.nsITimer.TYPE_REPEATING_PRECISE], + ["t4", 3500, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP], + ["t5", 5500, Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY], + ["t6", 7500, Ci.nsITimer.TYPE_ONE_SHOT_LOW_PRIORITY], + ]; + + info("Add timers one at a time."); + for (let [name, delay, type] of timerData) { + let timer = newTimer(name, delay, type); + let timers = getTimers(); + Assert.equal(timers.length, 1, "there should be only one timer"); + Assert.equal(name, timers[0].name, "the name is correct"); + Assert.equal(delay, timers[0].delay, "the delay is correct"); + Assert.equal(type, timers[0].type, "the type is correct"); + + timer.cancel(); + Assert.equal(getTimers().length, 0, "no timer left after cancelling"); + } + + info("Add all timers at once."); + let timers = []; + for (let [name, delay, type] of timerData) { + timers.push(newTimer(name, delay, type)); + } + while (timers.length) { + Assert.equal(getTimers().length, timers.length, "correct timer count"); + timers.pop().cancel(); + } + Assert.equal(getTimers().length, 0, "no timer left after cancelling"); +} diff --git a/xpcom/tests/unit/test_hidden_files.js b/xpcom/tests/unit/test_hidden_files.js new file mode 100644 index 0000000000..27d87e6f54 --- /dev/null +++ b/xpcom/tests/unit/test_hidden_files.js @@ -0,0 +1,24 @@ +const NS_OS_TEMP_DIR = "TmpD"; + +var hiddenUnixFile; +function createUNIXHiddenFile() { + var tmpDir = Services.dirsvc.get(NS_OS_TEMP_DIR, Ci.nsIFile); + hiddenUnixFile = tmpDir.clone(); + hiddenUnixFile.append(".foo"); + // we don't care if this already exists because we don't care + // about the file's contents (just the name) + if (!hiddenUnixFile.exists()) { + hiddenUnixFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + } + return hiddenUnixFile.exists(); +} + +function run_test() { + // Skip this test on Windows + if (mozinfo.os == "win") { + return; + } + + Assert.ok(createUNIXHiddenFile()); + Assert.ok(hiddenUnixFile.isHidden()); +} diff --git a/xpcom/tests/unit/test_home.js b/xpcom/tests/unit/test_home.js new file mode 100644 index 0000000000..e3a4af9796 --- /dev/null +++ b/xpcom/tests/unit/test_home.js @@ -0,0 +1,18 @@ +const CWD = do_get_cwd(); +function checkOS(os) { + const nsILocalFile_ = "nsILocalFile" + os; + return nsILocalFile_ in Ci && CWD instanceof Ci[nsILocalFile_]; +} + +const isWin = checkOS("Win"); + +function run_test() { + var envVar = isWin ? "USERPROFILE" : "HOME"; + + var homeDir = Services.dirsvc.get("Home", Ci.nsIFile); + + var expected = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + expected.initWithPath(Services.env.get(envVar)); + + Assert.equal(homeDir.path, expected.path); +} diff --git a/xpcom/tests/unit/test_iniParser.js b/xpcom/tests/unit/test_iniParser.js new file mode 100644 index 0000000000..a586c4c060 --- /dev/null +++ b/xpcom/tests/unit/test_iniParser.js @@ -0,0 +1,476 @@ +var testnum = 0; +var factory; + +function parserForFile(filename) { + let parser = null; + try { + let file = do_get_file(filename); + Assert.ok(!!file); + parser = factory.createINIParser(file); + Assert.ok(!!parser); + } catch (e) { + dump("INFO | caught error: " + e); + // checkParserOutput will handle a null parser when it's expected. + } + return parser; +} + +function checkParserOutput(parser, expected) { + // If the expected output is null, we expect the parser to have + // failed (and vice-versa). + if (!parser || !expected) { + Assert.equal(parser, null); + Assert.equal(expected, null); + return; + } + + let output = getParserOutput(parser); + for (let section in expected) { + Assert.ok(section in output); + for (let key in expected[section]) { + Assert.ok(key in output[section]); + Assert.equal(output[section][key], expected[section][key]); + delete output[section][key]; + } + for (let key in output[section]) { + Assert.equal(key, "wasn't expecting this key!"); + } + delete output[section]; + } + for (let section in output) { + Assert.equal(section, "wasn't expecting this section!"); + } +} + +function getParserOutput(parser) { + let output = {}; + + for (let section of parser.getSections()) { + Assert.equal(false, section in output); // catch dupes + output[section] = {}; + + for (let key of parser.getKeys(section)) { + Assert.equal(false, key in output[section]); // catch dupes + let value = parser.getString(section, key); + output[section][key] = value; + } + } + return output; +} + +function run_test() { + try { + var testdata = [ + { filename: "data/iniparser01.ini", reference: {} }, + { filename: "data/iniparser02.ini", reference: {} }, + { filename: "data/iniparser03.ini", reference: {} }, + { filename: "data/iniparser04.ini", reference: {} }, + { filename: "data/iniparser05.ini", reference: {} }, + { filename: "data/iniparser06.ini", reference: {} }, + { filename: "data/iniparser07.ini", reference: {} }, + { + filename: "data/iniparser08.ini", + reference: { section1: { name1: "" } }, + }, + { + filename: "data/iniparser09.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser10.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser11.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser12.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser13.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser14.ini", + reference: { + section1: { name1: "value1", name2: "value2" }, + section2: { name1: "value1", name2: "foopy" }, + }, + }, + { + filename: "data/iniparser15.ini", + reference: { + section1: { name1: "newValue1" }, + section2: { name1: "foopy" }, + }, + }, + { + filename: "data/iniparser16.ini", + reference: { + "☺♫": { "♫": "☻", "♪": "♥" }, + "☼": { "♣": "â™ ", "♦": "♥" }, + }, + }, + { filename: "data/iniparser17.ini", reference: { section: { key: "" } } }, + ]; + + testdata.push({ + filename: "data/iniparser01-utf8BOM.ini", + reference: testdata[0].reference, + }); + testdata.push({ + filename: "data/iniparser02-utf8BOM.ini", + reference: testdata[1].reference, + }); + testdata.push({ + filename: "data/iniparser03-utf8BOM.ini", + reference: testdata[2].reference, + }); + testdata.push({ + filename: "data/iniparser04-utf8BOM.ini", + reference: testdata[3].reference, + }); + testdata.push({ + filename: "data/iniparser05-utf8BOM.ini", + reference: testdata[4].reference, + }); + testdata.push({ + filename: "data/iniparser06-utf8BOM.ini", + reference: testdata[5].reference, + }); + testdata.push({ + filename: "data/iniparser07-utf8BOM.ini", + reference: testdata[6].reference, + }); + testdata.push({ + filename: "data/iniparser08-utf8BOM.ini", + reference: testdata[7].reference, + }); + testdata.push({ + filename: "data/iniparser09-utf8BOM.ini", + reference: testdata[8].reference, + }); + testdata.push({ + filename: "data/iniparser10-utf8BOM.ini", + reference: testdata[9].reference, + }); + testdata.push({ + filename: "data/iniparser11-utf8BOM.ini", + reference: testdata[10].reference, + }); + testdata.push({ + filename: "data/iniparser12-utf8BOM.ini", + reference: testdata[11].reference, + }); + testdata.push({ + filename: "data/iniparser13-utf8BOM.ini", + reference: testdata[12].reference, + }); + testdata.push({ + filename: "data/iniparser14-utf8BOM.ini", + reference: testdata[13].reference, + }); + testdata.push({ + filename: "data/iniparser15-utf8BOM.ini", + reference: testdata[14].reference, + }); + testdata.push({ + filename: "data/iniparser16-utf8BOM.ini", + reference: testdata[15].reference, + }); + + // Intentional test for appInfo that can't be preloaded. + // eslint-disable-next-line mozilla/use-services + let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS; + if ("WINNT" === os) { + testdata.push({ + filename: "data/iniparser01-utf16leBOM.ini", + reference: testdata[0].reference, + }); + testdata.push({ + filename: "data/iniparser02-utf16leBOM.ini", + reference: testdata[1].reference, + }); + testdata.push({ + filename: "data/iniparser03-utf16leBOM.ini", + reference: testdata[2].reference, + }); + testdata.push({ + filename: "data/iniparser04-utf16leBOM.ini", + reference: testdata[3].reference, + }); + testdata.push({ + filename: "data/iniparser05-utf16leBOM.ini", + reference: testdata[4].reference, + }); + testdata.push({ + filename: "data/iniparser06-utf16leBOM.ini", + reference: testdata[5].reference, + }); + testdata.push({ + filename: "data/iniparser07-utf16leBOM.ini", + reference: testdata[6].reference, + }); + testdata.push({ + filename: "data/iniparser08-utf16leBOM.ini", + reference: testdata[7].reference, + }); + testdata.push({ + filename: "data/iniparser09-utf16leBOM.ini", + reference: testdata[8].reference, + }); + testdata.push({ + filename: "data/iniparser10-utf16leBOM.ini", + reference: testdata[9].reference, + }); + testdata.push({ + filename: "data/iniparser11-utf16leBOM.ini", + reference: testdata[10].reference, + }); + testdata.push({ + filename: "data/iniparser12-utf16leBOM.ini", + reference: testdata[11].reference, + }); + testdata.push({ + filename: "data/iniparser13-utf16leBOM.ini", + reference: testdata[12].reference, + }); + testdata.push({ + filename: "data/iniparser14-utf16leBOM.ini", + reference: testdata[13].reference, + }); + testdata.push({ + filename: "data/iniparser15-utf16leBOM.ini", + reference: testdata[14].reference, + }); + testdata.push({ + filename: "data/iniparser16-utf16leBOM.ini", + reference: testdata[15].reference, + }); + } + + /* ========== 0 ========== */ + factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService( + Ci.nsIINIParserFactory + ); + Assert.ok(!!factory); + + // Test reading from a variety of files and strings. While we're at it, + // write out each one and read it back to ensure that nothing changed. + while (testnum < testdata.length) { + dump("\nINFO | test #" + ++testnum); + let filename = testdata[testnum - 1].filename; + dump(", filename " + filename + "\n"); + let parser = parserForFile(filename); + checkParserOutput(parser, testdata[testnum - 1].reference); + if (!parser) { + continue; + } + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + // write contents out to a new file + let newfilename = filename + ".new"; + let newfile = do_get_file(filename); + newfile.leafName += ".new"; + parser.writeFile(newfile); + // read new file and make sure the contents are the same. + parser = parserForFile(newfilename); + checkParserOutput(parser, testdata[testnum - 1].reference); + // cleanup after the test + newfile.remove(false); + + // ensure that `writeString` works correctly + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + let formatted = parser.writeToString(); + parser = factory.createINIParser(null); + // re-parsing the formatted string is the easiest + // way to verify correctness... + parser.initFromString(formatted); + checkParserOutput(parser, testdata[testnum - 1].reference); + } + + dump("INFO | test #" + ++testnum + "\n"); + + // test writing to a new file. + var newfile = do_get_file("data/"); + newfile.append("nonexistent-file.ini"); + if (newfile.exists()) { + newfile.remove(false); + } + Assert.ok(!newfile.exists()); + + try { + var parser = factory.createINIParser(newfile); + Assert.ok(false, "Should have thrown an exception"); + } catch (e) { + Assert.equal( + e.result, + Cr.NS_ERROR_FILE_NOT_FOUND, + "Caught a file not found exception" + ); + } + parser = factory.createINIParser(); + Assert.ok(!!parser); + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + checkParserOutput(parser, {}); + parser.writeFile(newfile); + Assert.ok(newfile.exists()); + + // test adding a new section and new key + parser.setString("section", "key", "value"); + parser.setString("section", "key2", ""); + parser.writeFile(newfile); + Assert.ok(newfile.exists()); + checkParserOutput(parser, { section: { key: "value", key2: "" } }); + // read it in again, check for same data. + parser = parserForFile("data/nonexistent-file.ini"); + checkParserOutput(parser, { section: { key: "value", key2: "" } }); + // cleanup after the test + newfile.remove(false); + + dump("INFO | test #" + ++testnum + "\n"); + + // test modifying a existing key's value (in an existing section) + parser = parserForFile("data/iniparser09.ini"); + checkParserOutput(parser, { section1: { name1: "value1" } }); + + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + parser.setString("section1", "name1", "value2"); + checkParserOutput(parser, { section1: { name1: "value2" } }); + + dump("INFO | test #" + ++testnum + "\n"); + + // test trying to set illegal characters + var caughtError; + caughtError = null; + checkParserOutput(parser, { section1: { name1: "value2" } }); + + // Bad characters in section name + try { + parser.setString("bad\0", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad\r", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad\n", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad[", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad]", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + + // Bad characters in key name + caughtError = null; + try { + parser.setString("ok", "bad\0", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "bad\r", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "bad\n", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "bad=", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + + // Bad characters in value + caughtError = null; + try { + parser.setString("ok", "ok", "bad\0"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "ok", "bad\r"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "ok", "bad\n"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "ok", "good="); + } catch (e) { + caughtError = e; + } + Assert.ok(!caughtError); + caughtError = null; + } catch (e) { + throw new Error(`FAILED in test #${testnum} -- ${e}`); + } +} diff --git a/xpcom/tests/unit/test_ioutil.js b/xpcom/tests/unit/test_ioutil.js new file mode 100644 index 0000000000..e269b424a2 --- /dev/null +++ b/xpcom/tests/unit/test_ioutil.js @@ -0,0 +1,29 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +const util = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil); + +function run_test() { + try { + util.inputStreamIsBuffered(null); + do_throw("inputStreamIsBuffered should have thrown"); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_INVALID_POINTER); + } + + try { + util.outputStreamIsBuffered(null); + do_throw("outputStreamIsBuffered should have thrown"); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_INVALID_POINTER); + } + + var s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + var body = "This is a test"; + s.setData(body, body.length); + Assert.equal(util.inputStreamIsBuffered(s), true); +} diff --git a/xpcom/tests/unit/test_localfile.js b/xpcom/tests/unit/test_localfile.js new file mode 100644 index 0000000000..c90d91b278 --- /dev/null +++ b/xpcom/tests/unit/test_localfile.js @@ -0,0 +1,288 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +const MAX_TIME_DIFFERENCE = 2500; +const MILLIS_PER_DAY = 1000 * 60 * 60 * 24; + +var LocalFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); + +function sleep(ms) { + // We are measuring timestamps, which are slightly fuzzed, and just need to + // measure that they are increasing. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + return new Promise(resolve => setTimeout(() => resolve(), ms)); +} + +add_task(function test_toplevel_parent_is_null() { + try { + var lf = new LocalFile("C:\\"); + + // not required by API, but a property on which the implementation of + // parent == null relies for correctness + Assert.ok(lf.path.length == 2); + + Assert.ok(lf.parent === null); + } catch (e) { + // not Windows + Assert.equal(e.result, Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH); + } +}); + +add_task(function test_normalize_crash_if_media_missing() { + const a = "a".charCodeAt(0); + const z = "z".charCodeAt(0); + for (var i = a; i <= z; ++i) { + try { + LocalFile(String.fromCharCode(i) + ":.\\test").normalize(); + } catch (e) {} + } +}); + +// Tests that changing a file's modification time is possible +add_task(async function test_file_modification_time() { + let file = do_get_profile(); + file.append("testfile"); + + // Should never happen but get rid of it anyway + if (file.exists()) { + file.remove(true); + } + + const now = Date.now(); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists()); + + const atime = file.lastAccessedTime; + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + let diff = Math.abs(file.lastModifiedTime - now); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const yesterday = now - MILLIS_PER_DAY; + file.lastModifiedTime = yesterday; + + diff = Math.abs(file.lastModifiedTime - yesterday); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + Assert.equal( + file.lastAccessedTime, + atime, + "Setting lastModifiedTime should not set lastAccessedTime" + ); + + const tomorrow = now + MILLIS_PER_DAY; + file.lastModifiedTime = tomorrow; + + diff = Math.abs(file.lastModifiedTime - tomorrow); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const bug377307 = 1172950238000; + file.lastModifiedTime = bug377307; + + diff = Math.abs(file.lastModifiedTime - bug377307); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + await sleep(1000); + + file.lastModifiedTime = 0; + Assert.greater( + file.lastModifiedTime, + now, + "Setting lastModifiedTime to 0 should set it to current date and time" + ); + + file.remove(true); +}); + +add_task(async function test_lastAccessedTime() { + const file = do_get_profile(); + + file.append("test-atime"); + if (file.exists()) { + file.remove(true); + } + + const now = Date.now(); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists()); + + const mtime = file.lastModifiedTime; + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + let diff = Math.abs(file.lastModifiedTime - now); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const yesterday = now - MILLIS_PER_DAY; + file.lastAccessedTime = yesterday; + + diff = Math.abs(file.lastAccessedTime - yesterday); + Assert.ok(diff < MAX_TIME_DIFFERENCE, `${diff} < ${MAX_TIME_DIFFERENCE}`); + Assert.equal( + file.lastModifiedTime, + mtime, + "Setting lastAccessedTime should not set lastModifiedTime" + ); + + const tomorrow = now + MILLIS_PER_DAY; + file.lastAccessedTime = tomorrow; + + diff = Math.abs(file.lastAccessedTime - tomorrow); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const bug377307 = 1172950238000; + file.lastAccessedTime = bug377307; + + diff = Math.abs(file.lastAccessedTime - bug377307); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + await sleep(1000); + + file.lastAccessedTime = 0; + Assert.greater( + file.lastAccessedTime, + now, + "Setting lastAccessedTime to 0 should set it to the current date and time" + ); +}); + +// Tests that changing a directory's modification time is possible +add_task(function test_directory_modification_time() { + var dir = do_get_profile(); + dir.append("testdir"); + + // Should never happen but get rid of it anyway + if (dir.exists()) { + dir.remove(true); + } + + var now = Date.now(); + dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + Assert.ok(dir.exists()); + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + var diff = Math.abs(dir.lastModifiedTime - now); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + var yesterday = now - MILLIS_PER_DAY; + dir.lastModifiedTime = yesterday; + + diff = Math.abs(dir.lastModifiedTime - yesterday); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + var tomorrow = now - MILLIS_PER_DAY; + dir.lastModifiedTime = tomorrow; + + diff = Math.abs(dir.lastModifiedTime - tomorrow); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + dir.remove(true); +}); + +add_task(function test_diskSpaceAvailable() { + let file = do_get_profile(); + file.QueryInterface(Ci.nsIFile); + + let bytes = file.diskSpaceAvailable; + Assert.ok(bytes > 0); + + file.append("testfile"); + if (file.exists()) { + file.remove(true); + } + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + bytes = file.diskSpaceAvailable; + Assert.ok(bytes > 0); + + file.remove(true); +}); + +add_task(function test_diskCapacity() { + let file = do_get_profile(); + file.QueryInterface(Ci.nsIFile); + + const startBytes = file.diskCapacity; + Assert.ok(!!startBytes); // Not 0, undefined etc. + + file.append("testfile"); + if (file.exists()) { + file.remove(true); + } + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + const endBytes = file.diskCapacity; + Assert.ok(!!endBytes); // Not 0, undefined etc. + Assert.ok(startBytes === endBytes); + + file.remove(true); +}); + +add_task( + { + // nsIFile::CreationTime is only supported on macOS and Windows. + skip_if: () => !["macosx", "win"].includes(AppConstants.platform), + }, + function test_file_creation_time() { + const file = do_get_profile(); + // If we re-use the same file name from the other tests, even if the + // file.exists() check fails at 165, this test will likely fail due to the + // creation time being copied over from the previous instance of the file on + // Windows. + file.append("testfile-creation-time"); + + if (file.exists()) { + file.remove(true); + } + + const now = Date.now(); + + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists()); + + const creationTime = file.creationTime; + Assert.ok(creationTime === file.lastModifiedTime); + + file.lastModifiedTime = now + MILLIS_PER_DAY; + + Assert.ok(creationTime !== file.lastModifiedTime); + Assert.ok(creationTime === file.creationTime); + + file.remove(true); + } +); + +add_task(function test_file_append_parent() { + const SEPARATOR = AppConstants.platform === "win" ? "\\" : "/"; + + const file = do_get_profile(); + + Assert.throws( + () => file.append(".."), + /NS_ERROR_FILE_UNRECOGNIZED_PATH/, + `nsLocalFile::Append("..") throws` + ); + + Assert.throws( + () => file.appendRelativePath(".."), + /NS_ERROR_FILE_UNRECOGNIZED_PATH/, + `nsLocalFile::AppendRelativePath("..") throws` + ); + + Assert.throws( + () => file.appendRelativePath(`foo${SEPARATOR}..${SEPARATOR}baz`), + /NS_ERROR_FILE_UNRECOGNIZED_PATH/, + `nsLocalFile::AppendRelativePath(path) fails when path contains ".."` + ); +}); diff --git a/xpcom/tests/unit/test_mac_bundle.js b/xpcom/tests/unit/test_mac_bundle.js new file mode 100644 index 0000000000..6703e8a2b8 --- /dev/null +++ b/xpcom/tests/unit/test_mac_bundle.js @@ -0,0 +1,18 @@ +function run_test() { + // this is a hack to skip the rest of the code on non-Mac platforms, + // since #ifdef is not available to xpcshell tests... + if (mozinfo.os != "mac") { + return; + } + + // OK, here's the real part of the test: + // make sure these two test bundles are recognized as bundles (or "packages") + var keynoteBundle = do_get_file("data/presentation.key"); + var appBundle = do_get_file("data/SmallApp.app"); + + Assert.ok(keynoteBundle instanceof Ci.nsILocalFileMac); + Assert.ok(appBundle instanceof Ci.nsILocalFileMac); + + Assert.ok(keynoteBundle.isPackage()); + Assert.ok(appBundle.isPackage()); +} diff --git a/xpcom/tests/unit/test_mac_xattrs.js b/xpcom/tests/unit/test_mac_xattrs.js new file mode 100644 index 0000000000..b387358d74 --- /dev/null +++ b/xpcom/tests/unit/test_mac_xattrs.js @@ -0,0 +1,98 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +const ATTR = "bogus.attr"; +const VALUE = new TextEncoder().encode("bogus"); + +async function run_test() { + const path = PathUtils.join( + Services.dirsvc.get("TmpD", Ci.nsIFile).path, + "macos-xattrs.tmp.d" + ); + await IOUtils.makeDirectory(path); + + try { + await test_macos_xattr(path); + } finally { + await IOUtils.remove(path, { recursive: true }); + } +} + +async function test_macos_xattr(tmpDir) { + const path = PathUtils.join(tmpDir, "file.tmp"); + + await IOUtils.writeUTF8(path, ""); + + const file = new FileUtils.File(path); + file.queryInterface(Cc.nsILocalFileMac); + + Assert.ok(!file.exists(), "File should not exist"); + + info("Testing reading an attribute on a file that does not exist"); + Assert.throws( + () => file.hasXAttr(ATTR), + /NS_ERROR_FILE_NOT_FOUND/, + "Non-existant files can't have attributes checked" + ); + Assert.throws( + () => file.getXAttr(ATTR), + /NS_ERROR_FILE_NOT_FOUND/, + "Non-existant files can't have attributes read" + ); + + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists(), "File exists after creation"); + + info("Testing reading an attribute that does not exist"); + Assert.ok( + !file.hasXAttr(ATTR), + "File should not have attribute before being set." + ); + Assert.throws( + () => file.getXAttr(ATTR), + /NS_ERROR_NOT_AVAILABLE/, + "Attempting to get an attribute that does not exist throws" + ); + + { + info("Testing setting and reading an attribute"); + file.setXAttr(ATTR, VALUE); + Assert.ok( + file.hasXAttr(ATTR), + "File should have attribute after being set" + ); + const result = file.getXAttr(ATTR); + + Assert.deepEqual( + Array.from(result), + Array.from(VALUE), + "File should have attribute value matching what was set" + ); + } + + info("Testing removing an attribute"); + file.delXAttr(ATTR); + Assert.ok( + !file.hasXAttr(ATTR), + "File should no longer have the attribute after removal" + ); + Assert.throws( + () => file.getXAttr(ATTR), + /NS_ERROR_NOT_AVAILABLE/, + "Attempting to get an attribute after removal results in an error" + ); + + info("Testing removing an attribute that does not exist"); + Assert.throws( + () => file.delXAttr(ATTR), + /NS_ERROR_NOT_AVAILABLE/, + "Attempting to remove an attribute that does not exist throws" + ); +} diff --git a/xpcom/tests/unit/test_notxpcom_scriptable.js b/xpcom/tests/unit/test_notxpcom_scriptable.js new file mode 100644 index 0000000000..5362894b70 --- /dev/null +++ b/xpcom/tests/unit/test_notxpcom_scriptable.js @@ -0,0 +1,67 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +const kCID = Components.ID("{1f9f7181-e6c5-4f4c-8f71-08005cec8468}"); +const kContract = "@testing/notxpcomtest"; + +function run_test() { + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + + ok(Ci.nsIScriptableWithNotXPCOM); + + let method1Called = false; + + let testObject = { + QueryInterface: ChromeUtils.generateQI([ + "nsIScriptableOK", + "nsIScriptableWithNotXPCOM", + ]), + + method1() { + method1Called = true; + }, + + method2() { + ok(false, "method2 should not have been called!"); + }, + + method3() { + ok(false, "mehod3 should not have been called!"); + }, + + jsonly: true, + }; + + let factory = { + QueryInterface: ChromeUtils.generateQI(["nsIFactory"]), + + createInstance(iid) { + return testObject.QueryInterface(iid); + }, + }; + + registrar.registerFactory(kCID, null, kContract, factory); + + let xpcomObject = Cc[kContract].createInstance(); + ok(xpcomObject); + strictEqual(xpcomObject.jsonly, undefined); + + xpcomObject.QueryInterface(Ci.nsIScriptableOK); + + xpcomObject.method1(); + ok(method1Called); + + try { + xpcomObject.QueryInterface(Ci.nsIScriptableWithNotXPCOM); + ok(false, "Should not have implemented nsIScriptableWithNotXPCOM"); + } catch (e) { + ok( + true, + "Should not have implemented nsIScriptableWithNotXPCOM. Correctly threw error: " + + e + ); + } + strictEqual(xpcomObject.method2, undefined); +} diff --git a/xpcom/tests/unit/test_nsIMutableArray.js b/xpcom/tests/unit/test_nsIMutableArray.js new file mode 100644 index 0000000000..525196a48f --- /dev/null +++ b/xpcom/tests/unit/test_nsIMutableArray.js @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var MutableArray = CC("@mozilla.org/array;1", "nsIMutableArray"); +var SupportsString = CC("@mozilla.org/supports-string;1", "nsISupportsString"); + +function create_n_element_array(n) { + var arr = new MutableArray(); + for (let i = 0; i < n; i++) { + let str = new SupportsString(); + str.data = "element " + i; + arr.appendElement(str); + } + return arr; +} + +function test_appending_null_actually_inserts() { + var arr = new MutableArray(); + Assert.equal(0, arr.length); + arr.appendElement(null); + Assert.equal(1, arr.length); +} + +function test_object_gets_appended() { + var arr = new MutableArray(); + var str = new SupportsString(); + str.data = "hello"; + arr.appendElement(str); + Assert.equal(1, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + Assert.equal(str, obj); +} + +function test_insert_at_beginning() { + var arr = create_n_element_array(5); + // just a sanity check + Assert.equal(5, arr.length); + var str = new SupportsString(); + str.data = "hello"; + arr.insertElementAt(str, 0); + Assert.equal(6, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + Assert.equal(str, obj); + // check the data of all the other objects + for (let i = 1; i < arr.length; i++) { + let obj2 = arr.queryElementAt(i, Ci.nsISupportsString); + Assert.equal("element " + (i - 1), obj2.data); + } +} + +function test_replace_element() { + var arr = create_n_element_array(5); + // just a sanity check + Assert.equal(5, arr.length); + var str = new SupportsString(); + str.data = "hello"; + // replace first element + arr.replaceElementAt(str, 0); + Assert.equal(5, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + Assert.equal(str, obj); + // replace last element + arr.replaceElementAt(str, arr.length - 1); + Assert.equal(5, arr.length); + obj = arr.queryElementAt(arr.length - 1, Ci.nsISupportsString); + Assert.equal(str, obj); + // replace after last element, should insert empty elements + arr.replaceElementAt(str, 9); + Assert.equal(10, arr.length); + obj = arr.queryElementAt(9, Ci.nsISupportsString); + Assert.equal(str, obj); + // AFAIK there's no way to check the empty elements, since you can't QI them. +} + +function test_clear() { + var arr = create_n_element_array(5); + // just a sanity check + Assert.equal(5, arr.length); + arr.clear(); + Assert.equal(0, arr.length); +} + +function test_enumerate() { + var arr = create_n_element_array(5); + Assert.equal(5, arr.length); + var i = 0; + for (let str of arr.enumerate()) { + Assert.ok(str instanceof Ci.nsISupportsString); + Assert.equal(str.data, "element " + i); + i++; + } + Assert.equal(arr.length, i); +} + +function test_nsiarrayextensions() { + // Tests to check that the extensions that make an nsArray act like an + // nsISupportsArray for iteration purposes works. + // Note: we do not want to QI here, just want to make sure the magic glue + // works as a drop-in replacement. + + let fake_nsisupports_array = create_n_element_array(5); + + // Check that |Count| works. + Assert.equal(5, fake_nsisupports_array.Count()); + + for (let i = 0; i < fake_nsisupports_array.Count(); i++) { + // Check that the generic |GetElementAt| works. + let elm = fake_nsisupports_array.GetElementAt(i); + Assert.notEqual(elm, null); + let str = elm.QueryInterface(Ci.nsISupportsString); + Assert.notEqual(str, null); + Assert.equal(str.data, "element " + i); + } +} + +var tests = [ + test_appending_null_actually_inserts, + test_object_gets_appended, + test_insert_at_beginning, + test_replace_element, + test_clear, + test_enumerate, + test_nsiarrayextensions, +]; + +function run_test() { + for (var i = 0; i < tests.length; i++) { + tests[i](); + } +} diff --git a/xpcom/tests/unit/test_nsIProcess.js b/xpcom/tests/unit/test_nsIProcess.js new file mode 100644 index 0000000000..582d10440c --- /dev/null +++ b/xpcom/tests/unit/test_nsIProcess.js @@ -0,0 +1,184 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// nsIProcess unit test +const TEST_ARGS = [ + "mozilla", + "firefox", + "thunderbird", + "seamonkey", + "foo", + "bar", + "argument with spaces", + '"argument with quotes"', +]; + +const TEST_UNICODE_ARGS = [ + "M\u00F8z\u00EEll\u00E5", + "\u041C\u043E\u0437\u0438\u043B\u043B\u0430", + "\u09AE\u09CB\u099C\u09BF\u09B2\u09BE", + "\uD808\uDE2C\uD808\uDF63\uD808\uDDB7", +]; + +// test if a process can be started, polled for its running status +// and then killed +function test_kill() { + var file = get_test_program("TestBlockingProcess"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + Assert.ok(!process.isRunning); + + try { + process.kill(); + do_throw("Attempting to kill a not-running process should throw"); + } catch (e) {} + + process.run(false, [], 0); + + Assert.ok(process.isRunning); + + process.kill(); + + Assert.ok(!process.isRunning); + + try { + process.kill(); + do_throw("Attempting to kill a not-running process should throw"); + } catch (e) {} +} + +// test if we can get an exit value from an application that is +// guaranteed to return an exit value of 42 +function test_quick() { + var file = get_test_program("TestQuickReturn"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + // to get an exit value it must be a blocking process + process.run(true, [], 0); + + Assert.equal(process.exitValue, 42); +} + +function test_args(file, args, argsAreASCII) { + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + if (argsAreASCII) { + process.run(true, args, args.length); + } else { + process.runw(true, args, args.length); + } + + Assert.equal(process.exitValue, 0); +} + +// test if an argument can be successfully passed to an application +// that will return 0 if "mozilla" is the only argument +function test_arguments() { + test_args(get_test_program("TestArguments"), TEST_ARGS, true); +} + +// test if Unicode arguments can be successfully passed to an application +function test_unicode_arguments() { + test_args(get_test_program("TestUnicodeArguments"), TEST_UNICODE_ARGS, false); +} + +function rename_and_test(asciiName, unicodeName, args, argsAreASCII) { + var asciiFile = get_test_program(asciiName); + var asciiLeaf = asciiFile.leafName; + var unicodeLeaf = asciiLeaf.replace(asciiName, unicodeName); + + asciiFile.moveTo(null, unicodeLeaf); + + var unicodeFile = get_test_program(unicodeName); + + test_args(unicodeFile, args, argsAreASCII); + + unicodeFile.moveTo(null, asciiLeaf); +} + +// test passing ASCII and Unicode arguments to an application with a Unicode name +function test_unicode_app() { + rename_and_test( + "TestArguments", + // "Unicode" in Tamil + "\u0BAF\u0BC1\u0BA9\u0BBF\u0B95\u0BCB\u0B9F\u0BCD", + TEST_ARGS, + true + ); + + rename_and_test( + "TestUnicodeArguments", + // "Unicode" in Thai + "\u0E22\u0E39\u0E19\u0E34\u0E42\u0E04\u0E14", + TEST_UNICODE_ARGS, + false + ); +} + +// test if we get notified about a blocking process +function test_notify_blocking() { + var file = get_test_program("TestQuickReturn"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + process.runAsync([], 0, { + observe(subject, topic, data) { + process = subject.QueryInterface(Ci.nsIProcess); + Assert.equal(topic, "process-failed"); + Assert.equal(process.exitValue, 42); + test_notify_nonblocking(); + }, + }); +} + +// test if we get notified about a non-blocking process +function test_notify_nonblocking() { + var file = get_test_program("TestArguments"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + process.runAsync(TEST_ARGS, TEST_ARGS.length, { + observe(subject, topic, data) { + process = subject.QueryInterface(Ci.nsIProcess); + Assert.equal(topic, "process-finished"); + Assert.equal(process.exitValue, 0); + test_notify_killed(); + }, + }); +} + +// test if we get notified about a killed process +function test_notify_killed() { + var file = get_test_program("TestBlockingProcess"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + process.runAsync([], 0, { + observe(subject, topic, data) { + process = subject.QueryInterface(Ci.nsIProcess); + Assert.equal(topic, "process-failed"); + do_test_finished(); + }, + }); + + process.kill(); +} + +function run_test() { + set_process_running_environment(); + test_kill(); + test_quick(); + test_arguments(); + test_unicode_arguments(); + test_unicode_app(); + do_test_pending(); + test_notify_blocking(); +} diff --git a/xpcom/tests/unit/test_nsIProcess_stress.js b/xpcom/tests/unit/test_nsIProcess_stress.js new file mode 100644 index 0000000000..802f9d70aa --- /dev/null +++ b/xpcom/tests/unit/test_nsIProcess_stress.js @@ -0,0 +1,24 @@ +function run_test() { + set_process_running_environment(); + + var file = get_test_program("TestQuickReturn"); + var tm = Cc["@mozilla.org/thread-manager;1"].getService(); + + for (var i = 0; i < 1000; i++) { + var process = Cc["@mozilla.org/process/util;1"].createInstance( + Ci.nsIProcess + ); + process.init(file); + + process.run(false, [], 0); + + try { + process.kill(); + } catch (e) {} + + // We need to ensure that we process any events on the main thread - + // this allow threads to clean up properly and avoid out of memory + // errors during the test. + tm.spinEventLoopUntilEmpty(); + } +} diff --git a/xpcom/tests/unit/test_pipe.js b/xpcom/tests/unit/test_pipe.js new file mode 100644 index 0000000000..e3ccc6ef3f --- /dev/null +++ b/xpcom/tests/unit/test_pipe.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init"); + +function run_test() { + test_not_initialized(); + test_ends_are_threadsafe(); +} + +function test_not_initialized() { + var p = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + try { + var dummy = p.outputStream; + dump("dummy: " + dummy + "\n"); + throw Components.Exception("", Cr.NS_ERROR_FAILURE); + } catch (e) { + if (e.result != Cr.NS_ERROR_NOT_INITIALIZED) { + do_throw( + "using a pipe before initializing it should throw NS_ERROR_NOT_INITIALIZED" + ); + } + } +} + +function test_ends_are_threadsafe() { + var p, is, os; + + p = new Pipe(true, true, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(true, false, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(false, true, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(false, false, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); +} diff --git a/xpcom/tests/unit/test_process_directives.js b/xpcom/tests/unit/test_process_directives.js new file mode 100644 index 0000000000..b1975a43da --- /dev/null +++ b/xpcom/tests/unit/test_process_directives.js @@ -0,0 +1,20 @@ +function categoryExists(category, entry) { + try { + Services.catMan.getCategoryEntry(category, entry); + return true; + } catch (e) { + return false; + } +} + +function run_test() { + Components.manager.autoRegister( + do_get_file("data/process_directive.manifest") + ); + + let isChild = + Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + + Assert.equal(categoryExists("directives-test", "main-process"), !isChild); + Assert.equal(categoryExists("directives-test", "content-process"), isChild); +} diff --git a/xpcom/tests/unit/test_process_directives_child.js b/xpcom/tests/unit/test_process_directives_child.js new file mode 100644 index 0000000000..dca63b3563 --- /dev/null +++ b/xpcom/tests/unit/test_process_directives_child.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("test_process_directives.js"); +} diff --git a/xpcom/tests/unit/test_seek_multiplex.js b/xpcom/tests/unit/test_seek_multiplex.js new file mode 100644 index 0000000000..7a469d9f48 --- /dev/null +++ b/xpcom/tests/unit/test_seek_multiplex.js @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The string we use as data. +const data = "0123456789"; +// Number of streams in the multiplex stream. +const count = 10; + +function test_multiplex_streams() { + var MultiplexStream = CC( + "@mozilla.org/io/multiplex-input-stream;1", + "nsIMultiplexInputStream" + ); + Assert.equal(1, 1); + + var multiplex = new MultiplexStream(); + for (var i = 0; i < count; ++i) { + let s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + s.setData(data, data.length); + + multiplex.appendStream(s); + } + var seekable = multiplex.QueryInterface(Ci.nsISeekableStream); + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(seekable); + // Read some data. + var readData = sis.read(20); + Assert.equal(readData, data + data); + // -- Tests for NS_SEEK_SET + // Seek to a non-zero, non-stream-boundary offset. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 2); + Assert.equal(seekable.tell(), 2); + Assert.equal(seekable.available(), 98); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 9); + Assert.equal(seekable.tell(), 9); + Assert.equal(seekable.available(), 91); + // Seek across stream boundary. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 35); + Assert.equal(seekable.tell(), 35); + Assert.equal(seekable.available(), 65); + readData = sis.read(5); + Assert.equal(readData, data.slice(5)); + Assert.equal(seekable.available(), 60); + // Seek at stream boundaries. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 40); + Assert.equal(seekable.tell(), 40); + Assert.equal(seekable.available(), 60); + readData = sis.read(10); + Assert.equal(readData, data); + Assert.equal(seekable.tell(), 50); + Assert.equal(seekable.available(), 50); + // Rewind and read across streams. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 39); + Assert.equal(seekable.tell(), 39); + Assert.equal(seekable.available(), 61); + readData = sis.read(11); + Assert.equal(readData, data.slice(9) + data); + Assert.equal(seekable.tell(), 50); + Assert.equal(seekable.available(), 50); + // Rewind to the beginning. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + Assert.equal(seekable.tell(), 0); + Assert.equal(seekable.available(), 100); + // Seek to some random location + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 50); + // -- Tests for NS_SEEK_CUR + // Positive seek. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 15); + Assert.equal(seekable.tell(), 65); + Assert.equal(seekable.available(), 35); + readData = sis.read(10); + Assert.equal(readData, data.slice(5) + data.slice(0, 5)); + Assert.equal(seekable.tell(), 75); + Assert.equal(seekable.available(), 25); + // Negative seek. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15); + Assert.equal(seekable.tell(), 60); + Assert.equal(seekable.available(), 40); + readData = sis.read(10); + Assert.equal(readData, data); + Assert.equal(seekable.tell(), 70); + Assert.equal(seekable.available(), 30); + + // -- Tests for NS_SEEK_END + // Normal read. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -5); + Assert.equal(seekable.tell(), data.length * count - 5); + readData = sis.read(5); + Assert.equal(readData, data.slice(5)); + Assert.equal(seekable.tell(), data.length * count); + // Read across streams. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -15); + Assert.equal(seekable.tell(), data.length * count - 15); + readData = sis.read(15); + Assert.equal(readData, data.slice(5) + data); + Assert.equal(seekable.tell(), data.length * count); + + // -- Try to do various edge cases + // Forward seek from the end, should throw. + var caught = false; + try { + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, 15); + } catch (e) { + caught = true; + } + Assert.equal(caught, true); + Assert.equal(seekable.tell(), data.length * count); + // Backward seek from the beginning, should be clamped. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + Assert.equal(seekable.tell(), 0); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15); + Assert.equal(seekable.tell(), 0); + // Seek too far: should be clamped. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + Assert.equal(seekable.tell(), 0); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 3 * data.length * count); + Assert.equal(seekable.tell(), 100); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, data.length * count); + Assert.equal(seekable.tell(), 100); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -2 * data.length * count); + Assert.equal(seekable.tell(), 0); +} + +function test_multiplex_bug797871() { + var data2 = "1234567890123456789012345678901234567890"; + + var MultiplexStream = CC( + "@mozilla.org/io/multiplex-input-stream;1", + "nsIMultiplexInputStream" + ); + Assert.equal(1, 1); + + var multiplex = new MultiplexStream(); + let s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + s.setData(data2, data2.length); + + multiplex.appendStream(s); + + var seekable = multiplex.QueryInterface(Ci.nsISeekableStream); + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(seekable); + + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 8); + Assert.equal(seekable.tell(), 8); + sis.read(2); + Assert.equal(seekable.tell(), 10); + + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 20); + Assert.equal(seekable.tell(), 20); +} + +function run_test() { + test_multiplex_streams(); + test_multiplex_bug797871(); +} diff --git a/xpcom/tests/unit/test_storagestream.js b/xpcom/tests/unit/test_storagestream.js new file mode 100644 index 0000000000..2a0fb1b569 --- /dev/null +++ b/xpcom/tests/unit/test_storagestream.js @@ -0,0 +1,152 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "unusedVariable" }] */ + +function run_test() { + test1(); + test2(); + test3(); + test4(); +} + +/** + * Checks that getting an input stream from a storage stream which has never had + * anything written to it throws a not-initialized exception. + */ +function test1() { + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var unusedVariable = ss.getOutputStream(0); + var inp2 = ss.newInputStream(0); + Assert.equal(inp2.available(), 0); + Assert.ok(inp2.isNonBlocking()); + + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(inp2); + + var threw = false; + try { + sis.read(1); + } catch (ex) { + if (ex.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) { + threw = true; + } else { + throw ex; + } + } + Assert.ok(threw); +} + +/** + * Checks that getting an input stream from a storage stream to which 0 bytes of + * data have been explicitly written doesn't throw an exception. + */ +function test2() { + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var out = ss.getOutputStream(0); + out.write("", 0); + try { + ss.newInputStream(0); + } catch (e) { + do_throw("shouldn't throw exception when new input stream created"); + } +} + +/** + * Checks that reading any non-zero amount of data from a storage stream + * which has had 0 bytes written to it explicitly works correctly. + */ +function test3() { + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var out = ss.getOutputStream(0); + out.write("", 0); + try { + var inp = ss.newInputStream(0); + } catch (e) { + do_throw("newInputStream(0) shouldn't throw if write() is called: " + e); + } + + Assert.ok(inp.isNonBlocking(), "next test expects a non-blocking stream"); + + try { + var threw = false; + var bis = BIS(inp); + bis.readByteArray(5); + } catch (e) { + if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) { + do_throw("wrong error thrown: " + e); + } + threw = true; + } + Assert.ok(threw, "should have thrown (nsStorageInputStream is nonblocking)"); +} + +/** + * Basic functionality test for storagestream: write data to it, get an input + * stream, and read the data back to see that it matches. + */ +function test4() { + var bytes = [65, 66, 67, 68, 69, 70, 71, 72, 73, 74]; + + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var outStream = ss.getOutputStream(0); + + var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance( + Ci.nsIBinaryOutputStream + ); + bos.setOutputStream(outStream); + + bos.writeByteArray(bytes); + bos.close(); + + var inp = ss.newInputStream(0); + var bis = BIS(inp); + + var count = 0; + while (count < bytes.length) { + var data = bis.read8(1); + Assert.equal(data, bytes[count++]); + } + + var threw = false; + try { + data = bis.read8(1); + } catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + do_throw("wrong error thrown: " + e); + } + threw = true; + } + if (!threw) { + do_throw("should have thrown but instead returned: '" + data + "'"); + } +} + +function BIS(input) { + var bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + bis.setInputStream(input); + return bis; +} diff --git a/xpcom/tests/unit/test_streams.js b/xpcom/tests/unit/test_streams.js new file mode 100644 index 0000000000..c0c644bf19 --- /dev/null +++ b/xpcom/tests/unit/test_streams.js @@ -0,0 +1,170 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init"); +var BinaryOutput = CC( + "@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream" +); +var BinaryInput = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +/** + * Binary stream tests. + */ +function test_binary_streams() { + var p, is, os; + + p = new Pipe(false, false, 1024, 1, null); + is = new BinaryInput(p.inputStream); + os = new BinaryOutput(p.outputStream); + + const LargeNum = Math.pow(2, 18) + Math.pow(2, 12) + 1; + const HugeNum = Math.pow(2, 62); + const HelloStr = "Hello World"; + const HelloArray = Array.from(HelloStr, function (c) { + return c.charCodeAt(0); + }); + var countObj = {}; + var msg = {}; + var buffer = new ArrayBuffer(HelloArray.length); + + // Test reading immediately after writing. + os.writeBoolean(true); + Assert.equal(is.readBoolean(), true); + os.write8(4); + Assert.equal(is.read8(), 4); + os.write16(4); + Assert.equal(is.read16(), 4); + os.write16(1024); + Assert.equal(is.read16(), 1024); + os.write32(7); + Assert.equal(is.read32(), 7); + os.write32(LargeNum); + Assert.equal(is.read32(), LargeNum); + os.write64(LargeNum); + Assert.equal(is.read64(), LargeNum); + os.write64(1024); + Assert.equal(is.read64(), 1024); + os.write64(HugeNum); + Assert.equal(is.read64(), HugeNum); + os.writeFloat(2.5); + Assert.equal(is.readFloat(), 2.5); + // os.writeDouble(Math.SQRT2); + // do_check_eq(is.readDouble(), Math.SQRT2); + os.writeStringZ("Mozilla"); + Assert.equal(is.readCString(), "Mozilla"); + os.writeWStringZ("Gecko"); + Assert.equal(is.readString(), "Gecko"); + os.writeBytes(HelloStr, HelloStr.length); + Assert.equal(is.available(), HelloStr.length); + msg = is.readBytes(HelloStr.length); + Assert.equal(msg, HelloStr); + msg = null; + countObj.value = -1; + os.writeByteArray(HelloArray); + Assert.equal(is.available(), HelloStr.length); + msg = is.readByteArray(HelloStr.length); + Assert.equal(typeof msg, typeof HelloArray); + Assert.equal(msg.toSource(), HelloArray.toSource()); + Assert.equal(is.available(), 0); + os.writeByteArray(HelloArray); + Assert.equal( + is.readArrayBuffer(buffer.byteLength, buffer), + HelloArray.length + ); + Assert.equal([...new Uint8Array(buffer)].toSource(), HelloArray.toSource()); + Assert.equal(is.available(), 0); + + // Test writing in one big chunk. + os.writeBoolean(true); + os.write8(4); + os.write16(4); + os.write16(1024); + os.write32(7); + os.write32(LargeNum); + os.write64(LargeNum); + os.write64(1024); + os.write64(HugeNum); + os.writeFloat(2.5); + // os.writeDouble(Math.SQRT2); + os.writeStringZ("Mozilla"); + os.writeWStringZ("Gecko"); + os.writeBytes(HelloStr, HelloStr.length); + os.writeByteArray(HelloArray); + // Available should not be zero after a long write like this. + Assert.notEqual(is.available(), 0); + + // Test reading in one big chunk. + Assert.equal(is.readBoolean(), true); + Assert.equal(is.read8(), 4); + Assert.equal(is.read16(), 4); + Assert.equal(is.read16(), 1024); + Assert.equal(is.read32(), 7); + Assert.equal(is.read32(), LargeNum); + Assert.equal(is.read64(), LargeNum); + Assert.equal(is.read64(), 1024); + Assert.equal(is.read64(), HugeNum); + Assert.equal(is.readFloat(), 2.5); + // do_check_eq(is.readDouble(), Math.SQRT2); + Assert.equal(is.readCString(), "Mozilla"); + Assert.equal(is.readString(), "Gecko"); + // Remember, we wrote HelloStr twice - once as a string, and then as an array. + Assert.equal(is.available(), HelloStr.length * 2); + msg = is.readBytes(HelloStr.length); + Assert.equal(msg, HelloStr); + msg = null; + countObj.value = -1; + Assert.equal(is.available(), HelloStr.length); + msg = is.readByteArray(HelloStr.length); + Assert.equal(typeof msg, typeof HelloArray); + Assert.equal(msg.toSource(), HelloArray.toSource()); + Assert.equal(is.available(), 0); + + // Test for invalid actions. + os.close(); + is.close(); + + try { + os.writeBoolean(false); + do_throw("Not reached!"); + } catch (e) { + if ( + !(e instanceof Ci.nsIException && e.result == Cr.NS_BASE_STREAM_CLOSED) + ) { + throw e; + } + // do nothing + } + + try { + is.available(); + do_throw("Not reached!"); + } catch (e) { + if ( + !(e instanceof Ci.nsIException && e.result == Cr.NS_BASE_STREAM_CLOSED) + ) { + throw e; + } + // do nothing + } + + try { + is.readBoolean(); + do_throw("Not reached!"); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + // do nothing + } +} + +function run_test() { + test_binary_streams(); +} diff --git a/xpcom/tests/unit/test_stringstream.js b/xpcom/tests/unit/test_stringstream.js new file mode 100644 index 0000000000..0b9dcf3c5e --- /dev/null +++ b/xpcom/tests/unit/test_stringstream.js @@ -0,0 +1,20 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +function run_test() { + var s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + var body = "This is a test"; + s.setData(body, body.length); + Assert.equal(s.available(), body.length); + + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(s); + + Assert.equal(sis.read(body.length), body); +} diff --git a/xpcom/tests/unit/test_symlinks.js b/xpcom/tests/unit/test_symlinks.js new file mode 100644 index 0000000000..8c0d25f792 --- /dev/null +++ b/xpcom/tests/unit/test_symlinks.js @@ -0,0 +1,139 @@ +const CWD = do_get_cwd(); + +const DIR_TARGET = "target"; +const DIR_LINK = "link"; +const DIR_LINK_LINK = "link_link"; +const FILE_TARGET = "target.txt"; +const FILE_LINK = "link.txt"; +const FILE_LINK_LINK = "link_link.txt"; + +const DOES_NOT_EXIST = "doesnotexist"; +const DANGLING_LINK = "dangling_link"; +const LOOP_LINK = "loop_link"; + +const nsIFile = Ci.nsIFile; + +var process; +function createSymLink(from, to) { + if (!process) { + var ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + ln.initWithPath("/bin/ln"); + + process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(ln); + } + + const args = ["-s", from, to]; + process.run(true, args, args.length); + Assert.equal(process.exitValue, 0); +} + +function makeSymLink(from, toName, relative) { + var to = from.parent; + to.append(toName); + + if (relative) { + createSymLink(from.leafName, to.path); + } else { + createSymLink(from.path, to.path); + } + + Assert.ok(to.isSymlink()); + + print("---"); + print(from.path); + print(to.path); + print(to.target); + + if (from.leafName != DOES_NOT_EXIST && from.isSymlink()) { + // XXXjag wish I could set followLinks to false so we'd just get + // the symlink's direct target instead of the final target. + Assert.equal(from.target, to.target); + } else { + Assert.equal(from.path, to.target); + } + + return to; +} + +function setupTestDir(testDir, relative) { + var targetDir = testDir.clone(); + targetDir.append(DIR_TARGET); + + if (testDir.exists()) { + testDir.remove(true); + } + Assert.ok(!testDir.exists()); + + testDir.create(nsIFile.DIRECTORY_TYPE, 0o777); + + targetDir.create(nsIFile.DIRECTORY_TYPE, 0o777); + + var targetFile = testDir.clone(); + targetFile.append(FILE_TARGET); + targetFile.create(nsIFile.NORMAL_FILE_TYPE, 0o666); + + var imaginary = testDir.clone(); + imaginary.append(DOES_NOT_EXIST); + + var loop = testDir.clone(); + loop.append(LOOP_LINK); + + var dirLink = makeSymLink(targetDir, DIR_LINK, relative); + var fileLink = makeSymLink(targetFile, FILE_LINK, relative); + + makeSymLink(dirLink, DIR_LINK_LINK, relative); + makeSymLink(fileLink, FILE_LINK_LINK, relative); + + makeSymLink(imaginary, DANGLING_LINK, relative); + + try { + makeSymLink(loop, LOOP_LINK, relative); + Assert.ok(false); + } catch (e) {} +} + +function createSpaces(dirs, files, links) { + function longest(a, b) { + return a.length > b.length ? a : b; + } + return dirs.concat(files, links).reduce(longest, "").replace(/./g, " "); +} + +function testSymLinks(testDir, relative) { + setupTestDir(testDir, relative); + + const dirLinks = [DIR_LINK, DIR_LINK_LINK]; + const fileLinks = [FILE_LINK, FILE_LINK_LINK]; + const otherLinks = [DANGLING_LINK, LOOP_LINK]; + const dirs = [DIR_TARGET].concat(dirLinks); + const files = [FILE_TARGET].concat(fileLinks); + const links = otherLinks.concat(dirLinks, fileLinks); + + const spaces = createSpaces(dirs, files, links); + const bools = { false: " false", true: " true " }; + print(spaces + " dir file symlink"); + var dirEntries = testDir.directoryEntries; + while (dirEntries.hasMoreElements()) { + const file = dirEntries.nextFile; + const name = file.leafName; + print( + name + + spaces.substring(name.length) + + bools[file.isDirectory()] + + bools[file.isFile()] + + bools[file.isSymlink()] + ); + Assert.equal(file.isDirectory(), dirs.includes(name)); + Assert.equal(file.isFile(), files.includes(name)); + Assert.equal(file.isSymlink(), links.includes(name)); + } +} + +function run_test() { + var testDir = CWD; + testDir.append("test_symlinks"); + + testSymLinks(testDir, false); + testSymLinks(testDir, true); +} diff --git a/xpcom/tests/unit/test_systemInfo.js b/xpcom/tests/unit/test_systemInfo.js new file mode 100644 index 0000000000..6ab67796c9 --- /dev/null +++ b/xpcom/tests/unit/test_systemInfo.js @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + const PROPERTIES = [ + "name", + "arch", + "version", + "pagesize", + "pageshift", + "memmapalign", + "memsize", + ]; + let sysInfo = Services.sysinfo; + + PROPERTIES.forEach(function (aPropertyName) { + print("Testing property: " + aPropertyName); + let value = sysInfo.getProperty(aPropertyName); + Assert.ok(!!value); + }); + + // This property must exist, but its value might be zero. + print("Testing property: umask"); + Assert.equal(typeof sysInfo.getProperty("umask"), "number"); +} diff --git a/xpcom/tests/unit/test_versioncomparator.js b/xpcom/tests/unit/test_versioncomparator.js new file mode 100644 index 0000000000..5744d62721 --- /dev/null +++ b/xpcom/tests/unit/test_versioncomparator.js @@ -0,0 +1,55 @@ +// Versions to test listed in ascending order, none can be equal +var comparisons = [ + "pre", + // A number that is too large to be supported should be normalized to 0. + String(0x1f0000000), + "0.9", + "0.9.1", + "1.0pre1", + "1.0pre2", + "1.0", + "1.1pre", + "1.1pre1a", + "1.1pre1", + "1.1pre10a", + "1.1pre10", + "1.1", + "1.1.0.1", + "1.1.1", + "1.1.*", + "1.*", + "2.0", + "2.1", + "3.0.-1", + "3.0", +]; + +// Every version in this list means the same version number +var equality = ["1.1pre", "1.1pre0", "1.0+"]; + +function run_test() { + for (var i = 0; i < comparisons.length; i++) { + for (var j = 0; j < comparisons.length; j++) { + var result = Services.vc.compare(comparisons[i], comparisons[j]); + if (i == j) { + if (result != 0) { + do_throw(comparisons[i] + " should be the same as itself"); + } + } else if (i < j) { + if (!(result < 0)) { + do_throw(comparisons[i] + " should be less than " + comparisons[j]); + } + } else if (!(result > 0)) { + do_throw(comparisons[i] + " should be greater than " + comparisons[j]); + } + } + } + + for (i = 0; i < equality.length; i++) { + for (j = 0; j < equality.length; j++) { + if (Services.vc.compare(equality[i], equality[j]) != 0) { + do_throw(equality[i] + " should equal " + equality[j]); + } + } + } +} diff --git a/xpcom/tests/unit/test_windows_cmdline_file.js b/xpcom/tests/unit/test_windows_cmdline_file.js new file mode 100644 index 0000000000..318f93ebec --- /dev/null +++ b/xpcom/tests/unit/test_windows_cmdline_file.js @@ -0,0 +1,22 @@ +let executableFile = Services.dirsvc.get("CurProcD", Ci.nsIFile); +executableFile.append("xpcshell.exe"); +function run_test() { + let quote = '"'; // Windows' cmd processor doesn't actually use single quotes. + for (let suffix of ["", " -osint", ` --blah "%PROGRAMFILES%"`]) { + let cmdline = quote + executableFile.path + quote + suffix; + info(`Testing with ${cmdline}`); + let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin); + f.initWithCommandLine(cmdline); + Assert.equal( + f.path, + executableFile.path, + "Should be able to recover executable path" + ); + } + + let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin); + f.initWithCommandLine("%ComSpec% -c echo 'hi'"); + let cmd = Services.dirsvc.get("SysD", Ci.nsIFile); + cmd.append("cmd.exe"); + Assert.equal(f.path, cmd.path, "Should be able to replace env vars."); +} diff --git a/xpcom/tests/unit/test_windows_registry.js b/xpcom/tests/unit/test_windows_registry.js new file mode 100644 index 0000000000..691c0461c3 --- /dev/null +++ b/xpcom/tests/unit/test_windows_registry.js @@ -0,0 +1,204 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const nsIWindowsRegKey = Ci.nsIWindowsRegKey; +let regKeyComponent = Cc["@mozilla.org/windows-registry-key;1"]; + +function run_test() { + //* create a key structure in a spot that's normally writable (somewhere under HKCU). + let testKey = regKeyComponent.createInstance(nsIWindowsRegKey); + + // If it's already present because a previous test crashed or didn't clean up properly, clean it up first. + let keyName = BASE_PATH + "\\" + TESTDATA_KEYNAME; + setup_test_run(testKey, keyName); + + //* test that the write* functions write stuff + test_writing_functions(testKey); + + //* check that the valueCount/getValueName functions work for the values we just wrote + test_value_functions(testKey); + + //* check that the get* functions work for the values we just wrote. + test_reading_functions(testKey); + + //* check that the get* functions fail with the right exception codes if we ask for the wrong type or if the value name doesn't exist at all + test_invalidread_functions(testKey); + + //* check that creating/enumerating/deleting child keys works + test_childkey_functions(testKey); + + //* clean up + cleanup_test_run(testKey, keyName); +} + +function setup_test_run(testKey, keyName) { + info("Setup test run"); + try { + testKey.open( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + keyName, + nsIWindowsRegKey.ACCESS_READ + ); + info("Test key exists. Needs cleanup."); + cleanup_test_run(testKey, keyName); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + testKey.create( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + keyName, + nsIWindowsRegKey.ACCESS_ALL + ); +} + +function test_writing_functions(testKey) { + strictEqual(testKey.valueCount, 0); + + strictEqual(testKey.hasValue(TESTDATA_STRNAME), false); + testKey.writeStringValue(TESTDATA_STRNAME, TESTDATA_STRVALUE); + strictEqual(testKey.hasValue(TESTDATA_STRNAME), true); + + strictEqual(testKey.hasValue(TESTDATA_INTNAME), false); + testKey.writeIntValue(TESTDATA_INTNAME, TESTDATA_INTVALUE); + + strictEqual(testKey.hasValue(TESTDATA_INT64NAME), false); + testKey.writeInt64Value(TESTDATA_INT64NAME, TESTDATA_INT64VALUE); + + strictEqual(testKey.hasValue(TESTDATA_BINARYNAME), false); + testKey.writeBinaryValue(TESTDATA_BINARYNAME, TESTDATA_BINARYVALUE); +} + +function test_value_functions(testKey) { + strictEqual(testKey.valueCount, 4); + strictEqual(testKey.getValueName(0), TESTDATA_STRNAME); + strictEqual(testKey.getValueName(1), TESTDATA_INTNAME); + strictEqual(testKey.getValueName(2), TESTDATA_INT64NAME); + strictEqual(testKey.getValueName(3), TESTDATA_BINARYNAME); +} + +function test_reading_functions(testKey) { + strictEqual( + testKey.getValueType(TESTDATA_STRNAME), + nsIWindowsRegKey.TYPE_STRING + ); + strictEqual(testKey.readStringValue(TESTDATA_STRNAME), TESTDATA_STRVALUE); + + strictEqual( + testKey.getValueType(TESTDATA_INTNAME), + nsIWindowsRegKey.TYPE_INT + ); + strictEqual(testKey.readIntValue(TESTDATA_INTNAME), TESTDATA_INTVALUE); + + strictEqual( + testKey.getValueType(TESTDATA_INT64NAME), + nsIWindowsRegKey.TYPE_INT64 + ); + strictEqual(testKey.readInt64Value(TESTDATA_INT64NAME), TESTDATA_INT64VALUE); + + strictEqual( + testKey.getValueType(TESTDATA_BINARYNAME), + nsIWindowsRegKey.TYPE_BINARY + ); + strictEqual( + testKey.readBinaryValue(TESTDATA_BINARYNAME), + TESTDATA_BINARYVALUE + ); +} + +function test_invalidread_functions(testKey) { + try { + testKey.readIntValue(TESTDATA_STRNAME); + do_throw("Reading an integer from a string registry value should throw."); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + try { + let val = testKey.readStringValue(TESTDATA_INTNAME); + do_throw( + "Reading an string from an Int registry value should throw." + val + ); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + try { + testKey.readStringValue(TESTDATA_INT64NAME); + do_throw("Reading an string from an Int64 registry value should throw."); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + try { + testKey.readStringValue(TESTDATA_BINARYNAME); + do_throw("Reading a string from an Binary registry value should throw."); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } +} + +function test_childkey_functions(testKey) { + strictEqual(testKey.childCount, 0); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false); + + let childKey = testKey.createChild( + TESTDATA_CHILD_KEY, + nsIWindowsRegKey.ACCESS_ALL + ); + childKey.close(); + + strictEqual(testKey.childCount, 1); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), true); + strictEqual(testKey.getChildName(0), TESTDATA_CHILD_KEY); + + childKey = testKey.openChild(TESTDATA_CHILD_KEY, nsIWindowsRegKey.ACCESS_ALL); + testKey.removeChild(TESTDATA_CHILD_KEY); + strictEqual(testKey.childCount, 0); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false); +} + +function cleanup_test_run(testKey, keyName) { + info("Cleaning up test."); + + for (var i = 0; i < testKey.childCount; i++) { + testKey.removeChild(testKey.getChildName(i)); + } + testKey.close(); + + let baseKey = regKeyComponent.createInstance(nsIWindowsRegKey); + baseKey.open( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + BASE_PATH, + nsIWindowsRegKey.ACCESS_ALL + ); + baseKey.removeChild(TESTDATA_KEYNAME); + baseKey.close(); +} + +// Test data used above. +const BASE_PATH = "SOFTWARE"; +const TESTDATA_KEYNAME = "TestRegXPC"; +const TESTDATA_STRNAME = "AString"; +const TESTDATA_STRVALUE = "The quick brown fox jumps over the lazy dog."; +const TESTDATA_INTNAME = "AnInteger"; +const TESTDATA_INTVALUE = 65536; +const TESTDATA_INT64NAME = "AnInt64"; +const TESTDATA_INT64VALUE = 9223372036854775000; +const TESTDATA_BINARYNAME = "ABinary"; +const TESTDATA_BINARYVALUE = "She sells seashells by the seashore"; +const TESTDATA_CHILD_KEY = "TestChildKey"; diff --git a/xpcom/tests/unit/xpcshell.toml b/xpcom/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..a542804b64 --- /dev/null +++ b/xpcom/tests/unit/xpcshell.toml @@ -0,0 +1,113 @@ +[DEFAULT] +head = "head_xpcom.js" +support-files = [ + "data/**", + "xpcomtest.xpt", +] +generated-files = "xpcomtest.xpt" + +["test_bug121341.js"] + +["test_bug325418.js"] + +["test_bug332389.js"] + +["test_bug333505.js"] + +["test_bug364285-1.js"] + +["test_bug374754.js"] + +["test_bug476919.js"] +# Creating a symlink requires admin or developer mode on Windows. +skip-if = ["os == 'win'"] +# Bug 676998: test fails consistently on Android +fail-if = ["os == 'android'"] + +["test_bug478086.js"] + +["test_bug745466.js"] +skip-if = ["os == 'win'"] +# Bug 676998: test fails consistently on Android +fail-if = ["os == 'android'"] + +["test_bug1434856.js"] + +["test_console_service_callFunctionAndLogException.js"] + +["test_debugger_malloc_size_of.js"] + +["test_file_createUnique.js"] + +["test_file_equality.js"] + +["test_file_renameTo.js"] + +["test_getTimers.js"] +skip-if = ["os == 'android'"] + +["test_hidden_files.js"] + +["test_home.js"] +# Bug 676998: test fails consistently on Android +fail-if = ["os == 'android'"] + +["test_iniParser.js"] + +["test_ioutil.js"] + +["test_localfile.js"] + +["test_mac_bundle.js"] + +["test_mac_xattrs.js"] +run-if = ["os == 'mac'"] + +["test_notxpcom_scriptable.js"] + +["test_nsIMutableArray.js"] + +["test_nsIProcess.js"] +skip-if = [ + "os == 'win'", # Bug 1325609 + "os == 'linux'", # Bug 676998 + "os == 'android'", # Bug 1631671 +] + +["test_nsIProcess_stress.js"] +skip-if = [ + "os == 'win'", # bug 676412, test isn't needed on windows and runs really slowly + "ccov", # bug 1394989 +] + +["test_pipe.js"] + +["test_process_directives.js"] + +["test_process_directives_child.js"] +skip-if = ["os == 'android'"] + +["test_seek_multiplex.js"] + +["test_storagestream.js"] + +["test_streams.js"] + +["test_stringstream.js"] + +["test_symlinks.js"] +# Creating a symlink requires admin or developer mode on Windows. +skip-if = ["os == 'win'"] +# Bug 676998: test fails consistently on Android +fail-if = ["os == 'android'"] + +["test_systemInfo.js"] + +["test_versioncomparator.js"] +skip-if = ["os == 'win' && asan"] # Bug 1763002 + +["test_windows_cmdline_file.js"] +run-if = ["os == 'win'"] + +["test_windows_registry.js"] +run-if = ["os == 'win'"] diff --git a/xpcom/tests/windows/TestCOM.cpp b/xpcom/tests/windows/TestCOM.cpp new file mode 100644 index 0000000000..49f67db835 --- /dev/null +++ b/xpcom/tests/windows/TestCOM.cpp @@ -0,0 +1,97 @@ +/* -*- 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 <windows.h> +#include <unknwn.h> +#include <stdio.h> +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/RefPtr.h" + +// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN +#include <unknwn.h> + +#include "gtest/gtest.h" + +// {5846BA30-B856-11d1-A98A-00805F8A7AC4} +#define NS_ITEST_COM_IID \ + { \ + 0x5846ba30, 0xb856, 0x11d1, { \ + 0xa9, 0x8a, 0x0, 0x80, 0x5f, 0x8a, 0x7a, 0xc4 \ + } \ + } + +class nsITestCom : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEST_COM_IID) + NS_IMETHOD Test() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITestCom, NS_ITEST_COM_IID) + +/* + * nsTestCom + */ + +class nsTestCom final : public nsITestCom { + NS_DECL_ISUPPORTS + + public: + nsTestCom() {} + + NS_IMETHOD Test() override { return NS_OK; } + + static int sDestructions; + + private: + ~nsTestCom() { sDestructions++; } +}; + +int nsTestCom::sDestructions; + +NS_IMPL_QUERY_INTERFACE(nsTestCom, nsITestCom) + +MozExternalRefCountType nsTestCom::AddRef() { + nsrefcnt res = ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "nsTestCom", sizeof(*this)); + return res; +} + +MozExternalRefCountType nsTestCom::Release() { + nsrefcnt res = --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "nsTestCom"); + if (res == 0) { + delete this; + } + return res; +} + +TEST(TestCOM, WindowsInterop) +{ + // Test that we can QI an nsITestCom to an IUnknown. + RefPtr<nsTestCom> t = new nsTestCom(); + IUnknown* iUnknown = nullptr; + nsresult rv = t->QueryInterface(NS_GET_IID(nsISupports), (void**)&iUnknown); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(iUnknown); + + // Test we can QI an IUnknown to nsITestCom. + nsCOMPtr<nsITestCom> iTestCom; + GUID testGUID = NS_ITEST_COM_IID; + HRESULT hr = iUnknown->QueryInterface(testGUID, getter_AddRefs(iTestCom)); + ASSERT_TRUE(SUCCEEDED(hr)); + ASSERT_TRUE(iTestCom); + + // Make sure we can call our test function (and the pointer is valid). + rv = iTestCom->Test(); + ASSERT_NS_SUCCEEDED(rv); + + iUnknown->Release(); + iTestCom = nullptr; + t = nullptr; + + ASSERT_EQ(nsTestCom::sDestructions, 1); +} diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp new file mode 100644 index 0000000000..ac29ddac50 --- /dev/null +++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp @@ -0,0 +1,176 @@ +/* -*- 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 <windows.h> +#include <winnetwk.h> + +#include "mozilla/FileUtilsWin.h" +#include "mozilla/DebugOnly.h" +#include "nsCRTGlue.h" + +#include "gtest/gtest.h" + +class DriveMapping { + public: + explicit DriveMapping(const nsAString& aRemoteUNCPath); + ~DriveMapping(); + + bool Init(); + bool ChangeDriveLetter(); + wchar_t GetDriveLetter() { return mDriveLetter; } + + private: + bool DoMapping(); + void Disconnect(wchar_t aDriveLetter); + + wchar_t mDriveLetter; + nsString mRemoteUNCPath; +}; + +DriveMapping::DriveMapping(const nsAString& aRemoteUNCPath) + : mDriveLetter(0), mRemoteUNCPath(aRemoteUNCPath) {} + +bool DriveMapping::Init() { + if (mDriveLetter) { + return false; + } + return DoMapping(); +} + +bool DriveMapping::DoMapping() { + wchar_t drvTemplate[] = L" :"; + NETRESOURCEW netRes = {0}; + netRes.dwType = RESOURCETYPE_DISK; + netRes.lpLocalName = drvTemplate; + netRes.lpRemoteName = + reinterpret_cast<wchar_t*>(mRemoteUNCPath.BeginWriting()); + wchar_t driveLetter = L'D'; + DWORD result = NO_ERROR; + do { + drvTemplate[0] = driveLetter; + result = WNetAddConnection2W(&netRes, nullptr, nullptr, CONNECT_TEMPORARY); + } while (result == ERROR_ALREADY_ASSIGNED && ++driveLetter <= L'Z'); + if (result != NO_ERROR) { + return false; + } + mDriveLetter = driveLetter; + return true; +} + +bool DriveMapping::ChangeDriveLetter() { + wchar_t prevDriveLetter = mDriveLetter; + bool result = DoMapping(); + MOZ_RELEASE_ASSERT(mDriveLetter != prevDriveLetter); + if (result && prevDriveLetter) { + Disconnect(prevDriveLetter); + } + return result; +} + +void DriveMapping::Disconnect(wchar_t aDriveLetter) { + wchar_t drvTemplate[] = {aDriveLetter, L':', L'\0'}; + DWORD result = WNetCancelConnection2W(drvTemplate, 0, TRUE); + MOZ_RELEASE_ASSERT(result == NO_ERROR); +} + +DriveMapping::~DriveMapping() { + if (mDriveLetter) { + Disconnect(mDriveLetter); + } +} + +bool DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath) { + const wchar_t drvTpl[] = {aDriveLetter, L':', L'\0'}; + aNtPath.SetLength(MAX_PATH); + DWORD pathLen; + while (true) { + pathLen = QueryDosDeviceW( + drvTpl, reinterpret_cast<wchar_t*>(aNtPath.BeginWriting()), + aNtPath.Length()); + if (pathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + aNtPath.SetLength(aNtPath.Length() * 2); + } + if (!pathLen) { + return false; + } + // aNtPath contains embedded NULLs, so we need to figure out the real length + // via wcslen. + aNtPath.SetLength(NS_strlen(aNtPath.BeginReading())); + return true; +} + +bool TestNtPathToDosPath(const wchar_t* aNtPath, + const wchar_t* aExpectedDosPath) { + nsAutoString output; + bool result = mozilla::NtPathToDosPath(nsDependentString(aNtPath), output); + return result && output == reinterpret_cast<const nsAString::char_type*>( + aExpectedDosPath); +} + +TEST(NtPathToDosPath, Tests) +{ + nsAutoString cDrive; + ASSERT_TRUE(DriveToNtPath(L'C', cDrive)); + + // empty string + EXPECT_TRUE(TestNtPathToDosPath(L"", L"")); + + // non-existent device, must fail + EXPECT_FALSE( + TestNtPathToDosPath(L"\\Device\\ThisDeviceDoesNotExist\\Foo", nullptr)); + + // base case + nsAutoString testPath(cDrive); + testPath.Append(L"\\Program Files"); + EXPECT_TRUE(TestNtPathToDosPath(testPath.get(), L"C:\\Program Files")); + + // short filename + nsAutoString ntShortName(cDrive); + ntShortName.Append(L"\\progra~1"); + EXPECT_TRUE(TestNtPathToDosPath(ntShortName.get(), L"C:\\Program Files")); + + // drive letters as symbolic links (NtCreateFile uses these) + EXPECT_TRUE(TestNtPathToDosPath(L"\\??\\C:\\Foo", L"C:\\Foo")); + + // other symbolic links (should fail) + EXPECT_FALSE(TestNtPathToDosPath(L"\\??\\MountPointManager", nullptr)); + + // socket (should fail) + EXPECT_FALSE(TestNtPathToDosPath(L"\\Device\\Afd\\Endpoint", nullptr)); + + // UNC path (using MUP) + EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\Mup\\127.0.0.1\\C$", + L"\\\\127.0.0.1\\C$")); + + // UNC path (using LanmanRedirector) + EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\LanmanRedirector\\127.0.0.1\\C$", + L"\\\\127.0.0.1\\C$")); + + DriveMapping drvMapping(u"\\\\127.0.0.1\\C$"_ns); + // Only run these tests if we were able to map; some machines don't have perms + if (drvMapping.Init()) { + wchar_t expected[] = L" :\\"; + expected[0] = drvMapping.GetDriveLetter(); + nsAutoString networkPath; + ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)); + + networkPath += u"\\"; + EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected)); + + // NtPathToDosPath must correctly handle paths whose drive letter mapping + // has changed. We need to test this because the APIs called by + // NtPathToDosPath return different info if this has happened. + ASSERT_TRUE(drvMapping.ChangeDriveLetter()); + + expected[0] = drvMapping.GetDriveLetter(); + ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)); + + networkPath += u"\\"; + EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected)); + } +} diff --git a/xpcom/tests/windows/TestPoisonIOInterposer.cpp b/xpcom/tests/windows/TestPoisonIOInterposer.cpp new file mode 100644 index 0000000000..9b29307f34 --- /dev/null +++ b/xpcom/tests/windows/TestPoisonIOInterposer.cpp @@ -0,0 +1,155 @@ +/* -*- 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 <windows.h> + +#include "gtest/gtest.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/NativeNt.h" +#include "nsWindowsHelpers.h" + +using namespace mozilla; + +class TempFile final { + wchar_t mFullPath[MAX_PATH + 1]; + + public: + TempFile() : mFullPath{0} { + wchar_t tempDir[MAX_PATH + 1]; + DWORD len = ::GetTempPathW(ArrayLength(tempDir), tempDir); + if (!len) { + return; + } + + len = ::GetTempFileNameW(tempDir, L"poi", 0, mFullPath); + if (!len) { + return; + } + } + + operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; } +}; + +class Overlapped final { + nsAutoHandle mEvent; + OVERLAPPED mOverlapped; + + public: + Overlapped() + : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)), mOverlapped{} { + mOverlapped.hEvent = mEvent.get(); + } + + operator OVERLAPPED*() { return &mOverlapped; } + + bool Wait(HANDLE aHandle) { + DWORD numBytes; + if (!::GetOverlappedResult(aHandle, &mOverlapped, &numBytes, TRUE)) { + return false; + } + + return true; + } +}; + +const uint32_t kMagic = 0x12345678; + +void FileOpSync(const wchar_t* aPath) { + nsAutoHandle file(::CreateFileW(aPath, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, nullptr)); + ASSERT_NE(file.get(), INVALID_HANDLE_VALUE); + + DWORD buffer = kMagic, numBytes = 0; + OVERLAPPED seek = {}; + EXPECT_TRUE(WriteFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek)); + EXPECT_TRUE(::FlushFileBuffers(file.get())); + + seek.Offset = 0; + buffer = 0; + EXPECT_TRUE( + ::ReadFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek)); + EXPECT_EQ(buffer, kMagic); + + WIN32_FILE_ATTRIBUTE_DATA fullAttr = {}; + EXPECT_TRUE(::GetFileAttributesExW(aPath, GetFileExInfoStandard, &fullAttr)); +} + +void FileOpAsync(const wchar_t* aPath) { + constexpr int kNumPages = 10; + constexpr int kPageSize = 4096; + + Array<UniquePtr<void, VirtualFreeDeleter>, kNumPages> pages; + Array<FILE_SEGMENT_ELEMENT, kNumPages + 1> segments; + for (int i = 0; i < kNumPages; ++i) { + auto p = reinterpret_cast<uint32_t*>(::VirtualAlloc( + nullptr, kPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + ASSERT_TRUE(p); + + pages[i].reset(p); + segments[i].Buffer = p; + + p[0] = kMagic; + } + + nsAutoHandle file(::CreateFileW( + aPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, + nullptr)); + ASSERT_NE(file.get(), INVALID_HANDLE_VALUE); + + Overlapped writeOp; + if (!::WriteFileGather(file.get(), segments.begin(), kNumPages * kPageSize, + nullptr, writeOp)) { + EXPECT_EQ(::GetLastError(), static_cast<DWORD>(ERROR_IO_PENDING)); + EXPECT_TRUE(writeOp.Wait(file.get())); + } + + for (int i = 0; i < kNumPages; ++i) { + *reinterpret_cast<uint32_t*>(pages[i].get()) = 0; + } + + Overlapped readOp; + if (!::ReadFileScatter(file.get(), segments.begin(), kNumPages * kPageSize, + nullptr, readOp)) { + EXPECT_EQ(::GetLastError(), static_cast<DWORD>(ERROR_IO_PENDING)); + EXPECT_TRUE(readOp.Wait(file.get())); + } + + for (int i = 0; i < kNumPages; ++i) { + EXPECT_EQ(*reinterpret_cast<uint32_t*>(pages[i].get()), kMagic); + } +} + +TEST(PoisonIOInterposer, NormalThread) +{ + mozilla::AutoIOInterposer ioInterposerGuard; + ioInterposerGuard.Init(); + + TempFile tempFile; + FileOpSync(tempFile); + FileOpAsync(tempFile); + EXPECT_TRUE(::DeleteFileW(tempFile)); +} + +TEST(PoisonIOInterposer, NullTlsPointer) +{ + void* originalTls = mozilla::nt::RtlGetThreadLocalStoragePointer(); + mozilla::AutoIOInterposer ioInterposerGuard; + ioInterposerGuard.Init(); + + // Simulate a loader worker thread (TEB::LoaderWorker = 1) + // where ThreadLocalStorage is never allocated. + mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(nullptr); + + TempFile tempFile; + FileOpSync(tempFile); + FileOpAsync(tempFile); + EXPECT_TRUE(::DeleteFileW(tempFile)); + + mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(originalTls); +} diff --git a/xpcom/tests/windows/moz.build b/xpcom/tests/windows/moz.build new file mode 100644 index 0000000000..d77ec264e1 --- /dev/null +++ b/xpcom/tests/windows/moz.build @@ -0,0 +1,17 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + "TestCOM.cpp", + "TestNtPathToDosPath.cpp", + "TestPoisonIOInterposer.cpp", +] + +OS_LIBS += [ + "mpr", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/xpcom/threads/AbstractThread.cpp b/xpcom/threads/AbstractThread.cpp new file mode 100644 index 0000000000..61289a6789 --- /dev/null +++ b/xpcom/threads/AbstractThread.cpp @@ -0,0 +1,359 @@ +/* -*- 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/AbstractThread.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DelayedRunnable.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file. +#include "mozilla/ProfilerRunnable.h" +#include "mozilla/StateWatching.h" // We initialize the StateWatching logging in this file. +#include "mozilla/StaticPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsContentUtils.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsIThreadInternal.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include <memory> + +namespace mozilla { + +LazyLogModule gMozPromiseLog("MozPromise"); +LazyLogModule gStateWatchingLog("StateWatching"); + +StaticRefPtr<AbstractThread> sMainThread; +MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS; + +class XPCOMThreadWrapper final : public AbstractThread, + public nsIThreadObserver, + public nsIDirectTaskDispatcher { + public: + XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch, + bool aOnThread) + : AbstractThread(aRequireTailDispatch), + mThread(aThread), + mDirectTaskDispatcher(do_QueryInterface(aThread)), + mOnThread(aOnThread) { + MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher); + MOZ_DIAGNOSTIC_ASSERT(!aOnThread || IsCurrentThreadIn()); + if (aOnThread) { + MOZ_ASSERT(!sCurrentThreadTLS.get(), + "There can only be a single XPCOMThreadWrapper available on a " + "thread"); + // Set the default current thread so that GetCurrent() never returns + // nullptr. + sCurrentThreadTLS.set(this); + } + } + + NS_DECL_THREADSAFE_ISUPPORTS + + nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable, + DispatchReason aReason = NormalDispatch) override { + nsCOMPtr<nsIRunnable> r = aRunnable; + AbstractThread* currentThread; + if (aReason != TailDispatch && (currentThread = GetCurrent()) && + RequiresTailDispatch(currentThread) && + currentThread->IsTailDispatcherAvailable()) { + return currentThread->TailDispatcher().AddTask(this, r.forget()); + } + + // At a certain point during shutdown, we stop processing events from the + // main thread event queue (this happens long after all _other_ XPCOM + // threads have been shut down). However, various bits of subsequent + // teardown logic (the media shutdown blocker and the final shutdown cycle + // collection) can trigger state watching and state mirroring notifications + // that result in dispatch to the main thread. This causes shutdown leaks, + // because the |Runner| wrapper below creates a guaranteed cycle + // (Thread->EventQueue->Runnable->Thread) until the event is processed. So + // if we put the event into a queue that will never be processed, we'll wind + // up with a leak. + // + // We opt to just release the runnable in that case. Ordinarily, this + // approach could cause problems for runnables that are only safe to be + // released on the target thread (and not the dispatching thread). This is + // why XPCOM thread dispatch explicitly leaks the runnable when dispatch + // fails, rather than releasing it. But given that this condition only + // applies very late in shutdown when only one thread remains operational, + // that concern is unlikely to apply. + if (gXPCOMMainThreadEventsAreDoomed) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsIRunnable> runner = new Runner(this, r.forget()); + return mThread->Dispatch(runner.forget(), NS_DISPATCH_NORMAL); + } + + // Prevent a GCC warning about the other overload of Dispatch being hidden. + using AbstractThread::Dispatch; + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override { + return mThread->RegisterShutdownTask(aTask); + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override { + return mThread->UnregisterShutdownTask(aTask); + } + + bool IsCurrentThreadIn() const override { + return mThread->IsOnCurrentThread(); + } + + TaskDispatcher& TailDispatcher() override { + MOZ_ASSERT(IsCurrentThreadIn()); + MOZ_ASSERT(IsTailDispatcherAvailable()); + if (!mTailDispatcher) { + mTailDispatcher = + std::make_unique<AutoTaskDispatcher>(mDirectTaskDispatcher, + /* aIsTailDispatcher = */ true); + mThread->AddObserver(this); + } + + return *mTailDispatcher; + } + + bool IsTailDispatcherAvailable() override { + // Our tail dispatching implementation relies on nsIThreadObserver + // callbacks. If we're not doing event processing, it won't work. + bool inEventLoop = + static_cast<nsThread*>(mThread.get())->RecursionDepth() > 0; + return inEventLoop; + } + + bool MightHaveTailTasks() override { return !!mTailDispatcher; } + + nsIEventTarget* AsEventTarget() override { return mThread; } + + //----------------------------------------------------------------------------- + // nsIThreadObserver + //----------------------------------------------------------------------------- + NS_IMETHOD OnDispatchedEvent() override { return NS_OK; } + + NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread, + bool eventWasProcessed) override { + // This is the primary case. + MaybeFireTailDispatcher(); + return NS_OK; + } + + NS_IMETHOD OnProcessNextEvent(nsIThreadInternal* thread, + bool mayWait) override { + // In general, the tail dispatcher is handled at the end of the current in + // AfterProcessNextEvent() above. However, if start spinning a nested event + // loop, it's generally better to fire the tail dispatcher before the first + // nested event, rather than after it. This check handles that case. + MaybeFireTailDispatcher(); + return NS_OK; + } + + //----------------------------------------------------------------------------- + // nsIDirectTaskDispatcher + //----------------------------------------------------------------------------- + // Forward calls to nsIDirectTaskDispatcher to the underlying nsThread object. + // We can't use the generated NS_FORWARD_NSIDIRECTTASKDISPATCHER macro + // as already_AddRefed type must be moved. + NS_IMETHOD DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) override { + return mDirectTaskDispatcher->DispatchDirectTask(std::move(aEvent)); + } + NS_IMETHOD DrainDirectTasks() override { + return mDirectTaskDispatcher->DrainDirectTasks(); + } + NS_IMETHOD HaveDirectTasks(bool* aResult) override { + return mDirectTaskDispatcher->HaveDirectTasks(aResult); + } + + private: + const RefPtr<nsIThreadInternal> mThread; + const nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher; + std::unique_ptr<AutoTaskDispatcher> mTailDispatcher; + const bool mOnThread; + + ~XPCOMThreadWrapper() { + if (mOnThread) { + MOZ_DIAGNOSTIC_ASSERT(IsCurrentThreadIn(), + "Must be destroyed on the thread it was created"); + sCurrentThreadTLS.set(nullptr); + } + } + + void MaybeFireTailDispatcher() { + if (mTailDispatcher) { + mTailDispatcher->DrainDirectTasks(); + mThread->RemoveObserver(this); + mTailDispatcher.reset(); + } + } + + class Runner : public Runnable { + public: + explicit Runner(XPCOMThreadWrapper* aThread, + already_AddRefed<nsIRunnable> aRunnable) + : Runnable("XPCOMThreadWrapper::Runner"), + mThread(aThread), + mRunnable(aRunnable) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(mThread == AbstractThread::GetCurrent()); + MOZ_ASSERT(mThread->IsCurrentThreadIn()); + SerialEventTargetGuard guard(mThread); + AUTO_PROFILE_FOLLOWING_RUNNABLE(mRunnable); + return mRunnable->Run(); + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("AbstractThread::Runner"); + if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) { + nsAutoCString name; + named->GetName(name); + if (!name.IsEmpty()) { + aName.AppendLiteral(" for "); + aName.Append(name); + } + } + return NS_OK; + } +#endif + + private: + const RefPtr<XPCOMThreadWrapper> mThread; + const RefPtr<nsIRunnable> mRunnable; + }; +}; + +NS_IMPL_ISUPPORTS(XPCOMThreadWrapper, nsIThreadObserver, + nsIDirectTaskDispatcher, nsISerialEventTarget, nsIEventTarget) + +NS_IMETHODIMP_(bool) +AbstractThread::IsOnCurrentThreadInfallible() { return IsCurrentThreadIn(); } + +NS_IMETHODIMP +AbstractThread::IsOnCurrentThread(bool* aResult) { + *aResult = IsCurrentThreadIn(); + return NS_OK; +} + +NS_IMETHODIMP +AbstractThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +AbstractThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + return Dispatch(std::move(aEvent), NormalDispatch); +} + +NS_IMETHODIMP +AbstractThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelayMs) { + nsCOMPtr<nsIRunnable> event = aEvent; + NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED); + + RefPtr<DelayedRunnable> r = + new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs); + nsresult rv = r->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + return Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +nsresult AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) { + if (MightHaveTailTasks()) { + return TailDispatcher().DispatchTasksFor(aThread); + } + + return NS_OK; +} + +bool AbstractThread::HasTailTasksFor(AbstractThread* aThread) { + if (!MightHaveTailTasks()) { + return false; + } + return TailDispatcher().HasTasksFor(aThread); +} + +bool AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const { + MOZ_ASSERT(aThread); + // We require tail dispatch if both the source and destination + // threads support it. + return SupportsTailDispatch() && aThread->SupportsTailDispatch(); +} + +bool AbstractThread::RequiresTailDispatchFromCurrentThread() const { + AbstractThread* current = GetCurrent(); + return current && RequiresTailDispatch(current); +} + +AbstractThread* AbstractThread::MainThread() { + MOZ_ASSERT(sMainThread); + return sMainThread; +} + +void AbstractThread::InitTLS() { + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } +} + +void AbstractThread::InitMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sMainThread); + nsCOMPtr<nsIThreadInternal> mainThread = + do_QueryInterface(nsThreadManager::get().GetMainThreadWeak()); + MOZ_DIAGNOSTIC_ASSERT(mainThread); + + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } + sMainThread = new XPCOMThreadWrapper(mainThread.get(), + /* aRequireTailDispatch = */ true, + true /* onThread */); +} + +void AbstractThread::ShutdownMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + sMainThread = nullptr; +} + +void AbstractThread::DispatchStateChange( + already_AddRefed<nsIRunnable> aRunnable) { + AbstractThread* currentThread = GetCurrent(); + MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist"); + if (currentThread->IsTailDispatcherAvailable()) { + currentThread->TailDispatcher().AddStateChangeTask(this, + std::move(aRunnable)); + } else { + // If the tail dispatcher isn't available, we just avoid sending state + // updates. + // + // This happens, specifically (1) During async shutdown (via the media + // shutdown blocker), and (2) During the final shutdown cycle collection. + // Both of these trigger changes to various watched and mirrored state. + nsCOMPtr<nsIRunnable> neverDispatched = aRunnable; + } +} + +/* static */ +void AbstractThread::DispatchDirectTask( + already_AddRefed<nsIRunnable> aRunnable) { + AbstractThread* currentThread = GetCurrent(); + MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist"); + if (currentThread->IsTailDispatcherAvailable()) { + currentThread->TailDispatcher().AddDirectTask(std::move(aRunnable)); + } else { + // If the tail dispatcher isn't available, we post as a regular task. + currentThread->Dispatch(std::move(aRunnable)); + } +} + +} // namespace mozilla diff --git a/xpcom/threads/AbstractThread.h b/xpcom/threads/AbstractThread.h new file mode 100644 index 0000000000..b53bcf8ca3 --- /dev/null +++ b/xpcom/threads/AbstractThread.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(AbstractThread_h_) +# define AbstractThread_h_ + +# include "mozilla/AlreadyAddRefed.h" +# include "mozilla/ThreadLocal.h" +# include "nscore.h" +# include "nsISerialEventTarget.h" +# include "nsISupports.h" + +class nsIEventTarget; +class nsIRunnable; +class nsIThread; + +namespace mozilla { + +class TaskDispatcher; + +/* + * We often want to run tasks on a target that guarantees that events will never + * run in parallel. There are various target types that achieve this - namely + * nsIThread and TaskQueue. Note that nsIThreadPool (which implements + * nsIEventTarget) does not have this property, so we do not want to use + * nsIEventTarget for this purpose. This class encapsulates the specifics of + * the structures we might use here and provides a consistent interface. + * + * At present, the supported AbstractThread implementations are TaskQueue, + * AbstractThread::MainThread() and XPCOMThreadWrapper which can wrap any + * nsThread. + * + * The primary use of XPCOMThreadWrapper is to allow any threads to provide + * Direct Task dispatching which is similar (but not identical to) the microtask + * semantics of JS promises. Instantiating a XPCOMThreadWrapper on the current + * nsThread is sufficient to enable direct task dispatching. + * + * You shouldn't use pointers when comparing AbstractThread or nsIThread to + * determine if you are currently on the thread, but instead use the + * nsISerialEventTarget::IsOnCurrentThread() method. + */ +class AbstractThread : public nsISerialEventTarget { + public: + // Returns the AbstractThread that the caller is currently running in, or null + // if the caller is not running in an AbstractThread. + static AbstractThread* GetCurrent() { return sCurrentThreadTLS.get(); } + + AbstractThread(bool aSupportsTailDispatch) + : mSupportsTailDispatch(aSupportsTailDispatch) {} + + // We don't use NS_DECL_NSIEVENTTARGET so that we can remove the default + // |flags| parameter from Dispatch. Otherwise, a single-argument Dispatch call + // would be ambiguous. + using nsISerialEventTarget::IsOnCurrentThread; + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override; + NS_IMETHOD IsOnCurrentThread(bool* _retval) override; + NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event, + uint32_t flags) override; + NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override; + NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable> event, + uint32_t delay) override; + + enum DispatchReason { NormalDispatch, TailDispatch }; + virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable, + DispatchReason aReason = NormalDispatch) = 0; + + virtual bool IsCurrentThreadIn() const = 0; + + // Returns a TaskDispatcher that will dispatch its tasks when the currently- + // running tasks pops off the stack. + // + // May only be called when running within the it is invoked up, and only on + // threads which support it. + virtual TaskDispatcher& TailDispatcher() = 0; + + // Returns true if we have tail tasks scheduled, or if this isn't known. + // Returns false if we definitely don't have any tail tasks. + virtual bool MightHaveTailTasks() { return true; } + + // Returns true if the tail dispatcher is available. In certain edge cases + // like shutdown, it might not be. + virtual bool IsTailDispatcherAvailable() { return true; } + + // Helper functions for methods on the tail TasklDispatcher. These check + // HasTailTasks to avoid allocating a TailDispatcher if it isn't + // needed. + nsresult TailDispatchTasksFor(AbstractThread* aThread); + bool HasTailTasksFor(AbstractThread* aThread); + + // Returns true if this supports the tail dispatcher. + bool SupportsTailDispatch() const { return mSupportsTailDispatch; } + + // Returns true if this thread requires all dispatches originating from + // aThread go through the tail dispatcher. + bool RequiresTailDispatch(AbstractThread* aThread) const; + bool RequiresTailDispatchFromCurrentThread() const; + + virtual nsIEventTarget* AsEventTarget() { MOZ_CRASH("Not an event target!"); } + + // Returns the non-DocGroup version of AbstractThread on the main thread. + // A DocGroup-versioned one is available in + // DispatcherTrait::AbstractThreadFor(). Note: + // DispatcherTrait::AbstractThreadFor() SHALL be used when possible. + static AbstractThread* MainThread(); + + // Must be called exactly once during startup. + static void InitTLS(); + static void InitMainThread(); + static void ShutdownMainThread(); + + void DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable); + + static void DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable); + + protected: + virtual ~AbstractThread() = default; + static MOZ_THREAD_LOCAL(AbstractThread*) sCurrentThreadTLS; + + // True if we want to require that every task dispatched from tasks running in + // this queue go through our queue's tail dispatcher. + const bool mSupportsTailDispatch; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/BlockingResourceBase.cpp b/xpcom/threads/BlockingResourceBase.cpp new file mode 100644 index 0000000000..c2ba82e07a --- /dev/null +++ b/xpcom/threads/BlockingResourceBase.cpp @@ -0,0 +1,547 @@ +/* -*- 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/BlockingResourceBase.h" + +#ifdef DEBUG +# include "prthread.h" + +# ifndef MOZ_CALLSTACK_DISABLED +# include "CodeAddressService.h" +# include "nsHashKeys.h" +# include "mozilla/StackWalk.h" +# include "nsTHashtable.h" +# endif + +# include "mozilla/Attributes.h" +# include "mozilla/CondVar.h" +# include "mozilla/DeadlockDetector.h" +# include "mozilla/RecursiveMutex.h" +# include "mozilla/ReentrantMonitor.h" +# include "mozilla/Mutex.h" +# include "mozilla/RWLock.h" +# include "mozilla/UniquePtr.h" + +# if defined(MOZILLA_INTERNAL_API) +# include "mozilla/ProfilerThreadSleep.h" +# endif // MOZILLA_INTERNAL_API + +#endif // ifdef DEBUG + +namespace mozilla { +// +// BlockingResourceBase implementation +// + +// static members +const char* const BlockingResourceBase::kResourceTypeName[] = { + // needs to be kept in sync with BlockingResourceType + "Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"}; + +#ifdef DEBUG + +PRCallOnceType BlockingResourceBase::sCallOnce; +MOZ_THREAD_LOCAL(BlockingResourceBase*) +BlockingResourceBase::sResourceAcqnChainFront; +BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector; + +void BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc, + void* aSp, void* aClosure) { +# ifndef MOZ_CALLSTACK_DISABLED + AcquisitionState* state = (AcquisitionState*)aClosure; + state->ref().AppendElement(aPc); +# endif +} + +void BlockingResourceBase::GetStackTrace(AcquisitionState& aState, + const void* aFirstFramePC) { +# ifndef MOZ_CALLSTACK_DISABLED + // Clear the array... + aState.reset(); + // ...and create a new one; this also puts the state to 'acquired' status + // regardless of whether we obtain a stack trace or not. + aState.emplace(); + + MozStackWalk(StackWalkCallback, aFirstFramePC, kAcquisitionStateStackSize, + aState.ptr()); +# endif +} + +/** + * PrintCycle + * Append to |aOut| detailed information about the circular + * dependency in |aCycle|. Returns true if it *appears* that this + * cycle may represent an imminent deadlock, but this is merely a + * heuristic; the value returned may be a false positive or false + * negative. + * + * *NOT* thread safe. Calls |Print()|. + * + * FIXME bug 456272 hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but only + * some info is written into |aOut| + */ +static bool PrintCycle( + const BlockingResourceBase::DDT::ResourceAcquisitionArray& aCycle, + nsACString& aOut) { + NS_ASSERTION(aCycle.Length() > 1, "need > 1 element for cycle!"); + + bool maybeImminent = true; + + fputs("=== Cyclical dependency starts at\n", stderr); + aOut += "Cyclical dependency starts at\n"; + + const BlockingResourceBase::DDT::ResourceAcquisitionArray::value_type res = + aCycle.ElementAt(0); + maybeImminent &= res->Print(aOut); + + BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i; + BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len = + aCycle.Length(); + const BlockingResourceBase::DDT::ResourceAcquisitionArray::value_type* it = + 1 + aCycle.Elements(); + for (i = 1; i < len - 1; ++i, ++it) { + fputs("\n--- Next dependency:\n", stderr); + aOut += "\nNext dependency:\n"; + + maybeImminent &= (*it)->Print(aOut); + } + + fputs("\n=== Cycle completed at\n", stderr); + aOut += "Cycle completed at\n"; + (*it)->Print(aOut); + + return maybeImminent; +} + +bool BlockingResourceBase::Print(nsACString& aOut) const { + fprintf(stderr, "--- %s : %s", kResourceTypeName[mType], mName); + aOut += BlockingResourceBase::kResourceTypeName[mType]; + aOut += " : "; + aOut += mName; + + bool acquired = IsAcquired(); + + if (acquired) { + fputs(" (currently acquired)\n", stderr); + aOut += " (currently acquired)\n"; + } + + fputs(" calling context\n", stderr); +# ifdef MOZ_CALLSTACK_DISABLED + fputs(" [stack trace unavailable]\n", stderr); +# else + const AcquisitionState& state = acquired ? mAcquired : mFirstSeen; + + CodeAddressService<> addressService; + + for (uint32_t i = 0; i < state.ref().Length(); i++) { + const size_t kMaxLength = 1024; + char buffer[kMaxLength]; + addressService.GetLocation(i + 1, state.ref()[i], buffer, kMaxLength); + const char* fmt = " %s\n"; + aOut.AppendLiteral(" "); + aOut.Append(buffer); + aOut.AppendLiteral("\n"); + fprintf(stderr, fmt, buffer); + } + +# endif + + return acquired; +} + +BlockingResourceBase::BlockingResourceBase( + const char* aName, BlockingResourceBase::BlockingResourceType aType) + : mName(aName), + mType(aType) +# ifdef MOZ_CALLSTACK_DISABLED + , + mAcquired(false) +# else + , + mAcquired() +# endif +{ + MOZ_ASSERT(mName, "Name must be nonnull"); + // PR_CallOnce guaranatees that InitStatics is called in a + // thread-safe way + if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) { + MOZ_CRASH("can't initialize blocking resource static members"); + } + + mChainPrev = 0; + sDeadlockDetector->Add(this); +} + +BlockingResourceBase::~BlockingResourceBase() { + // we don't check for really obviously bad things like freeing + // Mutexes while they're still locked. it is assumed that the + // base class, or its underlying primitive, will check for such + // stupid mistakes. + mChainPrev = 0; // racy only for stupidly buggy client code + if (sDeadlockDetector) { + sDeadlockDetector->Remove(this); + } +} + +size_t BlockingResourceBase::SizeOfDeadlockDetector( + MallocSizeOf aMallocSizeOf) { + return sDeadlockDetector + ? sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf) + : 0; +} + +PRStatus BlockingResourceBase::InitStatics() { + MOZ_ASSERT(sResourceAcqnChainFront.init()); + sDeadlockDetector = new DDT(); + if (!sDeadlockDetector) { + MOZ_CRASH("can't allocate deadlock detector"); + } + return PR_SUCCESS; +} + +void BlockingResourceBase::Shutdown() { + delete sDeadlockDetector; + sDeadlockDetector = 0; +} + +MOZ_NEVER_INLINE void BlockingResourceBase::CheckAcquire() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + mozilla::UniquePtr<DDT::ResourceAcquisitionArray> cycle( + sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this)); + if (!cycle) { + return; + } + +# ifndef MOZ_CALLSTACK_DISABLED + // Update the current stack before printing. + GetStackTrace(mAcquired, CallerPC()); +# endif + + fputs("###!!! ERROR: Potential deadlock detected:\n", stderr); + nsAutoCString out("Potential deadlock detected:\n"); + bool maybeImminent = PrintCycle(*cycle, out); + + if (maybeImminent) { + fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr); + out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n"); + } else { + fputs("\nDeadlock may happen for some other execution\n\n", stderr); + out.AppendLiteral("\nDeadlock may happen for some other execution\n\n"); + } + + // Only error out if we think a deadlock is imminent. + if (maybeImminent) { + NS_ERROR(out.get()); + } else { + NS_WARNING(out.get()); + } +} + +MOZ_NEVER_INLINE void BlockingResourceBase::Acquire() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow Acquire()ing condvars"); + return; + } + NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource"); + + ResourceChainAppend(ResourceChainFront()); + +# ifdef MOZ_CALLSTACK_DISABLED + mAcquired = true; +# else + // Take a stack snapshot. + GetStackTrace(mAcquired, CallerPC()); + MOZ_ASSERT(IsAcquired()); + + if (!mFirstSeen) { + mFirstSeen = mAcquired.map( + [](AcquisitionState::ValueType& state) { return state.Clone(); }); + } +# endif +} + +void BlockingResourceBase::Release() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow Release()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + NS_ASSERTION(chainFront && IsAcquired(), + "Release()ing something that hasn't been Acquire()ed"); + + if (chainFront == this) { + ResourceChainRemove(); + } else { + // remove this resource from wherever it lives in the chain + // we walk backwards in order of acquisition: + // (1) ...node<-prev<-curr... + // / / + // (2) ...prev<-curr... + BlockingResourceBase* curr = chainFront; + BlockingResourceBase* prev = nullptr; + while (curr && (prev = curr->mChainPrev) && (prev != this)) { + curr = prev; + } + if (prev == this) { + curr->mChainPrev = prev->mChainPrev; + } + } + + ClearAcquisitionState(); +} + +// +// Debug implementation of (OffTheBooks)Mutex +void OffTheBooksMutex::Lock() { + CheckAcquire(); + this->lock(); + mOwningThread = PR_GetCurrentThread(); + Acquire(); +} + +bool OffTheBooksMutex::TryLock() { + bool locked = this->tryLock(); + if (locked) { + mOwningThread = PR_GetCurrentThread(); + Acquire(); + } + return locked; +} + +void OffTheBooksMutex::Unlock() { + Release(); + mOwningThread = nullptr; + this->unlock(); +} + +void OffTheBooksMutex::AssertCurrentThreadOwns() const { + MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread()); +} + +// +// Debug implementation of RWLock +// + +bool RWLock::TryReadLock() { + bool locked = this->detail::RWLockImpl::tryReadLock(); + MOZ_ASSERT_IF(locked, mOwningThread == nullptr); + return locked; +} + +void RWLock::ReadLock() { + // All we want to ensure here is that we're not attempting to acquire the + // read lock while this thread is holding the write lock. + CheckAcquire(); + this->detail::RWLockImpl::readLock(); + MOZ_ASSERT(mOwningThread == nullptr); +} + +void RWLock::ReadUnlock() { + MOZ_ASSERT(mOwningThread == nullptr); + this->detail::RWLockImpl::readUnlock(); +} + +bool RWLock::TryWriteLock() { + bool locked = this->detail::RWLockImpl::tryWriteLock(); + if (locked) { + mOwningThread = PR_GetCurrentThread(); + Acquire(); + } + return locked; +} + +void RWLock::WriteLock() { + CheckAcquire(); + this->detail::RWLockImpl::writeLock(); + mOwningThread = PR_GetCurrentThread(); + Acquire(); +} + +void RWLock::WriteUnlock() { + Release(); + mOwningThread = nullptr; + this->detail::RWLockImpl::writeUnlock(); +} + +// +// Debug implementation of ReentrantMonitor +void ReentrantMonitor::Enter() { + BlockingResourceBase* chainFront = ResourceChainFront(); + + // the code below implements monitor reentrancy semantics + + if (this == chainFront) { + // immediately re-entered the monitor: acceptable + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + + // this is sort of a hack around not recording the thread that + // owns this monitor + if (chainFront) { + for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br; + br = ResourceChainPrev(br)) { + if (br == this) { + NS_WARNING( + "Re-entering ReentrantMonitor after acquiring other resources."); + + // show the caller why this is potentially bad + CheckAcquire(); + + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(); + PR_EnterMonitor(mReentrantMonitor); + NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!"); + Acquire(); // protected by mReentrantMonitor + mEntryCount = 1; +} + +void ReentrantMonitor::Exit() { + if (--mEntryCount == 0) { + Release(); // protected by mReentrantMonitor + } + PRStatus status = PR_ExitMonitor(mReentrantMonitor); + NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()"); +} + +nsresult ReentrantMonitor::Wait(PRIntervalTime aInterval) { + AssertCurrentThreadIn(); + + // save monitor state and reset it to empty + int32_t savedEntryCount = mEntryCount; + AcquisitionState savedAcquisitionState = TakeAcquisitionState(); + BlockingResourceBase* savedChainPrev = mChainPrev; + mEntryCount = 0; + mChainPrev = 0; + + nsresult rv; + { +# if defined(MOZILLA_INTERNAL_API) + AUTO_PROFILER_THREAD_SLEEP; +# endif + // give up the monitor until we're back from Wait() + rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + + // restore saved state + mEntryCount = savedEntryCount; + SetAcquisitionState(std::move(savedAcquisitionState)); + mChainPrev = savedChainPrev; + + return rv; +} + +// +// Debug implementation of RecursiveMutex +void RecursiveMutex::Lock() { + BlockingResourceBase* chainFront = ResourceChainFront(); + + // the code below implements mutex reentrancy semantics + + if (this == chainFront) { + // immediately re-entered the mutex: acceptable + LockInternal(); + ++mEntryCount; + return; + } + + // this is sort of a hack around not recording the thread that + // owns this monitor + if (chainFront) { + for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br; + br = ResourceChainPrev(br)) { + if (br == this) { + NS_WARNING( + "Re-entering RecursiveMutex after acquiring other resources."); + + // show the caller why this is potentially bad + CheckAcquire(); + + LockInternal(); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(); + LockInternal(); + NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!"); + Acquire(); // protected by us + mOwningThread = PR_GetCurrentThread(); + mEntryCount = 1; +} + +void RecursiveMutex::Unlock() { + if (--mEntryCount == 0) { + Release(); // protected by us + mOwningThread = nullptr; + } + UnlockInternal(); +} + +void RecursiveMutex::AssertCurrentThreadIn() const { + MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread()); +} + +// +// Debug implementation of CondVar +void OffTheBooksCondVar::Wait() { + // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code + // duplication. + CVStatus status = Wait(TimeDuration::Forever()); + MOZ_ASSERT(status == CVStatus::NoTimeout); +} + +CVStatus OffTheBooksCondVar::Wait(TimeDuration aDuration) { + AssertCurrentThreadOwnsMutex(); + + // save mutex state and reset to empty + AcquisitionState savedAcquisitionState = mLock->TakeAcquisitionState(); + BlockingResourceBase* savedChainPrev = mLock->mChainPrev; + PRThread* savedOwningThread = mLock->mOwningThread; + mLock->mChainPrev = 0; + mLock->mOwningThread = nullptr; + + // give up mutex until we're back from Wait() + CVStatus status; + { +# if defined(MOZILLA_INTERNAL_API) + AUTO_PROFILER_THREAD_SLEEP; +# endif + status = mImpl.wait_for(*mLock, aDuration); + } + + // restore saved state + mLock->SetAcquisitionState(std::move(savedAcquisitionState)); + mLock->mChainPrev = savedChainPrev; + mLock->mOwningThread = savedOwningThread; + + return status; +} + +#endif // ifdef DEBUG + +} // namespace mozilla diff --git a/xpcom/threads/BlockingResourceBase.h b/xpcom/threads/BlockingResourceBase.h new file mode 100644 index 0000000000..8bb7a78f6f --- /dev/null +++ b/xpcom/threads/BlockingResourceBase.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/. */ + +#ifndef mozilla_BlockingResourceBase_h +#define mozilla_BlockingResourceBase_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/Attributes.h" + +#include "nscore.h" +#include "nsDebug.h" + +#include "prtypes.h" + +#ifdef DEBUG + +// NB: Comment this out to enable callstack tracking. +# define MOZ_CALLSTACK_DISABLED + +# include "prinit.h" + +# ifndef MOZ_CALLSTACK_DISABLED +# include "mozilla/Maybe.h" +# include "nsTArray.h" +# endif + +#endif + +// +// This header is not meant to be included by client code. +// + +namespace mozilla { + +#ifdef DEBUG +template <class T> +class DeadlockDetector; +#endif + +/** + * BlockingResourceBase + * Base class of resources that might block clients trying to acquire them. + * Does debugging and deadlock detection in DEBUG builds. + **/ +class BlockingResourceBase { + public: + // Needs to be kept in sync with kResourceTypeNames. + enum BlockingResourceType { + eMutex, + eReentrantMonitor, + eCondVar, + eRecursiveMutex + }; + + /** + * kResourceTypeName + * Human-readable version of BlockingResourceType enum. + */ + static const char* const kResourceTypeName[]; + +#ifdef DEBUG + + static size_t SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf); + + /** + * Print + * Write a description of this blocking resource to |aOut|. If + * the resource appears to be currently acquired, the current + * acquisition context is printed and true is returned. + * Otherwise, we print the context from |aFirstSeen|, the + * first acquisition from which the code calling |Print()| + * became interested in us, and return false. + * + * *NOT* thread safe. Reads |mAcquisitionContext| without + * synchronization, but this will not cause correctness + * problems. + * + * FIXME bug 456272: hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but + * only some info is written into |aOut| + */ + bool Print(nsACString& aOut) const; + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + // NB: |mName| is not reported as it's expected to be a static string. + // If we switch to a nsString it should be added to the tally. + // |mChainPrev| is not reported because its memory is not owned. + size_t n = aMallocSizeOf(this); + return n; + } + + // ``DDT'' = ``Deadlock Detector Type'' + typedef DeadlockDetector<BlockingResourceBase> DDT; + + protected: +# ifdef MOZ_CALLSTACK_DISABLED + typedef bool AcquisitionState; +# else + // Using maybe to use emplacement as the acquisition state flag; we may not + // always get a stack trace because of possible stack walk suppression or + // errors, hence can't use !IsEmpty() on the array itself as indication. + static size_t const kAcquisitionStateStackSize = 24; + typedef Maybe<AutoTArray<void*, kAcquisitionStateStackSize> > + AcquisitionState; +# endif + + /** + * BlockingResourceBase + * Initialize this blocking resource. Also hooks the resource into + * instrumentation code. + * + * Thread safe. + * + * @param aName A meaningful, unique name that can be used in + * error messages, et al. + * @param aType The specific type of |this|, if any. + **/ + BlockingResourceBase(const char* aName, BlockingResourceType aType); + + ~BlockingResourceBase(); + + /** + * CheckAcquire + * + * Thread safe. + **/ + void CheckAcquire(); + + /** + * Acquire + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Acquire(); // NS_NEEDS_RESOURCE(this) + + /** + * Release + * Remove this resource from the current thread's acquisition chain. + * The resource does not have to be at the front of the chain, although + * it is confusing to release resources in a different order than they + * are acquired. This generates a warning. + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Release(); // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainFront + * + * Thread safe. + * + * @return the front of the resource acquisition chain, i.e., the last + * resource acquired. + */ + static BlockingResourceBase* ResourceChainFront() { + return sResourceAcqnChainFront.get(); + } + + /** + * ResourceChainPrev + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + static BlockingResourceBase* ResourceChainPrev( + const BlockingResourceBase* aResource) { + return aResource->mChainPrev; + } // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainAppend + * Set |this| to the front of the resource acquisition chain, and link + * |this| to |aPrev|. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainAppend(BlockingResourceBase* aPrev) { + mChainPrev = aPrev; + sResourceAcqnChainFront.set(this); + } // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainRemove + * Remove |this| from the front of the resource acquisition chain. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainRemove() { + NS_ASSERTION(this == ResourceChainFront(), "not at chain front"); + sResourceAcqnChainFront.set(mChainPrev); + } // NS_NEEDS_RESOURCE(this) + + /** + * TakeAcquisitionState + * Return whether or not this resource was acquired and mark the resource + * as not acquired for subsequent uses. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + AcquisitionState TakeAcquisitionState() { +# ifdef MOZ_CALLSTACK_DISABLED + bool acquired = mAcquired; + ClearAcquisitionState(); + return acquired; +# else + return mAcquired.take(); +# endif + } + + /** + * SetAcquisitionState + * Set whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void SetAcquisitionState(AcquisitionState&& aAcquisitionState) { + mAcquired = std::move(aAcquisitionState); + } + + /** + * ClearAcquisitionState + * Indicate this resource is not acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ClearAcquisitionState() { +# ifdef MOZ_CALLSTACK_DISABLED + mAcquired = false; +# else + mAcquired.reset(); +# endif + } + + /** + * IsAcquired + * Indicates if this resource is acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + bool IsAcquired() const { return (bool)mAcquired; } + + /** + * mChainPrev + * A series of resource acquisitions creates a chain of orders. This + * chain is implemented as a linked list; |mChainPrev| points to the + * resource most recently Acquire()'d before this one. + **/ + BlockingResourceBase* mChainPrev; + + private: + /** + * mName + * A descriptive name for this resource. Used in error + * messages etc. + */ + const char* mName; + + /** + * mType + * The more specific type of this resource. Used to implement + * special semantics (e.g., reentrancy of monitors). + **/ + BlockingResourceType mType; + + /** + * mAcquired + * Indicates if this resource is currently acquired. + */ + AcquisitionState mAcquired; + +# ifndef MOZ_CALLSTACK_DISABLED + /** + * mFirstSeen + * Inidicates where this resource was first acquired. + */ + AcquisitionState mFirstSeen; +# endif + + /** + * sCallOnce + * Ensures static members are initialized only once, and in a + * thread-safe way. + */ + static PRCallOnceType sCallOnce; + + /** + * Thread-private pointer to the front of each thread's resource + * acquisition chain. + */ + static MOZ_THREAD_LOCAL(BlockingResourceBase*) sResourceAcqnChainFront; + + /** + * sDeadlockDetector + * Does as named. + */ + static DDT* sDeadlockDetector; + + /** + * InitStatics + * Inititialize static members of BlockingResourceBase that can't + * be statically initialized. + * + * *NOT* thread safe. + */ + static PRStatus InitStatics(); + + /** + * Shutdown + * Free static members. + * + * *NOT* thread safe. + */ + static void Shutdown(); + + static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp, + void* aClosure); + static void GetStackTrace(AcquisitionState& aState, + const void* aFirstFramePC); + +# ifdef MOZILLA_INTERNAL_API + // so it can call BlockingResourceBase::Shutdown() + friend void LogTerm(); +# endif // ifdef MOZILLA_INTERNAL_API + +#else // non-DEBUG implementation + + BlockingResourceBase(const char* aName, BlockingResourceType aType) {} + + ~BlockingResourceBase() {} + +#endif +}; + +} // namespace mozilla + +#endif // mozilla_BlockingResourceBase_h diff --git a/xpcom/threads/CPUUsageWatcher.cpp b/xpcom/threads/CPUUsageWatcher.cpp new file mode 100644 index 0000000000..66579b6f44 --- /dev/null +++ b/xpcom/threads/CPUUsageWatcher.cpp @@ -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 http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/CPUUsageWatcher.h" +#include "mozilla/Try.h" + +#include "prsystem.h" + +#ifdef XP_MACOSX +# include <sys/resource.h> +# include <mach/clock.h> +# include <mach/mach_host.h> +#endif + +#ifdef CPU_USAGE_WATCHER_ACTIVE +# include "mozilla/BackgroundHangMonitor.h" +#endif + +namespace mozilla { + +#ifdef CPU_USAGE_WATCHER_ACTIVE + +// Even if the machine only has one processor, tolerate up to 50% +// external CPU usage. +static const float kTolerableExternalCPUUsageFloor = 0.5f; + +struct CPUStats { + // The average CPU usage time, which can be summed across all cores in the + // system, or averaged between them. Whichever it is, it needs to be in the + // same units as updateTime. + uint64_t usageTime; + // A monotonically increasing value in the same units as usageTime, which can + // be used to determine the percentage of active vs idle time + uint64_t updateTime; +}; + +# ifdef XP_MACOSX + +static const uint64_t kMicrosecondsPerSecond = 1000000LL; +static const uint64_t kNanosecondsPerMicrosecond = 1000LL; + +static uint64_t GetMicroseconds(timeval time) { + return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + + (uint64_t)time.tv_usec; +} + +static uint64_t GetMicroseconds(mach_timespec_t time) { + return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + + ((uint64_t)time.tv_nsec) / kNanosecondsPerMicrosecond; +} + +static Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats( + int32_t numCPUs) { + CPUStats result = {}; + rusage usage; + int32_t rusageResult = getrusage(RUSAGE_SELF, &usage); + if (rusageResult == -1) { + return Err(GetProcessTimesError); + } + result.usageTime = + GetMicroseconds(usage.ru_utime) + GetMicroseconds(usage.ru_stime); + + clock_serv_t realtimeClock; + kern_return_t errorResult = + host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &realtimeClock); + if (errorResult != KERN_SUCCESS) { + return Err(GetProcessTimesError); + } + mach_timespec_t time; + errorResult = clock_get_time(realtimeClock, &time); + if (errorResult != KERN_SUCCESS) { + return Err(GetProcessTimesError); + } + result.updateTime = GetMicroseconds(time); + + // getrusage will give us the sum of the values across all + // of our cores. Divide by the number of CPUs to get an average. + result.usageTime /= numCPUs; + return result; +} + +static Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() { + CPUStats result = {}; + host_cpu_load_info_data_t loadInfo; + mach_msg_type_number_t loadInfoCount = HOST_CPU_LOAD_INFO_COUNT; + kern_return_t statsResult = + host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, + (host_info_t)&loadInfo, &loadInfoCount); + if (statsResult != KERN_SUCCESS) { + return Err(HostStatisticsError); + } + + result.usageTime = loadInfo.cpu_ticks[CPU_STATE_USER] + + loadInfo.cpu_ticks[CPU_STATE_NICE] + + loadInfo.cpu_ticks[CPU_STATE_SYSTEM]; + result.updateTime = result.usageTime + loadInfo.cpu_ticks[CPU_STATE_IDLE]; + return result; +} + +# endif // XP_MACOSX + +# ifdef XP_WIN + +// A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC +uint64_t FiletimeToInteger(FILETIME filetime) { + return ((uint64_t)filetime.dwLowDateTime) | (uint64_t)filetime.dwHighDateTime + << 32; +} + +Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats(int32_t numCPUs) { + CPUStats result = {}; + FILETIME creationFiletime; + FILETIME exitFiletime; + FILETIME kernelFiletime; + FILETIME userFiletime; + bool success = GetProcessTimes(GetCurrentProcess(), &creationFiletime, + &exitFiletime, &kernelFiletime, &userFiletime); + if (!success) { + return Err(GetProcessTimesError); + } + + result.usageTime = + FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime); + + FILETIME nowFiletime; + GetSystemTimeAsFileTime(&nowFiletime); + result.updateTime = FiletimeToInteger(nowFiletime); + + result.usageTime /= numCPUs; + + return result; +} + +Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() { + CPUStats result = {}; + FILETIME idleFiletime; + FILETIME kernelFiletime; + FILETIME userFiletime; + bool success = GetSystemTimes(&idleFiletime, &kernelFiletime, &userFiletime); + + if (!success) { + return Err(GetSystemTimesError); + } + + result.usageTime = + FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime); + result.updateTime = result.usageTime + FiletimeToInteger(idleFiletime); + + return result; +} + +# endif // XP_WIN + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() { + mNumCPUs = PR_GetNumberOfProcessors(); + if (mNumCPUs <= 0) { + mExternalUsageThreshold = 1.0f; + return Err(GetNumberOfProcessorsError); + } + mExternalUsageThreshold = + std::max(1.0f - 1.0f / (float)mNumCPUs, kTolerableExternalCPUUsageFloor); + + CPUStats processTimes; + MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); + mProcessUpdateTime = processTimes.updateTime; + mProcessUsageTime = processTimes.usageTime; + + CPUStats globalTimes; + MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); + mGlobalUpdateTime = globalTimes.updateTime; + mGlobalUsageTime = globalTimes.usageTime; + + mInitialized = true; + + CPUUsageWatcher* self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "CPUUsageWatcher::Init", + [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); })); + + return Ok(); +} + +void CPUUsageWatcher::Uninit() { + if (mInitialized) { + BackgroundHangMonitor::UnregisterAnnotator(*this); + } + mInitialized = false; +} + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() { + if (!mInitialized) { + return Ok(); + } + + mExternalUsageRatio = 0.0f; + + CPUStats processTimes; + MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); + CPUStats globalTimes; + MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); + + uint64_t processUsageDelta = processTimes.usageTime - mProcessUsageTime; + uint64_t processUpdateDelta = processTimes.updateTime - mProcessUpdateTime; + float processUsageNormalized = + processUsageDelta > 0 + ? (float)processUsageDelta / (float)processUpdateDelta + : 0.0f; + + uint64_t globalUsageDelta = globalTimes.usageTime - mGlobalUsageTime; + uint64_t globalUpdateDelta = globalTimes.updateTime - mGlobalUpdateTime; + float globalUsageNormalized = + globalUsageDelta > 0 ? (float)globalUsageDelta / (float)globalUpdateDelta + : 0.0f; + + mProcessUsageTime = processTimes.usageTime; + mProcessUpdateTime = processTimes.updateTime; + mGlobalUsageTime = globalTimes.usageTime; + mGlobalUpdateTime = globalTimes.updateTime; + + mExternalUsageRatio = + std::max(0.0f, globalUsageNormalized - processUsageNormalized); + + return Ok(); +} + +void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) { + if (!mInitialized) { + return; + } + + if (mExternalUsageRatio > mExternalUsageThreshold) { + aAnnotations.AddAnnotation(u"ExternalCPUHigh"_ns, true); + } +} + +#else // !CPU_USAGE_WATCHER_ACTIVE + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() { return Ok(); } + +void CPUUsageWatcher::Uninit() {} + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() { + return Ok(); +} + +void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {} + +#endif // CPU_USAGE_WATCHER_ACTIVE + +} // namespace mozilla diff --git a/xpcom/threads/CPUUsageWatcher.h b/xpcom/threads/CPUUsageWatcher.h new file mode 100644 index 0000000000..c3a643378a --- /dev/null +++ b/xpcom/threads/CPUUsageWatcher.h @@ -0,0 +1,100 @@ +/* -*- 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_CPUUsageWatcher_h +#define mozilla_CPUUsageWatcher_h + +#include <stdint.h> + +#include "mozilla/HangAnnotations.h" +#include "mozilla/Result.h" + +// We only support OSX and Windows, because on Linux we're forced to read +// from /proc/stat in order to get global CPU values. We would prefer to not +// eat that cost for this. +#if defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX)) +# define CPU_USAGE_WATCHER_ACTIVE +#endif + +namespace mozilla { + +// Start error values at 1 to allow using the UnusedZero Result +// optimization. +enum CPUUsageWatcherError : uint8_t { + ClockGetTimeError = 1, + GetNumberOfProcessorsError, + GetProcessTimesError, + GetSystemTimesError, + HostStatisticsError, + ProcStatError, +}; + +namespace detail { + +template <> +struct UnusedZero<CPUUsageWatcherError> : UnusedZeroEnum<CPUUsageWatcherError> { +}; + +} // namespace detail + +class CPUUsageHangAnnotator : public BackgroundHangAnnotator { + public: +}; + +class CPUUsageWatcher : public BackgroundHangAnnotator { + public: +#ifdef CPU_USAGE_WATCHER_ACTIVE + CPUUsageWatcher() + : mInitialized(false), + mExternalUsageThreshold(0), + mExternalUsageRatio(0), + mProcessUsageTime(0), + mProcessUpdateTime(0), + mGlobalUsageTime(0), + mGlobalUpdateTime(0), + mNumCPUs(0) {} +#endif + + Result<Ok, CPUUsageWatcherError> Init(); + + void Uninit(); + + // Updates necessary values to allow AnnotateHang to function. This must be + // called on some semi-regular basis, as it will calculate the mean CPU + // usage values between now and the last time it was called. + Result<Ok, CPUUsageWatcherError> CollectCPUUsage(); + + void AnnotateHang(BackgroundHangAnnotations& aAnnotations) final; + + private: +#ifdef CPU_USAGE_WATCHER_ACTIVE + bool mInitialized; + // The threshold above which we will mark a hang as occurring under high + // external CPU usage conditions + float mExternalUsageThreshold; + // The CPU usage (0-1) external to our process, averaged between the two + // most recent monitor thread runs + float mExternalUsageRatio; + // The total cumulative CPU usage time by our process as of the last + // CollectCPUUsage or Startup + uint64_t mProcessUsageTime; + // A time value in the same units as mProcessUsageTime used to + // determine the ratio of CPU usage time to idle time + uint64_t mProcessUpdateTime; + // The total cumulative CPU usage time by all processes as of the last + // CollectCPUUsage or Startup + uint64_t mGlobalUsageTime; + // A time value in the same units as mGlobalUsageTime used to + // determine the ratio of CPU usage time to idle time + uint64_t mGlobalUpdateTime; + // The number of virtual cores on our machine + uint64_t mNumCPUs; +#endif +}; + +} // namespace mozilla + +#endif // mozilla_CPUUsageWatcher_h diff --git a/xpcom/threads/CondVar.h b/xpcom/threads/CondVar.h new file mode 100644 index 0000000000..e427fc2d9e --- /dev/null +++ b/xpcom/threads/CondVar.h @@ -0,0 +1,139 @@ +/* -*- 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_CondVar_h +#define mozilla_CondVar_h + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PlatformConditionVariable.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" + +#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG) +# include "mozilla/ProfilerThreadSleep.h" +#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG) + +namespace mozilla { + +/** + * Similarly to OffTheBooksMutex, OffTheBooksCondvar is identical to CondVar, + * except that OffTheBooksCondVar doesn't include leak checking. Sometimes + * you want to intentionally "leak" a CondVar until shutdown; in these cases, + * OffTheBooksCondVar is for you. + */ +class OffTheBooksCondVar : BlockingResourceBase { + public: + /** + * OffTheBooksCondVar + * + * The CALLER owns |aLock|. + * + * @param aLock A Mutex to associate with this condition variable. + * @param aName A name which can reference this monitor + * @returns If failure, nullptr. + * If success, a valid Monitor* which must be destroyed + * by Monitor::DestroyMonitor() + **/ + OffTheBooksCondVar(OffTheBooksMutex& aLock, const char* aName) + : BlockingResourceBase(aName, eCondVar), mLock(&aLock) {} + + /** + * ~OffTheBooksCondVar + * Clean up after this OffTheBooksCondVar, but NOT its associated Mutex. + **/ + ~OffTheBooksCondVar() = default; + + /** + * Wait + * @see prcvar.h + **/ +#ifndef DEBUG + void Wait() { +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + mImpl.wait(*mLock); + } + + CVStatus Wait(TimeDuration aDuration) { +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + return mImpl.wait_for(*mLock, aDuration); + } +#else + // NOTE: debug impl is in BlockingResourceBase.cpp + void Wait(); + CVStatus Wait(TimeDuration aDuration); +#endif + + /** + * Notify + * @see prcvar.h + **/ + void Notify() { mImpl.notify_one(); } + + /** + * NotifyAll + * @see prcvar.h + **/ + void NotifyAll() { mImpl.notify_all(); } + +#ifdef DEBUG + /** + * AssertCurrentThreadOwnsMutex + * @see Mutex::AssertCurrentThreadOwns + **/ + void AssertCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(mLock) { + mLock->AssertCurrentThreadOwns(); + } + + /** + * AssertNotCurrentThreadOwnsMutex + * @see Mutex::AssertNotCurrentThreadOwns + **/ + void AssertNotCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(!mLock) { + mLock->AssertNotCurrentThreadOwns(); + } + +#else + void AssertCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(mLock) {} + void AssertNotCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(!mLock) {} + +#endif // ifdef DEBUG + + private: + OffTheBooksCondVar(); + OffTheBooksCondVar(const OffTheBooksCondVar&) = delete; + OffTheBooksCondVar& operator=(const OffTheBooksCondVar&) = delete; + + OffTheBooksMutex* mLock; + detail::ConditionVariableImpl mImpl; +}; + +/** + * CondVar + * Vanilla condition variable. Please don't use this unless you have a + * compelling reason --- Monitor provides a simpler API. + */ +class CondVar : public OffTheBooksCondVar { + public: + CondVar(OffTheBooksMutex& aLock, const char* aName) + : OffTheBooksCondVar(aLock, aName) { + MOZ_COUNT_CTOR(CondVar); + } + + MOZ_COUNTED_DTOR(CondVar) + + private: + CondVar(); + CondVar(const CondVar&); + CondVar& operator=(const CondVar&); +}; + +} // namespace mozilla + +#endif // ifndef mozilla_CondVar_h diff --git a/xpcom/threads/DataMutex.h b/xpcom/threads/DataMutex.h new file mode 100644 index 0000000000..44f0a35762 --- /dev/null +++ b/xpcom/threads/DataMutex.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 DataMutex_h__ +#define DataMutex_h__ + +#include <utility> +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" + +namespace mozilla { + +// A template to wrap a type with a mutex so that accesses to the type's +// data are required to take the lock before accessing it. This ensures +// that a mutex is explicitly associated with the data that it protects, +// and makes it impossible to access the data without first taking the +// associated mutex. +// +// This is based on Rust's std::sync::Mutex, which operates under the +// strategy of locking data, rather than code. +// +// Examples: +// +// DataMutex<uint32_t> u32DataMutex(1, "u32DataMutex"); +// auto x = u32DataMutex.Lock(); +// *x = 4; +// assert(*x, 4u); +// +// DataMutex<nsTArray<uint32_t>> arrayDataMutex("arrayDataMutex"); +// auto a = arrayDataMutex.Lock(); +// auto& x = a.ref(); +// x.AppendElement(1u); +// assert(x[0], 1u); +// +template <typename T, typename MutexType> +class DataMutexBase { + public: + template <typename V> + class MOZ_STACK_CLASS AutoLockBase { + public: + V* operator->() const& { return &ref(); } + V* operator->() const&& = delete; + + V& operator*() const& { return ref(); } + V& operator*() const&& = delete; + + // Like RefPtr, make this act like its underlying raw pointer type + // whenever it is used in a context where a raw pointer is expected. + operator V*() const& { return &ref(); } + + // Like RefPtr, don't allow implicit conversion of temporary to raw pointer. + operator V*() const&& = delete; + + V& ref() const& { + MOZ_ASSERT(mOwner); + return mOwner->mValue; + } + V& ref() const&& = delete; + + AutoLockBase(AutoLockBase&& aOther) : mOwner(aOther.mOwner) { + aOther.mOwner = nullptr; + } + + ~AutoLockBase() { + if (mOwner) { + mOwner->mMutex.Unlock(); + mOwner = nullptr; + } + } + + private: + friend class DataMutexBase; + + AutoLockBase(const AutoLockBase& aOther) = delete; + + explicit AutoLockBase(DataMutexBase<T, MutexType>* aDataMutex) + : mOwner(aDataMutex) { + MOZ_ASSERT(!!mOwner); + mOwner->mMutex.Lock(); + } + + DataMutexBase<T, MutexType>* mOwner; + }; + + using AutoLock = AutoLockBase<T>; + using ConstAutoLock = AutoLockBase<const T>; + + explicit DataMutexBase(const char* aName) : mMutex(aName) {} + + DataMutexBase(T&& aValue, const char* aName) + : mMutex(aName), mValue(std::move(aValue)) {} + + AutoLock Lock() { return AutoLock(this); } + ConstAutoLock ConstLock() { return ConstAutoLock(this); } + + const MutexType& Mutex() const { return mMutex; } + + private: + MutexType mMutex; + T mValue; +}; + +// Craft a version of StaticMutex that takes a const char* in its ctor. +// We need this so it works interchangeably with Mutex which requires a const +// char* aName in its ctor. +class StaticMutexNameless : public StaticMutex { + public: + explicit StaticMutexNameless(const char* aName) : StaticMutex() {} + + private: + // Disallow copy construction, `=`, `new`, and `delete` like BaseStaticMutex. +#ifdef DEBUG + StaticMutexNameless(StaticMutexNameless& aOther); +#endif // DEBUG + StaticMutexNameless& operator=(StaticMutexNameless* aRhs); + static void* operator new(size_t) noexcept(true); + static void operator delete(void*); +}; + +template <typename T> +using DataMutex = DataMutexBase<T, Mutex>; +template <typename T> +using StaticDataMutex = DataMutexBase<T, StaticMutexNameless>; + +} // namespace mozilla + +#endif // DataMutex_h__ diff --git a/xpcom/threads/DeadlockDetector.h b/xpcom/threads/DeadlockDetector.h new file mode 100644 index 0000000000..5c40941328 --- /dev/null +++ b/xpcom/threads/DeadlockDetector.h @@ -0,0 +1,359 @@ +/* -*- 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_DeadlockDetector_h +#define mozilla_DeadlockDetector_h + +#include "mozilla/Attributes.h" + +#include <stdlib.h> + +#include "prlock.h" + +#include "nsClassHashtable.h" +#include "nsTArray.h" + +namespace mozilla { + +/** + * DeadlockDetector + * + * The following is an approximate description of how the deadlock detector + * works. + * + * The deadlock detector ensures that all blocking resources are + * acquired according to a partial order P. One type of blocking + * resource is a lock. If a lock l1 is acquired (locked) before l2, + * then we say that |l1 <_P l2|. The detector flags an error if two + * locks l1 and l2 have an inconsistent ordering in P; that is, if + * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error + * because a thread acquiring l1,l2 according to the first order might + * race with a thread acquiring them according to the second order. + * If this happens under the right conditions, then the acquisitions + * will deadlock. + * + * This deadlock detector doesn't know at compile-time what P is. So, + * it tries to discover the order at run time. More precisely, it + * finds <i>some</i> order P, then tries to find chains of resource + * acquisitions that violate P. An example acquisition sequence, and + * the orders they impose, is + * l1.lock() // current chain: [ l1 ] + * // order: { } + * + * l2.lock() // current chain: [ l1, l2 ] + * // order: { l1 <_P l2 } + * + * l3.lock() // current chain: [ l1, l2, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: <_P is transitive, so also |l1 <_P l3|) + * + * l2.unlock() // current chain: [ l1, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: it's OK, but weird, that l2 was unlocked out + * // of order. we still have l1 <_P l3). + * + * l2.lock() // current chain: [ l1, l3, l2 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3, + * l3 <_P l2 (!!!) } + * BEEP BEEP! Here the detector will flag a potential error, since + * l2 and l3 were used inconsistently (and potentially in ways that + * would deadlock). + */ +template <typename T> +class DeadlockDetector { + public: + typedef nsTArray<const T*> ResourceAcquisitionArray; + + private: + struct OrderingEntry; + typedef nsTArray<OrderingEntry*> HashEntryArray; + typedef typename HashEntryArray::index_type index_type; + typedef typename HashEntryArray::size_type size_type; + static const index_type NoIndex = HashEntryArray::NoIndex; + + /** + * Value type for the ordering table. Contains the other + * resources on which an ordering constraint |key < other| + * exists. The catch is that we also store the calling context at + * which the other resource was acquired; this improves the + * quality of error messages when potential deadlock is detected. + */ + struct OrderingEntry { + explicit OrderingEntry(const T* aResource) + : mOrderedLT() // FIXME bug 456272: set to empirical dep size? + , + mExternalRefs(), + mResource(aResource) {} + ~OrderingEntry() {} + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + n += mOrderedLT.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mExternalRefs.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; + } + + HashEntryArray mOrderedLT; // this <_o Other + HashEntryArray mExternalRefs; // hash entries that reference this + const T* mResource; + }; + + // Throwaway RAII lock to make the following code safer. + struct PRAutoLock { + explicit PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); } + ~PRAutoLock() { PR_Unlock(mLock); } + PRLock* mLock; + }; + + public: + static const uint32_t kDefaultNumBuckets; + + /** + * DeadlockDetector + * Create a new deadlock detector. + * + * @param aNumResourcesGuess Guess at approximate number of resources + * that will be checked. + */ + explicit DeadlockDetector(uint32_t aNumResourcesGuess = kDefaultNumBuckets) + : mOrdering(aNumResourcesGuess) { + mLock = PR_NewLock(); + if (!mLock) { + MOZ_CRASH("couldn't allocate deadlock detector lock"); + } + } + + /** + * ~DeadlockDetector + * + * *NOT* thread safe. + */ + ~DeadlockDetector() { PR_DestroyLock(mLock); } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + + { + PRAutoLock _(mLock); + n += mOrdering.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mOrdering.Values()) { + // NB: Key is accounted for in the entry. + n += data->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return n; + } + + /** + * Add + * Make the deadlock detector aware of |aResource|. + * + * WARNING: The deadlock detector owns |aResource|. + * + * Thread safe. + * + * @param aResource Resource to make deadlock detector aware of. + */ + void Add(const T* aResource) { + PRAutoLock _(mLock); + mOrdering.InsertOrUpdate(aResource, MakeUnique<OrderingEntry>(aResource)); + } + + void Remove(const T* aResource) { + PRAutoLock _(mLock); + + OrderingEntry* entry = mOrdering.Get(aResource); + + // Iterate the external refs and remove the entry from them. + HashEntryArray& refs = entry->mExternalRefs; + for (index_type i = 0; i < refs.Length(); i++) { + refs[i]->mOrderedLT.RemoveElementSorted(entry); + } + + // Iterate orders and remove this entry from their refs. + HashEntryArray& orders = entry->mOrderedLT; + for (index_type i = 0; i < orders.Length(); i++) { + orders[i]->mExternalRefs.RemoveElementSorted(entry); + } + + // Now the entry can be safely removed. + mOrdering.Remove(aResource); + } + + /** + * CheckAcquisition This method is called after acquiring |aLast|, + * but before trying to acquire |aProposed|. + * It determines whether actually trying to acquire |aProposed| + * will create problems. It is OK if |aLast| is nullptr; this is + * interpreted as |aProposed| being the thread's first acquisition + * of its current chain. + * + * Iff acquiring |aProposed| may lead to deadlock for some thread + * interleaving (including the current one!), the cyclical + * dependency from which this was deduced is returned. Otherwise, + * 0 is returned. + * + * If a potential deadlock is detected and a resource cycle is + * returned, it is the *caller's* responsibility to free it. + * + * Thread safe. + * + * @param aLast Last resource acquired by calling thread (or 0). + * @param aProposed Resource calling thread proposes to acquire. + */ + ResourceAcquisitionArray* CheckAcquisition(const T* aLast, + const T* aProposed) { + if (!aLast) { + // don't check if |0 < aProposed|; just vamoose + return 0; + } + + NS_ASSERTION(aProposed, "null resource"); + PRAutoLock _(mLock); + + OrderingEntry* proposed = mOrdering.Get(aProposed); + NS_ASSERTION(proposed, "missing ordering entry"); + + OrderingEntry* current = mOrdering.Get(aLast); + NS_ASSERTION(current, "missing ordering entry"); + + // this is the crux of the deadlock detector algorithm + + if (current == proposed) { + // reflexive deadlock. fastpath b/c InTransitiveClosure is + // not applicable here. + ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray(); + if (!cycle) { + MOZ_CRASH("can't allocate dep. cycle array"); + } + cycle->AppendElement(current->mResource); + cycle->AppendElement(aProposed); + return cycle; + } + if (InTransitiveClosure(current, proposed)) { + // we've already established |aLast < aProposed|. all is well. + return 0; + } + if (InTransitiveClosure(proposed, current)) { + // the order |aProposed < aLast| has been deduced, perhaps + // transitively. we're attempting to violate that + // constraint by acquiring resources in the order + // |aLast < aProposed|, and thus we may deadlock under the + // right conditions. + ResourceAcquisitionArray* cycle = GetDeductionChain(proposed, current); + // show how acquiring |aProposed| would complete the cycle + cycle->AppendElement(aProposed); + return cycle; + } + // |aLast|, |aProposed| are unordered according to our + // poset. this is fine, but we now need to add this + // ordering constraint. + current->mOrderedLT.InsertElementSorted(proposed); + proposed->mExternalRefs.InsertElementSorted(current); + return 0; + } + + /** + * Return true iff |aTarget| is in the transitive closure of |aStart| + * over the ordering relation `<_this'. + * + * @precondition |aStart != aTarget| + */ + bool InTransitiveClosure(const OrderingEntry* aStart, + const OrderingEntry* aTarget) const { + // NB: Using a static comparator rather than default constructing one shows + // a 9% improvement in scalability tests on some systems. + static nsDefaultComparator<const OrderingEntry*, const OrderingEntry*> comp; + if (aStart->mOrderedLT.BinaryIndexOf(aTarget, comp) != NoIndex) { + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + if (InTransitiveClosure(*it, aTarget)) { + return true; + } + } + return false; + } + + /** + * Return an array of all resource acquisitions + * aStart <_this r1 <_this r2 <_ ... <_ aTarget + * from which |aStart <_this aTarget| was deduced, including + * |aStart| and |aTarget|. + * + * Nb: there may be multiple deductions of |aStart <_this + * aTarget|. This function returns the first ordering found by + * depth-first search. + * + * Nb: |InTransitiveClosure| could be replaced by this function. + * However, this one is more expensive because we record the DFS + * search stack on the heap whereas the other doesn't. + * + * @precondition |aStart != aTarget| + */ + ResourceAcquisitionArray* GetDeductionChain(const OrderingEntry* aStart, + const OrderingEntry* aTarget) { + ResourceAcquisitionArray* chain = new ResourceAcquisitionArray(); + if (!chain) { + MOZ_CRASH("can't allocate dep. cycle array"); + } + chain->AppendElement(aStart->mResource); + + NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain), + "GetDeductionChain called when there's no deadlock"); + return chain; + } + + // precondition: |aStart != aTarget| + // invariant: |aStart| is the last element in |aChain| + bool GetDeductionChain_Helper(const OrderingEntry* aStart, + const OrderingEntry* aTarget, + ResourceAcquisitionArray* aChain) { + if (aStart->mOrderedLT.BinaryIndexOf(aTarget) != NoIndex) { + aChain->AppendElement(aTarget->mResource); + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + aChain->AppendElement((*it)->mResource); + if (GetDeductionChain_Helper(*it, aTarget, aChain)) { + return true; + } + aChain->RemoveLastElement(); + } + return false; + } + + /** + * The partial order on resource acquisitions used by the deadlock + * detector. + */ + nsClassHashtable<nsPtrHashKey<const T>, OrderingEntry> mOrdering; + + /** + * Protects contentious methods. + * Nb: can't use mozilla::Mutex since we are used as its deadlock + * detector. + */ + PRLock* mLock; + + private: + DeadlockDetector(const DeadlockDetector& aDD) = delete; + DeadlockDetector& operator=(const DeadlockDetector& aDD) = delete; +}; + +template <typename T> +// FIXME bug 456272: tune based on average workload +const uint32_t DeadlockDetector<T>::kDefaultNumBuckets = 32; + +} // namespace mozilla + +#endif // ifndef mozilla_DeadlockDetector_h diff --git a/xpcom/threads/DelayedRunnable.cpp b/xpcom/threads/DelayedRunnable.cpp new file mode 100644 index 0000000000..a9231442a7 --- /dev/null +++ b/xpcom/threads/DelayedRunnable.cpp @@ -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/. */ + +#include "DelayedRunnable.h" + +#include "mozilla/ProfilerRunnable.h" + +namespace mozilla { + +DelayedRunnable::DelayedRunnable(already_AddRefed<nsISerialEventTarget> aTarget, + already_AddRefed<nsIRunnable> aRunnable, + uint32_t aDelay) + : mozilla::Runnable("DelayedRunnable"), + mTarget(aTarget), + mDelayedFrom(TimeStamp::NowLoRes()), + mDelay(aDelay), + mWrappedRunnable(aRunnable) {} + +nsresult DelayedRunnable::Init() { + MutexAutoLock lock(mMutex); + if (!mWrappedRunnable) { + MOZ_ASSERT_UNREACHABLE(); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = mTarget->RegisterShutdownTask(this); + if (NS_FAILED(rv)) { + MOZ_DIAGNOSTIC_ASSERT( + rv == NS_ERROR_UNEXPECTED, + "DelayedRunnable target must support RegisterShutdownTask"); + NS_WARNING("DelayedRunnable init after target is shutdown"); + return rv; + } + + rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay, + nsITimer::TYPE_ONE_SHOT, mTarget); + if (NS_WARN_IF(NS_FAILED(rv))) { + mTarget->UnregisterShutdownTask(this); + } + return rv; +} + +NS_IMETHODIMP DelayedRunnable::Run() { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + + nsCOMPtr<nsIRunnable> runnable; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mTimer, "Init() must have been called"); + + // Already ran? + if (!mWrappedRunnable) { + return NS_OK; + } + + // Are we too early? + if ((mozilla::TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < + mDelay) { + return NS_OK; // Let the nsITimer run us. + } + + mTimer->Cancel(); + mTarget->UnregisterShutdownTask(this); + runnable = mWrappedRunnable.forget(); + } + + AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable); + return runnable->Run(); +} + +NS_IMETHODIMP DelayedRunnable::Notify(nsITimer* aTimer) { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + + nsCOMPtr<nsIRunnable> runnable; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mTimer, "Init() must have been called"); + + // We may have already run due to races + if (!mWrappedRunnable) { + return NS_OK; + } + + mTarget->UnregisterShutdownTask(this); + runnable = mWrappedRunnable.forget(); + } + + AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable); + return runnable->Run(); +} + +void DelayedRunnable::TargetShutdown() { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + + // Called at shutdown + MutexAutoLock lock(mMutex); + if (!mWrappedRunnable) { + return; + } + mWrappedRunnable = nullptr; + + if (mTimer) { + mTimer->Cancel(); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback, + nsITargetShutdownTask) + +} // namespace mozilla diff --git a/xpcom/threads/DelayedRunnable.h b/xpcom/threads/DelayedRunnable.h new file mode 100644 index 0000000000..242278fa5b --- /dev/null +++ b/xpcom/threads/DelayedRunnable.h @@ -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/. */ + +#ifndef XPCOM_THREADS_DELAYEDRUNNABLE_H_ +#define XPCOM_THREADS_DELAYEDRUNNABLE_H_ + +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsITargetShutdownTask.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class DelayedRunnable : public Runnable, + public nsITimerCallback, + public nsITargetShutdownTask { + public: + DelayedRunnable(already_AddRefed<nsISerialEventTarget> aTarget, + already_AddRefed<nsIRunnable> aRunnable, uint32_t aDelay); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSITIMERCALLBACK + + nsresult Init(); + + /** + * Called when the target is going away so the runnable can be released safely + * on the target thread. + */ + void TargetShutdown() override; + + private: + ~DelayedRunnable() = default; + nsresult DoRun(); + + const nsCOMPtr<nsISerialEventTarget> mTarget; + const TimeStamp mDelayedFrom; + const uint32_t mDelay; + + mozilla::Mutex mMutex{"DelayedRunnable"}; + nsCOMPtr<nsIRunnable> mWrappedRunnable; + nsCOMPtr<nsITimer> mTimer; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/EventQueue.cpp b/xpcom/threads/EventQueue.cpp new file mode 100644 index 0000000000..0decc3ce4c --- /dev/null +++ b/xpcom/threads/EventQueue.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 "mozilla/EventQueue.h" + +#include "GeckoProfiler.h" +#include "InputTaskManager.h" +#include "VsyncTaskManager.h" +#include "nsIRunnable.h" +#include "TaskController.h" + +using namespace mozilla; +using namespace mozilla::detail; + +template <size_t ItemsPerPage> +void EventQueueInternal<ItemsPerPage>::PutEvent( + already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority, + const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aDelay) { + nsCOMPtr<nsIRunnable> event(aEvent); + + static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_IDLE) == + static_cast<uint32_t>(EventQueuePriority::Idle)); + static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_NORMAL) == + static_cast<uint32_t>(EventQueuePriority::Normal)); + static_assert( + static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_MEDIUMHIGH) == + static_cast<uint32_t>(EventQueuePriority::MediumHigh)); + static_assert( + static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_INPUT_HIGH) == + static_cast<uint32_t>(EventQueuePriority::InputHigh)); + static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_VSYNC) == + static_cast<uint32_t>(EventQueuePriority::Vsync)); + static_assert( + static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) == + static_cast<uint32_t>(EventQueuePriority::RenderBlocking)); + static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_CONTROL) == + static_cast<uint32_t>(EventQueuePriority::Control)); + + if (mForwardToTC) { + TaskController* tc = TaskController::Get(); + + TaskManager* manager = nullptr; + if (aPriority == EventQueuePriority::InputHigh) { + manager = InputTaskManager::Get(); + } else if (aPriority == EventQueuePriority::DeferredTimers || + aPriority == EventQueuePriority::Idle) { + manager = TaskController::Get()->GetIdleTaskManager(); + } else if (aPriority == EventQueuePriority::Vsync) { + manager = VsyncTaskManager::Get(); + } + + tc->DispatchRunnable(event.forget(), static_cast<uint32_t>(aPriority), + manager); + return; + } + + if (profiler_thread_is_being_profiled(ThreadProfilingFeatures::Sampling)) { + // check to see if the profiler has been enabled since the last PutEvent + while (mDispatchTimes.Count() < mQueue.Count()) { + mDispatchTimes.Push(TimeStamp()); + } + mDispatchTimes.Push(aDelay ? TimeStamp::Now() - *aDelay : TimeStamp::Now()); + } + + mQueue.Push(std::move(event)); +} + +template <size_t ItemsPerPage> +already_AddRefed<nsIRunnable> EventQueueInternal<ItemsPerPage>::GetEvent( + const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aLastEventDelay) { + if (mQueue.IsEmpty()) { + if (aLastEventDelay) { + *aLastEventDelay = TimeDuration(); + } + return nullptr; + } + + // We always want to clear the dispatch times, even if the profiler is turned + // off, because we want to empty the (previously-collected) dispatch times, if + // any, from when the profiler was turned on. We only want to do something + // interesting with the dispatch times if the profiler is turned on, though. + if (!mDispatchTimes.IsEmpty()) { + TimeStamp dispatch_time = mDispatchTimes.Pop(); + if (profiler_is_active()) { + if (!dispatch_time.IsNull()) { + if (aLastEventDelay) { + *aLastEventDelay = TimeStamp::Now() - dispatch_time; + } + } + } + } else if (profiler_is_active()) { + if (aLastEventDelay) { + // if we just turned on the profiler, we don't have dispatch + // times for events already in the queue. + *aLastEventDelay = TimeDuration(); + } + } + + nsCOMPtr<nsIRunnable> result = mQueue.Pop(); + return result.forget(); +} + +template <size_t ItemsPerPage> +bool EventQueueInternal<ItemsPerPage>::IsEmpty( + const MutexAutoLock& aProofOfLock) { + return mQueue.IsEmpty(); +} + +template <size_t ItemsPerPage> +bool EventQueueInternal<ItemsPerPage>::HasReadyEvent( + const MutexAutoLock& aProofOfLock) { + return !IsEmpty(aProofOfLock); +} + +template <size_t ItemsPerPage> +size_t EventQueueInternal<ItemsPerPage>::Count( + const MutexAutoLock& aProofOfLock) const { + return mQueue.Count(); +} + +namespace mozilla { +template class EventQueueSized<16>; // Used by ThreadEventQueue +template class EventQueueSized<64>; // Used by ThrottledEventQueue +namespace detail { +template class EventQueueInternal<16>; // Used by ThreadEventQueue +template class EventQueueInternal<64>; // Used by ThrottledEventQueue +} // namespace detail +} // namespace mozilla diff --git a/xpcom/threads/EventQueue.h b/xpcom/threads/EventQueue.h new file mode 100644 index 0000000000..b7af71be82 --- /dev/null +++ b/xpcom/threads/EventQueue.h @@ -0,0 +1,135 @@ +/* -*- 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_EventQueue_h +#define mozilla_EventQueue_h + +#include "mozilla/Mutex.h" +#include "mozilla/Queue.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" + +namespace mozilla { + +#define EVENT_QUEUE_PRIORITY_LIST(EVENT_PRIORITY) \ + EVENT_PRIORITY(Idle, 0) \ + EVENT_PRIORITY(DeferredTimers, 1) \ + EVENT_PRIORITY(Low, 2) \ + EVENT_PRIORITY(InputLow, 3) \ + EVENT_PRIORITY(Normal, 4) \ + EVENT_PRIORITY(MediumHigh, 5) \ + EVENT_PRIORITY(InputHigh, 6) \ + EVENT_PRIORITY(Vsync, 7) \ + EVENT_PRIORITY(InputHighest, 8) \ + EVENT_PRIORITY(RenderBlocking, 9) \ + EVENT_PRIORITY(Control, 10) + +enum class EventQueuePriority { +#define EVENT_PRIORITY(NAME, VALUE) NAME = VALUE, + EVENT_QUEUE_PRIORITY_LIST(EVENT_PRIORITY) +#undef EVENT_PRIORITY + Invalid +}; + +class IdlePeriodState; + +namespace detail { + +// EventQueue is our unsynchronized event queue implementation. It is a queue +// of runnables used for non-main thread, as well as optionally providing +// forwarding to TaskController. +// +// Since EventQueue is unsynchronized, it should be wrapped in an outer +// SynchronizedEventQueue implementation (like ThreadEventQueue). +template <size_t ItemsPerPage> +class EventQueueInternal { + public: + explicit EventQueueInternal(bool aForwardToTC) : mForwardToTC(aForwardToTC) {} + + // Add an event to the end of the queue. Implementors are free to use + // aPriority however they wish. If the runnable supports + // nsIRunnablePriority and the implementing class supports + // prioritization, aPriority represents the result of calling + // nsIRunnablePriority::GetPriority(). *aDelay is time the event has + // already been delayed (used when moving an event from one queue to + // another) + void PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority, const MutexAutoLock& aProofOfLock, + mozilla::TimeDuration* aDelay = nullptr); + + // Get an event from the front of the queue. This should return null if the + // queue is non-empty but the event in front is not ready to run. + // *aLastEventDelay is the time the event spent in queues before being + // retrieved. + already_AddRefed<nsIRunnable> GetEvent( + const MutexAutoLock& aProofOfLock, + mozilla::TimeDuration* aLastEventDelay = nullptr); + + // Returns true if the queue is empty. Implies !HasReadyEvent(). + bool IsEmpty(const MutexAutoLock& aProofOfLock); + + // Returns true if the queue is non-empty and if the event in front is ready + // to run. Implies !IsEmpty(). This should return true iff GetEvent returns a + // non-null value. + bool HasReadyEvent(const MutexAutoLock& aProofOfLock); + + // Returns the number of events in the queue. + size_t Count(const MutexAutoLock& aProofOfLock) const; + // For some reason, if we put this in the .cpp file the linker can't find it + already_AddRefed<nsIRunnable> PeekEvent(const MutexAutoLock& aProofOfLock) { + if (mQueue.IsEmpty()) { + return nullptr; + } + + nsCOMPtr<nsIRunnable> result = mQueue.FirstElement(); + return result.forget(); + } + + void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t size = mQueue.ShallowSizeOfExcludingThis(aMallocSizeOf); + size += mDispatchTimes.ShallowSizeOfExcludingThis(aMallocSizeOf); + return size; + } + + private: + mozilla::Queue<nsCOMPtr<nsIRunnable>, ItemsPerPage> mQueue; + // This queue is only populated when the profiler is turned on. + mozilla::Queue<mozilla::TimeStamp, ItemsPerPage> mDispatchTimes; + TimeDuration mLastEventDelay; + // This indicates PutEvent forwards runnables to the TaskController. This + // should be true for the top level event queue on the main thread. + bool mForwardToTC; +}; + +} // namespace detail + +class EventQueue final : public mozilla::detail::EventQueueInternal<16> { + public: + explicit EventQueue(bool aForwardToTC = false) + : mozilla::detail::EventQueueInternal<16>(aForwardToTC) {} +}; + +template <size_t ItemsPerPage = 16> +class EventQueueSized final + : public mozilla::detail::EventQueueInternal<ItemsPerPage> { + public: + explicit EventQueueSized(bool aForwardToTC = false) + : mozilla::detail::EventQueueInternal<ItemsPerPage>(aForwardToTC) {} +}; + +} // namespace mozilla + +#endif // mozilla_EventQueue_h diff --git a/xpcom/threads/EventTargetCapability.h b/xpcom/threads/EventTargetCapability.h new file mode 100644 index 0000000000..0cd85a7523 --- /dev/null +++ b/xpcom/threads/EventTargetCapability.h @@ -0,0 +1,95 @@ +/* -*- 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_THREADS_EVENTTARGETCAPABILITY_H_ +#define XPCOM_THREADS_EVENTTARGETCAPABILITY_H_ + +#include "mozilla/ThreadSafety.h" +#include "nsIEventTarget.h" + +namespace mozilla { + +// A helper to ensure that data access and function usage only take place on a +// specific nsIEventTarget. +// +// This class works with Clang's thread safety analysis so that static analysis +// can ensure AssertOnCurrentThread is called before using guarded data and +// functions. +// +// This means using the class is similar to calling +// `MOZ_ASSERT(mTarget->IsOnCurrentThread())` +// prior to accessing things we expect to be on `mTarget`. However, using this +// helper has the added benefit that static analysis will warn you if you +// fail to assert prior to usage. +// +// The following is a basic example of a class using this to ensure +// a data member is only accessed on a specific target. +// +// class SomeMediaHandlerThing { +// public: +// SomeMediaHandlerThing(nsIEventTarget* aTarget) : mTargetCapability(aTarget) +// {} +// +// void UpdateMediaState() { +// mTargetCapability.Dispatch( +// NS_NewRunnableFunction("UpdateMediaState", [this] { +// mTargetCapability.AssertOnCurrentThread(); +// IncreaseMediaCount(); +// })); +// } +// +// private: +// void IncreaseMediaCount() MOZ_REQUIRES(mTargetCapability) { mMediaCount += +// 1; } +// +// uint32_t mMediaCount MOZ_GUARDED_BY(mTargetCapability) = 0; +// EventTargetCapability<nsIEventTarget> mTargetCapability; +// }; +// +// NOTE: If you need a thread-safety capability for specifically the main +// thread, the static `mozilla::sMainThreadCapability` capability exists, and +// can be asserted using `AssertIsOnMainThread()`. + +template <typename T> +class MOZ_CAPABILITY("event target") EventTargetCapability final { + static_assert(std::is_base_of_v<nsIEventTarget, T>, + "T must derive from nsIEventTarget"); + + public: + explicit EventTargetCapability(T* aTarget) : mTarget(aTarget) { + MOZ_ASSERT(mTarget, "mTarget should be non-null"); + } + ~EventTargetCapability() = default; + + EventTargetCapability(const EventTargetCapability&) = default; + EventTargetCapability(EventTargetCapability&&) = default; + EventTargetCapability& operator=(const EventTargetCapability&) = default; + EventTargetCapability& operator=(EventTargetCapability&&) = default; + + void AssertOnCurrentThread() const MOZ_ASSERT_CAPABILITY(this) { + MOZ_ASSERT(IsOnCurrentThread()); + } + + // Allow users to check if we're on the same thread as the event target. + bool IsOnCurrentThread() const { return mTarget->IsOnCurrentThread(); } + + // Allow users to get the event target, so classes don't have to store the + // target as a separate member to use it. + T* GetEventTarget() const { return mTarget; } + + // Helper to simplify dispatching to mTarget. + nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags = NS_DISPATCH_NORMAL) const { + return mTarget->Dispatch(std::move(aRunnable), aFlags); + } + + private: + RefPtr<T> mTarget; +}; + +} // namespace mozilla + +#endif // XPCOM_THREADS_EVENTTARGETCAPABILITY_H_ diff --git a/xpcom/threads/IdlePeriodState.cpp b/xpcom/threads/IdlePeriodState.cpp new file mode 100644 index 0000000000..ff1a9f4096 --- /dev/null +++ b/xpcom/threads/IdlePeriodState.cpp @@ -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/. */ + +#include "mozilla/AppShutdown.h" +#include "mozilla/IdlePeriodState.h" +#include "mozilla/StaticPrefs_idle_period.h" +#include "mozilla/ipc/IdleSchedulerChild.h" +#include "mozilla/dom/ContentChild.h" +#include "nsIIdlePeriod.h" +#include "nsThreadManager.h" +#include "nsXPCOM.h" +#include "nsXULAppAPI.h" + +static uint64_t sIdleRequestCounter = 0; + +namespace mozilla { + +IdlePeriodState::IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod) + : mIdlePeriod(aIdlePeriod) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); +} + +IdlePeriodState::~IdlePeriodState() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + if (mIdleScheduler) { + mIdleScheduler->Disconnect(); + } +} + +size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mIdlePeriod) { + n += aMallocSizeOf(mIdlePeriod); + } + + return n; +} + +void IdlePeriodState::FlagNotIdle() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + EnsureIsActive(); + if (mIdleToken && mIdleToken < TimeStamp::Now()) { + ClearIdleToken(); + } +} + +void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent); + EnsureIsPaused(aProofOfUnlock); + ClearIdleToken(); +} + +TimeStamp IdlePeriodState::GetIdleDeadlineInternal( + bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + bool shuttingDown; + TimeStamp localIdleDeadline = + GetLocalIdleDeadline(shuttingDown, aProofOfUnlock); + if (!localIdleDeadline) { + if (!aIsPeek) { + EnsureIsPaused(aProofOfUnlock); + ClearIdleToken(); + } + return TimeStamp(); + } + + TimeStamp idleDeadline = + mHasPendingEventsPromisedIdleEvent || shuttingDown + ? localIdleDeadline + : GetIdleToken(localIdleDeadline, aProofOfUnlock); + if (!idleDeadline) { + if (!aIsPeek) { + EnsureIsPaused(aProofOfUnlock); + + // Don't call ClearIdleToken() here, since we may have a pending + // request already. + // + // RequestIdleToken can do all sorts of IPC stuff that might + // take mutexes. This is one reason why we need the + // MutexAutoUnlock reference! + RequestIdleToken(localIdleDeadline); + } + return TimeStamp(); + } + + if (!aIsPeek) { + EnsureIsActive(); + } + return idleDeadline; +} + +TimeStamp IdlePeriodState::GetLocalIdleDeadline( + bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + // If we are shutting down, we won't honor the idle period, and we will + // always process idle runnables. This will ensure that the idle queue + // gets exhausted at shutdown time to prevent intermittently leaking + // some runnables inside that queue and even worse potentially leaving + // some important cleanup work unfinished. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads) || + nsThreadManager::get().GetCurrentThread()->ShuttingDown()) { + aShuttingDown = true; + return TimeStamp::Now(); + } + + aShuttingDown = false; + TimeStamp idleDeadline; + // This GetIdlePeriodHint() call is the reason we need a MutexAutoUnlock here. + mIdlePeriod->GetIdlePeriodHint(&idleDeadline); + + // If HasPendingEvents() has been called and it has returned true because of + // pending idle events, there is a risk that we may decide here that we aren't + // idle and return null, in which case HasPendingEvents() has effectively + // lied. Since we can't go back and fix the past, we have to adjust what we + // do here and forcefully pick the idle queue task here. Note that this means + // that we are choosing to run a task from the idle queue when we would + // normally decide that we aren't in an idle period, but this can only happen + // if we fall out of the idle period in between the call to HasPendingEvents() + // and here, which should hopefully be quite rare. We are effectively + // choosing to prioritize the sanity of our API semantics over the optimal + // scheduling. + if (!mHasPendingEventsPromisedIdleEvent && + (!idleDeadline || idleDeadline < TimeStamp::Now())) { + return TimeStamp(); + } + if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) { + // If HasPendingEvents() has been called and it has returned true, but we're + // no longer in the idle period, we must return a valid timestamp to pretend + // that we are still in the idle period. + return TimeStamp::Now(); + } + return idleDeadline; +} + +TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint, + const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + if (!ShouldGetIdleToken()) { + // If the process was in background, it may have an idle token, but it can + // be cleared now. + ClearIdleToken(); + return aLocalIdlePeriodHint; + } + + if (mIdleToken) { + TimeStamp now = TimeStamp::Now(); + if (mIdleToken < now) { + ClearIdleToken(); + return mIdleToken; + } + return mIdleToken < aLocalIdlePeriodHint ? mIdleToken + : aLocalIdlePeriodHint; + } + return TimeStamp(); +} + +void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mActive); + + if (!mIdleScheduler && ShouldGetIdleToken()) { + // For now cross-process idle scheduler is supported only on the main + // threads of the child processes. + mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler(); + if (mIdleScheduler) { + mIdleScheduler->Init(this); + } + } + + if (mIdleScheduler && !mIdleRequestId) { + TimeStamp now = TimeStamp::Now(); + if (aLocalIdlePeriodHint <= now) { + return; + } + + mIdleRequestId = ++sIdleRequestCounter; + mIdleScheduler->SendRequestIdleTime(mIdleRequestId, + aLocalIdlePeriodHint - now); + } +} + +void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + // We check the request ID. It's possible that the server may be granting a + // an ealier request that the client has since cancelled and re-requested. + if (mIdleRequestId == aId) { + mIdleToken = TimeStamp::Now() + aDuration; + } +} + +void IdlePeriodState::SetActive() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mActive); + if (mIdleScheduler) { + mIdleScheduler->SetActive(); + } + mActive = true; +} + +void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(mActive); + if (mIdleScheduler && mIdleScheduler->SetPaused()) { + // We may have gotten a free cpu core for running idle tasks. + // We don't try to catch the case when there are prioritized processes + // running. + + // This SendSchedule call is why we need the MutexAutoUnlock here, because + // IPC can do weird things with mutexes. + mIdleScheduler->SendSchedule(); + } + mActive = false; +} + +void IdlePeriodState::ClearIdleToken() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + if (mIdleRequestId) { + if (mIdleScheduler) { + // This SendIdleTimeUsed call is why we need to not be holding + // any locks here, because IPC can do weird things with mutexes. + // Ideally we'd have a MutexAutoUnlock& reference here, but some + // callers end up here while just not holding any locks at all. + mIdleScheduler->SendIdleTimeUsed(mIdleRequestId); + } + mIdleRequestId = 0; + mIdleToken = TimeStamp(); + } +} + +bool IdlePeriodState::ShouldGetIdleToken() { + return StaticPrefs::idle_period_cross_process_scheduling() && + dom::ContentChild::GetSingleton() && + dom::ContentChild::GetSingleton()->GetProcessPriority() < + hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND; +} +} // namespace mozilla diff --git a/xpcom/threads/IdlePeriodState.h b/xpcom/threads/IdlePeriodState.h new file mode 100644 index 0000000000..94d6615677 --- /dev/null +++ b/xpcom/threads/IdlePeriodState.h @@ -0,0 +1,196 @@ +/* -*- 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_IdlePeriodState_h +#define mozilla_IdlePeriodState_h + +/** + * A class for tracking the state of our idle period. This includes keeping + * track of both the state of our process-local idle period estimate and, for + * content processes, managing communication with the parent process for + * cross-pprocess idle detection. + */ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" + +#include <stdint.h> + +class nsIIdlePeriod; + +namespace mozilla { +class TaskManager; +namespace ipc { +class IdleSchedulerChild; +} // namespace ipc + +class IdlePeriodState { + public: + explicit IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod); + + ~IdlePeriodState(); + + // Integration with memory reporting. + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + // Notification that whoever we are tracking idle state for has found a + // non-idle task to process. + // + // Must not be called while holding any locks. + void FlagNotIdle(); + + // Notification that whoever we are tracking idle state for has no more + // tasks (idle or not) to process. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock); + + // Notification that whoever we are tracking idle state has idle tasks that + // they are considering ready to run and that we should keep claiming they are + // ready to run until they call ForgetPendingTaskGuarantee(). + void EnforcePendingTaskGuarantee() { + mHasPendingEventsPromisedIdleEvent = true; + } + + // Notification that whoever we are tracking idle state for is done with our + // "we have an idle event ready to run" guarantee. When this happens, we can + // reset mHasPendingEventsPromisedIdleEvent to false, because we have + // fulfilled our contract. + void ForgetPendingTaskGuarantee() { + mHasPendingEventsPromisedIdleEvent = false; + } + + // Update our cached idle deadline so consumers can use it while holding + // locks. Consumers must ClearCachedIdleDeadline() once they are done. + void UpdateCachedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) { + mCachedIdleDeadline = GetIdleDeadlineInternal(false, aProofOfUnlock); + } + + // If we have local idle deadline, but don't have an idle token, this will + // request such from the parent process when this is called in a child + // process. + void RequestIdleDeadlineIfNeeded(const MutexAutoUnlock& aProofOfUnlock) { + GetIdleDeadlineInternal(false, aProofOfUnlock); + } + + // Reset our cached idle deadline, so we stop allowing idle runnables to run. + void ClearCachedIdleDeadline() { mCachedIdleDeadline = TimeStamp(); } + + // Get the current cached idle deadline. This may return a null timestamp. + TimeStamp GetCachedIdleDeadline() { return mCachedIdleDeadline; } + + // Peek our current idle deadline into mCachedIdleDeadline. This can cause + // mCachedIdleDeadline to be a null timestamp (which means we are not idle + // right now). This method does not have any side-effects on our state, apart + // from guaranteeing that if it returns non-null then GetDeadlineForIdleTask + // will return non-null until ForgetPendingTaskGuarantee() is called, and its + // effects on mCachedIdleDeadline. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void CachePeekedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) { + mCachedIdleDeadline = GetIdleDeadlineInternal(true, aProofOfUnlock); + } + + void SetIdleToken(uint64_t aId, TimeDuration aDuration); + + bool IsActive() { return mActive; } + + protected: + void EnsureIsActive() { + if (!mActive) { + SetActive(); + } + } + + void EnsureIsPaused(const MutexAutoUnlock& aProofOfUnlock) { + if (mActive) { + SetPaused(aProofOfUnlock); + } + } + + // Returns a null TimeStamp if we're not in the idle period. + TimeStamp GetLocalIdleDeadline(bool& aShuttingDown, + const MutexAutoUnlock& aProofOfUnlock); + + // Gets the idle token, which is the end time of the idle period. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint, + const MutexAutoUnlock& aProofOfUnlock); + + // In case of child processes, requests idle time from the cross-process + // idle scheduler. + void RequestIdleToken(TimeStamp aLocalIdlePeriodHint); + + // Mark that we don't have idle time to use, nor are expecting to get an idle + // token from the idle scheduler. This must be called while not holding any + // locks, but some of the callers aren't holding locks to start with, so + // consumers just need to make sure they are not holding locks when they call + // this. + void ClearIdleToken(); + + // SetActive should be called when the event queue is running any type of + // tasks. + void SetActive(); + // SetPaused should be called once the event queue doesn't have more + // tasks to process, or is waiting for the idle token. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void SetPaused(const MutexAutoUnlock& aProofOfUnlock); + + // Get or peek our idle deadline. When peeking, we generally don't change any + // of our internal state. When getting, we may request an idle token as + // needed. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + TimeStamp GetIdleDeadlineInternal(bool aIsPeek, + const MutexAutoUnlock& aProofOfUnlock); + + // Whether we should be getting an idle token (i.e. are a content process + // and are using cross process idle scheduling). + bool ShouldGetIdleToken(); + + // Set to true if we have claimed we have a ready-to-run idle task when asked. + // In that case, we will ensure that we allow at least one task to run when + // someone tries to run a task, even if we have run out of idle period at that + // point. This ensures that we never fail to produce a task to run if we + // claim we have a task ready to run. + bool mHasPendingEventsPromisedIdleEvent = false; + + // mIdlePeriod keeps track of the current idle period. Calling + // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when + // the current idle period will end. + nsCOMPtr<nsIIdlePeriod> mIdlePeriod; + + // If non-null, this timestamp represents the end time of the idle period. An + // idle period starts when we get the idle token from the parent process and + // ends when either there are no more things we want to run at idle priority + // or mIdleToken < TimeStamp::Now(), so we have reached our idle deadline. + TimeStamp mIdleToken; + + // The id of the last idle request to the cross-process idle scheduler. + uint64_t mIdleRequestId = 0; + + // If we're in a content process, we use mIdleScheduler to communicate with + // the parent process for purposes of cross-process idle tracking. + RefPtr<mozilla::ipc::IdleSchedulerChild> mIdleScheduler; + + // Our cached idle deadline. This is set by UpdateCachedIdleDeadline() and + // cleared by ClearCachedIdleDeadline(). Consumers should do the former while + // not holding any locks, but may do the latter while holding locks. + TimeStamp mCachedIdleDeadline; + + // mActive is true when the PrioritizedEventQueue or TaskController we are + // associated with is running tasks. + bool mActive = true; +}; + +} // namespace mozilla + +#endif // mozilla_IdlePeriodState_h diff --git a/xpcom/threads/IdleTaskRunner.cpp b/xpcom/threads/IdleTaskRunner.cpp new file mode 100644 index 0000000000..e9bb895dee --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.cpp @@ -0,0 +1,281 @@ +/* -*- 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 "IdleTaskRunner.h" +#include "mozilla/TaskController.h" +#include "nsRefreshDriver.h" + +namespace mozilla { + +already_AddRefed<IdleTaskRunner> IdleTaskRunner::Create( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt) { + if (aMayStopProcessing && aMayStopProcessing()) { + return nullptr; + } + + RefPtr<IdleTaskRunner> runner = new IdleTaskRunner( + aCallback, aRunnableName, aStartDelay, aMaxDelay, aMinimumUsefulBudget, + aRepeating, aMayStopProcessing, aRequestInterrupt); + runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch. + return runner.forget(); +} + +class IdleTaskRunnerTask : public Task { + public: + explicit IdleTaskRunnerTask(IdleTaskRunner* aRunner) + : Task(Kind::MainThreadOnly, EventQueuePriority::Idle), + mRunner(aRunner), + mRequestInterrupt(aRunner->mRequestInterrupt) { + SetManager(TaskController::Get()->GetIdleTaskManager()); + } + + TaskResult Run() override { + if (mRunner) { + // IdleTaskRunner::Run can actually trigger the destruction of the + // IdleTaskRunner. Make sure it doesn't get destroyed before the method + // finished. + RefPtr<IdleTaskRunner> runner(mRunner); + runner->Run(); + } + return TaskResult::Complete; + } + + void SetIdleDeadline(TimeStamp aDeadline) override { + if (mRunner) { + mRunner->SetIdleDeadline(aDeadline); + } + } + + void Cancel() { mRunner = nullptr; } + + bool GetName(nsACString& aName) override { + if (mRunner) { + aName.Assign(mRunner->GetName()); + } else { + aName.Assign("ExpiredIdleTaskRunner"); + } + return true; + } + + void RequestInterrupt(uint32_t aInterruptPriority) override { + if (mRequestInterrupt) { + mRequestInterrupt(aInterruptPriority); + } + } + + private: + IdleTaskRunner* mRunner; + + // Copied here and invoked even if there is no mRunner currently, to avoid + // race conditions checking mRunner when an interrupt is requested. + IdleTaskRunner::RequestInterruptCallbackType mRequestInterrupt; +}; + +IdleTaskRunner::IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt) + : mCallback(aCallback), + mStartTime(TimeStamp::Now() + aStartDelay), + mMaxDelay(aMaxDelay), + mMinimumUsefulBudget(aMinimumUsefulBudget), + mRepeating(aRepeating), + mTimerActive(false), + mMayStopProcessing(aMayStopProcessing), + mRequestInterrupt(aRequestInterrupt), + mName(aRunnableName) {} + +void IdleTaskRunner::Run() { + if (!mCallback) { + return; + } + + // Deadline is null when called from timer or RunNextCollectorTimer rather + // than during idle time. + TimeStamp now = TimeStamp::Now(); + + // Note that if called from RunNextCollectorTimer, we may not have reached + // mStartTime yet. Pretend we are overdue for idle time. + bool overdueForIdle = mDeadline.IsNull(); + bool didRun = false; + bool allowIdleDispatch = false; + + if (mTask) { + // If we find ourselves here we should usually be running from this task, + // but there are exceptions. In any case we're doing the work now and don't + // need our task going forward unless we're re-scheduled. + nsRefreshDriver::CancelIdleTask(mTask); + // Extra safety, make sure a task can never have a dangling ptr. + mTask->Cancel(); + mTask = nullptr; + } + + if (overdueForIdle || ((now + mMinimumUsefulBudget) < mDeadline)) { + CancelTimer(); + didRun = mCallback(mDeadline); + // If we didn't do meaningful work, don't schedule using immediate + // idle dispatch, since that could lead to a loop until the idle + // period ends. + allowIdleDispatch = didRun; + } else if (now >= mDeadline) { + allowIdleDispatch = true; + } + + if (mCallback && (mRepeating || !didRun)) { + Schedule(allowIdleDispatch); + } else { + mCallback = nullptr; + } +} + +static void TimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleTaskRunner> runner = static_cast<IdleTaskRunner*>(aClosure); + runner->Run(); +} + +void IdleTaskRunner::SetIdleDeadline(mozilla::TimeStamp aDeadline) { + mDeadline = aDeadline; +} + +void IdleTaskRunner::SetMinimumUsefulBudget(int64_t aMinimumUsefulBudget) { + mMinimumUsefulBudget = TimeDuration::FromMilliseconds(aMinimumUsefulBudget); +} + +void IdleTaskRunner::SetTimer(TimeDuration aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTarget->IsOnCurrentThread()); + // aTarget is always the main thread event target provided from + // NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that + // CollectorRunner always run specifically the main thread. + SetTimerInternal(aDelay); +} + +void IdleTaskRunner::Cancel() { + CancelTimer(); + mTimer = nullptr; + mScheduleTimer = nullptr; + mCallback = nullptr; +} + +static void ScheduleTimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleTaskRunner> runnable = static_cast<IdleTaskRunner*>(aClosure); + runnable->Schedule(true); +} + +void IdleTaskRunner::Schedule(bool aAllowIdleDispatch) { + if (!mCallback) { + return; + } + + if (mMayStopProcessing && mMayStopProcessing()) { + Cancel(); + return; + } + + mDeadline = TimeStamp(); + + TimeStamp now = TimeStamp::Now(); + + if (aAllowIdleDispatch) { + SetTimerInternal(mMaxDelay); + if (!mTask) { + mTask = new IdleTaskRunnerTask(this); + RefPtr<Task> task(mTask); + TaskController::Get()->AddTask(task.forget()); + } + return; + } + + bool useRefreshDriver = false; + if (now >= mStartTime) { + // Detect whether the refresh driver is ticking by checking if + // GetIdleDeadlineHint returns its input parameter. + useRefreshDriver = + (nsRefreshDriver::GetIdleDeadlineHint( + now, nsRefreshDriver::IdleCheck::OnlyThisProcessRefreshDriver) != + now); + } + + if (useRefreshDriver) { + if (!mTask) { + // If a task was already scheduled, no point rescheduling. + mTask = new IdleTaskRunnerTask(this); + // RefreshDriver is ticking, let it schedule the idle dispatch. + nsRefreshDriver::DispatchIdleTaskAfterTickUnlessExists(mTask); + } + // Ensure we get called at some point, even if RefreshDriver is stopped. + SetTimerInternal(mMaxDelay); + } else { + // RefreshDriver doesn't seem to be running. + if (!mScheduleTimer) { + mScheduleTimer = NS_NewTimer(); + if (!mScheduleTimer) { + return; + } + } else { + mScheduleTimer->Cancel(); + } + // We weren't allowed to do idle dispatch immediately, do it after a + // short timeout. (Or wait for our start time if we haven't started yet.) + uint32_t waitToSchedule = 16; /* ms */ + if (now < mStartTime) { + // + 1 to round milliseconds up to be sure to wait until after + // mStartTime. + waitToSchedule = (mStartTime - now).ToMilliseconds() + 1; + } + mScheduleTimer->InitWithNamedFuncCallback( + ScheduleTimedOut, this, waitToSchedule, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, mName); + } +} + +IdleTaskRunner::~IdleTaskRunner() { CancelTimer(); } + +void IdleTaskRunner::CancelTimer() { + if (mTask) { + nsRefreshDriver::CancelIdleTask(mTask); + mTask->Cancel(); + mTask = nullptr; + } + if (mTimer) { + mTimer->Cancel(); + } + if (mScheduleTimer) { + mScheduleTimer->Cancel(); + } + mTimerActive = false; +} + +void IdleTaskRunner::SetTimerInternal(TimeDuration aDelay) { + if (mTimerActive) { + return; + } + ResetTimer(aDelay); +} + +void IdleTaskRunner::ResetTimer(TimeDuration aDelay) { + mTimerActive = false; + + if (!mTimer) { + mTimer = NS_NewTimer(); + } else { + mTimer->Cancel(); + } + + if (mTimer) { + mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay.ToMilliseconds(), + nsITimer::TYPE_ONE_SHOT, mName); + mTimerActive = true; + } +} + +} // end of namespace mozilla diff --git a/xpcom/threads/IdleTaskRunner.h b/xpcom/threads/IdleTaskRunner.h new file mode 100644 index 0000000000..97a8c0b09e --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.h @@ -0,0 +1,125 @@ +/* -*- 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 IdleTaskRunner_h +#define IdleTaskRunner_h + +#include "mozilla/TimeStamp.h" +#include "nsIEventTarget.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include <functional> + +namespace mozilla { + +class IdleTaskRunnerTask; + +// A general purpose repeating callback runner (it can be configured to a +// one-time runner, too.) If it is running repeatedly, one has to either +// explicitly Cancel() the runner or have MayStopProcessing() callback return +// true to completely remove the runner. +class IdleTaskRunner { + public: + friend class IdleTaskRunnerTask; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IdleTaskRunner) + + // Return true if some meaningful work was done. + using CallbackType = std::function<bool(TimeStamp aDeadline)>; + + // A callback for "stop processing" decision. Return true to + // stop processing. This can be an alternative to Cancel() or + // work together in different way. + using MayStopProcessingCallbackType = std::function<bool()>; + + // A callback to be invoked when an interrupt is requested + // (eg during an idle activity when the user presses a key.) + // The callback takes an "interrupt priority" value as its + // sole parameter. + using RequestInterruptCallbackType = std::function<void(uint32_t)>; + + public: + // An IdleTaskRunner has (up to) three phases: + // + // - (duration aStartDelay) waiting to run (aStartDelay can be zero) + // + // - (duration aMaxDelay) attempting to find a long enough amount of idle + // time, at least aMinimumUsefulBudget + // + // - overdue for idle time, run as soon as possible + // + // If aRepeating is true, then aStartDelay applies only to the first run; the + // second run will attempt to run in the first idle slice that is long + // enough. + // + // All durations are in milliseconds. + // + static already_AddRefed<IdleTaskRunner> Create( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt = nullptr); + + void Run(); + + // (Used by the task triggering code.) Record the end of the current idle + // period, or null if not running during idle time. + void SetIdleDeadline(mozilla::TimeStamp aDeadline); + + // If the timer is already active, SetTimer doesn't do anything. + void SetTimer(TimeDuration aDelay, nsIEventTarget* aTarget); + + void ResetTimer(TimeDuration aDelay); + + // Update the minimum idle time that this callback would be invoked for. + void SetMinimumUsefulBudget(int64_t aMinimumUsefulBudget); + + void Cancel(); + + void Schedule(bool aAllowIdleDispatch); + + const char* GetName() { return mName; } + + private: + explicit IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt); + ~IdleTaskRunner(); + void CancelTimer(); + void SetTimerInternal(TimeDuration aDelay); + + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsITimer> mScheduleTimer; + CallbackType mCallback; + + // Do not run until this time. + const mozilla::TimeStamp mStartTime; + + // Wait this long for idle time before giving up and running a non-idle + // callback. + TimeDuration mMaxDelay; + + // If running during idle time, the expected end of the current idle period. + // The null timestamp when the run is triggered by aMaxDelay instead of idle. + TimeStamp mDeadline; + + // The least duration worth calling the callback for during idle time. + TimeDuration mMinimumUsefulBudget; + + bool mRepeating; + bool mTimerActive; + MayStopProcessingCallbackType mMayStopProcessing; + RequestInterruptCallbackType mRequestInterrupt; + const char* mName; + RefPtr<IdleTaskRunnerTask> mTask; +}; + +} // end of namespace mozilla. + +#endif diff --git a/xpcom/threads/InputTaskManager.cpp b/xpcom/threads/InputTaskManager.cpp new file mode 100644 index 0000000000..42b2814290 --- /dev/null +++ b/xpcom/threads/InputTaskManager.cpp @@ -0,0 +1,156 @@ +/* -*- 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 "InputTaskManager.h" +#include "VsyncTaskManager.h" +#include "nsRefreshDriver.h" + +namespace mozilla { + +StaticRefPtr<InputTaskManager> InputTaskManager::gInputTaskManager; + +void InputTaskManager::EnableInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_DISABLED); + mInputQueueState = STATE_ENABLED; +} + +void InputTaskManager::FlushInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_ENABLED || + mInputQueueState == STATE_SUSPEND); + mInputQueueState = + mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND; +} + +void InputTaskManager::SuspendInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_ENABLED || + mInputQueueState == STATE_FLUSHING); + mInputQueueState = STATE_SUSPEND; +} + +void InputTaskManager::ResumeInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_SUSPEND); + mInputQueueState = STATE_ENABLED; +} + +int32_t InputTaskManager::GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) { + // When the state is disabled, the input task that we have is + // very likely SuspendInputEventQueue, so here we also use + // normal priority as ResumeInputEventQueue, FlushInputEventQueue and + // SetInputEventQueueEnabled all uses normal priority, to + // ensure the ordering is correct. + if (State() == InputTaskManager::STATE_DISABLED) { + return static_cast<int32_t>(EventQueuePriority::Normal) - + static_cast<int32_t>(EventQueuePriority::InputHigh); + } + + return GetPriorityModifierForEventLoopTurnForStrictVsyncAlignment(); +} + +void InputTaskManager::WillRunTask() { + TaskManager::WillRunTask(); + mInputPriorityController.WillRunTask(); +} + +int32_t +InputTaskManager::GetPriorityModifierForEventLoopTurnForStrictVsyncAlignment() { + MOZ_ASSERT(!IsSuspended()); + + size_t inputCount = PendingTaskCount(); + if (inputCount > 0 && + mInputPriorityController.ShouldUseHighestPriority(this)) { + return static_cast<int32_t>(EventQueuePriority::InputHighest) - + static_cast<int32_t>(EventQueuePriority::InputHigh); + } + + if (State() == STATE_FLUSHING || + nsRefreshDriver::GetNextTickHint().isNothing()) { + return 0; + } + + return static_cast<int32_t>(EventQueuePriority::InputLow) - + static_cast<int32_t>(EventQueuePriority::InputHigh); +} + +InputTaskManager::InputPriorityController::InputPriorityController() + : mInputVsyncState(InputVsyncState::NoPendingVsync) {} + +bool InputTaskManager::InputPriorityController::ShouldUseHighestPriority( + InputTaskManager* aInputTaskManager) { + if (mInputVsyncState == InputVsyncState::HasPendingVsync) { + return true; + } + + if (mInputVsyncState == InputVsyncState::RunVsync) { + return false; + } + + if (mInputVsyncState == InputVsyncState::NoPendingVsync && + VsyncTaskManager::Get()->PendingTaskCount()) { + EnterPendingVsyncState(aInputTaskManager->PendingTaskCount()); + return true; + } + + return false; +} + +void InputTaskManager::InputPriorityController::EnterPendingVsyncState( + uint32_t aNumPendingTasks) { + MOZ_ASSERT(mInputVsyncState == InputVsyncState::NoPendingVsync); + + mInputVsyncState = InputVsyncState::HasPendingVsync; + mMaxInputTasksToRun = aNumPendingTasks; + mRunInputStartTime = TimeStamp::Now(); +} + +void InputTaskManager::InputPriorityController::WillRunVsync() { + if (mInputVsyncState == InputVsyncState::RunVsync || + mInputVsyncState == InputVsyncState::HasPendingVsync) { + LeavePendingVsyncState(false); + } +} + +void InputTaskManager::InputPriorityController::LeavePendingVsyncState( + bool aRunVsync) { + if (aRunVsync) { + MOZ_ASSERT(mInputVsyncState == InputVsyncState::HasPendingVsync); + mInputVsyncState = InputVsyncState::RunVsync; + } else { + mInputVsyncState = InputVsyncState::NoPendingVsync; + } + + mMaxInputTasksToRun = 0; +} + +void InputTaskManager::InputPriorityController::WillRunTask() { + switch (mInputVsyncState) { + case InputVsyncState::NoPendingVsync: + return; + case InputVsyncState::HasPendingVsync: + MOZ_ASSERT(mMaxInputTasksToRun > 0); + --mMaxInputTasksToRun; + if (!mMaxInputTasksToRun || + TimeStamp::Now() - mRunInputStartTime >= + TimeDuration::FromMilliseconds( + StaticPrefs::dom_input_event_queue_duration_max())) { + LeavePendingVsyncState(true); + } + return; + default: + MOZ_DIAGNOSTIC_ASSERT( + false, "Shouldn't run this input task when we suppose to run vsync"); + return; + } +} + +// static +void InputTaskManager::Init() { gInputTaskManager = new InputTaskManager(); } + +} // namespace mozilla diff --git a/xpcom/threads/InputTaskManager.h b/xpcom/threads/InputTaskManager.h new file mode 100644 index 0000000000..2f920a31ae --- /dev/null +++ b/xpcom/threads/InputTaskManager.h @@ -0,0 +1,141 @@ +/* -*- 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_InputTaskManager_h +#define mozilla_InputTaskManager_h + +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "TaskController.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/StaticPrefs_dom.h" + +namespace mozilla { + +class InputTaskManager : public TaskManager { + public: + int32_t GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) final; + void WillRunTask() final; + + enum InputEventQueueState { + STATE_DISABLED, + STATE_FLUSHING, + STATE_SUSPEND, + STATE_ENABLED + }; + + void EnableInputEventPrioritization(); + void FlushInputEventPrioritization(); + void SuspendInputEventPrioritization(); + void ResumeInputEventPrioritization(); + + InputEventQueueState State() { return mInputQueueState; } + + void SetState(InputEventQueueState aState) { mInputQueueState = aState; } + + static InputTaskManager* Get() { return gInputTaskManager.get(); } + static void Cleanup() { gInputTaskManager = nullptr; } + static void Init(); + + bool IsSuspended(const MutexAutoLock& aProofOfLock) override { + MOZ_ASSERT(NS_IsMainThread()); + return mInputQueueState == STATE_SUSPEND || mSuspensionLevel > 0; + } + + bool IsSuspended() { + MOZ_ASSERT(NS_IsMainThread()); + return mSuspensionLevel > 0; + } + + void IncSuspensionLevel() { + MOZ_ASSERT(NS_IsMainThread()); + ++mSuspensionLevel; + } + + void DecSuspensionLevel() { + MOZ_ASSERT(NS_IsMainThread()); + --mSuspensionLevel; + } + + static bool CanSuspendInputEvent() { + // Ensure it's content process because InputTaskManager only + // works in e10s. + // + // Input tasks will have nullptr as their task manager when the + // event queue state is STATE_DISABLED, so we can't suspend + // input events. + return XRE_IsContentProcess() && + StaticPrefs::dom_input_events_canSuspendInBCG_enabled() && + InputTaskManager::Get()->State() != + InputEventQueueState::STATE_DISABLED; + } + + void NotifyVsync() { mInputPriorityController.WillRunVsync(); } + + private: + InputTaskManager() : mInputQueueState(STATE_DISABLED) {} + + class InputPriorityController { + public: + InputPriorityController(); + // Determines whether we should use the highest input priority for input + // tasks + bool ShouldUseHighestPriority(InputTaskManager*); + + void WillRunVsync(); + + // Gets called when a input task is going to run; If the current + // input vsync state is `HasPendingVsync`, determines whether we + // should continue running input tasks or leave the `HasPendingVsync` state + // based on + // 1. Whether we still have time to process input tasks + // 2. Whether we have processed the max number of tasks that + // we should process. + void WillRunTask(); + + private: + // Used to represents the relationship between Input and Vsync. + // + // HasPendingVsync: There are pending vsync tasks and we are using + // InputHighest priority for inputs. + // NoPendingVsync: No pending vsync tasks and no need to use InputHighest + // priority. + // RunVsync: Finished running input tasks and the vsync task + // should be run. + enum class InputVsyncState { + HasPendingVsync, + NoPendingVsync, + RunVsync, + }; + + void EnterPendingVsyncState(uint32_t aNumPendingTasks); + void LeavePendingVsyncState(bool aRunVsync); + + // Stores the number of pending input tasks when we enter the + // InputVsyncState::HasPendingVsync state. + uint32_t mMaxInputTasksToRun = 0; + + InputVsyncState mInputVsyncState; + + TimeStamp mRunInputStartTime; + }; + + int32_t GetPriorityModifierForEventLoopTurnForStrictVsyncAlignment(); + + Atomic<InputEventQueueState> mInputQueueState; + + static StaticRefPtr<InputTaskManager> gInputTaskManager; + + // Number of BCGs have asked InputTaskManager to suspend input events + uint32_t mSuspensionLevel = 0; + + InputPriorityController mInputPriorityController; +}; + +} // namespace mozilla + +#endif // mozilla_InputTaskManager_h diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp new file mode 100644 index 0000000000..4187fcc4ff --- /dev/null +++ b/xpcom/threads/LazyIdleThread.cpp @@ -0,0 +1,126 @@ +/* -*- 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 "LazyIdleThread.h" + +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +#ifdef DEBUG +# define ASSERT_OWNING_THREAD() \ + do { \ + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \ + } while (0) +#else +# define ASSERT_OWNING_THREAD() /* nothing */ +#endif + +namespace mozilla { + +LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const char* aName, + ShutdownMethod aShutdownMethod) + : mOwningEventTarget(GetCurrentSerialEventTarget()), + mThreadPool(new nsThreadPool()), + mTaskQueue(TaskQueue::Create(do_AddRef(mThreadPool), aName)) { + // Configure the threadpool to host a single thread. It will be responsible + // for managing the thread's lifetime. + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetThreadLimit(1)); + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadLimit(1)); + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadTimeout(aIdleTimeoutMS)); + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetName(nsDependentCString(aName))); + + if (aShutdownMethod == ShutdownMethod::AutomaticShutdown && + NS_IsMainThread()) { + if (nsCOMPtr<nsIObserverService> obs = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) { + MOZ_ALWAYS_SUCCEEDS( + obs->AddObserver(this, "xpcom-shutdown-threads", false)); + } + } +} + +static void LazyIdleThreadShutdown(nsThreadPool* aThreadPool, + TaskQueue* aTaskQueue) { + aTaskQueue->BeginShutdown(); + aTaskQueue->AwaitShutdownAndIdle(); + aThreadPool->Shutdown(); +} + +LazyIdleThread::~LazyIdleThread() { + if (!mShutdown) { + mOwningEventTarget->Dispatch(NS_NewRunnableFunction( + "LazyIdleThread::~LazyIdleThread", + [threadPool = mThreadPool, taskQueue = mTaskQueue] { + LazyIdleThreadShutdown(threadPool, taskQueue); + })); + } +} + +void LazyIdleThread::Shutdown() { + ASSERT_OWNING_THREAD(); + + if (!mShutdown) { + mShutdown = true; + LazyIdleThreadShutdown(mThreadPool, mTaskQueue); + } +} + +nsresult LazyIdleThread::SetListener(nsIThreadPoolListener* aListener) { + return mThreadPool->SetListener(aListener); +} + +NS_IMPL_ISUPPORTS(LazyIdleThread, nsIEventTarget, nsISerialEventTarget, + nsIObserver) + +NS_IMETHODIMP +LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + return mTaskQueue->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) { + return mTaskQueue->IsOnCurrentThread(aIsOnCurrentThread); +} + +NS_IMETHODIMP_(bool) +LazyIdleThread::IsOnCurrentThreadInfallible() { + return mTaskQueue->IsOnCurrentThreadInfallible(); +} + +NS_IMETHODIMP +LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); + + Shutdown(); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/threads/LazyIdleThread.h b/xpcom/threads/LazyIdleThread.h new file mode 100644 index 0000000000..8a720bc69b --- /dev/null +++ b/xpcom/threads/LazyIdleThread.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 mozilla_lazyidlethread_h__ +#define mozilla_lazyidlethread_h__ + +#ifndef MOZILLA_INTERNAL_API +# error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)." +#endif + +#include "mozilla/TaskQueue.h" +#include "nsIObserver.h" +#include "nsThreadPool.h" + +namespace mozilla { + +/** + * This class provides a basic event target that creates its thread lazily and + * destroys its thread after a period of inactivity. It may be created and used + * on any thread but it may only be shut down from the thread on which it is + * created. + */ +class LazyIdleThread final : public nsISerialEventTarget, public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSIOBSERVER + + /** + * If AutomaticShutdown is specified, and the LazyIdleThread is created on the + * main thread, Shutdown() will automatically be called during + * xpcom-shutdown-threads. + */ + enum ShutdownMethod { AutomaticShutdown = 0, ManualShutdown }; + + /** + * Create a new LazyIdleThread that will destroy its thread after the given + * number of milliseconds. + */ + LazyIdleThread(uint32_t aIdleTimeoutMS, const char* aName, + ShutdownMethod aShutdownMethod = AutomaticShutdown); + + /** + * Shuts down the LazyIdleThread, waiting for any pending work to complete. + * Must be called from mOwningEventTarget. + */ + void Shutdown(); + + /** + * Register a nsIThreadPoolListener on the underlying threadpool to track the + * thread as it is created/destroyed. + */ + nsresult SetListener(nsIThreadPoolListener* aListener); + + private: + /** + * Asynchronously shuts down the LazyIdleThread on mOwningEventTarget. + */ + ~LazyIdleThread(); + + /** + * The thread which created this LazyIdleThread and is responsible for + * shutting it down. + */ + const nsCOMPtr<nsISerialEventTarget> mOwningEventTarget; + + /** + * The single-thread backing threadpool which provides the actual threads used + * by LazyIdleThread, and implements the timeout. + */ + const RefPtr<nsThreadPool> mThreadPool; + + /** + * The serial event target providing a `nsISerialEventTarget` implementation + * when on the LazyIdleThread. + */ + const RefPtr<TaskQueue> mTaskQueue; + + /** + * Only safe to access on the owning thread or in the destructor (as no other + * threads have access then). If `true`, means the LazyIdleThread has already + * been shut down, so it does not need to be shut down asynchronously from the + * destructor. + */ + bool mShutdown = false; +}; + +} // namespace mozilla + +#endif // mozilla_lazyidlethread_h__ diff --git a/xpcom/threads/LeakRefPtr.h b/xpcom/threads/LeakRefPtr.h new file mode 100644 index 0000000000..ee260dad7e --- /dev/null +++ b/xpcom/threads/LeakRefPtr.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/. */ + +/* Smart pointer which leaks its owning refcounted object by default. */ + +#ifndef LeakRefPtr_h +#define LeakRefPtr_h + +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { + +/** + * Instance of this class behaves like a raw pointer which leaks the + * resource it's owning if not explicitly released. + */ +template <class T> +class LeakRefPtr { + public: + explicit LeakRefPtr(already_AddRefed<T>&& aPtr) : mRawPtr(aPtr.take()) {} + + explicit operator bool() const { return !!mRawPtr; } + + LeakRefPtr<T>& operator=(already_AddRefed<T>&& aPtr) { + mRawPtr = aPtr.take(); + return *this; + } + + T* get() const { return mRawPtr; } + + already_AddRefed<T> take() { + T* rawPtr = mRawPtr; + mRawPtr = nullptr; + return already_AddRefed<T>(rawPtr); + } + + void release() { NS_RELEASE(mRawPtr); } + + private: + T* MOZ_OWNING_REF mRawPtr; +}; + +} // namespace mozilla + +#endif // LeakRefPtr_h diff --git a/xpcom/threads/MainThreadIdlePeriod.cpp b/xpcom/threads/MainThreadIdlePeriod.cpp new file mode 100644 index 0000000000..805626dd67 --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.cpp @@ -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/. */ + +#include "MainThreadIdlePeriod.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_idle_period.h" +#include "mozilla/dom/Document.h" +#include "VRManagerChild.h" +#include "nsRefreshDriver.h" +#include "nsThreadUtils.h" + +// The amount of idle time (milliseconds) reserved for a long idle period. +static const double kLongIdlePeriodMS = 50.0; + +// The minimum amount of time (milliseconds) required for an idle period to be +// scheduled on the main thread. N.B. layout.idle_period.time_limit adds +// padding at the end of the idle period, which makes the point in time that we +// expect to become busy again be: +// now + idle_period.min + layout.idle_period.time_limit +// or during page load +// now + idle_period.during_page_load.min + layout.idle_period.time_limit + +static const uint32_t kMaxTimerThreadBound = 25; // Number of timers to check. + +namespace mozilla { + +NS_IMETHODIMP +MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aIdleDeadline); + + TimeStamp now = TimeStamp::Now(); + TimeStamp currentGuess = + now + TimeDuration::FromMilliseconds(kLongIdlePeriodMS); + + currentGuess = nsRefreshDriver::GetIdleDeadlineHint( + currentGuess, nsRefreshDriver::IdleCheck::AllVsyncListeners); + if (XRE_IsContentProcess()) { + currentGuess = gfx::VRManagerChild::GetIdleDeadlineHint(currentGuess); + } + currentGuess = NS_GetTimerDeadlineHintOnCurrentThread(currentGuess, + kMaxTimerThreadBound); + + // If the idle period is too small, then just return a null time + // to indicate we are busy. Otherwise return the actual deadline. + double highRateMultiplier = nsRefreshDriver::HighRateMultiplier(); + TimeDuration minIdlePeriod = TimeDuration::FromMilliseconds( + std::max(highRateMultiplier * StaticPrefs::idle_period_min(), 1.0)); + bool busySoon = currentGuess.IsNull() || + (now >= (currentGuess - minIdlePeriod)) || + currentGuess < mLastIdleDeadline; + + // During page load use higher minimum idle period. + if (!busySoon && XRE_IsContentProcess() && + mozilla::dom::Document::HasRecentlyStartedForegroundLoads()) { + TimeDuration minIdlePeriod = TimeDuration::FromMilliseconds(std::max( + highRateMultiplier * StaticPrefs::idle_period_during_page_load_min(), + 1.0)); + busySoon = (now >= (currentGuess - minIdlePeriod)); + } + + if (!busySoon) { + *aIdleDeadline = mLastIdleDeadline = currentGuess; + } + + return NS_OK; +} + +/* static */ +float MainThreadIdlePeriod::GetLongIdlePeriod() { return kLongIdlePeriodMS; } + +} // namespace mozilla diff --git a/xpcom/threads/MainThreadIdlePeriod.h b/xpcom/threads/MainThreadIdlePeriod.h new file mode 100644 index 0000000000..7e382f6771 --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.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 mozilla_dom_mainthreadidleperiod_h +#define mozilla_dom_mainthreadidleperiod_h + +#include "mozilla/TimeStamp.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class MainThreadIdlePeriod final : public IdlePeriod { + public: + NS_DECL_NSIIDLEPERIOD + + MainThreadIdlePeriod() : mLastIdleDeadline(TimeStamp::Now()) {} + + static float GetLongIdlePeriod(); + + private: + virtual ~MainThreadIdlePeriod() = default; + + TimeStamp mLastIdleDeadline; +}; + +} // namespace mozilla + +#endif // mozilla_dom_mainthreadidleperiod_h diff --git a/xpcom/threads/MainThreadUtils.h b/xpcom/threads/MainThreadUtils.h new file mode 100644 index 0000000000..152805eb66 --- /dev/null +++ b/xpcom/threads/MainThreadUtils.h @@ -0,0 +1,60 @@ +/* -*- 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 MainThreadUtils_h_ +#define MainThreadUtils_h_ + +#include "mozilla/Assertions.h" +#include "mozilla/ThreadSafety.h" +#include "nscore.h" + +class nsIThread; + +/** + * Get a reference to the main thread. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetMainThread(nsIThread** aResult); + +#ifdef MOZILLA_INTERNAL_API +bool NS_IsMainThreadTLSInitialized(); +extern "C" { +bool NS_IsMainThread(); +} + +namespace mozilla { + +/** + * A dummy static capability for the thread safety analysis which can be + * required by functions and members using `MOZ_REQUIRE(sMainThreadCapability)` + * and `MOZ_GUARDED_BY(sMainThreadCapability)` and asserted using + * `AssertIsOnMainThread()`. + * + * If you want a thread-safety-analysis capability for a non-main thread, + * consider using the `EventTargetCapability` type. + */ +class MOZ_CAPABILITY("main thread") MainThreadCapability final {}; +constexpr MainThreadCapability sMainThreadCapability; + +# ifdef DEBUG +void AssertIsOnMainThread() MOZ_ASSERT_CAPABILITY(sMainThreadCapability); +# else +inline void AssertIsOnMainThread() + MOZ_ASSERT_CAPABILITY(sMainThreadCapability) {} +# endif + +inline void ReleaseAssertIsOnMainThread() + MOZ_ASSERT_CAPABILITY(sMainThreadCapability) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); +} + +} // namespace mozilla + +#endif + +#endif // MainThreadUtils_h_ diff --git a/xpcom/threads/Monitor.h b/xpcom/threads/Monitor.h new file mode 100644 index 0000000000..7bb91f9ad7 --- /dev/null +++ b/xpcom/threads/Monitor.h @@ -0,0 +1,316 @@ +/* -*- 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_Monitor_h +#define mozilla_Monitor_h + +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" + +namespace mozilla { + +/** + * Monitor provides a *non*-reentrant monitor: *not* a Java-style + * monitor. If your code needs support for reentrancy, use + * ReentrantMonitor instead. (Rarely should reentrancy be needed.) + * + * Instead of directly calling Monitor methods, it's safer and simpler + * to instead use the RAII wrappers MonitorAutoLock and + * MonitorAutoUnlock. + */ +class MOZ_CAPABILITY("monitor") Monitor { + public: + explicit Monitor(const char* aName) + : mMutex(aName), mCondVar(mMutex, "[Monitor.mCondVar]") {} + + ~Monitor() = default; + + void Lock() MOZ_CAPABILITY_ACQUIRE() { mMutex.Lock(); } + [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true) { + return mMutex.TryLock(); + } + void Unlock() MOZ_CAPABILITY_RELEASE() { mMutex.Unlock(); } + + void Wait() MOZ_REQUIRES(this) { mCondVar.Wait(); } + CVStatus Wait(TimeDuration aDuration) MOZ_REQUIRES(this) { + return mCondVar.Wait(aDuration); + } + + void Notify() { mCondVar.Notify(); } + void NotifyAll() { mCondVar.NotifyAll(); } + + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this) { + mMutex.AssertCurrentThreadOwns(); + } + void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) { + mMutex.AssertNotCurrentThreadOwns(); + } + + private: + Monitor() = delete; + Monitor(const Monitor&) = delete; + Monitor& operator=(const Monitor&) = delete; + + Mutex mMutex; + CondVar mCondVar; +}; + +/** + * MonitorSingleWriter + * + * Monitor where a single writer exists, so that reads from the same thread + * will not generate data races or consistency issues. + * + * When possible, use MonitorAutoLock/MonitorAutoUnlock to lock/unlock this + * monitor within a scope, instead of calling Lock/Unlock directly. + * + * This requires an object implementing Mutex's SingleWriterLockOwner, so + * we can do correct-thread checks. + */ + +class MonitorSingleWriter : public Monitor { + public: + // aOwner should be the object that contains the mutex, typically. We + // will use that object (which must have a lifetime the same or greater + // than this object) to verify that we're running on the correct thread, + // typically only in DEBUG builds + explicit MonitorSingleWriter(const char* aName, SingleWriterLockOwner* aOwner) + : Monitor(aName) +#ifdef DEBUG + , + mOwner(aOwner) +#endif + { + MOZ_COUNT_CTOR(MonitorSingleWriter); + MOZ_ASSERT(mOwner); + } + + MOZ_COUNTED_DTOR(MonitorSingleWriter) + + void AssertOnWritingThread() const MOZ_ASSERT_CAPABILITY(this) { + MOZ_ASSERT(mOwner->OnWritingThread()); + } + void AssertOnWritingThreadOrHeld() const MOZ_ASSERT_CAPABILITY(this) { +#ifdef DEBUG + if (!mOwner->OnWritingThread()) { + AssertCurrentThreadOwns(); + } +#endif + } + + private: +#ifdef DEBUG + SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF( + "This is normally the object that contains the MonitorSingleWriter, so " + "we don't want to hold a reference to ourselves"); +#endif + + MonitorSingleWriter() = delete; + MonitorSingleWriter(const MonitorSingleWriter&) = delete; + MonitorSingleWriter& operator=(const MonitorSingleWriter&) = delete; +}; + +/** + * Lock the monitor for the lexical scope instances of this class are + * bound to (except for MonitorAutoUnlock in nested scopes). + * + * The monitor must be unlocked when instances of this class are + * created. + */ +namespace detail { +template <typename T> +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS BaseMonitorAutoLock { + public: + explicit BaseMonitorAutoLock(T& aMonitor) MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Lock(); + } + + ~BaseMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor->Unlock(); } + // It's very hard to mess up MonitorAutoLock lock(mMonitor); ... lock.Wait(). + // The only way you can fail to hold the lock when you call lock.Wait() is to + // use MonitorAutoUnlock. For now we'll ignore that case. + void Wait() { + mMonitor->AssertCurrentThreadOwns(); + mMonitor->Wait(); + } + CVStatus Wait(TimeDuration aDuration) { + mMonitor->AssertCurrentThreadOwns(); + return mMonitor->Wait(aDuration); + } + + void Notify() { mMonitor->Notify(); } + void NotifyAll() { mMonitor->NotifyAll(); } + + // Assert that aLock is the monitor passed to the constructor and that the + // current thread owns the monitor. In coding patterns such as: + // + // void LockedMethod(const BaseAutoLock<T>& aProofOfLock) + // { + // aProofOfLock.AssertOwns(mMonitor); + // ... + // } + // + // Without this assertion, it could be that mMonitor is not actually + // locked. It's possible to have code like: + // + // BaseAutoLock lock(someMonitor); + // ... + // BaseAutoUnlock unlock(someMonitor); + // ... + // LockedMethod(lock); + // + // and in such a case, simply asserting that the monitor pointers match is not + // sufficient; monitor ownership must be asserted as well. + // + // Note that if you are going to use the coding pattern presented above, you + // should use this method in preference to using AssertCurrentThreadOwns on + // the mutex you expected to be held, since this method provides stronger + // guarantees. + void AssertOwns(const T& aMonitor) const MOZ_ASSERT_CAPABILITY(aMonitor) { + MOZ_ASSERT(&aMonitor == mMonitor); + mMonitor->AssertCurrentThreadOwns(); + } + + private: + BaseMonitorAutoLock() = delete; + BaseMonitorAutoLock(const BaseMonitorAutoLock&) = delete; + BaseMonitorAutoLock& operator=(const BaseMonitorAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + friend class MonitorAutoUnlock; + + protected: + T* mMonitor; +}; +} // namespace detail +typedef detail::BaseMonitorAutoLock<Monitor> MonitorAutoLock; +typedef detail::BaseMonitorAutoLock<MonitorSingleWriter> + MonitorSingleWriterAutoLock; + +// clang-format off +// Use if we've done AssertOnWritingThread(), and then later need to take the +// lock to write to a protected member. Instead of +// MutexSingleWriterAutoLock lock(mutex) +// use +// MutexSingleWriterAutoLockOnThread(lock, mutex) +// clang-format on +#define MonitorSingleWriterAutoLockOnThread(lock, monitor) \ + MOZ_PUSH_IGNORE_THREAD_SAFETY \ + MonitorSingleWriterAutoLock lock(monitor); \ + MOZ_POP_THREAD_SAFETY + +/** + * Unlock the monitor for the lexical scope instances of this class + * are bound to (except for MonitorAutoLock in nested scopes). + * + * The monitor must be locked by the current thread when instances of + * this class are created. + */ +namespace detail { +template <typename T> +class MOZ_STACK_CLASS MOZ_SCOPED_CAPABILITY BaseMonitorAutoUnlock { + public: + explicit BaseMonitorAutoUnlock(T& aMonitor) + MOZ_SCOPED_UNLOCK_RELEASE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Unlock(); + } + + ~BaseMonitorAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mMonitor->Lock(); } + + private: + BaseMonitorAutoUnlock() = delete; + BaseMonitorAutoUnlock(const BaseMonitorAutoUnlock&) = delete; + BaseMonitorAutoUnlock& operator=(const BaseMonitorAutoUnlock&) = delete; + static void* operator new(size_t) noexcept(true); + + T* mMonitor; +}; +} // namespace detail +typedef detail::BaseMonitorAutoUnlock<Monitor> MonitorAutoUnlock; +typedef detail::BaseMonitorAutoUnlock<MonitorSingleWriter> + MonitorSingleWriterAutoUnlock; + +/** + * Lock the monitor for the lexical scope instances of this class are + * bound to (except for MonitorAutoUnlock in nested scopes). + * + * The monitor must be unlocked when instances of this class are + * created. + */ +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS ReleasableMonitorAutoLock { + public: + explicit ReleasableMonitorAutoLock(Monitor& aMonitor) + MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Lock(); + mLocked = true; + } + + ~ReleasableMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { + if (mLocked) { + mMonitor->Unlock(); + } + } + + // See BaseMonitorAutoLock::Wait + void Wait() { + mMonitor->AssertCurrentThreadOwns(); // someone could have called Unlock() + mMonitor->Wait(); + } + CVStatus Wait(TimeDuration aDuration) { + mMonitor->AssertCurrentThreadOwns(); + return mMonitor->Wait(aDuration); + } + + void Notify() { + MOZ_ASSERT(mLocked); + mMonitor->Notify(); + } + void NotifyAll() { + MOZ_ASSERT(mLocked); + mMonitor->NotifyAll(); + } + + // Allow dropping the lock prematurely; for example to support something like: + // clang-format off + // MonitorAutoLock lock(mMonitor); + // ... + // if (foo) { + // lock.Unlock(); + // MethodThatCantBeCalledWithLock() + // return; + // } + // clang-format on + void Unlock() MOZ_CAPABILITY_RELEASE() { + MOZ_ASSERT(mLocked); + mMonitor->Unlock(); + mLocked = false; + } + void Lock() MOZ_CAPABILITY_ACQUIRE() { + MOZ_ASSERT(!mLocked); + mMonitor->Lock(); + mLocked = true; + } + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY() { + mMonitor->AssertCurrentThreadOwns(); + } + + private: + bool mLocked; + Monitor* mMonitor; + + ReleasableMonitorAutoLock() = delete; + ReleasableMonitorAutoLock(const ReleasableMonitorAutoLock&) = delete; + ReleasableMonitorAutoLock& operator=(const ReleasableMonitorAutoLock&) = + delete; + static void* operator new(size_t) noexcept(true); +}; + +} // namespace mozilla + +#endif // mozilla_Monitor_h diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h new file mode 100644 index 0000000000..f17e085c2f --- /dev/null +++ b/xpcom/threads/MozPromise.h @@ -0,0 +1,1757 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(MozPromise_h_) +# define MozPromise_h_ + +# include <type_traits> +# include <utility> + +# include "mozilla/ErrorNames.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/Monitor.h" +# include "mozilla/Mutex.h" +# include "mozilla/RefPtr.h" +# include "mozilla/UniquePtr.h" +# include "mozilla/Variant.h" +# include "nsIDirectTaskDispatcher.h" +# include "nsISerialEventTarget.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +# ifdef MOZ_WIDGET_ANDROID +# include "mozilla/jni/GeckoResultUtils.h" +# endif + +# if MOZ_DIAGNOSTIC_ASSERT_ENABLED +# define PROMISE_DEBUG +# endif + +# ifdef PROMISE_DEBUG +# define PROMISE_ASSERT MOZ_RELEASE_ASSERT +# else +# define PROMISE_ASSERT(...) \ + do { \ + } while (0) +# endif + +# if DEBUG +# include "nsPrintfCString.h" +# endif + +namespace mozilla { + +namespace dom { +class Promise; +} + +extern LazyLogModule gMozPromiseLog; + +# define PROMISE_LOG(x, ...) \ + MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__)) + +namespace detail { +template <typename F> +struct MethodTraitsHelper : MethodTraitsHelper<decltype(&F::operator())> {}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...)> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) volatile> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const volatile> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename T> +struct MethodTrait : MethodTraitsHelper<std::remove_reference_t<T>> {}; + +} // namespace detail + +template <typename MethodType> +using TakesArgument = + std::integral_constant<bool, detail::MethodTrait<MethodType>::ArgSize != 0>; + +template <typename MethodType, typename TargetType> +using ReturnTypeIs = + std::is_convertible<typename detail::MethodTrait<MethodType>::ReturnType, + TargetType>; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise; + +template <typename Return> +struct IsMozPromise : std::false_type {}; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +struct IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>> + : std::true_type {}; + +/* + * A promise manages an asynchronous request that may or may not be able to be + * fulfilled immediately. When an API returns a promise, the consumer may attach + * callbacks to be invoked (asynchronously, on a specified thread) when the + * request is either completed (resolved) or cannot be completed (rejected). + * Whereas JS promise callbacks are dispatched from Microtask checkpoints, + * MozPromises resolution/rejection make a normal round-trip through the event + * loop, which simplifies their ordering semantics relative to other native + * code. + * + * MozPromises attempt to mirror the spirit of JS Promises to the extent that + * is possible (and desirable) in C++. While the intent is that MozPromises + * feel familiar to programmers who are accustomed to their JS-implemented + * cousin, we don't shy away from imposing restrictions and adding features that + * make sense for the use cases we encounter. + * + * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then() + * call accepts resolve and reject callbacks, and returns a magic object which + * will be implicitly converted to a MozPromise::Request or a MozPromise object + * depending on how the return value is used. The magic object serves several + * purposes for the consumer. + * + * (1) When converting to a MozPromise::Request, it allows the caller to + * cancel the delivery of the resolve/reject value if it has not already + * occurred, via Disconnect() (this must be done on the target thread to + * avoid racing). + * + * (2) When converting to a MozPromise (which is called a completion promise), + * it allows promise chaining so ->Then() can be called again to attach + * more resolve and reject callbacks. If the resolve/reject callback + * returns a new MozPromise, that promise is chained to the completion + * promise, such that its resolve/reject value will be forwarded along + * when it arrives. If the resolve/reject callback returns void, the + * completion promise is resolved/rejected with the same value that was + * passed to the callback. + * + * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs + * (rather than already_AddRefed) from various methods. This is done to allow + * elegant chaining of calls without cluttering up the code with intermediate + * variables, and without introducing separate API variants for callers that + * want a return value (from, say, ->Then()) from those that don't. + * + * When IsExclusive is true, the MozPromise does a release-mode assertion that + * there is at most one call to either Then(...) or ChainTo(...). + */ + +class MozPromiseRefcountable { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable) + protected: + virtual ~MozPromiseRefcountable() = default; +}; + +class MozPromiseBase : public MozPromiseRefcountable { + public: + virtual void AssertIsDead() = 0; +}; + +template <typename T> +class MozPromiseHolder; +template <typename T> +class MozPromiseRequestHolder; +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise : public MozPromiseBase { + static const uint32_t sMagic = 0xcecace11; + + // Return a |T&&| to enable move when IsExclusive is true or + // a |const T&| to enforce copy otherwise. + template <typename T, + typename R = std::conditional_t<IsExclusive, T&&, const T&>> + static R MaybeMove(T& aX) { + return static_cast<R>(aX); + } + + public: + typedef ResolveValueT ResolveValueType; + typedef RejectValueT RejectValueType; + class ResolveOrRejectValue { + public: + template <typename ResolveValueType_> + void SetResolve(ResolveValueType_&& aResolveValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex<ResolveIndex>{}, + std::forward<ResolveValueType_>(aResolveValue)); + } + + template <typename RejectValueType_> + void SetReject(RejectValueType_&& aRejectValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex<RejectIndex>{}, + std::forward<RejectValueType_>(aRejectValue)); + } + + template <typename ResolveValueType_> + static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) { + ResolveOrRejectValue val; + val.SetResolve(std::forward<ResolveValueType_>(aResolveValue)); + return val; + } + + template <typename RejectValueType_> + static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) { + ResolveOrRejectValue val; + val.SetReject(std::forward<RejectValueType_>(aRejectValue)); + return val; + } + + bool IsResolve() const { return mValue.template is<ResolveIndex>(); } + bool IsReject() const { return mValue.template is<RejectIndex>(); } + bool IsNothing() const { return mValue.template is<NothingIndex>(); } + + const ResolveValueType& ResolveValue() const { + return mValue.template as<ResolveIndex>(); + } + ResolveValueType& ResolveValue() { + return mValue.template as<ResolveIndex>(); + } + const RejectValueType& RejectValue() const { + return mValue.template as<RejectIndex>(); + } + RejectValueType& RejectValue() { return mValue.template as<RejectIndex>(); } + + private: + enum { NothingIndex, ResolveIndex, RejectIndex }; + using Storage = Variant<Nothing, ResolveValueType, RejectValueType>; + Storage mValue = Storage(VariantIndex<NothingIndex>{}); + }; + + protected: + // MozPromise is the public type, and never constructed directly. Construct + // a MozPromise::Private, defined below. + MozPromise(const char* aCreationSite, bool aIsCompletionPromise) + : mCreationSite(aCreationSite), + mMutex("MozPromise Mutex"), + mHaveRequest(false), + mIsCompletionPromise(aIsCompletionPromise) +# ifdef PROMISE_DEBUG + , + mMagic4(&mMutex) +# endif + { + PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this); + } + + public: + // MozPromise::Private allows us to separate the public interface (upon which + // consumers of the promise may invoke methods like Then()) from the private + // interface (upon which the creator of the promise may invoke Resolve() or + // Reject()). APIs should create and store a MozPromise::Private (usually + // via a MozPromiseHolder), and return a MozPromise to consumers. + // + // NB: We can include the definition of this class inline once B2G ICS is + // gone. + class Private; + + template <typename ResolveValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndResolve( + ResolveValueType_&& aResolveValue, const char* aResolveSite) { + static_assert(std::is_convertible_v<ResolveValueType_, ResolveValueT>, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + RefPtr<typename MozPromise::Private> p = + new MozPromise::Private(aResolveSite); + p->Resolve(std::forward<ResolveValueType_>(aResolveValue), aResolveSite); + return p; + } + + template <typename RejectValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndReject( + RejectValueType_&& aRejectValue, const char* aRejectSite) { + static_assert(std::is_convertible_v<RejectValueType_, RejectValueT>, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + RefPtr<typename MozPromise::Private> p = + new MozPromise::Private(aRejectSite); + p->Reject(std::forward<RejectValueType_>(aRejectValue), aRejectSite); + return p; + } + + template <typename ResolveOrRejectValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndResolveOrReject( + ResolveOrRejectValueType_&& aValue, const char* aSite) { + RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aSite); + p->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), aSite); + return p; + } + + typedef MozPromise<CopyableTArray<ResolveValueType>, RejectValueType, + IsExclusive> + AllPromiseType; + + typedef MozPromise<CopyableTArray<ResolveOrRejectValue>, bool, IsExclusive> + AllSettledPromiseType; + + private: + class AllPromiseHolder : public MozPromiseRefcountable { + public: + explicit AllPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllPromiseType::Private(__func__)), + mOutstandingPromises(aDependentPromises) { + MOZ_ASSERT(aDependentPromises > 0); + mResolveValues.SetLength(aDependentPromises); + } + + template <typename ResolveValueType_> + void Resolve(size_t aIndex, ResolveValueType_&& aResolveValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mResolveValues[aIndex].emplace( + std::forward<ResolveValueType_>(aResolveValue)); + if (--mOutstandingPromises == 0) { + nsTArray<ResolveValueType> resolveValues; + resolveValues.SetCapacity(mResolveValues.Length()); + for (auto&& resolveValue : mResolveValues) { + resolveValues.AppendElement(std::move(resolveValue.ref())); + } + + mPromise->Resolve(std::move(resolveValues), __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + } + + template <typename RejectValueType_> + void Reject(RejectValueType_&& aRejectValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mPromise->Reject(std::forward<RejectValueType_>(aRejectValue), __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + + AllPromiseType* Promise() { return mPromise; } + + private: + nsTArray<Maybe<ResolveValueType>> mResolveValues; + RefPtr<typename AllPromiseType::Private> mPromise; + size_t mOutstandingPromises; + }; + + // Trying to pass ResolveOrRejectValue by value fails static analysis checks, + // so we need to use either a const& or an rvalue reference, depending on + // whether IsExclusive is true or not. + typedef std::conditional_t<IsExclusive, ResolveOrRejectValue&&, + const ResolveOrRejectValue&> + ResolveOrRejectValueParam; + + typedef std::conditional_t<IsExclusive, ResolveValueType&&, + const ResolveValueType&> + ResolveValueTypeParam; + + typedef std::conditional_t<IsExclusive, RejectValueType&&, + const RejectValueType&> + RejectValueTypeParam; + + class AllSettledPromiseHolder : public MozPromiseRefcountable { + public: + explicit AllSettledPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllSettledPromiseType::Private(__func__)), + mOutstandingPromises(aDependentPromises) { + MOZ_ASSERT(aDependentPromises > 0); + mValues.SetLength(aDependentPromises); + } + + void Settle(size_t aIndex, ResolveOrRejectValueParam aValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mValues[aIndex].emplace(MaybeMove(aValue)); + if (--mOutstandingPromises == 0) { + nsTArray<ResolveOrRejectValue> values; + values.SetCapacity(mValues.Length()); + for (auto&& value : mValues) { + values.AppendElement(std::move(value.ref())); + } + + mPromise->Resolve(std::move(values), __func__); + mPromise = nullptr; + mValues.Clear(); + } + } + + AllSettledPromiseType* Promise() { return mPromise; } + + private: + nsTArray<Maybe<ResolveOrRejectValue>> mValues; + RefPtr<typename AllSettledPromiseType::Private> mPromise; + size_t mOutstandingPromises; + }; + + public: + [[nodiscard]] static RefPtr<AllPromiseType> All( + nsISerialEventTarget* aProcessingTarget, + nsTArray<RefPtr<MozPromise>>& aPromises) { + if (aPromises.Length() == 0) { + return AllPromiseType::CreateAndResolve( + CopyableTArray<ResolveValueType>(), __func__); + } + + RefPtr<AllPromiseHolder> holder = new AllPromiseHolder(aPromises.Length()); + RefPtr<AllPromiseType> promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then( + aProcessingTarget, __func__, + [holder, i](ResolveValueTypeParam aResolveValue) -> void { + holder->Resolve(i, MaybeMove(aResolveValue)); + }, + [holder](RejectValueTypeParam aRejectValue) -> void { + holder->Reject(MaybeMove(aRejectValue)); + }); + } + return promise; + } + + [[nodiscard]] static RefPtr<AllSettledPromiseType> AllSettled( + nsISerialEventTarget* aProcessingTarget, + nsTArray<RefPtr<MozPromise>>& aPromises) { + if (aPromises.Length() == 0) { + return AllSettledPromiseType::CreateAndResolve( + CopyableTArray<ResolveOrRejectValue>(), __func__); + } + + RefPtr<AllSettledPromiseHolder> holder = + new AllSettledPromiseHolder(aPromises.Length()); + RefPtr<AllSettledPromiseType> promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then(aProcessingTarget, __func__, + [holder, i](ResolveOrRejectValueParam aValue) -> void { + holder->Settle(i, MaybeMove(aValue)); + }); + } + return promise; + } + + class Request : public MozPromiseRefcountable { + public: + virtual void Disconnect() = 0; + + protected: + Request() : mComplete(false), mDisconnected(false) {} + virtual ~Request() = default; + + bool mComplete; + bool mDisconnected; + }; + + protected: + /* + * A ThenValue tracks a single consumer waiting on the promise. When a + * consumer invokes promise->Then(...), a ThenValue is created. Once the + * Promise is resolved or rejected, a {Resolve,Reject}Runnable is dispatched, + * which invokes the resolve/reject method and then deletes the ThenValue. + */ + class ThenValueBase : public Request { + friend class MozPromise; + static const uint32_t sMagic = 0xfadece11; + + public: + class ResolveOrRejectRunnable final + : public PrioritizableCancelableRunnable { + public: + ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise) + : PrioritizableCancelableRunnable( + aPromise->mPriority, + "MozPromise::ThenValueBase::ResolveOrRejectRunnable"), + mThenValue(aThenValue), + mPromise(aPromise) { + MOZ_DIAGNOSTIC_ASSERT(!mPromise->IsPending()); + } + + ~ResolveOrRejectRunnable() { + if (mThenValue) { + mThenValue->AssertIsDead(); + } + } + + NS_IMETHOD Run() override { + PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this); + mThenValue->DoResolveOrReject(mPromise->Value()); + mThenValue = nullptr; + mPromise = nullptr; + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<ThenValueBase> mThenValue; + RefPtr<MozPromise> mPromise; + }; + + ThenValueBase(nsISerialEventTarget* aResponseTarget, const char* aCallSite) + : mResponseTarget(aResponseTarget), mCallSite(aCallSite) { + MOZ_ASSERT(aResponseTarget); + } + +# ifdef PROMISE_DEBUG + ~ThenValueBase() { + mMagic1 = 0; + mMagic2 = 0; + } +# endif + + void AssertIsDead() { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + // We want to assert that this ThenValues is dead - that is to say, that + // there are no consumers waiting for the result. In the case of a normal + // ThenValue, we check that it has been disconnected, which is the way + // that the consumer signals that it no longer wishes to hear about the + // result. If this ThenValue has a completion promise (which is mutually + // exclusive with being disconnectable), we recursively assert that every + // ThenValue associated with the completion promise is dead. + if (MozPromiseBase* p = CompletionPromise()) { + p->AssertIsDead(); + } else { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (MOZ_UNLIKELY(!Request::mDisconnected)) { + MOZ_CRASH_UNSAFE_PRINTF( + "MozPromise::ThenValue created from '%s' destroyed without being " + "either disconnected, resolved, or rejected (dispatchRv: %s)", + mCallSite, + mDispatchRv ? GetStaticErrorName(*mDispatchRv) + : "not dispatched"); + } +# endif + } + } + + void Dispatch(MozPromise* aPromise) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + aPromise->mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!aPromise->IsPending()); + + nsCOMPtr<nsIRunnable> r = new ResolveOrRejectRunnable(this, aPromise); + PROMISE_LOG( + "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p] " + "%s dispatch", + aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite, + r.get(), aPromise, this, + aPromise->mUseSynchronousTaskDispatch ? "synchronous" + : aPromise->mUseDirectTaskDispatch ? "directtask" + : "normal"); + + if (aPromise->mUseSynchronousTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG("ThenValue::Dispatch running task synchronously [this=%p]", + this); + r->Run(); + return; + } + + if (aPromise->mUseDirectTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG( + "ThenValue::Dispatch dispatch task via direct task queue [this=%p]", + this); + nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = + do_QueryInterface(mResponseTarget); + if (dispatcher) { + SetDispatchRv(dispatcher->DispatchDirectTask(r.forget())); + return; + } + NS_WARNING( + nsPrintfCString( + "Direct Task dispatching not available for thread \"%s\"", + PR_GetThreadName(PR_GetCurrentThread())) + .get()); + MOZ_DIAGNOSTIC_ASSERT( + false, + "mResponseTarget must implement nsIDirectTaskDispatcher for direct " + "task dispatching"); + } + + // Promise consumers are allowed to disconnect the Request object and + // then shut down the thread or task queue that the promise result would + // be dispatched on. So we unfortunately can't assert that promise + // dispatch succeeds. :-( + // We do record whether or not it succeeds so that if the ThenValueBase is + // then destroyed and it was not disconnected, we can include that + // information in the assertion message. + SetDispatchRv(mResponseTarget->Dispatch(r.forget())); + } + + void Disconnect() override { + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); + MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete); + Request::mDisconnected = true; + + // We could support rejecting the completion promise on disconnection, but + // then we'd need to have some sort of default reject value. The use cases + // of disconnection and completion promise chaining seem pretty + // orthogonal, so let's use assert against it. + MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise()); + } + + protected: + virtual MozPromiseBase* CompletionPromise() const = 0; + virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0; + + void DoResolveOrReject(ResolveOrRejectValue& aValue) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); + Request::mComplete = true; + if (Request::mDisconnected) { + PROMISE_LOG( + "ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", + this); + return; + } + + // Invoke the resolve or reject method. + DoResolveOrRejectInternal(aValue); + } + + void SetDispatchRv(nsresult aRv) { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mDispatchRv = Some(aRv); +# endif + } + + nsCOMPtr<nsISerialEventTarget> + mResponseTarget; // May be released on any thread. +# ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +# endif + const char* mCallSite; +# ifdef PROMISE_DEBUG + uint32_t mMagic2 = sMagic; +# endif +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + Maybe<nsresult> mDispatchRv; +# endif + }; + + /* + * We create two overloads for invoking Resolve/Reject Methods so as to + * make the resolve/reject value argument "optional". + */ + template <typename ThisType, typename MethodType, typename ValueType> + static std::enable_if_t<TakesArgument<MethodType>::value, + typename detail::MethodTrait<MethodType>::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(std::forward<ValueType>(aValue)); + } + + template <typename ThisType, typename MethodType, typename ValueType> + static std::enable_if_t<!TakesArgument<MethodType>::value, + typename detail::MethodTrait<MethodType>::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(); + } + + // Called when promise chaining is supported. + template <bool SupportChaining, typename ThisType, typename MethodType, + typename ValueType, typename CompletionPromiseType> + static std::enable_if_t<SupportChaining, void> InvokeCallbackMethod( + ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, + CompletionPromiseType&& aCompletionPromise) { + auto p = InvokeMethod(aThisVal, aMethod, std::forward<ValueType>(aValue)); + if (aCompletionPromise) { + p->ChainTo(aCompletionPromise.forget(), "<chained completion promise>"); + } + } + + // Called when promise chaining is not supported. + template <bool SupportChaining, typename ThisType, typename MethodType, + typename ValueType, typename CompletionPromiseType> + static std::enable_if_t<!SupportChaining, void> InvokeCallbackMethod( + ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, + CompletionPromiseType&& aCompletionPromise) { + MOZ_DIAGNOSTIC_ASSERT( + !aCompletionPromise, + "Can't do promise chaining for a non-promise-returning method."); + InvokeMethod(aThisVal, aMethod, std::forward<ValueType>(aValue)); + } + + template <typename> + class ThenCommand; + + template <typename...> + class ThenValue; + + template <typename ThisType, typename ResolveMethodType, + typename RejectMethodType> + class ThenValue<ThisType*, ResolveMethodType, RejectMethodType> + : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveMethodType>::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait<RejectMethodType>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value && + std::is_same_v<R1, R2>>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, + ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite), + mThisVal(aThisVal), + mResolveMethod(aResolveMethod), + mRejectMethod(aRejectMethod) {} + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Null out our refcounted + // this-value now so that it's released predictably on the dispatch + // thread. + mThisVal = nullptr; + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + if (aValue.IsResolve()) { + InvokeCallbackMethod<SupportChaining::value>( + mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()), + std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod<SupportChaining::value>( + mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()), + std::move(mCompletionPromise)); + } + + // Null out mThisVal after invoking the callback so that any references + // are released predictably on the dispatch thread. Otherwise, it would be + // released on whatever thread last drops its reference to the ThenValue, + // which may or may not be ok. + mThisVal = nullptr; + } + + private: + RefPtr<ThisType> + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + template <typename ThisType, typename ResolveRejectMethodType> + class ThenValue<ThisType*, ResolveRejectMethodType> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer<typename detail::MethodTrait< + ResolveRejectMethodType>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, + ResolveRejectMethodType aResolveRejectMethod, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite), + mThisVal(aThisVal), + mResolveRejectMethod(aResolveRejectMethod) {} + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Null out our refcounted + // this-value now so that it's released predictably on the dispatch + // thread. + mThisVal = nullptr; + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + InvokeCallbackMethod<SupportChaining::value>( + mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue), + std::move(mCompletionPromise)); + + // Null out mThisVal after invoking the callback so that any references + // are released predictably on the dispatch thread. Otherwise, it would be + // released on whatever thread last drops its reference to the ThenValue, + // which may or may not be ok. + mThisVal = nullptr; + } + + private: + RefPtr<ThisType> + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveRejectMethodType mResolveRejectMethod; + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + // NB: We could use std::function here instead of a template if it were + // supported. :-( + template <typename ResolveFunction, typename RejectFunction> + class ThenValue<ResolveFunction, RejectFunction> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveFunction>::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait<RejectFunction>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value && + std::is_same_v<R1, R2>>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, + ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction, const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) { + mResolveFunction.emplace(std::move(aResolveFunction)); + mRejectFunction.emplace(std::move(aRejectFunction)); + } + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Destroy our callbacks + // now so that any references in closures are released predictable on + // the dispatch thread. + mResolveFunction.reset(); + mRejectFunction.reset(); + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + // Note: The usage of InvokeCallbackMethod here requires that + // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous + // classes with ::operator()), since it allows us to share code more + // easily. We could fix this if need be, though it's quite easy to work + // around by just capturing something. + if (aValue.IsResolve()) { + InvokeCallbackMethod<SupportChaining::value>( + mResolveFunction.ptr(), &ResolveFunction::operator(), + MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod<SupportChaining::value>( + mRejectFunction.ptr(), &RejectFunction::operator(), + MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise)); + } + + // Destroy callbacks after invocation so that any references in closures + // are released predictably on the dispatch thread. Otherwise, they would + // be released on whatever thread last drops its reference to the + // ThenValue, which may or may not be ok. + mResolveFunction.reset(); + mRejectFunction.reset(); + } + + private: + Maybe<ResolveFunction> + mResolveFunction; // Only accessed and deleted on dispatch thread. + Maybe<RejectFunction> + mRejectFunction; // Only accessed and deleted on dispatch thread. + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + template <typename ResolveRejectFunction> + class ThenValue<ResolveRejectFunction> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveRejectFunction>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, + ResolveRejectFunction&& aResolveRejectFunction, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) { + mResolveRejectFunction.emplace(std::move(aResolveRejectFunction)); + } + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Destroy our callbacks + // now so that any references in closures are released predictable on + // the dispatch thread. + mResolveRejectFunction.reset(); + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + // Note: The usage of InvokeCallbackMethod here requires that + // ResolveRejectFunction is capture-lambdas (i.e. anonymous + // classes with ::operator()), since it allows us to share code more + // easily. We could fix this if need be, though it's quite easy to work + // around by just capturing something. + InvokeCallbackMethod<SupportChaining::value>( + mResolveRejectFunction.ptr(), &ResolveRejectFunction::operator(), + MaybeMove(aValue), std::move(mCompletionPromise)); + + // Destroy callbacks after invocation so that any references in closures + // are released predictably on the dispatch thread. Otherwise, they would + // be released on whatever thread last drops its reference to the + // ThenValue, which may or may not be ok. + mResolveRejectFunction.reset(); + } + + private: + Maybe<ResolveRejectFunction> + mResolveRejectFunction; // Only accessed and deleted on dispatch + // thread. + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + public: + void ThenInternal(already_AddRefed<ThenValueBase> aThenValue, + const char* aCallSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + RefPtr<ThenValueBase> thenValue = aThenValue; + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT( + !IsExclusive || !mHaveRequest, + "Using an exclusive promise in a non-exclusive fashion"); + mHaveRequest = true; + PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]", + aCallSite, this, thenValue.get(), (int)IsPending()); + if (!IsPending()) { + thenValue->Dispatch(this); + } else { + mThenValues.AppendElement(thenValue.forget()); + } + } + + protected: + /* + * A command object to store all information needed to make a request to + * the promise. This allows us to delay the request until further use is + * known (whether it is ->Then() again for more promise chaining or ->Track() + * to terminate chaining and issue the request). + * + * This allows a unified syntax for promise chaining and disconnection + * and feels more like its JS counterpart. + */ + template <typename ThenValueType> + class ThenCommand { + // Allow Promise1::ThenCommand to access the private constructor, + // Promise2::ThenCommand(ThenCommand&&). + template <typename, typename, bool> + friend class MozPromise; + + using PromiseType = typename ThenValueType::PromiseType; + using Private = typename PromiseType::Private; + + ThenCommand(const char* aCallSite, + already_AddRefed<ThenValueType> aThenValue, + MozPromise* aReceiver) + : mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {} + + ThenCommand(ThenCommand&& aOther) = default; + + public: + ~ThenCommand() { + // Issue the request now if the return value of Then() is not used. + if (mThenValue) { + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + } + } + + // Allow RefPtr<MozPromise> p = somePromise->Then(); + // p->Then(thread1, ...); + // p->Then(thread2, ...); + operator RefPtr<PromiseType>() { + static_assert( + ThenValueType::SupportChaining::value, + "The resolve/reject callback needs to return a RefPtr<MozPromise> " + "in order to do promise chaining."); + + // mCompletionPromise must be created before ThenInternal() to avoid race. + RefPtr<Private> p = + new Private("<completion promise>", true /* aIsCompletionPromise */); + mThenValue->mCompletionPromise = p; + // Note ThenInternal() might nullify mCompletionPromise before return. + // So we need to return p instead of mCompletionPromise. + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + return p; + } + + template <typename... Ts> + auto Then(Ts&&... aArgs) -> decltype(std::declval<PromiseType>().Then( + std::forward<Ts>(aArgs)...)) { + return static_cast<RefPtr<PromiseType>>(*this)->Then( + std::forward<Ts>(aArgs)...); + } + + void Track(MozPromiseRequestHolder<MozPromise>& aRequestHolder) { + aRequestHolder.Track(do_AddRef(mThenValue)); + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + } + + // Allow calling ->Then() again for more promise chaining or ->Track() to + // end chaining and track the request for future disconnection. + ThenCommand* operator->() { return this; } + + private: + const char* mCallSite; + RefPtr<ThenValueType> mThenValue; + RefPtr<MozPromise> mReceiver; + }; + + public: + template <typename ThisType, typename... Methods, + typename ThenValueType = ThenValue<ThisType*, Methods...>, + typename ReturnType = ThenCommand<ThenValueType>> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + ThisType* aThisVal, Methods... aMethods) { + RefPtr<ThenValueType> thenValue = + new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + template <typename... Functions, + typename ThenValueType = ThenValue<Functions...>, + typename ReturnType = ThenCommand<ThenValueType>> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + Functions&&... aFunctions) { + RefPtr<ThenValueType> thenValue = + new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + void ChainTo(already_AddRefed<Private> aChainedPromise, + const char* aCallSite) { + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT( + !IsExclusive || !mHaveRequest, + "Using an exclusive promise in a non-exclusive fashion"); + mHaveRequest = true; + RefPtr<Private> chainedPromise = aChainedPromise; + PROMISE_LOG( + "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]", + aCallSite, this, chainedPromise.get(), (int)IsPending()); + + // We want to use the same type of dispatching method with the chained + // promises. + + // We need to ensure that the UseSynchronousTaskDispatch branch isn't taken + // at compilation time to ensure we're not triggering the static_assert in + // UseSynchronousTaskDispatch method. if constexpr (IsExclusive) ensures + // that. + if (mUseDirectTaskDispatch) { + chainedPromise->UseDirectTaskDispatch(aCallSite); + } else if constexpr (IsExclusive) { + if (mUseSynchronousTaskDispatch) { + chainedPromise->UseSynchronousTaskDispatch(aCallSite); + } + } else { + chainedPromise->SetTaskPriority(mPriority, aCallSite); + } + + if (!IsPending()) { + ForwardTo(chainedPromise); + } else { + mChainedPromises.AppendElement(chainedPromise); + } + } + +# ifdef MOZ_WIDGET_ANDROID + // Creates a C++ MozPromise from its Java counterpart, GeckoResult. + [[nodiscard]] static RefPtr<MozPromise> FromGeckoResult( + java::GeckoResult::Param aGeckoResult) { + using jni::GeckoResultCallback; + RefPtr<Private> p = new Private("GeckoResult Glue", false); + auto resolve = GeckoResultCallback::CreateAndAttach<ResolveValueType>( + [p](ResolveValueType&& aArg) { + p->Resolve(MaybeMove(aArg), __func__); + }); + auto reject = GeckoResultCallback::CreateAndAttach<RejectValueType>( + [p](RejectValueType&& aArg) { p->Reject(MaybeMove(aArg), __func__); }); + aGeckoResult->NativeThen(resolve, reject); + return p; + } +# endif + + // Note we expose the function AssertIsDead() instead of IsDead() since + // checking IsDead() is a data race in the situation where the request is not + // dead. Therefore we enforce the form |Assert(IsDead())| by exposing + // AssertIsDead() only. + void AssertIsDead() override { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + for (auto&& then : mThenValues) { + then->AssertIsDead(); + } + for (auto&& chained : mChainedPromises) { + chained->AssertIsDead(); + } + } + + bool IsResolved() const { return mValue.IsResolve(); } + + protected: + bool IsPending() const { return mValue.IsNothing(); } + + ResolveOrRejectValue& Value() { + // This method should only be called once the value has stabilized. As + // such, we don't need to acquire the lock here. + MOZ_DIAGNOSTIC_ASSERT(!IsPending()); + return mValue; + } + + void DispatchAll() { + mMutex.AssertCurrentThreadOwns(); + for (auto&& thenValue : mThenValues) { + thenValue->Dispatch(this); + } + mThenValues.Clear(); + + for (auto&& chainedPromise : mChainedPromises) { + ForwardTo(chainedPromise); + } + mChainedPromises.Clear(); + } + + void ForwardTo(Private* aOther) { + MOZ_ASSERT(!IsPending()); + if (mValue.IsResolve()) { + aOther->Resolve(MaybeMove(mValue.ResolveValue()), "<chained promise>"); + } else { + aOther->Reject(MaybeMove(mValue.RejectValue()), "<chained promise>"); + } + } + + virtual ~MozPromise() { + PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this); + AssertIsDead(); + // We can't guarantee a completion promise will always be revolved or + // rejected since ResolveOrRejectRunnable might not run when dispatch fails. + if (!mIsCompletionPromise) { + MOZ_ASSERT(!IsPending()); + MOZ_ASSERT(mThenValues.IsEmpty()); + MOZ_ASSERT(mChainedPromises.IsEmpty()); + } +# ifdef PROMISE_DEBUG + mMagic1 = 0; + mMagic2 = 0; + mMagic3 = 0; + mMagic4 = nullptr; +# endif + }; + + const char* mCreationSite; // For logging + Mutex mMutex MOZ_UNANNOTATED; + ResolveOrRejectValue mValue; + bool mUseSynchronousTaskDispatch = false; + bool mUseDirectTaskDispatch = false; + uint32_t mPriority = nsIRunnablePriority::PRIORITY_NORMAL; +# ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +# endif + // Try shows we never have more than 3 elements when IsExclusive is false. + // So '3' is a good value to avoid heap allocation in most cases. + AutoTArray<RefPtr<ThenValueBase>, IsExclusive ? 1 : 3> mThenValues; +# ifdef PROMISE_DEBUG + uint32_t mMagic2 = sMagic; +# endif + nsTArray<RefPtr<Private>> mChainedPromises; +# ifdef PROMISE_DEBUG + uint32_t mMagic3 = sMagic; +# endif + bool mHaveRequest; + const bool mIsCompletionPromise; +# ifdef PROMISE_DEBUG + void* mMagic4; +# endif +}; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private + : public MozPromise<ResolveValueT, RejectValueT, IsExclusive> { + public: + explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false) + : MozPromise(aCreationSite, aIsCompletionPromise) {} + + template <typename ResolveValueT_> + void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, + this, mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aResolveSite, this, mCreationSite); + return; + } + mValue.SetResolve(std::forward<ResolveValueT_>(aResolveValue)); + DispatchAll(); + } + + template <typename RejectValueT_> + void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, + mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aRejectSite, this, mCreationSite); + return; + } + mValue.SetReject(std::forward<RejectValueT_>(aRejectValue)); + DispatchAll(); + } + + template <typename ResolveOrRejectValue_> + void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, + this, mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aSite, this, mCreationSite); + return; + } + mValue = std::forward<ResolveOrRejectValue_>(aValue); + DispatchAll(); + } + + // If the caller and target are both on the same thread, run the the resolve + // or reject callback synchronously. Otherwise, the task will be dispatched + // via the target Dispatch method. + void UseSynchronousTaskDispatch(const char* aSite) { + static_assert( + IsExclusive, + "Synchronous dispatch can only be used with exclusive promises"); + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)", + aSite, this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + mUseSynchronousTaskDispatch = true; + } + + // If the caller and target are both on the same thread, run the + // resolve/reject callback off the direct task queue instead. This avoids a + // full trip to the back of the event queue for each additional asynchronous + // step when using MozPromise, and is similar (but not identical to) the + // microtask semantics of JS promises. + void UseDirectTaskDispatch(const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite, + this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + MOZ_ASSERT(!mUseSynchronousTaskDispatch, + "Promise already set for synchronous dispatch"); + mUseDirectTaskDispatch = true; + } + + // If the resolve/reject will be handled on a thread supporting priorities, + // one may want to tweak the priority of the task by passing a + // nsIRunnablePriority::PRIORITY_* to SetTaskPriority. + void SetTaskPriority(uint32_t aPriority, const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s TaskPriority MozPromise (%p created at %s)", aSite, this, + mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + MOZ_ASSERT(!mUseSynchronousTaskDispatch, + "Promise already set for synchronous dispatch"); + MOZ_ASSERT(!mUseDirectTaskDispatch, + "Promise already set for direct dispatch"); + mPriority = aPriority; + } +}; + +// A generic promise type that does the trick for simple use cases. +typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> GenericPromise; + +// A generic, non-exclusive promise type that does the trick for simple use +// cases. +typedef MozPromise<bool, nsresult, /* IsExclusive = */ false> + GenericNonExclusivePromise; + +/* + * Class to encapsulate a promise for a particular role. Use this as the member + * variable for a class whose method returns a promise. + */ +template <typename PromiseType, typename ImplType> +class MozPromiseHolderBase { + public: + MozPromiseHolderBase() = default; + + MozPromiseHolderBase(MozPromiseHolderBase&& aOther) = default; + MozPromiseHolderBase& operator=(MozPromiseHolderBase&& aOther) = default; + + ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); } + + already_AddRefed<PromiseType> Ensure(const char* aMethodName) { + static_cast<ImplType*>(this)->Check(); + if (!mPromise) { + mPromise = new (typename PromiseType::Private)(aMethodName); + } + RefPtr<PromiseType> p = mPromise.get(); + return p.forget(); + } + + bool IsEmpty() const { + static_cast<const ImplType*>(this)->Check(); + return !mPromise; + } + + already_AddRefed<typename PromiseType::Private> Steal() { + static_cast<ImplType*>(this)->Check(); + return mPromise.forget(); + } + + template <typename ResolveValueType_> + void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) { + static_assert(std::is_convertible_v<ResolveValueType_, + typename PromiseType::ResolveValueType>, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Resolve(std::forward<ResolveValueType_>(aResolveValue), + aMethodName); + mPromise = nullptr; + } + + template <typename ResolveValueType_> + void ResolveIfExists(ResolveValueType_&& aResolveValue, + const char* aMethodName) { + if (!IsEmpty()) { + Resolve(std::forward<ResolveValueType_>(aResolveValue), aMethodName); + } + } + + template <typename RejectValueType_> + void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) { + static_assert(std::is_convertible_v<RejectValueType_, + typename PromiseType::RejectValueType>, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName); + mPromise = nullptr; + } + + template <typename RejectValueType_> + void RejectIfExists(RejectValueType_&& aRejectValue, + const char* aMethodName) { + if (!IsEmpty()) { + Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName); + } + } + + template <typename ResolveOrRejectValueType_> + void ResolveOrReject(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), + aMethodName); + mPromise = nullptr; + } + + template <typename ResolveOrRejectValueType_> + void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + if (!IsEmpty()) { + ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), + aMethodName); + } + } + + void UseSynchronousTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseSynchronousTaskDispatch(aSite); + } + + void UseDirectTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseDirectTaskDispatch(aSite); + } + + void SetTaskPriority(uint32_t aPriority, const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->SetTaskPriority(aPriority, aSite); + } + + private: + RefPtr<typename PromiseType::Private> mPromise; +}; + +template <typename PromiseType> +class MozPromiseHolder + : public MozPromiseHolderBase<PromiseType, MozPromiseHolder<PromiseType>> { + public: + using MozPromiseHolderBase< + PromiseType, MozPromiseHolder<PromiseType>>::MozPromiseHolderBase; + static constexpr void Check(){}; +}; + +template <typename PromiseType> +class MozMonitoredPromiseHolder + : public MozPromiseHolderBase<PromiseType, + MozMonitoredPromiseHolder<PromiseType>> { + public: + // Provide a Monitor that should always be held when accessing this instance. + explicit MozMonitoredPromiseHolder(Monitor* const aMonitor) + : mMonitor(aMonitor) { + MOZ_ASSERT(aMonitor); + } + + MozMonitoredPromiseHolder(MozMonitoredPromiseHolder&& aOther) = delete; + MozMonitoredPromiseHolder& operator=(MozMonitoredPromiseHolder&& aOther) = + delete; + + void Check() const { mMonitor->AssertCurrentThreadOwns(); } + + private: + Monitor* const mMonitor; +}; + +/* + * Class to encapsulate a MozPromise::Request reference. Use this as the member + * variable for a class waiting on a MozPromise. + */ +template <typename PromiseType> +class MozPromiseRequestHolder { + public: + MozPromiseRequestHolder() = default; + ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); } + + void Track(already_AddRefed<typename PromiseType::Request> aRequest) { + MOZ_DIAGNOSTIC_ASSERT(!Exists()); + mRequest = aRequest; + } + + void Complete() { + MOZ_DIAGNOSTIC_ASSERT(Exists()); + mRequest = nullptr; + } + + // Disconnects and forgets an outstanding promise. The resolve/reject methods + // will never be called. + void Disconnect() { + MOZ_ASSERT(Exists()); + mRequest->Disconnect(); + mRequest = nullptr; + } + + void DisconnectIfExists() { + if (Exists()) { + Disconnect(); + } + } + + bool Exists() const { return !!mRequest; } + + private: + RefPtr<typename PromiseType::Request> mRequest; +}; + +// Asynchronous Potentially-Cross-Thread Method Calls. +// +// This machinery allows callers to schedule a promise-returning function +// (a method and object, or a function object like a lambda) to be invoked +// asynchronously on a given thread, while at the same time receiving a +// promise upon which to invoke Then() immediately. InvokeAsync dispatches a +// task to invoke the function on the proper thread and also chain the +// resulting promise to the one that the caller received, so that resolve/ +// reject values are forwarded through. + +namespace detail { + +// Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause +// assertions when used on templated types. +class MethodCallBase { + public: + MOZ_COUNTED_DEFAULT_CTOR(MethodCallBase) + MOZ_COUNTED_DTOR_VIRTUAL(MethodCallBase) +}; + +template <typename PromiseType, typename MethodType, typename ThisType, + typename... Storages> +class MethodCall : public MethodCallBase { + public: + template <typename... Args> + MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs) + : mMethod(aMethod), + mThisVal(aThisVal), + mArgs(std::forward<Args>(aArgs)...) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "Storages and Args should have equal sizes"); + } + + RefPtr<PromiseType> Invoke() { return mArgs.apply(mThisVal.get(), mMethod); } + + private: + MethodType mMethod; + RefPtr<ThisType> mThisVal; + RunnableMethodArguments<Storages...> mArgs; +}; + +template <typename PromiseType, typename MethodType, typename ThisType, + typename... Storages> +class ProxyRunnable : public CancelableRunnable { + public: + ProxyRunnable( + typename PromiseType::Private* aProxyPromise, + MethodCall<PromiseType, MethodType, ThisType, Storages...>* aMethodCall) + : CancelableRunnable("detail::ProxyRunnable"), + mProxyPromise(aProxyPromise), + mMethodCall(aMethodCall) {} + + NS_IMETHOD Run() override { + RefPtr<PromiseType> p = mMethodCall->Invoke(); + mMethodCall = nullptr; + p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>"); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<typename PromiseType::Private> mProxyPromise; + UniquePtr<MethodCall<PromiseType, MethodType, ThisType, Storages...>> + mMethodCall; +}; + +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes> +static RefPtr<PromiseType> InvokeAsyncImpl( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + MOZ_ASSERT(aTarget); + + typedef RefPtr<PromiseType> (ThisType::*MethodType)(ArgTypes...); + typedef detail::MethodCall<PromiseType, MethodType, ThisType, Storages...> + MethodCallType; + typedef detail::ProxyRunnable<PromiseType, MethodType, ThisType, Storages...> + ProxyRunnableType; + + MethodCallType* methodCall = new MethodCallType( + aMethod, aThisVal, std::forward<ActualArgTypes>(aArgs)...); + RefPtr<typename PromiseType::Private> p = + new (typename PromiseType::Private)(aCallerName); + RefPtr<ProxyRunnableType> r = new ProxyRunnableType(p, methodCall); + aTarget->Dispatch(r.forget()); + return p; +} + +constexpr bool Any() { return false; } + +template <typename T1> +constexpr bool Any(T1 a) { + return static_cast<bool>(a); +} + +template <typename T1, typename... Ts> +constexpr bool Any(T1 a, Ts... aOthers) { + return a || Any(aOthers...); +} + +} // namespace detail + +// InvokeAsync with explicitly-specified storages. +// See ParameterStorage in nsThreadUtils.h for help. +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes, + std::enable_if_t<sizeof...(Storages) != 0, int> = 0> +static RefPtr<PromiseType> InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + static_assert( + sizeof...(Storages) == sizeof...(ArgTypes), + "Provided Storages and method's ArgTypes should have equal sizes"); + static_assert(sizeof...(Storages) == sizeof...(ActualArgTypes), + "Provided Storages and ActualArgTypes should have equal sizes"); + return detail::InvokeAsyncImpl<Storages...>( + aTarget, aThisVal, aCallerName, aMethod, + std::forward<ActualArgTypes>(aArgs)...); +} + +// InvokeAsync with no explicitly-specified storages, will copy arguments and +// then move them out of the runnable into the target method parameters. +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes, + std::enable_if_t<sizeof...(Storages) == 0, int> = 0> +static RefPtr<PromiseType> InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + static_assert( + !detail::Any( + std::is_pointer_v<std::remove_reference_t<ActualArgTypes>>...), + "Cannot pass pointer types through InvokeAsync, Storages must be " + "provided"); + static_assert(sizeof...(ArgTypes) == sizeof...(ActualArgTypes), + "Method's ArgTypes and ActualArgTypes should have equal sizes"); + return detail::InvokeAsyncImpl< + StoreCopyPassByRRef<std::decay_t<ActualArgTypes>>...>( + aTarget, aThisVal, aCallerName, aMethod, + std::forward<ActualArgTypes>(aArgs)...); +} + +namespace detail { + +template <typename Function, typename PromiseType> +class ProxyFunctionRunnable : public CancelableRunnable { + using FunctionStorage = std::decay_t<Function>; + + public: + template <typename F> + ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise, + F&& aFunction) + : CancelableRunnable("detail::ProxyFunctionRunnable"), + mProxyPromise(aProxyPromise), + mFunction(new FunctionStorage(std::forward<F>(aFunction))) {} + + NS_IMETHOD Run() override { + RefPtr<PromiseType> p = (*mFunction)(); + mFunction = nullptr; + p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>"); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<typename PromiseType::Private> mProxyPromise; + UniquePtr<FunctionStorage> mFunction; +}; + +// Note: The following struct and function are not for public consumption (yet?) +// as we would prefer all calls to pass on-the-spot lambdas (or at least moved +// function objects). They could be moved outside of detail if really needed. + +// We prefer getting function objects by non-lvalue-ref (to avoid copying them +// and their captures). This struct is a tag that allows the use of objects +// through lvalue-refs where necessary. +struct AllowInvokeAsyncFunctionLVRef {}; + +// Invoke a function object (e.g., lambda or std/mozilla::function) +// asynchronously; note that the object will be copied if provided by +// lvalue-ref. Return a promise that the function should eventually resolve or +// reject. +template <typename Function> +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + AllowInvokeAsyncFunctionLVRef, Function&& aFunction) + -> decltype(aFunction()) { + static_assert( + IsRefcountedSmartPointer<decltype(aFunction())>::value && + IsMozPromise< + typename RemoveSmartPointer<decltype(aFunction())>::Type>::value, + "Function object must return RefPtr<MozPromise>"); + MOZ_ASSERT(aTarget); + typedef typename RemoveSmartPointer<decltype(aFunction())>::Type PromiseType; + typedef detail::ProxyFunctionRunnable<Function, PromiseType> + ProxyRunnableType; + + auto p = MakeRefPtr<typename PromiseType::Private>(aCallerName); + auto r = MakeRefPtr<ProxyRunnableType>(p, std::forward<Function>(aFunction)); + aTarget->Dispatch(r.forget()); + return p; +} + +} // namespace detail + +// Invoke a function object (e.g., lambda) asynchronously. +// Return a promise that the function should eventually resolve or reject. +template <typename Function> +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + Function&& aFunction) -> decltype(aFunction()) { + static_assert(!std::is_lvalue_reference_v<Function>, + "Function object must not be passed by lvalue-ref (to avoid " + "unplanned copies); Consider move()ing the object."); + return detail::InvokeAsync(aTarget, aCallerName, + detail::AllowInvokeAsyncFunctionLVRef(), + std::forward<Function>(aFunction)); +} + +# undef PROMISE_LOG +# undef PROMISE_ASSERT +# undef PROMISE_DEBUG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/Mutex.h b/xpcom/threads/Mutex.h new file mode 100644 index 0000000000..5116f1d7c3 --- /dev/null +++ b/xpcom/threads/Mutex.h @@ -0,0 +1,485 @@ +/* -*- 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_Mutex_h +#define mozilla_Mutex_h + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/ThreadSafety.h" +#include "mozilla/PlatformMutex.h" +#include "mozilla/Maybe.h" +#include "nsISupports.h" + +// +// Provides: +// +// - Mutex, a non-recursive mutex +// - MutexAutoLock, an RAII class for ensuring that Mutexes are properly +// locked and unlocked +// - MutexAutoUnlock, complementary sibling to MutexAutoLock +// +// - OffTheBooksMutex, a non-recursive mutex that doesn't do leak checking +// - OffTheBooksMutexAuto{Lock,Unlock} - Like MutexAuto{Lock,Unlock}, but for +// an OffTheBooksMutex. +// +// Using MutexAutoLock/MutexAutoUnlock etc. is MUCH preferred to making bare +// calls to Lock and Unlock. +// +namespace mozilla { + +/** + * OffTheBooksMutex is identical to Mutex, except that OffTheBooksMutex doesn't + * include leak checking. Sometimes you want to intentionally "leak" a mutex + * until shutdown; in these cases, OffTheBooksMutex is for you. + */ +class MOZ_CAPABILITY("mutex") OffTheBooksMutex : public detail::MutexImpl, + BlockingResourceBase { + public: + /** + * @param aName A name which can reference this lock + * @returns If failure, nullptr + * If success, a valid Mutex* which must be destroyed + * by Mutex::DestroyMutex() + **/ + explicit OffTheBooksMutex(const char* aName) + : BlockingResourceBase(aName, eMutex) +#ifdef DEBUG + , + mOwningThread(nullptr) +#endif + { + } + + ~OffTheBooksMutex() { +#ifdef DEBUG + MOZ_ASSERT(!mOwningThread, "destroying a still-owned lock!"); +#endif + } + +#ifndef DEBUG + /** + * Lock this mutex. + **/ + void Lock() MOZ_CAPABILITY_ACQUIRE() { this->lock(); } + + /** + * Try to lock this mutex, returning true if we were successful. + **/ + [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true) { return this->tryLock(); } + + /** + * Unlock this mutex. + **/ + void Unlock() MOZ_CAPABILITY_RELEASE() { this->unlock(); } + + /** + * Assert that the current thread owns this mutex in debug builds. + * + * Does nothing in non-debug builds. + **/ + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this) {} + + /** + * Assert that the current thread does not own this mutex. + * + * Note that this function is not implemented for debug builds *and* + * non-debug builds due to difficulties in dealing with memory ordering. + * + * It is therefore mostly useful as documentation. + **/ + void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) {} + +#else + void Lock() MOZ_CAPABILITY_ACQUIRE(); + + [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true); + void Unlock() MOZ_CAPABILITY_RELEASE(); + + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this); + void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) { + // FIXME bug 476536 + } +#endif // ifndef DEBUG + + private: + OffTheBooksMutex() = delete; + OffTheBooksMutex(const OffTheBooksMutex&) = delete; + OffTheBooksMutex& operator=(const OffTheBooksMutex&) = delete; + + friend class OffTheBooksCondVar; + +#ifdef DEBUG + PRThread* mOwningThread; +#endif +}; + +/** + * Mutex + * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this + * mutex within a scope, instead of calling Lock/Unlock directly. + */ +class Mutex : public OffTheBooksMutex { + public: + explicit Mutex(const char* aName) : OffTheBooksMutex(aName) { + MOZ_COUNT_CTOR(Mutex); + } + + MOZ_COUNTED_DTOR(Mutex) + + private: + Mutex() = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; +}; + +/** + * MutexSingleWriter + * + * Mutex where a single writer exists, so that reads from the same thread + * will not generate data races or consistency issues. + * + * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this + * mutex within a scope, instead of calling Lock/Unlock directly. + * + * This requires an object implementing Mutex's SingleWriterLockOwner, so + * we can do correct-thread checks. + */ +// Subclass this in the object owning the mutex +class SingleWriterLockOwner { + public: + SingleWriterLockOwner() = default; + ~SingleWriterLockOwner() = default; + + virtual bool OnWritingThread() const = 0; +}; + +class MutexSingleWriter : public OffTheBooksMutex { + public: + // aOwner should be the object that contains the mutex, typically. We + // will use that object (which must have a lifetime the same or greater + // than this object) to verify that we're running on the correct thread, + // typically only in DEBUG builds + explicit MutexSingleWriter(const char* aName, SingleWriterLockOwner* aOwner) + : OffTheBooksMutex(aName) +#ifdef DEBUG + , + mOwner(aOwner) +#endif + { + MOZ_COUNT_CTOR(MutexSingleWriter); + MOZ_ASSERT(mOwner); + } + + MOZ_COUNTED_DTOR(MutexSingleWriter) + + /** + * Statically assert that we're on the only thread that modifies data + * guarded by this Mutex. This allows static checking for the pattern of + * having a single thread modify a set of data, and read it (under lock) + * on other threads, and reads on the thread that modifies it doesn't + * require a lock. This doesn't solve the issue of some data under the + * Mutex following this pattern, and other data under the mutex being + * written from multiple threads. + * + * We could set the writing thread and dynamically check it in debug + * builds, but this doesn't. We could also use thread-safety/capability + * system to provide direct thread assertions. + **/ + void AssertOnWritingThread() const MOZ_ASSERT_CAPABILITY(this) { + MOZ_ASSERT(mOwner->OnWritingThread()); + } + void AssertOnWritingThreadOrHeld() const MOZ_ASSERT_CAPABILITY(this) { +#ifdef DEBUG + if (!mOwner->OnWritingThread()) { + AssertCurrentThreadOwns(); + } +#endif + } + + private: +#ifdef DEBUG + SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF( + "This is normally the object that contains the MonitorSingleWriter, so " + "we don't want to hold a reference to ourselves"); +#endif + + MutexSingleWriter() = delete; + MutexSingleWriter(const MutexSingleWriter&) = delete; + MutexSingleWriter& operator=(const MutexSingleWriter&) = delete; +}; + +namespace detail { +template <typename T> +class MOZ_RAII BaseAutoUnlock; + +/** + * MutexAutoLock + * Acquires the Mutex when it enters scope, and releases it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Lock and Unlock. + */ +template <typename T> +class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoLock { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aLock A valid mozilla::Mutex* returned by + * mozilla::Mutex::NewMutex. + **/ + explicit BaseAutoLock(T aLock) MOZ_CAPABILITY_ACQUIRE(aLock) : mLock(aLock) { + mLock.Lock(); + } + + ~BaseAutoLock(void) MOZ_CAPABILITY_RELEASE() { mLock.Unlock(); } + + // Assert that aLock is the mutex passed to the constructor and that the + // current thread owns the mutex. In coding patterns such as: + // + // void LockedMethod(const BaseAutoLock<T>& aProofOfLock) + // { + // aProofOfLock.AssertOwns(mMutex); + // ... + // } + // + // Without this assertion, it could be that mMutex is not actually + // locked. It's possible to have code like: + // + // BaseAutoLock lock(someMutex); + // ... + // BaseAutoUnlock unlock(someMutex); + // ... + // LockedMethod(lock); + // + // and in such a case, simply asserting that the mutex pointers match is not + // sufficient; mutex ownership must be asserted as well. + // + // Note that if you are going to use the coding pattern presented above, you + // should use this method in preference to using AssertCurrentThreadOwns on + // the mutex you expected to be held, since this method provides stronger + // guarantees. + void AssertOwns(const T& aMutex) const MOZ_ASSERT_CAPABILITY(aMutex) { + MOZ_ASSERT(&aMutex == &mLock); + mLock.AssertCurrentThreadOwns(); + } + + private: + BaseAutoLock() = delete; + BaseAutoLock(BaseAutoLock&) = delete; + BaseAutoLock& operator=(BaseAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + friend class BaseAutoUnlock<T>; + + T mLock; +}; + +template <typename MutexType> +BaseAutoLock(MutexType&) -> BaseAutoLock<MutexType&>; +} // namespace detail + +typedef detail::BaseAutoLock<Mutex&> MutexAutoLock; +typedef detail::BaseAutoLock<MutexSingleWriter&> MutexSingleWriterAutoLock; +typedef detail::BaseAutoLock<OffTheBooksMutex&> OffTheBooksMutexAutoLock; + +// Specialization of Maybe<*AutoLock> for space efficiency and to silence +// thread-safety analysis, which cannot track what's going on. +template <class MutexType> +class Maybe<detail::BaseAutoLock<MutexType&>> { + public: + Maybe() : mLock(nullptr) {} + ~Maybe() MOZ_NO_THREAD_SAFETY_ANALYSIS { + if (mLock) { + mLock->Unlock(); + } + } + + constexpr bool isSome() const { return mLock; } + constexpr bool isNothing() const { return !mLock; } + + void emplace(MutexType& aMutex) MOZ_NO_THREAD_SAFETY_ANALYSIS { + MOZ_RELEASE_ASSERT(!mLock); + mLock = &aMutex; + mLock->Lock(); + } + + void reset() MOZ_NO_THREAD_SAFETY_ANALYSIS { + if (mLock) { + mLock->Unlock(); + mLock = nullptr; + } + } + + private: + MutexType* mLock; +}; + +// Use if we've done AssertOnWritingThread(), and then later need to take the +// lock to write to a protected member. Instead of +// MutexSingleWriterAutoLock lock(mutex) +// use +// MutexSingleWriterAutoLockOnThread(lock, mutex) +#define MutexSingleWriterAutoLockOnThread(lock, mutex) \ + MOZ_PUSH_IGNORE_THREAD_SAFETY \ + MutexSingleWriterAutoLock lock(mutex); \ + MOZ_POP_THREAD_SAFETY + +namespace detail { +/** + * ReleasableMutexAutoLock + * Acquires the Mutex when it enters scope, and releases it when it leaves + * scope. Allows calling Unlock (and Lock) as an alternative to + * MutexAutoUnlock; this can avoid an extra lock/unlock pair. + * + */ +template <typename T> +class MOZ_RAII MOZ_SCOPED_CAPABILITY ReleasableBaseAutoLock { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aLock A valid mozilla::Mutex& returned by + * mozilla::Mutex::NewMutex. + **/ + explicit ReleasableBaseAutoLock(T aLock) MOZ_CAPABILITY_ACQUIRE(aLock) + : mLock(aLock) { + mLock.Lock(); + mLocked = true; + } + + ~ReleasableBaseAutoLock(void) MOZ_CAPABILITY_RELEASE() { + if (mLocked) { + Unlock(); + } + } + + void AssertOwns(const T& aMutex) const MOZ_ASSERT_CAPABILITY(aMutex) { + MOZ_ASSERT(&aMutex == &mLock); + mLock.AssertCurrentThreadOwns(); + } + + // Allow dropping the lock prematurely; for example to support something like: + // clang-format off + // MutexAutoLock lock(mMutex); + // ... + // if (foo) { + // lock.Unlock(); + // MethodThatCantBeCalledWithLock() + // return; + // } + // clang-format on + void Unlock() MOZ_CAPABILITY_RELEASE() { + MOZ_ASSERT(mLocked); + mLock.Unlock(); + mLocked = false; + } + void Lock() MOZ_CAPABILITY_ACQUIRE() { + MOZ_ASSERT(!mLocked); + mLock.Lock(); + mLocked = true; + } + + private: + ReleasableBaseAutoLock() = delete; + ReleasableBaseAutoLock(ReleasableBaseAutoLock&) = delete; + ReleasableBaseAutoLock& operator=(ReleasableBaseAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + bool mLocked; + T mLock; +}; + +template <typename MutexType> +ReleasableBaseAutoLock(MutexType&) -> ReleasableBaseAutoLock<MutexType&>; +} // namespace detail + +typedef detail::ReleasableBaseAutoLock<Mutex&> ReleasableMutexAutoLock; + +namespace detail { +/** + * BaseAutoUnlock + * Releases the Mutex when it enters scope, and re-acquires it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Unlock and Lock. + */ +template <typename T> +class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoUnlock { + public: + explicit BaseAutoUnlock(T aLock) MOZ_SCOPED_UNLOCK_RELEASE(aLock) + : mLock(aLock) { + mLock.Unlock(); + } + + explicit BaseAutoUnlock(BaseAutoLock<T>& aAutoLock) + /* MOZ_CAPABILITY_RELEASE(aAutoLock.mLock) */ + : mLock(aAutoLock.mLock) { + NS_ASSERTION(mLock, "null lock"); + mLock->Unlock(); + } + + ~BaseAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mLock.Lock(); } + + private: + BaseAutoUnlock() = delete; + BaseAutoUnlock(BaseAutoUnlock&) = delete; + BaseAutoUnlock& operator=(BaseAutoUnlock&) = delete; + static void* operator new(size_t) noexcept(true); + + T mLock; +}; + +template <typename MutexType> +BaseAutoUnlock(MutexType&) -> BaseAutoUnlock<MutexType&>; +} // namespace detail + +typedef detail::BaseAutoUnlock<Mutex&> MutexAutoUnlock; +typedef detail::BaseAutoUnlock<MutexSingleWriter&> MutexSingleWriterAutoUnlock; +typedef detail::BaseAutoUnlock<OffTheBooksMutex&> OffTheBooksMutexAutoUnlock; + +namespace detail { +/** + * BaseAutoTryLock + * Tries to acquire the Mutex when it enters scope, and releases it when it + * leaves scope. + * + * MUCH PREFERRED to bare calls to Mutex.TryLock and Unlock. + */ +template <typename T> +class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoTryLock { + public: + explicit BaseAutoTryLock(T& aLock) MOZ_CAPABILITY_ACQUIRE(aLock) + : mLock(aLock.TryLock() ? &aLock : nullptr) {} + + ~BaseAutoTryLock() MOZ_CAPABILITY_RELEASE() { + if (mLock) { + mLock->Unlock(); + mLock = nullptr; + } + } + + explicit operator bool() const { return mLock; } + + private: + BaseAutoTryLock(BaseAutoTryLock&) = delete; + BaseAutoTryLock& operator=(BaseAutoTryLock&) = delete; + static void* operator new(size_t) noexcept(true); + + T* mLock; +}; +} // namespace detail + +typedef detail::BaseAutoTryLock<Mutex> MutexAutoTryLock; +typedef detail::BaseAutoTryLock<OffTheBooksMutex> OffTheBooksMutexAutoTryLock; + +} // namespace mozilla + +#endif // ifndef mozilla_Mutex_h diff --git a/xpcom/threads/Queue.h b/xpcom/threads/Queue.h new file mode 100644 index 0000000000..fa36433fdf --- /dev/null +++ b/xpcom/threads/Queue.h @@ -0,0 +1,265 @@ +/* -*- 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_Queue_h +#define mozilla_Queue_h + +#include <utility> +#include <stdint.h> +#include "mozilla/MemoryReporting.h" +#include "mozilla/Assertions.h" +#include "mozalloc.h" + +namespace mozilla { + +// define to turn on additional (DEBUG) asserts +// #define EXTRA_ASSERTS 1 + +// A queue implements a singly linked list of pages, each of which contains some +// number of elements. Since the queue needs to store a "next" pointer, the +// actual number of elements per page won't be quite as many as were requested. +// +// Each page consists of N entries. We use the head buffer as a circular buffer +// if it's the only buffer; if we have more than one buffer when the head is +// empty we release it. This avoids occasional freeing and reallocating buffers +// every N entries. We'll still allocate and free every N if the normal queue +// depth is greated than N. A fancier solution would be to move an empty Head +// buffer to be an empty tail buffer, freeing if we have multiple empty tails, +// but that probably isn't worth it. +// +// Cases: +// a) single buffer, circular +// Push: if not full: +// Add to tail, bump tail and reset to 0 if at end +// full: +// Add new page, insert there and set tail to 1 +// Pop: +// take entry and bump head, reset to 0 if at end +// b) multiple buffers: +// Push: if not full: +// Add to tail, bump tail +// full: +// Add new page, insert there and set tail to 1 +// Pop: +// take entry and bump head, reset to 0 if at end +// if buffer is empty, free head buffer and promote next to head +// +template <class T, size_t RequestedItemsPerPage = 256> +class Queue { + public: + Queue() = default; + + Queue(Queue&& aOther) noexcept + : mHead(std::exchange(aOther.mHead, nullptr)), + mTail(std::exchange(aOther.mTail, nullptr)), + mOffsetHead(std::exchange(aOther.mOffsetHead, 0)), + mHeadLength(std::exchange(aOther.mHeadLength, 0)), + mTailLength(std::exchange(aOther.mTailLength, 0)) {} + + Queue& operator=(Queue&& aOther) noexcept { + Clear(); + + mHead = std::exchange(aOther.mHead, nullptr); + mTail = std::exchange(aOther.mTail, nullptr); + mOffsetHead = std::exchange(aOther.mOffsetHead, 0); + mHeadLength = std::exchange(aOther.mHeadLength, 0); + mTailLength = std::exchange(aOther.mTailLength, 0); + return *this; + } + + ~Queue() { Clear(); } + + // Discard all elements form the queue, clearing it to be empty. + void Clear() { + while (!IsEmpty()) { + Pop(); + } + if (mHead) { + free(mHead); + mHead = nullptr; + } + } + + T& Push(T&& aElement) { +#if defined(EXTRA_ASSERTS) && DEBUG + size_t original_length = Count(); +#endif + if (!mHead) { + mHead = NewPage(); + MOZ_ASSERT(mHead); + + mTail = mHead; + T* eltPtr = &mTail->mEvents[0]; + new (eltPtr) T(std::move(aElement)); + mOffsetHead = 0; + mHeadLength = 1; +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + if ((mHead == mTail && mHeadLength == ItemsPerPage) || + (mHead != mTail && mTailLength == ItemsPerPage)) { + // either we have one (circular) buffer and it's full, or + // we have multiple buffers and the last buffer is full + Page* page = NewPage(); + MOZ_ASSERT(page); + + mTail->mNext = page; + mTail = page; + T* eltPtr = &page->mEvents[0]; + new (eltPtr) T(std::move(aElement)); + mTailLength = 1; +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + if (mHead == mTail) { + // we have space in the (single) head buffer + uint16_t offset = (mOffsetHead + mHeadLength++) % ItemsPerPage; + T* eltPtr = &mTail->mEvents[offset]; + new (eltPtr) T(std::move(aElement)); +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + // else we have space to insert into last buffer + T* eltPtr = &mTail->mEvents[mTailLength++]; + new (eltPtr) T(std::move(aElement)); +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + + bool IsEmpty() const { + return !mHead || (mHead == mTail && mHeadLength == 0); + } + + T Pop() { +#if defined(EXTRA_ASSERTS) && DEBUG + size_t original_length = Count(); +#endif + MOZ_ASSERT(!IsEmpty()); + + T result = std::move(mHead->mEvents[mOffsetHead]); + mHead->mEvents[mOffsetHead].~T(); + mOffsetHead = (mOffsetHead + 1) % ItemsPerPage; + mHeadLength -= 1; + + // Check if mHead points to empty (circular) Page and we have more + // pages + if (mHead != mTail && mHeadLength == 0) { + Page* dead = mHead; + mHead = mHead->mNext; + free(dead); + mOffsetHead = 0; + // if there are still >1 pages, the new head is full. + if (mHead != mTail) { + mHeadLength = ItemsPerPage; + } else { + mHeadLength = mTailLength; + mTailLength = 0; + } + } + +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length - 1); +#endif + return result; + } + + T& FirstElement() { + MOZ_ASSERT(!IsEmpty()); + return mHead->mEvents[mOffsetHead]; + } + + const T& FirstElement() const { + MOZ_ASSERT(!IsEmpty()); + return mHead->mEvents[mOffsetHead]; + } + + T& LastElement() { + MOZ_ASSERT(!IsEmpty()); + uint16_t offset = + mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1; + return mTail->mEvents[offset]; + } + + const T& LastElement() const { + MOZ_ASSERT(!IsEmpty()); + uint16_t offset = + mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1; + return mTail->mEvents[offset]; + } + + size_t Count() const { + // It is obvious count is 0 when the queue is empty. + if (!mHead) { + return 0; + } + + // Compute full (intermediate) pages; Doesn't count first or last page + int count = 0; + // 1 buffer will have mHead == mTail; 2 will have mHead->mNext == mTail + for (Page* page = mHead; page != mTail && page->mNext != mTail; + page = page->mNext) { + count += ItemsPerPage; + } + // add first and last page + count += mHeadLength + mTailLength; + MOZ_ASSERT(count >= 0); + + return count; + } + + size_t ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mHead) { + for (Page* page = mHead; page != mTail; page = page->mNext) { + n += aMallocSizeOf(page); + } + } + return n; + } + + size_t ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + static_assert( + (RequestedItemsPerPage & (RequestedItemsPerPage - 1)) == 0, + "RequestedItemsPerPage should be a power of two to avoid heap slop."); + + // Since a Page must also contain a "next" pointer, we use one of the items to + // store this pointer. If sizeof(T) > sizeof(Page*), then some space will be + // wasted. So be it. + static const size_t ItemsPerPage = RequestedItemsPerPage - 1; + + // Page objects are linked together to form a simple deque. + struct Page { + struct Page* mNext; + T mEvents[ItemsPerPage]; + }; + + static Page* NewPage() { + return static_cast<Page*>(moz_xcalloc(1, sizeof(Page))); + } + + Page* mHead = nullptr; + Page* mTail = nullptr; + + uint16_t mOffsetHead = 0; // Read position in head page + uint16_t mHeadLength = 0; // Number of items in the head page + uint16_t mTailLength = 0; // Number of items in the tail page +}; + +} // namespace mozilla + +#endif // mozilla_Queue_h diff --git a/xpcom/threads/RWLock.cpp b/xpcom/threads/RWLock.cpp new file mode 100644 index 0000000000..949934c8cc --- /dev/null +++ b/xpcom/threads/RWLock.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/RWLock.h" + +namespace mozilla { + +RWLock::RWLock(const char* aName) + : BlockingResourceBase(aName, eMutex) +#ifdef DEBUG + , + mOwningThread(nullptr) +#endif +{ +} + +#ifdef DEBUG +bool RWLock::LockedForWritingByCurrentThread() { + return mOwningThread == PR_GetCurrentThread(); +} +#endif + +} // namespace mozilla + +#undef NativeHandle diff --git a/xpcom/threads/RWLock.h b/xpcom/threads/RWLock.h new file mode 100644 index 0000000000..e03d008631 --- /dev/null +++ b/xpcom/threads/RWLock.h @@ -0,0 +1,243 @@ +/* -*- 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/. */ + +// An interface for read-write locks. + +#ifndef mozilla_RWLock_h +#define mozilla_RWLock_h + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PlatformRWLock.h" +#include "mozilla/ThreadSafety.h" + +namespace mozilla { + +// A RWLock is similar to a Mutex, but whereas a Mutex permits only a single +// reader thread or a single writer thread to access a piece of data, a +// RWLock distinguishes between readers and writers: you may have multiple +// reader threads concurrently accessing a piece of data or a single writer +// thread. This difference should guide your usage of RWLock: if you are not +// reading the data from multiple threads simultaneously or you are writing +// to the data roughly as often as read from it, then Mutex will suit your +// purposes just fine. +// +// You should be using the AutoReadLock and AutoWriteLock classes, below, +// for RAII read locking and write locking, respectively. If you really must +// take a read lock manually, call the ReadLock method; to relinquish that +// read lock, call the ReadUnlock method. Similarly, WriteLock and WriteUnlock +// perform the same operations, but for write locks. +// +// It is unspecified what happens when a given thread attempts to acquire the +// same lock in multiple ways; some underlying implementations of RWLock do +// support acquiring a read lock multiple times on a given thread, but you +// should not rely on this behavior. +// +// It is unspecified whether RWLock gives priority to waiting readers or +// a waiting writer when unlocking. +class MOZ_CAPABILITY("rwlock") RWLock : public detail::RWLockImpl, + public BlockingResourceBase { + public: + explicit RWLock(const char* aName); + +#ifdef DEBUG + bool LockedForWritingByCurrentThread(); + [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true); + void ReadLock() MOZ_ACQUIRE_SHARED(); + void ReadUnlock() MOZ_RELEASE_SHARED(); + [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true); + void WriteLock() MOZ_CAPABILITY_ACQUIRE(); + void WriteUnlock() MOZ_EXCLUSIVE_RELEASE(); +#else + [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) { + return detail::RWLockImpl::tryReadLock(); + } + void ReadLock() MOZ_ACQUIRE_SHARED() { detail::RWLockImpl::readLock(); } + void ReadUnlock() MOZ_RELEASE_SHARED() { detail::RWLockImpl::readUnlock(); } + [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) { + return detail::RWLockImpl::tryWriteLock(); + } + void WriteLock() MOZ_CAPABILITY_ACQUIRE() { detail::RWLockImpl::writeLock(); } + void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() { + detail::RWLockImpl::writeUnlock(); + } +#endif + + private: + RWLock() = delete; + RWLock(const RWLock&) = delete; + RWLock& operator=(const RWLock&) = delete; + +#ifdef DEBUG + // We record the owning thread for write locks only. + PRThread* mOwningThread; +#endif +}; + +// We only use this once; not sure we can add thread safety attributions here +template <typename T> +class MOZ_RAII BaseAutoTryReadLock { + public: + explicit BaseAutoTryReadLock(T& aLock) + : mLock(aLock.TryReadLock() ? &aLock : nullptr) {} + + ~BaseAutoTryReadLock() { + if (mLock) { + mLock->ReadUnlock(); + } + } + + explicit operator bool() const { return mLock; } + + private: + BaseAutoTryReadLock() = delete; + BaseAutoTryReadLock(const BaseAutoTryReadLock&) = delete; + BaseAutoTryReadLock& operator=(const BaseAutoTryReadLock&) = delete; + + T* mLock; +}; + +template <typename T> +class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoReadLock { + public: + explicit BaseAutoReadLock(T& aLock) MOZ_ACQUIRE_SHARED(aLock) + : mLock(&aLock) { + MOZ_ASSERT(mLock, "null lock"); + mLock->ReadLock(); + } + + // Not MOZ_RELEASE_SHARED(), which would make sense - apparently this trips + // over a bug in clang's static analyzer and it says it expected an + // exclusive unlock. + ~BaseAutoReadLock() MOZ_RELEASE_GENERIC() { mLock->ReadUnlock(); } + + private: + BaseAutoReadLock() = delete; + BaseAutoReadLock(const BaseAutoReadLock&) = delete; + BaseAutoReadLock& operator=(const BaseAutoReadLock&) = delete; + + T* mLock; +}; + +// XXX Mutex attributions? +template <typename T> +class MOZ_RAII BaseAutoTryWriteLock { + public: + explicit BaseAutoTryWriteLock(T& aLock) + : mLock(aLock.TryWriteLock() ? &aLock : nullptr) {} + + ~BaseAutoTryWriteLock() { + if (mLock) { + mLock->WriteUnlock(); + } + } + + explicit operator bool() const { return mLock; } + + private: + BaseAutoTryWriteLock() = delete; + BaseAutoTryWriteLock(const BaseAutoTryWriteLock&) = delete; + BaseAutoTryWriteLock& operator=(const BaseAutoTryWriteLock&) = delete; + + T* mLock; +}; + +template <typename T> +class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoWriteLock final { + public: + explicit BaseAutoWriteLock(T& aLock) MOZ_CAPABILITY_ACQUIRE(aLock) + : mLock(&aLock) { + MOZ_ASSERT(mLock, "null lock"); + mLock->WriteLock(); + } + + ~BaseAutoWriteLock() MOZ_CAPABILITY_RELEASE() { mLock->WriteUnlock(); } + + private: + BaseAutoWriteLock() = delete; + BaseAutoWriteLock(const BaseAutoWriteLock&) = delete; + BaseAutoWriteLock& operator=(const BaseAutoWriteLock&) = delete; + + T* mLock; +}; + +// Read try-lock and unlock a RWLock with RAII semantics. Much preferred to +// bare calls to TryReadLock() and ReadUnlock(). +typedef BaseAutoTryReadLock<RWLock> AutoTryReadLock; + +// Read lock and unlock a RWLock with RAII semantics. Much preferred to bare +// calls to ReadLock() and ReadUnlock(). +typedef BaseAutoReadLock<RWLock> AutoReadLock; + +// Write try-lock and unlock a RWLock with RAII semantics. Much preferred to +// bare calls to TryWriteLock() and WriteUnlock(). +typedef BaseAutoTryWriteLock<RWLock> AutoTryWriteLock; + +// Write lock and unlock a RWLock with RAII semantics. Much preferred to bare +// calls to WriteLock() and WriteUnlock(). +typedef BaseAutoWriteLock<RWLock> AutoWriteLock; + +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("rwlock") + StaticRWLock { + public: + // In debug builds, check that mLock 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 + StaticRWLock() { MOZ_ASSERT(!mLock); } +#endif + + [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) { + return Lock()->TryReadLock(); + } + void ReadLock() MOZ_ACQUIRE_SHARED() { Lock()->ReadLock(); } + void ReadUnlock() MOZ_RELEASE_SHARED() { Lock()->ReadUnlock(); } + [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) { + return Lock()->TryWriteLock(); + } + void WriteLock() MOZ_CAPABILITY_ACQUIRE() { Lock()->WriteLock(); } + void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() { Lock()->WriteUnlock(); } + + private: + [[nodiscard]] RWLock* Lock() MOZ_RETURN_CAPABILITY(*mLock) { + if (mLock) { + return mLock; + } + + RWLock* lock = new RWLock("StaticRWLock"); + if (!mLock.compareExchange(nullptr, lock)) { + delete lock; + } + + return mLock; + } + + Atomic<RWLock*> mLock; + + // 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 + StaticRWLock(const StaticRWLock& aOther); +#endif + + // Disallow these operators. + StaticRWLock& operator=(StaticRWLock* aRhs) = delete; + static void* operator new(size_t) noexcept(true) = delete; + static void operator delete(void*) = delete; +}; + +typedef BaseAutoTryReadLock<StaticRWLock> StaticAutoTryReadLock; +typedef BaseAutoReadLock<StaticRWLock> StaticAutoReadLock; +typedef BaseAutoTryWriteLock<StaticRWLock> StaticAutoTryWriteLock; +typedef BaseAutoWriteLock<StaticRWLock> StaticAutoWriteLock; + +} // namespace mozilla + +#endif // mozilla_RWLock_h diff --git a/xpcom/threads/RecursiveMutex.cpp b/xpcom/threads/RecursiveMutex.cpp new file mode 100644 index 0000000000..7c45052c07 --- /dev/null +++ b/xpcom/threads/RecursiveMutex.cpp @@ -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/. */ + +#include "mozilla/RecursiveMutex.h" + +#ifdef XP_WIN +# include <windows.h> + +# define NativeHandle(m) (reinterpret_cast<CRITICAL_SECTION*>(&m)) +#endif + +namespace mozilla { + +RecursiveMutex::RecursiveMutex(const char* aName) + : BlockingResourceBase(aName, eRecursiveMutex) +#ifdef DEBUG + , + mOwningThread(nullptr), + mEntryCount(0) +#endif +{ +#ifdef XP_WIN + // This number was adapted from NSPR. + static const DWORD sLockSpinCount = 100; + +# if defined(RELEASE_OR_BETA) + // Vista and later automatically allocate and subsequently leak a debug info + // object for each critical section that we allocate unless we tell the + // system not to do that. + DWORD flags = CRITICAL_SECTION_NO_DEBUG_INFO; +# else + DWORD flags = 0; +# endif + BOOL r = + InitializeCriticalSectionEx(NativeHandle(mMutex), sLockSpinCount, flags); + MOZ_RELEASE_ASSERT(r); +#else + pthread_mutexattr_t attr; + + MOZ_RELEASE_ASSERT(pthread_mutexattr_init(&attr) == 0, + "pthread_mutexattr_init failed"); + + MOZ_RELEASE_ASSERT( + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0, + "pthread_mutexattr_settype failed"); + + MOZ_RELEASE_ASSERT(pthread_mutex_init(&mMutex, &attr) == 0, + "pthread_mutex_init failed"); + + MOZ_RELEASE_ASSERT(pthread_mutexattr_destroy(&attr) == 0, + "pthread_mutexattr_destroy failed"); +#endif +} + +RecursiveMutex::~RecursiveMutex() { +#ifdef XP_WIN + DeleteCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_destroy(&mMutex) == 0, + "pthread_mutex_destroy failed"); +#endif +} + +void RecursiveMutex::LockInternal() { +#ifdef XP_WIN + EnterCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_lock(&mMutex) == 0, + "pthread_mutex_lock failed"); +#endif +} + +void RecursiveMutex::UnlockInternal() { +#ifdef XP_WIN + LeaveCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_unlock(&mMutex) == 0, + "pthread_mutex_unlock failed"); +#endif +} + +} // namespace mozilla diff --git a/xpcom/threads/RecursiveMutex.h b/xpcom/threads/RecursiveMutex.h new file mode 100644 index 0000000000..dde21c9a35 --- /dev/null +++ b/xpcom/threads/RecursiveMutex.h @@ -0,0 +1,120 @@ +/* -*- 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 lock that can be acquired multiple times on the same thread. + +#ifndef mozilla_RecursiveMutex_h +#define mozilla_RecursiveMutex_h + +#include "mozilla/ThreadSafety.h" +#include "mozilla/BlockingResourceBase.h" + +#ifndef XP_WIN +# include <pthread.h> +#endif + +namespace mozilla { + +class MOZ_CAPABILITY("recursive mutex") RecursiveMutex + : public BlockingResourceBase { + public: + explicit RecursiveMutex(const char* aName); + ~RecursiveMutex(); + +#ifdef DEBUG + void Lock() MOZ_CAPABILITY_ACQUIRE(); + void Unlock() MOZ_CAPABILITY_RELEASE(); +#else + void Lock() MOZ_CAPABILITY_ACQUIRE() { LockInternal(); } + void Unlock() MOZ_CAPABILITY_RELEASE() { UnlockInternal(); } +#endif + +#ifdef DEBUG + /** + * AssertCurrentThreadIn + **/ + void AssertCurrentThreadIn() const MOZ_ASSERT_CAPABILITY(this); + /** + * AssertNotCurrentThreadIn + **/ + void AssertNotCurrentThreadIn() const MOZ_EXCLUDES(this) { + // Not currently implemented. See bug 476536 for discussion. + } +#else + void AssertCurrentThreadIn() const MOZ_ASSERT_CAPABILITY(this) {} + void AssertNotCurrentThreadIn() const MOZ_EXCLUDES(this) {} +#endif + + private: + RecursiveMutex() = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; + + void LockInternal(); + void UnlockInternal(); + +#ifdef DEBUG + PRThread* mOwningThread; + size_t mEntryCount; +#endif + +#if !defined(XP_WIN) + pthread_mutex_t mMutex; +#else + // We eschew including windows.h and using CRITICAL_SECTION here so that files + // including us don't also pull in windows.h. Just use a type that's big + // enough for CRITICAL_SECTION, and we'll fix it up later. + void* mMutex[6]; +#endif +}; + +class MOZ_RAII MOZ_SCOPED_CAPABILITY RecursiveMutexAutoLock { + public: + explicit RecursiveMutexAutoLock(RecursiveMutex& aRecursiveMutex) + MOZ_CAPABILITY_ACQUIRE(aRecursiveMutex) + : mRecursiveMutex(&aRecursiveMutex) { + NS_ASSERTION(mRecursiveMutex, "null mutex"); + mRecursiveMutex->Lock(); + } + + ~RecursiveMutexAutoLock(void) MOZ_CAPABILITY_RELEASE() { + mRecursiveMutex->Unlock(); + } + + private: + RecursiveMutexAutoLock() = delete; + RecursiveMutexAutoLock(const RecursiveMutexAutoLock&) = delete; + RecursiveMutexAutoLock& operator=(const RecursiveMutexAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + mozilla::RecursiveMutex* mRecursiveMutex; +}; + +class MOZ_RAII MOZ_SCOPED_CAPABILITY RecursiveMutexAutoUnlock { + public: + explicit RecursiveMutexAutoUnlock(RecursiveMutex& aRecursiveMutex) + MOZ_SCOPED_UNLOCK_RELEASE(aRecursiveMutex) + : mRecursiveMutex(&aRecursiveMutex) { + NS_ASSERTION(mRecursiveMutex, "null mutex"); + mRecursiveMutex->Unlock(); + } + + ~RecursiveMutexAutoUnlock(void) MOZ_SCOPED_UNLOCK_REACQUIRE() { + mRecursiveMutex->Lock(); + } + + private: + RecursiveMutexAutoUnlock() = delete; + RecursiveMutexAutoUnlock(const RecursiveMutexAutoUnlock&) = delete; + RecursiveMutexAutoUnlock& operator=(const RecursiveMutexAutoUnlock&) = delete; + static void* operator new(size_t) noexcept(true); + + mozilla::RecursiveMutex* mRecursiveMutex; +}; + +} // namespace mozilla + +#endif // mozilla_RecursiveMutex_h diff --git a/xpcom/threads/ReentrantMonitor.h b/xpcom/threads/ReentrantMonitor.h new file mode 100644 index 0000000000..09debad577 --- /dev/null +++ b/xpcom/threads/ReentrantMonitor.h @@ -0,0 +1,251 @@ +/* -*- 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_ReentrantMonitor_h +#define mozilla_ReentrantMonitor_h + +#include "prmon.h" + +#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG) +# include "mozilla/ProfilerThreadSleep.h" +#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG) + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/ThreadSafety.h" +#include "nsISupports.h" +// +// Provides: +// +// - ReentrantMonitor, a Java-like monitor +// - ReentrantMonitorAutoEnter, an RAII class for ensuring that +// ReentrantMonitors are properly entered and exited +// +// Using ReentrantMonitorAutoEnter is MUCH preferred to making bare calls to +// ReentrantMonitor.Enter and Exit. +// +namespace mozilla { + +/** + * ReentrantMonitor + * Java-like monitor. + * When possible, use ReentrantMonitorAutoEnter to hold this monitor within a + * scope, instead of calling Enter/Exit directly. + **/ +class MOZ_CAPABILITY("reentrant monitor") ReentrantMonitor + : BlockingResourceBase { + public: + /** + * ReentrantMonitor + * @param aName A name which can reference this monitor + */ + explicit ReentrantMonitor(const char* aName) + : BlockingResourceBase(aName, eReentrantMonitor) +#ifdef DEBUG + , + mEntryCount(0) +#endif + { + MOZ_COUNT_CTOR(ReentrantMonitor); + mReentrantMonitor = PR_NewMonitor(); + if (!mReentrantMonitor) { + MOZ_CRASH("Can't allocate mozilla::ReentrantMonitor"); + } + } + + /** + * ~ReentrantMonitor + **/ + ~ReentrantMonitor() { + NS_ASSERTION(mReentrantMonitor, + "improperly constructed ReentrantMonitor or double free"); + PR_DestroyMonitor(mReentrantMonitor); + mReentrantMonitor = 0; + MOZ_COUNT_DTOR(ReentrantMonitor); + } + +#ifndef DEBUG + /** + * Enter + * @see prmon.h + **/ + void Enter() MOZ_CAPABILITY_ACQUIRE() { PR_EnterMonitor(mReentrantMonitor); } + + /** + * Exit + * @see prmon.h + **/ + void Exit() MOZ_CAPABILITY_RELEASE() { PR_ExitMonitor(mReentrantMonitor); } + + /** + * Wait + * @see prmon.h + **/ + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) { + PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor); +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + return PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS + ? NS_OK + : NS_ERROR_FAILURE; + } + +#else // ifndef DEBUG + void Enter() MOZ_CAPABILITY_ACQUIRE(); + void Exit() MOZ_CAPABILITY_RELEASE(); + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT); + +#endif // ifndef DEBUG + + /** + * Notify + * @see prmon.h + **/ + nsresult Notify() { + return PR_Notify(mReentrantMonitor) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + + /** + * NotifyAll + * @see prmon.h + **/ + nsresult NotifyAll() { + return PR_NotifyAll(mReentrantMonitor) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + +#ifdef DEBUG + /** + * AssertCurrentThreadIn + * @see prmon.h + **/ + void AssertCurrentThreadIn() MOZ_ASSERT_CAPABILITY(this) { + PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor); + } + + /** + * AssertNotCurrentThreadIn + * @see prmon.h + **/ + void AssertNotCurrentThreadIn() MOZ_ASSERT_CAPABILITY(!this) { + // FIXME bug 476536 + } + +#else + void AssertCurrentThreadIn() MOZ_ASSERT_CAPABILITY(this) {} + void AssertNotCurrentThreadIn() MOZ_ASSERT_CAPABILITY(!this) {} + +#endif // ifdef DEBUG + + private: + ReentrantMonitor(); + ReentrantMonitor(const ReentrantMonitor&); + ReentrantMonitor& operator=(const ReentrantMonitor&); + + PRMonitor* mReentrantMonitor; +#ifdef DEBUG + int32_t mEntryCount; +#endif +}; + +/** + * ReentrantMonitorAutoEnter + * Enters the ReentrantMonitor when it enters scope, and exits it when + * it leaves scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Enter and Exit. + */ +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS ReentrantMonitorAutoEnter { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. + **/ + explicit ReentrantMonitorAutoEnter( + mozilla::ReentrantMonitor& aReentrantMonitor) + MOZ_CAPABILITY_ACQUIRE(aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->Enter(); + } + + ~ReentrantMonitorAutoEnter(void) MOZ_CAPABILITY_RELEASE() { + mReentrantMonitor->Exit(); + } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) { + return mReentrantMonitor->Wait(aInterval); + } + + nsresult Notify() { return mReentrantMonitor->Notify(); } + nsresult NotifyAll() { return mReentrantMonitor->NotifyAll(); } + + private: + ReentrantMonitorAutoEnter(); + ReentrantMonitorAutoEnter(const ReentrantMonitorAutoEnter&); + ReentrantMonitorAutoEnter& operator=(const ReentrantMonitorAutoEnter&); + static void* operator new(size_t) noexcept(true); + + friend class ReentrantMonitorAutoExit; + + mozilla::ReentrantMonitor* mReentrantMonitor; +}; + +/** + * ReentrantMonitorAutoExit + * Exit the ReentrantMonitor when it enters scope, and enters it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Exit and Enter. + */ +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS ReentrantMonitorAutoExit { + public: + /** + * Constructor + * The constructor releases the given lock. The destructor + * acquires the lock. The lock must be held before constructing + * this object! + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. It + * must be already locked. + **/ + explicit ReentrantMonitorAutoExit(ReentrantMonitor& aReentrantMonitor) + MOZ_EXCLUSIVE_RELEASE(aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + explicit ReentrantMonitorAutoExit( + ReentrantMonitorAutoEnter& aReentrantMonitorAutoEnter) + MOZ_EXCLUSIVE_RELEASE(aReentrantMonitorAutoEnter.mReentrantMonitor) + : mReentrantMonitor(aReentrantMonitorAutoEnter.mReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + ~ReentrantMonitorAutoExit(void) MOZ_EXCLUSIVE_RELEASE() { + mReentrantMonitor->Enter(); + } + + private: + ReentrantMonitorAutoExit(); + ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&); + ReentrantMonitorAutoExit& operator=(const ReentrantMonitorAutoExit&); + static void* operator new(size_t) noexcept(true); + + ReentrantMonitor* mReentrantMonitor; +}; + +} // namespace mozilla + +#endif // ifndef mozilla_ReentrantMonitor_h diff --git a/xpcom/threads/SchedulerGroup.cpp b/xpcom/threads/SchedulerGroup.cpp new file mode 100644 index 0000000000..f69ce07003 --- /dev/null +++ b/xpcom/threads/SchedulerGroup.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 "mozilla/SchedulerGroup.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +/* static */ +nsresult SchedulerGroup::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) { + if (NS_IsMainThread()) { + return NS_DispatchToCurrentThread(std::move(aRunnable)); + } + return NS_DispatchToMainThread(std::move(aRunnable)); +} + +} // namespace mozilla diff --git a/xpcom/threads/SchedulerGroup.h b/xpcom/threads/SchedulerGroup.h new file mode 100644 index 0000000000..09f0aac26a --- /dev/null +++ b/xpcom/threads/SchedulerGroup.h @@ -0,0 +1,26 @@ +/* -*- 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_SchedulerGroup_h +#define mozilla_SchedulerGroup_h + +#include "nscore.h" +#include "mozilla/AlreadyAddRefed.h" + +class nsIEventTarget; +class nsIRunnable; +class nsISerialEventTarget; + +namespace mozilla { + +class SchedulerGroup { + public: + static nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable); +}; + +} // namespace mozilla + +#endif // mozilla_SchedulerGroup_h diff --git a/xpcom/threads/SharedThreadPool.cpp b/xpcom/threads/SharedThreadPool.cpp new file mode 100644 index 0000000000..a2de1c4495 --- /dev/null +++ b/xpcom/threads/SharedThreadPool.cpp @@ -0,0 +1,221 @@ +/* -*- 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/SharedThreadPool.h" +#include "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "nsTHashMap.h" +#include "nsXPCOMCIDInternal.h" +#include "nsComponentManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIThreadManager.h" +#include "nsThreadPool.h" + +namespace mozilla { + +// Created and destroyed on the main thread. +static StaticAutoPtr<ReentrantMonitor> sMonitor; + +// Hashtable, maps thread pool name to SharedThreadPool instance. +// Modified only on the main thread. +static StaticAutoPtr<nsTHashMap<nsCStringHashKey, SharedThreadPool*>> sPools; + +static already_AddRefed<nsIThreadPool> CreateThreadPool(const nsCString& aName); + +class SharedThreadPoolShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + protected: + virtual ~SharedThreadPoolShutdownObserver() = default; +}; + +NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports) + +NS_IMETHODIMP +SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads")); +#ifdef EARLY_BETA_OR_EARLIER + { + ReentrantMonitorAutoEnter mon(*sMonitor); + if (!sPools->IsEmpty()) { + nsAutoCString str; + for (const auto& key : sPools->Keys()) { + str.AppendPrintf("\"%s\" ", nsAutoCString(key).get()); + } + printf_stderr( + "SharedThreadPool in xpcom-shutdown-threads. Waiting for " + "pools %s\n", + str.get()); + } + } +#endif + SharedThreadPool::SpinUntilEmpty(); + sMonitor = nullptr; + sPools = nullptr; + return NS_OK; +} + +void SharedThreadPool::InitStatics() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sMonitor && !sPools); + sMonitor = new ReentrantMonitor("SharedThreadPool"); + sPools = new nsTHashMap<nsCStringHashKey, SharedThreadPool*>(); + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + nsCOMPtr<nsIObserver> obs = new SharedThreadPoolShutdownObserver(); + obsService->AddObserver(obs, "xpcom-shutdown-threads", false); +} + +/* static */ +bool SharedThreadPool::IsEmpty() { + ReentrantMonitorAutoEnter mon(*sMonitor); + return !sPools->Count(); +} + +/* static */ +void SharedThreadPool::SpinUntilEmpty() { + MOZ_ASSERT(NS_IsMainThread()); + SpinEventLoopUntil("SharedThreadPool::SpinUntilEmpty"_ns, []() -> bool { + sMonitor->AssertNotCurrentThreadIn(); + return IsEmpty(); + }); +} + +already_AddRefed<SharedThreadPool> SharedThreadPool::Get( + const nsCString& aName, uint32_t aThreadLimit) { + MOZ_ASSERT(sMonitor && sPools); + ReentrantMonitorAutoEnter mon(*sMonitor); + RefPtr<SharedThreadPool> pool; + + return sPools->WithEntryHandle( + aName, [&](auto&& entry) -> already_AddRefed<SharedThreadPool> { + if (entry) { + pool = entry.Data(); + if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { + NS_WARNING("Failed to set limits on thread pool"); + } + } else { + nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName)); + if (NS_WARN_IF(!threadPool)) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + pool = new SharedThreadPool(aName, threadPool); + + // Set the thread and idle limits. Note that we don't rely on the + // EnsureThreadLimitIsAtLeast() call below, as the default thread + // limit is 4, and if aThreadLimit is less than 4 we'll end up with a + // pool with 4 threads rather than what we expected; so we'll have + // unexpected behaviour. + nsresult rv = pool->SetThreadLimit(aThreadLimit); + if (NS_WARN_IF(NS_FAILED(rv))) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + + rv = pool->SetIdleThreadLimit(aThreadLimit); + if (NS_WARN_IF(NS_FAILED(rv))) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + + entry.Insert(pool.get()); + } + + return pool.forget(); + }); +} + +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) { + MOZ_ASSERT(sMonitor); + ReentrantMonitorAutoEnter mon(*sMonitor); + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count = ++mRefCnt; + NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) { + MOZ_ASSERT(sMonitor); + ReentrantMonitorAutoEnter mon(*sMonitor); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "SharedThreadPool"); + if (count) { + return count; + } + + // Remove SharedThreadPool from table of pools. + sPools->Remove(mName); + MOZ_ASSERT(!sPools->Get(mName)); + + // Dispatch an event to the main thread to call Shutdown() on + // the nsIThreadPool. The Runnable here will add a refcount to the pool, + // and when the Runnable releases the nsIThreadPool it will be deleted. + NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool, + &nsIThreadPool::Shutdown)); + + // Stabilize refcount, so that if something in the dtor QIs, it won't explode. + mRefCnt = 1; + delete this; + return 0; +} + +NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) + +SharedThreadPool::SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool) + : mName(aName), mPool(aPool), mRefCnt(0) {} + +SharedThreadPool::~SharedThreadPool() = default; + +nsresult SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) { + // We limit the number of threads that we use. Note that we + // set the thread limit to the same as the idle limit so that we're not + // constantly creating and destroying threads (see Bug 881954). When the + // thread pool threads shutdown they dispatch an event to the main thread + // to call nsIThread::Shutdown(), and if we're very busy that can take a + // while to run, and we end up with dozens of extra threads. Note that + // threads that are idle for 60 seconds are shutdown naturally. + uint32_t existingLimit = 0; + nsresult rv; + + rv = mPool->GetThreadLimit(&existingLimit); + NS_ENSURE_SUCCESS(rv, rv); + if (aLimit > existingLimit) { + rv = mPool->SetThreadLimit(aLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mPool->GetIdleThreadLimit(&existingLimit); + NS_ENSURE_SUCCESS(rv, rv); + if (aLimit > existingLimit) { + rv = mPool->SetIdleThreadLimit(aLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +static already_AddRefed<nsIThreadPool> CreateThreadPool( + const nsCString& aName) { + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + + nsresult rv = pool->SetName(aName); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, nullptr); + + return pool.forget(); +} + +} // namespace mozilla diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h new file mode 100644 index 0000000000..f2c7068051 --- /dev/null +++ b/xpcom/threads/SharedThreadPool.h @@ -0,0 +1,132 @@ +/* -*- 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 SharedThreadPool_h_ +#define SharedThreadPool_h_ + +#include <utility> +#include <type_traits> +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefCountType.h" +#include "nsCOMPtr.h" +#include "nsID.h" +#include "nsIThreadPool.h" +#include "nsString.h" +#include "nscore.h" + +class nsIRunnable; + +namespace mozilla { + +// Wrapper that makes an nsIThreadPool a singleton, and provides a +// consistent threadsafe interface to get instances. Callers simply get a +// SharedThreadPool by the name of its nsIThreadPool. All get requests of +// the same name get the same SharedThreadPool. Users must store a reference +// to the pool, and when the last reference to a SharedThreadPool is dropped +// the pool is shutdown and deleted. Users aren't required to manually +// shutdown the pool, and can release references on any thread. This can make +// it significantly easier to use thread pools, because the caller doesn't need +// to worry about joining and tearing it down. +// +// On Windows all threads in the pool have MSCOM initialized with +// COINIT_MULTITHREADED. Note that not all users of MSCOM use this mode see [1], +// and mixing MSCOM objects between the two is terrible for performance, and can +// cause some functions to fail. So be careful when using Win32 APIs on a +// SharedThreadPool, and avoid sharing objects if at all possible. +// +// [1] +// https://searchfox.org/mozilla-central/search?q=coinitialize&redirect=false +class SharedThreadPool : public nsIThreadPool { + public: + // Gets (possibly creating) the shared thread pool singleton instance with + // thread pool named aName. + static already_AddRefed<SharedThreadPool> Get(const nsCString& aName, + uint32_t aThreadLimit = 4); + + // We implement custom threadsafe AddRef/Release pair, that destroys the + // the shared pool singleton when the refcount drops to 0. The addref/release + // are implemented using locking, so it's not recommended that you use them + // in a tight loop. + 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; + + // Forward behaviour to wrapped thread pool implementation. + NS_FORWARD_SAFE_NSITHREADPOOL(mPool); + + // Call this when dispatching from an event on the same + // threadpool that is about to complete. We should not create a new thread + // in that case since a thread is about to become idle. + nsresult DispatchFromEndOfTaskInThisPool(nsIRunnable* event) { + return Dispatch(event, NS_DISPATCH_AT_END); + } + + NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override { + return Dispatch(event, flags); + } + + NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event, + uint32_t flags = NS_DISPATCH_NORMAL) override { + return !mPool ? NS_ERROR_NULL_POINTER + : mPool->Dispatch(std::move(event), flags); + } + + NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + using nsIEventTarget::Dispatch; + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* task) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* task) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD IsOnCurrentThread(bool* _retval) override { + return !mPool ? NS_ERROR_NULL_POINTER : mPool->IsOnCurrentThread(_retval); + } + + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible() override { + return mPool && mPool->IsOnCurrentThread(); + } + + // Creates necessary statics. Called once at startup. + static void InitStatics(); + + // Spins the event loop until all thread pools are shutdown. + // *Must* be called on the main thread. + static void SpinUntilEmpty(); + + private: + // Returns whether there are no pools in existence at the moment. + static bool IsEmpty(); + + // Creates a singleton SharedThreadPool wrapper around aPool. + // aName is the name of the aPool, and is used to lookup the + // SharedThreadPool in the hash table of all created pools. + SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool); + virtual ~SharedThreadPool(); + + nsresult EnsureThreadLimitIsAtLeast(uint32_t aThreadLimit); + + // Name of mPool. + const nsCString mName; + + // Thread pool being wrapped. + nsCOMPtr<nsIThreadPool> mPool; + + // Refcount. We implement custom ref counting so that the thread pool is + // shutdown in a threadsafe manner and singletonness is preserved. + nsrefcnt mRefCnt; +}; + +} // namespace mozilla + +#endif // SharedThreadPool_h_ diff --git a/xpcom/threads/SpinEventLoopUntil.h b/xpcom/threads/SpinEventLoopUntil.h new file mode 100644 index 0000000000..a281177268 --- /dev/null +++ b/xpcom/threads/SpinEventLoopUntil.h @@ -0,0 +1,191 @@ +/* -*- 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_threads_SpinEventLoopUntil_h__ +#define xpcom_threads_SpinEventLoopUntil_h__ + +#include "MainThreadUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StaticMutex.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +class nsIThread; + +// A wrapper for nested event loops. +// +// This function is intended to make code more obvious (do you remember +// what NS_ProcessNextEvent(nullptr, true) means?) and slightly more +// efficient, as people often pass nullptr or NS_GetCurrentThread to +// NS_ProcessNextEvent, which results in needless querying of the current +// thread every time through the loop. +// +// You should use this function in preference to NS_ProcessNextEvent inside +// a loop unless one of the following is true: +// +// * You need to pass `false` to NS_ProcessNextEvent; or +// * You need to do unusual things around the call to NS_ProcessNextEvent, +// such as unlocking mutexes that you are holding. +// +// If you *do* need to call NS_ProcessNextEvent manually, please do call +// NS_GetCurrentThread() outside of your loop and pass the returned pointer +// into NS_ProcessNextEvent for a tiny efficiency win. +namespace mozilla { + +// You should normally not need to deal with this template parameter. If +// you enjoy esoteric event loop details, read on. +// +// If you specify that NS_ProcessNextEvent wait for an event, it is possible +// for NS_ProcessNextEvent to return false, i.e. to indicate that an event +// was not processed. This can only happen when the thread has been shut +// down by another thread, but is still attempting to process events outside +// of a nested event loop. +// +// This behavior is admittedly strange. The scenario it deals with is the +// following: +// +// * The current thread has been shut down by some owner thread. +// * The current thread is spinning an event loop waiting for some condition +// to become true. +// * Said condition is actually being fulfilled by another thread, so there +// are timing issues in play. +// +// Thus, there is a small window where the current thread's event loop +// spinning can check the condition, find it false, and call +// NS_ProcessNextEvent to wait for another event. But we don't actually +// want it to wait indefinitely, because there might not be any other events +// in the event loop, and the current thread can't accept dispatched events +// because it's being shut down. Thus, actually blocking would hang the +// thread, which is bad. The solution, then, is to detect such a scenario +// and not actually block inside NS_ProcessNextEvent. +// +// But this is a problem, because we want to return the status of +// NS_ProcessNextEvent to the caller of SpinEventLoopUntil if possible. In +// the above scenario, however, we'd stop spinning prematurely and cause +// all sorts of havoc. We therefore have this template parameter to +// control whether errors are ignored or passed out to the caller of +// SpinEventLoopUntil. The latter is the default; if you find yourself +// wanting to use the former, you should think long and hard before doing +// so, and write a comment like this defending your choice. + +enum class ProcessFailureBehavior { + IgnoreAndContinue, + ReportToCaller, +}; + +// SpinEventLoopUntil is a dangerous operation that can result in hangs. +// In particular during shutdown we want to know if we are hanging +// inside a nested event loop on the main thread. +// This is a helper annotation class to keep track of this. +struct MOZ_STACK_CLASS AutoNestedEventLoopAnnotation { + explicit AutoNestedEventLoopAnnotation(const nsACString& aEntry) + : mPrev(nullptr) { + if (NS_IsMainThread()) { + StaticMutexAutoLock lock(sStackMutex); + mPrev = sCurrent; + sCurrent = this; + if (mPrev) { + mStack = mPrev->mStack + "|"_ns + aEntry; + } else { + mStack = aEntry; + } + AnnotateXPCOMSpinEventLoopStack(mStack); + } + } + + ~AutoNestedEventLoopAnnotation() { + if (NS_IsMainThread()) { + StaticMutexAutoLock lock(sStackMutex); + MOZ_ASSERT(sCurrent == this); + sCurrent = mPrev; + if (mPrev) { + AnnotateXPCOMSpinEventLoopStack(mPrev->mStack); + } else { + AnnotateXPCOMSpinEventLoopStack(""_ns); + } + } + } + + static void CopyCurrentStack(nsCString& aNestedSpinStack) { + // We need to copy this behind a mutex as the + // memory for our instances is stack-bound and + // can go away at any time. + StaticMutexAutoLock lock(sStackMutex); + if (sCurrent) { + aNestedSpinStack = sCurrent->mStack; + } else { + aNestedSpinStack = "(no nested event loop active)"_ns; + } + } + + private: + AutoNestedEventLoopAnnotation(const AutoNestedEventLoopAnnotation&) = delete; + AutoNestedEventLoopAnnotation& operator=( + const AutoNestedEventLoopAnnotation&) = delete; + + // The declarations of these statics live in nsThreadManager.cpp. + static AutoNestedEventLoopAnnotation* sCurrent MOZ_GUARDED_BY(sStackMutex); + static StaticMutex sStackMutex; + + // We need this to avoid the inclusion of nsExceptionHandler.h here + // which can include windows.h which disturbs some dom/media/gtest. + // The implementation lives in nsThreadManager.cpp. + static void AnnotateXPCOMSpinEventLoopStack(const nsACString& aStack); + + AutoNestedEventLoopAnnotation* mPrev MOZ_GUARDED_BY(sStackMutex); + nsCString mStack MOZ_GUARDED_BY(sStackMutex); +}; + +// Please see the above notes for the Behavior template parameter. +// +// aVeryGoodReasonToDoThis is usually a literal string unique to each +// caller that can be recognized in the XPCOMSpinEventLoopStack +// annotation. +// aPredicate is the condition we wait for. +// aThread can be used to specify a thread, see the above introduction. +// It defaults to the current thread. +template < + ProcessFailureBehavior Behavior = ProcessFailureBehavior::ReportToCaller, + typename Pred> +bool SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis, + Pred&& aPredicate, nsIThread* aThread = nullptr) { + // Prepare the annotations + AutoNestedEventLoopAnnotation annotation(aVeryGoodReasonToDoThis); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "SpinEventLoopUntil", OTHER, aVeryGoodReasonToDoThis); + AUTO_PROFILER_MARKER_TEXT("SpinEventLoop", OTHER, MarkerStack::Capture(), + aVeryGoodReasonToDoThis); + + nsIThread* thread = aThread ? aThread : NS_GetCurrentThread(); + + // From a latency perspective, spinning the event loop is like leaving script + // and returning to the event loop. Tell the watchdog we stopped running + // script (until we return). + mozilla::Maybe<xpc::AutoScriptActivity> asa; + if (NS_IsMainThread()) { + asa.emplace(false); + } + + while (!aPredicate()) { + bool didSomething = NS_ProcessNextEvent(thread, true); + + if (Behavior == ProcessFailureBehavior::IgnoreAndContinue) { + // Don't care what happened, continue on. + continue; + } else if (!didSomething) { + return false; + } + } + + return true; +} + +} // namespace mozilla + +#endif // xpcom_threads_SpinEventLoopUntil_h__ diff --git a/xpcom/threads/StateMirroring.h b/xpcom/threads/StateMirroring.h new file mode 100644 index 0000000000..9f8ded70f4 --- /dev/null +++ b/xpcom/threads/StateMirroring.h @@ -0,0 +1,453 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(StateMirroring_h_) +# define StateMirroring_h_ + +# include <cstddef> +# include "mozilla/AbstractThread.h" +# include "mozilla/AlreadyAddRefed.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/RefPtr.h" +# include "mozilla/StateWatching.h" +# include "nsCOMPtr.h" +# include "nsIRunnable.h" +# include "nsISupports.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +/* + * The state-mirroring machinery allows pieces of interesting state to be + * observed on multiple thread without locking. The basic strategy is to track + * changes in a canonical value and post updates to other threads that hold + * mirrors for that value. + * + * One problem with the naive implementation of such a system is that some + * pieces of state need to be updated atomically, and certain other operations + * need to wait for these atomic updates to complete before executing. The + * state-mirroring machinery solves this problem by requiring that its owner + * thread uses tail dispatch, and posting state update events (which should + * always be run first by TaskDispatcher implementations) to that tail + * dispatcher. This ensures that state changes are always atomic from the + * perspective of observing threads. + * + * Given that state-mirroring is an automatic background process, we try to + * avoid burdening the caller with worrying too much about teardown. To that + * end, we don't assert dispatch success for any of the notifications, and + * assume that any canonical or mirror owned by a thread for whom dispatch fails + * will soon be disconnected by its holder anyway. + * + * Given that semantics may change and comments tend to go out of date, we + * deliberately don't provide usage examples here. Grep around to find them. + */ + +namespace mozilla { + +// Mirror<T> and Canonical<T> inherit WatchTarget, so we piggy-back on the +// logging that WatchTarget already does. Given that, it makes sense to share +// the same log module. +# define MIRROR_LOG(x, ...) \ + MOZ_ASSERT(gStateWatchingLog); \ + MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) + +template <typename T> +class AbstractMirror; + +/* + * AbstractCanonical is a superclass from which all Canonical values must + * inherit. It serves as the interface of operations which may be performed (via + * asynchronous dispatch) by other threads, in particular by the corresponding + * Mirror value. + */ +template <typename T> +class AbstractCanonical { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractCanonical) + AbstractCanonical(AbstractThread* aThread) : mOwnerThread(aThread) {} + virtual void AddMirror(AbstractMirror<T>* aMirror) = 0; + virtual void RemoveMirror(AbstractMirror<T>* aMirror) = 0; + + AbstractThread* OwnerThread() const { return mOwnerThread; } + + protected: + virtual ~AbstractCanonical() {} + RefPtr<AbstractThread> mOwnerThread; +}; + +/* + * AbstractMirror is a superclass from which all Mirror values must + * inherit. It serves as the interface of operations which may be performed (via + * asynchronous dispatch) by other threads, in particular by the corresponding + * Canonical value. + */ +template <typename T> +class AbstractMirror { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractMirror) + AbstractMirror(AbstractThread* aThread) : mOwnerThread(aThread) {} + virtual void ConnectedOnCanonicalThread(AbstractCanonical<T>* aCanonical) = 0; + virtual void UpdateValue(const T& aNewValue) = 0; + virtual void NotifyDisconnected() = 0; + + AbstractThread* OwnerThread() const { return mOwnerThread; } + + protected: + virtual ~AbstractMirror() {} + RefPtr<AbstractThread> mOwnerThread; +}; + +/* + * Canonical<T> is a wrapper class that allows a given value to be mirrored by + * other threads. It maintains a list of active mirrors, and queues updates for + * them when the internal value changes. When changing the value, the caller + * needs to pass a TaskDispatcher object, which fires the updates at the + * appropriate time. Canonical<T> is also a WatchTarget, and may be set up to + * trigger other routines (on the same thread) when the canonical value changes. + * + * Canonical<T> is intended to be used as a member variable, so it doesn't + * actually inherit AbstractCanonical<T> (a refcounted type). Rather, it + * contains an inner class called |Impl| that implements most of the interesting + * logic. + */ +template <typename T> +class Canonical { + public: + Canonical(AbstractThread* aThread, const T& aInitialValue, + const char* aName) { + mImpl = new Impl(aThread, aInitialValue, aName); + } + + ~Canonical() {} + + private: + class Impl : public AbstractCanonical<T>, public WatchTarget { + public: + using AbstractCanonical<T>::OwnerThread; + + Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName) + : AbstractCanonical<T>(aThread), + WatchTarget(aName), + mValue(aInitialValue) { + MIRROR_LOG("%s [%p] initialized", mName, this); + MOZ_ASSERT(aThread->SupportsTailDispatch(), + "Can't get coherency without tail dispatch"); + } + + void ConnectMirror(AbstractMirror<T>* aMirror) { + MIRROR_LOG("%s [%p] canonical-init connecting mirror %p", mName, this, + aMirror); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(OwnerThread()->RequiresTailDispatch(aMirror->OwnerThread()), + "Can't get coherency without tail dispatch"); + aMirror->ConnectedOnCanonicalThread(this); + AddMirror(aMirror); + } + + void AddMirror(AbstractMirror<T>* aMirror) override { + MIRROR_LOG("%s [%p] adding mirror %p", mName, this, aMirror); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(!mMirrors.Contains(aMirror)); + mMirrors.AppendElement(aMirror); + aMirror->OwnerThread()->DispatchStateChange(MakeNotifier(aMirror)); + } + + void RemoveMirror(AbstractMirror<T>* aMirror) override { + MIRROR_LOG("%s [%p] removing mirror %p", mName, this, aMirror); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(mMirrors.Contains(aMirror)); + mMirrors.RemoveElement(aMirror); + } + + void DisconnectAll() { + MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this); + for (size_t i = 0; i < mMirrors.Length(); ++i) { + mMirrors[i]->OwnerThread()->Dispatch( + NewRunnableMethod("AbstractMirror::NotifyDisconnected", mMirrors[i], + &AbstractMirror<T>::NotifyDisconnected)); + } + mMirrors.Clear(); + } + + operator const T&() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + return mValue; + } + + void Set(const T& aNewValue) { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + + if (aNewValue == mValue) { + return; + } + + // Notify same-thread watchers. The state watching machinery will make + // sure that notifications run at the right time. + NotifyWatchers(); + + // Check if we've already got a pending update. If so we won't schedule + // another one. + bool alreadyNotifying = mInitialValue.isSome(); + + // Stash the initial value if needed, then update to the new value. + if (mInitialValue.isNothing()) { + mInitialValue.emplace(mValue); + } + mValue = aNewValue; + + // We wait until things have stablized before sending state updates so + // that we can avoid sending multiple updates, and possibly avoid sending + // any updates at all if the value ends up where it started. + if (!alreadyNotifying) { + AbstractThread::DispatchDirectTask(NewRunnableMethod( + "Canonical::Impl::DoNotify", this, &Impl::DoNotify)); + } + } + + Impl& operator=(const T& aNewValue) { + Set(aNewValue); + return *this; + } + Impl& operator=(const Impl& aOther) { + Set(aOther); + return *this; + } + Impl(const Impl& aOther) = delete; + + protected: + ~Impl() { MOZ_DIAGNOSTIC_ASSERT(mMirrors.IsEmpty()); } + + private: + void DoNotify() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(mInitialValue.isSome()); + bool same = mInitialValue.ref() == mValue; + mInitialValue.reset(); + + if (same) { + MIRROR_LOG("%s [%p] unchanged - not sending update", mName, this); + return; + } + + for (size_t i = 0; i < mMirrors.Length(); ++i) { + mMirrors[i]->OwnerThread()->DispatchStateChange( + MakeNotifier(mMirrors[i])); + } + } + + already_AddRefed<nsIRunnable> MakeNotifier(AbstractMirror<T>* aMirror) { + return NewRunnableMethod<T>("AbstractMirror::UpdateValue", aMirror, + &AbstractMirror<T>::UpdateValue, mValue); + } + + T mValue; + Maybe<T> mInitialValue; + nsTArray<RefPtr<AbstractMirror<T>>> mMirrors; + }; + + public: + /* + * Connect this Canonical to aMirror. Note that the canonical value starts + * being mirrored to aMirror immediately, and requires one thread hop to + * aMirror's owning thread before the connection is established. + * + * Note that this could race with Mirror::DisconnectIfConnected(). It is up to + * the caller to provide the guarantee that disconnection happens after the + * connection has been established. There is no race between this and + * DisconnectAll(). + */ + void ConnectMirror(AbstractMirror<T>* aMirror) { + return mImpl->ConnectMirror(aMirror); + } + void DisconnectAll() { return mImpl->DisconnectAll(); } + + // Access to the Impl. + operator Impl&() { return *mImpl; } + Impl* operator&() { return mImpl; } + + // Access to the T. + const T& Ref() const { return *mImpl; } + operator const T&() const { return Ref(); } + void Set(const T& aNewValue) { mImpl->Set(aNewValue); } + Canonical& operator=(const T& aNewValue) { + Set(aNewValue); + return *this; + } + Canonical& operator=(const Canonical& aOther) { + Set(aOther); + return *this; + } + Canonical(const Canonical& aOther) = delete; + + private: + RefPtr<Impl> mImpl; +}; + +/* + * Mirror<T> is a wrapper class that allows a given value to mirror that of a + * Canonical<T> owned by another thread. It registers itself with a + * Canonical<T>, and is periodically updated with new values. Mirror<T> is also + * a WatchTarget, and may be set up to trigger other routines (on the same + * thread) when the mirrored value changes. + * + * Mirror<T> is intended to be used as a member variable, so it doesn't actually + * inherit AbstractMirror<T> (a refcounted type). Rather, it contains an inner + * class called |Impl| that implements most of the interesting logic. + */ +template <typename T> +class Mirror { + public: + Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName) { + mImpl = new Impl(aThread, aInitialValue, aName); + } + + ~Mirror() { + // As a member of complex objects, a Mirror<T> may be destroyed on a + // different thread than its owner, or late in shutdown during CC. Given + // that, we require manual disconnection so that callers can put things in + // the right place. + MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected()); + mImpl->AssertNoIncomingConnects(); + } + + private: + class Impl : public AbstractMirror<T>, public WatchTarget { + public: + using AbstractMirror<T>::OwnerThread; + + Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName) + : AbstractMirror<T>(aThread), + WatchTarget(aName), + mValue(aInitialValue) { + MIRROR_LOG("%s [%p] initialized", mName, this); + MOZ_ASSERT(aThread->SupportsTailDispatch(), + "Can't get coherency without tail dispatch"); + } + + operator const T&() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + return mValue; + } + + void ConnectedOnCanonicalThread(AbstractCanonical<T>* aCanonical) override { + MOZ_ASSERT(aCanonical->OwnerThread()->IsCurrentThreadIn()); +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + ++mIncomingConnects; +# endif + OwnerThread()->DispatchStateChange( + NewRunnableMethod<StoreRefPtrPassByPtr<AbstractCanonical<T>>>( + "Mirror::Impl::SetCanonical", this, &Impl::SetCanonical, + aCanonical)); + } + + void SetCanonical(AbstractCanonical<T>* aCanonical) { + MIRROR_LOG("%s [%p] Canonical-init setting canonical %p", mName, this, + aCanonical); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(!IsConnected()); +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + --mIncomingConnects; +# endif + mCanonical = aCanonical; + } + + void UpdateValue(const T& aNewValue) override { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + if (mValue != aNewValue) { + mValue = aNewValue; + WatchTarget::NotifyWatchers(); + } + } + + void NotifyDisconnected() override { + MIRROR_LOG("%s [%p] Notifed of disconnection from %p", mName, this, + mCanonical.get()); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + mCanonical = nullptr; + } + + bool IsConnected() const { return !!mCanonical; } + + void Connect(AbstractCanonical<T>* aCanonical) { + MIRROR_LOG("%s [%p] Connecting to %p", mName, this, aCanonical); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(!IsConnected()); + MOZ_ASSERT(OwnerThread()->RequiresTailDispatch(aCanonical->OwnerThread()), + "Can't get coherency without tail dispatch"); + + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>( + "AbstractCanonical::AddMirror", aCanonical, + &AbstractCanonical<T>::AddMirror, this); + aCanonical->OwnerThread()->Dispatch(r.forget()); + mCanonical = aCanonical; + } + + void DisconnectIfConnected() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + if (!IsConnected()) { + return; + } + + MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this, + mCanonical.get()); + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>( + "AbstractCanonical::RemoveMirror", mCanonical, + &AbstractCanonical<T>::RemoveMirror, this); + mCanonical->OwnerThread()->Dispatch(r.forget()); + mCanonical = nullptr; + } + + void AssertNoIncomingConnects() { + MOZ_DIAGNOSTIC_ASSERT(mIncomingConnects == 0); + } + + protected: + ~Impl() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); } + + private: + T mValue; + RefPtr<AbstractCanonical<T>> mCanonical; +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + std::atomic<size_t> mIncomingConnects = 0; +# endif + }; + + public: + // Forward control operations to the Impl<T>. + /* + * Connect aCanonical to this Mirror. Note that this requires one thread hop + * back to aCanonical's owning thread before the canonical value starts + * being mirrored, and another to our owning thread before the connection is + * established. + * + * Note that this mirror-initialized connection could race with + * Canonical::DisconnectAll(). It is up to the caller to provide the guarantee + * that disconnection happens after the connection has been established. There + * is no race between this and DisconnectIfConnected(). + */ + void Connect(AbstractCanonical<T>* aCanonical) { mImpl->Connect(aCanonical); } + void DisconnectIfConnected() { mImpl->DisconnectIfConnected(); } + + // Access to the Impl<T>. + operator Impl&() { return *mImpl; } + Impl* operator&() { return mImpl; } + + // Access to the T. + const T& Ref() const { return *mImpl; } + operator const T&() const { return Ref(); } + + private: + RefPtr<Impl> mImpl; +}; + +# undef MIRROR_LOG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/StateWatching.h b/xpcom/threads/StateWatching.h new file mode 100644 index 0000000000..3da0c63bfe --- /dev/null +++ b/xpcom/threads/StateWatching.h @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(StateWatching_h_) +# define StateWatching_h_ + +# include <cstddef> +# include <new> +# include <utility> +# include "mozilla/AbstractThread.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/RefPtr.h" +# include "nsISupports.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +/* + * The state-watching machinery automates the process of responding to changes + * in various pieces of state. + * + * A standard programming pattern is as follows: + * + * mFoo = ...; + * NotifyStuffChanged(); + * ... + * mBar = ...; + * NotifyStuffChanged(); + * + * This pattern is error-prone and difficult to audit because it requires the + * programmer to manually trigger the update routine. This can be especially + * problematic when the update routine depends on numerous pieces of state, and + * when that state is modified across a variety of helper methods. In these + * cases the responsibility for invoking the routine is often unclear, causing + * developers to scatter calls to it like pixie dust. This can result in + * duplicate invocations (which is wasteful) and missing invocations in corner- + * cases (which is a source of bugs). + * + * This file provides a set of primitives that automatically handle updates and + * allow the programmers to explicitly construct a graph of state dependencies. + * When used correctly, it eliminates the guess-work and wasted cycles described + * above. + * + * There are two basic pieces: + * (1) Objects that can be watched for updates. These inherit WatchTarget. + * (2) Objects that receive objects and trigger processing. These inherit + * AbstractWatcher. In the current machinery, these exist only internally + * within the WatchManager, though that could change. + * + * Note that none of this machinery is thread-safe - it must all happen on the + * same owning thread. To solve multi-threaded use-cases, use state mirroring + * and watch the mirrored value. + * + * Given that semantics may change and comments tend to go out of date, we + * deliberately don't provide usage examples here. Grep around to find them. + */ + +namespace mozilla { + +extern LazyLogModule gStateWatchingLog; + +# define WATCH_LOG(x, ...) \ + MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) + +/* + * AbstractWatcher is a superclass from which all watchers must inherit. + */ +class AbstractWatcher { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher) + AbstractWatcher() : mDestroyed(false) {} + bool IsDestroyed() { return mDestroyed; } + virtual void Notify() = 0; + + protected: + virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); } + bool mDestroyed; +}; + +/* + * WatchTarget is a superclass from which all watchable things must inherit. + * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass + * needs only to invoke NotifyWatchers when something changes. + * + * The functionality that this class provides is not threadsafe, and should only + * be used on the thread that owns that WatchTarget. + */ +class WatchTarget { + public: + explicit WatchTarget(const char* aName) : mName(aName) {} + + void AddWatcher(AbstractWatcher* aWatcher) { + MOZ_ASSERT(!mWatchers.Contains(aWatcher)); + mWatchers.AppendElement(aWatcher); + } + + void RemoveWatcher(AbstractWatcher* aWatcher) { + MOZ_ASSERT(mWatchers.Contains(aWatcher)); + mWatchers.RemoveElement(aWatcher); + } + + protected: + void NotifyWatchers() { + WATCH_LOG("%s[%p] notifying watchers\n", mName, this); + PruneWatchers(); + for (size_t i = 0; i < mWatchers.Length(); ++i) { + mWatchers[i]->Notify(); + } + } + + private: + // We don't have Watchers explicitly unregister themselves when they die, + // because then they'd need back-references to all the WatchTargets they're + // subscribed to, and WatchTargets aren't reference-counted. So instead we + // just prune dead ones at appropriate times, which works just fine. + void PruneWatchers() { + mWatchers.RemoveElementsBy( + [](const auto& watcher) { return watcher->IsDestroyed(); }); + } + + nsTArray<RefPtr<AbstractWatcher>> mWatchers; + + protected: + const char* mName; +}; + +/* + * Watchable is a wrapper class that turns any primitive into a WatchTarget. + */ +template <typename T> +class Watchable : public WatchTarget { + public: + Watchable(const T& aInitialValue, const char* aName) + : WatchTarget(aName), mValue(aInitialValue) {} + + const T& Ref() const { return mValue; } + operator const T&() const { return Ref(); } + Watchable& operator=(const T& aNewValue) { + if (aNewValue != mValue) { + mValue = aNewValue; + NotifyWatchers(); + } + + return *this; + } + + private: + Watchable(const Watchable& aOther) = delete; + Watchable& operator=(const Watchable& aOther) = delete; + + T mValue; +}; + +// Manager class for state-watching. Declare one of these in any class for which +// you want to invoke method callbacks. +// +// Internally, WatchManager maintains one AbstractWatcher per callback method. +// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple. +// This causes an AbstractWatcher for |Callback| to be instantiated if it +// doesn't already exist, and registers it with |WatchTarget|. +// +// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire +// watch callbacks no more than once per task, once all other operations for +// that task have been completed. +// +// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType| +// objects. Given that, it and its owned objects can't hold permanent strong +// refs to the owner, since that would keep the owner alive indefinitely. +// Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire. +// This ensures that everything is kept alive just long enough. +template <typename OwnerType> +class WatchManager { + public: + typedef void (OwnerType::*CallbackMethod)(); + explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread) + : mOwner(aOwner), mOwnerThread(aOwnerThread) {} + + ~WatchManager() { + if (!IsShutdown()) { + Shutdown(); + } + } + + bool IsShutdown() const { return !mOwner; } + + // Shutdown needs to happen on mOwnerThread. If the WatchManager will be + // destroyed on a different thread, Shutdown() must be called manually. + void Shutdown() { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + for (auto& watcher : mWatchers) { + watcher->Destroy(); + } + mWatchers.Clear(); + mOwner = nullptr; + } + + void Watch(WatchTarget& aTarget, CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + aTarget.AddWatcher(&EnsureWatcher(aMethod)); + } + + void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + MOZ_ASSERT(watcher); + aTarget.RemoveWatcher(watcher); + } + + void ManualNotify(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + MOZ_ASSERT(watcher); + watcher->Notify(); + } + + private: + class PerCallbackWatcher : public AbstractWatcher { + public: + PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread, + CallbackMethod aMethod) + : mOwner(aOwner), + mOwnerThread(aOwnerThread), + mCallbackMethod(aMethod) {} + + void Destroy() { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + mDestroyed = true; + mOwner = nullptr; + } + + void Notify() override { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_DIAGNOSTIC_ASSERT(mOwner, + "mOwner is only null after destruction, " + "at which point we shouldn't be notified"); + if (mNotificationPending) { + // We've already got a notification job in the pipe. + return; + } + mNotificationPending = true; + + // Queue up our notification jobs to run in a stable state. + AbstractThread::DispatchDirectTask( + NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify", + [self = RefPtr<PerCallbackWatcher>(this), + owner = RefPtr<OwnerType>(mOwner)]() { + if (!self->mDestroyed) { + ((*owner).*(self->mCallbackMethod))(); + } + self->mNotificationPending = false; + })); + } + + bool CallbackMethodIs(CallbackMethod aMethod) const { + return mCallbackMethod == aMethod; + } + + private: + ~PerCallbackWatcher() = default; + + OwnerType* mOwner; // Never null. + bool mNotificationPending = false; + RefPtr<AbstractThread> mOwnerThread; + CallbackMethod mCallbackMethod; + }; + + PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + for (auto& watcher : mWatchers) { + if (watcher->CallbackMethodIs(aMethod)) { + return watcher; + } + } + return nullptr; + } + + PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + if (watcher) { + return *watcher; + } + watcher = mWatchers + .AppendElement(MakeAndAddRef<PerCallbackWatcher>( + mOwner, mOwnerThread, aMethod)) + ->get(); + return *watcher; + } + + nsTArray<RefPtr<PerCallbackWatcher>> mWatchers; + OwnerType* mOwner; + RefPtr<AbstractThread> mOwnerThread; +}; + +# undef WATCH_LOG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/SyncRunnable.h b/xpcom/threads/SyncRunnable.h new file mode 100644 index 0000000000..77f82ba313 --- /dev/null +++ b/xpcom/threads/SyncRunnable.h @@ -0,0 +1,157 @@ +/* -*- 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_SyncRunnable_h +#define mozilla_SyncRunnable_h + +#include <utility> + +#include "mozilla/AbstractThread.h" +#include "mozilla/Monitor.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +/** + * This class will wrap a nsIRunnable and dispatch it to the target thread + * synchronously. This is different from + * NS_DispatchAndSpinEventLoopUntilComplete: this class does not spin the event + * loop waiting for the event to be dispatched. This means that you don't risk + * reentrance from pending messages, but you must be sure that the target thread + * does not ever block on this thread, or else you will deadlock. + * + * Typical usage: + * RefPtr<SyncRunnable> sr = new SyncRunnable(new myrunnable...()); + * sr->DispatchToThread(t); + * + * We also provide convenience wrappers: + * SyncRunnable::DispatchToThread(pThread, new myrunnable...()); + * SyncRunnable::DispatchToThread(pThread, NS_NewRunnableFunction(...)); + * + */ +class SyncRunnable : public Runnable { + public: + explicit SyncRunnable(nsIRunnable* aRunnable) + : Runnable("SyncRunnable"), + mRunnable(aRunnable), + mMonitor("SyncRunnable"), + mDone(false) {} + + explicit SyncRunnable(already_AddRefed<nsIRunnable> aRunnable) + : Runnable("SyncRunnable"), + mRunnable(std::move(aRunnable)), + mMonitor("SyncRunnable"), + mDone(false) {} + + nsresult DispatchToThread(nsIEventTarget* aThread, + bool aForceDispatch = false) { + nsresult rv; + bool on; + + if (!aForceDispatch) { + rv = aThread->IsOnCurrentThread(&on); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv) && on) { + mRunnable->Run(); + return NS_OK; + } + } + + rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) { + mozilla::MonitorAutoLock lock(mMonitor); + // This could be synchronously dispatching to a thread currently waiting + // for JS execution clearance. Yield JS execution. + dom::AutoYieldJSThreadExecution yield; + + while (!mDone) { + lock.Wait(); + } + } + return rv; + } + + nsresult DispatchToThread(AbstractThread* aThread, + bool aForceDispatch = false) { + if (!aForceDispatch && aThread->IsCurrentThreadIn()) { + mRunnable->Run(); + return NS_OK; + } + + // Check we don't have tail dispatching here. Otherwise we will deadlock + // ourself when spinning the loop below. + MOZ_ASSERT(!aThread->RequiresTailDispatchFromCurrentThread()); + + nsresult rv = aThread->Dispatch(RefPtr<nsIRunnable>(this).forget()); + if (NS_SUCCEEDED(rv)) { + mozilla::MonitorAutoLock lock(mMonitor); + while (!mDone) { + lock.Wait(); + } + } + return rv; + } + + static nsresult DispatchToThread(nsIEventTarget* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) { + RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable)); + return s->DispatchToThread(aThread, aForceDispatch); + } + + static nsresult DispatchToThread(AbstractThread* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) { + RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable)); + return s->DispatchToThread(aThread, aForceDispatch); + } + + static nsresult DispatchToThread(nsIEventTarget* aThread, + already_AddRefed<nsIRunnable> aRunnable, + bool aForceDispatch = false) { + RefPtr<SyncRunnable> s(new SyncRunnable(std::move(aRunnable))); + return s->DispatchToThread(aThread, aForceDispatch); + } + + static nsresult DispatchToThread(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable, + bool aForceDispatch = false) { + RefPtr<SyncRunnable> s(new SyncRunnable(std::move(aRunnable))); + return s->DispatchToThread(aThread, aForceDispatch); + } + + // These deleted overloads prevent accidentally (if harmlessly) double- + // wrapping SyncRunnable, which was previously a common anti-pattern. + static nsresult DispatchToThread(nsIEventTarget* aThread, + SyncRunnable* aRunnable, + bool aForceDispatch = false) = delete; + static nsresult DispatchToThread(AbstractThread* aThread, + SyncRunnable* aRunnable, + bool aForceDispatch = false) = delete; + + protected: + NS_IMETHOD Run() override { + mRunnable->Run(); + + mozilla::MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(!mDone); + + mDone = true; + mMonitor.Notify(); + + return NS_OK; + } + + private: + nsCOMPtr<nsIRunnable> mRunnable; + mozilla::Monitor mMonitor; + bool mDone MOZ_GUARDED_BY(mMonitor); +}; + +} // namespace mozilla + +#endif // mozilla_SyncRunnable_h diff --git a/xpcom/threads/SynchronizedEventQueue.cpp b/xpcom/threads/SynchronizedEventQueue.cpp new file mode 100644 index 0000000000..59161b7f9d --- /dev/null +++ b/xpcom/threads/SynchronizedEventQueue.cpp @@ -0,0 +1,26 @@ +/* -*- 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 "SynchronizedEventQueue.h" +#include "nsIThreadInternal.h" + +using namespace mozilla; + +void SynchronizedEventQueue::AddObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ASSERT(!mEventObservers.Contains(aObserver)); + mEventObservers.AppendElement(aObserver); +} + +void SynchronizedEventQueue::RemoveObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ALWAYS_TRUE(mEventObservers.RemoveElement(aObserver)); +} + +const nsTObserverArray<nsCOMPtr<nsIThreadObserver>>& +SynchronizedEventQueue::EventObservers() { + return mEventObservers; +} diff --git a/xpcom/threads/SynchronizedEventQueue.h b/xpcom/threads/SynchronizedEventQueue.h new file mode 100644 index 0000000000..e4cf1a62af --- /dev/null +++ b/xpcom/threads/SynchronizedEventQueue.h @@ -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/. */ + +#ifndef mozilla_SynchronizedEventQueue_h +#define mozilla_SynchronizedEventQueue_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/EventQueue.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "nsIThreadInternal.h" +#include "nsCOMPtr.h" +#include "nsTObserverArray.h" + +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThreadObserver; + +namespace mozilla { + +// A SynchronizedEventQueue is an abstract class for event queues that can be +// used across threads. A SynchronizedEventQueue implementation will typically +// use locks and condition variables to guarantee consistency. The methods of +// SynchronizedEventQueue are split between ThreadTargetSink (which contains +// methods for posting events) and SynchronizedEventQueue (which contains +// methods for getting events). This split allows event targets (specifically +// ThreadEventTarget) to use a narrow interface, since they only need to post +// events. +// +// ThreadEventQueue is the canonical implementation of +// SynchronizedEventQueue. When Quantum DOM is implemented, we will use a +// different synchronized queue on the main thread, SchedulerEventQueue, which +// will handle the cooperative threading model. + +class ThreadTargetSink { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadTargetSink) + + virtual bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) = 0; + + // After this method is called, no more events can be posted. + virtual void Disconnect(const MutexAutoLock& aProofOfLock) = 0; + + virtual nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) = 0; + virtual nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) = 0; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + // Not const because overrides may need to take a lock + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) = 0; + + protected: + virtual ~ThreadTargetSink() = default; +}; + +class SynchronizedEventQueue : public ThreadTargetSink { + public: + virtual already_AddRefed<nsIRunnable> GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) = 0; + virtual bool HasPendingEvent() = 0; + + // This method atomically checks if there are pending events and, if there are + // none, forbids future events from being posted. It returns true if there + // were no pending events. + virtual bool ShutdownIfNoPendingEvents() = 0; + + // These methods provide access to an nsIThreadObserver, whose methods are + // called when posting and processing events. SetObserver should only be + // called on the thread that processes events. GetObserver can be called from + // any thread. GetObserverOnThread must be used from the thread that processes + // events; it does not acquire a lock. + virtual already_AddRefed<nsIThreadObserver> GetObserver() = 0; + virtual already_AddRefed<nsIThreadObserver> GetObserverOnThread() = 0; + virtual void SetObserver(nsIThreadObserver* aObserver) = 0; + + void AddObserver(nsIThreadObserver* aObserver); + void RemoveObserver(nsIThreadObserver* aObserver); + const nsTObserverArray<nsCOMPtr<nsIThreadObserver>>& EventObservers(); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) override { + // Normally we'd return + // mEventObservers.ShallowSizeOfExcludingThis(aMallocSizeOf); However, + // mEventObservers may be being mutated on another thread, and we don't lock + // around access, so locking here wouldn't help. They're small, so + return 0; + } + + /** + * This method causes any events currently enqueued on the thread to be + * suppressed until PopEventQueue is called, and any event dispatched to this + * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be + * nested and must each be paired with a call to PopEventQueue in order to + * restore the original state of the thread. The returned nsIEventTarget may + * be used to push events onto the nested queue. Dispatching will be disabled + * once the event queue is popped. The thread will only ever process pending + * events for the innermost event queue. Must only be called on the target + * thread. + */ + virtual already_AddRefed<nsISerialEventTarget> PushEventQueue() = 0; + + /** + * Revert a call to PushEventQueue. When an event queue is popped, any events + * remaining in the queue are appended to the elder queue. This also causes + * the nsIEventTarget returned from PushEventQueue to stop dispatching events. + * Must only be called on the target thread, and with the innermost event + * queue. + */ + virtual void PopEventQueue(nsIEventTarget* aTarget) = 0; + + /** + * Flush the list of shutdown tasks which were previously registered. After + * this is called, new shutdown tasks cannot be registered. + */ + virtual void RunShutdownTasks() = 0; + + protected: + virtual ~SynchronizedEventQueue() = default; + + private: + nsTObserverArray<nsCOMPtr<nsIThreadObserver>> mEventObservers; +}; + +} // namespace mozilla + +#endif // mozilla_SynchronizedEventQueue_h diff --git a/xpcom/threads/TaskController.cpp b/xpcom/threads/TaskController.cpp new file mode 100644 index 0000000000..8e3aae185a --- /dev/null +++ b/xpcom/threads/TaskController.cpp @@ -0,0 +1,1098 @@ +/* -*- 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 "TaskController.h" +#include "nsIIdleRunnable.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" +#include <algorithm> +#include "GeckoProfiler.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/EventQueue.h" +#include "mozilla/Hal.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/VsyncTaskManager.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "nsIThreadInternal.h" +#include "nsThread.h" +#include "prenv.h" +#include "prsystem.h" + +namespace mozilla { + +StaticAutoPtr<TaskController> TaskController::sSingleton; + +thread_local size_t mThreadPoolIndex = -1; +std::atomic<uint64_t> Task::sCurrentTaskSeqNo = 0; + +const int32_t kMinimumPoolThreadCount = 2; +const int32_t kMaximumPoolThreadCount = 8; + +/* static */ +int32_t TaskController::GetPoolThreadCount() { + if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) { + return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0); + } + + int32_t numCores = 0; +#if defined(XP_MACOSX) && defined(__aarch64__) + if (const auto& cpuInfo = hal::GetHeterogeneousCpuInfo()) { + // -1 because of the main thread. + numCores = cpuInfo->mBigCpus.Count() + cpuInfo->mMediumCpus.Count() - 1; + } else +#endif + { + numCores = std::max<int32_t>(1, PR_GetNumberOfProcessors()); + } + + return std::clamp<int32_t>(numCores, kMinimumPoolThreadCount, + kMaximumPoolThreadCount); +} + +#if defined(MOZ_COLLECTING_RUNNABLE_TELEMETRY) + +struct TaskMarker : BaseMarkerType<TaskMarker> { + static constexpr const char* Name = "Task"; + static constexpr const char* Description = + "Marker representing a task being executed in TaskController."; + + using MS = MarkerSchema; + static constexpr MS::PayloadField PayloadFields[] = { + {"name", MS::InputType::CString, "Task Name", MS::Format::String, + MS::PayloadFlags::Searchable}, + {"priority", MS::InputType::Uint32, "Priority level", + MS::Format::Integer}, + {"priorityName", MS::InputType::CString, "Priority Name"}}; + + static constexpr MS::Location Locations[] = {MS::Location::MarkerChart, + MS::Location::MarkerTable}; + static constexpr const char* ChartLabel = "{marker.data.name}"; + static constexpr const char* TableLabel = + "{marker.name} - {marker.data.name} - priority: " + "{marker.data.priorityName} ({marker.data.priority})"; + + static constexpr MS::ETWMarkerGroup Group = MS::ETWMarkerGroup::Scheduling; + + static void TranslateMarkerInputToSchema(void* aContext, + const nsCString& aName, + uint32_t aPriority) { + ETW::OutputMarkerSchema(aContext, TaskMarker{}, aName, aPriority, + ProfilerStringView("")); + } + + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const nsCString& aName, uint32_t aPriority) { + aWriter.StringProperty("name", aName); + aWriter.IntProperty("priority", aPriority); + +# define EVENT_PRIORITY(NAME, VALUE) \ + if (aPriority == (VALUE)) { \ + aWriter.StringProperty("priorityName", #NAME); \ + } else + EVENT_QUEUE_PRIORITY_LIST(EVENT_PRIORITY) +# undef EVENT_PRIORITY + { + aWriter.StringProperty("priorityName", "Invalid Value"); + } + } +}; + +class MOZ_RAII AutoProfileTask { + public: + explicit AutoProfileTask(nsACString& aName, uint64_t aPriority) + : mName(aName), mPriority(aPriority) { + if (profiler_is_collecting_markers()) { + mStartTime = TimeStamp::Now(); + } + } + + ~AutoProfileTask() { + if (!profiler_thread_is_being_profiled_for_markers()) { + return; + } + + AUTO_PROFILER_LABEL("AutoProfileTask", PROFILER); + AUTO_PROFILER_STATS(AUTO_PROFILE_TASK); + profiler_add_marker("Runnable", ::mozilla::baseprofiler::category::OTHER, + mStartTime.IsNull() + ? MarkerTiming::IntervalEnd() + : MarkerTiming::IntervalUntilNowFrom(mStartTime), + TaskMarker{}, mName, mPriority); + } + + private: + TimeStamp mStartTime; + nsAutoCString mName; + uint32_t mPriority; +}; + +# define AUTO_PROFILE_FOLLOWING_TASK(task) \ + nsAutoCString name; \ + (task)->GetName(name); \ + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \ + mozilla::AutoProfileTask PROFILER_RAII(name, (task)->GetPriority()); +#else +# define AUTO_PROFILE_FOLLOWING_TASK(task) +#endif + +bool TaskManager:: + UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + const MutexAutoLock& aProofOfLock, IterationType aIterationType) { + mCurrentSuspended = IsSuspended(aProofOfLock); + + if (aIterationType == IterationType::EVENT_LOOP_TURN && !mCurrentSuspended) { + int32_t oldModifier = mCurrentPriorityModifier; + mCurrentPriorityModifier = + GetPriorityModifierForEventLoopTurn(aProofOfLock); + + if (mCurrentPriorityModifier != oldModifier) { + return true; + } + } + return false; +} + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +class MOZ_RAII AutoSetMainThreadRunnableName { + public: + explicit AutoSetMainThreadRunnableName(const nsCString& aName) { + MOZ_ASSERT(NS_IsMainThread()); + // We want to record our current runnable's name in a static so + // that BHR can record it. + mRestoreRunnableName = nsThread::sMainThreadRunnableName; + + // Copy the name into sMainThreadRunnableName's buffer, and append a + // terminating null. + uint32_t length = std::min((uint32_t)nsThread::kRunnableNameBufSize - 1, + (uint32_t)aName.Length()); + memcpy(nsThread::sMainThreadRunnableName.begin(), aName.BeginReading(), + length); + nsThread::sMainThreadRunnableName[length] = '\0'; + } + + ~AutoSetMainThreadRunnableName() { + nsThread::sMainThreadRunnableName = mRestoreRunnableName; + } + + private: + Array<char, nsThread::kRunnableNameBufSize> mRestoreRunnableName; +}; +#endif + +Task* Task::GetHighestPriorityDependency() { + Task* currentTask = this; + + while (!currentTask->mDependencies.empty()) { + auto iter = currentTask->mDependencies.begin(); + + while (iter != currentTask->mDependencies.end()) { + if ((*iter)->mCompleted) { + auto oldIter = iter; + iter++; + // Completed tasks are removed here to prevent needlessly keeping them + // alive or iterating over them in the future. + currentTask->mDependencies.erase(oldIter); + continue; + } + + currentTask = iter->get(); + break; + } + } + + return currentTask == this ? nullptr : currentTask; +} + +void TaskController::Initialize() { + MOZ_ASSERT(!sSingleton); + sSingleton = new TaskController(); +} + +void ThreadFuncPoolThread(void* aIndex) { + mThreadPoolIndex = *reinterpret_cast<int32_t*>(aIndex); + delete reinterpret_cast<int32_t*>(aIndex); + TaskController::Get()->RunPoolThread(); +} + +TaskController::TaskController() + : mGraphMutex("TaskController::mGraphMutex"), + mThreadPoolCV(mGraphMutex, "TaskController::mThreadPoolCV"), + mMainThreadCV(mGraphMutex, "TaskController::mMainThreadCV"), + mRunOutOfMTTasksCounter(0) { + InputTaskManager::Init(); + VsyncTaskManager::Init(); + mMTProcessingRunnable = NS_NewRunnableFunction( + "TaskController::ExecutePendingMTTasks()", + []() { TaskController::Get()->ProcessPendingMTTask(); }); + mMTBlockingProcessingRunnable = NS_NewRunnableFunction( + "TaskController::ExecutePendingMTTasks()", + []() { TaskController::Get()->ProcessPendingMTTask(true); }); +} + +// We want our default stack size limit to be approximately 2MB, to be safe for +// JS helper tasks that can use a lot of stack, but expect most threads to use +// much less. On Linux, however, requesting a stack of 2MB or larger risks the +// kernel allocating an entire 2MB huge page for it on first access, which we do +// not want. To avoid this possibility, we subtract 2 standard VM page sizes +// from our default. +constexpr PRUint32 sBaseStackSize = 2048 * 1024 - 2 * 4096; + +// TSan enforces a minimum stack size that's just slightly larger than our +// default helper stack size. It does this to store blobs of TSan-specific data +// on each thread's stack. Unfortunately, that means that even though we'll +// actually receive a larger stack than we requested, the effective usable space +// of that stack is significantly less than what we expect. To offset TSan +// stealing our stack space from underneath us, double the default. +// +// Similarly, ASan requires more stack space due to red-zones. +#if defined(MOZ_TSAN) || defined(MOZ_ASAN) +constexpr PRUint32 sStackSize = 2 * sBaseStackSize; +#else +constexpr PRUint32 sStackSize = sBaseStackSize; +#endif + +void TaskController::InitializeThreadPool() { + mPoolInitializationMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!mThreadPoolInitialized); + mThreadPoolInitialized = true; + + int32_t poolSize = GetPoolThreadCount(); + for (int32_t i = 0; i < poolSize; i++) { + int32_t* index = new int32_t(i); + mPoolThreads.push_back( + {PR_CreateThread(PR_USER_THREAD, ThreadFuncPoolThread, index, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, sStackSize), + nullptr}); + } +} + +/* static */ +size_t TaskController::GetThreadStackSize() { return sStackSize; } + +void TaskController::SetPerformanceCounterState( + PerformanceCounterState* aPerformanceCounterState) { + mPerformanceCounterState = aPerformanceCounterState; +} + +/* static */ +void TaskController::Shutdown() { + InputTaskManager::Cleanup(); + VsyncTaskManager::Cleanup(); + if (sSingleton) { + sSingleton->ShutdownThreadPoolInternal(); + sSingleton = nullptr; + } + MOZ_ASSERT(!sSingleton); +} + +void TaskController::ShutdownThreadPoolInternal() { + { + // Prevent race condition on mShuttingDown and wait. + MutexAutoLock lock(mGraphMutex); + mShuttingDown = true; + mThreadPoolCV.NotifyAll(); + } + for (PoolThread& thread : mPoolThreads) { + PR_JoinThread(thread.mThread); + } +} + +void TaskController::RunPoolThread() { + IOInterposer::RegisterCurrentThread(); + + // This is used to hold on to a task to make sure it is released outside the + // lock. This is required since it's perfectly feasible for task destructors + // to post events themselves. + RefPtr<Task> lastTask; + + nsAutoCString threadName; + threadName.AppendLiteral("TaskController #"); + threadName.AppendInt(static_cast<int64_t>(mThreadPoolIndex)); + AUTO_PROFILER_REGISTER_THREAD(threadName.BeginReading()); + + MutexAutoLock lock(mGraphMutex); + while (true) { + bool ranTask = false; + + if (!mThreadableTasks.empty()) { + for (auto iter = mThreadableTasks.begin(); iter != mThreadableTasks.end(); + ++iter) { + // Search for the highest priority dependency of the highest priority + // task. + + // We work with rawptrs to avoid needless refcounting. All our tasks + // are always kept alive by the graph. If one is removed from the graph + // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask. + Task* task = iter->get(); + + MOZ_ASSERT(!task->mTaskManager); + + mPoolThreads[mThreadPoolIndex].mEffectiveTaskPriority = + task->GetPriority(); + + Task* nextTask; + while ((nextTask = task->GetHighestPriorityDependency())) { + task = nextTask; + } + + if (task->GetKind() == Task::Kind::MainThreadOnly || + task->mInProgress) { + continue; + } + + mPoolThreads[mThreadPoolIndex].mCurrentTask = task; + mThreadableTasks.erase(task->mIterator); + task->mIterator = mThreadableTasks.end(); + task->mInProgress = true; + + if (!mThreadableTasks.empty()) { + // Ensure at least one additional thread is woken up if there are + // more threadable tasks to process. Notifying all threads at once + // isn't actually better for performance since they all need the + // GraphMutex to proceed anyway. + mThreadPoolCV.Notify(); + } + + bool taskCompleted = false; + { + MutexAutoUnlock unlock(mGraphMutex); + lastTask = nullptr; + AUTO_PROFILE_FOLLOWING_TASK(task); + taskCompleted = task->Run() == Task::TaskResult::Complete; + ranTask = true; + } + + task->mInProgress = false; + + if (!taskCompleted) { + // Presumably this task was interrupted, leave its dependencies + // unresolved and reinsert into the queue. + auto insertion = mThreadableTasks.insert( + mPoolThreads[mThreadPoolIndex].mCurrentTask); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + } else { + task->mCompleted = true; +#ifdef DEBUG + task->mIsInGraph = false; +#endif + task->mDependencies.clear(); + // This may have unblocked a main thread task. We could do this only + // if there was a main thread task before this one in the dependency + // chain. + mMayHaveMainThreadTask = true; + // Since this could have multiple dependencies thare are restricted + // to the main thread. Let's make sure that's awake. + EnsureMainThreadTasksScheduled(); + + MaybeInterruptTask(GetHighestPriorityMTTask()); + } + + // Store last task for release next time we release the lock or enter + // wait state. + lastTask = mPoolThreads[mThreadPoolIndex].mCurrentTask.forget(); + break; + } + } + + // Ensure the last task is released before we enter the wait state. + if (lastTask) { + MutexAutoUnlock unlock(mGraphMutex); + lastTask = nullptr; + + // Run another loop iteration, while we were unlocked there was an + // opportunity for another task to be posted or shutdown to be initiated. + continue; + } + + if (!ranTask) { + if (mShuttingDown) { + IOInterposer::UnregisterCurrentThread(); + MOZ_ASSERT(mThreadableTasks.empty()); + return; + } + + AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE); + mThreadPoolCV.Wait(); + } + } +} + +void TaskController::AddTask(already_AddRefed<Task>&& aTask) { + RefPtr<Task> task(aTask); + + if (task->GetKind() == Task::Kind::OffMainThreadOnly) { + MutexAutoLock lock(mPoolInitializationMutex); + if (!mThreadPoolInitialized) { + InitializeThreadPool(); + } + } + + MutexAutoLock lock(mGraphMutex); + + if (TaskManager* manager = task->GetManager()) { + if (manager->mTaskCount == 0) { + mTaskManagers.insert(manager); + } + manager->DidQueueTask(); + + // Set this here since if this manager's priority modifier doesn't change + // we will not reprioritize when iterating over the queue. + task->mPriorityModifier = manager->mCurrentPriorityModifier; + } + + if (profiler_is_active_and_unpaused()) { + task->mInsertionTime = TimeStamp::Now(); + } + +#ifdef DEBUG + task->mIsInGraph = true; + + for (const RefPtr<Task>& otherTask : task->mDependencies) { + MOZ_ASSERT(!otherTask->mTaskManager || + otherTask->mTaskManager == task->mTaskManager); + } +#endif + + LogTask::LogDispatch(task); + + std::pair<std::set<RefPtr<Task>, Task::PriorityCompare>::iterator, bool> + insertion; + switch (task->GetKind()) { + case Task::Kind::MainThreadOnly: + if (task->GetPriority() >= + static_cast<uint32_t>(EventQueuePriority::Normal) && + !mMainThreadTasks.empty()) { + insertion = std::pair( + mMainThreadTasks.insert(--mMainThreadTasks.end(), std::move(task)), + true); + } else { + insertion = mMainThreadTasks.insert(std::move(task)); + } + break; + case Task::Kind::OffMainThreadOnly: + insertion = mThreadableTasks.insert(std::move(task)); + break; + } + (*insertion.first)->mIterator = insertion.first; + MOZ_ASSERT(insertion.second); + + MaybeInterruptTask(*insertion.first); +} + +void TaskController::WaitForTaskOrMessage() { + MutexAutoLock lock(mGraphMutex); + while (!mMayHaveMainThreadTask) { + AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE); + mMainThreadCV.Wait(); + } +} + +void TaskController::ExecuteNextTaskOnlyMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mGraphMutex); + ExecuteNextTaskOnlyMainThreadInternal(lock); +} + +void TaskController::ProcessPendingMTTask(bool aMayWait) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mGraphMutex); + + for (;;) { + // We only ever process one event here. However we may sometimes + // not actually process a real event because of suspended tasks. + // This loop allows us to wait until we've processed something + // in that scenario. + + mMTTaskRunnableProcessedTask = ExecuteNextTaskOnlyMainThreadInternal(lock); + + if (mMTTaskRunnableProcessedTask || !aMayWait) { + break; + } + +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + // Unlock before calling into the BackgroundHangMonitor API as it uses + // the timer API. + { + MutexAutoUnlock unlock(mGraphMutex); + BackgroundHangMonitor().NotifyWait(); + } +#endif + + { + // ProcessNextEvent will also have attempted to wait, however we may have + // given it a Runnable when all the tasks in our task graph were suspended + // but we weren't able to cheaply determine that. + AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE); + mMainThreadCV.Wait(); + } + +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + { + MutexAutoUnlock unlock(mGraphMutex); + BackgroundHangMonitor().NotifyActivity(); + } +#endif + } + + if (mMayHaveMainThreadTask) { + EnsureMainThreadTasksScheduled(); + } +} + +void TaskController::ReprioritizeTask(Task* aTask, uint32_t aPriority) { + MutexAutoLock lock(mGraphMutex); + std::set<RefPtr<Task>, Task::PriorityCompare>* queue = &mMainThreadTasks; + if (aTask->GetKind() == Task::Kind::OffMainThreadOnly) { + queue = &mThreadableTasks; + } + + MOZ_ASSERT(aTask->mIterator != queue->end()); + queue->erase(aTask->mIterator); + + aTask->mPriority = aPriority; + + auto insertion = queue->insert(aTask); + MOZ_ASSERT(insertion.second); + aTask->mIterator = insertion.first; + + MaybeInterruptTask(aTask); +} + +// Code supporting runnable compatibility. +// Task that wraps a runnable. +class RunnableTask : public Task { + public: + RunnableTask(already_AddRefed<nsIRunnable>&& aRunnable, int32_t aPriority, + Kind aKind) + : Task(aKind, aPriority), mRunnable(aRunnable) {} + + virtual TaskResult Run() override { + mRunnable->Run(); + mRunnable = nullptr; + return TaskResult::Complete; + } + + void SetIdleDeadline(TimeStamp aDeadline) override { + nsCOMPtr<nsIIdleRunnable> idleRunnable = do_QueryInterface(mRunnable); + if (idleRunnable) { + idleRunnable->SetDeadline(aDeadline); + } + } + + virtual bool GetName(nsACString& aName) override { +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName))); + } else { + aName.AssignLiteral("non-nsINamed runnable"); + } + if (aName.IsEmpty()) { + aName.AssignLiteral("anonymous runnable"); + } + return true; +#else + return false; +#endif + } + + private: + RefPtr<nsIRunnable> mRunnable; +}; + +void TaskController::DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aPriority, + TaskManager* aManager) { + RefPtr<RunnableTask> task = new RunnableTask(std::move(aRunnable), aPriority, + Task::Kind::MainThreadOnly); + + task->SetManager(aManager); + TaskController::Get()->AddTask(task.forget()); +} + +nsIRunnable* TaskController::GetRunnableForMTTask(bool aReallyWait) { + MutexAutoLock lock(mGraphMutex); + + while (mMainThreadTasks.empty()) { + if (!aReallyWait) { + return nullptr; + } + + AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE); + mMainThreadCV.Wait(); + } + + return aReallyWait ? mMTBlockingProcessingRunnable : mMTProcessingRunnable; +} + +bool TaskController::HasMainThreadPendingTasks() { + MOZ_ASSERT(NS_IsMainThread()); + auto resetIdleState = MakeScopeExit([&idleManager = mIdleTaskManager] { + if (idleManager) { + idleManager->State().ClearCachedIdleDeadline(); + } + }); + + for (bool considerIdle : {false, true}) { + if (considerIdle && !mIdleTaskManager) { + continue; + } + + MutexAutoLock lock(mGraphMutex); + + if (considerIdle) { + mIdleTaskManager->State().ForgetPendingTaskGuarantee(); + // Temporarily unlock so we can peek our idle deadline. + // XXX We could do this _before_ we take the lock if the API would let us. + // We do want to do this before looking at mMainThreadTasks, in case + // someone adds one while we're unlocked. + { + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().CachePeekedIdleDeadline(unlock); + } + } + + // Return early if there's no tasks at all. + if (mMainThreadTasks.empty()) { + return false; + } + + // We can cheaply count how many tasks are suspended. + uint64_t totalSuspended = 0; + for (TaskManager* manager : mTaskManagers) { + DebugOnly<bool> modifierChanged = + manager + ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + lock, TaskManager::IterationType::NOT_EVENT_LOOP_TURN); + MOZ_ASSERT(!modifierChanged); + + // The idle manager should be suspended unless we're doing the idle pass. + MOZ_ASSERT(manager != mIdleTaskManager || manager->mCurrentSuspended || + considerIdle, + "Why are idle tasks not suspended here?"); + + if (manager->mCurrentSuspended) { + // XXX - If managers manage off-main-thread tasks this breaks! This + // scenario is explicitly not supported. + // + // This is only incremented inside the lock -or- decremented on the main + // thread so this is safe. + totalSuspended += manager->mTaskCount; + } + } + + // This would break down if we have a non-suspended task depending on a + // suspended task. This is why for the moment we do not allow tasks + // to be dependent on tasks managed by another taskmanager. + if (mMainThreadTasks.size() > totalSuspended) { + // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended + // state of mIdleTaskManager above, hence shouldn't even check it here. + // But in that case idle tasks are not contributing to our suspended task + // count anyway. + if (mIdleTaskManager && mIdleTaskManager->mTaskCount && + !mIdleTaskManager->mCurrentSuspended) { + MOZ_ASSERT(considerIdle, "Why is mIdleTaskManager not suspended?"); + // Check whether the idle tasks were really needed to make our "we have + // an unsuspended task" decision. If they were, we need to force-enable + // idle tasks until we run our next task. + if (mMainThreadTasks.size() - mIdleTaskManager->mTaskCount <= + totalSuspended) { + mIdleTaskManager->State().EnforcePendingTaskGuarantee(); + } + } + return true; + } + } + return false; +} + +uint64_t TaskController::PendingMainthreadTaskCountIncludingSuspended() { + MutexAutoLock lock(mGraphMutex); + return mMainThreadTasks.size(); +} + +bool TaskController::ExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(NS_IsMainThread()); + mGraphMutex.AssertCurrentThreadOwns(); + // Block to make it easier to jump to our cleanup. + bool taskRan = false; + do { + taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock); + if (taskRan) { + if (mIdleTaskManager && mIdleTaskManager->mTaskCount && + mIdleTaskManager->IsSuspended(aProofOfLock)) { + uint32_t activeTasks = mMainThreadTasks.size(); + for (TaskManager* manager : mTaskManagers) { + if (manager->IsSuspended(aProofOfLock)) { + activeTasks -= manager->mTaskCount; + } else { + break; + } + } + + if (!activeTasks) { + // We have only idle (and maybe other suspended) tasks left, so need + // to update the idle state. We need to temporarily release the lock + // while we do that. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RequestIdleDeadlineIfNeeded(unlock); + } + } + break; + } + + if (!mIdleTaskManager) { + break; + } + + if (mIdleTaskManager->mTaskCount) { + // We have idle tasks that we may not have gotten above because + // our idle state is not up to date. We need to update the idle state + // and try again. We need to temporarily release the lock while we do + // that. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().UpdateCachedIdleDeadline(unlock); + } else { + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RanOutOfTasks(unlock); + } + + // When we unlocked, someone may have queued a new task on us. So try to + // see whether we can run things again. + taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock); + } while (false); + + if (mIdleTaskManager) { + // The pending task guarantee is not needed anymore, since we just tried + // running a task + mIdleTaskManager->State().ForgetPendingTaskGuarantee(); + + if (mMainThreadTasks.empty()) { + ++mRunOutOfMTTasksCounter; + + // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it. + // Otherwise we could perhaps just do this after we exit the locked block, + // by pushing the lock down into this method. Though it's not clear that + // we could check mMainThreadTasks.size() once we unlock, and whether we + // could maybe substitute mMayHaveMainThreadTask for that check. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RanOutOfTasks(unlock); + } + } + + return taskRan; +} + +bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock) { + mGraphMutex.AssertCurrentThreadOwns(); + + nsCOMPtr<nsIThread> mainIThread; + NS_GetMainThread(getter_AddRefs(mainIThread)); + + nsThread* mainThread = static_cast<nsThread*>(mainIThread.get()); + if (mainThread) { + mainThread->SetRunningEventDelay(TimeDuration(), TimeStamp()); + } + + uint32_t totalSuspended = 0; + for (TaskManager* manager : mTaskManagers) { + bool modifierChanged = + manager + ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + aProofOfLock, TaskManager::IterationType::EVENT_LOOP_TURN); + if (modifierChanged) { + ProcessUpdatedPriorityModifier(manager); + } + if (manager->mCurrentSuspended) { + totalSuspended += manager->mTaskCount; + } + } + + MOZ_ASSERT(mMainThreadTasks.size() >= totalSuspended); + + // This would break down if we have a non-suspended task depending on a + // suspended task. This is why for the moment we do not allow tasks + // to be dependent on tasks managed by another taskmanager. + if (mMainThreadTasks.size() > totalSuspended) { + for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end(); + iter++) { + Task* task = iter->get(); + + if (task->mTaskManager && task->mTaskManager->mCurrentSuspended) { + // Even though we may want to run some dependencies of this task, we + // will run them at their own priority level and not the priority + // level of their dependents. + continue; + } + + task = GetFinalDependency(task); + + if (task->GetKind() == Task::Kind::OffMainThreadOnly || + task->mInProgress || + (task->mTaskManager && task->mTaskManager->mCurrentSuspended)) { + continue; + } + + mCurrentTasksMT.push(task); + mMainThreadTasks.erase(task->mIterator); + task->mIterator = mMainThreadTasks.end(); + task->mInProgress = true; + TaskManager* manager = task->GetManager(); + bool result = false; + + { + MutexAutoUnlock unlock(mGraphMutex); + if (manager) { + manager->WillRunTask(); + if (manager != mIdleTaskManager) { + // Notify the idle period state that we're running a non-idle task. + // This needs to happen while our mutex is not locked! + mIdleTaskManager->State().FlagNotIdle(); + } else { + TimeStamp idleDeadline = + mIdleTaskManager->State().GetCachedIdleDeadline(); + MOZ_ASSERT( + idleDeadline, + "How can we not have a deadline if our manager is enabled?"); + task->SetIdleDeadline(idleDeadline); + } + } + if (mIdleTaskManager) { + // We found a task to run; we can clear the idle deadline on our idle + // task manager. This _must_ be done before we actually run the task, + // because running the task could reenter via spinning the event loop + // and we want to make sure there's no cached idle deadline at that + // point. But we have to make sure we do it after out SetIdleDeadline + // call above, in the case when the task is actually an idle task. + mIdleTaskManager->State().ClearCachedIdleDeadline(); + } + + TimeStamp now = TimeStamp::Now(); + + if (mainThread) { + if (task->GetPriority() < uint32_t(EventQueuePriority::InputHigh) || + task->mInsertionTime.IsNull()) { + mainThread->SetRunningEventDelay(TimeDuration(), now); + } else { + mainThread->SetRunningEventDelay(now - task->mInsertionTime, now); + } + } + + nsAutoCString name; +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + task->GetName(name); +#endif + + PerformanceCounterState::Snapshot snapshot = + mPerformanceCounterState->RunnableWillRun( + now, manager == mIdleTaskManager); + + { + LogTask::Run log(task); +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + AutoSetMainThreadRunnableName nameGuard(name); +#endif + AUTO_PROFILE_FOLLOWING_TASK(task); + result = task->Run() == Task::TaskResult::Complete; + } + + // Task itself should keep manager alive. + if (manager) { + manager->DidRunTask(); + } + + mPerformanceCounterState->RunnableDidRun(name, std::move(snapshot)); + } + + // Task itself should keep manager alive. + if (manager && result && manager->mTaskCount == 0) { + mTaskManagers.erase(manager); + } + + task->mInProgress = false; + + if (!result) { + // Presumably this task was interrupted, leave its dependencies + // unresolved and reinsert into the queue. + auto insertion = + mMainThreadTasks.insert(std::move(mCurrentTasksMT.top())); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + manager->WillRunTask(); + } else { + task->mCompleted = true; +#ifdef DEBUG + task->mIsInGraph = false; +#endif + // Clear dependencies to release references. + task->mDependencies.clear(); + + if (!mThreadableTasks.empty()) { + // We're going to wake up a single thread in our pool. This thread + // is responsible for waking up additional threads in the situation + // where more than one task became available. + mThreadPoolCV.Notify(); + } + } + + mCurrentTasksMT.pop(); + return true; + } + } + + mMayHaveMainThreadTask = false; + if (mIdleTaskManager) { + // We did not find a task to run. We still need to clear the cached idle + // deadline on our idle state, because that deadline was only relevant to + // the execution of this function. Had we found a task, we would have + // cleared the deadline before running that task. + mIdleTaskManager->State().ClearCachedIdleDeadline(); + } + return false; +} + +Task* TaskController::GetFinalDependency(Task* aTask) { + Task* nextTask; + + while ((nextTask = aTask->GetHighestPriorityDependency())) { + aTask = nextTask; + } + + return aTask; +} + +void TaskController::MaybeInterruptTask(Task* aTask) { + mGraphMutex.AssertCurrentThreadOwns(); + + if (!aTask) { + return; + } + + // This optimization prevents many slow lookups in long chains of similar + // priority. + if (!aTask->mDependencies.empty()) { + Task* firstDependency = aTask->mDependencies.begin()->get(); + if (aTask->GetPriority() <= firstDependency->GetPriority() && + !firstDependency->mCompleted && + aTask->GetKind() == firstDependency->GetKind()) { + // This task has the same or a higher priority as one of its dependencies, + // never any need to interrupt. + return; + } + } + + Task* finalDependency = GetFinalDependency(aTask); + + if (finalDependency->mInProgress) { + // No need to wake anything, we can't schedule this task right now anyway. + return; + } + + if (aTask->GetKind() == Task::Kind::MainThreadOnly) { + mMayHaveMainThreadTask = true; + + EnsureMainThreadTasksScheduled(); + + if (mCurrentTasksMT.empty()) { + return; + } + + // We could go through the steps above here and interrupt an off main + // thread task in case it has a lower priority. + if (finalDependency->GetKind() == Task::Kind::OffMainThreadOnly) { + return; + } + + if (mCurrentTasksMT.top()->GetPriority() < aTask->GetPriority()) { + mCurrentTasksMT.top()->RequestInterrupt(aTask->GetPriority()); + } + } else { + Task* lowestPriorityTask = nullptr; + for (PoolThread& thread : mPoolThreads) { + if (!thread.mCurrentTask) { + mThreadPoolCV.Notify(); + // There's a free thread, no need to interrupt anything. + return; + } + + if (!lowestPriorityTask) { + lowestPriorityTask = thread.mCurrentTask.get(); + continue; + } + + // This should possibly select the lowest priority task which was started + // the latest. But for now we ignore that optimization. + // This also doesn't guarantee a task is interruptable, so that's an + // avenue for improvements as well. + if (lowestPriorityTask->GetPriority() > thread.mEffectiveTaskPriority) { + lowestPriorityTask = thread.mCurrentTask.get(); + } + } + + if (lowestPriorityTask->GetPriority() < aTask->GetPriority()) { + lowestPriorityTask->RequestInterrupt(aTask->GetPriority()); + } + + // We choose not to interrupt main thread tasks for tasks which may be + // executed off the main thread. + } +} + +Task* TaskController::GetHighestPriorityMTTask() { + mGraphMutex.AssertCurrentThreadOwns(); + + if (!mMainThreadTasks.empty()) { + return mMainThreadTasks.begin()->get(); + } + return nullptr; +} + +void TaskController::EnsureMainThreadTasksScheduled() { + if (mObserver) { + mObserver->OnDispatchedEvent(); + } + if (mExternalCondVar) { + mExternalCondVar->Notify(); + } + mMainThreadCV.Notify(); +} + +void TaskController::ProcessUpdatedPriorityModifier(TaskManager* aManager) { + mGraphMutex.AssertCurrentThreadOwns(); + + MOZ_ASSERT(NS_IsMainThread()); + + int32_t modifier = aManager->mCurrentPriorityModifier; + + std::vector<RefPtr<Task>> storedTasks; + // Find all relevant tasks. + for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();) { + if ((*iter)->mTaskManager == aManager) { + storedTasks.push_back(*iter); + iter = mMainThreadTasks.erase(iter); + } else { + iter++; + } + } + + // Reinsert found tasks with their new priorities. + for (RefPtr<Task>& ref : storedTasks) { + // Kept alive at first by the vector and then by mMainThreadTasks. + Task* task = ref; + task->mPriorityModifier = modifier; + auto insertion = mMainThreadTasks.insert(std::move(ref)); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + } +} + +} // namespace mozilla diff --git a/xpcom/threads/TaskController.h b/xpcom/threads/TaskController.h new file mode 100644 index 0000000000..f51e61f4cd --- /dev/null +++ b/xpcom/threads/TaskController.h @@ -0,0 +1,459 @@ +/* -*- 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_TaskController_h +#define mozilla_TaskController_h + +#include "MainThreadUtils.h" +#include "mozilla/CondVar.h" +#include "mozilla/IdlePeriodState.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/EventQueue.h" +#include "nsISupportsImpl.h" + +#include <atomic> +#include <vector> +#include <set> +#include <stack> + +class nsIRunnable; +class nsIThreadObserver; + +namespace mozilla { + +class Task; +class TaskController; +class PerformanceCounter; +class PerformanceCounterState; + +const EventQueuePriority kDefaultPriorityValue = EventQueuePriority::Normal; + +// This file contains the core classes to access the Gecko scheduler. The +// scheduler forms a graph of prioritize tasks, and is responsible for ensuring +// the execution of tasks or their dependencies in order of inherited priority. +// +// The core class is the 'Task' class. The task class describes a single unit of +// work. Users scheduling work implement this class and are required to +// reimplement the 'Run' function in order to do work. +// +// The TaskManager class is reimplemented by users that require +// the ability to reprioritize or suspend tasks. +// +// The TaskController is responsible for scheduling the work itself. The AddTask +// function is used to schedule work. The ReprioritizeTask function may be used +// to change the priority of a task already in the task graph, without +// unscheduling it. + +// The TaskManager is the baseclass used to atomically manage a large set of +// tasks. API users reimplementing TaskManager may reimplement a number of +// functions that they may use to indicate to the scheduler changes in the state +// for any tasks they manage. They may be used to reprioritize or suspend tasks +// under their control, and will also be notified before and after tasks under +// their control are executed. Their methods will only be called once per event +// loop turn, however they may still incur some performance overhead. In +// addition to this frequent reprioritizations may incur a significant +// performance overhead and are discouraged. A TaskManager may currently only be +// used to manage tasks that are bound to the Gecko Main Thread. +class TaskManager { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TaskManager) + + TaskManager() : mTaskCount(0) {} + + // Subclasses implementing task manager will have this function called to + // determine whether their associated tasks are currently suspended. This + // will only be called once per iteration of the task queue, this means that + // suspension of tasks managed by a single TaskManager may be assumed to + // occur atomically. + virtual bool IsSuspended(const MutexAutoLock& aProofOfLock) { return false; } + + // Subclasses may implement this in order to supply a priority adjustment + // to their managed tasks. This is called once per iteration of the task + // queue, and may be assumed to occur atomically for all managed tasks. + virtual int32_t GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) { + return 0; + } + + void DidQueueTask() { ++mTaskCount; } + // This is called when a managed task is about to be executed by the + // scheduler. Anyone reimplementing this should ensure to call the parent or + // decrement mTaskCount. + virtual void WillRunTask() { --mTaskCount; } + // This is called when a managed task has finished being executed by the + // scheduler. + virtual void DidRunTask() {} + uint32_t PendingTaskCount() { return mTaskCount; } + + protected: + virtual ~TaskManager() {} + + private: + friend class TaskController; + + enum class IterationType { NOT_EVENT_LOOP_TURN, EVENT_LOOP_TURN }; + bool UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + const MutexAutoLock& aProofOfLock, IterationType aIterationType); + + bool mCurrentSuspended = false; + int32_t mCurrentPriorityModifier = 0; + + std::atomic<uint32_t> mTaskCount; +}; + +// A Task is the the base class for any unit of work that may be scheduled. +// +// Subclasses may specify their priority and whether they should be bound to +// either the Gecko Main thread or off main thread. When not bound to the main +// thread tasks may be executed on any available thread excluding the main +// thread, but they may also be executed in parallel to any other task they do +// not have a dependency relationship with. +// +// Tasks will be run in order of object creation. +class Task { + public: + enum class Kind : uint8_t { + // This task should be executed on any available thread excluding the Gecko + // Main thread. + OffMainThreadOnly, + + // This task should be executed on the Gecko Main thread. + MainThreadOnly + + // NOTE: "any available thread including the main thread" option is not + // supported (See bug 1839102). + }; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Task) + + Kind GetKind() { return mKind; } + + // This returns the current task priority with its modifier applied. + uint32_t GetPriority() { return mPriority + mPriorityModifier; } + uint64_t GetSeqNo() { return mSeqNo; } + + // Callee needs to assume this may be called on any thread. + // aInterruptPriority passes the priority of the higher priority task that + // is ready to be executed. The task may safely ignore this function, or + // interrupt any work being done. It may return 'false' from its run function + // in order to be run automatically in the future, or true if it will + // reschedule incomplete work manually. + virtual void RequestInterrupt(uint32_t aInterruptPriority) {} + + // At the moment this -must- be called before the task is added to the + // controller. Calling this after tasks have been added to the controller + // results in undefined behavior! + // At submission, tasks must depend only on tasks managed by the same, or + // no idle manager. + void AddDependency(Task* aTask) { + MOZ_ASSERT(aTask); + MOZ_ASSERT(!mIsInGraph); + mDependencies.insert(aTask); + } + + // This sets the TaskManager for the current task. Calling this after the + // task has been added to the TaskController results in undefined behavior. + void SetManager(TaskManager* aManager) { + MOZ_ASSERT(mKind == Kind::MainThreadOnly); + MOZ_ASSERT(!mIsInGraph); + mTaskManager = aManager; + } + TaskManager* GetManager() { return mTaskManager; } + + struct PriorityCompare { + bool operator()(const RefPtr<Task>& aTaskA, + const RefPtr<Task>& aTaskB) const { + uint32_t prioA = aTaskA->GetPriority(); + uint32_t prioB = aTaskB->GetPriority(); + return (prioA > prioB) || + (prioA == prioB && (aTaskA->GetSeqNo() < aTaskB->GetSeqNo())); + } + }; + + // Tell the task about its idle deadline. Will only be called for + // tasks managed by an IdleTaskManager, right before the task runs. + virtual void SetIdleDeadline(TimeStamp aDeadline) {} + + virtual PerformanceCounter* GetPerformanceCounter() const { return nullptr; } + + // Get a name for this task. This returns false if the task has no name. +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + virtual bool GetName(nsACString& aName) = 0; +#else + virtual bool GetName(nsACString& aName) { return false; } +#endif + + protected: + Task(Kind aKind, + uint32_t aPriority = static_cast<uint32_t>(kDefaultPriorityValue)) + : mKind(aKind), mSeqNo(sCurrentTaskSeqNo++), mPriority(aPriority) {} + + Task(Kind aKind, EventQueuePriority aPriority = kDefaultPriorityValue) + : mKind(aKind), + mSeqNo(sCurrentTaskSeqNo++), + mPriority(static_cast<uint32_t>(aPriority)) {} + + virtual ~Task() {} + + friend class TaskController; + + enum class TaskResult { + Complete, + Incomplete, + }; + + // When this returns TaskResult::Incomplete, it will be rescheduled at the + // current 'mPriority' level. + virtual TaskResult Run() = 0; + + private: + Task* GetHighestPriorityDependency(); + + // Iterator pointing to this task's position in + // mThreadableTasks/mMainThreadTasks if, and only if this task is currently + // scheduled to be executed. This allows fast access to the task's position + // in the set, allowing for fast removal. + // This is safe, and remains valid unless the task is removed from the set. + // See also iterator invalidation in: + // https://en.cppreference.com/w/cpp/container + // + // Or the spec: + // "All Associative Containers: The insert and emplace members shall not + // affect the validity of iterators and references to the container + // [26.2.6/9]" "All Associative Containers: The erase members shall invalidate + // only iterators and references to the erased elements [26.2.6/9]" + std::set<RefPtr<Task>, PriorityCompare>::iterator mIterator; + std::set<RefPtr<Task>, PriorityCompare> mDependencies; + + RefPtr<TaskManager> mTaskManager; + + // Access to these variables is protected by the GraphMutex. + Kind mKind; + bool mCompleted = false; + bool mInProgress = false; +#ifdef DEBUG + bool mIsInGraph = false; +#endif + + static std::atomic<uint64_t> sCurrentTaskSeqNo; + int64_t mSeqNo; + uint32_t mPriority; + // Modifier currently being applied to this task by its taskmanager. + int32_t mPriorityModifier = 0; + // Time this task was inserted into the task graph, this is used by the + // profiler. + mozilla::TimeStamp mInsertionTime; +}; + +struct PoolThread { + PRThread* mThread; + RefPtr<Task> mCurrentTask; + // This may be higher than mCurrentTask's priority due to priority + // propagation. This is -only- valid when mCurrentTask != nullptr. + uint32_t mEffectiveTaskPriority; +}; + +// A task manager implementation for priority levels that should only +// run during idle periods. +class IdleTaskManager : public TaskManager { + public: + explicit IdleTaskManager(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod) + : mIdlePeriodState(std::move(aIdlePeriod)), mProcessedTaskCount(0) {} + + IdlePeriodState& State() { return mIdlePeriodState; } + + bool IsSuspended(const MutexAutoLock& aProofOfLock) override { + TimeStamp idleDeadline = State().GetCachedIdleDeadline(); + return !idleDeadline; + } + + void DidRunTask() override { + TaskManager::DidRunTask(); + ++mProcessedTaskCount; + } + + uint64_t ProcessedTaskCount() { return mProcessedTaskCount; } + + private: + // Tracking of our idle state of various sorts. + IdlePeriodState mIdlePeriodState; + + std::atomic<uint64_t> mProcessedTaskCount; +}; + +// The TaskController is the core class of the scheduler. It is used to +// schedule tasks to be executed, as well as to reprioritize tasks that have +// already been scheduled. The core functions to do this are AddTask and +// ReprioritizeTask. +class TaskController { + public: + TaskController(); + + static TaskController* Get() { + MOZ_ASSERT(sSingleton.get()); + return sSingleton.get(); + } + + static void Initialize(); + + void SetThreadObserver(nsIThreadObserver* aObserver) { + MutexAutoLock lock(mGraphMutex); + mObserver = aObserver; + } + void SetConditionVariable(CondVar* aExternalCondVar) { + MutexAutoLock lock(mGraphMutex); + mExternalCondVar = aExternalCondVar; + } + + void SetIdleTaskManager(IdleTaskManager* aIdleTaskManager) { + mIdleTaskManager = aIdleTaskManager; + } + IdleTaskManager* GetIdleTaskManager() { return mIdleTaskManager.get(); } + + uint64_t RunOutOfMTTasksCount() { return mRunOutOfMTTasksCounter; } + + // Initialization and shutdown code. + void SetPerformanceCounterState( + PerformanceCounterState* aPerformanceCounterState); + + static void Shutdown(); + + // This adds a task to the TaskController graph. + // This may be called on any thread. + void AddTask(already_AddRefed<Task>&& aTask); + + // This wait function is the theoretical function you would need if our main + // thread needs to also process OS messages or something along those lines. + void WaitForTaskOrMessage(); + + // This gets the next (highest priority) task that is only allowed to execute + // on the main thread. + void ExecuteNextTaskOnlyMainThread(); + + // Process all pending main thread tasks. + void ProcessPendingMTTask(bool aMayWait = false); + + // This allows reprioritization of a task already in the task graph. + // This may be called on any thread. + void ReprioritizeTask(Task* aTask, uint32_t aPriority); + + void DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aPriority, TaskManager* aManager = nullptr); + + nsIRunnable* GetRunnableForMTTask(bool aReallyWait); + + bool HasMainThreadPendingTasks(); + + uint64_t PendingMainthreadTaskCountIncludingSuspended(); + + // Let users know whether the last main thread task runnable did work. + bool MTTaskRunnableProcessedTask() { + MOZ_ASSERT(NS_IsMainThread()); + return mMTTaskRunnableProcessedTask; + } + + static int32_t GetPoolThreadCount(); + static size_t GetThreadStackSize(); + + private: + friend void ThreadFuncPoolThread(void* aIndex); + static StaticAutoPtr<TaskController> sSingleton; + + void InitializeThreadPool(); + + // This gets the next (highest priority) task that is only allowed to execute + // on the main thread, if any, and executes it. + // Returns true if it succeeded. + bool ExecuteNextTaskOnlyMainThreadInternal(const MutexAutoLock& aProofOfLock); + + // The guts of ExecuteNextTaskOnlyMainThreadInternal, which get idle handling + // wrapped around them. Returns whether a task actually ran. + bool DoExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock); + + Task* GetFinalDependency(Task* aTask); + void MaybeInterruptTask(Task* aTask); + Task* GetHighestPriorityMTTask(); + + void EnsureMainThreadTasksScheduled(); + + void ProcessUpdatedPriorityModifier(TaskManager* aManager); + + void ShutdownThreadPoolInternal(); + + void RunPoolThread(); + + // This protects access to the task graph. + Mutex mGraphMutex MOZ_UNANNOTATED; + + // This protects thread pool initialization. We cannot do this from within + // the GraphMutex, since thread creation on Windows can generate events on + // the main thread that need to be handled. + Mutex mPoolInitializationMutex = + Mutex("TaskController::mPoolInitializationMutex"); + // Created under the PoolInitialization mutex, then never extended, and + // only freed when the object is freed. mThread is set at creation time; + // mCurrentTask and mEffectiveTaskPriority are only accessed from the + // thread, so no locking is needed to access this. + std::vector<PoolThread> mPoolThreads; + + CondVar mThreadPoolCV; + CondVar mMainThreadCV; + + // Variables below are protected by mGraphMutex. + + std::stack<RefPtr<Task>> mCurrentTasksMT; + + // A list of all tasks ordered by priority. + std::set<RefPtr<Task>, Task::PriorityCompare> mThreadableTasks; + std::set<RefPtr<Task>, Task::PriorityCompare> mMainThreadTasks; + + // TaskManagers currently active. + // We can use a raw pointer since tasks always hold on to their TaskManager. + std::set<TaskManager*> mTaskManagers; + + // This ensures we keep running the main thread if we processed a task there. + bool mMayHaveMainThreadTask = true; + bool mShuttingDown = false; + + // This stores whether the last main thread task runnable did work. + // Accessed only on MainThread + bool mMTTaskRunnableProcessedTask = false; + + // Whether our thread pool is initialized. We use this currently to avoid + // starting the threads in processes where it's never used. This is protected + // by mPoolInitializationMutex. + bool mThreadPoolInitialized = false; + + // Whether we have scheduled a runnable on the main thread event loop. + // This is used for nsIRunnable compatibility. + RefPtr<nsIRunnable> mMTProcessingRunnable; + RefPtr<nsIRunnable> mMTBlockingProcessingRunnable; + + // XXX - Thread observer to notify when a new event has been dispatched + // Set immediately, then simply accessed from any thread + nsIThreadObserver* mObserver = nullptr; + // XXX - External condvar to notify when we have received an event + CondVar* mExternalCondVar = nullptr; + // Idle task manager so we can properly do idle state stuff. + RefPtr<IdleTaskManager> mIdleTaskManager; + + // How many times the main thread was empty. + std::atomic<uint64_t> mRunOutOfMTTasksCounter; + + // Our tracking of our performance counter and long task state, + // shared with nsThread. + // Set once when MainThread is created, never changed, only accessed from + // DoExecuteNextTaskOnlyMainThreadInternal() + PerformanceCounterState* mPerformanceCounterState = nullptr; +}; + +} // namespace mozilla + +#endif // mozilla_TaskController_h diff --git a/xpcom/threads/TaskDispatcher.h b/xpcom/threads/TaskDispatcher.h new file mode 100644 index 0000000000..1f27c32c7d --- /dev/null +++ b/xpcom/threads/TaskDispatcher.h @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(TaskDispatcher_h_) +# define TaskDispatcher_h_ + +# include <queue> + +# include "mozilla/AbstractThread.h" +# include "mozilla/Maybe.h" +# include "mozilla/ProfilerRunnable.h" +# include "mozilla/UniquePtr.h" +# include "nsIDirectTaskDispatcher.h" +# include "nsISupportsImpl.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +namespace mozilla { + +class SimpleTaskQueue { + public: + SimpleTaskQueue() = default; + virtual ~SimpleTaskQueue() = default; + + void AddTask(already_AddRefed<nsIRunnable> aRunnable) { + if (!mTasks) { + mTasks.emplace(); + } + mTasks->push(std::move(aRunnable)); + } + + void DrainTasks() { + if (!mTasks) { + return; + } + auto& queue = mTasks.ref(); + while (!queue.empty()) { + nsCOMPtr<nsIRunnable> r = std::move(queue.front()); + queue.pop(); + AUTO_PROFILE_FOLLOWING_RUNNABLE(r); + r->Run(); + } + } + + bool HaveTasks() const { return mTasks && !mTasks->empty(); } + + private: + // We use a Maybe<> because (a) when used for DirectTasks it often doesn't get + // anything put into it, and (b) the std::queue implementation in GNU + // libstdc++ does two largish heap allocations when creating a new std::queue. + Maybe<std::queue<nsCOMPtr<nsIRunnable>>> mTasks; +}; + +/* + * A classic approach to cross-thread communication is to dispatch asynchronous + * runnables to perform updates on other threads. This generally works well, but + * there are sometimes reasons why we might want to delay the actual dispatch of + * these tasks until a specified moment. At present, this is primarily useful to + * ensure that mirrored state gets updated atomically - but there may be other + * applications as well. + * + * TaskDispatcher is a general abstract class that accepts tasks and dispatches + * them at some later point. These groups of tasks are per-target-thread, and + * contain separate queues for several kinds of tasks (see comments below). - + * "state change tasks" (which run first, and are intended to be used to update + * the value held by mirrors), and regular tasks, which are other arbitrary + * operations that the are gated to run after all the state changes have + * completed. + */ +class TaskDispatcher { + public: + TaskDispatcher() = default; + virtual ~TaskDispatcher() = default; + + // Direct tasks are run directly (rather than dispatched asynchronously) when + // the tail dispatcher fires. A direct task may cause other tasks to be added + // to the tail dispatcher. + virtual void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) = 0; + + // State change tasks are dispatched asynchronously always run before regular + // tasks. They are intended to be used to update the value held by mirrors + // before any other dispatched tasks are run on the target thread. + virtual void AddStateChangeTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) = 0; + + // Regular tasks are dispatched asynchronously, and run after state change + // tasks. + virtual nsresult AddTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) = 0; + + virtual nsresult DispatchTasksFor(AbstractThread* aThread) = 0; + virtual bool HasTasksFor(AbstractThread* aThread) = 0; + virtual void DrainDirectTasks() = 0; +}; + +/* + * AutoTaskDispatcher is a stack-scoped TaskDispatcher implementation that fires + * its queued tasks when it is popped off the stack. + */ +class AutoTaskDispatcher : public TaskDispatcher { + public: + explicit AutoTaskDispatcher(nsIDirectTaskDispatcher* aDirectTaskDispatcher, + bool aIsTailDispatcher = false) + : mDirectTaskDispatcher(aDirectTaskDispatcher), + mIsTailDispatcher(aIsTailDispatcher) {} + + ~AutoTaskDispatcher() { + // Given that direct tasks may trigger other code that uses the tail + // dispatcher, it's better to avoid processing them in the tail dispatcher's + // destructor. So we require TailDispatchers to manually invoke + // DrainDirectTasks before the AutoTaskDispatcher gets destroyed. In truth, + // this is only necessary in the case where this AutoTaskDispatcher can be + // accessed by the direct tasks it dispatches (true for TailDispatchers, but + // potentially not true for other hypothetical AutoTaskDispatchers). Feel + // free to loosen this restriction to apply only to mIsTailDispatcher if a + // use-case requires it. + MOZ_ASSERT(!HaveDirectTasks()); + + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + DispatchTaskGroup(std::move(mTaskGroups[i])); + } + } + + bool HaveDirectTasks() { + return mDirectTaskDispatcher && mDirectTaskDispatcher->HaveDirectTasks(); + } + + void DrainDirectTasks() override { + if (mDirectTaskDispatcher) { + mDirectTaskDispatcher->DrainDirectTasks(); + } + } + + void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) override { + MOZ_ASSERT(mDirectTaskDispatcher); + mDirectTaskDispatcher->DispatchDirectTask(std::move(aRunnable)); + } + + void AddStateChangeTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) override { + nsCOMPtr<nsIRunnable> r = aRunnable; + MOZ_RELEASE_ASSERT(r); + EnsureTaskGroup(aThread).mStateChangeTasks.AppendElement(r.forget()); + } + + nsresult AddTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) override { + nsCOMPtr<nsIRunnable> r = aRunnable; + MOZ_RELEASE_ASSERT(r); + // To preserve the event order, we need to append a new group if the last + // group is not targeted for |aThread|. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318226&mark=0-3#c0 + // for the details of the issue. + if (mTaskGroups.Length() == 0 || + mTaskGroups.LastElement()->mThread != aThread) { + mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread)); + } + + PerThreadTaskGroup& group = *mTaskGroups.LastElement(); + group.mRegularTasks.AppendElement(r.forget()); + + return NS_OK; + } + + bool HasTasksFor(AbstractThread* aThread) override { + return !!GetTaskGroup(aThread) || + (aThread == AbstractThread::GetCurrent() && HaveDirectTasks()); + } + + nsresult DispatchTasksFor(AbstractThread* aThread) override { + nsresult rv = NS_OK; + + // Dispatch all groups that match |aThread|. + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + if (mTaskGroups[i]->mThread == aThread) { + nsresult rv2 = DispatchTaskGroup(std::move(mTaskGroups[i])); + + if (NS_WARN_IF(NS_FAILED(rv2)) && NS_SUCCEEDED(rv)) { + // We should try our best to call DispatchTaskGroup() as much as + // possible and return an error if any of DispatchTaskGroup() calls + // failed. + rv = rv2; + } + + mTaskGroups.RemoveElementAt(i--); + } + } + + return rv; + } + + private: + struct PerThreadTaskGroup { + public: + explicit PerThreadTaskGroup(AbstractThread* aThread) : mThread(aThread) { + MOZ_COUNT_CTOR(PerThreadTaskGroup); + } + + MOZ_COUNTED_DTOR(PerThreadTaskGroup) + + RefPtr<AbstractThread> mThread; + nsTArray<nsCOMPtr<nsIRunnable>> mStateChangeTasks; + nsTArray<nsCOMPtr<nsIRunnable>> mRegularTasks; + }; + + class TaskGroupRunnable : public Runnable { + public: + explicit TaskGroupRunnable(UniquePtr<PerThreadTaskGroup>&& aTasks) + : Runnable("AutoTaskDispatcher::TaskGroupRunnable"), + mTasks(std::move(aTasks)) {} + + NS_IMETHOD Run() override { + // State change tasks get run all together before any code is run, so + // that all state changes are made in an atomic unit. + for (size_t i = 0; i < mTasks->mStateChangeTasks.Length(); ++i) { + mTasks->mStateChangeTasks[i]->Run(); + } + + // Once the state changes have completed, drain any direct tasks + // generated by those state changes (i.e. watcher notification tasks). + // This needs to be outside the loop because we don't want to run code + // that might observe intermediate states. + MaybeDrainDirectTasks(); + + for (size_t i = 0; i < mTasks->mRegularTasks.Length(); ++i) { + AUTO_PROFILE_FOLLOWING_RUNNABLE(mTasks->mRegularTasks[i]); + mTasks->mRegularTasks[i]->Run(); + + // Scope direct tasks tightly to the task that generated them. + MaybeDrainDirectTasks(); + } + + return NS_OK; + } + + private: + void MaybeDrainDirectTasks() { + AbstractThread* currentThread = AbstractThread::GetCurrent(); + if (currentThread && currentThread->MightHaveTailTasks()) { + currentThread->TailDispatcher().DrainDirectTasks(); + } + } + + UniquePtr<PerThreadTaskGroup> mTasks; + }; + + PerThreadTaskGroup& EnsureTaskGroup(AbstractThread* aThread) { + PerThreadTaskGroup* existing = GetTaskGroup(aThread); + if (existing) { + return *existing; + } + + mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread)); + return *mTaskGroups.LastElement(); + } + + PerThreadTaskGroup* GetTaskGroup(AbstractThread* aThread) { + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + if (mTaskGroups[i]->mThread == aThread) { + return mTaskGroups[i].get(); + } + } + + // Not found. + return nullptr; + } + + nsresult DispatchTaskGroup(UniquePtr<PerThreadTaskGroup> aGroup) { + RefPtr<AbstractThread> thread = aGroup->mThread; + + AbstractThread::DispatchReason reason = + mIsTailDispatcher ? AbstractThread::TailDispatch + : AbstractThread::NormalDispatch; + nsCOMPtr<nsIRunnable> r = new TaskGroupRunnable(std::move(aGroup)); + return thread->Dispatch(r.forget(), reason); + } + + // Task groups, organized by thread. + nsTArray<UniquePtr<PerThreadTaskGroup>> mTaskGroups; + + nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher; + // True if this TaskDispatcher represents the tail dispatcher for the thread + // upon which it runs. + const bool mIsTailDispatcher; +}; + +// Little utility class to allow declaring AutoTaskDispatcher as a default +// parameter for methods that take a TaskDispatcher&. +template <typename T> +class PassByRef { + public: + PassByRef() = default; + operator T&() { return mVal; } + + private: + T mVal; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/TaskQueue.cpp b/xpcom/threads/TaskQueue.cpp new file mode 100644 index 0000000000..febb609784 --- /dev/null +++ b/xpcom/threads/TaskQueue.cpp @@ -0,0 +1,345 @@ +/* -*- 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/TaskQueue.h" + +#include "mozilla/ProfilerRunnable.h" +#include "nsIEventTarget.h" +#include "nsITargetShutdownTask.h" +#include "nsThreadUtils.h" +#include "nsQueryObject.h" + +namespace mozilla { + +// Handle for a TaskQueue being tracked by a TaskQueueTracker. When created, +// it is registered with the TaskQueueTracker, and when destroyed it is +// unregistered. Holds a threadsafe weak reference to the TaskQueue. +class TaskQueueTrackerEntry final + : private LinkedListElement<TaskQueueTrackerEntry> { + public: + TaskQueueTrackerEntry(TaskQueueTracker* aTracker, + const RefPtr<TaskQueue>& aQueue) + : mTracker(aTracker), mQueue(aQueue) { + MutexAutoLock lock(mTracker->mMutex); + mTracker->mEntries.insertFront(this); + } + ~TaskQueueTrackerEntry() { + MutexAutoLock lock(mTracker->mMutex); + removeFrom(mTracker->mEntries); + } + + TaskQueueTrackerEntry(const TaskQueueTrackerEntry&) = delete; + TaskQueueTrackerEntry(TaskQueueTrackerEntry&&) = delete; + TaskQueueTrackerEntry& operator=(const TaskQueueTrackerEntry&) = delete; + TaskQueueTrackerEntry& operator=(TaskQueueTrackerEntry&&) = delete; + + RefPtr<TaskQueue> GetQueue() const { return RefPtr<TaskQueue>(mQueue); } + + private: + friend class LinkedList<TaskQueueTrackerEntry>; + friend class LinkedListElement<TaskQueueTrackerEntry>; + + const RefPtr<TaskQueueTracker> mTracker; + const ThreadSafeWeakPtr<TaskQueue> mQueue; +}; + +RefPtr<TaskQueue> TaskQueue::Create(already_AddRefed<nsIEventTarget> aTarget, + const char* aName, + bool aSupportsTailDispatch) { + nsCOMPtr<nsIEventTarget> target(std::move(aTarget)); + RefPtr<TaskQueue> queue = + new TaskQueue(do_AddRef(target), aName, aSupportsTailDispatch); + + // If |target| is a TaskQueueTracker, register this TaskQueue with it. It will + // be unregistered when the TaskQueue is destroyed or shut down. + if (RefPtr<TaskQueueTracker> tracker = do_QueryObject(target)) { + MonitorAutoLock lock(queue->mQueueMonitor); + queue->mTrackerEntry = MakeUnique<TaskQueueTrackerEntry>(tracker, queue); + } + + return queue; +} + +TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget, + const char* aName, bool aSupportsTailDispatch) + : AbstractThread(aSupportsTailDispatch), + mTarget(aTarget), + mQueueMonitor("TaskQueue::Queue"), + mTailDispatcher(nullptr), + mIsRunning(false), + mIsShutdown(false), + mName(aName) {} + +TaskQueue::~TaskQueue() { + // We should never free the TaskQueue if it was destroyed abnormally, meaning + // that all cleanup tasks should be complete if we do. + MOZ_ASSERT(mShutdownTasks.IsEmpty()); +} + +NS_IMPL_ADDREF_INHERITED(TaskQueue, SupportsThreadSafeWeakPtr<TaskQueue>) +NS_IMPL_RELEASE_INHERITED(TaskQueue, SupportsThreadSafeWeakPtr<TaskQueue>) +NS_IMPL_QUERY_INTERFACE(TaskQueue, nsIDirectTaskDispatcher, + nsISerialEventTarget, nsIEventTarget) + +TaskDispatcher& TaskQueue::TailDispatcher() { + MOZ_ASSERT(IsCurrentThreadIn()); + MOZ_ASSERT(mTailDispatcher); + return *mTailDispatcher; +} + +// Note aRunnable is passed by ref to support conditional ownership transfer. +// See Dispatch() in TaskQueue.h for more details. +nsresult TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable, + uint32_t aFlags, DispatchReason aReason) { + mQueueMonitor.AssertCurrentThreadOwns(); + + // Continue to allow dispatches after shutdown until the last message has been + // processed, at which point no more messages will be accepted. + if (mIsShutdown && !mIsRunning) { + return NS_ERROR_UNEXPECTED; + } + + AbstractThread* currentThread; + if (aReason != TailDispatch && (currentThread = GetCurrent()) && + RequiresTailDispatch(currentThread) && + currentThread->IsTailDispatcherAvailable()) { + MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL, + "Tail dispatch doesn't support flags"); + return currentThread->TailDispatcher().AddTask(this, aRunnable.forget()); + } + + LogRunnable::LogDispatch(aRunnable); + mTasks.Push({std::move(aRunnable), aFlags}); + + if (mIsRunning) { + return NS_OK; + } + RefPtr<nsIRunnable> runner(new Runner(this)); + nsresult rv = mTarget->Dispatch(runner.forget(), aFlags); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch runnable to run TaskQueue"); + return rv; + } + mIsRunning = true; + + return NS_OK; +} + +nsresult TaskQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MonitorAutoLock mon(mQueueMonitor); + if (mIsShutdown) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(!mShutdownTasks.Contains(aTask)); + mShutdownTasks.AppendElement(aTask); + return NS_OK; +} + +nsresult TaskQueue::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MonitorAutoLock mon(mQueueMonitor); + if (mIsShutdown) { + return NS_ERROR_UNEXPECTED; + } + + return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +void TaskQueue::AwaitIdle() { + MonitorAutoLock mon(mQueueMonitor); + AwaitIdleLocked(); +} + +void TaskQueue::AwaitIdleLocked() { + // Make sure there are no tasks for this queue waiting in the caller's tail + // dispatcher. + MOZ_ASSERT_IF(AbstractThread::GetCurrent(), + !AbstractThread::GetCurrent()->HasTailTasksFor(this)); + + mQueueMonitor.AssertCurrentThreadOwns(); + MOZ_ASSERT(mIsRunning || mTasks.IsEmpty()); + while (mIsRunning) { + mQueueMonitor.Wait(); + } +} + +void TaskQueue::AwaitShutdownAndIdle() { + MOZ_ASSERT(!IsCurrentThreadIn()); + // Make sure there are no tasks for this queue waiting in the caller's tail + // dispatcher. + MOZ_ASSERT_IF(AbstractThread::GetCurrent(), + !AbstractThread::GetCurrent()->HasTailTasksFor(this)); + + MonitorAutoLock mon(mQueueMonitor); + while (!mIsShutdown) { + mQueueMonitor.Wait(); + } + AwaitIdleLocked(); +} +RefPtr<ShutdownPromise> TaskQueue::BeginShutdown() { + // Dispatch any tasks for this queue waiting in the caller's tail dispatcher, + // since this is the last opportunity to do so. + if (AbstractThread* currentThread = AbstractThread::GetCurrent()) { + currentThread->TailDispatchTasksFor(this); + } + + MonitorAutoLock mon(mQueueMonitor); + // Dispatch any cleanup tasks to the queue before we put it into full + // shutdown. + for (auto& task : mShutdownTasks) { + nsCOMPtr runnable{task->AsRunnable()}; + MOZ_ALWAYS_SUCCEEDS( + DispatchLocked(runnable, NS_DISPATCH_NORMAL, TailDispatch)); + } + mShutdownTasks.Clear(); + mIsShutdown = true; + + RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__); + MaybeResolveShutdown(); + mon.NotifyAll(); + return p; +} + +void TaskQueue::MaybeResolveShutdown() { + mQueueMonitor.AssertCurrentThreadOwns(); + if (mIsShutdown && !mIsRunning) { + mShutdownPromise.ResolveIfExists(true, __func__); + // Disconnect from our target as we won't try to dispatch any more events. + mTrackerEntry = nullptr; + mTarget = nullptr; + } +} + +bool TaskQueue::IsEmpty() { + MonitorAutoLock mon(mQueueMonitor); + return mTasks.IsEmpty(); +} + +bool TaskQueue::IsCurrentThreadIn() const { + bool in = mRunningThread == PR_GetCurrentThread(); + return in; +} + +nsresult TaskQueue::Runner::Run() { + TaskStruct event; + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + MOZ_ASSERT(mQueue->mIsRunning); + if (mQueue->mTasks.IsEmpty()) { + mQueue->mIsRunning = false; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + return NS_OK; + } + event = mQueue->mTasks.Pop(); + } + MOZ_ASSERT(event.event); + + // Note that dropping the queue monitor before running the task, and + // taking the monitor again after the task has run ensures we have memory + // fences enforced. This means that if the object we're calling wasn't + // designed to be threadsafe, it will be, provided we're only calling it + // in this task queue. + { + AutoTaskGuard g(mQueue); + SerialEventTargetGuard tg(mQueue); + { + LogRunnable::Run log(event.event); + + AUTO_PROFILE_FOLLOWING_RUNNABLE(event.event); + event.event->Run(); + + // Drop the reference to event. The event will hold a reference to the + // object it's calling, and we don't want to keep it alive, it may be + // making assumptions what holds references to it. This is especially + // the case if the object is waiting for us to shutdown, so that it + // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case). + event.event = nullptr; + } + } + + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + if (mQueue->mTasks.IsEmpty()) { + // No more events to run. Exit the task runner. + mQueue->mIsRunning = false; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + return NS_OK; + } + } + + // There's at least one more event that we can run. Dispatch this Runner + // to the target again to ensure it runs again. Note that we don't just + // run in a loop here so that we don't hog the target. This means we may + // run on another thread next time, but we rely on the memory fences from + // mQueueMonitor for thread safety of non-threadsafe tasks. + nsresult rv; + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + rv = mQueue->mTarget->Dispatch( + this, mQueue->mTasks.FirstElement().flags | NS_DISPATCH_AT_END); + } + if (NS_FAILED(rv)) { + // Failed to dispatch, shutdown! + MonitorAutoLock mon(mQueue->mQueueMonitor); + mQueue->mIsRunning = false; + mQueue->mIsShutdown = true; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +TaskQueue::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.AddTask(std::move(aEvent)); + return NS_OK; +} + +NS_IMETHODIMP TaskQueue::DrainDirectTasks() { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.DrainTasks(); + return NS_OK; +} + +NS_IMETHODIMP TaskQueue::HaveDirectTasks(bool* aValue) { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + + *aValue = mDirectTasks.HaveTasks(); + return NS_OK; +} + +nsTArray<RefPtr<TaskQueue>> TaskQueueTracker::GetAllTrackedTaskQueues() { + MutexAutoLock lock(mMutex); + nsTArray<RefPtr<TaskQueue>> queues; + for (auto* entry : mEntries) { + if (auto queue = entry->GetQueue()) { + queues.AppendElement(queue); + } + } + return queues; +} + +TaskQueueTracker::~TaskQueueTracker() = default; + +} // namespace mozilla diff --git a/xpcom/threads/TaskQueue.h b/xpcom/threads/TaskQueue.h new file mode 100644 index 0000000000..0d99d385d3 --- /dev/null +++ b/xpcom/threads/TaskQueue.h @@ -0,0 +1,276 @@ +/* -*- 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 TaskQueue_h_ +#define TaskQueue_h_ + +#include "mozilla/AbstractThread.h" +#include "mozilla/Maybe.h" +#include "mozilla/Monitor.h" +#include "mozilla/MozPromise.h" +#include "mozilla/Queue.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +typedef MozPromise<bool, bool, false> ShutdownPromise; + +class TaskQueueTrackerEntry; + +// Abstracts executing runnables in order on an arbitrary event target. The +// runnables dispatched to the TaskQueue will be executed in the order in which +// they're received, and are guaranteed to not be executed concurrently. +// They may be executed on different threads, and a memory barrier is used +// to make this threadsafe for objects that aren't already threadsafe. +// +// Note, since a TaskQueue can also be converted to an nsIEventTarget using +// WrapAsEventTarget() its possible to construct a hierarchy of TaskQueues. +// Consider these three TaskQueues: +// +// TQ1 dispatches to the main thread +// TQ2 dispatches to TQ1 +// TQ3 dispatches to TQ1 +// +// This ensures there is only ever a single runnable from the entire chain on +// the main thread. It also ensures that TQ2 and TQ3 only have a single +// runnable in TQ1 at any time. +// +// This arrangement lets you prioritize work by dispatching runnables directly +// to TQ1. You can issue many runnables for important work. Meanwhile the TQ2 +// and TQ3 work will always execute at most one runnable and then yield. +// +// A TaskQueue does not require explicit shutdown, however it provides a +// BeginShutdown() method that places TaskQueue in a shut down state and returns +// a promise that gets resolved once all pending tasks have completed +class TaskQueue final : public AbstractThread, + public nsIDirectTaskDispatcher, + public SupportsThreadSafeWeakPtr<TaskQueue> { + class EventTargetWrapper; + + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIDIRECTTASKDISPATCHER + MOZ_DECLARE_REFCOUNTED_TYPENAME(TaskQueue) + + static RefPtr<TaskQueue> Create(already_AddRefed<nsIEventTarget> aTarget, + const char* aName, + bool aSupportsTailDispatch = false); + + TaskDispatcher& TailDispatcher() override; + + NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) override { + nsCOMPtr<nsIRunnable> runnable = aEvent; + { + MonitorAutoLock mon(mQueueMonitor); + return DispatchLocked(/* passed by ref */ runnable, aFlags, + NormalDispatch); + } + // If the ownership of |r| is not transferred in DispatchLocked() due to + // dispatch failure, it will be deleted here outside the lock. We do so + // since the destructor of the runnable might access TaskQueue and result + // in deadlocks. + } + + [[nodiscard]] nsresult Dispatch( + already_AddRefed<nsIRunnable> aRunnable, + DispatchReason aReason = NormalDispatch) override { + nsCOMPtr<nsIRunnable> r = aRunnable; + { + MonitorAutoLock mon(mQueueMonitor); + return DispatchLocked(/* passed by ref */ r, NS_DISPATCH_NORMAL, aReason); + } + // If the ownership of |r| is not transferred in DispatchLocked() due to + // dispatch failure, it will be deleted here outside the lock. We do so + // since the destructor of the runnable might access TaskQueue and result + // in deadlocks. + } + + // So we can access nsIEventTarget::Dispatch(nsIRunnable*, uint32_t aFlags) + using nsIEventTarget::Dispatch; + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override; + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override; + + using CancelPromise = MozPromise<bool, bool, false>; + + // Puts the queue in a shutdown state and returns immediately. The queue will + // remain alive at least until all the events are drained, because the Runners + // hold a strong reference to the task queue, and one of them is always held + // by the target event queue when the task queue is non-empty. + // + // The returned promise is resolved when the queue goes empty. + RefPtr<ShutdownPromise> BeginShutdown(); + + // Blocks until all task finish executing. + void AwaitIdle(); + + // Blocks until the queue is flagged for shutdown and all tasks have finished + // executing. + void AwaitShutdownAndIdle(); + + bool IsEmpty(); + + // Returns true if the current thread is currently running a Runnable in + // the task queue. + bool IsCurrentThreadIn() const override; + using nsISerialEventTarget::IsOnCurrentThread; + + private: + friend class SupportsThreadSafeWeakPtr<TaskQueue>; + + TaskQueue(already_AddRefed<nsIEventTarget> aTarget, const char* aName, + bool aSupportsTailDispatch); + + virtual ~TaskQueue(); + + // Blocks until all task finish executing. Called internally by methods + // that need to wait until the task queue is idle. + // mQueueMonitor must be held. + void AwaitIdleLocked(); + + nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable, uint32_t aFlags, + DispatchReason aReason = NormalDispatch); + + void MaybeResolveShutdown(); + + nsCOMPtr<nsIEventTarget> mTarget MOZ_GUARDED_BY(mQueueMonitor); + + // Handle for this TaskQueue being registered with our target if it implements + // TaskQueueTracker. + UniquePtr<TaskQueueTrackerEntry> mTrackerEntry MOZ_GUARDED_BY(mQueueMonitor); + + // Monitor that protects the queue, mIsRunning, mIsShutdown and + // mShutdownTasks; + Monitor mQueueMonitor; + + typedef struct TaskStruct { + nsCOMPtr<nsIRunnable> event; + uint32_t flags; + } TaskStruct; + + // Queue of tasks to run. + Queue<TaskStruct> mTasks MOZ_GUARDED_BY(mQueueMonitor); + + // List of tasks to run during shutdown. + nsTArray<nsCOMPtr<nsITargetShutdownTask>> mShutdownTasks + MOZ_GUARDED_BY(mQueueMonitor); + + // The thread currently running the task queue. We store a reference + // to this so that IsCurrentThreadIn() can tell if the current thread + // is the thread currently running in the task queue. + // + // This may be read on any thread, but may only be written on mRunningThread. + // The thread can't die while we're running in it, and we only use it for + // pointer-comparison with the current thread anyway - so we make it atomic + // and don't refcount it. + Atomic<PRThread*> mRunningThread; + + // RAII class that gets instantiated for each dispatched task. + class AutoTaskGuard { + public: + explicit AutoTaskGuard(TaskQueue* aQueue) + : mQueue(aQueue), mLastCurrentThread(nullptr) { + // NB: We don't hold the lock to aQueue here. Don't do anything that + // might require it. + MOZ_ASSERT(!mQueue->mTailDispatcher); + mTaskDispatcher.emplace(aQueue, + /* aIsTailDispatcher = */ true); + mQueue->mTailDispatcher = mTaskDispatcher.ptr(); + + mLastCurrentThread = sCurrentThreadTLS.get(); + sCurrentThreadTLS.set(aQueue); + + MOZ_ASSERT(mQueue->mRunningThread == nullptr); + mQueue->mRunningThread = PR_GetCurrentThread(); + } + + ~AutoTaskGuard() { + mTaskDispatcher->DrainDirectTasks(); + mTaskDispatcher.reset(); + + MOZ_ASSERT(mQueue->mRunningThread == PR_GetCurrentThread()); + mQueue->mRunningThread = nullptr; + + sCurrentThreadTLS.set(mLastCurrentThread); + mQueue->mTailDispatcher = nullptr; + } + + private: + Maybe<AutoTaskDispatcher> mTaskDispatcher; + TaskQueue* mQueue; + AbstractThread* mLastCurrentThread; + }; + + TaskDispatcher* mTailDispatcher; + + // True if we've dispatched an event to the target to execute events from + // the queue. + bool mIsRunning MOZ_GUARDED_BY(mQueueMonitor); + + // True if we've started our shutdown process. + bool mIsShutdown MOZ_GUARDED_BY(mQueueMonitor); + MozPromiseHolder<ShutdownPromise> mShutdownPromise + MOZ_GUARDED_BY(mQueueMonitor); + + // The name of this TaskQueue. Useful when debugging dispatch failures. + const char* const mName; + + SimpleTaskQueue mDirectTasks; + + class Runner : public Runnable { + public: + explicit Runner(TaskQueue* aQueue) + : Runnable("TaskQueue::Runner"), mQueue(aQueue) {} + NS_IMETHOD Run() override; + + private: + RefPtr<TaskQueue> mQueue; + }; +}; + +#define MOZILLA_TASKQUEUETRACKER_IID \ + { \ + 0x765c4b56, 0xd5f6, 0x4a9f, { \ + 0x91, 0xcf, 0x51, 0x47, 0xb3, 0xc1, 0x7e, 0xa6 \ + } \ + } + +// XPCOM "interface" which may be implemented by nsIEventTarget implementations +// which want to keep track of what TaskQueue instances are currently targeting +// them. This may be used to asynchronously shutdown TaskQueues targeting a +// threadpool or other event target before the threadpool goes away. +// +// This explicitly TaskQueue-aware tracker is used instead of +// `nsITargetShutdownTask` as the operations required to shut down a TaskQueue +// are asynchronous, which is not a requirement of that interface. +class TaskQueueTracker : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_TASKQUEUETRACKER_IID) + + // Get a strong reference to every TaskQueue currently tracked by this + // TaskQueueTracker. May be called from any thraed. + nsTArray<RefPtr<TaskQueue>> GetAllTrackedTaskQueues(); + + protected: + virtual ~TaskQueueTracker(); + + private: + friend class TaskQueueTrackerEntry; + + Mutex mMutex{"TaskQueueTracker"}; + LinkedList<TaskQueueTrackerEntry> mEntries MOZ_GUARDED_BY(mMutex); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(TaskQueueTracker, MOZILLA_TASKQUEUETRACKER_IID) + +} // namespace mozilla + +#endif // TaskQueue_h_ diff --git a/xpcom/threads/ThreadBound.h b/xpcom/threads/ThreadBound.h new file mode 100644 index 0000000000..4d5e0088b5 --- /dev/null +++ b/xpcom/threads/ThreadBound.h @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 values only accessible from a single designated thread. + +#ifndef mozilla_ThreadBound_h +#define mozilla_ThreadBound_h + +#include "mozilla/Atomics.h" +#include "prthread.h" + +#include <type_traits> + +namespace mozilla { + +template <typename T> +class ThreadBound; + +namespace detail { + +template <bool Condition, typename T> +struct AddConstIf { + using type = T; +}; + +template <typename T> +struct AddConstIf<true, T> { + using type = typename std::add_const<T>::type; +}; + +} // namespace detail + +// A ThreadBound<T> is a T that can only be accessed by a specific +// thread. To enforce this rule, the inner T is only accessible +// through a non-copyable, immovable accessor object. +// Given a ThreadBound<T> threadBoundData, it can be accessed like so: +// +// auto innerData = threadBoundData.Access(); +// innerData->DoStuff(); +// +// Trying to access a ThreadBound<T> from a different thread will +// trigger a MOZ_DIAGNOSTIC_ASSERT. +// The encapsulated T is constructed during the construction of the +// enclosing ThreadBound<T> by forwarding all of the latter's +// constructor parameters to the former. A newly constructed +// ThreadBound<T> is bound to the thread it's constructed in. It's +// possible to rebind the data to some otherThread by calling +// +// threadBoundData.Transfer(otherThread); +// +// on the thread that threadBoundData is currently bound to, as long +// as it's not currently being accessed. (Trying to rebind from +// another thread or while an accessor exists will trigger an +// assertion.) +// +// Note: A ThreadBound<T> may be destructed from any thread, not just +// its designated thread at the time the destructor is invoked. +template <typename T> +class ThreadBound final { + public: + template <typename... Args> + explicit ThreadBound(Args&&... aArgs) + : mData(std::forward<Args>(aArgs)...), + mThread(PR_GetCurrentThread()), + mAccessCount(0) {} + + ~ThreadBound() { AssertIsNotCurrentlyAccessed(); } + + void Transfer(const PRThread* const aDest) { + AssertIsCorrectThread(); + AssertIsNotCurrentlyAccessed(); + mThread = aDest; + } + + private: + T mData; + + // This member is (potentially) accessed by multiple threads and is + // thus the first point of synchronization between them. + Atomic<const PRThread*, ReleaseAcquire> mThread; + + // In order to support nested accesses (e.g. from different stack + // frames) it's necessary to maintain a counter of the existing + // accessor. Since it's possible to access a const ThreadBound, the + // counter is mutable. It's atomic because accessing it synchronizes + // access to mData (see comment in Accessor's constructor). + using AccessCountType = Atomic<int, ReleaseAcquire>; + mutable AccessCountType mAccessCount; + + public: + template <bool IsConst> + class MOZ_STACK_CLASS Accessor final { + using DataType = typename detail::AddConstIf<IsConst, T>::type; + + public: + explicit Accessor( + typename detail::AddConstIf<IsConst, ThreadBound>::type& aThreadBound) + : mData(aThreadBound.mData), mAccessCount(aThreadBound.mAccessCount) { + aThreadBound.AssertIsCorrectThread(); + + // This load/store serves as a memory fence that guards mData + // against accesses that would trip the thread assertion. + // (Otherwise one of the loads in the caller's instruction + // stream might be scheduled before the assertion.) + ++mAccessCount; + } + + Accessor(const Accessor&) = delete; + Accessor(Accessor&&) = delete; + Accessor& operator=(const Accessor&) = delete; + Accessor& operator=(Accessor&&) = delete; + + ~Accessor() { --mAccessCount; } + + DataType* operator->() { return &mData; } + + private: + DataType& mData; + AccessCountType& mAccessCount; + }; + + auto Access() { return Accessor<false>{*this}; } + + auto Access() const { return Accessor<true>{*this}; } + + private: + bool IsCorrectThread() const { return mThread == PR_GetCurrentThread(); } + + bool IsNotCurrentlyAccessed() const { return mAccessCount == 0; } + +#define MOZ_DEFINE_THREAD_BOUND_ASSERT(predicate) \ + void Assert##predicate() const { MOZ_DIAGNOSTIC_ASSERT(predicate()); } + + MOZ_DEFINE_THREAD_BOUND_ASSERT(IsCorrectThread) + MOZ_DEFINE_THREAD_BOUND_ASSERT(IsNotCurrentlyAccessed) + +#undef MOZ_DEFINE_THREAD_BOUND_ASSERT +}; + +} // namespace mozilla + +#endif // mozilla_ThreadBound_h diff --git a/xpcom/threads/ThreadDelay.cpp b/xpcom/threads/ThreadDelay.cpp new file mode 100644 index 0000000000..1c38e25510 --- /dev/null +++ b/xpcom/threads/ThreadDelay.cpp @@ -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/. */ + +#include "ThreadDelay.h" +#include "mozilla/Assertions.h" +#include "mozilla/ChaosMode.h" + +#if defined(XP_WIN) +# include <windows.h> +#else +# include <unistd.h> +#endif + +namespace mozilla { + +void DelayForChaosMode(ChaosFeature aFeature, + const uint32_t aMicrosecondLimit) { + if (!ChaosMode::isActive(aFeature)) { + return; + } + + MOZ_ASSERT(aMicrosecondLimit <= 1000); +#if defined(XP_WIN) + // Windows doesn't support sleeping at less than millisecond resolution. + // We could spin here, or we could just sleep for one millisecond. + // Sleeping for a full millisecond causes heavy delays, so we don't do + // anything here for now until we have found a good way to sleep more + // precisely here. +#else + const uint32_t duration = ChaosMode::randomUint32LessThan(aMicrosecondLimit); + ::usleep(duration); +#endif +} + +} // namespace mozilla diff --git a/xpcom/threads/ThreadDelay.h b/xpcom/threads/ThreadDelay.h new file mode 100644 index 0000000000..5cfc116e4d --- /dev/null +++ b/xpcom/threads/ThreadDelay.h @@ -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 "mozilla/ChaosMode.h" + +namespace mozilla { + +// Sleep for a random number of microseconds less than aMicrosecondLimit +// if aFeature is enabled. On Windows, the sleep will always be 1 millisecond +// due to platform limitations. +void DelayForChaosMode(ChaosFeature aFeature, const uint32_t aMicrosecondLimit); + +} // namespace mozilla diff --git a/xpcom/threads/ThreadEventQueue.cpp b/xpcom/threads/ThreadEventQueue.cpp new file mode 100644 index 0000000000..dec317beef --- /dev/null +++ b/xpcom/threads/ThreadEventQueue.cpp @@ -0,0 +1,324 @@ +/* -*- 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/ThreadEventQueue.h" +#include "mozilla/EventQueue.h" + +#include "LeakRefPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsITargetShutdownTask.h" +#include "nsIThreadInternal.h" +#include "nsThreadUtils.h" +#include "nsThread.h" +#include "ThreadEventTarget.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/TaskController.h" +#include "mozilla/StaticPrefs_threads.h" + +using namespace mozilla; + +class ThreadEventQueue::NestedSink : public ThreadTargetSink { + public: + NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner) + : mQueue(aQueue), mOwner(aOwner) {} + + bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) final { + return mOwner->PutEventInternal(std::move(aEvent), aPriority, this); + } + + void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; } + + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final { + return NS_ERROR_NOT_IMPLEMENTED; + } + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final { + return NS_ERROR_NOT_IMPLEMENTED; + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + if (mQueue) { + return mQueue->SizeOfIncludingThis(aMallocSizeOf); + } + return 0; + } + + private: + friend class ThreadEventQueue; + + // This is a non-owning reference. It must live at least until Disconnect is + // called to clear it out. + EventQueue* mQueue; + RefPtr<ThreadEventQueue> mOwner; +}; + +ThreadEventQueue::ThreadEventQueue(UniquePtr<EventQueue> aQueue, + bool aIsMainThread) + : mBaseQueue(std::move(aQueue)), + mLock("ThreadEventQueue"), + mEventsAvailable(mLock, "EventsAvail"), + mIsMainThread(aIsMainThread) { + if (aIsMainThread) { + TaskController::Get()->SetConditionVariable(&mEventsAvailable); + } +} + +ThreadEventQueue::~ThreadEventQueue() { MOZ_ASSERT(mNestedQueues.IsEmpty()); } + +bool ThreadEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) { + return PutEventInternal(std::move(aEvent), aPriority, nullptr); +} + +bool ThreadEventQueue::PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority, + NestedSink* aSink) { + // We want to leak the reference when we fail to dispatch it, so that + // we won't release the event in a wrong thread. + LeakRefPtr<nsIRunnable> event(std::move(aEvent)); + nsCOMPtr<nsIThreadObserver> obs; + + { + // Check if the runnable wants to override the passed-in priority. + // Do this outside the lock, so runnables implemented in JS can QI + // (and possibly GC) outside of the lock. + if (mIsMainThread) { + auto* e = event.get(); // can't do_QueryInterface on LeakRefPtr. + if (nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(e)) { + uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL; + runnablePrio->GetPriority(&prio); + if (prio == nsIRunnablePriority::PRIORITY_CONTROL) { + aPriority = EventQueuePriority::Control; + } else if (prio == nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) { + aPriority = EventQueuePriority::RenderBlocking; + } else if (prio == nsIRunnablePriority::PRIORITY_VSYNC) { + aPriority = EventQueuePriority::Vsync; + } else if (prio == nsIRunnablePriority::PRIORITY_INPUT_HIGH) { + aPriority = EventQueuePriority::InputHigh; + } else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) { + aPriority = EventQueuePriority::MediumHigh; + } else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) { + aPriority = EventQueuePriority::DeferredTimers; + } else if (prio == nsIRunnablePriority::PRIORITY_IDLE) { + aPriority = EventQueuePriority::Idle; + } else if (prio == nsIRunnablePriority::PRIORITY_LOW) { + aPriority = EventQueuePriority::Low; + } + } + + if (aPriority == EventQueuePriority::Control && + !StaticPrefs::threads_control_event_queue_enabled()) { + aPriority = EventQueuePriority::MediumHigh; + } + } + + MutexAutoLock lock(mLock); + + if (mEventsAreDoomed) { + return false; + } + + if (aSink) { + if (!aSink->mQueue) { + return false; + } + + aSink->mQueue->PutEvent(event.take(), aPriority, lock); + } else { + mBaseQueue->PutEvent(event.take(), aPriority, lock); + } + + mEventsAvailable.Notify(); + + // Make sure to grab the observer before dropping the lock, otherwise the + // event that we just placed into the queue could run and eventually delete + // this nsThread before the calling thread is scheduled again. We would then + // crash while trying to access a dead nsThread. + obs = mObserver; + } + + if (obs) { + obs->OnDispatchedEvent(); + } + + return true; +} + +already_AddRefed<nsIRunnable> ThreadEventQueue::GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay) { + nsCOMPtr<nsIRunnable> event; + { + // Scope for lock. When we are about to return, we will exit this + // scope so we can do some work after releasing the lock but + // before returning. + MutexAutoLock lock(mLock); + + for (;;) { + const bool noNestedQueue = mNestedQueues.IsEmpty(); + if (noNestedQueue) { + event = mBaseQueue->GetEvent(lock, aLastEventDelay); + } else { + // We always get events from the topmost queue when there are nested + // queues. + event = + mNestedQueues.LastElement().mQueue->GetEvent(lock, aLastEventDelay); + } + + if (event) { + break; + } + + // No runnable available. Sleep waiting for one if if we're supposed to. + // Otherwise just go ahead and return null. + if (!aMayWait) { + break; + } + + AUTO_PROFILER_LABEL("ThreadEventQueue::GetEvent::Wait", IDLE); + mEventsAvailable.Wait(); + } + } + + return event.forget(); +} + +bool ThreadEventQueue::HasPendingEvent() { + MutexAutoLock lock(mLock); + + // We always get events from the topmost queue when there are nested queues. + if (mNestedQueues.IsEmpty()) { + return mBaseQueue->HasReadyEvent(lock); + } else { + return mNestedQueues.LastElement().mQueue->HasReadyEvent(lock); + } +} + +bool ThreadEventQueue::ShutdownIfNoPendingEvents() { + MutexAutoLock lock(mLock); + if (mNestedQueues.IsEmpty() && mBaseQueue->IsEmpty(lock)) { + mEventsAreDoomed = true; + return true; + } + return false; +} + +already_AddRefed<nsISerialEventTarget> ThreadEventQueue::PushEventQueue() { + auto queue = MakeUnique<EventQueue>(); + RefPtr<NestedSink> sink = new NestedSink(queue.get(), this); + RefPtr<ThreadEventTarget> eventTarget = + new ThreadEventTarget(sink, NS_IsMainThread(), false); + + MutexAutoLock lock(mLock); + + mNestedQueues.AppendElement(NestedQueueItem(std::move(queue), eventTarget)); + return eventTarget.forget(); +} + +void ThreadEventQueue::PopEventQueue(nsIEventTarget* aTarget) { + MutexAutoLock lock(mLock); + + MOZ_ASSERT(!mNestedQueues.IsEmpty()); + + NestedQueueItem& item = mNestedQueues.LastElement(); + + MOZ_ASSERT(aTarget == item.mEventTarget); + + // Disconnect the event target that will be popped. + item.mEventTarget->Disconnect(lock); + + EventQueue* prevQueue = + mNestedQueues.Length() == 1 + ? mBaseQueue.get() + : mNestedQueues[mNestedQueues.Length() - 2].mQueue.get(); + + // Move events from the old queue to the new one. + nsCOMPtr<nsIRunnable> event; + TimeDuration delay; + while ((event = item.mQueue->GetEvent(lock, &delay))) { + // preserve the event delay so far + prevQueue->PutEvent(event.forget(), EventQueuePriority::Normal, lock, + &delay); + } + + mNestedQueues.RemoveLastElement(); +} + +size_t ThreadEventQueue::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = 0; + + { + MutexAutoLock lock(mLock); + n += mBaseQueue->SizeOfIncludingThis(aMallocSizeOf); + n += mNestedQueues.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto& queue : mNestedQueues) { + n += queue.mEventTarget->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf) + n; +} + +already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserver() { + MutexAutoLock lock(mLock); + return do_AddRef(mObserver); +} + +already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserverOnThread() + MOZ_NO_THREAD_SAFETY_ANALYSIS { + // only written on this thread + return do_AddRef(mObserver); +} + +void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) { + // Always called from the thread - single writer. + nsCOMPtr<nsIThreadObserver> observer = aObserver; + { + MutexAutoLock lock(mLock); + mObserver.swap(observer); + } + if (NS_IsMainThread()) { + TaskController::Get()->SetThreadObserver(aObserver); + } +} + +nsresult ThreadEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + MutexAutoLock lock(mLock); + if (mEventsAreDoomed || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + MOZ_ASSERT(!mShutdownTasks.Contains(aTask)); + mShutdownTasks.AppendElement(aTask); + return NS_OK; +} + +nsresult ThreadEventQueue::UnregisterShutdownTask( + nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + MutexAutoLock lock(mLock); + if (mEventsAreDoomed || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +void ThreadEventQueue::RunShutdownTasks() { + nsTArray<nsCOMPtr<nsITargetShutdownTask>> shutdownTasks; + { + MutexAutoLock lock(mLock); + shutdownTasks = std::move(mShutdownTasks); + mShutdownTasks.Clear(); + mShutdownTasksRun = true; + } + for (auto& task : shutdownTasks) { + task->TargetShutdown(); + } +} + +ThreadEventQueue::NestedQueueItem::NestedQueueItem( + UniquePtr<EventQueue> aQueue, ThreadEventTarget* aEventTarget) + : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {} diff --git a/xpcom/threads/ThreadEventQueue.h b/xpcom/threads/ThreadEventQueue.h new file mode 100644 index 0000000000..5244acb3dc --- /dev/null +++ b/xpcom/threads/ThreadEventQueue.h @@ -0,0 +1,95 @@ +/* -*- 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_ThreadEventQueue_h +#define mozilla_ThreadEventQueue_h + +#include "mozilla/EventQueue.h" +#include "mozilla/CondVar.h" +#include "mozilla/SynchronizedEventQueue.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThreadObserver; + +namespace mozilla { + +class EventQueue; +class ThreadEventTarget; + +// A ThreadEventQueue implements normal monitor-style synchronization over the +// EventQueue. It also implements PushEventQueue and PopEventQueue for workers +// (see the documentation below for an explanation of those). All threads use a +// ThreadEventQueue as their event queue. Although for the main thread this +// simply forwards events to the TaskController. +class ThreadEventQueue final : public SynchronizedEventQueue { + public: + explicit ThreadEventQueue(UniquePtr<EventQueue> aQueue, + bool aIsMainThread = false); + + bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) final; + + already_AddRefed<nsIRunnable> GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) final; + bool HasPendingEvent() final; + + bool ShutdownIfNoPendingEvents() final; + + void Disconnect(const MutexAutoLock& aProofOfLock) final {} + + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final; + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final; + void RunShutdownTasks() final; + + already_AddRefed<nsISerialEventTarget> PushEventQueue() final; + void PopEventQueue(nsIEventTarget* aTarget) final; + + already_AddRefed<nsIThreadObserver> GetObserver() final; + already_AddRefed<nsIThreadObserver> GetObserverOnThread() final; + void SetObserver(nsIThreadObserver* aObserver) final; + + Mutex& MutexRef() { return mLock; } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) override; + + private: + class NestedSink; + + virtual ~ThreadEventQueue(); + + bool PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority, NestedSink* aQueue); + + const UniquePtr<EventQueue> mBaseQueue MOZ_GUARDED_BY(mLock); + + struct NestedQueueItem { + UniquePtr<EventQueue> mQueue; + RefPtr<ThreadEventTarget> mEventTarget; + + NestedQueueItem(UniquePtr<EventQueue> aQueue, + ThreadEventTarget* aEventTarget); + }; + + nsTArray<NestedQueueItem> mNestedQueues MOZ_GUARDED_BY(mLock); + + Mutex mLock; + CondVar mEventsAvailable MOZ_GUARDED_BY(mLock); + + bool mEventsAreDoomed MOZ_GUARDED_BY(mLock) = false; + nsCOMPtr<nsIThreadObserver> mObserver MOZ_GUARDED_BY(mLock); + nsTArray<nsCOMPtr<nsITargetShutdownTask>> mShutdownTasks + MOZ_GUARDED_BY(mLock); + bool mShutdownTasksRun MOZ_GUARDED_BY(mLock) = false; + + const bool mIsMainThread; +}; + +} // namespace mozilla + +#endif // mozilla_ThreadEventQueue_h diff --git a/xpcom/threads/ThreadEventTarget.cpp b/xpcom/threads/ThreadEventTarget.cpp new file mode 100644 index 0000000000..17844d4886 --- /dev/null +++ b/xpcom/threads/ThreadEventTarget.cpp @@ -0,0 +1,137 @@ +/* -*- 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 "ThreadEventTarget.h" +#include "mozilla/ThreadEventQueue.h" + +#include "LeakRefPtr.h" +#include "mozilla/DelayedRunnable.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" +#include "nsITimer.h" +#include "nsThreadManager.h" +#include "nsThreadSyncDispatch.h" +#include "nsThreadUtils.h" +#include "ThreadDelay.h" + +using namespace mozilla; + +#ifdef DEBUG +// This flag will be set right after XPCOMShutdownThreads finished but before +// we continue with other processing. It is exclusively meant to prime the +// assertion of ThreadEventTarget::Dispatch as early as possible. +// Please use AppShutdown::IsInOrBeyond(ShutdownPhase::???) +// elsewhere to check for shutdown phases. +static mozilla::Atomic<bool, mozilla::SequentiallyConsistent> + gXPCOMThreadsShutDownNotified(false); +#endif + +ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink, + bool aIsMainThread, bool aBlockDispatch) + : mSink(aSink), +#ifdef DEBUG + mIsMainThread(aIsMainThread), +#endif + mBlockDispatch(aBlockDispatch) { + mThread = PR_GetCurrentThread(); +} + +ThreadEventTarget::~ThreadEventTarget() = default; + +void ThreadEventTarget::SetCurrentThread(PRThread* aThread) { + mThread = aThread; +} + +void ThreadEventTarget::ClearCurrentThread() { mThread = nullptr; } + +NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget) + +NS_IMETHODIMP +ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) { + return Dispatch(do_AddRef(aRunnable), aFlags); +} + +#ifdef DEBUG +// static +void ThreadEventTarget::XPCOMShutdownThreadsNotificationFinished() { + gXPCOMThreadsShutDownNotified = true; +} +#endif + +NS_IMETHODIMP +ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + // We want to leak the reference when we fail to dispatch it, so that + // we won't release the event in a wrong thread. + LeakRefPtr<nsIRunnable> event(std::move(aEvent)); + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + NS_ASSERTION(!gXPCOMThreadsShutDownNotified || mIsMainThread || + PR_GetCurrentThread() == mThread || + (aFlags & NS_DISPATCH_IGNORE_BLOCK_DISPATCH), + "Dispatch to non-main thread after xpcom-shutdown-threads"); + + if (mBlockDispatch && !(aFlags & NS_DISPATCH_IGNORE_BLOCK_DISPATCH)) { + MOZ_DIAGNOSTIC_ASSERT( + false, + "Attempt to dispatch to thread which does not usually process " + "dispatched runnables until shutdown"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + LogRunnable::LogDispatch(event.get()); + + NS_ASSERTION((aFlags & (NS_DISPATCH_AT_END | + NS_DISPATCH_IGNORE_BLOCK_DISPATCH)) == aFlags, + "unexpected dispatch flags"); + if (!mSink->PutEvent(event.take(), EventQueuePriority::Normal)) { + return NS_ERROR_UNEXPECTED; + } + // Delay to encourage the receiving task to run before we do work. + DelayForChaosMode(ChaosFeature::TaskDispatching, 1000); + return NS_OK; +} + +NS_IMETHODIMP +ThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelayMs) { + nsCOMPtr<nsIRunnable> event = aEvent; + NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED); + + RefPtr<DelayedRunnable> r = + new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs); + nsresult rv = r->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + return Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ThreadEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mSink->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThreadEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mSink->UnregisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { + *aIsOnCurrentThread = IsOnCurrentThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +ThreadEventTarget::IsOnCurrentThreadInfallible() { + // This method is only going to be called if `mThread` is null, which + // only happens when the thread has exited the event loop. Therefore, when + // we are called, we can never be on this thread. + return false; +} diff --git a/xpcom/threads/ThreadEventTarget.h b/xpcom/threads/ThreadEventTarget.h new file mode 100644 index 0000000000..b78411aa80 --- /dev/null +++ b/xpcom/threads/ThreadEventTarget.h @@ -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/. */ + +#ifndef mozilla_ThreadEventTarget_h +#define mozilla_ThreadEventTarget_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/SynchronizedEventQueue.h" // for ThreadTargetSink +#include "nsISerialEventTarget.h" + +namespace mozilla { +class DelayedRunnable; + +// ThreadEventTarget handles the details of posting an event to a thread. It can +// be used with any ThreadTargetSink implementation. +class ThreadEventTarget final : public nsISerialEventTarget { + public: + ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread, + bool aBlockDispatch); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + // Disconnects the target so that it can no longer post events. + void Disconnect(const MutexAutoLock& aProofOfLock) { + mSink->Disconnect(aProofOfLock); + } + + // Sets the thread for which IsOnCurrentThread returns true to the current + // thread. + void SetCurrentThread(PRThread* aThread); + // Call ClearCurrentThread() before the PRThread is deleted on thread join. + void ClearCurrentThread(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mSink) { + n += mSink->SizeOfIncludingThis(aMallocSizeOf); + } + return aMallocSizeOf(this) + n; + } + +#ifdef DEBUG + static void XPCOMShutdownThreadsNotificationFinished(); +#endif + + private: + ~ThreadEventTarget(); + + RefPtr<ThreadTargetSink> mSink; +#ifdef DEBUG + const bool mIsMainThread; +#endif + const bool mBlockDispatch; +}; + +} // namespace mozilla + +#endif // mozilla_ThreadEventTarget_h diff --git a/xpcom/threads/ThreadLocalVariables.cpp b/xpcom/threads/ThreadLocalVariables.cpp new file mode 100644 index 0000000000..606c0c6384 --- /dev/null +++ b/xpcom/threads/ThreadLocalVariables.cpp @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/ThreadLocal.h" + +// This variable is used to ensure creating new URI doesn't put us in an +// infinite loop +MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount; + +void InitThreadLocalVariables() { + if (!gTlsURLRecursionCount.init()) { + MOZ_CRASH("Could not init gTlsURLRecursionCount"); + } + gTlsURLRecursionCount.set(0); +} diff --git a/xpcom/threads/ThrottledEventQueue.cpp b/xpcom/threads/ThrottledEventQueue.cpp new file mode 100644 index 0000000000..9e4219b305 --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.cpp @@ -0,0 +1,459 @@ +/* -*- 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 "ThrottledEventQueue.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CondVar.h" +#include "mozilla/EventQueue.h" +#include "mozilla/Mutex.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +namespace {} // anonymous namespace + +// The ThrottledEventQueue is designed with inner and outer objects: +// +// XPCOM code base event target +// | | +// v v +// +-------+ +--------+ +// | Outer | +-->|executor| +// +-------+ | +--------+ +// | | | +// | +-------+ | +// +-->| Inner |<--+ +// +-------+ +// +// Client code references the outer nsIEventTarget which in turn references +// an inner object, which actually holds the queue of runnables. +// +// Whenever the queue is non-empty (and not paused), it keeps an "executor" +// runnable dispatched to the base event target. Each time the executor is run, +// it draws the next event from Inner's queue and runs it. If that queue has +// more events, the executor is dispatched to the base again. +// +// The executor holds a strong reference to the Inner object. This means that if +// the outer object is dereferenced and destroyed, the Inner object will remain +// live for as long as the executor exists - that is, until the Inner's queue is +// empty. +// +// A Paused ThrottledEventQueue does not enqueue an executor when new events are +// added. Any executor previously queued on the base event target draws no +// events from a Paused ThrottledEventQueue, and returns without re-enqueueing +// itself. Since there is no executor keeping the Inner object alive until its +// queue is empty, dropping a Paused ThrottledEventQueue may drop the Inner +// while it still owns events. This is the correct behavior: if there are no +// references to it, it will never be Resumed, and thus it will never dispatch +// events again. +// +// Resuming a ThrottledEventQueue must dispatch an executor, so calls to Resume +// are fallible for the same reasons as calls to Dispatch. +// +// The xpcom shutdown process drains the main thread's event queue several +// times, so if a ThrottledEventQueue is being driven by the main thread, it +// should get emptied out by the time we reach the "eventq shutdown" phase. +class ThrottledEventQueue::Inner final : public nsISupports { + // The runnable which is dispatched to the underlying base target. Since + // we only execute one event at a time we just re-use a single instance + // of this class while there are events left in the queue. + class Executor final : public Runnable, public nsIRunnablePriority { + // The Inner whose runnables we execute. mInner->mExecutor points + // to this executor, forming a reference loop. + RefPtr<Inner> mInner; + + ~Executor() = default; + + public: + explicit Executor(Inner* aInner) + : Runnable("ThrottledEventQueue::Inner::Executor"), mInner(aInner) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHODIMP + Run() override { + mInner->ExecuteRunnable(); + return NS_OK; + } + + NS_IMETHODIMP + GetPriority(uint32_t* aPriority) override { + *aPriority = mInner->mPriority; + return NS_OK; + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHODIMP + GetName(nsACString& aName) override { return mInner->CurrentName(aName); } +#endif + }; + + mutable Mutex mMutex; + mutable CondVar mIdleCondVar MOZ_GUARDED_BY(mMutex); + + // As-of-yet unexecuted runnables queued on this ThrottledEventQueue. + // + // Used from any thread; protected by mMutex. Signals mIdleCondVar when + // emptied. + EventQueueSized<64> mEventQueue MOZ_GUARDED_BY(mMutex); + + // The event target we dispatch our events (actually, just our Executor) to. + // + // Written only during construction. Readable by any thread without locking. + const nsCOMPtr<nsISerialEventTarget> mBaseTarget; + + // The Executor that we dispatch to mBaseTarget to draw runnables from our + // queue. mExecutor->mInner points to this Inner, forming a reference loop. + // + // Used from any thread; protected by mMutex. + nsCOMPtr<nsIRunnable> mExecutor MOZ_GUARDED_BY(mMutex); + + const char* const mName; + + const uint32_t mPriority; + + // True if this queue is currently paused. + // Used from any thread; protected by mMutex. + bool mIsPaused MOZ_GUARDED_BY(mMutex); + + explicit Inner(nsISerialEventTarget* aBaseTarget, const char* aName, + uint32_t aPriority) + : mMutex("ThrottledEventQueue"), + mIdleCondVar(mMutex, "ThrottledEventQueue:Idle"), + mBaseTarget(aBaseTarget), + mName(aName), + mPriority(aPriority), + mIsPaused(false) { + MOZ_ASSERT(mName, "Must pass a valid name!"); + } + + ~Inner() { +#ifdef DEBUG + MutexAutoLock lock(mMutex); + + // As long as an executor exists, it had better keep us alive, since it's + // going to call ExecuteRunnable on us. + MOZ_ASSERT(!mExecutor); + + // If we have any events in our queue, there should be an executor queued + // for them, and that should have kept us alive. The exception is that, if + // we're paused, we don't enqueue an executor. + MOZ_ASSERT(mEventQueue.IsEmpty(lock) || IsPaused(lock)); + + // Some runnables are only safe to drop on the main thread, so if our queue + // isn't empty, we'd better be on the main thread. + MOZ_ASSERT_IF(!mEventQueue.IsEmpty(lock), NS_IsMainThread()); +#endif + } + + // Make sure an executor has been queued on our base target. If we already + // have one, do nothing; otherwise, create and dispatch it. + nsresult EnsureExecutor(MutexAutoLock& lock) MOZ_REQUIRES(mMutex) { + if (mExecutor) return NS_OK; + + // Note, this creates a ref cycle keeping the inner alive + // until the queue is drained. + mExecutor = new Executor(this); + nsresult rv = mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + mExecutor = nullptr; + return rv; + } + + return NS_OK; + } + + nsresult CurrentName(nsACString& aName) { + nsCOMPtr<nsIRunnable> event; + +#ifdef DEBUG + bool currentThread = false; + mBaseTarget->IsOnCurrentThread(¤tThread); + MOZ_ASSERT(currentThread); +#endif + + { + MutexAutoLock lock(mMutex); + event = mEventQueue.PeekEvent(lock); + // It is possible that mEventQueue wasn't empty when the executor + // was added to the queue, but someone processed events from mEventQueue + // before the executor, this is why mEventQueue is empty here + if (!event) { + aName.AssignLiteral("no runnables left in the ThrottledEventQueue"); + return NS_OK; + } + } + + if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) { + nsresult rv = named->GetName(aName); + return rv; + } + + aName.AssignASCII(mName); + return NS_OK; + } + + void ExecuteRunnable() { + // Any thread + nsCOMPtr<nsIRunnable> event; + +#ifdef DEBUG + bool currentThread = false; + mBaseTarget->IsOnCurrentThread(¤tThread); + MOZ_ASSERT(currentThread); +#endif + + { + MutexAutoLock lock(mMutex); + + // Normally, a paused queue doesn't dispatch any executor, but we might + // have been paused after the executor was already in flight. There's no + // way to yank the executor out of the base event target, so we just check + // for a paused queue here and return without running anything. We'll + // create a new executor when we're resumed. + if (IsPaused(lock)) { + // Note, this breaks a ref cycle. + mExecutor = nullptr; + return; + } + + // We only dispatch an executor runnable when we know there is something + // in the queue, so this should never fail. + event = mEventQueue.GetEvent(lock); + MOZ_ASSERT(event); + + // If there are more events in the queue, then dispatch the next + // executor. We do this now, before running the event, because + // the event might spin the event loop and we don't want to stall + // the queue. + if (mEventQueue.HasReadyEvent(lock)) { + // Dispatch the next base target runnable to attempt to execute + // the next throttled event. We must do this before executing + // the event in case the event spins the event loop. + MOZ_ALWAYS_SUCCEEDS( + mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL)); + } + + // Otherwise the queue is empty and we can stop dispatching the + // executor. + else { + // Break the Executor::mInner / Inner::mExecutor reference loop. + mExecutor = nullptr; + mIdleCondVar.NotifyAll(); + } + } + + // Execute the event now that we have unlocked. + LogRunnable::Run log(event); + Unused << event->Run(); + + // To cover the event's destructor code in the LogRunnable log + event = nullptr; + } + + public: + static already_AddRefed<Inner> Create(nsISerialEventTarget* aBaseTarget, + const char* aName, uint32_t aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + // FIXME: This assertion only worked when `sCurrentShutdownPhase` was not + // being updated. + // MOZ_ASSERT(ClearOnShutdown_Internal::sCurrentShutdownPhase == + // ShutdownPhase::NotInShutdown); + + RefPtr<Inner> ref = new Inner(aBaseTarget, aName, aPriority); + return ref.forget(); + } + + bool IsEmpty() const { + // Any thread + return Length() == 0; + } + + uint32_t Length() const { + // Any thread + MutexAutoLock lock(mMutex); + return mEventQueue.Count(lock); + } + + already_AddRefed<nsIRunnable> GetEvent() { + MutexAutoLock lock(mMutex); + return mEventQueue.GetEvent(lock); + } + + void AwaitIdle() const { + // Any thread, except the main thread or our base target. Blocking the + // main thread is forbidden. Blocking the base target is guaranteed to + // produce a deadlock. + MOZ_ASSERT(!NS_IsMainThread()); +#ifdef DEBUG + bool onBaseTarget = false; + Unused << mBaseTarget->IsOnCurrentThread(&onBaseTarget); + MOZ_ASSERT(!onBaseTarget); +#endif + + MutexAutoLock lock(mMutex); + while (mExecutor || IsPaused(lock)) { + mIdleCondVar.Wait(); + } + } + + bool IsPaused() const { + MutexAutoLock lock(mMutex); + return IsPaused(lock); + } + + bool IsPaused(const MutexAutoLock& aProofOfLock) const MOZ_REQUIRES(mMutex) { + return mIsPaused; + } + + nsresult SetIsPaused(bool aIsPaused) { + MutexAutoLock lock(mMutex); + + // If we will be unpaused, and we have events in our queue, make sure we + // have an executor queued on the base event target to run them. Do this + // before we actually change mIsPaused, since this is fallible. + if (!aIsPaused && !mEventQueue.IsEmpty(lock)) { + nsresult rv = EnsureExecutor(lock); + if (NS_FAILED(rv)) { + return rv; + } + } + + mIsPaused = aIsPaused; + return NS_OK; + } + + nsresult DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + // Any thread + nsCOMPtr<nsIRunnable> r = aEvent; + return Dispatch(r.forget(), aFlags); + } + + nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) { + MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END); + + // Any thread + MutexAutoLock lock(mMutex); + + if (!IsPaused(lock)) { + // Make sure we have an executor in flight to process events. This is + // fallible, so do it first. Our lock will prevent the executor from + // accessing the event queue before we add the event below. + nsresult rv = EnsureExecutor(lock); + if (NS_FAILED(rv)) return rv; + } + + // Only add the event to the underlying queue if are able to + // dispatch to our base target. + nsCOMPtr<nsIRunnable> event(aEvent); + LogRunnable::LogDispatch(event); + mEventQueue.PutEvent(event.forget(), EventQueuePriority::Normal, lock); + return NS_OK; + } + + nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelay) { + // The base target may implement this, but we don't. Always fail + // to provide consistent behavior. + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mBaseTarget->RegisterShutdownTask(aTask); + } + + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mBaseTarget->UnregisterShutdownTask(aTask); + } + + bool IsOnCurrentThread() { return mBaseTarget->IsOnCurrentThread(); } + + NS_DECL_THREADSAFE_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsISupports); + +NS_IMPL_ISUPPORTS_INHERITED(ThrottledEventQueue::Inner::Executor, Runnable, + nsIRunnablePriority) + +NS_IMPL_ISUPPORTS(ThrottledEventQueue, ThrottledEventQueue, nsIEventTarget, + nsISerialEventTarget); + +ThrottledEventQueue::ThrottledEventQueue(already_AddRefed<Inner> aInner) + : mInner(aInner) { + MOZ_ASSERT(mInner); +} + +already_AddRefed<ThrottledEventQueue> ThrottledEventQueue::Create( + nsISerialEventTarget* aBaseTarget, const char* aName, uint32_t aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBaseTarget); + + RefPtr<Inner> inner = Inner::Create(aBaseTarget, aName, aPriority); + + RefPtr<ThrottledEventQueue> ref = new ThrottledEventQueue(inner.forget()); + return ref.forget(); +} + +bool ThrottledEventQueue::IsEmpty() const { return mInner->IsEmpty(); } + +uint32_t ThrottledEventQueue::Length() const { return mInner->Length(); } + +// Get the next runnable from the queue +already_AddRefed<nsIRunnable> ThrottledEventQueue::GetEvent() { + return mInner->GetEvent(); +} + +void ThrottledEventQueue::AwaitIdle() const { return mInner->AwaitIdle(); } + +nsresult ThrottledEventQueue::SetIsPaused(bool aIsPaused) { + return mInner->SetIsPaused(aIsPaused); +} + +bool ThrottledEventQueue::IsPaused() const { return mInner->IsPaused(); } + +NS_IMETHODIMP +ThrottledEventQueue::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + return mInner->DispatchFromScript(aEvent, aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + return mInner->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + return mInner->DelayedDispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mInner->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThrottledEventQueue::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mInner->UnregisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThrottledEventQueue::IsOnCurrentThread(bool* aResult) { + *aResult = mInner->IsOnCurrentThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +ThrottledEventQueue::IsOnCurrentThreadInfallible() { + return mInner->IsOnCurrentThread(); +} + +} // namespace mozilla diff --git a/xpcom/threads/ThrottledEventQueue.h b/xpcom/threads/ThrottledEventQueue.h new file mode 100644 index 0000000000..cf37a10a6d --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// nsIEventTarget wrapper for throttling event dispatch. + +#ifndef mozilla_ThrottledEventQueue_h +#define mozilla_ThrottledEventQueue_h + +#include "nsISerialEventTarget.h" + +#define NS_THROTTLEDEVENTQUEUE_IID \ + { \ + 0x8f3cf7dc, 0xfc14, 0x4ad5, { \ + 0x9f, 0xd5, 0xdb, 0x79, 0xbc, 0xe6, 0xd5, 0x08 \ + } \ + } + +namespace mozilla { + +// A ThrottledEventQueue is an event target that can be used to throttle +// events being dispatched to another base target. It maintains its +// own queue of events and only dispatches one at a time to the wrapped +// target. This can be used to avoid flooding the base target. +// +// Flooding is avoided via a very simple principle. Runnables dispatched +// to the ThrottledEventQueue are only dispatched to the base target +// one at a time. Only once that runnable has executed will we dispatch +// the next runnable to the base target. This in effect makes all +// runnables passing through the ThrottledEventQueue yield to other work +// on the base target. +// +// ThrottledEventQueue keeps runnables waiting to be dispatched to the +// base in its own internal queue. Code can query the length of this +// queue using IsEmpty() and Length(). Further, code implement back +// pressure by checking the depth of the queue and deciding to stop +// issuing runnables if they see the ThrottledEventQueue is backed up. +// Code running on other threads could even use AwaitIdle() to block +// all operation until the ThrottledEventQueue drains. +// +// Note, this class is similar to TaskQueue, but also differs in a few +// ways. First, it is a very simple nsIEventTarget implementation. It +// does not use the AbstractThread API. +// +// In addition, ThrottledEventQueue currently dispatches its next +// runnable to the base target *before* running the current event. This +// allows the event code to spin the event loop without stalling the +// ThrottledEventQueue. In contrast, TaskQueue only dispatches its next +// runnable after running the current event. That approach is necessary +// for TaskQueue in order to work with thread pool targets. +// +// So, if you are targeting a thread pool you probably want a TaskQueue. +// If you are targeting a single thread or other non-concurrent event +// target, you probably want a ThrottledEventQueue. +// +// If you drop a ThrottledEventQueue while its queue still has events to be run, +// they will continue to be dispatched as usual to the base. Only once the last +// event has run will all the ThrottledEventQueue's memory be freed. +class ThrottledEventQueue final : public nsISerialEventTarget { + class Inner; + RefPtr<Inner> mInner; + + explicit ThrottledEventQueue(already_AddRefed<Inner> aInner); + ~ThrottledEventQueue() = default; + + public: + // Create a ThrottledEventQueue for the given target. + static already_AddRefed<ThrottledEventQueue> Create( + nsISerialEventTarget* aBaseTarget, const char* aName, + uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL); + + // Determine if there are any events pending in the queue. + bool IsEmpty() const; + + // Determine how many events are pending in the queue. + uint32_t Length() const; + + already_AddRefed<nsIRunnable> GetEvent(); + + // Block the current thread until the queue is empty. This may not be called + // on the main thread or the base target. The ThrottledEventQueue must not be + // paused. + void AwaitIdle() const; + + // If |aIsPaused| is true, pause execution of events from this queue. No + // events from this queue will be run until this is called with |aIsPaused| + // false. + // + // To un-pause a ThrottledEventQueue, we need to dispatch a runnable to the + // underlying event target. That operation may fail, so this method is + // fallible as well. + // + // Note that, although ThrottledEventQueue's behavior is descibed as queueing + // events on the base target, an event queued on a TEQ is never actually moved + // to any other queue. What is actually dispatched to the base is an + // "executor" event which, when run, removes an event from the TEQ and runs it + // immediately. This means that you can pause a TEQ even after the executor + // has been queued on the base target, and even so, no events from the TEQ + // will run. When the base target gets around to running the executor, the + // executor will see that the TEQ is paused, and do nothing. + [[nodiscard]] nsresult SetIsPaused(bool aIsPaused); + + // Return true if this ThrottledEventQueue is paused. + bool IsPaused() const; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_THROTTLEDEVENTQUEUE_IID); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ThrottledEventQueue, NS_THROTTLEDEVENTQUEUE_IID); + +} // namespace mozilla + +#endif // mozilla_ThrottledEventQueue_h diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp new file mode 100644 index 0000000000..d0ff778f20 --- /dev/null +++ b/xpcom/threads/TimerThread.cpp @@ -0,0 +1,1565 @@ +/* -*- 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 "nsTimerImpl.h" +#include "TimerThread.h" + +#include "GeckoProfiler.h" +#include "nsThreadUtils.h" + +#include "nsIObserverService.h" +#include "nsIPropertyBag2.h" +#include "mozilla/Services.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/StaticPrefs_timer.h" + +#include "mozilla/glean/GleanMetrics.h" + +#include <math.h> + +using namespace mozilla; + +#ifdef XP_WIN +// Include Windows header required for enabling high-precision timers. +# include <windows.h> +# include <mmsystem.h> + +static constexpr UINT kTimerPeriodHiRes = 1; +static constexpr UINT kTimerPeriodLowRes = 16; + +// Helper functions to determine what Windows timer resolution to target. +static constexpr UINT GetDesiredTimerPeriod(const bool aOnBatteryPower, + const bool aLowProcessPriority) { + const bool useLowResTimer = aOnBatteryPower || aLowProcessPriority; + return useLowResTimer ? kTimerPeriodLowRes : kTimerPeriodHiRes; +} + +static_assert(GetDesiredTimerPeriod(true, false) == kTimerPeriodLowRes); +static_assert(GetDesiredTimerPeriod(false, true) == kTimerPeriodLowRes); +static_assert(GetDesiredTimerPeriod(true, true) == kTimerPeriodLowRes); +static_assert(GetDesiredTimerPeriod(false, false) == kTimerPeriodHiRes); + +UINT TimerThread::ComputeDesiredTimerPeriod() const { + const bool lowPriorityProcess = + mCachedPriority.load(std::memory_order_relaxed) < + hal::PROCESS_PRIORITY_FOREGROUND; + + // NOTE: Using short-circuiting here to avoid call to GetSystemPowerStatus() + // when we know that that result will not affect the final result. (As + // confirmed by the static_assert's above, onBatteryPower does not affect the + // result when the lowPriorityProcess is true.) + SYSTEM_POWER_STATUS status; + const bool onBatteryPower = !lowPriorityProcess && + GetSystemPowerStatus(&status) && + (status.ACLineStatus == 0); + + return GetDesiredTimerPeriod(onBatteryPower, lowPriorityProcess); +} +#endif + +// Uncomment the following line to enable runtime stats during development. +// #define TIMERS_RUNTIME_STATS + +#ifdef TIMERS_RUNTIME_STATS +// This class gathers durations and displays some basic stats when destroyed. +// It is intended to be used as a static variable (see `AUTO_TIMERS_STATS` +// below), to display stats at the end of the program. +class StaticTimersStats { + public: + explicit StaticTimersStats(const char* aName) : mName(aName) {} + + ~StaticTimersStats() { + // Using unsigned long long for computations and printfs. + using ULL = unsigned long long; + ULL n = static_cast<ULL>(mCount); + if (n == 0) { + printf("[%d] Timers stats `%s`: (nothing)\n", + int(profiler_current_process_id().ToNumber()), mName); + } else if (ULL sumNs = static_cast<ULL>(mSumDurationsNs); sumNs == 0) { + printf("[%d] Timers stats `%s`: %llu\n", + int(profiler_current_process_id().ToNumber()), mName, n); + } else { + printf("[%d] Timers stats `%s`: %llu ns / %llu = %llu ns, max %llu ns\n", + int(profiler_current_process_id().ToNumber()), mName, sumNs, n, + sumNs / n, static_cast<ULL>(mLongestDurationNs)); + } + } + + void AddDurationFrom(TimeStamp aStart) { + // Duration between aStart and now, rounded to the nearest nanosecond. + DurationNs duration = static_cast<DurationNs>( + (TimeStamp::Now() - aStart).ToMicroseconds() * 1000 + 0.5); + mSumDurationsNs += duration; + ++mCount; + // Update mLongestDurationNs if this one is longer. + for (;;) { + DurationNs longest = mLongestDurationNs; + if (MOZ_LIKELY(longest >= duration)) { + // This duration is not the longest, nothing to do. + break; + } + if (MOZ_LIKELY(mLongestDurationNs.compareExchange(longest, duration))) { + // Successfully updated `mLongestDurationNs` with the new value. + break; + } + // Otherwise someone else just updated `mLongestDurationNs`, we need to + // try again by looping. + } + } + + void AddCount() { + MOZ_ASSERT(mSumDurationsNs == 0, "Don't mix counts and durations"); + ++mCount; + } + + private: + using DurationNs = uint64_t; + using Count = uint32_t; + + Atomic<DurationNs> mSumDurationsNs{0}; + Atomic<DurationNs> mLongestDurationNs{0}; + Atomic<Count> mCount{0}; + const char* mName; +}; + +// RAII object that measures its scoped lifetime duration and reports it to a +// `StaticTimersStats`. +class MOZ_RAII AutoTimersStats { + public: + explicit AutoTimersStats(StaticTimersStats& aStats) + : mStats(aStats), mStart(TimeStamp::Now()) {} + + ~AutoTimersStats() { mStats.AddDurationFrom(mStart); } + + private: + StaticTimersStats& mStats; + TimeStamp mStart; +}; + +// Macro that should be used to collect basic statistics from measurements of +// block durations, from where this macro is, until the end of its enclosing +// scope. The name is used in the static variable name and when displaying stats +// at the end of the program; Another location could use the same name but their +// stats will not be combined, so use different name if these locations should +// be distinguished. +# define AUTO_TIMERS_STATS(name) \ + static ::StaticTimersStats sStat##name(#name); \ + ::AutoTimersStats autoStat##name(sStat##name); + +// This macro only counts the number of times it's used, not durations. +// Don't mix with AUTO_TIMERS_STATS! +# define COUNT_TIMERS_STATS(name) \ + static ::StaticTimersStats sStat##name(#name); \ + sStat##name.AddCount(); + +#else // TIMERS_RUNTIME_STATS + +# define AUTO_TIMERS_STATS(name) +# define COUNT_TIMERS_STATS(name) + +#endif // TIMERS_RUNTIME_STATS else + +NS_IMPL_ISUPPORTS_INHERITED(TimerThread, Runnable, nsIObserver) + +TimerThread::TimerThread() + : Runnable("TimerThread"), + mInitialized(false), + mMonitor("TimerThread.mMonitor"), + mShutdown(false), + mWaiting(false), + mNotified(false), + mSleeping(false), + mAllowedEarlyFiringMicroseconds(0) {} + +TimerThread::~TimerThread() { + mThread = nullptr; + + NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread"); + +#if TIMER_THREAD_STATISTICS + { + MonitorAutoLock lock(mMonitor); + PrintStatistics(); + } +#endif +} + +namespace { + +class TimerObserverRunnable : public Runnable { + public: + explicit TimerObserverRunnable(nsIObserver* aObserver) + : mozilla::Runnable("TimerObserverRunnable"), mObserver(aObserver) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsIObserver> mObserver; +}; + +NS_IMETHODIMP +TimerObserverRunnable::Run() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(mObserver, "sleep_notification", false); + observerService->AddObserver(mObserver, "wake_notification", false); + observerService->AddObserver(mObserver, "suspend_process_notification", + false); + observerService->AddObserver(mObserver, "resume_process_notification", + false); + observerService->AddObserver(mObserver, "ipc:process-priority-changed", + false); + } + return NS_OK; +} + +} // namespace + +namespace { + +// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. +// It's needed to avoid contention over the default allocator lock when +// firing timer events (see bug 733277). The thread-safety is required because +// nsTimerEvent objects are allocated on the timer thread, and freed on another +// thread. Because TimerEventAllocator has its own lock, contention over that +// lock is limited to the allocation and deallocation of nsTimerEvent objects. +// +// Because this is layered over ArenaAllocator, it never shrinks -- even +// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list +// for later recycling. So the amount of memory consumed will always be equal +// to the high-water mark consumption. But nsTimerEvents are small and it's +// unusual to have more than a few hundred of them, so this shouldn't be a +// problem in practice. + +class TimerEventAllocator { + private: + struct FreeEntry { + FreeEntry* mNext; + }; + + ArenaAllocator<4096> mPool MOZ_GUARDED_BY(mMonitor); + FreeEntry* mFirstFree MOZ_GUARDED_BY(mMonitor); + mozilla::Monitor mMonitor; + + public: + TimerEventAllocator() + : mFirstFree(nullptr), mMonitor("TimerEventAllocator") {} + + ~TimerEventAllocator() = default; + + void* Alloc(size_t aSize); + void Free(void* aPtr); +}; + +} // namespace + +// This is a nsICancelableRunnable because we can dispatch it to Workers and +// those can be shut down at any time, and in these cases, Cancel() is called +// instead of Run(). +class nsTimerEvent final : public CancelableRunnable { + public: + NS_IMETHOD Run() override; + + nsresult Cancel() override { + mTimer->Cancel(); + return NS_OK; + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +#endif + + explicit nsTimerEvent(already_AddRefed<nsTimerImpl> aTimer, + ProfilerThreadId aTimerThreadId) + : mozilla::CancelableRunnable("nsTimerEvent"), + mTimer(aTimer), + mGeneration(mTimer->GetGeneration()), + mTimerThreadId(aTimerThreadId) { + // Note: We override operator new for this class, and the override is + // fallible! + sAllocatorUsers++; + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug) || + profiler_thread_is_being_profiled_for_markers(mTimerThreadId)) { + mInitTime = TimeStamp::Now(); + } + } + + static void Init(); + static void Shutdown(); + static void DeleteAllocatorIfNeeded(); + + static void* operator new(size_t aSize) noexcept(true) { + return sAllocator->Alloc(aSize); + } + void operator delete(void* aPtr) { + sAllocator->Free(aPtr); + sAllocatorUsers--; + DeleteAllocatorIfNeeded(); + } + + already_AddRefed<nsTimerImpl> ForgetTimer() { return mTimer.forget(); } + + private: + nsTimerEvent(const nsTimerEvent&) = delete; + nsTimerEvent& operator=(const nsTimerEvent&) = delete; + nsTimerEvent& operator=(const nsTimerEvent&&) = delete; + + ~nsTimerEvent() { + MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, + "This will result in us attempting to deallocate the " + "nsTimerEvent allocator twice"); + } + + TimeStamp mInitTime; + RefPtr<nsTimerImpl> mTimer; + const int32_t mGeneration; + ProfilerThreadId mTimerThreadId; + + static TimerEventAllocator* sAllocator; + + static Atomic<int32_t, SequentiallyConsistent> sAllocatorUsers; + static Atomic<bool, SequentiallyConsistent> sCanDeleteAllocator; +}; + +TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; +Atomic<int32_t, SequentiallyConsistent> nsTimerEvent::sAllocatorUsers; +Atomic<bool, SequentiallyConsistent> nsTimerEvent::sCanDeleteAllocator; + +namespace { + +void* TimerEventAllocator::Alloc(size_t aSize) { + MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); + + mozilla::MonitorAutoLock lock(mMonitor); + + void* p; + if (mFirstFree) { + p = mFirstFree; + mFirstFree = mFirstFree->mNext; + } else { + p = mPool.Allocate(aSize, fallible); + } + + return p; +} + +void TimerEventAllocator::Free(void* aPtr) { + mozilla::MonitorAutoLock lock(mMonitor); + + FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr); + + entry->mNext = mFirstFree; + mFirstFree = entry; +} + +} // namespace + +struct TimerMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("Timer"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint32_t aDelay, uint8_t aType, + MarkerThreadId aThreadId, bool aCanceled) { + aWriter.IntProperty("delay", aDelay); + if (!aThreadId.IsUnspecified()) { + // Tech note: If `ToNumber()` returns a uint64_t, the conversion to + // int64_t is "implementation-defined" before C++20. This is + // acceptable here, because this is a one-way conversion to a unique + // identifier that's used to visually separate data by thread on the + // front-end. + aWriter.IntProperty( + "threadId", static_cast<int64_t>(aThreadId.ThreadId().ToNumber())); + } + if (aCanceled) { + aWriter.BoolProperty("canceled", true); + // Show a red 'X' as a prefix on the marker chart for canceled timers. + aWriter.StringProperty("prefix", "âŒ"); + } + + // The string property for the timer type is not written when the type is + // one shot, as that's the type used almost all the time, and that would + // consume space in the profiler buffer and then in the profile JSON, + // getting in the way of capturing long power profiles. + // Bug 1815677 might make this cheap to capture. + if (aType != nsITimer::TYPE_ONE_SHOT) { + if (aType == nsITimer::TYPE_REPEATING_SLACK) { + aWriter.StringProperty("ttype", "repeating slack"); + } else if (aType == nsITimer::TYPE_REPEATING_PRECISE) { + aWriter.StringProperty("ttype", "repeating precise"); + } else if (aType == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP) { + aWriter.StringProperty("ttype", "repeating precise can skip"); + } else if (aType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY) { + aWriter.StringProperty("ttype", "repeating slack low priority"); + } else if (aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY) { + aWriter.StringProperty("ttype", "low priority"); + } + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("delay", "Delay", MS::Format::Milliseconds); + schema.AddKeyLabelFormat("ttype", "Timer Type", MS::Format::String); + schema.AddKeyLabelFormat("canceled", "Canceled", MS::Format::String); + schema.SetChartLabel("{marker.data.prefix} {marker.data.delay}"); + schema.SetTableLabel( + "{marker.name} - {marker.data.prefix} {marker.data.delay}"); + return schema; + } +}; + +struct AddRemoveTimerMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("AddRemoveTimer"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aTimerName, + uint32_t aDelay, MarkerThreadId aThreadId) { + aWriter.StringProperty("name", aTimerName); + aWriter.IntProperty("delay", aDelay); + if (!aThreadId.IsUnspecified()) { + // Tech note: If `ToNumber()` returns a uint64_t, the conversion to + // int64_t is "implementation-defined" before C++20. This is + // acceptable here, because this is a one-way conversion to a unique + // identifier that's used to visually separate data by thread on the + // front-end. + aWriter.IntProperty( + "threadId", static_cast<int64_t>(aThreadId.ThreadId().ToNumber())); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String, + MS::Searchable::Searchable); + schema.AddKeyLabelFormat("delay", "Delay", MS::Format::Milliseconds); + schema.SetTableLabel( + "{marker.name} - {marker.data.name} - {marker.data.delay}"); + return schema; + } +}; + +void nsTimerEvent::Init() { sAllocator = new TimerEventAllocator(); } + +void nsTimerEvent::Shutdown() { + sCanDeleteAllocator = true; + DeleteAllocatorIfNeeded(); +} + +void nsTimerEvent::DeleteAllocatorIfNeeded() { + if (sCanDeleteAllocator && sAllocatorUsers == 0) { + delete sAllocator; + sAllocator = nullptr; + } +} + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +nsTimerEvent::GetName(nsACString& aName) { + bool current; + MOZ_RELEASE_ASSERT( + NS_SUCCEEDED(mTimer->mEventTarget->IsOnCurrentThread(¤t)) && + current); + + mTimer->GetName(aName); + return NS_OK; +} +#endif + +NS_IMETHODIMP +nsTimerEvent::Run() { + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + TimeStamp now = TimeStamp::Now(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", this, + (now - mInitTime).ToMilliseconds())); + } + + if (profiler_thread_is_being_profiled_for_markers(mTimerThreadId)) { + MutexAutoLock lock(mTimer->mMutex); + nsAutoCString name; + mTimer->GetName(name, lock); + // This adds a marker with the timer name as the marker name, to make it + // obvious which timers are being used. This marker will be useful to + // understand which timers might be added and firing excessively often. + profiler_add_marker( + name, geckoprofiler::category::TIMER, + MarkerOptions(MOZ_LIKELY(mInitTime) + ? MarkerTiming::Interval( + mTimer->mTimeout - mTimer->mDelay, mInitTime) + : MarkerTiming::IntervalUntilNowFrom( + mTimer->mTimeout - mTimer->mDelay), + MarkerThreadId(mTimerThreadId)), + TimerMarker{}, mTimer->mDelay.ToMilliseconds(), mTimer->mType, + MarkerThreadId::CurrentThread(), false); + // This marker is meant to help understand the behavior of the timer thread. + profiler_add_marker( + "PostTimerEvent", geckoprofiler::category::OTHER, + MarkerOptions(MOZ_LIKELY(mInitTime) + ? MarkerTiming::IntervalUntilNowFrom(mInitTime) + : MarkerTiming::InstantNow(), + MarkerThreadId(mTimerThreadId)), + AddRemoveTimerMarker{}, name, mTimer->mDelay.ToMilliseconds(), + MarkerThreadId::CurrentThread()); + } + + mTimer->Fire(mGeneration); + + return NS_OK; +} + +nsresult TimerThread::Init() { + mMonitor.AssertCurrentThreadOwns(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("TimerThread::Init [%d]\n", mInitialized)); + + if (!mInitialized) { + nsTimerEvent::Init(); + + // We hold on to mThread to keep the thread alive. + nsresult rv = + NS_NewNamedThread("Timer", getter_AddRefs(mThread), this, + {.stackSize = nsIThreadManager::DEFAULT_STACK_SIZE, + .blockDispatch = true}); + if (NS_FAILED(rv)) { + mThread = nullptr; + } else { + RefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this); + if (NS_IsMainThread()) { + r->Run(); + } else { + NS_DispatchToMainThread(r); + } + } + + mInitialized = true; + } + + if (!mThread) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult TimerThread::Shutdown() { + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown begin\n")); + + if (!mThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsTArray<RefPtr<nsTimerImpl>> timers; + { + // lock scope + MonitorAutoLock lock(mMonitor); + + mShutdown = true; + + // notify the cond var so that Run() can return + if (mWaiting) { + mNotified = true; + mMonitor.Notify(); + } + + // Need to copy content of mTimers array to a local array + // because call to timers' Cancel() (and release its self) + // must not be done under the lock. Destructor of a callback + // might potentially call some code reentering the same lock + // that leads to unexpected behavior or deadlock. + // See bug 422472. + timers.SetCapacity(mTimers.Length()); + for (Entry& entry : mTimers) { + if (entry.Value()) { + timers.AppendElement(entry.Take()); + } + } + + mTimers.Clear(); + } + + for (const RefPtr<nsTimerImpl>& timer : timers) { + MOZ_ASSERT(timer); + timer->Cancel(); + } + + mThread->Shutdown(); // wait for the thread to die + + nsTimerEvent::Shutdown(); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n")); + return NS_OK; +} + +namespace { + +struct MicrosecondsToInterval { + PRIntervalTime operator[](size_t aMs) const { + return PR_MicrosecondsToInterval(aMs); + } +}; + +struct IntervalComparator { + int operator()(PRIntervalTime aInterval) const { + return (0 < aInterval) ? -1 : 1; + } +}; + +} // namespace + +#ifdef DEBUG +void TimerThread::VerifyTimerListConsistency() const { + mMonitor.AssertCurrentThreadOwns(); + + // Find the first non-canceled timer (and check its cached timeout if we find + // it). + const size_t timerCount = mTimers.Length(); + size_t lastNonCanceledTimerIndex = 0; + while (lastNonCanceledTimerIndex < timerCount && + !mTimers[lastNonCanceledTimerIndex].Value()) { + ++lastNonCanceledTimerIndex; + } + MOZ_ASSERT(lastNonCanceledTimerIndex == timerCount || + mTimers[lastNonCanceledTimerIndex].Value()); + MOZ_ASSERT(lastNonCanceledTimerIndex == timerCount || + mTimers[lastNonCanceledTimerIndex].Value()->mTimeout == + mTimers[lastNonCanceledTimerIndex].Timeout()); + + // Verify that mTimers is sorted and the cached timeouts are consistent. + for (size_t timerIndex = lastNonCanceledTimerIndex + 1; + timerIndex < timerCount; ++timerIndex) { + if (mTimers[timerIndex].Value()) { + MOZ_ASSERT(mTimers[timerIndex].Timeout() == + mTimers[timerIndex].Value()->mTimeout); + MOZ_ASSERT(mTimers[timerIndex].Timeout() >= + mTimers[lastNonCanceledTimerIndex].Timeout()); + lastNonCanceledTimerIndex = timerIndex; + } + } +} +#endif + +size_t TimerThread::ComputeTimerInsertionIndex(const TimeStamp& timeout) const { + mMonitor.AssertCurrentThreadOwns(); + + const size_t timerCount = mTimers.Length(); + + size_t firstGtIndex = 0; + while (firstGtIndex < timerCount && + (!mTimers[firstGtIndex].Value() || + mTimers[firstGtIndex].Timeout() <= timeout)) { + ++firstGtIndex; + } + + return firstGtIndex; +} + +TimeStamp TimerThread::ComputeWakeupTimeFromTimers() const { + mMonitor.AssertCurrentThreadOwns(); + + // Timer list should be non-empty and first timer should always be + // non-canceled at this point and we rely on that here. + MOZ_ASSERT(!mTimers.IsEmpty()); + MOZ_ASSERT(mTimers[0].Value()); + + // Overview: Find the last timer in the list that can be "bundled" together in + // the same wake-up with mTimers[0] and use its timeout as our target wake-up + // time. + + // bundleWakeup is when we should wake up in order to be able to fire all of + // the timers in our selected bundle. It will always be the timeout of the + // last timer in the bundle. + TimeStamp bundleWakeup = mTimers[0].Timeout(); + + // cutoffTime is the latest that we can wake up for the timers currently + // accepted into the bundle. These needs to be updated as we go through the + // list because later timers may have more strict delay tolerances. + const TimeDuration minTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_minimum_firing_delay_tolerance_ms()); + const TimeDuration maxTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_maximum_firing_delay_tolerance_ms()); + TimeStamp cutoffTime = + bundleWakeup + ComputeAcceptableFiringDelay(mTimers[0].Delay(), + minTimerDelay, maxTimerDelay); + + const size_t timerCount = mTimers.Length(); + for (size_t entryIndex = 1; entryIndex < timerCount; ++entryIndex) { + const Entry& curEntry = mTimers[entryIndex]; + const nsTimerImpl* curTimer = curEntry.Value(); + if (!curTimer) { + // Canceled timer - skip it + continue; + } + + const TimeStamp curTimerDue = curEntry.Timeout(); + if (curTimerDue > cutoffTime) { + // Can't include this timer in the bundle - it fires too late. + break; + } + + // This timer can be included in the bundle. Update bundleWakeup and + // cutoffTime. + bundleWakeup = curTimerDue; + cutoffTime = std::min( + curTimerDue + ComputeAcceptableFiringDelay( + curEntry.Delay(), minTimerDelay, maxTimerDelay), + cutoffTime); + MOZ_ASSERT(bundleWakeup <= cutoffTime); + } + +#if !defined(XP_WIN) + // Due to the fact that, on Windows, each TimeStamp object holds two distinct + // "values", this assert is not valid there. See bug 1829983 for the details. + MOZ_ASSERT(bundleWakeup - mTimers[0].Timeout() <= + ComputeAcceptableFiringDelay(mTimers[0].Delay(), minTimerDelay, + maxTimerDelay)); +#endif + + return bundleWakeup; +} + +TimeDuration TimerThread::ComputeAcceptableFiringDelay( + TimeDuration timerDuration, TimeDuration minDelay, + TimeDuration maxDelay) const { + // Use the timer's duration divided by this value as a base for how much + // firing delay a timer can accept. 8 was chosen specifically because it is a + // power of two which means that this division turns nicely into a shift. + constexpr int64_t timerDurationDivider = 8; + static_assert(IsPowerOfTwo(static_cast<uint64_t>(timerDurationDivider))); + const TimeDuration tmp = timerDuration / timerDurationDivider; + return std::min(std::max(minDelay, tmp), maxDelay); +} + +NS_IMETHODIMP +TimerThread::Run() { + MonitorAutoLock lock(mMonitor); + + mProfilerThreadId = profiler_current_thread_id(); + + // TODO: Make mAllowedEarlyFiringMicroseconds const and initialize it in the + // constructor. + mAllowedEarlyFiringMicroseconds = 250; + const TimeDuration allowedEarlyFiring = + TimeDuration::FromMicroseconds(mAllowedEarlyFiringMicroseconds); + + bool forceRunNextTimer = false; + + // Queue for tracking of how many timers are fired on each wake-up. We need to + // buffer these locally and only send off to glean occasionally to avoid + // performance hit. + static constexpr size_t kMaxQueuedTimerFired = 128; + size_t queuedTimerFiredCount = 0; + AutoTArray<uint64_t, kMaxQueuedTimerFired> queuedTimersFiredPerWakeup; + queuedTimersFiredPerWakeup.SetLengthAndRetainStorage(kMaxQueuedTimerFired); + +#ifdef XP_WIN + // kTimerPeriodEvalIntervalSec is the minimum amount of time that must pass + // before we will consider changing the timer period again. + static constexpr float kTimerPeriodEvalIntervalSec = 2.0f; + const TimeDuration timerPeriodEvalInterval = + TimeDuration::FromSeconds(kTimerPeriodEvalIntervalSec); + TimeStamp nextTimerPeriodEval = TimeStamp::Now() + timerPeriodEvalInterval; + + // If this is false, we will perform all of the logic but will stop short of + // actually changing the timer period. + const bool adjustTimerPeriod = + StaticPrefs::timer_auto_increase_timer_resolution(); + UINT lastTimePeriodSet = ComputeDesiredTimerPeriod(); + + if (adjustTimerPeriod) { + timeBeginPeriod(lastTimePeriodSet); + } +#endif + + uint64_t timersFiredThisWakeup = 0; + while (!mShutdown) { + // Have to use PRIntervalTime here, since PR_WaitCondVar takes it + TimeDuration waitFor; + bool forceRunThisTimer = forceRunNextTimer; + forceRunNextTimer = false; + +#ifdef DEBUG + VerifyTimerListConsistency(); +#endif + + if (mSleeping) { + // Sleep for 0.1 seconds while not firing timers. + uint32_t milliseconds = 100; + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + milliseconds = ChaosMode::randomUint32LessThan(200); + } + waitFor = TimeDuration::FromMilliseconds(milliseconds); + } else { + waitFor = TimeDuration::Forever(); + TimeStamp now = TimeStamp::Now(); + +#ifdef XP_WIN + if (now >= nextTimerPeriodEval) { + const UINT newTimePeriod = ComputeDesiredTimerPeriod(); + if (newTimePeriod != lastTimePeriodSet) { + if (adjustTimerPeriod) { + timeEndPeriod(lastTimePeriodSet); + timeBeginPeriod(newTimePeriod); + } + lastTimePeriodSet = newTimePeriod; + } + nextTimerPeriodEval = now + timerPeriodEvalInterval; + } +#endif + +#if TIMER_THREAD_STATISTICS + if (!mNotified && !mIntendedWakeupTime.IsNull() && + now < mIntendedWakeupTime) { + ++mEarlyWakeups; + const double earlinessms = (mIntendedWakeupTime - now).ToMilliseconds(); + mTotalEarlyWakeupTime += earlinessms; + } +#endif + + RemoveLeadingCanceledTimersInternal(); + + if (!mTimers.IsEmpty()) { + if (now + allowedEarlyFiring >= mTimers[0].Value()->mTimeout || + forceRunThisTimer) { + next: + // NB: AddRef before the Release under RemoveTimerInternal to avoid + // mRefCnt passing through zero, in case all other refs than the one + // from mTimers have gone away (the last non-mTimers[i]-ref's Release + // must be racing with us, blocked in gThread->RemoveTimer waiting + // for TimerThread::mMonitor, under nsTimerImpl::Release. + + RefPtr<nsTimerImpl> timerRef(mTimers[0].Take()); + RemoveFirstTimerInternal(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("Timer thread woke up %fms from when it was supposed to\n", + fabs((now - timerRef->mTimeout).ToMilliseconds()))); + + // We are going to let the call to PostTimerEvent here handle the + // release of the timer so that we don't end up releasing the timer + // on the TimerThread instead of on the thread it targets. + { + ++timersFiredThisWakeup; + LogTimerEvent::Run run(timerRef.get()); + PostTimerEvent(timerRef.forget()); + } + + if (mShutdown) { + break; + } + + // Update now, as PostTimerEvent plus the locking may have taken a + // tick or two, and we may goto next below. + now = TimeStamp::Now(); + } + } + + RemoveLeadingCanceledTimersInternal(); + + if (!mTimers.IsEmpty()) { + TimeStamp timeout = mTimers[0].Value()->mTimeout; + + // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer + // is due now or overdue. + // + // Note that we can only sleep for integer values of a certain + // resolution. We use mAllowedEarlyFiringMicroseconds, calculated + // before, to do the optimal rounding (i.e., of how to decide what + // interval is so small we should not wait at all). + double microseconds = (timeout - now).ToMicroseconds(); + + // The mean value of sFractions must be 1 to ensure that the average of + // a long sequence of timeouts converges to the actual sum of their + // times. + static constexpr double sChaosFractions[] = {0.0, 0.25, 0.5, 0.75, + 1.0, 1.75, 2.75}; + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + microseconds *= sChaosFractions[ChaosMode::randomUint32LessThan( + ArrayLength(sChaosFractions))]; + forceRunNextTimer = true; + } + + if (microseconds < mAllowedEarlyFiringMicroseconds) { + forceRunNextTimer = false; + goto next; // round down; execute event now + } + + // TECHNICAL NOTE: Determining waitFor (by subtracting |now| from our + // desired wake-up time) at this point is not ideal. For one thing, the + // |now| that we have at this point is somewhat old. Secondly, there is + // quite a bit of code between here and where we actually use waitFor to + // request sleep. If I am thinking about this correctly, both of these + // will contribute to us requesting more sleep than is actually needed + // to wake up at our desired time. We could avoid this problem by only + // determining our desired wake-up time here and then calculating the + // wait time when we're actually about to sleep. + const TimeStamp wakeupTime = ComputeWakeupTimeFromTimers(); + waitFor = wakeupTime - now; + + // If this were to fail that would mean that we had more timers that we + // should have fired. + MOZ_ASSERT(!waitFor.IsZero()); + + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + // If chaos mode is active then mess with the amount of time that we + // request to sleep (without changing what we record as our expected + // wake-up time). This will simulate unintended early/late wake-ups. + const double waitInMs = waitFor.ToMilliseconds(); + const double chaosWaitInMs = + waitInMs * sChaosFractions[ChaosMode::randomUint32LessThan( + ArrayLength(sChaosFractions))]; + waitFor = TimeDuration::FromMilliseconds(chaosWaitInMs); + } + + mIntendedWakeupTime = wakeupTime; + } else { + mIntendedWakeupTime = TimeStamp{}; + } + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + if (waitFor == TimeDuration::Forever()) + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("waiting forever\n")); + else + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("waiting for %f\n", waitFor.ToMilliseconds())); + } + } + + { + // About to sleep - let's make note of how many timers we processed and + // see if we should send out a new batch of telemetry. + queuedTimersFiredPerWakeup[queuedTimerFiredCount] = timersFiredThisWakeup; + ++queuedTimerFiredCount; + if (queuedTimerFiredCount == kMaxQueuedTimerFired) { + glean::timer_thread::timers_fired_per_wakeup.AccumulateSamples( + queuedTimersFiredPerWakeup); + queuedTimerFiredCount = 0; + } + } + +#if TIMER_THREAD_STATISTICS + { + size_t bucketIndex = 0; + while (bucketIndex < sTimersFiredPerWakeupBucketCount - 1 && + timersFiredThisWakeup > + sTimersFiredPerWakeupThresholds[bucketIndex]) { + ++bucketIndex; + } + MOZ_ASSERT(bucketIndex < sTimersFiredPerWakeupBucketCount); + ++mTimersFiredPerWakeup[bucketIndex]; + + ++mTotalWakeupCount; + if (mNotified) { + ++mTimersFiredPerNotifiedWakeup[bucketIndex]; + ++mTotalNotifiedWakeupCount; + } else { + ++mTimersFiredPerUnnotifiedWakeup[bucketIndex]; + ++mTotalUnnotifiedWakeupCount; + } + } +#endif + + timersFiredThisWakeup = 0; + + mWaiting = true; + mNotified = false; + + { + AUTO_PROFILER_TRACING_MARKER("TimerThread", "Wait", OTHER); + mMonitor.Wait(waitFor); + } + if (mNotified) { + forceRunNextTimer = false; + } + mWaiting = false; + } + + // About to shut down - let's send out the final batch of timers fired counts. + if (queuedTimerFiredCount != 0) { + queuedTimersFiredPerWakeup.SetLengthAndRetainStorage(queuedTimerFiredCount); + glean::timer_thread::timers_fired_per_wakeup.AccumulateSamples( + queuedTimersFiredPerWakeup); + } + +#ifdef XP_WIN + // About to shut down - let's finish off the last time period that we set. + if (adjustTimerPeriod) { + timeEndPeriod(lastTimePeriodSet); + } +#endif + + return NS_OK; +} + +nsresult TimerThread::AddTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + MonitorAutoLock lock(mMonitor); + AUTO_TIMERS_STATS(TimerThread_AddTimer); + + if (!aTimer->mEventTarget) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = Init(); + if (NS_FAILED(rv)) { + return rv; + } + + // Awaken the timer thread if: + // - This timer needs to fire *before* the Timer Thread is scheduled to wake + // up. + // AND/OR + // - The delay is 0, which is usually meant to be run as soon as possible. + // Note: Even if the thread is scheduled to wake up now/soon, on some + // systems there could be a significant delay compared to notifying, which + // is almost immediate; and some users of 0-delay depend on it being this + // fast! + const TimeDuration minTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_minimum_firing_delay_tolerance_ms()); + const TimeDuration maxTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_maximum_firing_delay_tolerance_ms()); + const TimeDuration firingDelay = ComputeAcceptableFiringDelay( + aTimer->mDelay, minTimerDelay, maxTimerDelay); + const bool firingBeforeNextWakeup = + mIntendedWakeupTime.IsNull() || + (aTimer->mTimeout + firingDelay < mIntendedWakeupTime); + const bool wakeUpTimerThread = + mWaiting && (firingBeforeNextWakeup || aTimer->mDelay.IsZero()); + +#if TIMER_THREAD_STATISTICS + if (mTotalTimersAdded == 0) { + mFirstTimerAdded = TimeStamp::Now(); + } + ++mTotalTimersAdded; +#endif + + // Add the timer to our list. + if (!AddTimerInternal(*aTimer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (wakeUpTimerThread) { + mNotified = true; + mMonitor.Notify(); + } + + if (profiler_thread_is_being_profiled_for_markers(mProfilerThreadId)) { + nsAutoCString name; + aTimer->GetName(name, aProofOfLock); + + nsLiteralCString prefix("Anonymous_"); + profiler_add_marker( + "AddTimer", geckoprofiler::category::OTHER, + MarkerOptions(MarkerThreadId(mProfilerThreadId), + MarkerStack::MaybeCapture( + name.Equals("nonfunction:JS") || + StringHead(name, prefix.Length()) == prefix)), + AddRemoveTimerMarker{}, name, aTimer->mDelay.ToMilliseconds(), + MarkerThreadId::CurrentThread()); + } + + return NS_OK; +} + +nsresult TimerThread::RemoveTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + MonitorAutoLock lock(mMonitor); + AUTO_TIMERS_STATS(TimerThread_RemoveTimer); + + // Remove the timer from our array. Tell callers that aTimer was not found + // by returning NS_ERROR_NOT_AVAILABLE. + + if (!RemoveTimerInternal(*aTimer)) { + return NS_ERROR_NOT_AVAILABLE; + } + +#if TIMER_THREAD_STATISTICS + ++mTotalTimersRemoved; +#endif + + // Note: The timer thread is *not* awoken. + // The removed-timer entry is just left null, and will be reused (by a new or + // re-set timer) or discarded (when the timer thread logic handles non-null + // timers around it). + // If this was the front timer, and in the unlikely case that its entry is not + // soon reused by a re-set timer, the timer thread will wake up at the + // previously-scheduled time, but will quickly notice that there is no actual + // pending timer, and will restart its wait until the following real timeout. + + if (profiler_thread_is_being_profiled_for_markers(mProfilerThreadId)) { + nsAutoCString name; + aTimer->GetName(name, aProofOfLock); + + nsLiteralCString prefix("Anonymous_"); + // This marker is meant to help understand the behavior of the timer thread. + profiler_add_marker( + "RemoveTimer", geckoprofiler::category::OTHER, + MarkerOptions(MarkerThreadId(mProfilerThreadId), + MarkerStack::MaybeCapture( + name.Equals("nonfunction:JS") || + StringHead(name, prefix.Length()) == prefix)), + AddRemoveTimerMarker{}, name, aTimer->mDelay.ToMilliseconds(), + MarkerThreadId::CurrentThread()); + // This adds a marker with the timer name as the marker name, to make it + // obvious which timers are being used. This marker will be useful to + // understand which timers might be added and removed excessively often. + profiler_add_marker(name, geckoprofiler::category::TIMER, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom( + aTimer->mTimeout - aTimer->mDelay), + MarkerThreadId(mProfilerThreadId)), + TimerMarker{}, aTimer->mDelay.ToMilliseconds(), + aTimer->mType, MarkerThreadId::CurrentThread(), true); + } + + return NS_OK; +} + +TimeStamp TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound) { + MonitorAutoLock lock(mMonitor); + AUTO_TIMERS_STATS(TimerThread_FindNextFireTimeForCurrentThread); + + for (const Entry& entry : mTimers) { + const nsTimerImpl* timer = entry.Value(); + if (timer) { + if (entry.Timeout() > aDefault) { + return aDefault; + } + + // Don't yield to timers created with the *_LOW_PRIORITY type. + if (!timer->IsLowPriority()) { + bool isOnCurrentThread = false; + nsresult rv = + timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread); + if (NS_SUCCEEDED(rv) && isOnCurrentThread) { + return entry.Timeout(); + } + } + + if (aSearchBound == 0) { + // Couldn't find any non-low priority timers for the current thread. + // Return a compromise between a very short and a long idle time. + TimeStamp fallbackDeadline = + TimeStamp::Now() + TimeDuration::FromMilliseconds(16); + return fallbackDeadline < aDefault ? fallbackDeadline : aDefault; + } + + --aSearchBound; + } + } + + // No timers for this thread, return the default. + return aDefault; +} + +// This function must be called from within a lock +// Also: we hold the mutex for the nsTimerImpl. +bool TimerThread::AddTimerInternal(nsTimerImpl& aTimer) { + mMonitor.AssertCurrentThreadOwns(); + aTimer.mMutex.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal); + if (mShutdown) { + return false; + } + + LogTimerEvent::LogDispatch(&aTimer); + + const TimeStamp& timeout = aTimer.mTimeout; + const size_t insertionIndex = ComputeTimerInsertionIndex(timeout); + + if (insertionIndex != 0 && !mTimers[insertionIndex - 1].Value()) { + // Very common scenario in practice: The timer just before the insertion + // point is canceled, overwrite it. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_overwrite_before); + mTimers[insertionIndex - 1] = Entry{aTimer}; + return true; + } + + const size_t length = mTimers.Length(); + if (insertionIndex == length) { + // We're at the end (including it's the very first insertion), add new timer + // at the end. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_append); + return mTimers.AppendElement(Entry{aTimer}, mozilla::fallible); + } + + if (!mTimers[insertionIndex].Value()) { + // The timer at the insertion point is canceled, overwrite it. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_overwrite); + mTimers[insertionIndex] = Entry{aTimer}; + return true; + } + + // The new timer has to be inserted. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_insert); + // The capacity should be checked first, because if it needs to be increased + // and the memory allocation fails, only the new timer should be lost. + if (length == mTimers.Capacity() && mTimers[length - 1].Value()) { + // We have reached capacity, and the last entry is not canceled, so we + // really want to increase the capacity in case the extra slot is required. + // To force-expand the array, append a canceled-timer entry with a timestamp + // far in the future. + // This empty Entry may be used below to receive the moved-from previous + // entry. If not, it may be used in a later call if we need to append a new + // timer at the end. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_insert_expand); + if (!mTimers.AppendElement( + Entry{mTimers[length - 1].Timeout() + + TimeDuration::FromSeconds(365.0 * 24.0 * 60.0 * 60.0)}, + mozilla::fallible)) { + return false; + } + } + + // Extract the timer at the insertion point, and put the new timer in its + // place. + Entry extractedEntry = std::exchange(mTimers[insertionIndex], Entry{aTimer}); + // Following entries can be pushed until we hit a canceled timer or the end. + for (size_t i = insertionIndex + 1; i < length; ++i) { + Entry& entryRef = mTimers[i]; + if (!entryRef.Value()) { + // Canceled entry, overwrite it with the extracted entry from before. + COUNT_TIMERS_STATS(TimerThread_AddTimerInternal_insert_overwrite); + entryRef = std::move(extractedEntry); + return true; + } + // Write extracted entry from before, and extract current entry. + COUNT_TIMERS_STATS(TimerThread_AddTimerInternal_insert_shifts); + std::swap(entryRef, extractedEntry); + } + // We've reached the end of the list, with still one extracted entry to + // re-insert. We've checked the capacity above, this cannot fail. + COUNT_TIMERS_STATS(TimerThread_AddTimerInternal_insert_append); + mTimers.AppendElement(std::move(extractedEntry)); + return true; +} + +// This function must be called from within a lock +// Also: we hold the mutex for the nsTimerImpl. +bool TimerThread::RemoveTimerInternal(nsTimerImpl& aTimer) { + mMonitor.AssertCurrentThreadOwns(); + aTimer.mMutex.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_RemoveTimerInternal); + if (!aTimer.IsInTimerThread()) { + COUNT_TIMERS_STATS(TimerThread_RemoveTimerInternal_not_in_list); + return false; + } + AUTO_TIMERS_STATS(TimerThread_RemoveTimerInternal_in_list); + for (auto& entry : mTimers) { + if (entry.Value() == &aTimer) { + entry.Forget(); + return true; + } + } + MOZ_ASSERT(!aTimer.IsInTimerThread(), + "Not found in the list but it should be!?"); + return false; +} + +void TimerThread::RemoveLeadingCanceledTimersInternal() { + mMonitor.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_RemoveLeadingCanceledTimersInternal); + + size_t toRemove = 0; + while (toRemove < mTimers.Length() && !mTimers[toRemove].Value()) { + ++toRemove; + } + mTimers.RemoveElementsAt(0, toRemove); +} + +void TimerThread::RemoveFirstTimerInternal() { + mMonitor.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_RemoveFirstTimerInternal); + MOZ_ASSERT(!mTimers.IsEmpty()); + mTimers.RemoveElementAt(0); +} + +void TimerThread::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef) { + mMonitor.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_PostTimerEvent); + + RefPtr<nsTimerImpl> timer(aTimerRef); + +#if TIMER_THREAD_STATISTICS + const double actualFiringDelay = + std::max((TimeStamp::Now() - timer->mTimeout).ToMilliseconds(), 0.0); + if (mNotified) { + ++mTotalTimersFiredNotified; + mTotalActualTimerFiringDelayNotified += actualFiringDelay; + } else { + ++mTotalTimersFiredUnnotified; + mTotalActualTimerFiringDelayUnnotified += actualFiringDelay; + } +#endif + + if (!timer->mEventTarget) { + NS_ERROR("Attempt to post timer event to NULL event target"); + return; + } + + // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. + + // Since we already addref'd 'timer', we don't need to addref here. + // We will release either in ~nsTimerEvent(), or pass the reference back to + // the caller. We need to copy the generation number from this timer into the + // event, so we can avoid firing a timer that was re-initialized after being + // canceled. + + nsCOMPtr<nsIEventTarget> target = timer->mEventTarget; + + void* p = nsTimerEvent::operator new(sizeof(nsTimerEvent)); + if (!p) { + return; + } + RefPtr<nsTimerEvent> event = + ::new (KnownNotNull, p) nsTimerEvent(timer.forget(), mProfilerThreadId); + + nsresult rv; + { + // We release mMonitor around the Dispatch because if the Dispatch interacts + // with the timer API we'll deadlock. + MonitorAutoUnlock unlock(mMonitor); + rv = target->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + timer = event->ForgetTimer(); + // We do this to avoid possible deadlock by taking the two locks in a + // different order than is used in RemoveTimer(). RemoveTimer() has + // aTimer->mMutex first. We use timer.get() to keep static analysis + // happy + // NOTE: I'm not sure that any of the below is actually necessary. It + // seems to me that the timer that we're trying to fire will have already + // been removed prior to this. + MutexAutoLock lock1(timer.get()->mMutex); + MonitorAutoLock lock2(mMonitor); + RemoveTimerInternal(*timer); + } + } +} + +void TimerThread::DoBeforeSleep() { + // Mainthread + MonitorAutoLock lock(mMonitor); + mSleeping = true; +} + +// Note: wake may be notified without preceding sleep notification +void TimerThread::DoAfterSleep() { + // Mainthread + MonitorAutoLock lock(mMonitor); + mSleeping = false; + + // Wake up the timer thread to re-process the array to ensure the sleep delay + // is correct, and fire any expired timers (perhaps quite a few) + mNotified = true; + PROFILER_MARKER_UNTYPED("AfterSleep", OTHER, + MarkerThreadId(mProfilerThreadId)); + mMonitor.Notify(); +} + +NS_IMETHODIMP +TimerThread::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (strcmp(aTopic, "ipc:process-priority-changed") == 0) { + nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); + MOZ_ASSERT(props != nullptr); + + int32_t priority = static_cast<int32_t>(hal::PROCESS_PRIORITY_UNKNOWN); + props->GetPropertyAsInt32(u"priority"_ns, &priority); + mCachedPriority.store(static_cast<hal::ProcessPriority>(priority), + std::memory_order_relaxed); + } + + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return NS_OK; + } + + if (strcmp(aTopic, "sleep_notification") == 0 || + strcmp(aTopic, "suspend_process_notification") == 0) { + DoBeforeSleep(); + } else if (strcmp(aTopic, "wake_notification") == 0 || + strcmp(aTopic, "resume_process_notification") == 0) { + DoAfterSleep(); + } + + return NS_OK; +} + +uint32_t TimerThread::AllowedEarlyFiringMicroseconds() { + MonitorAutoLock lock(mMonitor); + return mAllowedEarlyFiringMicroseconds; +} + +#if TIMER_THREAD_STATISTICS +void TimerThread::PrintStatistics() const { + mMonitor.AssertCurrentThreadOwns(); + + const TimeStamp freshNow = TimeStamp::Now(); + const double timeElapsed = mFirstTimerAdded.IsNull() + ? 0.0 + : (freshNow - mFirstTimerAdded).ToSeconds(); + printf_stderr("TimerThread Stats (Total time %8.2fs)\n", timeElapsed); + + printf_stderr("Added: %6llu Removed: %6llu Fired: %6llu\n", mTotalTimersAdded, + mTotalTimersRemoved, + mTotalTimersFiredNotified + mTotalTimersFiredUnnotified); + + auto PrintTimersFiredBucket = + [](const AutoTArray<size_t, sTimersFiredPerWakeupBucketCount>& buckets, + const size_t wakeupCount, const size_t timersFiredCount, + const double totalTimerDelay, const char* label) { + printf_stderr("%s : [", label); + for (size_t bucketVal : buckets) { + printf_stderr(" %5llu", bucketVal); + } + printf_stderr( + " ] Wake-ups/timer %6llu / %6llu (%7.4f) Avg Timer Delay %7.4f\n", + wakeupCount, timersFiredCount, + static_cast<double>(wakeupCount) / timersFiredCount, + totalTimerDelay / timersFiredCount); + }; + + printf_stderr("Wake-ups:\n"); + PrintTimersFiredBucket( + mTimersFiredPerWakeup, mTotalWakeupCount, + mTotalTimersFiredNotified + mTotalTimersFiredUnnotified, + mTotalActualTimerFiringDelayNotified + + mTotalActualTimerFiringDelayUnnotified, + "Total "); + PrintTimersFiredBucket(mTimersFiredPerNotifiedWakeup, + mTotalNotifiedWakeupCount, mTotalTimersFiredNotified, + mTotalActualTimerFiringDelayNotified, "Notified "); + PrintTimersFiredBucket(mTimersFiredPerUnnotifiedWakeup, + mTotalUnnotifiedWakeupCount, + mTotalTimersFiredUnnotified, + mTotalActualTimerFiringDelayUnnotified, "Unnotified "); + + printf_stderr("Early Wake-ups: %6llu Avg: %7.4fms\n", mEarlyWakeups, + mTotalEarlyWakeupTime / mEarlyWakeups); +} +#endif + +/* This nsReadOnlyTimer class is used for the values returned by the + * TimerThread::GetTimers method. + * It is not possible to return a strong reference to the nsTimerImpl + * instance (that could extend the lifetime of the timer and cause it to fire + * a callback pointing to already freed memory) or a weak reference + * (nsSupportsWeakReference doesn't support freeing the referee on a thread + * that isn't the thread that owns the weak reference), so instead the timer + * name, delay and type are copied to a new object. */ +class nsReadOnlyTimer final : public nsITimer { + public: + explicit nsReadOnlyTimer(const nsACString& aName, uint32_t aDelay, + uint32_t aType) + : mName(aName), mDelay(aDelay), mType(aType) {} + NS_DECL_ISUPPORTS + + NS_IMETHOD Init(nsIObserver* aObserver, uint32_t aDelayInMs, + uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD InitWithCallback(nsITimerCallback* aCallback, uint32_t aDelayInMs, + uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD InitHighResolutionWithCallback(nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD Cancel(void) override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD InitWithNamedFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, uint32_t aDelay, + uint32_t aType, + const char* aName) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD InitHighResolutionWithNamedFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, + const mozilla::TimeDuration& aDelay, uint32_t aType, + const char* aName) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD GetName(nsACString& aName) override { + aName = mName; + return NS_OK; + } + NS_IMETHOD GetDelay(uint32_t* aDelay) override { + *aDelay = mDelay; + return NS_OK; + } + NS_IMETHOD SetDelay(uint32_t aDelay) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetType(uint32_t* aType) override { + *aType = mType; + return NS_OK; + } + NS_IMETHOD SetType(uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetClosure(void** aClosure) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetCallback(nsITimerCallback** aCallback) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetTarget(nsIEventTarget** aTarget) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD SetTarget(nsIEventTarget* aTarget) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAllowedEarlyFiringMicroseconds( + uint32_t* aAllowedEarlyFiringMicroseconds) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) override { + return sizeof(*this); + } + + private: + nsCString mName; + uint32_t mDelay; + uint32_t mType; + ~nsReadOnlyTimer() = default; +}; + +NS_IMPL_ISUPPORTS(nsReadOnlyTimer, nsITimer) + +nsresult TimerThread::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) { + nsTArray<RefPtr<nsTimerImpl>> timers; + { + MonitorAutoLock lock(mMonitor); + for (const auto& entry : mTimers) { + nsTimerImpl* timer = entry.Value(); + if (!timer) { + continue; + } + timers.AppendElement(timer); + } + } + + for (nsTimerImpl* timer : timers) { + nsAutoCString name; + timer->GetName(name); + + uint32_t delay; + timer->GetDelay(&delay); + + uint32_t type; + timer->GetType(&type); + + aRetVal.AppendElement(new nsReadOnlyTimer(name, delay, type)); + } + + return NS_OK; +} diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h new file mode 100644 index 0000000000..f6a5827b94 --- /dev/null +++ b/xpcom/threads/TimerThread.h @@ -0,0 +1,247 @@ +/* -*- 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 TimerThread_h___ +#define TimerThread_h___ + +#include "nsIObserver.h" +#include "nsIRunnable.h" +#include "nsIThread.h" + +#include "nsTimerImpl.h" +#include "nsThreadUtils.h" + +#include "nsTArray.h" + +#include "mozilla/Attributes.h" +#include "mozilla/HalTypes.h" +#include "mozilla/Monitor.h" +#include "mozilla/ProfilerUtils.h" + +// Enable this to compute lots of interesting statistics and print them out when +// PrintStatistics() is called. +#define TIMER_THREAD_STATISTICS 0 + +class TimerThread final : public mozilla::Runnable, public nsIObserver { + public: + typedef mozilla::Monitor Monitor; + typedef mozilla::MutexAutoLock MutexAutoLock; + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + + TimerThread(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + + nsresult Shutdown(); + + nsresult AddTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + nsresult RemoveTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + // Considering only the first 'aSearchBound' timers (in firing order), returns + // the timeout of the first non-low-priority timer, on the current thread, + // that will fire before 'aDefault'. If no such timer exists, 'aDefault' is + // returned. + TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound); + + void DoBeforeSleep(); + void DoAfterSleep(); + + bool IsOnTimerThread() const { return mThread->IsOnCurrentThread(); } + + uint32_t AllowedEarlyFiringMicroseconds(); + nsresult GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal); + + private: + ~TimerThread(); + + bool mInitialized; + + // These internal helper methods must be called while mMonitor is held. + // AddTimerInternal returns false if the insertion failed. + bool AddTimerInternal(nsTimerImpl& aTimer) MOZ_REQUIRES(mMonitor); + bool RemoveTimerInternal(nsTimerImpl& aTimer) + MOZ_REQUIRES(mMonitor, aTimer.mMutex); + void RemoveLeadingCanceledTimersInternal() MOZ_REQUIRES(mMonitor); + void RemoveFirstTimerInternal() MOZ_REQUIRES(mMonitor); + nsresult Init() MOZ_REQUIRES(mMonitor); + + void PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef) + MOZ_REQUIRES(mMonitor); + + // Using atomic because this value is written to in one place, and read from + // in another, and those two locations are likely to be executed from separate + // threads. Reads/writes to an aligned value this size should be atomic even + // without using std::atomic, but doing this explicitly provides a good + // reminder that this is accessed from multiple threads. + std::atomic<mozilla::hal::ProcessPriority> mCachedPriority = + mozilla::hal::PROCESS_PRIORITY_UNKNOWN; + + nsCOMPtr<nsIThread> mThread; + // Lock ordering requirements: + // (optional) ThreadWrapper::sMutex -> + // (optional) nsTimerImpl::mMutex -> + // TimerThread::mMonitor + Monitor mMonitor; + + bool mShutdown MOZ_GUARDED_BY(mMonitor); + bool mWaiting MOZ_GUARDED_BY(mMonitor); + bool mNotified MOZ_GUARDED_BY(mMonitor); + bool mSleeping MOZ_GUARDED_BY(mMonitor); + + class Entry final { + public: + explicit Entry(nsTimerImpl& aTimerImpl) + : mTimeout(aTimerImpl.mTimeout), + mDelay(aTimerImpl.mDelay), + mTimerImpl(&aTimerImpl) { + aTimerImpl.SetIsInTimerThread(true); + } + + // Create an already-canceled entry with the given timeout. + explicit Entry(TimeStamp aTimeout) + : mTimeout(std::move(aTimeout)), mTimerImpl(nullptr) {} + + // Don't allow copies, otherwise which one would manage `IsInTimerThread`? + Entry(const Entry&) = delete; + Entry& operator=(const Entry&) = delete; + + // Move-only. + Entry(Entry&&) = default; + Entry& operator=(Entry&&) = default; + + ~Entry() { + if (mTimerImpl) { + mTimerImpl->mMutex.AssertCurrentThreadOwns(); + mTimerImpl->SetIsInTimerThread(false); + } + } + + nsTimerImpl* Value() const { return mTimerImpl; } + + void Forget() { + if (MOZ_UNLIKELY(!mTimerImpl)) { + return; + } + mTimerImpl->mMutex.AssertCurrentThreadOwns(); + mTimerImpl->SetIsInTimerThread(false); + mTimerImpl = nullptr; + } + + // Called with the Monitor held, but not the TimerImpl's mutex + already_AddRefed<nsTimerImpl> Take() { + if (MOZ_LIKELY(mTimerImpl)) { + MOZ_ASSERT(mTimerImpl->IsInTimerThread()); + mTimerImpl->SetIsInTimerThread(false); + } + return mTimerImpl.forget(); + } + + const TimeStamp& Timeout() const { return mTimeout; } + const TimeDuration& Delay() const { return mDelay; } + + private: + // These values are simply cached from the timer. Keeping them here is good + // for cache usage and allows us to avoid worrying about locking conflicts + // with the timer. + TimeStamp mTimeout; + TimeDuration mDelay; + + RefPtr<nsTimerImpl> mTimerImpl; + }; + + // Computes and returns the index in mTimers at which a new timer with the + // specified timeout should be inserted in order to maintain "sorted" order. + size_t ComputeTimerInsertionIndex(const TimeStamp& timeout) const + MOZ_REQUIRES(mMonitor); + + // Computes and returns when we should next try to wake up in order to handle + // the triggering of the timers in mTimers. Currently this is very simple and + // we always just plan to wake up for the next timer in the list. In the + // future this will be more sophisticated. + TimeStamp ComputeWakeupTimeFromTimers() const MOZ_REQUIRES(mMonitor); + + // Computes how late a timer can acceptably fire. + // timerDuration is the duration of the timer whose delay we are calculating. + // Longer timers can tolerate longer firing delays. + // minDelay is an amount by which any timer can be delayed. + // This function will never return a value smaller than minDelay (unless this + // conflicts with maxDelay). maxDelay is the upper limit on the amount by + // which we will ever delay any timer. Takes precedence over minDelay if there + // is a conflict. (Zero will effectively disable timer coalescing.) + TimeDuration ComputeAcceptableFiringDelay(TimeDuration timerDuration, + TimeDuration minDelay, + TimeDuration maxDelay) const; + +#ifdef XP_WIN + UINT ComputeDesiredTimerPeriod() const; +#endif + +#ifdef DEBUG + // Checks mTimers to see if any entries are out of order or any cached + // timeouts are incorrect and will assert if any inconsistency is found. Has + // no side effects other than asserting so has no use in non-DEBUG builds. + void VerifyTimerListConsistency() const MOZ_REQUIRES(mMonitor); +#endif + + // mTimers is maintained in a "pseudo-sorted" order wrt the timeouts. + // Specifcally, mTimers is sorted according to the timeouts *if you ignore the + // canceled entries* (those whose mTimerImpl is nullptr). Notably this means + // that you cannot use a binary search on this list. + nsTArray<Entry> mTimers MOZ_GUARDED_BY(mMonitor); + + // Set only at the start of the thread's Run(): + uint32_t mAllowedEarlyFiringMicroseconds MOZ_GUARDED_BY(mMonitor); + + ProfilerThreadId mProfilerThreadId MOZ_GUARDED_BY(mMonitor); + + // Time at which we were intending to wake up the last time that we slept. + // Is "null" if we have never slept or if our last sleep was "forever". + TimeStamp mIntendedWakeupTime; + +#if TIMER_THREAD_STATISTICS + static constexpr size_t sTimersFiredPerWakeupBucketCount = 16; + static inline constexpr std::array<size_t, sTimersFiredPerWakeupBucketCount> + sTimersFiredPerWakeupThresholds = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 20, 30, 40, 50, 70, (size_t)(-1)}; + + mutable AutoTArray<size_t, sTimersFiredPerWakeupBucketCount> + mTimersFiredPerWakeup MOZ_GUARDED_BY(mMonitor) = {0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + mutable AutoTArray<size_t, sTimersFiredPerWakeupBucketCount> + mTimersFiredPerUnnotifiedWakeup MOZ_GUARDED_BY(mMonitor) = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + mutable AutoTArray<size_t, sTimersFiredPerWakeupBucketCount> + mTimersFiredPerNotifiedWakeup MOZ_GUARDED_BY(mMonitor) = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + mutable size_t mTotalTimersAdded MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalTimersRemoved MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalTimersFiredNotified MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalTimersFiredUnnotified MOZ_GUARDED_BY(mMonitor) = 0; + + mutable size_t mTotalWakeupCount MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalUnnotifiedWakeupCount MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalNotifiedWakeupCount MOZ_GUARDED_BY(mMonitor) = 0; + + mutable double mTotalActualTimerFiringDelayNotified MOZ_GUARDED_BY(mMonitor) = + 0.0; + mutable double mTotalActualTimerFiringDelayUnnotified + MOZ_GUARDED_BY(mMonitor) = 0.0; + + mutable TimeStamp mFirstTimerAdded MOZ_GUARDED_BY(mMonitor); + + mutable size_t mEarlyWakeups MOZ_GUARDED_BY(mMonitor) = 0; + mutable double mTotalEarlyWakeupTime MOZ_GUARDED_BY(mMonitor) = 0.0; + + void PrintStatistics() const; +#endif +}; +#endif /* TimerThread_h___ */ diff --git a/xpcom/threads/VsyncTaskManager.cpp b/xpcom/threads/VsyncTaskManager.cpp new file mode 100644 index 0000000000..ba4201af45 --- /dev/null +++ b/xpcom/threads/VsyncTaskManager.cpp @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VsyncTaskManager.h" +#include "InputTaskManager.h" + +namespace mozilla { + +StaticRefPtr<VsyncTaskManager> VsyncTaskManager::gHighPriorityTaskManager; + +void VsyncTaskManager::Init() { + gHighPriorityTaskManager = new VsyncTaskManager(); +} + +void VsyncTaskManager::WillRunTask() { + TaskManager::WillRunTask(); + InputTaskManager::Get()->NotifyVsync(); +}; +} // namespace mozilla diff --git a/xpcom/threads/VsyncTaskManager.h b/xpcom/threads/VsyncTaskManager.h new file mode 100644 index 0000000000..e284ebf47b --- /dev/null +++ b/xpcom/threads/VsyncTaskManager.h @@ -0,0 +1,26 @@ +/* -*- 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_VsyncTaskManager_h +#define mozilla_VsyncTaskManager_h + +#include "TaskController.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +class VsyncTaskManager : public TaskManager { + public: + static VsyncTaskManager* Get() { return gHighPriorityTaskManager.get(); } + static void Cleanup() { gHighPriorityTaskManager = nullptr; } + static void Init(); + + void WillRunTask() override; + + private: + static StaticRefPtr<VsyncTaskManager> gHighPriorityTaskManager; +}; +} // namespace mozilla +#endif diff --git a/xpcom/threads/WinHandleWatcher.cpp b/xpcom/threads/WinHandleWatcher.cpp new file mode 100644 index 0000000000..d475993925 --- /dev/null +++ b/xpcom/threads/WinHandleWatcher.cpp @@ -0,0 +1,306 @@ +/* -*- 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 <windows.h> +#include <threadpoolapiset.h> + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThreadSafety.h" +#include "mozilla/WinHandleWatcher.h" + +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsISerialEventTarget.h" +#include "nsISupportsImpl.h" +#include "nsITargetShutdownTask.h" +#include "nsIWeakReferenceUtils.h" +#include "nsThreadUtils.h" + +mozilla::LazyLogModule sHWLog("HandleWatcher"); + +namespace mozilla { +namespace details { +struct WaitHandleDeleter { + void operator()(PTP_WAIT waitHandle) { + MOZ_LOG(sHWLog, LogLevel::Debug, ("Closing PTP_WAIT %p", waitHandle)); + ::CloseThreadpoolWait(waitHandle); + } +}; +} // namespace details +using WaitHandlePtr = UniquePtr<TP_WAIT, details::WaitHandleDeleter>; + +// HandleWatcher::Impl +// +// The backing implementation of HandleWatcher is a PTP_WAIT, an OS-threadpool +// wait-object. Windows doesn't actually create a new thread per wait-object; +// OS-threadpool threads are assigned to wait-objects only when their associated +// handle become signaled -- although explicit documentation of this fact is +// somewhat obscurely placed. [1] +// +// Throughout this class, we use manual locking and unlocking guarded by Clang's +// thread-safety warnings, rather than scope-based lock-guards. See `Replace()` +// for an explanation and justification. +// +// [1]https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects#remarks +class HandleWatcher::Impl final : public nsITargetShutdownTask { + NS_DECL_THREADSAFE_ISUPPORTS + + public: + Impl() = default; + + private: + ~Impl() { MOZ_ASSERT(IsStopped()); } + + struct Data { + // The watched handle and its callback. + HANDLE handle; + RefPtr<nsIEventTarget> target; + nsCOMPtr<nsIRunnable> runnable; + + // Handle to the threadpool wait-object. + WaitHandlePtr waitHandle; + // A pointer to ourselves, notionally owned by the wait-object. + RefPtr<Impl> self; + + // (We can't actually do this because a) it has annoying consequences in + // C++20 thanks to P1008R1, and b) Clang just ignores it anyway.) + // + // ~Data() MOZ_EXCLUDES(mMutex) = default; + }; + + mozilla::Mutex mMutex{"HandleWatcher::Impl"}; + Data mData MOZ_GUARDED_BY(mMutex) = {}; + + // Callback from OS threadpool wait-object. + static void CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE, void* ctx, + PTP_WAIT aWaitHandle, + TP_WAIT_RESULT aResult) { + static_cast<Impl*>(ctx)->OnWaitCompleted(aWaitHandle, aResult); + } + + void OnWaitCompleted(PTP_WAIT aWaitHandle, TP_WAIT_RESULT aResult) + MOZ_EXCLUDES(mMutex) { + MOZ_ASSERT(aResult == WAIT_OBJECT_0); + + mMutex.Lock(); + // If this callback is no longer the active callback, skip out. + // All cleanup is someone else's problem. + if (aWaitHandle != mData.waitHandle.get()) { + MOZ_LOG(sHWLog, LogLevel::Debug, + ("Recv'd already-stopped callback: HW %p | PTP_WAIT %p", this, + aWaitHandle)); + mMutex.Unlock(); + return; + } + + // Take our self-pointer so that we release it on exit. + RefPtr<Impl> self = std::move(mData.self); + + MOZ_LOG(sHWLog, LogLevel::Info, + ("Recv'd callback: HW %p | handle %p | target %p | PTP_WAIT %p", + this, mData.handle, mData.target.get(), aWaitHandle)); + + // This may fail if (for example) `mData.target` is being shut down, but we + // have not yet received the shutdown callback. + mData.target->Dispatch(mData.runnable.forget()); + Replace(Data{}); + } + + public: + static RefPtr<Impl> Create(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed<nsIRunnable> aRunnable) { + auto impl = MakeRefPtr<Impl>(); + bool const ok [[maybe_unused]] = + impl->Watch(aHandle, aTarget, std::move(aRunnable)); + MOZ_ASSERT(ok); + return impl; + } + + private: + bool Watch(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed<nsIRunnable> aRunnable) MOZ_EXCLUDES(mMutex) { + MOZ_ASSERT(aHandle); + MOZ_ASSERT(aTarget); + + RefPtr<nsIEventTarget> target(aTarget); + + WaitHandlePtr waitHandle{ + ::CreateThreadpoolWait(&WaitCallback, this, nullptr)}; + if (!waitHandle) { + return false; + } + + { + mMutex.Lock(); + + nsresult const ret = aTarget->RegisterShutdownTask(this); + if (NS_FAILED(ret)) { + mMutex.Unlock(); + return false; + } + + MOZ_LOG(sHWLog, LogLevel::Info, + ("Setting callback: HW %p | handle %p | target %p | PTP_WAIT %p", + this, aHandle, aTarget, waitHandle.get())); + + // returns `void`; presumably always succeeds given a successful + // `::CreateThreadpoolWait()` + ::SetThreadpoolWait(waitHandle.get(), aHandle, nullptr); + // After this point, you must call `FlushWaitHandle(waitHandle.get())` + // before destroying the wait handle. (Note that this must be done while + // *not* holding `mMutex`!) + + Replace(Data{.handle = aHandle, + .target = std::move(target), + .runnable = aRunnable, + .waitHandle = std::move(waitHandle), + .self = this}); + } + + return true; + } + + void TargetShutdown() MOZ_EXCLUDES(mMutex) override final { + mMutex.Lock(); + + MOZ_LOG(sHWLog, LogLevel::Debug, + ("Target shutdown: HW %p | handle %p | target %p | PTP_WAIT %p", + this, mData.handle, mData.target.get(), mData.waitHandle.get())); + + // Clear mData.target, since there's no need to unregister the shutdown task + // anymore. Hold onto it until we release the mutex, though, to avoid any + // reentrancy issues. + // + // This is more for internal consistency than safety: someone has to be + // shutting `target` down, and that someone isn't us, so there's necessarily + // another reference out there. (Although decrementing the refcount might + // still have arbitrary effects if someone's been excessively clever with + // nsISupports::Release...) + auto const oldTarget = std::move(mData.target); + Replace(Data{}); + // (Static-assert that the mutex has indeed been released.) + ([&]() MOZ_EXCLUDES(mMutex) {})(); + } + + public: + void Stop() MOZ_EXCLUDES(mMutex) { + mMutex.Lock(); + Replace(Data{}); + } + + bool IsStopped() MOZ_EXCLUDES(mMutex) { + mozilla::MutexAutoLock lock(mMutex); + return !mData.handle; + } + + private: + // Throughout this class, we use manual locking and unlocking guarded by + // Clang's thread-safety warnings, rather than scope-based lock-guards. This + // is largely driven by `Replace()`, below, which performs both operations + // which require the mutex to be held and operations which require it to not + // be held, and therefore must explicitly sequence the mutex release. + // + // These explicit locks, unlocks, and annotations are both alien to C++ and + // offensively tedious; but they _are_ still checked for state consistency at + // scope boundaries. (The concerned reader is invited to test this by + // deliberately removing an `mMutex.Unlock()` call from anywhere in the class + // and viewing the resultant compiler diagnostics.) + // + // A more principled, or at least differently-principled, implementation might + // create a scope-based lock-guard and pass it to `Replace()` to dispose of at + // the proper time. Alas, it cannot be communicated to Clang's thread-safety + // checker that such a guard is associated with `mMutex`. + // + void Replace(Data&& aData) MOZ_CAPABILITY_RELEASE(mMutex) { + // either both handles are NULL, or neither is + MOZ_ASSERT(!!aData.handle == !!aData.waitHandle); + + if (mData.handle) { + MOZ_LOG(sHWLog, LogLevel::Info, + ("Stop callback: HW %p | handle %p | target %p | PTP_WAIT %p", + this, mData.handle, mData.target.get(), mData.waitHandle.get())); + } + + if (mData.target) { + mData.target->UnregisterShutdownTask(this); + } + + // Extract the old data and insert the new -- but hold onto the old data for + // now. (See [1] and [2], below.) + Data oldData = std::exchange(mData, std::move(aData)); + + //////////////////////////////////////////////////////////////////////////// + // Release the mutex. + mMutex.Unlock(); + //////////////////////////////////////////////////////////////////////////// + + // [1] `oldData.self` will be unset if the old callback already ran (or if + // there was no old callback in the first place). If it's set, though, we + // need to explicitly clear out the wait-object first. + if (oldData.self) { + MOZ_ASSERT(oldData.waitHandle); + FlushWaitHandle(oldData.waitHandle.get()); + } + + // [2] oldData also includes several other reference-counted pointers. It's + // possible that these may be the last pointer to something, so releasing + // them may have arbitrary side-effects -- like calling this->Stop(), which + // will try to reacquire the mutex. + // + // Now that we've released the mutex, we can (implicitly) release them all + // here. + } + + // Either confirm as complete or cancel any callbacks on aWaitHandle. Block + // until this is done. (See documentation for ::CloseThreadpoolWait().) + void FlushWaitHandle(PTP_WAIT aWaitHandle) MOZ_EXCLUDES(mMutex) { + ::SetThreadpoolWait(aWaitHandle, nullptr, nullptr); + // This might block on `OnWaitCompleted()`, so we can't hold `mMutex` here. + ::WaitForThreadpoolWaitCallbacks(aWaitHandle, TRUE); + // ::CloseThreadpoolWait() itself is the caller's responsibility. + } +}; + +NS_IMPL_ISUPPORTS(HandleWatcher::Impl, nsITargetShutdownTask) + +////// +// HandleWatcher member function implementations + +HandleWatcher::HandleWatcher() : mImpl{} {} +HandleWatcher::~HandleWatcher() { + if (mImpl) { + MOZ_ASSERT(mImpl->IsStopped()); + mImpl->Stop(); // just in case, in release + } +} + +HandleWatcher::HandleWatcher(HandleWatcher&&) noexcept = default; +HandleWatcher& HandleWatcher::operator=(HandleWatcher&&) noexcept = default; + +void HandleWatcher::Watch(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed<nsIRunnable> aRunnable) { + auto impl = Impl::Create(aHandle, aTarget, std::move(aRunnable)); + MOZ_ASSERT(impl); + + if (mImpl) { + mImpl->Stop(); + } + mImpl = std::move(impl); +} + +void HandleWatcher::Stop() { + if (mImpl) { + mImpl->Stop(); + } +} + +bool HandleWatcher::IsStopped() { return !mImpl || mImpl->IsStopped(); } + +} // namespace mozilla diff --git a/xpcom/threads/WinHandleWatcher.h b/xpcom/threads/WinHandleWatcher.h new file mode 100644 index 0000000000..eb1e1c0245 --- /dev/null +++ b/xpcom/threads/WinHandleWatcher.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 WinHandleWatcher_h__ +#define WinHandleWatcher_h__ + +#include <minwindef.h> + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +#include "nsIEventTarget.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +namespace mozilla { +/////////////////////////////////////////////////////////////////////// +// HandleWatcher +// +// Enqueues a task onto an event target when a watched Win32 synchronization +// object [1] enters the signaled state. +// +// The HandleWatcher must be stopped before either it or the synchronization +// object is destroyed. +// +////// +// +// Example of use: +// +// ``` +// class MyClass { +// /* ... */ +// +// HANDLE CreateThing(); +// void OnComplete(); +// public: +// void Fire() { +// mHandle.set(CreateThing()); +// mWatcher.Watch( +// mHandle.get(), NS_GetCurrentThread(), // (or any other thread) +// NS_NewRunnableFunction("OnComplete", [this] { OnComplete(); })); +// } +// +// ~MyClass() { mWatcher.Stop(); } +// +// HandleWatcher mWatcher; +// HandlePtr mHandle; // calls ::CloseHandle() on destruction +// }; +// ``` +// +// Note: this example demonstrates why an explicit `Stop()` is necessary in +// MyClass's destructor. Without it, the `HandlePtr` would destroy the HANDLE -- +// and possibly whatever other data `OnComplete()` depends on -- before the +// watch was stopped! +// +// Rather than make code correctness silently dependent on member object order, +// we require that HandleWatcher already be stopped at its destruction time. +// (This does not guarantee correctness, as the task may still reference a +// partially-destroyed transitive owner; but, short of RIIR, a guarantee of +// correctness is probably not possible here.) +// +////// +// +// [1]https://learn.microsoft.com/en-us/windows/win32/sync/synchronization-objects +class HandleWatcher { + public: + class Impl; + + HandleWatcher(); + ~HandleWatcher(); + + HandleWatcher(HandleWatcher const&) = delete; + HandleWatcher& operator=(HandleWatcher const&) = delete; + + HandleWatcher(HandleWatcher&&) noexcept; + HandleWatcher& operator=(HandleWatcher&&) noexcept; + + // Watches the given Win32 HANDLE, which must be a synchronization object. As + // soon as the HANDLE is signaled, posts `aRunnable` to `aTarget`. + // + // `aHandle` is merely borrowed for the duration of the watch: the + // HandleWatcher does not attempt to close it, and its lifetime must exceed + // that of the watch. + // + // If the watch is stopped for any reason other than completion, `aRunnable` + // is released immediately, on the same thread from which the Watch was + // stopped. + // + // The watch is stopped when any of the following occurs: + // * `Stop()` is called. + // * `Watch()` is called again, even without an intervening `Stop()`. + // * This object is destroyed. + // * `aTarget` shuts down. + // * `aHandle` becomes signaled. + // + void Watch(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed<nsIRunnable> aRunnable); + + // Cancels the current watch, if any. + // + // Idempotent. Thread-safe with respect to other calls of `Stop()`. + void Stop(); + + // Potentially racy. Only intended for tests. + bool IsStopped(); + + private: + RefPtr<Impl> mImpl; +}; +} // namespace mozilla + +#endif // WinHandleWatcher_h__ diff --git a/xpcom/threads/components.conf b/xpcom/threads/components.conf new file mode 100644 index 0000000000..53f76d3b89 --- /dev/null +++ b/xpcom/threads/components.conf @@ -0,0 +1,29 @@ +# -*- 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': '{03d68f92-9513-4e25-9be9-7cb239874172}', + 'contract_ids': ['@mozilla.org/process/environment;1'], + 'legacy_constructor': 'nsEnvironment::Create', + 'headers': ['/xpcom/threads/nsEnvironment.h'], + 'js_name': 'env', + 'interfaces': ['nsIEnvironment'], + }, + { + 'cid': '{5ff24248-1dd2-11b2-8427-fbab44f29bc8}', + 'contract_ids': ['@mozilla.org/timer;1'], + 'legacy_constructor': 'nsTimer::XPCOMConstructor', + 'headers': ['/xpcom/threads/nsTimerImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{d39a8904-2e09-4a3a-a273-c3bec7db2bfe}', + 'contract_ids': ['@mozilla.org/timer-manager;1'], + 'headers': ['/xpcom/threads/nsTimerImpl.h'], + 'type': 'nsTimerManager', + }, +] diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build new file mode 100644 index 0000000000..06d10ad331 --- /dev/null +++ b/xpcom/threads/moz.build @@ -0,0 +1,144 @@ +# -*- 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 += [ + "nsIDirectTaskDispatcher.idl", + "nsIEnvironment.idl", + "nsIEventTarget.idl", + "nsIIdlePeriod.idl", + "nsINamed.idl", + "nsIProcess.idl", + "nsIRunnable.idl", + "nsISerialEventTarget.idl", + "nsISupportsPriority.idl", + "nsIThread.idl", + "nsIThreadInternal.idl", + "nsIThreadManager.idl", + "nsIThreadPool.idl", + "nsIThreadShutdown.idl", + "nsITimer.idl", +] + +XPIDL_MODULE = "xpcom_threads" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "MainThreadUtils.h", + "nsICancelableRunnable.h", + "nsIDiscardableRunnable.h", + "nsIIdleRunnable.h", + "nsITargetShutdownTask.h", + "nsMemoryPressure.h", + "nsProcess.h", + "nsProxyRelease.h", + "nsThread.h", + "nsThreadManager.h", + "nsThreadPool.h", + "nsThreadUtils.h", +] + +EXPORTS.mozilla += [ + "AbstractThread.h", + "BlockingResourceBase.h", + "CondVar.h", + "CPUUsageWatcher.h", + "DataMutex.h", + "DeadlockDetector.h", + "DelayedRunnable.h", + "EventQueue.h", + "EventTargetCapability.h", + "IdlePeriodState.h", + "IdleTaskRunner.h", + "InputTaskManager.h", + "LazyIdleThread.h", + "MainThreadIdlePeriod.h", + "Monitor.h", + "MozPromise.h", + "Mutex.h", + "Queue.h", + "RecursiveMutex.h", + "ReentrantMonitor.h", + "RWLock.h", + "SchedulerGroup.h", + "SharedThreadPool.h", + "SpinEventLoopUntil.h", + "StateMirroring.h", + "StateWatching.h", + "SynchronizedEventQueue.h", + "SyncRunnable.h", + "TaskController.h", + "TaskDispatcher.h", + "TaskQueue.h", + "ThreadBound.h", + "ThreadEventQueue.h", + "ThrottledEventQueue.h", + "VsyncTaskManager.h", +] + +SOURCES += [ + "IdlePeriodState.cpp", + "IdleTaskRunner.cpp", + "ThreadDelay.cpp", +] + +UNIFIED_SOURCES += [ + "AbstractThread.cpp", + "BlockingResourceBase.cpp", + "CPUUsageWatcher.cpp", + "DelayedRunnable.cpp", + "EventQueue.cpp", + "InputTaskManager.cpp", + "LazyIdleThread.cpp", + "MainThreadIdlePeriod.cpp", + "nsEnvironment.cpp", + "nsMemoryPressure.cpp", + "nsProcessCommon.cpp", + "nsProxyRelease.cpp", + "nsThread.cpp", + "nsThreadManager.cpp", + "nsThreadPool.cpp", + "nsThreadUtils.cpp", + "nsTimerImpl.cpp", + "RecursiveMutex.cpp", + "RWLock.cpp", + "SchedulerGroup.cpp", + "SharedThreadPool.cpp", + "SynchronizedEventQueue.cpp", + "TaskController.cpp", + "TaskQueue.cpp", + "ThreadEventQueue.cpp", + "ThreadEventTarget.cpp", + "ThreadLocalVariables.cpp", + "ThrottledEventQueue.cpp", + "TimerThread.cpp", + "VsyncTaskManager.cpp", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla += ["WinHandleWatcher.h"] + UNIFIED_SOURCES += ["WinHandleWatcher.cpp"] + +# Should match the conditions in toolkit/components/backgroundhangmonitor/moz.build +if ( + CONFIG["NIGHTLY_BUILD"] + and not CONFIG["MOZ_DEBUG"] + and not CONFIG["MOZ_TSAN"] + and not CONFIG["MOZ_ASAN"] +): + DEFINES["MOZ_ENABLE_BACKGROUND_HANG_MONITOR"] = 1 + +LOCAL_INCLUDES += [ + "../build", + "/caps", + "/tools/profiler", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/xpcom/threads/nsEnvironment.cpp b/xpcom/threads/nsEnvironment.cpp new file mode 100644 index 0000000000..54efd9194a --- /dev/null +++ b/xpcom/threads/nsEnvironment.cpp @@ -0,0 +1,136 @@ +/* -*- 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 "nsEnvironment.h" +#include "prenv.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsPromiseFlatString.h" +#include "nsDependentString.h" +#include "nsNativeCharsetUtils.h" +#include "mozilla/Printf.h" +#include "mozilla/StaticMutex.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsEnvironment, nsIEnvironment) + +nsresult nsEnvironment::Create(REFNSIID aIID, void** aResult) { + nsresult rv; + *aResult = nullptr; + + nsEnvironment* obj = new nsEnvironment(); + + rv = obj->QueryInterface(aIID, aResult); + if (NS_FAILED(rv)) { + delete obj; + } + return rv; +} + +NS_IMETHODIMP +nsEnvironment::Exists(const nsAString& aName, bool* aOutValue) { + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString nativeVal; +#if defined(XP_UNIX) + /* For Unix/Linux platforms we follow the Unix definition: + * An environment variable exists when |getenv()| returns a non-nullptr + * value. An environment variable does not exist when |getenv()| returns + * nullptr. + */ + const char* value = PR_GetEnv(nativeName.get()); + *aOutValue = value && *value; +#else + /* For non-Unix/Linux platforms we have to fall back to a + * "portable" definition (which is incorrect for Unix/Linux!!!!) + * which simply checks whether the string returned by |Get()| is empty + * or not. + */ + nsAutoString value; + Get(aName, value); + *aOutValue = !value.IsEmpty(); +#endif /* XP_UNIX */ + + return NS_OK; +} + +NS_IMETHODIMP +nsEnvironment::Get(const nsAString& aName, nsAString& aOutValue) { + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString nativeVal; + const char* value = PR_GetEnv(nativeName.get()); + if (value && *value) { + rv = NS_CopyNativeToUnicode(nsDependentCString(value), aOutValue); + } else { + aOutValue.Truncate(); + rv = NS_OK; + } + + return rv; +} + +/* Environment strings must have static duration; We're gonna leak all of this + * at shutdown: this is by design, caused how Unix/Linux implement environment + * vars. + */ + +typedef nsBaseHashtableET<nsCharPtrHashKey, char*> EnvEntryType; +typedef nsTHashtable<EnvEntryType> EnvHashType; + +static StaticMutex gEnvHashMutex; +static EnvHashType* gEnvHash MOZ_GUARDED_BY(gEnvHashMutex) = nullptr; + +static EnvHashType* EnsureEnvHash() MOZ_REQUIRES(gEnvHashMutex) { + if (!gEnvHash) { + gEnvHash = new EnvHashType; + } + return gEnvHash; +} + +NS_IMETHODIMP +nsEnvironment::Set(const nsAString& aName, const nsAString& aValue) { + nsAutoCString nativeName; + nsAutoCString nativeVal; + + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = NS_CopyUnicodeToNative(aValue, nativeVal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + StaticMutexAutoLock lock(gEnvHashMutex); + EnvEntryType* entry = EnsureEnvHash()->PutEntry(nativeName.get()); + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + + SmprintfPointer newData = + mozilla::Smprintf("%s=%s", nativeName.get(), nativeVal.get()); + if (!newData) { + return NS_ERROR_OUT_OF_MEMORY; + } + + PR_SetEnv(newData.get()); + if (entry->GetData()) { + free(entry->GetData()); + } + entry->SetData(newData.release()); + return NS_OK; +} diff --git a/xpcom/threads/nsEnvironment.h b/xpcom/threads/nsEnvironment.h new file mode 100644 index 0000000000..d371050ec5 --- /dev/null +++ b/xpcom/threads/nsEnvironment.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsEnvironment_h__ +#define nsEnvironment_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsIEnvironment.h" + +#define NS_ENVIRONMENT_CID \ + { \ + 0X3D68F92UL, 0X9513, 0X4E25, { \ + 0X9B, 0XE9, 0X7C, 0XB2, 0X39, 0X87, 0X41, 0X72 \ + } \ + } +#define NS_ENVIRONMENT_CONTRACTID "@mozilla.org/process/environment;1" + +class nsEnvironment final : public nsIEnvironment { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIENVIRONMENT + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + nsEnvironment() {} + ~nsEnvironment() = default; +}; + +#endif /* !nsEnvironment_h__ */ diff --git a/xpcom/threads/nsICancelableRunnable.h b/xpcom/threads/nsICancelableRunnable.h new file mode 100644 index 0000000000..7aa98c86b6 --- /dev/null +++ b/xpcom/threads/nsICancelableRunnable.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 nsICancelableRunnable_h__ +#define nsICancelableRunnable_h__ + +#include "nsISupports.h" + +#define NS_ICANCELABLERUNNABLE_IID \ + { \ + 0xde93dc4c, 0x5eea, 0x4eb7, { \ + 0xb6, 0xd1, 0xdb, 0xf1, 0xe0, 0xce, 0xf6, 0x5c \ + } \ + } + +class nsICancelableRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANCELABLERUNNABLE_IID) + + /* + * Cancels a pending task, so that calling run() on the task is a no-op. + * Calling cancel after the task execution has begun will be a no-op. + * Calling this method twice is considered an error. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that the runnable has already been canceled. + */ + virtual nsresult Cancel() = 0; + + protected: + nsICancelableRunnable() = default; + virtual ~nsICancelableRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICancelableRunnable, NS_ICANCELABLERUNNABLE_IID) + +#endif // nsICancelableRunnable_h__ diff --git a/xpcom/threads/nsIDirectTaskDispatcher.idl b/xpcom/threads/nsIDirectTaskDispatcher.idl new file mode 100644 index 0000000000..7d44608708 --- /dev/null +++ b/xpcom/threads/nsIDirectTaskDispatcher.idl @@ -0,0 +1,57 @@ +/* -*- 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 "nsIRunnable.idl" + +%{C++ +#include "mozilla/AlreadyAddRefed.h" +%} + +native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>); + +/* + * The primary use of this interface is to allow any nsISerialEventTarget to + * provide Direct Task dispatching which is similar (but not identical to) the + * microtask semantics of JS promises. + * New direct task may be dispatched when a current direct task is running. In + * which case they will be run in FIFO order. + */ +[uuid(e05bf0fe-94b7-4e28-8462-a8368da9c136)] +interface nsIDirectTaskDispatcher : nsISupports +{ + /** + * Dispatch an event for the nsISerialEventTarget, using the direct task + * queue. + * + * This function must be called from the same nsISerialEventTarget + * implementing direct task dispatching. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * + */ + [noscript] void dispatchDirectTask(in alreadyAddRefed_nsIRunnable event); + + /** + * Synchronously run any pending direct tasks queued. + */ + [noscript] void drainDirectTasks(); + + /** + * Returns true if any direct tasks are pending. + */ + [noscript] bool haveDirectTasks(); + + %{C++ + // Infallible version of the above. Will assert that it is successful. + bool HaveDirectTasks() { + bool value = false; + MOZ_ALWAYS_SUCCEEDS(HaveDirectTasks(&value)); + return value; + } + %} + +}; diff --git a/xpcom/threads/nsIDiscardableRunnable.h b/xpcom/threads/nsIDiscardableRunnable.h new file mode 100644 index 0000000000..873b1f5d93 --- /dev/null +++ b/xpcom/threads/nsIDiscardableRunnable.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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_THREADS_NSIDISCARDABLERUNNABLE_H_ +#define XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ + +#include "nsISupports.h" + +/** + * An interface implemented by nsIRunnable tasks for which nsIRunnable::Run() + * might not be called. + */ +#define NS_IDISCARDABLERUNNABLE_IID \ + { \ + 0xde93dc4c, 0x755c, 0x4cdc, { \ + 0x96, 0x76, 0x35, 0xc6, 0x48, 0x81, 0x59, 0x78 \ + } \ + } + +class NS_NO_VTABLE nsIDiscardableRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDISCARDABLERUNNABLE_IID) + + /** + * Called exactly once on a queued task only if nsIRunnable::Run() is not + * called. + */ + virtual void OnDiscard() = 0; + + protected: + nsIDiscardableRunnable() = default; + virtual ~nsIDiscardableRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIDiscardableRunnable, + NS_IDISCARDABLERUNNABLE_IID) + +#endif // XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ diff --git a/xpcom/threads/nsIEnvironment.idl b/xpcom/threads/nsIEnvironment.idl new file mode 100644 index 0000000000..60da8ba76a --- /dev/null +++ b/xpcom/threads/nsIEnvironment.idl @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Scriptable access to the current process environment. + * + */ +[scriptable, uuid(101d5941-d820-4e85-a266-9a3469940807)] +interface nsIEnvironment : nsISupports +{ + /** + * Set the value of an environment variable. + * + * @param aName the variable name to set. + * @param aValue the value to set. + */ + void set(in AString aName, in AString aValue); + + /** + * Get the value of an environment variable. + * + * @param aName the variable name to retrieve. + * @return returns the value of the env variable. An empty string + * will be returned when the env variable does not exist or + * when the value itself is an empty string - please use + * |exists()| to probe whether the env variable exists + * or not. + */ + AString get(in AString aName); + + /** + * Check the existence of an environment variable. + * This method checks whether an environment variable is present in + * the environment or not. + * + * - For Unix/Linux platforms we follow the Unix definition: + * An environment variable exists when |getenv()| returns a non-NULL value. + * An environment variable does not exist when |getenv()| returns NULL. + * - For non-Unix/Linux platforms we have to fall back to a + * "portable" definition (which is incorrect for Unix/Linux!!!!) + * which simply checks whether the string returned by |Get()| is empty + * or not. + * + * @param aName the variable name to probe. + * @return if the variable has been set, the value returned is + * PR_TRUE. If the variable was not defined in the + * environment PR_FALSE will be returned. + */ + boolean exists(in AString aName); +}; diff --git a/xpcom/threads/nsIEventTarget.idl b/xpcom/threads/nsIEventTarget.idl new file mode 100644 index 0000000000..edd49f306d --- /dev/null +++ b/xpcom/threads/nsIEventTarget.idl @@ -0,0 +1,227 @@ +/* -*- 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 "nsISupports.idl" +#include "nsIRunnable.idl" +%{C++ +#include "nsCOMPtr.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" + +class nsITargetShutdownTask; +%} + +native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>); +[ptr] native nsITargetShutdownTask(nsITargetShutdownTask); + +[builtinclass, scriptable, rust_sync, uuid(a03b8b63-af8b-4164-b0e5-c41e8b2b7cfa)] +interface nsIEventTarget : nsISupports +{ + /* until we can get rid of all uses, keep the non-alreadyAddRefed<> version */ +%{C++ + nsresult Dispatch(nsIRunnable* aEvent, uint32_t aFlags) { + return Dispatch(nsCOMPtr<nsIRunnable>(aEvent).forget(), aFlags); + } +%} + + /** + * This flag specifies the default mode of event dispatch, whereby the event + * is simply queued for later processing. When this flag is specified, + * dispatch returns immediately after the event is queued. + */ + const unsigned long DISPATCH_NORMAL = 0; + + // NOTE: 1 used to be DISPATCH_SYNC + + /** + * This flag specifies that the dispatch is occurring from a running event + * that was dispatched to the same event target, and that event is about to + * finish. + * + * A thread pool can use this as an optimization hint to not spin up + * another thread, since the current thread is about to become idle. + * + * These events are always async. + */ + const unsigned long DISPATCH_AT_END = 2; + + /** + * This flag specifies that the dispatched event may block the thread on + * which it executes, usually by doing some sort of I/O. This information + * may be used by the event target to execute the job on a thread + * specifically dedicated to doing I/O, leaving other threads available for + * CPU-intensive work. + */ + const unsigned long DISPATCH_EVENT_MAY_BLOCK = 4; + + /** + * This flag specifies that the dispatched event should be delivered to the + * target thread even if the thread has been configured to block dispatching + * of runnables. This is generally done for threads which have their own + * internal event loop, such as thread pools or the timer thread, and will not + * service runnables dispatched to them until shutdown. + */ + const unsigned long DISPATCH_IGNORE_BLOCK_DISPATCH = 8; + + /** + * IsOnCurrentThread() should return true if events dispatched to this target + * can possibly run on the current thread, and false otherwise. In the case + * of an nsIEventTarget for a thread pool, it should return true on all + * threads in the pool. In the case of a non-thread nsIEventTarget such as + * ThrottledEventQueue, it should return true on the thread where events are + * expected to be processed, even if no events from the queue are actually + * being processed right now. + * + * When called on an nsISerialEventTarget, IsOnCurrentThread can be used to + * ensure that no other thread has "ownership" of the event target. As such, + * it's useful for asserting that an object is only used on a particular + * thread. IsOnCurrentThread can't guarantee that the current event has been + * dispatched through a particular event target. + * + * The infallible version of IsOnCurrentThread() is optimized to avoid a + * virtual call for non-thread event targets. Thread targets should set + * mThread to their virtual PRThread. Non-thread targets should leave + * mThread null and implement IsOnCurrentThreadInfallible() to + * return the correct answer. + * + * The fallible version of IsOnCurrentThread may return errors, such as during + * shutdown. If it does not return an error, it should return the same result + * as the infallible version. The infallible method should return the correct + * result regardless of whether the fallible method returns an error. + */ + %{C++ +public: + // Infallible. Defined in nsThreadUtils.cpp. Delegates to + // IsOnCurrentThreadInfallible when mThread is null. + bool IsOnCurrentThread(); + +protected: + mozilla::Atomic<PRThread*> mThread; + + nsIEventTarget() : mThread(nullptr) {} + %} + // Note that this method is protected. We define it through IDL, rather than + // in a %{C++ block, to ensure that the correct method indices are recorded + // for XPConnect purposes. + [noscript,notxpcom] boolean isOnCurrentThreadInfallible(); + %{C++ +public: + %} + + // Fallible version of IsOnCurrentThread. + boolean isOnCurrentThread(); + + /** + * Dispatch an event to this event target. This function may be called from + * any thread, and it may be called re-entrantly. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * NOTE that the event will be leaked if it fails to dispatch. + * @param flags + * The flags modifying event dispatch. The flags are described in detail + * below. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [noscript, binaryname(Dispatch)] void dispatchFromC(in alreadyAddRefed_nsIRunnable event, + [default(DISPATCH_NORMAL)] in unsigned long flags); + /** + * Version of Dispatch to expose to JS, which doesn't require an alreadyAddRefed<> + * (it will be converted to that internally) + * + * @param event + * The (raw) event to dispatch. + * @param flags + * The flags modifying event dispatch. The flags are described in detail + * below. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [binaryname(DispatchFromScript)] void dispatch(in nsIRunnable event, in unsigned long flags); + /** + * Dispatch an event to this event target, but do not run it before delay + * milliseconds have passed. This function may be called from any thread. + * + * @param event + * The alreadyAddrefed<> event to dispatch. + * @param delay + * The delay (in ms) before running the event. If event does not rise to + * the top of the event queue before the delay has passed, it will be set + * aside to execute once the delay has passed. Otherwise, it will be + * executed immediately. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched, or + * that delay is zero. + */ + [noscript] void delayedDispatch(in alreadyAddRefed_nsIRunnable event, in unsigned long delay); + + /** + * Register an task to be run on this event target when it begins shutting + * down. Shutdown tasks may be run in any order, and this function may be + * called from any thread. + * + * The event target may or may not continue accepting events during or after + * the shutdown task. The precise behaviour here depends on the event target. + * + * @param task + * The task to be registered to the target thread. + * NOTE that unlike `dispatch`, this will not leak the task if it fails. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that task is null. + * @throws NS_ERROR_NOT_IMPLEMENTED + * Indicates that this event target doesn't support shutdown tasks. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is already shutting down, and no longer + * accepting events. + */ + [noscript] void registerShutdownTask(in nsITargetShutdownTask task); + + /** + * Unregisters an task previously registered with registerShutdownTask. This + * function may be called from any thread. + * + * @param task + * The task previously registered with registerShutdownTask + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that task is null. + * @throws NS_ERROR_NOT_IMPLEMENTED + * Indicates that this event target doesn't support shutdown tasks. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is already shutting down, and no longer + * accepting events, or that the shutdown task cannot be found. + */ + [noscript] void unregisterShutdownTask(in nsITargetShutdownTask task); +}; + +%{C++ +// convenient aliases: +#define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL +#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END +#define NS_DISPATCH_EVENT_MAY_BLOCK nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK +#define NS_DISPATCH_IGNORE_BLOCK_DISPATCH nsIEventTarget::DISPATCH_IGNORE_BLOCK_DISPATCH + +// Convenient NS_DECL variant that includes some C++-only methods. +#define NS_DECL_NSIEVENTTARGET_FULL \ + NS_DECL_NSIEVENTTARGET \ + /* Avoid hiding these methods */ \ + using nsIEventTarget::Dispatch; \ + using nsIEventTarget::IsOnCurrentThread; +%} diff --git a/xpcom/threads/nsIIdlePeriod.idl b/xpcom/threads/nsIIdlePeriod.idl new file mode 100644 index 0000000000..03ab45d80d --- /dev/null +++ b/xpcom/threads/nsIIdlePeriod.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +namespace mozilla { +class TimeStamp; +} +%} + +native TimeStamp(mozilla::TimeStamp); + +/** + * An instance implementing nsIIdlePeriod is used by an associated + * nsIThread to estimate when it is likely that it will receive an + * event. + */ +[uuid(21dd35a2-eae9-4bd8-b470-0dfa35a0e3b9)] +interface nsIIdlePeriod : nsISupports +{ + /** + * Return an estimate of a point in time in the future when we + * think that the associated thread will become busy. Should + * return TimeStamp() (i.e. the null time) or a time less than + * TimeStamp::Now() if the thread is currently busy or will become + * busy very soon. + */ + TimeStamp getIdlePeriodHint(); +}; diff --git a/xpcom/threads/nsIIdleRunnable.h b/xpcom/threads/nsIIdleRunnable.h new file mode 100644 index 0000000000..7fe6149154 --- /dev/null +++ b/xpcom/threads/nsIIdleRunnable.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 nsIIdleRunnable_h__ +#define nsIIdleRunnable_h__ + +#include "nsISupports.h" +#include "mozilla/TimeStamp.h" + +#define NS_IIDLERUNNABLE_IID \ + { \ + 0x688be92e, 0x7ade, 0x4fdc, { \ + 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c \ + } \ + } + +class nsIEventTarget; + +/** + * A task interface for tasks that can schedule their work to happen + * in increments bounded by a deadline. + */ +class nsIIdleRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIDLERUNNABLE_IID) + + /** + * Notify the task of a point in time in the future when the task + * should stop executing. + */ + virtual void SetDeadline(mozilla::TimeStamp aDeadline){}; + virtual void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT_UNREACHABLE( + "The nsIIdleRunnable instance does not support " + "idle dispatch with timeout!"); + }; + + protected: + nsIIdleRunnable() = default; + virtual ~nsIIdleRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIdleRunnable, NS_IIDLERUNNABLE_IID) + +#endif // nsIIdleRunnable_h__ diff --git a/xpcom/threads/nsINamed.idl b/xpcom/threads/nsINamed.idl new file mode 100644 index 0000000000..cdb7d88f30 --- /dev/null +++ b/xpcom/threads/nsINamed.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Represents an object with a name, such as a runnable or a timer. + */ + +[scriptable, uuid(0c5fe7de-7e83-4d0d-a8a6-4a6518b9a7b3)] +interface nsINamed : nsISupports +{ + /* + * A string describing the purpose of the runnable/timer/whatever. Useful + * for debugging. This attribute is read-only, but you can change it to a + * compile-time string literal with setName. + * + * WARNING: This attribute will be included in telemetry, so it should + * never contain privacy sensitive information. + */ + readonly attribute AUTF8String name; +}; diff --git a/xpcom/threads/nsIProcess.idl b/xpcom/threads/nsIProcess.idl new file mode 100644 index 0000000000..c15ded7a2f --- /dev/null +++ b/xpcom/threads/nsIProcess.idl @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIObserver; + +[scriptable, uuid(609610de-9954-4a63-8a7c-346350a86403)] +interface nsIProcess : nsISupports +{ + /** + * Initialises the process with an executable to be run. Call the run method + * to run the executable. + * @param executable The executable to run. + */ + void init(in nsIFile executable); + + /** + * Kills the running process. After exiting the process will either have + * been killed or a failure will have been returned. + */ + void kill(); + + /** + * Executes the file this object was initialized with + * @param blocking Whether to wait until the process terminates before + returning or not. + * @param args An array of arguments to pass to the process in the + * native character set. + * @param count The length of the args array. + */ + void run(in boolean blocking, [array, size_is(count)] in string args, + in unsigned long count); + + /** + * Executes the file this object was initialized with optionally calling + * an observer after the process has finished running. + * @param args An array of arguments to pass to the process in the + * native character set. + * @param count The length of the args array. + * @param observer An observer to notify when the process has completed. It + * will receive this process instance as the subject and + * "process-finished" or "process-failed" as the topic. The + * observer will be notified on the main thread. + * @param holdWeak Whether to use a weak reference to hold the observer. + */ + void runAsync([array, size_is(count)] in string args, in unsigned long count, + [optional] in nsIObserver observer, [optional] in boolean holdWeak); + + /** + * Executes the file this object was initialized with + * @param blocking Whether to wait until the process terminates before + returning or not. + * @param args An array of arguments to pass to the process in UTF-16 + * @param count The length of the args array. + */ + void runw(in boolean blocking, [array, size_is(count)] in wstring args, + in unsigned long count); + + /** + * Executes the file this object was initialized with optionally calling + * an observer after the process has finished running. + * @param args An array of arguments to pass to the process in UTF-16 + * @param count The length of the args array. + * @param observer An observer to notify when the process has completed. It + * will receive this process instance as the subject and + * "process-finished" or "process-failed" as the topic. The + * observer will be notified on the main thread. + * @param holdWeak Whether to use a weak reference to hold the observer. + */ + void runwAsync([array, size_is(count)] in wstring args, + in unsigned long count, + [optional] in nsIObserver observer, [optional] in boolean holdWeak); + + /** + * When set to true the process will not open a new window when started and + * will run hidden from the user. This currently affects only the Windows + * platform. + */ + attribute boolean startHidden; + + /** + * When set to true the process will be launched directly without using the + * shell. This currently affects only the Windows platform. + */ + attribute boolean noShell; + + /** + * The process identifier of the currently running process. This will only + * be available after the process has started and may not be available on + * some platforms. + */ + readonly attribute unsigned long pid; + + /** + * The exit value of the process. This is only valid after the process has + * exited. + */ + readonly attribute long exitValue; + + /** + * Returns whether the process is currently running or not. + */ + readonly attribute boolean isRunning; +}; + +%{C++ + +#define NS_PROCESS_CONTRACTID "@mozilla.org/process/util;1" +%} diff --git a/xpcom/threads/nsIRunnable.idl b/xpcom/threads/nsIRunnable.idl new file mode 100644 index 0000000000..bfe9669a9f --- /dev/null +++ b/xpcom/threads/nsIRunnable.idl @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Represents a task which can be dispatched to a thread for execution. + */ + +[scriptable, function, uuid(4a2abaf0-6886-11d3-9382-00104ba0fd40)] +interface nsIRunnable : nsISupports +{ + /** + * The function implementing the task to be run. + */ + void run(); +}; + +[scriptable, uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)] +interface nsIRunnablePriority : nsISupports +{ + const unsigned long PRIORITY_IDLE = 0; + const unsigned long PRIORITY_DEFERRED_TIMERS = 1; + const unsigned long PRIORITY_LOW = 2; + // INPUT_LOW isn't supposed to be used directly. + // const unsigned long PRIORITY_INPUT_LOW = 3; + const unsigned long PRIORITY_NORMAL = 4; + const unsigned long PRIORITY_MEDIUMHIGH = 5; + const unsigned long PRIORITY_INPUT_HIGH = 6; + const unsigned long PRIORITY_VSYNC = 7; + // INPUT_HIGHEST is InputTaskManager's internal priority + //const unsigned long PRIORITY_INPUT_HIGHEST = 8; + const unsigned long PRIORITY_RENDER_BLOCKING = 9; + const unsigned long PRIORITY_CONTROL = 10; + + readonly attribute unsigned long priority; +}; + +[uuid(3114c36c-a482-4c6e-9523-1dcfc6f605b9)] +interface nsIRunnableIPCMessageType : nsISupports +{ + readonly attribute unsigned long type; +}; diff --git a/xpcom/threads/nsISerialEventTarget.idl b/xpcom/threads/nsISerialEventTarget.idl new file mode 100644 index 0000000000..9cc8ff9066 --- /dev/null +++ b/xpcom/threads/nsISerialEventTarget.idl @@ -0,0 +1,27 @@ +/* -*- 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 "nsIEventTarget.idl" + +/** + * A serial event target is an event dispatching interface like + * nsIEventTarget. Runnables dispatched to an nsISerialEventTarget are required + * to execute serially. That is, two different runnables dispatched to the + * target should never be allowed to execute simultaneously. One exception to + * this rule is nested event loops. If a runnable spins a nested event loop, + * causing another runnable dispatched to the target to run, the target may + * still be considered "serial". + * + * Examples: + * - nsIThread is a serial event target. + * - Thread pools are not serial event targets. + * - However, one can "convert" a thread pool into an nsISerialEventTarget + * by putting a TaskQueue in front of it. + */ +[builtinclass, scriptable, rust_sync, uuid(9f982380-24b4-49f3-88f6-45e2952036c7)] +interface nsISerialEventTarget : nsIEventTarget +{ +}; diff --git a/xpcom/threads/nsISupportsPriority.idl b/xpcom/threads/nsISupportsPriority.idl new file mode 100644 index 0000000000..d0b8b9a3dd --- /dev/null +++ b/xpcom/threads/nsISupportsPriority.idl @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 interface exposes the general notion of a scheduled object with a + * integral priority value. Following UNIX conventions, smaller (and possibly + * negative) values have higher priority. + * + * This interface does not strictly define what happens when the priority of an + * object is changed. An implementation of this interface is free to define + * the side-effects of changing the priority of an object. In some cases, + * changing the priority of an object may be disallowed (resulting in an + * exception being thrown) or may simply be ignored. + */ +[scriptable, uuid(aa578b44-abd5-4c19-8b14-36d4de6fdc36)] +interface nsISupportsPriority : nsISupports +{ + /** + * Typical priority values. + */ + const long PRIORITY_HIGHEST = -20; + const long PRIORITY_HIGH = -10; + const long PRIORITY_NORMAL = 0; + const long PRIORITY_LOW = 10; + const long PRIORITY_LOWEST = 20; + + /** + * This attribute may be modified to change the priority of this object. The + * implementation of this interface is free to truncate a given priority + * value to whatever limits are appropriate. Typically, this attribute is + * initialized to PRIORITY_NORMAL, but implementations may choose to assign a + * different initial value. + */ + attribute long priority; + + /** + * This method adjusts the priority attribute by a given delta. It helps + * reduce the amount of coding required to increment or decrement the value + * of the priority attribute. + */ + void adjustPriority(in long delta); +}; diff --git a/xpcom/threads/nsITargetShutdownTask.h b/xpcom/threads/nsITargetShutdownTask.h new file mode 100644 index 0000000000..09ac3c5e5f --- /dev/null +++ b/xpcom/threads/nsITargetShutdownTask.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XPCOM_THREADS_NSITARGETSHUTDOWNTASK_H_ +#define XPCOM_THREADS_NSITARGETSHUTDOWNTASK_H_ + +#include "nsISupports.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" + +#define NS_ITARGETSHUTDOWNTASK_IID \ + { \ + 0xb08647aa, 0xcfb5, 0x4630, { \ + 0x8e, 0x26, 0x9a, 0xbe, 0xb3, 0x3f, 0x08, 0x40 \ + } \ + } + +class NS_NO_VTABLE nsITargetShutdownTask : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISHUTDOWNTASK_IID) + + virtual void TargetShutdown() = 0; + + already_AddRefed<nsIRunnable> AsRunnable() { + // FIXME: Try QI to nsINamed if available? + return mozilla::NewRunnableMethod("nsITargetShutdownTask::TargetShutdown", + this, + &nsITargetShutdownTask::TargetShutdown); + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITargetShutdownTask, NS_ITARGETSHUTDOWNTASK_IID) + +#endif diff --git a/xpcom/threads/nsIThread.idl b/xpcom/threads/nsIThread.idl new file mode 100644 index 0000000000..3ba764ac12 --- /dev/null +++ b/xpcom/threads/nsIThread.idl @@ -0,0 +1,222 @@ +/* -*- 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 "nsISerialEventTarget.idl" +#include "nsIThreadShutdown.idl" + +%{C++ +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { +class TimeStamp; +class TimeDurationValueCalculator; +template <typename T> class BaseTimeDuration; +typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration; +enum class EventQueuePriority; +} +%} + +[ptr] native PRThread(PRThread); +native EventQueuePriority(mozilla::EventQueuePriority); + +native nsIEventTargetPtr(nsIEventTarget*); +native nsISerialEventTargetPtr(nsISerialEventTarget*); +native TimeStamp(mozilla::TimeStamp); +native TimeDuration(mozilla::TimeDuration); + +/** + * This interface provides a high-level abstraction for an operating system + * thread. + * + * Threads have a built-in event queue, and a thread is an event target that + * can receive nsIRunnable objects (events) to be processed on the thread. + * + * See nsIThreadManager for the API used to create and locate threads. + */ +[builtinclass, scriptable, rust_sync, uuid(5801d193-29d1-4964-a6b7-70eb697ddf2b)] +interface nsIThread : nsISerialEventTarget +{ + /** + * @returns + * The NSPR thread object corresponding to this nsIThread. + */ + [noscript] readonly attribute PRThread PRThread; + + /** + * @returns + * Whether or not this thread may call into JS. Used in the profiler + * to avoid some unnecessary locking. + */ + [noscript] attribute boolean CanInvokeJS; + + /** + * Thread QoS priorities. Currently only supported on MacOS. + */ + + cenum QoSPriority : 32 { + QOS_PRIORITY_NORMAL, + QOS_PRIORITY_LOW + }; + + /** + * Shutdown the thread. This method prevents further dispatch of events to + * the thread, and it causes any pending events to run to completion before + * the thread joins (see PR_JoinThread) with the current thread. During this + * method call, events for the current thread may be processed. + * + * This method MAY NOT be executed from the thread itself. Instead, it is + * meant to be executed from another thread (usually the thread that created + * this thread or the main application thread). When this function returns, + * the thread will be shutdown, and it will no longer be possible to dispatch + * events to the thread. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * the current thread, that this thread was not created with a call to + * nsIThreadManager::NewThread, or if this method was called more than once + * on the thread object. + */ + void shutdown(); + + /** + * This method may be called to determine if there are any events ready to be + * processed. It may only be called when this thread is the current thread. + * + * Because events may be added to this thread by another thread, a "false" + * result does not mean that this thread has no pending events. It only + * means that there were no pending events when this method was called. + * + * @returns + * A boolean value that if "true" indicates that this thread has one or + * more pending events. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * not the current thread. + */ + boolean hasPendingEvents(); + + /** + * Similar to above, but checks only possible high priority queue. + */ + boolean hasPendingHighPriorityEvents(); + + /** + * Process the next event. If there are no pending events, then this method + * may wait -- depending on the value of the mayWait parameter -- until an + * event is dispatched to this thread. This method is re-entrant but may + * only be called if this thread is the current thread. + * + * @param mayWait + * A boolean parameter that if "true" indicates that the method may block + * the calling thread to wait for a pending event. + * + * @returns + * A boolean value that if "true" indicates that an event was processed. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * not the current thread. + */ + boolean processNextEvent(in boolean mayWait); + + /** + * Shutdown the thread asynchronously. This method immediately prevents + * further dispatch of events to the thread, and it causes any pending events + * to run to completion before this thread joins with the current thread. + * + * UNLIKE shutdown() this does not process events on the current thread. + * Instead it merely ensures that the current thread continues running until + * this thread has shut down. + * + * This method MAY NOT be executed from the thread itself. Instead, it is + * meant to be executed from another thread (usually the thread that created + * this thread or the main application thread). When this function returns, + * the thread will continue running until it exhausts its event queue. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * the current thread, that this thread was not created with a call to + * nsIThreadManager::NewNamedThread, or that this method was called more + * than once on the thread object. + */ + void asyncShutdown(); + + /** + * Like `asyncShutdown`, but also returns a nsIThreadShutdown instance to + * allow observing and controlling the thread's async shutdown progress. + */ + nsIThreadShutdown beginShutdown(); + + /** + * Dispatch an event to a specified queue for the thread. This function + * may be called from any thread, and it may be called re-entrantly. + * Most users should use the NS_Dispatch*() functions in nsThreadUtils instead + * of calling this directly. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * NOTE that the event will be leaked if it fails to dispatch. + * @param queue + * Which event priority queue this should be added to + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [noscript] void dispatchToQueue(in alreadyAddRefed_nsIRunnable event, + in EventQueuePriority queue); + + /** + * This is set to the end of the last 50+ms event that was executed on + * this thread (for MainThread only). Otherwise returns a null TimeStamp. + */ + [noscript] readonly attribute TimeStamp lastLongTaskEnd; + [noscript] readonly attribute TimeStamp lastLongNonIdleTaskEnd; + + /** + * Get information on the timing of the currently-running event. + * + * @param delay + * The amount of time the current running event in the specified queue waited + * to run. Will return TimeDuration() if the queue is empty or has not run any + * new events since event delay monitoring started. NOTE: delay will be + * TimeDuration() if this thread uses a PrioritizedEventQueue (i.e. MainThread) + * and the event priority is below Input. + * @param start + * The time the currently running event began to run, or TimeStamp() if no + * event is running. + */ + [noscript] void getRunningEventDelay(out TimeDuration delay, out TimeStamp start); + + /** + * Set information on the timing of the currently-running event. + * Overrides the values returned by getRunningEventDelay + * + * @param delay + * Delay the running event spent in queues, or TimeDuration() if + * there's no running event. + * @param start + * The time the currently running event began to run, or TimeStamp() if no + * event is running. + */ + [noscript] void setRunningEventDelay(in TimeDuration delay, in TimeStamp start); + + [noscript] void setNameForWakeupTelemetry(in ACString name); + + /** + * Set the QoS priority of threads where this may be available. Currently + * restricted to MacOS. Must be on the thread to call this method. + * + * @param aPriority + * The specified priority we will adjust to. Can be low (background) or + * normal (default / user-interactive) + */ + [noscript] void setThreadQoS(in nsIThread_QoSPriority aPriority); + +}; diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl new file mode 100644 index 0000000000..ecc0f540f1 --- /dev/null +++ b/xpcom/threads/nsIThreadInternal.idl @@ -0,0 +1,110 @@ +/* -*- 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 "nsIThread.idl" + +interface nsIRunnable; +interface nsIThreadObserver; + +/** + * The XPCOM thread object implements this interface, which allows a consumer + * to observe dispatch activity on the thread. + */ +[builtinclass, scriptable, rust_sync, uuid(a3a72e5f-71d9-4add-8f30-59a78fb6d5eb)] +interface nsIThreadInternal : nsIThread +{ + /** + * Get/set the current thread observer (may be null). This attribute may be + * read from any thread, but must only be set on the thread corresponding to + * this thread object. The observer will be released on the thread + * corresponding to this thread object after all other events have been + * processed during a call to Shutdown. + */ + attribute nsIThreadObserver observer; + + /** + * Add an observer that will *only* receive onProcessNextEvent, + * beforeProcessNextEvent. and afterProcessNextEvent callbacks. Always called + * on the target thread, and the implementation does not have to be + * threadsafe. Order of callbacks is not guaranteed (i.e. + * afterProcessNextEvent may be called first depending on whether or not the + * observer is added in a nested loop). Holds a strong ref. + */ + void addObserver(in nsIThreadObserver observer); + + /** + * Remove an observer added via the addObserver call. Once removed the + * observer will never be called again by the thread. + */ + void removeObserver(in nsIThreadObserver observer); +}; + +/** + * This interface provides the observer with hooks to implement a layered + * event queue. For example, it is possible to overlay processing events + * for a GUI toolkit on top of the events for a thread: + * + * var NativeQueue; + * Observer = { + * onDispatchedEvent() { + * NativeQueue.signal(); + * } + * onProcessNextEvent(thread, mayWait) { + * if (NativeQueue.hasNextEvent()) + * NativeQueue.processNextEvent(); + * while (mayWait && !thread.hasPendingEvent()) { + * NativeQueue.wait(); + * NativeQueue.processNextEvent(); + * } + * } + * }; + * + * NOTE: The implementation of this interface must be threadsafe. + * + * NOTE: It is valid to change the thread's observer during a call to an + * observer method. + * + * NOTE: Will be split into two interfaces soon: one for onProcessNextEvent and + * afterProcessNextEvent, then another that inherits the first and adds + * onDispatchedEvent. + */ +[uuid(cc8da053-1776-44c2-9199-b5a629d0a19d)] +interface nsIThreadObserver : nsISupports +{ + /** + * This method is called after an event has been dispatched to the thread. + * This method may be called from any thread. + */ + void onDispatchedEvent(); + + /** + * This method is called when nsIThread::ProcessNextEvent is called. It does + * not guarantee that an event is actually going to be processed. This method + * is only called on the target thread. + * + * @param thread + * The thread being asked to process another event. + * @param mayWait + * Indicates whether or not the method is allowed to block the calling + * thread. For example, this parameter is false during thread shutdown. + */ + void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait); + + /** + * This method is called (from nsIThread::ProcessNextEvent) after an event + * is processed. It does not guarantee that an event was actually processed + * (depends on the value of |eventWasProcessed|. This method is only called + * on the target thread. DO NOT EVER RUN SCRIPT FROM THIS CALLBACK!!! + * + * @param thread + * The thread that processed another event. + * @param eventWasProcessed + * Indicates whether an event was actually processed. May be false if the + * |mayWait| flag was false when calling nsIThread::ProcessNextEvent(). + */ + void afterProcessNextEvent(in nsIThreadInternal thread, + in bool eventWasProcessed); +}; diff --git a/xpcom/threads/nsIThreadManager.idl b/xpcom/threads/nsIThreadManager.idl new file mode 100644 index 0000000000..879ec05e3a --- /dev/null +++ b/xpcom/threads/nsIThreadManager.idl @@ -0,0 +1,182 @@ +/* -*- 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 "nsISupports.idl" + +[ptr] native PRThread(PRThread); + native ThreadCreationOptions(nsIThreadManager::ThreadCreationOptions); + +interface nsIEventTarget; +interface nsIRunnable; +interface nsIThread; + +%{ C++ +#include "mozilla/Maybe.h" +%} + +[scriptable, function, uuid(039a227d-0cb7-44a5-a8f9-dbb7071979f2)] +interface nsINestedEventLoopCondition : nsISupports +{ + /** + * Returns true if the current nested event loop should stop spinning. + */ + bool isDone(); +}; + +/** + * An interface for creating and locating nsIThread instances. + */ +[scriptable, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)] +interface nsIThreadManager : nsISupports +{ + /** + * Default number of bytes reserved for a thread's stack, if no stack size + * is specified in newThread(). + * + * Defaults can be a little overzealous for many platforms. + * + * On Linux and OS X, for instance, the default thread stack size is whatever + * getrlimit(RLIMIT_STACK) returns, which is often set at 8MB. Or, on Linux, + * if the stack size is unlimited, we fall back to 2MB. This causes particular + * problems on Linux, which allocates 2MB huge VM pages, and will often + * immediately allocate them for any stacks which are 2MB or larger. + * + * The default on Windows is 1MB, which is a little more reasonable. But the + * vast majority of our threads don't need anywhere near that much space. + * + * ASan, TSan and non-opt builds, however, often need a bit more, so give + * them the platform default. + */ +%{C++ +#if defined(MOZ_ASAN) || defined(MOZ_TSAN) || !defined(__OPTIMIZE__) + static constexpr uint32_t DEFAULT_STACK_SIZE = 0; +#else + static constexpr uint32_t DEFAULT_STACK_SIZE = 256 * 1024; +#endif + + static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE; + + struct ThreadCreationOptions { + // The size in bytes to reserve for the thread's stack. A value of `0` means + // to use the platform default. + uint32_t stackSize = nsIThreadManager::DEFAULT_STACK_SIZE; + + // If set to `true`, any attempts to dispatch runnables to this thread + // without `DISPATCH_IGNORE_BLOCK_DISPATCH` will fail. + // + // This is intended to be used for threads which are expected to generally + // only service a single runnable (other than thread lifecycle runnables), + // and perform their own event dispatching internaly, such as thread pool + // threads or the timer thread. + bool blockDispatch = false; + + // (Windows-only) Whether the thread should have a MessageLoop capable of + // processing native UI events. Defaults to false. + bool isUiThread = false; + + // If set, long task markers will be collected for tasks + // longer than longTaskLength ms when profiling is enabled. + // See https://www.w3.org/TR/longtasks + mozilla::Maybe<uint32_t> longTaskLength; + }; +%} + + /** + * Create a new thread (a global, user PRThread) with the specified name. + * + * @param name + * The name of the thread. If it is empty the thread will not be named. + * @param options + * Configuration options for the newly created thread. + * + * @returns + * The newly created nsIThread object. + */ + [noscript] nsIThread newNamedThread(in ACString name, in ThreadCreationOptions options); + + /** + * Get the main thread. + */ + readonly attribute nsIThread mainThread; + + /** + * Get the current thread. If the calling thread does not already have a + * nsIThread associated with it, then a new nsIThread will be created and + * associated with the current PRThread. + */ + readonly attribute nsIThread currentThread; + + /** + * This queues a runnable to the main thread. It's a shortcut for JS callers + * to be used instead of + * .mainThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL); + * or + * .currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL); + * C++ callers should instead use NS_DispatchToMainThread. + */ + [optional_argc] + void dispatchToMainThread(in nsIRunnable event, [optional] in uint32_t priority); + + /** + * Similar to dispatchToMainThread, but wraps the event with extra + * runnable that allocates nsAutoMicroTask. + */ + [optional_argc] + void dispatchToMainThreadWithMicroTask(in nsIRunnable event, [optional] in uint32_t priority); + + /** + * This queues a runnable to the main thread's idle queue. + * + * @param event + * The event to dispatch. + * @param timeout + * The time in milliseconds until this event should be moved from the idle + * queue to the regular queue if it hasn't been executed by then. If not + * passed or a zero value is specified, the event will never be moved to + * the regular queue. + */ + void idleDispatchToMainThread(in nsIRunnable event, + [optional] in uint32_t timeout); + + /* + * A helper method to dispatch a task through nsIDirectTaskDispatcher to the + * current thread. + */ + void dispatchDirectTaskToCurrentThread(in nsIRunnable event); + + /** + * Enter a nested event loop on the current thread, waiting on, and + * processing events until condition.isDone() returns true. + * + * If condition.isDone() throws, this function will throw as well. + * + * C++ code should not use this function, instead preferring + * mozilla::SpinEventLoopUntil. + */ + void spinEventLoopUntil(in ACString aVeryGoodReasonToDoThis, in nsINestedEventLoopCondition condition); + + /** + * Similar to the previous method, but the spinning of the event loop + * terminates when the quit application shutting down starts. + * + * C++ code should not use this function, instead preferring + * mozilla::SpinEventLoopUntil. + */ + void spinEventLoopUntilOrQuit(in ACString aVeryGoodReasonToDoThis, in nsINestedEventLoopCondition condition); + + /** + * Spin the current thread's event loop until there are no more pending + * events. This could be done with spinEventLoopUntil, but that would + * require access to the current thread from JavaScript, which we are + * moving away from. + */ + void spinEventLoopUntilEmpty(); + + /** + * Return the EventTarget for the main thread. + */ + readonly attribute nsIEventTarget mainThreadEventTarget; +}; diff --git a/xpcom/threads/nsIThreadPool.idl b/xpcom/threads/nsIThreadPool.idl new file mode 100644 index 0000000000..48b4a465d8 --- /dev/null +++ b/xpcom/threads/nsIThreadPool.idl @@ -0,0 +1,115 @@ +/* -*- 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 "nsIEventTarget.idl" +#include "nsIThread.idl" + +[uuid(ef194cab-3f86-4b61-b132-e5e96a79e5d1)] +interface nsIThreadPoolListener : nsISupports +{ + /** + * Called when a new thread is created by the thread pool. The notification + * happens on the newly-created thread. + */ + void onThreadCreated(); + + /** + * Called when a thread is about to be destroyed by the thread pool. The + * notification happens on the thread that is about to be destroyed. + */ + void onThreadShuttingDown(); +}; + +/** + * An interface to a thread pool. A thread pool creates a limited number of + * anonymous (unnamed) worker threads. An event dispatched to the thread pool + * will be run on the next available worker thread. + */ +[rust_sync, uuid(76ce99c9-8e43-489a-9789-f27cc4424965)] +interface nsIThreadPool : nsIEventTarget +{ + /** + * Set the entire pool's QoS priority. If the priority has not changed, do nothing. + * Existing threads will update their QoS priority the next time they become + * active, and newly created threads will set this QoS priority upon creation. + */ + [noscript] void setQoSForThreads(in nsIThread_QoSPriority aPriority); + + /** + * Shutdown the thread pool. This method may not be executed from any thread + * in the thread pool. Instead, it is meant to be executed from another + * thread (usually the thread that created this thread pool). When this + * function returns, the thread pool and all of its threads will be shutdown, + * and it will no longer be possible to dispatch tasks to the thread pool. + * + * As a side effect, events on the current thread will be processed. + */ + void shutdown(); + + /** + * Shutdown the thread pool, but only wait for aTimeoutMs. After the timeout + * expires, any threads that have not shutdown yet are leaked and will not + * block shutdown. + * + * This method should only be used at during shutdown to cleanup threads that + * made blocking calls to code outside our control, and can't be safely + * terminated. We choose to leak them intentionally to avoid a shutdown hang. + */ + [noscript] void shutdownWithTimeout(in long aTimeoutMs); + + /** + * Get/set the maximum number of threads allowed at one time in this pool. + */ + attribute unsigned long threadLimit; + + /** + * Get/set the maximum number of idle threads kept alive. + */ + attribute unsigned long idleThreadLimit; + + /** + * Get/set the amount of time in milliseconds before an idle thread is + * destroyed. + */ + attribute unsigned long idleThreadTimeout; + + /** + * If set to true the idle timeout will be calculated as idleThreadTimeout + * divideded by the number of idle threads at the moment. This may help + * save memory allocations but still keep reasonable amount of idle threads. + * Default is false, use |idleThreadTimeout| for all threads. + */ + attribute boolean idleThreadTimeoutRegressive; + + /** + * Get/set the number of bytes reserved for the stack of all threads in + * the pool. By default this is nsIThreadManager::DEFAULT_STACK_SIZE. + */ + attribute unsigned long threadStackSize; + + /** + * An optional listener that will be notified when a thread is created or + * destroyed in the course of the thread pool's operation. + * + * A listener will only receive notifications about threads created after the + * listener is set so it is recommended that the consumer set the listener + * before dispatching the first event. A listener that receives an + * onThreadCreated() notification is guaranteed to always receive the + * corresponding onThreadShuttingDown() notification. + * + * The thread pool takes ownership of the listener and releases it when the + * shutdown() method is called. Threads created after the listener is set will + * also take ownership of the listener so that the listener will be kept alive + * long enough to receive the guaranteed onThreadShuttingDown() notification. + */ + attribute nsIThreadPoolListener listener; + + /** + * Set the label for threads in the pool. All threads will be named + * "<aName> #<n>", where <n> is a serial number. + */ + void setName(in ACString aName); +}; diff --git a/xpcom/threads/nsIThreadShutdown.idl b/xpcom/threads/nsIThreadShutdown.idl new file mode 100644 index 0000000000..a08d64165b --- /dev/null +++ b/xpcom/threads/nsIThreadShutdown.idl @@ -0,0 +1,57 @@ +/* -*- 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 "nsISupports.idl" + +interface nsIRunnable; + +/** + * Handle for the ongoing shutdown progress of a given thread which can be used + * to observe and interrupt async shutdown progress. Methods on this interface + * may generally only be used on the thread which called + * `nsIThread::beginShutdown`. + */ +[scriptable, builtinclass, uuid(70a43748-6130-4ea6-a440-7c74e1b7dd7c)] +interface nsIThreadShutdown : nsISupports +{ + /** + * Register a runnable to be executed when the thread has completed shutdown, + * or shutdown has been cancelled due to `stopWaitingAndLeakThread()`. + * + * If the thread has already completed or cancelled shutdown, the runnable + * may be executed synchronously. + * + * May only be called on the thread which invoked `nsIThread::beginShutdown`. + */ + void onCompletion(in nsIRunnable aEvent); + + /** + * Check if the target thread has completed shutdown. + * + * May only be accessed on the thread which called `nsIThread::beginShutdown`. + */ + [infallible] readonly attribute boolean completed; + + /** + * Give up on waiting for the shutting down thread to exit. Calling this + * method will allow the thread to continue running, no longer block shutdown, + * and the thread will never be joined or have its resources reclaimed. + * + * Completion callbacks attached to this `nsIThreadShutdown` may be executed + * during this call. + * + * This method should NOT be called except in exceptional circumstances during + * shutdown, as it will cause resources for the shutting down thread to be + * leaked. + * + * May only be called on the thread which called `nsIThread::beginShutdown` + * + * @throws NS_ERROR_NOT_AVAILABLE + * Indicates that the target thread has already stopped running and a + * request to be joined is already being dispatched to the waiting thread. + */ + void stopWaitingAndLeakThread(); +}; diff --git a/xpcom/threads/nsITimer.idl b/xpcom/threads/nsITimer.idl new file mode 100644 index 0000000000..5d20c315b4 --- /dev/null +++ b/xpcom/threads/nsITimer.idl @@ -0,0 +1,376 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsINamed.idl" + +interface nsIObserver; +interface nsIEventTarget; + +%{C++ +#include "mozilla/MemoryReporting.h" +#include "mozilla/TimeStamp.h" +#include <functional> + +/** + * The signature of the timer callback function passed to initWithFuncCallback. + * This is the function that will get called when the timer expires if the + * timer is initialized via initWithFuncCallback. + * + * @param aTimer the timer which has expired + * @param aClosure opaque parameter passed to initWithFuncCallback + */ +class nsITimer; +typedef void (*nsTimerCallbackFunc) (nsITimer *aTimer, void *aClosure); +%} + +native MallocSizeOf(mozilla::MallocSizeOf); +native nsTimerCallbackFunc(nsTimerCallbackFunc); +[ref] native TimeDuration(mozilla::TimeDuration); + +/** + * The callback interface for timers. + */ +interface nsITimer; + +[function, scriptable, uuid(a796816d-7d47-4348-9ab8-c7aeb3216a7d)] +interface nsITimerCallback : nsISupports +{ + /** + * @param aTimer the timer which has expired + */ + void notify(in nsITimer timer); +}; + +%{C++ +// Two timer deadlines must differ by less than half the PRIntervalTime domain. +#define DELAY_INTERVAL_LIMIT PR_BIT(8 * sizeof(PRIntervalTime) - 1) +%} + +/** + * nsITimer instances must be initialized by calling one of the "init" methods + * documented below. You may also re-initialize (using one of the init() + * methods) an existing instance to avoid the overhead of destroying and + * creating a timer. It is not necessary to cancel the timer in that case. + * + * By default a timer will fire on the thread that created it. Set the .target + * attribute to fire on a different thread. Once you have set a timer's .target + * and called one of its init functions, any further interactions with the timer + * (calling cancel(), changing member fields, etc) should only be done by the + * target thread, or races may occur with bad results like timers firing after + * they've been canceled, and/or not firing after re-initiatization. + */ +[scriptable, builtinclass, uuid(3de4b105-363c-482c-a409-baac83a01bfc)] +interface nsITimer : nsISupports +{ + /* Timer types */ + + /** + * Type of a timer that fires once only. + */ + const short TYPE_ONE_SHOT = 0; + + /** + * After firing, a TYPE_REPEATING_SLACK timer is stopped and not restarted + * until its callback completes. Specified timer period will be at least + * the time between when processing for last firing the callback completes + * and when the next firing occurs. + * + * This is the preferable repeating type for most situations. + */ + const short TYPE_REPEATING_SLACK = 1; + + /** + * TYPE_REPEATING_PRECISE is just a synonym for + * TYPE_REPEATING_PRECISE_CAN_SKIP. They used to be distinct, but the old + * TYPE_REPEATING_PRECISE kind was similar to TYPE_REPEATING_PRECISE_CAN_SKIP + * while also being less useful. So the distinction was removed. + */ + const short TYPE_REPEATING_PRECISE = 2; + + /** + * A TYPE_REPEATING_PRECISE_CAN_SKIP repeating timer aims to have constant + * period between firings. The processing time for each timer callback will + * not influence the timer period. If the callback finishes after the next + * firing(s) should have happened (either because the callback took a long + * time, or the callback was called extremely late), that firing(s) is + * skipped, but the following sequence of firing times will not be altered. + * This timer type guarantees that it will not queue up new events to fire + * the callback until the previous callback event finishes firing. This is + * the only non-slack timer available. + */ + const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; + + /** + * Same as TYPE_REPEATING_SLACK with the exception that idle events + * won't yield to timers with this type. Use this when you want an + * idle callback to be scheduled to run even though this timer is + * about to fire. + */ + const short TYPE_REPEATING_SLACK_LOW_PRIORITY = 4; + + /** + * Same as TYPE_ONE_SHOT with the exception that idle events won't + * yield to timers with this type. Use this when you want an idle + * callback to be scheduled to run even though this timer is about + * to fire. + */ + const short TYPE_ONE_SHOT_LOW_PRIORITY = 5; + + /** + * Initialize a timer that will fire after the said delay. + * A user must keep a reference to this timer till it is + * is no longer needed or has been cancelled. + * + * @param aObserver the callback object that observes the + * ``timer-callback'' topic with the subject being + * the timer itself when the timer fires: + * + * observe(nsISupports aSubject, => nsITimer + * string aTopic, => ``timer-callback'' + * wstring data => null + * + * @param aDelayInMs delay in milliseconds for timer to fire + * @param aType timer type per TYPE* consts defined above + */ + void init(in nsIObserver aObserver, in unsigned long aDelayInMs, + in unsigned long aType); + + + /** + * Initialize a timer to fire after the given millisecond interval. + * This version takes a callback object. + * + * @param aFunc nsITimerCallback interface to call when timer expires + * @param aDelayInMs The millisecond interval + * @param aType Timer type per TYPE* consts defined above + */ + void initWithCallback(in nsITimerCallback aCallback, + in unsigned long aDelayInMs, + in unsigned long aType); + + /** + * Initialize a timer to fire after the high resolution TimeDuration. + * This version takes a callback object. + * + * @param aFunc nsITimerCallback interface to call when timer expires + * @param aDelay The high resolution interval + * @param aType Timer type per TYPE* consts defined above + */ + [noscript] void initHighResolutionWithCallback(in nsITimerCallback aCallback, + [const] in TimeDuration aDelay, + in unsigned long aType); + + /** + * Cancel the timer. This method works on all types, not just on repeating + * timers -- you might want to cancel a TYPE_ONE_SHOT timer, and even reuse + * it by re-initializing it (to avoid object destruction and creation costs + * by conserving one timer instance). + */ + void cancel(); + + /** + * Like initWithFuncCallback, but also takes a name for the timer; the name + * will be used when timer profiling is enabled via the "TimerFirings" log + * module. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The millisecond interval + * @param aType Timer type per TYPE* consts defined above + * @param aName The timer's name + */ + [noscript] void initWithNamedFuncCallback(in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType, + in string aName); + + /** + * Initialize a timer to fire after the high resolution TimeDuration. + * This version takes a named function callback. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The high resolution interval + * @param aType Timer type per TYPE* consts defined above + * @param aName The timer's name + */ + [noscript] void initHighResolutionWithNamedFuncCallback( + in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + [const] in TimeDuration aDelay, + in unsigned long aType, + in string aName); + + /** + * The millisecond delay of the timeout. + * + * NOTE: Re-setting the delay on a one-shot timer that has already fired + * doesn't restart the timer. Call one of the init() methods to restart + * a one-shot timer. + */ + attribute unsigned long delay; + + /** + * The timer type - one of the above TYPE_* constants. + */ + attribute unsigned long type; + + /** + * The opaque pointer pass to initWithFuncCallback. + */ + [noscript] readonly attribute voidPtr closure; + + /** + * The nsITimerCallback object passed to initWithCallback. + */ + readonly attribute nsITimerCallback callback; + + /** + * The nsIEventTarget where the callback will be dispatched. Note that this + * target may only be set before the call to one of the init methods above. + * + * By default the target is the thread that created the timer. + */ + attribute nsIEventTarget target; + + readonly attribute ACString name; + + /** + * The number of microseconds this nsITimer implementation can possibly + * fire early. + */ + [noscript] readonly attribute unsigned long allowedEarlyFiringMicroseconds; + + [notxpcom, nostdcall] size_t sizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); +}; + +%{C++ +#include "nsCOMPtr.h" + +already_AddRefed<nsITimer> NS_NewTimer(); + +already_AddRefed<nsITimer> NS_NewTimer(nsIEventTarget* aTarget); + +nsresult +NS_NewTimerWithObserver(nsITimer** aTimer, + nsIObserver* aObserver, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithObserver(nsIObserver* aObserver, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithCallback(nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithCallback(nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + std::function<void(nsITimer*)>&& aCallback, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithCallback(std::function<void(nsITimer*)>&& aCallback, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + std::function<void(nsITimer*)>&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithCallback(std::function<void(nsITimer*)>&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +#define NS_TIMER_CALLBACK_TOPIC "timer-callback" + +#ifndef RELEASE_OR_BETA +#undef NS_DECL_NSITIMERCALLBACK +#define NS_DECL_NSITIMERCALLBACK \ + NS_IMETHOD Notify(nsITimer *timer) override; \ + inline void _ensure_GetName_exists(void) { \ + static_assert(std::is_convertible<decltype(this), nsINamed*>::value, \ + "nsITimerCallback implementations must also implement nsINamed"); \ + } +#endif +%} + +[scriptable, builtinclass, uuid(5482506d-1d21-4d08-b01c-95c87e1295ad)] +interface nsITimerManager : nsISupports +{ + /** + * Returns a read-only list of nsITimer objects, implementing only the name, + * delay and type attribute getters. + * This is meant to be used for tests, to verify that no timer is leftover + * at the end of a test. */ + Array<nsITimer> getTimers(); +}; diff --git a/xpcom/threads/nsMemoryPressure.cpp b/xpcom/threads/nsMemoryPressure.cpp new file mode 100644 index 0000000000..dbd3a92f79 --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.cpp @@ -0,0 +1,104 @@ +/* -*- 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 "nsMemoryPressure.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Services.h" + +#include "nsThreadUtils.h" +#include "nsIObserverService.h" + +using namespace mozilla; + +const char* const kTopicMemoryPressure = "memory-pressure"; +const char* const kTopicMemoryPressureStop = "memory-pressure-stop"; +const char16_t* const kSubTopicLowMemoryNew = u"low-memory"; +const char16_t* const kSubTopicLowMemoryOngoing = u"low-memory-ongoing"; + +// This is accessed from any thread through NS_NotifyOfEventualMemoryPressure +static Atomic<MemoryPressureState, Relaxed> sMemoryPressurePending( + MemoryPressureState::NoPressure); + +void NS_NotifyOfEventualMemoryPressure(MemoryPressureState aState) { + MOZ_ASSERT(aState != MemoryPressureState::None); + + /* + * A new memory pressure event erases an ongoing (or stop of) memory pressure, + * but an existing "new" memory pressure event takes precedence over a new + * "ongoing" or "stop" memory pressure event. + */ + switch (aState) { + case MemoryPressureState::None: + case MemoryPressureState::LowMemory: + sMemoryPressurePending = aState; + break; + case MemoryPressureState::NoPressure: + sMemoryPressurePending.compareExchange(MemoryPressureState::None, aState); + break; + } +} + +nsresult NS_NotifyOfMemoryPressure(MemoryPressureState aState) { + NS_NotifyOfEventualMemoryPressure(aState); + nsCOMPtr<nsIRunnable> event = + new Runnable("NS_DispatchEventualMemoryPressure"); + return NS_DispatchToMainThread(event); +} + +void NS_DispatchMemoryPressure() { + MOZ_ASSERT(NS_IsMainThread()); + static MemoryPressureState sMemoryPressureStatus = + MemoryPressureState::NoPressure; + + MemoryPressureState mpPending = + sMemoryPressurePending.exchange(MemoryPressureState::None); + if (mpPending == MemoryPressureState::None) { + return; + } + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (!os) { + NS_WARNING("Can't get observer service!"); + return; + } + + switch (mpPending) { + case MemoryPressureState::None: + MOZ_ASSERT_UNREACHABLE("Already handled this case above."); + break; + case MemoryPressureState::LowMemory: + switch (sMemoryPressureStatus) { + case MemoryPressureState::None: + MOZ_ASSERT_UNREACHABLE("The internal status should never be None."); + break; + case MemoryPressureState::NoPressure: + sMemoryPressureStatus = MemoryPressureState::LowMemory; + os->NotifyObservers(nullptr, kTopicMemoryPressure, + kSubTopicLowMemoryNew); + break; + case MemoryPressureState::LowMemory: + os->NotifyObservers(nullptr, kTopicMemoryPressure, + kSubTopicLowMemoryOngoing); + break; + } + break; + case MemoryPressureState::NoPressure: + switch (sMemoryPressureStatus) { + case MemoryPressureState::None: + MOZ_ASSERT_UNREACHABLE("The internal status should never be None."); + break; + case MemoryPressureState::NoPressure: + // Already no pressure. Do nothing. + break; + case MemoryPressureState::LowMemory: + sMemoryPressureStatus = MemoryPressureState::NoPressure; + os->NotifyObservers(nullptr, kTopicMemoryPressureStop, nullptr); + break; + } + break; + } +} diff --git a/xpcom/threads/nsMemoryPressure.h b/xpcom/threads/nsMemoryPressure.h new file mode 100644 index 0000000000..5a68b0bce5 --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.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 nsMemoryPressure_h__ +#define nsMemoryPressure_h__ + +#include "nscore.h" + +/* + * These pre-defined strings are the topic to pass to the observer + * service to declare the memory-pressure or lift the memory-pressure. + * + * 1. Notify kTopicMemoryPressure with kSubTopicLowMemoryNew + * New memory pressure deteced + * On a new memory pressure, we stop everything to start cleaning + * aggresively the memory used, in order to free as much memory as + * possible. + * + * 2. Notify kTopicMemoryPressure with kSubTopicLowMemoryOngoing + * Repeated memory pressure. + * A repeated memory pressure implies to clean softly recent allocations. + * It is supposed to happen after a new memory pressure which already + * cleaned aggressivley. So there is no need to damage the reactivity of + * Gecko by stopping the world again. + * + * In case of conflict with an new memory pressue, the new memory pressure + * takes precedence over an ongoing memory pressure. The reason being + * that if no events are processed between 2 notifications (new followed + * by ongoing, or ongoing followed by a new) we want to be as aggresive as + * possible on the clean-up of the memory. After all, we are trying to + * keep Gecko alive as long as possible. + * + * 3. Notify kTopicMemoryPressureStop with nullptr + * Memory pressure stopped. + * We're no longer under acute memory pressure, so we might want to have a + * chance of (cautiously) re-enabling some things we previously turned off. + * As above, an already enqueued new memory pressure event takes precedence. + * The priority ordering between concurrent attempts to queue both stopped + * and ongoing memory pressure is currently not defined. + */ +extern const char* const kTopicMemoryPressure; +extern const char* const kTopicMemoryPressureStop; +extern const char16_t* const kSubTopicLowMemoryNew; +extern const char16_t* const kSubTopicLowMemoryOngoing; + +enum class MemoryPressureState : uint32_t { + None, // For internal use. Don't use this value. + LowMemory, + NoPressure, +}; + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event, but if there are no events pending in + * the main thread's event queue, the memory pressure event would not be + * dispatched until one is enqueued. It is infallible and does not allocate + * any memory. + * + * You may call this function from any thread. + */ +void NS_NotifyOfEventualMemoryPressure(MemoryPressureState aState); + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event. We wake up the main thread by adding a + * dummy event to its event loop, so, unlike with + * NS_NotifyOfEventualMemoryPressure, this memory-pressure event is always + * fired relatively quickly, even if the event loop is otherwise empty. + * + * You may call this function from any thread. + */ +nsresult NS_NotifyOfMemoryPressure(MemoryPressureState aState); + +#endif // nsMemoryPressure_h__ diff --git a/xpcom/threads/nsProcess.h b/xpcom/threads/nsProcess.h new file mode 100644 index 0000000000..95d6748640 --- /dev/null +++ b/xpcom/threads/nsProcess.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsPROCESSWIN_H_ +#define _nsPROCESSWIN_H_ + +#if defined(XP_WIN) +# define PROCESSMODEL_WINAPI +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsIProcess.h" +#include "nsIObserver.h" +#include "nsMaybeWeakPtr.h" +#include "nsString.h" +#ifndef XP_UNIX +# include "prproces.h" +#endif +#if defined(PROCESSMODEL_WINAPI) +# include <windows.h> +# include <shellapi.h> +#endif + +#define NS_PROCESS_CID \ + { \ + 0x7b4eeb20, 0xd781, 0x11d4, { \ + 0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca \ + } \ + } + +class nsIFile; + +class nsProcess final : public nsIProcess, public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROCESS + NS_DECL_NSIOBSERVER + + nsProcess(); + + private: + ~nsProcess(); + PRThread* CreateMonitorThread(); + static void Monitor(void* aArg); + void ProcessComplete(); + nsresult CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); + nsresult CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); + // The 'args' array is null-terminated. + nsresult RunProcess(bool aBlocking, char** aArgs, nsIObserver* aObserver, + bool aHoldWeak, bool aArgsUTF8); + + PRThread* mThread; + mozilla::Mutex mLock; + bool mShutdown MOZ_GUARDED_BY(mLock); + bool mBlocking; + bool mStartHidden; + bool mNoShell; + + nsCOMPtr<nsIFile> mExecutable; + nsString mTargetPath; + int32_t mPid; + nsMaybeWeakPtr<nsIObserver> mObserver; + + // These members are modified by multiple threads, any accesses should be + // protected with mLock. + int32_t mExitValue MOZ_GUARDED_BY(mLock); +#if defined(PROCESSMODEL_WINAPI) + HANDLE mProcess MOZ_GUARDED_BY(mLock); +#elif !defined(XP_UNIX) + PRProcess* mProcess MOZ_GUARDED_BY(mLock); +#endif +}; + +#endif diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp new file mode 100644 index 0000000000..dbd9993f54 --- /dev/null +++ b/xpcom/threads/nsProcessCommon.cpp @@ -0,0 +1,600 @@ +/* -*- 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/. */ + +/***************************************************************************** + * + * nsProcess is used to execute new processes and specify if you want to + * wait (blocking) or continue (non-blocking). + * + ***************************************************************************** + */ + +#include "mozilla/ArrayUtils.h" + +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsProcess.h" +#include "prio.h" +#include "prenv.h" +#include "nsCRT.h" +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include "mozilla/Services.h" + +#include <stdlib.h> + +#if defined(PROCESSMODEL_WINAPI) +# include "nsString.h" +# include "nsLiteralString.h" +# include "nsReadableUtils.h" +# include "mozilla/AssembleCmdLine.h" +# include "mozilla/UniquePtrExtensions.h" +#else +# ifdef XP_MACOSX +# include <crt_externs.h> +# include <spawn.h> +# endif +# ifdef XP_UNIX +# ifndef XP_MACOSX +# include "base/process_util.h" +# endif +# include <sys/wait.h> +# include <sys/errno.h> +# endif +# include <sys/types.h> +# include <signal.h> +#endif + +using namespace mozilla; + +//-------------------------------------------------------------------// +// nsIProcess implementation +//-------------------------------------------------------------------// +NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, nsIObserver) + +// Constructor +nsProcess::nsProcess() + : mThread(nullptr), + mLock("nsProcess.mLock"), + mShutdown(false), + mBlocking(false), + mStartHidden(false), + mNoShell(false), + mPid(-1), + mExitValue(-1) +#if !defined(XP_UNIX) + , + mProcess(nullptr) +#endif +{ +} + +// Destructor +nsProcess::~nsProcess() = default; + +NS_IMETHODIMP +nsProcess::Init(nsIFile* aExecutable) { + if (mExecutable) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (NS_WARN_IF(!aExecutable)) { + return NS_ERROR_INVALID_ARG; + } + bool isFile; + + // First make sure the file exists + nsresult rv = aExecutable->IsFile(&isFile); + if (NS_FAILED(rv)) { + return rv; + } + if (!isFile) { + return NS_ERROR_FAILURE; + } + + // Store the nsIFile in mExecutable + mExecutable = aExecutable; + // Get the path because it is needed by the NSPR process creation +#ifdef XP_WIN + rv = mExecutable->GetTarget(mTargetPath); + if (NS_FAILED(rv) || mTargetPath.IsEmpty()) +#endif + rv = mExecutable->GetPath(mTargetPath); + + return rv; +} + +void nsProcess::Monitor(void* aArg) { + RefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(aArg)); + + if (!process->mBlocking) { + NS_SetCurrentThreadName("RunProcess"); + } + +#if defined(PROCESSMODEL_WINAPI) + HANDLE processHandle; + { + // The mutex region cannot include WaitForSingleObject otherwise we'll + // block calls such as Kill. So lock on access and store a local. + MutexAutoLock lock(process->mLock); + processHandle = process->mProcess; + } + + DWORD dwRetVal; + unsigned long exitCode = -1; + + dwRetVal = WaitForSingleObject(processHandle, INFINITE); + if (dwRetVal != WAIT_FAILED) { + if (GetExitCodeProcess(processHandle, &exitCode) == FALSE) { + exitCode = -1; + } + } + + // Lock in case Kill or GetExitCode are called during this. + { + MutexAutoLock lock(process->mLock); + CloseHandle(process->mProcess); + process->mProcess = nullptr; + process->mExitValue = exitCode; + if (process->mShutdown) { + return; + } + } +#else +# ifdef XP_UNIX + int exitCode = -1; + int status = 0; + pid_t result; + do { + result = waitpid(process->mPid, &status, 0); + } while (result == -1 && errno == EINTR); + if (result == process->mPid) { + if (WIFEXITED(status)) { + exitCode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + exitCode = 256; // match NSPR's signal exit status + } + } +# else + int32_t exitCode = -1; + PRProcess* prProcess; + { + // The mutex region cannot include PR_WaitProcess otherwise we'll + // block calls such as Kill. So lock on access and store a local. + MutexAutoLock lock(process->mLock); + prProcess = process->mProcess; + } + if (PR_WaitProcess(prProcess, &exitCode) != PR_SUCCESS) { + exitCode = -1; + } +# endif + + // Lock in case Kill or GetExitCode are called during this + { + MutexAutoLock lock(process->mLock); +# if !defined(XP_UNIX) + process->mProcess = nullptr; +# endif + process->mExitValue = exitCode; + if (process->mShutdown) { + return; + } + } +#endif + + // If we ran a background thread for the monitor then notify on the main + // thread + if (NS_IsMainThread()) { + process->ProcessComplete(); + } else { + NS_DispatchToMainThread(NewRunnableMethod( + "nsProcess::ProcessComplete", process, &nsProcess::ProcessComplete)); + } +} + +void nsProcess::ProcessComplete() { + if (mThread) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + PR_JoinThread(mThread); + mThread = nullptr; + } + + const char* topic; + { + MutexAutoLock lock(mLock); + if (mExitValue != 0) { + topic = "process-failed"; + } else { + topic = "process-finished"; + } + } + + mPid = -1; + nsCOMPtr<nsIObserver> observer = mObserver.GetValue(); + mObserver = nullptr; + + if (observer) { + observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr); + } +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount) { + return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false); +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunAsync(const char** aArgs, uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak) { + return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak); +} + +nsresult nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, + nsIObserver* aObserver, + bool aHoldWeak) { + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2)); + + my_argv[0] = ToNewUTF8String(mTargetPath); + + for (uint32_t i = 0; i < aCount; ++i) { + my_argv[i + 1] = const_cast<char*>(aArgs[i]); + } + + my_argv[aCount + 1] = nullptr; + + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false); + + free(my_argv[0]); + free(my_argv); + return rv; +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount) { + return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false); +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunwAsync(const char16_t** aArgs, uint32_t aCount, + nsIObserver* aObserver, bool aHoldWeak) { + return CopyArgsAndRunProcessw(false, aArgs, aCount, aObserver, aHoldWeak); +} + +nsresult nsProcess::CopyArgsAndRunProcessw(bool aBlocking, + const char16_t** aArgs, + uint32_t aCount, + nsIObserver* aObserver, + bool aHoldWeak) { + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2)); + + my_argv[0] = ToNewUTF8String(mTargetPath); + + for (uint32_t i = 0; i < aCount; i++) { + my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i])); + } + + my_argv[aCount + 1] = nullptr; + + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true); + + for (uint32_t i = 0; i <= aCount; ++i) { + free(my_argv[i]); + } + free(my_argv); + return rv; +} + +nsresult nsProcess::RunProcess(bool aBlocking, char** aMyArgv, + nsIObserver* aObserver, bool aHoldWeak, + bool aArgsUTF8) { + NS_WARNING_ASSERTION(!XRE_IsContentProcess(), + "No launching of new processes in the content process"); + + if (NS_WARN_IF(!mExecutable)) { + return NS_ERROR_NOT_INITIALIZED; + } + if (NS_WARN_IF(mThread)) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aObserver) { + if (aHoldWeak) { + nsresult rv = NS_OK; + mObserver = do_GetWeakReference(aObserver, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mObserver = aObserver; + } + } + + { + MutexAutoLock lock(mLock); + mExitValue = -1; + mPid = -1; + } + +#if defined(PROCESSMODEL_WINAPI) + BOOL retVal; + UniqueFreePtr<wchar_t> cmdLine; + + // |aMyArgv| is null-terminated and always starts with the program path. If + // the second slot is non-null then arguments are being passed. + if (aMyArgv[1] || mNoShell) { + // Pass the executable path as argv[0] to the launched program when calling + // CreateProcess(). + char** argv = mNoShell ? aMyArgv : aMyArgv + 1; + + wchar_t* assembledCmdLine = nullptr; + if (assembleCmdLine(argv, &assembledCmdLine, + aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + cmdLine.reset(assembledCmdLine); + } + + // The program name in aMyArgv[0] is always UTF-8 + NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]); + + if (mNoShell) { + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + startupInfo.dwFlags = STARTF_USESHOWWINDOW; + startupInfo.wShowWindow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL; + + PROCESS_INFORMATION processInfo; + retVal = CreateProcess(/* lpApplicationName = */ wideFile.get(), + /* lpCommandLine */ cmdLine.get(), + /* lpProcessAttributes = */ NULL, + /* lpThreadAttributes = */ NULL, + /* bInheritHandles = */ FALSE, + /* dwCreationFlags = */ 0, + /* lpEnvironment = */ NULL, + /* lpCurrentDirectory = */ NULL, + /* lpStartupInfo = */ &startupInfo, + /* lpProcessInformation */ &processInfo); + + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + CloseHandle(processInfo.hThread); + + // TODO(bug 1763051): assess if we need further work around this locking. + MutexAutoLock lock(mLock); + mProcess = processInfo.hProcess; + } else { + SHELLEXECUTEINFOW sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + sinfo.hwnd = nullptr; + sinfo.lpFile = wideFile.get(); + sinfo.nShow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL; + + /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows + * from appearing. This makes behavior the same on all platforms. The flag + * will not have any effect on non-console applications. + */ + sinfo.fMask = + SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS; + + if (cmdLine) { + sinfo.lpParameters = cmdLine.get(); + } + + retVal = ShellExecuteExW(&sinfo); + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + MutexAutoLock lock(mLock); + mProcess = sinfo.hProcess; + } + + { + MutexAutoLock lock(mLock); + mPid = GetProcessId(mProcess); + } +#elif defined(XP_MACOSX) + // Note: |aMyArgv| is already null-terminated as required by posix_spawnp. + pid_t newPid = 0; + int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, nullptr, aMyArgv, + *_NSGetEnviron()); + mPid = static_cast<int32_t>(newPid); + + if (result != 0) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_UNIX) + base::LaunchOptions options; + std::vector<std::string> argvVec; + for (char** arg = aMyArgv; *arg != nullptr; ++arg) { + argvVec.push_back(*arg); + } + pid_t newPid; + if (base::LaunchApp(argvVec, std::move(options), &newPid).isOk()) { + static_assert(sizeof(pid_t) <= sizeof(int32_t), + "mPid is large enough to hold a pid"); + mPid = static_cast<int32_t>(newPid); + } else { + return NS_ERROR_FAILURE; + } +#else + { + PRProcess* prProcess = + PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr); + if (!prProcess) { + return NS_ERROR_FAILURE; + } + { + MutexAutoLock lock(mLock); + mProcess = prProcess; + } + struct MYProcess { + uint32_t pid; + }; + MYProcess* ptrProc = (MYProcess*)mProcess; + mPid = ptrProc->pid; + } +#endif + + NS_ADDREF_THIS(); + mBlocking = aBlocking; + if (aBlocking) { + Monitor(this); + MutexAutoLock lock(mLock); + if (mExitValue < 0) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } else { + mThread = CreateMonitorThread(); + if (!mThread) { + NS_RELEASE_THIS(); + return NS_ERROR_FAILURE; + } + + // It isn't a failure if we just can't watch for shutdown + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, "xpcom-shutdown", false); + } + } + + return NS_OK; +} + +// We don't guarantee that monitor threads are joined before Gecko exits, which +// can cause TSAN to complain about thread leaks. We handle this with a TSAN +// suppression, and route thread creation through this helper so that the +// suppression is as narrowly-scoped as possible. +PRThread* nsProcess::CreateMonitorThread() { + return PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); +} + +NS_IMETHODIMP +nsProcess::GetIsRunning(bool* aIsRunning) { + if (mThread) { + *aIsRunning = true; + } else { + *aIsRunning = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetStartHidden(bool* aStartHidden) { + *aStartHidden = mStartHidden; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::SetStartHidden(bool aStartHidden) { + mStartHidden = aStartHidden; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetNoShell(bool* aNoShell) { + *aNoShell = mNoShell; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::SetNoShell(bool aNoShell) { + mNoShell = aNoShell; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetPid(uint32_t* aPid) { + if (!mThread) { + return NS_ERROR_FAILURE; + } + if (mPid < 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + *aPid = mPid; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::Kill() { + if (!mThread) { + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mLock); +#if defined(PROCESSMODEL_WINAPI) + if (TerminateProcess(mProcess, 0) == 0) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_UNIX) + if (kill(mPid, SIGKILL) != 0) { + return NS_ERROR_FAILURE; + } +#else + if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) { + return NS_ERROR_FAILURE; + } +#endif + } + + // We must null out mThread if we want IsRunning to return false immediately + // after this call. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + PR_JoinThread(mThread); + mThread = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetExitValue(int32_t* aExitValue) { + MutexAutoLock lock(mLock); + + *aExitValue = mExitValue; + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + // Shutting down, drop all references + if (mThread) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + mThread = nullptr; + } + + mObserver = nullptr; + + MutexAutoLock lock(mLock); + mShutdown = true; + + return NS_OK; +} diff --git a/xpcom/threads/nsProxyRelease.cpp b/xpcom/threads/nsProxyRelease.cpp new file mode 100644 index 0000000000..86ed18c8b5 --- /dev/null +++ b/xpcom/threads/nsProxyRelease.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 "nsProxyRelease.h" +#include "nsThreadUtils.h" + +namespace detail { + +/* static */ nsresult ProxyReleaseChooser<true>::ProxyReleaseISupports( + const char* aName, nsIEventTarget* aTarget, nsISupports* aDoomed, + bool aAlwaysProxy) { + return ::detail::ProxyRelease<nsISupports>( + aName, aTarget, dont_AddRef(aDoomed), aAlwaysProxy); +} + +} // namespace detail + +extern "C" { + +// This function uses C linkage because it's exposed to Rust to support the +// `ThreadPtrHolder` wrapper in the `moz_task` crate. +void NS_ProxyReleaseISupports(const char* aName, nsIEventTarget* aTarget, + nsISupports* aDoomed, bool aAlwaysProxy) { + NS_ProxyRelease(aName, aTarget, dont_AddRef(aDoomed), aAlwaysProxy); +} + +} // extern "C" diff --git a/xpcom/threads/nsProxyRelease.h b/xpcom/threads/nsProxyRelease.h new file mode 100644 index 0000000000..c125eae2aa --- /dev/null +++ b/xpcom/threads/nsProxyRelease.h @@ -0,0 +1,380 @@ +/* -*- 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 nsProxyRelease_h__ +#define nsProxyRelease_h__ + +#include <utility> + +#include "MainThreadUtils.h" +#include "mozilla/Likely.h" +#include "mozilla/Unused.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsISerialEventTarget.h" +#include "nsIThread.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" + +#ifdef XPCOM_GLUE_AVOID_NSPR +# error NS_ProxyRelease implementation depends on NSPR. +#endif + +class nsIRunnable; + +namespace detail { + +template <typename T> +class ProxyReleaseEvent : public mozilla::CancelableRunnable { + public: + ProxyReleaseEvent(const char* aName, already_AddRefed<T> aDoomed) + : CancelableRunnable(aName), mDoomed(aDoomed.take()) {} + + NS_IMETHOD Run() override { + NS_IF_RELEASE(mDoomed); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + if (mName) { + aName.Append(nsPrintfCString("ProxyReleaseEvent for %s", mName)); + } else { + aName.AssignLiteral("ProxyReleaseEvent"); + } + return NS_OK; + } +#endif + + private: + T* MOZ_OWNING_REF mDoomed; +}; + +template <typename T> +nsresult ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy) { + // Auto-managing release of the pointer. + RefPtr<T> doomed = aDoomed; + nsresult rv; + + if (!doomed || !aTarget) { + return NS_ERROR_INVALID_ARG; + } + + if (!aAlwaysProxy) { + bool onCurrentThread = false; + rv = aTarget->IsOnCurrentThread(&onCurrentThread); + if (NS_SUCCEEDED(rv) && onCurrentThread) { + return NS_OK; + } + } + + nsCOMPtr<nsIRunnable> ev = new ProxyReleaseEvent<T>(aName, doomed.forget()); + + rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING(nsPrintfCString( + "failed to post proxy release event for %s, leaking!", aName) + .get()); + // It is better to leak the aDoomed object than risk crashing as + // a result of deleting it on the wrong thread. + } + return rv; +} + +template <bool nsISupportsBased> +struct ProxyReleaseChooser { + template <typename T> + static nsresult ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy) { + return ::detail::ProxyRelease(aName, aTarget, std::move(aDoomed), + aAlwaysProxy); + } +}; + +template <> +struct ProxyReleaseChooser<true> { + // We need an intermediate step for handling classes with ambiguous + // inheritance to nsISupports. + template <typename T> + static nsresult ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy) { + return ProxyReleaseISupports(aName, aTarget, ToSupports(aDoomed.take()), + aAlwaysProxy); + } + + static nsresult ProxyReleaseISupports(const char* aName, + nsIEventTarget* aTarget, + nsISupports* aDoomed, + bool aAlwaysProxy); +}; + +} // namespace detail + +/** + * Ensures that the delete of a smart pointer occurs on the target thread. + * Note: The doomed object will be leaked if dispatch to the target thread + * fails, as releasing it on the current thread may be unsafe + * + * @param aName + * the labelling name of the runnable involved in the releasing. + * @param aTarget + * the target thread where the doomed object should be released. + * @param aDoomed + * the doomed object; the object to be released on the target thread. + * @param aAlwaysProxy + * normally, if NS_ProxyRelease is called on the target thread, then the + * doomed object will be released directly. However, if this parameter is + * true, then an event will always be posted to the target thread for + * asynchronous release. + * @return result of the task which is dispatched to delete the smart pointer + * on the target thread. + * Note: The caller should not attempt to recover from an + * error code returned by trying to perform the final ->Release() + * manually. + */ +template <class T> +inline NS_HIDDEN_(nsresult) + NS_ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy = false) { + return ::detail::ProxyReleaseChooser< + std::is_base_of<nsISupports, T>::value>::ProxyRelease(aName, aTarget, + std::move(aDoomed), + aAlwaysProxy); +} + +/** + * Ensures that the delete of a smart pointer occurs on the main thread. + * + * @param aName + * the labelling name of the runnable involved in the releasing + * @param aDoomed + * the doomed object; the object to be released on the main thread. + * @param aAlwaysProxy + * normally, if NS_ReleaseOnMainThread is called on the main + * thread, then the doomed object will be released directly. However, if + * this parameter is true, then an event will always be posted to the + * main thread for asynchronous release. + */ +template <class T> +inline NS_HIDDEN_(void) + NS_ReleaseOnMainThread(const char* aName, already_AddRefed<T> aDoomed, + bool aAlwaysProxy = false) { + RefPtr<T> doomed = aDoomed; + if (!doomed) { + return; // Nothing to do. + } + + // NS_ProxyRelease treats a null event target as "the current thread". So a + // handle on the main thread is only necessary when we're not already on the + // main thread or the release must happen asynchronously. + nsCOMPtr<nsIEventTarget> target; + if (!NS_IsMainThread() || aAlwaysProxy) { + target = mozilla::GetMainThreadSerialEventTarget(); + + if (!target) { + MOZ_ASSERT_UNREACHABLE("Could not get main thread; leaking an object!"); + mozilla::Unused << doomed.forget().take(); + return; + } + } + + NS_ProxyRelease(aName, target, doomed.forget(), aAlwaysProxy); +} + +/** + * Class to safely handle main-thread-only pointers off the main thread. + * + * Classes like XPCWrappedJS are main-thread-only, which means that it is + * forbidden to call methods on instances of these classes off the main thread. + * For various reasons (see bug 771074), this restriction applies to + * AddRef/Release as well. + * + * This presents a problem for consumers that wish to hold a callback alive + * on non-main-thread code. A common example of this is the proxy callback + * pattern, where non-main-thread code holds a strong-reference to the callback + * object, and dispatches new Runnables (also with a strong reference) to the + * main thread in order to execute the callback. This involves several AddRef + * and Release calls on the other thread, which is verboten. + * + * The basic idea of this class is to introduce a layer of indirection. + * nsMainThreadPtrHolder is a threadsafe reference-counted class that internally + * maintains one strong reference to the main-thread-only object. It must be + * instantiated on the main thread (so that the AddRef of the underlying object + * happens on the main thread), but consumers may subsequently pass references + * to the holder anywhere they please. These references are meant to be opaque + * when accessed off-main-thread (assertions enforce this). + * + * The semantics of RefPtr<nsMainThreadPtrHolder<T>> would be cumbersome, so we + * also introduce nsMainThreadPtrHandle<T>, which is conceptually identical to + * the above (though it includes various convenience methods). The basic pattern + * is as follows. + * + * // On the main thread: + * nsCOMPtr<nsIFooCallback> callback = ...; + * nsMainThreadPtrHandle<nsIFooCallback> callbackHandle = + * new nsMainThreadPtrHolder<nsIFooCallback>(callback); + * // Pass callbackHandle to structs/classes that might be accessed on other + * // threads. + * + * All structs and classes that might be accessed on other threads should store + * an nsMainThreadPtrHandle<T> rather than an nsCOMPtr<T>. + */ +template <class T> +class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHolder final { + public: + // We can only acquire a pointer on the main thread. We want to fail fast for + // threading bugs, so by default we assert if our pointer is used or acquired + // off-main-thread. But some consumers need to use the same pointer for + // multiple classes, some of which are main-thread-only and some of which + // aren't. So we allow them to explicitly disable this strict checking. + nsMainThreadPtrHolder(const char* aName, T* aPtr, bool aStrict = true) + : mRawPtr(aPtr), + mStrict(aStrict) +#ifndef RELEASE_OR_BETA + , + mName(aName) +#endif + { + // We can only AddRef our pointer on the main thread, which means that the + // holder must be constructed on the main thread. + MOZ_ASSERT(!mStrict || NS_IsMainThread()); + NS_IF_ADDREF(mRawPtr); + } + nsMainThreadPtrHolder(const char* aName, already_AddRefed<T> aPtr, + bool aStrict = true) + : mRawPtr(aPtr.take()), + mStrict(aStrict) +#ifndef RELEASE_OR_BETA + , + mName(aName) +#endif + { + // Since we don't need to AddRef the pointer, this constructor is safe to + // call on any thread. + } + + // Copy constructor and operator= deleted. Once constructed, the holder is + // immutable. + T& operator=(nsMainThreadPtrHolder& aOther) = delete; + nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther) = delete; + + private: + // We can be released on any thread. + ~nsMainThreadPtrHolder() { + if (NS_IsMainThread()) { + NS_IF_RELEASE(mRawPtr); + } else if (mRawPtr) { + NS_ReleaseOnMainThread( +#ifdef RELEASE_OR_BETA + nullptr, +#else + mName, +#endif + dont_AddRef(mRawPtr)); + } + } + + public: + T* get() const { + // Nobody should be touching the raw pointer off-main-thread. + if (mStrict && MOZ_UNLIKELY(!NS_IsMainThread())) { + NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread"); + MOZ_CRASH(); + } + return mRawPtr; + } + + bool operator==(const nsMainThreadPtrHolder<T>& aOther) const { + return mRawPtr == aOther.mRawPtr; + } + bool operator!() const { return !mRawPtr; } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<T>) + + private: + // Our wrapped pointer. + T* mRawPtr = nullptr; + + // Whether to strictly enforce thread invariants in this class. + bool mStrict = true; + +#ifndef RELEASE_OR_BETA + const char* mName = nullptr; +#endif +}; + +template <class T> +class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHandle { + public: + nsMainThreadPtrHandle() : mPtr(nullptr) {} + MOZ_IMPLICIT nsMainThreadPtrHandle(decltype(nullptr)) : mPtr(nullptr) {} + explicit nsMainThreadPtrHandle(nsMainThreadPtrHolder<T>* aHolder) + : mPtr(aHolder) {} + explicit nsMainThreadPtrHandle( + already_AddRefed<nsMainThreadPtrHolder<T>> aHolder) + : mPtr(aHolder) {} + nsMainThreadPtrHandle(const nsMainThreadPtrHandle& aOther) = default; + nsMainThreadPtrHandle(nsMainThreadPtrHandle&& aOther) = default; + nsMainThreadPtrHandle& operator=(const nsMainThreadPtrHandle& aOther) = + default; + nsMainThreadPtrHandle& operator=(nsMainThreadPtrHandle&& aOther) = default; + nsMainThreadPtrHandle& operator=(nsMainThreadPtrHolder<T>* aHolder) { + mPtr = aHolder; + return *this; + } + + // These all call through to nsMainThreadPtrHolder, and thus implicitly + // assert that we're on the main thread (if strict). Off-main-thread consumers + // must treat these handles as opaque. + T* get() const { + if (mPtr) { + return mPtr.get()->get(); + } + return nullptr; + } + + operator T*() const { return get(); } + T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return get(); } + + // These are safe to call on other threads with appropriate external locking. + bool operator==(const nsMainThreadPtrHandle<T>& aOther) const { + if (!mPtr || !aOther.mPtr) { + return mPtr == aOther.mPtr; + } + return *mPtr == *aOther.mPtr; + } + bool operator!=(const nsMainThreadPtrHandle<T>& aOther) const { + return !operator==(aOther); + } + bool operator==(decltype(nullptr)) const { return mPtr == nullptr; } + bool operator!=(decltype(nullptr)) const { return mPtr != nullptr; } + bool operator!() const { return !mPtr || !*mPtr; } + + private: + RefPtr<nsMainThreadPtrHolder<T>> mPtr; +}; + +class nsCycleCollectionTraversalCallback; +template <typename T> +void CycleCollectionNoteChild(nsCycleCollectionTraversalCallback& aCallback, + T* aChild, const char* aName, uint32_t aFlags); + +template <typename T> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsMainThreadPtrHandle<T>& aField, const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +template <typename T> +inline void ImplCycleCollectionUnlink(nsMainThreadPtrHandle<T>& aField) { + aField = nullptr; +} + +#endif diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp new file mode 100644 index 0000000000..98542e9438 --- /dev/null +++ b/xpcom/threads/nsThread.cpp @@ -0,0 +1,1560 @@ +/* -*- 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 "nsThread.h" + +#include "base/message_loop.h" +#include "base/platform_thread.h" + +// Chromium's logging can sometimes leak through... +#ifdef LOG +# undef LOG +#endif + +#include "mozilla/ReentrantMonitor.h" +#include "nsMemoryPressure.h" +#include "nsThreadManager.h" +#include "nsIClassInfoImpl.h" +#include "nsCOMPtr.h" +#include "nsQueryObject.h" +#include "pratom.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "nsIObserverService.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerRunnable.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticLocalPtr.h" +#include "mozilla/StaticPrefs_threads.h" +#include "mozilla/TaskController.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsThreadSyncDispatch.h" +#include "nsServiceManagerUtils.h" +#include "GeckoProfiler.h" +#include "ThreadEventQueue.h" +#include "ThreadEventTarget.h" +#include "ThreadDelay.h" + +#include <limits> + +#ifdef XP_LINUX +# ifdef __GLIBC__ +# include <gnu/libc-version.h> +# endif +# include <sys/mman.h> +# include <sys/time.h> +# include <sys/resource.h> +# include <sched.h> +# include <stdio.h> +#endif + +#ifdef XP_WIN +# include "mozilla/DynamicallyLinkedFunctionPtr.h" + +# include <winbase.h> + +using GetCurrentThreadStackLimitsFn = void(WINAPI*)(PULONG_PTR LowLimit, + PULONG_PTR HighLimit); +#endif + +#define HAVE_UALARM \ + _BSD_SOURCE || \ + (_XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \ + !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) + +#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE) +# define HAVE_SCHED_SETAFFINITY +#endif + +#ifdef XP_MACOSX +# include <mach/mach.h> +# include <mach/thread_policy.h> +# include <sys/qos.h> +#endif + +#ifdef MOZ_CANARY +# include <unistd.h> +# include <execinfo.h> +# include <signal.h> +# include <fcntl.h> +# include "nsXULAppAPI.h" +#endif + +using namespace mozilla; + +extern void InitThreadLocalVariables(); + +static LazyLogModule sThreadLog("nsThread"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args) + +NS_DECL_CI_INTERFACE_GETTER(nsThread) + +Array<char, nsThread::kRunnableNameBufSize> nsThread::sMainThreadRunnableName; + +#ifdef EARLY_BETA_OR_EARLIER +const uint32_t kTelemetryWakeupCountLimit = 100; +#endif + +//----------------------------------------------------------------------------- +// Because we do not have our own nsIFactory, we have to implement nsIClassInfo +// somewhat manually. + +class nsThreadClassInfo : public nsIClassInfo { + public: + NS_DECL_ISUPPORTS_INHERITED // no mRefCnt + NS_DECL_NSICLASSINFO + + nsThreadClassInfo() = default; +}; + +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::Release() { return 1; } +NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo) + +NS_IMETHODIMP +nsThreadClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) { + return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aArray); +} + +NS_IMETHODIMP +nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) { + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetContractID(nsACString& aResult) { + aResult.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassDescription(nsACString& aResult) { + aResult.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassID(nsCID** aResult) { + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetFlags(uint32_t* aResult) { + *aResult = THREADSAFE; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) { + return NS_ERROR_NOT_AVAILABLE; +} + +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsThread) +NS_IMPL_RELEASE(nsThread) +NS_INTERFACE_MAP_BEGIN(nsThread) + NS_INTERFACE_MAP_ENTRY(nsIThread) + NS_INTERFACE_MAP_ENTRY(nsIThreadInternal) + NS_INTERFACE_MAP_ENTRY(nsIEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) + NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread) + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { + static nsThreadClassInfo sThreadClassInfo; + foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo); + } else +NS_INTERFACE_MAP_END +NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal, + nsIEventTarget, nsISerialEventTarget, + nsISupportsPriority) + +//----------------------------------------------------------------------------- + +// This event is responsible for notifying nsThread::Shutdown that it is time +// to call PR_JoinThread. It implements nsICancelableRunnable so that it can +// run on a DOM Worker thread (where all events must implement +// nsICancelableRunnable.) +class nsThreadShutdownAckEvent : public CancelableRunnable { + public: + explicit nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext*> aCtx) + : CancelableRunnable("nsThreadShutdownAckEvent"), + mShutdownContext(aCtx) {} + NS_IMETHOD Run() override { + mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext); + return NS_OK; + } + nsresult Cancel() override { return Run(); } + + private: + virtual ~nsThreadShutdownAckEvent() = default; + + NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext; +}; + +// This event is responsible for setting mShutdownContext +class nsThreadShutdownEvent : public Runnable { + public: + nsThreadShutdownEvent(NotNull<nsThread*> aThr, + NotNull<nsThreadShutdownContext*> aCtx) + : Runnable("nsThreadShutdownEvent"), + mThread(aThr), + mShutdownContext(aCtx) {} + NS_IMETHOD Run() override { + // Creates a cycle between `mThread` and the shutdown context which will be + // broken when the thread exits. + mThread->mShutdownContext = mShutdownContext; + MessageLoop::current()->Quit(); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // Let's leave a trace that we passed here in the thread's name. + nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread())); + threadName.Append(",SHDRCV"_ns); + NS_SetCurrentThreadName(threadName.get()); +#endif + return NS_OK; + } + + private: + NotNull<RefPtr<nsThread>> mThread; + NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext; +}; + +//----------------------------------------------------------------------------- + +static void SetThreadAffinity(unsigned int cpu) { +#ifdef HAVE_SCHED_SETAFFINITY + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(cpu, &cpus); + sched_setaffinity(0, sizeof(cpus), &cpus); + // Don't assert sched_setaffinity's return value because it intermittently (?) + // fails with EINVAL on Linux x64 try runs. +#elif defined(XP_MACOSX) + // OS X does not provide APIs to pin threads to specific processors, but you + // can tag threads as belonging to the same "affinity set" and the OS will try + // to run them on the same processor. To run threads on different processors, + // tag them as belonging to different affinity sets. Tag 0, the default, means + // "no affinity" so let's pretend each CPU has its own tag `cpu+1`. + thread_affinity_policy_data_t policy; + policy.affinity_tag = cpu + 1; + kern_return_t kr = thread_policy_set( + mach_thread_self(), THREAD_AFFINITY_POLICY, &policy.affinity_tag, 1); + // Setting the thread affinity is not supported on ARM. + MOZ_ALWAYS_TRUE(kr == KERN_SUCCESS || kr == KERN_NOT_SUPPORTED); +#elif defined(XP_WIN) + MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) != + (DWORD)-1); +#endif +} + +static void SetupCurrentThreadForChaosMode() { + if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) { + return; + } + +#ifdef XP_LINUX + // PR_SetThreadPriority doesn't really work since priorities > + // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use + // setpriority(2) to set random 'nice values'. In regular Linux this is only + // a dynamic adjustment so it still doesn't really do what we want, but tools + // like 'rr' can be more aggressive about honoring these values. + // Some of these calls may fail due to trying to lower the priority + // (e.g. something may have already called setpriority() for this thread). + // This makes it hard to have non-main threads with higher priority than the + // main thread, but that's hard to fix. Tools like rr can choose to honor the + // requested values anyway. + // Use just 4 priorities so there's a reasonable chance of any two threads + // having equal priority. + setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4)); +#else + // We should set the affinity here but NSPR doesn't provide a way to expose + // it. + uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1); + PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority)); +#endif + + // Force half the threads to CPU 0 so they compete for CPU + if (ChaosMode::randomUint32LessThan(2)) { + SetThreadAffinity(0); + } +} + +namespace { + +struct ThreadInitData { + RefPtr<nsThread> thread; + nsCString name; +}; + +} // namespace + +void nsThread::MaybeRemoveFromThreadList() { + nsThreadManager& tm = nsThreadManager::get(); + OffTheBooksMutexAutoLock mal(tm.ThreadListMutex()); + if (isInList()) { + removeFrom(tm.ThreadList()); + } +} + +/*static*/ +void nsThread::ThreadFunc(void* aArg) { + using mozilla::ipc::BackgroundChild; + + UniquePtr<ThreadInitData> initData(static_cast<ThreadInitData*>(aArg)); + RefPtr<nsThread>& self = initData->thread; + + MOZ_ASSERT(self->mEventTarget); + MOZ_ASSERT(self->mEvents); + + // Note: see the comment in nsThread::Init, where we set these same values. + DebugOnly<PRThread*> prev = self->mThread.exchange(PR_GetCurrentThread()); + MOZ_ASSERT(!prev || prev == PR_GetCurrentThread()); + self->mEventTarget->SetCurrentThread(self->mThread); + SetupCurrentThreadForChaosMode(); + + if (!initData->name.IsEmpty()) { + NS_SetCurrentThreadName(initData->name.BeginReading()); + } + + self->InitCommon(); + + // Inform the ThreadManager + nsThreadManager::get().RegisterCurrentThread(*self); + + mozilla::IOInterposer::RegisterCurrentThread(); + + // This must come after the call to nsThreadManager::RegisterCurrentThread(), + // because that call is needed to properly set up this thread as an nsThread, + // which profiler_register_thread() requires. See bug 1347007. + const bool registerWithProfiler = !initData->name.IsEmpty(); + if (registerWithProfiler) { + PROFILER_REGISTER_THREAD(initData->name.BeginReading()); + } + + { + // Scope for MessageLoop. + MessageLoop loop( +#if defined(XP_WIN) || defined(XP_MACOSX) + self->mIsUiThread ? MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD + : MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, +#else + MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, +#endif + self); + + // Now, process incoming events... + loop.Run(); + + self->mEvents->RunShutdownTasks(); + + BackgroundChild::CloseForCurrentThread(); + + // NB: The main thread does not shut down here! It shuts down via + // nsThreadManager::Shutdown. + + // Do NS_ProcessPendingEvents but with special handling to set + // mEventsAreDoomed atomically with the removal of the last event. The key + // invariant here is that we will never permit PutEvent to succeed if the + // event would be left in the queue after our final call to + // NS_ProcessPendingEvents. We also have to keep processing events as long + // as we have outstanding mRequestedShutdownContexts. + while (true) { + // Check and see if we're waiting on any threads. + self->WaitForAllAsynchronousShutdowns(); + + if (self->mEvents->ShutdownIfNoPendingEvents()) { + break; + } + NS_ProcessPendingEvents(self); + } + } + + mozilla::IOInterposer::UnregisterCurrentThread(); + + // Inform the threadmanager that this thread is going away + nsThreadManager::get().UnregisterCurrentThread(*self); + + // The thread should only unregister itself if it was registered above. + if (registerWithProfiler) { + PROFILER_UNREGISTER_THREAD(); + } + + NotNull<RefPtr<nsThreadShutdownContext>> context = + WrapNotNull(self->mShutdownContext); + self->mShutdownContext = nullptr; + MOZ_ASSERT(context->mTerminatingThread == self); + + // Take the joining thread from our shutdown context. This may have been + // cleared by the joining thread if it decided to cancel waiting on us, in + // which case we won't notify our caller, and leak. + RefPtr<nsThread> joiningThread; + { + MutexAutoLock lock(context->mJoiningThreadMutex); + joiningThread = context->mJoiningThread.forget(); + MOZ_RELEASE_ASSERT(joiningThread || context->mThreadLeaked); + } + if (joiningThread) { + // Dispatch shutdown ACK + nsCOMPtr<nsIRunnable> event = new nsThreadShutdownAckEvent(context); + nsresult dispatch_ack_rv = + joiningThread->Dispatch(event, NS_DISPATCH_NORMAL); + + // We do not expect this to ever happen, but If we cannot dispatch + // the ack event, someone probably blocks waiting on us and will + // crash with a hang later anyways. The best we can do is to tell + // the world what happened right here. + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(dispatch_ack_rv)); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // Let's leave a trace that we passed here in the thread's name. + nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread())); + threadName.Append(",SHDACK"_ns); + NS_SetCurrentThreadName(threadName.get()); +#endif + } else { + NS_WARNING( + "nsThread exiting after StopWaitingAndLeakThread was called, thread " + "resources will be leaked!"); + } + + // Release any observer of the thread here. + self->SetObserver(nullptr); + + // The PRThread will be deleted in PR_JoinThread(), so clear references. + self->mThread = nullptr; + self->mEventTarget->ClearCurrentThread(); +} + +void nsThread::InitCommon() { + mThreadId = uint32_t(PlatformThread::CurrentId()); + + { +#if defined(XP_LINUX) + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_getattr_np(pthread_self(), &attr); + + size_t stackSize; + pthread_attr_getstack(&attr, &mStackBase, &stackSize); + + // Glibc prior to 2.27 reports the stack size and base including the guard + // region, so we need to compensate for it to get accurate accounting. + // Also, this behavior difference isn't guarded by a versioned symbol, so we + // actually need to check the runtime glibc version, not the version we were + // compiled against. + static bool sAdjustForGuardSize = ({ +# ifdef __GLIBC__ + unsigned major, minor; + sscanf(gnu_get_libc_version(), "%u.%u", &major, &minor) < 2 || + major < 2 || (major == 2 && minor < 27); +# else + false; +# endif + }); + if (sAdjustForGuardSize) { + size_t guardSize; + pthread_attr_getguardsize(&attr, &guardSize); + + // Note: This assumes that the stack grows down, as is the case on all of + // our tier 1 platforms. On platforms where the stack grows up, the + // mStackBase adjustment is unnecessary, but doesn't cause any harm other + // than under-counting stack memory usage by one page. + mStackBase = reinterpret_cast<char*>(mStackBase) + guardSize; + stackSize -= guardSize; + } + + mStackSize = stackSize; + + // This is a bit of a hack. + // + // We really do want the NOHUGEPAGE flag on our thread stacks, since we + // don't expect any of them to need anywhere near 2MB of space. But setting + // it here is too late to have an effect, since the first stack page has + // already been faulted in existence, and NSPR doesn't give us a way to set + // it beforehand. + // + // What this does get us, however, is a different set of VM flags on our + // thread stacks compared to normal heap memory. Which makes the Linux + // kernel report them as separate regions, even when they are adjacent to + // heap memory. This allows us to accurately track the actual memory + // consumption of our allocated stacks. + madvise(mStackBase, stackSize, MADV_NOHUGEPAGE); + + pthread_attr_destroy(&attr); +#elif defined(XP_WIN) + static const StaticDynamicallyLinkedFunctionPtr< + GetCurrentThreadStackLimitsFn> + sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits"); + + if (sGetStackLimits) { + ULONG_PTR stackBottom, stackTop; + sGetStackLimits(&stackBottom, &stackTop); + mStackBase = reinterpret_cast<void*>(stackBottom); + mStackSize = stackTop - stackBottom; + } +#endif + } + + InitThreadLocalVariables(); +} + +//----------------------------------------------------------------------------- + +#ifdef MOZ_CANARY +int sCanaryOutputFD = -1; +#endif + +nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue, + MainThreadFlag aMainThread, + nsIThreadManager::ThreadCreationOptions aOptions) + : mEvents(aQueue.get()), + mEventTarget(new ThreadEventTarget( + mEvents.get(), aMainThread == MAIN_THREAD, aOptions.blockDispatch)), + mOutstandingShutdownContexts(0), + mShutdownContext(nullptr), + mScriptObserver(nullptr), + mThreadName("<uninitialized>"), + mStackSize(aOptions.stackSize), + mNestedEventLoopDepth(0), + mShutdownRequired(false), + mPriority(PRIORITY_NORMAL), + mIsMainThread(aMainThread == MAIN_THREAD), + mUseHangMonitor(aMainThread == MAIN_THREAD), + mIsUiThread(aOptions.isUiThread), + mIsAPoolThreadFree(nullptr), + mCanInvokeJS(false), +#ifdef EARLY_BETA_OR_EARLIER + mLastWakeupCheckTime(TimeStamp::Now()), +#endif + mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread, + aOptions.longTaskLength) { +#if !(defined(XP_WIN) || defined(XP_MACOSX)) + MOZ_ASSERT(!mIsUiThread, + "Non-main UI threads are only supported on Windows and macOS"); +#endif + if (mIsMainThread) { + MOZ_ASSERT(!mIsUiThread, + "Setting isUIThread is not supported for main threads"); + mozilla::TaskController::Get()->SetPerformanceCounterState( + &mPerformanceCounterState); + } +} + +nsThread::nsThread() + : mEvents(nullptr), + mEventTarget(nullptr), + mOutstandingShutdownContexts(0), + mShutdownContext(nullptr), + mScriptObserver(nullptr), + mThreadName("<uninitialized>"), + mStackSize(0), + mNestedEventLoopDepth(0), + mShutdownRequired(false), + mPriority(PRIORITY_NORMAL), + mIsMainThread(false), + mUseHangMonitor(false), + mIsUiThread(false), + mCanInvokeJS(false), +#ifdef EARLY_BETA_OR_EARLIER + mLastWakeupCheckTime(TimeStamp::Now()), +#endif + mPerformanceCounterState(mNestedEventLoopDepth) { + MOZ_ASSERT(!NS_IsMainThread()); +} + +nsThread::~nsThread() { + NS_ASSERTION(mOutstandingShutdownContexts == 0, + "shouldn't be waiting on other threads to shutdown"); + + MaybeRemoveFromThreadList(); +} + +nsresult nsThread::Init(const nsACString& aName) { + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(!mThread); + + SetThreadNameInternal(aName); + + PRThread* thread = nullptr; + + nsThreadManager& tm = nsThreadManager::get(); + { + OffTheBooksMutexAutoLock lock(tm.ThreadListMutex()); + if (!tm.AllowNewXPCOMThreadsLocked()) { + return NS_ERROR_NOT_INITIALIZED; + } + + // We need to fully start the thread while holding the thread list lock, as + // the next acquire of the lock could try to shut down this thread (e.g. + // during xpcom shutdown), which would hang if `PR_CreateThread` failed. + + UniquePtr<ThreadInitData> initData( + new ThreadInitData{this, nsCString(aName)}); + + // ThreadFunc is responsible for setting mThread + if (!(thread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, initData.get(), + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, mStackSize))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // The created thread now owns initData, so release our ownership of it. + Unused << initData.release(); + + // The thread has successfully started, so we can mark it as requiring + // shutdown & add it to the thread list. + mShutdownRequired = true; + tm.ThreadList().insertBack(this); + } + + // Note: we set these both here and inside ThreadFunc, to what should be + // the same value. This is because calls within ThreadFunc need these values + // to be set, and our callers need these values to be set. + DebugOnly<PRThread*> prev = mThread.exchange(thread); + MOZ_ASSERT(!prev || prev == thread); + + mEventTarget->SetCurrentThread(thread); + return NS_OK; +} + +nsresult nsThread::InitCurrentThread() { + mThread = PR_GetCurrentThread(); + + nsThreadManager& tm = nsThreadManager::get(); + { + OffTheBooksMutexAutoLock lock(tm.ThreadListMutex()); + // NOTE: We don't check AllowNewXPCOMThreads here, as threads initialized + // this way do not need shutdown, so are OK to create after nsThreadManager + // shutdown. In addition, the main thread is initialized this way, which + // happens before AllowNewXPCOMThreads begins to return true. + tm.ThreadList().insertBack(this); + } + + SetupCurrentThreadForChaosMode(); + InitCommon(); + + tm.RegisterCurrentThread(*this); + return NS_OK; +} + +void nsThread::GetThreadName(nsACString& aNameBuffer) { + auto lock = mThreadName.Lock(); + aNameBuffer = lock.ref(); +} + +void nsThread::SetThreadNameInternal(const nsACString& aName) { + auto lock = mThreadName.Lock(); + lock->Assign(aName); +} + +//----------------------------------------------------------------------------- +// nsIEventTarget + +NS_IMETHODIMP +nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIRunnable> event(aEvent); + return mEventTarget->Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */ nullptr, aFlags)); + + return mEventTarget->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelayMs) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs); +} + +NS_IMETHODIMP +nsThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +nsThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->UnregisterShutdownTask(aTask); +} + +NS_IMETHODIMP +nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) { + if (mIsAPoolThreadFree && *mIsAPoolThreadFree) { + // if there are unstarted threads in the pool, a new event to the + // pool would not be delayed at all (beyond thread start time) + *aDelay = TimeDuration(); + *aStart = TimeStamp(); + } else { + *aDelay = mLastEventDelay; + *aStart = mLastEventStart; + } + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) { + mLastEventDelay = aDelay; + mLastEventStart = aStart; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::IsOnCurrentThread(bool* aResult) { + if (mEventTarget) { + return mEventTarget->IsOnCurrentThread(aResult); + } + *aResult = PR_GetCurrentThread() == mThread; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +nsThread::IsOnCurrentThreadInfallible() { + // This method is only going to be called if `mThread` is null, which + // only happens when the thread has exited the event loop. Therefore, when + // we are called, we can never be on this thread. + return false; +} + +//----------------------------------------------------------------------------- +// nsIThread + +NS_IMETHODIMP +nsThread::GetPRThread(PRThread** aResult) { + PRThread* thread = mThread; // atomic load + *aResult = thread; + return thread ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsThread::GetCanInvokeJS(bool* aResult) { + *aResult = mCanInvokeJS; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetCanInvokeJS(bool aCanInvokeJS) { + mCanInvokeJS = aCanInvokeJS; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::GetLastLongTaskEnd(TimeStamp* _retval) { + *_retval = mPerformanceCounterState.LastLongTaskEnd(); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) { + *_retval = mPerformanceCounterState.LastLongNonIdleTaskEnd(); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetNameForWakeupTelemetry(const nsACString& aName) { +#ifdef EARLY_BETA_OR_EARLIER + mNameForWakeupTelemetry = aName; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AsyncShutdown() { + LOG(("THRD(%p) async shutdown\n", this)); + + nsCOMPtr<nsIThreadShutdown> shutdown; + BeginShutdown(getter_AddRefs(shutdown)); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::BeginShutdown(nsIThreadShutdown** aShutdown) { + LOG(("THRD(%p) begin shutdown\n", this)); + + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(mThread != PR_GetCurrentThread()); + if (NS_WARN_IF(mThread == PR_GetCurrentThread())) { + return NS_ERROR_UNEXPECTED; + } + + // Prevent multiple calls to this method. + if (!mShutdownRequired.compareExchange(true, false)) { + return NS_ERROR_UNEXPECTED; + } + MOZ_ASSERT(mThread); + + RefPtr<nsThread> currentThread = nsThreadManager::get().GetCurrentThread(); + + MOZ_DIAGNOSTIC_ASSERT(currentThread->EventQueue(), + "Shutdown() may only be called from an XPCOM thread"); + + // Allocate a shutdown context, and record that we're waiting for it. + RefPtr<nsThreadShutdownContext> context = + new nsThreadShutdownContext(WrapNotNull(this), currentThread); + + ++currentThread->mOutstandingShutdownContexts; + nsCOMPtr<nsIRunnable> clearOutstanding = NS_NewRunnableFunction( + "nsThread::ClearOutstandingShutdownContext", + [currentThread] { --currentThread->mOutstandingShutdownContexts; }); + context->OnCompletion(clearOutstanding); + + // Set mShutdownContext and wake up the thread in case it is waiting for + // events to process. + nsCOMPtr<nsIRunnable> event = + new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context)); + if (!mEvents->PutEvent(event.forget(), EventQueuePriority::Normal)) { + // We do not expect this to happen. Let's collect some diagnostics. + nsAutoCString threadName; + GetThreadName(threadName); + MOZ_CRASH_UNSAFE_PRINTF("Attempt to shutdown an already dead thread: %s", + threadName.get()); + } + + // We could still end up with other events being added after the shutdown + // task, but that's okay because we process pending events in ThreadFunc + // after setting mShutdownContext just before exiting. + context.forget(aShutdown); + return NS_OK; +} + +void nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext) { + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(aContext->mTerminatingThread == this); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + { + MutexAutoLock lock(aContext->mJoiningThreadMutex); + + // StopWaitingAndLeakThread is explicitely meant to not cause a + // nsThreadShutdownAckEvent on the joining thread, which is the only + // caller of ShutdownComplete. + MOZ_DIAGNOSTIC_ASSERT(!aContext->mThreadLeaked); + } +#endif + + MaybeRemoveFromThreadList(); + + // Now, it should be safe to join without fear of dead-locking. + PR_JoinThread(aContext->mTerminatingPRThread); + MOZ_ASSERT(!mThread); + +#ifdef DEBUG + nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver(); + MOZ_ASSERT(!obs, "Should have been cleared at shutdown!"); +#endif + + aContext->MarkCompleted(); +} + +void nsThread::WaitForAllAsynchronousShutdowns() { + // This is the motivating example for why SpinEventLoopUntil + // has the template parameter we are providing here. + SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( + "nsThread::WaitForAllAsynchronousShutdowns"_ns, + [&]() { return mOutstandingShutdownContexts == 0; }, this); +} + +NS_IMETHODIMP +nsThread::Shutdown() { + LOG(("THRD(%p) sync shutdown\n", this)); + + nsCOMPtr<nsIThreadShutdown> context; + nsresult rv = BeginShutdown(getter_AddRefs(context)); + if (NS_FAILED(rv)) { + return NS_OK; // The thread has already shut down. + } + + // If we are going to hang here we want to see the thread's name + nsAutoCString threadName; + GetThreadName(threadName); + + // Process events on the current thread until we receive a shutdown ACK. + // Allows waiting; ensure no locks are held that would deadlock us! + SpinEventLoopUntil("nsThread::Shutdown: "_ns + threadName, + [&]() { return context->GetCompleted(); }); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingEvents(bool* aResult) { + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + if (mIsMainThread) { + *aResult = TaskController::Get()->HasMainThreadPendingTasks(); + } else { + *aResult = mEvents->HasPendingEvent(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingHighPriorityEvents(bool* aResult) { + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // This function appears to never be called anymore. + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent, + EventQueuePriority aQueue) { + nsCOMPtr<nsIRunnable> event = aEvent; + + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + if (!mEvents->PutEvent(event.forget(), aQueue)) { + NS_WARNING( + "An idle event was posted to a thread that will never run it " + "(rejected)"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsThread::SetThreadQoS(nsIThread::QoSPriority aPriority) { + if (!StaticPrefs::threads_use_low_power_enabled()) { + return NS_OK; + } + // The approach here is to have a thread set itself for its QoS level, + // so we assert if we aren't on the current thread. + MOZ_ASSERT(IsOnCurrentThread(), "Can only change the current thread's QoS"); + +#if defined(XP_MACOSX) + // Only arm64 macs may possess heterogeneous cores. On these, we can tell + // a thread to set its own QoS status. On intel macs things should behave + // normally, and the OS will ignore the QoS state of the thread. + if (aPriority == nsIThread::QOS_PRIORITY_LOW) { + pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0); + } else if (NS_IsMainThread()) { + // MacOS documentation specifies that a main thread should be initialized at + // the USER_INTERACTIVE priority, so when we restore thread priorities the + // main thread should be setting itself to this. + pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); + } else { + pthread_set_qos_class_self_np(QOS_CLASS_DEFAULT, 0); + } +#endif + // Do nothing if an OS-specific implementation is unavailable. + return NS_OK; +} + +#ifdef MOZ_CANARY +void canary_alarm_handler(int signum); + +class Canary { + // XXX ToDo: support nested loops + public: + Canary() { + if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) { + signal(SIGALRM, canary_alarm_handler); + ualarm(15000, 0); + } + } + + ~Canary() { + if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) { + ualarm(0, 0); + } + } + + static bool EventLatencyIsImportant() { + return NS_IsMainThread() && XRE_IsParentProcess(); + } +}; + +void canary_alarm_handler(int signum) { + void* array[30]; + const char msg[29] = "event took too long to run:\n"; + // use write to be safe in the signal handler + write(sCanaryOutputFD, msg, sizeof(msg)); + backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD); +} + +#endif + +#define NOTIFY_EVENT_OBSERVERS(observers_, func_, params_) \ + do { \ + if (!observers_.IsEmpty()) { \ + for (nsCOMPtr<nsIThreadObserver> obs_ : observers_.ForwardRange()) { \ + obs_->func_ params_; \ + } \ + } \ + } while (0) + +size_t nsThread::ShallowSizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mShutdownContext) { + n += aMallocSizeOf(mShutdownContext); + } + return aMallocSizeOf(this) + aMallocSizeOf(mThread) + n; +} + +size_t nsThread::SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mEventTarget) { + // The size of mEvents is reported by mEventTarget. + n += mEventTarget->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +size_t nsThread::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return ShallowSizeOfIncludingThis(aMallocSizeOf) + + SizeOfEventQueues(aMallocSizeOf); +} + +NS_IMETHODIMP +nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait, + mNestedEventLoopDepth)); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // The toplevel event loop normally blocks waiting for the next event, but + // if we're trying to shut this thread down, we must exit the event loop + // when the event queue is empty. This only applys to the toplevel event + // loop! Nested event loops (e.g. during sync dispatch) are waiting for + // some state change and must be able to block even if something has + // requested shutdown of the thread. Otherwise we'll just busywait as we + // endlessly look for an event, fail to find one, and repeat the nested + // event loop since its state change hasn't happened yet. + bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown()); + + Maybe<dom::AutoNoJSAPI> noJSAPI; + + if (mUseHangMonitor && reallyWait) { + BackgroundHangMonitor().NotifyWait(); + } + + if (mIsMainThread) { + DoMainThreadSpecificProcessing(); + } + + ++mNestedEventLoopDepth; + + // We only want to create an AutoNoJSAPI on threads that actually do DOM + // stuff (including workers). Those are exactly the threads that have an + // mScriptObserver. + bool callScriptObserver = !!mScriptObserver; + if (callScriptObserver) { + noJSAPI.emplace(); + mScriptObserver->BeforeProcessTask(reallyWait); + } + + DrainDirectTasks(); + +#ifdef EARLY_BETA_OR_EARLIER + // Need to capture mayWaitForWakeup state before OnProcessNextEvent, + // since on the main thread OnProcessNextEvent ends up waiting for the new + // events. + bool mayWaitForWakeup = reallyWait && !mEvents->HasPendingEvent(); +#endif + + nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserverOnThread(); + if (obs) { + obs->OnProcessNextEvent(this, reallyWait); + } + + NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), OnProcessNextEvent, + (this, reallyWait)); + + DrainDirectTasks(); + +#ifdef MOZ_CANARY + Canary canary; +#endif + nsresult rv = NS_OK; + + { + // Scope for |event| to make sure that its destructor fires while + // mNestedEventLoopDepth has been incremented, since that destructor can + // also do work. + nsCOMPtr<nsIRunnable> event; + bool usingTaskController = mIsMainThread; + if (usingTaskController) { + event = TaskController::Get()->GetRunnableForMTTask(reallyWait); + } else { + event = mEvents->GetEvent(reallyWait, &mLastEventDelay); + } + + *aResult = (event.get() != nullptr); + + if (event) { +#ifdef EARLY_BETA_OR_EARLIER + if (mayWaitForWakeup && mThread) { + ++mWakeupCount; + if (mWakeupCount == kTelemetryWakeupCountLimit) { + TimeStamp now = TimeStamp::Now(); + double ms = (now - mLastWakeupCheckTime).ToMilliseconds(); + if (ms < 0) { + ms = 0; + } + const char* name = !mNameForWakeupTelemetry.IsEmpty() + ? mNameForWakeupTelemetry.get() + : PR_GetThreadName(mThread); + if (!name) { + name = mIsMainThread ? "MainThread" : "(nameless thread)"; + } + nsDependentCString key(name); + Telemetry::Accumulate(Telemetry::THREAD_WAKEUP, key, + static_cast<uint32_t>(ms)); + mLastWakeupCheckTime = now; + mWakeupCount = 0; + } + } +#endif + + LOG(("THRD(%p) running [%p]\n", this, event.get())); + + Maybe<LogRunnable::Run> log; + + if (!usingTaskController) { + log.emplace(event); + } + + // Delay event processing to encourage whoever dispatched this event + // to run. + DelayForChaosMode(ChaosFeature::TaskRunning, 1000); + + mozilla::TimeStamp now = mozilla::TimeStamp::Now(); + + if (mUseHangMonitor) { + BackgroundHangMonitor().NotifyActivity(); + } + + Maybe<PerformanceCounterState::Snapshot> snapshot; + if (!usingTaskController) { + snapshot.emplace(mPerformanceCounterState.RunnableWillRun(now, false)); + } + + mLastEventStart = now; + + if (!usingTaskController) { + AUTO_PROFILE_FOLLOWING_RUNNABLE(event); + event->Run(); + } else { + // Avoid generating "Runnable" profiler markers for the + // "TaskController::ExecutePendingMTTasks" runnables created + // by TaskController, which already adds "Runnable" markers + // when executing tasks. + event->Run(); + } + + if (usingTaskController) { + *aResult = TaskController::Get()->MTTaskRunnableProcessedTask(); + } else { + mPerformanceCounterState.RunnableDidRun(EmptyCString(), + std::move(snapshot.ref())); + } + + // To cover the event's destructor code inside the LogRunnable span. + event = nullptr; + } else { + mLastEventDelay = TimeDuration(); + mLastEventStart = TimeStamp(); + if (aMayWait) { + MOZ_ASSERT(ShuttingDown(), + "This should only happen when shutting down"); + rv = NS_ERROR_UNEXPECTED; + } + } + } + + DrainDirectTasks(); + + NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), AfterProcessNextEvent, + (this, *aResult)); + + if (obs) { + obs->AfterProcessNextEvent(this, *aResult); + } + + // In case some EventObserver dispatched some direct tasks; process them + // now. + DrainDirectTasks(); + + if (callScriptObserver) { + if (mScriptObserver) { + mScriptObserver->AfterProcessTask(mNestedEventLoopDepth); + } + noJSAPI.reset(); + } + + --mNestedEventLoopDepth; + + return rv; +} + +//----------------------------------------------------------------------------- +// nsISupportsPriority + +NS_IMETHODIMP +nsThread::GetPriority(int32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetPriority(int32_t aPriority) { + if (NS_WARN_IF(!mThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // NSPR defines the following four thread priorities: + // PR_PRIORITY_LOW + // PR_PRIORITY_NORMAL + // PR_PRIORITY_HIGH + // PR_PRIORITY_URGENT + // We map the priority values defined on nsISupportsPriority to these + // values. + + mPriority = aPriority; + + PRThreadPriority pri; + if (mPriority <= PRIORITY_HIGHEST) { + pri = PR_PRIORITY_URGENT; + } else if (mPriority < PRIORITY_NORMAL) { + pri = PR_PRIORITY_HIGH; + } else if (mPriority > PRIORITY_NORMAL) { + pri = PR_PRIORITY_LOW; + } else { + pri = PR_PRIORITY_NORMAL; + } + // If chaos mode is active, retain the randomly chosen priority + if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) { + PR_SetThreadPriority(mThread, pri); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AdjustPriority(int32_t aDelta) { + return SetPriority(mPriority + aDelta); +} + +//----------------------------------------------------------------------------- +// nsIThreadInternal + +NS_IMETHODIMP +nsThread::GetObserver(nsIThreadObserver** aObs) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver(); + obs.forget(aObs); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetObserver(nsIThreadObserver* aObs) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + mEvents->SetObserver(aObs); + return NS_OK; +} + +uint32_t nsThread::RecursionDepth() const { + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + return mNestedEventLoopDepth; +} + +NS_IMETHODIMP +nsThread::AddObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + EventQueue()->AddObserver(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::RemoveObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + EventQueue()->RemoveObserver(aObserver); + + return NS_OK; +} + +void nsThread::SetScriptObserver( + mozilla::CycleCollectedJSContext* aScriptObserver) { + if (!aScriptObserver) { + mScriptObserver = nullptr; + return; + } + + MOZ_ASSERT(!mScriptObserver); + mScriptObserver = aScriptObserver; +} + +void NS_DispatchMemoryPressure(); + +void nsThread::DoMainThreadSpecificProcessing() const { + MOZ_ASSERT(mIsMainThread); + + ipc::CancelCPOWs(); + + // Fire a memory pressure notification, if one is pending. + if (!ShuttingDown()) { + NS_DispatchMemoryPressure(); + } +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher + +NS_IMETHODIMP +nsThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.AddTask(std::move(aEvent)); + return NS_OK; +} + +NS_IMETHODIMP nsThread::DrainDirectTasks() { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.DrainTasks(); + return NS_OK; +} + +NS_IMETHODIMP nsThread::HaveDirectTasks(bool* aValue) { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + + *aValue = mDirectTasks.HaveTasks(); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsThreadShutdownContext, nsIThreadShutdown) + +NS_IMETHODIMP +nsThreadShutdownContext::OnCompletion(nsIRunnable* aEvent) { + if (mCompleted) { + aEvent->Run(); + } else { + mCompletionCallbacks.AppendElement(aEvent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadShutdownContext::GetCompleted(bool* aCompleted) { + *aCompleted = mCompleted; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadShutdownContext::StopWaitingAndLeakThread() { + // Take the joining thread from `mJoiningThread` so that the terminating + // thread won't try to dispatch nsThreadShutdownAckEvent to us anymore. + RefPtr<nsThread> joiningThread; + { + MutexAutoLock lock(mJoiningThreadMutex); + if (!mJoiningThread) { + // Shutdown is already being resolved, so there's nothing for us to do. + return NS_ERROR_NOT_AVAILABLE; + } + joiningThread = mJoiningThread.forget(); + mThreadLeaked = true; + } + + MOZ_DIAGNOSTIC_ASSERT(joiningThread->IsOnCurrentThread()); + + MarkCompleted(); + + return NS_OK; +} + +void nsThreadShutdownContext::MarkCompleted() { + MOZ_ASSERT(!mCompleted); + mCompleted = true; + nsTArray<nsCOMPtr<nsIRunnable>> callbacks(std::move(mCompletionCallbacks)); + for (auto& callback : callbacks) { + callback->Run(); + } +} + +namespace mozilla { +PerformanceCounterState::Snapshot PerformanceCounterState::RunnableWillRun( + TimeStamp aNow, bool aIsIdleRunnable) { + if (mIsMainThread && IsNestedRunnable()) { + // Flush out any accumulated time that should be accounted to the + // current runnable before we start running a nested runnable. Don't + // do this for non-mainthread threads that may be running their own + // event loops, like SocketThread. + MaybeReportAccumulatedTime("nested runnable"_ns, aNow); + } + + Snapshot snapshot(mCurrentEventLoopDepth, mCurrentRunnableIsIdleRunnable); + + mCurrentEventLoopDepth = mNestedEventLoopDepth; + mCurrentRunnableIsIdleRunnable = aIsIdleRunnable; + mCurrentTimeSliceStart = aNow; + + return snapshot; +} + +void PerformanceCounterState::RunnableDidRun(const nsCString& aName, + Snapshot&& aSnapshot) { + // First thing: Restore our mCurrentEventLoopDepth so we can use + // IsNestedRunnable(). + mCurrentEventLoopDepth = aSnapshot.mOldEventLoopDepth; + + // We may not need the current timestamp; don't bother computing it if we + // don't. + TimeStamp now; + if (mLongTaskLength.isSome() || IsNestedRunnable()) { + now = TimeStamp::Now(); + } + if (mLongTaskLength.isSome()) { + MaybeReportAccumulatedTime(aName, now); + } + + // And now restore the rest of our state. + mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable; + if (IsNestedRunnable()) { + // Reset mCurrentTimeSliceStart to right now, so our parent runnable's + // next slice can be properly accounted for. + mCurrentTimeSliceStart = now; + } else { + // We are done at the outermost level; we are no longer in a timeslice. + mCurrentTimeSliceStart = TimeStamp(); + } +} + +void PerformanceCounterState::MaybeReportAccumulatedTime(const nsCString& aName, + TimeStamp aNow) { + MOZ_ASSERT(mCurrentTimeSliceStart, + "How did we get here if we're not in a timeslice?"); + if (!mLongTaskLength.isSome()) { + return; + } + + TimeDuration duration = aNow - mCurrentTimeSliceStart; +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_TELEMETRY_MS) { + Telemetry::Accumulate(Telemetry::EVENT_LONGTASK, aName, + duration.ToMilliseconds()); + } +#endif + + // Long tasks only matter on the main thread. + if (duration.ToMilliseconds() >= mLongTaskLength.value()) { + // Idle events (gc...) don't *really* count here + if (!mCurrentRunnableIsIdleRunnable) { + mLastLongNonIdleTaskEnd = aNow; + } + mLastLongTaskEnd = aNow; + + if (profiler_thread_is_being_profiled_for_markers()) { + struct LongTaskMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("MainThreadLongTask"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) { + aWriter.StringProperty("category", "LongTask"); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormatSearchable("category", "Type", + MS::Format::String, + MS::Searchable::Searchable); + return schema; + } + }; + + profiler_add_marker(mCurrentRunnableIsIdleRunnable + ? ProfilerString8View("LongIdleTask") + : ProfilerString8View("LongTask"), + geckoprofiler::category::OTHER, + MarkerTiming::Interval(mCurrentTimeSliceStart, aNow), + LongTaskMarker{}); + } + } +} + +} // namespace mozilla diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h new file mode 100644 index 0000000000..5b177f0c0c --- /dev/null +++ b/xpcom/threads/nsThread.h @@ -0,0 +1,364 @@ +/* -*- 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 nsThread_h__ +#define nsThread_h__ + +#include "MainThreadUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" +#include "mozilla/EventQueue.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/NotNull.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsIEventTarget.h" +#include "nsISerialEventTarget.h" +#include "nsISupportsPriority.h" +#include "nsIThread.h" +#include "nsIThreadInternal.h" +#include "nsTArray.h" + +namespace mozilla { +class CycleCollectedJSContext; +class DelayedRunnable; +class SynchronizedEventQueue; +class ThreadEventQueue; +class ThreadEventTarget; + +template <typename T, size_t Length> +class Array; +} // namespace mozilla + +using mozilla::NotNull; + +class nsIRunnable; +class nsThreadShutdownContext; + +// See https://www.w3.org/TR/longtasks +#define W3_LONGTASK_BUSY_WINDOW_MS 50 + +// Time a Runnable executes before we accumulate telemetry on it +#define LONGTASK_TELEMETRY_MS 30 + +// A class for managing performance counter state. +namespace mozilla { +class PerformanceCounterState { + public: + explicit PerformanceCounterState( + const uint32_t& aNestedEventLoopDepthRef, bool aIsMainThread = false, + const Maybe<uint32_t>& aLongTaskLength = Nothing()) + : mNestedEventLoopDepth(aNestedEventLoopDepthRef), + mIsMainThread(aIsMainThread), + mLongTaskLength(aLongTaskLength), + // Does it really make sense to initialize these to "now" when we + // haven't run any tasks? + mLastLongTaskEnd(TimeStamp::Now()), + mLastLongNonIdleTaskEnd(mLastLongTaskEnd) {} + + class Snapshot { + public: + Snapshot(uint32_t aOldEventLoopDepth, bool aOldIsIdleRunnable) + : mOldEventLoopDepth(aOldEventLoopDepth), + mOldIsIdleRunnable(aOldIsIdleRunnable) {} + + Snapshot(const Snapshot&) = default; + Snapshot(Snapshot&&) = default; + + private: + friend class PerformanceCounterState; + + const uint32_t mOldEventLoopDepth; + const bool mOldIsIdleRunnable; + }; + + // Notification that a runnable is about to run. This captures a snapshot of + // our current state before we reset to prepare for the new runnable. This + // muast be called after mNestedEventLoopDepth has been incremented for the + // runnable execution. The performance counter passed in should be the one + // for the relevant runnable and may be null. aIsIdleRunnable should be true + // if and only if the runnable has idle priority. + Snapshot RunnableWillRun(TimeStamp aNow, bool aIsIdleRunnable); + + // Notification that a runnable finished executing. This must be passed the + // snapshot that RunnableWillRun returned for the same runnable. This must be + // called before mNestedEventLoopDepth is decremented after the runnable's + // execution. + void RunnableDidRun(const nsCString& aName, Snapshot&& aSnapshot); + + const TimeStamp& LastLongTaskEnd() const { return mLastLongTaskEnd; } + const TimeStamp& LastLongNonIdleTaskEnd() const { + return mLastLongNonIdleTaskEnd; + } + + private: + // Called to report accumulated time, as needed, when we're about to run a + // runnable or just finished running one. + void MaybeReportAccumulatedTime(const nsCString& aName, TimeStamp aNow); + + // Whether the runnable we are about to run, or just ran, is a nested + // runnable, in the sense that there is some other runnable up the stack + // spinning the event loop. This must be called before we change our + // mCurrentEventLoopDepth (when about to run a new event) or after we restore + // it (after we ran one). + bool IsNestedRunnable() const { + return mNestedEventLoopDepth > mCurrentEventLoopDepth; + } + + // The event loop depth of the currently running runnable. Set to the max + // value of a uint32_t when there is no runnable running, so when starting to + // run a toplevel (not nested) runnable IsNestedRunnable() will test false. + uint32_t mCurrentEventLoopDepth = std::numeric_limits<uint32_t>::max(); + + // A reference to the nsThread's mNestedEventLoopDepth, so we can + // see what it is right now. + const uint32_t& mNestedEventLoopDepth; + + // A boolean that indicates whether the currently running runnable is an idle + // runnable. Only has a useful value between RunnableWillRun() being called + // and RunnableDidRun() returning. + bool mCurrentRunnableIsIdleRunnable = false; + + // Whether we're attached to the mainthread nsThread. + const bool mIsMainThread; + + // what is considered a LongTask (in ms) + const Maybe<uint32_t> mLongTaskLength; + + // The timestamp from which time to be accounted for should be measured. This + // can be the start of a runnable running or the end of a nested runnable + // running. + TimeStamp mCurrentTimeSliceStart; + + // Information about when long tasks last ended. + TimeStamp mLastLongTaskEnd; + TimeStamp mLastLongNonIdleTaskEnd; +}; +} // namespace mozilla + +// A native thread +class nsThread : public nsIThreadInternal, + public nsISupportsPriority, + public nsIDirectTaskDispatcher, + private mozilla::LinkedListElement<nsThread> { + friend mozilla::LinkedList<nsThread>; + friend mozilla::LinkedListElement<nsThread>; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREAD + NS_DECL_NSITHREADINTERNAL + NS_DECL_NSISUPPORTSPRIORITY + NS_DECL_NSIDIRECTTASKDISPATCHER + + enum MainThreadFlag { MAIN_THREAD, NOT_MAIN_THREAD }; + + nsThread(NotNull<mozilla::SynchronizedEventQueue*> aQueue, + MainThreadFlag aMainThread, + nsIThreadManager::ThreadCreationOptions aOptions); + + private: + nsThread(); + + public: + // Initialize this as a named wrapper for a new PRThread. + nsresult Init(const nsACString& aName); + + // Initialize this as a wrapper for the current PRThread. + nsresult InitCurrentThread(); + + // Get this thread's name, thread-safe. + void GetThreadName(nsACString& aNameBuffer); + + // Set this thread's name. Consider using + // NS_SetCurrentThreadName if you are not sure. + void SetThreadNameInternal(const nsACString& aName); + + private: + // Initializes the mThreadId and stack base/size members, and adds the thread + // to the ThreadList(). + void InitCommon(); + + public: + // The PRThread corresponding to this thread. + PRThread* GetPRThread() const { return mThread; } + + const void* StackBase() const { return mStackBase; } + size_t StackSize() const { return mStackSize; } + + uint32_t ThreadId() const { return mThreadId; } + + // If this flag is true, then the nsThread was created using + // nsIThreadManager::NewThread. + bool ShutdownRequired() { return mShutdownRequired; } + + // Lets GetRunningEventDelay() determine if the pool this is part + // of has an unstarted thread + void SetPoolThreadFreePtr(mozilla::Atomic<bool, mozilla::Relaxed>* aPtr) { + mIsAPoolThreadFree = aPtr; + } + + void SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver); + + uint32_t RecursionDepth() const; + + void ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext); + + void WaitForAllAsynchronousShutdowns(); + + static const uint32_t kRunnableNameBufSize = 1000; + static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName; + + mozilla::SynchronizedEventQueue* EventQueue() { return mEvents.get(); } + + bool ShuttingDown() const { return mShutdownContext != nullptr; } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Returns the size of this object, its PRThread, and its shutdown contexts, + // but excluding its event queues. + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + size_t SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const; + + void SetUseHangMonitor(bool aValue) { + MOZ_ASSERT(IsOnCurrentThread()); + mUseHangMonitor = aValue; + } + + private: + void DoMainThreadSpecificProcessing() const; + + protected: + friend class nsThreadShutdownEvent; + + virtual ~nsThread(); + + static void ThreadFunc(void* aArg); + + // Helper + already_AddRefed<nsIThreadObserver> GetObserver() { + nsIThreadObserver* obs; + nsThread::GetObserver(&obs); + return already_AddRefed<nsIThreadObserver>(obs); + } + + already_AddRefed<nsThreadShutdownContext> ShutdownInternal(bool aSync); + + friend class nsThreadManager; + friend class nsThreadPool; + + void MaybeRemoveFromThreadList(); + + // Whether or not these members have a value determines whether the nsThread + // is treated as a full XPCOM thread or as a thin wrapper. + // + // For full nsThreads, they will always contain valid pointers. For thin + // wrappers around non-XPCOM threads, they will be null, and event dispatch + // methods which rely on them will fail (and assert) if called. + RefPtr<mozilla::SynchronizedEventQueue> mEvents; + RefPtr<mozilla::ThreadEventTarget> mEventTarget; + + // The number of outstanding nsThreadShutdownContext started by this thread. + // The thread will not be allowed to exit until this number reaches 0. + uint32_t mOutstandingShutdownContexts; + // The shutdown context for ourselves. + RefPtr<nsThreadShutdownContext> mShutdownContext; + + mozilla::CycleCollectedJSContext* mScriptObserver; + + // Our name. + mozilla::DataMutex<nsCString> mThreadName; + + void* mStackBase = nullptr; + uint32_t mStackSize; + uint32_t mThreadId; + + uint32_t mNestedEventLoopDepth; + + mozilla::Atomic<bool> mShutdownRequired; + + int8_t mPriority; + + const bool mIsMainThread; + bool mUseHangMonitor; + const bool mIsUiThread; + mozilla::Atomic<bool, mozilla::Relaxed>* mIsAPoolThreadFree; + + // Set to true if this thread creates a JSRuntime. + bool mCanInvokeJS; + + // The time the currently running event spent in event queues, and + // when it started running. If no event is running, they are + // TimeDuration() & TimeStamp(). + mozilla::TimeDuration mLastEventDelay; + mozilla::TimeStamp mLastEventStart; + +#ifdef EARLY_BETA_OR_EARLIER + nsCString mNameForWakeupTelemetry; + mozilla::TimeStamp mLastWakeupCheckTime; + uint32_t mWakeupCount = 0; +#endif + + mozilla::PerformanceCounterState mPerformanceCounterState; + + mozilla::SimpleTaskQueue mDirectTasks; +}; + +class nsThreadShutdownContext final : public nsIThreadShutdown { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITHREADSHUTDOWN + + private: + friend class nsThread; + friend class nsThreadShutdownEvent; + friend class nsThreadShutdownAckEvent; + + nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread, + nsThread* aJoiningThread) + : mTerminatingThread(aTerminatingThread), + mTerminatingPRThread(aTerminatingThread->GetPRThread()), + mJoiningThreadMutex("nsThreadShutdownContext::mJoiningThreadMutex"), + mJoiningThread(aJoiningThread) {} + + ~nsThreadShutdownContext() = default; + + // Must be called on the joining thread. + void MarkCompleted(); + + // NB: This may be the last reference. + NotNull<RefPtr<nsThread>> const mTerminatingThread; + PRThread* const mTerminatingPRThread; + + // May only be accessed on the joining thread. + bool mCompleted = false; + nsTArray<nsCOMPtr<nsIRunnable>> mCompletionCallbacks; + + // The thread waiting for this thread to shut down. Will either be cleared by + // the joining thread if `StopWaitingAndLeakThread` is called or by the + // terminating thread upon exiting and notifying the joining thread. + mozilla::Mutex mJoiningThreadMutex; + RefPtr<nsThread> mJoiningThread MOZ_GUARDED_BY(mJoiningThreadMutex); + bool mThreadLeaked MOZ_GUARDED_BY(mJoiningThreadMutex) = false; +}; + +#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM && \ + defined(_GNU_SOURCE) +# define MOZ_CANARY + +extern int sCanaryOutputFD; +#endif + +#endif // nsThread_h__ diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp new file mode 100644 index 0000000000..805be6ee64 --- /dev/null +++ b/xpcom/threads/nsThreadManager.cpp @@ -0,0 +1,841 @@ +/* -*- 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 "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsExceptionHandler.h" +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "nsExceptionHandler.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask +#include "mozilla/EventQueue.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/Mutex.h" +#include "mozilla/NeverDestroyed.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/ThreadEventQueue.h" +#include "mozilla/ThreadLocal.h" +#include "TaskController.h" +#include "ThreadEventTarget.h" +#ifdef MOZ_CANARY +# include <fcntl.h> +# include <unistd.h> +#endif + +#include "MainThreadIdlePeriod.h" + +using namespace mozilla; + +static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread; + +bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); } + +class BackgroundEventTarget final : public nsIEventTarget, + public TaskQueueTracker { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + BackgroundEventTarget() = default; + + nsresult Init(); + + already_AddRefed<TaskQueue> CreateBackgroundTaskQueue(const char* aName); + + void BeginShutdown(nsTArray<RefPtr<ShutdownPromise>>&); + void FinishShutdown(); + + private: + ~BackgroundEventTarget() = default; + + nsCOMPtr<nsIThreadPool> mPool; + nsCOMPtr<nsIThreadPool> mIOPool; +}; + +NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget, TaskQueueTracker) + +nsresult BackgroundEventTarget::Init() { + nsCOMPtr<nsIThreadPool> pool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + nsresult rv = pool->SetName("BackgroundThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 2 makes deadlock during synchronous dispatch less likely. + rv = pool->SetThreadLimit(2); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = pool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + // Initialize the background I/O event target. + nsCOMPtr<nsIThreadPool> ioPool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + // The io pool spends a lot of its time blocking on io, so we want to offload + // these jobs on a lower priority if available. + rv = ioPool->SetQoSForThreads(nsIThread::QOS_PRIORITY_LOW); + NS_ENSURE_SUCCESS( + rv, rv); // note: currently infallible, keeping this for brevity. + + rv = ioPool->SetName("BgIOThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 4 makes deadlock during synchronous dispatch less likely. + rv = ioPool->SetThreadLimit(4); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ioPool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = ioPool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + pool.swap(mPool); + ioPool.swap(mIOPool); + + return NS_OK; +} + +NS_IMETHODIMP_(bool) +BackgroundEventTarget::IsOnCurrentThreadInfallible() { + return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread(); +} + +NS_IMETHODIMP +BackgroundEventTarget::IsOnCurrentThread(bool* aValue) { + bool value = false; + if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) { + *aValue = value; + return NS_OK; + } + return mIOPool->IsOnCurrentThread(aValue); +} + +NS_IMETHODIMP +BackgroundEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags) { + // We need to be careful here, because if an event is getting dispatched here + // from within TaskQueue::Runner::Run, it will be dispatched with + // NS_DISPATCH_AT_END, but we might not be running the event on the same + // pool, depending on which pool we were on and the dispatch flags. If we + // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool + // may not process the event in a timely fashion, which can lead to deadlock. + uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK; + bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK); + nsCOMPtr<nsIThreadPool>& pool = mayBlock ? mIOPool : mPool; + + // If we're already running on the pool we want to dispatch to, we can + // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin + // up a new thread. + // + // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues + // like those in the above comment. + if (pool->IsOnCurrentThread()) { + flags |= NS_DISPATCH_AT_END; + } else { + flags &= ~NS_DISPATCH_AT_END; + } + + return pool->Dispatch(std::move(aRunnable), flags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + return Dispatch(runnable.forget(), aFlags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t) { + nsCOMPtr<nsIRunnable> dropRunnable(aRunnable); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BackgroundEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BackgroundEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BackgroundEventTarget::BeginShutdown( + nsTArray<RefPtr<ShutdownPromise>>& promises) { + auto queues = GetAllTrackedTaskQueues(); + for (auto& queue : queues) { + promises.AppendElement(queue->BeginShutdown()); + } +} + +void BackgroundEventTarget::FinishShutdown() { + mPool->Shutdown(); + mIOPool->Shutdown(); +} + +already_AddRefed<TaskQueue> BackgroundEventTarget::CreateBackgroundTaskQueue( + const char* aName) { + return TaskQueue::Create(do_AddRef(this), aName).forget(); +} + +extern "C" { +// This uses the C language linkage because it's exposed to Rust +// via the xpcom/rust/moz_task crate. +bool NS_IsMainThread() { return sTLSIsMainThread.get(); } +} + +void NS_SetMainThread() { + if (!sTLSIsMainThread.init()) { + MOZ_CRASH(); + } + sTLSIsMainThread.set(true); + MOZ_ASSERT(NS_IsMainThread()); + // We initialize the SerialEventTargetGuard's TLS here for simplicity as it + // needs to be initialized around the same time you would initialize + // sTLSIsMainThread. + SerialEventTargetGuard::InitTLS(); + nsThreadPool::InitTLS(); +} + +#ifdef DEBUG + +namespace mozilla { + +void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); } + +} // namespace mozilla + +#endif + +//----------------------------------------------------------------------------- + +/* static */ +void nsThreadManager::ReleaseThread(void* aData) { + static_cast<nsThread*>(aData)->Release(); +} + +// statically allocated instance +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::Release() { return 1; } +NS_IMPL_CLASSINFO(nsThreadManager, nullptr, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, + NS_THREADMANAGER_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) +NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) + +//----------------------------------------------------------------------------- + +/*static*/ nsThreadManager& nsThreadManager::get() { + static NeverDestroyed<nsThreadManager> sInstance; + return *sInstance; +} + +nsThreadManager::nsThreadManager() + : mCurThreadIndex(0), + mMutex("nsThreadManager::mMutex"), + mState(State::eUninit) {} + +nsThreadManager::~nsThreadManager() = default; + +nsresult nsThreadManager::Init() { + // Child processes need to initialize the thread manager before they + // initialize XPCOM in order to set up the crash reporter. This leads to + // situations where we get initialized twice. + { + OffTheBooksMutexAutoLock lock(mMutex); + if (mState > State::eUninit) { + return NS_OK; + } + } + + if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseThread) == PR_FAILURE) { + return NS_ERROR_FAILURE; + } + +#ifdef MOZ_CANARY + const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + char* env_var_flag = getenv("MOZ_KILL_CANARIES"); + sCanaryOutputFD = + env_var_flag + ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO) + : 0; +#endif + + TaskController::Initialize(); + + // Initialize idle handling. + nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod(); + TaskController::Get()->SetIdleTaskManager( + new IdleTaskManager(idlePeriod.forget())); + + // Create main thread queue that forwards events to TaskController and + // construct main thread. + UniquePtr<EventQueue> queue = MakeUnique<EventQueue>(true); + + RefPtr<ThreadEventQueue> synchronizedQueue = + new ThreadEventQueue(std::move(queue), true); + + mMainThread = + new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, + {0, false, false, Some(W3_LONGTASK_BUSY_WINDOW_MS)}); + + nsresult rv = mMainThread->InitCurrentThread(); + if (NS_FAILED(rv)) { + mMainThread = nullptr; + return rv; + } +#ifdef MOZ_MEMORY + jemalloc_set_main_thread(); +#endif + + // Init AbstractThread. + AbstractThread::InitTLS(); + AbstractThread::InitMainThread(); + + // Initialize the background event target. + RefPtr<BackgroundEventTarget> target(new BackgroundEventTarget()); + + rv = target->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + { + OffTheBooksMutexAutoLock lock(mMutex); + + mBackgroundEventTarget = std::move(target); + + mState = State::eActive; + } + + return NS_OK; +} + +void nsThreadManager::ShutdownNonMainThreads() { + MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); + + // Empty the main thread event queue before we begin shutting down threads. + NS_ProcessPendingEvents(mMainThread); + + mMainThread->mEvents->RunShutdownTasks(); + + RefPtr<BackgroundEventTarget> backgroundEventTarget; + { + OffTheBooksMutexAutoLock lock(mMutex); + MOZ_ASSERT(mState == State::eActive, "shutdown called multiple times"); + backgroundEventTarget = mBackgroundEventTarget; + } + + nsTArray<RefPtr<ShutdownPromise>> promises; + backgroundEventTarget->BeginShutdown(promises); + + bool taskQueuesShutdown = false; + // It's fine to capture everything by reference in the Then handler since it + // runs before we exit the nested event loop, thanks to the SpinEventLoopUntil + // below. + ShutdownPromise::All(mMainThread, promises)->Then(mMainThread, __func__, [&] { + backgroundEventTarget->FinishShutdown(); + taskQueuesShutdown = true; + }); + + // Wait for task queues to shutdown, so we don't shut down the underlying + // threads of the background event target in the block below, thereby + // preventing the task queues from emptying, preventing the shutdown promises + // from resolving, and prevent anything checking `taskQueuesShutdown` from + // working. + mozilla::SpinEventLoopUntil( + "nsThreadManager::Shutdown"_ns, [&]() { return taskQueuesShutdown; }, + mMainThread); + + { + // Prevent new nsThreads from being created, and collect a list of threads + // which need to be shut down. + // + // We don't prevent new thread creation until we've shut down background + // task queues, to ensure that they are able to start thread pool threads + // for shutdown tasks. + nsTArray<RefPtr<nsThread>> threadsToShutdown; + { + OffTheBooksMutexAutoLock lock(mMutex); + mState = State::eShutdown; + + for (auto* thread : mThreadList) { + if (thread->ShutdownRequired()) { + threadsToShutdown.AppendElement(thread); + } + } + } + + // It's tempting to walk the list of threads here and tell them each to stop + // accepting new events, but that could lead to badness if one of those + // threads is stuck waiting for a response from another thread. To do it + // right, we'd need some way to interrupt the threads. + // + // Instead, we process events on the current thread while waiting for + // threads to shutdown. This means that we have to preserve a mostly + // functioning world until such time as the threads exit. + + // As we're going to be waiting for all asynchronous shutdowns below, we + // can begin asynchronously shutting down all XPCOM threads here, rather + // than shutting each thread down one-at-a-time. + for (const auto& thread : threadsToShutdown) { + thread->AsyncShutdown(); + } + } + + // NB: It's possible that there are events in the queue that want to *start* + // an asynchronous shutdown. But we have already started async shutdown of + // the threads above, so there's no need to worry about them. We only have to + // wait for all in-flight asynchronous thread shutdowns to complete. + mMainThread->WaitForAllAsynchronousShutdowns(); + + // There are no more background threads at this point. +} + +void nsThreadManager::ShutdownMainThread() { +#ifdef DEBUG + { + OffTheBooksMutexAutoLock lock(mMutex); + MOZ_ASSERT(mState == State::eShutdown, "Must have called BeginShutdown"); + } +#endif + + // Do NS_ProcessPendingEvents but with special handling to set + // mEventsAreDoomed atomically with the removal of the last event. This means + // that PutEvent cannot succeed if the event would be left in the main thread + // queue after our final call to NS_ProcessPendingEvents. + // See comments in `nsThread::ThreadFunc` for a more detailed explanation. + while (true) { + if (mMainThread->mEvents->ShutdownIfNoPendingEvents()) { + break; + } + NS_ProcessPendingEvents(mMainThread); + } + + // Normally thread shutdown clears the observer for the thread, but since the + // main thread is special we do it manually here after we're sure all events + // have been processed. + mMainThread->SetObserver(nullptr); + + OffTheBooksMutexAutoLock lock(mMutex); + mBackgroundEventTarget = nullptr; +} + +void nsThreadManager::ReleaseMainThread() { +#ifdef DEBUG + { + OffTheBooksMutexAutoLock lock(mMutex); + MOZ_ASSERT(mState == State::eShutdown, "Must have called BeginShutdown"); + MOZ_ASSERT(!mBackgroundEventTarget, "Must have called ShutdownMainThread"); + } +#endif + MOZ_ASSERT(mMainThread); + + // Release main thread object. + mMainThread = nullptr; + + // Remove the TLS entry for the main thread. + PR_SetThreadPrivate(mCurThreadIndex, nullptr); +} + +void nsThreadManager::RegisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + aThread.AddRef(); // for TLS entry + PR_SetThreadPrivate(mCurThreadIndex, &aThread); + +#ifdef DEBUG + { + OffTheBooksMutexAutoLock lock(mMutex); + MOZ_ASSERT(aThread.isInList(), + "Thread was not added to the thread list before registering!"); + } +#endif +} + +void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + PR_SetThreadPrivate(mCurThreadIndex, nullptr); + // Ref-count balanced via ReleaseThread +} + +// Not to be used for MainThread! +nsThread* nsThreadManager::CreateCurrentThread(SynchronizedEventQueue* aQueue) { + // Make sure we don't have an nsThread yet. + MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex)); + + if (!AllowNewXPCOMThreads()) { + return nullptr; + } + + RefPtr<nsThread> thread = new nsThread( + WrapNotNull(aQueue), nsThread::NOT_MAIN_THREAD, {.stackSize = 0}); + if (NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags) { + RefPtr<BackgroundEventTarget> backgroundTarget; + { + OffTheBooksMutexAutoLock lock(mMutex); + if (!AllowNewXPCOMThreadsLocked() || !mBackgroundEventTarget) { + return NS_ERROR_FAILURE; + } + backgroundTarget = mBackgroundEventTarget; + } + + return backgroundTarget->Dispatch(aEvent, aDispatchFlags); +} + +already_AddRefed<TaskQueue> nsThreadManager::CreateBackgroundTaskQueue( + const char* aName) { + RefPtr<BackgroundEventTarget> backgroundTarget; + { + OffTheBooksMutexAutoLock lock(mMutex); + if (!AllowNewXPCOMThreadsLocked() || !mBackgroundEventTarget) { + return nullptr; + } + backgroundTarget = mBackgroundEventTarget; + } + + return backgroundTarget->CreateBackgroundTaskQueue(aName); +} + +nsThread* nsThreadManager::GetCurrentThread() { + // read thread local storage + void* data = PR_GetThreadPrivate(mCurThreadIndex); + if (data) { + return static_cast<nsThread*>(data); + } + + // Keep this function working early during startup or late during shutdown on + // the main thread. + if (!AllowNewXPCOMThreads() || NS_IsMainThread()) { + return nullptr; + } + + // OK, that's fine. We'll dynamically create one :-) + // + // We assume that if we're implicitly creating a thread here that it doesn't + // want an event queue. Any thread which wants an event queue should + // explicitly create its nsThread wrapper. + // + // nsThread::InitCurrentThread() will check AllowNewXPCOMThreads, and return + // an error if we're too late in shutdown to create new XPCOM threads. + RefPtr<nsThread> thread = new nsThread(); + if (NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +bool nsThreadManager::IsNSThread() const { + { + OffTheBooksMutexAutoLock lock(mMutex); + if (mState == State::eUninit) { + return false; + } + } + if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) { + return thread->EventQueue(); + } + return false; +} + +NS_IMETHODIMP +nsThreadManager::NewNamedThread( + const nsACString& aName, nsIThreadManager::ThreadCreationOptions aOptions, + nsIThread** aResult) { + // Note: can be called from arbitrary threads + + [[maybe_unused]] TimeStamp startTime = TimeStamp::Now(); + + RefPtr<ThreadEventQueue> queue = + new ThreadEventQueue(MakeUnique<EventQueue>()); + RefPtr<nsThread> thr = + new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aOptions); + + // Note: nsThread::Init() will check AllowNewXPCOMThreads, and return an + // error if we're too late in shutdown to create new XPCOM threads. If we + // aren't, the thread will be synchronously added to mThreadList. + nsresult rv = thr->Init(aName); + if (NS_FAILED(rv)) { + return rv; + } + + PROFILER_MARKER_TEXT( + "NewThread", OTHER, + MarkerOptions(MarkerStack::Capture(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + if (!NS_IsMainThread()) { + PROFILER_MARKER_TEXT( + "NewThread (non-main thread)", OTHER, + MarkerOptions(MarkerStack::Capture(), MarkerThreadId::MainThread(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + } + + thr.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + if (!NS_IsMainThread()) { + NS_WARNING( + "Called GetMainThread but there isn't a main thread and " + "we're not the main thread."); + } + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aResult = mMainThread); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetCurrentThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = GetCurrentThread(); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, + ShutdownPhase::NotInShutdown); +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilOrQuit( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, + ShutdownPhase::AppShutdownConfirmed); +} + +// statics from SpinEventLoopUntil.h +AutoNestedEventLoopAnnotation* AutoNestedEventLoopAnnotation::sCurrent = + nullptr; +StaticMutex AutoNestedEventLoopAnnotation::sStackMutex; + +// static from SpinEventLoopUntil.h +void AutoNestedEventLoopAnnotation::AnnotateXPCOMSpinEventLoopStack( + const nsACString& aStack) { + if (aStack.Length() > 0) { + nsCString prefixedStack(XRE_GetProcessTypeString()); + prefixedStack += ": "_ns + aStack; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::XPCOMSpinEventLoopStack, prefixedStack); + } else { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::XPCOMSpinEventLoopStack, ""_ns); + } +} + +nsresult nsThreadManager::SpinEventLoopUntilInternal( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition, + ShutdownPhase aShutdownPhaseToCheck) { + // XXX: We would want to AssertIsOnMainThread(); but that breaks some GTest. + nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition); + nsresult rv = NS_OK; + + if (!mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, [&]() -> bool { + // Check if an ongoing shutdown reached our limits. + if (aShutdownPhaseToCheck > ShutdownPhase::NotInShutdown && + AppShutdown::GetCurrentShutdownPhase() >= aShutdownPhaseToCheck) { + return true; + } + + bool isDone = false; + rv = condition->IsDone(&isDone); + // JS failure should be unusual, but we need to stop and propagate + // the error back to the caller. + if (NS_FAILED(rv)) { + return true; + } + + return isDone; + })) { + // We stopped early for some reason, which is unexpected. + return NS_ERROR_UNEXPECTED; + } + + // If we exited when the condition told us to, we need to return whether + // the condition encountered failure when executing. + return rv; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilEmpty() { + nsIThread* thread = NS_GetCurrentThread(); + + while (NS_HasPendingEvents(thread)) { + (void)NS_ProcessNextEvent(thread, false); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) { + nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget(); + target.forget(aTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority, + uint8_t aArgc) { + // Note: C++ callers should instead use NS_DispatchToMainThread. + MOZ_ASSERT(NS_IsMainThread()); + + // Keep this functioning during Shutdown + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + // If aPriority wasn't explicitly passed, that means it should be treated as + // PRIORITY_NORMAL. + if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) { + nsCOMPtr<nsIRunnable> event(aEvent); + return mMainThread->DispatchFromScript( + new PrioritizableRunnable(event.forget(), aPriority), 0); + } + return mMainThread->DispatchFromScript(aEvent, 0); +} + +class AutoMicroTaskWrapperRunnable final : public Runnable { + public: + explicit AutoMicroTaskWrapperRunnable(nsIRunnable* aEvent) + : Runnable("AutoMicroTaskWrapperRunnable"), mEvent(aEvent) { + MOZ_ASSERT(aEvent); + } + + private: + ~AutoMicroTaskWrapperRunnable() = default; + + NS_IMETHOD Run() override { + nsAutoMicroTask mt; + + return mEvent->Run(); + } + + RefPtr<nsIRunnable> mEvent; +}; + +NS_IMETHODIMP +nsThreadManager::DispatchToMainThreadWithMicroTask(nsIRunnable* aEvent, + uint32_t aPriority, + uint8_t aArgc) { + RefPtr<AutoMicroTaskWrapperRunnable> runnable = + new AutoMicroTaskWrapperRunnable(aEvent); + + return DispatchToMainThread(runnable, aPriority, aArgc); +} + +void nsThreadManager::EnableMainThreadEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->EnableInputEventPrioritization(); +} + +void nsThreadManager::FlushInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->FlushInputEventPrioritization(); +} + +void nsThreadManager::SuspendInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->SuspendInputEventPrioritization(); +} + +void nsThreadManager::ResumeInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->ResumeInputEventPrioritization(); +} + +// static +bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() { + MOZ_ASSERT(NS_IsMainThread()); + bool retVal = false; + if (get().mMainThread) { + get().mMainThread->HasPendingHighPriorityEvents(&retVal); + } + return retVal; +} + +NS_IMETHODIMP +nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent, + uint32_t aTimeout) { + // Note: C++ callers should instead use NS_DispatchToThreadQueue or + // NS_DispatchToCurrentThreadQueue. + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIRunnable> event(aEvent); + if (aTimeout) { + return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread, + EventQueuePriority::Idle); + } + + return NS_DispatchToThreadQueue(event.forget(), mMainThread, + EventQueuePriority::Idle); +} + +NS_IMETHODIMP +nsThreadManager::DispatchDirectTaskToCurrentThread(nsIRunnable* aEvent) { + NS_ENSURE_STATE(aEvent); + nsCOMPtr<nsIRunnable> runnable = aEvent; + return GetCurrentThread()->DispatchDirectTask(runnable.forget()); +} + +bool nsThreadManager::AllowNewXPCOMThreads() { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + return AllowNewXPCOMThreadsLocked(); +} diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h new file mode 100644 index 0000000000..46e061a1ad --- /dev/null +++ b/xpcom/threads/nsThreadManager.h @@ -0,0 +1,156 @@ +/* -*- 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 nsThreadManager_h__ +#define nsThreadManager_h__ + +#include "nsIThreadManager.h" +#include "nsThread.h" +#include "mozilla/ShutdownPhase.h" + +class nsIRunnable; +class nsIThread; + +namespace mozilla { +class IdleTaskManager; +class SynchronizedEventQueue; +class TaskQueue; + +template <typename T> +class NeverDestroyed; +} // namespace mozilla + +class BackgroundEventTarget; + +class nsThreadManager : public nsIThreadManager { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITHREADMANAGER + + static nsThreadManager& get(); + + nsresult Init(); + + // Shutdown all threads other than the main thread. This function should only + // be called on the main thread of the application process. + void ShutdownNonMainThreads(); + + // Finish shutting down all threads. This function must be called after + // ShutdownNonMainThreads and will delete the BackgroundEventTarget and + // take the main thread event target out of commission, but without + // releasing the underlying nsThread object. + void ShutdownMainThread(); + + // Release the underlying main thread nsThread object. + void ReleaseMainThread(); + + // Called by nsThread to inform the ThreadManager it exists. This method + // must be called when the given thread is the current thread. + void RegisterCurrentThread(nsThread& aThread); + + // Called by nsThread to inform the ThreadManager it is going away. This + // method must be called when the given thread is the current thread. + void UnregisterCurrentThread(nsThread& aThread); + + // Returns the current thread. Returns null if OOM or if ThreadManager isn't + // initialized. Creates the nsThread if one does not exist yet. + nsThread* GetCurrentThread(); + + // Returns true iff the currently running thread has an nsThread associated + // with it (ie; whether this is a thread that we can dispatch runnables to). + bool IsNSThread() const; + + // CreateCurrentThread sets up an nsThread for the current thread. It uses the + // event queue and main thread flags passed in. It should only be called once + // for the current thread. After it returns, GetCurrentThread() will return + // the thread that was created. GetCurrentThread() will also create a thread + // (lazily), but it doesn't allow the queue or main-thread attributes to be + // specified. + nsThread* CreateCurrentThread(mozilla::SynchronizedEventQueue* aQueue); + + nsresult DispatchToBackgroundThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags); + + already_AddRefed<mozilla::TaskQueue> CreateBackgroundTaskQueue( + const char* aName); + + ~nsThreadManager(); + + void EnableMainThreadEventPrioritization(); + void FlushInputEventPrioritization(); + void SuspendInputEventPrioritization(); + void ResumeInputEventPrioritization(); + + static bool MainThreadHasPendingHighPriorityEvents(); + + nsIThread* GetMainThreadWeak() { return mMainThread; } + + // Low level methods for interacting with the global thread list. Be very + // careful when holding `ThreadListMutex()` as no new threads can be started + // while it is held. + mozilla::OffTheBooksMutex& ThreadListMutex() MOZ_RETURN_CAPABILITY(mMutex) { + return mMutex; + } + + bool AllowNewXPCOMThreads() MOZ_EXCLUDES(mMutex); + bool AllowNewXPCOMThreadsLocked() MOZ_REQUIRES(mMutex) { + return mState == State::eActive; + } + + mozilla::LinkedList<nsThread>& ThreadList() MOZ_REQUIRES(mMutex) { + return mThreadList; + } + + private: + friend class mozilla::NeverDestroyed<nsThreadManager>; + + nsThreadManager(); + + nsresult SpinEventLoopUntilInternal( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition, + mozilla::ShutdownPhase aShutdownPhaseToCheck); + + static void ReleaseThread(void* aData); + + enum class State : uint8_t { + // The thread manager has yet to be initialized. + eUninit, + // The thread manager is active, and operating normally. + eActive, + // The thread manager is in XPCOM shutdown. New calls to NS_NewNamedThread + // will fail, as all XPCOM threads are required to be shutting down. + eShutdown, + }; + + unsigned mCurThreadIndex; // thread-local-storage index + RefPtr<nsThread> mMainThread; + + mutable mozilla::OffTheBooksMutex mMutex; + + // Current state in the thread manager's lifecycle. See docs above. + State mState MOZ_GUARDED_BY(mMutex); + + // Global list of active nsThread instances, including both explicitly and + // implicitly created threads. + // + // NOTE: New entries to this list _may_ be added after mAllowNewThreads has + // been cleared, but only for implicitly created thread wrappers which are + // not shut down during XPCOM shutdown. + mozilla::LinkedList<nsThread> mThreadList MOZ_GUARDED_BY(mMutex); + + // Shared event target used for background runnables. + RefPtr<BackgroundEventTarget> mBackgroundEventTarget MOZ_GUARDED_BY(mMutex); +}; + +#define NS_THREADMANAGER_CID \ + { /* 7a4204c6-e45a-4c37-8ebb-6709a22c917c */ \ + 0x7a4204c6, 0xe45a, 0x4c37, { \ + 0x8e, 0xbb, 0x67, 0x09, 0xa2, 0x2c, 0x91, 0x7c \ + } \ + } + +#endif // nsThreadManager_h__ diff --git a/xpcom/threads/nsThreadPool.cpp b/xpcom/threads/nsThreadPool.cpp new file mode 100644 index 0000000000..c2ef022035 --- /dev/null +++ b/xpcom/threads/nsThreadPool.cpp @@ -0,0 +1,610 @@ +/* -*- 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 "nsThreadPool.h" + +#include "nsCOMArray.h" +#include "ThreadDelay.h" +#include "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "prinrval.h" +#include "mozilla/Logging.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerRunnable.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsThreadSyncDispatch.h" + +#include <mutex> + +using namespace mozilla; + +static LazyLogModule sThreadPoolLog("nsThreadPool"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args) + +static MOZ_THREAD_LOCAL(nsThreadPool*) gCurrentThreadPool; + +void nsThreadPool::InitTLS() { gCurrentThreadPool.infallibleInit(); } + +// DESIGN: +// o Allocate anonymous threads. +// o Use nsThreadPool::Run as the main routine for each thread. +// o Each thread waits on the event queue's monitor, checking for +// pending events and rescheduling itself as an idle thread. + +#define DEFAULT_THREAD_LIMIT 4 +#define DEFAULT_IDLE_THREAD_LIMIT 1 +#define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60) + +NS_IMPL_ISUPPORTS_INHERITED(nsThreadPool, Runnable, nsIThreadPool, + nsIEventTarget) + +nsThreadPool* nsThreadPool::GetCurrentThreadPool() { + return gCurrentThreadPool.get(); +} + +nsThreadPool::nsThreadPool() + : Runnable("nsThreadPool"), + mMutex("[nsThreadPool.mMutex]"), + mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]"), + mThreadLimit(DEFAULT_THREAD_LIMIT), + mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT), + mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT), + mIdleCount(0), + mQoSPriority(nsIThread::QOS_PRIORITY_NORMAL), + mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE), + mShutdown(false), + mRegressiveMaxIdleTime(false), + mIsAPoolThreadFree(true) { + LOG(("THRD-P(%p) constructor!!!\n", this)); +} + +nsThreadPool::~nsThreadPool() { + // Threads keep a reference to the nsThreadPool until they return from Run() + // after removing themselves from mThreads. + MOZ_ASSERT(mThreads.IsEmpty()); +} + +nsresult nsThreadPool::PutEvent(nsIRunnable* aEvent) { + nsCOMPtr<nsIRunnable> event(aEvent); + return PutEvent(event.forget(), 0); +} + +nsresult nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + // Avoid spawning a new thread while holding the event queue lock... + + bool spawnThread = false; + uint32_t stackSize = 0; + nsCString name; + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(), + mThreadLimit)); + MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops"); + + // Make sure we have a thread to service this event. + if (mThreads.Count() < (int32_t)mThreadLimit && + !(aFlags & NS_DISPATCH_AT_END) && + // Spawn a new thread if we don't have enough idle threads to serve + // pending events immediately. + mEvents.Count(lock) >= mIdleCount) { + spawnThread = true; + } + + nsCOMPtr<nsIRunnable> event(aEvent); + LogRunnable::LogDispatch(event); + mEvents.PutEvent(event.forget(), EventQueuePriority::Normal, lock); + mEventsAvailable.Notify(); + stackSize = mStackSize; + name = mName; + } + + auto delay = MakeScopeExit([&]() { + // Delay to encourage the receiving task to run before we do work. + DelayForChaosMode(ChaosFeature::TaskDispatching, 1000); + }); + + LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread)); + if (!spawnThread) { + return NS_OK; + } + + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread( + mThreadNaming.GetNextThreadName(name), getter_AddRefs(thread), nullptr, + {.stackSize = stackSize, .blockDispatch = true}); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + + bool killThread = false; + { + MutexAutoLock lock(mMutex); + if (mShutdown) { + killThread = true; + } else if (mThreads.Count() < (int32_t)mThreadLimit) { + mThreads.AppendObject(thread); + if (mThreads.Count() >= (int32_t)mThreadLimit) { + mIsAPoolThreadFree = false; + } + } else { + // Someone else may have also been starting a thread + killThread = true; // okay, we don't need this thread anymore + } + } + LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread)); + if (killThread) { + // We never dispatched any events to the thread, so we can shut it down + // asynchronously without worrying about anything. + ShutdownThread(thread); + } else { + thread->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH); + } + + return NS_OK; +} + +void nsThreadPool::ShutdownThread(nsIThread* aThread) { + LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread)); + + // This is either called by a threadpool thread that is out of work, or + // a thread that attempted to create a threadpool thread and raced in + // such a way that the newly created thread is no longer necessary. + // In the first case, we must go to another thread to shut aThread down + // (because it is the current thread). In the second case, we cannot + // synchronously shut down the current thread (because then Dispatch() would + // spin the event loop, and that could blow up the world), and asynchronous + // shutdown requires this thread have an event loop (and it may not, see bug + // 10204784). The simplest way to cover all cases is to asynchronously + // shutdown aThread from the main thread. + SchedulerGroup::Dispatch(NewRunnableMethod( + "nsIThread::AsyncShutdown", aThread, &nsIThread::AsyncShutdown)); +} + +NS_IMETHODIMP +nsThreadPool::SetQoSForThreads(nsIThread::QoSPriority aPriority) { + MutexAutoLock lock(mMutex); + mQoSPriority = aPriority; + + // We don't notify threads here to observe the change, because we don't want + // to create spurious wakeups during idle. Rather, we want threads to simply + // observe the change on their own if they wake up to do some task. + + return NS_OK; +} + +// This event 'runs' for the lifetime of the worker thread. The actual +// eventqueue is mEvents, and is shared by all the worker threads. This +// means that the set of threads together define the delay seen by a new +// event sent to the pool. +// +// To model the delay experienced by the pool, we can have each thread in +// the pool report 0 if it's idle OR if the pool is below the threadlimit; +// or otherwise the current event's queuing delay plus current running +// time. +// +// To reconstruct the delays for the pool, the profiler can look at all the +// threads that are part of a pool (pools have defined naming patterns that +// can be user to connect them). If all threads have delays at time X, +// that means that all threads saturated at that point and any event +// dispatched to the pool would get a delay. +// +// The delay experienced by an event dispatched when all pool threads are +// busy is based on the calculations shown in platform.cpp. Run that +// algorithm for each thread in the pool, and the delay at time X is the +// longest value for time X of any of the threads, OR the time from X until +// any one of the threads reports 0 (i.e. it's not busy), whichever is +// shorter. + +// In order to record this when the profiler samples threads in the pool, +// each thread must (effectively) override GetRunnningEventDelay, by +// resetting the mLastEventDelay/Start values in the nsThread when we start +// to run an event (or when we run out of events to run). Note that handling +// the shutdown of a thread may be a little tricky. + +NS_IMETHODIMP +nsThreadPool::Run() { + nsCOMPtr<nsIThread> current; + nsThreadManager::get().GetCurrentThread(getter_AddRefs(current)); + + bool shutdownThreadOnExit = false; + bool exitThread = false; + bool wasIdle = false; + TimeStamp idleSince; + nsIThread::QoSPriority threadPriority = nsIThread::QOS_PRIORITY_NORMAL; + + // This thread is an nsThread created below with NS_NewNamedThread() + static_cast<nsThread*>(current.get()) + ->SetPoolThreadFreePtr(&mIsAPoolThreadFree); + + nsCOMPtr<nsIThreadPoolListener> listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading())); + + // Go ahead and check for thread priority. If priority is normal, do nothing + // because threads are created with default priority. + if (threadPriority != mQoSPriority) { + current->SetThreadQoS(threadPriority); + threadPriority = mQoSPriority; + } + } + + if (listener) { + listener->OnThreadCreated(); + } + + MOZ_ASSERT(!gCurrentThreadPool.get()); + gCurrentThreadPool.set(this); + + do { + nsCOMPtr<nsIRunnable> event; + TimeDuration delay; + { + MutexAutoLock lock(mMutex); + + // Before getting the next event, we can adjust priority as needed. + if (threadPriority != mQoSPriority) { + current->SetThreadQoS(threadPriority); + threadPriority = mQoSPriority; + } + + event = mEvents.GetEvent(lock, &delay); + if (!event) { + TimeStamp now = TimeStamp::Now(); + uint32_t idleTimeoutDivider = + (mIdleCount && mRegressiveMaxIdleTime) ? mIdleCount : 1; + TimeDuration timeout = TimeDuration::FromMilliseconds( + static_cast<double>(mIdleThreadTimeout) / idleTimeoutDivider); + + // If we are shutting down, then don't keep any idle threads. + if (mShutdown) { + exitThread = true; + } else { + if (wasIdle) { + // if too many idle threads or idle for too long, then bail. + if (mIdleCount > mIdleThreadLimit || + (mIdleThreadTimeout != UINT32_MAX && + (now - idleSince) >= timeout)) { + exitThread = true; + } + } else { + // if would be too many idle threads... + if (mIdleCount == mIdleThreadLimit) { + exitThread = true; + } else { + ++mIdleCount; + idleSince = now; + wasIdle = true; + } + } + } + + if (exitThread) { + if (wasIdle) { + --mIdleCount; + } + shutdownThreadOnExit = mThreads.RemoveObject(current); + + // keep track if there are threads available to start + mIsAPoolThreadFree = (mThreads.Count() < (int32_t)mThreadLimit); + } else { + current->SetRunningEventDelay(TimeDuration(), TimeStamp()); + + AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE); + + TimeDuration delta = timeout - (now - idleSince); + LOG(("THRD-P(%p) %s waiting [%f]\n", this, mName.BeginReading(), + delta.ToMilliseconds())); + mEventsAvailable.Wait(delta); + LOG(("THRD-P(%p) done waiting\n", this)); + } + } else if (wasIdle) { + wasIdle = false; + --mIdleCount; + } + } + if (event) { + if (MOZ_LOG_TEST(sThreadPoolLog, mozilla::LogLevel::Debug)) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(), + event.get())); + } + + // Delay event processing to encourage whoever dispatched this event + // to run. + DelayForChaosMode(ChaosFeature::TaskRunning, 1000); + + if (profiler_thread_is_being_profiled( + ThreadProfilingFeatures::Sampling)) { + // We'll handle the case of unstarted threads available + // when we sample. + current->SetRunningEventDelay(delay, TimeStamp::Now()); + } + + LogRunnable::Run log(event); + AUTO_PROFILE_FOLLOWING_RUNNABLE(event); + event->Run(); + // To cover the event's destructor code in the LogRunnable span + event = nullptr; + } + } while (!exitThread); + + if (listener) { + listener->OnThreadShuttingDown(); + } + + MOZ_ASSERT(gCurrentThreadPool.get() == this); + gCurrentThreadPool.set(nullptr); + + if (shutdownThreadOnExit) { + ShutdownThread(current); + } + + LOG(("THRD-P(%p) leave\n", this)); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThreadPool::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) { + LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags)); + + if (NS_WARN_IF(mShutdown)) { + nsCOMPtr<nsIRunnable> event(aEvent); + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END, + "unexpected dispatch flags"); + PutEvent(std::move(aEvent), aFlags); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsThreadPool::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsThreadPool::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP_(bool) +nsThreadPool::IsOnCurrentThreadInfallible() { + return gCurrentThreadPool.get() == this; +} + +NS_IMETHODIMP +nsThreadPool::IsOnCurrentThread(bool* aResult) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aResult = IsOnCurrentThreadInfallible(); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::Shutdown() { return ShutdownWithTimeout(-1); } + +NS_IMETHODIMP +nsThreadPool::ShutdownWithTimeout(int32_t aTimeoutMs) { + nsCOMArray<nsIThread> threads; + nsCOMPtr<nsIThreadPoolListener> listener; + { + MutexAutoLock lock(mMutex); + if (mShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + mShutdown = true; + mEventsAvailable.NotifyAll(); + + threads.AppendObjects(mThreads); + mThreads.Clear(); + + // Swap in a null listener so that we release the listener at the end of + // this method. The listener will be kept alive as long as the other threads + // that were created when it was set. + mListener.swap(listener); + } + + nsTArray<nsCOMPtr<nsIThreadShutdown>> contexts; + for (int32_t i = 0; i < threads.Count(); ++i) { + nsCOMPtr<nsIThreadShutdown> context; + if (NS_SUCCEEDED(threads[i]->BeginShutdown(getter_AddRefs(context)))) { + contexts.AppendElement(std::move(context)); + } + } + + // Start a timer which will stop waiting & leak the thread, forcing + // onCompletion to be called when it expires. + nsCOMPtr<nsITimer> timer; + if (aTimeoutMs >= 0) { + NS_NewTimerWithCallback( + getter_AddRefs(timer), + [&](nsITimer*) { + for (auto& context : contexts) { + context->StopWaitingAndLeakThread(); + } + }, + aTimeoutMs, nsITimer::TYPE_ONE_SHOT, + "nsThreadPool::ShutdownWithTimeout"); + } + + // Start a counter and register a callback to decrement outstandingThreads + // when the threads finish exiting. We'll spin an event loop until + // outstandingThreads reaches 0. + uint32_t outstandingThreads = contexts.Length(); + RefPtr onCompletion = NS_NewCancelableRunnableFunction( + "nsThreadPool thread completion", [&] { --outstandingThreads; }); + for (auto& context : contexts) { + context->OnCompletion(onCompletion); + } + + mozilla::SpinEventLoopUntil("nsThreadPool::ShutdownWithTimeout"_ns, + [&] { return outstandingThreads == 0; }); + + if (timer) { + timer->Cancel(); + } + onCompletion->Cancel(); + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetThreadLimit(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mThreadLimit; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetThreadLimit(uint32_t aValue) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue)); + mThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { + mIdleThreadLimit = mThreadLimit; + } + + if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mIdleThreadLimit; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadLimit(uint32_t aValue) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue)); + mIdleThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { + mIdleThreadLimit = mThreadLimit; + } + + // Do we need to kill some idle threads? + if (mIdleCount > mIdleThreadLimit) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mIdleThreadTimeout; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadTimeout(uint32_t aValue) { + MutexAutoLock lock(mMutex); + uint32_t oldTimeout = mIdleThreadTimeout; + mIdleThreadTimeout = aValue; + + // Do we need to notify any idle threads that their sleep time has shortened? + if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mRegressiveMaxIdleTime; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadTimeoutRegressive(bool aValue) { + MutexAutoLock lock(mMutex); + bool oldRegressive = mRegressiveMaxIdleTime; + mRegressiveMaxIdleTime = aValue; + + // Would setting regressive timeout effect idle threads? + if (mRegressiveMaxIdleTime > oldRegressive && mIdleCount > 1) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetThreadStackSize(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mStackSize; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetThreadStackSize(uint32_t aValue) { + MutexAutoLock lock(mMutex); + mStackSize = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetListener(nsIThreadPoolListener** aListener) { + MutexAutoLock lock(mMutex); + NS_IF_ADDREF(*aListener = mListener); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetListener(nsIThreadPoolListener* aListener) { + nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener); + { + MutexAutoLock lock(mMutex); + mListener.swap(swappedListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetName(const nsACString& aName) { + MutexAutoLock lock(mMutex); + if (mThreads.Count()) { + return NS_ERROR_NOT_AVAILABLE; + } + mName = aName; + return NS_OK; +} diff --git a/xpcom/threads/nsThreadPool.h b/xpcom/threads/nsThreadPool.h new file mode 100644 index 0000000000..b10a2f5265 --- /dev/null +++ b/xpcom/threads/nsThreadPool.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 nsThreadPool_h__ +#define nsThreadPool_h__ + +#include "nsIThreadPool.h" +#include "nsIRunnable.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/CondVar.h" +#include "mozilla/EventQueue.h" +#include "mozilla/Mutex.h" + +class nsIThread; + +class nsThreadPool final : public mozilla::Runnable, public nsIThreadPool { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREADPOOL + NS_DECL_NSIRUNNABLE + + nsThreadPool(); + + static void InitTLS(); + static nsThreadPool* GetCurrentThreadPool(); + + private: + ~nsThreadPool(); + + void ShutdownThread(nsIThread* aThread); + nsresult PutEvent(nsIRunnable* aEvent); + nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags); + + mozilla::Mutex mMutex; + nsCOMArray<nsIThread> mThreads MOZ_GUARDED_BY(mMutex); + mozilla::CondVar mEventsAvailable MOZ_GUARDED_BY(mMutex); + mozilla::EventQueue mEvents MOZ_GUARDED_BY(mMutex); + uint32_t mThreadLimit MOZ_GUARDED_BY(mMutex); + uint32_t mIdleThreadLimit MOZ_GUARDED_BY(mMutex); + uint32_t mIdleThreadTimeout MOZ_GUARDED_BY(mMutex); + uint32_t mIdleCount MOZ_GUARDED_BY(mMutex); + nsIThread::QoSPriority mQoSPriority MOZ_GUARDED_BY(mMutex); + uint32_t mStackSize MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsIThreadPoolListener> mListener MOZ_GUARDED_BY(mMutex); + mozilla::Atomic<bool, mozilla::Relaxed> mShutdown; + bool mRegressiveMaxIdleTime MOZ_GUARDED_BY(mMutex); + mozilla::Atomic<bool, mozilla::Relaxed> mIsAPoolThreadFree; + // set once before we start threads + nsCString mName MOZ_GUARDED_BY(mMutex); + nsThreadPoolNaming mThreadNaming; // all data inside this is atomic +}; + +#define NS_THREADPOOL_CID \ + { /* 547ec2a8-315e-4ec4-888e-6e4264fe90eb */ \ + 0x547ec2a8, 0x315e, 0x4ec4, { \ + 0x88, 0x8e, 0x6e, 0x42, 0x64, 0xfe, 0x90, 0xeb \ + } \ + } + +#endif // nsThreadPool_h__ diff --git a/xpcom/threads/nsThreadSyncDispatch.h b/xpcom/threads/nsThreadSyncDispatch.h new file mode 100644 index 0000000000..1673453f9d --- /dev/null +++ b/xpcom/threads/nsThreadSyncDispatch.h @@ -0,0 +1,65 @@ +/* -*- 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 nsThreadSyncDispatch_h_ +#define nsThreadSyncDispatch_h_ + +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include "nsThreadUtils.h" +#include "LeakRefPtr.h" + +class nsThreadSyncDispatch : public mozilla::Runnable { + public: + nsThreadSyncDispatch(already_AddRefed<nsIEventTarget> aOrigin, + already_AddRefed<nsIRunnable>&& aTask) + : Runnable("nsThreadSyncDispatch"), + mOrigin(aOrigin), + mSyncTask(std::move(aTask)), + mIsPending(true) {} + + bool IsPending() { + // This is an atomic acquire on the origin thread. + return mIsPending; + } + + void SpinEventLoopUntilComplete(const nsACString& aVeryGoodReasonToDoThis) { + mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, + [&]() -> bool { return !IsPending(); }); + } + + private: + NS_IMETHOD Run() override { + if (nsCOMPtr<nsIRunnable> task = mSyncTask.take()) { + MOZ_ASSERT(!mSyncTask); + + mozilla::DebugOnly<nsresult> result = task->Run(); + MOZ_ASSERT(NS_SUCCEEDED(result), "task in sync dispatch should not fail"); + + // We must release the task here to ensure that when the original + // thread is unblocked, this task has been released. + task = nullptr; + + // This is an atomic release on the target thread. + mIsPending = false; + + // unblock the origin thread + mOrigin->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH); + } + + return NS_OK; + } + + nsCOMPtr<nsIEventTarget> mOrigin; + // The task is leaked by default when Run() is not called, because + // otherwise we may release it in an incorrect thread. + mozilla::LeakRefPtr<nsIRunnable> mSyncTask; + mozilla::Atomic<bool, mozilla::ReleaseAcquire> mIsPending; +}; + +#endif // nsThreadSyncDispatch_h_ diff --git a/xpcom/threads/nsThreadUtils.cpp b/xpcom/threads/nsThreadUtils.cpp new file mode 100644 index 0000000000..68d36605b3 --- /dev/null +++ b/xpcom/threads/nsThreadUtils.cpp @@ -0,0 +1,769 @@ +/* -*- 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 "chrome/common/ipc_message.h" // for IPC::Message +#include "LeakRefPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" +#include "nsExceptionHandler.h" +#include "nsIEventTarget.h" +#include "nsITimer.h" +#include "nsString.h" +#include "nsThreadSyncDispatch.h" +#include "nsTimerImpl.h" +#include "prsystem.h" + +#include "nsThreadManager.h" +#include "nsThreadPool.h" +#include "TaskController.h" + +#ifdef XP_WIN +# include <windows.h> +#elif defined(XP_MACOSX) +# include <sys/resource.h> +#endif + +#if defined(ANDROID) +# include <sys/prctl.h> +#endif + +static mozilla::LazyLogModule sEventDispatchAndRunLog("events"); +#ifdef LOG1 +# undef LOG1 +#endif +#define LOG1(args) \ + MOZ_LOG(sEventDispatchAndRunLog, mozilla::LogLevel::Error, args) +#define LOG1_ENABLED() \ + MOZ_LOG_TEST(sEventDispatchAndRunLog, mozilla::LogLevel::Error) + +using namespace mozilla; + +#ifndef XPCOM_GLUE_AVOID_NSPR + +NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod) + +NS_IMETHODIMP +IdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) { + *aIdleDeadline = TimeStamp(); + return NS_OK; +} + +// NS_IMPL_NAMED_* relies on the mName field, which is not present on +// release or beta. Instead, fall back to using "Runnable" for all +// runnables. +# ifndef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMPL_ISUPPORTS(Runnable, nsIRunnable) +# else +NS_IMPL_NAMED_ADDREF(Runnable, mName) +NS_IMPL_NAMED_RELEASE(Runnable, mName) +NS_IMPL_QUERY_INTERFACE(Runnable, nsIRunnable, nsINamed) +# endif + +NS_IMETHODIMP +Runnable::Run() { + // Do nothing + return NS_OK; +} + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +Runnable::GetName(nsACString& aName) { + if (mName) { + aName.AssignASCII(mName); + } else { + aName.Truncate(); + } + return NS_OK; +} +# endif + +NS_IMPL_ISUPPORTS_INHERITED(DiscardableRunnable, Runnable, + nsIDiscardableRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(CancelableRunnable, DiscardableRunnable, + nsICancelableRunnable) + +void CancelableRunnable::OnDiscard() { + // Tasks that implement Cancel() can be safely cleaned up if it turns out + // that the task will not run. + (void)NS_WARN_IF(NS_FAILED(Cancel())); +} + +NS_IMPL_ISUPPORTS_INHERITED(IdleRunnable, DiscardableRunnable, nsIIdleRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(CancelableIdleRunnable, CancelableRunnable, + nsIIdleRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(PrioritizableRunnable, Runnable, + nsIRunnablePriority) + +PrioritizableRunnable::PrioritizableRunnable( + already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aPriority) + // Real runnable name is managed by overridding the GetName function. + : Runnable("PrioritizableRunnable"), + mRunnable(std::move(aRunnable)), + mPriority(aPriority) { +# if DEBUG + nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable); + MOZ_ASSERT(!runnablePrio); +# endif +} + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +PrioritizableRunnable::GetName(nsACString& aName) { + // Try to get a name from the underlying runnable. + nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable); + if (named) { + named->GetName(aName); + } + return NS_OK; +} +# endif + +NS_IMETHODIMP +PrioritizableRunnable::Run() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + return mRunnable->Run(); +} + +NS_IMETHODIMP +PrioritizableRunnable::GetPriority(uint32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +already_AddRefed<nsIRunnable> mozilla::CreateRenderBlockingRunnable( + already_AddRefed<nsIRunnable>&& aRunnable) { + nsCOMPtr<nsIRunnable> runnable = new PrioritizableRunnable( + std::move(aRunnable), nsIRunnablePriority::PRIORITY_RENDER_BLOCKING); + return runnable.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED(PrioritizableCancelableRunnable, CancelableRunnable, + nsIRunnablePriority) + +NS_IMETHODIMP +PrioritizableCancelableRunnable::GetPriority(uint32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +#endif // XPCOM_GLUE_AVOID_NSPR + +//----------------------------------------------------------------------------- + +nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult, + nsIRunnable* aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions) { + nsCOMPtr<nsIRunnable> event = aInitialEvent; + return NS_NewNamedThread(aName, aResult, event.forget(), aOptions); +} + +nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult, + already_AddRefed<nsIRunnable> aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions) { + nsCOMPtr<nsIRunnable> event = std::move(aInitialEvent); + nsCOMPtr<nsIThread> thread; + nsresult rv = nsThreadManager::get().nsThreadManager::NewNamedThread( + aName, aOptions, getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (event) { + rv = thread->Dispatch(event.forget(), NS_DISPATCH_IGNORE_BLOCK_DISPATCH); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + *aResult = nullptr; + thread.swap(*aResult); + return NS_OK; +} + +nsresult NS_GetCurrentThread(nsIThread** aResult) { + return nsThreadManager::get().nsThreadManager::GetCurrentThread(aResult); +} + +nsresult NS_GetMainThread(nsIThread** aResult) { + return nsThreadManager::get().nsThreadManager::GetMainThread(aResult); +} + +nsresult NS_DispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent) { + nsresult rv; + nsCOMPtr<nsIRunnable> event(aEvent); + // XXX: Consider using GetCurrentSerialEventTarget() to support TaskQueues. + nsISerialEventTarget* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + return rv; +} + +// It is common to call NS_DispatchToCurrentThread with a newly +// allocated runnable with a refcount of zero. To keep us from leaking +// the runnable if the dispatch method fails, we take a death grip. +nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent) { + nsCOMPtr<nsIRunnable> event(aEvent); + return NS_DispatchToCurrentThread(event.forget()); +} + +nsresult NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aDispatchFlags) { + LeakRefPtr<nsIRunnable> event(std::move(aEvent)); + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_GetMainThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_ASSERTION(false, + "Failed NS_DispatchToMainThread() in shutdown; leaking"); + // NOTE: if you stop leaking here, adjust Promise::MaybeReportRejected(), + // which assumes a leak here, or split into leaks and no-leaks versions + return rv; + } + return thread->Dispatch(event.take(), aDispatchFlags); +} + +// In the case of failure with a newly allocated runnable with a +// refcount of zero, we intentionally leak the runnable, because it is +// likely that the runnable is being dispatched to the main thread +// because it owns main thread only objects, so it is not safe to +// release them here. +nsresult NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return NS_DispatchToMainThread(event.forget(), aDispatchFlags); +} + +nsresult NS_DelayedDispatchToCurrentThread( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs) { + nsCOMPtr<nsIRunnable> event(aEvent); + + // XXX: Consider using GetCurrentSerialEventTarget() to support TaskQueues. + nsISerialEventTarget* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } + + return thread->DelayedDispatch(event.forget(), aDelayMs); +} + +nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + nsIThread* aThread, + EventQueuePriority aQueue) { + nsresult rv; + nsCOMPtr<nsIRunnable> event(aEvent); + NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG); + if (!aThread) { + return NS_ERROR_UNEXPECTED; + } + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = aThread->DispatchToQueue(event.forget(), aQueue); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + + return rv; +} + +nsresult NS_DispatchToCurrentThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aQueue) { + return NS_DispatchToThreadQueue(std::move(aEvent), NS_GetCurrentThread(), + aQueue); +} + +extern nsresult NS_DispatchToMainThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aQueue) { + nsCOMPtr<nsIThread> mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_SUCCEEDED(rv)) { + return NS_DispatchToThreadQueue(std::move(aEvent), mainThread, aQueue); + } + return rv; +} + +class IdleRunnableWrapper final : public Runnable, + public nsIDiscardableRunnable, + public nsIIdleRunnable { + public: + explicit IdleRunnableWrapper(already_AddRefed<nsIRunnable>&& aEvent) + : Runnable("IdleRunnableWrapper"), + mRunnable(std::move(aEvent)), + mDiscardable(do_QueryInterface(mRunnable)) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Run() override { + if (!mRunnable) { + return NS_OK; + } + CancelTimer(); + // Don't clear mDiscardable because that would cause QueryInterface to + // change behavior during the lifetime of an instance. + nsCOMPtr<nsIRunnable> runnable = std::move(mRunnable); + return runnable->Run(); + } + + // nsIDiscardableRunnable + void OnDiscard() override { + if (!mRunnable) { + // Run() was already called from TimedOut(). + return; + } + mDiscardable->OnDiscard(); + mRunnable = nullptr; + } + + static void TimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleRunnableWrapper> runnable = + static_cast<IdleRunnableWrapper*>(aClosure); + LogRunnable::Run log(runnable); + runnable->Run(); + runnable = nullptr; + } + + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override { + MOZ_ASSERT(aTarget); + MOZ_ASSERT(!mTimer); + NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "IdleRunnableWrapper::SetTimer", aTarget); + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("IdleRunnableWrapper"); + if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) { + nsAutoCString name; + named->GetName(name); + if (!name.IsEmpty()) { + aName.AppendLiteral(" for "); + aName.Append(name); + } + } + return NS_OK; + } +#endif + + private: + ~IdleRunnableWrapper() { CancelTimer(); } + + void CancelTimer() { + if (mTimer) { + mTimer->Cancel(); + } + } + + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIRunnable> mRunnable; + nsCOMPtr<nsIDiscardableRunnable> mDiscardable; +}; + +NS_IMPL_ADDREF_INHERITED(IdleRunnableWrapper, Runnable) +NS_IMPL_RELEASE_INHERITED(IdleRunnableWrapper, Runnable) + +NS_INTERFACE_MAP_BEGIN(IdleRunnableWrapper) + NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDiscardableRunnable, mDiscardable) +NS_INTERFACE_MAP_END_INHERITING(Runnable) + +extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aTimeout, nsIThread* aThread, + EventQueuePriority aQueue) { + nsCOMPtr<nsIRunnable> event(std::move(aEvent)); + NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG); + MOZ_ASSERT(aQueue == EventQueuePriority::Idle || + aQueue == EventQueuePriority::DeferredTimers); + if (!aThread) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event); + + if (!idleEvent) { + idleEvent = new IdleRunnableWrapper(event.forget()); + event = do_QueryInterface(idleEvent); + MOZ_DIAGNOSTIC_ASSERT(event); + } + idleEvent->SetTimer(aTimeout, aThread); + + nsresult rv = NS_DispatchToThreadQueue(event.forget(), aThread, aQueue); + if (NS_SUCCEEDED(rv)) { + // This is intended to bind with the "DISP" log made from inside + // NS_DispatchToThreadQueue for the `event`. There is no possibly to inject + // another "DISP" for a different event on this thread. + LOG1(("TIMEOUT %u", aTimeout)); + } + + return rv; +} + +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout, + EventQueuePriority aQueue) { + return NS_DispatchToThreadQueue(std::move(aEvent), aTimeout, + NS_GetCurrentThread(), aQueue); +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +nsresult NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout) { + nsresult rv = NS_OK; + + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return NS_ERROR_UNEXPECTED; + } + } + + PRIntervalTime start = PR_IntervalNow(); + for (;;) { + bool processedEvent; + rv = aThread->ProcessNextEvent(false, &processedEvent); + if (NS_FAILED(rv) || !processedEvent) { + break; + } + if (PR_IntervalNow() - start > aTimeout) { + break; + } + } + return rv; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +inline bool hasPendingEvents(nsIThread* aThread) { + bool val; + return NS_SUCCEEDED(aThread->HasPendingEvents(&val)) && val; +} + +bool NS_HasPendingEvents(nsIThread* aThread) { + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } + } + return hasPendingEvents(aThread); +} + +bool NS_ProcessNextEvent(nsIThread* aThread, bool aMayWait) { + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } + } + bool val; + return NS_SUCCEEDED(aThread->ProcessNextEvent(aMayWait, &val)) && val; +} + +void NS_SetCurrentThreadName(const char* aName) { + PR_SetCurrentThreadName(aName); +#if defined(ANDROID) && defined(DEBUG) + // Check nspr does the right thing on Android. + char buffer[16] = {'\0'}; + prctl(PR_GET_NAME, buffer); + MOZ_ASSERT(0 == strncmp(buffer, aName, 15)); +#endif + if (nsThreadManager::get().IsNSThread()) { + nsThread* thread = nsThreadManager::get().GetCurrentThread(); + thread->SetThreadNameInternal(nsDependentCString(aName)); + } +} + +nsIThread* NS_GetCurrentThread() { + return nsThreadManager::get().GetCurrentThread(); +} + +nsIThread* NS_GetCurrentThreadNoCreate() { + if (nsThreadManager::get().IsNSThread()) { + return NS_GetCurrentThread(); + } + return nullptr; +} + +// nsThreadPoolNaming +nsCString nsThreadPoolNaming::GetNextThreadName(const nsACString& aPoolName) { + nsCString name(aPoolName); + name.AppendLiteral(" #"); + name.AppendInt(++mCounter, 10); // The counter is declared as atomic + return name; +} + +nsresult NS_DispatchBackgroundTask(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDispatchFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return nsThreadManager::get().DispatchToBackgroundThread(event, + aDispatchFlags); +} + +// nsAutoLowPriorityIO +nsAutoLowPriorityIO::nsAutoLowPriorityIO() { +#if defined(XP_WIN) + lowIOPrioritySet = + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); +#elif defined(XP_MACOSX) + oldPriority = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD); + lowIOPrioritySet = + oldPriority != -1 && + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE) != -1; +#else + lowIOPrioritySet = false; +#endif +} + +nsAutoLowPriorityIO::~nsAutoLowPriorityIO() { +#if defined(XP_WIN) + if (MOZ_LIKELY(lowIOPrioritySet)) { + // On Windows the old thread priority is automatically restored + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); + } +#elif defined(XP_MACOSX) + if (MOZ_LIKELY(lowIOPrioritySet)) { + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, oldPriority); + } +#endif +} + +namespace mozilla { + +nsISerialEventTarget* GetCurrentSerialEventTarget() { + if (nsISerialEventTarget* current = + SerialEventTargetGuard::GetCurrentSerialEventTarget()) { + return current; + } + + MOZ_DIAGNOSTIC_ASSERT(!nsThreadPool::GetCurrentThreadPool(), + "Call to GetCurrentSerialEventTarget() from thread " + "pool without an active TaskQueue"); + + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return thread; +} + +nsISerialEventTarget* GetMainThreadSerialEventTarget() { + return static_cast<nsThread*>(nsThreadManager::get().GetMainThreadWeak()); +} + +size_t GetNumberOfProcessors() { +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + static const PRInt32 procs = PR_GetNumberOfProcessors(); +#else + PRInt32 procs = PR_GetNumberOfProcessors(); +#endif + MOZ_ASSERT(procs > 0); + return static_cast<size_t>(procs); +} + +template <typename T> +void LogTaskBase<T>::LogDispatch(T* aEvent) { + LOG1(("DISP %p", aEvent)); +} +template <typename T> +void LogTaskBase<T>::LogDispatch(T* aEvent, void* aContext) { + LOG1(("DISP %p (%p)", aEvent, aContext)); +} + +template <> +void LogTaskBase<IPC::Message>::LogDispatchWithPid(IPC::Message* aEvent, + int32_t aPid) { + if (aEvent->seqno() && aPid > 0) { + LOG1(("SEND %p %d %d", aEvent, aEvent->seqno(), aPid)); + } +} + +template <typename T> +LogTaskBase<T>::Run::Run(T* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + // Logging address of this RAII so that we can use it to identify the DONE log + // while not keeping any ref to the event that could be invalid at the dtor + // time. + LOG1(("EXEC %p %p", aEvent, this)); +} +template <typename T> +LogTaskBase<T>::Run::Run(T* aEvent, void* aContext, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + LOG1(("EXEC %p (%p) %p", aEvent, aContext, this)); +} + +template <> +LogTaskBase<nsIRunnable>::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + if (!LOG1_ENABLED()) { + return; + } + + nsCOMPtr<nsINamed> named(do_QueryInterface(aEvent)); + if (!named) { + LOG1(("EXEC %p %p", aEvent, this)); + return; + } + + nsAutoCString name; + named->GetName(name); + LOG1(("EXEC %p %p [%s]", aEvent, this, name.BeginReading())); +} + +template <> +LogTaskBase<Task>::Run::Run(Task* aTask, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + if (!LOG1_ENABLED()) { + return; + } + + nsAutoCString name; + if (!aTask->GetName(name)) { + LOG1(("EXEC %p %p", aTask, this)); + return; + } + + LOG1(("EXEC %p %p [%s]", aTask, this, name.BeginReading())); +} + +template <> +LogTaskBase<IPC::Message>::Run::Run(IPC::Message* aMessage, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + LOG1(("RECV %p %p %d [%s]", aMessage, this, aMessage->seqno(), + aMessage->name())); +} + +template <> +LogTaskBase<nsTimerImpl>::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + // The name of the timer will be logged when running it on the target thread. + // Logging it here (on the `Timer` thread) would be redundant. + LOG1(("EXEC %p %p [nsTimerImpl]", aEvent, this)); +} + +template <typename T> +LogTaskBase<T>::Run::~Run() { + LOG1((mWillRunAgain ? "INTERRUPTED %p" : "DONE %p", this)); +} + +template class LogTaskBase<nsIRunnable>; +template class LogTaskBase<MicroTaskRunnable>; +template class LogTaskBase<IPC::Message>; +template class LogTaskBase<nsTimerImpl>; +template class LogTaskBase<Task>; +template class LogTaskBase<PresShell>; +template class LogTaskBase<dom::FrameRequestCallback>; + +MOZ_THREAD_LOCAL(nsISerialEventTarget*) +SerialEventTargetGuard::sCurrentThreadTLS; +void SerialEventTargetGuard::InitTLS() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } +} + +} // namespace mozilla + +bool nsIEventTarget::IsOnCurrentThread() { + if (mThread) { + return mThread == PR_GetCurrentThread(); + } + return IsOnCurrentThreadInfallible(); +} + +extern "C" { +// These functions use the C language linkage because they're exposed to Rust +// via the xpcom/rust/moz_task crate, which wraps them in safe Rust functions +// that enable Rust code to get/create threads and dispatch runnables on them. + +nsresult NS_GetCurrentThreadRust(nsIThread** aResult) { + return NS_GetCurrentThread(aResult); +} + +nsresult NS_GetMainThreadRust(nsIThread** aResult) { + return NS_GetMainThread(aResult); +} + +// NS_NewNamedThread's aStackSize parameter has the default argument +// nsIThreadManager::DEFAULT_STACK_SIZE, but we can't omit default arguments +// when calling a C++ function from Rust, and we can't access +// nsIThreadManager::DEFAULT_STACK_SIZE in Rust to pass it explicitly, +// since it is defined in a %{C++ ... %} block within nsIThreadManager.idl. +// So we indirect through this function. +nsresult NS_NewNamedThreadWithDefaultStackSize(const nsACString& aName, + nsIThread** aResult, + nsIRunnable* aEvent) { + return NS_NewNamedThread(aName, aResult, aEvent); +} + +bool NS_IsOnCurrentThread(nsIEventTarget* aTarget) { + return aTarget->IsOnCurrentThread(); +} + +nsresult NS_DispatchBackgroundTask(nsIRunnable* aEvent, + uint32_t aDispatchFlags) { + return nsThreadManager::get().DispatchToBackgroundThread(aEvent, + aDispatchFlags); +} + +nsresult NS_CreateBackgroundTaskQueue(const char* aName, + nsISerialEventTarget** aTarget) { + nsCOMPtr<nsISerialEventTarget> target = + nsThreadManager::get().CreateBackgroundTaskQueue(aName); + if (!target) { + return NS_ERROR_FAILURE; + } + + target.forget(aTarget); + return NS_OK; +} + +} // extern "C" + +nsresult NS_DispatchAndSpinEventLoopUntilComplete( + const nsACString& aVeryGoodReasonToDoThis, nsIEventTarget* aEventTarget, + already_AddRefed<nsIRunnable> aEvent) { + // NOTE: Get the current thread specifically, as `SpinEventLoopUntil` can + // only spin that event target's loop. The reply will specify + // NS_DISPATCH_IGNORE_BLOCK_DISPATCH to ensure the reply is received even if + // the caller is a threadpool thread. + nsCOMPtr<nsIThread> current = NS_GetCurrentThread(); + if (NS_WARN_IF(!current)) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<nsThreadSyncDispatch> wrapper = + new nsThreadSyncDispatch(current.forget(), std::move(aEvent)); + nsresult rv = aEventTarget->Dispatch(do_AddRef(wrapper)); + if (NS_WARN_IF(NS_FAILED(rv))) { + // FIXME: Consider avoiding leaking the `nsThreadSyncDispatch` as well by + // using a fallible version of `Dispatch` once that is added. + return rv; + } + + wrapper->SpinEventLoopUntilComplete(aVeryGoodReasonToDoThis); + return NS_OK; +} diff --git a/xpcom/threads/nsThreadUtils.h b/xpcom/threads/nsThreadUtils.h new file mode 100644 index 0000000000..72041da295 --- /dev/null +++ b/xpcom/threads/nsThreadUtils.h @@ -0,0 +1,1925 @@ +/* -*- 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 nsThreadUtils_h__ +#define nsThreadUtils_h__ + +#include <type_traits> +#include <tuple> +#include <utility> + +#include "MainThreadUtils.h" +#include "mozilla/EventQueue.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/TimeStamp.h" + +#include "nsCOMPtr.h" +#include "nsICancelableRunnable.h" +#include "nsIDiscardableRunnable.h" +#include "nsIIdlePeriod.h" +#include "nsIIdleRunnable.h" +#include "nsINamed.h" +#include "nsIRunnable.h" +#include "nsIThreadManager.h" +#include "nsITimer.h" +#include "nsString.h" +#include "prinrval.h" +#include "prthread.h" + +class MessageLoop; +class nsIThread; + +//----------------------------------------------------------------------------- +// These methods are alternatives to the methods on nsIThreadManager, provided +// for convenience. + +/** + * Create a new thread, and optionally provide an initial event for the thread. + * + * @param aName + * The name of the thread. + * @param aResult + * The resulting nsIThread object. + * @param aInitialEvent + * The initial event to run on this thread. This parameter may be null. + * @param aOptions + * Options used to configure thread creation. + * Options are documented in nsIThreadManager.idl. + * + * @returns NS_ERROR_INVALID_ARG + * Indicates that the given name is not unique. + */ + +extern nsresult NS_NewNamedThread( + const nsACString& aName, nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + nsIThreadManager::ThreadCreationOptions aOptions = {}); + +extern nsresult NS_NewNamedThread( + const nsACString& aName, nsIThread** aResult, + already_AddRefed<nsIRunnable> aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions = {}); + +template <size_t LEN> +inline nsresult NS_NewNamedThread( + const char (&aName)[LEN], nsIThread** aResult, + already_AddRefed<nsIRunnable> aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions = {}) { + static_assert(LEN <= 16, "Thread name must be no more than 16 characters"); + return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult, + std::move(aInitialEvent), aOptions); +} + +template <size_t LEN> +inline nsresult NS_NewNamedThread( + const char (&aName)[LEN], nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + nsIThreadManager::ThreadCreationOptions aOptions = {}) { + nsCOMPtr<nsIRunnable> event = aInitialEvent; + static_assert(LEN <= 16, "Thread name must be no more than 16 characters"); + return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult, + event.forget(), aOptions); +} + +/** + * Get a reference to the current thread, creating it if it does not exist yet. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetCurrentThread(nsIThread** aResult); + +/** + * Dispatch the given event to the current thread. + * + * @param aEvent + * The event to dispatch. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent); +extern nsresult NS_DispatchToCurrentThread( + already_AddRefed<nsIRunnable>&& aEvent); + +/** + * Dispatch the given event to the main thread. + * + * @param aEvent + * The event to dispatch. + * @param aDispatchFlags + * The flags to pass to the main thread's dispatch method. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult NS_DispatchToMainThread( + nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); +extern nsresult NS_DispatchToMainThread( + already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + +extern nsresult NS_DelayedDispatchToCurrentThread( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs); + +/** + * Dispatch the given event to the specified queue of the current thread. + * + * @param aEvent The event to dispatch. + * @param aQueue The event queue for the thread to use + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to the specified queue of the main thread. + * + * @param aEvent The event to dispatch. + * @param aQueue The event queue for the thread to use + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToMainThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to an idle queue of the current thread. + * + * @param aEvent The event to dispatch. If the event implements + * nsIIdleRunnable, it will receive a call on + * nsIIdleRunnable::SetTimer when dispatched, with the value of + * aTimeout. + * + * @param aTimeout The time in milliseconds until the event should be + * moved from an idle queue to the regular queue, if it hasn't been + * executed. If aEvent is also an nsIIdleRunnable, it is expected + * that it should handle the timeout itself, after a call to + * nsIIdleRunnable::SetTimer. + * + * @param aQueue + * The event queue for the thread to use. Must be an idle queue + * (Idle or DeferredTimers) + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout, + mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to a queue of a thread. + * + * @param aEvent The event to dispatch. + * @param aThread The target thread for the dispatch. + * @param aQueue The event queue for the thread to use. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + nsIThread* aThread, + mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to an idle queue of a thread. + * + * @param aEvent The event to dispatch. If the event implements + * nsIIdleRunnable, it will receive a call on + * nsIIdleRunnable::SetTimer when dispatched, with the value of + * aTimeout. + * + * @param aTimeout The time in milliseconds until the event should be + * moved from an idle queue to the regular queue, if it hasn't been + * executed. If aEvent is also an nsIIdleRunnable, it is expected + * that it should handle the timeout itself, after a call to + * nsIIdleRunnable::SetTimer. + * + * @param aThread The target thread for the dispatch. + * + * @param aQueue + * The event queue for the thread to use. Must be an idle queue + * (Idle or DeferredTimers) + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aTimeout, nsIThread* aThread, + mozilla::EventQueuePriority aQueue); + +#ifndef XPCOM_GLUE_AVOID_NSPR +/** + * Process all pending events for the given thread before returning. This + * method simply calls ProcessNextEvent on the thread while HasPendingEvents + * continues to return true and the time spent in NS_ProcessPendingEvents + * does not exceed the given timeout value. + * + * @param aThread + * The thread object for which to process pending events. If null, then + * events will be processed for the current thread. + * @param aTimeout + * The maximum number of milliseconds to spend processing pending events. + * Events are not pre-empted to honor this timeout. Rather, the timeout + * value is simply used to determine whether or not to process another event. + * Pass PR_INTERVAL_NO_TIMEOUT to specify no timeout. + */ +extern nsresult NS_ProcessPendingEvents( + nsIThread* aThread, PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT); +#endif + +/** + * Shortcut for nsIThread::HasPendingEvents. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will return false if called from some + * other thread. + * + * @param aThread + * The current thread or null. + * + * @returns + * A boolean value that if "true" indicates that there are pending events + * in the current thread's event queue. + */ +extern bool NS_HasPendingEvents(nsIThread* aThread = nullptr); + +/** + * Shortcut for nsIThread::ProcessNextEvent. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will simply return false if called + * from some other thread. + * + * @param aThread + * The current thread or null. + * @param aMayWait + * A boolean parameter that if "true" indicates that the method may block + * the calling thread to wait for a pending event. + * + * @returns + * A boolean value that if "true" indicates that an event from the current + * thread's event queue was processed. + */ +extern bool NS_ProcessNextEvent(nsIThread* aThread = nullptr, + bool aMayWait = true); + +/** + * Returns true if we're in the compositor thread. + * + * We declare this here because the headers required to invoke + * CompositorThreadHolder::IsInCompositorThread() also pull in a bunch of system + * headers that #define various tokens in a way that can break the build. + */ +extern bool NS_IsInCompositorThread(); + +extern bool NS_IsInCanvasThreadOrWorker(); + +extern bool NS_IsInVRThread(); + +//----------------------------------------------------------------------------- +// Helpers that work with nsCOMPtr: + +inline already_AddRefed<nsIThread> do_GetCurrentThread() { + nsIThread* thread = nullptr; + NS_GetCurrentThread(&thread); + return already_AddRefed<nsIThread>(thread); +} + +inline already_AddRefed<nsIThread> do_GetMainThread() { + nsIThread* thread = nullptr; + NS_GetMainThread(&thread); + return already_AddRefed<nsIThread>(thread); +} + +//----------------------------------------------------------------------------- + +// Fast access to the current thread. Will create an nsIThread if one does not +// exist already! Do not release the returned pointer! If you want to use this +// pointer from some other thread, then you will need to AddRef it. Otherwise, +// you should only consider this pointer valid from code running on the current +// thread. +extern nsIThread* NS_GetCurrentThread(); + +// Exactly the same as NS_GetCurrentThread, except it will not create an +// nsThread if one does not exist yet. This is useful in cases where you have +// code that runs on threads that may or may not not be driven by an nsThread +// event loop, and wish to avoid inadvertently creating a superfluous nsThread. +extern nsIThread* NS_GetCurrentThreadNoCreate(); + +/** + * Set the name of the current thread. Prefer this function over + * PR_SetCurrentThreadName() if possible. The name will also be included in the + * crash report. + * + * @param aName + * Name of the thread. A C language null-terminated string. + */ +extern void NS_SetCurrentThreadName(const char* aName); + +//----------------------------------------------------------------------------- + +#ifndef XPCOM_GLUE_AVOID_NSPR + +namespace mozilla { + +// This class is designed to be subclassed. +class IdlePeriod : public nsIIdlePeriod { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIDLEPERIOD + + IdlePeriod() = default; + + protected: + virtual ~IdlePeriod() = default; + + private: + IdlePeriod(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&&) = delete; +}; + +// Cancelable runnable methods implement nsICancelableRunnable, and +// Idle and IdleWithTimer also nsIIdleRunnable. +enum class RunnableKind { Standard, Cancelable, Idle, IdleWithTimer }; + +// Implementing nsINamed on Runnable bloats vtables for the hundreds of +// Runnable subclasses that we have, so we want to avoid that overhead +// when we're not using nsINamed for anything. +# ifndef RELEASE_OR_BETA +# define MOZ_COLLECTING_RUNNABLE_TELEMETRY +# endif + +// This class is designed to be subclassed. +class Runnable : public nsIRunnable +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + , + public nsINamed +# endif +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_DECL_NSINAMED +# endif + + Runnable() = delete; + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + explicit Runnable(const char* aName) : mName(aName) {} +# else + explicit Runnable(const char* aName) {} +# endif + + protected: + virtual ~Runnable() = default; + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + const char* mName = nullptr; +# endif + + private: + Runnable(const Runnable&) = delete; + Runnable& operator=(const Runnable&) = delete; + Runnable& operator=(const Runnable&&) = delete; +}; + +// This is a base class for tasks that might not be run, such as those that may +// be dispatched to workers. +// The owner of an event target will call either Run() or OnDiscard() +// exactly once. +// Derived classes should override Run(). An OnDiscard() override may +// provide cleanup when Run() will not be called. +class DiscardableRunnable : public Runnable, public nsIDiscardableRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + // nsIDiscardableRunnable + void OnDiscard() override {} + + DiscardableRunnable() = delete; + explicit DiscardableRunnable(const char* aName) : Runnable(aName) {} + + protected: + virtual ~DiscardableRunnable() = default; + + private: + DiscardableRunnable(const DiscardableRunnable&) = delete; + DiscardableRunnable& operator=(const DiscardableRunnable&) = delete; + DiscardableRunnable& operator=(const DiscardableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +// Derived classes should override Run() and Cancel() to provide that +// calling Run() after Cancel() is a no-op. +class CancelableRunnable : public DiscardableRunnable, + public nsICancelableRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + // nsIDiscardableRunnable + void OnDiscard() override; + // nsICancelableRunnable + virtual nsresult Cancel() override = 0; + + CancelableRunnable() = delete; + explicit CancelableRunnable(const char* aName) : DiscardableRunnable(aName) {} + + protected: + virtual ~CancelableRunnable() = default; + + private: + CancelableRunnable(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class IdleRunnable : public DiscardableRunnable, public nsIIdleRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + + explicit IdleRunnable(const char* aName) : DiscardableRunnable(aName) {} + + protected: + virtual ~IdleRunnable() = default; + + private: + IdleRunnable(const IdleRunnable&) = delete; + IdleRunnable& operator=(const IdleRunnable&) = delete; + IdleRunnable& operator=(const IdleRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class CancelableIdleRunnable : public CancelableRunnable, + public nsIIdleRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + + CancelableIdleRunnable() : CancelableRunnable("CancelableIdleRunnable") {} + explicit CancelableIdleRunnable(const char* aName) + : CancelableRunnable(aName) {} + + protected: + virtual ~CancelableIdleRunnable() = default; + + private: + CancelableIdleRunnable(const CancelableIdleRunnable&) = delete; + CancelableIdleRunnable& operator=(const CancelableIdleRunnable&) = delete; + CancelableIdleRunnable& operator=(const CancelableIdleRunnable&&) = delete; +}; + +// This class is designed to be a wrapper of a real runnable to support event +// prioritizable. +class PrioritizableRunnable : public Runnable, public nsIRunnablePriority { + public: + PrioritizableRunnable(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aPriority); + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +# endif + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIRUNNABLEPRIORITY + + protected: + virtual ~PrioritizableRunnable() = default; + + nsCOMPtr<nsIRunnable> mRunnable; + uint32_t mPriority; +}; + +class PrioritizableCancelableRunnable : public CancelableRunnable, + public nsIRunnablePriority { + public: + PrioritizableCancelableRunnable(uint32_t aPriority, const char* aName) + : CancelableRunnable(aName), mPriority(aPriority) {} + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLEPRIORITY + + protected: + virtual ~PrioritizableCancelableRunnable() = default; + + const uint32_t mPriority; +}; + +extern already_AddRefed<nsIRunnable> CreateRenderBlockingRunnable( + already_AddRefed<nsIRunnable>&& aRunnable); + +namespace detail { + +// An event that can be used to call a C++11 functions or function objects, +// including lambdas. The function must have no required arguments, and must +// return void. +template <typename StoredFunction> +class RunnableFunction : public Runnable { + public: + template <typename F> + explicit RunnableFunction(const char* aName, F&& aFunction) + : Runnable(aName), mFunction(std::forward<F>(aFunction)) {} + + NS_IMETHOD Run() override { + static_assert(std::is_void_v<decltype(mFunction())>, + "The lambda must return void!"); + mFunction(); + return NS_OK; + } + + private: + StoredFunction mFunction; +}; + +// Type alias for NS_NewRunnableFunction +template <typename Function> +using RunnableFunctionImpl = + // Make sure we store a non-reference in nsRunnableFunction. + typename detail::RunnableFunction<std::remove_reference_t<Function>>; +} // namespace detail + +namespace detail { + +template <typename CVRemoved> +struct IsRefcountedSmartPointerHelper : std::false_type {}; + +template <typename Pointee> +struct IsRefcountedSmartPointerHelper<RefPtr<Pointee>> : std::true_type {}; + +template <typename Pointee> +struct IsRefcountedSmartPointerHelper<nsCOMPtr<Pointee>> : std::true_type {}; + +} // namespace detail + +template <typename T> +struct IsRefcountedSmartPointer + : detail::IsRefcountedSmartPointerHelper<std::remove_cv_t<T>> {}; + +namespace detail { + +template <typename T, typename CVRemoved> +struct RemoveSmartPointerHelper { + typedef T Type; +}; + +template <typename T, typename Pointee> +struct RemoveSmartPointerHelper<T, RefPtr<Pointee>> { + typedef Pointee Type; +}; + +template <typename T, typename Pointee> +struct RemoveSmartPointerHelper<T, nsCOMPtr<Pointee>> { + typedef Pointee Type; +}; + +} // namespace detail + +template <typename T> +struct RemoveSmartPointer + : detail::RemoveSmartPointerHelper<T, std::remove_cv_t<T>> {}; + +namespace detail { + +template <typename T, typename CVRemoved> +struct RemoveRawOrSmartPointerHelper { + typedef T Type; +}; + +template <typename T, typename Pointee> +struct RemoveRawOrSmartPointerHelper<T, Pointee*> { + typedef Pointee Type; +}; + +template <typename T, typename Pointee> +struct RemoveRawOrSmartPointerHelper<T, RefPtr<Pointee>> { + typedef Pointee Type; +}; + +template <typename T, typename Pointee> +struct RemoveRawOrSmartPointerHelper<T, nsCOMPtr<Pointee>> { + typedef Pointee Type; +}; + +} // namespace detail + +template <typename T> +struct RemoveRawOrSmartPointer + : detail::RemoveRawOrSmartPointerHelper<T, std::remove_cv_t<T>> {}; + +} // namespace mozilla + +inline nsISupports* ToSupports(mozilla::Runnable* p) { + return static_cast<nsIRunnable*>(p); +} + +template <typename Function> +already_AddRefed<mozilla::Runnable> NS_NewRunnableFunction( + const char* aName, Function&& aFunction) { + // We store a non-reference in RunnableFunction, but still forward aFunction + // to move if possible. + return do_AddRef(new mozilla::detail::RunnableFunctionImpl<Function>( + aName, std::forward<Function>(aFunction))); +} + +// Creates a new object implementing nsIRunnable and nsICancelableRunnable, +// which runs a given function on Run and clears the stored function object on a +// call to `Cancel` (and thus destroys all objects it holds). +template <typename Function> +already_AddRefed<mozilla::CancelableRunnable> NS_NewCancelableRunnableFunction( + const char* aName, Function&& aFunc) { + class FuncCancelableRunnable final : public mozilla::CancelableRunnable { + public: + static_assert( + std::is_void_v< + decltype(std::declval<std::remove_reference_t<Function>>()())>); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(FuncCancelableRunnable, + CancelableRunnable) + + explicit FuncCancelableRunnable(const char* aName, Function&& aFunc) + : CancelableRunnable{aName}, + mFunc{mozilla::Some(std::forward<Function>(aFunc))} {} + + NS_IMETHOD Run() override { + if (mFunc) { + (*mFunc)(); + } + + return NS_OK; + } + + nsresult Cancel() override { + mFunc.reset(); + return NS_OK; + } + + private: + ~FuncCancelableRunnable() = default; + + mozilla::Maybe<std::remove_reference_t<Function>> mFunc; + }; + + return mozilla::MakeAndAddRef<FuncCancelableRunnable>( + aName, std::forward<Function>(aFunc)); +} + +namespace mozilla { +namespace detail { + +template <RunnableKind Kind> +class TimerBehaviour { + public: + nsITimer* GetTimer() { return nullptr; } + void CancelTimer() {} + + protected: + ~TimerBehaviour() = default; +}; + +template <> +class TimerBehaviour<RunnableKind::IdleWithTimer> { + public: + nsITimer* GetTimer() { + if (!mTimer) { + mTimer = NS_NewTimer(); + } + + return mTimer; + } + + void CancelTimer() { + if (mTimer) { + mTimer->Cancel(); + } + } + + protected: + ~TimerBehaviour() { CancelTimer(); } + + private: + nsCOMPtr<nsITimer> mTimer; +}; + +} // namespace detail +} // namespace mozilla + +// An event that can be used to call a method on a class. The class type must +// support reference counting. This event supports Revoke for use +// with nsRevocableEventPtr. +template <class ClassType, typename ReturnType = void, bool Owning = true, + mozilla::RunnableKind Kind = mozilla::RunnableKind::Standard> +class nsRunnableMethod + : public std::conditional_t< + Kind == mozilla::RunnableKind::Standard, mozilla::Runnable, + std::conditional_t<Kind == mozilla::RunnableKind::Cancelable, + mozilla::CancelableRunnable, + mozilla::CancelableIdleRunnable>>, + protected mozilla::detail::TimerBehaviour<Kind> { + using BaseType = std::conditional_t< + Kind == mozilla::RunnableKind::Standard, mozilla::Runnable, + std::conditional_t<Kind == mozilla::RunnableKind::Cancelable, + mozilla::CancelableRunnable, + mozilla::CancelableIdleRunnable>>; + + public: + nsRunnableMethod(const char* aName) : BaseType(aName) {} + + virtual void Revoke() = 0; + + // These ReturnTypeEnforcer classes disallow return types that + // we know are not safe. The default ReturnTypeEnforcer compiles just fine but + // already_AddRefed will not. + template <typename OtherReturnType> + class ReturnTypeEnforcer { + public: + typedef int ReturnTypeIsSafe; + }; + + template <class T> + class ReturnTypeEnforcer<already_AddRefed<T>> { + // No ReturnTypeIsSafe makes this illegal! + }; + + // Make sure this return type is safe. + typedef typename ReturnTypeEnforcer<ReturnType>::ReturnTypeIsSafe check; +}; + +template <class ClassType, bool Owning> +struct nsRunnableMethodReceiver { + RefPtr<ClassType> mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + explicit nsRunnableMethodReceiver(RefPtr<ClassType>&& aObj) + : mObj(std::move(aObj)) {} + ~nsRunnableMethodReceiver() { Revoke(); } + ClassType* Get() const { return mObj.get(); } + void Revoke() { mObj = nullptr; } +}; + +template <class ClassType> +struct nsRunnableMethodReceiver<ClassType, false> { + ClassType* MOZ_NON_OWNING_REF mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + ClassType* Get() const { return mObj; } + void Revoke() { mObj = nullptr; } +}; + +static inline constexpr bool IsIdle(mozilla::RunnableKind aKind) { + return aKind == mozilla::RunnableKind::Idle || + aKind == mozilla::RunnableKind::IdleWithTimer; +} + +template <typename PtrType, typename Method, bool Owning, + mozilla::RunnableKind Kind> +struct nsRunnableMethodTraits; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (C::*)(As...), Owning, Kind> { + typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (C::*)(As...) const, Owning, Kind> { + typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type + class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +# ifdef NS_HAVE_STDCALL +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (__stdcall C::*)(As...), Owning, + Kind> { + typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind> +struct nsRunnableMethodTraits<PtrType, R (NS_STDCALL C::*)(), Owning, Kind> { + typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (__stdcall C::*)(As...) const, Owning, + Kind> { + typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type + class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind> +struct nsRunnableMethodTraits<PtrType, R (NS_STDCALL C::*)() const, Owning, + Kind> { + typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type + class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; +# endif + +// IsParameterStorageClass<T>::value is true if T is a parameter-storage class +// that will be recognized by NS_New[NonOwning]RunnableMethodWithArg[s] to +// force a specific storage&passing strategy (instead of inferring one, +// see ParameterStorage). +// When creating a new storage class, add a specialization for it to be +// recognized. +template <typename T> +struct IsParameterStorageClass : public std::false_type {}; + +// StoreXPassByY structs used to inform nsRunnableMethodArguments how to +// store arguments, and how to pass them to the target method. + +template <typename T> +struct StoreCopyPassByValue { + using stored_type = std::decay_t<T>; + typedef stored_type passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByValue(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByValue<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByConstLRef { + using stored_type = std::decay_t<T>; + typedef const stored_type& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByConstLRef(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByConstLRef<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByLRef { + using stored_type = std::decay_t<T>; + typedef stored_type& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByLRef(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByLRef<S>> : public std::true_type { +}; + +template <typename T> +struct StoreCopyPassByRRef { + using stored_type = std::decay_t<T>; + typedef stored_type&& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByRRef(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return std::move(m); } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByRRef<S>> : public std::true_type { +}; + +template <typename T> +struct StoreRefPassByLRef { + typedef T& stored_type; + typedef T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreRefPassByLRef(A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreRefPassByLRef<S>> : public std::true_type { +}; + +template <typename T> +struct StoreConstRefPassByConstLRef { + typedef const T& stored_type; + typedef const T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreConstRefPassByConstLRef(const A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreConstRefPassByConstLRef<S>> + : public std::true_type {}; + +template <typename T> +struct StoreRefPtrPassByPtr { + typedef RefPtr<T> stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreRefPtrPassByPtr(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m.get(); } +}; +template <typename S> +struct IsParameterStorageClass<StoreRefPtrPassByPtr<S>> + : public std::true_type {}; + +template <typename T> +struct StorePtrPassByPtr { + typedef T* stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StorePtrPassByPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StorePtrPassByPtr<S>> : public std::true_type {}; + +template <typename T> +struct StoreConstPtrPassByConstPtr { + typedef const T* stored_type; + typedef const T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreConstPtrPassByConstPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreConstPtrPassByConstPtr<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByConstPtr { + typedef T stored_type; + typedef const T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByConstPtr(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByConstPtr<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByPtr { + typedef T stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByPtr(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByPtr<S>> : public std::true_type { +}; + +namespace detail { + +template <typename> +struct SFINAE1True : std::true_type {}; + +template <class T> +static auto HasRefCountMethodsTest(int) + -> SFINAE1True<decltype(std::declval<T>().AddRef(), + std::declval<T>().Release())>; +template <class> +static auto HasRefCountMethodsTest(long) -> std::false_type; + +template <class T> +struct HasRefCountMethods : decltype(HasRefCountMethodsTest<T>(0)) {}; + +template <typename TWithoutPointer> +struct NonnsISupportsPointerStorageClass + : std::conditional< + std::is_const_v<TWithoutPointer>, + StoreConstPtrPassByConstPtr<std::remove_const_t<TWithoutPointer>>, + StorePtrPassByPtr<TWithoutPointer>> { + using Type = typename NonnsISupportsPointerStorageClass::conditional::type; +}; + +template <typename TWithoutPointer> +struct PointerStorageClass + : std::conditional< + HasRefCountMethods<TWithoutPointer>::value, + StoreRefPtrPassByPtr<TWithoutPointer>, + typename NonnsISupportsPointerStorageClass<TWithoutPointer>::Type> { + using Type = typename PointerStorageClass::conditional::type; +}; + +template <typename TWithoutRef> +struct LValueReferenceStorageClass + : std::conditional< + std::is_const_v<TWithoutRef>, + StoreConstRefPassByConstLRef<std::remove_const_t<TWithoutRef>>, + StoreRefPassByLRef<TWithoutRef>> { + using Type = typename LValueReferenceStorageClass::conditional::type; +}; + +template <typename T> +struct SmartPointerStorageClass + : std::conditional< + mozilla::IsRefcountedSmartPointer<T>::value, + StoreRefPtrPassByPtr<typename mozilla::RemoveSmartPointer<T>::Type>, + StoreCopyPassByConstLRef<T>> { + using Type = typename SmartPointerStorageClass::conditional::type; +}; + +template <typename T> +struct NonLValueReferenceStorageClass + : std::conditional<std::is_rvalue_reference_v<T>, + StoreCopyPassByRRef<std::remove_reference_t<T>>, + typename SmartPointerStorageClass<T>::Type> { + using Type = typename NonLValueReferenceStorageClass::conditional::type; +}; + +template <typename T> +struct NonPointerStorageClass + : std::conditional<std::is_lvalue_reference_v<T>, + typename LValueReferenceStorageClass< + std::remove_reference_t<T>>::Type, + typename NonLValueReferenceStorageClass<T>::Type> { + using Type = typename NonPointerStorageClass::conditional::type; +}; + +template <typename T> +struct NonParameterStorageClass + : std::conditional< + std::is_pointer_v<T>, + typename PointerStorageClass<std::remove_pointer_t<T>>::Type, + typename NonPointerStorageClass<T>::Type> { + using Type = typename NonParameterStorageClass::conditional::type; +}; + +// Choose storage&passing strategy based on preferred storage type: +// - If IsParameterStorageClass<T>::value is true, use as-is. +// - RC* -> StoreRefPtrPassByPtr<RC> :Store RefPtr<RC>, pass RC* +// ^^ RC quacks like a ref-counted type (i.e., has AddRef and Release methods) +// - const T* -> StoreConstPtrPassByConstPtr<T> :Store const T*, pass const T* +// - T* -> StorePtrPassByPtr<T> :Store T*, pass T*. +// - const T& -> StoreConstRefPassByConstLRef<T>:Store const T&, pass const T&. +// - T& -> StoreRefPassByLRef<T> :Store T&, pass T&. +// - T&& -> StoreCopyPassByRRef<T> :Store T, pass std::move(T). +// - RefPtr<T>, nsCOMPtr<T> +// -> StoreRefPtrPassByPtr<T> :Store RefPtr<T>, pass T* +// - Other T -> StoreCopyPassByConstLRef<T> :Store T, pass const T&. +// Other available explicit options: +// - StoreCopyPassByValue<T> :Store T, pass T. +// - StoreCopyPassByLRef<T> :Store T, pass T& (of copy!) +// - StoreCopyPassByConstPtr<T> :Store T, pass const T* +// - StoreCopyPassByPtr<T> :Store T, pass T* (of copy!) +// Or create your own class with PassAsParameter() method, optional +// clean-up in destructor, and with associated IsParameterStorageClass<>. +template <typename T> +struct ParameterStorage + : std::conditional<IsParameterStorageClass<T>::value, T, + typename NonParameterStorageClass<T>::Type> { + using Type = typename ParameterStorage::conditional::type; +}; + +template <class T> +static auto HasSetDeadlineTest(int) + -> SFINAE1True<decltype(std::declval<T>().SetDeadline( + std::declval<mozilla::TimeStamp>()))>; + +template <class T> +static auto HasSetDeadlineTest(long) -> std::false_type; + +template <class T> +struct HasSetDeadline : decltype(HasSetDeadlineTest<T>(0)) {}; + +template <class T> +std::enable_if_t<::detail::HasSetDeadline<T>::value> SetDeadlineImpl( + T* aObj, mozilla::TimeStamp aTimeStamp) { + aObj->SetDeadline(aTimeStamp); +} + +template <class T> +std::enable_if_t<!::detail::HasSetDeadline<T>::value> SetDeadlineImpl( + T* aObj, mozilla::TimeStamp aTimeStamp) {} +} /* namespace detail */ + +namespace mozilla { +namespace detail { + +// struct used to store arguments and later apply them to a method. +template <typename... Ts> +struct RunnableMethodArguments final { + std::tuple<typename ::detail::ParameterStorage<Ts>::Type...> mArguments; + template <typename... As> + explicit RunnableMethodArguments(As&&... aArguments) + : mArguments(std::forward<As>(aArguments)...) {} + template <class C, typename M> + decltype(auto) apply(C* o, M m) { + return std::apply( + [&o, m](auto&&... args) { + return ((*o).*m)(args.PassAsParameter()...); + }, + mArguments); + } +}; + +template <typename PtrType, typename Method, bool Owning, RunnableKind Kind, + typename... Storages> +class RunnableMethodImpl final + : public ::nsRunnableMethodTraits<PtrType, Method, Owning, + Kind>::base_type { + typedef typename ::nsRunnableMethodTraits<PtrType, Method, Owning, Kind> + Traits; + + typedef typename Traits::class_type ClassType; + typedef typename Traits::base_type BaseType; + ::nsRunnableMethodReceiver<ClassType, Owning> mReceiver; + Method mMethod; + RunnableMethodArguments<Storages...> mArgs; + using BaseType::CancelTimer; + using BaseType::GetTimer; + + private: + virtual ~RunnableMethodImpl() { Revoke(); }; + static void TimedOut(nsITimer* aTimer, void* aClosure) { + static_assert(IsIdle(Kind), "Don't use me!"); + RefPtr<CancelableIdleRunnable> r = + static_cast<CancelableIdleRunnable*>(aClosure); + r->SetDeadline(TimeStamp()); + r->Run(); + r->Cancel(); + } + + public: + template <typename ForwardedPtrType, typename... Args> + explicit RunnableMethodImpl(const char* aName, ForwardedPtrType&& aObj, + Method aMethod, Args&&... aArgs) + : BaseType(aName), + mReceiver(std::forward<ForwardedPtrType>(aObj)), + mMethod(aMethod), + mArgs(std::forward<Args>(aArgs)...) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "Storages and Args should have equal sizes"); + } + + NS_IMETHOD Run() { + CancelTimer(); + + if (MOZ_LIKELY(mReceiver.Get())) { + mArgs.apply(mReceiver.Get(), mMethod); + } + + return NS_OK; + } + + nsresult Cancel() { + static_assert(Kind >= RunnableKind::Cancelable, "Don't use me!"); + Revoke(); + return NS_OK; + } + + void Revoke() { + CancelTimer(); + mReceiver.Revoke(); + } + + void SetDeadline(TimeStamp aDeadline) { + if (MOZ_LIKELY(mReceiver.Get())) { + ::detail::SetDeadlineImpl(mReceiver.Get(), aDeadline); + } + } + + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT(aTarget); + + if (nsCOMPtr<nsITimer> timer = GetTimer()) { + timer->Cancel(); + timer->SetTarget(aTarget); + timer->InitWithNamedFuncCallback(TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "detail::RunnableMethodImpl::SetTimer"); + } + } +}; + +// Type aliases for NewRunnableMethod. +template <typename PtrType, typename Method> +using OwningRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, RunnableKind::Standard>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using OwningRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::Standard, Storages...>; + +// Type aliases for NewCancelableRunnableMethod. +template <typename PtrType, typename Method> +using CancelableRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, + RunnableKind::Cancelable>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using CancelableRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::Cancelable, Storages...>; + +// Type aliases for NewIdleRunnableMethod. +template <typename PtrType, typename Method> +using IdleRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, RunnableKind::Idle>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using IdleRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::Idle, Storages...>; + +// Type aliases for NewIdleRunnableMethodWithTimer. +template <typename PtrType, typename Method> +using IdleRunnableMethodWithTimer = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, + RunnableKind::IdleWithTimer>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using IdleRunnableMethodWithTimerImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::IdleWithTimer, Storages...>; + +// Type aliases for NewNonOwningRunnableMethod. +template <typename PtrType, typename Method> +using NonOwningRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, RunnableKind::Standard>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::Standard, Storages...>; + +// Type aliases for NonOwningCancelableRunnableMethod +template <typename PtrType, typename Method> +using NonOwningCancelableRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, + RunnableKind::Cancelable>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningCancelableRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::Cancelable, Storages...>; + +// Type aliases for NonOwningIdleRunnableMethod +template <typename PtrType, typename Method> +using NonOwningIdleRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, RunnableKind::Idle>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningIdleRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::Idle, Storages...>; + +// Type aliases for NewIdleRunnableMethodWithTimer. +template <typename PtrType, typename Method> +using NonOwningIdleRunnableMethodWithTimer = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, + RunnableKind::IdleWithTimer>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningIdleRunnableMethodWithTimerImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::IdleWithTimer, Storages...>; + +} // namespace detail + +// NewRunnableMethod and friends +// +// Very often in Gecko, you'll find yourself in a situation where you want +// to invoke a method (with or without arguments) asynchronously. You +// could write a small helper class inheriting from nsRunnable to handle +// all these details, or you could let NewRunnableMethod take care of all +// those details for you. +// +// The simplest use of NewRunnableMethod looks like: +// +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod("description", myObject, +// &MyClass::HandleEvent); +// NS_DispatchToCurrentThread(event); +// +// Statically enforced constraints: +// - myObject must be of (or implicitly convertible to) type MyClass +// - MyClass must define AddRef and Release methods +// +// The "description" string should specify a human-readable name for the +// runnable; the provided string is used by various introspection tools +// in the browser. +// +// The created runnable will take a strong reference to `myObject`. For +// non-refcounted objects, or refcounted objects with unusual refcounting +// requirements, and if and only if you are 110% certain that `myObject` +// will live long enough, you can use NewNonOwningRunnableMethod instead, +// which will, as its name implies, take a non-owning reference. If you +// find yourself having to use this function, you should accompany your use +// with a proof comment describing why the runnable will not lead to +// use-after-frees. +// +// (If you find yourself writing contorted code to Release() an object +// asynchronously on a different thread, you should use the +// NS_ProxyRelease function.) +// +// Invoking a method with arguments takes a little more care. The +// natural extension of the above: +// +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod("description", myObject, +// &MyClass::HandleEvent, +// arg1, arg2, ...); +// +// can lead to security hazards (e.g. passing in raw pointers to refcounted +// objects and storing those raw pointers in the runnable). We therefore +// require you to specify the storage types used by the runnable, just as +// you would if you were writing out the class by hand: +// +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod<RefPtr<T>, nsTArray<U>> +// ("description", myObject, &MyClass::HandleEvent, arg1, arg2); +// +// Please note that you do not have to pass the same argument type as you +// specify in the template arguments. For example, if you want to transfer +// ownership to a runnable, you can write: +// +// RefPtr<T> ptr = ...; +// nsTArray<U> array = ...; +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod<RefPtr<T>, nsTArray<U>> +// ("description", myObject, &MyClass::DoSomething, +// std::move(ptr), std::move(array)); +// +// and there will be no extra AddRef/Release traffic, or copying of the array. +// +// Each type that you specify as a template argument to NewRunnableMethod +// comes with its own style of storage in the runnable and its own style +// of argument passing to the invoked method. See the comment for +// ParameterStorage above for more details. +// +// If you need to customize the storage type and/or argument passing type, +// you can write your own class to use as a template argument to +// NewRunnableMethod. If you find yourself having to do that frequently, +// please file a bug in Core::XPCOM about adding the custom type to the +// core code in this file, and/or for custom rules for ParameterStorage +// to select that strategy. +// +// For places that require you to use cancelable runnables, such as +// workers, there's also NewCancelableRunnableMethod and its non-owning +// counterpart. The runnables returned by these methods additionally +// implement nsICancelableRunnable. +// +// Finally, all of the functions discussed above have additional overloads +// that do not take a `const char*` as their first parameter; you may see +// these in older code. The `const char*` overload is preferred and +// should be used in new code exclusively. + +template <typename PtrType, typename Method> +already_AddRefed<detail::OwningRunnableMethod<PtrType, Method>> +NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::OwningRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::CancelableRunnableMethod<PtrType, Method>> +NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::CancelableRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::IdleRunnableMethod<PtrType, Method>> +NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::IdleRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::IdleRunnableMethodWithTimer<PtrType, Method>> +NewIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef(new detail::IdleRunnableMethodWithTimerImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningRunnableMethod<PtrType, Method>> +NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::NonOwningRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningCancelableRunnableMethod<PtrType, Method>> +NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef( + new detail::NonOwningCancelableRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningIdleRunnableMethod<PtrType, Method>> +NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef(new detail::NonOwningIdleRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningIdleRunnableMethodWithTimer<PtrType, Method>> +NewNonOwningIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef( + new detail::NonOwningIdleRunnableMethodWithTimerImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +// Similar to NewRunnableMethod. Call like so: +// nsCOMPtr<nsIRunnable> event = +// NewRunnableMethod<Types,...>(myObject, &MyClass::HandleEvent, myArg1,...); +// 'Types' are the stored type for each argument, see ParameterStorage for +// details. +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::OwningRunnableMethod<PtrType, Method>> +NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::OwningRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::NonOwningRunnableMethod<PtrType, Method>> +NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::CancelableRunnableMethod<PtrType, Method>> +NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::CancelableRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::NonOwningCancelableRunnableMethod<PtrType, Method>> +NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod, Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningCancelableRunnableMethodImpl<PtrType, Method, + Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::IdleRunnableMethod<PtrType, Method>> +NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::IdleRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::NonOwningIdleRunnableMethod<PtrType, Method>> +NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod, Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningIdleRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +} // namespace mozilla + +#endif // XPCOM_GLUE_AVOID_NSPR + +// This class is designed to be used when you have an event class E that has a +// pointer back to resource class R. If R goes away while E is still pending, +// then it is important to "revoke" E so that it does not try use R after R has +// been destroyed. nsRevocableEventPtr makes it easy for R to manage such +// situations: +// +// class R; +// +// class E : public mozilla::Runnable { +// public: +// void Revoke() { +// mResource = nullptr; +// } +// private: +// R *mResource; +// }; +// +// class R { +// public: +// void EventHandled() { +// mEvent.Forget(); +// } +// private: +// nsRevocableEventPtr<E> mEvent; +// }; +// +// void R::PostEvent() { +// // Make sure any pending event is revoked. +// mEvent->Revoke(); +// +// nsCOMPtr<nsIRunnable> event = new E(); +// if (NS_SUCCEEDED(NS_DispatchToCurrentThread(event))) { +// // Keep pointer to event so we can revoke it. +// mEvent = event; +// } +// } +// +// NS_IMETHODIMP E::Run() { +// if (!mResource) +// return NS_OK; +// ... +// mResource->EventHandled(); +// return NS_OK; +// } +// +template <class T> +class nsRevocableEventPtr { + public: + nsRevocableEventPtr() : mEvent(nullptr) {} + ~nsRevocableEventPtr() { Revoke(); } + + const nsRevocableEventPtr& operator=(RefPtr<T>&& aEvent) { + if (mEvent != aEvent) { + Revoke(); + mEvent = std::move(aEvent); + } + return *this; + } + + void Revoke() { + if (mEvent) { + mEvent->Revoke(); + mEvent = nullptr; + } + } + + void Forget() { mEvent = nullptr; } + bool IsPending() { return mEvent != nullptr; } + T* get() { return mEvent; } + + private: + // Not implemented + nsRevocableEventPtr(const nsRevocableEventPtr&); + nsRevocableEventPtr& operator=(const nsRevocableEventPtr&); + + RefPtr<T> mEvent; +}; + +template <class T> +inline already_AddRefed<T> do_AddRef(nsRevocableEventPtr<T>& aObj) { + return do_AddRef(aObj.get()); +} + +/** + * A simple helper to suffix thread pool name + * with incremental numbers. + */ +class nsThreadPoolNaming { + public: + nsThreadPoolNaming() = default; + + /** + * Returns a thread name as "<aPoolName> #<n>" and increments the counter. + */ + nsCString GetNextThreadName(const nsACString& aPoolName); + + template <size_t LEN> + nsCString GetNextThreadName(const char (&aPoolName)[LEN]) { + return GetNextThreadName(nsDependentCString(aPoolName, LEN - 1)); + } + + private: + mozilla::Atomic<uint32_t> mCounter{0}; + + nsThreadPoolNaming(const nsThreadPoolNaming&) = delete; + void operator=(const nsThreadPoolNaming&) = delete; +}; + +/** + * Thread priority in most operating systems affect scheduling, not IO. This + * helper is used to set the current thread to low IO priority for the lifetime + * of the created object. You can only use this low priority IO setting within + * the context of the current thread. + */ +class MOZ_STACK_CLASS nsAutoLowPriorityIO { + public: + nsAutoLowPriorityIO(); + ~nsAutoLowPriorityIO(); + + private: + bool lowIOPrioritySet; +#if defined(XP_MACOSX) + int oldPriority; +#endif +}; + +void NS_SetMainThread(); + +// Used only on cooperatively scheduled "main" threads. Causes the thread to be +// considered a main thread and also causes GetCurrentVirtualThread to return +// aVirtualThread. +void NS_SetMainThread(PRThread* aVirtualThread); + +// Used only on cooperatively scheduled "main" threads. Causes the thread to no +// longer be considered a main thread. Also causes GetCurrentVirtualThread() to +// return a unique value. +void NS_UnsetMainThread(); + +/** + * Return the expiration time of the next timer to run on the current + * thread. If that expiration time is greater than aDefault, then + * return aDefault. aSearchBound specifies a maximum number of timers + * to examine to find a timer on the current thread. If no timer that + * will run on the current thread is found after examining + * aSearchBound timers, return the highest seen expiration time as a + * best effort guess. + * + * Timers with either the type nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY or + * nsITIMER::TYPE_REPEATING_SLACK_LOW_PRIORITY will be skipped when + * searching for the next expiration time. This enables timers to + * have lower priority than callbacks dispatched from + * nsIThread::IdleDispatch. + */ +extern mozilla::TimeStamp NS_GetTimerDeadlineHintOnCurrentThread( + mozilla::TimeStamp aDefault, uint32_t aSearchBound); + +/** + * Dispatches the given event to a background thread. The primary benefit of + * this API is that you do not have to manage the lifetime of your own thread + * for running your own events; the thread manager will take care of the + * background thread's lifetime. Not having to manage your own thread also + * means less resource usage, as the underlying implementation here can manage + * spinning up and shutting down threads appropriately. + * + * NOTE: there is no guarantee that events dispatched via these APIs are run + * serially, in dispatch order; several dispatched events may run in parallel. + * If you depend on serial execution of dispatched events, you should use + * NS_CreateBackgroundTaskQueue instead, and dispatch events to the returned + * event target. + */ +extern nsresult NS_DispatchBackgroundTask( + already_AddRefed<nsIRunnable> aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); +extern "C" nsresult NS_DispatchBackgroundTask( + nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + +/** + * Obtain a new serial event target that dispatches runnables to a background + * thread. In many cases, this is a straight replacement for creating your + * own, private thread, and is generally preferred to creating your own, + * private thread. + */ +extern "C" nsresult NS_CreateBackgroundTaskQueue( + const char* aName, nsISerialEventTarget** aTarget); + +/** + * Dispatch the given runnable to the given event target, spinning the current + * thread's event loop until the runnable has finished executing. + * + * This is roughly equivalent to the previously-supported `NS_DISPATCH_SYNC` + * flag. + */ +extern nsresult NS_DispatchAndSpinEventLoopUntilComplete( + const nsACString& aVeryGoodReasonToDoThis, nsIEventTarget* aEventTarget, + already_AddRefed<nsIRunnable> aEvent); + +// Predeclaration for logging function below +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +} // namespace IPC + +class nsTimerImpl; + +namespace mozilla { + +// RAII class that will set the TLS entry to return the currently running +// nsISerialEventTarget. +// It should be used from inner event loop implementation. +class SerialEventTargetGuard { + public: + explicit SerialEventTargetGuard(nsISerialEventTarget* aThread) + : mLastCurrentThread(sCurrentThreadTLS.get()) { + Set(aThread); + } + + ~SerialEventTargetGuard() { sCurrentThreadTLS.set(mLastCurrentThread); } + + static void InitTLS(); + static nsISerialEventTarget* GetCurrentSerialEventTarget() { + return sCurrentThreadTLS.get(); + } + + protected: + friend class ::MessageLoop; + static void Set(nsISerialEventTarget* aThread) { + MOZ_ASSERT(aThread->IsOnCurrentThread()); + sCurrentThreadTLS.set(aThread); + } + + private: + static MOZ_THREAD_LOCAL(nsISerialEventTarget*) sCurrentThreadTLS; + nsISerialEventTarget* mLastCurrentThread; +}; + +// Get the serial event target corresponding to the currently executing task +// queue or thread. This method will assert if called on a thread pool without +// an active task queue. +// +// This function should generally be preferred over NS_GetCurrentThread since it +// will return a more useful answer when called from a task queue running on a +// thread pool or on a non-xpcom thread which accepts runnable dispatches. +// +// NOTE: The returned nsISerialEventTarget may not accept runnable dispatches +// (e.g. if it corresponds to a non-xpcom thread), however it may still be used +// to check if you're on the given thread/queue using IsOnCurrentThread(). + +nsISerialEventTarget* GetCurrentSerialEventTarget(); + +// Get a weak reference to a serial event target which can be used to dispatch +// runnables to the main thread. +// +// NOTE: While this is currently a weak pointer to the nsIThread* returned from +// NS_GetMainThread(), this may change in the future. + +nsISerialEventTarget* GetMainThreadSerialEventTarget(); + +// Returns the number of CPUs, like PR_GetNumberOfProcessors, except +// that it can return a cached value on platforms where sandboxing +// would prevent reading the current value (currently Linux). CPU +// hotplugging is uncommon, so this is unlikely to make a difference +// in practice. +size_t GetNumberOfProcessors(); + +/** + * A helper class to log tasks dispatch and run with "MOZ_LOG=events:1". The + * output is more machine readable and creates a link between dispatch and run. + * + * Usage example for the concrete template type nsIRunnable. + * To log a dispatch, which means putting an event to a queue: + * LogRunnable::LogDispatch(event); + * theQueue.putEvent(event); + * + * To log execution (running) of the event: + * nsCOMPtr<nsIRunnable> event = theQueue.popEvent(); + * { + * LogRunnable::Run log(event); + * event->Run(); + * event = null; // to include the destructor code in the span + * } + * + * The class is a template so that we can support various specific super-types + * of tasks in the future. We can't use void* because it may cast differently + * and tracking the pointer in logs would then be impossible. + */ +template <typename T> +class LogTaskBase { + public: + LogTaskBase() = delete; + + // Adds a simple log about dispatch of this runnable. + static void LogDispatch(T* aEvent); + // The `aContext` pointer adds another uniqe identifier, nothing more + static void LogDispatch(T* aEvent, void* aContext); + + // Logs dispatch of the message and along that also the PID of the target + // proccess, purposed for uniquely identifying IPC messages. + static void LogDispatchWithPid(T* aEvent, int32_t aPid); + + // This is designed to surround a call to `Run()` or any code representing + // execution of the task body. + // The constructor adds a simple log about start of the runnable execution and + // the destructor adds a log about ending the execution. + class MOZ_RAII Run { + public: + Run() = delete; + explicit Run(T* aEvent, bool aWillRunAgain = false); + explicit Run(T* aEvent, void* aContext, bool aWillRunAgain = false); + ~Run(); + + // When this is called, the log in this RAII dtor will only say + // "interrupted" expecting that the event will run again. + void WillRunAgain() { mWillRunAgain = true; } + + private: + bool mWillRunAgain = false; + }; +}; + +class MicroTaskRunnable; +class Task; // TaskController +class PresShell; +namespace dom { +class FrameRequestCallback; +} // namespace dom + +// Specialized methods must be explicitly predeclared. +template <> +LogTaskBase<nsIRunnable>::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain); +template <> +LogTaskBase<Task>::Run::Run(Task* aTask, bool aWillRunAgain); +template <> +void LogTaskBase<IPC::Message>::LogDispatchWithPid(IPC::Message* aEvent, + int32_t aPid); +template <> +LogTaskBase<IPC::Message>::Run::Run(IPC::Message* aMessage, bool aWillRunAgain); +template <> +LogTaskBase<nsTimerImpl>::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain); + +typedef LogTaskBase<nsIRunnable> LogRunnable; +typedef LogTaskBase<MicroTaskRunnable> LogMicroTaskRunnable; +typedef LogTaskBase<IPC::Message> LogIPCMessage; +typedef LogTaskBase<nsTimerImpl> LogTimerEvent; +typedef LogTaskBase<Task> LogTask; +typedef LogTaskBase<PresShell> LogPresShellObserver; +typedef LogTaskBase<dom::FrameRequestCallback> LogFrameRequestCallback; +// If you add new types don't forget to add: +// `template class LogTaskBase<YourType>;` to nsThreadUtils.cpp + +} // namespace mozilla + +#endif // nsThreadUtils_h__ diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp new file mode 100644 index 0000000000..b350e9c0a9 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.cpp @@ -0,0 +1,822 @@ +/* -*- 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 "nsTimerImpl.h" + +#include <utility> + +#include "TimerThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/Try.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "pratom.h" + +#ifdef XP_WIN +# include <process.h> +# ifndef getpid +# define getpid _getpid +# endif +#else +# include <unistd.h> +#endif + +using mozilla::Atomic; +using mozilla::LogLevel; +using mozilla::MakeRefPtr; +using mozilla::MutexAutoLock; +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +// Holds the timer thread and manages all interactions with it +// under a locked mutex. This wrapper is not destroyed until after +// nsThreadManager shutdown to ensure we don't UAF during an offthread access to +// the timer thread. +class TimerThreadWrapper { + public: + constexpr TimerThreadWrapper() : mThread(nullptr){}; + ~TimerThreadWrapper() = default; + + nsresult Init(); + void Shutdown(); + + nsresult AddTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + nsresult RemoveTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound); + uint32_t AllowedEarlyFiringMicroseconds(); + nsresult GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal); + + private: + static mozilla::StaticMutex sMutex; + TimerThread* mThread MOZ_GUARDED_BY(sMutex); +}; + +mozilla::StaticMutex TimerThreadWrapper::sMutex; + +nsresult TimerThreadWrapper::Init() { + mozilla::StaticMutexAutoLock lock(sMutex); + mThread = new TimerThread(); + + NS_ADDREF(mThread); + + return NS_OK; +} + +void TimerThreadWrapper::Shutdown() { + RefPtr<TimerThread> thread; + + { + mozilla::StaticMutexAutoLock lock(sMutex); + if (!mThread) { + return; + } + thread = mThread; + } + // Shutdown calls |nsTimerImpl::Cancel| which needs to make a call into + // |RemoveTimer|. This can't be done under the lock. + thread->Shutdown(); + + { + mozilla::StaticMutexAutoLock lock(sMutex); + NS_RELEASE(mThread); + } +} + +nsresult TimerThreadWrapper::AddTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + mozilla::StaticMutexAutoLock lock(sMutex); + if (mThread) { + return mThread->AddTimer(aTimer, aProofOfLock); + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult TimerThreadWrapper::RemoveTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + mozilla::StaticMutexAutoLock lock(sMutex); + if (mThread) { + return mThread->RemoveTimer(aTimer, aProofOfLock); + } + return NS_ERROR_NOT_AVAILABLE; +} + +TimeStamp TimerThreadWrapper::FindNextFireTimeForCurrentThread( + TimeStamp aDefault, uint32_t aSearchBound) { + mozilla::StaticMutexAutoLock lock(sMutex); + return mThread + ? mThread->FindNextFireTimeForCurrentThread(aDefault, aSearchBound) + : TimeStamp(); +} + +uint32_t TimerThreadWrapper::AllowedEarlyFiringMicroseconds() { + mozilla::StaticMutexAutoLock lock(sMutex); + return mThread ? mThread->AllowedEarlyFiringMicroseconds() : 0; +} + +nsresult TimerThreadWrapper::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) { + RefPtr<TimerThread> thread; + { + mozilla::StaticMutexAutoLock lock(sMutex); + if (!mThread) { + return NS_ERROR_NOT_AVAILABLE; + } + thread = mThread; + } + return thread->GetTimers(aRetVal); +} + +static TimerThreadWrapper gThreadWrapper; + +// This module prints info about the precision of timers. +static mozilla::LazyLogModule sTimerLog("nsTimerImpl"); + +mozilla::LogModule* GetTimerLog() { return sTimerLog; } + +TimeStamp NS_GetTimerDeadlineHintOnCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound) { + return gThreadWrapper.FindNextFireTimeForCurrentThread(aDefault, + aSearchBound); +} + +already_AddRefed<nsITimer> NS_NewTimer() { return NS_NewTimer(nullptr); } + +already_AddRefed<nsITimer> NS_NewTimer(nsIEventTarget* aTarget) { + return nsTimer::WithEventTarget(aTarget).forget(); +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithObserver( + nsIObserver* aObserver, uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithObserver(getter_AddRefs(timer), aObserver, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithObserver(nsITimer** aTimer, nsIObserver* aObserver, + uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->Init(aObserver, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback( + nsITimerCallback* aCallback, uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback, + uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithCallback(aCallback, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback( + nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback, + const TimeDuration& aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitHighResolutionWithCallback(aCallback, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback( + std::function<void(nsITimer*)>&& aCallback, uint32_t aDelay, uint32_t aType, + const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), std::move(aCallback), + aDelay, aType, aNameString, aTarget)); + return timer; +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, + std::function<void(nsITimer*)>&& aCallback, + uint32_t aDelay, uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget) { + return NS_NewTimerWithCallback(aTimer, std::move(aCallback), + TimeDuration::FromMilliseconds(aDelay), aType, + aNameString, aTarget); +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback( + std::function<void(nsITimer*)>&& aCallback, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), std::move(aCallback), + aDelay, aType, aNameString, aTarget)); + return timer; +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, + std::function<void(nsITimer*)>&& aCallback, + const TimeDuration& aDelay, uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget) { + RefPtr<nsTimer> timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithClosureCallback(std::move(aCallback), aDelay, aType, + aNameString)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay, + uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback, + aClosure, aDelay, aType, aNameString, + aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, uint32_t aDelay, + uint32_t aType, const char* aNameString, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithNamedFuncCallback(aCallback, aClosure, aDelay, aType, + aNameString)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback, + aClosure, aDelay, aType, aNameString, + aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitHighResolutionWithNamedFuncCallback( + aCallback, aClosure, aDelay, aType, aNameString)); + timer.forget(aTimer); + return NS_OK; +} + +// This module prints info about which timers are firing, which is useful for +// wakeups for the purposes of power profiling. Set the following environment +// variable before starting the browser. +// +// MOZ_LOG=TimerFirings:4 +// +// Then a line will be printed for every timer that fires. +// +// If you redirect this output to a file called "out", you can then +// post-process it with a command something like the following. +// +// cat out | grep timer | sort | uniq -c | sort -r -n +// +// This will show how often each unique line appears, with the most common ones +// first. +// +// More detailed docs are here: +// https://developer.mozilla.org/en-US/docs/Mozilla/Performance/TimerFirings_logging +// +static mozilla::LazyLogModule sTimerFiringsLog("TimerFirings"); + +static mozilla::LogModule* GetTimerFiringsLog() { return sTimerFiringsLog; } + +#include <math.h> + +/* static */ +mozilla::StaticMutex nsTimerImpl::sDeltaMutex; +/* static */ +double nsTimerImpl::sDeltaSumSquared MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = + 0; +/* static */ +double nsTimerImpl::sDeltaSum MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = 0; +/* static */ +double nsTimerImpl::sDeltaNum MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = 0; + +static void myNS_MeanAndStdDev(double n, double sumOfValues, + double sumOfSquaredValues, double* meanResult, + double* stdDevResult) { + double mean = 0.0, var = 0.0, stdDev = 0.0; + if (n > 0.0 && sumOfValues >= 0) { + mean = sumOfValues / n; + double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues); + if (temp < 0.0 || n <= 1) { + var = 0.0; + } else { + var = temp / (n * (n - 1)); + } + // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: + stdDev = var != 0.0 ? sqrt(var) : 0.0; + } + *meanResult = mean; + *stdDevResult = stdDev; +} + +NS_IMPL_QUERY_INTERFACE(nsTimer, nsITimer) +NS_IMPL_ADDREF(nsTimer) + +NS_IMPL_ISUPPORTS(nsTimerManager, nsITimerManager) + +NS_IMETHODIMP nsTimerManager::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) { + return gThreadWrapper.GetTimers(aRetVal); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsTimer::Release(void) { + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "nsTimer"); + + if (count == 1) { + // Last ref, in nsTimerImpl::mITimer. Make sure the cycle is broken. + mImpl->CancelImpl(true); + } else if (count == 0) { + delete this; + } + + return count; +} + +nsTimerImpl::nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget) + : mEventTarget(aTarget), + mIsInTimerThread(false), + mType(0), + mGeneration(0), + mITimer(aTimer), + mMutex("nsTimerImpl::mMutex"), + mCallback(UnknownCallback{}), + mFiring(0) { + // XXX some code creates timers during xpcom shutdown, when threads are no + // longer available, so we cannot turn this on yet. + // MOZ_ASSERT(mEventTarget); +} + +// static +nsresult nsTimerImpl::Startup() { return gThreadWrapper.Init(); } + +void nsTimerImpl::Shutdown() { + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + mozilla::StaticMutexAutoLock lock(sDeltaMutex); + double mean = 0, stddev = 0; + myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", + sDeltaNum, sDeltaSum, sDeltaSumSquared)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("mean: %fms, stddev: %fms\n", mean, stddev)); + } + + gThreadWrapper.Shutdown(); +} + +nsresult nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType, + Callback&& newCallback, + const MutexAutoLock& aProofOfLock) { + if (!mEventTarget) { + return NS_ERROR_NOT_INITIALIZED; + } + + gThreadWrapper.RemoveTimer(this, aProofOfLock); + + // If we have an existing callback, using `swap` ensures it's destroyed after + // the mutex is unlocked in our caller. + std::swap(mCallback, newCallback); + ++mGeneration; + + mType = (uint8_t)aType; + mDelay = aDelay; + mTimeout = TimeStamp::Now() + mDelay; + + return gThreadWrapper.AddTimer(this, aProofOfLock); +} + +nsresult nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, uint32_t aDelay, + uint32_t aType, + const char* aName) { + return InitHighResolutionWithNamedFuncCallback( + aFunc, aClosure, TimeDuration::FromMilliseconds(aDelay), aType, aName); +} + +nsresult nsTimerImpl::InitHighResolutionWithNamedFuncCallback( + nsTimerCallbackFunc aFunc, void* aClosure, const TimeDuration& aDelay, + uint32_t aType, const char* aName) { + if (NS_WARN_IF(!aFunc)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb{FuncCallback{aFunc, aClosure, aName}}; + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb), lock); +} + +nsresult nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback, + uint32_t aDelayInMs, uint32_t aType) { + return InitHighResolutionWithCallback( + aCallback, TimeDuration::FromMilliseconds(aDelayInMs), aType); +} + +nsresult nsTimerImpl::InitHighResolutionWithCallback( + nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType) { + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + + // Goes out of scope after the unlock, prevents deadlock + Callback cb{nsCOMPtr{aCallback}}; + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb), lock); +} + +nsresult nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelayInMs, + uint32_t aType) { + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb{nsCOMPtr{aObserver}}; + + MutexAutoLock lock(mMutex); + return InitCommon(TimeDuration::FromMilliseconds(aDelayInMs), aType, + std::move(cb), lock); +} + +nsresult nsTimerImpl::InitWithClosureCallback( + std::function<void(nsITimer*)>&& aCallback, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString) { + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb{ClosureCallback{std::move(aCallback), aNameString}}; + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb), lock); +} + +nsresult nsTimerImpl::Cancel() { + CancelImpl(false); + return NS_OK; +} + +void nsTimerImpl::CancelImpl(bool aClearITimer) { + Callback cbTrash{UnknownCallback{}}; + RefPtr<nsITimer> timerTrash; + + { + MutexAutoLock lock(mMutex); + gThreadWrapper.RemoveTimer(this, lock); + + // The swap ensures our callback isn't dropped until after the mutex is + // unlocked. + std::swap(cbTrash, mCallback); + ++mGeneration; + + // Don't clear this if we're firing; once Fire returns, we'll get this call + // again. + if (aClearITimer && !mFiring) { + MOZ_RELEASE_ASSERT( + mITimer, + "mITimer was nulled already! " + "This indicates that someone has messed up the refcount on nsTimer!"); + timerTrash.swap(mITimer); + } + } +} + +nsresult nsTimerImpl::SetDelay(uint32_t aDelay) { + MutexAutoLock lock(mMutex); + if (GetCallback().is<UnknownCallback>() && !IsRepeating()) { + // This may happen if someone tries to re-use a one-shot timer + // by re-setting delay instead of reinitializing the timer. + NS_ERROR( + "nsITimer->SetDelay() called when the " + "one-shot timer is not set up."); + return NS_ERROR_NOT_INITIALIZED; + } + + bool reAdd = false; + reAdd = NS_SUCCEEDED(gThreadWrapper.RemoveTimer(this, lock)); + + mDelay = TimeDuration::FromMilliseconds(aDelay); + mTimeout = TimeStamp::Now() + mDelay; + + if (reAdd) { + gThreadWrapper.AddTimer(this, lock); + } + + return NS_OK; +} + +nsresult nsTimerImpl::GetDelay(uint32_t* aDelay) { + MutexAutoLock lock(mMutex); + *aDelay = mDelay.ToMilliseconds(); + return NS_OK; +} + +nsresult nsTimerImpl::SetType(uint32_t aType) { + MutexAutoLock lock(mMutex); + mType = (uint8_t)aType; + // XXX if this is called, we should change the actual type.. this could effect + // repeating timers. we need to ensure in Fire() that if mType has changed + // during the callback that we don't end up with the timer in the queue twice. + return NS_OK; +} + +nsresult nsTimerImpl::GetType(uint32_t* aType) { + MutexAutoLock lock(mMutex); + *aType = mType; + return NS_OK; +} + +nsresult nsTimerImpl::GetClosure(void** aClosure) { + MutexAutoLock lock(mMutex); + if (GetCallback().is<FuncCallback>()) { + *aClosure = GetCallback().as<FuncCallback>().mClosure; + } else { + *aClosure = nullptr; + } + return NS_OK; +} + +nsresult nsTimerImpl::GetCallback(nsITimerCallback** aCallback) { + MutexAutoLock lock(mMutex); + if (GetCallback().is<InterfaceCallback>()) { + NS_IF_ADDREF(*aCallback = GetCallback().as<InterfaceCallback>()); + } else { + *aCallback = nullptr; + } + return NS_OK; +} + +nsresult nsTimerImpl::GetTarget(nsIEventTarget** aTarget) { + MutexAutoLock lock(mMutex); + NS_IF_ADDREF(*aTarget = mEventTarget); + return NS_OK; +} + +nsresult nsTimerImpl::SetTarget(nsIEventTarget* aTarget) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mCallback.is<UnknownCallback>())) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aTarget) { + mEventTarget = aTarget; + } else { + mEventTarget = mozilla::GetCurrentSerialEventTarget(); + } + return NS_OK; +} + +nsresult nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut) { + *aValueOut = gThreadWrapper.AllowedEarlyFiringMicroseconds(); + return NS_OK; +} + +void nsTimerImpl::Fire(int32_t aGeneration) { + uint8_t oldType; + uint32_t oldDelay; + TimeStamp oldTimeout; + Callback callbackDuringFire{UnknownCallback{}}; + nsCOMPtr<nsITimer> timer; + + { + // Don't fire callbacks or fiddle with refcounts when the mutex is locked. + // If some other thread Cancels/Inits after this, they're just too late. + MutexAutoLock lock(mMutex); + if (aGeneration != mGeneration) { + // This timer got rescheduled or cancelled before we fired, so ignore this + // firing + return; + } + + // We modify mTimeout, so we must not be in the current TimerThread's + // mTimers list. + MOZ_ASSERT(!mIsInTimerThread); + + ++mFiring; + callbackDuringFire = mCallback; + oldType = mType; + oldDelay = mDelay.ToMilliseconds(); + oldTimeout = mTimeout; + // Ensure that the nsITimer does not unhook from the nsTimerImpl during + // Fire; this will cause null pointer crashes if the user of the timer drops + // its reference, and then uses the nsITimer* passed in the callback. + timer = mITimer; + } + + AUTO_PROFILER_LABEL("nsTimerImpl::Fire", OTHER); + + TimeStamp fireTime; + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + fireTime = TimeStamp::Now(); + TimeDuration delta = fireTime - oldTimeout; + int32_t d = delta.ToMilliseconds(); // delta in ms + { + mozilla::StaticMutexAutoLock lock(sDeltaMutex); + sDeltaSum += abs(d); + sDeltaSumSquared += double(d) * double(d); + sDeltaNum++; + } + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] expected delay time %4ums\n", this, oldDelay)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] actual delay time %4dms\n", this, oldDelay + d)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] (mType is %d) -------\n", this, oldType)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] delta %4dms\n", this, d)); + } + + if (MOZ_LOG_TEST(GetTimerFiringsLog(), LogLevel::Debug)) { + LogFiring(callbackDuringFire, oldType, oldDelay); + } + + callbackDuringFire.match( + [](const UnknownCallback&) {}, + [&](const InterfaceCallback& i) { i->Notify(timer); }, + [&](const ObserverCallback& o) { + o->Observe(timer, NS_TIMER_CALLBACK_TOPIC, nullptr); + }, + [&](const FuncCallback& f) { f.mFunc(timer, f.mClosure); }, + [&](const ClosureCallback& c) { c.mFunc(timer); }); + + TimeStamp now = TimeStamp::Now(); + + MutexAutoLock lock(mMutex); + if (aGeneration == mGeneration) { + if (IsRepeating()) { + // Repeating timer has not been re-init or canceled; reschedule + if (IsSlack()) { + mTimeout = now + mDelay; + } else { + if (mDelay) { + // If we are late enough finishing the callback that we have missed + // some firings, do not attempt to play catchup, just get back on the + // cadence we're supposed to maintain. + unsigned missedFirings = + static_cast<unsigned>((now - mTimeout) / mDelay); + mTimeout += mDelay * (missedFirings + 1); + } else { + // Can we stop allowing repeating timers with delay 0? + mTimeout = now; + } + } + gThreadWrapper.AddTimer(this, lock); + } else { + // Non-repeating timer that has not been re-scheduled. Clear. + // XXX(nika): Other callsites seem to go to some effort to avoid + // destroying mCallback when it's held. Why not this one? + mCallback = mozilla::AsVariant(UnknownCallback{}); + } + } + + --mFiring; + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] Took %fms to fire timer callback\n", this, + (now - fireTime).ToMilliseconds())); +} + +// See the big comment above GetTimerFiringsLog() to understand this code. +void nsTimerImpl::LogFiring(const Callback& aCallback, uint8_t aType, + uint32_t aDelay) { + const char* typeStr; + switch (aType) { + case nsITimer::TYPE_ONE_SHOT: + typeStr = "ONE_SHOT "; + break; + case nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY: + typeStr = "ONE_LOW "; + break; + case nsITimer::TYPE_REPEATING_SLACK: + typeStr = "SLACK "; + break; + case nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY: + typeStr = "SLACK_LOW "; + break; + case nsITimer::TYPE_REPEATING_PRECISE: /* fall through */ + case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP: + typeStr = "PRECISE "; + break; + default: + MOZ_CRASH("bad type"); + } + + aCallback.match( + [&](const UnknownCallback&) { + MOZ_LOG( + GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] ??? timer (%s, %5d ms)\n", getpid(), typeStr, aDelay)); + }, + [&](const InterfaceCallback& i) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] iface timer (%s %5d ms): %p\n", getpid(), typeStr, + aDelay, i.get())); + }, + [&](const ObserverCallback& o) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] obs timer (%s %5d ms): %p\n", getpid(), typeStr, + aDelay, o.get())); + }, + [&](const FuncCallback& f) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] fn timer (%s %5d ms): %s\n", getpid(), typeStr, + aDelay, f.mName)); + }, + [&](const ClosureCallback& c) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] closure timer (%s %5d ms): %s\n", getpid(), typeStr, + aDelay, c.mName)); + }); +} + +void nsTimerImpl::GetName(nsACString& aName, + const MutexAutoLock& aProofOfLock) { + GetCallback().match( + [&](const UnknownCallback&) { aName.AssignLiteral("Canceled_timer"); }, + [&](const InterfaceCallback& i) { + if (nsCOMPtr<nsINamed> named = do_QueryInterface(i)) { + named->GetName(aName); + } else { + aName.AssignLiteral("Anonymous_interface_timer"); + } + }, + [&](const ObserverCallback& o) { + if (nsCOMPtr<nsINamed> named = do_QueryInterface(o)) { + named->GetName(aName); + } else { + aName.AssignLiteral("Anonymous_observer_timer"); + } + }, + [&](const FuncCallback& f) { aName.Assign(f.mName); }, + [&](const ClosureCallback& c) { aName.Assign(c.mName); }); +} + +nsresult nsTimerImpl::GetName(nsACString& aName) { + MutexAutoLock lock(mMutex); + GetName(aName, lock); + return NS_OK; +} + +nsTimer::~nsTimer() = default; + +size_t nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this); +} + +/* static */ +RefPtr<nsTimer> nsTimer::WithEventTarget(nsIEventTarget* aTarget) { + if (!aTarget) { + aTarget = mozilla::GetCurrentSerialEventTarget(); + } + return do_AddRef(new nsTimer(aTarget)); +} + +/* static */ +nsresult nsTimer::XPCOMConstructor(REFNSIID aIID, void** aResult) { + *aResult = nullptr; + auto timer = WithEventTarget(nullptr); + + return timer->QueryInterface(aIID, aResult); +} diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h new file mode 100644 index 0000000000..49f1fd00d5 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.h @@ -0,0 +1,231 @@ +/* -*- 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 nsTimerImpl_h___ +#define nsTimerImpl_h___ + +#include "nsITimer.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" + +#include "nsCOMPtr.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" +#include "mozilla/Logging.h" + +extern mozilla::LogModule* GetTimerLog(); + +#define NS_TIMER_CID \ + { /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */ \ + 0x5ff24248, 0x1dd2, 0x11b2, { \ + 0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8 \ + } \ + } + +class nsIObserver; + +namespace mozilla { +class LogModule; +} + +// TimerThread, nsTimerEvent, and nsTimer have references to these. nsTimer has +// a separate lifecycle so we can Cancel() the underlying timer when the user of +// the nsTimer has let go of its last reference. +class nsTimerImpl { + ~nsTimerImpl() { + MOZ_ASSERT(!mIsInTimerThread); + + // The nsITimer interface requires that its users keep a reference to the + // timers they use while those timers are initialized but have not yet + // fired. If this assert ever fails, it is a bug in the code that created + // and used the timer. + // + // Further, note that this should never fail even with a misbehaving user, + // because nsTimer::Release checks for a refcount of 1 with an armed timer + // (a timer whose only reference is from the timer thread) and when it hits + // this will remove the timer from the timer thread and thus destroy the + // last reference, preventing this situation from occurring. + MOZ_ASSERT( + mCallback.is<UnknownCallback>() || mEventTarget->IsOnCurrentThread(), + "Must not release mCallback off-target without canceling"); + } + + public: + typedef mozilla::TimeStamp TimeStamp; + + nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsTimerImpl) + NS_DECL_NON_VIRTUAL_NSITIMER + + static nsresult Startup(); + static void Shutdown(); + + void SetDelayInternal(uint32_t aDelay, TimeStamp aBase = TimeStamp::Now()); + void CancelImpl(bool aClearITimer); + + void Fire(int32_t aGeneration); + + int32_t GetGeneration() { return mGeneration; } + + struct UnknownCallback {}; + + using InterfaceCallback = nsCOMPtr<nsITimerCallback>; + + using ObserverCallback = nsCOMPtr<nsIObserver>; + + /// A raw function pointer and its closed-over state, along with its name for + /// logging purposes. + struct FuncCallback { + nsTimerCallbackFunc mFunc; + void* mClosure; + const char* mName; + }; + + /// A callback defined by an owned closure and its name for logging purposes. + struct ClosureCallback { + std::function<void(nsITimer*)> mFunc; + const char* mName; + }; + + using Callback = + mozilla::Variant<UnknownCallback, InterfaceCallback, ObserverCallback, + FuncCallback, ClosureCallback>; + + nsresult InitCommon(const mozilla::TimeDuration& aDelay, uint32_t aType, + Callback&& newCallback, + const mozilla::MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mMutex); + + Callback& GetCallback() MOZ_REQUIRES(mMutex) { + mMutex.AssertCurrentThreadOwns(); + return mCallback; + } + + bool IsRepeating() const { + static_assert(nsITimer::TYPE_ONE_SHOT < nsITimer::TYPE_REPEATING_SLACK, + "invalid ordering of timer types!"); + static_assert( + nsITimer::TYPE_REPEATING_SLACK < nsITimer::TYPE_REPEATING_PRECISE, + "invalid ordering of timer types!"); + static_assert(nsITimer::TYPE_REPEATING_PRECISE < + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "invalid ordering of timer types!"); + return mType >= nsITimer::TYPE_REPEATING_SLACK && + mType < nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY; + } + + bool IsLowPriority() const { + return mType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + bool IsSlack() const { + return mType == nsITimer::TYPE_REPEATING_SLACK || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + void GetName(nsACString& aName, const mozilla::MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mMutex); + + bool IsInTimerThread() const { return mIsInTimerThread; } + void SetIsInTimerThread(bool aIsInTimerThread) { + mIsInTimerThread = aIsInTimerThread; + } + + nsCOMPtr<nsIEventTarget> mEventTarget; + + void LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay); + + nsresult InitWithClosureCallback(std::function<void(nsITimer*)>&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, const char* aNameString); + + // Is this timer currently referenced from a TimerThread::Entry? + // Note: It is cleared before the Entry is destroyed. Take() also sets it to + // false, to indicate it's no longer in the TimerThread's list. This Take() + // call is NOT made under the nsTimerImpl's mutex (all other + // SetIsInTimerThread calls are under the mutex). However, ALL accesses to + // mIsInTimerThread are under the TimerThread's Monitor lock, so consistency + // is guaranteed by that. + bool mIsInTimerThread; + + // These members are set by the initiating thread, when the timer's type is + // changed and during the period where it fires on that thread. + uint8_t mType; + + // The generation number of this timer, re-generated each time the timer is + // initialized so one-shot timers can be canceled and re-initialized by the + // arming thread without any bad race conditions. + // Updated only after this timer has been removed from the timer thread. + int32_t mGeneration; + + mozilla::TimeDuration mDelay MOZ_GUARDED_BY(mMutex); + // Never updated while in the TimerThread's timer list. Only updated + // before adding to that list or during nsTimerImpl::Fire(), when it has + // been removed from the TimerThread's list. TimerThread can access + // mTimeout of any timer in the list safely + mozilla::TimeStamp mTimeout; + + RefPtr<nsITimer> mITimer MOZ_GUARDED_BY(mMutex); + mozilla::Mutex mMutex; + Callback mCallback MOZ_GUARDED_BY(mMutex); + // Counter because in rare cases we can Fire reentrantly + unsigned int mFiring MOZ_GUARDED_BY(mMutex); + + static mozilla::StaticMutex sDeltaMutex; + static double sDeltaSum MOZ_GUARDED_BY(sDeltaMutex); + static double sDeltaSumSquared MOZ_GUARDED_BY(sDeltaMutex); + static double sDeltaNum MOZ_GUARDED_BY(sDeltaMutex); +}; + +class nsTimer final : public nsITimer { + explicit nsTimer(nsIEventTarget* aTarget) + : mImpl(new nsTimerImpl(this, aTarget)) {} + + virtual ~nsTimer(); + + public: + friend class TimerThread; + friend class nsTimerEvent; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSITIMER(mImpl); + + // NOTE: This constructor is not exposed on `nsITimer` as NS_FORWARD_SAFE_ + // does not support forwarding rvalue references. + nsresult InitWithClosureCallback(std::function<void(nsITimer*)>&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, const char* aNameString) { + return mImpl ? mImpl->InitWithClosureCallback(std::move(aCallback), aDelay, + aType, aNameString) + : NS_ERROR_NULL_POINTER; + } + + // Create a timer targeting the given target. nullptr indicates that the + // current thread should be used as the timer's target. + static RefPtr<nsTimer> WithEventTarget(nsIEventTarget* aTarget); + + static nsresult XPCOMConstructor(REFNSIID aIID, void** aResult); + + private: + // nsTimerImpl holds a strong ref to us. When our refcount goes to 1, we will + // null this to break the cycle. + RefPtr<nsTimerImpl> mImpl; +}; + +class nsTimerManager final : public nsITimerManager { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERMANAGER + private: + ~nsTimerManager() = default; +}; + +#endif /* nsTimerImpl_h___ */ diff --git a/xpcom/windbgdlg/Makefile.in b/xpcom/windbgdlg/Makefile.in new file mode 100644 index 0000000000..254509ee77 --- /dev/null +++ b/xpcom/windbgdlg/Makefile.in @@ -0,0 +1,6 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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_WINCONSOLE = 0 diff --git a/xpcom/windbgdlg/moz.build b/xpcom/windbgdlg/moz.build new file mode 100644 index 0000000000..7d1798b545 --- /dev/null +++ b/xpcom/windbgdlg/moz.build @@ -0,0 +1,12 @@ +# -*- 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/. + +SimplePrograms(["windbgdlg"]) + +OS_LIBS += [ + "advapi32", + "user32", +] diff --git a/xpcom/windbgdlg/windbgdlg.cpp b/xpcom/windbgdlg/windbgdlg.cpp new file mode 100644 index 0000000000..2bd1139b23 --- /dev/null +++ b/xpcom/windbgdlg/windbgdlg.cpp @@ -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/. */ + +/* Windows only app to show a modal debug dialog - launched by nsDebug.cpp */ +#include <windows.h> +#include <stdlib.h> +#ifdef _MSC_VER +# include <strsafe.h> +#endif +#ifdef __MINGW32__ +/* MingW currently does not implement a wide version of the + startup routines. Workaround is to implement something like + it ourselves. See bug 472063 */ +# include <stdio.h> +# include <shellapi.h> +int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int); + +# undef __argc +# undef __wargv + +static int __argc; +static wchar_t** __wargv; + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpszCommandLine, int nCmdShow) { + LPWSTR commandLine = GetCommandLineW(); + + /* parse for __argc and __wargv for compatibility, since mingw + * doesn't claim to support it :( + */ + __wargv = CommandLineToArgvW(commandLine, &__argc); + if (!__wargv) return 127; + + /* need to strip off any leading whitespace plus the first argument + * (the executable itself) to match what should be passed to wWinMain + */ + while ((*commandLine <= L' ') && *commandLine) { + ++commandLine; + } + if (*commandLine == L'"') { + ++commandLine; + while ((*commandLine != L'"') && *commandLine) { + ++commandLine; + } + if (*commandLine) { + ++commandLine; + } + } else { + while (*commandLine > L' ') { + ++commandLine; + } + } + while ((*commandLine <= L' ') && *commandLine) { + ++commandLine; + } + + int result = wWinMain(hInstance, hPrevInstance, commandLine, nCmdShow); + LocalFree(__wargv); + return result; +} +#endif /* __MINGW32__ */ + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPWSTR lpszCmdLine, int nCmdShow) { + /* support for auto answering based on words in the assertion. + * the assertion message is sent as a series of arguements (words) to the + * commandline. set a "word" to 0xffffffff to let the word not affect this + * code. set a "word" to 0xfffffffe to show the dialog. set a "word" to 0x5 to + * ignore (program should continue). set a "word" to 0x4 to retry (should fall + * into debugger). set a "word" to 0x3 to abort (die). + */ + DWORD regType; + DWORD regValue = -1; + DWORD regLength = sizeof regValue; + HKEY hkeyCU, hkeyLM; + RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\mozilla.org\\windbgdlg", 0, + KEY_READ, &hkeyCU); + RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\mozilla.org\\windbgdlg", 0, + KEY_READ, &hkeyLM); + for (int i = __argc - 1; regValue == (DWORD)-1 && i; --i) { + bool ok = false; + if (hkeyCU) + ok = RegQueryValueExW(hkeyCU, __wargv[i], 0, ®Type, (LPBYTE)®Value, + ®Length) == ERROR_SUCCESS; + if (!ok && hkeyLM) + ok = RegQueryValueExW(hkeyLM, __wargv[i], 0, ®Type, (LPBYTE)®Value, + ®Length) == ERROR_SUCCESS; + if (!ok) regValue = -1; + } + if (hkeyCU) RegCloseKey(hkeyCU); + if (hkeyLM) RegCloseKey(hkeyLM); + if (regValue != (DWORD)-1 && regValue != (DWORD)-2) return regValue; + static const int size = 4096; + static WCHAR msg[size]; + +#ifdef _MSC_VER + StringCchPrintfW(msg, +#else + snwprintf(msg, +#endif + size, + L"%s\n\nClick Abort to exit the Application.\n" + L"Click Retry to Debug the Application.\n" + L"Click Ignore to continue running the Application.", + lpszCmdLine); + msg[size - 1] = L'\0'; + return MessageBoxW( + nullptr, msg, L"NSGlue_Assertion", + MB_ICONSTOP | MB_SYSTEMMODAL | MB_ABORTRETRYIGNORE | MB_DEFBUTTON3); +} diff --git a/xpcom/xpidl/Makefile.in b/xpcom/xpidl/Makefile.in new file mode 100644 index 0000000000..2eec4cad81 --- /dev/null +++ b/xpcom/xpidl/Makefile.in @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +export:: + $(call SUBMAKE,xpidl,$(DEPTH)/config/makefiles/xpidl) diff --git a/xpcom/xpidl/moz.build b/xpcom/xpidl/moz.build new file mode 100644 index 0000000000..568f361a54 --- /dev/null +++ b/xpcom/xpidl/moz.build @@ -0,0 +1,5 @@ +# -*- 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/. |