testArrayBuffer.cpp (13762B)
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 5 #include "builtin/TestingFunctions.h" 6 #include "js/Array.h" // JS::NewArrayObject 7 #include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject,NewArrayBuffer{,WithContents},StealArrayBufferContents} 8 #include "js/ArrayBufferMaybeShared.h" 9 #include "js/CallAndConstruct.h" 10 #include "js/Exception.h" 11 #include "js/experimental/TypedData.h" // JS_New{Int32,Uint8}ArrayWithBuffer 12 #include "js/friend/ErrorMessages.h" // JSMSG_* 13 #include "js/MemoryFunctions.h" 14 #include "js/PropertyAndElement.h" // JS_GetElement, JS_GetProperty, JS_SetElement 15 #include "js/Realm.h" 16 #include "jsapi-tests/tests.h" 17 18 #include "vm/Realm-inl.h" 19 20 BEGIN_TEST(testArrayBuffer_bug720949_steal) { 21 static const unsigned NUM_TEST_BUFFERS = 2; 22 static const unsigned MAGIC_VALUE_1 = 3; 23 static const unsigned MAGIC_VALUE_2 = 17; 24 25 JS::RootedObject buf_len1(cx), buf_len200(cx); 26 JS::RootedObject tarray_len1(cx), tarray_len200(cx); 27 28 uint32_t sizes[NUM_TEST_BUFFERS] = {sizeof(uint32_t), 200 * sizeof(uint32_t)}; 29 JS::HandleObject testBuf[NUM_TEST_BUFFERS] = {buf_len1, buf_len200}; 30 JS::HandleObject testArray[NUM_TEST_BUFFERS] = {tarray_len1, tarray_len200}; 31 32 // Single-element ArrayBuffer (uses fixed slots for storage) 33 CHECK(buf_len1 = JS::NewArrayBuffer(cx, sizes[0])); 34 CHECK(tarray_len1 = JS_NewInt32ArrayWithBuffer(cx, testBuf[0], 0, -1)); 35 36 CHECK(JS_SetElement(cx, testArray[0], 0, MAGIC_VALUE_1)); 37 38 // Many-element ArrayBuffer (uses dynamic storage) 39 CHECK(buf_len200 = JS::NewArrayBuffer(cx, 200 * sizeof(uint32_t))); 40 CHECK(tarray_len200 = JS_NewInt32ArrayWithBuffer(cx, testBuf[1], 0, -1)); 41 42 for (unsigned i = 0; i < NUM_TEST_BUFFERS; i++) { 43 JS::HandleObject obj = testBuf[i]; 44 JS::HandleObject view = testArray[i]; 45 uint32_t size = sizes[i]; 46 JS::RootedValue v(cx); 47 48 // Byte lengths should all agree 49 CHECK(JS::IsArrayBufferObject(obj)); 50 CHECK_EQUAL(JS::GetArrayBufferByteLength(obj), size); 51 CHECK(JS_GetProperty(cx, obj, "byteLength", &v)); 52 CHECK(v.isInt32(size)); 53 CHECK(JS_GetProperty(cx, view, "byteLength", &v)); 54 CHECK(v.isInt32(size)); 55 56 // Modifying the underlying data should update the value returned through 57 // the view 58 { 59 JS::AutoCheckCannotGC nogc; 60 bool sharedDummy; 61 uint8_t* data = JS::GetArrayBufferData(obj, &sharedDummy, nogc); 62 CHECK(data != nullptr); 63 *reinterpret_cast<uint32_t*>(data) = MAGIC_VALUE_2; 64 } 65 CHECK(JS_GetElement(cx, view, 0, &v)); 66 CHECK(v.isInt32(MAGIC_VALUE_2)); 67 68 // Steal the contents 69 mozilla::UniquePtr<void, JS::FreePolicy> contents{ 70 JS::StealArrayBufferContents(cx, obj)}; 71 CHECK(contents != nullptr); 72 73 CHECK(JS::IsDetachedArrayBufferObject(obj)); 74 75 // Transfer to a new ArrayBuffer 76 JS::RootedObject dst( 77 cx, JS::NewArrayBufferWithContents(cx, size, std::move(contents))); 78 CHECK(JS::IsArrayBufferObject(dst)); 79 { 80 JS::AutoCheckCannotGC nogc; 81 bool sharedDummy; 82 (void)JS::GetArrayBufferData(obj, &sharedDummy, nogc); 83 } 84 85 JS::RootedObject dstview(cx, JS_NewInt32ArrayWithBuffer(cx, dst, 0, -1)); 86 CHECK(dstview != nullptr); 87 88 CHECK_EQUAL(JS::GetArrayBufferByteLength(dst), size); 89 { 90 JS::AutoCheckCannotGC nogc; 91 bool sharedDummy; 92 uint8_t* data = JS::GetArrayBufferData(dst, &sharedDummy, nogc); 93 CHECK(data != nullptr); 94 CHECK_EQUAL(*reinterpret_cast<uint32_t*>(data), MAGIC_VALUE_2); 95 } 96 CHECK(JS_GetElement(cx, dstview, 0, &v)); 97 CHECK(v.isInt32(MAGIC_VALUE_2)); 98 } 99 100 return true; 101 } 102 END_TEST(testArrayBuffer_bug720949_steal) 103 104 // Varying number of views of a buffer, to test the detachment weak pointers 105 BEGIN_TEST(testArrayBuffer_bug720949_viewList) { 106 JS::RootedObject buffer(cx); 107 108 // No views 109 buffer = JS::NewArrayBuffer(cx, 2000); 110 buffer = nullptr; 111 GC(cx); 112 113 // One view. 114 { 115 buffer = JS::NewArrayBuffer(cx, 2000); 116 JS::RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, -1)); 117 void* contents = JS::StealArrayBufferContents(cx, buffer); 118 CHECK(contents != nullptr); 119 JS_free(nullptr, contents); 120 GC(cx); 121 CHECK(hasDetachedBuffer(view)); 122 CHECK(JS::IsDetachedArrayBufferObject(buffer)); 123 view = nullptr; 124 GC(cx); 125 buffer = nullptr; 126 GC(cx); 127 } 128 129 // Two views 130 { 131 buffer = JS::NewArrayBuffer(cx, 2000); 132 133 JS::RootedObject view1(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, -1)); 134 JS::RootedObject view2(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 1, 200)); 135 136 // Remove, re-add a view 137 view2 = nullptr; 138 GC(cx); 139 view2 = JS_NewUint8ArrayWithBuffer(cx, buffer, 1, 200); 140 141 // Detach 142 void* contents = JS::StealArrayBufferContents(cx, buffer); 143 CHECK(contents != nullptr); 144 JS_free(nullptr, contents); 145 146 CHECK(hasDetachedBuffer(view1)); 147 CHECK(hasDetachedBuffer(view2)); 148 CHECK(JS::IsDetachedArrayBufferObject(buffer)); 149 150 view1 = nullptr; 151 GC(cx); 152 view2 = nullptr; 153 GC(cx); 154 buffer = nullptr; 155 GC(cx); 156 } 157 158 return true; 159 } 160 161 static void GC(JSContext* cx) { 162 JS_GC(cx); 163 JS_GC(cx); // Trigger another to wait for background finalization to end 164 } 165 166 bool hasDetachedBuffer(JS::HandleObject obj) { 167 JS::RootedValue v(cx); 168 return JS_GetProperty(cx, obj, "byteLength", &v) && v.toInt32() == 0; 169 } 170 171 END_TEST(testArrayBuffer_bug720949_viewList) 172 173 BEGIN_TEST(testArrayBuffer_customFreeFunc) { 174 ExternalData data("One two three four"); 175 auto dataPointer = data.pointer(); 176 177 // The buffer takes ownership of the data. 178 JS::RootedObject buffer( 179 cx, JS::NewExternalArrayBuffer(cx, data.len(), std::move(dataPointer))); 180 CHECK(buffer); 181 CHECK(!data.wasFreed()); 182 183 size_t len; 184 bool isShared; 185 uint8_t* bufferData; 186 JS::GetArrayBufferLengthAndData(buffer, &len, &isShared, &bufferData); 187 CHECK_EQUAL(len, data.len()); 188 CHECK(bufferData == data.contents()); 189 CHECK(strcmp(reinterpret_cast<char*>(bufferData), data.asString()) == 0); 190 191 buffer = nullptr; 192 JS_GC(cx); 193 JS_GC(cx); 194 CHECK(data.wasFreed()); 195 196 return true; 197 } 198 END_TEST(testArrayBuffer_customFreeFunc) 199 200 BEGIN_TEST(testArrayBuffer_staticContents) { 201 ExternalData data("One two three four"); 202 203 JS::RootedObject buffer(cx, JS::NewArrayBufferWithUserOwnedContents( 204 cx, data.len(), data.contents())); 205 CHECK(buffer); 206 CHECK(!data.wasFreed()); 207 208 size_t len; 209 bool isShared; 210 uint8_t* bufferData; 211 JS::GetArrayBufferLengthAndData(buffer, &len, &isShared, &bufferData); 212 CHECK_EQUAL(len, data.len()); 213 CHECK(bufferData == data.contents()); 214 CHECK(strcmp(reinterpret_cast<char*>(bufferData), data.asString()) == 0); 215 216 buffer = nullptr; 217 JS_GC(cx); 218 JS_GC(cx); 219 CHECK(!data.wasFreed()); 220 221 data.free(); 222 return true; 223 } 224 END_TEST(testArrayBuffer_staticContents) 225 226 BEGIN_TEST(testArrayBuffer_stealDetachExternal) { 227 static const char dataBytes[] = "One two three four"; 228 ExternalData data(dataBytes); 229 auto dataPointer = data.pointer(); 230 JS::RootedObject buffer( 231 cx, JS::NewExternalArrayBuffer(cx, data.len(), std::move(dataPointer))); 232 CHECK(buffer); 233 CHECK(!data.wasFreed()); 234 235 void* stolenContents = JS::StealArrayBufferContents(cx, buffer); 236 237 // External buffers are stealable: the data is copied into freshly allocated 238 // memory, and the buffer's data pointer is cleared (immediately freeing the 239 // data) and the buffer is marked as detached. 240 CHECK(stolenContents != data.contents()); 241 CHECK(strcmp(reinterpret_cast<char*>(stolenContents), dataBytes) == 0); 242 CHECK(data.wasFreed()); 243 CHECK(JS::IsDetachedArrayBufferObject(buffer)); 244 245 JS_free(cx, stolenContents); 246 return true; 247 } 248 END_TEST(testArrayBuffer_stealDetachExternal) 249 250 BEGIN_TEST(testArrayBuffer_serializeExternal) { 251 JS::RootedValue serializeValue(cx); 252 253 { 254 JS::RootedFunction serialize(cx); 255 serialize = 256 JS_NewFunction(cx, js::testingFunc_serialize, 1, 0, "serialize"); 257 CHECK(serialize); 258 259 serializeValue.setObject(*JS_GetFunctionObject(serialize)); 260 } 261 262 ExternalData data("One two three four"); 263 auto dataPointer = data.pointer(); 264 JS::RootedObject externalBuffer( 265 cx, JS::NewExternalArrayBuffer(cx, data.len(), std::move(dataPointer))); 266 CHECK(externalBuffer); 267 CHECK(!data.wasFreed()); 268 269 JS::RootedValue v(cx, JS::ObjectValue(*externalBuffer)); 270 JS::RootedObject transferMap(cx, 271 JS::NewArrayObject(cx, JS::HandleValueArray(v))); 272 CHECK(transferMap); 273 274 JS::RootedValueArray<2> args(cx); 275 args[0].setObject(*externalBuffer); 276 args[1].setObject(*transferMap); 277 278 // serialize(externalBuffer, [externalBuffer]) should throw for an unhandled 279 // BufferContents kind. 280 CHECK(!JS::Call(cx, JS::UndefinedHandleValue, serializeValue, 281 JS::HandleValueArray(args), &v)); 282 283 JS::ExceptionStack exnStack(cx); 284 CHECK(JS::StealPendingExceptionStack(cx, &exnStack)); 285 286 JS::ErrorReportBuilder report(cx); 287 CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)); 288 289 CHECK_EQUAL(report.report()->errorNumber, 290 static_cast<unsigned int>(JSMSG_SC_NOT_TRANSFERABLE)); 291 292 // Data should have been left alone. 293 CHECK(!data.wasFreed()); 294 295 v.setNull(); 296 transferMap = nullptr; 297 args[0].setNull(); 298 args[1].setNull(); 299 externalBuffer = nullptr; 300 301 JS_GC(cx); 302 JS_GC(cx); 303 CHECK(data.wasFreed()); 304 305 return true; 306 } 307 END_TEST(testArrayBuffer_serializeExternal) 308 309 BEGIN_TEST(testArrayBuffer_copyData) { 310 ExternalData data1("One two three four"); 311 JS::RootedObject buffer1(cx, JS::NewArrayBufferWithUserOwnedContents( 312 cx, data1.len(), data1.contents())); 313 314 CHECK(buffer1); 315 316 ExternalData data2("Six"); 317 JS::RootedObject buffer2(cx, JS::NewArrayBufferWithUserOwnedContents( 318 cx, data2.len(), data2.contents())); 319 320 CHECK(buffer2); 321 322 // Check we can't copy from a larger to a smaller buffer. 323 CHECK(!JS::ArrayBufferCopyData(cx, buffer2, 0, buffer1, 0, data1.len())); 324 325 // Verify expected exception is thrown. 326 { 327 JS::ExceptionStack exnStack(cx); 328 CHECK(JS::StealPendingExceptionStack(cx, &exnStack)); 329 330 JS::ErrorReportBuilder report(cx); 331 CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)); 332 333 CHECK_EQUAL(report.report()->errorNumber, 334 static_cast<unsigned int>(JSMSG_ARRAYBUFFER_COPY_RANGE)); 335 } 336 337 CHECK(JS::ArrayBufferCopyData( 338 cx, buffer1, 0, buffer2, 0, 339 data2.len() - 1 /* don't copy null terminator */)); 340 341 { 342 size_t len; 343 bool isShared; 344 uint8_t* bufferData; 345 JS::GetArrayBufferLengthAndData(buffer1, &len, &isShared, &bufferData); 346 347 ExternalData expected1("Six two three four"); 348 349 fprintf(stderr, "expected %s actual %s\n", expected1.asString(), 350 bufferData); 351 352 CHECK_EQUAL(len, expected1.len()); 353 CHECK_EQUAL(memcmp(expected1.contents(), bufferData, expected1.len()), 0); 354 } 355 356 return true; 357 } 358 END_TEST(testArrayBuffer_copyData) 359 360 BEGIN_TEST(testArrayBuffer_copyDataAcrossGlobals) { 361 JS::RootedObject otherGlobal(cx, createGlobal(nullptr)); 362 if (!otherGlobal) { 363 return false; 364 } 365 366 ExternalData data1("One two three four"); 367 JS::RootedObject buffer1(cx); 368 { 369 js::AutoRealm realm(cx, otherGlobal); 370 buffer1 = JS::NewArrayBufferWithUserOwnedContents(cx, data1.len(), 371 data1.contents()); 372 } 373 CHECK(buffer1); 374 CHECK(JS_WrapObject(cx, &buffer1)); 375 376 ExternalData data2("Six"); 377 JS::RootedObject buffer2(cx, JS::NewArrayBufferWithUserOwnedContents( 378 cx, data2.len(), data2.contents())); 379 380 CHECK(buffer2); 381 382 // Check we can't copy from a larger to a smaller buffer. 383 CHECK(!JS::ArrayBufferCopyData(cx, buffer2, 0, buffer1, 0, data1.len())); 384 385 // Verify expected exception is thrown. 386 { 387 JS::ExceptionStack exnStack(cx); 388 CHECK(JS::StealPendingExceptionStack(cx, &exnStack)); 389 390 JS::ErrorReportBuilder report(cx); 391 CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)); 392 393 CHECK_EQUAL(report.report()->errorNumber, 394 static_cast<unsigned int>(JSMSG_ARRAYBUFFER_COPY_RANGE)); 395 } 396 397 CHECK(JS::ArrayBufferCopyData( 398 cx, buffer1, 0, buffer2, 0, 399 data2.len() - 1 /* don't copy null terminator */)); 400 401 { 402 JS::RootedObject unwrappedBuffer1( 403 cx, JS::UnwrapArrayBufferMaybeShared(buffer1)); 404 CHECK(unwrappedBuffer1); 405 406 size_t len; 407 bool isShared; 408 uint8_t* bufferData; 409 JS::GetArrayBufferLengthAndData(unwrappedBuffer1, &len, &isShared, 410 &bufferData); 411 412 ExternalData expected1("Six two three four"); 413 414 fprintf(stderr, "expected %s actual %s\n", expected1.asString(), 415 bufferData); 416 417 CHECK_EQUAL(len, expected1.len()); 418 CHECK_EQUAL(memcmp(expected1.contents(), bufferData, expected1.len()), 0); 419 } 420 421 return true; 422 } 423 END_TEST(testArrayBuffer_copyDataAcrossGlobals) 424 425 BEGIN_TEST(testArrayBuffer_ArrayBufferClone) { 426 ExternalData data("One two three four"); 427 JS::RootedObject externalBuffer(cx, JS::NewArrayBufferWithUserOwnedContents( 428 cx, data.len(), data.contents())); 429 430 CHECK(externalBuffer); 431 432 size_t lengthToCopy = 3; 433 JS::RootedObject clonedBuffer( 434 cx, JS::ArrayBufferClone(cx, externalBuffer, 4, lengthToCopy)); 435 CHECK(clonedBuffer); 436 437 size_t len; 438 bool isShared; 439 uint8_t* bufferData; 440 JS::GetArrayBufferLengthAndData(clonedBuffer, &len, &isShared, &bufferData); 441 442 CHECK_EQUAL(len, lengthToCopy); 443 444 ExternalData expectedData("two"); 445 CHECK_EQUAL(memcmp(expectedData.contents(), bufferData, len), 0); 446 447 return true; 448 } 449 END_TEST(testArrayBuffer_ArrayBufferClone)