e84ac8bc3b
In a follow-up CL, the backing stores will, when the sandbox is enabled, be referenced from V8 objects through offsets rather than raw pointers. For that to work, all backing stores must be located inside the virtual memory cage. This CL prepares for that. Bug: chromium:1218005 Change-Id: Ibb989626ed7094bd4f02ca15464539f4e2bda90f Cq-Include-Trybots: luci.v8.try:v8_linux64_heap_sandbox_dbg_ng Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3114136 Commit-Queue: Samuel Groß <saelo@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Reviewed-by: Andreas Haas <ahaas@chromium.org> Cr-Commit-Position: refs/heads/main@{#76486}
768 lines
27 KiB
C++
768 lines
27 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/api/api-inl.h"
|
|
#include "src/base/strings.h"
|
|
#include "src/objects/js-array-buffer-inl.h"
|
|
#include "test/cctest/test-api.h"
|
|
|
|
using ::v8::Array;
|
|
using ::v8::Context;
|
|
using ::v8::Local;
|
|
using ::v8::Value;
|
|
|
|
namespace {
|
|
|
|
void CheckDataViewIsDetached(v8::Local<v8::DataView> dv) {
|
|
CHECK_EQ(0, static_cast<int>(dv->ByteLength()));
|
|
CHECK_EQ(0, static_cast<int>(dv->ByteOffset()));
|
|
}
|
|
|
|
void CheckIsDetached(v8::Local<v8::TypedArray> ta) {
|
|
CHECK_EQ(0, static_cast<int>(ta->ByteLength()));
|
|
CHECK_EQ(0, static_cast<int>(ta->Length()));
|
|
CHECK_EQ(0, static_cast<int>(ta->ByteOffset()));
|
|
}
|
|
|
|
void CheckIsTypedArrayVarDetached(const char* name) {
|
|
v8::base::ScopedVector<char> source(1024);
|
|
v8::base::SNPrintF(
|
|
source, "%s.byteLength == 0 && %s.byteOffset == 0 && %s.length == 0",
|
|
name, name, name);
|
|
CHECK(CompileRun(source.begin())->IsTrue());
|
|
v8::Local<v8::TypedArray> ta = CompileRun(name).As<v8::TypedArray>();
|
|
CheckIsDetached(ta);
|
|
}
|
|
|
|
template <typename TypedArray, int kElementSize>
|
|
Local<TypedArray> CreateAndCheck(Local<v8::ArrayBuffer> ab, int byteOffset,
|
|
int length) {
|
|
v8::Local<TypedArray> ta = TypedArray::New(ab, byteOffset, length);
|
|
CheckInternalFieldsAreZero<v8::ArrayBufferView>(ta);
|
|
CHECK_EQ(byteOffset, static_cast<int>(ta->ByteOffset()));
|
|
CHECK_EQ(length, static_cast<int>(ta->Length()));
|
|
CHECK_EQ(length * kElementSize, static_cast<int>(ta->ByteLength()));
|
|
return ta;
|
|
}
|
|
|
|
std::shared_ptr<v8::BackingStore> Externalize(Local<v8::ArrayBuffer> ab) {
|
|
std::shared_ptr<v8::BackingStore> backing_store = ab->GetBackingStore();
|
|
return backing_store;
|
|
}
|
|
|
|
std::shared_ptr<v8::BackingStore> Externalize(Local<v8::SharedArrayBuffer> ab) {
|
|
std::shared_ptr<v8::BackingStore> backing_store = ab->GetBackingStore();
|
|
return backing_store;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
THREADED_TEST(ArrayBuffer_ApiInternalToExternal) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 1024);
|
|
CheckInternalFieldsAreZero(ab);
|
|
CHECK_EQ(1024, ab->ByteLength());
|
|
CcTest::CollectAllGarbage();
|
|
|
|
std::shared_ptr<v8::BackingStore> backing_store = Externalize(ab);
|
|
CHECK_EQ(1024, backing_store->ByteLength());
|
|
|
|
uint8_t* data = static_cast<uint8_t*>(backing_store->Data());
|
|
CHECK_NOT_NULL(data);
|
|
CHECK(env->Global()->Set(env.local(), v8_str("ab"), ab).FromJust());
|
|
|
|
v8::Local<v8::Value> result = CompileRun("ab.byteLength");
|
|
CHECK_EQ(1024, result->Int32Value(env.local()).FromJust());
|
|
|
|
result = CompileRun(
|
|
"var u8 = new Uint8Array(ab);"
|
|
"u8[0] = 0xFF;"
|
|
"u8[1] = 0xAA;"
|
|
"u8.length");
|
|
CHECK_EQ(1024, result->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0xFF, data[0]);
|
|
CHECK_EQ(0xAA, data[1]);
|
|
data[0] = 0xCC;
|
|
data[1] = 0x11;
|
|
result = CompileRun("u8[0] + u8[1]");
|
|
CHECK_EQ(0xDD, result->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
THREADED_TEST(ArrayBuffer_JSInternalToExternal) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"var ab1 = new ArrayBuffer(2);"
|
|
"var u8_a = new Uint8Array(ab1);"
|
|
"u8_a[0] = 0xAA;"
|
|
"u8_a[1] = 0xFF; u8_a.buffer");
|
|
Local<v8::ArrayBuffer> ab1 = result.As<v8::ArrayBuffer>();
|
|
CheckInternalFieldsAreZero(ab1);
|
|
CHECK_EQ(2, ab1->ByteLength());
|
|
std::shared_ptr<v8::BackingStore> backing_store = Externalize(ab1);
|
|
|
|
result = CompileRun("ab1.byteLength");
|
|
CHECK_EQ(2, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun("u8_a[0]");
|
|
CHECK_EQ(0xAA, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun("u8_a[1]");
|
|
CHECK_EQ(0xFF, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun(
|
|
"var u8_b = new Uint8Array(ab1);"
|
|
"u8_b[0] = 0xBB;"
|
|
"u8_a[0]");
|
|
CHECK_EQ(0xBB, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun("u8_b[1]");
|
|
CHECK_EQ(0xFF, result->Int32Value(env.local()).FromJust());
|
|
|
|
CHECK_EQ(2, backing_store->ByteLength());
|
|
uint8_t* ab1_data = static_cast<uint8_t*>(backing_store->Data());
|
|
CHECK_EQ(0xBB, ab1_data[0]);
|
|
CHECK_EQ(0xFF, ab1_data[1]);
|
|
ab1_data[0] = 0xCC;
|
|
ab1_data[1] = 0x11;
|
|
result = CompileRun("u8_a[0] + u8_a[1]");
|
|
CHECK_EQ(0xDD, result->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
THREADED_TEST(ArrayBuffer_DisableDetach) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
|
|
CHECK(ab->IsDetachable());
|
|
|
|
i::Handle<i::JSArrayBuffer> buf = v8::Utils::OpenHandle(*ab);
|
|
buf->set_is_detachable(false);
|
|
|
|
CHECK(!ab->IsDetachable());
|
|
}
|
|
|
|
THREADED_TEST(ArrayBuffer_DetachingApi) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, 1024);
|
|
|
|
v8::Local<v8::Uint8Array> u8a =
|
|
CreateAndCheck<v8::Uint8Array, 1>(buffer, 1, 1023);
|
|
v8::Local<v8::Uint8ClampedArray> u8c =
|
|
CreateAndCheck<v8::Uint8ClampedArray, 1>(buffer, 1, 1023);
|
|
v8::Local<v8::Int8Array> i8a =
|
|
CreateAndCheck<v8::Int8Array, 1>(buffer, 1, 1023);
|
|
|
|
v8::Local<v8::Uint16Array> u16a =
|
|
CreateAndCheck<v8::Uint16Array, 2>(buffer, 2, 511);
|
|
v8::Local<v8::Int16Array> i16a =
|
|
CreateAndCheck<v8::Int16Array, 2>(buffer, 2, 511);
|
|
|
|
v8::Local<v8::Uint32Array> u32a =
|
|
CreateAndCheck<v8::Uint32Array, 4>(buffer, 4, 255);
|
|
v8::Local<v8::Int32Array> i32a =
|
|
CreateAndCheck<v8::Int32Array, 4>(buffer, 4, 255);
|
|
|
|
v8::Local<v8::Float32Array> f32a =
|
|
CreateAndCheck<v8::Float32Array, 4>(buffer, 4, 255);
|
|
v8::Local<v8::Float64Array> f64a =
|
|
CreateAndCheck<v8::Float64Array, 8>(buffer, 8, 127);
|
|
|
|
v8::Local<v8::DataView> dv = v8::DataView::New(buffer, 1, 1023);
|
|
CheckInternalFieldsAreZero<v8::ArrayBufferView>(dv);
|
|
CHECK_EQ(1, dv->ByteOffset());
|
|
CHECK_EQ(1023, dv->ByteLength());
|
|
|
|
Externalize(buffer);
|
|
buffer->Detach();
|
|
CHECK_EQ(0, buffer->ByteLength());
|
|
CheckIsDetached(u8a);
|
|
CheckIsDetached(u8c);
|
|
CheckIsDetached(i8a);
|
|
CheckIsDetached(u16a);
|
|
CheckIsDetached(i16a);
|
|
CheckIsDetached(u32a);
|
|
CheckIsDetached(i32a);
|
|
CheckIsDetached(f32a);
|
|
CheckIsDetached(f64a);
|
|
CheckDataViewIsDetached(dv);
|
|
}
|
|
|
|
THREADED_TEST(ArrayBuffer_DetachingScript) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
CompileRun(
|
|
"var ab = new ArrayBuffer(1024);"
|
|
"var u8a = new Uint8Array(ab, 1, 1023);"
|
|
"var u8c = new Uint8ClampedArray(ab, 1, 1023);"
|
|
"var i8a = new Int8Array(ab, 1, 1023);"
|
|
"var u16a = new Uint16Array(ab, 2, 511);"
|
|
"var i16a = new Int16Array(ab, 2, 511);"
|
|
"var u32a = new Uint32Array(ab, 4, 255);"
|
|
"var i32a = new Int32Array(ab, 4, 255);"
|
|
"var f32a = new Float32Array(ab, 4, 255);"
|
|
"var f64a = new Float64Array(ab, 8, 127);"
|
|
"var dv = new DataView(ab, 1, 1023);");
|
|
|
|
v8::Local<v8::ArrayBuffer> ab = CompileRun("ab").As<v8::ArrayBuffer>();
|
|
v8::Local<v8::DataView> dv = CompileRun("dv").As<v8::DataView>();
|
|
|
|
Externalize(ab);
|
|
ab->Detach();
|
|
CHECK_EQ(0, ab->ByteLength());
|
|
CHECK_EQ(0, v8_run_int32value(v8_compile("ab.byteLength")));
|
|
|
|
CheckIsTypedArrayVarDetached("u8a");
|
|
CheckIsTypedArrayVarDetached("u8c");
|
|
CheckIsTypedArrayVarDetached("i8a");
|
|
CheckIsTypedArrayVarDetached("u16a");
|
|
CheckIsTypedArrayVarDetached("i16a");
|
|
CheckIsTypedArrayVarDetached("u32a");
|
|
CheckIsTypedArrayVarDetached("i32a");
|
|
CheckIsTypedArrayVarDetached("f32a");
|
|
CheckIsTypedArrayVarDetached("f64a");
|
|
|
|
CHECK(CompileRun("dv.byteLength == 0 && dv.byteOffset == 0")->IsTrue());
|
|
CheckDataViewIsDetached(dv);
|
|
}
|
|
|
|
THREADED_TEST(ArrayBuffer_ExternalizeEmpty) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 2);
|
|
CheckInternalFieldsAreZero(ab);
|
|
CHECK_EQ(2, ab->ByteLength());
|
|
|
|
// Externalize the buffer (taking ownership of the backing store memory).
|
|
std::shared_ptr<v8::BackingStore> backing_store = Externalize(ab);
|
|
|
|
Local<v8::Uint8Array> u8a = v8::Uint8Array::New(ab, 0, 0);
|
|
// Calling Buffer() will materialize the ArrayBuffer (transitioning it from
|
|
// on-heap to off-heap if need be). This should not affect whether it is
|
|
// marked as is_external or not.
|
|
USE(u8a->Buffer());
|
|
|
|
CHECK_EQ(2, backing_store->ByteLength());
|
|
}
|
|
|
|
THREADED_TEST(SharedArrayBuffer_ApiInternalToExternal) {
|
|
i::FLAG_harmony_sharedarraybuffer = true;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::SharedArrayBuffer> ab = v8::SharedArrayBuffer::New(isolate, 1024);
|
|
CheckInternalFieldsAreZero(ab);
|
|
CHECK_EQ(1024, ab->ByteLength());
|
|
CcTest::CollectAllGarbage();
|
|
|
|
std::shared_ptr<v8::BackingStore> backing_store = Externalize(ab);
|
|
|
|
CHECK_EQ(1024, backing_store->ByteLength());
|
|
uint8_t* data = static_cast<uint8_t*>(backing_store->Data());
|
|
CHECK_NOT_NULL(data);
|
|
CHECK(env->Global()->Set(env.local(), v8_str("ab"), ab).FromJust());
|
|
|
|
v8::Local<v8::Value> result = CompileRun("ab.byteLength");
|
|
CHECK_EQ(1024, result->Int32Value(env.local()).FromJust());
|
|
|
|
result = CompileRun(
|
|
"var u8 = new Uint8Array(ab);"
|
|
"u8[0] = 0xFF;"
|
|
"u8[1] = 0xAA;"
|
|
"u8.length");
|
|
CHECK_EQ(1024, result->Int32Value(env.local()).FromJust());
|
|
CHECK_EQ(0xFF, data[0]);
|
|
CHECK_EQ(0xAA, data[1]);
|
|
data[0] = 0xCC;
|
|
data[1] = 0x11;
|
|
result = CompileRun("u8[0] + u8[1]");
|
|
CHECK_EQ(0xDD, result->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
THREADED_TEST(SharedArrayBuffer_JSInternalToExternal) {
|
|
i::FLAG_harmony_sharedarraybuffer = true;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
v8::Local<v8::Value> result = CompileRun(
|
|
"var ab1 = new SharedArrayBuffer(2);"
|
|
"var u8_a = new Uint8Array(ab1);"
|
|
"u8_a[0] = 0xAA;"
|
|
"u8_a[1] = 0xFF; u8_a.buffer");
|
|
Local<v8::SharedArrayBuffer> ab1 = result.As<v8::SharedArrayBuffer>();
|
|
CheckInternalFieldsAreZero(ab1);
|
|
CHECK_EQ(2, ab1->ByteLength());
|
|
CHECK(!ab1->IsExternal());
|
|
std::shared_ptr<v8::BackingStore> backing_store = Externalize(ab1);
|
|
|
|
result = CompileRun("ab1.byteLength");
|
|
CHECK_EQ(2, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun("u8_a[0]");
|
|
CHECK_EQ(0xAA, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun("u8_a[1]");
|
|
CHECK_EQ(0xFF, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun(
|
|
"var u8_b = new Uint8Array(ab1);"
|
|
"u8_b[0] = 0xBB;"
|
|
"u8_a[0]");
|
|
CHECK_EQ(0xBB, result->Int32Value(env.local()).FromJust());
|
|
result = CompileRun("u8_b[1]");
|
|
CHECK_EQ(0xFF, result->Int32Value(env.local()).FromJust());
|
|
|
|
CHECK_EQ(2, backing_store->ByteLength());
|
|
uint8_t* ab1_data = static_cast<uint8_t*>(backing_store->Data());
|
|
CHECK_EQ(0xBB, ab1_data[0]);
|
|
CHECK_EQ(0xFF, ab1_data[1]);
|
|
ab1_data[0] = 0xCC;
|
|
ab1_data[1] = 0x11;
|
|
result = CompileRun("u8_a[0] + u8_a[1]");
|
|
CHECK_EQ(0xDD, result->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
THREADED_TEST(SkipArrayBufferBackingStoreDuringGC) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
void* buffer = CcTest::array_buffer_allocator()->Allocate(100);
|
|
// Make sure the pointer looks like a heap object
|
|
uintptr_t address = reinterpret_cast<uintptr_t>(buffer) | i::kHeapObjectTag;
|
|
void* store_ptr = reinterpret_cast<void*>(address);
|
|
auto backing_store = v8::ArrayBuffer::NewBackingStore(
|
|
store_ptr, 8, [](void*, size_t, void*) {}, nullptr);
|
|
|
|
// Create ArrayBuffer with pointer-that-cannot-be-visited in the backing store
|
|
Local<v8::ArrayBuffer> ab =
|
|
v8::ArrayBuffer::New(isolate, std::move(backing_store));
|
|
|
|
// Should not crash
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
CcTest::CollectAllGarbage();
|
|
CcTest::CollectAllGarbage();
|
|
|
|
// Should not move the pointer
|
|
CHECK_EQ(ab->GetBackingStore()->Data(), store_ptr);
|
|
|
|
CcTest::array_buffer_allocator()->Free(buffer, 100);
|
|
}
|
|
|
|
THREADED_TEST(SkipArrayBufferDuringScavenge) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
// Make sure the pointer looks like a heap object
|
|
Local<v8::Object> tmp = v8::Object::New(isolate);
|
|
uint8_t* store_ptr =
|
|
reinterpret_cast<uint8_t*>(*reinterpret_cast<uintptr_t*>(*tmp));
|
|
auto backing_store = v8::ArrayBuffer::NewBackingStore(
|
|
store_ptr, 8, [](void*, size_t, void*) {}, nullptr);
|
|
|
|
// Make `store_ptr` point to from space
|
|
CcTest::CollectGarbage(i::NEW_SPACE);
|
|
|
|
// Create ArrayBuffer with pointer-that-cannot-be-visited in the backing store
|
|
Local<v8::ArrayBuffer> ab =
|
|
v8::ArrayBuffer::New(isolate, std::move(backing_store));
|
|
|
|
// Should not crash,
|
|
// i.e. backing store pointer should not be treated as a heap object pointer
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in survivor space now
|
|
CcTest::CollectGarbage(i::NEW_SPACE); // in old gen now
|
|
|
|
// Use `ab` to silence compiler warning
|
|
CHECK_EQ(ab->GetBackingStore()->Data(), store_ptr);
|
|
}
|
|
|
|
THREADED_TEST(Regress1006600) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
|
|
Local<v8::Value> ab = CompileRunChecked(isolate, "new ArrayBuffer()");
|
|
for (int i = 0; i < v8::ArrayBuffer::kEmbedderFieldCount; i++) {
|
|
CHECK_NULL(ab.As<v8::Object>()->GetAlignedPointerFromInternalField(i));
|
|
}
|
|
}
|
|
|
|
THREADED_TEST(ArrayBuffer_NewBackingStore) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
std::shared_ptr<v8::BackingStore> backing_store =
|
|
v8::ArrayBuffer::NewBackingStore(isolate, 100);
|
|
CHECK(!backing_store->IsShared());
|
|
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, backing_store);
|
|
CHECK_EQ(backing_store.get(), ab->GetBackingStore().get());
|
|
}
|
|
|
|
THREADED_TEST(SharedArrayBuffer_NewBackingStore) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
std::shared_ptr<v8::BackingStore> backing_store =
|
|
v8::SharedArrayBuffer::NewBackingStore(isolate, 100);
|
|
CHECK(backing_store->IsShared());
|
|
Local<v8::SharedArrayBuffer> ab =
|
|
v8::SharedArrayBuffer::New(isolate, backing_store);
|
|
CHECK_EQ(backing_store.get(), ab->GetBackingStore().get());
|
|
}
|
|
|
|
static void* backing_store_custom_data = nullptr;
|
|
static size_t backing_store_custom_length = 0;
|
|
static bool backing_store_custom_called = false;
|
|
const intptr_t backing_store_custom_deleter_data = 1234567;
|
|
|
|
static void BackingStoreCustomDeleter(void* data, size_t length,
|
|
void* deleter_data) {
|
|
CHECK(!backing_store_custom_called);
|
|
CHECK_EQ(backing_store_custom_data, data);
|
|
CHECK_EQ(backing_store_custom_length, length);
|
|
CHECK_EQ(backing_store_custom_deleter_data,
|
|
reinterpret_cast<intptr_t>(deleter_data));
|
|
CcTest::array_buffer_allocator()->Free(data, length);
|
|
backing_store_custom_called = true;
|
|
}
|
|
|
|
TEST(ArrayBuffer_NewBackingStore_CustomDeleter) {
|
|
{
|
|
// Create and destroy a backing store.
|
|
backing_store_custom_called = false;
|
|
backing_store_custom_data = CcTest::array_buffer_allocator()->Allocate(100);
|
|
backing_store_custom_length = 100;
|
|
v8::ArrayBuffer::NewBackingStore(
|
|
backing_store_custom_data, backing_store_custom_length,
|
|
BackingStoreCustomDeleter,
|
|
reinterpret_cast<void*>(backing_store_custom_deleter_data));
|
|
}
|
|
CHECK(backing_store_custom_called);
|
|
}
|
|
|
|
TEST(SharedArrayBuffer_NewBackingStore_CustomDeleter) {
|
|
{
|
|
// Create and destroy a backing store.
|
|
backing_store_custom_called = false;
|
|
backing_store_custom_data = CcTest::array_buffer_allocator()->Allocate(100);
|
|
backing_store_custom_length = 100;
|
|
v8::SharedArrayBuffer::NewBackingStore(
|
|
backing_store_custom_data, backing_store_custom_length,
|
|
BackingStoreCustomDeleter,
|
|
reinterpret_cast<void*>(backing_store_custom_deleter_data));
|
|
}
|
|
CHECK(backing_store_custom_called);
|
|
}
|
|
|
|
TEST(ArrayBuffer_NewBackingStore_EmptyDeleter) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
size_t size = 100;
|
|
void* buffer = CcTest::array_buffer_allocator()->Allocate(size);
|
|
std::unique_ptr<v8::BackingStore> backing_store =
|
|
v8::ArrayBuffer::NewBackingStore(buffer, size,
|
|
v8::BackingStore::EmptyDeleter, nullptr);
|
|
uint64_t external_memory_before =
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(0);
|
|
v8::ArrayBuffer::New(isolate, std::move(backing_store));
|
|
uint64_t external_memory_after =
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(0);
|
|
// The ArrayBuffer constructor does not increase the external memory counter.
|
|
// The counter may decrease however if the allocation triggers GC.
|
|
CHECK_GE(external_memory_before, external_memory_after);
|
|
CcTest::array_buffer_allocator()->Free(buffer, size);
|
|
}
|
|
|
|
TEST(SharedArrayBuffer_NewBackingStore_EmptyDeleter) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
size_t size = 100;
|
|
void* buffer = CcTest::array_buffer_allocator()->Allocate(size);
|
|
std::unique_ptr<v8::BackingStore> backing_store =
|
|
v8::SharedArrayBuffer::NewBackingStore(
|
|
buffer, size, v8::BackingStore::EmptyDeleter, nullptr);
|
|
uint64_t external_memory_before =
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(0);
|
|
v8::SharedArrayBuffer::New(isolate, std::move(backing_store));
|
|
uint64_t external_memory_after =
|
|
isolate->AdjustAmountOfExternalAllocatedMemory(0);
|
|
// The SharedArrayBuffer constructor does not increase the external memory
|
|
// counter. The counter may decrease however if the allocation triggers GC.
|
|
CHECK_GE(external_memory_before, external_memory_after);
|
|
CcTest::array_buffer_allocator()->Free(buffer, size);
|
|
}
|
|
|
|
THREADED_TEST(BackingStore_NotShared) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 8);
|
|
CHECK(!ab->GetBackingStore()->IsShared());
|
|
CHECK(!v8::ArrayBuffer::NewBackingStore(isolate, 8)->IsShared());
|
|
backing_store_custom_called = false;
|
|
backing_store_custom_data = CcTest::array_buffer_allocator()->Allocate(100);
|
|
backing_store_custom_length = 100;
|
|
CHECK(!v8::ArrayBuffer::NewBackingStore(
|
|
backing_store_custom_data, backing_store_custom_length,
|
|
BackingStoreCustomDeleter,
|
|
reinterpret_cast<void*>(backing_store_custom_deleter_data))
|
|
->IsShared());
|
|
}
|
|
|
|
THREADED_TEST(BackingStore_Shared) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
Local<v8::SharedArrayBuffer> ab = v8::SharedArrayBuffer::New(isolate, 8);
|
|
CHECK(ab->GetBackingStore()->IsShared());
|
|
CHECK(v8::SharedArrayBuffer::NewBackingStore(isolate, 8)->IsShared());
|
|
backing_store_custom_called = false;
|
|
backing_store_custom_data = CcTest::array_buffer_allocator()->Allocate(100);
|
|
backing_store_custom_length = 100;
|
|
CHECK(v8::SharedArrayBuffer::NewBackingStore(
|
|
backing_store_custom_data, backing_store_custom_length,
|
|
BackingStoreCustomDeleter,
|
|
reinterpret_cast<void*>(backing_store_custom_deleter_data))
|
|
->IsShared());
|
|
}
|
|
|
|
THREADED_TEST(ArrayBuffer_NewBackingStore_NullData) {
|
|
// This test creates a BackingStore with nullptr as data. The test then
|
|
// creates an ArrayBuffer and a TypedArray from this BackingStore. Writing
|
|
// into that TypedArray at index 0 is expected to be a no-op, reading from
|
|
// that TypedArray at index 0 should result in the default value '0'.
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
std::unique_ptr<v8::BackingStore> backing_store =
|
|
v8::ArrayBuffer::NewBackingStore(nullptr, 0,
|
|
v8::BackingStore::EmptyDeleter, nullptr);
|
|
v8::Local<v8::ArrayBuffer> buffer =
|
|
v8::ArrayBuffer::New(isolate, std::move(backing_store));
|
|
|
|
CHECK(env->Global()->Set(env.local(), v8_str("buffer"), buffer).FromJust());
|
|
|
|
v8::Local<v8::Value> result =
|
|
CompileRunChecked(isolate,
|
|
"const view = new Int32Array(buffer);"
|
|
"view[0] = 14;"
|
|
"view[0];");
|
|
CHECK_EQ(0, result->Int32Value(env.local()).FromJust());
|
|
}
|
|
|
|
class DummyAllocator final : public v8::ArrayBuffer::Allocator {
|
|
public:
|
|
DummyAllocator() : allocator_(NewDefaultAllocator()) {}
|
|
|
|
~DummyAllocator() override { CHECK_EQ(allocation_count(), 0); }
|
|
|
|
void* Allocate(size_t length) override {
|
|
allocation_count_++;
|
|
return allocator_->Allocate(length);
|
|
}
|
|
void* AllocateUninitialized(size_t length) override {
|
|
allocation_count_++;
|
|
return allocator_->AllocateUninitialized(length);
|
|
}
|
|
void Free(void* data, size_t length) override {
|
|
allocation_count_--;
|
|
allocator_->Free(data, length);
|
|
}
|
|
|
|
uint64_t allocation_count() const { return allocation_count_; }
|
|
|
|
private:
|
|
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator_;
|
|
uint64_t allocation_count_ = 0;
|
|
};
|
|
|
|
TEST(BackingStore_HoldAllocatorAlive_UntilIsolateShutdown) {
|
|
std::shared_ptr<DummyAllocator> allocator =
|
|
std::make_shared<DummyAllocator>();
|
|
std::weak_ptr<DummyAllocator> allocator_weak(allocator);
|
|
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator_shared = allocator;
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
|
|
allocator.reset();
|
|
create_params.array_buffer_allocator_shared.reset();
|
|
CHECK(!allocator_weak.expired());
|
|
CHECK_EQ(allocator_weak.lock()->allocation_count(), 0);
|
|
|
|
{
|
|
// Create an ArrayBuffer and do not garbage collect it. This should make
|
|
// the allocator be released automatically once the Isolate is disposed.
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Context::Scope context_scope(Context::New(isolate));
|
|
v8::ArrayBuffer::New(isolate, 8);
|
|
|
|
// This should be inside the HandleScope, so that we can be sure that
|
|
// the allocation is not garbage collected yet.
|
|
CHECK(!allocator_weak.expired());
|
|
CHECK_EQ(allocator_weak.lock()->allocation_count(), 1);
|
|
}
|
|
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
CHECK(allocator_weak.expired());
|
|
}
|
|
|
|
TEST(BackingStore_HoldAllocatorAlive_AfterIsolateShutdown) {
|
|
std::shared_ptr<DummyAllocator> allocator =
|
|
std::make_shared<DummyAllocator>();
|
|
std::weak_ptr<DummyAllocator> allocator_weak(allocator);
|
|
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator_shared = allocator;
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
|
|
allocator.reset();
|
|
create_params.array_buffer_allocator_shared.reset();
|
|
CHECK(!allocator_weak.expired());
|
|
CHECK_EQ(allocator_weak.lock()->allocation_count(), 0);
|
|
|
|
std::shared_ptr<v8::BackingStore> backing_store;
|
|
{
|
|
// Create an ArrayBuffer and do not garbage collect it. This should make
|
|
// the allocator be released automatically once the Isolate is disposed.
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Context::Scope context_scope(Context::New(isolate));
|
|
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 8);
|
|
backing_store = ab->GetBackingStore();
|
|
}
|
|
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
CHECK(!allocator_weak.expired());
|
|
CHECK_EQ(allocator_weak.lock()->allocation_count(), 1);
|
|
backing_store.reset();
|
|
CHECK(allocator_weak.expired());
|
|
}
|
|
|
|
class NullptrAllocator final : public v8::ArrayBuffer::Allocator {
|
|
public:
|
|
void* Allocate(size_t length) override {
|
|
CHECK_EQ(length, 0);
|
|
return nullptr;
|
|
}
|
|
void* AllocateUninitialized(size_t length) override {
|
|
CHECK_EQ(length, 0);
|
|
return nullptr;
|
|
}
|
|
void Free(void* data, size_t length) override { CHECK_EQ(data, nullptr); }
|
|
};
|
|
|
|
TEST(BackingStore_ReleaseAllocator_NullptrBackingStore) {
|
|
std::shared_ptr<NullptrAllocator> allocator =
|
|
std::make_shared<NullptrAllocator>();
|
|
std::weak_ptr<NullptrAllocator> allocator_weak(allocator);
|
|
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator_shared = allocator;
|
|
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
|
isolate->Enter();
|
|
|
|
allocator.reset();
|
|
create_params.array_buffer_allocator_shared.reset();
|
|
CHECK(!allocator_weak.expired());
|
|
|
|
{
|
|
std::shared_ptr<v8::BackingStore> backing_store =
|
|
v8::ArrayBuffer::NewBackingStore(isolate, 0);
|
|
// This should release a reference to the allocator, even though the
|
|
// buffer is empty/nullptr.
|
|
backing_store.reset();
|
|
}
|
|
|
|
isolate->Exit();
|
|
isolate->Dispose();
|
|
CHECK(allocator_weak.expired());
|
|
}
|
|
|
|
TEST(BackingStore_ReallocateExpand) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
std::unique_ptr<v8::BackingStore> backing_store =
|
|
v8::ArrayBuffer::NewBackingStore(isolate, 10);
|
|
{
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(
|
|
reinterpret_cast<uintptr_t>(backing_store->Data()));
|
|
for (uint8_t i = 0; i < 10; i++) {
|
|
data[i] = i;
|
|
}
|
|
}
|
|
std::unique_ptr<v8::BackingStore> new_backing_store =
|
|
v8::BackingStore::Reallocate(isolate, std::move(backing_store), 20);
|
|
CHECK_EQ(new_backing_store->ByteLength(), 20);
|
|
CHECK(!new_backing_store->IsShared());
|
|
{
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(
|
|
reinterpret_cast<uintptr_t>(new_backing_store->Data()));
|
|
for (uint8_t i = 0; i < 10; i++) {
|
|
CHECK_EQ(data[i], i);
|
|
}
|
|
for (uint8_t i = 10; i < 20; i++) {
|
|
CHECK_EQ(data[i], 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(BackingStore_ReallocateShrink) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
std::unique_ptr<v8::BackingStore> backing_store =
|
|
v8::ArrayBuffer::NewBackingStore(isolate, 20);
|
|
{
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(backing_store->Data());
|
|
for (uint8_t i = 0; i < 20; i++) {
|
|
data[i] = i;
|
|
}
|
|
}
|
|
std::unique_ptr<v8::BackingStore> new_backing_store =
|
|
v8::BackingStore::Reallocate(isolate, std::move(backing_store), 10);
|
|
CHECK_EQ(new_backing_store->ByteLength(), 10);
|
|
CHECK(!new_backing_store->IsShared());
|
|
{
|
|
uint8_t* data = reinterpret_cast<uint8_t*>(new_backing_store->Data());
|
|
for (uint8_t i = 0; i < 10; i++) {
|
|
CHECK_EQ(data[i], i);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(BackingStore_ReallocateNotShared) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
std::unique_ptr<v8::BackingStore> backing_store =
|
|
v8::ArrayBuffer::NewBackingStore(isolate, 20);
|
|
std::unique_ptr<v8::BackingStore> new_backing_store =
|
|
v8::BackingStore::Reallocate(isolate, std::move(backing_store), 10);
|
|
CHECK(!new_backing_store->IsShared());
|
|
}
|
|
|
|
TEST(BackingStore_ReallocateShared) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
std::unique_ptr<v8::BackingStore> backing_store =
|
|
v8::SharedArrayBuffer::NewBackingStore(isolate, 20);
|
|
std::unique_ptr<v8::BackingStore> new_backing_store =
|
|
v8::BackingStore::Reallocate(isolate, std::move(backing_store), 10);
|
|
CHECK(new_backing_store->IsShared());
|
|
}
|