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

#include "LoaderObserver.h"

#include "mozilla/AutoProfilerLabel.h"
#include "mozilla/BaseProfilerMarkers.h"
#include "mozilla/glue/WindowsUnicode.h"
#include "mozilla/StackWalk_windows.h"
#include "mozilla/TimeStamp.h"

namespace mozilla {

extern glue::Win32SRWLock gDllServicesLock;
extern glue::detail::DllServicesBase* gDllServices;

namespace glue {

void LoaderObserver::OnBeginDllLoad(void** aContext,
                                    PCUNICODE_STRING aRequestedDllName) {
  MOZ_ASSERT(aContext);
  if (IsProfilerPresent()) {
    UniquePtr<char[]> utf8RequestedDllName(WideToUTF8(aRequestedDllName));
    BASE_PROFILER_MARKER_TEXT(
        "DllLoad", OTHER, MarkerTiming::IntervalStart(),
        mozilla::ProfilerString8View::WrapNullTerminatedString(
            utf8RequestedDllName.get()));
    *aContext = utf8RequestedDllName.release();
  }

#ifdef _M_AMD64
  // Prevent the stack walker from suspending this thread when LdrLoadDll
  // holds the RtlLookupFunctionEntry lock.
  SuppressStackWalking();
#endif
}

bool LoaderObserver::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
                                      PHANDLE aOutHandle) {
  // Currently unsupported
  return false;
}

void LoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
                                  ModuleLoadInfo&& aModuleLoadInfo) {
#ifdef _M_AMD64
  DesuppressStackWalking();
#endif

  if (aContext) {
    UniquePtr<char[]> utf8RequestedDllName{static_cast<char*>(aContext)};
    BASE_PROFILER_MARKER_TEXT(
        "DllLoad", OTHER, MarkerTiming::IntervalEnd(),
        mozilla::ProfilerString8View::WrapNullTerminatedString(
            utf8RequestedDllName.get()));
  }

  // We want to record a denied DLL load regardless of |aNtStatus| because
  // |aNtStatus| is set to access-denied when DLL load was blocked.
  if ((!NT_SUCCESS(aNtStatus) && !aModuleLoadInfo.WasDenied()) ||
      !aModuleLoadInfo.WasMapped()) {
    return;
  }

  {  // Scope for lock
    AutoSharedLock lock(gDllServicesLock);
    if (gDllServices) {
      gDllServices->DispatchDllLoadNotification(std::move(aModuleLoadInfo));
      return;
    }
  }

  // No dll services, save for later
  AutoExclusiveLock lock(mLock);
  if (!mEnabled) {
    return;
  }

  if (!mModuleLoads) {
    mModuleLoads = new ModuleLoadInfoVec();
  }

  Unused << mModuleLoads->emplaceBack(
      std::forward<ModuleLoadInfo>(aModuleLoadInfo));
}

void LoaderObserver::Forward(nt::LoaderObserver* aNext) {
  MOZ_ASSERT_UNREACHABLE(
      "This implementation does not forward to any more "
      "nt::LoaderObserver objects");
}

void LoaderObserver::Forward(detail::DllServicesBase* aNext) {
  MOZ_ASSERT(aNext);
  if (!aNext) {
    return;
  }

  ModuleLoadInfoVec* moduleLoads = nullptr;

  {  // Scope for lock
    AutoExclusiveLock lock(mLock);
    moduleLoads = mModuleLoads;
    mModuleLoads = nullptr;
  }

  if (!moduleLoads) {
    return;
  }

  aNext->DispatchModuleLoadBacklogNotification(std::move(*moduleLoads));
  delete moduleLoads;
}

void LoaderObserver::Disable() {
  ModuleLoadInfoVec* moduleLoads = nullptr;

  {  // Scope for lock
    AutoExclusiveLock lock(mLock);
    moduleLoads = mModuleLoads;
    mModuleLoads = nullptr;
    mEnabled = false;
  }

  delete moduleLoads;
}

void LoaderObserver::OnForward(ModuleLoadInfoVec&& aInfo) {
  AutoExclusiveLock lock(mLock);
  if (!mModuleLoads) {
    mModuleLoads = new ModuleLoadInfoVec();
  }

  MOZ_ASSERT(mModuleLoads->empty());
  if (mModuleLoads->empty()) {
    *mModuleLoads = std::move(aInfo);
  } else {
    // This should not happen, but we can handle it
    for (auto&& item : aInfo) {
      Unused << mModuleLoads->append(std::move(item));
    }
  }
}

}  // namespace glue
}  // namespace mozilla
