summaryrefslogtreecommitdiffstats
path: root/xpcom/tests/windows
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/tests/windows/TestCOM.cpp97
-rw-r--r--xpcom/tests/windows/TestNtPathToDosPath.cpp176
-rw-r--r--xpcom/tests/windows/TestPoisonIOInterposer.cpp152
-rw-r--r--xpcom/tests/windows/moz.build17
4 files changed, 442 insertions, 0 deletions
diff --git a/xpcom/tests/windows/TestCOM.cpp b/xpcom/tests/windows/TestCOM.cpp
new file mode 100644
index 0000000000..49f67db835
--- /dev/null
+++ b/xpcom/tests/windows/TestCOM.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; 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 <windows.h>
+#include <unknwn.h>
+#include <stdio.h>
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/RefPtr.h"
+
+// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <unknwn.h>
+
+#include "gtest/gtest.h"
+
+// {5846BA30-B856-11d1-A98A-00805F8A7AC4}
+#define NS_ITEST_COM_IID \
+ { \
+ 0x5846ba30, 0xb856, 0x11d1, { \
+ 0xa9, 0x8a, 0x0, 0x80, 0x5f, 0x8a, 0x7a, 0xc4 \
+ } \
+ }
+
+class nsITestCom : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEST_COM_IID)
+ NS_IMETHOD Test() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITestCom, NS_ITEST_COM_IID)
+
+/*
+ * nsTestCom
+ */
+
+class nsTestCom final : public nsITestCom {
+ NS_DECL_ISUPPORTS
+
+ public:
+ nsTestCom() {}
+
+ NS_IMETHOD Test() override { return NS_OK; }
+
+ static int sDestructions;
+
+ private:
+ ~nsTestCom() { sDestructions++; }
+};
+
+int nsTestCom::sDestructions;
+
+NS_IMPL_QUERY_INTERFACE(nsTestCom, nsITestCom)
+
+MozExternalRefCountType nsTestCom::AddRef() {
+ nsrefcnt res = ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "nsTestCom", sizeof(*this));
+ return res;
+}
+
+MozExternalRefCountType nsTestCom::Release() {
+ nsrefcnt res = --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "nsTestCom");
+ if (res == 0) {
+ delete this;
+ }
+ return res;
+}
+
+TEST(TestCOM, WindowsInterop)
+{
+ // Test that we can QI an nsITestCom to an IUnknown.
+ RefPtr<nsTestCom> t = new nsTestCom();
+ IUnknown* iUnknown = nullptr;
+ nsresult rv = t->QueryInterface(NS_GET_IID(nsISupports), (void**)&iUnknown);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(iUnknown);
+
+ // Test we can QI an IUnknown to nsITestCom.
+ nsCOMPtr<nsITestCom> iTestCom;
+ GUID testGUID = NS_ITEST_COM_IID;
+ HRESULT hr = iUnknown->QueryInterface(testGUID, getter_AddRefs(iTestCom));
+ ASSERT_TRUE(SUCCEEDED(hr));
+ ASSERT_TRUE(iTestCom);
+
+ // Make sure we can call our test function (and the pointer is valid).
+ rv = iTestCom->Test();
+ ASSERT_NS_SUCCEEDED(rv);
+
+ iUnknown->Release();
+ iTestCom = nullptr;
+ t = nullptr;
+
+ ASSERT_EQ(nsTestCom::sDestructions, 1);
+}
diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp
new file mode 100644
index 0000000000..ac29ddac50
--- /dev/null
+++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp
@@ -0,0 +1,176 @@
+/* -*- 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 <windows.h>
+#include <winnetwk.h>
+
+#include "mozilla/FileUtilsWin.h"
+#include "mozilla/DebugOnly.h"
+#include "nsCRTGlue.h"
+
+#include "gtest/gtest.h"
+
+class DriveMapping {
+ public:
+ explicit DriveMapping(const nsAString& aRemoteUNCPath);
+ ~DriveMapping();
+
+ bool Init();
+ bool ChangeDriveLetter();
+ wchar_t GetDriveLetter() { return mDriveLetter; }
+
+ private:
+ bool DoMapping();
+ void Disconnect(wchar_t aDriveLetter);
+
+ wchar_t mDriveLetter;
+ nsString mRemoteUNCPath;
+};
+
+DriveMapping::DriveMapping(const nsAString& aRemoteUNCPath)
+ : mDriveLetter(0), mRemoteUNCPath(aRemoteUNCPath) {}
+
+bool DriveMapping::Init() {
+ if (mDriveLetter) {
+ return false;
+ }
+ return DoMapping();
+}
+
+bool DriveMapping::DoMapping() {
+ wchar_t drvTemplate[] = L" :";
+ NETRESOURCEW netRes = {0};
+ netRes.dwType = RESOURCETYPE_DISK;
+ netRes.lpLocalName = drvTemplate;
+ netRes.lpRemoteName =
+ reinterpret_cast<wchar_t*>(mRemoteUNCPath.BeginWriting());
+ wchar_t driveLetter = L'D';
+ DWORD result = NO_ERROR;
+ do {
+ drvTemplate[0] = driveLetter;
+ result = WNetAddConnection2W(&netRes, nullptr, nullptr, CONNECT_TEMPORARY);
+ } while (result == ERROR_ALREADY_ASSIGNED && ++driveLetter <= L'Z');
+ if (result != NO_ERROR) {
+ return false;
+ }
+ mDriveLetter = driveLetter;
+ return true;
+}
+
+bool DriveMapping::ChangeDriveLetter() {
+ wchar_t prevDriveLetter = mDriveLetter;
+ bool result = DoMapping();
+ MOZ_RELEASE_ASSERT(mDriveLetter != prevDriveLetter);
+ if (result && prevDriveLetter) {
+ Disconnect(prevDriveLetter);
+ }
+ return result;
+}
+
+void DriveMapping::Disconnect(wchar_t aDriveLetter) {
+ wchar_t drvTemplate[] = {aDriveLetter, L':', L'\0'};
+ DWORD result = WNetCancelConnection2W(drvTemplate, 0, TRUE);
+ MOZ_RELEASE_ASSERT(result == NO_ERROR);
+}
+
+DriveMapping::~DriveMapping() {
+ if (mDriveLetter) {
+ Disconnect(mDriveLetter);
+ }
+}
+
+bool DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath) {
+ const wchar_t drvTpl[] = {aDriveLetter, L':', L'\0'};
+ aNtPath.SetLength(MAX_PATH);
+ DWORD pathLen;
+ while (true) {
+ pathLen = QueryDosDeviceW(
+ drvTpl, reinterpret_cast<wchar_t*>(aNtPath.BeginWriting()),
+ aNtPath.Length());
+ if (pathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ break;
+ }
+ aNtPath.SetLength(aNtPath.Length() * 2);
+ }
+ if (!pathLen) {
+ return false;
+ }
+ // aNtPath contains embedded NULLs, so we need to figure out the real length
+ // via wcslen.
+ aNtPath.SetLength(NS_strlen(aNtPath.BeginReading()));
+ return true;
+}
+
+bool TestNtPathToDosPath(const wchar_t* aNtPath,
+ const wchar_t* aExpectedDosPath) {
+ nsAutoString output;
+ bool result = mozilla::NtPathToDosPath(nsDependentString(aNtPath), output);
+ return result && output == reinterpret_cast<const nsAString::char_type*>(
+ aExpectedDosPath);
+}
+
+TEST(NtPathToDosPath, Tests)
+{
+ nsAutoString cDrive;
+ ASSERT_TRUE(DriveToNtPath(L'C', cDrive));
+
+ // empty string
+ EXPECT_TRUE(TestNtPathToDosPath(L"", L""));
+
+ // non-existent device, must fail
+ EXPECT_FALSE(
+ TestNtPathToDosPath(L"\\Device\\ThisDeviceDoesNotExist\\Foo", nullptr));
+
+ // base case
+ nsAutoString testPath(cDrive);
+ testPath.Append(L"\\Program Files");
+ EXPECT_TRUE(TestNtPathToDosPath(testPath.get(), L"C:\\Program Files"));
+
+ // short filename
+ nsAutoString ntShortName(cDrive);
+ ntShortName.Append(L"\\progra~1");
+ EXPECT_TRUE(TestNtPathToDosPath(ntShortName.get(), L"C:\\Program Files"));
+
+ // drive letters as symbolic links (NtCreateFile uses these)
+ EXPECT_TRUE(TestNtPathToDosPath(L"\\??\\C:\\Foo", L"C:\\Foo"));
+
+ // other symbolic links (should fail)
+ EXPECT_FALSE(TestNtPathToDosPath(L"\\??\\MountPointManager", nullptr));
+
+ // socket (should fail)
+ EXPECT_FALSE(TestNtPathToDosPath(L"\\Device\\Afd\\Endpoint", nullptr));
+
+ // UNC path (using MUP)
+ EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\Mup\\127.0.0.1\\C$",
+ L"\\\\127.0.0.1\\C$"));
+
+ // UNC path (using LanmanRedirector)
+ EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\LanmanRedirector\\127.0.0.1\\C$",
+ L"\\\\127.0.0.1\\C$"));
+
+ DriveMapping drvMapping(u"\\\\127.0.0.1\\C$"_ns);
+ // Only run these tests if we were able to map; some machines don't have perms
+ if (drvMapping.Init()) {
+ wchar_t expected[] = L" :\\";
+ expected[0] = drvMapping.GetDriveLetter();
+ nsAutoString networkPath;
+ ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath));
+
+ networkPath += u"\\";
+ EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected));
+
+ // NtPathToDosPath must correctly handle paths whose drive letter mapping
+ // has changed. We need to test this because the APIs called by
+ // NtPathToDosPath return different info if this has happened.
+ ASSERT_TRUE(drvMapping.ChangeDriveLetter());
+
+ expected[0] = drvMapping.GetDriveLetter();
+ ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath));
+
+ networkPath += u"\\";
+ EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected));
+ }
+}
diff --git a/xpcom/tests/windows/TestPoisonIOInterposer.cpp b/xpcom/tests/windows/TestPoisonIOInterposer.cpp
new file mode 100644
index 0000000000..057f262c67
--- /dev/null
+++ b/xpcom/tests/windows/TestPoisonIOInterposer.cpp
@@ -0,0 +1,152 @@
+/* -*- 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 <windows.h>
+
+#include "gtest/gtest.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/NativeNt.h"
+#include "nsWindowsHelpers.h"
+
+using namespace mozilla;
+
+class TempFile final {
+ wchar_t mFullPath[MAX_PATH + 1];
+
+ public:
+ TempFile() : mFullPath{0} {
+ wchar_t tempDir[MAX_PATH + 1];
+ DWORD len = ::GetTempPathW(ArrayLength(tempDir), tempDir);
+ if (!len) {
+ return;
+ }
+
+ len = ::GetTempFileNameW(tempDir, L"poi", 0, mFullPath);
+ if (!len) {
+ return;
+ }
+ }
+
+ operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; }
+};
+
+class Overlapped final {
+ nsAutoHandle mEvent;
+ OVERLAPPED mOverlapped;
+
+ public:
+ Overlapped()
+ : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)), mOverlapped{} {
+ mOverlapped.hEvent = mEvent.get();
+ }
+
+ operator OVERLAPPED*() { return &mOverlapped; }
+
+ bool Wait(HANDLE aHandle) {
+ DWORD numBytes;
+ if (!::GetOverlappedResult(aHandle, &mOverlapped, &numBytes, TRUE)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+const uint32_t kMagic = 0x12345678;
+
+void FileOpSync(const wchar_t* aPath) {
+ nsAutoHandle file(::CreateFileW(aPath, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ, nullptr, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, nullptr));
+ ASSERT_NE(file.get(), INVALID_HANDLE_VALUE);
+
+ DWORD buffer = kMagic, numBytes = 0;
+ OVERLAPPED seek = {};
+ EXPECT_TRUE(WriteFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek));
+ EXPECT_TRUE(::FlushFileBuffers(file.get()));
+
+ seek.Offset = 0;
+ buffer = 0;
+ EXPECT_TRUE(
+ ::ReadFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek));
+ EXPECT_EQ(buffer, kMagic);
+
+ WIN32_FILE_ATTRIBUTE_DATA fullAttr = {};
+ EXPECT_TRUE(::GetFileAttributesExW(aPath, GetFileExInfoStandard, &fullAttr));
+}
+
+void FileOpAsync(const wchar_t* aPath) {
+ constexpr int kNumPages = 10;
+ constexpr int kPageSize = 4096;
+
+ Array<UniquePtr<void, VirtualFreeDeleter>, kNumPages> pages;
+ Array<FILE_SEGMENT_ELEMENT, kNumPages + 1> segments;
+ for (int i = 0; i < kNumPages; ++i) {
+ auto p = reinterpret_cast<uint32_t*>(::VirtualAlloc(
+ nullptr, kPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
+ ASSERT_TRUE(p);
+
+ pages[i].reset(p);
+ segments[i].Buffer = p;
+
+ p[0] = kMagic;
+ }
+
+ nsAutoHandle file(::CreateFileW(
+ aPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
+ nullptr));
+ ASSERT_NE(file.get(), INVALID_HANDLE_VALUE);
+
+ Overlapped writeOp;
+ if (!::WriteFileGather(file.get(), segments.begin(), kNumPages * kPageSize,
+ nullptr, writeOp)) {
+ EXPECT_EQ(::GetLastError(), static_cast<DWORD>(ERROR_IO_PENDING));
+ EXPECT_TRUE(writeOp.Wait(file.get()));
+ }
+
+ for (int i = 0; i < kNumPages; ++i) {
+ *reinterpret_cast<uint32_t*>(pages[i].get()) = 0;
+ }
+
+ Overlapped readOp;
+ if (!::ReadFileScatter(file.get(), segments.begin(), kNumPages * kPageSize,
+ nullptr, readOp)) {
+ EXPECT_EQ(::GetLastError(), static_cast<DWORD>(ERROR_IO_PENDING));
+ EXPECT_TRUE(readOp.Wait(file.get()));
+ }
+
+ for (int i = 0; i < kNumPages; ++i) {
+ EXPECT_EQ(*reinterpret_cast<uint32_t*>(pages[i].get()), kMagic);
+ }
+}
+
+TEST(PoisonIOInterposer, NormalThread)
+{
+ mozilla::IOInterposerInit ioInterposerGuard;
+ TempFile tempFile;
+ FileOpSync(tempFile);
+ FileOpAsync(tempFile);
+ EXPECT_TRUE(::DeleteFileW(tempFile));
+}
+
+TEST(PoisonIOInterposer, NullTlsPointer)
+{
+ void* originalTls = mozilla::nt::RtlGetThreadLocalStoragePointer();
+ mozilla::IOInterposerInit ioInterposerGuard;
+
+ // Simulate a loader worker thread (TEB::LoaderWorker = 1)
+ // where ThreadLocalStorage is never allocated.
+ mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(nullptr);
+
+ TempFile tempFile;
+ FileOpSync(tempFile);
+ FileOpAsync(tempFile);
+ EXPECT_TRUE(::DeleteFileW(tempFile));
+
+ mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(originalTls);
+}
diff --git a/xpcom/tests/windows/moz.build b/xpcom/tests/windows/moz.build
new file mode 100644
index 0000000000..d77ec264e1
--- /dev/null
+++ b/xpcom/tests/windows/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; 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/.
+
+UNIFIED_SOURCES += [
+ "TestCOM.cpp",
+ "TestNtPathToDosPath.cpp",
+ "TestPoisonIOInterposer.cpp",
+]
+
+OS_LIBS += [
+ "mpr",
+]
+
+FINAL_LIBRARY = "xul-gtest"