/* -*- 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/. */

#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
#define NS_WINDOWS_DLL_INTERCEPTOR_H_

#include <wchar.h>
#include <windows.h>
#include <winternl.h>

#include <utility>

#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/NativeNt.h"

#include "mozilla/Types.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "mozilla/interceptor/MMPolicies.h"
#include "mozilla/interceptor/PatcherDetour.h"
#include "mozilla/interceptor/PatcherNopSpace.h"
#include "mozilla/interceptor/VMSharingPolicies.h"
#include "nsWindowsHelpers.h"

/*
 * Simple function interception.
 *
 * We have two separate mechanisms for intercepting a function: We can use the
 * built-in nop space, if it exists, or we can create a detour.
 *
 * Using the built-in nop space works as follows: On x86-32, DLL functions
 * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
 * NOP instructions.
 *
 * When we detect a function with this prelude, we do the following:
 *
 * 1. Write a long jump to our interceptor function into the five bytes of NOPs
 *    before the function.
 *
 * 2. Write a short jump -5 into the two-byte nop at the beginning of the
 *    function.
 *
 * This mechanism is nice because it's thread-safe.  It's even safe to do if
 * another thread is currently running the function we're modifying!
 *
 * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
 * but not the long jump, so re-intercepting the same function won't work,
 * because its prelude won't match.
 *
 *
 * Unfortunately nop space patching doesn't work on functions which don't have
 * this magic prelude (and in particular, x86-64 never has the prelude).  So
 * when we can't use the built-in nop space, we fall back to using a detour,
 * which works as follows:
 *
 * 1. Save first N bytes of OrigFunction to trampoline, where N is a
 *    number of bytes >= 5 that are instruction aligned.
 *
 * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
 *    function.
 *
 * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
 *    continue original program flow.
 *
 * 4. Hook function needs to call the trampoline during its execution,
 *    to invoke the original function (so address of trampoline is
 *    returned).
 *
 * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
 * patched again to jump directly to the trampoline instead of going through
 * the hook function. As such, re-intercepting the same function won't work, as
 * jump instructions are not supported.
 *
 * Note that this is not thread-safe.  Sad day.
 *
 */

#if defined(_M_IX86) && defined(__clang__) && __has_declspec_attribute(guard)
// On x86, nop-space patches return to the second instruction of their target.
// This is a deliberate violation of Control Flow Guard, so disable the check.
#  define INTERCEPTOR_DISABLE_CFGUARD __declspec(guard(nocf))
#else
#  define INTERCEPTOR_DISABLE_CFGUARD /* nothing */
#endif

namespace mozilla {
namespace interceptor {

template <typename T>
struct OriginalFunctionPtrTraits;

template <typename R, typename... Args>
struct OriginalFunctionPtrTraits<R (*)(Args...)> {
  using ReturnType = R;
};

#if defined(_M_IX86)
template <typename R, typename... Args>
struct OriginalFunctionPtrTraits<R(__stdcall*)(Args...)> {
  using ReturnType = R;
};

template <typename R, typename... Args>
struct OriginalFunctionPtrTraits<R(__fastcall*)(Args...)> {
  using ReturnType = R;
};
#endif  // defined(_M_IX86)

template <typename InterceptorT, typename FuncPtrT>
class FuncHook final {
 public:
  using ThisType = FuncHook<InterceptorT, FuncPtrT>;
  using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;

  constexpr FuncHook() : mOrigFunc(nullptr), mInitOnce(INIT_ONCE_STATIC_INIT) {}

  ~FuncHook() = default;

  bool Set(InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
    LPVOID addHookOk = nullptr;
    InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, false);

    return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
                                 &addHookOk) &&
           addHookOk;
  }

  bool SetDetour(InterceptorT& aInterceptor, const char* aName,
                 FuncPtrT aHookDest) {
    LPVOID addHookOk = nullptr;
    InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, true);

    return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
                                 &addHookOk) &&
           addHookOk;
  }

  explicit operator bool() const { return !!mOrigFunc; }

  template <typename... ArgsType>
  INTERCEPTOR_DISABLE_CFGUARD ReturnType operator()(ArgsType&&... aArgs) const {
    return mOrigFunc(std::forward<ArgsType>(aArgs)...);
  }

  FuncPtrT GetStub() const { return mOrigFunc; }

  // One-time init stuff cannot be moved or copied
  FuncHook(const FuncHook&) = delete;
  FuncHook(FuncHook&&) = delete;
  FuncHook& operator=(const FuncHook&) = delete;
  FuncHook& operator=(FuncHook&& aOther) = delete;

 private:
  struct MOZ_RAII InitOnceContext final {
    InitOnceContext(ThisType* aHook, InterceptorT* aInterceptor,
                    const char* aName, FuncPtrT aHookDest, bool aForceDetour)
        : mHook(aHook),
          mInterceptor(aInterceptor),
          mName(aName),
          mHookDest(reinterpret_cast<void*>(aHookDest)),
          mForceDetour(aForceDetour) {}

    ThisType* mHook;
    InterceptorT* mInterceptor;
    const char* mName;
    void* mHookDest;
    bool mForceDetour;
  };

 private:
  bool Apply(InterceptorT* aInterceptor, const char* aName, void* aHookDest) {
    return aInterceptor->AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
                                 reinterpret_cast<void**>(&mOrigFunc));
  }

  bool ApplyDetour(InterceptorT* aInterceptor, const char* aName,
                   void* aHookDest) {
    return aInterceptor->AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
                                   reinterpret_cast<void**>(&mOrigFunc));
  }

  static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
                                        PVOID* aOutContext) {
    MOZ_ASSERT(aOutContext);

    bool result;
    auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
    if (ctx->mForceDetour) {
      result = ctx->mHook->ApplyDetour(ctx->mInterceptor, ctx->mName,
                                       ctx->mHookDest);
    } else {
      result = ctx->mHook->Apply(ctx->mInterceptor, ctx->mName, ctx->mHookDest);
    }

    *aOutContext =
        result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
               : nullptr;
    return TRUE;
  }

 private:
  FuncPtrT mOrigFunc;
  INIT_ONCE mInitOnce;
};

template <typename InterceptorT, typename FuncPtrT>
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FuncHookCrossProcess final {
 public:
  using ThisType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
  using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;

#if defined(DEBUG)
  FuncHookCrossProcess() {}
#endif  // defined(DEBUG)

  bool Set(nt::CrossExecTransferManager& aTransferMgr,
           InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
    FuncPtrT origFunc;
    if (!aInterceptor.AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
                              reinterpret_cast<void**>(&origFunc))) {
      return false;
    }

    return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
  }

  bool SetDetour(nt::CrossExecTransferManager& aTransferMgr,
                 InterceptorT& aInterceptor, const char* aName,
                 FuncPtrT aHookDest) {
    FuncPtrT origFunc;
    if (!aInterceptor.AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
                                reinterpret_cast<void**>(&origFunc))) {
      return false;
    }

    return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
  }

  explicit operator bool() const { return !!mOrigFunc; }

  /**
   * NB: This operator is only meaningful when invoked in the target process!
   */
  template <typename... ArgsType>
  ReturnType operator()(ArgsType&&... aArgs) const {
    return mOrigFunc(std::forward<ArgsType>(aArgs)...);
  }

#if defined(DEBUG)
  FuncHookCrossProcess(const FuncHookCrossProcess&) = delete;
  FuncHookCrossProcess(FuncHookCrossProcess&&) = delete;
  FuncHookCrossProcess& operator=(const FuncHookCrossProcess&) = delete;
  FuncHookCrossProcess& operator=(FuncHookCrossProcess&& aOther) = delete;
#endif  // defined(DEBUG)

 private:
  bool CopyStubToChildProcess(nt::CrossExecTransferManager& aTransferMgr,
                              InterceptorT& aInterceptor, FuncPtrT aStub) {
    LauncherVoidResult writeResult =
        aTransferMgr.Transfer(&mOrigFunc, &aStub, sizeof(FuncPtrT));
    if (writeResult.isErr()) {
#ifdef MOZ_USE_LAUNCHER_ERROR
      const mozilla::WindowsError& err = writeResult.inspectErr().mError;
#else
      const mozilla::WindowsError& err = writeResult.inspectErr();
#endif
      aInterceptor.SetLastDetourError(FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR,
                                      err.AsHResult());
      return false;
    }
    return true;
  }

 private:
  FuncPtrT mOrigFunc;
};

template <typename MMPolicyT, typename InterceptorT>
struct TypeResolver;

template <typename InterceptorT>
struct TypeResolver<mozilla::interceptor::MMPolicyInProcess, InterceptorT> {
  template <typename FuncPtrT>
  using FuncHookType = FuncHook<InterceptorT, FuncPtrT>;
};

template <typename InterceptorT>
struct TypeResolver<mozilla::interceptor::MMPolicyOutOfProcess, InterceptorT> {
  template <typename FuncPtrT>
  using FuncHookType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
};

template <typename VMPolicy = mozilla::interceptor::VMSharingPolicyShared>
class WindowsDllInterceptor final
    : public TypeResolver<typename VMPolicy::MMPolicyT,
                          WindowsDllInterceptor<VMPolicy>> {
  typedef WindowsDllInterceptor<VMPolicy> ThisType;

  interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
#if defined(_M_IX86)
  interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT>
      mNopSpacePatcher;
#endif  // defined(_M_IX86)

  HMODULE mModule;

 public:
  template <typename... Args>
  explicit WindowsDllInterceptor(Args&&... aArgs)
      : mDetourPatcher(std::forward<Args>(aArgs)...)
#if defined(_M_IX86)
        ,
        mNopSpacePatcher(std::forward<Args>(aArgs)...)
#endif  // defined(_M_IX86)
        ,
        mModule(nullptr) {
  }

  WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
  WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
  WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
  WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;

  ~WindowsDllInterceptor() { Clear(); }

  template <size_t N>
  void Init(const char (&aModuleName)[N]) {
    wchar_t moduleName[N];

    for (size_t i = 0; i < N; ++i) {
      MOZ_ASSERT(!(aModuleName[i] & 0x80),
                 "Use wide-character overload for non-ASCII module names");
      moduleName[i] = aModuleName[i];
    }

    Init(moduleName);
  }

  void Init(const wchar_t* aModuleName) {
    if (mModule) {
      return;
    }

    mModule = ::LoadLibraryW(aModuleName);
  }

  /** Force a specific configuration for testing purposes. NOT to be used in
      production code! **/
  void TestOnlyDetourInit(const wchar_t* aModuleName, DetourFlags aFlags) {
    Init(aModuleName);
    mDetourPatcher.Init(aFlags);
  }

  void Clear() {
    if (!mModule) {
      return;
    }

#if defined(_M_IX86)
    mNopSpacePatcher.Clear();
#endif  // defined(_M_IX86)
    mDetourPatcher.Clear();

    // NB: We intentionally leak mModule
  }

#if defined(NIGHTLY_BUILD)
  const Maybe<DetourError>& GetLastDetourError() const {
    return mDetourPatcher.GetLastDetourError();
  }
#endif  // defined(NIGHTLY_BUILD)
  template <typename... Args>
  void SetLastDetourError(Args&&... aArgs) {
    return mDetourPatcher.SetLastDetourError(std::forward<Args>(aArgs)...);
  }

  constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
    return WindowsDllDetourPatcherPrimitive<
        typename VMPolicy::MMPolicyT>::GetWorstCaseRequiredBytesToPatch();
  }

 private:
  /**
   * Hook/detour the method aName from the DLL we set in Init so that it calls
   * aHookDest instead.  Returns the original method pointer in aOrigFunc
   * and returns true if successful.
   *
   * IMPORTANT: If you use this method, please add your case to the
   * TestDllInterceptor in order to detect future failures.  Even if this
   * succeeds now, updates to the hooked DLL could cause it to fail in
   * the future.
   */
  bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
    // Use a nop space patch if possible, otherwise fall back to a detour.
    // This should be the preferred method for adding hooks.
    if (!mModule) {
      mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
      return false;
    }

    if (!mDetourPatcher.IsPageAccessible(
            nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
      mDetourPatcher.SetLastDetourError(
          DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
      return false;
    }

    FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
    if (!proc) {
      mDetourPatcher.SetLastDetourError(
          DetourResultCode::INTERCEPTOR_PROC_NULL);
      return false;
    }

    if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
      mDetourPatcher.SetLastDetourError(
          DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
      return false;
    }

#if defined(_M_IX86)
    if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
      return true;
    }
#endif  // defined(_M_IX86)

    return AddDetour(proc, aHookDest, aOrigFunc);
  }

  /**
   * Detour the method aName from the DLL we set in Init so that it calls
   * aHookDest instead.  Returns the original method pointer in aOrigFunc
   * and returns true if successful.
   *
   * IMPORTANT: If you use this method, please add your case to the
   * TestDllInterceptor in order to detect future failures.  Even if this
   * succeeds now, updates to the detoured DLL could cause it to fail in
   * the future.
   */
  bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
    // Generally, code should not call this method directly. Use AddHook unless
    // there is a specific need to avoid nop space patches.
    if (!mModule) {
      mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
      return false;
    }

    if (!mDetourPatcher.IsPageAccessible(
            nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
      mDetourPatcher.SetLastDetourError(
          DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
      return false;
    }

    FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
    if (!proc) {
      mDetourPatcher.SetLastDetourError(
          DetourResultCode::INTERCEPTOR_PROC_NULL);
      return false;
    }

    if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
      mDetourPatcher.SetLastDetourError(
          DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
      return false;
    }

    return AddDetour(proc, aHookDest, aOrigFunc);
  }

  bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc) {
    MOZ_ASSERT(mModule && aProc);

    if (!mDetourPatcher.Initialized()) {
      DetourFlags flags = DetourFlags::eDefault;
#if defined(_M_X64)
      // NTDLL hooks should attempt to use a 10-byte patch because some
      // injected DLLs do the same and interfere with our stuff.
      bool needs10BytePatch = (mModule == ::GetModuleHandleW(L"ntdll.dll"));

      bool isWin8Or81 = IsWin8OrLater() && (!IsWin10OrLater());
      bool isWin8 = IsWin8OrLater() && (!IsWin8Point1OrLater());

      bool isKernel32Dll = (mModule == ::GetModuleHandleW(L"kernel32.dll"));

      bool isDuplicateHandle = (reinterpret_cast<void*>(aProc) ==
                                reinterpret_cast<void*>(&::DuplicateHandle));

      // CloseHandle on Windows 8/8.1 only accomodates 10-byte patches.
      needs10BytePatch |= isWin8Or81 && isKernel32Dll &&
                          (reinterpret_cast<void*>(aProc) ==
                           reinterpret_cast<void*>(&CloseHandle));

      // CreateFileA and DuplicateHandle on Windows 8 require 10-byte patches.
      needs10BytePatch |= isWin8 && isKernel32Dll &&
                          ((reinterpret_cast<void*>(aProc) ==
                            reinterpret_cast<void*>(&::CreateFileA)) ||
                           isDuplicateHandle);

      if (needs10BytePatch) {
        flags |= DetourFlags::eEnable10BytePatch;
      }

      if (isWin8 && isDuplicateHandle) {
        // Because we can't detour Win8's KERNELBASE!DuplicateHandle,
        // we detour kernel32!DuplicateHandle (See bug 1659398).
        flags |= DetourFlags::eDontResolveRedirection;
      }
#endif  // defined(_M_X64)

      mDetourPatcher.Init(flags);
    }

    return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
  }

 private:
  template <typename InterceptorT, typename FuncPtrT>
  friend class FuncHook;

  template <typename InterceptorT, typename FuncPtrT>
  friend class FuncHookCrossProcess;
};

/**
 * IAT patching is intended for use when we only want to intercept a function
 * call originating from a specific module.
 */
class WindowsIATPatcher final {
 public:
  template <typename FuncPtrT>
  using FuncHookType = FuncHook<WindowsIATPatcher, FuncPtrT>;

 private:
  static bool CheckASCII(const char* aInStr) {
    while (*aInStr) {
      if (*aInStr & 0x80) {
        return false;
      }
      ++aInStr;
    }
    return true;
  }

  static bool AddHook(HMODULE aFromModule, const char* aToModuleName,
                      const char* aTargetFnName, void* aHookDest,
                      Atomic<void*>* aOutOrigFunc) {
    if (!aFromModule || !aToModuleName || !aTargetFnName || !aOutOrigFunc) {
      return false;
    }

    // PE Spec requires ASCII names for imported module names
    const bool isModuleNameAscii = CheckASCII(aToModuleName);
    MOZ_ASSERT(isModuleNameAscii);
    if (!isModuleNameAscii) {
      return false;
    }

    // PE Spec requires ASCII names for imported function names
    const bool isTargetFnNameAscii = CheckASCII(aTargetFnName);
    MOZ_ASSERT(isTargetFnNameAscii);
    if (!isTargetFnNameAscii) {
      return false;
    }

    nt::PEHeaders headers(aFromModule);
    if (!headers) {
      return false;
    }

    PIMAGE_IMPORT_DESCRIPTOR impDesc =
        headers.GetImportDescriptor(aToModuleName);
    if (!nt::PEHeaders::IsValid(impDesc)) {
      // Either aFromModule does not import aToModuleName at load-time, or
      // aToModuleName is a (currently unsupported) delay-load import.
      return false;
    }

    // Resolve the import name table (INT).
    auto firstINTThunk = headers.template RVAToPtr<PIMAGE_THUNK_DATA>(
        impDesc->OriginalFirstThunk);
    if (!nt::PEHeaders::IsValid(firstINTThunk)) {
      return false;
    }

    Maybe<ptrdiff_t> thunkIndex;

    // Scan the INT for the location of the thunk for the function named
    // 'aTargetFnName'.
    for (PIMAGE_THUNK_DATA curINTThunk = firstINTThunk;
         nt::PEHeaders::IsValid(curINTThunk); ++curINTThunk) {
      if (IMAGE_SNAP_BY_ORDINAL(curINTThunk->u1.Ordinal)) {
        // Currently not supporting import by ordinal; this isn't hard to add,
        // but we won't bother unless necessary.
        continue;
      }

      PIMAGE_IMPORT_BY_NAME curThunkFnName =
          headers.template RVAToPtr<PIMAGE_IMPORT_BY_NAME>(
              curINTThunk->u1.AddressOfData);
      MOZ_ASSERT(curThunkFnName);
      if (!curThunkFnName) {
        // Looks like we have a bad name descriptor. Try to continue.
        continue;
      }

      // Function name checks MUST be case-sensitive!
      if (!strcmp(aTargetFnName, curThunkFnName->Name)) {
        // We found the thunk. Save the index of this thunk, as the IAT thunk
        // is located at the same index in that table as in the INT.
        thunkIndex = Some(curINTThunk - firstINTThunk);
        break;
      }
    }

    if (thunkIndex.isNothing()) {
      // We never found a thunk for that function. Perhaps it's not imported?
      return false;
    }

    if (thunkIndex.value() < 0) {
      // That's just wrong.
      return false;
    }

    auto firstIATThunk =
        headers.template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
    if (!nt::PEHeaders::IsValid(firstIATThunk)) {
      return false;
    }

    // Resolve the IAT thunk for the function we want
    PIMAGE_THUNK_DATA targetThunk = &firstIATThunk[thunkIndex.value()];
    if (!nt::PEHeaders::IsValid(targetThunk)) {
      return false;
    }

    auto fnPtr = reinterpret_cast<Atomic<void*>*>(&targetThunk->u1.Function);

    // Now we can just change out its pointer with our hook function.
    AutoVirtualProtect prot(fnPtr, sizeof(void*), PAGE_EXECUTE_READWRITE);
    if (!prot) {
      return false;
    }

    // We do the exchange this way to ensure that *aOutOrigFunc is always valid
    // once the atomic exchange has taken place.
    void* tmp;

    do {
      tmp = *fnPtr;
      *aOutOrigFunc = tmp;
    } while (!fnPtr->compareExchange(tmp, aHookDest));

    return true;
  }

  template <typename InterceptorT, typename FuncPtrT>
  friend class FuncHook;
};

template <typename FuncPtrT>
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS
    FuncHook<WindowsIATPatcher, FuncPtrT>
        final {
 public:
  using ThisType = FuncHook<WindowsIATPatcher, FuncPtrT>;
  using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;

  constexpr FuncHook()
      : mInitOnce(INIT_ONCE_STATIC_INIT),
        mFromModule(nullptr),
        mOrigFunc(nullptr) {}

#if defined(DEBUG)
  ~FuncHook() = default;
#endif  // defined(DEBUG)

  bool Set(const wchar_t* aFromModuleName, const char* aToModuleName,
           const char* aFnName, FuncPtrT aHookDest) {
    nsModuleHandle fromModule(::LoadLibraryW(aFromModuleName));
    if (!fromModule) {
      return false;
    }

    return Set(fromModule, aToModuleName, aFnName, aHookDest);
  }

  // We offer this overload in case the client wants finer-grained control over
  // loading aFromModule.
  bool Set(nsModuleHandle& aFromModule, const char* aToModuleName,
           const char* aFnName, FuncPtrT aHookDest) {
    LPVOID addHookOk = nullptr;
    InitOnceContext ctx(this, aFromModule, aToModuleName, aFnName, aHookDest);

    bool result = ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
                                        &addHookOk) &&
                  addHookOk;
    if (!result) {
      return result;
    }

    // If we successfully set the hook then we must retain a strong reference
    // to the module that we modified.
    mFromModule = aFromModule.disown();
    return result;
  }

  explicit operator bool() const { return !!mOrigFunc; }

  template <typename... ArgsType>
  ReturnType operator()(ArgsType&&... aArgs) const {
    return mOrigFunc(std::forward<ArgsType>(aArgs)...);
  }

  FuncPtrT GetStub() const { return mOrigFunc; }

#if defined(DEBUG)
  // One-time init stuff cannot be moved or copied
  FuncHook(const FuncHook&) = delete;
  FuncHook(FuncHook&&) = delete;
  FuncHook& operator=(const FuncHook&) = delete;
  FuncHook& operator=(FuncHook&& aOther) = delete;
#endif  // defined(DEBUG)

 private:
  struct MOZ_RAII InitOnceContext final {
    InitOnceContext(ThisType* aHook, const nsModuleHandle& aFromModule,
                    const char* aToModuleName, const char* aFnName,
                    FuncPtrT aHookDest)
        : mHook(aHook),
          mFromModule(aFromModule),
          mToModuleName(aToModuleName),
          mFnName(aFnName),
          mHookDest(reinterpret_cast<void*>(aHookDest)) {}

    ThisType* mHook;
    const nsModuleHandle& mFromModule;
    const char* mToModuleName;
    const char* mFnName;
    void* mHookDest;
  };

 private:
  bool Apply(const nsModuleHandle& aFromModule, const char* aToModuleName,
             const char* aFnName, void* aHookDest) {
    return WindowsIATPatcher::AddHook(
        aFromModule, aToModuleName, aFnName, aHookDest,
        reinterpret_cast<Atomic<void*>*>(&mOrigFunc));
  }

  static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
                                        PVOID* aOutContext) {
    MOZ_ASSERT(aOutContext);

    auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
    bool result = ctx->mHook->Apply(ctx->mFromModule, ctx->mToModuleName,
                                    ctx->mFnName, ctx->mHookDest);

    *aOutContext =
        result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
               : nullptr;
    return TRUE;
  }

 private:
  INIT_ONCE mInitOnce;
  HMODULE mFromModule;  // never freed
  FuncPtrT mOrigFunc;
};

/**
 * This class applies an irreversible patch to jump to a target function
 * without backing up the original function.
 */
class WindowsDllEntryPointInterceptor final {
  using DllMainFn = BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID);
  using MMPolicyT = MMPolicyInProcessEarlyStage;

  MMPolicyT mMMPolicy;

 public:
  explicit WindowsDllEntryPointInterceptor(
      const MMPolicyT::Kernel32Exports& aK32Exports)
      : mMMPolicy(aK32Exports) {}

  bool Set(const nt::PEHeaders& aHeaders, DllMainFn aDestination) {
    if (!aHeaders) {
      return false;
    }

    WindowsDllDetourPatcherPrimitive<MMPolicyT> patcher;
    return patcher.AddIrreversibleHook(
        mMMPolicy, aHeaders.GetEntryPoint(),
        reinterpret_cast<uintptr_t>(aDestination));
  }
};

}  // namespace interceptor

using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;

using CrossProcessDllInterceptor = interceptor::WindowsDllInterceptor<
    mozilla::interceptor::VMSharingPolicyUnique<
        mozilla::interceptor::MMPolicyOutOfProcess>>;

using WindowsIATPatcher = interceptor::WindowsIATPatcher;

}  // namespace mozilla

#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
