Bug 1261107: Adds code to marshal a Microsoft COM object and transfer its serialized proxy across IPDL; r?jimm, r?billm draft
authorAaron Klotz <aklotz@mozilla.com>
Wed, 27 Jul 2016 11:44:29 -0600
changeset 393330 956dd048c67079458d65f5fa9b682b44f09b257e
parent 393329 36b47a0860c3b25302fc01b66427fd6d46c0d89c
child 393331 4b754205656c6f2b4df3be6dc92c8d3c023d1f16
child 397369 28caece13a0bc349a9f6002e878155a170b57ef5
push id24288
push useraklotz@mozilla.com
push dateWed, 27 Jul 2016 18:11:49 +0000
reviewersjimm, billm
bugs1261107
milestone50.0a1
Bug 1261107: Adds code to marshal a Microsoft COM object and transfer its serialized proxy across IPDL; r?jimm, r?billm MozReview-Commit-ID: BpSpue4Fq6G
ipc/mscom/COMPtrHolder.h
ipc/mscom/ProxyStream.cpp
ipc/mscom/ProxyStream.h
ipc/mscom/moz.build
new file mode 100644
--- /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<typename Interface, const IID& _IID>
+class COMPtrHolder
+{
+public:
+  typedef ProxyUniquePtr<Interface> COMPtrType;
+  typedef COMPtrHolder<Interface, _IID> ThisType;
+
+  COMPtrHolder() {}
+
+  MOZ_IMPLICIT COMPtrHolder(decltype(nullptr))
+  {
+  }
+
+  explicit COMPtrHolder(COMPtrType&& aPtr)
+    : mPtr(Forward<COMPtrType>(aPtr))
+  {
+  }
+
+  Interface* Get() const
+  {
+    return mPtr.get();
+  }
+
+  MOZ_MUST_USE Interface* Release()
+  {
+    return mPtr.release();
+  }
+
+  void Set(COMPtrType&& aPtr)
+  {
+    mPtr = Forward<COMPtrType>(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<typename Interface, const IID& _IID>
+struct ParamTraits<mozilla::mscom::COMPtrHolder<Interface, _IID>>
+{
+  typedef mozilla::mscom::COMPtrHolder<Interface, _IID> 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<const char*>(buf), bufLen);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    int length;
+    if (!aMsg->ReadLength(aIter, &length)) {
+      return false;
+    }
+
+    mozilla::UniquePtr<BYTE[]> buf;
+    if (length) {
+      buf = mozilla::MakeUnique<BYTE[]>(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
+
new file mode 100644
--- /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 <windows.h>
+#include <objbase.h>
+#include <shlwapi.h>
+
+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<const UINT>(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<HRESULT> 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<IStream>
+ProxyStream::InitStream(const BYTE* aInitBuf, const UINT aInitBufSize)
+{
+  // Need to link to this as ordinal 12 for Windows XP
+  static DynamicallyLinkedFunctionPtr<decltype(&::SHCreateMemStream)>
+    pSHCreateMemStream(L"shlwapi.dll", reinterpret_cast<const char*>(12));
+  if (!pSHCreateMemStream) {
+    return nullptr;
+  }
+  return already_AddRefed<IStream>(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<BOOL> 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<IStream> 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<BYTE*>(::GlobalLock(hglobal));
+    mHGlobal = hglobal;
+    mBufSize = static_cast<int>(::GlobalSize(hglobal));
+  }
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /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<IStream> InitStream(const BYTE* aInitBuf,
+                                       const UINT aInitBufSize);
+
+private:
+  RefPtr<IStream> mStream;
+  BYTE*           mGlobalLockedBuf;
+  HGLOBAL         mHGlobal;
+  int             mBufSize;
+  ProxyUniquePtr<IUnknown> mUnmarshaledProxy;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_ProxyStream_h
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -1,25 +1,28 @@
 # -*- 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/.
 
 EXPORTS.mozilla.mscom += [
     'COMApartmentRegion.h',
+    'COMPtrHolder.h',
     'EnsureMTA.h',
     'MainThreadRuntime.h',
+    'ProxyStream.h',
     'Utils.h',
 ]
 
 SOURCES += [
     'Utils.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'EnsureMTA.cpp',
     'MainThreadRuntime.cpp',
+    'ProxyStream.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'