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/. */
9 #include "mozilla/mscom/MainThreadHandoff.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/Attributes.h"
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/ThreadLocal.h"
17 #include "mozilla/TimeStamp.h"
18 #include "mozilla/Unused.h"
19 #include "mozilla/mscom/AgileReference.h"
20 #include "mozilla/mscom/InterceptorLog.h"
21 #include "mozilla/mscom/Registration.h"
22 #include "mozilla/mscom/Utils.h"
23 #include "nsProxyRelease.h"
24 #include "nsThreadUtils.h"
26 using mozilla::DebugOnly
;
27 using mozilla::Unused
;
28 using mozilla::mscom::AgileReference
;
32 class MOZ_NON_TEMPORARY_CLASS InParamWalker
: private ICallFrameWalker
{
34 InParamWalker() : mPreHandoff(true) {}
36 void SetHandoffDone() {
38 mAgileRefsItr
= mAgileRefs
.begin();
41 HRESULT
Walk(ICallFrame
* aFrame
) {
47 return aFrame
->WalkFrame(CALLFRAME_WALK_IN
, this);
52 STDMETHODIMP
QueryInterface(REFIID aIid
, void** aOutInterface
) override
{
56 *aOutInterface
= nullptr;
58 if (aIid
== IID_IUnknown
|| aIid
== IID_ICallFrameWalker
) {
59 *aOutInterface
= static_cast<ICallFrameWalker
*>(this);
66 STDMETHODIMP_(ULONG
) AddRef() override
{ return 2; }
68 STDMETHODIMP_(ULONG
) Release() override
{ return 1; }
71 STDMETHODIMP
OnWalkInterface(REFIID aIid
, PVOID
* aInterface
, BOOL aIn
,
78 IUnknown
* origInterface
= static_cast<IUnknown
*>(*aInterface
);
85 mAgileRefs
.AppendElement(AgileReference(aIid
, origInterface
));
89 MOZ_ASSERT(mAgileRefsItr
!= mAgileRefs
.end());
90 if (mAgileRefsItr
== mAgileRefs
.end()) {
94 HRESULT hr
= mAgileRefsItr
->Resolve(aIid
, aInterface
);
95 MOZ_ASSERT(SUCCEEDED(hr
));
103 InParamWalker(const InParamWalker
&) = delete;
104 InParamWalker(InParamWalker
&&) = delete;
105 InParamWalker
& operator=(const InParamWalker
&) = delete;
106 InParamWalker
& operator=(InParamWalker
&&) = delete;
110 AutoTArray
<AgileReference
, 1> mAgileRefs
;
111 nsTArray
<AgileReference
>::iterator mAgileRefsItr
;
114 class HandoffRunnable
: public mozilla::Runnable
{
116 explicit HandoffRunnable(ICallFrame
* aCallFrame
, IUnknown
* aTargetInterface
)
117 : Runnable("HandoffRunnable"),
118 mCallFrame(aCallFrame
),
119 mTargetInterface(aTargetInterface
),
120 mResult(E_UNEXPECTED
) {
121 DebugOnly
<HRESULT
> hr
= mInParamWalker
.Walk(aCallFrame
);
122 MOZ_ASSERT(SUCCEEDED(hr
));
125 NS_IMETHOD
Run() override
{
126 mInParamWalker
.SetHandoffDone();
127 // We declare hr a DebugOnly because if mInParamWalker.Walk() fails, then
128 // mCallFrame->Invoke will fail anyway.
129 DebugOnly
<HRESULT
> hr
= mInParamWalker
.Walk(mCallFrame
);
130 MOZ_ASSERT(SUCCEEDED(hr
));
131 mResult
= mCallFrame
->Invoke(mTargetInterface
);
135 HRESULT
GetResult() const { return mResult
; }
138 ICallFrame
* mCallFrame
;
139 InParamWalker mInParamWalker
;
140 IUnknown
* mTargetInterface
;
144 class MOZ_RAII SavedCallFrame final
{
146 explicit SavedCallFrame(mozilla::NotNull
<ICallFrame
*> aFrame
)
147 : mCallFrame(aFrame
) {
148 static const bool sIsInit
= tlsFrame
.init();
150 MOZ_ASSERT(!tlsFrame
.get());
156 MOZ_ASSERT(tlsFrame
.get());
157 tlsFrame
.set(nullptr);
160 HRESULT
GetIidAndMethod(mozilla::NotNull
<IID
*> aIid
,
161 mozilla::NotNull
<ULONG
*> aMethod
) const {
162 return mCallFrame
->GetIIDAndMethod(aIid
, aMethod
);
165 static const SavedCallFrame
& Get() {
166 SavedCallFrame
* saved
= tlsFrame
.get();
172 SavedCallFrame(const SavedCallFrame
&) = delete;
173 SavedCallFrame(SavedCallFrame
&&) = delete;
174 SavedCallFrame
& operator=(const SavedCallFrame
&) = delete;
175 SavedCallFrame
& operator=(SavedCallFrame
&&) = delete;
178 ICallFrame
* mCallFrame
;
181 static MOZ_THREAD_LOCAL(SavedCallFrame
*) tlsFrame
;
184 MOZ_THREAD_LOCAL(SavedCallFrame
*) SavedCallFrame::tlsFrame
;
186 class MOZ_RAII LogEvent final
{
188 LogEvent() : mCallStart(mozilla::TimeStamp::Now()) {}
191 if (mCapturedFrame
.IsEmpty()) {
195 mozilla::TimeStamp
callEnd(mozilla::TimeStamp::Now());
196 mozilla::TimeDuration
totalTime(callEnd
- mCallStart
);
197 mozilla::TimeDuration
overhead(totalTime
- mGeckoDuration
-
200 mozilla::mscom::InterceptorLog::Event(mCapturedFrame
, overhead
,
204 void CaptureFrame(ICallFrame
* aFrame
, IUnknown
* aTarget
,
205 const mozilla::TimeDuration
& aGeckoDuration
) {
206 mozilla::TimeStamp
captureStart(mozilla::TimeStamp::Now());
208 mozilla::mscom::InterceptorLog::CaptureFrame(aFrame
, aTarget
,
210 mGeckoDuration
= aGeckoDuration
;
212 mozilla::TimeStamp
captureEnd(mozilla::TimeStamp::Now());
214 // Make sure that the time we spent in CaptureFrame isn't charged against
216 mCaptureDuration
= captureEnd
- captureStart
;
219 LogEvent(const LogEvent
&) = delete;
220 LogEvent(LogEvent
&&) = delete;
221 LogEvent
& operator=(const LogEvent
&) = delete;
222 LogEvent
& operator=(LogEvent
&&) = delete;
225 mozilla::TimeStamp mCallStart
;
226 mozilla::TimeDuration mGeckoDuration
;
227 mozilla::TimeDuration mCaptureDuration
;
228 nsAutoCString mCapturedFrame
;
231 } // anonymous namespace
237 HRESULT
MainThreadHandoff::Create(IHandlerProvider
* aHandlerProvider
,
238 IInterceptorSink
** aOutput
) {
239 RefPtr
<MainThreadHandoff
> handoff(new MainThreadHandoff(aHandlerProvider
));
240 return handoff
->QueryInterface(IID_IInterceptorSink
, (void**)aOutput
);
243 MainThreadHandoff::MainThreadHandoff(IHandlerProvider
* aHandlerProvider
)
244 : mRefCnt(0), mHandlerProvider(aHandlerProvider
) {}
246 MainThreadHandoff::~MainThreadHandoff() { MOZ_ASSERT(NS_IsMainThread()); }
249 MainThreadHandoff::QueryInterface(REFIID riid
, void** ppv
) {
250 IUnknown
* punk
= nullptr;
255 if (riid
== IID_IUnknown
|| riid
== IID_ICallFrameEvents
||
256 riid
== IID_IInterceptorSink
|| riid
== IID_IMainThreadHandoff
) {
257 punk
= static_cast<IMainThreadHandoff
*>(this);
258 } else if (riid
== IID_ICallFrameWalker
) {
259 punk
= static_cast<ICallFrameWalker
*>(this);
264 return E_NOINTERFACE
;
272 MainThreadHandoff::AddRef() {
273 return (ULONG
)InterlockedIncrement((LONG
*)&mRefCnt
);
277 MainThreadHandoff::Release() {
278 ULONG newRefCnt
= (ULONG
)InterlockedDecrement((LONG
*)&mRefCnt
);
279 if (newRefCnt
== 0) {
280 // It is possible for the last Release() call to happen off-main-thread.
281 // If so, we need to dispatch an event to delete ourselves.
282 if (NS_IsMainThread()) {
285 // We need to delete this object on the main thread, but we aren't on the
286 // main thread right now, so we send a reference to ourselves to the main
287 // thread to be re-released there.
288 RefPtr
<MainThreadHandoff
> self
= this;
289 NS_ReleaseOnMainThread("MainThreadHandoff", self
.forget());
296 MainThreadHandoff::FixIServiceProvider(ICallFrame
* aFrame
) {
299 CALLFRAMEPARAMINFO iidOutParamInfo
;
300 HRESULT hr
= aFrame
->GetParamInfo(1, &iidOutParamInfo
);
306 hr
= aFrame
->GetParam(2, &varIfaceOut
);
311 MOZ_ASSERT(varIfaceOut
.vt
== (VT_UNKNOWN
| VT_BYREF
));
312 if (varIfaceOut
.vt
!= (VT_UNKNOWN
| VT_BYREF
)) {
313 return DISP_E_BADVARTYPE
;
317 reinterpret_cast<IID
**>(static_cast<BYTE
*>(aFrame
->GetStackLocation()) +
318 iidOutParamInfo
.stackOffset
);
320 return OnWalkInterface(**iidOutParam
,
321 reinterpret_cast<void**>(varIfaceOut
.ppunkVal
), FALSE
,
326 MainThreadHandoff::OnCall(ICallFrame
* aFrame
) {
329 // (1) Get info about the method call
333 hr
= aFrame
->GetIIDAndMethod(&iid
, &method
);
338 RefPtr
<IInterceptor
> interceptor
;
339 hr
= mInterceptor
->Resolve(IID_IInterceptor
,
340 (void**)getter_AddRefs(interceptor
));
345 InterceptorTargetPtr
<IUnknown
> targetInterface
;
346 hr
= interceptor
->GetTargetForIID(iid
, targetInterface
);
351 // (2) Execute the method call synchronously on the main thread
352 RefPtr
<HandoffRunnable
> handoffInfo(
353 new HandoffRunnable(aFrame
, targetInterface
.get()));
354 MainThreadInvoker invoker
;
355 if (!invoker
.Invoke(do_AddRef(handoffInfo
))) {
359 hr
= handoffInfo
->GetResult();
360 MOZ_ASSERT(SUCCEEDED(hr
));
365 // (3) Capture *before* wrapping outputs so that the log will contain pointers
366 // to the true target interface, not the wrapped ones.
367 logEvent
.CaptureFrame(aFrame
, targetInterface
.get(), invoker
.GetDuration());
369 // (4) Scan the function call for outparams that contain interface pointers.
370 // Those will need to be wrapped with MainThreadHandoff so that they too will
371 // be exeuted on the main thread.
373 hr
= aFrame
->GetReturnValue();
375 // If the call resulted in an error then there's not going to be anything
376 // that needs to be wrapped.
380 if (iid
== IID_IServiceProvider
) {
381 // The only possible method index for IID_IServiceProvider is for
382 // QueryService at index 3; its other methods are inherited from IUnknown
383 // and are not processed here.
384 MOZ_ASSERT(method
== 3);
385 // (5) If our interface is IServiceProvider, we need to manually ensure
386 // that the correct IID is provided for the interface outparam in
387 // IServiceProvider::QueryService.
388 hr
= FixIServiceProvider(aFrame
);
392 } else if (const ArrayData
* arrayData
= FindArrayData(iid
, method
)) {
393 // (6) Unfortunately ICallFrame::WalkFrame does not correctly handle array
394 // outparams. Instead, we find out whether anybody has called
395 // mscom::RegisterArrayData to supply array parameter information and use it
396 // if available. This is a terrible hack, but it works for the short term.
397 // In the longer term we want to be able to use COM proxy/stub metadata to
398 // resolve array information for us.
399 hr
= FixArrayElements(aFrame
, *arrayData
);
404 SavedCallFrame
savedFrame(WrapNotNull(aFrame
));
406 // (7) Scan the outputs looking for any outparam interfaces that need
407 // wrapping. NB: WalkFrame does not correctly handle array outparams. It
408 // processes the first element of an array but not the remaining elements
410 hr
= aFrame
->WalkFrame(CALLFRAME_WALK_OUT
, this);
419 static PVOID
ResolveArrayPtr(VARIANT
& aVariant
) {
420 if (!(aVariant
.vt
& VT_BYREF
)) {
423 return aVariant
.byref
;
426 static PVOID
* ResolveInterfacePtr(PVOID aArrayPtr
, VARTYPE aVartype
,
428 if (aVartype
!= (VT_VARIANT
| VT_BYREF
)) {
429 IUnknown
** ifaceArray
= reinterpret_cast<IUnknown
**>(aArrayPtr
);
430 return reinterpret_cast<PVOID
*>(&ifaceArray
[aIndex
]);
432 VARIANT
* variantArray
= reinterpret_cast<VARIANT
*>(aArrayPtr
);
433 VARIANT
& element
= variantArray
[aIndex
];
434 return &element
.byref
;
438 MainThreadHandoff::FixArrayElements(ICallFrame
* aFrame
,
439 const ArrayData
& aArrayData
) {
440 // Extract the array length
442 VariantInit(¶mVal
);
443 HRESULT hr
= aFrame
->GetParam(aArrayData
.mLengthParamIndex
, ¶mVal
);
444 MOZ_ASSERT(SUCCEEDED(hr
) && (paramVal
.vt
== (VT_I4
| VT_BYREF
) ||
445 paramVal
.vt
== (VT_UI4
| VT_BYREF
)));
446 if (FAILED(hr
) || (paramVal
.vt
!= (VT_I4
| VT_BYREF
) &&
447 paramVal
.vt
!= (VT_UI4
| VT_BYREF
))) {
451 const LONG arrayLength
= *(paramVal
.plVal
);
457 // Extract the array parameter
458 VariantInit(¶mVal
);
459 PVOID arrayPtr
= nullptr;
460 hr
= aFrame
->GetParam(aArrayData
.mArrayParamIndex
, ¶mVal
);
461 if (hr
== DISP_E_BADVARTYPE
) {
462 // ICallFrame::GetParam is not able to coerce the param into a VARIANT.
463 // That's ok, we can try to do it ourselves.
464 CALLFRAMEPARAMINFO paramInfo
;
465 hr
= aFrame
->GetParamInfo(aArrayData
.mArrayParamIndex
, ¶mInfo
);
469 PVOID stackBase
= aFrame
->GetStackLocation();
470 if (aArrayData
.mFlag
== ArrayData::Flag::eAllocatedByServer
) {
471 // In order for the server to allocate the array's buffer and store it in
472 // an outparam, the parameter must be typed as Type***. Since the base
473 // of the array is Type*, we must dereference twice.
474 arrayPtr
= **reinterpret_cast<PVOID
**>(
475 reinterpret_cast<PBYTE
>(stackBase
) + paramInfo
.stackOffset
);
477 // We dereference because we need to obtain the value of a parameter
478 // from a stack offset. This pointer is the base of the array.
479 arrayPtr
= *reinterpret_cast<PVOID
*>(reinterpret_cast<PBYTE
>(stackBase
) +
480 paramInfo
.stackOffset
);
482 } else if (FAILED(hr
)) {
485 arrayPtr
= ResolveArrayPtr(paramVal
);
488 MOZ_ASSERT(arrayPtr
);
490 return DISP_E_BADVARTYPE
;
493 // We walk the elements of the array and invoke OnWalkInterface to wrap each
494 // one, just as ICallFrame::WalkFrame would do.
495 for (LONG index
= 0; index
< arrayLength
; ++index
) {
496 hr
= OnWalkInterface(aArrayData
.mArrayParamIid
,
497 ResolveInterfacePtr(arrayPtr
, paramVal
.vt
, index
),
507 MainThreadHandoff::SetInterceptor(IWeakReference
* aInterceptor
) {
508 mInterceptor
= aInterceptor
;
513 MainThreadHandoff::GetHandler(NotNull
<CLSID
*> aHandlerClsid
) {
514 if (!mHandlerProvider
) {
518 return mHandlerProvider
->GetHandler(aHandlerClsid
);
522 MainThreadHandoff::GetHandlerPayloadSize(NotNull
<IInterceptor
*> aInterceptor
,
523 NotNull
<DWORD
*> aOutPayloadSize
) {
524 if (!mHandlerProvider
) {
527 return mHandlerProvider
->GetHandlerPayloadSize(aInterceptor
, aOutPayloadSize
);
531 MainThreadHandoff::WriteHandlerPayload(NotNull
<IInterceptor
*> aInterceptor
,
532 NotNull
<IStream
*> aStream
) {
533 if (!mHandlerProvider
) {
536 return mHandlerProvider
->WriteHandlerPayload(aInterceptor
, aStream
);
540 MainThreadHandoff::MarshalAs(REFIID aIid
) {
541 if (!mHandlerProvider
) {
544 return mHandlerProvider
->MarshalAs(aIid
);
548 MainThreadHandoff::DisconnectHandlerRemotes() {
549 if (!mHandlerProvider
) {
553 return mHandlerProvider
->DisconnectHandlerRemotes();
557 MainThreadHandoff::IsInterfaceMaybeSupported(REFIID aIid
) {
558 if (!mHandlerProvider
) {
561 return mHandlerProvider
->IsInterfaceMaybeSupported(aIid
);
565 MainThreadHandoff::OnWalkInterface(REFIID aIid
, PVOID
* aInterface
,
566 BOOL aIsInParam
, BOOL aIsOutParam
) {
567 MOZ_ASSERT(aInterface
&& aIsOutParam
);
568 if (!aInterface
|| !aIsOutParam
) {
572 // Adopt aInterface for the time being. We can't touch its refcount off
573 // the main thread, so we'll use STAUniquePtr so that we can safely
574 // Release() it if necessary.
575 STAUniquePtr
<IUnknown
> origInterface(static_cast<IUnknown
*>(*aInterface
));
576 *aInterface
= nullptr;
578 if (!origInterface
) {
583 // First make sure that aInterface isn't a proxy - we don't want to wrap
585 if (IsProxy(origInterface
.get())) {
586 *aInterface
= origInterface
.release();
590 RefPtr
<IInterceptor
> interceptor
;
591 HRESULT hr
= mInterceptor
->Resolve(IID_IInterceptor
,
592 (void**)getter_AddRefs(interceptor
));
593 MOZ_ASSERT(SUCCEEDED(hr
));
598 // Now make sure that origInterface isn't referring to the same IUnknown
599 // as an interface that we are already managing. We can determine this by
600 // querying (NOT casting!) both objects for IUnknown and then comparing the
601 // resulting pointers.
602 InterceptorTargetPtr
<IUnknown
> existingTarget
;
603 hr
= interceptor
->GetTargetForIID(aIid
, existingTarget
);
605 // We'll start by checking the raw pointers. If they are equal, then the
606 // objects are equal. OTOH, if they differ, we must compare their
607 // IUnknown pointers to know for sure.
608 bool areTargetsEqual
= existingTarget
.get() == origInterface
.get();
610 if (!areTargetsEqual
) {
611 // This check must be done on the main thread
612 auto checkFn
= [&existingTarget
, &origInterface
,
613 &areTargetsEqual
]() -> void {
614 RefPtr
<IUnknown
> unkExisting
;
615 HRESULT hrExisting
= existingTarget
->QueryInterface(
616 IID_IUnknown
, (void**)getter_AddRefs(unkExisting
));
617 RefPtr
<IUnknown
> unkNew
;
618 HRESULT hrNew
= origInterface
->QueryInterface(
619 IID_IUnknown
, (void**)getter_AddRefs(unkNew
));
621 SUCCEEDED(hrExisting
) && SUCCEEDED(hrNew
) && unkExisting
== unkNew
;
624 MainThreadInvoker invoker
;
625 invoker
.Invoke(NS_NewRunnableFunction(
626 "MainThreadHandoff::OnWalkInterface", checkFn
));
629 if (areTargetsEqual
) {
630 // The existing interface and the new interface both belong to the same
631 // target object. Let's just use the existing one.
632 void* intercepted
= nullptr;
633 hr
= interceptor
->GetInterceptorForIID(aIid
, &intercepted
);
634 MOZ_ASSERT(SUCCEEDED(hr
));
638 *aInterface
= intercepted
;
643 IID effectiveIid
= aIid
;
645 RefPtr
<IHandlerProvider
> payload
;
646 if (mHandlerProvider
) {
647 if (aIid
== IID_IUnknown
) {
648 const SavedCallFrame
& curFrame
= SavedCallFrame::Get();
652 hr
= curFrame
.GetIidAndMethod(WrapNotNull(&callIid
),
653 WrapNotNull(&callMethod
));
659 mHandlerProvider
->GetEffectiveOutParamIid(callIid
, callMethod
);
662 hr
= mHandlerProvider
->NewInstance(
663 effectiveIid
, ToInterceptorTargetPtr(origInterface
),
664 WrapNotNull((IHandlerProvider
**)getter_AddRefs(payload
)));
665 MOZ_ASSERT(SUCCEEDED(hr
));
671 // Now create a new MainThreadHandoff wrapper...
672 RefPtr
<IInterceptorSink
> handoff
;
673 hr
= MainThreadHandoff::Create(payload
, getter_AddRefs(handoff
));
674 MOZ_ASSERT(SUCCEEDED(hr
));
679 REFIID interceptorIid
=
680 payload
? payload
->MarshalAs(effectiveIid
) : effectiveIid
;
682 RefPtr
<IUnknown
> wrapped
;
683 hr
= Interceptor::Create(std::move(origInterface
), handoff
, interceptorIid
,
684 getter_AddRefs(wrapped
));
685 MOZ_ASSERT(SUCCEEDED(hr
));
690 // And replace the original interface pointer with the wrapped one.
691 wrapped
.forget(reinterpret_cast<IUnknown
**>(aInterface
));
697 } // namespace mozilla