Compartment.h (17456B)
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 #ifndef vm_Compartment_h 8 #define vm_Compartment_h 9 10 #include "mozilla/Maybe.h" 11 #include "mozilla/MemoryReporting.h" 12 13 #include <stddef.h> 14 #include <utility> 15 16 #include "gc/NurseryAwareHashMap.h" 17 #include "gc/ZoneAllocator.h" 18 #include "vm/Iteration.h" 19 #include "vm/JSObject.h" 20 #include "vm/JSScript.h" 21 22 namespace js { 23 24 JSString* CopyStringPure(JSContext* cx, JSString* str); 25 26 // The data structure use to storing JSObject CCWs for a given source 27 // compartment. These are partitioned by target compartment so that we can 28 // easily select wrappers by source and target compartment. String CCWs are 29 // stored in a per-zone separate map. 30 class ObjectWrapperMap { 31 static const size_t InitialInnerMapSize = 4; 32 33 using InnerMap = NurseryAwareHashMap<JSObject*, JSObject*, ZoneAllocPolicy>; 34 using OuterMap = GCHashMap<JS::Compartment*, InnerMap, 35 DefaultHasher<JS::Compartment*>, ZoneAllocPolicy>; 36 37 OuterMap map; 38 Zone* zone; 39 40 public: 41 class Enum { 42 Enum(const Enum&) = delete; 43 void operator=(const Enum&) = delete; 44 45 void goToNext() { 46 if (outer.isNothing()) { 47 return; 48 } 49 for (; !outer->empty(); outer->popFront()) { 50 JS::Compartment* c = outer->front().key(); 51 MOZ_ASSERT(c); 52 if (filter && !filter->match(c)) { 53 continue; 54 } 55 InnerMap& m = outer->front().value(); 56 if (!m.empty()) { 57 if (inner.isSome()) { 58 inner.reset(); 59 } 60 inner.emplace(m); 61 outer->popFront(); 62 return; 63 } 64 } 65 } 66 67 mozilla::Maybe<OuterMap::Enum> outer; 68 mozilla::Maybe<InnerMap::Enum> inner; 69 const CompartmentFilter* filter; 70 71 public: 72 explicit Enum(ObjectWrapperMap& m) : filter(nullptr) { 73 outer.emplace(m.map); 74 goToNext(); 75 } 76 77 Enum(ObjectWrapperMap& m, const CompartmentFilter& f) : filter(&f) { 78 outer.emplace(m.map); 79 goToNext(); 80 } 81 82 Enum(ObjectWrapperMap& m, JS::Compartment* target) { 83 // Leave the outer map as nothing and only iterate the inner map we 84 // find here. 85 auto p = m.map.lookup(target); 86 if (p) { 87 inner.emplace(p->value()); 88 } 89 } 90 91 bool empty() const { 92 return (outer.isNothing() || outer->empty()) && 93 (inner.isNothing() || inner->empty()); 94 } 95 96 InnerMap::Entry& front() const { 97 MOZ_ASSERT(inner.isSome() && !inner->empty()); 98 return inner->front(); 99 } 100 101 void popFront() { 102 MOZ_ASSERT(!empty()); 103 if (!inner->empty()) { 104 inner->popFront(); 105 if (!inner->empty()) { 106 return; 107 } 108 } 109 goToNext(); 110 } 111 112 void removeFront() { 113 MOZ_ASSERT(inner.isSome()); 114 inner->removeFront(); 115 } 116 }; 117 118 class Ptr : public InnerMap::Ptr { 119 friend class ObjectWrapperMap; 120 121 InnerMap* map; 122 123 Ptr() : map(nullptr) {} 124 Ptr(const InnerMap::Ptr& p, InnerMap& m) : InnerMap::Ptr(p), map(&m) {} 125 }; 126 127 // Iterator over compartments that the ObjectWrapperMap has wrappers for. 128 class WrappedCompartmentEnum { 129 OuterMap::Enum iter; 130 131 void settle() { 132 // It's possible for InnerMap to be empty after wrappers have been 133 // removed, e.g. by being nuked. 134 while (!iter.empty() && iter.front().value().empty()) { 135 iter.popFront(); 136 } 137 } 138 139 public: 140 explicit WrappedCompartmentEnum(ObjectWrapperMap& map) : iter(map.map) { 141 settle(); 142 } 143 bool empty() const { return iter.empty(); } 144 JS::Compartment* front() const { return iter.front().key(); } 145 operator JS::Compartment*() const { return front(); } 146 void popFront() { 147 iter.popFront(); 148 settle(); 149 } 150 }; 151 152 explicit ObjectWrapperMap(Zone* zone) : map(zone), zone(zone) {} 153 ObjectWrapperMap(Zone* zone, size_t aLen) : map(zone, aLen), zone(zone) {} 154 155 bool empty() { 156 if (map.empty()) { 157 return true; 158 } 159 for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { 160 if (!e.front().value().empty()) { 161 return false; 162 } 163 } 164 return true; 165 } 166 167 Ptr lookup(JSObject* obj) const { 168 auto op = map.lookup(obj->compartment()); 169 if (op) { 170 auto ip = op->value().lookup(obj); 171 if (ip) { 172 return Ptr(ip, op->value()); 173 } 174 } 175 return Ptr(); 176 } 177 178 void remove(Ptr p) { 179 if (p) { 180 p.map->remove(p); 181 } 182 } 183 184 [[nodiscard]] bool put(JSObject* key, JSObject* value) { 185 JS::Compartment* comp = key->compartment(); 186 auto ptr = map.lookupForAdd(comp); 187 if (!ptr) { 188 InnerMap m(zone, InitialInnerMapSize); 189 if (!map.add(ptr, comp, std::move(m))) { 190 return false; 191 } 192 } 193 return ptr->value().put(key, value); 194 } 195 196 size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 197 size_t size = map.shallowSizeOfExcludingThis(mallocSizeOf); 198 for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { 199 size += e.front().value().sizeOfExcludingThis(mallocSizeOf); 200 } 201 return size; 202 } 203 size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { 204 return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); 205 } 206 207 bool hasNurseryAllocatedWrapperEntries(const CompartmentFilter& f) { 208 for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { 209 JS::Compartment* c = e.front().key(); 210 if (c && !f.match(c)) { 211 continue; 212 } 213 InnerMap& m = e.front().value(); 214 if (m.hasNurseryEntries()) { 215 return true; 216 } 217 } 218 return false; 219 } 220 221 void sweepAfterMinorGC(JSTracer* trc) { 222 for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { 223 InnerMap& m = e.front().value(); 224 m.sweepAfterMinorGC(trc); 225 if (m.empty()) { 226 e.removeFront(); 227 } 228 } 229 } 230 231 void traceWeak(JSTracer* trc) { 232 for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { 233 InnerMap& m = e.front().value(); 234 m.traceWeak(trc); 235 if (m.empty()) { 236 e.removeFront(); 237 } 238 } 239 map.compact(); 240 } 241 }; 242 243 using StringWrapperMap = 244 NurseryAwareHashMap<JSString*, JSString*, ZoneAllocPolicy, 245 DuplicatesPossible>; 246 247 } // namespace js 248 249 class JS::Compartment { 250 JS::Zone* zone_; 251 JSRuntime* runtime_; 252 bool invisibleToDebugger_; 253 254 js::ObjectWrapperMap crossCompartmentObjectWrappers; 255 256 using RealmVector = js::Vector<JS::Realm*, 1, js::ZoneAllocPolicy>; 257 RealmVector realms_; 258 259 public: 260 /* 261 * During GC, stores the head of a list of incoming pointers from gray cells. 262 * 263 * The objects in the list are either cross-compartment wrappers, or 264 * debugger wrapper objects. The list link is either in the second extra 265 * slot for the former, or a special slot for the latter. 266 */ 267 JSObject* gcIncomingGrayPointers = nullptr; 268 269 void* data = nullptr; 270 271 // Fields set and used by the GC. Be careful, may be stale after we return 272 // to the mutator. 273 struct { 274 // These flags help us to discover if a compartment that shouldn't be 275 // alive manages to outlive a GC. Note that these flags have to be on 276 // the compartment, not the realm, because same-compartment realms can 277 // have cross-realm pointers without wrappers. 278 bool scheduledForDestruction = false; 279 bool hasMarkedCells = false; 280 bool maybeAlive = true; 281 282 // During GC, we may set this to |true| if we entered a realm in this 283 // compartment. Note that (without a stack walk) we don't know exactly 284 // *which* realms, because Realm::enterRealmDepthIgnoringJit_ does not 285 // account for cross-Realm calls in JIT code updating cx->realm_. See 286 // also the enterRealmDepthIgnoringJit_ comment. 287 bool hasEnteredRealm = false; 288 } gcState; 289 290 // True if all outgoing wrappers have been nuked. This happens when all realms 291 // have been nuked and NukeCrossCompartmentWrappers is called with the 292 // NukeAllReferences option. This prevents us from creating new wrappers for 293 // the compartment. 294 bool nukedOutgoingWrappers = false; 295 296 JS::Zone* zone() { return zone_; } 297 const JS::Zone* zone() const { return zone_; } 298 299 JSRuntime* runtimeFromMainThread() const { 300 MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_)); 301 return runtime_; 302 } 303 304 // Note: Unrestricted access to the zone's runtime from an arbitrary 305 // thread can easily lead to races. Use this method very carefully. 306 JSRuntime* runtimeFromAnyThread() const { return runtime_; } 307 308 // Certain compartments are implementation details of the embedding, and 309 // references to them should never leak out to script. For realms belonging to 310 // this compartment, onNewGlobalObject does not fire, and addDebuggee is a 311 // no-op. 312 bool invisibleToDebugger() const { return invisibleToDebugger_; } 313 314 RealmVector& realms() { return realms_; } 315 316 // Cross-compartment wrappers are shared by all realms in the compartment, but 317 // are still associated with a realm. To prevent us from having multiple 318 // realms, each with some cross-compartment wrappers potentially keeping the 319 // realm alive longer than necessary, we always allocate CCWs in the first 320 // realm. 321 js::GlobalObject& firstGlobal() const; 322 js::GlobalObject& globalForNewCCW() const { return firstGlobal(); } 323 324 void assertNoCrossCompartmentWrappers() { 325 MOZ_ASSERT(crossCompartmentObjectWrappers.empty()); 326 } 327 328 void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, 329 size_t* compartmentObjects, 330 size_t* crossCompartmentWrappersTables, 331 size_t* compartmentsPrivateData); 332 333 #ifdef JSGC_HASH_TABLE_CHECKS 334 void checkObjectWrappersAfterMovingGC(); 335 #endif 336 337 private: 338 bool getNonWrapperObjectForCurrentCompartment(JSContext* cx, 339 js::HandleObject origObj, 340 js::MutableHandleObject obj); 341 bool getOrCreateWrapper(JSContext* cx, js::HandleObject existing, 342 js::MutableHandleObject obj); 343 344 public: 345 explicit Compartment(JS::Zone* zone, bool invisibleToDebugger); 346 347 void destroy(JS::GCContext* gcx); 348 349 [[nodiscard]] inline bool wrap(JSContext* cx, JS::MutableHandleValue vp); 350 351 [[nodiscard]] inline bool wrap(JSContext* cx, 352 MutableHandle<mozilla::Maybe<Value>> vp); 353 354 [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandleString strp); 355 [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi); 356 [[nodiscard]] bool wrap(JSContext* cx, JS::MutableHandleObject obj); 357 [[nodiscard]] bool wrap(JSContext* cx, 358 JS::MutableHandle<JS::PropertyDescriptor> desc); 359 [[nodiscard]] bool wrap( 360 JSContext* cx, 361 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 362 [[nodiscard]] bool wrap(JSContext* cx, 363 JS::MutableHandle<JS::GCVector<JS::Value>> vec); 364 [[nodiscard]] bool rewrap(JSContext* cx, JS::MutableHandleObject obj, 365 JS::HandleObject existing); 366 367 [[nodiscard]] bool putWrapper(JSContext* cx, JSObject* wrapped, 368 JSObject* wrapper); 369 370 [[nodiscard]] bool putWrapper(JSContext* cx, JSString* wrapped, 371 JSString* wrapper); 372 373 js::ObjectWrapperMap::Ptr lookupWrapper(JSObject* obj) const { 374 return crossCompartmentObjectWrappers.lookup(obj); 375 } 376 377 inline js::StringWrapperMap::Ptr lookupWrapper(JSString* str) const; 378 379 void removeWrapper(js::ObjectWrapperMap::Ptr p); 380 381 bool hasNurseryAllocatedObjectWrapperEntries(const js::CompartmentFilter& f) { 382 return crossCompartmentObjectWrappers.hasNurseryAllocatedWrapperEntries(f); 383 } 384 385 // Iterator over |wrapped -> wrapper| entries for object CCWs in a given 386 // compartment. Can be optionally restricted by target compartment. 387 struct ObjectWrapperEnum : public js::ObjectWrapperMap::Enum { 388 explicit ObjectWrapperEnum(Compartment* c) 389 : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers) {} 390 explicit ObjectWrapperEnum(Compartment* c, const js::CompartmentFilter& f) 391 : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers, f) {} 392 explicit ObjectWrapperEnum(Compartment* c, Compartment* target) 393 : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers, 394 target) { 395 MOZ_ASSERT(target); 396 } 397 }; 398 399 // Iterator over compartments that this compartment has CCWs for. 400 struct WrappedObjectCompartmentEnum 401 : public js::ObjectWrapperMap::WrappedCompartmentEnum { 402 explicit WrappedObjectCompartmentEnum(Compartment* c) 403 : js::ObjectWrapperMap::WrappedCompartmentEnum( 404 c->crossCompartmentObjectWrappers) {} 405 }; 406 407 /* 408 * These methods mark pointers that cross compartment boundaries. They are 409 * called in per-zone GCs to prevent the wrappers' outgoing edges from 410 * dangling (full GCs naturally follow pointers across compartments) and 411 * when compacting to update cross-compartment pointers. 412 */ 413 enum EdgeSelector { AllEdges, NonGrayEdges, GrayEdges, BlackEdges }; 414 void traceWrapperTargetsInCollectedZones(JSTracer* trc, 415 EdgeSelector whichEdges); 416 static void traceIncomingCrossCompartmentEdgesForZoneGC( 417 JSTracer* trc, EdgeSelector whichEdges); 418 419 void sweepRealms(JS::GCContext* gcx, bool keepAtleastOne, 420 bool destroyingRuntime); 421 void sweepAfterMinorGC(JSTracer* trc); 422 void traceCrossCompartmentObjectWrapperEdges(JSTracer* trc); 423 424 void fixupCrossCompartmentObjectWrappersAfterMovingGC(JSTracer* trc); 425 void fixupAfterMovingGC(JSTracer* trc); 426 427 [[nodiscard]] bool findSweepGroupEdges(); 428 429 private: 430 // Head node of list of active iterators that may need deleted property 431 // suppression. 432 js::NativeIteratorListHead enumerators_; 433 434 public: 435 js::NativeIteratorListHead* enumeratorsAddr() { return &enumerators_; } 436 MOZ_ALWAYS_INLINE bool objectMaybeInIteration(JSObject* obj); 437 438 void traceWeakNativeIterators(JSTracer* trc); 439 }; 440 441 namespace js { 442 443 // We only set the hasMarkedCells flag for objects and scripts. It's assumed 444 // that, if a compartment is alive, then it will have at least some live object 445 // or script it in. Even if we get this wrong, the worst that will happen is 446 // that scheduledForDestruction will be set on the compartment, which will cause 447 // some extra GC activity to try to free the compartment. 448 template <typename T> 449 inline void SetCompartmentHasMarkedCells(T* thing) {} 450 451 template <> 452 inline void SetCompartmentHasMarkedCells(JSObject* thing) { 453 thing->compartment()->gcState.hasMarkedCells = true; 454 } 455 456 template <> 457 inline void SetCompartmentHasMarkedCells(JSScript* thing) { 458 thing->compartment()->gcState.hasMarkedCells = true; 459 } 460 461 /* 462 * AutoWrapperVector and AutoWrapperRooter can be used to store wrappers that 463 * are obtained from the cross-compartment map. However, these classes should 464 * not be used if the wrapper will escape. For example, it should not be stored 465 * in the heap. 466 * 467 * The AutoWrapper rooters are different from other autorooters because their 468 * wrappers are marked on every GC slice rather than just the first one. If 469 * there's some wrapper that we want to use temporarily without causing it to be 470 * marked, we can use these AutoWrapper classes. If we get unlucky and a GC 471 * slice runs during the code using the wrapper, the GC will mark the wrapper so 472 * that it doesn't get swept out from under us. Otherwise, the wrapper needn't 473 * be marked. This is useful in functions like JS_TransplantObject that 474 * manipulate wrappers in compartments that may no longer be alive. 475 */ 476 477 /* 478 * This class stores the data for AutoWrapperVector and AutoWrapperRooter. It 479 * should not be used in any other situations. 480 */ 481 struct WrapperValue { 482 /* 483 * We use unsafeGet() in the constructors to avoid invoking a read barrier 484 * on the wrapper, which may be dead (see the comment about bug 803376 in 485 * gc/GC.cpp regarding this). If there is an incremental GC while the 486 * wrapper is in use, the AutoWrapper rooter will ensure the wrapper gets 487 * marked. 488 */ 489 explicit WrapperValue(const ObjectWrapperMap::Ptr& ptr) 490 : value(*ptr->value().unsafeGet()) {} 491 492 explicit WrapperValue(const ObjectWrapperMap::Enum& e) 493 : value(*e.front().value().unsafeGet()) {} 494 495 JSObject*& get() { return value; } 496 JSObject* get() const { return value; } 497 operator JSObject*() const { return value; } 498 499 private: 500 JSObject* value; 501 }; 502 503 class MOZ_RAII AutoWrapperVector : public JS::GCVector<WrapperValue, 8>, 504 public JS::AutoGCRooter { 505 public: 506 explicit AutoWrapperVector(JSContext* cx) 507 : JS::GCVector<WrapperValue, 8>(cx), 508 JS::AutoGCRooter(cx, JS::AutoGCRooter::Kind::WrapperVector) {} 509 510 void trace(JSTracer* trc); 511 512 private: 513 }; 514 515 class MOZ_RAII AutoWrapperRooter : public JS::AutoGCRooter { 516 public: 517 AutoWrapperRooter(JSContext* cx, const WrapperValue& v) 518 : JS::AutoGCRooter(cx, JS::AutoGCRooter::Kind::Wrapper), value(v) {} 519 520 operator JSObject*() const { return value; } 521 522 void trace(JSTracer* trc); 523 524 private: 525 WrapperValue value; 526 }; 527 528 } /* namespace js */ 529 530 #endif /* vm_Compartment_h */