tor-browser

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

WeakRefObject.cpp (8825B)


      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 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "builtin/WeakRefObject.h"
      8 
      9 #include "jsapi.h"
     10 
     11 #include "gc/FinalizationObservers.h"
     12 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     13 #include "vm/GlobalObject.h"
     14 #include "vm/JSContext.h"
     15 
     16 #include "gc/PrivateIterators-inl.h"
     17 #include "gc/WeakMap-inl.h"
     18 #include "vm/JSObject-inl.h"
     19 #include "vm/NativeObject-inl.h"
     20 
     21 using namespace js::gc;
     22 
     23 namespace js {
     24 
     25 /* static */
     26 bool WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp) {
     27  CallArgs args = CallArgsFromVp(argc, vp);
     28 
     29  // https://tc39.es/proposal-weakrefs/#sec-weak-ref-constructor
     30  // The WeakRef constructor is not intended to be called as a function and will
     31  // throw an exception when called in that manner.
     32  if (!ThrowIfNotConstructing(cx, args, "WeakRef")) {
     33    return false;
     34  }
     35 
     36  // https://tc39.es/proposal-weakrefs/#sec-weak-ref-target
     37  // 1. If NewTarget is undefined, throw a TypeError exception.
     38  // 2. If Type(target) is not Object, throw a TypeError exception.
     39  if (!CanBeHeldWeakly(args.get(0))) {
     40    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
     41                              JSMSG_BAD_WEAKREF_TARGET);
     42    return false;
     43  }
     44 
     45  // 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget,
     46  //    "%WeakRefPrototype%", « [[Target]] »).
     47  RootedObject proto(cx);
     48  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WeakRef, &proto)) {
     49    return false;
     50  }
     51 
     52  Rooted<WeakRefObject*> weakRef(
     53      cx, NewObjectWithClassProto<WeakRefObject>(cx, proto));
     54  if (!weakRef) {
     55    return false;
     56  }
     57 
     58  RootedValue target(cx, args[0]);
     59  bool isPermanent = false;
     60  if (target.isObject()) {
     61    // Fully unwrap the target to register it with the GC.
     62    RootedObject object(cx, CheckedUnwrapDynamic(&target.toObject(), cx));
     63    if (!object) {
     64      ReportAccessDenied(cx);
     65      return false;
     66    }
     67 
     68    target = ObjectValue(*object);
     69 
     70    // If the target is a DOM wrapper, preserve it.
     71    if (!preserveDOMWrapper(cx, object)) {
     72      return false;
     73    }
     74  } else {
     75    JS::Symbol* symbol = target.toSymbol();
     76    isPermanent = symbol->isPermanentAndMayBeShared();
     77  }
     78 
     79  // Skip the following steps for permanent targets.
     80  // (See the note following https://tc39.es/ecma262/#sec-canbeheldweakly)
     81  if (!isPermanent) {
     82    // 4. Perform AddToKeptObjects(target).
     83    if (!target.toGCThing()->zone()->addToKeptObjects(target)) {
     84      ReportOutOfMemory(cx);
     85      return false;
     86    };
     87 
     88    // Add an entry to the per-zone maps from target JS object to a list of weak
     89    // ref objects.
     90    gc::GCRuntime* gc = &cx->runtime()->gc;
     91    if (!gc->registerWeakRef(cx, target, weakRef)) {
     92      ReportOutOfMemory(cx);
     93      return false;
     94    }
     95  }
     96 
     97  // 5. Set weakRef.[[Target]] to target.
     98  weakRef->setReservedSlotGCThingAsPrivate(TargetSlot, target.toGCThing());
     99 
    100  // 6. Return weakRef.
    101  args.rval().setObject(*weakRef);
    102 
    103  return true;
    104 }
    105 
    106 /* static */
    107 bool WeakRefObject::preserveDOMWrapper(JSContext* cx, HandleObject obj) {
    108  if (!MaybePreserveDOMWrapper(cx, obj)) {
    109    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    110                              JSMSG_BAD_WEAKREF_TARGET);
    111    return false;
    112  }
    113 
    114  cx->runtime()->commitPendingWrapperPreservations(obj->zone());
    115 
    116  return true;
    117 }
    118 
    119 /* static */
    120 void WeakRefObject::trace(JSTracer* trc, JSObject* obj) {
    121  WeakRefObject* weakRef = &obj->as<WeakRefObject>();
    122 
    123  // The next and prev slots added by the ObserverListObject base class are
    124  // internal weak pointers and are not traced, even if requested by the tracer.
    125 
    126  if (trc->traceWeakEdges()) {
    127    Value target = weakRef->target();
    128    Value prior = target;
    129    TraceManuallyBarrieredEdge(trc, &target, "WeakRefObject::target");
    130    if (target != prior) {
    131      weakRef->setTargetUnbarriered(target);
    132    }
    133  }
    134 }
    135 
    136 /* static */
    137 void WeakRefObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    138  auto* weakRef = &obj->as<WeakRefObject>();
    139  weakRef->clearTargetAndUnlink();
    140 }
    141 
    142 const JSClassOps WeakRefObject::classOps_ = {
    143    nullptr,   // addProperty
    144    nullptr,   // delProperty
    145    nullptr,   // enumerate
    146    nullptr,   // newEnumerate
    147    nullptr,   // resolve
    148    nullptr,   // mayResolve
    149    finalize,  // finalize
    150    nullptr,   // call
    151    nullptr,   // construct
    152    trace,     // trace
    153 };
    154 
    155 const ClassSpec WeakRefObject::classSpec_ = {
    156    GenericCreateConstructor<WeakRefObject::construct, 1,
    157                             gc::AllocKind::FUNCTION>,
    158    GenericCreatePrototype<WeakRefObject>,
    159    nullptr,
    160    nullptr,
    161    WeakRefObject::methods,
    162    WeakRefObject::properties,
    163 };
    164 
    165 const JSClass WeakRefObject::class_ = {
    166    "WeakRef",
    167    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
    168        JSCLASS_HAS_CACHED_PROTO(JSProto_WeakRef) | JSCLASS_FOREGROUND_FINALIZE,
    169    &classOps_, &classSpec_, &classExtension_};
    170 
    171 const JSClass WeakRefObject::protoClass_ = {
    172    // https://tc39.es/proposal-weakrefs/#sec-weak-ref.prototype
    173    // https://tc39.es/proposal-weakrefs/#sec-properties-of-the-weak-ref-prototype-object
    174    "WeakRef.prototype",
    175    JSCLASS_HAS_CACHED_PROTO(JSProto_WeakRef),
    176    JS_NULL_CLASS_OPS,
    177    &classSpec_,
    178 };
    179 
    180 const JSPropertySpec WeakRefObject::properties[] = {
    181    JS_STRING_SYM_PS(toStringTag, "WeakRef", JSPROP_READONLY),
    182    JS_PS_END,
    183 };
    184 
    185 const JSFunctionSpec WeakRefObject::methods[] = {
    186    JS_FN("deref", deref, 0, 0),
    187    JS_FS_END,
    188 };
    189 
    190 Value WeakRefObject::target() {
    191  Value value = getReservedSlot(TargetSlot);
    192  if (value.isUndefined()) {
    193    return UndefinedValue();
    194  }
    195 
    196  auto* cell = static_cast<Cell*>(value.toPrivate());
    197  if (cell->is<JSObject>()) {
    198    return ObjectValue(*cell->as<JSObject>());
    199  }
    200 
    201  return SymbolValue(cell->as<JS::Symbol>());
    202 }
    203 
    204 /* static */
    205 bool WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp) {
    206  CallArgs args = CallArgsFromVp(argc, vp);
    207 
    208  // https://tc39.es/proposal-weakrefs/#sec-weak-ref.prototype.deref
    209  // 1. Let weakRef be the this value.
    210  // 2. If Type(weakRef) is not Object, throw a TypeError exception.
    211  // 3. If weakRef does not have a [[Target]] internal slot, throw a TypeError
    212  //    exception.
    213  if (!args.thisv().isObject() ||
    214      !args.thisv().toObject().is<WeakRefObject>()) {
    215    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    216                              JSMSG_NOT_A_WEAK_REF,
    217                              "Receiver of WeakRef.deref call");
    218    return false;
    219  }
    220 
    221  Rooted<WeakRefObject*> weakRef(cx,
    222                                 &args.thisv().toObject().as<WeakRefObject>());
    223 
    224  // We need to perform a read barrier, which may clear the target.
    225  readBarrier(cx, weakRef);
    226 
    227  // 4. Let target be the value of weakRef.[[Target]].
    228  // 5. If target is not empty,
    229  //    a. Perform AddToKeptObjects(target).
    230  //    b. Return target.
    231  // 6. Return undefined.
    232  RootedValue target(cx, weakRef->target());
    233  if (target.isUndefined()) {
    234    args.rval().setUndefined();
    235    return true;
    236  }
    237 
    238  bool isPermanent =
    239      target.isSymbol() && target.toSymbol()->isPermanentAndMayBeShared();
    240  if (!isPermanent && !target.toGCThing()->zone()->addToKeptObjects(target)) {
    241    ReportOutOfMemory(cx);
    242    return false;
    243  }
    244 
    245  // Target should be wrapped into the current realm before returning it.
    246  if (!JS_WrapValue(cx, &target)) {
    247    return false;
    248  }
    249 
    250  args.rval().set(target);
    251  return true;
    252 }
    253 
    254 void WeakRefObject::setTargetUnbarriered(Value target) {
    255  setReservedSlotGCThingAsPrivateUnbarriered(TargetSlot, target.toGCThing());
    256 }
    257 
    258 void WeakRefObject::clearTargetAndUnlink() {
    259  unlink();
    260  clearReservedSlotGCThingAsPrivate(TargetSlot);
    261 }
    262 
    263 /* static */
    264 void WeakRefObject::readBarrier(JSContext* cx, Handle<WeakRefObject*> self) {
    265  RootedValue target(cx, self->target());
    266  if (target.isUndefined()) {
    267    return;
    268  }
    269 
    270  if (target.isObject() && target.toObject().getClass()->isDOMClass()) {
    271    // We preserved the target when the WeakRef was created. If it has since
    272    // been released then the DOM object it wraps has been collected, so clear
    273    // the target.
    274    RootedObject obj(cx, &target.toObject());
    275    MOZ_ASSERT(cx->runtime()->hasReleasedWrapperCallback);
    276    bool wasReleased = cx->runtime()->hasReleasedWrapperCallback(obj);
    277    if (wasReleased) {
    278      obj->zone()->finalizationObservers()->removeWeakRefTarget(target, self);
    279      return;
    280    }
    281  }
    282 
    283  gc::ValueReadBarrier(target);
    284 }
    285 
    286 namespace gc {
    287 
    288 void GCRuntime::traceKeptObjects(JSTracer* trc) {
    289  for (GCZonesIter zone(this); !zone.done(); zone.next()) {
    290    zone->traceKeptObjects(trc);
    291  }
    292 }
    293 
    294 }  // namespace gc
    295 
    296 }  // namespace js