summaryrefslogtreecommitdiffstats
path: root/ipc/mscom/Interceptor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/mscom/Interceptor.cpp')
-rw-r--r--ipc/mscom/Interceptor.cpp313
1 files changed, 313 insertions, 0 deletions
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