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