blob: d78145080adf74f890af44c34e2f2f18a30ea946 [file] [log] [blame]
// 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/logging.h"
#include "src/base/strings.h"
#include "src/common/globals.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/sandbox/sandbox.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/cctest/test-api.h"
#include "test/common/flag-utils.h"
using ::v8::Array;
using ::v8::Context;
using ::v8::Local;
using ::v8::Maybe;
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());
i::heap::InvokeMajorGC(CcTest::heap());
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_ApiMaybeNew) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
// Reasonable-sized ArrayBuffer.
v8::MaybeLocal<v8::ArrayBuffer> maybe_ab =
v8::ArrayBuffer::MaybeNew(isolate, 1024);
CHECK(!maybe_ab.IsEmpty());
auto ab = v8::Local<v8::ArrayBuffer>::Cast(maybe_ab.ToLocalChecked());
CheckInternalFieldsAreZero(ab);
CHECK_EQ(1024, ab->ByteLength());
i::heap::InvokeMajorGC(CcTest::heap());
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());
// Too large ArrayBuffer.
size_t unreasonable_size = 1;
#if V8_TARGET_ARCH_64_BIT
unreasonable_size <<= 53;
#else
unreasonable_size <<= 31;
#endif
v8::MaybeLocal<v8::ArrayBuffer> maybe_ab_2 =
v8::ArrayBuffer::MaybeNew(isolate, unreasonable_size);
CHECK(maybe_ab_2.IsEmpty());
}
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::DirectHandle<i::JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*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(v8::Local<v8::Value>()).Check();
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(v8::Local<v8::Value>()).Check();
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_WasDetached) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 0);
CHECK(!ab->WasDetached());
ab->Detach(v8::Local<v8::Value>()).Check();
CHECK(ab->WasDetached());
}
THREADED_TEST(ArrayBuffer_NonDetachableWasDetached) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
CompileRun(R"JS(
var wasmMemory = new WebAssembly.Memory({initial: 1, maximum: 2});
)JS");
Local<v8::ArrayBuffer> non_detachable =
CompileRun("wasmMemory.buffer").As<v8::ArrayBuffer>();
CHECK(!non_detachable->IsDetachable());
CHECK(!non_detachable->WasDetached());
CompileRun("wasmMemory.grow(1)");
CHECK(!non_detachable->IsDetachable());
CHECK(non_detachable->WasDetached());
}
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) {
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());
i::heap::InvokeMajorGC(CcTest::heap());
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) {
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
i::heap::EmptyNewSpaceUsingGC(CcTest::heap());
i::heap::InvokeMajorGC(CcTest::heap());
i::heap::InvokeMajorGC(CcTest::heap());
// Should not move the pointer
CHECK_EQ(ab->GetBackingStore()->Data(), store_ptr);
CHECK_EQ(ab->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*>(i::ValueHelper::ValueAsAddress(*tmp));
auto backing_store = v8::ArrayBuffer::NewBackingStore(
store_ptr, 8, [](void*, size_t, void*) {}, nullptr);
i::heap::InvokeMinorGC(CcTest::heap());
// 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
i::heap::EmptyNewSpaceUsingGC(CcTest::heap());
CHECK_EQ(ab->GetBackingStore()->Data(), store_ptr);
CHECK_EQ(ab->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());
CHECK(!backing_store->IsResizableByUserJavaScript());
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, backing_store);
CHECK_EQ(backing_store.get(), ab->GetBackingStore().get());
CHECK_EQ(backing_store->Data(), ab->Data());
}
THREADED_TEST(ArrayBuffer_NewResizableBackingStore) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
std::shared_ptr<v8::BackingStore> backing_store =
v8::ArrayBuffer::NewResizableBackingStore(32, 1024);
CHECK(!backing_store->IsShared());
CHECK(backing_store->IsResizableByUserJavaScript());
CHECK_EQ(1024, backing_store->MaxByteLength());
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, backing_store);
CHECK_EQ(backing_store.get(), ab->GetBackingStore().get());
CHECK_EQ(backing_store->Data(), ab->Data());
CHECK_EQ(backing_store->MaxByteLength(), ab->MaxByteLength());
}
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());
CHECK(!backing_store->IsResizableByUserJavaScript());
Local<v8::SharedArrayBuffer> ab =
v8::SharedArrayBuffer::New(isolate, backing_store);
CHECK_EQ(backing_store.get(), ab->GetBackingStore().get());
CHECK_EQ(backing_store->Data(), ab->Data());
}
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 = v8::ExternalMemoryAccounter::
GetTotalAmountOfExternalAllocatedMemoryForTesting(isolate);
v8::ArrayBuffer::New(isolate, std::move(backing_store));
uint64_t external_memory_after = v8::ExternalMemoryAccounter::
GetTotalAmountOfExternalAllocatedMemoryForTesting(isolate);
// 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 = v8::ExternalMemoryAccounter::
GetTotalAmountOfExternalAllocatedMemoryForTesting(isolate);
v8::SharedArrayBuffer::New(isolate, std::move(backing_store));
uint64_t external_memory_after = v8::ExternalMemoryAccounter::
GetTotalAmountOfExternalAllocatedMemoryForTesting(isolate);
// 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());
}
START_ALLOW_USE_DEPRECATED()
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());
}
END_ALLOW_USE_DEPRECATED()
TEST(ArrayBuffer_Resizable) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
const char rab_source[] = "new ArrayBuffer(32, { maxByteLength: 1024 });";
v8::Local<v8::ArrayBuffer> rab = CompileRun(rab_source).As<v8::ArrayBuffer>();
CHECK(rab->GetBackingStore()->IsResizableByUserJavaScript());
CHECK_EQ(32, rab->ByteLength());
CHECK_EQ(1024, rab->MaxByteLength());
const char gsab_source[] =
"new SharedArrayBuffer(32, { maxByteLength: 1024 });";
v8::Local<v8::SharedArrayBuffer> gsab =
CompileRun(gsab_source).As<v8::SharedArrayBuffer>();
CHECK(gsab->GetBackingStore()->IsResizableByUserJavaScript());
CHECK_EQ(32, gsab->ByteLength());
CHECK_EQ(1024, gsab->MaxByteLength());
CHECK_EQ(gsab->MaxByteLength(), gsab->GetBackingStore()->MaxByteLength());
}
TEST(ArrayBuffer_FixedLength) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
// Fixed-length ArrayBuffers' byte length are equal to their max byte length.
v8::Local<v8::ArrayBuffer> ab =
CompileRun("new ArrayBuffer(32);").As<v8::ArrayBuffer>();
CHECK(!ab->GetBackingStore()->IsResizableByUserJavaScript());
CHECK_EQ(32, ab->ByteLength());
CHECK_EQ(32, ab->MaxByteLength());
CHECK_EQ(ab->MaxByteLength(), ab->GetBackingStore()->MaxByteLength());
v8::Local<v8::SharedArrayBuffer> sab =
CompileRun("new SharedArrayBuffer(32);").As<v8::SharedArrayBuffer>();
CHECK(!sab->GetBackingStore()->IsResizableByUserJavaScript());
CHECK_EQ(32, sab->ByteLength());
CHECK_EQ(32, sab->MaxByteLength());
CHECK_EQ(sab->MaxByteLength(), sab->GetBackingStore()->MaxByteLength());
}
THREADED_TEST(ArrayBuffer_DataApiWithEmptyExternal) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 0);
void* empty_buffer_ptr = v8::internal::EmptyBackingStoreBuffer();
CHECK_EQ(empty_buffer_ptr, ab->Data());
CHECK_EQ(0, ab->ByteLength());
CHECK_EQ(empty_buffer_ptr, ab->GetBackingStore()->Data());
// Repeat test to make sure that accessing the backing store buffer hasn't
// changed what the AB's Data method returns.
CHECK_EQ(empty_buffer_ptr, ab->Data());
CHECK_EQ(0, ab->ByteLength());
void* buffer = CcTest::array_buffer_allocator()->Allocate(1);
std::unique_ptr<v8::BackingStore> backing_store =
v8::ArrayBuffer::NewBackingStore(buffer, 0,
v8::BackingStore::EmptyDeleter, nullptr);
Local<v8::ArrayBuffer> ab2 =
v8::ArrayBuffer::New(isolate, std::move(backing_store));
CHECK_EQ(buffer, ab2->Data());
CHECK_EQ(0, ab->ByteLength());
}
namespace {
void TestArrayBufferViewGetContent(const char* source, void* expected) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
auto view = v8::Local<v8::ArrayBufferView>::Cast(CompileRun(source));
uint8_t buffer[i::JSTypedArray::kMaxSizeInHeap];
v8::MemorySpan<uint8_t> storage(buffer);
storage = view->GetContents(storage);
CHECK_EQ(view->ByteLength(), storage.size());
if (expected) {
CHECK_EQ(0, memcmp(storage.data(), expected, view->ByteLength()));
} else {
CHECK_EQ(0, storage.size());
}
}
} // namespace
TEST(ArrayBufferView_GetContentsSmallUint8) {
const char* source = "new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9])";
uint8_t expected[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsLargeUint8) {
const char* source =
"let array = new Uint8Array(100);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"array";
uint8_t expected[100];
for (uint8_t i = 0; i < 100; ++i) {
expected[i] = i;
}
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsUint8View) {
const char* source =
"let array = new Uint8Array(100);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"new Uint8Array(array.buffer, 70, 9)";
uint8_t expected[]{70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsSmallUint32) {
const char* source = "new Uint16Array([1, 2, 3, 4, 5, 6, 7, 8, 9])";
uint16_t expected[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsLargeUint16) {
const char* source =
"let array = new Uint16Array(100);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"array";
uint16_t expected[100];
for (uint16_t i = 0; i < 100; ++i) {
expected[i] = i;
}
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsUint16View) {
const char* source =
"let array = new Uint16Array(100);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"new Uint16Array(array.buffer, 140, 9)";
uint16_t expected[]{70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsSmallDataView) {
const char* source =
"let array = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]);"
"new DataView(array.buffer)";
uint8_t expected[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsLargeDataView) {
const char* source =
"let array = new Uint8Array(100);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"new DataView(array.buffer)";
uint8_t expected[100];
for (uint8_t i = 0; i < 100; ++i) {
expected[i] = i;
}
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsDataViewWithOffset) {
const char* source =
"let array = new Uint8Array(100);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"new DataView(array.buffer, 70, 9)";
uint8_t expected[]{70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsSmallResizableDataView) {
const char* source =
"let rsab = new ArrayBuffer(10, {maxByteLength: 20});"
"let array = new Uint8Array(rsab);"
"for (let i = 0; i < 10; ++i) {"
" array[i] = i;"
"}"
"new DataView(rsab)";
uint8_t expected[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsResizableTypedArray) {
const char* source =
"let rsab = new ArrayBuffer(8, {maxByteLength: 8});"
"let array = new Uint8Array(rsab);"
"for (let i = 0; i < 8; ++i) {"
" array[i] = i;"
"};"
"array";
uint8_t expected[]{0, 1, 2, 3, 4, 5, 6, 7};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsLargeResizableDataView) {
const char* source =
"let rsab = new ArrayBuffer(100, {maxByteLength: 200});"
"let array = new Uint8Array(rsab);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"new DataView(rsab)";
uint8_t expected[100];
for (uint8_t i = 0; i < 100; ++i) {
expected[i] = i;
}
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsResizableDataViewWithOffset) {
const char* source =
"let rsab = new ArrayBuffer(100, {maxByteLength: 200});"
"let array = new Uint8Array(rsab);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"new DataView(rsab, 70, 9)";
uint8_t expected[]{70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
TestArrayBufferViewGetContent(source, expected);
}
TEST(ArrayBufferView_GetContentsDetached) {
const char* source =
"let array = new Uint8Array(100);"
"for (let i = 0; i < 100; ++i) {"
" array[i] = i;"
"}"
"const data_view = new DataView(array.buffer);"
"let buffer = array.buffer.transfer();"
"data_view";
TestArrayBufferViewGetContent(source, nullptr);
}