summaryrefslogtreecommitdiffstats
path: root/browser/app/winlauncher/test/TestSameBinary.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'browser/app/winlauncher/test/TestSameBinary.cpp')
-rw-r--r--browser/app/winlauncher/test/TestSameBinary.cpp255
1 files changed, 255 insertions, 0 deletions
diff --git a/browser/app/winlauncher/test/TestSameBinary.cpp b/browser/app/winlauncher/test/TestSameBinary.cpp
new file mode 100644
index 0000000000..2cb45f546f
--- /dev/null
+++ b/browser/app/winlauncher/test/TestSameBinary.cpp
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#define MOZ_USE_LAUNCHER_ERROR
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <utility>
+
+#include "SameBinary.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsWindowsHelpers.h"
+
+#define EXPECT_SAMEBINARY_IS(expected, option, message) \
+ do { \
+ mozilla::LauncherResult<bool> isSame = \
+ mozilla::IsSameBinaryAsParentProcess(option); \
+ if (isSame.isErr()) { \
+ PrintLauncherError(isSame, \
+ "IsSameBinaryAsParentProcess returned error " \
+ "when we were expecting success."); \
+ return 1; \
+ } \
+ if (isSame.unwrap() != expected) { \
+ PrintErrorMsg(message); \
+ return 1; \
+ } \
+ } while (0)
+
+/**
+ * This test involves three processes:
+ * 1. The "Monitor" process, which is executed by |MonitorMain|. This process
+ * is responsible for integrating with the test harness, so it spawns the
+ * "Parent" process (2), and then waits for the other two processes to
+ * finish.
+ * 2. The "Parent" process, which is executed by |ParentMain|. This process
+ * creates the "Child" process (3) and then waits indefinitely.
+ * 3. The "Child" process, which is executed by |ChildMain| and carries out
+ * the actual test. It terminates the Parent process during its execution,
+ * using the Child PID as the Parent process's exit code. This serves as a
+ * hacky yet effective way to signal to the Monitor process which PID it
+ * should wait on to ensure that the Child process has exited.
+ */
+
+static const char kMsgStart[] = "TEST-FAILED | SameBinary | ";
+
+inline void PrintErrorMsg(const char* aMsg) {
+ printf("%s%s\n", kMsgStart, aMsg);
+}
+
+inline void PrintWinError(const char* aMsg) {
+ mozilla::WindowsError err(mozilla::WindowsError::FromLastError());
+ printf("%s%s: %S\n", kMsgStart, aMsg, err.AsString().get());
+}
+
+template <typename T>
+inline void PrintLauncherError(const mozilla::LauncherResult<T>& aResult,
+ const char* aMsg = nullptr) {
+ const char* const kSep = aMsg ? ": " : "";
+ const char* msg = aMsg ? aMsg : "";
+ const mozilla::LauncherError& err = aResult.inspectErr();
+ printf("%s%s%s%S (%s:%d)\n", kMsgStart, msg, kSep,
+ err.mError.AsString().get(), err.mFile, err.mLine);
+}
+
+static int ChildMain(DWORD aExpectedParentPid) {
+ mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
+ if (parentPid.isErr()) {
+ PrintLauncherError(parentPid);
+ return 1;
+ }
+
+ if (parentPid.inspect() != aExpectedParentPid) {
+ PrintErrorMsg("Unexpected mismatch of parent PIDs");
+ return 1;
+ }
+
+ const DWORD kAccess = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
+ nsAutoHandle parentProcess(
+ ::OpenProcess(kAccess, FALSE, parentPid.inspect()));
+ if (!parentProcess) {
+ PrintWinError("Unexpectedly failed to call OpenProcess on parent");
+ return 1;
+ }
+
+ EXPECT_SAMEBINARY_IS(
+ true, mozilla::ImageFileCompareOption::Default,
+ "IsSameBinaryAsParentProcess returned incorrect result for identical "
+ "binaries");
+ EXPECT_SAMEBINARY_IS(
+ true, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
+ "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
+ "result for identical binaries");
+
+ // Total hack, but who cares? We'll set the parent's exit code as our PID
+ // so that the monitor process knows who to wait for!
+ if (!::TerminateProcess(parentProcess.get(), ::GetCurrentProcessId())) {
+ PrintWinError("Unexpected failure in TerminateProcess");
+ return 1;
+ }
+
+ // Close our handle to the parent process so that no references are held.
+ ::CloseHandle(parentProcess.disown());
+
+ // Querying a pid on a terminated process may still succeed some time after
+ // that process has been terminated. For the purposes of this test, we'll poll
+ // the OS until we cannot succesfully open the parentPid anymore.
+ const uint32_t kMaxAttempts = 100;
+ uint32_t curAttempt = 0;
+ while (HANDLE p = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
+ parentPid.inspect())) {
+ ::CloseHandle(p);
+ ::Sleep(100);
+ ++curAttempt;
+ if (curAttempt >= kMaxAttempts) {
+ PrintErrorMsg(
+ "Exhausted retry attempts waiting for parent pid to become invalid");
+ return 1;
+ }
+ }
+
+ EXPECT_SAMEBINARY_IS(
+ false, mozilla::ImageFileCompareOption::Default,
+ "IsSameBinaryAsParentProcess returned incorrect result for dead parent "
+ "process");
+ EXPECT_SAMEBINARY_IS(
+ false, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
+ "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
+ "result for dead parent process");
+
+ return 0;
+}
+
+static nsReturnRef<HANDLE> CreateSelfProcess(int argc, wchar_t* argv[]) {
+ nsAutoHandle empty;
+
+ DWORD myPid = ::GetCurrentProcessId();
+
+ wchar_t strPid[11] = {};
+#if defined(__MINGW32__)
+ _ultow(myPid, strPid, 16);
+#else
+ if (_ultow_s(myPid, strPid, 16)) {
+ PrintErrorMsg("_ultow_s failed");
+ return empty.out();
+ }
+#endif // defined(__MINGW32__)
+
+ wchar_t* extraArgs[] = {strPid};
+
+ auto cmdLine = mozilla::MakeCommandLine(
+ argc, argv, mozilla::ArrayLength(extraArgs), extraArgs);
+ if (!cmdLine) {
+ PrintErrorMsg("MakeCommandLine failed");
+ return empty.out();
+ }
+
+ STARTUPINFOW si = {sizeof(si)};
+ PROCESS_INFORMATION pi;
+ BOOL ok =
+ ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE,
+ CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &si, &pi);
+ if (!ok) {
+ PrintWinError("CreateProcess failed");
+ return empty.out();
+ }
+
+ nsAutoHandle proc(pi.hProcess);
+ nsAutoHandle thd(pi.hThread);
+
+ return proc.out();
+}
+
+static int ParentMain(int argc, wchar_t* argv[]) {
+ nsAutoHandle childProc(CreateSelfProcess(argc, argv));
+ if (!childProc) {
+ return 1;
+ }
+
+ if (::WaitForSingleObject(childProc.get(), INFINITE) != WAIT_OBJECT_0) {
+ PrintWinError(
+ "Unexpected result from WaitForSingleObject on child process");
+ return 1;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("This process should be terminated by now");
+ return 0;
+}
+
+static int MonitorMain(int argc, wchar_t* argv[]) {
+ // In this process, "parent" means the process that will be running
+ // ParentMain, which is our child process (confusing, I know...)
+ nsAutoHandle parentProc(CreateSelfProcess(argc, argv));
+ if (!parentProc) {
+ return 1;
+ }
+
+ if (::WaitForSingleObject(parentProc.get(), 60000) != WAIT_OBJECT_0) {
+ PrintWinError("Unexpected result from WaitForSingleObject on parent");
+ return 1;
+ }
+
+ DWORD childPid;
+ if (!::GetExitCodeProcess(parentProc.get(), &childPid)) {
+ PrintWinError("GetExitCodeProcess failed");
+ return 1;
+ }
+
+ nsAutoHandle childProc(::OpenProcess(SYNCHRONIZE, FALSE, childPid));
+ if (!childProc) {
+ // Nothing to wait on anymore, which is OK.
+ return 0;
+ }
+
+ // We want no more references to parentProc
+ ::CloseHandle(parentProc.disown());
+
+ if (::WaitForSingleObject(childProc.get(), 60000) != WAIT_OBJECT_0) {
+ PrintWinError("Unexpected result from WaitForSingleObject on child");
+ return 1;
+ }
+
+ return 0;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ if (argc == 3) {
+ return ChildMain(wcstoul(argv[2], nullptr, 16));
+ }
+
+ if (!mozilla::SetArgv0ToFullBinaryPath(argv)) {
+ return 1;
+ }
+
+ if (argc == 1) {
+ return MonitorMain(argc, argv);
+ }
+
+ if (argc == 2) {
+ return ParentMain(argc, argv);
+ }
+
+ PrintErrorMsg("Unexpected argc");
+ return 1;
+}