Compartment.cpp (19393B)
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 "vm/Compartment-inl.h" 8 9 #include "mozilla/MemoryReporting.h" 10 11 #include <stddef.h> 12 13 #include "jsfriendapi.h" 14 15 #include "debugger/DebugAPI.h" 16 #include "gc/GC.h" 17 #include "gc/Memory.h" 18 #include "gc/PublicIterators.h" 19 #include "gc/Zone.h" 20 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit 21 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow 22 #include "js/Proxy.h" 23 #include "js/RootingAPI.h" 24 #include "js/StableStringChars.h" 25 #include "js/Wrapper.h" 26 #include "js/WrapperCallbacks.h" 27 #include "proxy/DeadObjectProxy.h" 28 #include "proxy/DOMProxy.h" 29 #include "vm/JSContext.h" 30 #include "vm/WrapperObject.h" 31 32 #include "gc/Marking-inl.h" 33 #include "gc/WeakMap-inl.h" 34 #include "vm/JSObject-inl.h" 35 #include "vm/Realm-inl.h" 36 #include "vm/StringType-inl.h" 37 38 using namespace js; 39 40 using JS::AutoStableStringChars; 41 42 Compartment::Compartment(Zone* zone, bool invisibleToDebugger) 43 : zone_(zone), 44 runtime_(zone->runtimeFromAnyThread()), 45 invisibleToDebugger_(invisibleToDebugger), 46 crossCompartmentObjectWrappers(zone, 0), 47 realms_(zone) {} 48 49 #ifdef JSGC_HASH_TABLE_CHECKS 50 51 void Compartment::checkObjectWrappersAfterMovingGC() { 52 for (ObjectWrapperEnum e(this); !e.empty(); e.popFront()) { 53 auto key = e.front().key(); 54 CheckGCThingAfterMovingGC(key.get()); // Keys may be in a different zone. 55 CheckGCThingAfterMovingGC(e.front().value().unbarrieredGet(), zone()); 56 CheckTableEntryAfterMovingGC(crossCompartmentObjectWrappers, e, key); 57 } 58 } 59 60 #endif // JSGC_HASH_TABLE_CHECKS 61 62 bool Compartment::putWrapper(JSContext* cx, JSObject* wrapped, 63 JSObject* wrapper) { 64 MOZ_ASSERT(!js::IsProxy(wrapper) || js::GetProxyHandler(wrapper)->family() != 65 js::GetDOMRemoteProxyHandlerFamily()); 66 67 if (!crossCompartmentObjectWrappers.put(wrapped, wrapper)) { 68 ReportOutOfMemory(cx); 69 return false; 70 } 71 72 return true; 73 } 74 75 bool Compartment::putWrapper(JSContext* cx, JSString* wrapped, 76 JSString* wrapper) { 77 if (!zone()->crossZoneStringWrappers().put(wrapped, wrapper)) { 78 ReportOutOfMemory(cx); 79 return false; 80 } 81 82 return true; 83 } 84 85 void Compartment::removeWrapper(js::ObjectWrapperMap::Ptr p) { 86 JSObject* key = p->key(); 87 JSObject* value = p->value().unbarrieredGet(); 88 if (js::gc::detail::GetDelegate(value) == key) { 89 key->zone()->beforeClearDelegate(value, key); 90 } 91 92 crossCompartmentObjectWrappers.remove(p); 93 } 94 95 JSString* js::CopyStringPure(JSContext* cx, JSString* str) { 96 /* 97 * Directly allocate the copy in the destination compartment, rather than 98 * first flattening it (and possibly allocating in source compartment), 99 * because we don't know whether the flattening will pay off later. 100 */ 101 102 size_t len = str->length(); 103 JSString* copy; 104 if (str->isLinear()) { 105 // If the string has a refcounted StringBuffer, we can share it. 106 if (str->hasStringBuffer()) { 107 RefPtr<mozilla::StringBuffer> buffer(str->asLinear().stringBuffer()); 108 if (str->hasLatin1Chars()) { 109 Rooted<JSString::OwnedChars<Latin1Char>> owned(cx, std::move(buffer), 110 len); 111 return JSLinearString::newValidLength<CanGC, Latin1Char>( 112 cx, &owned, gc::Heap::Default); 113 } 114 Rooted<JSString::OwnedChars<char16_t>> owned(cx, std::move(buffer), len); 115 return JSLinearString::newValidLength<CanGC, char16_t>(cx, &owned, 116 gc::Heap::Default); 117 } 118 119 /* Only use AutoStableStringChars if the NoGC allocation fails. */ 120 if (str->hasLatin1Chars()) { 121 JS::AutoCheckCannotGC nogc; 122 copy = NewStringCopyN<NoGC>(cx, str->asLinear().latin1Chars(nogc), len); 123 } else { 124 JS::AutoCheckCannotGC nogc; 125 copy = NewStringCopyNDontDeflate<NoGC>( 126 cx, str->asLinear().twoByteChars(nogc), len); 127 } 128 if (copy) { 129 return copy; 130 } 131 132 AutoStableStringChars chars(cx); 133 if (!chars.init(cx, str)) { 134 return nullptr; 135 } 136 137 return chars.isLatin1() ? NewStringCopyN<CanGC>( 138 cx, chars.latin1Range().begin().get(), len) 139 : NewStringCopyNDontDeflate<CanGC>( 140 cx, chars.twoByteRange().begin().get(), len); 141 } 142 143 if (str->hasLatin1Chars()) { 144 UniquePtr<Latin1Char[], JS::FreePolicy> copiedChars = 145 str->asRope().copyLatin1Chars(cx, js::StringBufferArena); 146 if (!copiedChars) { 147 return nullptr; 148 } 149 150 return NewString<CanGC>(cx, std::move(copiedChars), len); 151 } 152 153 UniqueTwoByteChars copiedChars = 154 str->asRope().copyTwoByteChars(cx, js::StringBufferArena); 155 if (!copiedChars) { 156 return nullptr; 157 } 158 159 return NewStringDontDeflate<CanGC>(cx, std::move(copiedChars), len); 160 } 161 162 bool Compartment::wrap(JSContext* cx, MutableHandleString strp) { 163 MOZ_ASSERT(cx->compartment() == this); 164 165 /* If the string is already in this compartment, we are done. */ 166 JSString* str = strp; 167 if (str->zoneFromAnyThread() == zone()) { 168 return true; 169 } 170 171 /* 172 * If the string is an atom, we don't have to copy, but we do need to mark 173 * the atom as being in use by the new zone. 174 */ 175 if (str->isAtom()) { 176 cx->markAtom(&str->asAtom()); 177 return true; 178 } 179 180 /* Check the cache. */ 181 if (StringWrapperMap::Ptr p = lookupWrapper(str)) { 182 strp.set(p->value().get()); 183 return true; 184 } 185 186 /* No dice. Make a copy, and cache it. */ 187 JSString* copy = CopyStringPure(cx, str); 188 if (!copy) { 189 return false; 190 } 191 if (!putWrapper(cx, strp, copy)) { 192 return false; 193 } 194 195 strp.set(copy); 196 return true; 197 } 198 199 bool Compartment::wrap(JSContext* cx, MutableHandleBigInt bi) { 200 MOZ_ASSERT(cx->compartment() == this); 201 202 if (bi->zone() == cx->zone()) { 203 return true; 204 } 205 206 BigInt* copy = BigInt::copy(cx, bi); 207 if (!copy) { 208 return false; 209 } 210 bi.set(copy); 211 return true; 212 } 213 214 bool Compartment::getNonWrapperObjectForCurrentCompartment( 215 JSContext* cx, HandleObject origObj, MutableHandleObject obj) { 216 // Ensure that we have entered a realm. 217 MOZ_ASSERT(cx->global()); 218 219 // The object is already in the right compartment. Normally same- 220 // compartment returns the object itself, however, windows are always 221 // wrapped by a proxy, so we have to check for that case here manually. 222 if (obj->compartment() == this) { 223 obj.set(ToWindowProxyIfWindow(obj)); 224 return true; 225 } 226 227 // Note that if the object is same-compartment, but has been wrapped into a 228 // different compartment, we need to unwrap it and return the bare same- 229 // compartment object. Note again that windows are always wrapped by a 230 // WindowProxy even when same-compartment so take care not to strip this 231 // particular wrapper. 232 RootedObject objectPassedToWrap(cx, obj); 233 obj.set(UncheckedUnwrap(obj, /* stopAtWindowProxy = */ true)); 234 if (obj->compartment() == this) { 235 MOZ_ASSERT(!IsWindow(obj)); 236 return true; 237 } 238 239 // Disallow creating new wrappers if we nuked the object's realm or the 240 // current compartment. 241 if (!AllowNewWrapper(this, obj)) { 242 obj.set(NewDeadProxyObject(cx, obj)); 243 return !!obj; 244 } 245 246 // Use the WindowProxy instead of the Window here, so that we don't have to 247 // deal with this in the rest of the wrapping code. 248 if (IsWindow(obj)) { 249 obj.set(ToWindowProxyIfWindow(obj)); 250 251 // ToWindowProxyIfWindow can return a CCW if |obj| was a navigated-away-from 252 // Window. Strip any CCWs. 253 obj.set(UncheckedUnwrap(obj)); 254 255 if (JS_IsDeadWrapper(obj)) { 256 obj.set(NewDeadProxyObject(cx, obj)); 257 return !!obj; 258 } 259 260 MOZ_ASSERT(IsWindowProxy(obj) || IsDOMRemoteProxyObject(obj)); 261 262 // We crossed a compartment boundary there, so may now have a gray object. 263 // This function is not allowed to return gray objects, so don't do that. 264 ExposeObjectToActiveJS(obj); 265 } 266 267 // If the object is a dead wrapper, return a new dead wrapper rather than 268 // trying to wrap it for a different compartment. 269 if (JS_IsDeadWrapper(obj)) { 270 obj.set(NewDeadProxyObject(cx, obj)); 271 return !!obj; 272 } 273 274 // Invoke the prewrap callback. The prewrap callback is responsible for 275 // doing similar reification as above, but can account for any additional 276 // embedder requirements. 277 // 278 // We're a bit worried about infinite recursion here, so we do a check - 279 // see bug 809295. 280 auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap; 281 if (preWrap) { 282 AutoCheckRecursionLimit recursion(cx); 283 if (!recursion.checkSystem(cx)) { 284 return false; 285 } 286 preWrap(cx, cx->global(), origObj, obj, objectPassedToWrap, obj); 287 if (!obj) { 288 return false; 289 } 290 } 291 MOZ_ASSERT(!IsWindow(obj)); 292 293 return true; 294 } 295 296 bool Compartment::getOrCreateWrapper(JSContext* cx, HandleObject existing, 297 MutableHandleObject obj) { 298 // ScriptSourceObject is an internal object that we never need to wrap. 299 MOZ_ASSERT(!obj->is<ScriptSourceObject>()); 300 301 // If we already have a wrapper for this value, use it. 302 if (ObjectWrapperMap::Ptr p = lookupWrapper(obj)) { 303 obj.set(p->value().get()); 304 MOZ_ASSERT(obj->is<CrossCompartmentWrapperObject>()); 305 return true; 306 } 307 308 // Ensure that the wrappee is exposed in case we are creating a new wrapper 309 // for a gray object. 310 ExposeObjectToActiveJS(obj); 311 312 // If we're wrapping an object which emulates undefined then the runtime fuse 313 // should already have been popped. 314 MOZ_ASSERT_IF(obj->getClass()->emulatesUndefined(), 315 !cx->runtime() 316 ->runtimeFuses.ref() 317 .hasSeenObjectEmulateUndefinedFuse.intact()); 318 319 // Create a new wrapper for the object. 320 auto wrap = cx->runtime()->wrapObjectCallbacks->wrap; 321 RootedObject wrapper(cx, wrap(cx, existing, obj)); 322 if (!wrapper) { 323 return false; 324 } 325 326 // We maintain the invariant that the key in the cross-compartment wrapper 327 // map is always directly wrapped by the value. 328 MOZ_ASSERT(Wrapper::wrappedObject(wrapper) == obj); 329 330 if (!putWrapper(cx, obj, wrapper)) { 331 // Enforce the invariant that all cross-compartment wrapper object are 332 // in the map by nuking the wrapper if we couldn't add it. 333 // Unfortunately it's possible for the wrapper to still be marked if we 334 // took this path, for example if the object metadata callback stashes a 335 // reference to it. 336 if (wrapper->is<CrossCompartmentWrapperObject>()) { 337 NukeCrossCompartmentWrapper(cx, wrapper); 338 } 339 return false; 340 } 341 342 obj.set(wrapper); 343 return true; 344 } 345 346 bool Compartment::wrap(JSContext* cx, MutableHandleObject obj) { 347 MOZ_ASSERT(cx->compartment() == this); 348 349 if (!obj) { 350 return true; 351 } 352 353 AutoDisableProxyCheck adpc; 354 355 // Anything we're wrapping has already escaped into script, so must have 356 // been unmarked-gray at some point in the past. 357 JS::AssertObjectIsNotGray(obj); 358 359 // The passed object may already be wrapped, or may fit a number of special 360 // cases that we need to check for and manually correct. 361 if (!getNonWrapperObjectForCurrentCompartment(cx, /* origObj = */ nullptr, 362 obj)) { 363 return false; 364 } 365 366 // If the reification above did not result in a same-compartment object, 367 // get or create a new wrapper object in this compartment for it. 368 if (obj->compartment() != this) { 369 if (!getOrCreateWrapper(cx, nullptr, obj)) { 370 return false; 371 } 372 } 373 374 // Ensure that the wrapper is also exposed. 375 ExposeObjectToActiveJS(obj); 376 return true; 377 } 378 379 bool Compartment::rewrap(JSContext* cx, MutableHandleObject obj, 380 HandleObject existingArg) { 381 MOZ_ASSERT(cx->compartment() == this); 382 MOZ_ASSERT(obj); 383 MOZ_ASSERT(existingArg); 384 MOZ_ASSERT(existingArg->compartment() == cx->compartment()); 385 MOZ_ASSERT(IsDeadProxyObject(existingArg)); 386 387 AutoDisableProxyCheck adpc; 388 389 // It may not be possible to re-use existing; if so, clear it so that we 390 // are forced to create a new wrapper. Note that this cannot call out to 391 // |wrap| because of the different gray unmarking semantics. 392 RootedObject existing(cx, existingArg); 393 if (existing->hasStaticPrototype() || 394 // Note: Class asserted above, so all that's left to check is callability 395 existing->isCallable() || obj->isCallable()) { 396 existing.set(nullptr); 397 } 398 399 // The passed object may already be wrapped, or may fit a number of special 400 // cases that we need to check for and manually correct. We pass in 401 // |existingArg| instead of |existing|, because the purpose is to get the 402 // address of the object we are transplanting onto, not to find a wrapper 403 // to reuse. 404 if (!getNonWrapperObjectForCurrentCompartment(cx, existingArg, obj)) { 405 return false; 406 } 407 408 // If the reification above resulted in a same-compartment object, we do 409 // not need to create or return an existing wrapper. 410 if (obj->compartment() == this) { 411 return true; 412 } 413 414 return getOrCreateWrapper(cx, existing, obj); 415 } 416 417 bool Compartment::wrap(JSContext* cx, 418 MutableHandle<JS::PropertyDescriptor> desc) { 419 if (desc.hasGetter()) { 420 if (!wrap(cx, desc.getter())) { 421 return false; 422 } 423 } 424 if (desc.hasSetter()) { 425 if (!wrap(cx, desc.setter())) { 426 return false; 427 } 428 } 429 if (desc.hasValue()) { 430 if (!wrap(cx, desc.value())) { 431 return false; 432 } 433 } 434 return true; 435 } 436 437 bool Compartment::wrap(JSContext* cx, 438 MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) { 439 if (desc.isNothing()) { 440 return true; 441 } 442 443 Rooted<PropertyDescriptor> desc_(cx, *desc); 444 if (!wrap(cx, &desc_)) { 445 return false; 446 } 447 desc.set(mozilla::Some(desc_.get())); 448 return true; 449 } 450 451 bool Compartment::wrap(JSContext* cx, MutableHandle<GCVector<Value>> vec) { 452 for (size_t i = 0; i < vec.length(); ++i) { 453 if (!wrap(cx, vec[i])) { 454 return false; 455 } 456 } 457 return true; 458 } 459 460 static inline bool ShouldTraceWrapper(JSObject* wrapper, 461 Compartment::EdgeSelector whichEdges) { 462 switch (whichEdges) { 463 case Compartment::AllEdges: 464 return true; 465 case Compartment::NonGrayEdges: 466 return !wrapper->isMarkedGray(); 467 case Compartment::GrayEdges: 468 return wrapper->isMarkedGray(); 469 case Compartment::BlackEdges: 470 return wrapper->isMarkedBlack(); 471 default: 472 MOZ_CRASH("Unexpected EdgeSelector value"); 473 } 474 } 475 476 void Compartment::traceWrapperTargetsInCollectedZones(JSTracer* trc, 477 EdgeSelector whichEdges) { 478 // Trace cross compartment wrapper private pointers into collected zones to 479 // either mark or update them. Wrapped object pointers are updated by 480 // sweepCrossCompartmentObjectWrappers(). 481 482 MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting()); 483 MOZ_ASSERT(!zone()->isCollectingFromAnyThread() || 484 trc->runtime()->gc.isHeapCompacting()); 485 486 for (WrappedObjectCompartmentEnum c(this); !c.empty(); c.popFront()) { 487 Zone* zone = c.front()->zone(); 488 if (!zone->isCollectingFromAnyThread()) { 489 continue; 490 } 491 492 for (ObjectWrapperEnum e(this, c); !e.empty(); e.popFront()) { 493 JSObject* obj = e.front().value().unbarrieredGet(); 494 ProxyObject* wrapper = &obj->as<ProxyObject>(); 495 if (ShouldTraceWrapper(wrapper, whichEdges)) { 496 ProxyObject::traceEdgeToTarget(trc, wrapper); 497 } 498 } 499 } 500 } 501 502 /* static */ 503 void Compartment::traceIncomingCrossCompartmentEdgesForZoneGC( 504 JSTracer* trc, EdgeSelector whichEdges) { 505 MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting()); 506 507 for (ZonesIter zone(trc->runtime(), SkipAtoms); !zone.done(); zone.next()) { 508 if (zone->isCollectingFromAnyThread()) { 509 continue; 510 } 511 512 for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) { 513 c->traceWrapperTargetsInCollectedZones(trc, whichEdges); 514 } 515 } 516 517 // Currently we trace all debugger edges as black. 518 if (whichEdges != GrayEdges) { 519 DebugAPI::traceCrossCompartmentEdges(trc); 520 } 521 } 522 523 void Compartment::sweepAfterMinorGC(JSTracer* trc) { 524 crossCompartmentObjectWrappers.sweepAfterMinorGC(trc); 525 526 for (RealmsInCompartmentIter r(this); !r.done(); r.next()) { 527 r->sweepAfterMinorGC(trc); 528 } 529 } 530 531 // Remove dead wrappers from the table or update pointers to moved objects. 532 void Compartment::traceCrossCompartmentObjectWrapperEdges(JSTracer* trc) { 533 crossCompartmentObjectWrappers.traceWeak(trc); 534 } 535 536 void Compartment::fixupCrossCompartmentObjectWrappersAfterMovingGC( 537 JSTracer* trc) { 538 MOZ_ASSERT(trc->runtime()->gc.isHeapCompacting()); 539 540 // Sweep the wrapper map to update keys (wrapped values) in other 541 // compartments that may have been moved. 542 traceCrossCompartmentObjectWrapperEdges(trc); 543 544 // Trace the wrappers in the map to update their cross-compartment edges 545 // to wrapped values in other compartments that may have been moved. 546 traceWrapperTargetsInCollectedZones(trc, AllEdges); 547 } 548 549 void Compartment::fixupAfterMovingGC(JSTracer* trc) { 550 MOZ_ASSERT(zone()->isGCCompacting()); 551 552 for (RealmsInCompartmentIter r(this); !r.done(); r.next()) { 553 r->fixupAfterMovingGC(trc); 554 } 555 556 // Sweep the wrapper map to update values (wrapper objects) in this 557 // compartment that may have been moved. 558 traceCrossCompartmentObjectWrapperEdges(trc); 559 } 560 561 void Compartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, 562 size_t* compartmentObjects, 563 size_t* crossCompartmentWrappersTables, 564 size_t* compartmentsPrivateData) { 565 *compartmentObjects += mallocSizeOf(this); 566 *crossCompartmentWrappersTables += 567 crossCompartmentObjectWrappers.sizeOfExcludingThis(mallocSizeOf); 568 569 if (auto callback = runtime_->sizeOfIncludingThisCompartmentCallback) { 570 *compartmentsPrivateData += callback(mallocSizeOf, this); 571 } 572 } 573 574 GlobalObject& Compartment::firstGlobal() const { 575 for (Realm* realm : realms_) { 576 if (!realm->hasInitializedGlobal()) { 577 continue; 578 } 579 GlobalObject* global = realm->maybeGlobal(); 580 ExposeObjectToActiveJS(global); 581 return *global; 582 } 583 MOZ_CRASH("If all our globals are dead, why is someone expecting a global?"); 584 } 585 586 JS_PUBLIC_API JSObject* js::GetFirstGlobalInCompartment(JS::Compartment* comp) { 587 return &comp->firstGlobal(); 588 } 589 590 JS_PUBLIC_API bool js::CompartmentHasLiveGlobal(JS::Compartment* comp) { 591 MOZ_ASSERT(comp); 592 for (Realm* r : comp->realms()) { 593 if (r->hasLiveGlobal()) { 594 return true; 595 } 596 } 597 return false; 598 } 599 600 void Compartment::traceWeakNativeIterators(JSTracer* trc) { 601 /* Sweep list of native iterators. */ 602 NativeIteratorListIter iter(&enumerators_); 603 while (!iter.done()) { 604 NativeIterator* ni = iter.next(); 605 JSObject* iterObj = ni->iterObj(); 606 if (!TraceManuallyBarrieredWeakEdge(trc, &iterObj, 607 "Compartment::enumerators_")) { 608 ni->unlink(); 609 } 610 MOZ_ASSERT(ni->objectBeingIterated()->compartment() == this); 611 } 612 }