// Copyright 2017 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.

#include "include/v8-template.h"
#include "src/base/macros.h"
#include "src/common/globals.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"

using DictionaryTemplateTest = v8::TestWithContext;

namespace v8 {

namespace {

v8::Local<v8::String> v8_str(v8::Isolate* isolate, const char* x) {
  return v8::String::NewFromUtf8(isolate, x).ToLocalChecked();
}

v8::Local<v8::Integer> v8_int(v8::Isolate* isolate, int x) {
  return v8::Integer::New(isolate, x);
}

}  // namespace

TEST_F(DictionaryTemplateTest, SetPropertiesAndInstantiateWithoutValues) {
  HandleScope scope(isolate());
  constexpr std::string_view property_names[] = {"a", "b"};
  Local<DictionaryTemplate> tpl =
      DictionaryTemplate::New(isolate(), property_names);

  MaybeLocal<Value> values[2];
  Local<Object> instance = tpl->NewInstance(context(), values);
  EXPECT_FALSE(instance.IsEmpty());
  EXPECT_FALSE(
      instance->HasOwnProperty(context(), v8_str(isolate(), "a")).ToChecked());
  EXPECT_FALSE(
      instance->HasOwnProperty(context(), v8_str(isolate(), "b")).ToChecked());
}

TEST_F(DictionaryTemplateTest, SetPropertiesAndInstantiateWithSomeValues) {
  HandleScope scope(isolate());
  constexpr std::string_view property_names[] = {"a", "b"};
  Local<DictionaryTemplate> tpl =
      DictionaryTemplate::New(isolate(), property_names);

  MaybeLocal<Value> values[2] = {{}, v8_str(isolate(), "b_value")};
  Local<Object> instance = tpl->NewInstance(context(), values);
  EXPECT_FALSE(instance.IsEmpty());
  EXPECT_FALSE(
      instance->HasOwnProperty(context(), v8_str(isolate(), "a")).ToChecked());
  EXPECT_TRUE(
      instance->HasOwnProperty(context(), v8_str(isolate(), "b")).ToChecked());
}

TEST_F(DictionaryTemplateTest, SetPropertiesAndInstantiateWithAllValues) {
  HandleScope scope(isolate());
  constexpr std::string_view property_names[] = {"a", "b"};
  Local<DictionaryTemplate> tpl =
      DictionaryTemplate::New(isolate(), property_names);

  MaybeLocal<Value> values[2] = {v8_str(isolate(), "a_value"),
                                 v8_str(isolate(), "b_value")};
  Local<Object> instance = tpl->NewInstance(context(), values);
  EXPECT_FALSE(instance.IsEmpty());
  EXPECT_TRUE(
      instance->HasOwnProperty(context(), v8_str(isolate(), "a")).ToChecked());
  EXPECT_TRUE(
      instance->HasOwnProperty(context(), v8_str(isolate(), "b")).ToChecked());
}

TEST_F(DictionaryTemplateTest,
       TestPropertyTransitionWithDifferentRepresentation) {
  HandleScope scope(isolate());

  constexpr std::string_view property_names[] = {"q", "a"};
  Local<DictionaryTemplate> tpl =
      DictionaryTemplate::New(isolate(), property_names);

  constexpr int32_t kBoxedInt = 1 << 31;
  MaybeLocal<Value> values[2] = {{}, v8_int(isolate(), kBoxedInt)};
  Local<Object> instance1 = tpl->NewInstance(context(), values);
  auto value1 =
      instance1->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
  EXPECT_EQ(Int32::Cast(*value1)->Value(), kBoxedInt);

  // Now transition from a boxed int to a SMI.
  constexpr int32_t kSmi = 42;
  values[1] = v8_int(isolate(), kSmi);
  Local<Object> instance2 = tpl->NewInstance(context(), values);

  auto value2 =
      instance2->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
  EXPECT_EQ(Int32::Cast(*value2)->Value(), kSmi);

  // Now from SMI back to a boxed int again, just in case.
  values[1] = v8_int(isolate(), kBoxedInt);
  Local<Object> instance3 = tpl->NewInstance(context(), values);

  auto value3 =
      instance3->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
  EXPECT_EQ(Int32::Cast(*value3)->Value(), kBoxedInt);
}

TEST_F(DictionaryTemplateTest, PrototypeContext) {
  HandleScope handle_scope(isolate());

  constexpr std::string_view property_names[] = {"a", "b"};
  Local<DictionaryTemplate> tpl =
      DictionaryTemplate::New(isolate(), property_names);

  MaybeLocal<Value> fast_values[2] = {v8_str(isolate(), "a_value"),
                                      v8_str(isolate(), "b_value")};

  MaybeLocal<Value> slow_values[2] = {{}, v8_str(isolate(), "b_value")};

  Local<Object> instance1 = tpl->NewInstance(context(), fast_values);
  Local<Object> object1 = v8::Object::New(isolate());

  Local<Object> instance2, instance3;
  Local<Object> object2;
  {
    Local<Context> context2 = Context::New(isolate());
    v8::Context::Scope scope(context2);
    instance2 = tpl->NewInstance(context2, fast_values);
    instance3 = tpl->NewInstance(context2, slow_values);
    object2 = v8::Object::New(isolate());
  }

  EXPECT_TRUE(instance1->GetPrototype() == object1->GetPrototype());
  EXPECT_TRUE(instance2->GetPrototype() == object2->GetPrototype());

  EXPECT_FALSE(instance1->GetPrototype() == instance2->GetPrototype());
  EXPECT_TRUE(instance2->GetPrototype() == instance3->GetPrototype());
}

TEST_F(DictionaryTemplateTest, HoleNanCanonicalization) {
  HandleScope handle_scope(isolate());

  constexpr std::string_view property_names[] = {"a"};
  Local<DictionaryTemplate> tpl =
      DictionaryTemplate::New(isolate(), property_names);

  // 1. Create first instance with a normal double to seed the map cache with
  // double representation and const property details.
  MaybeLocal<Value> values1[1] = {v8::Number::New(isolate(), 1.25)};
  Local<Object> instance1 = tpl->NewInstance(context(), values1);
  EXPECT_FALSE(instance1.IsEmpty());

  // 2. Create second instance reusing cached map with the hole-NaN bit pattern.
  const double hole_nan = base::bit_cast<double>(i::kHoleNanInt64);
  MaybeLocal<Value> values2[1] = {v8::Number::New(isolate(), hole_nan)};
  Local<Object> instance2 = tpl->NewInstance(context(), values2);
  EXPECT_FALSE(instance2.IsEmpty());

  // The stored HeapNumber value must not have the hole-NaN bit pattern.
  auto value2 =
      instance2->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
  EXPECT_TRUE(value2->IsNumber());
  double val2 = value2.As<v8::Number>()->Value();
  EXPECT_TRUE(std::isnan(val2));
  EXPECT_NE(base::bit_cast<uint64_t>(val2), i::kHoleNanInt64);

  // The property must be mutable and not in Read-Only space.
  EXPECT_TRUE(instance2
                  ->Set(context(), v8_str(isolate(), "a"),
                        v8::Number::New(isolate(), 42.5))
                  .ToChecked());
  auto value2_mutated =
      instance2->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
  EXPECT_EQ(value2_mutated.As<v8::Number>()->Value(), 42.5);
}

}  // namespace v8
