diff options
Diffstat (limited to 'ipc/mscom')
29 files changed, 3948 insertions, 0 deletions
diff --git a/ipc/mscom/ActivationContext.cpp b/ipc/mscom/ActivationContext.cpp new file mode 100644 index 000000000..2d0261038 --- /dev/null +++ b/ipc/mscom/ActivationContext.cpp @@ -0,0 +1,46 @@ +/* -*- 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 "mozilla/mscom/ActivationContext.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" + +namespace mozilla { +namespace mscom { + +ActivationContext::ActivationContext(HMODULE aLoadFromModule) + : mActCtx(INVALID_HANDLE_VALUE) + , mActivationCookie(0) +{ + ACTCTX actCtx = {sizeof(actCtx)}; + actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID; + actCtx.lpResourceName = MAKEINTRESOURCE(2); + actCtx.hModule = aLoadFromModule; + + mActCtx = ::CreateActCtx(&actCtx); + MOZ_ASSERT(mActCtx != INVALID_HANDLE_VALUE); + if (mActCtx == INVALID_HANDLE_VALUE) { + return; + } + if (!::ActivateActCtx(mActCtx, &mActivationCookie)) { + ::ReleaseActCtx(mActCtx); + mActCtx = INVALID_HANDLE_VALUE; + } +} + +ActivationContext::~ActivationContext() +{ + if (mActCtx == INVALID_HANDLE_VALUE) { + return; + } + DebugOnly<BOOL> deactivated = ::DeactivateActCtx(0, mActivationCookie); + MOZ_ASSERT(deactivated); + ::ReleaseActCtx(mActCtx); +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/ActivationContext.h b/ipc/mscom/ActivationContext.h new file mode 100644 index 000000000..08eda7772 --- /dev/null +++ b/ipc/mscom/ActivationContext.h @@ -0,0 +1,35 @@ +/* -*- 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_ActivationContext_h +#define mozilla_mscom_ActivationContext_h + +#include <windows.h> + +namespace mozilla { +namespace mscom { + +class ActivationContext +{ +public: + explicit ActivationContext(HMODULE aLoadFromModule); + ~ActivationContext(); + + explicit operator bool() const + { + return mActCtx != INVALID_HANDLE_VALUE; + } + +private: + HANDLE mActCtx; + ULONG_PTR mActivationCookie; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_ActivationContext_h + diff --git a/ipc/mscom/COMApartmentRegion.h b/ipc/mscom/COMApartmentRegion.h new file mode 100644 index 000000000..dc7ddf9ae --- /dev/null +++ b/ipc/mscom/COMApartmentRegion.h @@ -0,0 +1,62 @@ +/* -*- 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_COMApartmentRegion_h +#define mozilla_mscom_COMApartmentRegion_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +#include <objbase.h> + +namespace mozilla { +namespace mscom { + +template<COINIT T> +class MOZ_NON_TEMPORARY_CLASS COMApartmentRegion +{ +public: + COMApartmentRegion() + : mInitResult(::CoInitializeEx(nullptr, T)) + { + // If this fires then we're probably mixing apartments on the same thread + MOZ_ASSERT(IsValid()); + } + + ~COMApartmentRegion() + { + if (IsValid()) { + ::CoUninitialize(); + } + } + + bool IsValidOutermost() const + { + return mInitResult == S_OK; + } + + bool IsValid() const + { + return SUCCEEDED(mInitResult); + } + +private: + COMApartmentRegion(const COMApartmentRegion&) = delete; + COMApartmentRegion& operator=(const COMApartmentRegion&) = delete; + COMApartmentRegion(COMApartmentRegion&&) = delete; + COMApartmentRegion& operator=(COMApartmentRegion&&) = delete; + + HRESULT mInitResult; +}; + +typedef COMApartmentRegion<COINIT_APARTMENTTHREADED> STARegion; +typedef COMApartmentRegion<COINIT_MULTITHREADED> MTARegion; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_COMApartmentRegion_h + diff --git a/ipc/mscom/COMPtrHolder.h b/ipc/mscom/COMPtrHolder.h new file mode 100644 index 000000000..88d372a27 --- /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 || !bufLen); + 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; + } + typename paramType::COMPtrType ptr(rawInterface); + aResult->Set(mozilla::Move(ptr)); + return true; + } +}; + +} // namespace IPC + +#endif // mozilla_mscom_COMPtrHolder_h + diff --git a/ipc/mscom/DispatchForwarder.cpp b/ipc/mscom/DispatchForwarder.cpp new file mode 100644 index 000000000..acc76f383 --- /dev/null +++ b/ipc/mscom/DispatchForwarder.cpp @@ -0,0 +1,167 @@ +/* -*- 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 "mozilla/Move.h" +#include "mozilla/mscom/DispatchForwarder.h" +#include "mozilla/mscom/MainThreadInvoker.h" + +#include <oleauto.h> + +namespace mozilla { +namespace mscom { + +/* static */ HRESULT +DispatchForwarder::Create(IInterceptor* aInterceptor, + STAUniquePtr<IDispatch>& aTarget, IUnknown** aOutput) +{ + MOZ_ASSERT(aInterceptor && aOutput); + if (!aOutput) { + return E_INVALIDARG; + } + *aOutput = nullptr; + if (!aInterceptor) { + return E_INVALIDARG; + } + DispatchForwarder* forwarder = new DispatchForwarder(aInterceptor, aTarget); + HRESULT hr = forwarder->QueryInterface(IID_IDispatch, (void**) aOutput); + forwarder->Release(); + return hr; +} + +DispatchForwarder::DispatchForwarder(IInterceptor* aInterceptor, + STAUniquePtr<IDispatch>& aTarget) + : mRefCnt(1) + , mInterceptor(aInterceptor) + , mTarget(Move(aTarget)) +{ +} + +DispatchForwarder::~DispatchForwarder() +{ +} + +HRESULT +DispatchForwarder::QueryInterface(REFIID riid, void** ppv) +{ + if (!ppv) { + return E_INVALIDARG; + } + + // Since this class implements a tearoff, any interfaces that are not + // IDispatch must be routed to the original object's QueryInterface. + // This is especially important for IUnknown since COM uses that interface + // to determine object identity. + if (riid != IID_IDispatch) { + return mInterceptor->QueryInterface(riid, ppv); + } + + IUnknown* punk = static_cast<IDispatch*>(this); + *ppv = punk; + if (!punk) { + return E_NOINTERFACE; + } + + punk->AddRef(); + return S_OK; +} + +ULONG +DispatchForwarder::AddRef() +{ + return (ULONG) InterlockedIncrement((LONG*)&mRefCnt); +} + +ULONG +DispatchForwarder::Release() +{ + ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt); + if (newRefCnt == 0) { + delete this; + } + return newRefCnt; +} + +HRESULT +DispatchForwarder::GetTypeInfoCount(UINT *pctinfo) +{ + if (!pctinfo) { + return E_INVALIDARG; + } + *pctinfo = 1; + return S_OK; +} + +HRESULT +DispatchForwarder::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) +{ + // ITypeInfo as implemented by COM is apartment-neutral, so we don't need + // to wrap it (yay!) + if (mTypeInfo) { + *ppTInfo = mTypeInfo.get(); + mTypeInfo->AddRef(); + return S_OK; + } + HRESULT hr = E_UNEXPECTED; + auto fn = [&]() -> void { + hr = mTarget->GetTypeInfo(iTInfo, lcid, ppTInfo); + }; + MainThreadInvoker invoker; + if (!invoker.Invoke(NS_NewRunnableFunction(fn))) { + return E_UNEXPECTED; + } + if (FAILED(hr)) { + return hr; + } + mTypeInfo = *ppTInfo; + return hr; +} + +HRESULT +DispatchForwarder::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, + LCID lcid, DISPID *rgDispId) +{ + HRESULT hr = E_UNEXPECTED; + auto fn = [&]() -> void { + hr = mTarget->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId); + }; + MainThreadInvoker invoker; + if (!invoker.Invoke(NS_NewRunnableFunction(fn))) { + return E_UNEXPECTED; + } + return hr; +} + +HRESULT +DispatchForwarder::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS *pDispParams, + VARIANT *pVarResult, EXCEPINFO *pExcepInfo, + UINT *puArgErr) +{ + HRESULT hr; + if (!mInterface) { + if (!mTypeInfo) { + return E_UNEXPECTED; + } + TYPEATTR* typeAttr = nullptr; + hr = mTypeInfo->GetTypeAttr(&typeAttr); + if (FAILED(hr)) { + return hr; + } + hr = mInterceptor->QueryInterface(typeAttr->guid, + (void**)getter_AddRefs(mInterface)); + mTypeInfo->ReleaseTypeAttr(typeAttr); + if (FAILED(hr)) { + return hr; + } + } + // We don't invoke IDispatch on the target, but rather on the interceptor! + hr = ::DispInvoke(mInterface.get(), mTypeInfo, dispIdMember, wFlags, + pDispParams, pVarResult, pExcepInfo, puArgErr); + return hr; +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/DispatchForwarder.h b/ipc/mscom/DispatchForwarder.h new file mode 100644 index 000000000..0da5f8548 --- /dev/null +++ b/ipc/mscom/DispatchForwarder.h @@ -0,0 +1,81 @@ +/* -*- 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_DispatchForwarder_h +#define mozilla_mscom_DispatchForwarder_h + +#include <oaidl.h> + +#include "mozilla/mscom/Interceptor.h" +#include "mozilla/mscom/Ptr.h" + +namespace mozilla { +namespace mscom { + +class DispatchForwarder final : public IDispatch +{ +public: + static HRESULT Create(IInterceptor* aInterceptor, + STAUniquePtr<IDispatch>& aTarget, IUnknown** aOutput); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IDispatch + STDMETHODIMP GetTypeInfoCount( + /* [out] */ __RPC__out UINT *pctinfo) override; + + STDMETHODIMP GetTypeInfo( + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo) override; + + STDMETHODIMP GetIDsOfNames( + /* [in] */ __RPC__in REFIID riid, + /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames, + /* [range][in] */ __RPC__in_range(0,16384) UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId) + override; + + STDMETHODIMP Invoke( + /* [annotation][in] */ + _In_ DISPID dispIdMember, + /* [annotation][in] */ + _In_ REFIID riid, + /* [annotation][in] */ + _In_ LCID lcid, + /* [annotation][in] */ + _In_ WORD wFlags, + /* [annotation][out][in] */ + _In_ DISPPARAMS *pDispParams, + /* [annotation][out] */ + _Out_opt_ VARIANT *pVarResult, + /* [annotation][out] */ + _Out_opt_ EXCEPINFO *pExcepInfo, + /* [annotation][out] */ + _Out_opt_ UINT *puArgErr) override; + +private: + DispatchForwarder(IInterceptor* aInterceptor, + STAUniquePtr<IDispatch>& aTarget); + ~DispatchForwarder(); + +private: + ULONG mRefCnt; + RefPtr<IInterceptor> mInterceptor; + STAUniquePtr<IDispatch> mTarget; + RefPtr<ITypeInfo> mTypeInfo; + RefPtr<IUnknown> mInterface; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_DispatchForwarder_h + diff --git a/ipc/mscom/DynamicallyLinkedFunctionPtr.h b/ipc/mscom/DynamicallyLinkedFunctionPtr.h new file mode 100644 index 000000000..c774119c8 --- /dev/null +++ b/ipc/mscom/DynamicallyLinkedFunctionPtr.h @@ -0,0 +1,68 @@ +/* -*- 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_DynamicallyLinkedFunctionPtr_h +#define mozilla_mscom_DynamicallyLinkedFunctionPtr_h + +#include "mozilla/Move.h" +#include <windows.h> + +namespace mozilla { +namespace mscom { + +template <typename T> +class DynamicallyLinkedFunctionPtr; + +template <typename R, typename... Args> +class DynamicallyLinkedFunctionPtr<R (__stdcall*)(Args...)> +{ + typedef R (__stdcall* FunctionPtrT)(Args...); + +public: + DynamicallyLinkedFunctionPtr(const wchar_t* aLibName, const char* aFuncName) + : mModule(NULL) + , mFunction(nullptr) + { + mModule = ::LoadLibraryW(aLibName); + if (mModule) { + mFunction = reinterpret_cast<FunctionPtrT>( + ::GetProcAddress(mModule, aFuncName)); + } + } + + DynamicallyLinkedFunctionPtr(const DynamicallyLinkedFunctionPtr&) = delete; + DynamicallyLinkedFunctionPtr& operator=(const DynamicallyLinkedFunctionPtr&) = delete; + + DynamicallyLinkedFunctionPtr(DynamicallyLinkedFunctionPtr&&) = delete; + DynamicallyLinkedFunctionPtr& operator=(DynamicallyLinkedFunctionPtr&&) = delete; + + ~DynamicallyLinkedFunctionPtr() + { + if (mModule) { + ::FreeLibrary(mModule); + } + } + + R operator()(Args... args) + { + return mFunction(mozilla::Forward<Args>(args)...); + } + + explicit operator bool() const + { + return !!mFunction; + } + +private: + HMODULE mModule; + FunctionPtrT mFunction; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_DynamicallyLinkedFunctionPtr_h + diff --git a/ipc/mscom/EnsureMTA.cpp b/ipc/mscom/EnsureMTA.cpp new file mode 100644 index 000000000..f4bc911e9 --- /dev/null +++ b/ipc/mscom/EnsureMTA.cpp @@ -0,0 +1,78 @@ +/* -*- 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 "mozilla/mscom/EnsureMTA.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsThreadUtils.h" + +#include "private/pprthred.h" + +namespace { + +class EnterMTARunnable : public mozilla::Runnable +{ +public: + NS_IMETHOD Run() override + { + mozilla::DebugOnly<HRESULT> hr = ::CoInitializeEx(nullptr, + COINIT_MULTITHREADED); + MOZ_ASSERT(SUCCEEDED(hr)); + return NS_OK; + } +}; + +class BackgroundMTAData +{ +public: + BackgroundMTAData() + { + nsCOMPtr<nsIRunnable> runnable = new EnterMTARunnable(); + nsresult rv = NS_NewNamedThread("COM MTA", + getter_AddRefs(mThread), runnable); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_NewNamedThread failed"); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + ~BackgroundMTAData() + { + if (mThread) { + mThread->Dispatch(NS_NewRunnableFunction(&::CoUninitialize), + NS_DISPATCH_NORMAL); + mThread->Shutdown(); + } + } + + nsCOMPtr<nsIThread> GetThread() const + { + return mThread; + } + +private: + nsCOMPtr<nsIThread> mThread; +}; + +} // anonymous namespace + +static mozilla::StaticAutoPtr<BackgroundMTAData> sMTAData; + +namespace mozilla { +namespace mscom { + +/* static */ nsCOMPtr<nsIThread> +EnsureMTA::GetMTAThread() +{ + if (!sMTAData) { + sMTAData = new BackgroundMTAData(); + ClearOnShutdown(&sMTAData, ShutdownPhase::ShutdownThreads); + } + return sMTAData->GetThread(); +} + +} // namespace mscom +} // namespace mozilla + diff --git a/ipc/mscom/EnsureMTA.h b/ipc/mscom/EnsureMTA.h new file mode 100644 index 000000000..1a8331daa --- /dev/null +++ b/ipc/mscom/EnsureMTA.h @@ -0,0 +1,87 @@ +/* -*- 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_EnsureMTA_h +#define mozilla_mscom_EnsureMTA_h + +#include "MainThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Function.h" +#include "mozilla/mscom/COMApartmentRegion.h" +#include "mozilla/mscom/Utils.h" +#include "nsCOMPtr.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#include <windows.h> + +namespace mozilla { +namespace mscom { + +// This class is OK to use as a temporary on the stack. +class MOZ_STACK_CLASS EnsureMTA +{ +public: + /** + * This constructor just ensures that the MTA thread is up and running. + */ + EnsureMTA() + { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIThread> thread = GetMTAThread(); + MOZ_ASSERT(thread); + } + + template <typename FuncT> + EnsureMTA(const FuncT& aClosure) + { + MOZ_ASSERT(NS_IsMainThread()); + if (IsCurrentThreadMTA()) { + // We're already on the MTA, we can run aClosure directly + aClosure(); + return; + } + + // In this case we need to run aClosure on a background thread in the MTA + nsCOMPtr<nsIThread> thread = GetMTAThread(); + MOZ_ASSERT(thread); + + HANDLE event = ::CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (!event) { + return; + } + + auto eventSetter = [&]() -> void { + aClosure(); + ::SetEvent(event); + }; + + nsresult rv = + thread->Dispatch(NS_NewRunnableFunction(eventSetter), NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_WARN_IF(NS_FAILED(rv))) { + ::CloseHandle(event); + return; + } + + DWORD waitResult; + while ((waitResult = ::WaitForSingleObjectEx(event, INFINITE, TRUE)) == + WAIT_IO_COMPLETION) { + } + MOZ_ASSERT(waitResult == WAIT_OBJECT_0); + ::CloseHandle(event); + } + +private: + static nsCOMPtr<nsIThread> GetMTAThread(); +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_EnsureMTA_h + diff --git a/ipc/mscom/Interceptor.cpp b/ipc/mscom/Interceptor.cpp new file mode 100644 index 000000000..0361cfc6d --- /dev/null +++ b/ipc/mscom/Interceptor.cpp @@ -0,0 +1,313 @@ +/* -*- 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/. */ + +#define INITGUID +#include "mozilla/mscom/Interceptor.h" +#include "mozilla/mscom/InterceptorLog.h" + +#include "mozilla/mscom/DispatchForwarder.h" +#include "mozilla/mscom/MainThreadInvoker.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/mscom/Utils.h" +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace mscom { + +/* static */ HRESULT +Interceptor::Create(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink, + REFIID aIid, void** aOutput) +{ + MOZ_ASSERT(aOutput && aTarget && aSink); + if (!aOutput) { + return E_INVALIDARG; + } + *aOutput = nullptr; + if (!aTarget || !aSink) { + return E_INVALIDARG; + } + Interceptor* intcpt = new Interceptor(Move(aTarget), aSink); + HRESULT hr = intcpt->QueryInterface(aIid, aOutput); + static_cast<WeakReferenceSupport*>(intcpt)->Release(); + return hr; +} + +Interceptor::Interceptor(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink) + : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread) + , mTarget(Move(aTarget)) + , mEventSink(aSink) + , mMutex("mozilla::mscom::Interceptor::mMutex") +{ + MOZ_ASSERT(aSink); + MOZ_ASSERT(!IsProxy(mTarget.get())); + RefPtr<IWeakReference> weakRef; + if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) { + aSink->SetInterceptor(weakRef); + } +} + +Interceptor::~Interceptor() +{ + // This needs to run on the main thread because it releases target interface + // reference counts which may not be thread-safe. + MOZ_ASSERT(NS_IsMainThread()); + for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) { + MapEntry& entry = mInterceptorMap[index]; + entry.mInterceptor->Release(); + entry.mTargetInterface->Release(); + } +} + +Interceptor::MapEntry* +Interceptor::Lookup(REFIID aIid) +{ + mMutex.AssertCurrentThreadOwns(); + for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) { + if (mInterceptorMap[index].mIID == aIid) { + return &mInterceptorMap[index]; + } + } + return nullptr; +} + +HRESULT +Interceptor::GetTargetForIID(REFIID aIid, InterceptorTargetPtr& aTarget) +{ + MutexAutoLock lock(mMutex); + MapEntry* entry = Lookup(aIid); + if (entry) { + aTarget.reset(entry->mTargetInterface); + return S_OK; + } + + return E_NOINTERFACE; +} + +// CoGetInterceptor requires information from a typelib to be able to +// generate its emulated vtable. If a typelib is unavailable, +// CoGetInterceptor returns 0x80070002. +static const HRESULT kFileNotFound = 0x80070002; + +HRESULT +Interceptor::CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput) +{ + // In order to aggregate, we *must* request IID_IUnknown as the initial + // interface for the interceptor, as that IUnknown is non-delegating. + // This is a fundamental rule for creating aggregated objects in COM. + HRESULT hr = ::CoGetInterceptor(aIid, aOuter, IID_IUnknown, (void**)aOutput); + if (hr != kFileNotFound) { + return hr; + } + + // In the case that CoGetInterceptor returns kFileNotFound, we can try to + // explicitly load typelib data from our runtime registration facility and + // pass that into CoGetInterceptorFromTypeInfo. + + RefPtr<ITypeInfo> typeInfo; + bool found = RegisteredProxy::Find(aIid, getter_AddRefs(typeInfo)); + // If this assert fires then we have omitted registering the typelib for a + // required interface. To fix this, review our calls to mscom::RegisterProxy + // and mscom::RegisterTypelib, and add the additional typelib as necessary. + MOZ_ASSERT(found); + if (!found) { + return kFileNotFound; + } + + hr = ::CoGetInterceptorFromTypeInfo(aIid, aOuter, typeInfo, IID_IUnknown, + (void**)aOutput); + // If this assert fires then the interceptor doesn't like something about + // the format of the typelib. One thing in particular that it doesn't like + // is complex types that contain unions. + MOZ_ASSERT(SUCCEEDED(hr)); + return hr; +} + +/** + * This method contains the core guts of the handling of QueryInterface calls + * that are delegated to us from the ICallInterceptor. + * + * @param aIid ID of the desired interface + * @param aOutInterceptor The resulting emulated vtable that corresponds to + * the interface specified by aIid. + */ +HRESULT +Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) +{ + if (!aOutInterceptor) { + return E_INVALIDARG; + } + + if (aIid == IID_IUnknown) { + // Special case: When we see IUnknown, we just provide a reference to this + *aOutInterceptor = static_cast<IInterceptor*>(this); + AddRef(); + return S_OK; + } + + RefPtr<IUnknown> unkInterceptor; + IUnknown* interfaceForQILog = nullptr; + + // (1) Check to see if we already have an existing interceptor for aIid. + + { // Scope for lock + MutexAutoLock lock(mMutex); + MapEntry* entry = Lookup(aIid); + if (entry) { + unkInterceptor = entry->mInterceptor; + interfaceForQILog = entry->mTargetInterface; + } + } + + // (1a) A COM interceptor already exists for this interface, so all we need + // to do is run a QI on it. + if (unkInterceptor) { + // Technically we didn't actually execute a QI on the target interface, but + // for logging purposes we would like to record the fact that this interface + // was requested. + InterceptorLog::QI(S_OK, mTarget.get(), aIid, interfaceForQILog); + + return unkInterceptor->QueryInterface(aIid, aOutInterceptor); + } + + // (2) Obtain a new target interface. + + // (2a) First, make sure that the target interface is available + // NB: We *MUST* query the correct interface! ICallEvents::Invoke casts its + // pvReceiver argument directly to the required interface! DO NOT assume + // that COM will use QI or upcast/downcast! + HRESULT hr; + + STAUniquePtr<IUnknown> targetInterface; + IUnknown* rawTargetInterface = nullptr; + hr = QueryInterfaceTarget(aIid, (void**)&rawTargetInterface); + targetInterface.reset(rawTargetInterface); + InterceptorLog::QI(hr, mTarget.get(), aIid, targetInterface.get()); + MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE); + if (FAILED(hr)) { + return hr; + } + + // We *really* shouldn't be adding interceptors to proxies + MOZ_ASSERT(aIid != IID_IMarshal); + + // (3) Create a new COM interceptor to that interface that delegates its + // IUnknown to |this|. + + // Raise the refcount for stabilization purposes during aggregation + RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>( + static_cast<WeakReferenceSupport*>(this))); + + hr = CreateInterceptor(aIid, kungFuDeathGrip, getter_AddRefs(unkInterceptor)); + if (FAILED(hr)) { + return hr; + } + + // (4) Obtain the interceptor's ICallInterceptor interface and register our + // event sink. + RefPtr<ICallInterceptor> interceptor; + hr = unkInterceptor->QueryInterface(IID_ICallInterceptor, + (void**)getter_AddRefs(interceptor)); + if (FAILED(hr)) { + return hr; + } + + hr = interceptor->RegisterSink(mEventSink); + if (FAILED(hr)) { + return hr; + } + + // (5) Now that we have this new COM interceptor, insert it into the map. + + { // Scope for lock + MutexAutoLock lock(mMutex); + // We might have raced with another thread, so first check that we don't + // already have an entry for this + MapEntry* entry = Lookup(aIid); + if (entry && entry->mInterceptor) { + unkInterceptor = entry->mInterceptor; + } else { + // We're inserting unkInterceptor into the map but we still want to hang + // onto it locally so that we can QI it below. + unkInterceptor->AddRef(); + // OTOH we must not touch the refcount for the target interface + // because we are just moving it into the map and its refcounting might + // not be thread-safe. + IUnknown* rawTargetInterface = targetInterface.release(); + mInterceptorMap.AppendElement(MapEntry(aIid, + unkInterceptor, + rawTargetInterface)); + } + } + + return unkInterceptor->QueryInterface(aIid, aOutInterceptor); +} + +HRESULT +Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput) +{ + // NB: This QI needs to run on the main thread because the target object + // is probably Gecko code that is not thread-safe. Note that this main + // thread invocation is *synchronous*. + MainThreadInvoker invoker; + HRESULT hr; + auto runOnMainThread = [&]() -> void { + MOZ_ASSERT(NS_IsMainThread()); + hr = mTarget->QueryInterface(aIid, aOutput); + }; + if (!invoker.Invoke(NS_NewRunnableFunction(runOnMainThread))) { + return E_FAIL; + } + return hr; +} + +HRESULT +Interceptor::QueryInterface(REFIID riid, void** ppv) +{ + return WeakReferenceSupport::QueryInterface(riid, ppv); +} + +HRESULT +Interceptor::ThreadSafeQueryInterface(REFIID aIid, IUnknown** aOutInterface) +{ + if (aIid == IID_IInterceptor) { + *aOutInterface = static_cast<IInterceptor*>(this); + (*aOutInterface)->AddRef(); + return S_OK; + } + + if (aIid == IID_IDispatch) { + STAUniquePtr<IDispatch> disp; + IDispatch* rawDisp = nullptr; + HRESULT hr = QueryInterfaceTarget(aIid, (void**)&rawDisp); + if (FAILED(hr)) { + return hr; + } + disp.reset(rawDisp); + return DispatchForwarder::Create(this, disp, aOutInterface); + } + + return GetInterceptorForIID(aIid, (void**)aOutInterface); +} + +ULONG +Interceptor::AddRef() +{ + return WeakReferenceSupport::AddRef(); +} + +ULONG +Interceptor::Release() +{ + return WeakReferenceSupport::Release(); +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/Interceptor.h b/ipc/mscom/Interceptor.h new file mode 100644 index 000000000..ecddbddf5 --- /dev/null +++ b/ipc/mscom/Interceptor.h @@ -0,0 +1,125 @@ +/* -*- 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_interceptor_h +#define mozilla_mscom_interceptor_h + +#include "mozilla/Move.h" +#include "mozilla/Mutex.h" +#include "nsTArray.h" +#include "mozilla/mscom/Ptr.h" +#include "mozilla/mscom/WeakRef.h" +#include "mozilla/RefPtr.h" + +#include <callobj.h> + +namespace mozilla { +namespace mscom { + +// {8831EB53-A937-42BC-9921-B3E1121FDF86} +DEFINE_GUID(IID_IInterceptorSink, +0x8831eb53, 0xa937, 0x42bc, 0x99, 0x21, 0xb3, 0xe1, 0x12, 0x1f, 0xdf, 0x86); + +struct IInterceptorSink : public ICallFrameEvents +{ + virtual STDMETHODIMP SetInterceptor(IWeakReference* aInterceptor) = 0; +}; + +// {3710799B-ECA2-4165-B9B0-3FA1E4A9B230} +DEFINE_GUID(IID_IInterceptor, +0x3710799b, 0xeca2, 0x4165, 0xb9, 0xb0, 0x3f, 0xa1, 0xe4, 0xa9, 0xb2, 0x30); + +struct IInterceptor : public IUnknown +{ + virtual STDMETHODIMP GetTargetForIID(REFIID aIid, InterceptorTargetPtr& aTarget) = 0; + virtual STDMETHODIMP GetInterceptorForIID(REFIID aIid, + void** aOutInterceptor) = 0; +}; + +/** + * The COM interceptor is the core functionality in mscom that allows us to + * redirect method calls to different threads. It emulates the vtable of a + * target interface. When a call is made on this emulated vtable, the call is + * packaged up into an instance of the ICallFrame interface which may be passed + * to other contexts for execution. + * + * In order to accomplish this, COM itself provides the CoGetInterceptor + * function, which instantiates an ICallInterceptor. Note, however, that + * ICallInterceptor only works on a single interface; we need to be able to + * interpose QueryInterface calls so that we can instantiate a new + * ICallInterceptor for each new interface that is requested. + * + * We accomplish this by using COM aggregation, which means that the + * ICallInterceptor delegates its IUnknown implementation to its outer object + * (the mscom::Interceptor we implement and control). + */ +class Interceptor final : public WeakReferenceSupport + , public IInterceptor +{ +public: + static HRESULT Create(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink, + REFIID aIid, void** aOutput); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IInterceptor + STDMETHODIMP GetTargetForIID(REFIID aIid, InterceptorTargetPtr& aTarget) override; + STDMETHODIMP GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) override; + +private: + struct MapEntry + { + MapEntry(REFIID aIid, IUnknown* aInterceptor, IUnknown* aTargetInterface) + : mIID(aIid) + , mInterceptor(aInterceptor) + , mTargetInterface(aTargetInterface) + {} + IID mIID; + IUnknown* mInterceptor; + IUnknown* mTargetInterface; + }; + +private: + Interceptor(STAUniquePtr<IUnknown> aTarget, IInterceptorSink* aSink); + ~Interceptor(); + MapEntry* Lookup(REFIID aIid); + HRESULT QueryInterfaceTarget(REFIID aIid, void** aOutput); + HRESULT ThreadSafeQueryInterface(REFIID aIid, + IUnknown** aOutInterface) override; + HRESULT CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput); + +private: + STAUniquePtr<IUnknown> mTarget; + RefPtr<IInterceptorSink> mEventSink; + mozilla::Mutex mMutex; // Guards mInterceptorMap + // Using a nsTArray since the # of interfaces is not going to be very high + nsTArray<MapEntry> mInterceptorMap; +}; + +template <typename InterfaceT> +inline HRESULT +CreateInterceptor(STAUniquePtr<InterfaceT> aTargetInterface, + IInterceptorSink* aEventSink, + InterfaceT** aOutInterface) +{ + if (!aTargetInterface || !aEventSink) { + return E_INVALIDARG; + } + + REFIID iidTarget = __uuidof(aTargetInterface); + + STAUniquePtr<IUnknown> targetUnknown(aTargetInterface.release()); + return Interceptor::Create(Move(targetUnknown), aEventSink, iidTarget, + (void**)aOutInterface); +} + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_interceptor_h diff --git a/ipc/mscom/InterceptorLog.cpp b/ipc/mscom/InterceptorLog.cpp new file mode 100644 index 000000000..c2cd3c7df --- /dev/null +++ b/ipc/mscom/InterceptorLog.cpp @@ -0,0 +1,466 @@ +/* -*- 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 "mozilla/mscom/InterceptorLog.h" + +#include "MainThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/Mutex.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIFileStreams.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsXPCOMPrivate.h" +#include "nsXULAppAPI.h" +#include "prenv.h" + +#include <callobj.h> + +using mozilla::DebugOnly; +using mozilla::mscom::ArrayData; +using mozilla::mscom::FindArrayData; +using mozilla::Mutex; +using mozilla::MutexAutoLock; +using mozilla::NewNonOwningRunnableMethod; +using mozilla::services::GetObserverService; +using mozilla::StaticAutoPtr; +using mozilla::TimeDuration; +using mozilla::TimeStamp; +using mozilla::Unused; + +namespace { + +class ShutdownEvent final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + ~ShutdownEvent() {} +}; + +NS_IMPL_ISUPPORTS(ShutdownEvent, nsIObserver) + +class Logger +{ +public: + explicit Logger(const nsACString& aLeafBaseName); + bool IsValid() + { + MutexAutoLock lock(mMutex); + return !!mThread; + } + void LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface); + void LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface); + nsresult Shutdown(); + +private: + void OpenFile(); + void Flush(); + void CloseFile(); + void AssertRunningOnLoggerThread(); + bool VariantToString(const VARIANT& aVariant, nsACString& aOut, LONG aIndex = 0); + static double GetElapsedTime(); + + nsCOMPtr<nsIFile> mLogFileName; + nsCOMPtr<nsIOutputStream> mLogFile; // Only accessed by mThread + Mutex mMutex; // Guards mThread and mEntries + nsCOMPtr<nsIThread> mThread; + nsTArray<nsCString> mEntries; +}; + +Logger::Logger(const nsACString& aLeafBaseName) + : mMutex("mozilla::com::InterceptorLog::Logger") +{ + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIFile> logFileName; + GeckoProcessType procType = XRE_GetProcessType(); + nsAutoCString leafName(aLeafBaseName); + nsresult rv; + if (procType == GeckoProcessType_Default) { + leafName.AppendLiteral("-Parent-"); + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(logFileName)); + } else if (procType == GeckoProcessType_Content) { + leafName.AppendLiteral("-Content-"); +#if defined(MOZ_CONTENT_SANDBOX) + rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR, + getter_AddRefs(logFileName)); +#else + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(logFileName)); +#endif // defined(MOZ_CONTENT_SANDBOX) + } else { + return; + } + if (NS_FAILED(rv)) { + return; + } + DWORD pid = GetCurrentProcessId(); + leafName.AppendPrintf("%u.log", pid); + // Using AppendNative here because Windows + rv = logFileName->AppendNative(leafName); + if (NS_FAILED(rv)) { + return; + } + mLogFileName.swap(logFileName); + + nsCOMPtr<nsIObserverService> obsSvc = GetObserverService(); + nsCOMPtr<nsIObserver> shutdownEvent = new ShutdownEvent(); + rv = obsSvc->AddObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, + false); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIRunnable> openRunnable( + NewNonOwningRunnableMethod(this, &Logger::OpenFile)); + rv = NS_NewNamedThread("COM Intcpt Log", getter_AddRefs(mThread), + openRunnable); + if (NS_FAILED(rv)) { + obsSvc->RemoveObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID); + } +} + +void +Logger::AssertRunningOnLoggerThread() +{ +#if defined(DEBUG) + nsCOMPtr<nsIThread> curThread; + if (NS_FAILED(NS_GetCurrentThread(getter_AddRefs(curThread)))) { + return; + } + MutexAutoLock lock(mMutex); + MOZ_ASSERT(curThread == mThread); +#endif +} + +void +Logger::OpenFile() +{ + AssertRunningOnLoggerThread(); + MOZ_ASSERT(mLogFileName && !mLogFile); + NS_NewLocalFileOutputStream(getter_AddRefs(mLogFile), mLogFileName, + PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + PR_IRUSR | PR_IWUSR | PR_IRGRP); +} + +void +Logger::CloseFile() +{ + AssertRunningOnLoggerThread(); + MOZ_ASSERT(mLogFile); + if (!mLogFile) { + return; + } + Flush(); + mLogFile->Close(); + mLogFile = nullptr; +} + +nsresult +Logger::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = mThread->Dispatch(NewNonOwningRunnableMethod(this, + &Logger::CloseFile), + NS_DISPATCH_NORMAL); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Dispatch failed"); + + rv = mThread->Shutdown(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Shutdown failed"); + return NS_OK; +} + +bool +Logger::VariantToString(const VARIANT& aVariant, nsACString& aOut, LONG aIndex) +{ + switch (aVariant.vt) { + case VT_DISPATCH: { + aOut.AppendPrintf("(IDispatch*) 0x%0p", aVariant.pdispVal); + return true; + } + case VT_DISPATCH | VT_BYREF: { + aOut.AppendPrintf("(IDispatch*) 0x%0p", (aVariant.ppdispVal)[aIndex]); + return true; + } + case VT_UNKNOWN: { + aOut.AppendPrintf("(IUnknown*) 0x%0p", aVariant.punkVal); + return true; + } + case VT_UNKNOWN | VT_BYREF: { + aOut.AppendPrintf("(IUnknown*) 0x%0p", (aVariant.ppunkVal)[aIndex]); + return true; + } + case VT_VARIANT | VT_BYREF: { + return VariantToString((aVariant.pvarVal)[aIndex], aOut); + } + case VT_I4 | VT_BYREF: { + aOut.AppendPrintf("%d", aVariant.plVal[aIndex]); + return true; + } + case VT_UI4 | VT_BYREF: { + aOut.AppendPrintf("%u", aVariant.pulVal[aIndex]); + return true; + } + case VT_I4: { + aOut.AppendPrintf("%d", aVariant.lVal); + return true; + } + case VT_UI4: { + aOut.AppendPrintf("%u", aVariant.ulVal); + return true; + } + case VT_EMPTY: { + aOut.AppendLiteral("(empty VARIANT)"); + return true; + } + case VT_NULL: { + aOut.AppendLiteral("(null VARIANT)"); + return true; + } + case VT_BSTR: { + aOut.AppendPrintf("\"%S\"", aVariant.bstrVal); + return true; + } + case VT_BSTR | VT_BYREF: { + aOut.AppendPrintf("\"%S\"", *aVariant.pbstrVal); + return true; + } + default: { + aOut.AppendPrintf("(VariantToString failed, VARTYPE == 0x%04hx)", + aVariant.vt); + return false; + } + } +} + +/* static */ double +Logger::GetElapsedTime() +{ + TimeStamp ts = TimeStamp::Now(); + bool inconsistent; + TimeDuration duration = ts - TimeStamp::ProcessCreation(inconsistent); + return duration.ToMicroseconds(); +} + +void +Logger::LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface) +{ + if (FAILED(aResult)) { + return; + } + double elapsed = GetElapsedTime(); + + nsPrintfCString line("%fus\t0x%0p\tIUnknown::QueryInterface\t([in] ", elapsed, + aTarget); + + WCHAR buf[39] = {0}; + if (StringFromGUID2(aIid, buf, mozilla::ArrayLength(buf))) { + line.AppendPrintf("%S", buf); + } else { + line.AppendLiteral("(IID Conversion Failed)"); + } + line.AppendPrintf(", [out] 0x%p)\t0x%08X\n", aInterface, aResult); + + MutexAutoLock lock(mMutex); + mEntries.AppendElement(line); + mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush), + NS_DISPATCH_NORMAL); +} + +void +Logger::LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface) +{ + // (1) Gather info about the call + double elapsed = GetElapsedTime(); + + CALLFRAMEINFO callInfo; + HRESULT hr = aCallFrame->GetInfo(&callInfo); + if (FAILED(hr)) { + return; + } + + PWSTR interfaceName = nullptr; + PWSTR methodName = nullptr; + hr = aCallFrame->GetNames(&interfaceName, &methodName); + if (FAILED(hr)) { + return; + } + + // (2) Serialize the call + nsPrintfCString line("%fus\t0x%p\t%S::%S\t(", elapsed, + aTargetInterface, interfaceName, methodName); + + CoTaskMemFree(interfaceName); + interfaceName = nullptr; + CoTaskMemFree(methodName); + methodName = nullptr; + + // Check for supplemental array data + const ArrayData* arrayData = FindArrayData(callInfo.iid, callInfo.iMethod); + + for (ULONG paramIndex = 0; paramIndex < callInfo.cParams; ++paramIndex) { + CALLFRAMEPARAMINFO paramInfo; + hr = aCallFrame->GetParamInfo(paramIndex, ¶mInfo); + if (SUCCEEDED(hr)) { + line.AppendLiteral("["); + if (paramInfo.fIn) { + line.AppendLiteral("in"); + } + if (paramInfo.fOut) { + line.AppendLiteral("out"); + } + line.AppendLiteral("] "); + } + VARIANT paramValue; + hr = aCallFrame->GetParam(paramIndex, ¶mValue); + if (SUCCEEDED(hr)) { + if (arrayData && paramIndex == arrayData->mArrayParamIndex) { + VARIANT lengthParam; + hr = aCallFrame->GetParam(arrayData->mLengthParamIndex, &lengthParam); + if (SUCCEEDED(hr)) { + line.AppendLiteral("{ "); + for (LONG i = 0; i < *lengthParam.plVal; ++i) { + VariantToString(paramValue, line, i); + if (i < *lengthParam.plVal - 1) { + line.AppendLiteral(", "); + } + } + line.AppendLiteral(" }"); + } else { + line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr); + } + } else { + VariantToString(paramValue, line); + } + } else { + line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr); + } + if (paramIndex < callInfo.cParams - 1) { + line.AppendLiteral(", "); + } + } + line.AppendLiteral(")\t"); + + HRESULT callResult = aCallFrame->GetReturnValue(); + line.AppendPrintf("0x%08X\n", callResult); + + // (3) Enqueue event for logging + MutexAutoLock lock(mMutex); + mEntries.AppendElement(line); + mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush), + NS_DISPATCH_NORMAL); +} + +void +Logger::Flush() +{ + AssertRunningOnLoggerThread(); + MOZ_ASSERT(mLogFile); + if (!mLogFile) { + return; + } + nsTArray<nsCString> linesToWrite; + { // Scope for lock + MutexAutoLock lock(mMutex); + linesToWrite.SwapElements(mEntries); + } + + for (uint32_t i = 0, len = linesToWrite.Length(); i < len; ++i) { + uint32_t bytesWritten; + nsCString& line = linesToWrite[i]; + nsresult rv = mLogFile->Write(line.get(), line.Length(), &bytesWritten); + NS_WARN_IF(NS_FAILED(rv)); + } +} + +StaticAutoPtr<Logger> sLogger; + +NS_IMETHODIMP +ShutdownEvent::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) { + MOZ_ASSERT(false); + return NS_ERROR_NOT_IMPLEMENTED; + } + MOZ_ASSERT(sLogger); + Unused << NS_WARN_IF(NS_FAILED(sLogger->Shutdown())); + nsCOMPtr<nsIObserver> kungFuDeathGrip(this); + nsCOMPtr<nsIObserverService> obsSvc = GetObserverService(); + obsSvc->RemoveObserver(this, aTopic); + return NS_OK; +} +} // anonymous namespace + + +static bool +MaybeCreateLog(const char* aEnvVarName) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess()); + MOZ_ASSERT(!sLogger); + const char* leafBaseName = PR_GetEnv(aEnvVarName); + if (!leafBaseName) { + return false; + } + nsDependentCString strLeafBaseName(leafBaseName); + if (strLeafBaseName.IsEmpty()) { + return false; + } + sLogger = new Logger(strLeafBaseName); + if (!sLogger->IsValid()) { + sLogger = nullptr; + return false; + } + ClearOnShutdown(&sLogger); + return true; +} + +namespace mozilla { +namespace mscom { + +/* static */ bool +InterceptorLog::Init() +{ + static const bool isEnabled = MaybeCreateLog("MOZ_MSCOM_LOG_BASENAME"); + return isEnabled; +} + +/* static */ void +InterceptorLog::QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface) +{ + if (!sLogger) { + return; + } + sLogger->LogQI(aResult, aTarget, aIid, aInterface); +} + +/* static */ void +InterceptorLog::Event(ICallFrame* aCallFrame, IUnknown* aTargetInterface) +{ + if (!sLogger) { + return; + } + sLogger->LogEvent(aCallFrame, aTargetInterface); +} + +} // namespace mscom +} // namespace mozilla + diff --git a/ipc/mscom/InterceptorLog.h b/ipc/mscom/InterceptorLog.h new file mode 100644 index 000000000..6469bd0ed --- /dev/null +++ b/ipc/mscom/InterceptorLog.h @@ -0,0 +1,28 @@ +/* -*- 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_InterceptorLog_h +#define mozilla_mscom_InterceptorLog_h + +struct ICallFrame; +struct IUnknown; + +namespace mozilla { +namespace mscom { + +class InterceptorLog +{ +public: + static bool Init(); + static void QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface); + static void Event(ICallFrame* aCallFrame, IUnknown* aTarget); +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_InterceptorLog_h + diff --git a/ipc/mscom/MainThreadHandoff.cpp b/ipc/mscom/MainThreadHandoff.cpp new file mode 100644 index 000000000..21ac20faa --- /dev/null +++ b/ipc/mscom/MainThreadHandoff.cpp @@ -0,0 +1,398 @@ +/* -*- 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 "mozilla/mscom/MainThreadHandoff.h" + +#include "mozilla/Move.h" +#include "mozilla/mscom/InterceptorLog.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" + +using mozilla::DebugOnly; + +namespace { + +class HandoffRunnable : public mozilla::Runnable +{ +public: + explicit HandoffRunnable(ICallFrame* aCallFrame, IUnknown* aTargetInterface) + : mCallFrame(aCallFrame) + , mTargetInterface(aTargetInterface) + , mResult(E_UNEXPECTED) + { + } + + NS_IMETHOD Run() override + { + mResult = mCallFrame->Invoke(mTargetInterface); + return NS_OK; + } + + HRESULT GetResult() const + { + return mResult; + } + +private: + ICallFrame* mCallFrame; + IUnknown* mTargetInterface; + HRESULT mResult; +}; + +} // anonymous namespace + +namespace mozilla { +namespace mscom { + +/* static */ HRESULT +MainThreadHandoff::Create(IInterceptorSink** aOutput) +{ + *aOutput = nullptr; + MainThreadHandoff* handoff = new MainThreadHandoff(); + HRESULT hr = handoff->QueryInterface(IID_IInterceptorSink, (void**) aOutput); + handoff->Release(); + return hr; +} + +MainThreadHandoff::MainThreadHandoff() + : mRefCnt(1) +{ +} + +MainThreadHandoff::~MainThreadHandoff() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +HRESULT +MainThreadHandoff::QueryInterface(REFIID riid, void** ppv) +{ + IUnknown* punk = nullptr; + if (!ppv) { + return E_INVALIDARG; + } + + if (riid == IID_IUnknown || riid == IID_ICallFrameEvents || + riid == IID_IInterceptorSink) { + punk = static_cast<IInterceptorSink*>(this); + } else if (riid == IID_ICallFrameWalker) { + punk = static_cast<ICallFrameWalker*>(this); + } + + *ppv = punk; + if (!punk) { + return E_NOINTERFACE; + } + + punk->AddRef(); + return S_OK; +} + +ULONG +MainThreadHandoff::AddRef() +{ + return (ULONG) InterlockedIncrement((LONG*)&mRefCnt); +} + +ULONG +MainThreadHandoff::Release() +{ + ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt); + if (newRefCnt == 0) { + // It is possible for the last Release() call to happen off-main-thread. + // If so, we need to dispatch an event to delete ourselves. + if (NS_IsMainThread()) { + delete this; + } else { + // We need to delete this object on the main thread, but we aren't on the + // main thread right now, so we send a reference to ourselves to the main + // thread to be re-released there. + RefPtr<MainThreadHandoff> self = this; + NS_ReleaseOnMainThread(self.forget()); + } + } + return newRefCnt; +} + +HRESULT +MainThreadHandoff::OnCall(ICallFrame* aFrame) +{ + // (1) Get info about the method call + HRESULT hr; + IID iid; + ULONG method; + hr = aFrame->GetIIDAndMethod(&iid, &method); + if (FAILED(hr)) { + return hr; + } + + RefPtr<IInterceptor> interceptor; + hr = mInterceptor->Resolve(IID_IInterceptor, + (void**)getter_AddRefs(interceptor)); + if (FAILED(hr)) { + return hr; + } + + InterceptorTargetPtr targetInterface; + hr = interceptor->GetTargetForIID(iid, targetInterface); + if (FAILED(hr)) { + return hr; + } + + // (2) Execute the method call syncrhonously on the main thread + RefPtr<HandoffRunnable> handoffInfo(new HandoffRunnable(aFrame, + targetInterface.get())); + MainThreadInvoker invoker; + if (!invoker.Invoke(do_AddRef(handoffInfo))) { + MOZ_ASSERT(false); + return E_UNEXPECTED; + } + hr = handoffInfo->GetResult(); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + + // (3) Log *before* wrapping outputs so that the log will contain pointers to + // the true target interface, not the wrapped ones. + InterceptorLog::Event(aFrame, targetInterface.get()); + + // (4) Scan the function call for outparams that contain interface pointers. + // Those will need to be wrapped with MainThreadHandoff so that they too will + // be exeuted on the main thread. + + hr = aFrame->GetReturnValue(); + if (FAILED(hr)) { + // If the call resulted in an error then there's not going to be anything + // that needs to be wrapped. + return S_OK; + } + + // (5) Unfortunately ICallFrame::WalkFrame does not correctly handle array + // outparams. Instead, we find out whether anybody has called + // mscom::RegisterArrayData to supply array parameter information and use it + // if available. This is a terrible hack, but it works for the short term. In + // the longer term we want to be able to use COM proxy/stub metadata to + // resolve array information for us. + const ArrayData* arrayData = FindArrayData(iid, method); + if (arrayData) { + hr = FixArrayElements(aFrame, *arrayData); + if (FAILED(hr)) { + return hr; + } + } else { + // (6) Scan the outputs looking for any outparam interfaces that need wrapping. + // NB: WalkFrame does not correctly handle array outparams. It processes the + // first element of an array but not the remaining elements (if any). + hr = aFrame->WalkFrame(CALLFRAME_WALK_OUT, this); + if (FAILED(hr)) { + return hr; + } + } + + return S_OK; +} + +static PVOID +ResolveArrayPtr(VARIANT& aVariant) +{ + if (!(aVariant.vt & VT_BYREF)) { + return nullptr; + } + return aVariant.byref; +} + +static PVOID* +ResolveInterfacePtr(PVOID aArrayPtr, VARTYPE aVartype, LONG aIndex) +{ + if (aVartype != (VT_VARIANT | VT_BYREF)) { + IUnknown** ifaceArray = reinterpret_cast<IUnknown**>(aArrayPtr); + return reinterpret_cast<PVOID*>(&ifaceArray[aIndex]); + } + VARIANT* variantArray = reinterpret_cast<VARIANT*>(aArrayPtr); + VARIANT& element = variantArray[aIndex]; + return &element.byref; +} + +HRESULT +MainThreadHandoff::FixArrayElements(ICallFrame* aFrame, + const ArrayData& aArrayData) +{ + // Extract the array length + VARIANT paramVal; + VariantInit(¶mVal); + HRESULT hr = aFrame->GetParam(aArrayData.mLengthParamIndex, ¶mVal); + MOZ_ASSERT(SUCCEEDED(hr) && + (paramVal.vt == (VT_I4 | VT_BYREF) || + paramVal.vt == (VT_UI4 | VT_BYREF))); + if (FAILED(hr) || (paramVal.vt != (VT_I4 | VT_BYREF) && + paramVal.vt != (VT_UI4 | VT_BYREF))) { + return hr; + } + + const LONG arrayLength = *(paramVal.plVal); + if (!arrayLength) { + // Nothing to do + return S_OK; + } + + // Extract the array parameter + VariantInit(¶mVal); + PVOID arrayPtr = nullptr; + hr = aFrame->GetParam(aArrayData.mArrayParamIndex, ¶mVal); + if (hr == DISP_E_BADVARTYPE) { + // ICallFrame::GetParam is not able to coerce the param into a VARIANT. + // That's ok, we can try to do it ourselves. + CALLFRAMEPARAMINFO paramInfo; + hr = aFrame->GetParamInfo(aArrayData.mArrayParamIndex, ¶mInfo); + if (FAILED(hr)) { + return hr; + } + PVOID stackBase = aFrame->GetStackLocation(); + if (aArrayData.mFlag == ArrayData::Flag::eAllocatedByServer) { + // In order for the server to allocate the array's buffer and store it in + // an outparam, the parameter must be typed as Type***. Since the base + // of the array is Type*, we must dereference twice. + arrayPtr = **reinterpret_cast<PVOID**>(reinterpret_cast<PBYTE>(stackBase) + + paramInfo.stackOffset); + } else { + // We dereference because we need to obtain the value of a parameter + // from a stack offset. This pointer is the base of the array. + arrayPtr = *reinterpret_cast<PVOID*>(reinterpret_cast<PBYTE>(stackBase) + + paramInfo.stackOffset); + } + } else if (FAILED(hr)) { + return hr; + } else { + arrayPtr = ResolveArrayPtr(paramVal); + } + + MOZ_ASSERT(arrayPtr); + if (!arrayPtr) { + return DISP_E_BADVARTYPE; + } + + // We walk the elements of the array and invoke OnWalkInterface to wrap each + // one, just as ICallFrame::WalkFrame would do. + for (LONG index = 0; index < arrayLength; ++index) { + hr = OnWalkInterface(aArrayData.mArrayParamIid, + ResolveInterfacePtr(arrayPtr, paramVal.vt, index), + FALSE, TRUE); + if (FAILED(hr)) { + return hr; + } + } + return S_OK; +} + +HRESULT +MainThreadHandoff::SetInterceptor(IWeakReference* aInterceptor) +{ + mInterceptor = aInterceptor; + return S_OK; +} + +HRESULT +MainThreadHandoff::OnWalkInterface(REFIID aIid, PVOID* aInterface, + BOOL aIsInParam, BOOL aIsOutParam) +{ + MOZ_ASSERT(aInterface && aIsOutParam); + if (!aInterface || !aIsOutParam) { + return E_UNEXPECTED; + } + + // Adopt aInterface for the time being. We can't touch its refcount off + // the main thread, so we'll use STAUniquePtr so that we can safely + // Release() it if necessary. + STAUniquePtr<IUnknown> origInterface(static_cast<IUnknown*>(*aInterface)); + *aInterface = nullptr; + + if (!origInterface) { + // Nothing to wrap. + return S_OK; + } + + // First make sure that aInterface isn't a proxy - we don't want to wrap + // those. + if (IsProxy(origInterface.get())) { + *aInterface = origInterface.release(); + return S_OK; + } + + RefPtr<IInterceptor> interceptor; + HRESULT hr = mInterceptor->Resolve(IID_IInterceptor, + (void**) getter_AddRefs(interceptor)); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + + // Now make sure that origInterface isn't referring to the same IUnknown + // as an interface that we are already managing. We can determine this by + // querying (NOT casting!) both objects for IUnknown and then comparing the + // resulting pointers. + InterceptorTargetPtr existingTarget; + hr = interceptor->GetTargetForIID(aIid, existingTarget); + if (SUCCEEDED(hr)) { + bool areIUnknownsEqual = false; + + // This check must be done on the main thread + auto checkFn = [&existingTarget, &origInterface, &areIUnknownsEqual]() -> void { + RefPtr<IUnknown> unkExisting; + HRESULT hrExisting = + existingTarget->QueryInterface(IID_IUnknown, + (void**)getter_AddRefs(unkExisting)); + RefPtr<IUnknown> unkNew; + HRESULT hrNew = + origInterface->QueryInterface(IID_IUnknown, + (void**)getter_AddRefs(unkNew)); + areIUnknownsEqual = SUCCEEDED(hrExisting) && SUCCEEDED(hrNew) && + unkExisting == unkNew; + }; + + MainThreadInvoker invoker; + if (invoker.Invoke(NS_NewRunnableFunction(checkFn)) && areIUnknownsEqual) { + // The existing interface and the new interface both belong to the same + // target object. Let's just use the existing one. + void* intercepted = nullptr; + hr = interceptor->GetInterceptorForIID(aIid, &intercepted); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + *aInterface = intercepted; + return S_OK; + } + } + + // Now create a new MainThreadHandoff wrapper... + RefPtr<IInterceptorSink> handoff; + hr = MainThreadHandoff::Create(getter_AddRefs(handoff)); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + + RefPtr<IUnknown> wrapped; + hr = Interceptor::Create(Move(origInterface), handoff, aIid, + getter_AddRefs(wrapped)); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + + // And replace the original interface pointer with the wrapped one. + wrapped.forget(reinterpret_cast<IUnknown**>(aInterface)); + + return S_OK; +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/MainThreadHandoff.h b/ipc/mscom/MainThreadHandoff.h new file mode 100644 index 000000000..a3ae47e1b --- /dev/null +++ b/ipc/mscom/MainThreadHandoff.h @@ -0,0 +1,71 @@ +/* -*- 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_MainThreadHandoff_h +#define mozilla_mscom_MainThreadHandoff_h + +#include "mozilla/Assertions.h" +#include "mozilla/Move.h" +#include "mozilla/mscom/Interceptor.h" +#include "mozilla/mscom/MainThreadInvoker.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/Mutex.h" +#include "nsTArray.h" + +namespace mozilla { +namespace mscom { + +struct ArrayData; + +class MainThreadHandoff final : public IInterceptorSink + , public ICallFrameWalker +{ +public: + static HRESULT Create(IInterceptorSink** aOutput); + + template <typename Interface> + static HRESULT WrapInterface(STAUniquePtr<Interface> aTargetInterface, + Interface** aOutInterface) + { + MOZ_ASSERT(!IsProxy(aTargetInterface.get())); + RefPtr<IInterceptorSink> handoff; + HRESULT hr = MainThreadHandoff::Create(getter_AddRefs(handoff)); + if (FAILED(hr)) { + return hr; + } + return CreateInterceptor(Move(aTargetInterface), handoff, aOutInterface); + } + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // ICallFrameEvents + STDMETHODIMP OnCall(ICallFrame* aFrame) override; + + // IInterceptorSink + STDMETHODIMP SetInterceptor(IWeakReference* aInterceptor) override; + + // ICallFrameWalker + STDMETHODIMP OnWalkInterface(REFIID aIid, PVOID* aInterface, BOOL aIsInParam, + BOOL aIsOutParam) override; + +private: + MainThreadHandoff(); + ~MainThreadHandoff(); + HRESULT FixArrayElements(ICallFrame* aFrame, + const ArrayData& aArrayData); + +private: + ULONG mRefCnt; + RefPtr<IWeakReference> mInterceptor; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_MainThreadHandoff_h diff --git a/ipc/mscom/MainThreadInvoker.cpp b/ipc/mscom/MainThreadInvoker.cpp new file mode 100644 index 000000000..cab62a727 --- /dev/null +++ b/ipc/mscom/MainThreadInvoker.cpp @@ -0,0 +1,180 @@ +/* -*- 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 "mozilla/mscom/MainThreadInvoker.h" + +#include "GeckoProfiler.h" +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/HangMonitor.h" +#include "mozilla/RefPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsSystemInfo.h" +#include "private/prpriv.h" // For PR_GetThreadID +#include "WinUtils.h" + +// This gives us compiler intrinsics for the x86 PAUSE instruction +#if defined(_MSC_VER) +#include <intrin.h> +#pragma intrinsic(_mm_pause) +#define CPU_PAUSE() _mm_pause() +#elif defined(__GNUC__) || defined(__clang__) +#define CPU_PAUSE() __builtin_ia32_pause() +#endif + +static bool sIsMulticore; + +namespace { + +/** + * SyncRunnable implements different code paths depending on whether or not + * we are running on a multiprocessor system. In the multiprocessor case, we + * leave the thread in a spin loop while waiting for the main thread to execute + * our runnable. Since spinning is pointless in the uniprocessor case, we block + * on an event that is set by the main thread once it has finished the runnable. + */ +class MOZ_RAII SyncRunnable +{ +public: + explicit SyncRunnable(already_AddRefed<nsIRunnable>&& aRunnable) + : mDoneEvent(sIsMulticore ? nullptr : + ::CreateEventW(nullptr, FALSE, FALSE, nullptr)) + , mDone(false) + , mRunnable(aRunnable) + { + MOZ_ASSERT(sIsMulticore || mDoneEvent); + MOZ_ASSERT(mRunnable); + } + + ~SyncRunnable() + { + if (mDoneEvent) { + ::CloseHandle(mDoneEvent); + } + } + + void Run() + { + mRunnable->Run(); + + if (mDoneEvent) { + ::SetEvent(mDoneEvent); + } else { + mDone = true; + } + } + + bool WaitUntilComplete() + { + if (mDoneEvent) { + HANDLE handles[] = {mDoneEvent, + mozilla::mscom::MainThreadInvoker::GetTargetThread()}; + DWORD waitResult = ::WaitForMultipleObjects(mozilla::ArrayLength(handles), + handles, FALSE, INFINITE); + return waitResult == WAIT_OBJECT_0; + } + + while (!mDone) { + // The PAUSE instruction is a hint to the CPU that we're doing a spin + // loop. It is a no-op on older processors that don't support it, so + // it is safe to use here without any CPUID checks. + CPU_PAUSE(); + } + return true; + } + +private: + HANDLE mDoneEvent; + mozilla::Atomic<bool> mDone; + nsCOMPtr<nsIRunnable> mRunnable; +}; + +} // anonymous namespace + +namespace mozilla { +namespace mscom { + +HANDLE MainThreadInvoker::sMainThread = nullptr; + +/* static */ bool +MainThreadInvoker::InitStatics() +{ + nsCOMPtr<nsIThread> mainThread; + nsresult rv = ::NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_FAILED(rv)) { + return false; + } + + PRThread* mainPrThread = nullptr; + rv = mainThread->GetPRThread(&mainPrThread); + if (NS_FAILED(rv)) { + return false; + } + + PRUint32 tid = ::PR_GetThreadID(mainPrThread); + sMainThread = ::OpenThread(SYNCHRONIZE | THREAD_SET_CONTEXT, FALSE, tid); + + nsCOMPtr<nsIPropertyBag2> infoService = do_GetService(NS_SYSTEMINFO_CONTRACTID); + if (infoService) { + uint32_t cpuCount; + nsresult rv = infoService->GetPropertyAsUint32(NS_LITERAL_STRING("cpucount"), + &cpuCount); + sIsMulticore = NS_SUCCEEDED(rv) && cpuCount > 1; + } + + return !!sMainThread; +} + +MainThreadInvoker::MainThreadInvoker() +{ + static const bool gotStatics = InitStatics(); + MOZ_ASSERT(gotStatics); +} + +bool +MainThreadInvoker::Invoke(already_AddRefed<nsIRunnable>&& aRunnable) +{ + nsCOMPtr<nsIRunnable> runnable(Move(aRunnable)); + if (!runnable) { + return false; + } + + if (NS_IsMainThread()) { + runnable->Run(); + return true; + } + + SyncRunnable syncRunnable(runnable.forget()); + + if (!::QueueUserAPC(&MainThreadAPC, sMainThread, + reinterpret_cast<UINT_PTR>(&syncRunnable))) { + return false; + } + + // We should ensure a call to NtTestAlert() is made on the main thread so + // that the main thread will check for APCs during event processing. If we + // omit this then the main thread will not check its APC queue until it is + // idle. + widget::WinUtils::SetAPCPending(); + + return syncRunnable.WaitUntilComplete(); +} + +/* static */ VOID CALLBACK +MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam) +{ + GeckoProfilerWakeRAII wakeProfiler; + mozilla::HangMonitor::NotifyActivity(mozilla::HangMonitor::kGeneralActivity); + MOZ_ASSERT(NS_IsMainThread()); + auto runnable = reinterpret_cast<SyncRunnable*>(aParam); + runnable->Run(); +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/MainThreadInvoker.h b/ipc/mscom/MainThreadInvoker.h new file mode 100644 index 000000000..4bd1f803f --- /dev/null +++ b/ipc/mscom/MainThreadInvoker.h @@ -0,0 +1,38 @@ +/* -*- 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_MainThreadInvoker_h +#define mozilla_mscom_MainThreadInvoker_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/StaticPtr.h" + +#include <windows.h> + +class nsIRunnable; + +namespace mozilla { +namespace mscom { + +class MainThreadInvoker +{ +public: + MainThreadInvoker(); + + bool Invoke(already_AddRefed<nsIRunnable>&& aRunnable); + static HANDLE GetTargetThread() { return sMainThread; } + +private: + static bool InitStatics(); + static VOID CALLBACK MainThreadAPC(ULONG_PTR aParam); + + static HANDLE sMainThread; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_MainThreadInvoker_h diff --git a/ipc/mscom/MainThreadRuntime.cpp b/ipc/mscom/MainThreadRuntime.cpp new file mode 100644 index 000000000..d86317966 --- /dev/null +++ b/ipc/mscom/MainThreadRuntime.cpp @@ -0,0 +1,180 @@ +/* -*- 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 "mozilla/mscom/MainThreadRuntime.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsVersion.h" +#include "nsDebug.h" +#include "nsWindowsHelpers.h" + +#include <accctrl.h> +#include <aclapi.h> +#include <objbase.h> +#include <objidl.h> + +namespace { + +struct LocalFreeDeleter +{ + void operator()(void* aPtr) + { + ::LocalFree(aPtr); + } +}; + +} // anonymous namespace + +namespace mozilla { +namespace mscom { + +MainThreadRuntime::MainThreadRuntime() + : mInitResult(E_UNEXPECTED) +{ + // We must be the outermost COM initialization on this thread. The COM runtime + // cannot be configured once we start manipulating objects + MOZ_ASSERT(mStaRegion.IsValidOutermost()); + if (NS_WARN_IF(!mStaRegion.IsValidOutermost())) { + return; + } + + // Windows XP doesn't support setting of the COM exception policy, so we'll + // just stop here in that case. + if (!IsVistaOrLater()) { + mInitResult = S_OK; + return; + } + + // We are required to initialize security in order to configure global options. + mInitResult = InitializeSecurity(); + MOZ_ASSERT(SUCCEEDED(mInitResult)); + if (FAILED(mInitResult)) { + return; + } + + RefPtr<IGlobalOptions> globalOpts; + mInitResult = ::CoCreateInstance(CLSID_GlobalOptions, nullptr, + CLSCTX_INPROC_SERVER, IID_IGlobalOptions, + (void**)getter_AddRefs(globalOpts)); + MOZ_ASSERT(SUCCEEDED(mInitResult)); + if (FAILED(mInitResult)) { + return; + } + + // Windows 7 has a policy that is even more strict. We should use that one + // whenever possible. + ULONG_PTR exceptionSetting = IsWin7OrLater() ? + COMGLB_EXCEPTION_DONOT_HANDLE_ANY : + COMGLB_EXCEPTION_DONOT_HANDLE; + mInitResult = globalOpts->Set(COMGLB_EXCEPTION_HANDLING, exceptionSetting); + MOZ_ASSERT(SUCCEEDED(mInitResult)); +} + +HRESULT +MainThreadRuntime::InitializeSecurity() +{ + HANDLE rawToken = nullptr; + BOOL ok = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &rawToken); + if (!ok) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + nsAutoHandle token(rawToken); + + DWORD len = 0; + ok = ::GetTokenInformation(token, TokenUser, nullptr, len, &len); + DWORD win32Error = ::GetLastError(); + if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) { + return HRESULT_FROM_WIN32(win32Error); + } + + auto tokenUserBuf = MakeUnique<BYTE[]>(len); + TOKEN_USER& tokenUser = *reinterpret_cast<TOKEN_USER*>(tokenUserBuf.get()); + ok = ::GetTokenInformation(token, TokenUser, tokenUserBuf.get(), len, &len); + if (!ok) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + len = 0; + ok = ::GetTokenInformation(token, TokenPrimaryGroup, nullptr, len, &len); + win32Error = ::GetLastError(); + if (!ok && win32Error != ERROR_INSUFFICIENT_BUFFER) { + return HRESULT_FROM_WIN32(win32Error); + } + + auto tokenPrimaryGroupBuf = MakeUnique<BYTE[]>(len); + TOKEN_PRIMARY_GROUP& tokenPrimaryGroup = + *reinterpret_cast<TOKEN_PRIMARY_GROUP*>(tokenPrimaryGroupBuf.get()); + ok = ::GetTokenInformation(token, TokenPrimaryGroup, tokenPrimaryGroupBuf.get(), + len, &len); + if (!ok) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + SECURITY_DESCRIPTOR sd; + if (!::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + BYTE systemSid[SECURITY_MAX_SID_SIZE]; + DWORD systemSidSize = sizeof(systemSid); + if (!::CreateWellKnownSid(WinLocalSystemSid, nullptr, systemSid, + &systemSidSize)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + BYTE adminSid[SECURITY_MAX_SID_SIZE]; + DWORD adminSidSize = sizeof(adminSid); + if (!::CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, adminSid, + &adminSidSize)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + // Grant access to SYSTEM, Administrators, and the user. + EXPLICIT_ACCESS entries[] = { + {COM_RIGHTS_EXECUTE, GRANT_ACCESS, NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, + reinterpret_cast<LPWSTR>(systemSid)}}, + {COM_RIGHTS_EXECUTE, GRANT_ACCESS, NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_WELL_KNOWN_GROUP, + reinterpret_cast<LPWSTR>(adminSid)}}, + {COM_RIGHTS_EXECUTE, GRANT_ACCESS, NO_INHERITANCE, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER, + reinterpret_cast<LPWSTR>(tokenUser.User.Sid)}} + }; + + PACL rawDacl = nullptr; + win32Error = ::SetEntriesInAcl(ArrayLength(entries), entries, nullptr, + &rawDacl); + if (win32Error != ERROR_SUCCESS) { + return HRESULT_FROM_WIN32(win32Error); + } + + UniquePtr<ACL, LocalFreeDeleter> dacl(rawDacl); + + if (!::SetSecurityDescriptorDacl(&sd, TRUE, dacl.get(), FALSE)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + if (!::SetSecurityDescriptorOwner(&sd, tokenUser.User.Sid, FALSE)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + if (!::SetSecurityDescriptorGroup(&sd, tokenPrimaryGroup.PrimaryGroup, FALSE)) { + return HRESULT_FROM_WIN32(::GetLastError()); + } + + return ::CoInitializeSecurity(&sd, -1, nullptr, nullptr, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_NONE, + nullptr); +} + +} // namespace mscom +} // namespace mozilla + diff --git a/ipc/mscom/MainThreadRuntime.h b/ipc/mscom/MainThreadRuntime.h new file mode 100644 index 000000000..0e3e52c5f --- /dev/null +++ b/ipc/mscom/MainThreadRuntime.h @@ -0,0 +1,42 @@ +/* -*- 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_MainThreadRuntime_h +#define mozilla_mscom_MainThreadRuntime_h + +#include "mozilla/Attributes.h" +#include "mozilla/mscom/COMApartmentRegion.h" + +namespace mozilla { +namespace mscom { + +class MOZ_NON_TEMPORARY_CLASS MainThreadRuntime +{ +public: + MainThreadRuntime(); + + explicit operator bool() const + { + return mStaRegion.IsValidOutermost() && SUCCEEDED(mInitResult); + } + + MainThreadRuntime(MainThreadRuntime&) = delete; + MainThreadRuntime(MainThreadRuntime&&) = delete; + MainThreadRuntime& operator=(MainThreadRuntime&) = delete; + MainThreadRuntime& operator=(MainThreadRuntime&&) = delete; + +private: + HRESULT InitializeSecurity(); + + STARegion mStaRegion; + HRESULT mInitResult; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_MainThreadRuntime_h + diff --git a/ipc/mscom/ProxyStream.cpp b/ipc/mscom/ProxyStream.cpp new file mode 100644 index 000000000..9bc471583 --- /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 + diff --git a/ipc/mscom/ProxyStream.h b/ipc/mscom/ProxyStream.h new file mode 100644 index 000000000..392fdd2d8 --- /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 diff --git a/ipc/mscom/Ptr.h b/ipc/mscom/Ptr.h new file mode 100644 index 000000000..c77e570b8 --- /dev/null +++ b/ipc/mscom/Ptr.h @@ -0,0 +1,111 @@ +/* -*- 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_Ptr_h +#define mozilla_mscom_Ptr_h + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/mscom/EnsureMTA.h" +#include "mozilla/UniquePtr.h" +#include "nsError.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +/** + * The glue code in mozilla::mscom often needs to pass around interface pointers + * belonging to a different apartment from the current one. We must not touch + * the reference counts of those objects on the wrong apartment. By using these + * UniquePtr specializations, we may ensure that the reference counts are always + * handled correctly. + */ + +namespace mozilla { +namespace mscom { + +namespace detail { + +template <typename T> +struct MainThreadRelease +{ + void operator()(T* aPtr) + { + if (!aPtr) { + return; + } + if (NS_IsMainThread()) { + aPtr->Release(); + return; + } + DebugOnly<nsresult> rv = + NS_DispatchToMainThread(NewNonOwningRunnableMethod(aPtr, + &T::Release)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } +}; + +template <typename T> +struct MTARelease +{ + void operator()(T* aPtr) + { + if (!aPtr) { + return; + } + EnsureMTA([&]() -> void + { + aPtr->Release(); + }); + } +}; + +template <typename T> +struct MTAReleaseInChildProcess +{ + void operator()(T* aPtr) + { + if (!aPtr) { + return; + } + if (XRE_IsParentProcess()) { + MOZ_ASSERT(NS_IsMainThread()); + aPtr->Release(); + return; + } + EnsureMTA([&]() -> void + { + aPtr->Release(); + }); + } +}; + +struct InterceptorTargetDeleter +{ + void operator()(IUnknown* aPtr) + { + // We intentionally do not touch the refcounts of interceptor targets! + } +}; + +} // namespace detail + +template <typename T> +using STAUniquePtr = mozilla::UniquePtr<T, detail::MainThreadRelease<T>>; + +template <typename T> +using MTAUniquePtr = mozilla::UniquePtr<T, detail::MTARelease<T>>; + +template <typename T> +using ProxyUniquePtr = mozilla::UniquePtr<T, detail::MTAReleaseInChildProcess<T>>; + +using InterceptorTargetPtr = + mozilla::UniquePtr<IUnknown, detail::InterceptorTargetDeleter>; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_Ptr_h + diff --git a/ipc/mscom/Registration.cpp b/ipc/mscom/Registration.cpp new file mode 100644 index 000000000..811989272 --- /dev/null +++ b/ipc/mscom/Registration.cpp @@ -0,0 +1,346 @@ +/* -*- 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/. */ + +// COM registration data structures are built with C code, so we need to +// simulate that in our C++ code by defining CINTERFACE before including +// anything else that could possibly pull in Windows header files. +#define CINTERFACE + +#include "mozilla/mscom/ActivationContext.h" +#include "mozilla/mscom/EnsureMTA.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/mscom/Utils.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Move.h" +#include "mozilla/Mutex.h" +#include "mozilla/Pair.h" +#include "mozilla/StaticPtr.h" +#include "nsTArray.h" +#include "nsWindowsHelpers.h" + +#include <oaidl.h> +#include <objidl.h> +#include <rpcproxy.h> +#include <shlwapi.h> + +/* This code MUST NOT use any non-inlined internal Mozilla APIs, as it will be + compiled into DLLs that COM may load into non-Mozilla processes! */ + +namespace { + +// This function is defined in generated code for proxy DLLs but is not declared +// in rpcproxy.h, so we need this typedef. +typedef void (RPC_ENTRY *GetProxyDllInfoFnPtr)(const ProxyFileInfo*** aInfo, + const CLSID** aId); + +} // anonymous namespace + +namespace mozilla { +namespace mscom { + +static bool +BuildLibPath(RegistrationFlags aFlags, wchar_t* aBuffer, size_t aBufferLen, + const wchar_t* aLeafName) +{ + if (aFlags == RegistrationFlags::eUseBinDirectory) { + HMODULE thisModule = nullptr; + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCTSTR>(&RegisterProxy), + &thisModule)) { + return false; + } + DWORD fileNameResult = GetModuleFileName(thisModule, aBuffer, aBufferLen); + if (!fileNameResult || (fileNameResult == aBufferLen && + ::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + return false; + } + if (!PathRemoveFileSpec(aBuffer)) { + return false; + } + } else if (aFlags == RegistrationFlags::eUseSystemDirectory) { + UINT result = GetSystemDirectoryW(aBuffer, static_cast<UINT>(aBufferLen)); + if (!result || result > aBufferLen) { + return false; + } + } else { + return false; + } + + if (!PathAppend(aBuffer, aLeafName)) { + return false; + } + return true; +} + +UniquePtr<RegisteredProxy> +RegisterProxy(const wchar_t* aLeafName, RegistrationFlags aFlags) +{ + wchar_t modulePathBuf[MAX_PATH + 1] = {0}; + if (!BuildLibPath(aFlags, modulePathBuf, ArrayLength(modulePathBuf), + aLeafName)) { + return nullptr; + } + + nsModuleHandle proxyDll(LoadLibrary(modulePathBuf)); + if (!proxyDll.get()) { + return nullptr; + } + + // Instantiate an activation context so that CoGetClassObject will use any + // COM metadata embedded in proxyDll's manifest to resolve CLSIDs. + ActivationContext actCtx(proxyDll); + if (!actCtx) { + return nullptr; + } + + auto GetProxyDllInfoFn = reinterpret_cast<GetProxyDllInfoFnPtr>( + GetProcAddress(proxyDll, "GetProxyDllInfo")); + if (!GetProxyDllInfoFn) { + return nullptr; + } + + const ProxyFileInfo** proxyInfo = nullptr; + const CLSID* proxyClsid = nullptr; + GetProxyDllInfoFn(&proxyInfo, &proxyClsid); + if (!proxyInfo || !proxyClsid) { + return nullptr; + } + + // We call CoGetClassObject instead of DllGetClassObject because it forces + // the COM runtime to manage the lifetime of the DLL. + IUnknown* classObject = nullptr; + HRESULT hr = CoGetClassObject(*proxyClsid, CLSCTX_INPROC_SERVER, nullptr, + IID_IUnknown, (void**) &classObject); + if (FAILED(hr)) { + return nullptr; + } + + DWORD regCookie; + hr = CoRegisterClassObject(*proxyClsid, classObject, CLSCTX_INPROC_SERVER, + REGCLS_MULTIPLEUSE, ®Cookie); + if (FAILED(hr)) { + classObject->lpVtbl->Release(classObject); + return nullptr; + } + + ITypeLib* typeLib = nullptr; + hr = LoadTypeLibEx(modulePathBuf, REGKIND_NONE, &typeLib); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + CoRevokeClassObject(regCookie); + classObject->lpVtbl->Release(classObject); + return nullptr; + } + + // RegisteredProxy takes ownership of proxyDll, classObject, and typeLib + // references + auto result(MakeUnique<RegisteredProxy>(reinterpret_cast<uintptr_t>(proxyDll.disown()), + classObject, regCookie, typeLib)); + + while (*proxyInfo) { + const ProxyFileInfo& curInfo = **proxyInfo; + for (unsigned short i = 0, e = curInfo.TableSize; i < e; ++i) { + hr = CoRegisterPSClsid(*(curInfo.pStubVtblList[i]->header.piid), + *proxyClsid); + if (FAILED(hr)) { + return nullptr; + } + } + ++proxyInfo; + } + + return result; +} + +UniquePtr<RegisteredProxy> +RegisterTypelib(const wchar_t* aLeafName, RegistrationFlags aFlags) +{ + wchar_t modulePathBuf[MAX_PATH + 1] = {0}; + if (!BuildLibPath(aFlags, modulePathBuf, ArrayLength(modulePathBuf), + aLeafName)) { + return nullptr; + } + + ITypeLib* typeLib = nullptr; + HRESULT hr = LoadTypeLibEx(modulePathBuf, REGKIND_NONE, &typeLib); + if (FAILED(hr)) { + return nullptr; + } + + // RegisteredProxy takes ownership of typeLib reference + auto result(MakeUnique<RegisteredProxy>(typeLib)); + return result; +} + +RegisteredProxy::RegisteredProxy(uintptr_t aModule, IUnknown* aClassObject, + uint32_t aRegCookie, ITypeLib* aTypeLib) + : mModule(aModule) + , mClassObject(aClassObject) + , mRegCookie(aRegCookie) + , mTypeLib(aTypeLib) + , mIsRegisteredInMTA(IsCurrentThreadMTA()) +{ + MOZ_ASSERT(aClassObject); + MOZ_ASSERT(aTypeLib); + AddToRegistry(this); +} + +// If we're initializing from a typelib, it doesn't matter which apartment we +// run in, so mIsRegisteredInMTA may always be set to false in this case. +RegisteredProxy::RegisteredProxy(ITypeLib* aTypeLib) + : mModule(0) + , mClassObject(nullptr) + , mRegCookie(0) + , mTypeLib(aTypeLib) + , mIsRegisteredInMTA(false) +{ + MOZ_ASSERT(aTypeLib); + AddToRegistry(this); +} + +RegisteredProxy::~RegisteredProxy() +{ + DeleteFromRegistry(this); + if (mTypeLib) { + mTypeLib->lpVtbl->Release(mTypeLib); + } + if (mClassObject) { + // NB: mClassObject and mRegCookie must be freed from inside the apartment + // which they were created in. + auto cleanupFn = [&]() -> void { + ::CoRevokeClassObject(mRegCookie); + mClassObject->lpVtbl->Release(mClassObject); + }; + if (mIsRegisteredInMTA) { + EnsureMTA mta(cleanupFn); + } else { + cleanupFn(); + } + } + if (mModule) { + ::FreeLibrary(reinterpret_cast<HMODULE>(mModule)); + } +} + +RegisteredProxy::RegisteredProxy(RegisteredProxy&& aOther) +{ + *this = mozilla::Forward<RegisteredProxy>(aOther); +} + +RegisteredProxy& +RegisteredProxy::operator=(RegisteredProxy&& aOther) +{ + mModule = aOther.mModule; + aOther.mModule = 0; + mClassObject = aOther.mClassObject; + aOther.mClassObject = nullptr; + mRegCookie = aOther.mRegCookie; + aOther.mRegCookie = 0; + mTypeLib = aOther.mTypeLib; + aOther.mTypeLib = nullptr; + return *this; +} + +HRESULT +RegisteredProxy::GetTypeInfoForInterface(REFIID aIid, + ITypeInfo** aOutTypeInfo) const +{ + if (!aOutTypeInfo) { + return E_INVALIDARG; + } + if (!mTypeLib) { + return E_UNEXPECTED; + } + return mTypeLib->lpVtbl->GetTypeInfoOfGuid(mTypeLib, aIid, aOutTypeInfo); +} + +static StaticAutoPtr<nsTArray<RegisteredProxy*>> sRegistry; +static StaticAutoPtr<Mutex> sRegMutex; +static StaticAutoPtr<nsTArray<Pair<const ArrayData*, size_t>>> sArrayData; + +static Mutex& +GetMutex() +{ + static Mutex& mutex = []() -> Mutex& { + if (!sRegMutex) { + sRegMutex = new Mutex("RegisteredProxy::sRegMutex"); + ClearOnShutdown(&sRegMutex, ShutdownPhase::ShutdownThreads); + } + return *sRegMutex; + }(); + return mutex; +} + +/* static */ bool +RegisteredProxy::Find(REFIID aIid, ITypeInfo** aTypeInfo) +{ + MutexAutoLock lock(GetMutex()); + nsTArray<RegisteredProxy*>& registry = *sRegistry; + for (uint32_t idx = 0, len = registry.Length(); idx < len; ++idx) { + if (SUCCEEDED(registry[idx]->GetTypeInfoForInterface(aIid, aTypeInfo))) { + return true; + } + } + return false; +} + +/* static */ void +RegisteredProxy::AddToRegistry(RegisteredProxy* aProxy) +{ + MutexAutoLock lock(GetMutex()); + if (!sRegistry) { + sRegistry = new nsTArray<RegisteredProxy*>(); + ClearOnShutdown(&sRegistry); + } + sRegistry->AppendElement(aProxy); +} + +/* static */ void +RegisteredProxy::DeleteFromRegistry(RegisteredProxy* aProxy) +{ + MutexAutoLock lock(GetMutex()); + sRegistry->RemoveElement(aProxy); +} + +void +RegisterArrayData(const ArrayData* aArrayData, size_t aLength) +{ + MutexAutoLock lock(GetMutex()); + if (!sArrayData) { + sArrayData = new nsTArray<Pair<const ArrayData*, size_t>>(); + ClearOnShutdown(&sArrayData, ShutdownPhase::ShutdownThreads); + } + sArrayData->AppendElement(MakePair(aArrayData, aLength)); +} + +const ArrayData* +FindArrayData(REFIID aIid, ULONG aMethodIndex) +{ + MutexAutoLock lock(GetMutex()); + if (!sArrayData) { + return nullptr; + } + for (uint32_t outerIdx = 0, outerLen = sArrayData->Length(); + outerIdx < outerLen; ++outerIdx) { + auto& data = sArrayData->ElementAt(outerIdx); + for (size_t innerIdx = 0, innerLen = data.second(); innerIdx < innerLen; + ++innerIdx) { + const ArrayData* array = data.first(); + if (aIid == array[innerIdx].mIid && + aMethodIndex == array[innerIdx].mMethodIndex) { + return &array[innerIdx]; + } + } + } + return nullptr; +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/Registration.h b/ipc/mscom/Registration.h new file mode 100644 index 000000000..930225ca7 --- /dev/null +++ b/ipc/mscom/Registration.h @@ -0,0 +1,145 @@ +/* -*- 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_Registration_h +#define mozilla_mscom_Registration_h + +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +#include <objbase.h> + +struct ITypeInfo; +struct ITypeLib; + +namespace mozilla { +namespace mscom { + +/** + * Assumptions: + * (1) The DLL exports GetProxyDllInfo. This is not exported by default; it must + * be specified in the EXPORTS section of the DLL's module definition file. + */ +class RegisteredProxy +{ +public: + RegisteredProxy(uintptr_t aModule, IUnknown* aClassObject, + uint32_t aRegCookie, ITypeLib* aTypeLib); + explicit RegisteredProxy(ITypeLib* aTypeLib); + RegisteredProxy(RegisteredProxy&& aOther); + RegisteredProxy& operator=(RegisteredProxy&& aOther); + + ~RegisteredProxy(); + + HRESULT GetTypeInfoForInterface(REFIID aIid, ITypeInfo** aOutTypeInfo) const; + + static bool Find(REFIID aIid, ITypeInfo** aOutTypeInfo); + +private: + RegisteredProxy() = delete; + RegisteredProxy(RegisteredProxy&) = delete; + RegisteredProxy& operator=(RegisteredProxy&) = delete; + + static void AddToRegistry(RegisteredProxy* aProxy); + static void DeleteFromRegistry(RegisteredProxy* aProxy); + +private: + // Not using Windows types here: We shouldn't #include windows.h + // since it might pull in COM code which we want to do very carefully in + // Registration.cpp. + uintptr_t mModule; + IUnknown* mClassObject; + uint32_t mRegCookie; + ITypeLib* mTypeLib; + bool mIsRegisteredInMTA; +}; + +enum class RegistrationFlags +{ + eUseBinDirectory, + eUseSystemDirectory +}; + +// For DLL files. Assumes corresponding TLB is embedded in resources. +UniquePtr<RegisteredProxy> RegisterProxy(const wchar_t* aLeafName, + RegistrationFlags aFlags = + RegistrationFlags::eUseBinDirectory); +// For standalone TLB files. +UniquePtr<RegisteredProxy> RegisterTypelib(const wchar_t* aLeafName, + RegistrationFlags aFlags = + RegistrationFlags::eUseBinDirectory); + +/** + * The COM interceptor uses type library information to build its interface + * proxies. Unfortunately type libraries do not encode size_is and length_is + * annotations that have been specified in IDL. This structure allows us to + * explicitly declare such relationships so that the COM interceptor may + * be made aware of them. + */ +struct ArrayData +{ + enum class Flag + { + eNone = 0, + eAllocatedByServer = 1 // This implies an extra level of indirection + }; + + ArrayData(REFIID aIid, ULONG aMethodIndex, ULONG aArrayParamIndex, + VARTYPE aArrayParamType, REFIID aArrayParamIid, + ULONG aLengthParamIndex, Flag aFlag = Flag::eNone) + : mIid(aIid) + , mMethodIndex(aMethodIndex) + , mArrayParamIndex(aArrayParamIndex) + , mArrayParamType(aArrayParamType) + , mArrayParamIid(aArrayParamIid) + , mLengthParamIndex(aLengthParamIndex) + , mFlag(aFlag) + { + } + + ArrayData(const ArrayData& aOther) + { + *this = aOther; + } + + ArrayData& operator=(const ArrayData& aOther) + { + mIid = aOther.mIid; + mMethodIndex = aOther.mMethodIndex; + mArrayParamIndex = aOther.mArrayParamIndex; + mArrayParamType = aOther.mArrayParamType; + mArrayParamIid = aOther.mArrayParamIid; + mLengthParamIndex = aOther.mLengthParamIndex; + mFlag = aOther.mFlag; + return *this; + } + + IID mIid; + ULONG mMethodIndex; + ULONG mArrayParamIndex; + VARTYPE mArrayParamType; + IID mArrayParamIid; + ULONG mLengthParamIndex; + Flag mFlag; +}; + +void RegisterArrayData(const ArrayData* aArrayData, size_t aLength); + +template <size_t N> +inline void +RegisterArrayData(const ArrayData (&aData)[N]) +{ + RegisterArrayData(aData, N); +} + +const ArrayData* +FindArrayData(REFIID aIid, ULONG aMethodIndex); + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_Registration_h + diff --git a/ipc/mscom/Utils.cpp b/ipc/mscom/Utils.cpp new file mode 100644 index 000000000..7c031bb52 --- /dev/null +++ b/ipc/mscom/Utils.cpp @@ -0,0 +1,94 @@ +/* -*- 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/. */ + +// We need Windows 7 headers +#ifdef NTDDI_VERSION +#undef NTDDI_VERSION +#endif +#define NTDDI_VERSION 0x06010000 +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0601 + +#include "DynamicallyLinkedFunctionPtr.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/RefPtr.h" + +#include <objidl.h> + +static bool +IsCurrentThreadMTALegacy() +{ + // We don't use RefPtr for token because CoGetContextToken does *not* + // increment its refcount! + IUnknown* token = nullptr; + HRESULT hr = + CoGetContextToken(reinterpret_cast<ULONG_PTR*>(&token)); + if (FAILED(hr)) { + return false; + } + + RefPtr<IComThreadingInfo> threadingInfo; + hr = token->QueryInterface(IID_IComThreadingInfo, + getter_AddRefs(threadingInfo)); + if (FAILED(hr)) { + return false; + } + + APTTYPE aptType; + hr = threadingInfo->GetCurrentApartmentType(&aptType); + if (FAILED(hr)) { + return false; + } + + return aptType == APTTYPE_MTA; +} + +namespace mozilla { +namespace mscom { + +bool +IsCurrentThreadMTA() +{ + static DynamicallyLinkedFunctionPtr<decltype(&::CoGetApartmentType)> + pCoGetApartmentType(L"ole32.dll", "CoGetApartmentType"); + + if (!pCoGetApartmentType) { + // XP and Vista do not expose the newer API. + return IsCurrentThreadMTALegacy(); + } + + APTTYPE aptType; + APTTYPEQUALIFIER aptTypeQualifier; + HRESULT hr = pCoGetApartmentType(&aptType, &aptTypeQualifier); + if (FAILED(hr)) { + return false; + } + + return aptType == APTTYPE_MTA; +} + +bool +IsProxy(IUnknown* aUnknown) +{ + if (!aUnknown) { + return false; + } + + // Only proxies implement this interface, so if it is present then we must + // be dealing with a proxied object. + RefPtr<IClientSecurity> clientSecurity; + HRESULT hr = aUnknown->QueryInterface(IID_IClientSecurity, + (void**)getter_AddRefs(clientSecurity)); + if (SUCCEEDED(hr) || hr == RPC_E_WRONG_THREAD) { + return true; + } + return false; +} + +} // namespace mscom +} // namespace mozilla diff --git a/ipc/mscom/Utils.h b/ipc/mscom/Utils.h new file mode 100644 index 000000000..eb1a37f3a --- /dev/null +++ b/ipc/mscom/Utils.h @@ -0,0 +1,22 @@ +/* -*- 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_Utils_h +#define mozilla_mscom_Utils_h + +struct IUnknown; + +namespace mozilla { +namespace mscom { + +bool IsCurrentThreadMTA(); +bool IsProxy(IUnknown* aUnknown); + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_Utils_h + diff --git a/ipc/mscom/WeakRef.cpp b/ipc/mscom/WeakRef.cpp new file mode 100644 index 000000000..f19310264 --- /dev/null +++ b/ipc/mscom/WeakRef.cpp @@ -0,0 +1,191 @@ +/* -*- 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/. */ + +#define INITGUID +#include "mozilla/mscom/WeakRef.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/RefPtr.h" +#include "nsThreadUtils.h" +#include "nsWindowsHelpers.h" +#include "nsProxyRelease.h" + +namespace mozilla { +namespace mscom { + +WeakReferenceSupport::WeakReferenceSupport(Flags aFlags) + : mRefCnt(1) + , mFlags(aFlags) +{ + ::InitializeCriticalSectionAndSpinCount(&mCS, 4000); +} + +WeakReferenceSupport::~WeakReferenceSupport() +{ + MOZ_ASSERT(mWeakRefs.IsEmpty()); + ::DeleteCriticalSection(&mCS); +} + +HRESULT +WeakReferenceSupport::QueryInterface(REFIID riid, void** ppv) +{ + AutoCriticalSection lock(&mCS); + RefPtr<IUnknown> punk; + if (!ppv) { + return E_INVALIDARG; + } + *ppv = nullptr; + + // Raise the refcount for stabilization purposes during aggregation + RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(this)); + + if (riid == IID_IUnknown || riid == IID_IWeakReferenceSource) { + punk = static_cast<IUnknown*>(this); + } else { + HRESULT hr = ThreadSafeQueryInterface(riid, getter_AddRefs(punk)); + if (FAILED(hr)) { + return hr; + } + } + + if (!punk) { + return E_NOINTERFACE; + } + + punk.forget(ppv); + return S_OK; +} + +ULONG +WeakReferenceSupport::AddRef() +{ + AutoCriticalSection lock(&mCS); + return ++mRefCnt; +} + +ULONG +WeakReferenceSupport::Release() +{ + ULONG newRefCnt; + { // Scope for lock + AutoCriticalSection lock(&mCS); + newRefCnt = --mRefCnt; + if (newRefCnt == 0) { + ClearWeakRefs(); + } + } + if (newRefCnt == 0) { + if (mFlags != Flags::eDestroyOnMainThread || NS_IsMainThread()) { + delete this; + } else { + // We need to delete this object on the main thread, but we aren't on the + // main thread right now, so we send a reference to ourselves to the main + // thread to be re-released there. + RefPtr<WeakReferenceSupport> self = this; + NS_ReleaseOnMainThread(self.forget()); + } + } + return newRefCnt; +} + +void +WeakReferenceSupport::ClearWeakRefs() +{ + for (uint32_t i = 0, len = mWeakRefs.Length(); i < len; ++i) { + mWeakRefs[i]->Clear(); + mWeakRefs[i]->Release(); + } + mWeakRefs.Clear(); +} + +HRESULT +WeakReferenceSupport::GetWeakReference(IWeakReference** aOutWeakRef) +{ + if (!aOutWeakRef) { + return E_INVALIDARG; + } + *aOutWeakRef = nullptr; + + AutoCriticalSection lock(&mCS); + RefPtr<WeakRef> weakRef = MakeAndAddRef<WeakRef>(this); + + HRESULT hr = weakRef->QueryInterface(IID_IWeakReference, (void**)aOutWeakRef); + if (FAILED(hr)) { + return hr; + } + + mWeakRefs.AppendElement(weakRef.get()); + weakRef->AddRef(); + return S_OK; +} + +WeakRef::WeakRef(WeakReferenceSupport* aSupport) + : mRefCnt(1) + , mMutex("mozilla::mscom::WeakRef::mMutex") + , mSupport(aSupport) +{ + MOZ_ASSERT(aSupport); +} + +HRESULT +WeakRef::QueryInterface(REFIID riid, void** ppv) +{ + IUnknown* punk = nullptr; + if (!ppv) { + return E_INVALIDARG; + } + + if (riid == IID_IUnknown || riid == IID_IWeakReference) { + punk = static_cast<IUnknown*>(this); + } + + *ppv = punk; + if (!punk) { + return E_NOINTERFACE; + } + + punk->AddRef(); + return S_OK; +} + +ULONG +WeakRef::AddRef() +{ + return (ULONG) InterlockedIncrement((LONG*)&mRefCnt); +} + +ULONG +WeakRef::Release() +{ + ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt); + if (newRefCnt == 0) { + delete this; + } + return newRefCnt; +} + +HRESULT +WeakRef::Resolve(REFIID aIid, void** aOutStrongReference) +{ + MutexAutoLock lock(mMutex); + if (!mSupport) { + return E_FAIL; + } + return mSupport->QueryInterface(aIid, aOutStrongReference); +} + +void +WeakRef::Clear() +{ + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mSupport); + mSupport = nullptr; +} + +} // namespace mscom +} // namespace mozilla + diff --git a/ipc/mscom/WeakRef.h b/ipc/mscom/WeakRef.h new file mode 100644 index 000000000..4b4835aa5 --- /dev/null +++ b/ipc/mscom/WeakRef.h @@ -0,0 +1,104 @@ +/* -*- 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_WeakRef_h +#define mozilla_mscom_WeakRef_h + +#include <guiddef.h> +#include <unknwn.h> + +#include "mozilla/Mutex.h" +#include "nsTArray.h" + +/** + * Thread-safe weak references for COM that works pre-Windows 8 and do not + * require WinRT. + */ + +namespace mozilla { +namespace mscom { + +// {F841AEFA-064C-49A4-B73D-EBD14A90F012} +DEFINE_GUID(IID_IWeakReference, +0xf841aefa, 0x64c, 0x49a4, 0xb7, 0x3d, 0xeb, 0xd1, 0x4a, 0x90, 0xf0, 0x12); + +struct IWeakReference : public IUnknown +{ + virtual STDMETHODIMP Resolve(REFIID aIid, void** aOutStringReference) = 0; +}; + +// {87611F0C-9BBB-4F78-9D43-CAC5AD432CA1} +DEFINE_GUID(IID_IWeakReferenceSource, +0x87611f0c, 0x9bbb, 0x4f78, 0x9d, 0x43, 0xca, 0xc5, 0xad, 0x43, 0x2c, 0xa1); + +struct IWeakReferenceSource : public IUnknown +{ + virtual STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) = 0; +}; + +class WeakRef; + +class WeakReferenceSupport : public IWeakReferenceSource +{ +public: + enum class Flags + { + eNone = 0, + eDestroyOnMainThread = 1 + }; + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IWeakReferenceSource + STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) override; + +protected: + explicit WeakReferenceSupport(Flags aFlags); + virtual ~WeakReferenceSupport(); + + virtual HRESULT ThreadSafeQueryInterface(REFIID aIid, + IUnknown** aOutInterface) = 0; + +private: + void ClearWeakRefs(); + +private: + // Using a raw CRITICAL_SECTION here because it can be reentered + CRITICAL_SECTION mCS; + ULONG mRefCnt; + nsTArray<WeakRef*> mWeakRefs; + Flags mFlags; +}; + +class WeakRef : public IWeakReference +{ +public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IWeakReference + STDMETHODIMP Resolve(REFIID aIid, void** aOutStrongReference) override; + + explicit WeakRef(WeakReferenceSupport* aSupport); + + void Clear(); + +private: + ULONG mRefCnt; + mozilla::Mutex mMutex; // Protects mSupport + WeakReferenceSupport* mSupport; +}; + +} // namespace mscom +} // namespace mozilla + +#endif // mozilla_mscom_WeakRef_h + diff --git a/ipc/mscom/moz.build b/ipc/mscom/moz.build new file mode 100644 index 000000000..e5edbf591 --- /dev/null +++ b/ipc/mscom/moz.build @@ -0,0 +1,60 @@ +# -*- 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', + 'Ptr.h', + 'Utils.h', +] + +SOURCES += [ + 'Utils.cpp', +] + +UNIFIED_SOURCES += [ + 'EnsureMTA.cpp', + 'MainThreadRuntime.cpp', + 'ProxyStream.cpp', +] + +if CONFIG['ACCESSIBILITY']: + EXPORTS.mozilla.mscom += [ + 'ActivationContext.h', + 'DispatchForwarder.h', + 'Interceptor.h', + 'InterceptorLog.h', + 'MainThreadHandoff.h', + 'MainThreadInvoker.h', + 'Registration.h', + 'WeakRef.h', + ] + + SOURCES += [ + 'Interceptor.cpp', + 'Registration.cpp', + 'WeakRef.cpp', + ] + + UNIFIED_SOURCES += [ + 'ActivationContext.cpp', + 'DispatchForwarder.cpp', + 'InterceptorLog.cpp', + 'MainThreadHandoff.cpp', + 'MainThreadInvoker.cpp', + ] + +LOCAL_INCLUDES += [ + '/xpcom/base', + '/xpcom/build', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' |