testTypedArrays.cpp (13064B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 */ 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "js/ArrayBuffer.h" // JS::{NewArrayBuffer,IsArrayBufferObject,GetArrayBuffer{ByteLength,Data}} 9 #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} 10 #include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement 11 #include "js/SharedArrayBuffer.h" // JS::{NewSharedArrayBuffer,GetSharedArrayBufferData} 12 #include "jsapi-tests/tests.h" 13 #include "vm/Realm.h" 14 15 using namespace js; 16 17 BEGIN_TEST(testTypedArrays) { 18 bool ok = true; 19 20 ok = ok && 21 TestPlainTypedArray<JS_NewInt8Array, int8_t, JS_GetInt8ArrayData>(cx) && 22 TestPlainTypedArray<JS_NewUint8Array, uint8_t, JS_GetUint8ArrayData>( 23 cx) && 24 TestPlainTypedArray<JS_NewUint8ClampedArray, uint8_t, 25 JS_GetUint8ClampedArrayData>(cx) && 26 TestPlainTypedArray<JS_NewInt16Array, int16_t, JS_GetInt16ArrayData>( 27 cx) && 28 TestPlainTypedArray<JS_NewUint16Array, uint16_t, JS_GetUint16ArrayData>( 29 cx) && 30 TestPlainTypedArray<JS_NewInt32Array, int32_t, JS_GetInt32ArrayData>( 31 cx) && 32 TestPlainTypedArray<JS_NewUint32Array, uint32_t, JS_GetUint32ArrayData>( 33 cx) && 34 TestPlainTypedArray<JS_NewFloat32Array, float, JS_GetFloat32ArrayData>( 35 cx) && 36 TestPlainTypedArray<JS_NewFloat64Array, double, JS_GetFloat64ArrayData>( 37 cx); 38 39 size_t nbytes = sizeof(double) * 8; 40 RootedObject buffer(cx, JS::NewArrayBuffer(cx, nbytes)); 41 CHECK(JS::IsArrayBufferObject(buffer)); 42 43 RootedObject proto(cx); 44 JS_GetPrototype(cx, buffer, &proto); 45 CHECK(!JS::IsArrayBufferObject(proto)); 46 47 { 48 JS::AutoCheckCannotGC nogc; 49 bool isShared; 50 CHECK_EQUAL(JS::GetArrayBufferByteLength(buffer), nbytes); 51 memset(JS::GetArrayBufferData(buffer, &isShared, nogc), 1, nbytes); 52 CHECK(!isShared); // Because ArrayBuffer 53 } 54 55 ok = 56 ok && 57 TestArrayFromBuffer<JS_NewInt8ArrayWithBuffer, JS_NewInt8ArrayFromArray, 58 int8_t, false, JS_GetInt8ArrayData>(cx) && 59 TestArrayFromBuffer<JS_NewUint8ArrayWithBuffer, JS_NewUint8ArrayFromArray, 60 uint8_t, false, JS_GetUint8ArrayData>(cx) && 61 TestArrayFromBuffer<JS_NewUint8ClampedArrayWithBuffer, 62 JS_NewUint8ClampedArrayFromArray, uint8_t, false, 63 JS_GetUint8ClampedArrayData>(cx) && 64 TestArrayFromBuffer<JS_NewInt16ArrayWithBuffer, JS_NewInt16ArrayFromArray, 65 int16_t, false, JS_GetInt16ArrayData>(cx) && 66 TestArrayFromBuffer<JS_NewUint16ArrayWithBuffer, 67 JS_NewUint16ArrayFromArray, uint16_t, false, 68 JS_GetUint16ArrayData>(cx) && 69 TestArrayFromBuffer<JS_NewInt32ArrayWithBuffer, JS_NewInt32ArrayFromArray, 70 int32_t, false, JS_GetInt32ArrayData>(cx) && 71 TestArrayFromBuffer<JS_NewUint32ArrayWithBuffer, 72 JS_NewUint32ArrayFromArray, uint32_t, false, 73 JS_GetUint32ArrayData>(cx) && 74 TestArrayFromBuffer<JS_NewFloat32ArrayWithBuffer, 75 JS_NewFloat32ArrayFromArray, float, false, 76 JS_GetFloat32ArrayData>(cx) && 77 TestArrayFromBuffer<JS_NewFloat64ArrayWithBuffer, 78 JS_NewFloat64ArrayFromArray, double, false, 79 JS_GetFloat64ArrayData>(cx); 80 81 ok = 82 ok && 83 TestArrayFromBuffer<JS_NewInt8ArrayWithBuffer, JS_NewInt8ArrayFromArray, 84 int8_t, true, JS_GetInt8ArrayData>(cx) && 85 TestArrayFromBuffer<JS_NewUint8ArrayWithBuffer, JS_NewUint8ArrayFromArray, 86 uint8_t, true, JS_GetUint8ArrayData>(cx) && 87 TestArrayFromBuffer<JS_NewUint8ClampedArrayWithBuffer, 88 JS_NewUint8ClampedArrayFromArray, uint8_t, true, 89 JS_GetUint8ClampedArrayData>(cx) && 90 TestArrayFromBuffer<JS_NewInt16ArrayWithBuffer, JS_NewInt16ArrayFromArray, 91 int16_t, true, JS_GetInt16ArrayData>(cx) && 92 TestArrayFromBuffer<JS_NewUint16ArrayWithBuffer, 93 JS_NewUint16ArrayFromArray, uint16_t, true, 94 JS_GetUint16ArrayData>(cx) && 95 TestArrayFromBuffer<JS_NewInt32ArrayWithBuffer, JS_NewInt32ArrayFromArray, 96 int32_t, true, JS_GetInt32ArrayData>(cx) && 97 TestArrayFromBuffer<JS_NewUint32ArrayWithBuffer, 98 JS_NewUint32ArrayFromArray, uint32_t, true, 99 JS_GetUint32ArrayData>(cx) && 100 TestArrayFromBuffer<JS_NewFloat32ArrayWithBuffer, 101 JS_NewFloat32ArrayFromArray, float, true, 102 JS_GetFloat32ArrayData>(cx) && 103 TestArrayFromBuffer<JS_NewFloat64ArrayWithBuffer, 104 JS_NewFloat64ArrayFromArray, double, true, 105 JS_GetFloat64ArrayData>(cx); 106 107 return ok; 108 } 109 110 // Test pinning a view's length. 111 bool TestViewLengthPinning(Handle<JSObject*> view) { 112 // Pin the length of an inline view. (Fails if shared memory.) 113 bool isShared = view.as<NativeObject>()->isSharedMemory(); 114 CHECK(JS::PinArrayBufferOrViewLength(view, true) == !isShared); 115 116 // Fail to pin an already-pinned length. 117 CHECK(!JS::PinArrayBufferOrViewLength(view, true)); 118 119 // Extract an ArrayBuffer. This may cause it to be created, in which case it 120 // will inherit the pinned status from the view. 121 bool bufferIsShared; 122 Rooted<JSObject*> buffer( 123 cx, JS_GetArrayBufferViewBuffer(cx, view, &bufferIsShared)); 124 CHECK(isShared == bufferIsShared); 125 126 // Cannot pin the buffer, since it is already pinned. 127 CHECK(!JS::PinArrayBufferOrViewLength(buffer, true)); 128 129 // Should fail to be detached, since its length is pinned. 130 CHECK(!JS::DetachArrayBuffer(cx, buffer)); 131 CHECK(cx->isExceptionPending()); 132 cx->clearPendingException(); 133 134 // Unpin (fails if shared memory). 135 CHECK(JS::PinArrayBufferOrViewLength(view, false) == !isShared); 136 137 // Fail to unpin when already unpinned. 138 CHECK(!JS::PinArrayBufferOrViewLength(view, false)); 139 140 return true; 141 } 142 143 // Test pinning the length of an ArrayBuffer or SharedArrayBuffer. 144 bool TestBufferLengthPinning(Handle<JSObject*> buffer) { 145 // Pin the length of an inline view. (Fails if shared memory.) 146 bool isShared = !buffer->is<ArrayBufferObject>(); 147 CHECK(JS::PinArrayBufferOrViewLength(buffer, true) == !isShared); 148 149 // Fail to pin an already-pinned length. 150 CHECK(!JS::PinArrayBufferOrViewLength(buffer, true)); 151 152 // Should fail to be detached, since its length is pinned. 153 CHECK(!JS::DetachArrayBuffer(cx, buffer)); 154 CHECK(cx->isExceptionPending()); 155 cx->clearPendingException(); 156 157 // Unpin (fails if shared memory). 158 CHECK(JS::PinArrayBufferOrViewLength(buffer, false) == !isShared); 159 160 // Fail to unpin when already unpinned. 161 CHECK(!JS::PinArrayBufferOrViewLength(buffer, false)); 162 163 return true; 164 } 165 166 // Shared memory can only be mapped by a TypedArray by creating the 167 // TypedArray with a SharedArrayBuffer explicitly, so no tests here. 168 169 template <JSObject* Create(JSContext*, size_t), typename Element, 170 Element* GetData(JSObject*, bool* isShared, 171 const JS::AutoRequireNoGC&)> 172 bool TestPlainTypedArray(JSContext* cx) { 173 { 174 RootedObject notArray(cx, Create(cx, SIZE_MAX)); 175 CHECK(!notArray); 176 JS_ClearPendingException(cx); 177 } 178 179 RootedObject array(cx, Create(cx, 7)); 180 CHECK(JS_IsTypedArrayObject(array)); 181 RootedObject proto(cx); 182 JS_GetPrototype(cx, array, &proto); 183 CHECK(!JS_IsTypedArrayObject(proto)); 184 185 CHECK_EQUAL(JS_GetTypedArrayLength(array), 7u); 186 CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u); 187 CHECK_EQUAL(JS_GetTypedArrayByteLength(array), sizeof(Element) * 7); 188 189 TestViewLengthPinning(array); 190 191 { 192 JS::AutoCheckCannotGC nogc; 193 Element* data; 194 bool isShared; 195 CHECK(data = GetData(array, &isShared, nogc)); 196 CHECK(!isShared); // Because ArrayBuffer 197 *data = 13; 198 } 199 RootedValue v(cx); 200 CHECK(JS_GetElement(cx, array, 0, &v)); 201 CHECK_SAME(v, Int32Value(13)); 202 203 return true; 204 } 205 206 template < 207 JSObject* CreateWithBuffer(JSContext*, JS::HandleObject, size_t, int64_t), 208 JSObject* CreateFromArray(JSContext*, JS::HandleObject), typename Element, 209 bool Shared, Element* GetData(JSObject*, bool*, const JS::AutoRequireNoGC&)> 210 bool TestArrayFromBuffer(JSContext* cx) { 211 if (Shared && 212 !cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) { 213 return true; 214 } 215 216 size_t elts = 8; 217 size_t nbytes = elts * sizeof(Element); 218 RootedObject buffer(cx, Shared ? JS::NewSharedArrayBuffer(cx, nbytes) 219 : JS::NewArrayBuffer(cx, nbytes)); 220 221 TestBufferLengthPinning(buffer); 222 223 { 224 JS::AutoCheckCannotGC nogc; 225 bool isShared; 226 void* data = Shared ? JS::GetSharedArrayBufferData(buffer, &isShared, nogc) 227 : JS::GetArrayBufferData(buffer, &isShared, nogc); 228 CHECK_EQUAL(Shared, isShared); 229 memset(data, 1, nbytes); 230 } 231 232 { 233 RootedObject notArray(cx, CreateWithBuffer(cx, buffer, UINT32_MAX, -1)); 234 CHECK(!notArray); 235 JS_ClearPendingException(cx); 236 } 237 238 RootedObject array(cx, CreateWithBuffer(cx, buffer, 0, -1)); 239 CHECK_EQUAL(JS_GetTypedArrayLength(array), elts); 240 CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u); 241 CHECK_EQUAL(JS_GetTypedArrayByteLength(array), nbytes); 242 { 243 bool isShared; 244 CHECK_EQUAL(JS_GetArrayBufferViewBuffer(cx, array, &isShared), 245 (JSObject*)buffer); 246 CHECK_EQUAL(Shared, isShared); 247 } 248 249 TestViewLengthPinning(array); 250 251 { 252 JS::AutoCheckCannotGC nogc; 253 Element* data; 254 bool isShared; 255 256 CHECK(data = GetData(array, &isShared, nogc)); 257 CHECK_EQUAL(Shared, isShared); 258 259 CHECK_EQUAL( 260 (void*)data, 261 Shared ? (void*)JS::GetSharedArrayBufferData(buffer, &isShared, nogc) 262 : (void*)JS::GetArrayBufferData(buffer, &isShared, nogc)); 263 CHECK_EQUAL(Shared, isShared); 264 265 CHECK_EQUAL(*reinterpret_cast<uint8_t*>(data), 1u); 266 } 267 268 RootedObject shortArray(cx, CreateWithBuffer(cx, buffer, 0, elts / 2)); 269 CHECK_EQUAL(JS_GetTypedArrayLength(shortArray), elts / 2); 270 CHECK_EQUAL(JS_GetTypedArrayByteOffset(shortArray), 0u); 271 CHECK_EQUAL(JS_GetTypedArrayByteLength(shortArray), nbytes / 2); 272 273 RootedObject ofsArray(cx, CreateWithBuffer(cx, buffer, nbytes / 2, -1)); 274 CHECK_EQUAL(JS_GetTypedArrayLength(ofsArray), elts / 2); 275 CHECK_EQUAL(JS_GetTypedArrayByteOffset(ofsArray), nbytes / 2); 276 CHECK_EQUAL(JS_GetTypedArrayByteLength(ofsArray), nbytes / 2); 277 278 // Make sure all 3 views reflect the same buffer at the expected locations 279 JS::RootedValue v(cx, JS::Int32Value(39)); 280 CHECK(JS_SetElement(cx, array, 0, v)); 281 JS::RootedValue v2(cx); 282 CHECK(JS_GetElement(cx, array, 0, &v2)); 283 CHECK_SAME(v, v2); 284 CHECK(JS_GetElement(cx, shortArray, 0, &v2)); 285 CHECK_SAME(v, v2); 286 { 287 JS::AutoCheckCannotGC nogc; 288 Element* data; 289 bool isShared; 290 CHECK(data = GetData(array, &isShared, nogc)); 291 CHECK_EQUAL(Shared, isShared); 292 CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[0])); 293 } 294 295 v.setInt32(40); 296 CHECK(JS_SetElement(cx, array, elts / 2, v)); 297 CHECK(JS_GetElement(cx, array, elts / 2, &v2)); 298 CHECK_SAME(v, v2); 299 CHECK(JS_GetElement(cx, ofsArray, 0, &v2)); 300 CHECK_SAME(v, v2); 301 { 302 JS::AutoCheckCannotGC nogc; 303 Element* data; 304 bool isShared; 305 CHECK(data = GetData(array, &isShared, nogc)); 306 CHECK_EQUAL(Shared, isShared); 307 CHECK_EQUAL(long(v.toInt32()), 308 long(reinterpret_cast<Element*>(data)[elts / 2])); 309 } 310 311 v.setInt32(41); 312 CHECK(JS_SetElement(cx, array, elts - 1, v)); 313 CHECK(JS_GetElement(cx, array, elts - 1, &v2)); 314 CHECK_SAME(v, v2); 315 CHECK(JS_GetElement(cx, ofsArray, elts / 2 - 1, &v2)); 316 CHECK_SAME(v, v2); 317 { 318 JS::AutoCheckCannotGC nogc; 319 Element* data; 320 bool isShared; 321 CHECK(data = GetData(array, &isShared, nogc)); 322 CHECK_EQUAL(Shared, isShared); 323 CHECK_EQUAL(long(v.toInt32()), 324 long(reinterpret_cast<Element*>(data)[elts - 1])); 325 } 326 327 JS::RootedObject copy(cx, CreateFromArray(cx, array)); 328 CHECK(JS_GetElement(cx, array, 0, &v)); 329 CHECK(JS_GetElement(cx, copy, 0, &v2)); 330 CHECK_SAME(v, v2); 331 332 /* The copy should not see changes in the original */ 333 v2.setInt32(42); 334 CHECK(JS_SetElement(cx, array, 0, v2)); 335 CHECK(JS_GetElement(cx, copy, 0, &v2)); 336 CHECK_SAME(v2, v); /* v is still the original value from 'array' */ 337 338 return true; 339 } 340 341 END_TEST(testTypedArrays)