DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (19704452bd54)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840
/* -*- 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/Move.h"
#include "mozilla/mscom/DispatchForwarder.h"
#include "mozilla/mscom/FastMarshaler.h"
#include "mozilla/mscom/Interceptor.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 "mozilla/ThreadLocal.h"
#include "MainThreadUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Unused.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, 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;
  }

  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;
  }

  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