diff options
Diffstat (limited to 'xpcom/tests/windows')
-rw-r--r-- | xpcom/tests/windows/TestCOM.cpp | 97 | ||||
-rw-r--r-- | xpcom/tests/windows/TestNtPathToDosPath.cpp | 176 | ||||
-rw-r--r-- | xpcom/tests/windows/TestPoisonIOInterposer.cpp | 152 | ||||
-rw-r--r-- | xpcom/tests/windows/moz.build | 17 |
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" |