// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/memory/raw_ptr_asan_event.h"

#if PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR_V2)

#include <sanitizer/allocator_interface.h>
#include <sanitizer/asan_interface.h>

#include <sstream>

#include "base/compiler_specific.h"
#include "base/debug/asan_service.h"
#include "base/memory/raw_ptr_asan_allocator.h"
#include "base/memory/raw_ptr_asan_service.h"

namespace base::internal {

namespace {

template <typename T>
struct InternalDeleter {
  constexpr InternalDeleter() = default;

  void operator()(T* ptr) const { RawPtrAsanAllocator<T>().deallocate(ptr, 1); }
};

}  // namespace

void RawPtrAsanEvent::PrintEventStack() const {
  using BufferType = std::array<char, 4096>;
  std::unique_ptr<BufferType, InternalDeleter<BufferType>> buffer(
      RawPtrAsanAllocator<BufferType>().allocate(1));
  size_t frame_index = 0;

  for (size_t i = 0; i < stack.size(); ++i) {
    if (!stack[i]) {
      break;
    }
    void* frame_ptr = const_cast<void*>(stack[i]);
    __sanitizer_symbolize_pc(frame_ptr, "%p %F %L", buffer->data(),
                             buffer->size());
    for (const char* ptr = buffer->data(); *ptr != '\0';
         UNSAFE_BUFFERS(ptr += strlen(ptr))) {
      debug::AsanService::GetInstance()->Log("    #%i %s", frame_index++, ptr);
    }
  }

  debug::AsanService::GetInstance()->Log("");
}

void RawPtrAsanEvent::PrintEvent(bool print_stack = false) const {
  const char* type_string = "quarantine-entry";
  if (type == RawPtrAsanEvent::Type::kQuarantineAssignment) {
    type_string = "quarantine-assignment";
  } else if (type == RawPtrAsanEvent::Type::kQuarantineRead) {
    type_string = "quarantine-read";
  } else if (type == RawPtrAsanEvent::Type::kQuarantineWrite) {
    type_string = "quarantine-write";
  } else if (type == RawPtrAsanEvent::Type::kQuarantineExit) {
    type_string = "quarantine-exit";
  } else if (type == RawPtrAsanEvent::Type::kFreeAssignment) {
    type_string = "free-assignment";
  }

  std::stringstream ss_for_thread_id;
  ss_for_thread_id << thread_id;
  debug::AsanService::GetInstance()->Log(
      "[0x%zx:%zu] (%s) %s", address, size,
      std::move(ss_for_thread_id).str().c_str(), type_string);

  if (print_stack) {
    PrintEventStack();
  }
}

RawPtrAsanEventLog::RawPtrAsanEventLog() = default;

RawPtrAsanEventLog::~RawPtrAsanEventLog() = default;

NO_SANITIZE("address")
void RawPtrAsanEventLog::Add(RawPtrAsanEvent&& event) {
  using Type = RawPtrAsanEvent::Type;
  internal::PartitionAutoLock event_lock(GetLock());

  // Check if we have previous logged accesses to this allocation; this will
  // tell us if we need to store it, and whether we need to synthesize the
  // corresponding kQuarantineEntry event.
  bool has_entry = false;
  for (auto iter = events_.rbegin(); iter != events_.rend(); ++iter) {
    if (event.IsSameAllocation(*iter)) {
      // If the last matching event that we find is a quarantine-exit event,
      // then this event refers to a reused allocation that doesn't have any
      // logged events.
      if (iter->type != Type::kQuarantineExit) {
        has_entry = true;
      }
      break;
    }
  }

  if (event.type == Type::kQuarantineEntry) {
    // We don't store kQuarantineEntry events directly, since we can synthesize
    // them later when they are needed.
    return;
  }
  if (event.type == Type::kQuarantineExit) {
    // kQuarantineExit without any other accesses is uninteresting, we can just
    // discard this event as long as the threads match.
    RawPtrAsanThreadId free_thread_id =
        RawPtrAsanService::GetInstance().GetFreeThreadIdOfAllocation(
            event.address);
    if (!has_entry && event.thread_id == free_thread_id) {
      return;
    }
  }

  if (!has_entry && (event.type == Type::kQuarantineAssignment ||
                     event.type == Type::kQuarantineRead ||
                     event.type == Type::kQuarantineWrite ||
                     event.type == Type::kQuarantineExit)) {
    // If we reach here, then this allocation in quarantine just became
    // interesting, so we should synthesize a kQuarantineEntry event for it.
    RawPtrAsanThreadId free_thread_id =
        RawPtrAsanService::GetInstance().GetFreeThreadIdOfAllocation(
            event.address);
    uintptr_t allocation_start_address =
        RawPtrAsanService::GetInstance().GetAllocationStart(event.address);
    RawPtrAsanEvent entry_event;
    entry_event.type = Type::kQuarantineEntry;
    entry_event.thread_id = free_thread_id;
    entry_event.address = allocation_start_address;
    if (allocation_start_address) {
      void* allocation_start_ptr =
          reinterpret_cast<void*>(allocation_start_address);
      entry_event.size = __sanitizer_get_allocated_size(allocation_start_ptr);
      __asan_get_alloc_stack(allocation_start_ptr,
                             const_cast<void**>(entry_event.stack.data()),
                             entry_event.stack.size(), nullptr);
    } else {
      // `ptr` does not point to ASAN allocated memory.
      entry_event.size = 0;
    }
    events_.emplace_back(std::move(entry_event));
  }

  events_.emplace_back(std::move(event));
}

void RawPtrAsanEventLog::Print(bool print_stack = false) {
  internal::PartitionAutoLock lock(GetLock());
  for (const auto& event : events_) {
    event.PrintEvent(print_stack);
  }
}

}  // namespace base::internal

#endif  // PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR_V2)
