/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. 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 CreatePrefixSet( const nsACString& aName) { auto pset = MakeRefPtr(); nsresult rv = pset->Init(aName); EXPECT_NS_SUCCEEDED(rv); return pset; } class TestHandleReport final : public nsIHandleReportCallback { public: NS_DECL_ISUPPORTS TestHandleReport(nsTArray& 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& mPaths; const nsCString mPrefix; }; NS_IMPL_ISUPPORTS(TestHandleReport, nsIHandleReportCallback) class TestFinishReporting final : public nsIFinishReportingCallback { public: NS_DECL_ISUPPORTS explicit TestFinishReporting(Atomic& aDone) : mDone(aDone) {} NS_IMETHOD Callback(nsISupports* aData) override { mDone = true; return NS_OK; } private: ~TestFinishReporting() = default; Atomic& mDone; }; NS_IMPL_ISUPPORTS(TestFinishReporting, nsIFinishReportingCallback) static nsTArray CollectReportPaths(const nsACString& aPathPrefix) { nsCOMPtr mgr = do_GetService("@mozilla.org/memory-reporter-manager;1"); EXPECT_TRUE(mgr); nsTArray paths; Atomic done{false}; auto handleReport = MakeRefPtr(paths, aPathPrefix); auto finishReporting = MakeRefPtr(done); mgr->GetReports(handleReport, nullptr, finishReporting, nullptr, false); SpinEventLoopUntil("TestPrefixSetMemoryReporter"_ns, [&]() { return static_cast(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 pset1 = CreatePrefixSet("test-table-a"_ns); RefPtr pset2 = CreatePrefixSet("test-table-b"_ns); (void)pset1; (void)pset2; nsTArray 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 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> 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 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 destroyed{0}; nsTArray> bgSets = std::move(sets); nsCOMPtr destroyTask = NS_NewRunnableFunction("DestroyPrefixSets", [&bgSets, &destroyed]() { for (uint32_t i = 0; i < bgSets.Length(); i++) { bgSets[i] = nullptr; destroyed++; } }); nsCOMPtr 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(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 paths = CollectReportPaths("explicit/storage/prefix-set/ephemeral-test"_ns); EXPECT_TRUE(paths.IsEmpty()); }