// 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 CheckElementValue(i::Isolate* isolate, int expected, i::Handle<i::Object> obj, int offset) { i::Object element = *i::Object::GetElement(isolate, obj, offset).ToHandleChecked(); CHECK_EQ(expected, i::Smi::ToInt(element)); } template <class ElementType> void ObjectWithExternalArrayTestHelper(Local<Context> context, v8::Local<v8::TypedArray> obj, int element_count, i::ExternalArrayType array_type, int64_t low, int64_t high) { i::Handle<i::JSTypedArray> jsobj = v8::Utils::OpenHandle(*obj); v8::Isolate* v8_isolate = context->GetIsolate(); i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate); obj->Set(context, v8_str("field"), v8::Int32::New(v8_isolate, 1503)) .FromJust(); CHECK(context->Global()->Set(context, v8_str("ext_array"), obj).FromJust()); v8::Local<v8::Value> result = CompileRun("ext_array.field"); CHECK_EQ(1503, result->Int32Value(context).FromJust()); result = CompileRun("ext_array[1]"); CHECK_EQ(1, result->Int32Value(context).FromJust()); // Check assigned smis result = CompileRun( "for (var i = 0; i < 8; i++) {" " ext_array[i] = i;" "}" "var sum = 0;" "for (var i = 0; i < 8; i++) {" " sum += ext_array[i];" "}" "sum;"); CHECK_EQ(28, result->Int32Value(context).FromJust()); // Check pass through of assigned smis result = CompileRun( "var sum = 0;" "for (var i = 0; i < 8; i++) {" " sum += ext_array[i] = ext_array[i] = -i;" "}" "sum;"); CHECK_EQ(-28, result->Int32Value(context).FromJust()); // Check assigned smis in reverse order result = CompileRun( "for (var i = 8; --i >= 0; ) {" " ext_array[i] = i;" "}" "var sum = 0;" "for (var i = 0; i < 8; i++) {" " sum += ext_array[i];" "}" "sum;"); CHECK_EQ(28, result->Int32Value(context).FromJust()); // Check pass through of assigned HeapNumbers result = CompileRun( "var sum = 0;" "for (var i = 0; i < 16; i+=2) {" " sum += ext_array[i] = ext_array[i] = (-i * 0.5);" "}" "sum;"); CHECK_EQ(-28, result->Int32Value(context).FromJust()); // Check assigned HeapNumbers result = CompileRun( "for (var i = 0; i < 16; i+=2) {" " ext_array[i] = (i * 0.5);" "}" "var sum = 0;" "for (var i = 0; i < 16; i+=2) {" " sum += ext_array[i];" "}" "sum;"); CHECK_EQ(28, result->Int32Value(context).FromJust()); // Check assigned HeapNumbers in reverse order result = CompileRun( "for (var i = 14; i >= 0; i-=2) {" " ext_array[i] = (i * 0.5);" "}" "var sum = 0;" "for (var i = 0; i < 16; i+=2) {" " sum += ext_array[i];" "}" "sum;"); CHECK_EQ(28, result->Int32Value(context).FromJust()); i::ScopedVector<char> test_buf(1024); // Check legal boundary conditions. // The repeated loads and stores ensure the ICs are exercised. const char* boundary_program = "var res = 0;" "for (var i = 0; i < 16; i++) {" " ext_array[i] = %lld;" " if (i > 8) {" " res = ext_array[i];" " }" "}" "res;"; i::SNPrintF(test_buf, boundary_program, low); result = CompileRun(test_buf.begin()); CHECK_EQ(low, result->IntegerValue(context).FromJust()); i::SNPrintF(test_buf, boundary_program, high); result = CompileRun(test_buf.begin()); CHECK_EQ(high, result->IntegerValue(context).FromJust()); // Check misprediction of type in IC. result = CompileRun( "var tmp_array = ext_array;" "var sum = 0;" "for (var i = 0; i < 8; i++) {" " tmp_array[i] = i;" " sum += tmp_array[i];" " if (i == 4) {" " tmp_array = {};" " }" "}" "sum;"); // Force GC to trigger verification. CcTest::CollectAllGarbage(); CHECK_EQ(28, result->Int32Value(context).FromJust()); // Make sure out-of-range loads do not throw. i::SNPrintF(test_buf, "var caught_exception = false;" "try {" " ext_array[%d];" "} catch (e) {" " caught_exception = true;" "}" "caught_exception;", element_count); result = CompileRun(test_buf.begin()); CHECK(!result->BooleanValue(v8_isolate)); // Make sure out-of-range stores do not throw. i::SNPrintF(test_buf, "var caught_exception = false;" "try {" " ext_array[%d] = 1;" "} catch (e) {" " caught_exception = true;" "}" "caught_exception;", element_count); result = CompileRun(test_buf.begin()); CHECK(!result->BooleanValue(v8_isolate)); // Check other boundary conditions, values and operations. result = CompileRun( "for (var i = 0; i < 8; i++) {" " ext_array[7] = undefined;" "}" "ext_array[7];"); CHECK_EQ(0, result->Int32Value(context).FromJust()); if (array_type == i::kExternalFloat64Array || array_type == i::kExternalFloat32Array) { CHECK(std::isnan( i::Object::GetElement(isolate, jsobj, 7).ToHandleChecked()->Number())); } else { CheckElementValue(isolate, 0, jsobj, 7); } result = CompileRun( "for (var i = 0; i < 8; i++) {" " ext_array[6] = '2.3';" "}" "ext_array[6];"); CHECK_EQ(2, result->Int32Value(context).FromJust()); CHECK_EQ(2, static_cast<int>(i::Object::GetElement(isolate, jsobj, 6) .ToHandleChecked() ->Number())); if (array_type != i::kExternalFloat32Array && array_type != i::kExternalFloat64Array) { // Though the specification doesn't state it, be explicit about // converting NaNs and +/-Infinity to zero. result = CompileRun( "for (var i = 0; i < 8; i++) {" " ext_array[i] = 5;" "}" "for (var i = 0; i < 8; i++) {" " ext_array[i] = NaN;" "}" "ext_array[5];"); CHECK_EQ(0, result->Int32Value(context).FromJust()); CheckElementValue(isolate, 0, jsobj, 5); result = CompileRun( "for (var i = 0; i < 8; i++) {" " ext_array[i] = 5;" "}" "for (var i = 0; i < 8; i++) {" " ext_array[i] = Infinity;" "}" "ext_array[5];"); int expected_value = (array_type == i::kExternalUint8ClampedArray) ? 255 : 0; CHECK_EQ(expected_value, result->Int32Value(context).FromJust()); CheckElementValue(isolate, expected_value, jsobj, 5); result = CompileRun( "for (var i = 0; i < 8; i++) {" " ext_array[i] = 5;" "}" "for (var i = 0; i < 8; i++) {" " ext_array[i] = -Infinity;" "}" "ext_array[5];"); CHECK_EQ(0, result->Int32Value(context).FromJust()); CheckElementValue(isolate, 0, jsobj, 5); // Check truncation behavior of integral arrays. const char* unsigned_data = "var source_data = [0.6, 10.6];" "var expected_results = [0, 10];"; const char* signed_data = "var source_data = [0.6, 10.6, -0.6, -10.6];" "var expected_results = [0, 10, 0, -10];"; const char* pixel_data = "var source_data = [0.6, 10.6];" "var expected_results = [1, 11];"; bool is_unsigned = (array_type == i::kExternalUint8Array || array_type == i::kExternalUint16Array || array_type == i::kExternalUint32Array); bool is_pixel_data = array_type == i::kExternalUint8ClampedArray; i::SNPrintF(test_buf, "%s" "var all_passed = true;" "for (var i = 0; i < source_data.length; i++) {" " for (var j = 0; j < 8; j++) {" " ext_array[j] = source_data[i];" " }" " all_passed = all_passed &&" " (ext_array[5] == expected_results[i]);" "}" "all_passed;", (is_unsigned ? unsigned_data : (is_pixel_data ? pixel_data : signed_data))); result = CompileRun(test_buf.begin()); CHECK(result->BooleanValue(v8_isolate)); } { ElementType* data_ptr = static_cast<ElementType*>(jsobj->DataPtr()); for (int i = 0; i < element_count; i++) { data_ptr[i] = static_cast<ElementType>(i); } } bool old_natives_flag_sentry = i::FLAG_allow_natives_syntax; i::FLAG_allow_natives_syntax = true; // Test complex assignments result = CompileRun( "function ee_op_test_complex_func(sum) {" " for (var i = 0; i < 40; ++i) {" " sum += (ext_array[i] += 1);" " sum += (ext_array[i] -= 1);" " } " " return sum;" "};" "%PrepareFunctionForOptimization(ee_op_test_complex_func);" "sum=0;" "sum=ee_op_test_complex_func(sum);" "sum=ee_op_test_complex_func(sum);" "%OptimizeFunctionOnNextCall(ee_op_test_complex_func);" "sum=ee_op_test_complex_func(sum);" "sum;"); CHECK_EQ(4800, result->Int32Value(context).FromJust()); // Test count operations result = CompileRun( "function ee_op_test_count_func(sum) {" " for (var i = 0; i < 40; ++i) {" " sum += (++ext_array[i]);" " sum += (--ext_array[i]);" " } " " return sum;" "};" "%PrepareFunctionForOptimization(ee_op_test_count_func);" "sum=0;" "sum=ee_op_test_count_func(sum);" "sum=ee_op_test_count_func(sum);" "%OptimizeFunctionOnNextCall(ee_op_test_count_func);" "sum=ee_op_test_count_func(sum);" "sum;"); CHECK_EQ(4800, result->Int32Value(context).FromJust()); i::FLAG_allow_natives_syntax = old_natives_flag_sentry; result = CompileRun( "ext_array[3] = 33;" "delete ext_array[3];" "ext_array[3];"); CHECK_EQ(33, result->Int32Value(context).FromJust()); result = CompileRun( "ext_array[0] = 10; ext_array[1] = 11;" "ext_array[2] = 12; ext_array[3] = 13;" "try { ext_array.__defineGetter__('2', function() { return 120; }); }" "catch (e) { }" "ext_array[2];"); CHECK_EQ(12, result->Int32Value(context).FromJust()); result = CompileRun( "var js_array = new Array(40);" "js_array[0] = 77;" "js_array;"); CHECK_EQ(77, v8::Object::Cast(*result) ->Get(context, v8_str("0")) .ToLocalChecked() ->Int32Value(context) .FromJust()); result = CompileRun( "ext_array[1] = 23;" "ext_array.__proto__ = [];" "js_array.__proto__ = ext_array;" "js_array.concat(ext_array);"); CHECK_EQ(77, v8::Object::Cast(*result) ->Get(context, v8_str("0")) .ToLocalChecked() ->Int32Value(context) .FromJust()); CHECK_EQ(23, v8::Object::Cast(*result) ->Get(context, v8_str("1")) .ToLocalChecked() ->Int32Value(context) .FromJust()); result = CompileRun("ext_array[1] = 23;"); CHECK_EQ(23, result->Int32Value(context).FromJust()); } template <typename ElementType, typename TypedArray, class ArrayBufferType> void TypedArrayTestHelper(i::ExternalArrayType array_type, int64_t low, int64_t high) { const int kElementCount = 50; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); Local<ArrayBufferType> ab = ArrayBufferType::New(isolate, (kElementCount + 2) * sizeof(ElementType)); Local<TypedArray> ta = TypedArray::New(ab, 2 * sizeof(ElementType), kElementCount); CheckInternalFieldsAreZero<v8::ArrayBufferView>(ta); CHECK_EQ(kElementCount, static_cast<int>(ta->Length())); CHECK_EQ(2 * sizeof(ElementType), ta->ByteOffset()); CHECK_EQ(kElementCount * sizeof(ElementType), ta->ByteLength()); CHECK(ab->Equals(env.local(), ta->Buffer()).FromJust()); ElementType* data = reinterpret_cast<ElementType*>(ab->GetBackingStore()->Data()) + 2; for (int i = 0; i < kElementCount; i++) { data[i] = static_cast<ElementType>(i); } ObjectWithExternalArrayTestHelper<ElementType>(env.local(), ta, kElementCount, array_type, low, high); } } // namespace THREADED_TEST(Uint8Array) { TypedArrayTestHelper<uint8_t, v8::Uint8Array, v8::ArrayBuffer>( i::kExternalUint8Array, 0, 0xFF); } THREADED_TEST(Int8Array) { TypedArrayTestHelper<int8_t, v8::Int8Array, v8::ArrayBuffer>( i::kExternalInt8Array, -0x80, 0x7F); } THREADED_TEST(Uint16Array) { TypedArrayTestHelper<uint16_t, v8::Uint16Array, v8::ArrayBuffer>( i::kExternalUint16Array, 0, 0xFFFF); } THREADED_TEST(Int16Array) { TypedArrayTestHelper<int16_t, v8::Int16Array, v8::ArrayBuffer>( i::kExternalInt16Array, -0x8000, 0x7FFF); } THREADED_TEST(Uint32Array) { TypedArrayTestHelper<uint32_t, v8::Uint32Array, v8::ArrayBuffer>( i::kExternalUint32Array, 0, UINT_MAX); } THREADED_TEST(Int32Array) { TypedArrayTestHelper<int32_t, v8::Int32Array, v8::ArrayBuffer>( i::kExternalInt32Array, INT_MIN, INT_MAX); } THREADED_TEST(Float32Array) { TypedArrayTestHelper<float, v8::Float32Array, v8::ArrayBuffer>( i::kExternalFloat32Array, -500, 500); } THREADED_TEST(Float64Array) { TypedArrayTestHelper<double, v8::Float64Array, v8::ArrayBuffer>( i::kExternalFloat64Array, -500, 500); } THREADED_TEST(Uint8ClampedArray) { TypedArrayTestHelper<uint8_t, v8::Uint8ClampedArray, v8::ArrayBuffer>( i::kExternalUint8ClampedArray, 0, 0xFF); } THREADED_TEST(DataView) { const int kSize = 50; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 2 + kSize); Local<v8::DataView> dv = v8::DataView::New(ab, 2, kSize); CheckInternalFieldsAreZero<v8::ArrayBufferView>(dv); CHECK_EQ(2u, dv->ByteOffset()); CHECK_EQ(kSize, static_cast<int>(dv->ByteLength())); CHECK(ab->Equals(env.local(), dv->Buffer()).FromJust()); } THREADED_TEST(SharedUint8Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<uint8_t, v8::Uint8Array, v8::SharedArrayBuffer>( i::kExternalUint8Array, 0, 0xFF); } THREADED_TEST(SharedInt8Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<int8_t, v8::Int8Array, v8::SharedArrayBuffer>( i::kExternalInt8Array, -0x80, 0x7F); } THREADED_TEST(SharedUint16Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<uint16_t, v8::Uint16Array, v8::SharedArrayBuffer>( i::kExternalUint16Array, 0, 0xFFFF); } THREADED_TEST(SharedInt16Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<int16_t, v8::Int16Array, v8::SharedArrayBuffer>( i::kExternalInt16Array, -0x8000, 0x7FFF); } THREADED_TEST(SharedUint32Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<uint32_t, v8::Uint32Array, v8::SharedArrayBuffer>( i::kExternalUint32Array, 0, UINT_MAX); } THREADED_TEST(SharedInt32Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<int32_t, v8::Int32Array, v8::SharedArrayBuffer>( i::kExternalInt32Array, INT_MIN, INT_MAX); } THREADED_TEST(SharedFloat32Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<float, v8::Float32Array, v8::SharedArrayBuffer>( i::kExternalFloat32Array, -500, 500); } THREADED_TEST(SharedFloat64Array) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<double, v8::Float64Array, v8::SharedArrayBuffer>( i::kExternalFloat64Array, -500, 500); } THREADED_TEST(SharedUint8ClampedArray) { i::FLAG_harmony_sharedarraybuffer = true; TypedArrayTestHelper<uint8_t, v8::Uint8ClampedArray, v8::SharedArrayBuffer>( i::kExternalUint8ClampedArray, 0, 0xFF); } THREADED_TEST(SharedDataView) { i::FLAG_harmony_sharedarraybuffer = true; const int kSize = 50; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); Local<v8::SharedArrayBuffer> ab = v8::SharedArrayBuffer::New(isolate, 2 + kSize); Local<v8::DataView> dv = v8::DataView::New(ab, 2, kSize); CheckInternalFieldsAreZero<v8::ArrayBufferView>(dv); CHECK_EQ(2u, dv->ByteOffset()); CHECK_EQ(kSize, static_cast<int>(dv->ByteLength())); CHECK(ab->Equals(env.local(), dv->Buffer()).FromJust()); } #define IS_ARRAY_BUFFER_VIEW_TEST(View) \ THREADED_TEST(Is##View) { \ LocalContext env; \ v8::Isolate* isolate = env->GetIsolate(); \ v8::HandleScope handle_scope(isolate); \ \ Local<Value> result = CompileRun( \ "var ab = new ArrayBuffer(128);" \ "new " #View "(ab)"); \ CHECK(result->IsArrayBufferView()); \ CHECK(result->Is##View()); \ CheckInternalFieldsAreZero<v8::ArrayBufferView>(result.As<v8::View>()); \ } IS_ARRAY_BUFFER_VIEW_TEST(Uint8Array) IS_ARRAY_BUFFER_VIEW_TEST(Int8Array) IS_ARRAY_BUFFER_VIEW_TEST(Uint16Array) IS_ARRAY_BUFFER_VIEW_TEST(Int16Array) IS_ARRAY_BUFFER_VIEW_TEST(Uint32Array) IS_ARRAY_BUFFER_VIEW_TEST(Int32Array) IS_ARRAY_BUFFER_VIEW_TEST(Float32Array) IS_ARRAY_BUFFER_VIEW_TEST(Float64Array) IS_ARRAY_BUFFER_VIEW_TEST(Uint8ClampedArray) IS_ARRAY_BUFFER_VIEW_TEST(DataView) #undef IS_ARRAY_BUFFER_VIEW_TEST TEST(InternalFieldsOnTypedArray) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local<v8::Context> context = env.local(); Context::Scope context_scope(context); v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, 1); v8::Local<v8::Uint8Array> array = v8::Uint8Array::New(buffer, 0, 1); for (int i = 0; i < v8::ArrayBufferView::kInternalFieldCount; i++) { CHECK_EQ(static_cast<void*>(nullptr), array->GetAlignedPointerFromInternalField(i)); } } TEST(InternalFieldsOnDataView) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local<v8::Context> context = env.local(); Context::Scope context_scope(context); v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, 1); v8::Local<v8::DataView> array = v8::DataView::New(buffer, 0, 1); for (int i = 0; i < v8::ArrayBufferView::kInternalFieldCount; i++) { CHECK_EQ(static_cast<void*>(nullptr), array->GetAlignedPointerFromInternalField(i)); } } namespace { void TestOnHeapHasBuffer(const char* array_name, size_t elem_size) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); i::ScopedVector<char> source(128); // Test on-heap sizes. for (size_t size = 0; size <= i::JSTypedArray::kMaxSizeInHeap; size += elem_size) { size_t length = size / elem_size; i::SNPrintF(source, "new %sArray(%zu)", array_name, length); auto typed_array = v8::Local<v8::TypedArray>::Cast(CompileRun(source.begin())); CHECK_EQ(length, typed_array->Length()); // Should not (yet) have a buffer. CHECK(!typed_array->HasBuffer()); // Get the buffer and check its length. i::Handle<i::JSTypedArray> i_typed_array = v8::Utils::OpenHandle(*typed_array); auto i_array_buffer1 = i_typed_array->GetBuffer(); CHECK_EQ(size, i_array_buffer1->byte_length()); CHECK(typed_array->HasBuffer()); // Should have the same buffer each time. auto i_array_buffer2 = i_typed_array->GetBuffer(); CHECK(i_array_buffer1.is_identical_to(i_array_buffer2)); } } void TestOffHeapHasBuffer(const char* array_name, size_t elem_size) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope handle_scope(isolate); i::ScopedVector<char> source(128); // Test off-heap sizes. size_t size = i::JSTypedArray::kMaxSizeInHeap; for (int i = 0; i < 3; i++) { size_t length = 1 + (size / elem_size); i::SNPrintF(source, "new %sArray(%zu)", array_name, length); auto typed_array = v8::Local<v8::TypedArray>::Cast(CompileRun(source.begin())); CHECK_EQ(length, typed_array->Length()); // Should already have a buffer. CHECK(typed_array->HasBuffer()); // Get the buffer and check its length. i::Handle<i::JSTypedArray> i_typed_array = v8::Utils::OpenHandle(*typed_array); auto i_array_buffer1 = i_typed_array->GetBuffer(); CHECK_EQ(length * elem_size, i_array_buffer1->byte_length()); size *= 2; } } } // namespace #define TEST_HAS_BUFFER(array_name, elem_size) \ TEST(OnHeap_##array_name##Array_HasBuffer) { \ TestOnHeapHasBuffer(#array_name, elem_size); \ } \ TEST(OffHeap_##array_name##_HasBuffer) { \ TestOffHeapHasBuffer(#array_name, elem_size); \ } TEST_HAS_BUFFER(Uint8, 1) TEST_HAS_BUFFER(Int8, 1) TEST_HAS_BUFFER(Uint16, 2) TEST_HAS_BUFFER(Int16, 2) TEST_HAS_BUFFER(Uint32, 4) TEST_HAS_BUFFER(Int32, 4) TEST_HAS_BUFFER(Float32, 4) TEST_HAS_BUFFER(Float64, 8) #undef TEST_HAS_BUFFER