//===--- rtsan.cpp - Realtime Sanitizer -------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
//===----------------------------------------------------------------------===//

#include "rtsan/rtsan.h"
#include "rtsan/rtsan_assertions.h"
#include "rtsan/rtsan_diagnostics.h"
#include "rtsan/rtsan_flags.h"
#include "rtsan/rtsan_interceptors.h"
#include "rtsan/rtsan_stats.h"
#include "rtsan/rtsan_suppressions.h"

#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "sanitizer_common/sanitizer_stackdepot.h"

using namespace __rtsan;
using namespace __sanitizer;

namespace {
enum class InitializationState : u8 {
  Uninitialized,
  Initializing,
  Initialized,
};
} // namespace

static StaticSpinMutex rtsan_inited_mutex;
static atomic_uint8_t rtsan_initialized = {
    static_cast<u8>(InitializationState::Uninitialized)};

static void SetInitializationState(InitializationState state) {
  atomic_store(&rtsan_initialized, static_cast<u8>(state),
               memory_order_release);
}

static InitializationState GetInitializationState() {
  return static_cast<InitializationState>(
      atomic_load(&rtsan_initialized, memory_order_acquire));
}

static void OnViolation(const BufferedStackTrace &stack,
                        const DiagnosticsInfo &info) {
  IncrementTotalErrorCount();

  // If in the future we interop with other sanitizers, we will
  // need to make our own stackdepot
  StackDepotHandle handle = StackDepotPut_WithHandle(stack);

  const bool is_stack_novel = handle.use_count() == 0;
  if (is_stack_novel || !flags().suppress_equal_stacks) {
    IncrementUniqueErrorCount();

    {
      ScopedErrorReportLock l;
      PrintDiagnostics(info);
      stack.Print();
      PrintErrorSummary(info, stack);
    }

    handle.inc_use_count_unsafe();
  }

  if (flags().halt_on_error) {
    if (flags().print_stats_on_exit)
      PrintStatisticsSummary();
    Die();
  }
}

extern "C" {

SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_init() {
  CHECK(GetInitializationState() == InitializationState::Uninitialized);
  SetInitializationState(InitializationState::Initializing);

  SanitizerToolName = "RealtimeSanitizer";
  InitializeFlags();

  InitializePlatformEarly();

  InitializeInterceptors();

  InitializeSuppressions();

  if (flags().print_stats_on_exit)
    Atexit(PrintStatisticsSummary);

  SetInitializationState(InitializationState::Initialized);
}

SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_ensure_initialized() {
  if (LIKELY(__rtsan_is_initialized()))
    return;

  SpinMutexLock lock(&rtsan_inited_mutex);

  // Someone may have initialized us while we were waiting for the lock
  if (__rtsan_is_initialized())
    return;

  __rtsan_init();
}

SANITIZER_INTERFACE_ATTRIBUTE bool __rtsan_is_initialized() {
  return GetInitializationState() == InitializationState::Initialized;
}

SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_realtime_enter() {
  GetContextForThisThread().RealtimePush();
}

SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_realtime_exit() {
  GetContextForThisThread().RealtimePop();
}

SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_disable() {
  GetContextForThisThread().BypassPush();
}

SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_enable() {
  GetContextForThisThread().BypassPop();
}

SANITIZER_INTERFACE_ATTRIBUTE void
__rtsan_notify_intercepted_call(const char *func_name) {
  // While initializing, we need all intercepted functions to behave normally
  if (GetInitializationState() == InitializationState::Initializing)
    return;

  __rtsan_ensure_initialized();
  GET_CALLER_PC_BP;
  ExpectNotRealtime(GetContextForThisThread(),
                    {DiagnosticsInfoType::InterceptedCall, func_name, pc, bp},
                    OnViolation);
}

SANITIZER_INTERFACE_ATTRIBUTE void
__rtsan_notify_blocking_call(const char *func_name) {
  __rtsan_ensure_initialized();
  GET_CALLER_PC_BP;
  ExpectNotRealtime(GetContextForThisThread(),
                    {DiagnosticsInfoType::BlockingCall, func_name, pc, bp},
                    OnViolation);
}

} // extern "C"
