CrossCompartmentWrapper.cpp (23831B)
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/FinalizationRegistryObject.h" 8 #include "debugger/Debugger.h" 9 #include "gc/GC.h" 10 #include "gc/PublicIterators.h" 11 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy 12 #include "js/Wrapper.h" 13 #include "proxy/DeadObjectProxy.h" 14 #include "proxy/DOMProxy.h" 15 #include "vm/Iteration.h" 16 #include "vm/Runtime.h" 17 #include "vm/WrapperObject.h" 18 19 #include "gc/Nursery-inl.h" 20 #include "vm/Compartment-inl.h" 21 #include "vm/JSObject-inl.h" 22 #include "vm/Realm-inl.h" 23 24 using namespace js; 25 26 #define PIERCE(cx, wrapper, pre, op, post) \ 27 JS_BEGIN_MACRO \ 28 bool ok; \ 29 { \ 30 AutoRealm call(cx, wrappedObject(wrapper)); \ 31 ok = (pre) && (op); \ 32 } \ 33 return ok && (post); \ 34 JS_END_MACRO 35 36 #define NOTHING (true) 37 38 static bool MarkAtoms(JSContext* cx, jsid id) { 39 cx->markId(id); 40 return true; 41 } 42 43 static bool MarkAtoms(JSContext* cx, HandleIdVector ids) { 44 for (size_t i = 0; i < ids.length(); i++) { 45 cx->markId(ids[i]); 46 } 47 return true; 48 } 49 50 bool CrossCompartmentWrapper::getOwnPropertyDescriptor( 51 JSContext* cx, HandleObject wrapper, HandleId id, 52 MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const { 53 PIERCE(cx, wrapper, MarkAtoms(cx, id), 54 Wrapper::getOwnPropertyDescriptor(cx, wrapper, id, desc), 55 cx->compartment()->wrap(cx, desc)); 56 } 57 58 bool CrossCompartmentWrapper::defineProperty(JSContext* cx, 59 HandleObject wrapper, HandleId id, 60 Handle<PropertyDescriptor> desc, 61 ObjectOpResult& result) const { 62 Rooted<PropertyDescriptor> desc2(cx, desc); 63 PIERCE(cx, wrapper, MarkAtoms(cx, id) && cx->compartment()->wrap(cx, &desc2), 64 Wrapper::defineProperty(cx, wrapper, id, desc2, result), NOTHING); 65 } 66 67 bool CrossCompartmentWrapper::ownPropertyKeys( 68 JSContext* cx, HandleObject wrapper, MutableHandleIdVector props) const { 69 PIERCE(cx, wrapper, NOTHING, Wrapper::ownPropertyKeys(cx, wrapper, props), 70 MarkAtoms(cx, props)); 71 } 72 73 bool CrossCompartmentWrapper::delete_(JSContext* cx, HandleObject wrapper, 74 HandleId id, 75 ObjectOpResult& result) const { 76 PIERCE(cx, wrapper, MarkAtoms(cx, id), 77 Wrapper::delete_(cx, wrapper, id, result), NOTHING); 78 } 79 80 bool CrossCompartmentWrapper::getPrototype(JSContext* cx, HandleObject wrapper, 81 MutableHandleObject protop) const { 82 { 83 RootedObject wrapped(cx, wrappedObject(wrapper)); 84 AutoRealm call(cx, wrapped); 85 if (!GetPrototype(cx, wrapped, protop)) { 86 return false; 87 } 88 } 89 90 return cx->compartment()->wrap(cx, protop); 91 } 92 93 bool CrossCompartmentWrapper::setPrototype(JSContext* cx, HandleObject wrapper, 94 HandleObject proto, 95 ObjectOpResult& result) const { 96 RootedObject protoCopy(cx, proto); 97 PIERCE(cx, wrapper, cx->compartment()->wrap(cx, &protoCopy), 98 Wrapper::setPrototype(cx, wrapper, protoCopy, result), NOTHING); 99 } 100 101 bool CrossCompartmentWrapper::getPrototypeIfOrdinary( 102 JSContext* cx, HandleObject wrapper, bool* isOrdinary, 103 MutableHandleObject protop) const { 104 { 105 RootedObject wrapped(cx, wrappedObject(wrapper)); 106 AutoRealm call(cx, wrapped); 107 if (!GetPrototypeIfOrdinary(cx, wrapped, isOrdinary, protop)) { 108 return false; 109 } 110 111 if (!*isOrdinary) { 112 return true; 113 } 114 } 115 116 return cx->compartment()->wrap(cx, protop); 117 } 118 119 bool CrossCompartmentWrapper::setImmutablePrototype(JSContext* cx, 120 HandleObject wrapper, 121 bool* succeeded) const { 122 PIERCE(cx, wrapper, NOTHING, 123 Wrapper::setImmutablePrototype(cx, wrapper, succeeded), NOTHING); 124 } 125 126 bool CrossCompartmentWrapper::preventExtensions(JSContext* cx, 127 HandleObject wrapper, 128 ObjectOpResult& result) const { 129 PIERCE(cx, wrapper, NOTHING, Wrapper::preventExtensions(cx, wrapper, result), 130 NOTHING); 131 } 132 133 bool CrossCompartmentWrapper::isExtensible(JSContext* cx, HandleObject wrapper, 134 bool* extensible) const { 135 PIERCE(cx, wrapper, NOTHING, Wrapper::isExtensible(cx, wrapper, extensible), 136 NOTHING); 137 } 138 139 bool CrossCompartmentWrapper::has(JSContext* cx, HandleObject wrapper, 140 HandleId id, bool* bp) const { 141 PIERCE(cx, wrapper, MarkAtoms(cx, id), Wrapper::has(cx, wrapper, id, bp), 142 NOTHING); 143 } 144 145 bool CrossCompartmentWrapper::hasOwn(JSContext* cx, HandleObject wrapper, 146 HandleId id, bool* bp) const { 147 PIERCE(cx, wrapper, MarkAtoms(cx, id), Wrapper::hasOwn(cx, wrapper, id, bp), 148 NOTHING); 149 } 150 151 static bool WrapReceiver(JSContext* cx, HandleObject wrapper, 152 MutableHandleValue receiver) { 153 // Usually the receiver is the wrapper and we can just unwrap it. If the 154 // wrapped object is also a wrapper, things are more complicated and we 155 // fall back to the slow path (it calls UncheckedUnwrap to unwrap all 156 // wrappers). 157 if (ObjectValue(*wrapper) == receiver) { 158 JSObject* wrapped = Wrapper::wrappedObject(wrapper); 159 if (!IsWrapper(wrapped)) { 160 MOZ_ASSERT(wrapped->compartment() == cx->compartment()); 161 MOZ_ASSERT(!IsWindow(wrapped)); 162 receiver.setObject(*wrapped); 163 return true; 164 } 165 } 166 167 return cx->compartment()->wrap(cx, receiver); 168 } 169 170 bool CrossCompartmentWrapper::get(JSContext* cx, HandleObject wrapper, 171 HandleValue receiver, HandleId id, 172 MutableHandleValue vp) const { 173 RootedValue receiverCopy(cx, receiver); 174 { 175 AutoRealm call(cx, wrappedObject(wrapper)); 176 if (!MarkAtoms(cx, id) || !WrapReceiver(cx, wrapper, &receiverCopy)) { 177 return false; 178 } 179 180 if (!Wrapper::get(cx, wrapper, receiverCopy, id, vp)) { 181 return false; 182 } 183 } 184 return cx->compartment()->wrap(cx, vp); 185 } 186 187 bool CrossCompartmentWrapper::set(JSContext* cx, HandleObject wrapper, 188 HandleId id, HandleValue v, 189 HandleValue receiver, 190 ObjectOpResult& result) const { 191 RootedValue valCopy(cx, v); 192 RootedValue receiverCopy(cx, receiver); 193 PIERCE(cx, wrapper, 194 MarkAtoms(cx, id) && cx->compartment()->wrap(cx, &valCopy) && 195 WrapReceiver(cx, wrapper, &receiverCopy), 196 Wrapper::set(cx, wrapper, id, valCopy, receiverCopy, result), NOTHING); 197 } 198 199 bool CrossCompartmentWrapper::getOwnEnumerablePropertyKeys( 200 JSContext* cx, HandleObject wrapper, MutableHandleIdVector props) const { 201 PIERCE(cx, wrapper, NOTHING, 202 Wrapper::getOwnEnumerablePropertyKeys(cx, wrapper, props), 203 MarkAtoms(cx, props)); 204 } 205 206 bool CrossCompartmentWrapper::enumerate(JSContext* cx, HandleObject wrapper, 207 MutableHandleIdVector props) const { 208 PIERCE(cx, wrapper, NOTHING, Wrapper::enumerate(cx, wrapper, props), 209 MarkAtoms(cx, props)); 210 } 211 212 bool CrossCompartmentWrapper::call(JSContext* cx, HandleObject wrapper, 213 const CallArgs& args) const { 214 RootedObject wrapped(cx, wrappedObject(wrapper)); 215 216 { 217 AutoRealm call(cx, wrapped); 218 219 args.setCallee(ObjectValue(*wrapped)); 220 if (!cx->compartment()->wrap(cx, args.mutableThisv())) { 221 return false; 222 } 223 224 for (size_t n = 0; n < args.length(); ++n) { 225 if (!cx->compartment()->wrap(cx, args[n])) { 226 return false; 227 } 228 } 229 230 if (!Wrapper::call(cx, wrapper, args)) { 231 return false; 232 } 233 } 234 235 return cx->compartment()->wrap(cx, args.rval()); 236 } 237 238 bool CrossCompartmentWrapper::construct(JSContext* cx, HandleObject wrapper, 239 const CallArgs& args) const { 240 RootedObject wrapped(cx, wrappedObject(wrapper)); 241 { 242 AutoRealm call(cx, wrapped); 243 244 for (size_t n = 0; n < args.length(); ++n) { 245 if (!cx->compartment()->wrap(cx, args[n])) { 246 return false; 247 } 248 } 249 if (!cx->compartment()->wrap(cx, args.newTarget())) { 250 return false; 251 } 252 if (!Wrapper::construct(cx, wrapper, args)) { 253 return false; 254 } 255 } 256 return cx->compartment()->wrap(cx, args.rval()); 257 } 258 259 bool CrossCompartmentWrapper::nativeCall(JSContext* cx, IsAcceptableThis test, 260 NativeImpl impl, 261 const CallArgs& srcArgs) const { 262 RootedObject wrapper(cx, &srcArgs.thisv().toObject()); 263 MOZ_ASSERT(srcArgs.thisv().isMagic(JS_IS_CONSTRUCTING) || 264 !UncheckedUnwrap(wrapper)->is<CrossCompartmentWrapperObject>()); 265 266 RootedObject wrapped(cx, wrappedObject(wrapper)); 267 { 268 AutoRealm call(cx, wrapped); 269 InvokeArgs dstArgs(cx); 270 if (!dstArgs.init(cx, srcArgs.length())) { 271 return false; 272 } 273 274 Value* src = srcArgs.base(); 275 Value* srcend = srcArgs.array() + srcArgs.length(); 276 Value* dst = dstArgs.base(); 277 278 RootedValue source(cx); 279 for (; src < srcend; ++src, ++dst) { 280 source = *src; 281 if (!cx->compartment()->wrap(cx, &source)) { 282 return false; 283 } 284 *dst = source.get(); 285 286 // Handle |this| specially. When we rewrap on the other side of the 287 // membrane, we might apply a same-compartment security wrapper that 288 // will stymie this whole process. If that happens, unwrap the wrapper. 289 // This logic can go away when same-compartment security wrappers go away. 290 if ((src == srcArgs.base() + 1) && dst->isObject()) { 291 RootedObject thisObj(cx, &dst->toObject()); 292 if (thisObj->is<WrapperObject>() && 293 Wrapper::wrapperHandler(thisObj)->hasSecurityPolicy()) { 294 MOZ_ASSERT(!thisObj->is<CrossCompartmentWrapperObject>()); 295 *dst = ObjectValue(*Wrapper::wrappedObject(thisObj)); 296 } 297 } 298 } 299 300 if (!CallNonGenericMethod(cx, test, impl, dstArgs)) { 301 return false; 302 } 303 304 srcArgs.rval().set(dstArgs.rval()); 305 } 306 return cx->compartment()->wrap(cx, srcArgs.rval()); 307 } 308 309 const char* CrossCompartmentWrapper::className(JSContext* cx, 310 HandleObject wrapper) const { 311 AutoRealm call(cx, wrappedObject(wrapper)); 312 return Wrapper::className(cx, wrapper); 313 } 314 315 JSString* CrossCompartmentWrapper::fun_toString(JSContext* cx, 316 HandleObject wrapper, 317 bool isToSource) const { 318 RootedString str(cx); 319 { 320 AutoRealm call(cx, wrappedObject(wrapper)); 321 str = Wrapper::fun_toString(cx, wrapper, isToSource); 322 if (!str) { 323 return nullptr; 324 } 325 } 326 if (!cx->compartment()->wrap(cx, &str)) { 327 return nullptr; 328 } 329 return str; 330 } 331 332 RegExpShared* CrossCompartmentWrapper::regexp_toShared( 333 JSContext* cx, HandleObject wrapper) const { 334 RootedRegExpShared re(cx); 335 { 336 AutoRealm call(cx, wrappedObject(wrapper)); 337 re = Wrapper::regexp_toShared(cx, wrapper); 338 if (!re) { 339 return nullptr; 340 } 341 } 342 343 // Get an equivalent RegExpShared associated with the current compartment. 344 Rooted<JSAtom*> source(cx, re->getSource()); 345 cx->markAtom(source); 346 return cx->zone()->regExps().get(cx, source, re->getFlags()); 347 } 348 349 bool CrossCompartmentWrapper::boxedValue_unbox(JSContext* cx, 350 HandleObject wrapper, 351 MutableHandleValue vp) const { 352 PIERCE(cx, wrapper, NOTHING, Wrapper::boxedValue_unbox(cx, wrapper, vp), 353 cx->compartment()->wrap(cx, vp)); 354 } 355 356 const CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u); 357 358 JS_PUBLIC_API void js::NukeCrossCompartmentWrapper(JSContext* cx, 359 JSObject* wrapper) { 360 JS::Compartment* comp = wrapper->compartment(); 361 auto ptr = comp->lookupWrapper(Wrapper::wrappedObject(wrapper)); 362 if (ptr) { 363 comp->removeWrapper(ptr); 364 } 365 NukeRemovedCrossCompartmentWrapper(cx, wrapper); 366 } 367 368 JS_PUBLIC_API void js::NukeCrossCompartmentWrapperIfExists( 369 JSContext* cx, JS::Compartment* source, JSObject* target) { 370 MOZ_ASSERT(source != target->compartment()); 371 MOZ_ASSERT(!target->is<CrossCompartmentWrapperObject>()); 372 auto ptr = source->lookupWrapper(target); 373 if (ptr) { 374 JSObject* wrapper = ptr->value().get(); 375 NukeCrossCompartmentWrapper(cx, wrapper); 376 } 377 } 378 379 // Returns true iff all realms in the compartment have been nuked. 380 static bool NukedAllRealms(JS::Compartment* comp) { 381 for (RealmsInCompartmentIter realm(comp); !realm.done(); realm.next()) { 382 if (!realm->nukedIncomingWrappers) { 383 return false; 384 } 385 } 386 return true; 387 } 388 389 /* 390 * NukeCrossCompartmentWrappers invalidates all cross-compartment wrappers 391 * that point to objects in the |target| realm. 392 * 393 * There is some complexity in targeting to preserve semantics which requires 394 * the filtering and behavioural options: 395 * 396 * - |sourceFilter| limits the compartments searched for source pointers 397 * - |nukeReferencesToWindow| will, if set to DontNukeWindowReferences skip 398 * wrappers whose target is the window proxy of the target realm. 399 * - |nukeReferencesFromTarget| will, when set to NukeAllReferences, disallow 400 * the creation of new wrappers to the target realm. This option can also 401 * allow more wrappers to be cleaned up transitively. 402 */ 403 JS_PUBLIC_API bool js::NukeCrossCompartmentWrappers( 404 JSContext* cx, const CompartmentFilter& sourceFilter, JS::Realm* target, 405 js::NukeReferencesToWindow nukeReferencesToWindow, 406 js::NukeReferencesFromTarget nukeReferencesFromTarget) { 407 CHECK_THREAD(cx); 408 JSRuntime* rt = cx->runtime(); 409 410 // If we're nuking all wrappers into the target realm, prevent us from 411 // creating new wrappers for it in the future. 412 if (nukeReferencesFromTarget == NukeAllReferences) { 413 target->nukedIncomingWrappers = true; 414 } 415 416 for (CompartmentsIter c(rt); !c.done(); c.next()) { 417 if (!sourceFilter.match(c)) { 418 continue; 419 } 420 421 // If the realm matches both the source and target filter, we may want to 422 // cut outgoing wrappers too, if we nuked all realms in the compartment. 423 bool nukeAll = 424 (nukeReferencesFromTarget == NukeAllReferences && 425 target->compartment() == c.get() && NukedAllRealms(c.get())); 426 427 // Iterate only the wrappers that have target compartment matched unless 428 // |nukeAll| is true. Use Maybe to avoid copying from conditionally 429 // initializing ObjectWrapperEnum. 430 mozilla::Maybe<Compartment::ObjectWrapperEnum> e; 431 if (MOZ_LIKELY(!nukeAll)) { 432 e.emplace(c, target->compartment()); 433 } else { 434 e.emplace(c); 435 c.get()->nukedOutgoingWrappers = true; 436 } 437 for (; !e->empty(); e->popFront()) { 438 JSObject* key = e->front().key(); 439 440 AutoWrapperRooter wobj(cx, WrapperValue(*e)); 441 442 // Unwrap from the wrapped object in key instead of the wrapper, this 443 // could save us a bit of time. 444 JSObject* wrapped = UncheckedUnwrap(key); 445 446 // Don't nuke wrappers for objects in other realms in the target 447 // compartment unless nukeAll is set because in that case we want to nuke 448 // all outgoing wrappers for the current compartment. 449 if (!nukeAll && wrapped->nonCCWRealm() != target) { 450 continue; 451 } 452 453 // Don't nuke wrappers for debugger objects. These are used in Breakpoints 454 // and nuking them breaks debugger invariants. 455 if (MOZ_UNLIKELY(wrapped->is<DebuggerInstanceObject>())) { 456 continue; 457 } 458 459 // We only skip nuking window references that point to a target 460 // compartment, not the ones that belong to it. 461 if (nukeReferencesToWindow == DontNukeWindowReferences && 462 MOZ_LIKELY(!nukeAll) && IsWindowProxy(wrapped)) { 463 continue; 464 } 465 466 // Now this is the wrapper we want to nuke. 467 e->removeFront(); 468 NukeRemovedCrossCompartmentWrapper(cx, wobj); 469 } 470 } 471 472 return true; 473 } 474 475 JS_PUBLIC_API bool js::AllowNewWrapper(JS::Compartment* target, JSObject* obj) { 476 // Disallow creating new wrappers if we nuked the object realm or target 477 // compartment. 478 479 MOZ_ASSERT(obj->compartment() != target); 480 481 // Wrappers for debugger objects are not nuked and we must continue to allow 482 // them to be created or we will break the invariants in Compartment::wrap. 483 if (MOZ_UNLIKELY(obj->is<DebuggerInstanceObject>())) { 484 return true; 485 } 486 487 if (target->nukedOutgoingWrappers || 488 obj->nonCCWRealm()->nukedIncomingWrappers) { 489 return false; 490 } 491 492 return true; 493 } 494 495 JS_PUBLIC_API bool js::NukedObjectRealm(JSObject* obj) { 496 return obj->nonCCWRealm()->nukedIncomingWrappers; 497 } 498 499 // Given a cross-compartment wrapper |wobj|, update it to point to 500 // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be 501 // useful even if wrapper already points to newTarget. 502 // This operation crashes on failure rather than leaving the heap in an 503 // inconsistent state. 504 void js::RemapWrapper(JSContext* cx, JSObject* wobjArg, 505 JSObject* newTargetArg) { 506 RootedObject wobj(cx, wobjArg); 507 RootedObject newTarget(cx, newTargetArg); 508 MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>()); 509 MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>()); 510 JSObject* origTarget = Wrapper::wrappedObject(wobj); 511 MOZ_ASSERT(origTarget); 512 JS::Compartment* wcompartment = wobj->compartment(); 513 MOZ_ASSERT(wcompartment != newTarget->compartment()); 514 515 AutoDisableProxyCheck adpc; 516 517 // This can't GC (and RemapDeadWrapper suppresses it). 518 JS::AutoAssertNoGC nogc(cx); 519 520 // If we're mapping to a different target (as opposed to just recomputing 521 // for the same target), we must not have an existing wrapper for the new 522 // target, otherwise this will break. 523 MOZ_ASSERT_IF(origTarget != newTarget, 524 !wcompartment->lookupWrapper(newTarget)); 525 526 // The old value should still be in the cross-compartment wrapper map, and 527 // the lookup should return wobj. 528 ObjectWrapperMap::Ptr p = wcompartment->lookupWrapper(origTarget); 529 MOZ_ASSERT(*p->value().unsafeGet() == wobj); 530 wcompartment->removeWrapper(p); 531 532 // When we remove origv from the wrapper map, its wrapper, wobj, must 533 // immediately cease to be a cross-compartment wrapper. Nuke it. 534 NukeCrossCompartmentWrapper(cx, wobj); 535 536 // If the target is a dead wrapper, and we're just fixing wrappers for 537 // it, then we're done now that the CCW is a dead wrapper. 538 if (JS_IsDeadWrapper(origTarget)) { 539 MOZ_RELEASE_ASSERT(origTarget == newTarget); 540 return; 541 } 542 543 js::RemapDeadWrapper(cx, wobj, newTarget); 544 } 545 546 // Given a dead proxy object |wobj|, turn it into a cross-compartment wrapper 547 // pointing at |newTarget|. 548 // This operation crashes on failure rather than leaving the heap in an 549 // inconsistent state. 550 void js::RemapDeadWrapper(JSContext* cx, HandleObject wobj, 551 HandleObject newTarget) { 552 MOZ_ASSERT(IsDeadProxyObject(wobj)); 553 MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>()); 554 555 // These are not exposed. Doing this would require updating the 556 // FinalizationObservers data structures. 557 MOZ_ASSERT(!newTarget->is<FinalizationRecordObject>()); 558 559 AutoDisableProxyCheck adpc; 560 561 // Suppress GC while we manipulate the wrapper map so that it can't observe 562 // intervening state. 563 gc::AutoSuppressGC nogc(cx); 564 565 // wobj is not a cross-compartment wrapper, so we can use nonCCWRealm. 566 Realm* wrealm = wobj->nonCCWRealm(); 567 568 // First, we wrap it in the new compartment. We try to use the existing 569 // wrapper, |wobj|, since it's been nuked anyway. The rewrap() function has 570 // the choice to reuse |wobj| or not. 571 RootedObject tobj(cx, newTarget); 572 AutoRealmUnchecked ar(cx, wrealm); 573 AutoEnterOOMUnsafeRegion oomUnsafe; 574 JS::Compartment* wcompartment = wobj->compartment(); 575 if (!wcompartment->rewrap(cx, &tobj, wobj)) { 576 oomUnsafe.crash("js::RemapWrapper"); 577 } 578 579 // If rewrap() reused |wobj|, it will have overwritten it and returned with 580 // |tobj == wobj|. Otherwise, |tobj| will point to a new wrapper and |wobj| 581 // will still be nuked. In the latter case, we replace |wobj| with the 582 // contents of the new wrapper in |tobj|. 583 if (tobj != wobj) { 584 // Now, because we need to maintain object identity, we do a brain 585 // transplant on the old object so that it contains the contents of the 586 // new one. 587 JSObject::swap(cx, wobj, tobj, oomUnsafe); 588 } 589 590 if (!wobj->is<WrapperObject>()) { 591 MOZ_ASSERT(js::IsDOMRemoteProxyObject(wobj) || IsDeadProxyObject(wobj)); 592 return; 593 } 594 595 // Before swapping, this wrapper came out of rewrap(), which enforces the 596 // invariant that the wrapper in the map points directly to the key. 597 MOZ_ASSERT(Wrapper::wrappedObject(wobj) == newTarget); 598 599 // Update the entry in the compartment's wrapper map to point to the old 600 // wrapper, which has now been updated (via reuse or swap). 601 if (!wcompartment->putWrapper(cx, newTarget, wobj)) { 602 oomUnsafe.crash("js::RemapWrapper"); 603 } 604 } 605 606 // Remap all cross-compartment wrappers pointing to |oldTarget| to point to 607 // |newTarget|. All wrappers are recomputed. 608 JS_PUBLIC_API bool js::RemapAllWrappersForObject(JSContext* cx, 609 HandleObject oldTarget, 610 HandleObject newTarget) { 611 AutoWrapperVector toTransplant(cx); 612 613 for (CompartmentsIter c(cx->runtime()); !c.done(); c.next()) { 614 if (ObjectWrapperMap::Ptr wp = c->lookupWrapper(oldTarget)) { 615 // We found a wrapper. Remember and root it. 616 if (!toTransplant.append(WrapperValue(wp))) { 617 return false; 618 } 619 } 620 } 621 622 for (const WrapperValue& v : toTransplant) { 623 RemapWrapper(cx, v, newTarget); 624 } 625 626 return true; 627 } 628 629 JS_PUBLIC_API bool js::RecomputeWrappers( 630 JSContext* cx, const CompartmentFilter& sourceFilter, 631 const CompartmentFilter& targetFilter) { 632 bool evictedNursery = false; 633 634 AutoWrapperVector toRecompute(cx); 635 for (CompartmentsIter c(cx->runtime()); !c.done(); c.next()) { 636 // Filter by source compartment. 637 if (!sourceFilter.match(c)) { 638 continue; 639 } 640 641 if (!evictedNursery && 642 c->hasNurseryAllocatedObjectWrapperEntries(targetFilter)) { 643 cx->runtime()->gc.evictNursery(); 644 evictedNursery = true; 645 } 646 647 // Iterate over object wrappers, filtering appropriately. 648 for (Compartment::ObjectWrapperEnum e(c, targetFilter); !e.empty(); 649 e.popFront()) { 650 // Don't remap wrappers to finalization record objects. These are used 651 // internally and are not exposed. 652 JSObject* wrapper = *e.front().value().unsafeGet(); 653 if (Wrapper::wrappedObject(wrapper)->is<FinalizationRecordObject>()) { 654 continue; 655 } 656 657 // Add the wrapper to the list. 658 if (!toRecompute.append(WrapperValue(e))) { 659 return false; 660 } 661 } 662 } 663 664 // Recompute all the wrappers in the list. 665 for (const WrapperValue& wrapper : toRecompute) { 666 JSObject* wrapped = Wrapper::wrappedObject(wrapper); 667 RemapWrapper(cx, wrapper, wrapped); 668 } 669 670 return true; 671 }