tor-browser

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

testStructuredClone.cpp (11125B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "builtin/TestingFunctions.h"
      6 #include "js/ArrayBuffer.h"  // JS::{IsArrayBufferObject,GetArrayBufferLengthAndData,NewExternalArrayBuffer}
      7 #include "js/GlobalObject.h"        // JS_NewGlobalObject
      8 #include "js/PropertyAndElement.h"  // JS_GetProperty, JS_SetProperty
      9 #include "js/StructuredClone.h"
     10 
     11 #include "jsapi-tests/tests.h"
     12 
     13 using namespace js;
     14 
     15 #ifdef DEBUG
     16 // Skip test, since it will abort with an assert in buf->Init(7).
     17 #else
     18 BEGIN_TEST(testStructuredClone_invalidLength) {
     19  auto buf = js::MakeUnique<JSStructuredCloneData>(
     20      JS::StructuredCloneScope::DifferentProcess);
     21  CHECK(buf);
     22  CHECK(buf->Init(7));
     23  RootedValue clone(cx);
     24  JS::CloneDataPolicy policy;
     25  CHECK(!JS_ReadStructuredClone(cx, *buf, JS_STRUCTURED_CLONE_VERSION,
     26                                JS::StructuredCloneScope::DifferentProcess,
     27                                &clone, policy, nullptr, nullptr));
     28  return true;
     29 }
     30 END_TEST(testStructuredClone_invalidLength)
     31 #endif
     32 
     33 BEGIN_TEST(testStructuredClone_object) {
     34  JS::RootedObject g1(cx, createGlobal());
     35  JS::RootedObject g2(cx, createGlobal());
     36  CHECK(g1);
     37  CHECK(g2);
     38 
     39  JS::RootedValue v1(cx);
     40 
     41  {
     42    JSAutoRealm ar(cx, g1);
     43    JS::RootedValue prop(cx, JS::Int32Value(1337));
     44 
     45    JS::RootedObject obj(cx, JS_NewPlainObject(cx));
     46    v1 = JS::ObjectOrNullValue(obj);
     47    CHECK(v1.isObject());
     48    CHECK(JS_SetProperty(cx, obj, "prop", prop));
     49  }
     50 
     51  {
     52    JSAutoRealm ar(cx, g2);
     53    JS::RootedValue v2(cx);
     54 
     55    CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
     56    CHECK(v2.isObject());
     57    JS::RootedObject obj(cx, &v2.toObject());
     58 
     59    JS::RootedValue prop(cx);
     60    CHECK(JS_GetProperty(cx, obj, "prop", &prop));
     61    CHECK(prop.isInt32());
     62    CHECK(&v1.toObject() != obj);
     63    CHECK_EQUAL(prop.toInt32(), 1337);
     64  }
     65 
     66  return true;
     67 }
     68 END_TEST(testStructuredClone_object)
     69 
     70 BEGIN_TEST(testStructuredClone_string) {
     71  JS::RootedObject g1(cx, createGlobal());
     72  JS::RootedObject g2(cx, createGlobal());
     73  CHECK(g1);
     74  CHECK(g2);
     75 
     76  JS::RootedValue v1(cx);
     77 
     78  {
     79    JSAutoRealm ar(cx, g1);
     80    JS::RootedValue prop(cx, JS::Int32Value(1337));
     81 
     82    v1 = JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!"));
     83    CHECK(v1.isString());
     84    CHECK(v1.toString());
     85  }
     86 
     87  {
     88    JSAutoRealm ar(cx, g2);
     89    JS::RootedValue v2(cx);
     90 
     91    CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
     92    CHECK(v2.isString());
     93    CHECK(v2.toString());
     94 
     95    JS::RootedValue expected(
     96        cx, JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!")));
     97    CHECK_SAME(v2, expected);
     98  }
     99 
    100  return true;
    101 }
    102 END_TEST(testStructuredClone_string)
    103 
    104 BEGIN_TEST(testStructuredClone_externalArrayBuffer) {
    105  ExternalData data("One two three four");
    106  auto dataPointer = data.pointer();
    107  JS::RootedObject g1(cx, createGlobal());
    108  JS::RootedObject g2(cx, createGlobal());
    109  CHECK(g1);
    110  CHECK(g2);
    111 
    112  JS::RootedValue v1(cx);
    113 
    114  {
    115    JSAutoRealm ar(cx, g1);
    116 
    117    JS::RootedObject obj(
    118        cx, JS::NewExternalArrayBuffer(cx, data.len(), std::move(dataPointer)));
    119    CHECK(!data.wasFreed());
    120 
    121    v1 = JS::ObjectOrNullValue(obj);
    122    CHECK(v1.isObject());
    123  }
    124 
    125  {
    126    JSAutoRealm ar(cx, g2);
    127    JS::RootedValue v2(cx);
    128 
    129    CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
    130    CHECK(v2.isObject());
    131 
    132    JS::RootedObject obj(cx, &v2.toObject());
    133    CHECK(&v1.toObject() != obj);
    134 
    135    size_t len;
    136    bool isShared;
    137    uint8_t* clonedData;
    138    JS::GetArrayBufferLengthAndData(obj, &len, &isShared, &clonedData);
    139 
    140    // The contents of the two array buffers should be equal, but not the
    141    // same pointer.
    142    CHECK_EQUAL(len, data.len());
    143    CHECK(clonedData != data.contents());
    144    CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
    145    CHECK(!data.wasFreed());
    146  }
    147 
    148  // GC the array buffer before data goes out of scope
    149  v1.setNull();
    150  JS_GC(cx);
    151  JS_GC(cx);  // Trigger another to wait for background finalization to end
    152 
    153  CHECK(data.wasFreed());
    154 
    155  return true;
    156 }
    157 END_TEST(testStructuredClone_externalArrayBuffer)
    158 
    159 BEGIN_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess) {
    160  CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::SameProcess));
    161  CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::DifferentProcess));
    162  return true;
    163 }
    164 
    165 bool testStructuredCloneCopy(JS::StructuredCloneScope scope) {
    166  ExternalData data("One two three four");
    167  auto dataPointer = data.pointer();
    168  JS::RootedObject buffer(
    169      cx, JS::NewExternalArrayBuffer(cx, data.len(), std::move(dataPointer)));
    170  CHECK(buffer);
    171  CHECK(!data.wasFreed());
    172 
    173  JS::RootedValue v1(cx, JS::ObjectValue(*buffer));
    174  JS::RootedValue v2(cx);
    175  CHECK(clone(scope, v1, &v2));
    176  JS::RootedObject bufferOut(cx, v2.toObjectOrNull());
    177  CHECK(bufferOut);
    178  CHECK(JS::IsArrayBufferObject(bufferOut));
    179 
    180  size_t len;
    181  bool isShared;
    182  uint8_t* clonedData;
    183  JS::GetArrayBufferLengthAndData(bufferOut, &len, &isShared, &clonedData);
    184 
    185  // Cloning should copy the data, so the contents of the two array buffers
    186  // should be equal, but not the same pointer.
    187  CHECK_EQUAL(len, data.len());
    188  CHECK(clonedData != data.contents());
    189  CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
    190  CHECK(!data.wasFreed());
    191 
    192  buffer = nullptr;
    193  bufferOut = nullptr;
    194  v1.setNull();
    195  v2.setNull();
    196  JS_GC(cx);
    197  JS_GC(cx);
    198  CHECK(data.wasFreed());
    199 
    200  return true;
    201 }
    202 
    203 bool clone(JS::StructuredCloneScope scope, JS::HandleValue v1,
    204           JS::MutableHandleValue v2) {
    205  JSAutoStructuredCloneBuffer clonedBuffer(scope, nullptr, nullptr);
    206  CHECK(clonedBuffer.write(cx, v1));
    207  CHECK(clonedBuffer.read(cx, v2));
    208  return true;
    209 }
    210 END_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess)
    211 
    212 struct StructuredCloneTestPrincipals final : public JSPrincipals {
    213  uint32_t rank;
    214 
    215  explicit StructuredCloneTestPrincipals(uint32_t rank, int32_t rc = 1)
    216      : rank(rank) {
    217    this->refcount = rc;
    218  }
    219 
    220  bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
    221    return JS_WriteUint32Pair(writer, rank, 0);
    222  }
    223 
    224  bool isSystemOrAddonPrincipal() override { return true; }
    225 
    226  static bool read(JSContext* cx, JSStructuredCloneReader* reader,
    227                   JSPrincipals** outPrincipals) {
    228    uint32_t rank;
    229    uint32_t unused;
    230    if (!JS_ReadUint32Pair(reader, &rank, &unused)) {
    231      return false;
    232    }
    233 
    234    *outPrincipals = new StructuredCloneTestPrincipals(rank);
    235    return !!*outPrincipals;
    236  }
    237 
    238  static void destroy(JSPrincipals* p) {
    239    auto p1 = static_cast<StructuredCloneTestPrincipals*>(p);
    240    delete p1;
    241  }
    242 
    243  static uint32_t getRank(JSPrincipals* p) {
    244    if (!p) {
    245      return 0;
    246    }
    247    return static_cast<StructuredCloneTestPrincipals*>(p)->rank;
    248  }
    249 
    250  static bool subsumes(JSPrincipals* a, JSPrincipals* b) {
    251    return getRank(a) > getRank(b);
    252  }
    253 
    254  static JSSecurityCallbacks securityCallbacks;
    255 
    256  static StructuredCloneTestPrincipals testPrincipals;
    257 };
    258 
    259 JSSecurityCallbacks StructuredCloneTestPrincipals::securityCallbacks = {
    260    nullptr,  // contentSecurityPolicyAllows
    261    nullptr,  // codeForEvalGets
    262    subsumes};
    263 
    264 BEGIN_TEST(testStructuredClone_SavedFrame) {
    265  JS_SetSecurityCallbacks(cx,
    266                          &StructuredCloneTestPrincipals::securityCallbacks);
    267  JS_InitDestroyPrincipalsCallback(cx, StructuredCloneTestPrincipals::destroy);
    268  JS_InitReadPrincipalsCallback(cx, StructuredCloneTestPrincipals::read);
    269 
    270  auto testPrincipals = new StructuredCloneTestPrincipals(42, 0);
    271  CHECK(testPrincipals);
    272 
    273  auto DONE = (JSPrincipals*)0xDEADBEEF;
    274 
    275  struct {
    276    const char* name;
    277    JSPrincipals* principals;
    278  } principalsToTest[] = {
    279      {"IsSystem", &js::ReconstructedSavedFramePrincipals::IsSystem},
    280      {"IsNotSystem", &js::ReconstructedSavedFramePrincipals::IsNotSystem},
    281      {"testPrincipals", testPrincipals},
    282      {"nullptr principals", nullptr},
    283      {"DONE", DONE}};
    284 
    285  const char* FILENAME = "filename.js";
    286 
    287  for (auto* pp = principalsToTest; pp->principals != DONE; pp++) {
    288    fprintf(stderr, "Testing with principals '%s'\n", pp->name);
    289 
    290    JS::RealmOptions options;
    291    JS::RootedObject g(cx,
    292                       JS_NewGlobalObject(cx, getGlobalClass(), pp->principals,
    293                                          JS::FireOnNewGlobalHook, options));
    294    CHECK(g);
    295    JSAutoRealm ar(cx, g);
    296 
    297    CHECK(js::DefineTestingFunctions(cx, g, false, false));
    298 
    299    JS::RootedValue srcVal(cx);
    300    CHECK(
    301        evaluate("(function one() {                      \n"   // 1
    302                 "  return (function two() {             \n"   // 2
    303                 "    return (function three() {         \n"   // 3
    304                 "      return saveStack();              \n"   // 4
    305                 "    }());                              \n"   // 5
    306                 "  }());                                \n"   // 6
    307                 "}());                                  \n",  // 7
    308                 FILENAME, 1, &srcVal));
    309 
    310    CHECK(srcVal.isObject());
    311    JS::RootedObject srcObj(cx, &srcVal.toObject());
    312 
    313    CHECK(srcObj->is<js::SavedFrame>());
    314    JS::Rooted<js::SavedFrame*> srcFrame(cx, &srcObj->as<js::SavedFrame>());
    315 
    316    CHECK(srcFrame->getPrincipals() == pp->principals);
    317 
    318    JS::RootedValue destVal(cx);
    319    CHECK(JS_StructuredClone(cx, srcVal, &destVal, nullptr, nullptr));
    320 
    321    CHECK(destVal.isObject());
    322    JS::RootedObject destObj(cx, &destVal.toObject());
    323 
    324    CHECK(destObj->is<js::SavedFrame>());
    325    JS::Handle<js::SavedFrame*> destFrame = destObj.as<js::SavedFrame>();
    326 
    327    size_t framesCopied = 0;
    328    for (JS::Handle<js::SavedFrame*> f :
    329         js::SavedFrame::RootedRange(cx, destFrame)) {
    330      framesCopied++;
    331 
    332      CHECK(f != srcFrame);
    333 
    334      if (pp->principals == testPrincipals) {
    335        // We shouldn't get a pointer to the same
    336        // StructuredCloneTestPrincipals instance since we should have
    337        // serialized and then deserialized it into a new instance.
    338        CHECK(f->getPrincipals() != pp->principals);
    339 
    340        // But it should certainly have the same rank.
    341        CHECK(StructuredCloneTestPrincipals::getRank(f->getPrincipals()) ==
    342              StructuredCloneTestPrincipals::getRank(pp->principals));
    343      } else {
    344        // For our singleton principals, we should always get the same
    345        // pointer back.
    346        CHECK(js::ReconstructedSavedFramePrincipals::is(pp->principals) ||
    347              pp->principals == nullptr);
    348        CHECK(f->getPrincipals() == pp->principals);
    349      }
    350 
    351      CHECK(EqualStrings(f->getSource(), srcFrame->getSource()));
    352      CHECK(f->getLine() == srcFrame->getLine());
    353      CHECK(f->getColumn() == srcFrame->getColumn());
    354      CHECK(EqualStrings(f->getFunctionDisplayName(),
    355                         srcFrame->getFunctionDisplayName()));
    356 
    357      srcFrame = srcFrame->getParent();
    358    }
    359 
    360    // Four function frames + one global frame.
    361    CHECK(framesCopied == 4);
    362  }
    363 
    364  return true;
    365 }
    366 END_TEST(testStructuredClone_SavedFrame)