Firefox · Toolkit
CVE-2026-92042
Logic Error in Toolkit
Overview
Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
UrlClassifierPrefixSetReportertoolkit/components/url-classifier/VariableLengthPrefixSet.cpp |
modified | |
iftoolkit/components/url-classifier/VariableLengthPrefixSet.cpp |
modified | |
fortoolkit/components/url-classifier/VariableLengthPrefixSet.cpp |
modified | |
mFixedPrefixSettoolkit/components/url-classifier/VariableLengthPrefixSet.cpp |
modified | |
whiletoolkit/components/url-classifier/VariableLengthPrefixSet.cpp |
modified | |
nsUrlClassifierPrefixSettoolkit/components/url-classifier/VariableLengthPrefixSet.h |
modified | |
VariableLengthPrefixSettoolkit/components/url-classifier/VariableLengthPrefixSet.h |
modified | |
TestHandleReporttoolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp |
modified | |
TestFinishReportingtoolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp |
modified | |
fortoolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp |
modified | |
NS_NewRunnableFunctiontoolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp |
modified |
Files Changed
toolkit/components/url-classifier/VariableLengthPrefixSet.cpptoolkit/components/url-classifier/VariableLengthPrefixSet.htoolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpptoolkit/components/url-classifier/tests/gtest/moz.build
Patch
diff --git a/toolkit/components/url-classifier/VariableLengthPrefixSet.cpp b/toolkit/components/url-classifier/VariableLengthPrefixSet.cpp
index e3e039b7b3a..24ca5819438 100644
--- a/toolkit/components/url-classifier/VariableLengthPrefixSet.cpp
+++ b/toolkit/components/url-classifier/VariableLengthPrefixSet.cpp
@@ -4,11 +4,14 @@
#include "VariableLengthPrefixSet.h"
#include "nsIInputStream.h"
+#include "nsIMemoryReporter.h"
#include "nsUrlClassifierPrefixSet.h"
#include "nsPrintfCString.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/Logging.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
#include "mozilla/UniquePtr.h"
#include <algorithm>
#include <bit>
@@ -37,28 +40,134 @@ void EnsureSorted(T* aArray) {
} // namespace
#endif
-NS_IMPL_ISUPPORTS(VariableLengthPrefixSet, nsIMemoryReporter)
+struct TrackedPrefixSet {
+ ThreadSafeWeakPtr<VariableLengthPrefixSet> mWeakRef;
+ nsCString mPath;
+
+ bool operator==(const VariableLengthPrefixSet* aPrefixSet) const {
+ return mWeakRef == aPrefixSet;
+ }
+};
+
+class UrlClassifierPrefixSetReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ static void Register(VariableLengthPrefixSet* aSet, const nsACString& aName);
+ static void PurgeStale(VariableLengthPrefixSet* aSet);
+
+ private:
+ ~UrlClassifierPrefixSetReporter() = default;
+
+ void Init() {
+ RefPtr reporter(this);
+ RegisterStrongMemoryReporter(reporter.forget());
+ }
+
+ static StaticMutex sMutex;
+ static StaticRefPtr<UrlClassifierPrefixSetReporter> sPrefixSetReporter;
+ nsTArray<TrackedPrefixSet> mSets MOZ_GUARDED_BY(sMutex);
+};
+
+StaticRefPtr<UrlClassifierPrefixSetReporter>
+ UrlClassifierPrefixSetReporter::sPrefixSetReporter;
+StaticMutex UrlClassifierPrefixSetReporter::sMutex;
+
+NS_IMPL_ISUPPORTS(UrlClassifierPrefixSetReporter, nsIMemoryReporter)
+
+/* static */
+void UrlClassifierPrefixSetReporter::Register(VariableLengthPrefixSet* aSet,
+ const nsACString& aName) {
+ StaticMutexAutoLock lock(sMutex);
+ if (!sPrefixSetReporter) {
+ sPrefixSetReporter = new UrlClassifierPrefixSetReporter();
+ sPrefixSetReporter->Init();
+ }
+ sPrefixSetReporter->mSets.RemoveElementsBy(
+ [](const TrackedPrefixSet& aEntry) { return aEntry.mWeakRef.IsDead(); });
+ nsCString path = nsPrintfCString(
+ "explicit/storage/prefix-set/%s",
+ (!aName.IsEmpty() ? PromiseFlatCString(aName).get() : "?!"));
+ sPrefixSetReporter->mSets.AppendElement(
+ TrackedPrefixSet{ThreadSafeWeakPtr<VariableLengthPrefixSet>(aSet), path});
+}
+
+/* static */
+void UrlClassifierPrefixSetReporter::PurgeStale(
+ VariableLengthPrefixSet* aPrefixSet) {
+ StaticMutexAutoLock lock(sMutex);
+ if (!sPrefixSetReporter) {
+ return;
+ }
+
+ sPrefixSetReporter->mSets.RemoveElement(aPrefixSet);
+
+ if (sPrefixSetReporter->mSets.IsEmpty()) {
+ // An async CollectReports dispatch may still hold a strong reference to
+ // sPrefixSetReporter after we null it here. That is safe because
+ // CollectReports releases sMutex before accessing the prefix sets.
+ UnregisterStrongMemoryReporter(sPrefixSetReporter);
+ sPrefixSetReporter = nullptr;
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(UrlClassifierMallocSizeOf)
+
+NS_IMETHODIMP
+UrlClassifierPrefixSetReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) {
+ struct PrefixSetToReport {
+ RefPtr<VariableLengthPrefixSet> mPrefixSet;
+ nsCString mPath;
+ };
+
+ nsTArray<PrefixSetToReport> prefixSetsToReport;
+ {
+ StaticMutexAutoLock lock(sMutex);
+
+ mSets.RemoveElementsBy([&](const TrackedPrefixSet& aEntry) {
+ RefPtr<VariableLengthPrefixSet> set(aEntry.mWeakRef);
+ if (!set) {
+ return true;
+ }
+
+ prefixSetsToReport.AppendElement(
+ PrefixSetToReport{std::move(set), aEntry.mPath});
+
+ return false;
+ });
+ }
+
+ for (auto&& aEntry : prefixSetsToReport) {
+ size_t amount =
+ aEntry.mPrefixSet->SizeOfIncludingThis(UrlClassifierMallocSizeOf);
+ aHandleReport->Callback(
+ ""_ns, std::move(aEntry.mPath), nsIMemoryReporter::KIND_HEAP,
+ nsIMemoryReporter::UNITS_BYTES, amount,
+ nsLiteralCString("Memory used by the variable-length prefix set for a "
+ "URL classifier."),
+ aData);
+ }
+
+ return NS_OK;
+}
-// This class will process prefix size between 4~32. But for 4 bytes prefixes,
-// they will be passed to nsUrlClassifierPrefixSet because of better
-// optimization.
VariableLengthPrefixSet::VariableLengthPrefixSet()
: mLock("VariableLengthPrefixSet.mLock"),
mFixedPrefixSet(new nsUrlClassifierPrefixSet) {}
nsresult VariableLengthPrefixSet::Init(const nsACString& aName) {
mName = aName;
- mMemoryReportPath = nsPrintfCString(
- "explicit/storage/prefix-set/%s",
- (!aName.IsEmpty() ? PromiseFlatCString(aName).get() : "?!"));
- RegisterWeakMemoryReporter(this);
+ UrlClassifierPrefixSetReporter::Register(this, aName);
return mFixedPrefixSet->Init(aName);
}
VariableLengthPrefixSet::~VariableLengthPrefixSet() {
- UnregisterWeakMemoryReporter(this);
+ UrlClassifierPrefixSetReporter::PurgeStale(this);
}
nsresult VariableLengthPrefixSet::SetPrefixes(AddPrefixArray& aAddPrefixes,
@@ -445,7 +554,7 @@ bool VariableLengthPrefixSet::BinarySearch(const nsACString& aFullHash,
int32_t begin = 0, end = aPrefixes.Length() / aPrefixSize;
while (end > begin) {
- int32_t mid = (begin + end) >> 1;
+ int32_t mid = begin + ((end - begin) >> 1);
int cmp = memcmp(fullhash, prefixes + mid * aPrefixSize, aPrefixSize);
if (cmp < 0) {
end = mid;
@@ -458,22 +567,6 @@ bool VariableLengthPrefixSet::BinarySearch(const nsACString& aFullHash,
return false;
}
-MOZ_DEFINE_MALLOC_SIZE_OF(UrlClassifierMallocSizeOf)
-
-NS_IMETHODIMP
-VariableLengthPrefixSet::CollectReports(nsIHandleReportCallback* aHandleReport,
- nsISupports* aData, bool aAnonymize) {
- MOZ_ASSERT(NS_IsMainThread());
-
- size_t amount = SizeOfIncludingThis(UrlClassifierMallocSizeOf);
-
- return aHandleReport->Callback(
- ""_ns, mMemoryReportPath, KIND_HEAP, UNITS_BYTES, amount,
- nsLiteralCString("Memory used by the variable-length prefix set for a "
- "URL classifier."),
- aData);
-}
-
size_t VariableLengthPrefixSet::SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const {
MutexAutoLock lock(mLock);
diff --git a/toolkit/components/url-classifier/VariableLengthPrefixSet.h b/toolkit/components/url-classifier/VariableLengthPrefixSet.h
index 11b8096eedf..3db31415ce9 100644
--- a/toolkit/components/url-classifier/VariableLengthPrefixSet.h
+++ b/toolkit/components/url-classifier/VariableLengthPrefixSet.h
@@ -6,19 +6,24 @@
#define VariableLengthPrefixSet_h
#include "nsISupports.h"
-#include "nsIMemoryReporter.h"
#include "Entries.h"
#include "nsTArray.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Mutex.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
class nsUrlClassifierPrefixSet;
namespace mozilla {
namespace safebrowsing {
-class VariableLengthPrefixSet final : public nsIMemoryReporter {
+class VariableLengthPrefixSet final
+ : public SupportsThreadSafeWeakPtr<VariableLengthPrefixSet> {
+ friend class SupportsThreadSafeWeakPtr<VariableLengthPrefixSet>;
+
public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(VariableLengthPrefixSet)
+
VariableLengthPrefixSet();
nsresult Init(const nsACString& aName);
@@ -41,11 +46,8 @@ class VariableLengthPrefixSet final : public nsIMemoryReporter {
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
- NS_DECL_THREADSAFE_ISUPPORTS
- NS_DECL_NSIMEMORYREPORTER
-
private:
- virtual ~VariableLengthPrefixSet();
+ ~VariableLengthPrefixSet();
static const uint32_t PREFIXSET_VERSION_MAGIC = 1;
@@ -62,7 +64,6 @@ class VariableLengthPrefixSet final : public nsIMemoryReporter {
mozilla::safebrowsing::PrefixStringMap mVLPrefixSet;
nsCString mName;
- nsCString mMemoryReportPath;
};
} // namespace safebrowsing
diff --git a/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp
new file mode 100644
index 00000000000..cff2609a861
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Common.h"
+#include "VariableLengthPrefixSet.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIMemoryReporter.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::safebrowsing;
+
+static RefPtr<VariableLengthPrefixSet> CreatePrefixSet(
+ const nsACString& aName) {
+ auto pset = MakeRefPtr<VariableLengthPrefixSet>();
+ nsresult rv = pset->Init(aName);
+ EXPECT_NS_SUCCEEDED(rv);
+ return pset;
+}
+
+class TestHandleReport final : public nsIHandleReportCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ TestHandleReport(nsTArray<nsCString>& aPaths, const nsACString& aPrefix)
+ : mPaths(aPaths), mPrefix(aPrefix) {}
+
+ NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
+ int32_t aKind, int32_t aUnits, int64_t aAmount,
+ const nsACString& aDescription,
+ nsISupports* aData) override {
+ if (StringBeginsWith(aPath, mPrefix)) {
+ mPaths.AppendElement(nsCString(aPath));
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~TestHandleReport() = default;
+ nsTArray<nsCString>& mPaths;
+ const nsCString mPrefix;
+};
+
+NS_IMPL_ISUPPORTS(TestHandleReport, nsIHandleReportCallback)
+
+class TestFinishReporting final : public nsIFinishReportingCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit TestFinishReporting(Atomic<bool>& aDone) : mDone(aDone) {}
+
+ NS_IMETHOD Callback(nsISupports* aData) override {
+ mDone = true;
+ return NS_OK;
+ }
+
+ private:
+ ~TestFinishReporting() = default;
+ Atomic<bool>& mDone;
+};
+
+NS_IMPL_ISUPPORTS(TestFinishReporting, nsIFinishReportingCallback)
+
+static nsTArray<nsCString> CollectReportPaths(const nsACString& aPathPrefix) {
+ nsCOMPtr<nsIMemoryReporterManager> mgr =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+ EXPECT_TRUE(mgr);
+
+ nsTArray<nsCString> paths;
+ Atomic<bool> done{false};
+
+ auto handleReport = MakeRefPtr<TestHandleReport>(paths, aPathPrefix);
+ auto finishReporting = MakeRefPtr<TestFinishReporting>(done);
+
+ mgr->GetReports(handleReport, nullptr, finishReporting, nullptr, false);
+
+ SpinEventLoopUntil("TestPrefixSetMemoryReporter"_ns,
+ [&]() { return static_cast<bool>(done); });
+
+ return paths;
+}
+
+// Verify the singleton reporter registers and reports correctly.
+TEST(UrlClassifierPrefixSetReporter, BasicLifecycle)
+{
+ {
+ // Keep prefix sets alive while we verify they appear in memory reports.
+ RefPtr<VariableLengthPrefixSet> pset1 = CreatePrefixSet("test-table-a"_ns);
+ RefPtr<VariableLengthPrefixSet> pset2 = CreatePrefixSet("test-table-b"_ns);
+ (void)pset1;
+ (void)pset2;
+
+ nsTArray<nsCString> paths =
+ CollectReportPaths("explicit/storage/prefix-set/"_ns);
+ bool foundA = false, foundB = false;
+ for (const auto& path : paths) {
+ if (path.EqualsLiteral("explicit/storage/prefix-set/test-table-a")) {
+ foundA = true;
+ }
+ if (path.EqualsLiteral("explicit/storage/prefix-set/test-table-b")) {
+ foundB = true;
+ }
+ }
+ EXPECT_TRUE(foundA);
+ EXPECT_TRUE(foundB);
+ }
+
+ // After both prefix sets are destroyed, their reports should be gone.
+ nsTArray<nsCString> paths =
+ CollectReportPaths("explicit/storage/prefix-set/test-table-"_ns);
+ EXPECT_TRUE(paths.IsEmpty());
+}
+
+// Destroy VariableLengthPrefixSet instances on a background thread while
+// collecting memory reports on the main thread. This exercises the race
+// that bug 2049342 fixed.
+TEST(UrlClassifierPrefixSetReporter, ConcurrentDestroyAndReport)
+{
+ static const uint32_t kNumSets = 20;
+ static const uint32_t kReportRounds = 10;
+
+ nsTArray<RefPtr<VariableLengthPrefixSet>> sets;
+ for (uint32_t i = 0; i < kNumSets; i++) {
+ nsPrintfCString name("concurrent-test-%u", i);
+ sets.AppendElement(CreatePrefixSet(name));
+ }
+
+ // Verify all are reported before we start destroying.
+ nsTArray<nsCString> paths =
+ CollectReportPaths("explicit/storage/prefix-set/concurrent-test-"_ns);
+ EXPECT_EQ(paths.Length(), kNumSets);
+
+ // Move sets to a background thread that will release them one at a time,
+ // while the main thread repeatedly collects memory reports.
+ Atomic<uint32_t> destroyed{0};
+ nsTArray<RefPtr<VariableLengthPrefixSet>> bgSets = std::move(sets);
+
+ nsCOMPtr<nsIRunnable> destroyTask =
+ NS_NewRunnableFunction("DestroyPrefixSets", [&bgSets, &destroyed]() {
+ for (uint32_t i = 0; i < bgSets.Length(); i++) {
+ bgSets[i] = nullptr;
+ destroyed++;
... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp
new file mode 100644
index 00000000000..cff2609a861
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/gtest/TestUrlClassifierPrefixSetReporter.cpp
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Common.h"
+#include "VariableLengthPrefixSet.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIMemoryReporter.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::safebrowsing;
+
+static RefPtr<VariableLengthPrefixSet> CreatePrefixSet(
+ const nsACString& aName) {
+ auto pset = MakeRefPtr<VariableLengthPrefixSet>();
+ nsresult rv = pset->Init(aName);
+ EXPECT_NS_SUCCEEDED(rv);
+ return pset;
+}
+
+class TestHandleReport final : public nsIHandleReportCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ TestHandleReport(nsTArray<nsCString>& aPaths, const nsACString& aPrefix)
+ : mPaths(aPaths), mPrefix(aPrefix) {}
+
+ NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
+ int32_t aKind, int32_t aUnits, int64_t aAmount,
+ const nsACString& aDescription,
+ nsISupports* aData) override {
+ if (StringBeginsWith(aPath, mPrefix)) {
+ mPaths.AppendElement(nsCString(aPath));
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~TestHandleReport() = default;
+ nsTArray<nsCString>& mPaths;
+ const nsCString mPrefix;
+};
+
+NS_IMPL_ISUPPORTS(TestHandleReport, nsIHandleReportCallback)
+
+class TestFinishReporting final : public nsIFinishReportingCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit TestFinishReporting(Atomic<bool>& aDone) : mDone(aDone) {}
+
+ NS_IMETHOD Callback(nsISupports* aData) override {
+ mDone = true;
+ return NS_OK;
+ }
+
+ private:
+ ~TestFinishReporting() = default;
+ Atomic<bool>& mDone;
+};
+
+NS_IMPL_ISUPPORTS(TestFinishReporting, nsIFinishReportingCallback)
+
+static nsTArray<nsCString> CollectReportPaths(const nsACString& aPathPrefix) {
+ nsCOMPtr<nsIMemoryReporterManager> mgr =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+ EXPECT_TRUE(mgr);
+
+ nsTArray<nsCString> paths;
+ Atomic<bool> done{false};
+
+ auto handleReport = MakeRefPtr<TestHandleReport>(paths, aPathPrefix);
+ auto finishReporting = MakeRefPtr<TestFinishReporting>(done);
+
+ mgr->GetReports(handleReport, nullptr, finishReporting, nullptr, false);
+
+ SpinEventLoopUntil("TestPrefixSetMemoryReporter"_ns,
+ [&]() { return static_cast<bool>(done); });
+
+ return paths;
+}
+
+// Verify the singleton reporter registers and reports correctly.
+TEST(UrlClassifierPrefixSetReporter, BasicLifecycle)
+{
+ {
+ // Keep prefix sets alive while we verify they appear in memory reports.
+ RefPtr<VariableLengthPrefixSet> pset1 = CreatePrefixSet("test-table-a"_ns);
+ RefPtr<VariableLengthPrefixSet> pset2 = CreatePrefixSet("test-table-b"_ns);
+ (void)pset1;
+ (void)pset2;
+
+ nsTArray<nsCString> paths =
+ CollectReportPaths("explicit/storage/prefix-set/"_ns);
+ bool foundA = false, foundB = false;
+ for (const auto& path : paths) {
+ if (path.EqualsLiteral("explicit/storage/prefix-set/test-table-a")) {
+ foundA = true;
+ }
+ if (path.EqualsLiteral("explicit/storage/prefix-set/test-table-b")) {
+ foundB = true;
+ }
+ }
+ EXPECT_TRUE(foundA);
+ EXPECT_TRUE(foundB);
+ }
+
+ // After both prefix sets are destroyed, their reports should be gone.
+ nsTArray<nsCString> paths =
+ CollectReportPaths("explicit/storage/prefix-set/test-table-"_ns);
+ EXPECT_TRUE(paths.IsEmpty());
+}
+
+// Destroy VariableLengthPrefixSet instances on a background thread while
+// collecting memory reports on the main thread. This exercises the race
+// that bug 2049342 fixed.
+TEST(UrlClassifierPrefixSetReporter, ConcurrentDestroyAndReport)
+{
+ static const uint32_t kNumSets = 20;
+ static const uint32_t kReportRounds = 10;
+
+ nsTArray<RefPtr<VariableLengthPrefixSet>> sets;
+ for (uint32_t i = 0; i < kNumSets; i++) {
+ nsPrintfCString name("concurrent-test-%u", i);
+ sets.AppendElement(CreatePrefixSet(name));
+ }
+
+ // Verify all are reported before we start destroying.
+ nsTArray<nsCString> paths =
+ CollectReportPaths("explicit/storage/prefix-set/concurrent-test-"_ns);
+ EXPECT_EQ(paths.Length(), kNumSets);
+
+ // Move sets to a background thread that will release them one at a time,
+ // while the main thread repeatedly collects memory reports.
+ Atomic<uint32_t> destroyed{0};
+ nsTArray<RefPtr<VariableLengthPrefixSet>> bgSets = std::move(sets);
+
+ nsCOMPtr<nsIRunnable> destroyTask =
+ NS_NewRunnableFunction("DestroyPrefixSets", [&bgSets, &destroyed]() {
+ for (uint32_t i = 0; i < bgSets.Length(); i++) {
+ bgSets[i] = nullptr;
+ destroyed++;
+ }
+ });
+ nsCOMPtr<nsIEventTarget> pool =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ ASSERT_TRUE(pool);
+ nsresult rv = pool->Dispatch(destroyTask, NS_DISPATCH_NORMAL);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Main thread collects reports repeatedly while destruction proceeds.
+ for (uint32_t round = 0; round < kReportRounds; round++) {
+ CollectReportPaths("explicit/storage/prefix-set/concurrent-test-"_ns);
+ }
+
+ SpinEventLoopUntil("WaitForDestroy"_ns,
+ [&]() { return destroyed == kNumSets; });
+ EXPECT_EQ(static_cast<uint32_t>(destroyed), kNumSets);
+
+ // All should be gone now.
+ paths = CollectReportPaths("explicit/storage/prefix-set/concurrent-test-"_ns);
+ EXPECT_TRUE(paths.IsEmpty());
+}
+
+// Verify that creating and immediately destroying a VariableLengthPrefixSet
+// (the Init-then-fail path in Classifier::GetLookupCache) doesn't leave
+// stale entries in the reporter.
+TEST(UrlClassifierPrefixSetReporter, CreateAndImmediatelyDestroy)
+{
+ for (uint32_t i = 0; i < 50; i++) {
+ MOZ_ALWAYS_TRUE(CreatePrefixSet("ephemeral-test"_ns));
+ }
+
+ nsTArray<nsCString> paths =
+ CollectReportPaths("explicit/storage/prefix-set/ephemeral-test"_ns);
+ EXPECT_TRUE(paths.IsEmpty());
+}
diff --git a/toolkit/components/url-classifier/tests/gtest/moz.build b/toolkit/components/url-classifier/tests/gtest/moz.build
index 404583c2a0c..ca78f47cfff 100644
--- a/toolkit/components/url-classifier/tests/gtest/moz.build
+++ b/toolkit/components/url-classifier/tests/gtest/moz.build
@@ -25,6 +25,7 @@ UNIFIED_SOURCES += [
"TestSafebrowsingHash.cpp",
"TestSafeBrowsingProtobuf.cpp",
"TestTable.cpp",
+ "TestUrlClassifierPrefixSetReporter.cpp",
"TestUrlClassifierTableUpdateV4.cpp",
"TestUrlClassifierUtils.cpp",
"TestURLsAndHashing.cpp",
Loading diff…
References
On This Page