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