diff options
Diffstat (limited to '')
-rw-r--r-- | ipc/mscom/Interceptor.cpp | 855 |
1 files changed, 855 insertions, 0 deletions
diff --git a/ipc/mscom/Interceptor.cpp b/ipc/mscom/Interceptor.cpp new file mode 100644 index 0000000000..576be1e44f --- /dev/null +++ b/ipc/mscom/Interceptor.cpp @@ -0,0 +1,855 @@ +/* -*- 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 <utility> + +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/Unused.h" +#include "mozilla/mscom/DispatchForwarder.h" +#include "mozilla/mscom/FastMarshaler.h" +#include "mozilla/mscom/InterceptorLog.h" +#include "mozilla/mscom/MainThreadInvoker.h" +#include "mozilla/mscom/Objref.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/mscom/Utils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsExceptionHandler.h" +#include "nsPrintfCString.h" +#include "nsRefPtrHashtable.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#define ENSURE_HR_SUCCEEDED(hr) \ + MOZ_ASSERT(SUCCEEDED((HRESULT)hr)); \ + if (FAILED((HRESULT)hr)) { \ + return hr; \ + } + +namespace mozilla { +namespace mscom { +namespace detail { + +class LiveSet final { + public: + LiveSet() : mMutex("mozilla::mscom::LiveSet::mMutex") {} + + void Lock() { mMutex.Lock(); } + + void Unlock() { mMutex.Unlock(); } + + void Put(IUnknown* aKey, already_AddRefed<IWeakReference> aValue) { + mMutex.AssertCurrentThreadOwns(); + mLiveSet.Put(aKey, RefPtr<IWeakReference>{std::move(aValue)}); + } + + RefPtr<IWeakReference> Get(IUnknown* aKey) { + mMutex.AssertCurrentThreadOwns(); + RefPtr<IWeakReference> result; + mLiveSet.Get(aKey, getter_AddRefs(result)); + return result; + } + + void Remove(IUnknown* aKey) { + mMutex.AssertCurrentThreadOwns(); + mLiveSet.Remove(aKey); + } + + private: + Mutex mMutex; + nsRefPtrHashtable<nsPtrHashKey<IUnknown>, IWeakReference> mLiveSet; +}; + +/** + * We don't use the normal XPCOM BaseAutoLock because we need the ability + * to explicitly Unlock. + */ +class MOZ_RAII LiveSetAutoLock final { + public: + explicit LiveSetAutoLock(LiveSet& aLiveSet) : mLiveSet(&aLiveSet) { + aLiveSet.Lock(); + } + + ~LiveSetAutoLock() { + if (mLiveSet) { + mLiveSet->Unlock(); + } + } + + void Unlock() { + MOZ_ASSERT(mLiveSet); + if (mLiveSet) { + mLiveSet->Unlock(); + mLiveSet = nullptr; + } + } + + LiveSetAutoLock(const LiveSetAutoLock& aOther) = delete; + LiveSetAutoLock(LiveSetAutoLock&& aOther) = delete; + LiveSetAutoLock& operator=(const LiveSetAutoLock& aOther) = delete; + LiveSetAutoLock& operator=(LiveSetAutoLock&& aOther) = delete; + + private: + LiveSet* mLiveSet; +}; + +class MOZ_RAII ReentrySentinel final { + public: + explicit ReentrySentinel(Interceptor* aCurrent) : mCurInterceptor(aCurrent) { + static const bool kHasTls = tlsSentinelStackTop.init(); + MOZ_RELEASE_ASSERT(kHasTls); + + mPrevSentinel = tlsSentinelStackTop.get(); + tlsSentinelStackTop.set(this); + } + + ~ReentrySentinel() { tlsSentinelStackTop.set(mPrevSentinel); } + + bool IsOutermost() const { + return !(mPrevSentinel && mPrevSentinel->IsMarshaling(mCurInterceptor)); + } + + ReentrySentinel(const ReentrySentinel&) = delete; + ReentrySentinel(ReentrySentinel&&) = delete; + ReentrySentinel& operator=(const ReentrySentinel&) = delete; + ReentrySentinel& operator=(ReentrySentinel&&) = delete; + + private: + bool IsMarshaling(Interceptor* aTopInterceptor) const { + return aTopInterceptor == mCurInterceptor || + (mPrevSentinel && mPrevSentinel->IsMarshaling(aTopInterceptor)); + } + + private: + Interceptor* mCurInterceptor; + ReentrySentinel* mPrevSentinel; + + static MOZ_THREAD_LOCAL(ReentrySentinel*) tlsSentinelStackTop; +}; + +MOZ_THREAD_LOCAL(ReentrySentinel*) ReentrySentinel::tlsSentinelStackTop; + +class MOZ_RAII LoggedQIResult final { + public: + explicit LoggedQIResult(REFIID aIid) + : mIid(aIid), + mHr(E_UNEXPECTED), + mTarget(nullptr), + mInterceptor(nullptr), + mBegin(TimeStamp::Now()) {} + + ~LoggedQIResult() { + if (!mTarget) { + return; + } + + TimeStamp end(TimeStamp::Now()); + TimeDuration total(end - mBegin); + TimeDuration overhead(total - mNonOverheadDuration); + + InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead, + &mNonOverheadDuration); + } + + void Log(IUnknown* aTarget, IUnknown* aInterceptor) { + mTarget = aTarget; + mInterceptor = aInterceptor; + } + + void operator=(HRESULT aHr) { mHr = aHr; } + + operator HRESULT() { return mHr; } + + operator TimeDuration*() { return &mNonOverheadDuration; } + + LoggedQIResult(const LoggedQIResult&) = delete; + LoggedQIResult(LoggedQIResult&&) = delete; + LoggedQIResult& operator=(const LoggedQIResult&) = delete; + LoggedQIResult& operator=(LoggedQIResult&&) = delete; + + private: + REFIID mIid; + HRESULT mHr; + IUnknown* mTarget; + IUnknown* mInterceptor; + TimeDuration mNonOverheadDuration; + TimeStamp mBegin; +}; + +} // namespace detail + +static detail::LiveSet& GetLiveSet() { + static detail::LiveSet sLiveSet; + return sLiveSet; +} + +MOZ_THREAD_LOCAL(bool) Interceptor::tlsCreatingStdMarshal; + +/* static */ +HRESULT Interceptor::Create(STAUniquePtr<IUnknown> aTarget, + IInterceptorSink* aSink, REFIID aInitialIid, + void** aOutInterface) { + MOZ_ASSERT(aOutInterface && aTarget && aSink); + if (!aOutInterface) { + return E_INVALIDARG; + } + + detail::LiveSetAutoLock lock(GetLiveSet()); + + RefPtr<IWeakReference> existingWeak(GetLiveSet().Get(aTarget.get())); + if (existingWeak) { + RefPtr<IWeakReferenceSource> existingStrong; + if (SUCCEEDED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) { + // QI on existingStrong may touch other threads. Since we now hold a + // strong ref on the interceptor, we may now release the lock. + lock.Unlock(); + return existingStrong->QueryInterface(aInitialIid, aOutInterface); + } + } + + *aOutInterface = nullptr; + + if (!aTarget || !aSink) { + return E_INVALIDARG; + } + + RefPtr<Interceptor> intcpt(new Interceptor(aSink)); + return intcpt->GetInitialInterceptorForIID(lock, aInitialIid, + std::move(aTarget), aOutInterface); +} + +Interceptor::Interceptor(IInterceptorSink* aSink) + : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread), + mEventSink(aSink), + mInterceptorMapMutex("mozilla::mscom::Interceptor::mInterceptorMapMutex"), + mStdMarshalMutex("mozilla::mscom::Interceptor::mStdMarshalMutex"), + mStdMarshal(nullptr) { + static const bool kHasTls = tlsCreatingStdMarshal.init(); + MOZ_ASSERT(kHasTls); + Unused << kHasTls; + + MOZ_ASSERT(aSink); + RefPtr<IWeakReference> weakRef; + if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) { + aSink->SetInterceptor(weakRef); + } +} + +Interceptor::~Interceptor() { + { // Scope for lock + detail::LiveSetAutoLock lock(GetLiveSet()); + GetLiveSet().Remove(mTarget.get()); + } + + // 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 = nullptr; + entry.mTargetInterface->Release(); + } +} + +HRESULT +Interceptor::GetClassForHandler(DWORD aDestContext, void* aDestContextPtr, + CLSID* aHandlerClsid) { + if (aDestContextPtr || !aHandlerClsid || + aDestContext == MSHCTX_DIFFERENTMACHINE) { + return E_INVALIDARG; + } + + MOZ_ASSERT(mEventSink); + return mEventSink->GetHandler(WrapNotNull(aHandlerClsid)); +} + +REFIID +Interceptor::MarshalAs(REFIID aIid) const { +#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) + return IsCallerExternalProcess() ? aIid : mEventSink->MarshalAs(aIid); +#else + return mEventSink->MarshalAs(aIid); +#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) +} + +HRESULT +Interceptor::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext, + void* pvDestContext, DWORD mshlflags, + CLSID* pCid) { + return mStdMarshal->GetUnmarshalClass(MarshalAs(riid), pv, dwDestContext, + pvDestContext, mshlflags, pCid); +} + +HRESULT +Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext, + void* pvDestContext, DWORD mshlflags, + DWORD* pSize) { + detail::ReentrySentinel sentinel(this); + + HRESULT hr = mStdMarshal->GetMarshalSizeMax( + MarshalAs(riid), pv, dwDestContext, pvDestContext, mshlflags, pSize); + if (FAILED(hr) || !sentinel.IsOutermost()) { + return hr; + } + +#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) + if (XRE_IsContentProcess() && IsCallerExternalProcess()) { + // The caller isn't our chrome process, so we do not provide a handler + // payload. Even though we're only getting the size here, calculating the + // payload size might actually require building the payload. + return hr; + } +#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) + + DWORD payloadSize = 0; + hr = mEventSink->GetHandlerPayloadSize(WrapNotNull(this), + WrapNotNull(&payloadSize)); + if (hr == E_NOTIMPL) { + return S_OK; + } + + if (SUCCEEDED(hr)) { + *pSize += payloadSize; + } + return hr; +} + +HRESULT +Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv, + DWORD dwDestContext, void* pvDestContext, + DWORD mshlflags) { + detail::ReentrySentinel sentinel(this); + + HRESULT hr; + +#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) + // Save the current stream position + LARGE_INTEGER seekTo; + seekTo.QuadPart = 0; + + ULARGE_INTEGER objrefPos; + + hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos); + if (FAILED(hr)) { + return hr; + } + +#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) + + hr = mStdMarshal->MarshalInterface(pStm, MarshalAs(riid), pv, dwDestContext, + pvDestContext, mshlflags); + if (FAILED(hr) || !sentinel.IsOutermost()) { + return hr; + } + +#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) + if (XRE_IsContentProcess() && IsCallerExternalProcess()) { + // The caller isn't our chrome process, so do not provide a handler. + + // First, save the current position that marks the current end of the + // OBJREF in the stream. + ULARGE_INTEGER endPos; + hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &endPos); + if (FAILED(hr)) { + return hr; + } + + // Now strip out the handler. + if (!StripHandlerFromOBJREF(WrapNotNull(pStm), objrefPos.QuadPart, + endPos.QuadPart)) { + return E_FAIL; + } + + return S_OK; + } +#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER) + + hr = mEventSink->WriteHandlerPayload(WrapNotNull(this), WrapNotNull(pStm)); + if (hr == E_NOTIMPL) { + return S_OK; + } + + return hr; +} + +HRESULT +Interceptor::UnmarshalInterface(IStream* pStm, REFIID riid, void** ppv) { + return mStdMarshal->UnmarshalInterface(pStm, riid, ppv); +} + +HRESULT +Interceptor::ReleaseMarshalData(IStream* pStm) { + return mStdMarshal->ReleaseMarshalData(pStm); +} + +HRESULT +Interceptor::DisconnectObject(DWORD dwReserved) { + mEventSink->DisconnectHandlerRemotes(); + return mStdMarshal->DisconnectObject(dwReserved); +} + +Interceptor::MapEntry* Interceptor::Lookup(REFIID aIid) { + mInterceptorMapMutex.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<IUnknown>& aTarget) { + MutexAutoLock lock(mInterceptorMapMutex); + MapEntry* entry = Lookup(aIid); + if (entry) { + aTarget.reset(entry->mTargetInterface); + return S_OK; + } + + return E_NOINTERFACE; +} + +// CoGetInterceptor requires type metadata to be able to generate its emulated +// vtable. If no registered metadata is available, CoGetInterceptor returns +// kFileNotFound. +static const HRESULT kFileNotFound = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + +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; +} + +HRESULT +Interceptor::PublishTarget(detail::LiveSetAutoLock& aLiveSetLock, + RefPtr<IUnknown> aInterceptor, REFIID aTargetIid, + STAUniquePtr<IUnknown> aTarget) { + RefPtr<IWeakReference> weakRef; + HRESULT hr = GetWeakReference(getter_AddRefs(weakRef)); + if (FAILED(hr)) { + return hr; + } + + // mTarget is a weak reference to aTarget. This is safe because we transfer + // ownership of aTarget into mInterceptorMap which remains live for the + // lifetime of this Interceptor. + mTarget = ToInterceptorTargetPtr(aTarget); + GetLiveSet().Put(mTarget.get(), weakRef.forget()); + + // Now we transfer aTarget's ownership into mInterceptorMap. + mInterceptorMap.AppendElement( + MapEntry(aTargetIid, aInterceptor, aTarget.release())); + + // Release the live set lock because subsequent operations may post work to + // the main thread, creating potential for deadlocks. + aLiveSetLock.Unlock(); + return S_OK; +} + +HRESULT +Interceptor::GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock, + REFIID aTargetIid, + STAUniquePtr<IUnknown> aTarget, + void** aOutInterceptor) { + MOZ_ASSERT(aOutInterceptor); + MOZ_ASSERT(aTargetIid != IID_IMarshal); + MOZ_ASSERT(!IsProxy(aTarget.get())); + + HRESULT hr = E_UNEXPECTED; + + auto hasFailed = [&hr]() -> bool { return FAILED(hr); }; + + auto cleanup = [&aLiveSetLock]() -> void { aLiveSetLock.Unlock(); }; + + ExecuteWhen<decltype(hasFailed), decltype(cleanup)> onFail(hasFailed, + cleanup); + + if (aTargetIid == IID_IUnknown) { + // We must lock mInterceptorMapMutex so that nothing can race with us once + // we have been published to the live set. + MutexAutoLock lock(mInterceptorMapMutex); + + hr = PublishTarget(aLiveSetLock, nullptr, aTargetIid, std::move(aTarget)); + ENSURE_HR_SUCCEEDED(hr); + + hr = QueryInterface(aTargetIid, aOutInterceptor); + ENSURE_HR_SUCCEEDED(hr); + return hr; + } + + // Raise the refcount for stabilization purposes during aggregation + WeakReferenceSupport::StabilizeRefCount stabilizer(*this); + + RefPtr<IUnknown> unkInterceptor; + hr = CreateInterceptor(aTargetIid, static_cast<WeakReferenceSupport*>(this), + getter_AddRefs(unkInterceptor)); + ENSURE_HR_SUCCEEDED(hr); + + RefPtr<ICallInterceptor> interceptor; + hr = unkInterceptor->QueryInterface(IID_ICallInterceptor, + getter_AddRefs(interceptor)); + ENSURE_HR_SUCCEEDED(hr); + + hr = interceptor->RegisterSink(mEventSink); + ENSURE_HR_SUCCEEDED(hr); + + // We must lock mInterceptorMapMutex so that nothing can race with us once we + // have been published to the live set. + MutexAutoLock lock(mInterceptorMapMutex); + + hr = PublishTarget(aLiveSetLock, unkInterceptor, aTargetIid, + std::move(aTarget)); + ENSURE_HR_SUCCEEDED(hr); + + if (MarshalAs(aTargetIid) == aTargetIid) { + hr = unkInterceptor->QueryInterface(aTargetIid, aOutInterceptor); + ENSURE_HR_SUCCEEDED(hr); + return hr; + } + + hr = GetInterceptorForIID(aTargetIid, aOutInterceptor, &lock); + ENSURE_HR_SUCCEEDED(hr); + return hr; +} + +HRESULT +Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) { + return GetInterceptorForIID(aIid, aOutInterceptor, nullptr); +} + +/** + * 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. + * @param aAlreadyLocked Proof of an existing lock on |mInterceptorMapMutex|, + * if present. + */ +HRESULT +Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor, + MutexAutoLock* aAlreadyLocked) { + detail::LoggedQIResult result(aIid); + + if (!aOutInterceptor) { + return E_INVALIDARG; + } + + if (aIid == IID_IUnknown) { + // Special case: When we see IUnknown, we just provide a reference to this + RefPtr<IInterceptor> intcpt(this); + intcpt.forget(aOutInterceptor); + return S_OK; + } + + REFIID interceptorIid = MarshalAs(aIid); + + RefPtr<IUnknown> unkInterceptor; + IUnknown* interfaceForQILog = nullptr; + + // (1) Check to see if we already have an existing interceptor for + // interceptorIid. + auto doLookup = [&]() -> void { + MapEntry* entry = Lookup(interceptorIid); + if (entry) { + unkInterceptor = entry->mInterceptor; + interfaceForQILog = entry->mTargetInterface; + } + }; + + if (aAlreadyLocked) { + doLookup(); + } else { + MutexAutoLock lock(mInterceptorMapMutex); + doLookup(); + } + + // (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. + result.Log(mTarget.get(), interfaceForQILog); + result = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor); + ENSURE_HR_SUCCEEDED(result); + return result; + } + + // (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(interceptorIid, (void**)&rawTargetInterface, result); + targetInterface.reset(rawTargetInterface); + result = hr; + result.Log(mTarget.get(), targetInterface.get()); + MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE); + if (hr == E_NOINTERFACE) { + return hr; + } + ENSURE_HR_SUCCEEDED(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 + WeakReferenceSupport::StabilizeRefCount stabilizer(*this); + + hr = CreateInterceptor(interceptorIid, + static_cast<WeakReferenceSupport*>(this), + getter_AddRefs(unkInterceptor)); + ENSURE_HR_SUCCEEDED(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)); + ENSURE_HR_SUCCEEDED(hr); + + hr = interceptor->RegisterSink(mEventSink); + ENSURE_HR_SUCCEEDED(hr); + + // (5) Now that we have this new COM interceptor, insert it into the map. + auto doInsertion = [&]() -> void { + // We might have raced with another thread, so first check that we don't + // already have an entry for this + MapEntry* entry = Lookup(interceptorIid); + if (entry && entry->mInterceptor) { + // Bug 1433046: Because of aggregation, the QI for |interceptor| + // AddRefed |this|, not |unkInterceptor|. Thus, releasing |unkInterceptor| + // will destroy the object. Before we do that, we must first release + // |interceptor|. Otherwise, |interceptor| would be invalidated when + // |unkInterceptor| is destroyed. + interceptor = nullptr; + unkInterceptor = entry->mInterceptor; + } else { + // MapEntry has a RefPtr to unkInterceptor, 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(interceptorIid, unkInterceptor, rawTargetInterface)); + } + }; + + if (aAlreadyLocked) { + doInsertion(); + } else { + MutexAutoLock lock(mInterceptorMapMutex); + doInsertion(); + } + + hr = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor); + ENSURE_HR_SUCCEEDED(hr); + return hr; +} + +HRESULT +Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput, + TimeDuration* aOutDuration) { + // 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*. + if (!NS_IsMainThread() && tlsCreatingStdMarshal.get()) { + mStdMarshalMutex.AssertCurrentThreadOwns(); + // COM queries for special interfaces such as IFastRundown when creating a + // marshaler. We don't want these being dispatched to the main thread, + // since this would cause a deadlock on mStdMarshalMutex if the main + // thread is also querying for IMarshal. If we do need to respond to these + // special interfaces, this should be done before this point; e.g. in + // Interceptor::QueryInterface like we do for INoMarshal. + return E_NOINTERFACE; + } + + if (mEventSink->IsInterfaceMaybeSupported(aIid) == E_NOINTERFACE) { + return E_NOINTERFACE; + } + + MainThreadInvoker invoker; + HRESULT hr; + auto runOnMainThread = [&]() -> void { + MOZ_ASSERT(NS_IsMainThread()); + hr = mTarget->QueryInterface(aIid, aOutput); + }; + if (!invoker.Invoke(NS_NewRunnableFunction("Interceptor::QueryInterface", + runOnMainThread))) { + return E_FAIL; + } + if (aOutDuration) { + *aOutDuration = invoker.GetDuration(); + } + return hr; +} + +HRESULT +Interceptor::QueryInterface(REFIID riid, void** ppv) { + if (riid == IID_INoMarshal) { + // This entire library is designed around marshaling, so there's no point + // propagating this QI request all over the place! + return E_NOINTERFACE; + } + + return WeakReferenceSupport::QueryInterface(riid, ppv); +} + +HRESULT +Interceptor::WeakRefQueryInterface(REFIID aIid, IUnknown** aOutInterface) { + if (aIid == IID_IStdMarshalInfo) { + detail::ReentrySentinel sentinel(this); + + if (!sentinel.IsOutermost()) { + return E_NOINTERFACE; + } + + // Do not indicate that this interface is available unless we actually + // support it. We'll check that by looking for a successful call to + // IInterceptorSink::GetHandler() + CLSID dummy; + if (FAILED(mEventSink->GetHandler(WrapNotNull(&dummy)))) { + return E_NOINTERFACE; + } + + RefPtr<IStdMarshalInfo> std(this); + std.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IMarshal) { + MutexAutoLock lock(mStdMarshalMutex); + + HRESULT hr; + + if (!mStdMarshalUnk) { + MOZ_ASSERT(!tlsCreatingStdMarshal.get()); + tlsCreatingStdMarshal.set(true); + if (XRE_IsContentProcess()) { + hr = FastMarshaler::Create(static_cast<IWeakReferenceSource*>(this), + getter_AddRefs(mStdMarshalUnk)); + } else { + hr = ::CoGetStdMarshalEx(static_cast<IWeakReferenceSource*>(this), + SMEXF_SERVER, getter_AddRefs(mStdMarshalUnk)); + } + tlsCreatingStdMarshal.set(false); + + ENSURE_HR_SUCCEEDED(hr); + } + + if (!mStdMarshal) { + hr = mStdMarshalUnk->QueryInterface(IID_IMarshal, (void**)&mStdMarshal); + ENSURE_HR_SUCCEEDED(hr); + + // mStdMarshal is weak, so drop its refcount + mStdMarshal->Release(); + } + + RefPtr<IMarshal> marshal(this); + marshal.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IInterceptor) { + RefPtr<IInterceptor> intcpt(this); + intcpt.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IDispatch) { + STAUniquePtr<IDispatch> disp; + IDispatch* rawDisp = nullptr; + HRESULT hr = QueryInterfaceTarget(aIid, (void**)&rawDisp); + ENSURE_HR_SUCCEEDED(hr); + + disp.reset(rawDisp); + return DispatchForwarder::Create(this, disp, aOutInterface); + } + + return GetInterceptorForIID(aIid, (void**)aOutInterface, nullptr); +} + +ULONG +Interceptor::AddRef() { return WeakReferenceSupport::AddRef(); } + +ULONG +Interceptor::Release() { return WeakReferenceSupport::Release(); } + +/* static */ +HRESULT Interceptor::DisconnectRemotesForTarget(IUnknown* aTarget) { + MOZ_ASSERT(aTarget); + + detail::LiveSetAutoLock lock(GetLiveSet()); + + // It is not an error if the interceptor doesn't exist, so we return + // S_FALSE instead of an error in that case. + RefPtr<IWeakReference> existingWeak(GetLiveSet().Get(aTarget)); + if (!existingWeak) { + return S_FALSE; + } + + RefPtr<IWeakReferenceSource> existingStrong; + if (FAILED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) { + return S_FALSE; + } + // Since we now hold a strong ref on the interceptor, we may now release the + // lock. + lock.Unlock(); + + return ::CoDisconnectObject(existingStrong, 0); +} + +} // namespace mscom +} // namespace mozilla |