// Copyright 2018 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/shared_memory_mapping.h"

#include <stdint.h>

#include <algorithm>
#include <atomic>
#include <limits>
#include <memory>
#include <type_traits>

#include "base/containers/span.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/writable_shared_memory_region.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {
template <typename T>
using ElementType = std::pointer_traits<T>::element_type;
}

class SharedMemoryMappingTest : public ::testing::Test {
 protected:
  void CreateMapping(size_t size) {
    auto result = ReadOnlySharedMemoryRegion::Create(size);
    ASSERT_TRUE(result.IsValid());
    write_mapping_ = std::move(result.mapping);
    read_mapping_ = result.region.Map();
    ASSERT_TRUE(read_mapping_.IsValid());
  }

  WritableSharedMemoryMapping write_mapping_;
  ReadOnlySharedMemoryMapping read_mapping_;
};

TEST_F(SharedMemoryMappingTest, Invalid) {
  EXPECT_EQ(nullptr, write_mapping_.GetMemoryAs<uint8_t>());
  EXPECT_EQ(nullptr, read_mapping_.GetMemoryAs<uint8_t>());
  EXPECT_TRUE(write_mapping_.GetMemoryAsSpan<uint8_t>().empty());
  EXPECT_TRUE(read_mapping_.GetMemoryAsSpan<uint8_t>().empty());
  EXPECT_TRUE(write_mapping_.GetMemoryAsSpan<uint8_t>(1).empty());
  EXPECT_TRUE(read_mapping_.GetMemoryAsSpan<uint8_t>(1).empty());
}

TEST_F(SharedMemoryMappingTest, Scalar) {
  CreateMapping(sizeof(uint32_t));

  uint32_t* write_ptr = write_mapping_.GetMemoryAs<uint32_t>();
  ASSERT_NE(nullptr, write_ptr);

  const uint32_t* read_ptr = read_mapping_.GetMemoryAs<uint32_t>();
  ASSERT_NE(nullptr, read_ptr);

  *write_ptr = 0u;
  EXPECT_EQ(0u, *read_ptr);

  *write_ptr = 0x12345678u;
  EXPECT_EQ(0x12345678u, *read_ptr);
}

TEST_F(SharedMemoryMappingTest, SpanWithAutoDeducedElementCount) {
  CreateMapping(sizeof(uint8_t) * 8);

  span<uint8_t> write_span = write_mapping_.GetMemoryAsSpan<uint8_t>();
  ASSERT_EQ(8u, write_span.size());

  span<const uint32_t> read_span = read_mapping_.GetMemoryAsSpan<uint32_t>();
  ASSERT_EQ(2u, read_span.size());

  std::ranges::fill(write_span, 0);
  EXPECT_EQ(0u, read_span[0]);
  EXPECT_EQ(0u, read_span[1]);

  for (size_t i = 0; i < write_span.size(); ++i) {
    write_span[i] = i + 1;
  }
  EXPECT_EQ(0x04030201u, read_span[0]);
  EXPECT_EQ(0x08070605u, read_span[1]);
}

TEST_F(SharedMemoryMappingTest, SpanWithExplicitElementCount) {
  CreateMapping(sizeof(uint8_t) * 8);

  span<uint8_t> write_span = write_mapping_.GetMemoryAsSpan<uint8_t>(8);
  ASSERT_EQ(8u, write_span.size());

  span<uint8_t> write_span_2 = write_mapping_.GetMemoryAsSpan<uint8_t>(4);
  ASSERT_EQ(4u, write_span_2.size());

  span<const uint32_t> read_span = read_mapping_.GetMemoryAsSpan<uint32_t>(2);
  ASSERT_EQ(2u, read_span.size());

  span<const uint32_t> read_span_2 = read_mapping_.GetMemoryAsSpan<uint32_t>(1);
  ASSERT_EQ(1u, read_span_2.size());

  std::ranges::fill(write_span, 0);
  EXPECT_EQ(0u, read_span[0]);
  EXPECT_EQ(0u, read_span[1]);
  EXPECT_EQ(0u, read_span_2[0]);

  for (size_t i = 0; i < write_span.size(); ++i) {
    write_span[i] = i + 1;
  }
  EXPECT_EQ(0x04030201u, read_span[0]);
  EXPECT_EQ(0x08070605u, read_span[1]);
  EXPECT_EQ(0x04030201u, read_span_2[0]);

  std::ranges::fill(write_span_2, 0);
  EXPECT_EQ(0u, read_span[0]);
  EXPECT_EQ(0x08070605u, read_span[1]);
  EXPECT_EQ(0u, read_span_2[0]);
}

TEST_F(SharedMemoryMappingTest, SpanWithZeroElementCount) {
  CreateMapping(sizeof(uint8_t) * 8);

  EXPECT_TRUE(write_mapping_.GetMemoryAsSpan<uint8_t>(0).empty());

  EXPECT_TRUE(read_mapping_.GetMemoryAsSpan<uint8_t>(0).empty());
}

TEST_F(SharedMemoryMappingTest, ConstCorrectness) {
  // All memory accessors for read-only mappings should return const T.
  ReadOnlySharedMemoryMapping ro;

  static_assert(std::is_const_v<ElementType<decltype(ro.data())>>);
  static_assert(std::is_const_v<ElementType<decltype(ro.begin())>>);
  static_assert(std::is_const_v<ElementType<decltype(ro.end())>>);
  static_assert(std::is_const_v<ElementType<decltype(ro.memory())>>);
  static_assert(
      std::is_const_v<ElementType<decltype(ro.GetMemoryAs<uint8_t>())>>);
  static_assert(
      std::is_const_v<ElementType<decltype(ro.GetMemoryAs<const uint8_t>())>>);
  static_assert(
      std::is_const_v<decltype(ro.GetMemoryAsSpan<uint8_t>())::element_type>);
  static_assert(std::is_const_v<
                decltype(ro.GetMemoryAsSpan<const uint8_t>())::element_type>);
  static_assert(
      std::is_const_v<decltype(ro.GetMemoryAsSpan<uint8_t>(1))::element_type>);
  static_assert(std::is_const_v<decltype(ro.GetMemoryAsSpan<const uint8_t>(
                    1))::element_type>);

  // Making the mapping const should still allow all accessors to be called.
  const ReadOnlySharedMemoryMapping cro;
  static_assert(std::is_const_v<ElementType<decltype(cro.data())>>);
  static_assert(std::is_const_v<ElementType<decltype(cro.begin())>>);
  static_assert(std::is_const_v<ElementType<decltype(cro.end())>>);
  static_assert(std::is_const_v<ElementType<decltype(cro.memory())>>);
  static_assert(
      std::is_const_v<ElementType<decltype(cro.GetMemoryAs<uint8_t>())>>);
  static_assert(
      std::is_const_v<ElementType<decltype(cro.GetMemoryAs<const uint8_t>())>>);
  static_assert(
      std::is_const_v<decltype(cro.GetMemoryAsSpan<uint8_t>())::element_type>);
  static_assert(std::is_const_v<
                decltype(cro.GetMemoryAsSpan<const uint8_t>())::element_type>);
  static_assert(
      std::is_const_v<decltype(cro.GetMemoryAsSpan<uint8_t>(1))::element_type>);
  static_assert(std::is_const_v<decltype(cro.GetMemoryAsSpan<const uint8_t>(
                    1))::element_type>);

  // Accessors for writable mappings should be non-const unless requested.
  WritableSharedMemoryMapping rw;
  static_assert(!std::is_const_v<ElementType<decltype(rw.data())>>);
  static_assert(!std::is_const_v<ElementType<decltype(rw.begin())>>);
  static_assert(!std::is_const_v<ElementType<decltype(rw.end())>>);
  static_assert(!std::is_const_v<ElementType<decltype(rw.memory())>>);
  static_assert(
      !std::is_const_v<ElementType<decltype(rw.GetMemoryAs<uint8_t>())>>);
  static_assert(
      std::is_const_v<ElementType<decltype(rw.GetMemoryAs<const uint8_t>())>>);
  static_assert(
      !std::is_const_v<decltype(rw.GetMemoryAsSpan<uint8_t>())::element_type>);
  static_assert(std::is_const_v<
                decltype(rw.GetMemoryAsSpan<const uint8_t>())::element_type>);
  static_assert(
      !std::is_const_v<decltype(rw.GetMemoryAsSpan<uint8_t>(1))::element_type>);
  static_assert(std::is_const_v<decltype(rw.GetMemoryAsSpan<const uint8_t>(
                    1))::element_type>);

  // Making the mapping const should still allow all accessors to be called, but
  // they should now return const T.
  const WritableSharedMemoryMapping crw;
  static_assert(std::is_const_v<ElementType<decltype(crw.data())>>);
  static_assert(std::is_const_v<ElementType<decltype(crw.begin())>>);
  static_assert(std::is_const_v<ElementType<decltype(crw.end())>>);
  static_assert(std::is_const_v<ElementType<decltype(crw.memory())>>);
  static_assert(
      std::is_const_v<ElementType<decltype(crw.GetMemoryAs<uint8_t>())>>);
  static_assert(
      std::is_const_v<ElementType<decltype(crw.GetMemoryAs<const uint8_t>())>>);
  static_assert(
      std::is_const_v<decltype(crw.GetMemoryAsSpan<uint8_t>())::element_type>);
  static_assert(std::is_const_v<
                decltype(crw.GetMemoryAsSpan<const uint8_t>())::element_type>);
  static_assert(
      std::is_const_v<decltype(crw.GetMemoryAsSpan<uint8_t>(1))::element_type>);
  static_assert(std::is_const_v<decltype(crw.GetMemoryAsSpan<const uint8_t>(
                    1))::element_type>);
}

TEST_F(SharedMemoryMappingTest, TooBigScalar) {
  CreateMapping(sizeof(uint8_t));

  EXPECT_EQ(nullptr, write_mapping_.GetMemoryAs<uint32_t>());

  EXPECT_EQ(nullptr, read_mapping_.GetMemoryAs<uint32_t>());
}

TEST_F(SharedMemoryMappingTest, TooBigSpanWithAutoDeducedElementCount) {
  CreateMapping(sizeof(uint8_t));

  EXPECT_TRUE(write_mapping_.GetMemoryAsSpan<uint32_t>().empty());

  EXPECT_TRUE(read_mapping_.GetMemoryAsSpan<uint32_t>().empty());
}

TEST_F(SharedMemoryMappingTest, TooBigSpanWithExplicitElementCount) {
  CreateMapping(sizeof(uint8_t));

  // Deliberately pick element counts such that a naive bounds calculation would
  // overflow.
  EXPECT_TRUE(write_mapping_
                  .GetMemoryAsSpan<uint32_t>(std::numeric_limits<size_t>::max())
                  .empty());

  EXPECT_TRUE(read_mapping_
                  .GetMemoryAsSpan<uint32_t>(std::numeric_limits<size_t>::max())
                  .empty());
}

TEST_F(SharedMemoryMappingTest, Atomic) {
  CreateMapping(sizeof(std::atomic<uint32_t>));

  auto* write_ptr = write_mapping_.GetMemoryAs<std::atomic<uint32_t>>();
  ASSERT_NE(nullptr, write_ptr);

  // Placement new to initialize the std::atomic in place.
  new (write_ptr) std::atomic<uint32_t>;

  const auto* read_ptr = read_mapping_.GetMemoryAs<std::atomic<uint32_t>>();
  ASSERT_NE(nullptr, read_ptr);

  write_ptr->store(0u, std::memory_order_relaxed);
  EXPECT_EQ(0u, read_ptr->load(std::memory_order_relaxed));

  write_ptr->store(0x12345678u, std::memory_order_relaxed);
  EXPECT_EQ(0x12345678u, read_ptr->load(std::memory_order_relaxed));
}

TEST_F(SharedMemoryMappingTest, TooBigAtomic) {
  CreateMapping(sizeof(std::atomic<uint8_t>));

  EXPECT_EQ(nullptr, write_mapping_.GetMemoryAs<std::atomic<uint32_t>>());

  EXPECT_EQ(nullptr, read_mapping_.GetMemoryAs<std::atomic<uint32_t>>());
}

// TODO(dcheng): This test is temporarily disabled on iOS. iOS devices allow
// the creation of a 1GB shared memory region, but don't allow the region to be
// mapped.
#if !BUILDFLAG(IS_IOS)
// TODO(crbug.com/40846204) Fix flakiness and re-enable on Linux and ChromeOS.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_TotalMappedSizeLimit DISABLED_TotalMappedSizeLimit
#else
#define MAYBE_TotalMappedSizeLimit TotalMappedSizeLimit
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
TEST_F(SharedMemoryMappingTest, MAYBE_TotalMappedSizeLimit) {
  // Nothing interesting to test if the address space isn't 64 bits, since
  // there's no real limit enforced on 32 bits other than complete address
  // space exhaustion.
#if defined(ARCH_CPU_64_BITS)
  auto region = WritableSharedMemoryRegion::Create(1024 * 1024 * 1024);
  ASSERT_TRUE(region.IsValid());
  // The limit is 32GB of mappings on 64-bit platforms, so the final mapping
  // should fail.
  std::vector<WritableSharedMemoryMapping> mappings(32);
  for (size_t i = 0; i < mappings.size(); ++i) {
    SCOPED_TRACE(i);
    auto& mapping = mappings[i];
    mapping = region.Map();
    EXPECT_EQ(&mapping != &mappings.back(), mapping.IsValid());
  }
#endif  // defined(ARCH_CPU_64_BITS)
}
#endif  // !BUILDFLAG(IS_IOS)

}  // namespace base
