// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "google/protobuf/offset_ptr.h"

#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>

#include <gtest/gtest.h>
#include "google/protobuf/unittest.pb.h"

namespace google {
namespace protobuf {
namespace internal {
namespace {

int array[10];

TEST(BasePtrTest, Basic) {
  BasePointer<int, false> b(&array[5], &array);
  EXPECT_EQ(&array[5], b.Resolve(&array));
}

TEST(BasePtrTest, Copyable) {
  EXPECT_TRUE(
      (std::is_trivially_copy_constructible_v<BasePointer<int, false>>));
  EXPECT_TRUE((std::is_trivially_copy_assignable_v<BasePointer<int, false>>));
  EXPECT_TRUE((std::is_trivially_destructible_v<BasePointer<int, false>>));

  BasePointer<int, false> b(&array[7], &array);
  EXPECT_EQ(&array[7], b.Resolve(&array));

  auto b2 = b;
  EXPECT_EQ(&array[7], b2.Resolve(&array));
}

TEST(BasePtrTest, BaseCanBeOnEitherSide) {
  EXPECT_EQ(&array[7],
            (BasePointer<int, false>(&array[7], &array[3])).Resolve(&array[3]));
  EXPECT_EQ(&array[4],
            (BasePointer<int, false>(&array[4], &array[7])).Resolve(&array[7]));
}

TEST(BasePtrTest, BaseCanBeSameAsPointer) {
  int var = 0;

  EXPECT_EQ(&var, (BasePointer<int, false>(&var, &var)).Resolve(&var));
}

TEST(BasePtrTest, NullIsAllowed) {
  bool dummy = false;
  BasePointer<int, true> b(nullptr, &dummy);
  EXPECT_EQ(nullptr, b.Resolve(&dummy));

  int p = 0;
  EXPECT_DEATH((BasePointer<int, true>(&p, &p)),
               "Nullable base pointer can't self reference");

  EXPECT_DEATH((BasePointer<int, false>(nullptr, &dummy)),
               "Non-nullable base pointer constructed with a null pointer");
}

TEST(BasePtrTest, OutOfScopeFails) {
  // For this one we manufacture pointers to make sure they trigger out of scope
  // failures.
  if (sizeof(void*) == 4) {
    GTEST_SKIP() << "We need 64-bit to have out-of-scope pointers.";
  }

  void* base =
      reinterpret_cast<void*>(static_cast<uintptr_t>(0x123457890abcdef));

  const auto make_ptr = [&](ptrdiff_t diff) {
    return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base) + diff);
  };

  using T = BasePointer<void, false>;

  // INT_MAX is fine.
  EXPECT_EQ(T(make_ptr(std::numeric_limits<int>::max()), base).Resolve(base),
            make_ptr(std::numeric_limits<int>::max()));

  // INT_MAX+1 is too much.
  EXPECT_DEATH(
      T(make_ptr(ptrdiff_t{std::numeric_limits<int>::max()} + 1), base),
      "Pointer out of scope for offset pointer");

  // INT_MIN is fine.
  EXPECT_EQ(T(make_ptr(std::numeric_limits<int>::min()), base).Resolve(base),
            make_ptr(std::numeric_limits<int>::min()));

  // INT_MIN-1 is too much.
  EXPECT_DEATH(
      T(make_ptr(ptrdiff_t{std::numeric_limits<int>::min()} - 1), base),
      "Pointer out of scope for offset pointer");
}

TEST(OffsetPtrTest, Basic) {
  // Collocate the pointer and the pointee in the same block of memory.
  struct Object {
    int array[10];
    OffsetPtr<int, false> ptr;
  } object{};

  object.ptr = object.array + 3;
  EXPECT_EQ(object.ptr, object.array + 3);
}

TEST(OffsetProtoPtr, Basic) {
  using P = proto2_unittest::TestAllTypes;

  // Collocate the pointer and the pointee in the same block of memory.
  struct Object {
    OffsetProtoPtr<const P> p;
    P msg;
  } object;

  object.p = nullptr;
  EXPECT_EQ(&P::default_instance(), object.p);

  object.p = &object.msg;
  EXPECT_EQ(&object.msg, object.p);

  object.p = &P::default_instance();
  EXPECT_EQ(&P::default_instance(), object.p);
}

}  // namespace
}  // namespace internal
}  // namespace protobuf
}  // namespace google
