summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/test/gtest
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/xre/test/gtest')
-rw-r--r--toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp173
-rw-r--r--toolkit/xre/test/gtest/TestCompatVersionCompare.cpp53
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules.cpp382
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp7
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc38
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build17
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp7
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build15
-rw-r--r--toolkit/xre/test/gtest/moz.build31
9 files changed, 723 insertions, 0 deletions
diff --git a/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp
new file mode 100644
index 0000000000..cba65ae71a
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "mozilla/AssembleCmdLine.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "WinRemoteMessage.h"
+
+using namespace mozilla;
+
+template <typename T>
+struct TestCase {
+ const T* mArgs[4];
+ const wchar_t* mExpected;
+};
+
+#define ALPHA_IN_UTF8 "\xe3\x82\xa2\xe3\x83\xab\xe3\x83\x95\xe3\x82\xa1"
+#define OMEGA_IN_UTF8 "\xe3\x82\xaa\xe3\x83\xa1\xe3\x82\xac"
+#define ALPHA_IN_UTF16 L"\u30A2\u30EB\u30D5\u30A1"
+#define OMEGA_IN_UTF16 L"\u30AA\u30E1\u30AC"
+#define UPPER_CYRILLIC_P_IN_UTF8 "\xd0\xa0"
+#define LOWER_CYRILLIC_P_IN_UTF8 "\xd1\x80"
+#define UPPER_CYRILLIC_P_IN_UTF16 L"\u0420"
+#define LOWER_CYRILLIC_P_IN_UTF16 L"\u0440"
+
+TestCase<char> testCases[] = {
+ // Copied from TestXREMakeCommandLineWin.ini
+ {{"a:\\", nullptr}, L"a:\\"},
+ {{"a:\"", nullptr}, L"a:\\\""},
+ {{"a:\\b c", nullptr}, L"\"a:\\b c\""},
+ {{"a:\\b c\"", nullptr}, L"\"a:\\b c\\\"\""},
+ {{"a:\\b c\\d e", nullptr}, L"\"a:\\b c\\d e\""},
+ {{"a:\\b c\\d e\"", nullptr}, L"\"a:\\b c\\d e\\\"\""},
+ {{"a:\\", nullptr}, L"a:\\"},
+ {{"a:\"", "b:\\c d", nullptr}, L"a:\\\" \"b:\\c d\""},
+ {{"a", "b:\" c:\\d", "e", nullptr}, L"a \"b:\\\" c:\\d\" e"},
+ {{"abc", "d", "e", nullptr}, L"abc d e"},
+ {{"a b c", "d", "e", nullptr}, L"\"a b c\" d e"},
+ {{"a\\\\\\b", "de fg", "h", nullptr}, L"a\\\\\\b \"de fg\" h"},
+ {{"a", "b", nullptr}, L"a b"},
+ {{"a\tb", nullptr}, L"\"a\tb\""},
+ {{"a\\\"b", "c", "d", nullptr}, L"a\\\\\\\"b c d"},
+ {{"a\\\"b", "c", nullptr}, L"a\\\\\\\"b c"},
+ {{"a\\\\\\b c", nullptr}, L"\"a\\\\\\b c\""},
+ {{"\"a", nullptr}, L"\\\"a"},
+ {{"\\a", nullptr}, L"\\a"},
+ {{"\\\\\\a", nullptr}, L"\\\\\\a"},
+ {{"\\\\\\\"a", nullptr}, L"\\\\\\\\\\\\\\\"a"},
+ {{"a\\\"b c\" d e", nullptr}, L"\"a\\\\\\\"b c\\\" d e\""},
+ {{"a\\\\\"b", "c d e", nullptr}, L"a\\\\\\\\\\\"b \"c d e\""},
+ {{"a:\\b", "c\\" ALPHA_IN_UTF8, OMEGA_IN_UTF8 "\\d", nullptr},
+ L"a:\\b c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d"},
+ {{"a:\\b", "c\\" ALPHA_IN_UTF8 " " OMEGA_IN_UTF8 "\\d", nullptr},
+ L"a:\\b \"c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d\""},
+ {{ALPHA_IN_UTF8, OMEGA_IN_UTF8, nullptr},
+ ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16},
+
+ // More single-argument cases
+ {{"", nullptr}, L""},
+ {{"a\fb", nullptr}, L"\"a\fb\""},
+ {{"a\nb", nullptr}, L"\"a\nb\""},
+ {{"a\rb", nullptr}, L"\"a\rb\""},
+ {{"a\vb", nullptr}, L"\"a\vb\""},
+ {{"\"a\" \"b\"", nullptr}, L"\"\\\"a\\\" \\\"b\\\"\""},
+ {{"\"a\\b\" \"c\\d\"", nullptr}, L"\"\\\"a\\b\\\" \\\"c\\d\\\"\""},
+ {{"\\\\ \\\\", nullptr}, L"\"\\\\ \\\\\\\\\""},
+ {{"\"\" \"\"", nullptr}, L"\"\\\"\\\" \\\"\\\"\""},
+ {{ALPHA_IN_UTF8 "\\" OMEGA_IN_UTF8, nullptr},
+ ALPHA_IN_UTF16 L"\\" OMEGA_IN_UTF16},
+ {{ALPHA_IN_UTF8 " " OMEGA_IN_UTF8, nullptr},
+ L"\"" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\""},
+};
+
+TEST(AssembleCommandLineWin, assembleCmdLine)
+{
+ for (const auto& testCase : testCases) {
+ UniqueFreePtr<wchar_t> assembled;
+ wchar_t* assembledRaw = nullptr;
+ EXPECT_EQ(assembleCmdLine(testCase.mArgs, &assembledRaw, CP_UTF8), 0);
+ assembled.reset(assembledRaw);
+
+ EXPECT_STREQ(assembled.get(), testCase.mExpected);
+ }
+}
+
+TEST(CommandLineParserWin, HandleCommandLine)
+{
+ CommandLineParserWin<char> parser;
+ for (const auto& testCase : testCases) {
+ NS_ConvertUTF16toUTF8 utf8(testCase.mExpected);
+ parser.HandleCommandLine(utf8.get());
+
+ if (utf8.Length() == 0) {
+ EXPECT_EQ(parser.Argc(), 0);
+ continue;
+ }
+
+ for (int i = 0; i < parser.Argc(); ++i) {
+ EXPECT_NE(testCase.mArgs[i], nullptr);
+ EXPECT_STREQ(parser.Argv()[i], testCase.mArgs[i]);
+ }
+ EXPECT_EQ(testCase.mArgs[parser.Argc()], nullptr);
+ }
+}
+
+TEST(WinRemoteMessage, SendReceive)
+{
+ const char kCommandline[] =
+ "dummy.exe /arg1 --arg2 \"3rd arg\" "
+ "4th=\"" UPPER_CYRILLIC_P_IN_UTF8 " " LOWER_CYRILLIC_P_IN_UTF8 "\"";
+ const wchar_t kCommandlineW[] =
+ L"dummy.exe /arg1 --arg2 \"3rd arg\" "
+ L"4th=\"" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16 L"\"";
+ const wchar_t* kExpectedArgsW[] = {
+ L"-arg1", L"-arg2", L"3rd arg",
+ L"4th=" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16};
+
+ char workingDirA[MAX_PATH];
+ wchar_t workingDirW[MAX_PATH];
+ EXPECT_NE(getcwd(workingDirA, MAX_PATH), nullptr);
+ EXPECT_NE(_wgetcwd(workingDirW, MAX_PATH), nullptr);
+
+ WinRemoteMessageSender v0(kCommandline);
+ WinRemoteMessageSender v1(kCommandline, workingDirA);
+ WinRemoteMessageSender v2(kCommandlineW, workingDirW);
+
+ WinRemoteMessageReceiver receiver;
+ int32_t len;
+ nsAutoString arg;
+ nsCOMPtr<nsIFile> workingDir;
+
+ receiver.Parse(v0.CopyData());
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len)));
+ EXPECT_EQ(len, ArrayLength(kExpectedArgsW));
+ for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ receiver.Parse(v1.CopyData());
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len)));
+ EXPECT_EQ(len, ArrayLength(kExpectedArgsW));
+ for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir))));
+ EXPECT_TRUE(NS_SUCCEEDED(workingDir->GetPath(arg)));
+ EXPECT_STREQ(arg.get(), workingDirW);
+
+ receiver.Parse(v2.CopyData());
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len)));
+ EXPECT_EQ(len, ArrayLength(kExpectedArgsW));
+ for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir))));
+ EXPECT_TRUE(NS_SUCCEEDED(workingDir->GetPath(arg)));
+ EXPECT_STREQ(arg.get(), workingDirW);
+}
diff --git a/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp
new file mode 100644
index 0000000000..bce64aacd5
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+#include "nsAppRunner.h"
+#include "nsString.h"
+
+void CheckCompatVersionCompare(const nsCString& aOldCompatVersion,
+ const nsCString& aNewCompatVersion,
+ bool aExpectedSame, bool aExpectedDowngrade) {
+ printf("Comparing '%s' to '%s'.\n", aOldCompatVersion.get(),
+ aNewCompatVersion.get());
+
+ int32_t result = CompareCompatVersions(aOldCompatVersion, aNewCompatVersion);
+
+ ASSERT_EQ(aExpectedSame, result == 0)
+ << "Version sameness check should match.";
+ ASSERT_EQ(aExpectedDowngrade, result > 0)
+ << "Version downgrade check should match.";
+}
+
+void CheckExpectedResult(const char* aOldAppVersion, const char* aNewAppVersion,
+ bool aExpectedSame, bool aExpectedDowngrade) {
+ nsCString oldCompatVersion;
+ BuildCompatVersion(aOldAppVersion, "", "", oldCompatVersion);
+
+ nsCString newCompatVersion;
+ BuildCompatVersion(aNewAppVersion, "", "", newCompatVersion);
+
+ CheckCompatVersionCompare(oldCompatVersion, newCompatVersion, aExpectedSame,
+ aExpectedDowngrade);
+}
+
+TEST(CompatVersionCompare, CompareVersionChange)
+{
+ // Identical
+ CheckExpectedResult("67.0", "67.0", true, false);
+
+ // Version changes
+ CheckExpectedResult("67.0", "68.0", false, false);
+ CheckExpectedResult("68.0", "67.0", false, true);
+ CheckExpectedResult("67.0", "67.0.1", true, false);
+ CheckExpectedResult("67.0.1", "67.0", true, false);
+ CheckExpectedResult("67.0.1", "67.0.1", true, false);
+ CheckExpectedResult("67.0.1", "67.0.2", true, false);
+ CheckExpectedResult("67.0.2", "67.0.1", true, false);
+
+ // Check that if the last run was safe mode then we consider this an upgrade.
+ CheckCompatVersionCompare(
+ "Safe Mode"_ns, "67.0.1_20000000000000/20000000000000"_ns, false, false);
+}
diff --git a/toolkit/xre/test/gtest/TestUntrustedModules.cpp b/toolkit/xre/test/gtest/TestUntrustedModules.cpp
new file mode 100644
index 0000000000..661b6919bf
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestUntrustedModules.cpp
@@ -0,0 +1,382 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+
+#include "js/RegExp.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/UntrustedModulesProcessor.h"
+#include "mozilla/WinDllServices.h"
+#include "nsContentUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "TelemetryFixture.h"
+#include "UntrustedModulesBackupService.h"
+#include "UntrustedModulesDataSerializer.h"
+
+class ModuleLoadCounter final {
+ nsDataHashtable<nsStringCaseInsensitiveHashKey, int> mCounters;
+
+ public:
+ template <int N>
+ ModuleLoadCounter(const nsString (&aNames)[N], const int (&aCounts)[N])
+ : mCounters(N) {
+ for (int i = 0; i < N; ++i) {
+ mCounters.Put(aNames[i], aCounts[i]);
+ }
+ }
+
+ template <int N>
+ bool Remains(const nsString (&aNames)[N], const int (&aCounts)[N]) {
+ EXPECT_EQ(mCounters.Count(), N);
+ if (mCounters.Count() != N) {
+ return false;
+ }
+
+ bool result = true;
+ for (int i = 0; i < N; ++i) {
+ int* entry = mCounters.GetValue(aNames[i]);
+ if (!entry) {
+ wprintf(L"%s is not registered.\n", aNames[i].get());
+ result = false;
+ } else if (*entry != aCounts[i]) {
+ // We can return false, but let's print out all unmet modules
+ // which may be helpful to investigate test failures.
+ wprintf(L"%s:%4d\n", aNames[i].get(), *entry);
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ bool IsDone() const {
+ bool allZero = true;
+ for (auto iter = mCounters.ConstIter(); !iter.Done(); iter.Next()) {
+ if (iter.Data() < 0) {
+ // If any counter is negative, we know the test fails.
+ // No need to continue.
+ return true;
+ }
+ if (iter.Data() > 0) {
+ allZero = false;
+ }
+ }
+ // If all counters are zero, the test finished nicely. Otherwise, those
+ // counters are expected to be decremented later. Let's continue.
+ return allZero;
+ }
+
+ void Decrement(const nsString& aName) {
+ if (int* entry = mCounters.GetValue(aName)) {
+ --(*entry);
+ }
+ }
+};
+
+class UntrustedModulesCollector {
+ static constexpr int kMaximumPendingQueries = 200;
+ Vector<UntrustedModulesData> mData;
+
+ public:
+ Vector<UntrustedModulesData>& Data() { return mData; }
+
+ nsresult Collect(ModuleLoadCounter& aChecker) {
+ nsresult rv = NS_OK;
+
+ mData.clear();
+ int pendingQueries = 0;
+
+ EXPECT_TRUE(SpinEventLoopUntil([this, &pendingQueries, &aChecker, &rv]() {
+ // Some of expected loaded modules are still missing
+ // after kMaximumPendingQueries queries were submitted.
+ // Giving up here to avoid an infinite loop.
+ if (pendingQueries >= kMaximumPendingQueries) {
+ rv = NS_ERROR_ABORT;
+ return true;
+ }
+
+ ++pendingQueries;
+
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->GetUntrustedModulesData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this, &pendingQueries,
+ &aChecker](Maybe<UntrustedModulesData>&& aResult) {
+ EXPECT_GT(pendingQueries, 0);
+ --pendingQueries;
+
+ if (aResult.isSome()) {
+ wprintf(L"Received data. (pendingQueries=%d)\n", pendingQueries);
+ for (const auto& evt : aResult.ref().mEvents) {
+ aChecker.Decrement(evt.mRequestedDllName);
+ }
+ EXPECT_TRUE(mData.emplaceBack(std::move(aResult.ref())));
+ }
+ },
+ [&pendingQueries, &rv](nsresult aReason) {
+ EXPECT_GT(pendingQueries, 0);
+ --pendingQueries;
+
+ wprintf(L"GetUntrustedModulesData() failed - %08x\n", aReason);
+ EXPECT_TRUE(false);
+ rv = aReason;
+ });
+
+ // Keep calling GetUntrustedModulesData() until we meet the condition.
+ return aChecker.IsDone();
+ }));
+
+ EXPECT_TRUE(SpinEventLoopUntil(
+ [&pendingQueries]() { return pendingQueries <= 0; }));
+
+ return rv;
+ }
+};
+
+static void ValidateUntrustedModules(const UntrustedModulesData& aData) {
+ EXPECT_EQ(aData.mProcessType, GeckoProcessType_Default);
+ EXPECT_EQ(aData.mPid, ::GetCurrentProcessId());
+
+ nsTHashtable<nsPtrHashKey<void>> moduleSet;
+ for (auto iter = aData.mModules.ConstIter(); !iter.Done(); iter.Next()) {
+ const RefPtr<ModuleRecord>& module = iter.Data();
+ moduleSet.PutEntry(module);
+ }
+
+ for (const auto& evt : aData.mEvents) {
+ EXPECT_EQ(evt.mThreadId, ::GetCurrentThreadId());
+ // Make sure mModule is pointing to an entry of mModules.
+ EXPECT_TRUE(moduleSet.Contains(evt.mModule));
+ EXPECT_FALSE(evt.mIsDependent);
+ EXPECT_EQ(evt.mLoadStatus, 0);
+ }
+
+ // No check for the mXULLoadDurationMS field because the field has a value
+ // in CCov build GTest, but it is empty in non-CCov build (bug 1681936).
+ EXPECT_GT(aData.mEvents.length(), 0);
+ EXPECT_GT(aData.mStacks.GetModuleCount(), 0);
+ EXPECT_EQ(aData.mSanitizationFailures, 0);
+ EXPECT_EQ(aData.mTrustTestFailures, 0);
+}
+
+class UntrustedModulesFixture : public TelemetryTestFixture {
+ static constexpr int kLoadCountBeforeDllServices = 5;
+ static constexpr int kLoadCountAfterDllServices = 5;
+ static constexpr uint32_t kMaxModulesArrayLen = 10;
+
+ // One of the important test scenarios is to load modules before DllServices
+ // is initialized and to make sure those loading events are forwarded when
+ // DllServices is initialized.
+ // However, GTest instantiates a Fixture class every testcase and there is
+ // no way to re-enable DllServices and UntrustedModulesProcessor once it's
+ // disabled, which means no matter how many testcases we have, only the
+ // first testcase exercises that scenario. That's why we implement that
+ // test scenario in InitialModuleLoadOnce as a static member and runs it
+ // in the first testcase to be executed.
+ static INIT_ONCE sInitLoadOnce;
+ static UntrustedModulesCollector sInitLoadDataCollector;
+
+ static nsString PrependWorkingDir(const nsAString& aLeaf) {
+ nsCOMPtr<nsIFile> file;
+ EXPECT_TRUE(NS_SUCCEEDED(NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR,
+ getter_AddRefs(file))));
+ EXPECT_TRUE(NS_SUCCEEDED(file->Append(aLeaf)));
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(file->Exists(&exists)) && exists);
+ nsString fullPath;
+ EXPECT_TRUE(NS_SUCCEEDED(file->GetPath(fullPath)));
+ return fullPath;
+ }
+
+ static BOOL CALLBACK InitialModuleLoadOnce(PINIT_ONCE, void*, void**);
+
+ protected:
+ static constexpr int kInitLoadCount =
+ kLoadCountBeforeDllServices + kLoadCountAfterDllServices;
+ static const nsString kTestModules[];
+
+ static void LoadAndFree(const nsAString& aLeaf) {
+ nsModuleHandle dll(::LoadLibraryW(PrependWorkingDir(aLeaf).get()));
+ EXPECT_TRUE(!!dll);
+ }
+
+ virtual void SetUp() override {
+ TelemetryTestFixture::SetUp();
+ ::InitOnceExecuteOnce(&sInitLoadOnce, InitialModuleLoadOnce, nullptr,
+ nullptr);
+ }
+
+ static const Vector<UntrustedModulesData>& GetInitLoadData() {
+ return sInitLoadDataCollector.Data();
+ }
+
+ // This method is useful if we want a new instance of UntrustedModulesData
+ // which is not copyable.
+ static UntrustedModulesData CollectSingleData() {
+ // If we call LoadAndFree more than once, those loading events are
+ // likely to be merged into an instance of UntrustedModulesData,
+ // meaning the length of the collector's vector is at least one but
+ // the exact number is unknown.
+ LoadAndFree(kTestModules[0]);
+
+ UntrustedModulesCollector collector;
+ ModuleLoadCounter waitForOne({kTestModules[0]}, {1});
+ EXPECT_TRUE(NS_SUCCEEDED(collector.Collect(waitForOne)));
+ EXPECT_TRUE(waitForOne.Remains({kTestModules[0]}, {0}));
+ EXPECT_EQ(collector.Data().length(), 1);
+
+ // Cannot "return collector.Data()[0]" as copy ctor is deleted.
+ return UntrustedModulesData(std::move(collector.Data()[0]));
+ }
+
+ template <typename DataFetcherT>
+ void ValidateJSValue(const char16_t* aPattern, size_t aPatternLength,
+ DataFetcherT&& aDataFetcher) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ mozilla::Telemetry::UntrustedModulesDataSerializer serializer(
+ cx.GetJSContext(), kMaxModulesArrayLen);
+ EXPECT_TRUE(!!serializer);
+ aDataFetcher(serializer);
+
+ JS::RootedValue jsval(cx.GetJSContext());
+ serializer.GetObject(&jsval);
+
+ nsAutoString json;
+ EXPECT_TRUE(nsContentUtils::StringifyJSON(cx.GetJSContext(), &jsval, json));
+
+ JS::RootedObject re(
+ cx.GetJSContext(),
+ JS::NewUCRegExpObject(cx.GetJSContext(), aPattern, aPatternLength,
+ JS::RegExpFlag::Global));
+ EXPECT_TRUE(!!re);
+
+ JS::RootedValue matchResult(cx.GetJSContext(), JS::NullValue());
+ size_t idx = 0;
+ EXPECT_TRUE(JS::ExecuteRegExpNoStatics(cx.GetJSContext(), re, json.get(),
+ json.Length(), &idx, true,
+ &matchResult));
+ // On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean
+ // true. If no match, ExecuteRegExpNoStatics returns Null.
+ EXPECT_TRUE(matchResult.isBoolean() && matchResult.toBoolean());
+ if (!matchResult.toBoolean()) {
+ // If match failed, print out the actual JSON kindly.
+ wprintf(L"JSON: %s\n", json.get());
+ wprintf(L"RE: %s\n", aPattern);
+ }
+ }
+};
+
+const nsString UntrustedModulesFixture::kTestModules[] = {
+ u"TestUntrustedModules_Dll1.dll"_ns, u"TestUntrustedModules_Dll2.dll"_ns};
+INIT_ONCE UntrustedModulesFixture::sInitLoadOnce = INIT_ONCE_STATIC_INIT;
+UntrustedModulesCollector UntrustedModulesFixture::sInitLoadDataCollector;
+
+BOOL CALLBACK UntrustedModulesFixture::InitialModuleLoadOnce(PINIT_ONCE, void*,
+ void**) {
+ for (int i = 0; i < kLoadCountBeforeDllServices; ++i) {
+ for (const auto& mod : kTestModules) {
+ LoadAndFree(mod);
+ }
+ }
+
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->StartUntrustedModulesProcessor();
+
+ for (int i = 0; i < kLoadCountAfterDllServices; ++i) {
+ for (const auto& mod : kTestModules) {
+ LoadAndFree(mod);
+ }
+ }
+
+ ModuleLoadCounter waitForTwo(kTestModules, {kInitLoadCount, kInitLoadCount});
+ EXPECT_EQ(sInitLoadDataCollector.Collect(waitForTwo), NS_OK);
+ EXPECT_TRUE(waitForTwo.Remains(kTestModules, {0, 0}));
+
+ for (const auto& event : GetInitLoadData()) {
+ ValidateUntrustedModules(event);
+ }
+
+ // Data was removed when retrieved. No data is retrieved again.
+ UntrustedModulesCollector collector;
+ ModuleLoadCounter waitOnceForEach(kTestModules, {1, 1});
+ EXPECT_EQ(collector.Collect(waitOnceForEach), NS_ERROR_ABORT);
+ EXPECT_TRUE(waitOnceForEach.Remains(kTestModules, {1, 1}));
+
+ return TRUE;
+}
+
+#define PROCESS_OBJ(TYPE, PID) \
+ u"\"" TYPE u"\\." PID u"\":{" \
+ u"\"processType\":\"" TYPE u"\",\"elapsed\":\\d+\\.\\d+," \
+ u"\"sanitizationFailures\":0,\"trustTestFailures\":0," \
+ u"\"events\":\\[{" \
+ u"\"processUptimeMS\":\\d+,\"loadDurationMS\":\\d+\\.\\d+," \
+ u"\"threadID\":\\d+,\"threadName\":\"Main Thread\"," \
+ u"\"baseAddress\":\"0x[0-9a-f]+\",\"moduleIndex\":0," \
+ u"\"isDependent\":false,\"loadStatus\":0}\\]," \
+ u"\"combinedStacks\":{" \
+ u"\"memoryMap\":\\[\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\"\\]" \
+ u"(,\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\\\"\\])*\\]," \
+ u"\"stacks\":\\[\\[\\[\\d+,\\d+\\]" \
+ u"(,\\[\\d+,\\d+\\])*\\]\\]}}"
+
+TEST_F(UntrustedModulesFixture, Serialize) {
+ // clang-format off
+ const char16_t kPattern[] = u"{\"structVersion\":1,"
+ u"\"modules\":\\[{"
+ u"\"resolvedDllName\":\"TestUntrustedModules_Dll1\\.dll\","
+ u"\"fileVersion\":\"1\\.2\\.3\\.4\","
+ u"\"companyName\":\"Mozilla Corporation\",\"trustFlags\":0}\\],"
+ u"\"processes\":{"
+ PROCESS_OBJ(u"browser", u"0xabc") u","
+ PROCESS_OBJ(u"browser", u"0x4") u","
+ PROCESS_OBJ(u"rdd", u"0x4")
+ u"}}";
+ // clang-format on
+
+ UntrustedModulesBackupData backup1, backup2;
+ {
+ UntrustedModulesData data1 = CollectSingleData();
+ UntrustedModulesData data2 = CollectSingleData();
+ UntrustedModulesData data3 = CollectSingleData();
+
+ data1.mPid = 0xabc;
+ data2.mPid = 0x4;
+ data2.mProcessType = GeckoProcessType_RDD;
+ data3.mPid = 0x4;
+
+ backup1.Add(std::move(data1));
+ backup2.Add(std::move(data2));
+ backup1.Add(std::move(data3));
+ }
+
+ ValidateJSValue(kPattern, ArrayLength(kPattern) - 1,
+ [&backup1, &backup2](
+ Telemetry::UntrustedModulesDataSerializer& aSerializer) {
+ EXPECT_TRUE(NS_SUCCEEDED(aSerializer.Add(backup1)));
+ EXPECT_TRUE(NS_SUCCEEDED(aSerializer.Add(backup2)));
+ });
+}
+
+TEST_F(UntrustedModulesFixture, Backup) {
+ using BackupType = UntrustedModulesBackupService::BackupType;
+
+ RefPtr<UntrustedModulesBackupService> backupSvc(
+ UntrustedModulesBackupService::Get());
+ for (int i = 0; i < 5; ++i) {
+ backupSvc->Backup(BackupType::Staging, CollectSingleData());
+ }
+
+ backupSvc->SettleAllStagingData();
+ EXPECT_TRUE(backupSvc->Ref(BackupType::Staging).IsEmpty());
+
+ for (auto iter = backupSvc->Ref(BackupType::Settled).ConstIter();
+ !iter.Done(); iter.Next()) {
+ const RefPtr<UntrustedModulesDataContainer>& container = iter.Data();
+ EXPECT_TRUE(!!container);
+ const UntrustedModulesData& data = container->mData;
+ EXPECT_EQ(iter.Key(), ProcessHashKey(data.mProcessType, data.mPid));
+ ValidateUntrustedModules(data);
+ }
+}
diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc
new file mode 100644
index 0000000000..2358b88b93
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,2,3,4 // This field will be collected
+ PRODUCTVERSION 5,6,7,8
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "Mozilla Corporation"
+ VALUE "OriginalFilename", "TestUntrustedModules_Dll1.dll"
+ VALUE "ProductName", "Test DLL"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build
new file mode 100644
index 0000000000..57fc59ca8a
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestUntrustedModules_Dll1")
+
+UNIFIED_SOURCES = [
+ "TestUntrustedModules_Dll1.cpp",
+]
+
+RCFILE = "TestUntrustedModules_Dll1.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll1.dll"]
diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build
new file mode 100644
index 0000000000..fcefe41329
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestUntrustedModules_Dll2")
+
+UNIFIED_SOURCES = [
+ "TestUntrustedModules_Dll2.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll2.dll"]
diff --git a/toolkit/xre/test/gtest/moz.build b/toolkit/xre/test/gtest/moz.build
new file mode 100644
index 0000000000..e98d2deea8
--- /dev/null
+++ b/toolkit/xre/test/gtest/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("xretest")
+
+UNIFIED_SOURCES = [
+ "TestCompatVersionCompare.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/remote",
+ "/toolkit/components/telemetry/other",
+ "/toolkit/components/telemetry/tests/gtest",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "TestAssembleCommandLineWin.cpp",
+ "TestUntrustedModules.cpp",
+ ]
+ TEST_DIRS += [
+ "TestUntrustedModules_Dll1",
+ "TestUntrustedModules_Dll2",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"