// 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 dv) { CHECK_EQ(0, static_cast(dv->ByteLength())); CHECK_EQ(0, static_cast(dv->ByteOffset())); } void CheckIsDetached(v8::Local ta) { CHECK_EQ(0, static_cast(ta->ByteLength())); CHECK_EQ(0, static_cast(ta->Length())); CHECK_EQ(0, static_cast(ta->ByteOffset())); } void CheckIsTypedArrayVarDetached(const char* name) { v8::base::ScopedVector 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 ta = CompileRun(name).As(); CheckIsDetached(ta); } template Local CreateAndCheck(Local ab, int byteOffset, int length) { v8::Local ta = TypedArray::New(ab, byteOffset, length); CheckInternalFieldsAreZero(ta); CHECK_EQ(byteOffset, static_cast(ta->ByteOffset())); CHECK_EQ(length, static_cast(ta->Length())); CHECK_EQ(length * kElementSize, static_cast(ta->ByteLength())); return ta; } std::shared_ptr Externalize(Local ab) { std::shared_ptr backing_store = ab->GetBackingStore(); return backing_store; } std::shared_ptr Externalize(Local ab) { std::shared_ptr 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 ab = v8::ArrayBuffer::New(isolate, 1024); CheckInternalFieldsAreZero(ab); CHECK_EQ(1024, ab->ByteLength()); CcTest::CollectAllGarbage(); std::shared_ptr backing_store = Externalize(ab); CHECK_EQ(1024, backing_store->ByteLength()); uint8_t* data = static_cast(backing_store->Data()); CHECK_NOT_NULL(data); CHECK(env->Global()->Set(env.local(), v8_str("ab"), ab).FromJust()); v8::Local 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 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 ab1 = result.As(); CheckInternalFieldsAreZero(ab1); CHECK_EQ(2, ab1->ByteLength()); std::shared_ptr 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(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 ab = v8::ArrayBuffer::New(isolate, 100); CHECK(ab->IsDetachable()); i::Handle 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 buffer = v8::ArrayBuffer::New(isolate, 1024); v8::Local u8a = CreateAndCheck(buffer, 1, 1023); v8::Local u8c = CreateAndCheck(buffer, 1, 1023); v8::Local i8a = CreateAndCheck(buffer, 1, 1023); v8::Local u16a = CreateAndCheck(buffer, 2, 511); v8::Local i16a = CreateAndCheck(buffer, 2, 511); v8::Local u32a = CreateAndCheck(buffer, 4, 255); v8::Local i32a = CreateAndCheck(buffer, 4, 255); v8::Local f32a = CreateAndCheck(buffer, 4, 255); v8::Local f64a = CreateAndCheck(buffer, 8, 127); v8::Local dv = v8::DataView::New(buffer, 1, 1023); CheckInternalFieldsAreZero(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 ab = CompileRun("ab").As(); v8::Local dv = CompileRun("dv").As(); 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"); { v8::TryCatch try_catch(isolate); CompileRun("dv.byteLength == 0 "); CHECK(try_catch.HasCaught()); } { v8::TryCatch try_catch(isolate); CompileRun("dv.byteOffset == 0"); CHECK(try_catch.HasCaught()); } CheckDataViewIsDetached(dv); } THREADED_TEST(ArrayBuffer_ExternalizeEmpty) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); Local 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 backing_store = Externalize(ab); Local 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 ab = v8::SharedArrayBuffer::New(isolate, 1024); CheckInternalFieldsAreZero(ab); CHECK_EQ(1024, ab->ByteLength()); CcTest::CollectAllGarbage(); std::shared_ptr backing_store = Externalize(ab); CHECK_EQ(1024, backing_store->ByteLength()); uint8_t* data = static_cast(backing_store->Data()); CHECK_NOT_NULL(data); CHECK(env->Global()->Set(env.local(), v8_str("ab"), ab).FromJust()); v8::Local 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 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 ab1 = result.As(); CheckInternalFieldsAreZero(ab1); CHECK_EQ(2, ab1->ByteLength()); CHECK(!ab1->IsExternal()); std::shared_ptr 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(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(buffer) | i::kHeapObjectTag; void* store_ptr = reinterpret_cast(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 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 tmp = v8::Object::New(isolate); uint8_t* store_ptr = reinterpret_cast(*reinterpret_cast(*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 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 ab = CompileRunChecked(isolate, "new ArrayBuffer()"); for (int i = 0; i < v8::ArrayBuffer::kEmbedderFieldCount; i++) { CHECK_NULL(ab.As()->GetAlignedPointerFromInternalField(i)); } } THREADED_TEST(ArrayBuffer_NewBackingStore) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); std::shared_ptr backing_store = v8::ArrayBuffer::NewBackingStore(isolate, 100); CHECK(!backing_store->IsShared()); Local 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 backing_store = v8::SharedArrayBuffer::NewBackingStore(isolate, 100); CHECK(backing_store->IsShared()); Local 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(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(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(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 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 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 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(backing_store_custom_deleter_data)) ->IsShared()); } THREADED_TEST(BackingStore_Shared) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); Local 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(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 backing_store = v8::ArrayBuffer::NewBackingStore(nullptr, 0, v8::BackingStore::EmptyDeleter, nullptr); v8::Local buffer = v8::ArrayBuffer::New(isolate, std::move(backing_store)); CHECK(env->Global()->Set(env.local(), v8_str("buffer"), buffer).FromJust()); v8::Local 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 allocator_; uint64_t allocation_count_ = 0; }; TEST(BackingStore_HoldAllocatorAlive_UntilIsolateShutdown) { std::shared_ptr allocator = std::make_shared(); std::weak_ptr 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 allocator = std::make_shared(); std::weak_ptr 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 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 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 allocator = std::make_shared(); std::weak_ptr 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 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 backing_store = v8::ArrayBuffer::NewBackingStore(isolate, 10); { uint8_t* data = reinterpret_cast( reinterpret_cast(backing_store->Data())); for (uint8_t i = 0; i < 10; i++) { data[i] = i; } } std::unique_ptr 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( reinterpret_cast(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 backing_store = v8::ArrayBuffer::NewBackingStore(isolate, 20); { uint8_t* data = reinterpret_cast(backing_store->Data()); for (uint8_t i = 0; i < 20; i++) { data[i] = i; } } std::unique_ptr 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(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 backing_store = v8::ArrayBuffer::NewBackingStore(isolate, 20); std::unique_ptr 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 backing_store = v8::SharedArrayBuffer::NewBackingStore(isolate, 20); std::unique_ptr new_backing_store = v8::BackingStore::Reallocate(isolate, std::move(backing_store), 10); CHECK(new_backing_store->IsShared()); }