diff options
Diffstat (limited to '')
47 files changed, 1191 insertions, 538 deletions
diff --git a/xpcom/base/MemoryTelemetry.cpp b/xpcom/base/MemoryTelemetry.cpp index d89c528049..e5013b9c73 100644 --- a/xpcom/base/MemoryTelemetry.cpp +++ b/xpcom/base/MemoryTelemetry.cpp @@ -42,10 +42,13 @@ 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 uint32_t kTelemetryIntervalMS = 60 * 1000; -static constexpr const char* kTopicCycleCollectorBegin = - "cycle-collector-begin"; +// Do not create a timer for telemetry this many seconds after the previous one +// fires. This exists so that we don't respond to our own timer. +static constexpr uint32_t kTelemetryCooldownS = 10; + +static constexpr const char* kTopicShutdown = "content-child-shutdown"; namespace { @@ -101,7 +104,7 @@ void MemoryTelemetry::Init() { nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); MOZ_RELEASE_ASSERT(obs); - obs->AddObserver(this, "content-child-shutdown", true); + obs->AddObserver(this, kTopicShutdown, true); } } @@ -118,24 +121,66 @@ void MemoryTelemetry::Init() { return *sInstance; } -nsresult MemoryTelemetry::DelayedInit() { - if (Telemetry::CanRecordExtended()) { - nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); - MOZ_RELEASE_ASSERT(obs); +void MemoryTelemetry::DelayedInit() { + mCanRun = true; + Poke(); +} - obs->AddObserver(this, kTopicCycleCollectorBegin, true); +void MemoryTelemetry::Poke() { + // Don't do anything that might delay process startup + if (!mCanRun) { + return; } - GatherReports(); + if (XRE_IsContentProcess() && !Telemetry::CanRecordReleaseData()) { + // All memory telemetry produced by content processes is release data, so if + // we're not recording release data then don't setup the timers on content + // processes. + return; + } - return NS_OK; + TimeStamp now = TimeStamp::Now(); + + if (mLastRun && mLastRun + TimeDuration::FromSeconds(10) < now) { + // If we last gathered telemetry less than ten seconds ago then Poke() does + // nothing. This is to prevent our own timer waking us up. + return; + } + + mLastPoke = now; + if (!mTimer) { + uint32_t delay = kTelemetryIntervalMS; + if (mLastRun) { + delay = uint32_t( + std::min( + TimeDuration::FromMilliseconds(kTelemetryIntervalMS), + std::max(TimeDuration::FromSeconds(kTelemetryCooldownS), + TimeDuration::FromMilliseconds(kTelemetryIntervalMS) - + (now - mLastRun))) + .ToMilliseconds()); + } + RefPtr<MemoryTelemetry> self(this); + auto res = NS_NewTimerWithCallback( + [self](nsITimer* aTimer) { self->GatherReports(); }, delay, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "MemoryTelemetry::GatherReports"); + + if (res.isOk()) { + // Errors are ignored, if there was an error then we just don't get + // telemetry. + mTimer = res.unwrap(); + } + } } nsresult MemoryTelemetry::Shutdown() { + if (mTimer) { + mTimer->Cancel(); + } + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); MOZ_RELEASE_ASSERT(obs); - obs->RemoveObserver(this, kTopicCycleCollectorBegin); + obs->RemoveObserver(this, kTopicShutdown); return NS_OK; } @@ -199,6 +244,9 @@ nsresult MemoryTelemetry::GatherReports( } }); + mLastRun = TimeStamp::Now(); + mTimer = nullptr; + RefPtr<nsMemoryReporterManager> mgr = nsMemoryReporterManager::GetOrCreate(); MOZ_DIAGNOSTIC_ASSERT(mgr); NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE); @@ -385,17 +433,22 @@ void MemoryTelemetry::GatherTotalMemory() { // Use our handle for the remote process to collect resident unique set // size information for that process. + bool success = true; for (const auto& info : infos) { #ifdef XP_MACOSX int64_t memory = nsMemoryReporterManager::PhysicalFootprint(info.mHandle); #else - int64_t memory = - nsMemoryReporterManager::ResidentUnique(info.mHandle); + int64_t memory = + nsMemoryReporterManager::ResidentUnique(info.mHandle); #endif if (memory > 0) { childSizes.AppendElement(memory); totalMemory += memory; + } else { + // We don't break out of the loop otherwise the cleanup code + // wouldn't run. + success = false; } #if defined(XP_WIN) @@ -405,17 +458,22 @@ void MemoryTelemetry::GatherTotalMemory() { #endif } + Maybe<int64_t> mbTotal; + if (success) { + mbTotal = Some(totalMemory); + } + NS_DispatchToMainThread(NS_NewRunnableFunction( "MemoryTelemetry::FinishGatheringTotalMemory", - [totalMemory, childSizes = std::move(childSizes)] { - MemoryTelemetry::Get().FinishGatheringTotalMemory(totalMemory, + [mbTotal, childSizes = std::move(childSizes)] { + MemoryTelemetry::Get().FinishGatheringTotalMemory(mbTotal, childSizes); })); })); } nsresult MemoryTelemetry::FinishGatheringTotalMemory( - int64_t aTotalMemory, const nsTArray<int64_t>& aChildSizes) { + Maybe<int64_t> aTotalMemory, const nsTArray<int64_t>& aChildSizes) { mGatheringTotalMemory = false; // Total memory usage can be difficult to measure both accurately and fast @@ -424,8 +482,10 @@ nsresult MemoryTelemetry::FinishGatheringTotalMemory( // 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 (aTotalMemory) { + HandleMemoryReport(Telemetry::MEMORY_TOTAL, nsIMemoryReporter::UNITS_BYTES, + aTotalMemory.value()); + } if (aChildSizes.Length() > 1) { int32_t tabsCount; @@ -502,21 +562,7 @@ nsresult MemoryTelemetry::FinishGatheringTotalMemory( 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 (strcmp(aTopic, kTopicShutdown) == 0) { if (nsCOMPtr<nsITelemetry> telemetry = do_GetService("@mozilla.org/base/telemetry;1")) { telemetry->FlushBatchedChildTelemetry(); diff --git a/xpcom/base/MemoryTelemetry.h b/xpcom/base/MemoryTelemetry.h index b7c7fe8ad6..f8c4bebb1a 100644 --- a/xpcom/base/MemoryTelemetry.h +++ b/xpcom/base/MemoryTelemetry.h @@ -8,6 +8,7 @@ #define mozilla_MemoryTelemetry_h #include "mozilla/TimeStamp.h" +#include "mozilla/Maybe.h" #include "mozilla/Result.h" #include "nsIObserver.h" #include "nsITimer.h" @@ -40,10 +41,14 @@ class MemoryTelemetry final : public nsIObserver, const std::function<void()>& aCompletionCallback = nullptr); /** - * Does expensive initialization, which should happen only after startup has - * completed, and the event loop is idle. + * Called to signal that we can begin collecting telemetry. */ - nsresult DelayedInit(); + void DelayedInit(); + + /** + * Notify that the browser is active and telemetry should be recorded soon. + */ + void Poke(); nsresult Shutdown(); @@ -57,14 +62,19 @@ class MemoryTelemetry final : public nsIObserver, static Result<uint32_t, nsresult> GetOpenTabsCount(); void GatherTotalMemory(); - nsresult FinishGatheringTotalMemory(int64_t aTotalMemory, + nsresult FinishGatheringTotalMemory(Maybe<int64_t> aTotalMemory, const nsTArray<int64_t>& aChildSizes); nsCOMPtr<nsIEventTarget> mThreadPool; bool mGatheringTotalMemory = false; - TimeStamp mLastPoll{}; + TimeStamp mLastRun{}; + TimeStamp mLastPoke{}; + nsCOMPtr<nsITimer> mTimer; + + // True if startup is finished and it's okay to start gathering telemetry. + bool mCanRun = false; }; } // namespace mozilla diff --git a/xpcom/base/nsMemoryImpl.cpp b/xpcom/base/nsMemoryImpl.cpp index 4996d27c7a..1529455da2 100644 --- a/xpcom/base/nsMemoryImpl.cpp +++ b/xpcom/base/nsMemoryImpl.cpp @@ -15,6 +15,7 @@ #include "nsCOMPtr.h" #include "mozilla/Services.h" #include "mozilla/Atomics.h" +#include "mozilla/IntegerPrintfMacros.h" #ifdef ANDROID # include <stdio.h> diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp index 2b567bda98..8bfd6577b3 100644 --- a/xpcom/base/nsSystemInfo.cpp +++ b/xpcom/base/nsSystemInfo.cpp @@ -16,6 +16,7 @@ #include "mozilla/LookAndFeel.h" #include "mozilla/Sprintf.h" #include "mozilla/Try.h" +#include "mozilla/Vector.h" #include "jsapi.h" #include "js/PropertyAndElement.h" // JS_SetProperty #include "mozilla/dom/Promise.h" @@ -799,51 +800,347 @@ nsresult CollectProcessInfo(ProcessInfo& info) { std::map<nsCString, nsCString> keyValuePairs; SimpleParseKeyValuePairs("/proc/cpuinfo", keyValuePairs); +# if defined(__arm__) || defined(__aarch64__) + // The tables below were taken from + // https://raw.githubusercontent.com/util-linux/util-linux/e3192bfd1dd129c70f5416e1464135d8cd22c956/sys-utils/lscpu-arm.c + + /* clang-format off */ + struct id_part { + const int id; + const char* name; + }; + + static const struct id_part arm_part[] = { + { 0x810, "ARM810" }, + { 0x920, "ARM920" }, + { 0x922, "ARM922" }, + { 0x926, "ARM926" }, + { 0x940, "ARM940" }, + { 0x946, "ARM946" }, + { 0x966, "ARM966" }, + { 0xa20, "ARM1020" }, + { 0xa22, "ARM1022" }, + { 0xa26, "ARM1026" }, + { 0xb02, "ARM11 MPCore" }, + { 0xb36, "ARM1136" }, + { 0xb56, "ARM1156" }, + { 0xb76, "ARM1176" }, + { 0xc05, "Cortex-A5" }, + { 0xc07, "Cortex-A7" }, + { 0xc08, "Cortex-A8" }, + { 0xc09, "Cortex-A9" }, + { 0xc0d, "Cortex-A17" }, /* Originally A12 */ + { 0xc0f, "Cortex-A15" }, + { 0xc0e, "Cortex-A17" }, + { 0xc14, "Cortex-R4" }, + { 0xc15, "Cortex-R5" }, + { 0xc17, "Cortex-R7" }, + { 0xc18, "Cortex-R8" }, + { 0xc20, "Cortex-M0" }, + { 0xc21, "Cortex-M1" }, + { 0xc23, "Cortex-M3" }, + { 0xc24, "Cortex-M4" }, + { 0xc27, "Cortex-M7" }, + { 0xc60, "Cortex-M0+" }, + { 0xd01, "Cortex-A32" }, + { 0xd02, "Cortex-A34" }, + { 0xd03, "Cortex-A53" }, + { 0xd04, "Cortex-A35" }, + { 0xd05, "Cortex-A55" }, + { 0xd06, "Cortex-A65" }, + { 0xd07, "Cortex-A57" }, + { 0xd08, "Cortex-A72" }, + { 0xd09, "Cortex-A73" }, + { 0xd0a, "Cortex-A75" }, + { 0xd0b, "Cortex-A76" }, + { 0xd0c, "Neoverse-N1" }, + { 0xd0d, "Cortex-A77" }, + { 0xd0e, "Cortex-A76AE" }, + { 0xd13, "Cortex-R52" }, + { 0xd15, "Cortex-R82" }, + { 0xd16, "Cortex-R52+" }, + { 0xd20, "Cortex-M23" }, + { 0xd21, "Cortex-M33" }, + { 0xd22, "Cortex-M55" }, + { 0xd23, "Cortex-M85" }, + { 0xd40, "Neoverse-V1" }, + { 0xd41, "Cortex-A78" }, + { 0xd42, "Cortex-A78AE" }, + { 0xd43, "Cortex-A65AE" }, + { 0xd44, "Cortex-X1" }, + { 0xd46, "Cortex-A510" }, + { 0xd47, "Cortex-A710" }, + { 0xd48, "Cortex-X2" }, + { 0xd49, "Neoverse-N2" }, + { 0xd4a, "Neoverse-E1" }, + { 0xd4b, "Cortex-A78C" }, + { 0xd4c, "Cortex-X1C" }, + { 0xd4d, "Cortex-A715" }, + { 0xd4e, "Cortex-X3" }, + { 0xd4f, "Neoverse-V2" }, + { 0xd80, "Cortex-A520" }, + { 0xd81, "Cortex-A720" }, + { 0xd82, "Cortex-X4" }, + { 0xd84, "Neoverse-V3" }, + { 0xd8e, "Neoverse-N3" }, + { -1, "unknown" }, + }; + + static const struct id_part brcm_part[] = { + { 0x0f, "Brahma-B15" }, + { 0x100, "Brahma-B53" }, + { 0x516, "ThunderX2" }, + { -1, "unknown" }, + }; + + static const struct id_part dec_part[] = { + { 0xa10, "SA110" }, + { 0xa11, "SA1100" }, + { -1, "unknown" }, + }; + + static const struct id_part cavium_part[] = { + { 0x0a0, "ThunderX" }, + { 0x0a1, "ThunderX-88XX" }, + { 0x0a2, "ThunderX-81XX" }, + { 0x0a3, "ThunderX-83XX" }, + { 0x0af, "ThunderX2-99xx" }, + { 0x0b0, "OcteonTX2" }, + { 0x0b1, "OcteonTX2-98XX" }, + { 0x0b2, "OcteonTX2-96XX" }, + { 0x0b3, "OcteonTX2-95XX" }, + { 0x0b4, "OcteonTX2-95XXN" }, + { 0x0b5, "OcteonTX2-95XXMM" }, + { 0x0b6, "OcteonTX2-95XXO" }, + { 0x0b8, "ThunderX3-T110" }, + { -1, "unknown" }, + }; + + static const struct id_part apm_part[] = { + { 0x000, "X-Gene" }, + { -1, "unknown" }, + }; + + static const struct id_part qcom_part[] = { + { 0x00f, "Scorpion" }, + { 0x02d, "Scorpion" }, + { 0x04d, "Krait" }, + { 0x06f, "Krait" }, + { 0x201, "Kryo" }, + { 0x205, "Kryo" }, + { 0x211, "Kryo" }, + { 0x800, "Falkor-V1/Kryo" }, + { 0x801, "Kryo-V2" }, + { 0x802, "Kryo-3XX-Gold" }, + { 0x803, "Kryo-3XX-Silver" }, + { 0x804, "Kryo-4XX-Gold" }, + { 0x805, "Kryo-4XX-Silver" }, + { 0xc00, "Falkor" }, + { 0xc01, "Saphira" }, + { -1, "unknown" }, + }; + + static const struct id_part samsung_part[] = { + { 0x001, "exynos-m1" }, + { 0x002, "exynos-m3" }, + { 0x003, "exynos-m4" }, + { 0x004, "exynos-m5" }, + { -1, "unknown" }, + }; + + static const struct id_part nvidia_part[] = { + { 0x000, "Denver" }, + { 0x003, "Denver 2" }, + { 0x004, "Carmel" }, + { -1, "unknown" }, + }; + + static const struct id_part marvell_part[] = { + { 0x131, "Feroceon-88FR131" }, + { 0x581, "PJ4/PJ4b" }, + { 0x584, "PJ4B-MP" }, + { -1, "unknown" }, + }; + + static const struct id_part apple_part[] = { + { 0x000, "Swift" }, + { 0x001, "Cyclone" }, + { 0x002, "Typhoon" }, + { 0x003, "Typhoon/Capri" }, + { 0x004, "Twister" }, + { 0x005, "Twister/Elba/Malta" }, + { 0x006, "Hurricane" }, + { 0x007, "Hurricane/Myst" }, + { 0x008, "Monsoon" }, + { 0x009, "Mistral" }, + { 0x00b, "Vortex" }, + { 0x00c, "Tempest" }, + { 0x00f, "Tempest-M9" }, + { 0x010, "Vortex/Aruba" }, + { 0x011, "Tempest/Aruba" }, + { 0x012, "Lightning" }, + { 0x013, "Thunder" }, + { 0x020, "Icestorm-A14" }, + { 0x021, "Firestorm-A14" }, + { 0x022, "Icestorm-M1" }, + { 0x023, "Firestorm-M1" }, + { 0x024, "Icestorm-M1-Pro" }, + { 0x025, "Firestorm-M1-Pro" }, + { 0x026, "Thunder-M10" }, + { 0x028, "Icestorm-M1-Max" }, + { 0x029, "Firestorm-M1-Max" }, + { 0x030, "Blizzard-A15" }, + { 0x031, "Avalanche-A15" }, + { 0x032, "Blizzard-M2" }, + { 0x033, "Avalanche-M2" }, + { 0x034, "Blizzard-M2-Pro" }, + { 0x035, "Avalanche-M2-Pro" }, + { 0x036, "Sawtooth-A16" }, + { 0x037, "Everest-A16" }, + { 0x038, "Blizzard-M2-Max" }, + { 0x039, "Avalanche-M2-Max" }, + { -1, "unknown" }, + }; + + static const struct id_part faraday_part[] = { + { 0x526, "FA526" }, + { 0x626, "FA626" }, + { -1, "unknown" }, + }; + + static const struct id_part intel_part[] = { + { 0x200, "i80200" }, + { 0x210, "PXA250A" }, + { 0x212, "PXA210A" }, + { 0x242, "i80321-400" }, + { 0x243, "i80321-600" }, + { 0x290, "PXA250B/PXA26x" }, + { 0x292, "PXA210B" }, + { 0x2c2, "i80321-400-B0" }, + { 0x2c3, "i80321-600-B0" }, + { 0x2d0, "PXA250C/PXA255/PXA26x" }, + { 0x2d2, "PXA210C" }, + { 0x411, "PXA27x" }, + { 0x41c, "IPX425-533" }, + { 0x41d, "IPX425-400" }, + { 0x41f, "IPX425-266" }, + { 0x682, "PXA32x" }, + { 0x683, "PXA930/PXA935" }, + { 0x688, "PXA30x" }, + { 0x689, "PXA31x" }, + { 0xb11, "SA1110" }, + { 0xc12, "IPX1200" }, + { -1, "unknown" }, + }; + + static const struct id_part fujitsu_part[] = { + { 0x001, "A64FX" }, + { -1, "unknown" }, + }; + + static const struct id_part hisi_part[] = { + { 0xd01, "TaiShan-v110" }, /* used in Kunpeng-920 SoC */ + { 0xd02, "TaiShan-v120" }, /* used in Kirin 990A and 9000S SoCs */ + { 0xd40, "Cortex-A76" }, /* HiSilicon uses this ID though advertises A76 */ + { 0xd41, "Cortex-A77" }, /* HiSilicon uses this ID though advertises A77 */ + { -1, "unknown" }, + }; + + static const struct id_part ampere_part[] = { + { 0xac3, "Ampere-1" }, + { 0xac4, "Ampere-1a" }, + { -1, "unknown" }, + }; + + static const struct id_part ft_part[] = { + { 0x303, "FTC310" }, + { 0x660, "FTC660" }, + { 0x661, "FTC661" }, + { 0x662, "FTC662" }, + { 0x663, "FTC663" }, + { 0x664, "FTC664" }, + { 0x862, "FTC862" }, + { -1, "unknown" }, + }; + + static const struct id_part ms_part[] = { + { 0xd49, "Azure-Cobalt-100" }, + { -1, "unknown" }, + }; + + static const struct id_part unknown_part[] = { + { -1, "unknown" }, + }; + + struct hw_impl { + const int id; + const struct id_part *parts; + const char *name; + }; + + static const struct hw_impl hw_implementer[] = { + { 0x41, arm_part, "ARM" }, + { 0x42, brcm_part, "Broadcom" }, + { 0x43, cavium_part, "Cavium" }, + { 0x44, dec_part, "DEC" }, + { 0x46, fujitsu_part, "FUJITSU" }, + { 0x48, hisi_part, "HiSilicon" }, + { 0x49, unknown_part, "Infineon" }, + { 0x4d, unknown_part, "Motorola/Freescale" }, + { 0x4e, nvidia_part, "NVIDIA" }, + { 0x50, apm_part, "APM" }, + { 0x51, qcom_part, "Qualcomm" }, + { 0x53, samsung_part, "Samsung" }, + { 0x56, marvell_part, "Marvell" }, + { 0x61, apple_part, "Apple" }, + { 0x66, faraday_part, "Faraday" }, + { 0x69, intel_part, "Intel" }, + { 0x6d, ms_part, "Microsoft" }, + { 0x70, ft_part, "Phytium" }, + { 0xc0, ampere_part, "Ampere" }, + { -1, unknown_part, "unknown" }, + }; + /* clang-format on */ + + // cpuFamily from "CPU implementer". Technically, this is only the vendor, + // but this is the closed to a family we can get. + (void)Tokenizer(keyValuePairs["CPU implementer"_ns]) + .ReadHexadecimal(&cpuFamily); + + // cpuModel from "CPU part". Not exactly a model number, but close enough, + // and that's what lscpu uses. + (void)Tokenizer(keyValuePairs["CPU part"_ns]).ReadHexadecimal(&cpuModel); + + // cpuStepping from "CPU variant" (that's what lscpu uses). + (void)Tokenizer(keyValuePairs["CPU variant"_ns]) + .ReadHexadecimal(&cpuStepping); + + for (auto& hw_impl : hw_implementer) { + if (hw_impl.id == (int)cpuFamily) { + info.cpuVendor.Assign(hw_impl.name); + for (auto* p = &hw_impl.parts[0]; p->id != -1; ++p) { + if (p->id == (int)cpuModel) { + info.cpuName.Assign(p->name); + } + } + } + } +# else // 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()); - } - } + // cpuFamily from "cpu family" + (void)Tokenizer(keyValuePairs["cpu family"_ns]).ReadInteger(&cpuFamily); - { - // 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()); - } - } + // cpuModel from "model" + (void)Tokenizer(keyValuePairs["model"_ns]).ReadInteger(&cpuModel); - { - // 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()); - } - } + // cpuStepping from "stepping" + (void)Tokenizer(keyValuePairs["stepping"_ns]).ReadInteger(&cpuStepping); +# endif } { @@ -852,12 +1149,8 @@ nsresult CollectProcessInfo(ProcessInfo& info) { "/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); - } + (void)Tokenizer(line.c_str()).ReadInteger(&cpuSpeed); + cpuSpeed /= 1000; } } @@ -866,12 +1159,7 @@ nsresult CollectProcessInfo(ProcessInfo& info) { 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()); - } + (void)Tokenizer(line.c_str(), nullptr, "K").ReadInteger(&cacheSizeL2); } } @@ -880,16 +1168,111 @@ nsresult CollectProcessInfo(ProcessInfo& info) { 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()); - } + (void)Tokenizer(line.c_str(), nullptr, "K").ReadInteger(&cacheSizeL3); } } info.cpuCount = PR_GetNumberOfProcessors(); + int max_cpu_bits = [&] { + // PR_GetNumberOfProcessors gets the value from + // /sys/devices/system/cpu/present, but the number of bits in the CPU masks + // we're going to read below can be larger (for instance, on the 32-core + // 64-threads Threadripper 3970X, PR_GetNumberOfProcessors returns 64, but + // the number of bits in the CPU masks is 128). That number of bits is + // correlated with the number of CPUs possible (which is different from the + // number of CPUs present). + std::ifstream input("/sys/devices/system/cpu/possible"); + std::string line; + if (getline(input, line)) { + int num; + Tokenizer p(line.c_str()); + // The expected format is `0-n` where n is the number of CPUs possible + // - 1. + if (p.ReadInteger(&num) && num == 0 && p.CheckChar('-') && + p.ReadInteger(&num) && p.CheckEOF()) { + return num + 1; + } + } + // If we weren't able to get the value from /sys/devices/system/cpu/possible + // from some reason, fallback to cpuCount, it might work. + return info.cpuCount; + }(); + + // /proc/cpuinfo doesn't have a cross-architecture way of counting physical + // cores. On x86, one could look at the number of unique combinations of + // `physical id` and `core id` or `cpu cores`, but those are not present on + // e.g. aarch64. (and that might not even be enough for NUMA nodes, but + // realistically, there probably aren't a lot of people running this code + // on such machines) + // As a shortcut on x86, you'd think you could just multiply the last + // physical id + 1 with the last core id + 1, but at least core ids are not + // even necessarily adjacent. (notably, on 13th or 14th generation Intel + // CPUs, they go in increments of 4 for performance cores, and then 1 after + // hitting the first efficiency core) + // /sys/devices/system/cpu/cpu*/topology/core_cpus does show which logical + // cores are associated together, such that running the command: + // sort -u /sys/devices/system/cpu/cpu*/topology/core_cpus | wc -l + // gives a count of physical cores. + // There are cpuCount /sys/devices/system/cpu/cpu* directories, and they + // are monotonically increasing. + // We're going to kind of do that, but reading the actual bitmasks contained + // in those files. + constexpr int mask_bits = sizeof(uint32_t) * 8; + + Vector<uint32_t> cpumasks; + physicalCPUs = [&] { + int cores = 0; + if (!cpumasks.appendN(0, (max_cpu_bits + mask_bits - 1) / mask_bits)) { + return -1; + } + for (int32_t cpu = 0; cpu < info.cpuCount; ++cpu) { + nsPrintfCString core_cpus( + "/sys/devices/system/cpu/cpu%d/topology/core_cpus", cpu); + std::ifstream input(core_cpus.Data()); + // Kernel versions before 5.3 didn't have core_cpus, they had + // thread_siblings instead, with the same content. As of writing, kernel + // version 6.9 still has both, but thread_siblings has been deprecated + // since the introduction of core_cpus. + if (input.fail()) { + core_cpus.Truncate(core_cpus.Length() - sizeof("core_cpus") + 1); + core_cpus.AppendLiteral("thread_siblings"); + input.open(core_cpus.Data()); + } + std::string line; + if (!getline(input, line)) { + return -1; + } + Tokenizer p(line.c_str()); + bool unknown_core = false; + // The format of the file is `bitmask0,bitmask1,..,bitmaskn` + // where each bitmask is 32-bits wide, and there are as many as + // necessary to print max_cpu_bits bits. + for (auto& mask : cpumasks) { + uint32_t m; + if (NS_WARN_IF(!p.ReadHexadecimal(&m, /* aPrefixed = */ false))) { + return -1; + } + if (!p.CheckEOF() && !p.CheckChar(',')) { + return -1; + } + // We're keeping track of all the CPU bits we've seen so far. + // If we're now seeing one that has never been set, it means + // we're seeing a new physical core (as opposed to a logical + // core). We don't want to end the loop now, though, because + // we also want to track all the bits we're seeing, in case + // subsequent masks have new bits as well. + if ((mask & m) != m) { + unknown_core = true; + } + mask |= m; + } + if (unknown_core) { + cores++; + } + } + return cores; + }(); + #else info.cpuCount = PR_GetNumberOfProcessors(); #endif @@ -1069,11 +1452,13 @@ nsresult nsSystemInfo::Init() { return rv; } - nsString pointerExplanation; - widget::WinUtils::GetPointerExplanation(&pointerExplanation); - rv = SetPropertyAsAString(u"pointingDevices"_ns, pointerExplanation); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; + if (XRE_IsParentProcess()) { + nsString pointerExplanation; + widget::WinUtils::GetPointerExplanation(&pointerExplanation); + rv = SetPropertyAsAString(u"pointingDevices"_ns, pointerExplanation); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } } #endif @@ -1374,7 +1759,8 @@ JSObject* GetJSObjForProcessInfo(JSContext* aCx, const ProcessInfo& info) { 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::Rooted<JS::Value> valCoreInfo( + aCx, info.cpuCores ? JS::Int32Value(info.cpuCores) : JS::NullValue()); JS_SetProperty(aCx, jsInfo, "cores", valCoreInfo); JSString* strVendor = diff --git a/xpcom/build/PoisonIOInterposer.h b/xpcom/build/PoisonIOInterposer.h index 20adeb835b..3217027b9a 100644 --- a/xpcom/build/PoisonIOInterposer.h +++ b/xpcom/build/PoisonIOInterposer.h @@ -10,6 +10,12 @@ #include "mozilla/Types.h" #include <stdio.h> +#ifdef _WIN32 +typedef void* platform_handle_t; +#else +typedef int platform_handle_t; +#endif + MOZ_BEGIN_EXTERN_C /** Register file handle to be ignored by poisoning IO interposer. This function @@ -18,7 +24,7 @@ MOZ_BEGIN_EXTERN_C * 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); +void MozillaRegisterDebugHandle(platform_handle_t aHandle); /** Register file descriptor to be ignored by poisoning IO interposer */ void MozillaRegisterDebugFD(int aFd); @@ -27,7 +33,7 @@ void MozillaRegisterDebugFD(int aFd); void MozillaRegisterDebugFILE(FILE* aFile); /** Unregister file handle from being ignored by poisoning IO interposer */ -void MozillaUnRegisterDebugHandle(intptr_t aHandle); +void MozillaUnRegisterDebugHandle(platform_handle_t aHandle); /** Unregister file descriptor from being ignored by poisoning IO interposer */ void MozillaUnRegisterDebugFD(int aFd); @@ -45,7 +51,7 @@ namespace mozilla { /** * Check if a file is registered as a debug file. */ -bool IsDebugFile(intptr_t aFileID); +bool IsDebugFile(platform_handle_t aFileID); /** * Initialize IO poisoning, this is only safe to do on the main-thread when no @@ -79,7 +85,7 @@ void ClearPoisonIOInterposer(); # ifdef __cplusplus namespace mozilla { -inline bool IsDebugFile(intptr_t aFileID) { return true; } +inline bool IsDebugFile(platform_handle_t aFileID) { return true; } inline void InitPoisonIOInterposer() {} inline void ClearPoisonIOInterposer() {} # ifdef XP_MACOSX diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp index 0a25a3d1f8..268c5672a8 100644 --- a/xpcom/build/PoisonIOInterposerBase.cpp +++ b/xpcom/build/PoisonIOInterposerBase.cpp @@ -21,17 +21,18 @@ // Auxiliary method to convert file descriptors to ids #if defined(XP_WIN) # include <io.h> -inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) { +inline mozilla::Maybe<platform_handle_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); + return mozilla::Some<platform_handle_t>( + reinterpret_cast<platform_handle_t>(handle)); } #else -inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) { - return mozilla::Some<intptr_t>(aFd); +inline mozilla::Maybe<platform_handle_t> FileDescriptorToHandle(int aFd) { + return mozilla::Some<platform_handle_t>(static_cast<platform_handle_t>(aFd)); } #endif /* if not XP_WIN */ @@ -161,7 +162,7 @@ class ChunkedList { } }; -typedef ChunkedList<intptr_t> FdList; +typedef ChunkedList<platform_handle_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. @@ -176,7 +177,7 @@ 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) { +bool IsDebugFile(platform_handle_t aFileID) { return getDebugFileIDs().Contains(aFileID); } @@ -184,7 +185,7 @@ bool IsDebugFile(intptr_t aFileID) { extern "C" { -void MozillaRegisterDebugHandle(intptr_t aHandle) { +void MozillaRegisterDebugHandle(platform_handle_t aHandle) { DebugFilesAutoLock lockedScope; FdList& DebugFileIDs = getDebugFileIDs(); MOZ_ASSERT(!DebugFileIDs.Contains(aHandle)); @@ -192,7 +193,7 @@ void MozillaRegisterDebugHandle(intptr_t aHandle) { } void MozillaRegisterDebugFD(int aFd) { - mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd); + mozilla::Maybe<platform_handle_t> handle = FileDescriptorToHandle(aFd); if (!handle.isSome()) { return; } @@ -207,7 +208,7 @@ void MozillaRegisterDebugFILE(FILE* aFile) { MozillaRegisterDebugFD(fd); } -void MozillaUnRegisterDebugHandle(intptr_t aHandle) { +void MozillaUnRegisterDebugHandle(platform_handle_t aHandle) { DebugFilesAutoLock lockedScope; FdList& DebugFileIDs = getDebugFileIDs(); MOZ_ASSERT(DebugFileIDs.Contains(aHandle)); @@ -215,7 +216,7 @@ void MozillaUnRegisterDebugHandle(intptr_t aHandle) { } void MozillaUnRegisterDebugFD(int aFd) { - mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd); + mozilla::Maybe<platform_handle_t> handle = FileDescriptorToHandle(aFd); if (!handle.isSome()) { return; } @@ -234,11 +235,11 @@ void MozillaUnRegisterDebugFILE(FILE* aFile) { } // extern "C" #ifdef MOZ_REPLACE_MALLOC -void mozilla::DebugFdRegistry::RegisterHandle(intptr_t aHandle) { +void mozilla::DebugFdRegistry::RegisterHandle(platform_handle_t aHandle) { MozillaRegisterDebugHandle(aHandle); } -void mozilla::DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) { +void mozilla::DebugFdRegistry::UnRegisterHandle(platform_handle_t aHandle) { MozillaUnRegisterDebugHandle(aHandle); } #endif diff --git a/xpcom/build/PoisonIOInterposerWin.cpp b/xpcom/build/PoisonIOInterposerWin.cpp index ad9a11dbb1..7c37a8cfe1 100644 --- a/xpcom/build/PoisonIOInterposerWin.cpp +++ b/xpcom/build/PoisonIOInterposerWin.cpp @@ -127,8 +127,7 @@ class WinIOAutoObservation : public mozilla::IOInterposeObserver::Observation { WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, HANDLE aFileHandle, const LARGE_INTEGER* aOffset) : mozilla::IOInterposeObserver::Observation( - aOp, sReference, - !mozilla::IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle))), + aOp, sReference, !mozilla::IsDebugFile(aFileHandle)), mFileHandle(aFileHandle), mFileHandleType(GetFileType(aFileHandle)), mHasQueriedFilename(false) { diff --git a/xpcom/build/XREShellData.h b/xpcom/build/XREShellData.h index a0f736f658..8fa47ab459 100644 --- a/xpcom/build/XREShellData.h +++ b/xpcom/build/XREShellData.h @@ -10,6 +10,9 @@ #if defined(LIBFUZZER) # include "FuzzerRegistry.h" // LibFuzzerDriver #endif +#if defined(AFLFUZZ) +# include "FuzzingInterface.h" // FuzzingTestFuncRaw +#endif #if defined(XP_WIN) && defined(MOZ_SANDBOX) namespace sandbox { @@ -34,6 +37,9 @@ struct XREShellData { #if defined(LIBFUZZER) LibFuzzerDriver fuzzerDriver; #endif +#if defined(AFLFUZZ) + int (*fuzzerDriver)(FuzzingTestFuncRaw); +#endif }; #endif // XREShellData_h diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py index 5be3323986..d50d31a6a3 100644 --- a/xpcom/ds/StaticAtoms.py +++ b/xpcom/ds/StaticAtoms.py @@ -386,6 +386,8 @@ STATIC_ATOMS = [ Atom("docNoteref", "doc-noteref"), Atom("docNotice", "doc-notice"), Atom("docPagebreak", "doc-pagebreak"), + Atom("docPagefooter", "doc-pagefooter"), + Atom("docPageheader", "doc-pageheader"), Atom("docPagelist", "doc-pagelist"), Atom("docPart", "doc-part"), Atom("docPreface", "doc-preface"), @@ -448,6 +450,7 @@ STATIC_ATOMS = [ Atom("figcaption", "figcaption"), Atom("figure", "figure"), Atom("findbar", "findbar"), + Atom("firstColumn", "first-column"), Atom("firstInput", "first-input"), Atom("fixed", "fixed"), Atom("flags", "flags"), @@ -969,6 +972,7 @@ STATIC_ATOMS = [ Atom("option", "option"), Atom("_or", "or"), Atom("order", "order"), + Atom("ordinal", "ordinal"), Atom("orient", "orient"), Atom("orientation", "orientation"), Atom("origin_trial", "origin-trial"), @@ -2461,6 +2465,7 @@ STATIC_ATOMS = [ Atom("DirectoryService_OS_SystemConfigDir", "SysConfD"), # Atom("DirectoryService_OS_HomeDirectory", "Home"), # "Home" is present above Atom("DirectoryService_OS_DesktopDirectory", "Desk"), + Atom("DirectoryService_OS_DocumentsDirectory", "Docs"), Atom("DirectoryService_InitCurrentProcess_dummy", "MozBinD"), Atom("DirectoryService_SystemDirectory", "SysD"), Atom("DirectoryService_UserLibDirectory", "ULibDir"), diff --git a/xpcom/ds/Tokenizer.h b/xpcom/ds/Tokenizer.h index 713b63f269..6a4ec89d51 100644 --- a/xpcom/ds/Tokenizer.h +++ b/xpcom/ds/Tokenizer.h @@ -439,6 +439,66 @@ class TTokenizer : public TokenizerBase<TChar> { } /** + * This is an hexadecimal 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 0, and it's not followed by x + * - the token(s) that follow don't make a valid hexadecimal number + * - 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 ReadHexadecimal(T* aValue, bool aPrefixed = true) { + 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; + }); + + if (aPrefixed) { + typename base::Token t; + if (!Check(base::TOKEN_INTEGER, t) && t.AsInteger() != 0) { + return false; + } + + if (!CheckChar([](const TChar aChar) { return aChar == 'x'; })) { + return false; + } + } + + TChar c = 'z'; + mozilla::CheckedInt<T> resultingNumber = 0; + while (ReadChar( + [](const TChar aChar) { + return (aChar >= '0' && aChar <= '9') || + (aChar >= 'A' && aChar <= 'F') || + (aChar >= 'a' && aChar <= 'f'); + }, + &c)) { + resultingNumber *= 16; + if (c <= '9') { + resultingNumber += static_cast<uint64_t>(c - '0'); + } else if (c <= 'F') { + resultingNumber += static_cast<uint64_t>(c - 'A') + 0xa; + } else { + resultingNumber += static_cast<uint64_t>(c - 'a') + 0xa; + } + } + if (c == 'z' || !resultingNumber.isValid()) { + return false; + } + + *aValue = resultingNumber.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 diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp index a03fdcce53..379aa647d4 100644 --- a/xpcom/ds/nsAtomTable.cpp +++ b/xpcom/ds/nsAtomTable.cpp @@ -82,7 +82,7 @@ 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); + RefPtr<nsStringBuffer> buffer = aString.GetStringBuffer(); if (!buffer) { buffer = nsStringBuffer::Create(aString.Data(), aString.Length()); if (MOZ_UNLIKELY(!buffer)) { @@ -111,7 +111,7 @@ void nsAtom::ToString(nsAString& aString) const { // which is what's important. aString.AssignLiteral(AsStatic()->String(), mLength); } else { - AsDynamic()->StringBuffer()->ToString(mLength, aString); + aString.Assign(AsDynamic()->StringBuffer(), mLength); } } @@ -577,7 +577,7 @@ already_AddRefed<nsAtom> nsAtomTable::Atomize(const nsACString& aUTF8String) { nsString str; CopyUTF8toUTF16(aUTF8String, str); - MOZ_ASSERT(nsStringBuffer::FromString(str), "Should create a string buffer"); + MOZ_ASSERT(str.GetStringBuffer(), "Should create a string buffer"); RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(str, key.mHash)); he->mAtom = atom; diff --git a/xpcom/ds/nsTArray.h b/xpcom/ds/nsTArray.h index 56f926ccad..c088f1c4bd 100644 --- a/xpcom/ds/nsTArray.h +++ b/xpcom/ds/nsTArray.h @@ -369,13 +369,6 @@ 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. @@ -393,11 +386,6 @@ class nsTArray_base { 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; diff --git a/xpcom/idl-parser/xpidl/fixtures/xpctest.d.json b/xpcom/idl-parser/xpidl/fixtures/xpctest.d.json index 087a323dd4..b81760c0f5 100644 --- a/xpcom/idl-parser/xpidl/fixtures/xpctest.d.json +++ b/xpcom/idl-parser/xpidl/fixtures/xpctest.d.json @@ -1313,7 +1313,7 @@ "callable": false, "consts": [], "enums": [], - "id": "nsIXPCTestNoScriptMembers", + "id": "nsIXPCTestTypeScript", "members": [ { "name": "exposedProp", diff --git a/xpcom/idl-parser/xpidl/xpidl.py b/xpcom/idl-parser/xpidl/xpidl.py index 11b5d05e58..59e4e355de 100755 --- a/xpcom/idl-parser/xpidl/xpidl.py +++ b/xpcom/idl-parser/xpidl/xpidl.py @@ -524,6 +524,8 @@ class Typedef(object): return "%s%s" % ("*mut " if "out" in calltype else "", self.name) def tsType(self): + # Make sure that underlying type is supported: doesn't throw TSNoncompat. + self.realtype.tsType() return self.name def __str__(self): @@ -1214,19 +1216,24 @@ def ensureInfallibleIsSound(methodOrAttribute): if methodOrAttribute.notxpcom: raise IDLError( - "[infallible] does not make sense for a [notxpcom] " "method or attribute", + "[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. +# method or attribute, or uses a by-value native type, 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 + + # notxpcom and nostdcall types change calling conventions, which breaks + # xptcall wrappers. We cannot allow XPCWrappedJS to be created for + # interfaces with these methods. if methodOrAttribute.notxpcom: raise IDLError( ( @@ -1246,6 +1253,84 @@ def ensureBuiltinClassIfNeeded(methodOrAttribute): methodOrAttribute.location, ) + # Methods with custom native parameters passed without indirection cannot be + # safely handled by xptcall (as it cannot know the calling stack/register + # layout), so require the interface to be builtinclass. + # + # Only "in" parameters and writable attributes are checked, as other + # parameters are always passed indirectly, so do not impact calling + # conventions. + def typeNeedsBuiltinclass(type): + inner = type + while inner.kind == "typedef": + inner = inner.realtype + return ( + inner.kind == "native" + and inner.specialtype is None + and inner.modifier is None + ) + + if methodOrAttribute.kind == "method": + for p in methodOrAttribute.params: + if p.paramtype == "in" and typeNeedsBuiltinclass(p.realtype): + raise IDLError( + ( + "scriptable interface '%s' must be marked [builtinclass] " + "because it contains method '%s' with a by-value custom native " + "parameter '%s'" + ) + % (iface.name, methodOrAttribute.name, p.name), + methodOrAttribute.location, + ) + elif methodOrAttribute.kind == "attribute" and not methodOrAttribute.readonly: + if typeNeedsBuiltinclass(methodOrAttribute.realtype): + raise IDLError( + ( + "scriptable interface '%s' must be marked [builtinclass] because it " + "contains writable attribute '%s' with a by-value custom native type" + ) + % (iface.name, methodOrAttribute.name), + methodOrAttribute.location, + ) + + +def ensureNoscriptIfNeeded(methodOrAttribute): + if not methodOrAttribute.isScriptable(): + return + + # NOTE: We can't check forward-declared interfaces to see if they're + # scriptable, as the information about whether they're scriptable is not + # known here. + def typeNeedsNoscript(type): + if type.kind in ["array", "legacyarray"]: + return typeNeedsNoscript(type.type) + if type.kind == "typedef": + return typeNeedsNoscript(type.realtype) + if type.kind == "native": + return type.specialtype is None + if type.kind == "interface": + return not type.attributes.scriptable + return False + + if typeNeedsNoscript(methodOrAttribute.realtype): + raise IDLError( + "%s '%s' must be marked [noscript] because it has a non-scriptable type" + % (methodOrAttribute.kind, methodOrAttribute.name), + methodOrAttribute.location, + ) + if methodOrAttribute.kind == "method": + for p in methodOrAttribute.params: + # iid_is arguments have their type ignored, so shouldn't be checked. + if not p.iid_is and typeNeedsNoscript(p.realtype): + raise IDLError( + ( + "method '%s' must be marked [noscript] because it has a " + "non-scriptable parameter '%s'" + ) + % (methodOrAttribute.name, p.name), + methodOrAttribute.location, + ) + class Attribute(object): kind = "attribute" @@ -1334,6 +1419,7 @@ class Attribute(object): ensureInfallibleIsSound(self) ensureBuiltinClassIfNeeded(self) + ensureNoscriptIfNeeded(self) def toIDL(self): attribs = attlistToIDL(self.attlist) @@ -1420,9 +1506,6 @@ class Method(object): 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: @@ -1462,6 +1545,10 @@ class Method(object): self.location, ) + ensureInfallibleIsSound(self) + ensureBuiltinClassIfNeeded(self) + ensureNoscriptIfNeeded(self) + def isScriptable(self): if not self.iface.attributes.scriptable: return False diff --git a/xpcom/io/SpecialSystemDirectory.cpp b/xpcom/io/SpecialSystemDirectory.cpp index 4b1055f3fe..419e783a31 100644 --- a/xpcom/io/SpecialSystemDirectory.cpp +++ b/xpcom/io/SpecialSystemDirectory.cpp @@ -94,7 +94,6 @@ static nsresult GetWindowsFolder(int aFolder, nsIFile** aFile) { 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. @@ -122,7 +121,6 @@ static nsresult GetLibrarySaveToPath(int aFallbackFolderId, return GetWindowsFolder(aFallbackFolderId, aFile); } -# endif /** * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by @@ -527,6 +525,9 @@ nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, case Mac_UserDesktopDirectory: { return GetOSXFolderType(kUserDomain, kDesktopFolderType, aFile); } + case Mac_UserDocumentsDirectory: { + return GetOSXFolderType(kUserDomain, kDocumentsFolderType, aFile); + } case Mac_LocalApplicationsDirectory: { return GetOSXFolderType(kLocalDomain, kApplicationsFolderType, aFile); } @@ -674,12 +675,10 @@ nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, } 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) @@ -687,6 +686,7 @@ nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, return GetUnixHomeDir(aFile); case Unix_XDG_Desktop: + case Unix_XDG_Documents: case Unix_XDG_Download: return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile); diff --git a/xpcom/io/SpecialSystemDirectory.h b/xpcom/io/SpecialSystemDirectory.h index e760b0ae26..f1bc101e44 100644 --- a/xpcom/io/SpecialSystemDirectory.h +++ b/xpcom/io/SpecialSystemDirectory.h @@ -30,6 +30,7 @@ enum SystemDirectories { Mac_UserPreferencesDirectory = 107, Mac_PictureDocumentsDirectory = 108, Mac_DefaultScreenshotDirectory = 109, + Mac_UserDocumentsDirectory = 110, Win_SystemDirectory = 201, Win_WindowsDirectory = 202, @@ -42,14 +43,13 @@ enum SystemDirectories { 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_Documents = 305, Unix_XDG_Download = 306, - Unix_SystemConfigDirectory = 307 + Unix_SystemConfigDirectory = 307, }; nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, diff --git a/xpcom/io/nsDirectoryService.cpp b/xpcom/io/nsDirectoryService.cpp index f5e841c6ea..d5b6c7710c 100644 --- a/xpcom/io/nsDirectoryService.cpp +++ b/xpcom/io/nsDirectoryService.cpp @@ -367,6 +367,9 @@ nsDirectoryService::GetFile(const char* aProp, bool* aPersistent, } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { rv = GetSpecialSystemDirectory(Mac_UserDesktopDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DocumentsDirectory) { + rv = GetSpecialSystemDirectory(Mac_UserDocumentsDirectory, + getter_AddRefs(localFile)); } else if (inAtom == nsGkAtoms::DirectoryService_LocalApplicationsDirectory) { rv = GetSpecialSystemDirectory(Mac_LocalApplicationsDirectory, getter_AddRefs(localFile)); @@ -399,6 +402,8 @@ nsDirectoryService::GetFile(const char* aProp, bool* aPersistent, } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { rv = GetSpecialSystemDirectory(Win_Desktopdirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DocumentsDirectory) { + rv = GetSpecialSystemDirectory(Win_Documents, getter_AddRefs(localFile)); } else if (inAtom == nsGkAtoms::DirectoryService_Appdata) { rv = GetSpecialSystemDirectory(Win_Appdata, getter_AddRefs(localFile)); } else if (inAtom == nsGkAtoms::DirectoryService_LocalAppdata) { @@ -422,6 +427,9 @@ nsDirectoryService::GetFile(const char* aProp, bool* aPersistent, } else if (inAtom == nsGkAtoms::DirectoryService_OS_SystemConfigDir) { rv = GetSpecialSystemDirectory(Unix_SystemConfigDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DocumentsDirectory) { + rv = GetSpecialSystemDirectory(Unix_XDG_Documents, + getter_AddRefs(localFile)); } #endif diff --git a/xpcom/io/nsDirectoryServiceDefs.h b/xpcom/io/nsDirectoryServiceDefs.h index 9f0368ff06..e2458abece 100644 --- a/xpcom/io/nsDirectoryServiceDefs.h +++ b/xpcom/io/nsDirectoryServiceDefs.h @@ -33,6 +33,7 @@ * if there is one, otherwise it's just the same as "Home" */ #define NS_OS_DESKTOP_DIR "Desk" +#define NS_OS_DOCUMENTS_DIR "Docs" #define NS_OS_DEFAULT_DOWNLOAD_DIR "DfltDwnld" diff --git a/xpcom/io/nsIConverterInputStream.idl b/xpcom/io/nsIConverterInputStream.idl index ad1f9bfbc4..1474564a37 100644 --- a/xpcom/io/nsIConverterInputStream.idl +++ b/xpcom/io/nsIConverterInputStream.idl @@ -12,7 +12,7 @@ interface nsIInputStream; * This allows reading unicode strings from a stream, automatically converting * the bytes from a selected character encoding. */ -[scriptable, uuid(FC66FFB6-5404-4908-A4A3-27F92FA0579D)] +[scriptable, builtinclass, uuid(FC66FFB6-5404-4908-A4A3-27F92FA0579D)] interface nsIConverterInputStream : nsIUnicharInputStream { /** * Default replacement char value, U+FFFD REPLACEMENT CHARACTER. diff --git a/xpcom/io/nsIUnicharInputStream.idl b/xpcom/io/nsIUnicharInputStream.idl index 3ae467cc83..5f1b4a6d8e 100644 --- a/xpcom/io/nsIUnicharInputStream.idl +++ b/xpcom/io/nsIUnicharInputStream.idl @@ -42,7 +42,7 @@ native nsWriteUnicharSegmentFun(nsWriteUnicharSegmentFun); * Abstract UTF-16 input stream * @see nsIInputStream */ -[scriptable, uuid(d5e3bd80-6723-4b92-b0c9-22f6162fd94f)] +[scriptable, builtinclass, uuid(d5e3bd80-6723-4b92-b0c9-22f6162fd94f)] interface nsIUnicharInputStream : nsISupports { /** * Reads into a caller-provided array. diff --git a/xpcom/io/nsInputStreamTee.cpp b/xpcom/io/nsInputStreamTee.cpp index 3c0d32e0cb..656f1dcb38 100644 --- a/xpcom/io/nsInputStreamTee.cpp +++ b/xpcom/io/nsInputStreamTee.cpp @@ -10,6 +10,7 @@ #include "mozilla/Maybe.h" #include "mozilla/Mutex.h" #include "mozilla/Attributes.h" +#include "mozilla/IntegerPrintfMacros.h" #include "nsIInputStreamTee.h" #include "nsIInputStream.h" #include "nsIOutputStream.h" diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp index 39a6b406f4..d66f300649 100644 --- a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp @@ -164,6 +164,18 @@ NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, vtable = *reinterpret_cast<vtable_func **>(that); func = vtable[methodIndex]; +/* !!! IMPORTANT !!! + * In the case of paramCount = 0 (and also some other cases in practice but + * the compiler doesn't know about them), the stack_space is not initialized. + * Reading the stack_space is technically undefined behavior, but practically, + * the values we read from there only matter to the called function when they + * are initialized. + * The asm volatile block makes the compiler ignore that the stack_space + * may not be initialized, avoiding it optimizing away e.g. the first loop + * test in invoke_copy_to_stack. + */ + asm volatile(";"); + return func(that, stack_space[base_size * 2 - 3], stack_space[base_size * 2 - 2], stack_space[base_size * 2 - 1]); diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_loongarch64.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_loongarch64.S index ae4e0cf73f..7dd6e3e1cd 100644 --- a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_loongarch64.S +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_loongarch64.S @@ -1,3 +1,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/. diff --git a/xpcom/reflect/xptinfo/xptcodegen.py b/xpcom/reflect/xptinfo/xptcodegen.py index 9dd54a6f07..67860ca1f2 100644 --- a/xpcom/reflect/xptinfo/xptcodegen.py +++ b/xpcom/reflect/xptinfo/xptcodegen.py @@ -330,30 +330,11 @@ def link_to_cpp(interfaces, fd, header_fd): ) ) - 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, builtinclass): methodname = "%s::%s" % (ifacename, method["name"]) isSymbol = "symbol" in method["flags"] - reflectable = is_method_reflectable(method) + reflectable = "hidden" not in method["flags"] if not reflectable and builtinclass: # Hide the parameters of methods that can't be called from JS and diff --git a/xpcom/string/moz.build b/xpcom/string/moz.build index c0f8091b8f..1220a16bc8 100644 --- a/xpcom/string/moz.build +++ b/xpcom/string/moz.build @@ -56,7 +56,4 @@ UNIFIED_SOURCES += [ "RustStringAPI.cpp", ] -if CONFIG["MOZ_DEBUG"]: - UNIFIED_SOURCES += ["nsStringStats.cpp"] - FINAL_LIBRARY = "xul" diff --git a/xpcom/string/nsStringBuffer.cpp b/xpcom/string/nsStringBuffer.cpp index b5d506333f..fbfc91f633 100644 --- a/xpcom/string/nsStringBuffer.cpp +++ b/xpcom/string/nsStringBuffer.cpp @@ -7,74 +7,7 @@ #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); -} +#include "mozilla/RefPtr.h" template <typename CharT> static already_AddRefed<nsStringBuffer> DoCreate(const CharT* aData, @@ -101,80 +34,31 @@ already_AddRefed<nsStringBuffer> nsStringBuffer::Create(const char16_t* aData, } 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"); + MOZ_ASSERT(aSize != 0, "zero capacity allocation not allowed"); + MOZ_ASSERT(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"); + MOZ_ASSERT(!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"); + { + mozilla::detail::RefCountLogger::ReleaseLogger logger(aHdr); + logger.logRelease(0); + } aHdr = (nsStringBuffer*)realloc(aHdr, sizeof(nsStringBuffer) + aSize); if (aHdr) { - NS_LOG_ADDREF(aHdr, 1, "nsStringBuffer", sizeof(*aHdr)); + mozilla::detail::RefCountLogger::logAddRef(aHdr, 1); 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); diff --git a/xpcom/string/nsStringBuffer.h b/xpcom/string/nsStringBuffer.h index 43628d6668..dad41e48f7 100644 --- a/xpcom/string/nsStringBuffer.h +++ b/xpcom/string/nsStringBuffer.h @@ -9,10 +9,9 @@ #include <atomic> #include "mozilla/MemoryReporting.h" -#include "nsStringFwd.h" - -template <class T> -struct already_AddRefed; +#include "mozilla/Assertions.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefCounted.h" /** * This structure precedes the string buffers "we" allocate. It may be the @@ -25,12 +24,12 @@ struct already_AddRefed; */ class nsStringBuffer { private: - friend class CheckStaticAtomSizes; - std::atomic<uint32_t> mRefCount; uint32_t mStorageSize; public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(nsStringBuffer) + /** * Allocates a new string buffer, with given size in bytes and a * reference count of one. When the string buffer is no longer needed, @@ -43,12 +42,25 @@ class nsStringBuffer { * (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 + * This guarantees that StorageSize() returns aSize 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); + static already_AddRefed<nsStringBuffer> Alloc(size_t aSize) { + MOZ_ASSERT(aSize != 0, "zero capacity allocation not allowed"); + MOZ_ASSERT(sizeof(nsStringBuffer) + aSize <= size_t(uint32_t(-1)) && + sizeof(nsStringBuffer) + aSize > aSize, + "mStorageSize will truncate"); + + auto* hdr = (nsStringBuffer*)malloc(sizeof(nsStringBuffer) + aSize); + if (hdr) { + hdr->mRefCount = 1; + hdr->mStorageSize = aSize; + mozilla::detail::RefCountLogger::logAddRef(hdr, 1); + } + return already_AddRefed(hdr); + } /** * Returns a string buffer initialized with the given string on it, or null on @@ -74,16 +86,35 @@ class nsStringBuffer { */ static nsStringBuffer* Realloc(nsStringBuffer* aBuf, size_t aStorageSize); - /** - * Increment the reference count on this string buffer. - */ - void NS_FASTCALL AddRef(); + void 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. + uint32_t count = mRefCount.fetch_add(1, std::memory_order_relaxed) + 1; + mozilla::detail::RefCountLogger::logAddRef(this, count); + } - /** - * Decrement the reference count on this string buffer. The string - * buffer will be destroyed when its reference count reaches zero. - */ - void NS_FASTCALL Release(); + void 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. + mozilla::detail::RefCountLogger::ReleaseLogger logger(this); + uint32_t count = mRefCount.fetch_sub(1, std::memory_order_release) - 1; + logger.logRelease(count); + 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); + free(this); // We were allocated with malloc. + } + } /** * This method returns the string buffer corresponding to the given data @@ -149,34 +180,6 @@ class nsStringBuffer { } /** - * 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( diff --git a/xpcom/string/nsStringStats.cpp b/xpcom/string/nsStringStats.cpp deleted file mode 100644 index 7fc3d82ad5..0000000000 --- a/xpcom/string/nsStringStats.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. 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 deleted file mode 100644 index a38304c2b7..0000000000 --- a/xpcom/string/nsStringStats.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. 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/nsTLiteralString.h b/xpcom/string/nsTLiteralString.h index 38ffd32bdb..0d14614583 100644 --- a/xpcom/string/nsTLiteralString.h +++ b/xpcom/string/nsTLiteralString.h @@ -8,6 +8,7 @@ #define nsTLiteralString_h #include "nsTStringRepr.h" +#include "mozilla/StaticString.h" /** * nsTLiteralString_CharT @@ -78,8 +79,10 @@ class nsTLiteralString : public mozilla::detail::nsTStringRepr<T> { * 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; } + constexpr const typename raw_type<T, int>::type get() const&& = delete; + constexpr 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 @@ -110,4 +113,9 @@ class nsTLiteralString : public mozilla::detail::nsTStringRepr<T> { extern template class nsTLiteralString<char>; extern template class nsTLiteralString<char16_t>; +namespace mozilla { +constexpr MOZ_IMPLICIT StaticString::StaticString(nsLiteralCString const& str) + : mStr(str.get()) {} +} // namespace mozilla + #endif diff --git a/xpcom/string/nsTSubstring.cpp b/xpcom/string/nsTSubstring.cpp index cff2031422..c89b6773d6 100644 --- a/xpcom/string/nsTSubstring.cpp +++ b/xpcom/string/nsTSubstring.cpp @@ -17,12 +17,6 @@ #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 @@ -63,7 +57,6 @@ static void ReleaseData(void* aData, nsAString::DataFlags aFlags) { MOZ_LOG_DTOR(aData, "StringAdopt", 1); free(aData); - STRING_STAT_INCREMENT(AdoptFree); } // otherwise, nothing to do. } @@ -79,7 +72,6 @@ nsTSubstring<T>::nsTSubstring(char_type* aData, size_type aLength, AssertValid(); if (aDataFlags & DataFlags::OWNED) { - STRING_STAT_INCREMENT(Adopt); MOZ_LOG_CTOR(this->mData, "StringAdopt", 1); } } @@ -412,6 +404,17 @@ void nsTSubstring<T>::Assign(const char_type* aData, size_type aLength) { } template <typename T> +void nsTSubstring<T>::Assign(already_AddRefed<nsStringBuffer> aBuffer, + size_type aLength) { + nsStringBuffer* buffer = aBuffer.take(); + auto* data = reinterpret_cast<char_type*>(buffer->Data()); + MOZ_DIAGNOSTIC_ASSERT(data[aLength] == char_type(0), + "data should be null terminated"); + Finalize(); + SetData(data, aLength, DataFlags::REFCOUNTED | DataFlags::TERMINATED); +} + +template <typename T> bool nsTSubstring<T>::Assign(const char_type* aData, const fallible_t& aFallible) { return Assign(aData, size_type(-1), aFallible); @@ -631,7 +634,6 @@ void nsTSubstring<T>::Adopt(char_type* aData, size_type aLength) { 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); diff --git a/xpcom/string/nsTSubstring.h b/xpcom/string/nsTSubstring.h index 622b931afb..7459333dc0 100644 --- a/xpcom/string/nsTSubstring.h +++ b/xpcom/string/nsTSubstring.h @@ -8,20 +8,17 @@ #ifndef nsTSubstring_h #define nsTSubstring_h -#include <iterator> #include <type_traits> -#include "mozilla/Casting.h" +#include "mozilla/Attributes.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 "nsStringBuffer.h" #include "nsTStringRepr.h" @@ -422,6 +419,13 @@ class nsTSubstring : public mozilla::detail::nsTStringRepr<T> { [[nodiscard]] bool NS_FASTCALL Assign(const substring_tuple_type&, const fallible_t&); + void Assign(nsStringBuffer* aBuffer, size_type aLength) { + aBuffer->AddRef(); + Assign(already_AddRefed<nsStringBuffer>(aBuffer), aLength); + } + void NS_FASTCALL Assign(already_AddRefed<nsStringBuffer> aBuffer, + size_type aLength); + #if defined(MOZ_USE_CHAR16_WRAPPER) template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>> void Assign(char16ptr_t aData) { @@ -1142,11 +1146,22 @@ class nsTSubstring : public mozilla::detail::nsTStringRepr<T> { * clears the pointer without releasing the buffer. */ void ForgetSharedBuffer() { - if (base_string_type::mDataFlags & DataFlags::REFCOUNTED) { + if (this->mDataFlags & DataFlags::REFCOUNTED) { SetToEmptyBuffer(); } } + /** + * If the string uses a reference-counted buffer, this method returns a + * pointer to it without incrementing the buffer's refcount. + */ + nsStringBuffer* GetStringBuffer() const { + if (this->mDataFlags & DataFlags::REFCOUNTED) { + return nsStringBuffer::FromData(this->mData); + } + return nullptr; + } + protected: void AssertValid() { MOZ_DIAGNOSTIC_ASSERT(!(this->mClassFlags & ClassFlags::INVALID_MASK)); diff --git a/xpcom/tests/TestHarness.h b/xpcom/tests/TestHarness.h index e575497746..40f90d988e 100644 --- a/xpcom/tests/TestHarness.h +++ b/xpcom/tests/TestHarness.h @@ -23,6 +23,7 @@ #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" +#include "mozilla/IntegerPrintfMacros.h" #include "nsIDirectoryService.h" #include "nsIFile.h" #include "nsIObserverService.h" diff --git a/xpcom/tests/gtest/TestFile.cpp b/xpcom/tests/gtest/TestFile.cpp index 6e95366584..fc7650ccbc 100644 --- a/xpcom/tests/gtest/TestFile.cpp +++ b/xpcom/tests/gtest/TestFile.cpp @@ -36,7 +36,7 @@ static void SetUseDOSDevicePathSyntax(nsIFile* aFile) { nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(aFile, &rv); VerifyResult(rv, "Querying nsILocalFileWin"); - MOZ_ASSERT(winFile); + MOZ_RELEASE_ASSERT(winFile); winFile->SetUseDOSDevicePathSyntax(true); } } diff --git a/xpcom/tests/gtest/TestHandleWatcher.cpp b/xpcom/tests/gtest/TestHandleWatcher.cpp index c003a026a1..b3997140d1 100644 --- a/xpcom/tests/gtest/TestHandleWatcher.cpp +++ b/xpcom/tests/gtest/TestHandleWatcher.cpp @@ -101,8 +101,9 @@ class TestHandleWatcher : public testing::Test { private: static bool sIsLive; // just for confirmation static void AssertIsLive() { - MOZ_ASSERT(sIsLive, - "attempted to use `class TestHandleWatcher` outside test group"); + MOZ_RELEASE_ASSERT( + sIsLive, + "attempted to use `class TestHandleWatcher` outside test group"); } static RefPtr<mozilla::SharedThreadPool> sPool; @@ -339,7 +340,7 @@ struct ActivationTestSetup { private: nsIEventTarget* GetQueue(TargetType targetTyoe) { - MOZ_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(NS_IsMainThread()); switch (targetTyoe) { case TargetType::Main: return NS_GetCurrentThread(); @@ -378,9 +379,9 @@ struct ActivationTestSetup { } bool Execute() { - MOZ_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(NS_IsMainThread()); bool const spin = SpinEventLoopUntil([this] { - MOZ_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(NS_IsMainThread()); return run.load(); }).ok(); return spin && watcher.IsStopped(); diff --git a/xpcom/tests/gtest/TestMozPromise.cpp b/xpcom/tests/gtest/TestMozPromise.cpp index 9b06304139..02dbef7114 100644 --- a/xpcom/tests/gtest/TestMozPromise.cpp +++ b/xpcom/tests/gtest/TestMozPromise.cpp @@ -45,7 +45,7 @@ class DelayedResolveOrReject : public Runnable { mIterations(aIterations) {} NS_IMETHOD Run() override { - MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + MOZ_RELEASE_ASSERT(mTaskQueue->IsCurrentThreadIn()); if (!mPromise) { // Canceled. return NS_OK; diff --git a/xpcom/tests/gtest/TestStrings.cpp b/xpcom/tests/gtest/TestStrings.cpp index b1458ec6ce..450befc20d 100644 --- a/xpcom/tests/gtest/TestStrings.cpp +++ b/xpcom/tests/gtest/TestStrings.cpp @@ -1183,11 +1183,9 @@ TEST_F(Strings, stringbuffer) { memcpy(data, kData, sizeof(kData)); nsCString str; - buf->ToString(sizeof(kData) - 1, str); - - nsStringBuffer* buf2; - buf2 = nsStringBuffer::FromString(str); + str.Assign(buf, sizeof(kData) - 1); + nsStringBuffer* buf2 = str.GetStringBuffer(); EXPECT_EQ(buf, buf2); } @@ -1283,9 +1281,6 @@ TEST_F(Strings, string_tointeger) { 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); } } @@ -2288,6 +2283,30 @@ TEST_F(Strings, printf) { create_printf_strings(format, (char*)anotherString); verify_printf_strings(expectedOutput); } + { + const char* format = "RightJustify %8s"; + const char* expectedOutput = "RightJustify foo"; + create_printf_strings(format, "foo"); + verify_printf_strings(expectedOutput); + } + { + const char* format = "LeftJustify %-8s"; + const char* expectedOutput = "LeftJustify foo "; + create_printf_strings(format, "foo"); + verify_printf_strings(expectedOutput); + } + { + const char* format = "RightJustify2 %*s"; + const char* expectedOutput = "RightJustify2 foo"; + create_printf_strings(format, 8, "foo"); + verify_printf_strings(expectedOutput); + } + { + const char* format = "LeftJustify2 %-*s"; + const char* expectedOutput = "LeftJustify2 foo "; + create_printf_strings(format, 8, "foo"); + verify_printf_strings(expectedOutput); + } } // We don't need these macros following the printf test. diff --git a/xpcom/tests/gtest/TestTimers.cpp b/xpcom/tests/gtest/TestTimers.cpp index ea8792e273..7bd4cb1a14 100644 --- a/xpcom/tests/gtest/TestTimers.cpp +++ b/xpcom/tests/gtest/TestTimers.cpp @@ -308,7 +308,7 @@ class FindExpirationTimeState final { // 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); + MOZ_RELEASE_ASSERT(aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); InitTimers(aNumLowPriority, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, nullptr); } diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp index 1457b82fff..3c1043a92a 100644 --- a/xpcom/tests/gtest/TestTokenizer.cpp +++ b/xpcom/tests/gtest/TestTokenizer.cpp @@ -1421,6 +1421,66 @@ TEST(Tokenizer, ReadIntegers) EXPECT_TRUE(t.CheckEOF()); } +TEST(Tokenizer, ReadHexadecimals) +{ + Tokenizer t("0x100,0x0a,0xFe02dXXX,0a,0xX,0xffffffff,0x7fffffff,0x100000000"); + + uint32_t value32; + int32_t signed_value32; + uint64_t value64; + + // "0x100," + EXPECT_TRUE(t.ReadHexadecimal(&value32)); + EXPECT_TRUE(value32 == 0x100); + EXPECT_TRUE(t.CheckChar(',')); + + // "0x0a," + EXPECT_TRUE(t.ReadHexadecimal(&value32)); + EXPECT_TRUE(value32 == 0xa); + EXPECT_TRUE(t.CheckChar(',')); + + // "0xFe02dX," + EXPECT_TRUE(t.ReadHexadecimal(&value32)); + EXPECT_TRUE(value32 == 0xfe02d); + EXPECT_TRUE(t.CheckWord("XXX")); + EXPECT_TRUE(t.CheckChar(',')); + + // "0a," + EXPECT_FALSE(t.ReadHexadecimal(&value32)); + EXPECT_TRUE(t.ReadHexadecimal(&value32, /* aPrefixed = */ false)); + EXPECT_TRUE(value32 == 0xa); + EXPECT_TRUE(t.CheckChar(',')); + + // "0xX," + EXPECT_FALSE(t.ReadHexadecimal(&value32)); + EXPECT_TRUE(t.Check(Tokenizer::Token::Number(0))); + EXPECT_TRUE(t.CheckWord("xX")); + EXPECT_TRUE(t.CheckChar(',')); + + // "0xffffffff," + // there is a case to be made that maybe this should be parsed as -1, + // but for now, this is not supported. + EXPECT_FALSE(t.ReadHexadecimal(&signed_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadHexadecimal(&value32)); + EXPECT_TRUE(value32 == std::numeric_limits<uint32_t>::max()); + EXPECT_TRUE(t.CheckChar(',')); + + // "0x7fffffff," + EXPECT_TRUE(t.ReadHexadecimal(&signed_value32)); + EXPECT_TRUE(signed_value32 == std::numeric_limits<int32_t>::max()); + EXPECT_TRUE(t.CheckChar(',')); + + // "0x100000000," + EXPECT_FALSE(t.ReadHexadecimal(&value32)); + EXPECT_FALSE(t.CheckEOF()); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadHexadecimal(&value64)); + EXPECT_TRUE(t.CheckEOF()); +} + TEST(Tokenizer, CheckPhrase) { Tokenizer t("foo bar baz"); diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build index 4d8d38e89a..22a5b1751d 100644 --- a/xpcom/tests/gtest/moz.build +++ b/xpcom/tests/gtest/moz.build @@ -66,8 +66,8 @@ UNIFIED_SOURCES += [ "TestTaskQueue.cpp", "TestTextFormatter.cpp", "TestThreadManager.cpp", - "TestThreadPool.cpp", "TestThreadPoolListener.cpp", + "TestThreadUtils.cpp", "TestThrottledEventQueue.cpp", "TestTimeStamp.cpp", "TestTokenizer.cpp", @@ -75,24 +75,24 @@ UNIFIED_SOURCES += [ "TestVariant.cpp", ] +# Bug 1894540 - Fails under TSAN +if not CONFIG["MOZ_TSAN"]: + UNIFIED_SOURCES += [ + "TestThreadPool.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" -): +if CONFIG["OS_TARGET"] != "Android": UNIFIED_SOURCES += ["TestTimers.cpp"] diff --git a/xpcom/tests/unit/test_windows_registry.js b/xpcom/tests/unit/test_windows_registry.js index ef5082a666..3abc9f1d2b 100644 --- a/xpcom/tests/unit/test_windows_registry.js +++ b/xpcom/tests/unit/test_windows_registry.js @@ -5,10 +5,47 @@ * License, v. 2.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 { MockRegistry } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistry.sys.mjs" +); + const nsIWindowsRegKey = Ci.nsIWindowsRegKey; let regKeyComponent = Cc["@mozilla.org/windows-registry-key;1"]; -function run_test() { +// We run these tests twice: once against the native Windows registry, and a +// second time against `MockRegistry`. This gives some confidence that +// `MockRegistry` implements the same APIs as the native Windows registry. + +// This order is important: the native registry test must run first, because +// otherwise we would end up running the `MockRegistry` test twice without +// checking it against the native Windows registry. + +add_task(function test_native_registry() { + info("Running test for native Windows registry"); + run_one_test(); +}); + +add_task(function test_MockRegistry() { + let registry = new MockRegistry(); + registerCleanupFunction(() => { + registry.shutdown(); + }); + + // Before, there's nothing -- just the 3 roots (HKLM, HKCU, HKCR). + let linesBefore = []; + MockRegistry.dump(null, "", linesBefore.push.bind(linesBefore)); + strictEqual(linesBefore.length, 3); + + info("Running test for MockRegistry"); + run_one_test({ cleanup: false }); + + // After, there's something -- more than just the roots. + let linesAfter = []; + MockRegistry.dump(null, "", linesAfter.push.bind(linesAfter)); + strictEqual(linesAfter.length, 8); +}); + +function run_one_test({ cleanup = true } = {}) { //* create a key structure in a spot that's normally writable (somewhere under HKCU). let testKey = regKeyComponent.createInstance(nsIWindowsRegKey); @@ -28,11 +65,16 @@ function run_test() { //* 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 removing/deleting values works + test_remove_functions(testKey); + //* check that creating/enumerating/deleting child keys works test_childkey_functions(testKey); //* clean up - cleanup_test_run(testKey, keyName); + if (cleanup) { + cleanup_test_run(testKey, keyName); + } } function setup_test_run(testKey, keyName) { @@ -152,6 +194,15 @@ function test_invalidread_functions(testKey) { } } +function test_remove_functions(testKey) { + strictEqual(testKey.valueCount, 4); + testKey.removeValue(TESTDATA_INT64NAME); + strictEqual(testKey.valueCount, 3); + + testKey.removeValue(TESTDATA_INT64NAME); + strictEqual(testKey.valueCount, 3); +} + function test_childkey_functions(testKey) { strictEqual(testKey.childCount, 0); strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false); diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h index c53037e119..3155d9ea63 100644 --- a/xpcom/threads/MozPromise.h +++ b/xpcom/threads/MozPromise.h @@ -17,6 +17,7 @@ #include "mozilla/Monitor.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" +#include "mozilla/StaticString.h" #include "mozilla/UniquePtr.h" #include "mozilla/Variant.h" #include "nsIDirectTaskDispatcher.h" @@ -231,7 +232,7 @@ class MozPromise : public MozPromiseBase { protected: // MozPromise is the public type, and never constructed directly. Construct // a MozPromise::Private, defined below. - MozPromise(const char* aCreationSite, bool aIsCompletionPromise) + MozPromise(StaticString aCreationSite, bool aIsCompletionPromise) : mCreationSite(aCreationSite), mMutex("MozPromise Mutex"), mHaveRequest(false), @@ -241,7 +242,7 @@ class MozPromise : public MozPromiseBase { mMagic4(&mMutex) #endif { - PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this); + PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite.get(), this); } public: @@ -257,7 +258,7 @@ class MozPromise : public MozPromiseBase { template <typename ResolveValueType_> [[nodiscard]] static RefPtr<MozPromise> CreateAndResolve( - ResolveValueType_&& aResolveValue, const char* aResolveSite) { + ResolveValueType_&& aResolveValue, StaticString aResolveSite) { static_assert(std::is_convertible_v<ResolveValueType_, ResolveValueT>, "Resolve() argument must be implicitly convertible to " "MozPromise's ResolveValueT"); @@ -269,7 +270,7 @@ class MozPromise : public MozPromiseBase { template <typename RejectValueType_> [[nodiscard]] static RefPtr<MozPromise> CreateAndReject( - RejectValueType_&& aRejectValue, const char* aRejectSite) { + RejectValueType_&& aRejectValue, StaticString aRejectSite) { static_assert(std::is_convertible_v<RejectValueType_, RejectValueT>, "Reject() argument must be implicitly convertible to " "MozPromise's RejectValueT"); @@ -281,7 +282,7 @@ class MozPromise : public MozPromiseBase { template <typename ResolveOrRejectValueType_> [[nodiscard]] static RefPtr<MozPromise> CreateAndResolveOrReject( - ResolveOrRejectValueType_&& aValue, const char* aSite) { + ResolveOrRejectValueType_&& aValue, StaticString aSite) { RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aSite); p->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), aSite); return p; @@ -497,7 +498,7 @@ class MozPromise : public MozPromiseBase { RefPtr<MozPromise> mPromise; }; - ThenValueBase(nsISerialEventTarget* aResponseTarget, const char* aCallSite) + ThenValueBase(nsISerialEventTarget* aResponseTarget, StaticString aCallSite) : mResponseTarget(aResponseTarget), mCallSite(aCallSite) { MOZ_ASSERT(aResponseTarget); } @@ -526,7 +527,7 @@ class MozPromise : public MozPromiseBase { MOZ_CRASH_UNSAFE_PRINTF( "MozPromise::ThenValue created from '%s' destroyed without being " "either disconnected, resolved, or rejected (dispatchRv: %s)", - mCallSite, + mCallSite.get(), mDispatchRv ? GetStaticErrorName(*mDispatchRv) : "not dispatched"); } @@ -543,8 +544,8 @@ class MozPromise : public MozPromiseBase { 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->mValue.IsResolve() ? "Resolving" : "Rejecting", + mCallSite.get(), r.get(), aPromise, this, aPromise->mUseSynchronousTaskDispatch ? "synchronous" : aPromise->mUseDirectTaskDispatch ? "directtask" : "normal"); @@ -631,7 +632,7 @@ class MozPromise : public MozPromiseBase { #ifdef PROMISE_DEBUG uint32_t mMagic1 = sMagic; #endif - const char* mCallSite; + StaticString mCallSite; #ifdef PROMISE_DEBUG uint32_t mMagic2 = sMagic; #endif @@ -706,7 +707,7 @@ class MozPromise : public MozPromiseBase { public: ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, - const char* aCallSite) + StaticString aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mThisVal(aThisVal), mResolveMethod(aResolveMethod), @@ -767,7 +768,7 @@ class MozPromise : public MozPromiseBase { public: ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, ResolveRejectMethodType aResolveRejectMethod, - const char* aCallSite) + StaticString aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mThisVal(aThisVal), mResolveRejectMethod(aResolveRejectMethod) {} @@ -824,7 +825,7 @@ class MozPromise : public MozPromiseBase { public: ThenValue(nsISerialEventTarget* aResponseTarget, ResolveFunction&& aResolveFunction, - RejectFunction&& aRejectFunction, const char* aCallSite) + RejectFunction&& aRejectFunction, StaticString aCallSite) : ThenValueBase(aResponseTarget, aCallSite) { mResolveFunction.emplace(std::move(aResolveFunction)); mRejectFunction.emplace(std::move(aRejectFunction)); @@ -892,7 +893,7 @@ class MozPromise : public MozPromiseBase { public: ThenValue(nsISerialEventTarget* aResponseTarget, ResolveRejectFunction&& aResolveRejectFunction, - const char* aCallSite) + StaticString aCallSite) : ThenValueBase(aResponseTarget, aCallSite) { mResolveRejectFunction.emplace(std::move(aResolveRejectFunction)); } @@ -945,7 +946,7 @@ class MozPromise : public MozPromiseBase { public: explicit MapValue(nsISerialEventTarget* aResponseTarget, - ResolveFunction&& f, const char* aCallSite) + ResolveFunction&& f, StaticString aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mResolveFunction(Some(std::forward<ResolveFunction>(f))) {} @@ -991,7 +992,7 @@ class MozPromise : public MozPromiseBase { public: explicit MapErrValue(nsISerialEventTarget* aResponseTarget, - RejectFunction&& f, const char* aCallSite) + RejectFunction&& f, StaticString aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mRejectFunction(Some(std::forward<RejectFunction>(f))) {} @@ -1030,7 +1031,7 @@ class MozPromise : public MozPromiseBase { public: void ThenInternal(already_AddRefed<ThenValueBase> aThenValue, - const char* aCallSite) { + StaticString aCallSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); RefPtr<ThenValueBase> thenValue = aThenValue; @@ -1040,7 +1041,7 @@ class MozPromise : public MozPromiseBase { "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()); + aCallSite.get(), this, thenValue.get(), (int)IsPending()); if (!IsPending()) { thenValue->Dispatch(this); } else { @@ -1072,7 +1073,7 @@ class MozPromise : public MozPromiseBase { using PromiseType = typename ThenValueType::PromiseType; using Private = typename PromiseType::Private; - ThenCommand(const char* aCallSite, + ThenCommand(StaticString aCallSite, already_AddRefed<ThenValueType> aThenValue, MozPromise* aReceiver) : mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {} @@ -1137,7 +1138,7 @@ class MozPromise : public MozPromiseBase { ThenCommand* operator->() { return this; } private: - const char* mCallSite; + StaticString mCallSite; RefPtr<ThenValueType> mThenValue; RefPtr<MozPromise> mReceiver; }; @@ -1146,7 +1147,7 @@ class MozPromise : public MozPromiseBase { template <typename ThisType, typename... Methods, typename ThenValueType = ThenValue<ThisType*, Methods...>, typename ReturnType = ThenCommand<ThenValueType>> - ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + ReturnType Then(nsISerialEventTarget* aResponseTarget, StaticString aCallSite, ThisType* aThisVal, Methods... aMethods) { RefPtr<ThenValueType> thenValue = new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite); @@ -1156,7 +1157,7 @@ class MozPromise : public MozPromiseBase { template <typename... Functions, typename ThenValueType = ThenValue<Functions...>, typename ReturnType = ThenCommand<ThenValueType>> - ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + ReturnType Then(nsISerialEventTarget* aResponseTarget, StaticString aCallSite, Functions&&... aFunctions) { RefPtr<ThenValueType> thenValue = new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite); @@ -1166,7 +1167,7 @@ class MozPromise : public MozPromiseBase { // Shorthand for a `Then` which simply forwards the reject-value, but performs // some additional work with the resolve-value. template <typename Function> - auto Map(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + auto Map(nsISerialEventTarget* aResponseTarget, StaticString aCallSite, Function&& function) { RefPtr<MapValue<Function>> thenValue = new MapValue<Function>( aResponseTarget, std::forward<Function>(function), aCallSite); @@ -1176,7 +1177,7 @@ class MozPromise : public MozPromiseBase { // Shorthand for a `Then` which simply forwards the resolve-value, but // performs some additional work with the reject-value. template <typename Function> - auto MapErr(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + auto MapErr(nsISerialEventTarget* aResponseTarget, StaticString aCallSite, Function&& function) { RefPtr<MapErrValue<Function>> thenValue = new MapErrValue<Function>( aResponseTarget, std::forward<Function>(function), aCallSite); @@ -1185,7 +1186,7 @@ class MozPromise : public MozPromiseBase { } void ChainTo(already_AddRefed<Private> aChainedPromise, - const char* aCallSite) { + StaticString aCallSite) { MutexAutoLock lock(mMutex); MOZ_DIAGNOSTIC_ASSERT( !IsExclusive || !mHaveRequest, @@ -1194,7 +1195,7 @@ class MozPromise : public MozPromiseBase { RefPtr<Private> chainedPromise = aChainedPromise; PROMISE_LOG( "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]", - aCallSite, this, chainedPromise.get(), (int)IsPending()); + aCallSite.get(), this, chainedPromise.get(), (int)IsPending()); // We want to use the same type of dispatching method with the chained // promises. @@ -1305,7 +1306,7 @@ class MozPromise : public MozPromiseBase { #endif }; - const char* mCreationSite; // For logging + StaticString mCreationSite; // For logging Mutex mMutex MOZ_UNANNOTATED; ResolveOrRejectValue mValue; bool mUseSynchronousTaskDispatch = false; @@ -1335,21 +1336,22 @@ 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) + explicit Private(StaticString aCreationSite, + bool aIsCompletionPromise = false) : MozPromise(aCreationSite, aIsCompletionPromise) {} template <typename ResolveValueT_> - void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite) { + void Resolve(ResolveValueT_&& aResolveValue, StaticString 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); + PROMISE_LOG("%s resolving MozPromise (%p created at %s)", + aResolveSite.get(), this, mCreationSite.get()); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", - aResolveSite, this, mCreationSite); + aResolveSite.get(), this, mCreationSite.get()); return; } mValue.SetResolve(std::forward<ResolveValueT_>(aResolveValue)); @@ -1357,17 +1359,17 @@ class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private } template <typename RejectValueT_> - void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) { + void Reject(RejectValueT_&& aRejectValue, StaticString 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); + PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite.get(), + this, mCreationSite.get()); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", - aRejectSite, this, mCreationSite); + aRejectSite.get(), this, mCreationSite.get()); return; } mValue.SetReject(std::forward<RejectValueT_>(aRejectValue)); @@ -1375,17 +1377,17 @@ class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private } template <typename ResolveOrRejectValue_> - void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) { + void ResolveOrReject(ResolveOrRejectValue_&& aValue, StaticString 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); + PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", + aSite.get(), this, mCreationSite.get()); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", - aSite, this, mCreationSite); + aSite.get(), this, mCreationSite.get()); return; } mValue = std::forward<ResolveOrRejectValue_>(aValue); @@ -1403,7 +1405,7 @@ class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)", - aSite, this, mCreationSite); + aSite, this, mCreationSite.get()); MOZ_ASSERT(IsPending(), "A Promise must not have been already resolved or rejected to " "set dispatch state"); @@ -1420,7 +1422,7 @@ class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite, - this, mCreationSite); + this, mCreationSite.get()); MOZ_ASSERT(IsPending(), "A Promise must not have been already resolved or rejected to " "set dispatch state"); @@ -1437,7 +1439,7 @@ class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s TaskPriority MozPromise (%p created at %s)", aSite, this, - mCreationSite); + mCreationSite.get()); MOZ_ASSERT(IsPending(), "A Promise must not have been already resolved or rejected to " "set dispatch state"); @@ -1478,7 +1480,7 @@ class MozPromiseHolderBase { ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); } - already_AddRefed<PromiseType> Ensure(const char* aMethodName) { + already_AddRefed<PromiseType> Ensure(StaticString aMethodName) { static_cast<ImplType*>(this)->Check(); if (!mPromise) { mPromise = new (typename PromiseType::Private)(aMethodName); @@ -1498,7 +1500,7 @@ class MozPromiseHolderBase { } template <typename ResolveValueType_> - void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) { + void Resolve(ResolveValueType_&& aResolveValue, StaticString aMethodName) { static_assert(std::is_convertible_v<ResolveValueType_, typename PromiseType::ResolveValueType>, "Resolve() argument must be implicitly convertible to " @@ -1513,14 +1515,14 @@ class MozPromiseHolderBase { template <typename ResolveValueType_> void ResolveIfExists(ResolveValueType_&& aResolveValue, - const char* aMethodName) { + StaticString aMethodName) { if (!IsEmpty()) { Resolve(std::forward<ResolveValueType_>(aResolveValue), aMethodName); } } template <typename RejectValueType_> - void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) { + void Reject(RejectValueType_&& aRejectValue, StaticString aMethodName) { static_assert(std::is_convertible_v<RejectValueType_, typename PromiseType::RejectValueType>, "Reject() argument must be implicitly convertible to " @@ -1534,7 +1536,7 @@ class MozPromiseHolderBase { template <typename RejectValueType_> void RejectIfExists(RejectValueType_&& aRejectValue, - const char* aMethodName) { + StaticString aMethodName) { if (!IsEmpty()) { Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName); } @@ -1542,7 +1544,7 @@ class MozPromiseHolderBase { template <typename ResolveOrRejectValueType_> void ResolveOrReject(ResolveOrRejectValueType_&& aValue, - const char* aMethodName) { + StaticString aMethodName) { static_cast<ImplType*>(this)->Check(); MOZ_ASSERT(mPromise); mPromise->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), @@ -1552,7 +1554,7 @@ class MozPromiseHolderBase { template <typename ResolveOrRejectValueType_> void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue, - const char* aMethodName) { + StaticString aMethodName) { if (!IsEmpty()) { ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), aMethodName); @@ -1718,7 +1720,7 @@ class ProxyRunnable : public CancelableRunnable { template <typename... Storages, typename PromiseType, typename ThisType, typename... ArgTypes, typename... ActualArgTypes> static RefPtr<PromiseType> InvokeAsyncImpl( - nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + nsISerialEventTarget* aTarget, ThisType* aThisVal, StaticString aCallerName, RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { MOZ_ASSERT(aTarget); @@ -1758,7 +1760,7 @@ 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, + nsISerialEventTarget* aTarget, ThisType* aThisVal, StaticString aCallerName, RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { static_assert( @@ -1777,7 +1779,7 @@ 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, + nsISerialEventTarget* aTarget, ThisType* aThisVal, StaticString aCallerName, RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { static_assert( @@ -1831,7 +1833,7 @@ constexpr static bool IsRefPtrMozPromise<RefPtr<MozPromise<T, U, B>>> = true; // 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, +static auto InvokeAsync(nsISerialEventTarget* aTarget, StaticString aCallerName, Function&& aFunction) -> decltype(aFunction()) { static_assert(!std::is_lvalue_reference_v<Function>, "Function object must not be passed by lvalue-ref (to avoid " diff --git a/xpcom/threads/StaticString.h b/xpcom/threads/StaticString.h new file mode 100644 index 0000000000..26c8675b24 --- /dev/null +++ b/xpcom/threads/StaticString.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 XPCOM_THREADS_STATICSTRING_H_ +#define XPCOM_THREADS_STATICSTRING_H_ + +#include <cstddef> +#include "mozilla/Attributes.h" + +// from "nsStringFwd.h" +template <typename T> +class nsTLiteralString; +using nsLiteralCString = nsTLiteralString<char>; + +namespace mozilla { +// class StaticString +// +// Wrapper type containing a C-style string which is guaranteed[*] to have been +// created (potentially indirectly) from static data. Primarily intended for +// text that may eventually be sent up via telemetry, to avoid the possibility +// of accidentally exfiltrating PII. +// +// `StaticString`, like `template <size_t N> const char (&str)[N]`, can be +// freely and implicitly converted to `const char *`, to simplify its use as a +// drop-in replacement when detemplatizing functions. +// +// ### Comparison/contrast with `nsLiteralCString` +// +// Concretely, `StaticString` is smaller than `nsLiteralCString`, as it does not +// store the string-length. It's also trivial to construct a `StaticString` from +// the variable `__func__` or the preprocessor token `__FILE__`, which would +// require additional work to be used with the latter's `_ns` constructor. +// +// Conventionally, the primary intended use case of `StaticString` is subtly +// different from that of `nsLiteralCString`: +// * `StaticString` is intended for correctness (etc.) in contexts where the +// consumer of the string requires that it be static. +// * `nsLiteralCString` is more for efficiency in contexts where the source +// string _happens to be_ static, but in which the consumer does not care +// (and so accepts `nsACString const &` or similar). +// +// This is not a hard rule, however, and is notably bent in dom::Promise. (See +// comments below.) +// +// Both are trivially-copyable/-movable/-destructible, guaranteed non-null, and +// can only contain static data. +// +// #### Footnotes +// +// [*] ``` +// CHORUS: "What, never?" +// CAPTAIN: "Well... hardly ever!" +// CHORUS: "He's hardly ever sick of C!" +// -- Gilbert & Sullivan, _H.M.S. Pinafore_ (emended) +// ``` +// +class StaticString { + /* TODO(C++20): convert `constexpr` to `consteval` wherever possible. */ + const char* mStr; // guaranteed nonnull + + public: + template <size_t N> + constexpr MOZ_IMPLICIT StaticString(const char (&str)[N]) : mStr(str) {} + + // `nsLiteralCString` has the same guarantees as `StaticString` (both in being + // nonnull and containing only static data), so it's safe to construct either + // from the other. + // + // At present we only support construction of a `StaticString` from an + // `nsLiteralCString`, since this is zero-cost (the converse would not be), + // and is probably the simplest way to support dom::Promise's interoperation + // with MozPromise. + // + // (A more principled approach, in some sense, would be to create a third type + // `StaticStringWithLength` (or whatever) acting as the lattice-join of the + // two, which could then decay to either one as necessary. This is overkill + // for our current goals... but might be worthwhile if, _e.g._, you really + // need to get `__func__` into an `nsLiteralCString` rather than just an + // `nsDependentCString` for some reason.) + // + constexpr explicit StaticString(nsLiteralCString const& str); + + constexpr StaticString(StaticString const&) = default; + constexpr StaticString(StaticString&&) = default; + ~StaticString() = default; + + constexpr MOZ_IMPLICIT operator const char*() const { return mStr; } + + // Not normally needed, but useful for variadic logging functions. + constexpr const char* get() const { return mStr; } +}; + +// Under the covers, StaticString is as lightweight as a single pointer: it does +// not store the length of its deta. +static_assert(sizeof(StaticString) == sizeof(const char*)); +static_assert(alignof(StaticString) == alignof(const char*)); +} // namespace mozilla + +#endif diff --git a/xpcom/threads/TaskController.cpp b/xpcom/threads/TaskController.cpp index d8c2d5e176..b9559bdb74 100644 --- a/xpcom/threads/TaskController.cpp +++ b/xpcom/threads/TaskController.cpp @@ -16,6 +16,7 @@ #include "mozilla/InputTaskManager.h" #include "mozilla/VsyncTaskManager.h" #include "mozilla/IOInterposer.h" +#include "mozilla/Perfetto.h" #include "mozilla/StaticPtr.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/ScopeExit.h" @@ -135,6 +136,7 @@ class MOZ_RAII AutoProfileTask { # define AUTO_PROFILE_FOLLOWING_TASK(task) \ nsAutoCString name; \ (task)->GetName(name); \ + PERFETTO_TRACE_EVENT("task", perfetto::DynamicString{name.get()}); \ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \ mozilla::AutoProfileTask PROFILER_RAII(name, (task)->GetPriority()); #else diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build index 06d10ad331..09d7649c6f 100644 --- a/xpcom/threads/moz.build +++ b/xpcom/threads/moz.build @@ -70,6 +70,7 @@ EXPORTS.mozilla += [ "SpinEventLoopUntil.h", "StateMirroring.h", "StateWatching.h", + "StaticString.h", "SynchronizedEventQueue.h", "SyncRunnable.h", "TaskController.h", diff --git a/xpcom/threads/nsIThreadManager.idl b/xpcom/threads/nsIThreadManager.idl index 9629cb630a..2c5de409fe 100644 --- a/xpcom/threads/nsIThreadManager.idl +++ b/xpcom/threads/nsIThreadManager.idl @@ -29,7 +29,7 @@ interface nsINestedEventLoopCondition : nsISupports /** * An interface for creating and locating nsIThread instances. */ -[scriptable, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)] +[scriptable, builtinclass, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)] interface nsIThreadManager : nsISupports { /** diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp index 3ea324a6c6..52c7db1be4 100644 --- a/xpcom/threads/nsThreadManager.cpp +++ b/xpcom/threads/nsThreadManager.cpp @@ -21,6 +21,7 @@ #include "mozilla/InputTaskManager.h" #include "mozilla/Mutex.h" #include "mozilla/NeverDestroyed.h" +#include "mozilla/Perfetto.h" #include "mozilla/Preferences.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/SpinEventLoopUntil.h" @@ -270,6 +271,9 @@ nsThreadManager::nsThreadManager() nsThreadManager::~nsThreadManager() = default; nsresult nsThreadManager::Init() { + // Initialize perfetto if on Android. + InitPerfetto(); + // 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. |