49c507dc99
Remove cctest's ability to run multiple tests (which has long been deprecated and mostly broken). We can then make platform & V8 initialisation be part of running the test's Run method. In particular, this allows us to inject custom logic into the platform initialisation, like setting up a platform wrapper. Add a TEST_WITH_PLATFORM which exercises this by registering a platform factory on the test, and wrapping the default platform using this factory. This allows these tests to guarantee that the lifetime of the platform is longer than the lifetime of the isolate. As a result of this, we can also remove the complexity around draining platform state in the TestPlatform (since it will now have a longer lifetime than the Isolate using it), and as a drive-by clean up the TestPlaform to use a CcTest-global "default platform" instead of trying to scope over the "current" platform. As another drive-by, change the linked-list of CcTests and the linear search through it into an std::map of tests. Change-Id: I610f6312fe042f29f45cc4dfba311e4184bc7759 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3569223 Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Commit-Queue: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/main@{#79772}
283 lines
9.8 KiB
C++
283 lines
9.8 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 "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 {
|
|
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;
|
|
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() : mock_task_runner_(new MockTaskRunner()) {}
|
|
|
|
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_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.TaskPosted());
|
|
}
|
|
|
|
TEST(PartiallyInitializedJSFunction) {
|
|
LocalContext env;
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Factory* factory = isolate->factory();
|
|
HandleScope scope(isolate);
|
|
Handle<JSFunction> js_function = factory->NewFunctionForTesting(
|
|
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, Smi::uninitialized_deserialization_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, Smi::uninitialized_deserialization_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
|