1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #define INITGUID
8
9 #include "mozilla/mscom/Interceptor.h"
10
11 #include <utility>
12
13 #include "MainThreadUtils.h"
14 #include "mozilla/Assertions.h"
15 #include "mozilla/ThreadLocal.h"
16 #include "mozilla/Unused.h"
17 #include "mozilla/mscom/DispatchForwarder.h"
18 #include "mozilla/mscom/FastMarshaler.h"
19 #include "mozilla/mscom/InterceptorLog.h"
20 #include "mozilla/mscom/MainThreadInvoker.h"
21 #include "mozilla/mscom/Objref.h"
22 #include "mozilla/mscom/Registration.h"
23 #include "mozilla/mscom/Utils.h"
24 #include "nsDirectoryServiceDefs.h"
25 #include "nsDirectoryServiceUtils.h"
26 #include "nsExceptionHandler.h"
27 #include "nsPrintfCString.h"
28 #include "nsRefPtrHashtable.h"
29 #include "nsThreadUtils.h"
30 #include "nsXULAppAPI.h"
31
32 #define ENSURE_HR_SUCCEEDED(hr) \
33 MOZ_ASSERT(SUCCEEDED((HRESULT)hr)); \
34 if (FAILED((HRESULT)hr)) { \
35 return hr; \
36 }
37
38 namespace mozilla {
39 namespace mscom {
40 namespace detail {
41
42 class LiveSet final {
43 public:
LiveSet()44 LiveSet() : mMutex("mozilla::mscom::LiveSet::mMutex") {}
45
Lock()46 void Lock() { mMutex.Lock(); }
47
Unlock()48 void Unlock() { mMutex.Unlock(); }
49
Put(IUnknown * aKey,already_AddRefed<IWeakReference> aValue)50 void Put(IUnknown* aKey, already_AddRefed<IWeakReference> aValue) {
51 mMutex.AssertCurrentThreadOwns();
52 mLiveSet.InsertOrUpdate(aKey, RefPtr<IWeakReference>{std::move(aValue)});
53 }
54
Get(IUnknown * aKey)55 RefPtr<IWeakReference> Get(IUnknown* aKey) {
56 mMutex.AssertCurrentThreadOwns();
57 RefPtr<IWeakReference> result;
58 mLiveSet.Get(aKey, getter_AddRefs(result));
59 return result;
60 }
61
Remove(IUnknown * aKey)62 void Remove(IUnknown* aKey) {
63 mMutex.AssertCurrentThreadOwns();
64 mLiveSet.Remove(aKey);
65 }
66
67 private:
68 Mutex mMutex;
69 nsRefPtrHashtable<nsPtrHashKey<IUnknown>, IWeakReference> mLiveSet;
70 };
71
72 /**
73 * We don't use the normal XPCOM BaseAutoLock because we need the ability
74 * to explicitly Unlock.
75 */
76 class MOZ_RAII LiveSetAutoLock final {
77 public:
LiveSetAutoLock(LiveSet & aLiveSet)78 explicit LiveSetAutoLock(LiveSet& aLiveSet) : mLiveSet(&aLiveSet) {
79 aLiveSet.Lock();
80 }
81
~LiveSetAutoLock()82 ~LiveSetAutoLock() {
83 if (mLiveSet) {
84 mLiveSet->Unlock();
85 }
86 }
87
Unlock()88 void Unlock() {
89 MOZ_ASSERT(mLiveSet);
90 if (mLiveSet) {
91 mLiveSet->Unlock();
92 mLiveSet = nullptr;
93 }
94 }
95
96 LiveSetAutoLock(const LiveSetAutoLock& aOther) = delete;
97 LiveSetAutoLock(LiveSetAutoLock&& aOther) = delete;
98 LiveSetAutoLock& operator=(const LiveSetAutoLock& aOther) = delete;
99 LiveSetAutoLock& operator=(LiveSetAutoLock&& aOther) = delete;
100
101 private:
102 LiveSet* mLiveSet;
103 };
104
105 class MOZ_RAII ReentrySentinel final {
106 public:
ReentrySentinel(Interceptor * aCurrent)107 explicit ReentrySentinel(Interceptor* aCurrent) : mCurInterceptor(aCurrent) {
108 static const bool kHasTls = tlsSentinelStackTop.init();
109 MOZ_RELEASE_ASSERT(kHasTls);
110
111 mPrevSentinel = tlsSentinelStackTop.get();
112 tlsSentinelStackTop.set(this);
113 }
114
~ReentrySentinel()115 ~ReentrySentinel() { tlsSentinelStackTop.set(mPrevSentinel); }
116
IsOutermost() const117 bool IsOutermost() const {
118 return !(mPrevSentinel && mPrevSentinel->IsMarshaling(mCurInterceptor));
119 }
120
121 ReentrySentinel(const ReentrySentinel&) = delete;
122 ReentrySentinel(ReentrySentinel&&) = delete;
123 ReentrySentinel& operator=(const ReentrySentinel&) = delete;
124 ReentrySentinel& operator=(ReentrySentinel&&) = delete;
125
126 private:
IsMarshaling(Interceptor * aTopInterceptor) const127 bool IsMarshaling(Interceptor* aTopInterceptor) const {
128 return aTopInterceptor == mCurInterceptor ||
129 (mPrevSentinel && mPrevSentinel->IsMarshaling(aTopInterceptor));
130 }
131
132 private:
133 Interceptor* mCurInterceptor;
134 ReentrySentinel* mPrevSentinel;
135
136 static MOZ_THREAD_LOCAL(ReentrySentinel*) tlsSentinelStackTop;
137 };
138
139 MOZ_THREAD_LOCAL(ReentrySentinel*) ReentrySentinel::tlsSentinelStackTop;
140
141 class MOZ_RAII LoggedQIResult final {
142 public:
LoggedQIResult(REFIID aIid)143 explicit LoggedQIResult(REFIID aIid)
144 : mIid(aIid),
145 mHr(E_UNEXPECTED),
146 mTarget(nullptr),
147 mInterceptor(nullptr),
148 mBegin(TimeStamp::Now()) {}
149
~LoggedQIResult()150 ~LoggedQIResult() {
151 if (!mTarget) {
152 return;
153 }
154
155 TimeStamp end(TimeStamp::Now());
156 TimeDuration total(end - mBegin);
157 TimeDuration overhead(total - mNonOverheadDuration);
158
159 InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead,
160 &mNonOverheadDuration);
161 }
162
Log(IUnknown * aTarget,IUnknown * aInterceptor)163 void Log(IUnknown* aTarget, IUnknown* aInterceptor) {
164 mTarget = aTarget;
165 mInterceptor = aInterceptor;
166 }
167
operator =(HRESULT aHr)168 void operator=(HRESULT aHr) { mHr = aHr; }
169
operator HRESULT()170 operator HRESULT() { return mHr; }
171
operator TimeDuration*()172 operator TimeDuration*() { return &mNonOverheadDuration; }
173
174 LoggedQIResult(const LoggedQIResult&) = delete;
175 LoggedQIResult(LoggedQIResult&&) = delete;
176 LoggedQIResult& operator=(const LoggedQIResult&) = delete;
177 LoggedQIResult& operator=(LoggedQIResult&&) = delete;
178
179 private:
180 REFIID mIid;
181 HRESULT mHr;
182 IUnknown* mTarget;
183 IUnknown* mInterceptor;
184 TimeDuration mNonOverheadDuration;
185 TimeStamp mBegin;
186 };
187
188 } // namespace detail
189
GetLiveSet()190 static detail::LiveSet& GetLiveSet() {
191 static detail::LiveSet sLiveSet;
192 return sLiveSet;
193 }
194
195 MOZ_THREAD_LOCAL(bool) Interceptor::tlsCreatingStdMarshal;
196
197 /* static */
Create(STAUniquePtr<IUnknown> aTarget,IInterceptorSink * aSink,REFIID aInitialIid,void ** aOutInterface)198 HRESULT Interceptor::Create(STAUniquePtr<IUnknown> aTarget,
199 IInterceptorSink* aSink, REFIID aInitialIid,
200 void** aOutInterface) {
201 MOZ_ASSERT(aOutInterface && aTarget && aSink);
202 if (!aOutInterface) {
203 return E_INVALIDARG;
204 }
205
206 detail::LiveSetAutoLock lock(GetLiveSet());
207
208 RefPtr<IWeakReference> existingWeak(GetLiveSet().Get(aTarget.get()));
209 if (existingWeak) {
210 RefPtr<IWeakReferenceSource> existingStrong;
211 if (SUCCEEDED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) {
212 // QI on existingStrong may touch other threads. Since we now hold a
213 // strong ref on the interceptor, we may now release the lock.
214 lock.Unlock();
215 return existingStrong->QueryInterface(aInitialIid, aOutInterface);
216 }
217 }
218
219 *aOutInterface = nullptr;
220
221 if (!aTarget || !aSink) {
222 return E_INVALIDARG;
223 }
224
225 RefPtr<Interceptor> intcpt(new Interceptor(aSink));
226 return intcpt->GetInitialInterceptorForIID(lock, aInitialIid,
227 std::move(aTarget), aOutInterface);
228 }
229
Interceptor(IInterceptorSink * aSink)230 Interceptor::Interceptor(IInterceptorSink* aSink)
231 : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread),
232 mEventSink(aSink),
233 mInterceptorMapMutex("mozilla::mscom::Interceptor::mInterceptorMapMutex"),
234 mStdMarshalMutex("mozilla::mscom::Interceptor::mStdMarshalMutex"),
235 mStdMarshal(nullptr) {
236 static const bool kHasTls = tlsCreatingStdMarshal.init();
237 MOZ_ASSERT(kHasTls);
238 Unused << kHasTls;
239
240 MOZ_ASSERT(aSink);
241 RefPtr<IWeakReference> weakRef;
242 if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) {
243 aSink->SetInterceptor(weakRef);
244 }
245 }
246
~Interceptor()247 Interceptor::~Interceptor() {
248 { // Scope for lock
249 detail::LiveSetAutoLock lock(GetLiveSet());
250 GetLiveSet().Remove(mTarget.get());
251 }
252
253 // This needs to run on the main thread because it releases target interface
254 // reference counts which may not be thread-safe.
255 MOZ_ASSERT(NS_IsMainThread());
256 for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len;
257 ++index) {
258 MapEntry& entry = mInterceptorMap[index];
259 entry.mInterceptor = nullptr;
260 entry.mTargetInterface->Release();
261 }
262 }
263
264 HRESULT
GetClassForHandler(DWORD aDestContext,void * aDestContextPtr,CLSID * aHandlerClsid)265 Interceptor::GetClassForHandler(DWORD aDestContext, void* aDestContextPtr,
266 CLSID* aHandlerClsid) {
267 if (aDestContextPtr || !aHandlerClsid ||
268 aDestContext == MSHCTX_DIFFERENTMACHINE) {
269 return E_INVALIDARG;
270 }
271
272 MOZ_ASSERT(mEventSink);
273 return mEventSink->GetHandler(WrapNotNull(aHandlerClsid));
274 }
275
276 REFIID
MarshalAs(REFIID aIid) const277 Interceptor::MarshalAs(REFIID aIid) const {
278 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
279 return IsCallerExternalProcess() ? aIid : mEventSink->MarshalAs(aIid);
280 #else
281 return mEventSink->MarshalAs(aIid);
282 #endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
283 }
284
285 HRESULT
GetUnmarshalClass(REFIID riid,void * pv,DWORD dwDestContext,void * pvDestContext,DWORD mshlflags,CLSID * pCid)286 Interceptor::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
287 void* pvDestContext, DWORD mshlflags,
288 CLSID* pCid) {
289 return mStdMarshal->GetUnmarshalClass(MarshalAs(riid), pv, dwDestContext,
290 pvDestContext, mshlflags, pCid);
291 }
292
293 HRESULT
GetMarshalSizeMax(REFIID riid,void * pv,DWORD dwDestContext,void * pvDestContext,DWORD mshlflags,DWORD * pSize)294 Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
295 void* pvDestContext, DWORD mshlflags,
296 DWORD* pSize) {
297 detail::ReentrySentinel sentinel(this);
298
299 HRESULT hr = mStdMarshal->GetMarshalSizeMax(
300 MarshalAs(riid), pv, dwDestContext, pvDestContext, mshlflags, pSize);
301 if (FAILED(hr) || !sentinel.IsOutermost()) {
302 return hr;
303 }
304
305 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
306 if (XRE_IsContentProcess() && IsCallerExternalProcess()) {
307 // The caller isn't our chrome process, so we do not provide a handler
308 // payload. Even though we're only getting the size here, calculating the
309 // payload size might actually require building the payload.
310 return hr;
311 }
312 #endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
313
314 DWORD payloadSize = 0;
315 hr = mEventSink->GetHandlerPayloadSize(WrapNotNull(this),
316 WrapNotNull(&payloadSize));
317 if (hr == E_NOTIMPL) {
318 return S_OK;
319 }
320
321 if (SUCCEEDED(hr)) {
322 *pSize += payloadSize;
323 }
324 return hr;
325 }
326
327 HRESULT
MarshalInterface(IStream * pStm,REFIID riid,void * pv,DWORD dwDestContext,void * pvDestContext,DWORD mshlflags)328 Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv,
329 DWORD dwDestContext, void* pvDestContext,
330 DWORD mshlflags) {
331 detail::ReentrySentinel sentinel(this);
332
333 HRESULT hr;
334
335 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
336 // Save the current stream position
337 LARGE_INTEGER seekTo;
338 seekTo.QuadPart = 0;
339
340 ULARGE_INTEGER objrefPos;
341
342 hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos);
343 if (FAILED(hr)) {
344 return hr;
345 }
346
347 #endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
348
349 hr = mStdMarshal->MarshalInterface(pStm, MarshalAs(riid), pv, dwDestContext,
350 pvDestContext, mshlflags);
351 if (FAILED(hr) || !sentinel.IsOutermost()) {
352 return hr;
353 }
354
355 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
356 if (XRE_IsContentProcess() && IsCallerExternalProcess()) {
357 // The caller isn't our chrome process, so do not provide a handler.
358
359 // First, save the current position that marks the current end of the
360 // OBJREF in the stream.
361 ULARGE_INTEGER endPos;
362 hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &endPos);
363 if (FAILED(hr)) {
364 return hr;
365 }
366
367 // Now strip out the handler.
368 if (!StripHandlerFromOBJREF(WrapNotNull(pStm), objrefPos.QuadPart,
369 endPos.QuadPart)) {
370 return E_FAIL;
371 }
372
373 return S_OK;
374 }
375 #endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
376
377 hr = mEventSink->WriteHandlerPayload(WrapNotNull(this), WrapNotNull(pStm));
378 if (hr == E_NOTIMPL) {
379 return S_OK;
380 }
381
382 return hr;
383 }
384
385 HRESULT
UnmarshalInterface(IStream * pStm,REFIID riid,void ** ppv)386 Interceptor::UnmarshalInterface(IStream* pStm, REFIID riid, void** ppv) {
387 return mStdMarshal->UnmarshalInterface(pStm, riid, ppv);
388 }
389
390 HRESULT
ReleaseMarshalData(IStream * pStm)391 Interceptor::ReleaseMarshalData(IStream* pStm) {
392 return mStdMarshal->ReleaseMarshalData(pStm);
393 }
394
395 HRESULT
DisconnectObject(DWORD dwReserved)396 Interceptor::DisconnectObject(DWORD dwReserved) {
397 mEventSink->DisconnectHandlerRemotes();
398 return mStdMarshal->DisconnectObject(dwReserved);
399 }
400
Lookup(REFIID aIid)401 Interceptor::MapEntry* Interceptor::Lookup(REFIID aIid) {
402 mInterceptorMapMutex.AssertCurrentThreadOwns();
403
404 for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len;
405 ++index) {
406 if (mInterceptorMap[index].mIID == aIid) {
407 return &mInterceptorMap[index];
408 }
409 }
410 return nullptr;
411 }
412
413 HRESULT
GetTargetForIID(REFIID aIid,InterceptorTargetPtr<IUnknown> & aTarget)414 Interceptor::GetTargetForIID(REFIID aIid,
415 InterceptorTargetPtr<IUnknown>& aTarget) {
416 MutexAutoLock lock(mInterceptorMapMutex);
417 MapEntry* entry = Lookup(aIid);
418 if (entry) {
419 aTarget.reset(entry->mTargetInterface);
420 return S_OK;
421 }
422
423 return E_NOINTERFACE;
424 }
425
426 // CoGetInterceptor requires type metadata to be able to generate its emulated
427 // vtable. If no registered metadata is available, CoGetInterceptor returns
428 // kFileNotFound.
429 static const HRESULT kFileNotFound = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
430
431 HRESULT
CreateInterceptor(REFIID aIid,IUnknown * aOuter,IUnknown ** aOutput)432 Interceptor::CreateInterceptor(REFIID aIid, IUnknown* aOuter,
433 IUnknown** aOutput) {
434 // In order to aggregate, we *must* request IID_IUnknown as the initial
435 // interface for the interceptor, as that IUnknown is non-delegating.
436 // This is a fundamental rule for creating aggregated objects in COM.
437 HRESULT hr = ::CoGetInterceptor(aIid, aOuter, IID_IUnknown, (void**)aOutput);
438 if (hr != kFileNotFound) {
439 return hr;
440 }
441
442 // In the case that CoGetInterceptor returns kFileNotFound, we can try to
443 // explicitly load typelib data from our runtime registration facility and
444 // pass that into CoGetInterceptorFromTypeInfo.
445
446 RefPtr<ITypeInfo> typeInfo;
447 bool found = RegisteredProxy::Find(aIid, getter_AddRefs(typeInfo));
448 // If this assert fires then we have omitted registering the typelib for a
449 // required interface. To fix this, review our calls to mscom::RegisterProxy
450 // and mscom::RegisterTypelib, and add the additional typelib as necessary.
451 MOZ_ASSERT(found);
452 if (!found) {
453 return kFileNotFound;
454 }
455
456 hr = ::CoGetInterceptorFromTypeInfo(aIid, aOuter, typeInfo, IID_IUnknown,
457 (void**)aOutput);
458 // If this assert fires then the interceptor doesn't like something about
459 // the format of the typelib. One thing in particular that it doesn't like
460 // is complex types that contain unions.
461 MOZ_ASSERT(SUCCEEDED(hr));
462 return hr;
463 }
464
465 HRESULT
PublishTarget(detail::LiveSetAutoLock & aLiveSetLock,RefPtr<IUnknown> aInterceptor,REFIID aTargetIid,STAUniquePtr<IUnknown> aTarget)466 Interceptor::PublishTarget(detail::LiveSetAutoLock& aLiveSetLock,
467 RefPtr<IUnknown> aInterceptor, REFIID aTargetIid,
468 STAUniquePtr<IUnknown> aTarget) {
469 RefPtr<IWeakReference> weakRef;
470 HRESULT hr = GetWeakReference(getter_AddRefs(weakRef));
471 if (FAILED(hr)) {
472 return hr;
473 }
474
475 // mTarget is a weak reference to aTarget. This is safe because we transfer
476 // ownership of aTarget into mInterceptorMap which remains live for the
477 // lifetime of this Interceptor.
478 mTarget = ToInterceptorTargetPtr(aTarget);
479 GetLiveSet().Put(mTarget.get(), weakRef.forget());
480
481 // Now we transfer aTarget's ownership into mInterceptorMap.
482 mInterceptorMap.AppendElement(
483 MapEntry(aTargetIid, aInterceptor, aTarget.release()));
484
485 // Release the live set lock because subsequent operations may post work to
486 // the main thread, creating potential for deadlocks.
487 aLiveSetLock.Unlock();
488 return S_OK;
489 }
490
491 HRESULT
GetInitialInterceptorForIID(detail::LiveSetAutoLock & aLiveSetLock,REFIID aTargetIid,STAUniquePtr<IUnknown> aTarget,void ** aOutInterceptor)492 Interceptor::GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock,
493 REFIID aTargetIid,
494 STAUniquePtr<IUnknown> aTarget,
495 void** aOutInterceptor) {
496 MOZ_ASSERT(aOutInterceptor);
497 MOZ_ASSERT(aTargetIid != IID_IMarshal);
498 MOZ_ASSERT(!IsProxy(aTarget.get()));
499
500 HRESULT hr = E_UNEXPECTED;
501
502 auto hasFailed = [&hr]() -> bool { return FAILED(hr); };
503
504 auto cleanup = [&aLiveSetLock]() -> void { aLiveSetLock.Unlock(); };
505
506 ExecuteWhen<decltype(hasFailed), decltype(cleanup)> onFail(hasFailed,
507 cleanup);
508
509 if (aTargetIid == IID_IUnknown) {
510 // We must lock mInterceptorMapMutex so that nothing can race with us once
511 // we have been published to the live set.
512 MutexAutoLock lock(mInterceptorMapMutex);
513
514 hr = PublishTarget(aLiveSetLock, nullptr, aTargetIid, std::move(aTarget));
515 ENSURE_HR_SUCCEEDED(hr);
516
517 hr = QueryInterface(aTargetIid, aOutInterceptor);
518 ENSURE_HR_SUCCEEDED(hr);
519 return hr;
520 }
521
522 // Raise the refcount for stabilization purposes during aggregation
523 WeakReferenceSupport::StabilizeRefCount stabilizer(*this);
524
525 RefPtr<IUnknown> unkInterceptor;
526 hr = CreateInterceptor(aTargetIid, static_cast<WeakReferenceSupport*>(this),
527 getter_AddRefs(unkInterceptor));
528 ENSURE_HR_SUCCEEDED(hr);
529
530 RefPtr<ICallInterceptor> interceptor;
531 hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
532 getter_AddRefs(interceptor));
533 ENSURE_HR_SUCCEEDED(hr);
534
535 hr = interceptor->RegisterSink(mEventSink);
536 ENSURE_HR_SUCCEEDED(hr);
537
538 // We must lock mInterceptorMapMutex so that nothing can race with us once we
539 // have been published to the live set.
540 MutexAutoLock lock(mInterceptorMapMutex);
541
542 hr = PublishTarget(aLiveSetLock, unkInterceptor, aTargetIid,
543 std::move(aTarget));
544 ENSURE_HR_SUCCEEDED(hr);
545
546 if (MarshalAs(aTargetIid) == aTargetIid) {
547 hr = unkInterceptor->QueryInterface(aTargetIid, aOutInterceptor);
548 ENSURE_HR_SUCCEEDED(hr);
549 return hr;
550 }
551
552 hr = GetInterceptorForIID(aTargetIid, aOutInterceptor, &lock);
553 ENSURE_HR_SUCCEEDED(hr);
554 return hr;
555 }
556
557 HRESULT
GetInterceptorForIID(REFIID aIid,void ** aOutInterceptor)558 Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) {
559 return GetInterceptorForIID(aIid, aOutInterceptor, nullptr);
560 }
561
562 /**
563 * This method contains the core guts of the handling of QueryInterface calls
564 * that are delegated to us from the ICallInterceptor.
565 *
566 * @param aIid ID of the desired interface
567 * @param aOutInterceptor The resulting emulated vtable that corresponds to
568 * the interface specified by aIid.
569 * @param aAlreadyLocked Proof of an existing lock on |mInterceptorMapMutex|,
570 * if present.
571 */
572 HRESULT
GetInterceptorForIID(REFIID aIid,void ** aOutInterceptor,MutexAutoLock * aAlreadyLocked)573 Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor,
574 MutexAutoLock* aAlreadyLocked) {
575 detail::LoggedQIResult result(aIid);
576
577 if (!aOutInterceptor) {
578 return E_INVALIDARG;
579 }
580
581 if (aIid == IID_IUnknown) {
582 // Special case: When we see IUnknown, we just provide a reference to this
583 RefPtr<IInterceptor> intcpt(this);
584 intcpt.forget(aOutInterceptor);
585 return S_OK;
586 }
587
588 REFIID interceptorIid = MarshalAs(aIid);
589
590 RefPtr<IUnknown> unkInterceptor;
591 IUnknown* interfaceForQILog = nullptr;
592
593 // (1) Check to see if we already have an existing interceptor for
594 // interceptorIid.
595 auto doLookup = [&]() -> void {
596 MapEntry* entry = Lookup(interceptorIid);
597 if (entry) {
598 unkInterceptor = entry->mInterceptor;
599 interfaceForQILog = entry->mTargetInterface;
600 }
601 };
602
603 if (aAlreadyLocked) {
604 doLookup();
605 } else {
606 MutexAutoLock lock(mInterceptorMapMutex);
607 doLookup();
608 }
609
610 // (1a) A COM interceptor already exists for this interface, so all we need
611 // to do is run a QI on it.
612 if (unkInterceptor) {
613 // Technically we didn't actually execute a QI on the target interface, but
614 // for logging purposes we would like to record the fact that this interface
615 // was requested.
616 result.Log(mTarget.get(), interfaceForQILog);
617 result = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
618 ENSURE_HR_SUCCEEDED(result);
619 return result;
620 }
621
622 // (2) Obtain a new target interface.
623
624 // (2a) First, make sure that the target interface is available
625 // NB: We *MUST* query the correct interface! ICallEvents::Invoke casts its
626 // pvReceiver argument directly to the required interface! DO NOT assume
627 // that COM will use QI or upcast/downcast!
628 HRESULT hr;
629
630 STAUniquePtr<IUnknown> targetInterface;
631 IUnknown* rawTargetInterface = nullptr;
632 hr =
633 QueryInterfaceTarget(interceptorIid, (void**)&rawTargetInterface, result);
634 targetInterface.reset(rawTargetInterface);
635 result = hr;
636 result.Log(mTarget.get(), targetInterface.get());
637 MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE);
638 if (hr == E_NOINTERFACE) {
639 return hr;
640 }
641 ENSURE_HR_SUCCEEDED(hr);
642
643 // We *really* shouldn't be adding interceptors to proxies
644 MOZ_ASSERT(aIid != IID_IMarshal);
645
646 // (3) Create a new COM interceptor to that interface that delegates its
647 // IUnknown to |this|.
648
649 // Raise the refcount for stabilization purposes during aggregation
650 WeakReferenceSupport::StabilizeRefCount stabilizer(*this);
651
652 hr = CreateInterceptor(interceptorIid,
653 static_cast<WeakReferenceSupport*>(this),
654 getter_AddRefs(unkInterceptor));
655 ENSURE_HR_SUCCEEDED(hr);
656
657 // (4) Obtain the interceptor's ICallInterceptor interface and register our
658 // event sink.
659 RefPtr<ICallInterceptor> interceptor;
660 hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
661 (void**)getter_AddRefs(interceptor));
662 ENSURE_HR_SUCCEEDED(hr);
663
664 hr = interceptor->RegisterSink(mEventSink);
665 ENSURE_HR_SUCCEEDED(hr);
666
667 // (5) Now that we have this new COM interceptor, insert it into the map.
668 auto doInsertion = [&]() -> void {
669 // We might have raced with another thread, so first check that we don't
670 // already have an entry for this
671 MapEntry* entry = Lookup(interceptorIid);
672 if (entry && entry->mInterceptor) {
673 // Bug 1433046: Because of aggregation, the QI for |interceptor|
674 // AddRefed |this|, not |unkInterceptor|. Thus, releasing |unkInterceptor|
675 // will destroy the object. Before we do that, we must first release
676 // |interceptor|. Otherwise, |interceptor| would be invalidated when
677 // |unkInterceptor| is destroyed.
678 interceptor = nullptr;
679 unkInterceptor = entry->mInterceptor;
680 } else {
681 // MapEntry has a RefPtr to unkInterceptor, OTOH we must not touch the
682 // refcount for the target interface because we are just moving it into
683 // the map and its refcounting might not be thread-safe.
684 IUnknown* rawTargetInterface = targetInterface.release();
685 mInterceptorMap.AppendElement(
686 MapEntry(interceptorIid, unkInterceptor, rawTargetInterface));
687 }
688 };
689
690 if (aAlreadyLocked) {
691 doInsertion();
692 } else {
693 MutexAutoLock lock(mInterceptorMapMutex);
694 doInsertion();
695 }
696
697 hr = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
698 ENSURE_HR_SUCCEEDED(hr);
699 return hr;
700 }
701
702 HRESULT
QueryInterfaceTarget(REFIID aIid,void ** aOutput,TimeDuration * aOutDuration)703 Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput,
704 TimeDuration* aOutDuration) {
705 // NB: This QI needs to run on the main thread because the target object
706 // is probably Gecko code that is not thread-safe. Note that this main
707 // thread invocation is *synchronous*.
708 if (!NS_IsMainThread() && tlsCreatingStdMarshal.get()) {
709 mStdMarshalMutex.AssertCurrentThreadOwns();
710 // COM queries for special interfaces such as IFastRundown when creating a
711 // marshaler. We don't want these being dispatched to the main thread,
712 // since this would cause a deadlock on mStdMarshalMutex if the main
713 // thread is also querying for IMarshal. If we do need to respond to these
714 // special interfaces, this should be done before this point; e.g. in
715 // Interceptor::QueryInterface like we do for INoMarshal.
716 return E_NOINTERFACE;
717 }
718
719 if (mEventSink->IsInterfaceMaybeSupported(aIid) == E_NOINTERFACE) {
720 return E_NOINTERFACE;
721 }
722
723 MainThreadInvoker invoker;
724 HRESULT hr;
725 auto runOnMainThread = [&]() -> void {
726 MOZ_ASSERT(NS_IsMainThread());
727 hr = mTarget->QueryInterface(aIid, aOutput);
728 };
729 if (!invoker.Invoke(NS_NewRunnableFunction("Interceptor::QueryInterface",
730 runOnMainThread))) {
731 return E_FAIL;
732 }
733 if (aOutDuration) {
734 *aOutDuration = invoker.GetDuration();
735 }
736 return hr;
737 }
738
739 HRESULT
QueryInterface(REFIID riid,void ** ppv)740 Interceptor::QueryInterface(REFIID riid, void** ppv) {
741 if (riid == IID_INoMarshal) {
742 // This entire library is designed around marshaling, so there's no point
743 // propagating this QI request all over the place!
744 return E_NOINTERFACE;
745 }
746
747 return WeakReferenceSupport::QueryInterface(riid, ppv);
748 }
749
750 HRESULT
WeakRefQueryInterface(REFIID aIid,IUnknown ** aOutInterface)751 Interceptor::WeakRefQueryInterface(REFIID aIid, IUnknown** aOutInterface) {
752 if (aIid == IID_IStdMarshalInfo) {
753 detail::ReentrySentinel sentinel(this);
754
755 if (!sentinel.IsOutermost()) {
756 return E_NOINTERFACE;
757 }
758
759 // Do not indicate that this interface is available unless we actually
760 // support it. We'll check that by looking for a successful call to
761 // IInterceptorSink::GetHandler()
762 CLSID dummy;
763 if (FAILED(mEventSink->GetHandler(WrapNotNull(&dummy)))) {
764 return E_NOINTERFACE;
765 }
766
767 RefPtr<IStdMarshalInfo> std(this);
768 std.forget(aOutInterface);
769 return S_OK;
770 }
771
772 if (aIid == IID_IMarshal) {
773 MutexAutoLock lock(mStdMarshalMutex);
774
775 HRESULT hr;
776
777 if (!mStdMarshalUnk) {
778 MOZ_ASSERT(!tlsCreatingStdMarshal.get());
779 tlsCreatingStdMarshal.set(true);
780 if (XRE_IsContentProcess()) {
781 hr = FastMarshaler::Create(static_cast<IWeakReferenceSource*>(this),
782 getter_AddRefs(mStdMarshalUnk));
783 } else {
784 hr = ::CoGetStdMarshalEx(static_cast<IWeakReferenceSource*>(this),
785 SMEXF_SERVER, getter_AddRefs(mStdMarshalUnk));
786 }
787 tlsCreatingStdMarshal.set(false);
788
789 ENSURE_HR_SUCCEEDED(hr);
790 }
791
792 if (!mStdMarshal) {
793 hr = mStdMarshalUnk->QueryInterface(IID_IMarshal, (void**)&mStdMarshal);
794 ENSURE_HR_SUCCEEDED(hr);
795
796 // mStdMarshal is weak, so drop its refcount
797 mStdMarshal->Release();
798 }
799
800 RefPtr<IMarshal> marshal(this);
801 marshal.forget(aOutInterface);
802 return S_OK;
803 }
804
805 if (aIid == IID_IInterceptor) {
806 RefPtr<IInterceptor> intcpt(this);
807 intcpt.forget(aOutInterface);
808 return S_OK;
809 }
810
811 if (aIid == IID_IDispatch) {
812 STAUniquePtr<IDispatch> disp;
813 IDispatch* rawDisp = nullptr;
814 HRESULT hr = QueryInterfaceTarget(aIid, (void**)&rawDisp);
815 ENSURE_HR_SUCCEEDED(hr);
816
817 disp.reset(rawDisp);
818 return DispatchForwarder::Create(this, disp, aOutInterface);
819 }
820
821 return GetInterceptorForIID(aIid, (void**)aOutInterface, nullptr);
822 }
823
824 ULONG
AddRef()825 Interceptor::AddRef() { return WeakReferenceSupport::AddRef(); }
826
827 ULONG
Release()828 Interceptor::Release() { return WeakReferenceSupport::Release(); }
829
830 /* static */
DisconnectRemotesForTarget(IUnknown * aTarget)831 HRESULT Interceptor::DisconnectRemotesForTarget(IUnknown* aTarget) {
832 MOZ_ASSERT(aTarget);
833
834 detail::LiveSetAutoLock lock(GetLiveSet());
835
836 // It is not an error if the interceptor doesn't exist, so we return
837 // S_FALSE instead of an error in that case.
838 RefPtr<IWeakReference> existingWeak(GetLiveSet().Get(aTarget));
839 if (!existingWeak) {
840 return S_FALSE;
841 }
842
843 RefPtr<IWeakReferenceSource> existingStrong;
844 if (FAILED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) {
845 return S_FALSE;
846 }
847 // Since we now hold a strong ref on the interceptor, we may now release the
848 // lock.
849 lock.Unlock();
850
851 return ::CoDisconnectObject(existingStrong, 0);
852 }
853
854 } // namespace mscom
855 } // namespace mozilla
856