/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "js/ArrayBuffer.h" // JS::{NewArrayBuffer,IsArrayBufferObject,GetArrayBuffer{ByteLength,Data}} #include "js/experimental/TypedData.h" // JS_GetArrayBufferViewBuffer, JS_GetTypedArray{Length,ByteOffset,ByteLength}, JS_Get{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped}ArrayData, JS_IsTypedArrayObject, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped}Array{,FromArray,WithBuffer} #include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement #include "js/SharedArrayBuffer.h" // JS::{NewSharedArrayBuffer,GetSharedArrayBufferData} #include "jsapi-tests/tests.h" #include "vm/Realm.h" using namespace js; BEGIN_TEST(testTypedArrays) { bool ok = true; ok = ok && TestPlainTypedArray(cx) && TestPlainTypedArray( cx) && TestPlainTypedArray(cx) && TestPlainTypedArray( cx) && TestPlainTypedArray( cx) && TestPlainTypedArray( cx) && TestPlainTypedArray( cx) && TestPlainTypedArray( cx) && TestPlainTypedArray( cx); size_t nbytes = sizeof(double) * 8; RootedObject buffer(cx, JS::NewArrayBuffer(cx, nbytes)); CHECK(JS::IsArrayBufferObject(buffer)); RootedObject proto(cx); JS_GetPrototype(cx, buffer, &proto); CHECK(!JS::IsArrayBufferObject(proto)); { JS::AutoCheckCannotGC nogc; bool isShared; CHECK_EQUAL(JS::GetArrayBufferByteLength(buffer), nbytes); memset(JS::GetArrayBufferData(buffer, &isShared, nogc), 1, nbytes); CHECK(!isShared); // Because ArrayBuffer } ok = ok && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx); ok = ok && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx) && TestArrayFromBuffer(cx); return ok; } // Shared memory can only be mapped by a TypedArray by creating the // TypedArray with a SharedArrayBuffer explicitly, so no tests here. template bool TestPlainTypedArray(JSContext* cx) { { RootedObject notArray(cx, Create(cx, SIZE_MAX)); CHECK(!notArray); JS_ClearPendingException(cx); } RootedObject array(cx, Create(cx, 7)); CHECK(JS_IsTypedArrayObject(array)); RootedObject proto(cx); JS_GetPrototype(cx, array, &proto); CHECK(!JS_IsTypedArrayObject(proto)); CHECK_EQUAL(JS_GetTypedArrayLength(array), 7u); CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u); CHECK_EQUAL(JS_GetTypedArrayByteLength(array), sizeof(Element) * 7); { JS::AutoCheckCannotGC nogc; Element* data; bool isShared; CHECK(data = GetData(array, &isShared, nogc)); CHECK(!isShared); // Because ArrayBuffer *data = 13; } RootedValue v(cx); CHECK(JS_GetElement(cx, array, 0, &v)); CHECK_SAME(v, Int32Value(13)); return true; } template < JSObject* CreateWithBuffer(JSContext*, JS::HandleObject, size_t, int64_t), JSObject* CreateFromArray(JSContext*, JS::HandleObject), typename Element, bool Shared, Element* GetData(JSObject*, bool*, const JS::AutoRequireNoGC&)> bool TestArrayFromBuffer(JSContext* cx) { if (Shared && !cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) { return true; } size_t elts = 8; size_t nbytes = elts * sizeof(Element); RootedObject buffer(cx, Shared ? JS::NewSharedArrayBuffer(cx, nbytes) : JS::NewArrayBuffer(cx, nbytes)); { JS::AutoCheckCannotGC nogc; bool isShared; void* data = Shared ? JS::GetSharedArrayBufferData(buffer, &isShared, nogc) : JS::GetArrayBufferData(buffer, &isShared, nogc); CHECK_EQUAL(Shared, isShared); memset(data, 1, nbytes); } { RootedObject notArray(cx, CreateWithBuffer(cx, buffer, UINT32_MAX, -1)); CHECK(!notArray); JS_ClearPendingException(cx); } RootedObject array(cx, CreateWithBuffer(cx, buffer, 0, -1)); CHECK_EQUAL(JS_GetTypedArrayLength(array), elts); CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u); CHECK_EQUAL(JS_GetTypedArrayByteLength(array), nbytes); { bool isShared; CHECK_EQUAL(JS_GetArrayBufferViewBuffer(cx, array, &isShared), (JSObject*)buffer); CHECK_EQUAL(Shared, isShared); } { JS::AutoCheckCannotGC nogc; Element* data; bool isShared; CHECK(data = GetData(array, &isShared, nogc)); CHECK_EQUAL(Shared, isShared); CHECK_EQUAL( (void*)data, Shared ? (void*)JS::GetSharedArrayBufferData(buffer, &isShared, nogc) : (void*)JS::GetArrayBufferData(buffer, &isShared, nogc)); CHECK_EQUAL(Shared, isShared); CHECK_EQUAL(*reinterpret_cast(data), 1u); } RootedObject shortArray(cx, CreateWithBuffer(cx, buffer, 0, elts / 2)); CHECK_EQUAL(JS_GetTypedArrayLength(shortArray), elts / 2); CHECK_EQUAL(JS_GetTypedArrayByteOffset(shortArray), 0u); CHECK_EQUAL(JS_GetTypedArrayByteLength(shortArray), nbytes / 2); RootedObject ofsArray(cx, CreateWithBuffer(cx, buffer, nbytes / 2, -1)); CHECK_EQUAL(JS_GetTypedArrayLength(ofsArray), elts / 2); CHECK_EQUAL(JS_GetTypedArrayByteOffset(ofsArray), nbytes / 2); CHECK_EQUAL(JS_GetTypedArrayByteLength(ofsArray), nbytes / 2); // Make sure all 3 views reflect the same buffer at the expected locations JS::RootedValue v(cx, JS::Int32Value(39)); CHECK(JS_SetElement(cx, array, 0, v)); JS::RootedValue v2(cx); CHECK(JS_GetElement(cx, array, 0, &v2)); CHECK_SAME(v, v2); CHECK(JS_GetElement(cx, shortArray, 0, &v2)); CHECK_SAME(v, v2); { JS::AutoCheckCannotGC nogc; Element* data; bool isShared; CHECK(data = GetData(array, &isShared, nogc)); CHECK_EQUAL(Shared, isShared); CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast(data)[0])); } v.setInt32(40); CHECK(JS_SetElement(cx, array, elts / 2, v)); CHECK(JS_GetElement(cx, array, elts / 2, &v2)); CHECK_SAME(v, v2); CHECK(JS_GetElement(cx, ofsArray, 0, &v2)); CHECK_SAME(v, v2); { JS::AutoCheckCannotGC nogc; Element* data; bool isShared; CHECK(data = GetData(array, &isShared, nogc)); CHECK_EQUAL(Shared, isShared); CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast(data)[elts / 2])); } v.setInt32(41); CHECK(JS_SetElement(cx, array, elts - 1, v)); CHECK(JS_GetElement(cx, array, elts - 1, &v2)); CHECK_SAME(v, v2); CHECK(JS_GetElement(cx, ofsArray, elts / 2 - 1, &v2)); CHECK_SAME(v, v2); { JS::AutoCheckCannotGC nogc; Element* data; bool isShared; CHECK(data = GetData(array, &isShared, nogc)); CHECK_EQUAL(Shared, isShared); CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast(data)[elts - 1])); } JS::RootedObject copy(cx, CreateFromArray(cx, array)); CHECK(JS_GetElement(cx, array, 0, &v)); CHECK(JS_GetElement(cx, copy, 0, &v2)); CHECK_SAME(v, v2); /* The copy should not see changes in the original */ v2.setInt32(42); CHECK(JS_SetElement(cx, array, 0, v2)); CHECK(JS_GetElement(cx, copy, 0, &v2)); CHECK_SAME(v2, v); /* v is still the original value from 'array' */ return true; } END_TEST(testTypedArrays)