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