// Copyright 2019 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 "src/heap/memory-measurement-inl.h"
#include "src/heap/memory-measurement.h"
#include "src/objects/smi.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"

namespace v8 {
namespace internal {
namespace heap {

namespace {
DirectHandle<NativeContext> GetNativeContext(
    Isolate* isolate, v8::Local<v8::Context> v8_context) {
  DirectHandle<Context> context = v8::Utils::OpenDirectHandle(*v8_context);
  return direct_handle(context->native_context(), isolate);
}
}  // anonymous namespace

TEST(NativeContextInferrerGlobalObject) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope handle_scope(isolate);
  DirectHandle<NativeContext> native_context =
      GetNativeContext(isolate, env.local());
  DirectHandle<JSGlobalObject> global(native_context->global_object(), isolate);
  NativeContextInferrer inferrer;
  Address inferred_context = 0;
  CHECK(inferrer.Infer(isolate, global->map(), *global, &inferred_context));
  CHECK_EQ(native_context->ptr(), inferred_context);
}

TEST(NativeContextInferrerJSFunction) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope scope(isolate);
  DirectHandle<NativeContext> native_context =
      GetNativeContext(isolate, env.local());
  v8::Local<v8::Value> result = CompileRun("(function () { return 1; })");
  DirectHandle<Object> object = Utils::OpenDirectHandle(*result);
  DirectHandle<HeapObject> function = Cast<HeapObject>(object);
  NativeContextInferrer inferrer;
  Address inferred_context = 0;
  CHECK(inferrer.Infer(isolate, function->map(), *function, &inferred_context));
  CHECK_EQ(native_context->ptr(), inferred_context);
}

TEST(NativeContextInferrerJSObject) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope scope(isolate);
  DirectHandle<NativeContext> native_context =
      GetNativeContext(isolate, env.local());
  v8::Local<v8::Value> result = CompileRun("({a : 10})");
  DirectHandle<Object> object = Utils::OpenDirectHandle(*result);
  DirectHandle<HeapObject> function = Cast<HeapObject>(object);
  NativeContextInferrer inferrer;
  Address inferred_context = 0;
  CHECK(inferrer.Infer(isolate, function->map(), *function, &inferred_context));
  CHECK_EQ(native_context->ptr(), inferred_context);
}

TEST(NativeContextStatsMerge) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope scope(isolate);
  DirectHandle<NativeContext> native_context =
      GetNativeContext(isolate, env.local());
  v8::Local<v8::Value> result = CompileRun("({a : 10})");
  DirectHandle<HeapObject> object =
      Cast<HeapObject>(Utils::OpenDirectHandle(*result));
  NativeContextStats stats1, stats2;
  stats1.IncrementSize(native_context->ptr(), object->map(), *object, 10);
  stats2.IncrementSize(native_context->ptr(), object->map(), *object, 20);
  stats1.Merge(stats2);
  CHECK_EQ(30, stats1.Get(native_context->ptr()));
}

TEST(NativeContextStatsArrayBuffers) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope scope(isolate);
  DirectHandle<NativeContext> native_context =
      GetNativeContext(isolate, env.local());
  v8::Local<v8::ArrayBuffer> array_buffer =
      v8::ArrayBuffer::New(CcTest::isolate(), 1000);
  DirectHandle<JSArrayBuffer> i_array_buffer =
      Utils::OpenDirectHandle(*array_buffer);
  NativeContextStats stats;
  stats.IncrementSize(native_context->ptr(), i_array_buffer->map(),
                      *i_array_buffer, 10);
  CHECK_EQ(1010, stats.Get(native_context->ptr()));
}

namespace {

class TestResource : public v8::String::ExternalStringResource {
 public:
  explicit TestResource(uint16_t* data) : data_(data), length_(0) {
    while (data[length_]) ++length_;
  }

  ~TestResource() override { i::DeleteArray(data_); }

  const uint16_t* data() const override { return data_; }

  size_t length() const override { return length_; }

 private:
  uint16_t* data_;
  size_t length_;
};

}  // anonymous namespace

TEST(NativeContextStatsExternalString) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope scope(isolate);
  DirectHandle<NativeContext> native_context =
      GetNativeContext(isolate, env.local());
  const char* c_source = "0123456789";
  uint16_t* two_byte_source = AsciiToTwoByteString(c_source);
  TestResource* resource = new TestResource(two_byte_source);
  Local<v8::String> string =
      v8::String::NewExternalTwoByte(CcTest::isolate(), resource)
          .ToLocalChecked();
  DirectHandle<String> i_string = Utils::OpenDirectHandle(*string);
  NativeContextStats stats;
  stats.IncrementSize(native_context->ptr(), i_string->map(), *i_string, 10);
  CHECK_EQ(10 + 10 * 2, stats.Get(native_context->ptr()));
}

namespace {

class MockMeasureMemoryDelegate : public v8::MeasureMemoryDelegate {
 public:
  bool ShouldMeasure(v8::Local<v8::Context> context) override { return true; }

  void MeasurementComplete(Result result) override {
    // Empty.
  }
};

}  // namespace

TEST_WITH_PLATFORM(RandomizedTimeout, MockPlatform) {
  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
  v8::Isolate* isolate = CcTest::isolate();
  std::vector<double> delays;
  for (int i = 0; i < 10; i++) {
    isolate->MeasureMemory(std::make_unique<MockMeasureMemoryDelegate>());
    delays.push_back(platform.Delay());
    platform.PerformTask();
  }
  std::sort(delays.begin(), delays.end());
  CHECK_LT(delays[0], delays.back());
}

TEST(LazyMemoryMeasurement) {
  CcTest::InitializeVM();
  MockPlatform platform;
  CcTest::isolate()->MeasureMemory(
      std::make_unique<MockMeasureMemoryDelegate>(),
      v8::MeasureMemoryExecution::kLazy);
  CHECK(!platform.PendingTask());
}

TEST(PartiallyInitializedJSFunction) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();
  HandleScope scope(isolate);
  DirectHandle<JSFunction> js_function = factory->NewFunctionForTesting(
      factory->NewStringFromAsciiChecked("test"));
  DirectHandle<Context> context(js_function->context(), isolate);

  // 1. Start simulating deserializaiton.
  isolate->RegisterDeserializerStarted();
  // 2. Set the context field to the uninitialized sentintel.
  TaggedField<Object, offsetof(JSFunction, context_)>::store(
      *js_function, Smi::uninitialized_deserialization_value());
  // 3. Request memory measurement and run all tasks. GC that runs as part
  // of the measurement should not crash.
  CcTest::isolate()->MeasureMemory(
      std::make_unique<MockMeasureMemoryDelegate>(),
      v8::MeasureMemoryExecution::kEager);
  while (v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(),
                                       CcTest::isolate())) {
  }
  // 4. Restore the value and complete deserialization.
  TaggedField<Object, offsetof(JSFunction, context_)>::store(*js_function,
                                                             *context);
  isolate->RegisterDeserializerFinished();
}

TEST(PartiallyInitializedContext) {
  LocalContext env;
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();
  HandleScope scope(isolate);
  DirectHandle<ScopeInfo> scope_info =
      factory->global_this_binding_scope_info();
  DirectHandle<Context> context = factory->NewScriptContext(
      GetNativeContext(isolate, env.local()), scope_info);
  DirectHandle<Map> map(context->map(), isolate);
  DirectHandle<NativeContext> native_context(map->native_context(), isolate);
  // 1. Start simulating deserializaiton.
  isolate->RegisterDeserializerStarted();
  // 2. Set the native context field to the uninitialized sentintel.
  TaggedField<Object,
              offsetof(Map, constructor_or_back_pointer_or_native_context_)>::
      store(*map, Smi::uninitialized_deserialization_value());
  // 3. Request memory measurement and run all tasks. GC that runs as part
  // of the measurement should not crash.
  CcTest::isolate()->MeasureMemory(
      std::make_unique<MockMeasureMemoryDelegate>(),
      v8::MeasureMemoryExecution::kEager);
  while (v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(),
                                       CcTest::isolate())) {
  }
  // 4. Restore the value and complete deserialization.
  TaggedField<Object,
              offsetof(Map, constructor_or_back_pointer_or_native_context_)>::
      store(*map, *native_context);
  isolate->RegisterDeserializerFinished();
}

}  // namespace heap
}  // namespace internal
}  // namespace v8
