// Copyright 2025 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_SANDBOX_SANDBOX_MALLOC_H_
#define V8_SANDBOX_SANDBOX_MALLOC_H_

#include "src/base/numerics/checked_math.h"
#include "src/init/isolate-group.h"

namespace v8 {
namespace internal {

// General-purpose memory allocator for in-sandbox memory.
//
// If the sandbox is not enabled, this will simply use base::Malloc.
//
// NOTE: This currently uses V8's own array buffer allocator (which is mainly
// used by d8). However, that is a fairly inefficient allocator. In the future
// it would be nice to have a "real" allocator for this purpose instead.
//
// NOTE: this allocator must only be used for UNTRUSTED memory as an attacker
// can corrupt the contents of the returned buffers arbitrarily. As such, it is
// for example generally unsafe to store any pointers in the returned memory.

template <typename T>
T* SandboxAlloc() {
  // It is generally unsafe to allocate non-trivial objects inside the sandbox,
  // in particular anything containing pointers. The static_assert is a
  // best-effort attempt to catch such cases, but it doesn't work for all
  // unsafe cases, for example raw pointer fields or Address values.
  // TODO(427464384): this is a little too strict, e.g. for std::atomic.
  static_assert(std::is_trivial<T>::value,
                "Must only allocate trivial C++ types inside the sandbox");

  size_t size = sizeof(T);

#ifdef V8_ENABLE_SANDBOX
  auto allocator =
      IsolateGroup::GetDefault()->GetSandboxedArrayBufferAllocator();
  void* raw_memory = allocator->Allocate(size);
#else
  void* raw_memory = base::Malloc(size);
#endif  // V8_ENABLE_SANDBOX

  memset(raw_memory, 0, size);
  return static_cast<T*>(raw_memory);
}

template <typename T>
T* SandboxAllocArray(size_t num_elements) {
  // It is generally unsafe to allocate non-trivial objects inside the sandbox,
  // in particular anything containing pointers. The static_assert is a
  // best-effort attempt to catch such cases, but it doesn't work for all
  // unsafe cases, for example raw pointer fields or Address values.
  // TODO(427464384): this is a little too strict, e.g. for std::atomic.
  static_assert(std::is_trivial<T>::value,
                "Must only allocate trivial C++ types inside the sandbox");

  size_t size = base::CheckMul(num_elements, sizeof(T)).ValueOrDie();

#ifdef V8_ENABLE_SANDBOX
  auto allocator =
      IsolateGroup::GetDefault()->GetSandboxedArrayBufferAllocator();
  void* raw_memory = allocator->Allocate(size);
#else
  void* raw_memory = base::Malloc(size);
#endif  // V8_ENABLE_SANDBOX

  memset(raw_memory, 0, size);
  return static_cast<T*>(raw_memory);
}

inline void SandboxFree(void* ptr) {
#ifdef V8_ENABLE_SANDBOX
  auto allocator =
      IsolateGroup::GetDefault()->GetSandboxedArrayBufferAllocator();
  allocator->Free(ptr);
#else
  return base::Free(ptr);
#endif  // V8_ENABLE_SANDBOX
}

// Deleter struct for use with std::unique_ptr:
//
// std::unique_ptr<int, SandboxFreeDeleter> foo_ptr(
//     static_cast<int*>(SandboxAlloc(sizeof(int))));
struct SandboxFreeDeleter {
  inline void operator()(void* p) const { SandboxFree(p); }
};

}  // namespace internal
}  // namespace v8

#endif  // V8_SANDBOX_SANDBOX_MALLOC_H_