v8/test/cctest/heap/test-memory-measurement.cc
Ulan Degenbaev 32d7ec1af4 [heap] Handle partially initialized objects in NativeContextInferrer
Since GC can now happen during deserialization, object fields may
contain the Smi sentinel value instead of pointers. This adds the
required guards to methods of NativeContextInferrer

Bug: chromium:1136801
Change-Id: I7338f31bf6ee34b8dee8431b8250d2cc2978e0c2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2461241
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70425}
2020-10-09 12:43:42 +00:00

292 lines
10 KiB
C++

// 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