tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)