tor-browser

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

testObjectSwap.cpp (13328B)


      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 /*
      9 * Test JSObject::swap.
     10 *
     11 * This test creates objects from a description of their configuration. Objects
     12 * are given unique property names and values. A list of configurations is
     13 * created and the result of swapping every combination checked.
     14 */
     15 
     16 #include "mozilla/Sprintf.h"
     17 
     18 #include "js/AllocPolicy.h"
     19 #include "js/Vector.h"
     20 #include "jsapi-tests/tests.h"
     21 #include "vm/PlainObject.h"
     22 
     23 #include "gc/StableCellHasher-inl.h"
     24 #include "vm/JSContext-inl.h"
     25 #include "vm/JSObject-inl.h"
     26 
     27 using namespace js;
     28 
     29 struct NativeConfig {
     30  uint32_t propCount;
     31  uint32_t elementCount;
     32  bool inDictionaryMode;
     33 };
     34 
     35 struct ProxyConfig {
     36  bool inlineValues;
     37 };
     38 
     39 struct ObjectConfig {
     40  const JSClass* clasp;
     41  bool isNative;
     42  bool nurseryAllocated;
     43  bool hasUniqueId;
     44  union {
     45    NativeConfig native;
     46    ProxyConfig proxy;
     47  };
     48 };
     49 
     50 using ObjectConfigVector = Vector<ObjectConfig, 0, SystemAllocPolicy>;
     51 
     52 static const JSClass TestProxyClasses[] = {
     53    PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(1 /* Min */)),
     54    PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(2)),
     55    PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(7)),
     56    PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(8)),
     57    PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(14 /* Max */)),
     58 };
     59 
     60 static void TestDOMObject_finalize(JS::GCContext* gcx, JSObject* obj) {
     61  // Dummy finalize method so we can swap with background finalized object.
     62 }
     63 
     64 static const JSClassOps TestDOMObjectClassOps = {
     65    nullptr,  // addProperty
     66    nullptr,  // delProperty
     67    nullptr,  // enumerate
     68    nullptr,  // newEnumerate
     69    nullptr,  // resolve
     70    nullptr,  // mayResolve
     71    TestDOMObject_finalize,
     72    nullptr,  // call
     73    nullptr,  // construct
     74    nullptr,
     75 };
     76 
     77 #define DEFINE_TEST_DOM_CLASS(Slots)                                \
     78  {"TestDOMObject",                                                 \
     79   JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(Slots) |      \
     80       JSCLASS_BACKGROUND_FINALIZE | JSCLASS_SKIP_NURSERY_FINALIZE, \
     81   &TestDOMObjectClassOps}
     82 
     83 static const JSClass TestDOMClasses[] = {
     84    DEFINE_TEST_DOM_CLASS(0), DEFINE_TEST_DOM_CLASS(1),
     85    DEFINE_TEST_DOM_CLASS(2), DEFINE_TEST_DOM_CLASS(7),
     86    DEFINE_TEST_DOM_CLASS(8), DEFINE_TEST_DOM_CLASS(20)};
     87 
     88 #undef DEFINE_TEST_DOM_CLASS
     89 
     90 static const uint32_t TestPropertyCounts[] = {0, 1, 2, 7, 8, 20};
     91 
     92 static const uint32_t TestElementCounts[] = {0, 20};
     93 
     94 static bool Verbose = false;
     95 
     96 class TenuredProxyHandler final : public Wrapper {
     97 public:
     98  static const TenuredProxyHandler singleton;
     99  constexpr TenuredProxyHandler() : Wrapper(0) {}
    100  bool canNurseryAllocate() const override { return false; }
    101 };
    102 
    103 const TenuredProxyHandler TenuredProxyHandler::singleton;
    104 
    105 class NurseryProxyHandler final : public Wrapper {
    106 public:
    107  static const NurseryProxyHandler singleton;
    108  constexpr NurseryProxyHandler() : Wrapper(0) {}
    109  bool canNurseryAllocate() const override { return true; }
    110 };
    111 
    112 const NurseryProxyHandler NurseryProxyHandler::singleton;
    113 
    114 BEGIN_TEST(testObjectSwap) {
    115  AutoLeaveZeal noZeal(cx);
    116 
    117  ObjectConfigVector objectConfigs = CreateObjectConfigs();
    118 
    119  for (const ObjectConfig& config1 : objectConfigs) {
    120    for (const ObjectConfig& config2 : objectConfigs) {
    121      {
    122        uint32_t id1;
    123        RootedObject obj1(cx, CreateObject(config1, &id1));
    124        CHECK(obj1);
    125 
    126        uint32_t id2;
    127        RootedObject obj2(cx, CreateObject(config2, &id2));
    128        CHECK(obj2);
    129 
    130        if (Verbose) {
    131          fprintf(stderr, "Swap %p (%s) and %p (%s)\n", obj1.get(),
    132                  GetLocation(obj1), obj2.get(), GetLocation(obj2));
    133        }
    134 
    135        uint64_t uid1 = 0;
    136        if (config1.hasUniqueId) {
    137          uid1 = gc::GetUniqueIdInfallible(obj1);
    138        }
    139        uint64_t uid2 = 0;
    140        if (config2.hasUniqueId) {
    141          uid2 = gc::GetUniqueIdInfallible(obj2);
    142        }
    143 
    144        {
    145          AutoEnterOOMUnsafeRegion oomUnsafe;
    146          JSObject::swap(cx, obj1, obj2, oomUnsafe);
    147        }
    148 
    149        CHECK(CheckObject(obj1, config2, id2));
    150        CHECK(CheckObject(obj2, config1, id1));
    151 
    152        CHECK(CheckUniqueIds(obj1, config1.hasUniqueId, uid1, obj2,
    153                             config2.hasUniqueId, uid2));
    154 
    155        // Check we can promote swapped nursery objects.
    156        cx->minorGC(JS::GCReason::API);
    157      }
    158 
    159      if (Verbose) {
    160        fprintf(stderr, "\n");
    161      }
    162    }
    163 
    164    // JSObject::swap can suppress GC so ensure we clean up occasionally.
    165    JS_GC(cx);
    166  }
    167 
    168  return true;
    169 }
    170 
    171 ObjectConfigVector CreateObjectConfigs() {
    172  ObjectConfigVector configs;
    173 
    174  ObjectConfig config;
    175 
    176  for (bool nurseryAllocated : {false, true}) {
    177    config.nurseryAllocated = nurseryAllocated;
    178 
    179    for (bool hasUniqueId : {false, true}) {
    180      config.hasUniqueId = hasUniqueId;
    181 
    182      config.isNative = true;
    183      config.native = NativeConfig{0, false};
    184 
    185      for (const JSClass& jsClass : TestDOMClasses) {
    186        config.clasp = &jsClass;
    187 
    188        for (uint32_t propCount : TestPropertyCounts) {
    189          config.native.propCount = propCount;
    190 
    191          for (uint32_t elementCount : TestElementCounts) {
    192            config.native.elementCount = elementCount;
    193 
    194            for (bool inDictionaryMode : {false, true}) {
    195              if (inDictionaryMode && propCount == 0) {
    196                continue;
    197              }
    198 
    199              config.native.inDictionaryMode = inDictionaryMode;
    200              MOZ_RELEASE_ASSERT(configs.append(config));
    201            }
    202          }
    203        }
    204      }
    205 
    206      config.isNative = false;
    207      config.proxy = ProxyConfig{false};
    208 
    209      for (const JSClass& jsClass : TestProxyClasses) {
    210        config.clasp = &jsClass;
    211 
    212        for (bool inlineValues : {true, false}) {
    213          config.proxy.inlineValues = inlineValues;
    214          MOZ_RELEASE_ASSERT(configs.append(config));
    215        }
    216      }
    217    }
    218  }
    219 
    220  return configs;
    221 }
    222 
    223 const char* GetLocation(JSObject* obj) {
    224  return obj->isTenured() ? "tenured heap" : "nursery";
    225 }
    226 
    227 // Counter used to give slots and property names unique values.
    228 uint32_t nextId = 0;
    229 
    230 JSObject* CreateObject(const ObjectConfig& config, uint32_t* idOut) {
    231  *idOut = nextId;
    232  JSObject* obj =
    233      config.isNative ? CreateNativeObject(config) : CreateProxy(config);
    234 
    235  if (config.hasUniqueId) {
    236    uint64_t unused;
    237    if (!gc::GetOrCreateUniqueId(obj, &unused)) {
    238      return nullptr;
    239    }
    240  }
    241 
    242  return obj;
    243 }
    244 
    245 JSObject* CreateNativeObject(const ObjectConfig& config) {
    246  MOZ_ASSERT(config.isNative);
    247 
    248  NewObjectKind kind = config.nurseryAllocated ? GenericObject : TenuredObject;
    249  Rooted<NativeObject*> obj(cx,
    250                            NewBuiltinClassInstance(cx, config.clasp, kind));
    251  if (!obj) {
    252    return nullptr;
    253  }
    254 
    255  MOZ_RELEASE_ASSERT(IsInsideNursery(obj) == config.nurseryAllocated);
    256 
    257  for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(config.clasp); i++) {
    258    JS::SetReservedSlot(obj, i, Int32Value(nextId++));
    259  }
    260 
    261  if (config.native.inDictionaryMode) {
    262    // Put object in dictionary mode by defining a non-last property and
    263    // deleting it later.
    264    MOZ_RELEASE_ASSERT(config.native.propCount != 0);
    265    if (!JS_DefineProperty(cx, obj, "dummy", 0, JSPROP_ENUMERATE)) {
    266      return nullptr;
    267    }
    268  }
    269 
    270  for (uint32_t i = 0; i < config.native.propCount; i++) {
    271    RootedId name(cx, CreatePropName(nextId++));
    272    if (name.isVoid()) {
    273      return nullptr;
    274    }
    275 
    276    if (!JS_DefinePropertyById(cx, obj, name, nextId++, JSPROP_ENUMERATE)) {
    277      return nullptr;
    278    }
    279  }
    280 
    281  if (config.native.elementCount) {
    282    if (!obj->ensureElements(cx, config.native.elementCount)) {
    283      return nullptr;
    284    }
    285    for (uint32_t i = 0; i < config.native.elementCount; i++) {
    286      obj->setDenseInitializedLength(i + 1);
    287      obj->initDenseElement(i, Int32Value(nextId++));
    288    }
    289    MOZ_ASSERT(obj->hasDynamicElements());
    290  }
    291 
    292  if (config.native.inDictionaryMode) {
    293    JS::ObjectOpResult result;
    294    JS_DeleteProperty(cx, obj, "dummy", result);
    295    MOZ_RELEASE_ASSERT(result.ok());
    296  }
    297 
    298  MOZ_RELEASE_ASSERT(obj->inDictionaryMode() == config.native.inDictionaryMode);
    299 
    300  return obj;
    301 }
    302 
    303 JSObject* CreateProxy(const ObjectConfig& config) {
    304  RootedValue priv(cx, Int32Value(nextId++));
    305 
    306  RootedObject expando(cx, NewPlainObject(cx));
    307  RootedValue expandoId(cx, Int32Value(nextId++));
    308  if (!expando || !JS_SetProperty(cx, expando, "id", expandoId)) {
    309    return nullptr;
    310  }
    311 
    312  ProxyOptions options;
    313  options.setClass(config.clasp);
    314  options.setLazyProto(true);
    315 
    316  const Wrapper* handler;
    317  if (config.nurseryAllocated) {
    318    handler = &NurseryProxyHandler::singleton;
    319  } else {
    320    handler = &TenuredProxyHandler::singleton;
    321  }
    322 
    323  RootedObject obj(cx, NewProxyObject(cx, handler, priv, nullptr, options));
    324  if (!obj) {
    325    return nullptr;
    326  }
    327 
    328  Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>());
    329  proxy->setExpando(expando);
    330 
    331  for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(config.clasp); i++) {
    332    JS::SetReservedSlot(proxy, i, Int32Value(nextId++));
    333  }
    334 
    335  if (!config.proxy.inlineValues) {
    336    // To create a proxy with non-inline values we must swap the proxy with an
    337    // object with a different size.
    338    NewObjectKind kind =
    339        config.nurseryAllocated ? GenericObject : TenuredObject;
    340    RootedObject dummy(cx,
    341                       NewBuiltinClassInstance(cx, &TestDOMClasses[0], kind));
    342    if (!dummy) {
    343      return nullptr;
    344    }
    345 
    346    AutoEnterOOMUnsafeRegion oomUnsafe;
    347    JSObject::swap(cx, obj, dummy, oomUnsafe);
    348    proxy = &dummy->as<ProxyObject>();
    349  }
    350 
    351  MOZ_RELEASE_ASSERT(IsInsideNursery(proxy) == config.nurseryAllocated);
    352  MOZ_RELEASE_ASSERT(proxy->usingInlineValueArray() ==
    353                     config.proxy.inlineValues);
    354 
    355  return proxy;
    356 }
    357 
    358 bool CheckObject(HandleObject obj, const ObjectConfig& config, uint32_t id) {
    359  CHECK(obj->is<NativeObject>() == config.isNative);
    360  CHECK(obj->getClass() == config.clasp);
    361 
    362  uint32_t reservedSlots = JSCLASS_RESERVED_SLOTS(config.clasp);
    363 
    364  if (Verbose) {
    365    fprintf(stderr, "Check %p is a %s object with %u reserved slots", obj.get(),
    366            config.isNative ? "native" : "proxy", reservedSlots);
    367    if (config.isNative) {
    368      fprintf(stderr,
    369              ", %u properties, %u elements and %s in dictionary mode\n",
    370              config.native.propCount, config.native.elementCount,
    371              config.native.inDictionaryMode ? "is" : "is not");
    372    } else {
    373      fprintf(stderr, " with %s values\n",
    374              config.proxy.inlineValues ? "inline" : "out-of-line");
    375    }
    376  }
    377 
    378  if (!config.isNative) {
    379    CHECK(GetProxyPrivate(obj) == Int32Value(id++));
    380 
    381    Value expandoValue = GetProxyExpando(obj);
    382    CHECK(expandoValue.isObject());
    383 
    384    RootedObject expando(cx, &expandoValue.toObject());
    385    RootedValue expandoId(cx);
    386    JS_GetProperty(cx, expando, "id", &expandoId);
    387    CHECK(expandoId == Int32Value(id++));
    388  }
    389 
    390  for (uint32_t i = 0; i < reservedSlots; i++) {
    391    CHECK(JS::GetReservedSlot(obj, i) == Int32Value(id++));
    392  }
    393 
    394  if (config.isNative) {
    395    Rooted<NativeObject*> nobj(cx, &obj->as<NativeObject>());
    396    uint32_t propCount = GetPropertyCount(nobj);
    397 
    398    CHECK(propCount == config.native.propCount);
    399 
    400    for (uint32_t i = 0; i < config.native.propCount; i++) {
    401      RootedId name(cx, CreatePropName(id++));
    402      CHECK(!name.isVoid());
    403 
    404      RootedValue value(cx);
    405      CHECK(JS_GetPropertyById(cx, obj, name, &value));
    406      CHECK(value == Int32Value(id++));
    407    }
    408 
    409    CHECK(nobj->getDenseInitializedLength() == config.native.elementCount);
    410    for (uint32_t i = 0; i < config.native.elementCount; i++) {
    411      Value value = nobj->getDenseElement(i);
    412      CHECK(value == Int32Value(id++));
    413    }
    414  }
    415 
    416  return true;
    417 }
    418 
    419 bool CheckUniqueIds(HandleObject obj1, bool hasUniqueId1, uint64_t uid1,
    420                    HandleObject obj2, bool hasUniqueId2, uint64_t uid2) {
    421  if (uid1 && uid2) {
    422    MOZ_RELEASE_ASSERT(uid1 != uid2);
    423  }
    424 
    425  // Check unique IDs are NOT swapped.
    426  CHECK(CheckUniqueId(obj1, hasUniqueId1, uid1));
    427  CHECK(CheckUniqueId(obj2, hasUniqueId2, uid2));
    428 
    429  // Check unique IDs are different if present.
    430  if (gc::HasUniqueId(obj1) && gc::HasUniqueId(obj2)) {
    431    CHECK(gc::GetUniqueIdInfallible(obj1) != gc::GetUniqueIdInfallible(obj2));
    432  }
    433 
    434  return true;
    435 }
    436 
    437 bool CheckUniqueId(HandleObject obj, bool hasUniqueId, uint64_t uid) {
    438  if (hasUniqueId) {
    439    CHECK(gc::HasUniqueId(obj));
    440    CHECK(gc::GetUniqueIdInfallible(obj) == uid);
    441  } else {
    442    // Swap may add a unique ID to an object.
    443  }
    444 
    445  if (obj->is<NativeObject>()) {
    446    CHECK(!obj->zone()->uniqueIds().has(obj));
    447  }
    448  return true;
    449 }
    450 
    451 uint32_t GetPropertyCount(NativeObject* obj) {
    452  uint32_t count = 0;
    453  for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
    454    count++;
    455  }
    456 
    457  return count;
    458 }
    459 
    460 jsid CreatePropName(uint32_t id) {
    461  char buffer[32];
    462  SprintfLiteral(buffer, "prop%u", id);
    463 
    464  RootedString atom(cx, JS_AtomizeString(cx, buffer));
    465  if (!atom) {
    466    return jsid::Void();
    467  }
    468 
    469  return jsid::NonIntAtom(atom);
    470 }
    471 
    472 END_TEST(testObjectSwap)