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/. */
7 #include "vm/Compartment-inl.h"
9 #include "mozilla/MemoryReporting.h"
13 #include "jsfriendapi.h"
15 #include "debugger/DebugAPI.h"
17 #include "gc/Memory.h"
18 #include "gc/PublicIterators.h"
20 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
21 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
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 #ifdef ENABLE_RECORD_TUPLE
31 # include "vm/RecordTupleShared.h"
33 #include "vm/WrapperObject.h"
35 #include "gc/Marking-inl.h"
36 #include "gc/WeakMap-inl.h"
37 #include "vm/JSObject-inl.h"
38 #include "vm/Realm-inl.h"
42 using JS::AutoStableStringChars
;
44 Compartment::Compartment(Zone
* zone
, bool invisibleToDebugger
)
46 runtime_(zone
->runtimeFromAnyThread()),
47 invisibleToDebugger_(invisibleToDebugger
),
48 crossCompartmentObjectWrappers(zone
, 0),
51 #ifdef JSGC_HASH_TABLE_CHECKS
53 void Compartment::checkObjectWrappersAfterMovingGC() {
54 for (ObjectWrapperEnum
e(this); !e
.empty(); e
.popFront()) {
55 // Assert that the postbarriers have worked and that nothing is left in the
56 // wrapper map that points into the nursery, and that the hash table entries
58 auto key
= e
.front().key();
59 CheckGCThingAfterMovingGC(key
.get());
61 auto ptr
= crossCompartmentObjectWrappers
.lookup(key
);
62 MOZ_RELEASE_ASSERT(ptr
.found() && &*ptr
== &e
.front());
66 #endif // JSGC_HASH_TABLE_CHECKS
68 bool Compartment::putWrapper(JSContext
* cx
, JSObject
* wrapped
,
70 MOZ_ASSERT(!js::IsProxy(wrapper
) || js::GetProxyHandler(wrapper
)->family() !=
71 js::GetDOMRemoteProxyHandlerFamily());
73 if (!crossCompartmentObjectWrappers
.put(wrapped
, wrapper
)) {
74 ReportOutOfMemory(cx
);
81 bool Compartment::putWrapper(JSContext
* cx
, JSString
* wrapped
,
83 if (!zone()->crossZoneStringWrappers().put(wrapped
, wrapper
)) {
84 ReportOutOfMemory(cx
);
91 void Compartment::removeWrapper(js::ObjectWrapperMap::Ptr p
) {
92 JSObject
* key
= p
->key();
93 JSObject
* value
= p
->value().unbarrieredGet();
94 if (js::gc::detail::GetDelegate(value
) == key
) {
95 key
->zone()->beforeClearDelegate(value
, key
);
98 crossCompartmentObjectWrappers
.remove(p
);
101 JSString
* js::CopyStringPure(JSContext
* cx
, JSString
* str
) {
103 * Directly allocate the copy in the destination compartment, rather than
104 * first flattening it (and possibly allocating in source compartment),
105 * because we don't know whether the flattening will pay off later.
108 size_t len
= str
->length();
110 if (str
->isLinear()) {
111 /* Only use AutoStableStringChars if the NoGC allocation fails. */
112 if (str
->hasLatin1Chars()) {
113 JS::AutoCheckCannotGC nogc
;
114 copy
= NewStringCopyN
<NoGC
>(cx
, str
->asLinear().latin1Chars(nogc
), len
);
116 JS::AutoCheckCannotGC nogc
;
117 copy
= NewStringCopyNDontDeflate
<NoGC
>(
118 cx
, str
->asLinear().twoByteChars(nogc
), len
);
124 AutoStableStringChars
chars(cx
);
125 if (!chars
.init(cx
, str
)) {
129 return chars
.isLatin1() ? NewStringCopyN
<CanGC
>(
130 cx
, chars
.latin1Range().begin().get(), len
)
131 : NewStringCopyNDontDeflate
<CanGC
>(
132 cx
, chars
.twoByteRange().begin().get(), len
);
135 if (str
->hasLatin1Chars()) {
136 UniquePtr
<Latin1Char
[], JS::FreePolicy
> copiedChars
=
137 str
->asRope().copyLatin1Chars(cx
, js::StringBufferArena
);
142 return NewString
<CanGC
>(cx
, std::move(copiedChars
), len
);
145 UniqueTwoByteChars copiedChars
=
146 str
->asRope().copyTwoByteChars(cx
, js::StringBufferArena
);
151 return NewStringDontDeflate
<CanGC
>(cx
, std::move(copiedChars
), len
);
154 bool Compartment::wrap(JSContext
* cx
, MutableHandleString strp
) {
155 MOZ_ASSERT(cx
->compartment() == this);
157 /* If the string is already in this compartment, we are done. */
158 JSString
* str
= strp
;
159 if (str
->zoneFromAnyThread() == zone()) {
164 * If the string is an atom, we don't have to copy, but we do need to mark
165 * the atom as being in use by the new zone.
168 cx
->markAtom(&str
->asAtom());
172 /* Check the cache. */
173 if (StringWrapperMap::Ptr p
= lookupWrapper(str
)) {
174 strp
.set(p
->value().get());
178 /* No dice. Make a copy, and cache it. */
179 JSString
* copy
= CopyStringPure(cx
, str
);
183 if (!putWrapper(cx
, strp
, copy
)) {
191 bool Compartment::wrap(JSContext
* cx
, MutableHandleBigInt bi
) {
192 MOZ_ASSERT(cx
->compartment() == this);
194 if (bi
->zone() == cx
->zone()) {
198 BigInt
* copy
= BigInt::copy(cx
, bi
);
206 bool Compartment::getNonWrapperObjectForCurrentCompartment(
207 JSContext
* cx
, HandleObject origObj
, MutableHandleObject obj
) {
208 // Ensure that we have entered a realm.
209 MOZ_ASSERT(cx
->global());
211 // The object is already in the right compartment. Normally same-
212 // compartment returns the object itself, however, windows are always
213 // wrapped by a proxy, so we have to check for that case here manually.
214 if (obj
->compartment() == this) {
215 obj
.set(ToWindowProxyIfWindow(obj
));
219 // Note that if the object is same-compartment, but has been wrapped into a
220 // different compartment, we need to unwrap it and return the bare same-
221 // compartment object. Note again that windows are always wrapped by a
222 // WindowProxy even when same-compartment so take care not to strip this
223 // particular wrapper.
224 RootedObject
objectPassedToWrap(cx
, obj
);
225 obj
.set(UncheckedUnwrap(obj
, /* stopAtWindowProxy = */ true));
226 if (obj
->compartment() == this) {
227 MOZ_ASSERT(!IsWindow(obj
));
231 // Disallow creating new wrappers if we nuked the object's realm or the
232 // current compartment.
233 if (!AllowNewWrapper(this, obj
)) {
234 obj
.set(NewDeadProxyObject(cx
, obj
));
238 // Use the WindowProxy instead of the Window here, so that we don't have to
239 // deal with this in the rest of the wrapping code.
241 obj
.set(ToWindowProxyIfWindow(obj
));
243 // ToWindowProxyIfWindow can return a CCW if |obj| was a navigated-away-from
244 // Window. Strip any CCWs.
245 obj
.set(UncheckedUnwrap(obj
));
247 if (JS_IsDeadWrapper(obj
)) {
248 obj
.set(NewDeadProxyObject(cx
, obj
));
252 MOZ_ASSERT(IsWindowProxy(obj
) || IsDOMRemoteProxyObject(obj
));
254 // We crossed a compartment boundary there, so may now have a gray object.
255 // This function is not allowed to return gray objects, so don't do that.
256 ExposeObjectToActiveJS(obj
);
259 // If the object is a dead wrapper, return a new dead wrapper rather than
260 // trying to wrap it for a different compartment.
261 if (JS_IsDeadWrapper(obj
)) {
262 obj
.set(NewDeadProxyObject(cx
, obj
));
266 // Invoke the prewrap callback. The prewrap callback is responsible for
267 // doing similar reification as above, but can account for any additional
268 // embedder requirements.
270 // We're a bit worried about infinite recursion here, so we do a check -
272 auto preWrap
= cx
->runtime()->wrapObjectCallbacks
->preWrap
;
273 AutoCheckRecursionLimit
recursion(cx
);
274 if (!recursion
.checkSystem(cx
)) {
278 preWrap(cx
, cx
->global(), origObj
, obj
, objectPassedToWrap
, obj
);
283 MOZ_ASSERT(!IsWindow(obj
));
288 bool Compartment::getOrCreateWrapper(JSContext
* cx
, HandleObject existing
,
289 MutableHandleObject obj
) {
290 // ScriptSourceObject is an internal object that we never need to wrap.
291 MOZ_ASSERT(!obj
->is
<ScriptSourceObject
>());
293 // If we already have a wrapper for this value, use it.
294 if (ObjectWrapperMap::Ptr p
= lookupWrapper(obj
)) {
295 obj
.set(p
->value().get());
296 MOZ_ASSERT(obj
->is
<CrossCompartmentWrapperObject
>());
300 // Ensure that the wrappee is exposed in case we are creating a new wrapper
301 // for a gray object.
302 ExposeObjectToActiveJS(obj
);
304 // Create a new wrapper for the object.
305 auto wrap
= cx
->runtime()->wrapObjectCallbacks
->wrap
;
306 RootedObject
wrapper(cx
, wrap(cx
, existing
, obj
));
311 // We maintain the invariant that the key in the cross-compartment wrapper
312 // map is always directly wrapped by the value.
313 MOZ_ASSERT(Wrapper::wrappedObject(wrapper
) == obj
);
315 if (!putWrapper(cx
, obj
, wrapper
)) {
316 // Enforce the invariant that all cross-compartment wrapper object are
317 // in the map by nuking the wrapper if we couldn't add it.
318 // Unfortunately it's possible for the wrapper to still be marked if we
319 // took this path, for example if the object metadata callback stashes a
321 if (wrapper
->is
<CrossCompartmentWrapperObject
>()) {
322 NukeCrossCompartmentWrapper(cx
, wrapper
);
331 #ifdef ENABLE_RECORD_TUPLE
332 bool Compartment::wrapExtendedPrimitive(JSContext
* cx
,
333 MutableHandleObject obj
) {
334 MOZ_ASSERT(IsExtendedPrimitive(*obj
));
335 MOZ_ASSERT(cx
->compartment() == this);
337 if (obj
->compartment() == this) {
341 JSObject
* copy
= CopyExtendedPrimitive(cx
, obj
);
351 bool Compartment::wrap(JSContext
* cx
, MutableHandleObject obj
) {
352 MOZ_ASSERT(cx
->compartment() == this);
358 #ifdef ENABLE_RECORD_TUPLE
359 MOZ_ASSERT(!IsExtendedPrimitive(*obj
));
362 AutoDisableProxyCheck adpc
;
364 // Anything we're wrapping has already escaped into script, so must have
365 // been unmarked-gray at some point in the past.
366 JS::AssertObjectIsNotGray(obj
);
368 // The passed object may already be wrapped, or may fit a number of special
369 // cases that we need to check for and manually correct.
370 if (!getNonWrapperObjectForCurrentCompartment(cx
, /* origObj = */ nullptr,
375 // If the reification above did not result in a same-compartment object,
376 // get or create a new wrapper object in this compartment for it.
377 if (obj
->compartment() != this) {
378 if (!getOrCreateWrapper(cx
, nullptr, obj
)) {
383 // Ensure that the wrapper is also exposed.
384 ExposeObjectToActiveJS(obj
);
388 bool Compartment::rewrap(JSContext
* cx
, MutableHandleObject obj
,
389 HandleObject existingArg
) {
390 MOZ_ASSERT(cx
->compartment() == this);
392 MOZ_ASSERT(existingArg
);
393 MOZ_ASSERT(existingArg
->compartment() == cx
->compartment());
394 MOZ_ASSERT(IsDeadProxyObject(existingArg
));
396 AutoDisableProxyCheck adpc
;
398 // It may not be possible to re-use existing; if so, clear it so that we
399 // are forced to create a new wrapper. Note that this cannot call out to
400 // |wrap| because of the different gray unmarking semantics.
401 RootedObject
existing(cx
, existingArg
);
402 if (existing
->hasStaticPrototype() ||
403 // Note: Class asserted above, so all that's left to check is callability
404 existing
->isCallable() || obj
->isCallable()) {
405 existing
.set(nullptr);
408 // The passed object may already be wrapped, or may fit a number of special
409 // cases that we need to check for and manually correct. We pass in
410 // |existingArg| instead of |existing|, because the purpose is to get the
411 // address of the object we are transplanting onto, not to find a wrapper
413 if (!getNonWrapperObjectForCurrentCompartment(cx
, existingArg
, obj
)) {
417 // If the reification above resulted in a same-compartment object, we do
418 // not need to create or return an existing wrapper.
419 if (obj
->compartment() == this) {
423 return getOrCreateWrapper(cx
, existing
, obj
);
426 bool Compartment::wrap(JSContext
* cx
,
427 MutableHandle
<JS::PropertyDescriptor
> desc
) {
428 if (desc
.hasGetter()) {
429 if (!wrap(cx
, desc
.getter())) {
433 if (desc
.hasSetter()) {
434 if (!wrap(cx
, desc
.setter())) {
438 if (desc
.hasValue()) {
439 if (!wrap(cx
, desc
.value())) {
446 bool Compartment::wrap(JSContext
* cx
,
447 MutableHandle
<mozilla::Maybe
<PropertyDescriptor
>> desc
) {
448 if (desc
.isNothing()) {
452 Rooted
<PropertyDescriptor
> desc_(cx
, *desc
);
453 if (!wrap(cx
, &desc_
)) {
456 desc
.set(mozilla::Some(desc_
.get()));
460 bool Compartment::wrap(JSContext
* cx
, MutableHandle
<GCVector
<Value
>> vec
) {
461 for (size_t i
= 0; i
< vec
.length(); ++i
) {
462 if (!wrap(cx
, vec
[i
])) {
469 static inline bool ShouldTraceWrapper(JSObject
* wrapper
,
470 Compartment::EdgeSelector whichEdges
) {
471 if (whichEdges
== Compartment::AllEdges
) {
475 bool isGray
= wrapper
->isMarkedGray();
476 return (whichEdges
== Compartment::NonGrayEdges
&& !isGray
) ||
477 (whichEdges
== Compartment::GrayEdges
&& isGray
);
480 void Compartment::traceWrapperTargetsInCollectedZones(JSTracer
* trc
,
481 EdgeSelector whichEdges
) {
482 // Trace cross compartment wrapper private pointers into collected zones to
483 // either mark or update them. Wrapped object pointers are updated by
484 // sweepCrossCompartmentObjectWrappers().
486 MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
487 MOZ_ASSERT(!zone()->isCollectingFromAnyThread() ||
488 trc
->runtime()->gc
.isHeapCompacting());
490 for (WrappedObjectCompartmentEnum
c(this); !c
.empty(); c
.popFront()) {
491 Zone
* zone
= c
.front()->zone();
492 if (!zone
->isCollectingFromAnyThread()) {
496 for (ObjectWrapperEnum
e(this, c
); !e
.empty(); e
.popFront()) {
497 JSObject
* obj
= e
.front().value().unbarrieredGet();
498 ProxyObject
* wrapper
= &obj
->as
<ProxyObject
>();
499 if (ShouldTraceWrapper(wrapper
, whichEdges
)) {
500 ProxyObject::traceEdgeToTarget(trc
, wrapper
);
507 void Compartment::traceIncomingCrossCompartmentEdgesForZoneGC(
508 JSTracer
* trc
, EdgeSelector whichEdges
) {
509 MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
511 for (ZonesIter
zone(trc
->runtime(), SkipAtoms
); !zone
.done(); zone
.next()) {
512 if (zone
->isCollectingFromAnyThread()) {
516 for (CompartmentsInZoneIter
c(zone
); !c
.done(); c
.next()) {
517 c
->traceWrapperTargetsInCollectedZones(trc
, whichEdges
);
521 // Currently we trace all debugger edges as black.
522 if (whichEdges
!= GrayEdges
) {
523 DebugAPI::traceCrossCompartmentEdges(trc
);
527 void Compartment::sweepAfterMinorGC(JSTracer
* trc
) {
528 crossCompartmentObjectWrappers
.sweepAfterMinorGC(trc
);
530 for (RealmsInCompartmentIter
r(this); !r
.done(); r
.next()) {
531 r
->sweepAfterMinorGC(trc
);
535 // Remove dead wrappers from the table or update pointers to moved objects.
536 void Compartment::traceCrossCompartmentObjectWrapperEdges(JSTracer
* trc
) {
537 crossCompartmentObjectWrappers
.traceWeak(trc
);
540 void Compartment::fixupCrossCompartmentObjectWrappersAfterMovingGC(
542 MOZ_ASSERT(trc
->runtime()->gc
.isHeapCompacting());
544 // Sweep the wrapper map to update keys (wrapped values) in other
545 // compartments that may have been moved.
546 traceCrossCompartmentObjectWrapperEdges(trc
);
548 // Trace the wrappers in the map to update their cross-compartment edges
549 // to wrapped values in other compartments that may have been moved.
550 traceWrapperTargetsInCollectedZones(trc
, AllEdges
);
553 void Compartment::fixupAfterMovingGC(JSTracer
* trc
) {
554 MOZ_ASSERT(zone()->isGCCompacting());
556 for (RealmsInCompartmentIter
r(this); !r
.done(); r
.next()) {
557 r
->fixupAfterMovingGC(trc
);
560 // Sweep the wrapper map to update values (wrapper objects) in this
561 // compartment that may have been moved.
562 traceCrossCompartmentObjectWrapperEdges(trc
);
565 void Compartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
,
566 size_t* compartmentObjects
,
567 size_t* crossCompartmentWrappersTables
,
568 size_t* compartmentsPrivateData
) {
569 *compartmentObjects
+= mallocSizeOf(this);
570 *crossCompartmentWrappersTables
+=
571 crossCompartmentObjectWrappers
.sizeOfExcludingThis(mallocSizeOf
);
573 if (auto callback
= runtime_
->sizeOfIncludingThisCompartmentCallback
) {
574 *compartmentsPrivateData
+= callback(mallocSizeOf
, this);
578 GlobalObject
& Compartment::firstGlobal() const {
579 for (Realm
* realm
: realms_
) {
580 if (!realm
->hasInitializedGlobal()) {
583 GlobalObject
* global
= realm
->maybeGlobal();
584 ExposeObjectToActiveJS(global
);
587 MOZ_CRASH("If all our globals are dead, why is someone expecting a global?");
590 JS_PUBLIC_API JSObject
* js::GetFirstGlobalInCompartment(JS::Compartment
* comp
) {
591 return &comp
->firstGlobal();
594 JS_PUBLIC_API
bool js::CompartmentHasLiveGlobal(JS::Compartment
* comp
) {
596 for (Realm
* r
: comp
->realms()) {
597 if (r
->hasLiveGlobal()) {
604 void Compartment::traceWeakNativeIterators(JSTracer
* trc
) {
605 /* Sweep list of native iterators. */
606 NativeIteratorListIter
iter(&enumerators_
);
607 while (!iter
.done()) {
608 NativeIterator
* ni
= iter
.next();
609 JSObject
* iterObj
= ni
->iterObj();
610 if (!TraceManuallyBarrieredWeakEdge(trc
, &iterObj
,
611 "Compartment::enumerators_")) {
614 MOZ_ASSERT(ni
->objectBeingIterated()->compartment() == this);