Bug 1658419 - Create a class that implements sync'd cross-process shared memory r=handyman

This class (which may be useful outside of just gamepad code) provides an API to share an object across processes in a synchronized manner.

Eventually it will be used to share gamepad data (axes, buttons, etc) immediately with child processes for performance reasons.

Differential Revision: https://phabricator.services.mozilla.com/D100215
This commit is contained in:
Chris Martin 2021-03-03 18:26:46 +00:00
parent 3ccb3d7437
commit 61073bd078
10 changed files with 984 additions and 0 deletions

View File

@ -0,0 +1,295 @@
/* -*- 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/. */
/* Implements a piece of cross-process shared memory that can be shared with
* many processes simultaniously, and is synchronized by a cross-process mutex.
*
* In almost all cases, IPDL and its machinery should be preferred to this.
* This class should only be used if you really know you need it, and
* precautions need to be taken to avoid introducing security bugs.
*
* Specifically:
* - Don't assume that any process other than the main process is trustworthy,
* since exploits may exist in graphics drivers, video codecs, etc.
* - Don't assume the other side can't modify the shared memory even while
* you hold the lock.
* - Don't assume that if you read the same piece of data twice, that it will
* return the same value.
* - Don't assume the other side will ever actually release the lock.
* IE using RunWithLock() on the main thread is probably a bad idea.
*
* If possible, design things so that the trusted side is on a separate
* thread and is write-only and you avoid those problems altogether. If you,
* must read, consider taking a "snapshot" of the shared memory by copying it
* to a local variable, operating on that, and then copying it back to shared
* memory when done.
*
*/
#ifndef DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_
#define DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include <cinttypes>
#include <type_traits>
namespace mozilla::ipc {
class IProtocol;
} // namespace mozilla::ipc
namespace mozilla::dom {
// IPDL structure that knows how to serialize this over IPC
class SynchronizedSharedMemoryRemoteInfo;
// Details that you don't need to know to use this class
//
// This mostly represents an interface/implementation firewall (Pimpl Idiom)
// because the implementation is per-platform and we want to keep this file
// free of platform-specific headers.
//
// It is implemented in terms of "void*" because template classes with template
// methods can't hide their implementations in .cpp files. The main class is a
// template wrapper for this that exposes it as a typesafe API for type `T`.
//
// See SynchronizedSharedMemory below for usage of the public API
class SynchronizedSharedMemoryDetail {
public:
static Maybe<SynchronizedSharedMemoryDetail> CreateNew(uint32_t aSize);
~SynchronizedSharedMemoryDetail();
void LockMutex();
void UnlockMutex();
void* GetPtr() const;
// Allows this to be transferred across IPDL
static Maybe<SynchronizedSharedMemoryDetail> CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo);
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut);
// Allow move
SynchronizedSharedMemoryDetail(SynchronizedSharedMemoryDetail&&);
SynchronizedSharedMemoryDetail& operator=(SynchronizedSharedMemoryDetail&&);
// Disallow copy
SynchronizedSharedMemoryDetail(const SynchronizedSharedMemoryDetail&) =
delete;
SynchronizedSharedMemoryDetail& operator=(
const SynchronizedSharedMemoryDetail&) = delete;
private:
class Impl;
SynchronizedSharedMemoryDetail();
explicit SynchronizedSharedMemoryDetail(UniquePtr<Impl> aImpl);
UniquePtr<Impl> mImpl;
// Testing-related helper
template <typename T>
friend class SynchronizedSharedMemory;
bool GenerateTestRemoteInfo(SynchronizedSharedMemoryRemoteInfo* aOut);
};
// Represents a piece of cross-process synchronized shared memory
//
// Can create a piece of shared memory to hold an object of type T,
// pass it over IPDL to another actor, and can perform synchronized
// cross-process access of the object
//
// See top of file for warnings about security.
//
// The type, T, must be TriviallyCopyable since sharing a piece of memory
// between 2 processes is akin to memcopy-ing its bytes from one program to
// another. StandardLayout may not be strictly necessary, but conforming to the
// platform C ABI is probably a good idea when sharing across processes.
//
// Although not statically enforcable, generally the object should not contain
// pointers, since they are usually meaningless across processes.
template <typename T>
class SynchronizedSharedMemory {
static_assert(std::is_trivially_copyable<T>::value,
"SynchronizedSharedMemory can only be used with types that are "
"trivially copyable");
static_assert(std::is_standard_layout<T>::value,
"SynchronizedSharedMemory can only be used with types that are "
"standard layout");
public:
// Construct a new object of type T in cross-process shareable memory
//
// Returns `Nothing` if an error occurs
//
// # Example
// ```
// struct MyObject {
// int i;
// };
// auto maybeSharedMemory = SynchronizedSharedMemory<MyObject>::CreateNew();
// MOZ_ASSERT(maybeSharedMemory);
// ```
template <typename... Args>
static Maybe<SynchronizedSharedMemory> CreateNew(Args&&... args) {
auto maybeDetail = SynchronizedSharedMemoryDetail::CreateNew(sizeof(T));
if (!maybeDetail) {
return Nothing{};
}
new ((*maybeDetail).GetPtr()) T(std::forward<Args>(args)...);
return Some(SynchronizedSharedMemory(std::move(*maybeDetail)));
}
// Run the given function on the current thread while holding a lock on the
// cross-process mutex
//
// Guarantees that no well-behaved users** will race or forget to unlock when
// accessing this object, even from other processes.
//
// ** Of course, proper locking/unlocking can never be enforced in a
// compromised process. See the security warning at the top of this file
//
// # Example
// ```
// ===== In one process =====
//
// auto maybeSharedMemory = SynchronizedSharedMemory<int>::CreateNew();
// maybeSharedMemory.RunWithLock([] (int* pInt) {
// *pInt = 5;
// });
//
// // ===== In another process =====
//
// int curValue = 0;
// maybeSharedMemory.RunWithLock([&] (int* pInt) {
// curValue = *pInt;
// });
//
// MOZ_ASSERT(curValue == 5);
// ```
template <typename Fn, typename... Args>
void RunWithLock(Fn&& aFn, Args&&... args) {
mDetail.LockMutex();
// Justification for volatile: We need to make a local copy of structure T
// so a compromised remote process can't exploit us by ignoring the lock
// and writing after we've performed validation and trust the data, but
// before we actually use it to do something privileged
//
// Without volatile, the optimizer may be allowed to evade the copy
// and continue to act on the original remote object (among other dangerous
// things)
//
T localCopy;
volatile_memcpy(&localCopy, mDetail.GetPtr(), sizeof(T));
(std::forward<Fn>(aFn))(&localCopy, std::forward<Args>(args)...);
volatile_memcpy(mDetail.GetPtr(), &localCopy, sizeof(T));
mDetail.UnlockMutex();
}
// Create from info generated using GenerateRemoteInfo()
//
// This is the other end to GenerateRemoteInfo(). It receives the
// serialized `SynchronizedSharedMemoryRemoteInfo` structure sent over IPDL
// and uses it to attach to the shared object.
//
// Returns `Nothing` if an error occurs.
//
// # Example
// ```
// // ===== One side of IPDL =====
//
// bool SetupSharedMemory(mozilla::ipc::PMyProtocol& aProtocol) {
// auto maybeRemoteInfo = mSharedMemory.GenerateRemoteInfo(&aProtocol);
// if (!maybeRemoteInfo) {
// return false;
// }
// return aProtocol.SendSharedMemoryInfo(*maybeRemoteInfo);
// }
//
// // ===== Other side of IPDL =====
//
// mozilla::ipc::IPCResult MyProtocol::RecvSharedMemoryInfo(
// const SynchronizedSharedMemoryRemoteInfo& aRemoteInfo) {
// auto maybeSharedMemory = SynchronizedSharedMemory::CreateFromRemoteInfo(
// aRemoteInfo);
// if (!maybeSharedMemory) {
// return IPC_FAIL_NO_REASON(this);
// }
// mSharedMemory = std::move(*maybeSharedMemory);
// return IPC_OK();
// }
// ```
static Maybe<SynchronizedSharedMemory> CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo) {
auto maybeDetail =
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(aIPCInfo);
if (!maybeDetail) {
return Nothing{};
}
return Some(SynchronizedSharedMemory(std::move(*maybeDetail)));
}
// Generate remote info needed to share object with specified actor
//
// Note that the returned information is likely specific to the actor. This
// should be called individually for each actor that will use the shared
// object.
//
// See `CreateFromRemoteInfo` for usage example
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut) {
return mDetail.GenerateRemoteInfo(aActor, aOut);
}
// Allow move
SynchronizedSharedMemory(SynchronizedSharedMemory&&) = default;
SynchronizedSharedMemory& operator=(SynchronizedSharedMemory&&) = default;
// Disallow copy
SynchronizedSharedMemory(const SynchronizedSharedMemory&) = delete;
SynchronizedSharedMemory& operator=(const SynchronizedSharedMemory&) = delete;
~SynchronizedSharedMemory() = default;
private:
SynchronizedSharedMemory() = default;
explicit SynchronizedSharedMemory(SynchronizedSharedMemoryDetail aDetail)
: mDetail(std::move(aDetail)) {}
void volatile_memcpy(void* aDst, const void* aSrc, size_t aSize) {
const volatile unsigned char* src =
static_cast<const volatile unsigned char*>(aSrc);
volatile unsigned char* dst = static_cast<volatile unsigned char*>(aDst);
size_t remaining = aSize;
while (remaining) {
*dst = *src;
++src;
++dst;
--remaining;
}
}
SynchronizedSharedMemoryDetail mDetail;
// The following is for testing of this API. These should be accessed
// from `SynchronizedSharedMemoryTestFriend`. This is used because generating
// a mock IProtocol actor is a fair amount of work.
friend class SynchronizedSharedMemoryTestFriend;
bool GenerateTestRemoteInfo(SynchronizedSharedMemoryRemoteInfo* aOut) {
return mDetail.GenerateTestRemoteInfo(aOut);
}
};
} // namespace mozilla::dom
#endif // DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_

View File

@ -0,0 +1,64 @@
/* -*- 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/. */
// This is the implementation of SynchronizedSharedMemory for unimplemented
// platforms
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
namespace mozilla::dom {
class SynchronizedSharedMemoryDetail::Impl {};
// static
Maybe<SynchronizedSharedMemoryDetail> SynchronizedSharedMemoryDetail::CreateNew(
uint32_t) {
return Nothing{};
}
void SynchronizedSharedMemoryDetail::LockMutex() {
MOZ_CRASH("Should never be called");
}
void SynchronizedSharedMemoryDetail::UnlockMutex() {
MOZ_CRASH("Should never be called");
}
void* SynchronizedSharedMemoryDetail::GetPtr() const {
MOZ_CRASH("Should never be called");
}
// static
Maybe<SynchronizedSharedMemoryDetail>
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo&) {
return Nothing{};
}
bool SynchronizedSharedMemoryDetail::GenerateRemoteInfo(
const mozilla::ipc::IProtocol*, SynchronizedSharedMemoryRemoteInfo*) {
MOZ_CRASH("Should never be called");
}
bool SynchronizedSharedMemoryDetail::GenerateTestRemoteInfo(
SynchronizedSharedMemoryRemoteInfo*) {
MOZ_CRASH("Should never be called");
}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::~SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
SynchronizedSharedMemoryDetail&&) = default;
SynchronizedSharedMemoryDetail& SynchronizedSharedMemoryDetail::operator=(
SynchronizedSharedMemoryDetail&&) = default;
} // namespace mozilla::dom

View File

@ -0,0 +1,12 @@
/* 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/. */
namespace mozilla {
namespace dom {
struct SynchronizedSharedMemoryRemoteInfo {
};
} // namespace dom
} // namespace mozilla

View File

@ -33,6 +33,7 @@ EXPORTS.mozilla.dom += [
"ipc/GamepadMessageUtils.h",
"ipc/GamepadTestChannelChild.h",
"ipc/GamepadTestChannelParent.h",
"SynchronizedSharedMemory.h",
]
UNIFIED_SOURCES = [
@ -64,10 +65,19 @@ elif CONFIG["OS_ARCH"] == "Linux":
else:
UNIFIED_SOURCES += ["fallback/FallbackGamepad.cpp"]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
IPDL_SOURCES += ["windows/SynchronizedSharedMemoryRemoteInfo.ipdlh"]
SOURCES += ["windows/SynchronizedSharedMemory.cpp"]
else:
IPDL_SOURCES += ["fallback/SynchronizedSharedMemoryRemoteInfo.ipdlh"]
SOURCES += ["fallback/SynchronizedSharedMemory.cpp"]
LOCAL_INCLUDES += [
"ipc",
]
TEST_DIRS += ["tests/gtest"]
include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"

View File

@ -0,0 +1,19 @@
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
#include "gtest/gtest.h"
// Dead-simple test for the shared memory fallback: As long as CreateNew()
// returns `Nothing`, there really isn't anything else that can be done with
// the API
namespace mozilla::dom {
// Ensure that we can't create SynchronizedSharedMemory
TEST(SynchronizedSharedMemoryTest, NoCreate)
{
auto maybeSharedMemory = SynchronizedSharedMemory<int>::CreateNew();
ASSERT_TRUE(!maybeSharedMemory);
}
} // namespace mozilla::dom

View File

@ -0,0 +1,183 @@
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
#include <array>
#include <atomic>
#include <chrono>
#include <thread>
#include <windows.h>
#include "gtest/gtest.h"
namespace mozilla::dom {
// Helpers that have private access to SynchronizedSharedMemory
// (As it turns out, creating an entire IPDL actor tree just for a test is
// hard)
class SynchronizedSharedMemoryTestFriend {
public:
template <typename T>
static bool GenerateRemoteInfo(SynchronizedSharedMemory<T>& aSharedMemory,
SynchronizedSharedMemoryRemoteInfo* aOut) {
return aSharedMemory.GenerateTestRemoteInfo(aOut);
}
};
// A basic test that ensures that reading/writing even works
TEST(SynchronizedSharedMemoryTest, Basic)
{
auto maybeLocalObject = SynchronizedSharedMemory<int>::CreateNew(0);
ASSERT_TRUE(maybeLocalObject);
SynchronizedSharedMemoryRemoteInfo remoteInfo;
ASSERT_TRUE(SynchronizedSharedMemoryTestFriend::GenerateRemoteInfo(
*maybeLocalObject, &remoteInfo));
auto maybeRemoteObject =
SynchronizedSharedMemory<int>::CreateFromRemoteInfo(remoteInfo);
ASSERT_TRUE(maybeRemoteObject);
(*maybeLocalObject).RunWithLock([](int* ptr, int value) { *ptr = value; }, 5);
int value = 0;
(*maybeRemoteObject).RunWithLock([&value](int* ptr) { value = *ptr; });
ASSERT_EQ(value, 5);
}
// Multi-threaded test (to try and simulate multi-process use case)
//
// Create a shared object, create a bunch of threads each with their own
// remote handle to the object, and have each thread perform an action
// that would be unsafe if locking were not working properly.
//
// In this case, the threads are incrementing the elements of an array by a
// a constant value, and the array is intentionally large enough that it can't
// fit in a cache line.
//
// Each thread will assert that it never sees a version of the array where all
// the elements aren't equal, and the main thread won't stop the test until it's
// seen a minimum amount of changes made to the entire array without any
// inconsistency.
TEST(SynchronizedSharedMemoryTest, Multithreaded)
{
struct TestStruct {
// Big enough that it can't fit into a single cache line (512 bits on x86)
std::array<uint64_t, 16> data;
void WriteValueToAll(uint64_t value) {
for (auto& x : data) {
x = value;
}
}
void AddValueToAll(uint64_t value) {
for (auto& x : data) {
x += value;
}
}
void AssertAllEqual(uint64_t value) const {
for (size_t i = 0; i < (data.size() - 1); ++i) {
ASSERT_EQ(data[i], data[i + 1]);
}
}
void AssertEq(const TestStruct& other) const {
for (size_t i = 0; i < data.size(); ++i) {
ASSERT_EQ(data[i], other.data[i]);
}
}
};
using SharedType = SynchronizedSharedMemory<TestStruct>;
// Some useful settings for the test
// Number of worker threads to spin up
constexpr size_t kNumThreads = 4;
// Value to start each element at
constexpr uint64_t kTestStartValue = 0x1234;
// Value to increment each element by
constexpr uint64_t kTestIncrementAmount = 0x5678;
// Number of changes the main thread must see before passing
constexpr uint32_t kMinimumChangesToWitness = 10000;
// Initial value to test that the implementation correctly handles copy
// construction of the contained object
constexpr TestStruct kInitialStruct = {0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15};
// Create the shared object, ensure it constructed properly, set all elements
// to kTestStartValue, and assert that it worked
auto maybeLocalObject = SharedType::CreateNew(kInitialStruct);
ASSERT_TRUE(maybeLocalObject);
maybeLocalObject->RunWithLock([&](TestStruct* shared) {
shared->AssertEq(kInitialStruct);
shared->WriteValueToAll(kTestStartValue);
shared->AssertAllEqual(kTestStartValue);
});
// This variable will be set on the main thread when it has seen enough
// changes happen, causing the threads to end
std::atomic_bool done{false};
// Fire up the worker threads, each with their own "remote info", and have
// them add a constant to each element in the array while holding the lock
// If any thread ever witnesses a situation where all elements aren't equal,
// the test is failed
std::array<std::thread, kNumThreads> workerThreads;
for (auto& t : workerThreads) {
SynchronizedSharedMemoryRemoteInfo remoteInfo;
ASSERT_TRUE(SynchronizedSharedMemoryTestFriend::GenerateRemoteInfo(
*maybeLocalObject, &remoteInfo));
t = std::thread([remoteInfo, &done] {
auto maybeRemoteObject = SharedType::CreateFromRemoteInfo(remoteInfo);
ASSERT_TRUE(maybeRemoteObject);
while (!done.load(std::memory_order_relaxed)) {
maybeRemoteObject->RunWithLock([&](TestStruct* shared) {
shared->AssertAllEqual(shared->data[0]);
shared->AddValueToAll(kTestIncrementAmount);
shared->AssertAllEqual(shared->data[0]);
});
}
});
}
// The main thread will just keep locking the mutex and sampling the state
// of the array, ensuring all elements are equal and keeping track of the
// number of times it's seen the value change.
// Once it's seen `kMinimumChangesToWitness` changes occur, it will set
// `done` to signal all the threads to stop and join with them.
uint64_t numChangesWitnessed = 0;
uint64_t lastValueWitnessed = kTestStartValue;
while (numChangesWitnessed < kMinimumChangesToWitness) {
// Grab the current value of the array and ensure they're all equal
uint64_t valueWitnessed = 0;
maybeLocalObject->RunWithLock([&](TestStruct* shared) {
valueWitnessed = shared->data[0];
shared->AssertAllEqual(valueWitnessed);
});
// If the value has changed, take note
if (lastValueWitnessed != valueWitnessed) {
++numChangesWitnessed;
lastValueWitnessed = valueWitnessed;
}
}
// We passed! Shut it all down
done.store(true, std::memory_order_relaxed);
for (auto& t : workerThreads) {
t.join();
}
}
} // namespace mozilla::dom

View File

@ -0,0 +1,16 @@
# 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/.
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
SOURCES = [
"TestSynchronizedSharedMemoryWindows.cpp",
]
else:
SOURCES = [
"TestSynchronizedSharedMemoryFallback.cpp",
]
FINAL_LIBRARY = "xul-gtest"
include("/ipc/chromium/chromium-config.mozbuild")

View File

@ -0,0 +1,156 @@
/* -*- 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/. */
#ifndef DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_
#define DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_
#include "mozilla/Assertions.h"
#include <utility>
#include <windows.h>
namespace mozilla::dom {
// UniqueHandle - A generic RAII wrapper for handle-like objects
//
// A type that behaves like UniquePtr, but for arbitrary handle types.
// The type, HandleType, only needs to be copyable (like all C primitive types
// and structures). The `Traits` template parameter will define the `HandleType`
// and the following 3 or 4 operations on the basic type:
//
// static HandleType Invalid()
// Must return an instance of the object that is considered invalid
// according to `IsValid()`. Note that UniqueHandle doesn't assume that
// there is a sentinal value that is invalid -- It only cares that
// IsValid(Invalid()) is false
//
// static bool IsValid(HandleType)
// Returns true if the handle is valid, false otherwise
//
// static void Close(HandleType)
// Called on destruction to close the handle (only called if IsValid())
//
// static bool IsEqual(HandleType, HandleType)
// OPTIONAL -- If you want the UniqueHandle type to support == and !=
// operators, implement this. It doesn't affect anything else.
//
// (Note that the functions can take the handle by value or by const-ref)
//
// # Example
//
// struct FdHandleTraits {
// using HandleType = int;
// static HandleType Invalid() { return -1; }
// static void Close(HandleType handle) { close(handle); }
// static bool IsValid(HandleType handle) { return handle >= 0; }
// static bool IsEqual(HandleType a, HandleType b) { return a == b; }
// };
//
// UniqueHandle<FdHandleTraits> uniqueFile(open(...));
//
template <typename Traits>
class UniqueHandle {
public:
using HandleType = typename Traits::HandleType;
UniqueHandle() : mHandle(Traits::Invalid()) {
MOZ_ASSERT(!Traits::IsValid(mHandle));
}
explicit UniqueHandle(HandleType t) : mHandle(std::move(t)) {
MOZ_ASSERT(!Traits::IsValid(Traits::Invalid()));
}
UniqueHandle(UniqueHandle&& other) : UniqueHandle() { swap(*this, other); }
~UniqueHandle() {
if (Traits::IsValid(mHandle)) {
Traits::Close(mHandle);
mHandle = Traits::Invalid();
}
}
UniqueHandle& operator=(UniqueHandle&& other) {
UniqueHandle temp(std::move(other));
swap(*this, temp);
return *this;
}
HandleType Release() {
using std::swap;
HandleType result = Traits::Invalid();
swap(result, mHandle);
return result;
}
explicit operator bool() const { return Traits::IsValid(mHandle); }
HandleType Get() const { return mHandle; }
friend void swap(UniqueHandle& a, UniqueHandle& b) {
// Well-behaved types *should* properly handle self-swap, but let's be
// defensive just in case
if (&a != &b) {
using std::swap;
swap(a.mHandle, b.mHandle);
}
}
// If the traits don't provide an isEqual() function, this will get SFINAE'd
// out and equality tests won't be supported
template <typename = decltype(Traits::IsEqual(Traits::Invalid(),
Traits::Invalid()))>
friend bool operator==(const UniqueHandle& a, const UniqueHandle& b) {
return Traits::IsEqual(a.mHandle, b.mHandle);
}
template <typename = decltype(Traits::IsEqual(Traits::Invalid(),
Traits::Invalid()))>
friend bool operator!=(const UniqueHandle& a, const UniqueHandle& b) {
return !(a == b);
}
UniqueHandle(const UniqueHandle&) = delete;
UniqueHandle& operator=(const UniqueHandle&) = delete;
private:
HandleType mHandle;
};
// UniqueHandle trait for NT Kernel handles.
// Has `Tag` to create a strong type for each kind of NT handle.
template <typename Tag>
struct NTKernelHandleTraits {
using HandleType = HANDLE;
static HandleType Invalid() { return nullptr; }
static bool IsValid(HandleType handle) { return !!handle; }
static void Close(HandleType handle) {
MOZ_ALWAYS_TRUE(::CloseHandle(handle));
}
static bool IsEqual(const HandleType& a, const HandleType& b) {
return a == b;
}
};
// Create strong handle traits for several different NT handle types
struct NTMutexHandleTag {};
struct NTEventHandleTag {};
struct NTFileMappingHandleTag {};
using NTMutexHandleTraits = NTKernelHandleTraits<NTMutexHandleTag>;
using NTEventHandleTraits = NTKernelHandleTraits<NTEventHandleTag>;
using NTFileMappingHandleTraits = NTKernelHandleTraits<NTFileMappingHandleTag>;
// UniqueHandle trait for NT file mapping
struct NTFileViewHandleTraits {
using HandleType = HANDLE;
static void* Invalid() { return nullptr; }
static bool IsValid(void* p) { return !!p; }
static void Close(void* p) { MOZ_ALWAYS_TRUE(::UnmapViewOfFile(p)); }
static bool IsEqual(const void* a, const void* b) { return a == b; }
};
} // namespace mozilla::dom
#endif // DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_

View File

@ -0,0 +1,213 @@
/* -*- 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/. */
// This is the implementation of SynchronizedSharedMemory for Windows.
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "GamepadWindowsUtil.h"
#include <utility>
#include <windows.h>
namespace mozilla::dom {
// This Impl class is where most of the actual logic is for creating/using
// the shared memory is located. The public member functions of
// SynchronizedSharedMemoryDetail mostly just pass the call through to this.
class SynchronizedSharedMemoryDetail::Impl {
public:
static UniquePtr<Impl> CreateNew(uint32_t aSize) {
UniqueHandle<NTMutexHandleTraits> mutex(::CreateMutex(
nullptr /*no ACL*/, FALSE /*not owned*/, nullptr /*no name*/));
if (!mutex) {
return nullptr;
}
UniqueHandle<NTFileMappingHandleTraits> fileMapping(
::CreateFileMapping(INVALID_HANDLE_VALUE /*memory-only*/,
nullptr /*no ACL*/, PAGE_READWRITE, 0 /*sizeHigh*/,
aSize /*sizeLow*/, nullptr /*no name*/));
if (!fileMapping) {
return nullptr;
}
UniqueHandle<NTFileViewHandleTraits> sharedPtr(::MapViewOfFile(
fileMapping.Get(), FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
0 /*offsetLow*/, 0 /*Map entire region*/));
if (!sharedPtr) {
return nullptr;
}
return UniquePtr<Impl>(new Impl(std::move(mutex), std::move(fileMapping),
std::move(sharedPtr)));
}
static UniquePtr<Impl> CreateFromRemote(
const SynchronizedSharedMemoryRemoteInfo& aRemoteCreationInfo) {
UniqueHandle<NTMutexHandleTraits> mutex(
reinterpret_cast<HANDLE>(aRemoteCreationInfo.mutexHandle()));
UniqueHandle<NTFileMappingHandleTraits> fileMapping(
reinterpret_cast<HANDLE>(aRemoteCreationInfo.sharedFileHandle()));
if (!mutex || !fileMapping) {
return nullptr;
}
UniqueHandle<NTFileViewHandleTraits> sharedPtr(::MapViewOfFile(
fileMapping.Get(), FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
0 /*offsetLow*/, 0 /*Map entire region*/));
if (!sharedPtr) {
return nullptr;
}
return UniquePtr<Impl>(new Impl(std::move(mutex), std::move(fileMapping),
std::move(sharedPtr)));
}
~Impl() {
MOZ_ASSERT(mMutex);
MOZ_ASSERT(mFileMapping);
MOZ_ASSERT(mSharedPtr);
}
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut) {
MOZ_ASSERT(mMutex);
MOZ_ASSERT(mFileMapping);
DWORD targetPID = aActor ? aActor->OtherPid() : ::GetCurrentProcessId();
HANDLE remoteMutexHandle = nullptr;
if (!mozilla::ipc::DuplicateHandle(mMutex.Get(), targetPID,
&remoteMutexHandle, 0,
DUPLICATE_SAME_ACCESS)) {
return false;
}
HANDLE remoteSharedFileHandle = nullptr;
if (!mozilla::ipc::DuplicateHandle(mFileMapping.Get(), targetPID,
&remoteSharedFileHandle, 0,
DUPLICATE_SAME_ACCESS)) {
// There's no reasonable way to prevent leaking remoteMutexHandle here,
// since we don't own it
return false;
}
MOZ_ASSERT(remoteMutexHandle);
MOZ_ASSERT(remoteSharedFileHandle);
(*aOut) = SynchronizedSharedMemoryRemoteInfo{
reinterpret_cast<WindowsHandle>(remoteMutexHandle),
reinterpret_cast<WindowsHandle>(remoteSharedFileHandle),
};
return true;
}
void LockMutex() {
MOZ_ALWAYS_TRUE(::WaitForSingleObject(mMutex.Get(), INFINITE) !=
WAIT_FAILED);
}
void UnlockMutex() { MOZ_ALWAYS_TRUE(::ReleaseMutex(mMutex.Get())); }
void* GetPtr() const { return mSharedPtr.Get(); }
// Disallow copying/moving
Impl(const Impl&) = delete;
Impl(Impl&&) = delete;
Impl& operator=(const Impl&) = delete;
Impl& operator=(Impl&&) = delete;
private:
Impl(UniqueHandle<NTMutexHandleTraits> aMutex,
UniqueHandle<NTFileMappingHandleTraits> aFileMapping,
UniqueHandle<NTFileViewHandleTraits> aSharedPtr)
: mMutex(std::move(aMutex)),
mFileMapping(std::move(aFileMapping)),
mSharedPtr(std::move(aSharedPtr)) {
MOZ_ASSERT(mMutex);
MOZ_ASSERT(mFileMapping);
MOZ_ASSERT(mSharedPtr);
}
UniqueHandle<NTMutexHandleTraits> mMutex;
UniqueHandle<NTFileMappingHandleTraits> mFileMapping;
UniqueHandle<NTFileViewHandleTraits> mSharedPtr;
};
//////////// Everything below this line is Pimpl boilerplate ///////////////////
// static
Maybe<SynchronizedSharedMemoryDetail> SynchronizedSharedMemoryDetail::CreateNew(
uint32_t aSize) {
MOZ_RELEASE_ASSERT(aSize);
UniquePtr<Impl> impl = Impl::CreateNew(aSize);
if (!impl) {
return Nothing{};
}
return Some(SynchronizedSharedMemoryDetail(std::move(impl)));
}
void SynchronizedSharedMemoryDetail::LockMutex() {
MOZ_RELEASE_ASSERT(mImpl);
mImpl->LockMutex();
}
void SynchronizedSharedMemoryDetail::UnlockMutex() {
MOZ_RELEASE_ASSERT(mImpl);
mImpl->UnlockMutex();
}
void* SynchronizedSharedMemoryDetail::GetPtr() const {
MOZ_RELEASE_ASSERT(mImpl);
return mImpl->GetPtr();
}
// static
Maybe<SynchronizedSharedMemoryDetail>
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo) {
UniquePtr<Impl> impl = Impl::CreateFromRemote(aIPCInfo);
if (!impl) {
return Nothing{};
}
return Some(SynchronizedSharedMemoryDetail(std::move(impl)));
}
bool SynchronizedSharedMemoryDetail::GenerateRemoteInfo(
const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut) {
MOZ_RELEASE_ASSERT(mImpl);
return mImpl->GenerateRemoteInfo(aActor, aOut);
}
bool SynchronizedSharedMemoryDetail::GenerateTestRemoteInfo(
SynchronizedSharedMemoryRemoteInfo* aOut) {
MOZ_RELEASE_ASSERT(mImpl);
return mImpl->GenerateRemoteInfo(nullptr, aOut);
}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::~SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {
MOZ_ASSERT(mImpl);
}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
SynchronizedSharedMemoryDetail&&) = default;
SynchronizedSharedMemoryDetail& SynchronizedSharedMemoryDetail::operator=(
SynchronizedSharedMemoryDetail&&) = default;
} // namespace mozilla::dom

View File

@ -0,0 +1,16 @@
/* 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/. */
using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
namespace mozilla {
namespace dom {
struct SynchronizedSharedMemoryRemoteInfo {
WindowsHandle mutexHandle;
WindowsHandle sharedFileHandle;
};
} // namespace dom
} // namespace mozilla