tor-browser

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

testWeakMap.cpp (8784B)


      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 "gc/Zone.h"
      9 #include "js/Array.h"               // JS::GetArrayLength
     10 #include "js/Exception.h"           // JS_IsExceptionPending
     11 #include "js/GlobalObject.h"        // JS_NewGlobalObject
     12 #include "js/PropertyAndElement.h"  // JS_DefineProperty
     13 #include "js/WeakMap.h"
     14 #include "jsapi-tests/tests.h"
     15 #include "vm/Realm.h"
     16 
     17 using namespace js;
     18 
     19 static bool checkSize(JSContext* cx, JS::HandleObject map, uint32_t expected) {
     20  JS::RootedObject keys(cx);
     21  if (!JS_NondeterministicGetWeakMapKeys(cx, map, &keys)) {
     22    return false;
     23  }
     24 
     25  uint32_t length;
     26  if (!JS::GetArrayLength(cx, keys, &length)) {
     27    return false;
     28  }
     29 
     30  return length == expected;
     31 }
     32 
     33 JSObject* keyDelegate = nullptr;
     34 
     35 BEGIN_TEST(testWeakMap_basicOperations) {
     36  JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
     37  CHECK(IsWeakMapObject(map));
     38 
     39  JS::RootedValue key(cx, JS::ObjectOrNullValue(newKey()));
     40  CHECK(!key.isNull());
     41  CHECK(!JS::IsWeakMapObject(&key.toObject()));
     42 
     43  JS::RootedValue r(cx);
     44  CHECK(GetWeakMapEntry(cx, map, key, &r));
     45  CHECK(r.isUndefined());
     46 
     47  CHECK(checkSize(cx, map, 0));
     48 
     49  JS::RootedValue val(cx, JS::Int32Value(1));
     50  CHECK(SetWeakMapEntry(cx, map, key, val));
     51 
     52  CHECK(GetWeakMapEntry(cx, map, key, &r));
     53  CHECK(r == val);
     54  CHECK(checkSize(cx, map, 1));
     55 
     56  JS_GC(cx);
     57 
     58  CHECK(GetWeakMapEntry(cx, map, key, &r));
     59  CHECK(r == val);
     60  CHECK(checkSize(cx, map, 1));
     61 
     62  key.setUndefined();
     63  JS_GC(cx);
     64 
     65  CHECK(checkSize(cx, map, 0));
     66 
     67  return true;
     68 }
     69 
     70 JSObject* newKey() { return JS_NewPlainObject(cx); }
     71 END_TEST(testWeakMap_basicOperations)
     72 
     73 BEGIN_TEST(testWeakMap_setWeakMapEntry_invalid_key) {
     74  JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
     75  CHECK(IsWeakMapObject(map));
     76  CHECK(checkSize(cx, map, 0));
     77 
     78  JS::RootedString test(cx, JS_NewStringCopyZ(cx, "test"));
     79  // sym is a Symbol in global Symbol registry and hence can't be used as a key.
     80  JS::RootedSymbol sym(cx, JS::GetSymbolFor(cx, test));
     81  JS::RootedValue key(cx, JS::SymbolValue(sym));
     82  CHECK(!key.isUndefined());
     83 
     84  JS::RootedValue val(cx, JS::Int32Value(1));
     85 
     86  CHECK(!JS_IsExceptionPending(cx));
     87  CHECK(SetWeakMapEntry(cx, map, key, val) == false);
     88 
     89  CHECK(JS_IsExceptionPending(cx));
     90  JS::Rooted<JS::Value> exn(cx);
     91  CHECK(JS_GetPendingException(cx, &exn));
     92  JS::Rooted<JSObject*> obj(cx, &exn.toObject());
     93  JSErrorReport* err = JS_ErrorFromException(cx, obj);
     94  CHECK(err->exnType == JSEXN_TYPEERR);
     95 
     96  JS_ClearPendingException(cx);
     97 
     98  JS::RootedValue r(cx);
     99  CHECK(GetWeakMapEntry(cx, map, key, &r));
    100  CHECK(r == JS::UndefinedValue());
    101  CHECK(checkSize(cx, map, 0));
    102 
    103  return true;
    104 }
    105 END_TEST(testWeakMap_setWeakMapEntry_invalid_key)
    106 
    107 BEGIN_TEST(testWeakMap_basicOperations_symbols_as_keys) {
    108  JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
    109  CHECK(IsWeakMapObject(map));
    110 
    111  JS::RootedString test(cx, JS_NewStringCopyZ(cx, "test"));
    112  JS::RootedSymbol sym(cx, JS::NewSymbol(cx, test));
    113  CHECK(sym);
    114  JS::RootedValue key(cx, JS::SymbolValue(sym));
    115 
    116  JS::RootedValue r(cx);
    117  CHECK(GetWeakMapEntry(cx, map, key, &r));
    118  CHECK(r.isUndefined());
    119 
    120  CHECK(checkSize(cx, map, 0));
    121 
    122  JS::RootedValue val(cx, JS::Int32Value(1));
    123  CHECK(SetWeakMapEntry(cx, map, key, val));
    124 
    125  CHECK(GetWeakMapEntry(cx, map, key, &r));
    126  CHECK(r == val);
    127  CHECK(checkSize(cx, map, 1));
    128 
    129  JS_GC(cx);
    130 
    131  CHECK(GetWeakMapEntry(cx, map, key, &r));
    132  CHECK(r == val);
    133  CHECK(checkSize(cx, map, 1));
    134 
    135  sym = nullptr;
    136  key.setUndefined();
    137  JS_GC(cx);
    138 
    139  CHECK(checkSize(cx, map, 0));
    140 
    141  return true;
    142 }
    143 END_TEST(testWeakMap_basicOperations_symbols_as_keys)
    144 
    145 BEGIN_TEST(testWeakMap_keyDelegates) {
    146  AutoLeaveZeal nozeal(cx);
    147 
    148  AutoGCParameter param(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
    149  JS_GC(cx);
    150  JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
    151  CHECK(map);
    152 
    153  JS::RootedObject delegate(cx, newDelegate());
    154  JS::RootedObject key(cx, delegate);
    155  if (!JS_WrapObject(cx, &key)) {
    156    return false;
    157  }
    158  CHECK(key);
    159  CHECK(delegate);
    160 
    161  keyDelegate = delegate;
    162 
    163  JS::RootedObject delegateRoot(cx);
    164  {
    165    JSAutoRealm ar(cx, delegate);
    166    delegateRoot = JS_NewPlainObject(cx);
    167    CHECK(delegateRoot);
    168    JS::RootedValue delegateValue(cx, JS::ObjectValue(*delegate));
    169    CHECK(JS_DefineProperty(cx, delegateRoot, "delegate", delegateValue, 0));
    170  }
    171  delegate = nullptr;
    172 
    173  /*
    174   * Perform an incremental GC, introducing an unmarked CCW to force the map
    175   * zone to finish marking before the delegate zone.
    176   */
    177  CHECK(newCCW(map, delegateRoot));
    178  performIncrementalGC();
    179 #ifdef DEBUG
    180  CHECK(map->zone()->lastSweepGroupIndex() <
    181        delegateRoot->zone()->lastSweepGroupIndex());
    182 #endif
    183 
    184  /* Add our entry to the weakmap. */
    185  JS::RootedValue keyVal(cx, JS::ObjectValue(*key));
    186  JS::RootedValue val(cx, JS::Int32Value(1));
    187  CHECK(SetWeakMapEntry(cx, map, keyVal, val));
    188  CHECK(checkSize(cx, map, 1));
    189 
    190  /*
    191   * Check the delegate keeps the entry alive even if the key is not reachable.
    192   */
    193  key = nullptr;
    194  keyVal.setUndefined();
    195  CHECK(newCCW(map, delegateRoot));
    196  performIncrementalGC();
    197  CHECK(checkSize(cx, map, 1));
    198 
    199  /*
    200   * Check that the zones finished marking at the same time, which is
    201   * necessary because of the presence of the delegate and the CCW.
    202   */
    203 #ifdef DEBUG
    204  CHECK(map->zone()->lastSweepGroupIndex() ==
    205        delegateRoot->zone()->lastSweepGroupIndex());
    206 #endif
    207 
    208  /* Check that when the delegate becomes unreachable the entry is removed. */
    209  delegateRoot = nullptr;
    210  keyDelegate = nullptr;
    211  JS_GC(cx);
    212  CHECK(checkSize(cx, map, 0));
    213 
    214  return true;
    215 }
    216 
    217 static size_t DelegateObjectMoved(JSObject* obj, JSObject* old) {
    218  if (!keyDelegate) {
    219    return 0;  // Object got moved before we set keyDelegate to point to it.
    220  }
    221 
    222  MOZ_RELEASE_ASSERT(keyDelegate == old);
    223  keyDelegate = obj;
    224  return 0;
    225 }
    226 
    227 JSObject* newKey() {
    228  static const JSClass keyClass = {
    229      "keyWithDelegate", JSCLASS_HAS_RESERVED_SLOTS(1),
    230      JS_NULL_CLASS_OPS, JS_NULL_CLASS_SPEC,
    231      JS_NULL_CLASS_EXT, JS_NULL_OBJECT_OPS,
    232  };
    233 
    234  JS::RootedObject key(cx, JS_NewObject(cx, &keyClass));
    235  if (!key) {
    236    return nullptr;
    237  }
    238 
    239  return key;
    240 }
    241 
    242 JSObject* newCCW(JS::HandleObject sourceZone, JS::HandleObject destZone) {
    243  /*
    244   * Now ensure that this zone will be swept first by adding a cross
    245   * compartment wrapper to a new object in the same zone as the
    246   * delegate object.
    247   */
    248  JS::RootedObject object(cx);
    249  {
    250    JSAutoRealm ar(cx, destZone);
    251    object = JS_NewPlainObject(cx);
    252    if (!object) {
    253      return nullptr;
    254    }
    255  }
    256  {
    257    JSAutoRealm ar(cx, sourceZone);
    258    if (!JS_WrapObject(cx, &object)) {
    259      return nullptr;
    260    }
    261  }
    262 
    263  // In order to test the SCC algorithm, we need the wrapper/wrappee to be
    264  // tenured.
    265  cx->runtime()->gc.evictNursery();
    266 
    267  return object;
    268 }
    269 
    270 JSObject* newDelegate() {
    271  static const JSClassOps delegateClassOps = {
    272      nullptr,                   // addProperty
    273      nullptr,                   // delProperty
    274      nullptr,                   // enumerate
    275      nullptr,                   // newEnumerate
    276      nullptr,                   // resolve
    277      nullptr,                   // mayResolve
    278      nullptr,                   // finalize
    279      nullptr,                   // call
    280      nullptr,                   // construct
    281      JS_GlobalObjectTraceHook,  // trace
    282  };
    283 
    284  static const js::ClassExtension delegateClassExtension = {
    285      DelegateObjectMoved,  // objectMovedOp
    286  };
    287 
    288  static const JSClass delegateClass = {
    289      "delegate",
    290      JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
    291      &delegateClassOps,
    292      JS_NULL_CLASS_SPEC,
    293      &delegateClassExtension,
    294      JS_NULL_OBJECT_OPS,
    295  };
    296 
    297  /* Create the global object. */
    298  JS::RealmOptions options;
    299  JS::RootedObject global(cx,
    300                          JS_NewGlobalObject(cx, &delegateClass, nullptr,
    301                                             JS::FireOnNewGlobalHook, options));
    302  if (!global) {
    303    return nullptr;
    304  }
    305 
    306  JS_SetReservedSlot(global, 0, JS::Int32Value(42));
    307  return global;
    308 }
    309 
    310 void performIncrementalGC() {
    311  JSRuntime* rt = cx->runtime();
    312  JS::SliceBudget budget(JS::WorkBudget(1000));
    313  rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
    314 
    315  // Wait until we've started marking before finishing the GC
    316  // non-incrementally.
    317  while (rt->gc.state() == gc::State::Prepare) {
    318    rt->gc.debugGCSlice(budget);
    319  }
    320  if (JS::IsIncrementalGCInProgress(cx)) {
    321    rt->gc.finishGC(JS::GCReason::DEBUG_GC);
    322  }
    323 }
    324 END_TEST(testWeakMap_keyDelegates)