// 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 "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 { Handle<NativeContext> GetNativeContext(Isolate* isolate, v8::Local<v8::Context> v8_context) { Handle<Context> context = v8::Utils::OpenHandle(*v8_context); return handle(context->native_context(), isolate); } } // anonymous namespace TEST(NativeContextInferrerGlobalObject) { LocalContext env; Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); Handle<NativeContext> native_context = GetNativeContext(isolate, env.local()); Handle<JSGlobalObject> global = handle(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); Handle<NativeContext> native_context = GetNativeContext(isolate, env.local()); v8::Local<v8::Value> result = CompileRun("(function () { return 1; })"); Handle<Object> object = Utils::OpenHandle(*result); Handle<HeapObject> function = Handle<HeapObject>::cast(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); Handle<NativeContext> native_context = GetNativeContext(isolate, env.local()); v8::Local<v8::Value> result = CompileRun("({a : 10})"); Handle<Object> object = Utils::OpenHandle(*result); Handle<HeapObject> function = Handle<HeapObject>::cast(object); NativeContextInferrer inferrer; Address inferred_context = 0; // TODO(ulan): Enable this test once we have more precise native // context inference. 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); Handle<NativeContext> native_context = GetNativeContext(isolate, env.local()); v8::Local<v8::Value> result = CompileRun("({a : 10})"); Handle<HeapObject> object = Handle<HeapObject>::cast(Utils::OpenHandle(*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); Handle<NativeContext> native_context = GetNativeContext(isolate, env.local()); v8::Local<v8::ArrayBuffer> array_buffer = v8::ArrayBuffer::New(CcTest::isolate(), 1000); Handle<JSArrayBuffer> i_array_buffer = Utils::OpenHandle(*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); Handle<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(); Handle<String> i_string = Utils::OpenHandle(*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 MockPlatform : public TestPlatform { public: MockPlatform() : TestPlatform(), mock_task_runner_(new MockTaskRunner()) { // Now that it's completely constructed, make this the current platform. i::V8::SetPlatformForTesting(this); } std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner( v8::Isolate*) override { return mock_task_runner_; } double Delay() { return mock_task_runner_->Delay(); } void PerformTask() { mock_task_runner_->PerformTask(); } bool TaskPosted() { return mock_task_runner_->TaskPosted(); } private: class MockTaskRunner : public v8::TaskRunner { public: void PostTask(std::unique_ptr<v8::Task> task) override {} void PostDelayedTask(std::unique_ptr<Task> task, double delay_in_seconds) override { task_ = std::move(task); delay_ = delay_in_seconds; } void PostIdleTask(std::unique_ptr<IdleTask> task) override { UNREACHABLE(); } bool NonNestableTasksEnabled() const override { return true; } bool NonNestableDelayedTasksEnabled() const override { return true; } bool IdleTasksEnabled() override { return false; } double Delay() { return delay_; } void PerformTask() { std::unique_ptr<Task> task = std::move(task_); task->Run(); } bool TaskPosted() { return task_.get(); } private: double delay_ = -1; std::unique_ptr<Task> task_; }; std::shared_ptr<MockTaskRunner> mock_task_runner_; }; class MockMeasureMemoryDelegate : public v8::MeasureMemoryDelegate { public: bool ShouldMeasure(v8::Local<v8::Context> context) override { return true; } void MeasurementComplete( const std::vector<std::pair<v8::Local<v8::Context>, size_t>>& context_sizes_in_bytes, size_t unattributed_size_in_bytes) override { // Empty. } }; } // namespace TEST(RandomizedTimeout) { MockPlatform platform; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); // We have to create the isolate manually here. Using CcTest::isolate() would // lead to the situation when the isolate outlives MockPlatform which may lead // to UAF on the background thread. v8::Isolate* isolate = v8::Isolate::New(create_params); 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()); isolate->Dispose(); CHECK_LT(delays[0], delays.back()); } TEST(LazyMemoryMeasurement) { CcTest::InitializeVM(); MockPlatform platform; CcTest::isolate()->MeasureMemory( std::make_unique<MockMeasureMemoryDelegate>(), v8::MeasureMemoryExecution::kLazy); CHECK(!platform.TaskPosted()); } TEST(PartiallyInitializedJSFunction) { LocalContext env; Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); HandleScope scope(isolate); Handle<JSFunction> js_function = factory->NewFunctionForTest(factory->NewStringFromAsciiChecked("test")); Handle<Context> context = handle(js_function->context(), isolate); // 1. Start simulating deserializaiton. isolate->RegisterDeserializerStarted(); // 2. Set the context field to the uninitialized sentintel. TaggedField<Object, JSFunction::kContextOffset>::store( *js_function, Deserializer::uninitialized_field_value()); // 3. Request memory meaurement 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, JSFunction::kContextOffset>::store(*js_function, *context); isolate->RegisterDeserializerFinished(); } TEST(PartiallyInitializedContext) { LocalContext env; Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); HandleScope scope(isolate); Handle<ScopeInfo> scope_info = ReadOnlyRoots(isolate).global_this_binding_scope_info_handle(); Handle<Context> context = factory->NewScriptContext( GetNativeContext(isolate, env.local()), scope_info); Handle<Map> map = handle(context->map(), isolate); Handle<NativeContext> native_context = handle(map->native_context(), isolate); // 1. Start simulating deserializaiton. isolate->RegisterDeserializerStarted(); // 2. Set the native context field to the uninitialized sentintel. TaggedField<Object, Map::kConstructorOrBackPointerOrNativeContextOffset>:: store(*map, Deserializer::uninitialized_field_value()); // 3. Request memory meaurement 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, Map::kConstructorOrBackPointerOrNativeContextOffset>:: store(*map, *native_context); isolate->RegisterDeserializerFinished(); } } // namespace heap } // namespace internal } // namespace v8