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