diff --git a/ipc/mscom/COMPtrHolder.h b/ipc/mscom/COMPtrHolder.h new file mode 100644 index 000000000000..56ca7a5d41c1 --- /dev/null +++ b/ipc/mscom/COMPtrHolder.h @@ -0,0 +1,144 @@ +/* -*- 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 mozilla_mscom_COMPtrHolder_h +#define mozilla_mscom_COMPtrHolder_h + +#include "mozilla/Attributes.h" +#include "mozilla/Move.h" +#include "mozilla/mscom/ProxyStream.h" +#include "mozilla/mscom/Ptr.h" + +namespace mozilla { +namespace mscom { + +template +class COMPtrHolder +{ +public: + typedef ProxyUniquePtr COMPtrType; + typedef COMPtrHolder ThisType; + + COMPtrHolder() {} + + MOZ_IMPLICIT COMPtrHolder(decltype(nullptr)) + { + } + + explicit COMPtrHolder(COMPtrType&& aPtr) + : mPtr(Forward(aPtr)) + { + } + + Interface* Get() const + { + return mPtr.get(); + } + + MOZ_MUST_USE Interface* Release() + { + return mPtr.release(); + } + + void Set(COMPtrType&& aPtr) + { + mPtr = Forward(aPtr); + } + + COMPtrHolder(const COMPtrHolder& aOther) = delete; + + COMPtrHolder(COMPtrHolder&& aOther) + : mPtr(Move(aOther.mPtr)) + { + } + + // COMPtrHolder is eventually added as a member of a struct that is declared + // in IPDL. The generated C++ code for that IPDL struct includes copy + // constructors and assignment operators that assume that all members are + // copyable. I don't think that those copy constructors and operator= are + // actually used by any generated code, but they are made available. Since no + // move semantics are available, this terrible hack makes COMPtrHolder build + // when used as a member of an IPDL struct. + ThisType& operator=(const ThisType& aOther) + { + Set(Move(aOther.mPtr)); + return *this; + } + + ThisType& operator=(ThisType&& aOther) + { + Set(Move(aOther.mPtr)); + return *this; + } + + bool operator==(const ThisType& aOther) const + { + return mPtr == aOther.mPtr; + } + + bool IsNull() const + { + return !mPtr; + } + +private: + // This is mutable to facilitate the above operator= hack + mutable COMPtrType mPtr; +}; + +} // namespace mscom +} // namespace mozilla + +namespace IPC { + +template +struct ParamTraits> +{ + typedef mozilla::mscom::COMPtrHolder paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + mozilla::mscom::ProxyStream proxyStream(_IID, aParam.Get()); + int bufLen; + const BYTE* buf = proxyStream.GetBuffer(bufLen); + MOZ_ASSERT(buf); + aMsg->WriteInt(bufLen); + aMsg->WriteBytes(reinterpret_cast(buf), bufLen); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int length; + if (!aMsg->ReadLength(aIter, &length)) { + return false; + } + + mozilla::UniquePtr buf; + if (length) { + buf = mozilla::MakeUnique(length); + if (!aMsg->ReadBytesInto(aIter, buf.get(), length)) { + return false; + } + } + + mozilla::mscom::ProxyStream proxyStream(buf.get(), length); + if (!proxyStream.IsValid()) { + return false; + } + Interface* rawInterface = nullptr; + if (!proxyStream.GetInterface(_IID, (void**)&rawInterface)) { + return false; + } + paramType::COMPtrType ptr(rawInterface); + aResult->Set(mozilla::Move(ptr)); + return true; + } +}; + +} // namespace IPC + +#endif // mozilla_mscom_COMPtrHolder_h + diff --git a/ipc/mscom/ProxyStream.cpp b/ipc/mscom/ProxyStream.cpp new file mode 100644 index 000000000000..c43cc82f0019 --- /dev/null +++ b/ipc/mscom/ProxyStream.cpp @@ -0,0 +1,202 @@ +/* -*- 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 "DynamicallyLinkedFunctionPtr.h" +#include "mozilla/mscom/EnsureMTA.h" +#include "mozilla/mscom/ProxyStream.h" +#include "mozilla/mscom/utils.h" + +#include "mozilla/Move.h" + +#include +#include +#include + +namespace mozilla { +namespace mscom { + +ProxyStream::ProxyStream() + : mGlobalLockedBuf(nullptr) + , mHGlobal(nullptr) + , mBufSize(0) +{ +} + +// GetBuffer() fails with this variant, but that's okay because we're just +// reconstructing the stream from a buffer anyway. +ProxyStream::ProxyStream(const BYTE* aInitBuf, const int aInitBufSize) + : mStream(InitStream(aInitBuf, static_cast(aInitBufSize))) + , mGlobalLockedBuf(nullptr) + , mHGlobal(nullptr) + , mBufSize(aInitBufSize) +{ + if (!aInitBufSize) { + // We marshaled a nullptr. Nothing else to do here. + return; + } + // NB: We can't check for a null mStream until after we have checked for + // the zero aInitBufSize above. This is because InitStream will also fail + // in that case, even though marshaling a nullptr is allowable. + MOZ_ASSERT(mStream); + if (!mStream) { + return; + } + + // We need to convert to an interface here otherwise we mess up const + // correctness with IPDL. We'll request an IUnknown and then QI the + // actual interface later. + + auto marshalFn = [&]() -> void + { + IUnknown* rawUnmarshaledProxy = nullptr; + // OK to forget mStream when calling into this function because the stream + // gets released even if the unmarshaling part fails. + DebugOnly hr = + ::CoGetInterfaceAndReleaseStream(mStream.forget().take(), IID_IUnknown, + (void**)&rawUnmarshaledProxy); + MOZ_ASSERT(SUCCEEDED(hr)); + mUnmarshaledProxy.reset(rawUnmarshaledProxy); + }; + + if (XRE_IsParentProcess()) { + // We'll marshal this stuff directly using the current thread, therefore its + // proxy will reside in the same apartment as the current thread. + marshalFn(); + } else { + // When marshaling in child processes, we want to force the MTA. + EnsureMTA mta(marshalFn); + } +} + +already_AddRefed +ProxyStream::InitStream(const BYTE* aInitBuf, const UINT aInitBufSize) +{ + // Need to link to this as ordinal 12 for Windows XP + static DynamicallyLinkedFunctionPtr + pSHCreateMemStream(L"shlwapi.dll", reinterpret_cast(12)); + if (!pSHCreateMemStream) { + return nullptr; + } + return already_AddRefed(pSHCreateMemStream(aInitBuf, aInitBufSize)); +} + +ProxyStream::ProxyStream(ProxyStream&& aOther) +{ + *this = mozilla::Move(aOther); +} + +ProxyStream& +ProxyStream::operator=(ProxyStream&& aOther) +{ + mStream = mozilla::Move(aOther.mStream); + mGlobalLockedBuf = aOther.mGlobalLockedBuf; + aOther.mGlobalLockedBuf = nullptr; + mHGlobal = aOther.mHGlobal; + aOther.mHGlobal = nullptr; + mBufSize = aOther.mBufSize; + aOther.mBufSize = 0; + return *this; +} + +ProxyStream::~ProxyStream() +{ + if (mHGlobal && mGlobalLockedBuf) { + DebugOnly result = ::GlobalUnlock(mHGlobal); + MOZ_ASSERT(!result && ::GetLastError() == NO_ERROR); + // ::GlobalFree() is called implicitly when mStream is released + } +} + +const BYTE* +ProxyStream::GetBuffer(int& aReturnedBufSize) const +{ + aReturnedBufSize = 0; + if (!mStream) { + return nullptr; + } + if (!mGlobalLockedBuf) { + return nullptr; + } + aReturnedBufSize = mBufSize; + return mGlobalLockedBuf; +} + +bool +ProxyStream::GetInterface(REFIID aIID, void** aOutInterface) const +{ + // We should not have a locked buffer on this side + MOZ_ASSERT(!mGlobalLockedBuf); + MOZ_ASSERT(aOutInterface); + + if (!aOutInterface) { + return false; + } + + if (!mUnmarshaledProxy) { + *aOutInterface = nullptr; + return true; + } + + HRESULT hr = E_UNEXPECTED; + auto qiFn = [&]() -> void + { + hr = mUnmarshaledProxy->QueryInterface(aIID, aOutInterface); + }; + + if (XRE_IsParentProcess()) { + qiFn(); + } else { + // mUnmarshaledProxy requires that we execute this in the MTA + EnsureMTA mta(qiFn); + } + return SUCCEEDED(hr); +} + +ProxyStream::ProxyStream(REFIID aIID, IUnknown* aObject) + : mGlobalLockedBuf(nullptr) + , mHGlobal(nullptr) + , mBufSize(0) +{ + RefPtr stream; + HGLOBAL hglobal = NULL; + + auto marshalFn = [&]() -> void + { + HRESULT hr = ::CreateStreamOnHGlobal(nullptr, TRUE, getter_AddRefs(stream)); + if (FAILED(hr)) { + return; + } + + hr = ::CoMarshalInterface(stream, aIID, aObject, MSHCTX_LOCAL, nullptr, + MSHLFLAGS_NORMAL); + if (FAILED(hr)) { + return; + } + + hr = ::GetHGlobalFromStream(stream, &hglobal); + MOZ_ASSERT(SUCCEEDED(hr)); + }; + + if (XRE_IsParentProcess()) { + // We'll marshal this stuff directly using the current thread, therefore its + // stub will reside in the same apartment as the current thread. + marshalFn(); + } else { + // When marshaling in child processes, we want to force the MTA. + EnsureMTA mta(marshalFn); + } + + mStream = mozilla::Move(stream); + if (hglobal) { + mGlobalLockedBuf = reinterpret_cast(::GlobalLock(hglobal)); + mHGlobal = hglobal; + mBufSize = static_cast(::GlobalSize(hglobal)); + } +} + +} // namespace mscom +} // namespace mozilla + diff --git a/ipc/mscom/ProxyStream.h b/ipc/mscom/ProxyStream.h new file mode 100644 index 000000000000..392fdd2d8b7d --- /dev/null +++ b/ipc/mscom/ProxyStream.h @@ -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/. */ + +#ifndef mozilla_mscom_ProxyStream_h +#define mozilla_mscom_ProxyStream_h + +#include "ipc/IPCMessageUtils.h" + +#include "mozilla/mscom/Ptr.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace mscom { + +class ProxyStream +{ +public: + ProxyStream(); + ProxyStream(REFIID aIID, IUnknown* aObject); + ProxyStream(const BYTE* aInitBuf, const int aInitBufSize); + + ~ProxyStream(); + + // Not copyable because this would mess up the COM marshaling. + ProxyStream(const ProxyStream& aOther) = delete; + ProxyStream& operator=(const ProxyStream& aOther) = delete; + + ProxyStream(ProxyStream&& aOther); + ProxyStream& operator=(ProxyStream&& aOther); + + inline bool IsValid() const + { + // This check must be exclusive OR + return (mStream && !mUnmarshaledProxy) || (mUnmarshaledProxy && !mStream); + } + + bool GetInterface(REFIID aIID, void** aOutInterface) const; + const BYTE* GetBuffer(int& aReturnedBufSize) const; + + bool operator==(const ProxyStream& aOther) const + { + return this == &aOther; + } + +private: + already_AddRefed InitStream(const BYTE* aInitBuf, + const UINT aInitBufSize); + +private: + RefPtr mStream; + BYTE* mGlobalLockedBuf; + HGLOBAL mHGlobal; + int mBufSize; + ProxyUniquePtr mUnmarshaledProxy; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_ProxyStream_h diff --git a/ipc/mscom/moz.build b/ipc/mscom/moz.build index 05cfea9fd040..14b86b4ff86b 100644 --- a/ipc/mscom/moz.build +++ b/ipc/mscom/moz.build @@ -6,8 +6,10 @@ EXPORTS.mozilla.mscom += [ 'COMApartmentRegion.h', + 'COMPtrHolder.h', 'EnsureMTA.h', 'MainThreadRuntime.h', + 'ProxyStream.h', 'Utils.h', ] @@ -18,6 +20,7 @@ SOURCES += [ UNIFIED_SOURCES += [ 'EnsureMTA.cpp', 'MainThreadRuntime.cpp', + 'ProxyStream.cpp', ] include('/ipc/chromium/chromium-config.mozbuild')