// 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 "test/cctest/test-api.h" #include "src/api/api-inl.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) { i::ScopedVector source(1024); i::SNPrintF(source, "%s.byteLength == 0 && %s.byteOffset == 0 && %s.length == 0", name, name, name); CHECK(CompileRun(source.begin())->IsTrue()); v8::Local ta = v8::Local::Cast(CompileRun(name)); 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(); ab->Externalize(backing_store); CHECK(ab->IsExternal()); return backing_store; } std::shared_ptr Externalize(Local ab) { std::shared_ptr backing_store = ab->GetBackingStore(); ab->Externalize(backing_store); CHECK(ab->IsExternal()); 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()); CHECK(!ab->IsExternal()); 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 = Local::Cast(result); 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(ArrayBuffer_External) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); i::ScopedVector my_data(100); memset(my_data.begin(), 0, 100); Local ab3 = v8::ArrayBuffer::New(isolate, my_data.begin(), 100); CheckInternalFieldsAreZero(ab3); CHECK_EQ(100, ab3->ByteLength()); CHECK(ab3->IsExternal()); CHECK(env->Global()->Set(env.local(), v8_str("ab3"), ab3).FromJust()); v8::Local result = CompileRun("ab3.byteLength"); CHECK_EQ(100, result->Int32Value(env.local()).FromJust()); result = CompileRun( "var u8_b = new Uint8Array(ab3);" "u8_b[0] = 0xBB;" "u8_b[1] = 0xCC;" "u8_b.length"); CHECK_EQ(100, result->Int32Value(env.local()).FromJust()); CHECK_EQ(0xBB, my_data[0]); CHECK_EQ(0xCC, my_data[1]); my_data[0] = 0xCC; my_data[1] = 0x11; result = CompileRun("u8_b[0] + u8_b[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); i::ScopedVector my_data(100); memset(my_data.begin(), 0, 100); Local ab = v8::ArrayBuffer::New(isolate, my_data.begin(), 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 = Local::Cast(CompileRun("ab")); v8::Local dv = v8::Local::Cast(CompileRun("dv")); 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); } // TODO(v8:9380) the Contents data structure should be deprecated. THREADED_TEST(ArrayBuffer_AllocationInformation) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); const size_t ab_size = 1024; Local ab = v8::ArrayBuffer::New(isolate, ab_size); v8::ArrayBuffer::Contents contents(ab->GetContents()); // Array buffers should have normal allocation mode. CHECK_EQ(contents.AllocationMode(), v8::ArrayBuffer::Allocator::AllocationMode::kNormal); // The allocation must contain the buffer (normally they will be equal, but // this is not required by the contract). CHECK_NOT_NULL(contents.AllocationBase()); const uintptr_t alloc = reinterpret_cast(contents.AllocationBase()); const uintptr_t data = reinterpret_cast(contents.Data()); CHECK_LE(alloc, data); CHECK_LE(data + contents.ByteLength(), alloc + contents.AllocationLength()); } 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()); CHECK(!ab->IsExternal()); // 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(ab->IsExternal()); 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()); CHECK(!ab->IsExternal()); 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_ExternalReused) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); i::ScopedVector data(100); Local ab1 = v8::ArrayBuffer::New(isolate, data.begin(), 100); std::shared_ptr bs1 = ab1->GetBackingStore(); ab1->Detach(); Local ab2 = v8::ArrayBuffer::New(isolate, data.begin(), 100); std::shared_ptr bs2 = ab2->GetBackingStore(); CHECK_EQ(bs1->Data(), bs2->Data()); } THREADED_TEST(SharedArrayBuffer_ExternalReused) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); i::ScopedVector data(100); Local ab1 = v8::SharedArrayBuffer::New(isolate, data.begin(), 100); std::shared_ptr bs1 = ab1->GetBackingStore(); Local ab2 = v8::SharedArrayBuffer::New(isolate, data.begin(), 100); std::shared_ptr bs2 = ab2->GetBackingStore(); CHECK_EQ(bs1->Data(), bs2->Data()); } 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 = Local::Cast(result); 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(SharedArrayBuffer_External) { i::FLAG_harmony_sharedarraybuffer = true; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); i::ScopedVector my_data(100); memset(my_data.begin(), 0, 100); Local ab3 = v8::SharedArrayBuffer::New(isolate, my_data.begin(), 100); CheckInternalFieldsAreZero(ab3); CHECK_EQ(100, static_cast(ab3->ByteLength())); CHECK(ab3->IsExternal()); CHECK(env->Global()->Set(env.local(), v8_str("ab3"), ab3).FromJust()); v8::Local result = CompileRun("ab3.byteLength"); CHECK_EQ(100, result->Int32Value(env.local()).FromJust()); result = CompileRun( "var u8_b = new Uint8Array(ab3);" "u8_b[0] = 0xBB;" "u8_b[1] = 0xCC;" "u8_b.length"); CHECK_EQ(100, result->Int32Value(env.local()).FromJust()); CHECK_EQ(0xBB, my_data[0]); CHECK_EQ(0xCC, my_data[1]); my_data[0] = 0xCC; my_data[1] = 0x11; result = CompileRun("u8_b[0] + u8_b[1]"); CHECK_EQ(0xDD, result->Int32Value(env.local()).FromJust()); } // TODO(v8:9380) the Contents data structure should be deprecated. THREADED_TEST(SharedArrayBuffer_AllocationInformation) { i::FLAG_harmony_sharedarraybuffer = true; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); const size_t ab_size = 1024; Local ab = v8::SharedArrayBuffer::New(isolate, ab_size); v8::SharedArrayBuffer::Contents contents(ab->GetContents()); // Array buffers should have normal allocation mode. CHECK_EQ(contents.AllocationMode(), v8::ArrayBuffer::Allocator::AllocationMode::kNormal); // The allocation must contain the buffer (normally they will be equal, but // this is not required by the contract). CHECK_NOT_NULL(contents.AllocationBase()); const uintptr_t alloc = reinterpret_cast(contents.AllocationBase()); const uintptr_t data = reinterpret_cast(contents.Data()); CHECK_LE(alloc, data); CHECK_LE(data + contents.ByteLength(), alloc + contents.AllocationLength()); } THREADED_TEST(SkipArrayBufferBackingStoreDuringGC) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); // Make sure the pointer looks like a heap object uint8_t* store_ptr = reinterpret_cast(i::kHeapObjectTag); // Create ArrayBuffer with pointer-that-cannot-be-visited in the backing store Local ab = v8::ArrayBuffer::New(isolate, store_ptr, 8); // 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); } 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)); // 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, store_ptr, 8); // 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)); } }