Backed out changeset 8f976ed899d7 (bug 1847231) for causing bc failures on browser_se...
[gecko.git] / js / src / vm / Compartment.cpp
bloba99a9145c7a7066125826197840557c2eb782c0c
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"
11 #include <stddef.h>
13 #include "jsfriendapi.h"
15 #include "debugger/DebugAPI.h"
16 #include "gc/GC.h"
17 #include "gc/Memory.h"
18 #include "gc/PublicIterators.h"
19 #include "gc/Zone.h"
20 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
21 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
22 #include "js/Proxy.h"
23 #include "js/RootingAPI.h"
24 #include "js/StableStringChars.h"
25 #include "js/Wrapper.h"
26 #include "js/WrapperCallbacks.h"
27 #include "proxy/DeadObjectProxy.h"
28 #include "proxy/DOMProxy.h"
29 #include "vm/JSContext.h"
30 #ifdef ENABLE_RECORD_TUPLE
31 # include "vm/RecordTupleShared.h"
32 #endif
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"
40 using namespace js;
42 using JS::AutoStableStringChars;
44 Compartment::Compartment(Zone* zone, bool invisibleToDebugger)
45 : zone_(zone),
46 runtime_(zone->runtimeFromAnyThread()),
47 invisibleToDebugger_(invisibleToDebugger),
48 crossCompartmentObjectWrappers(zone, 0),
49 realms_(zone) {}
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
57 // are discoverable.
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,
69 JSObject* wrapper) {
70 MOZ_ASSERT(!js::IsProxy(wrapper) || js::GetProxyHandler(wrapper)->family() !=
71 js::GetDOMRemoteProxyHandlerFamily());
73 if (!crossCompartmentObjectWrappers.put(wrapped, wrapper)) {
74 ReportOutOfMemory(cx);
75 return false;
78 return true;
81 bool Compartment::putWrapper(JSContext* cx, JSString* wrapped,
82 JSString* wrapper) {
83 if (!zone()->crossZoneStringWrappers().put(wrapped, wrapper)) {
84 ReportOutOfMemory(cx);
85 return false;
88 return true;
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();
109 JSString* copy;
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);
115 } else {
116 JS::AutoCheckCannotGC nogc;
117 copy = NewStringCopyNDontDeflate<NoGC>(
118 cx, str->asLinear().twoByteChars(nogc), len);
120 if (copy) {
121 return copy;
124 AutoStableStringChars chars(cx);
125 if (!chars.init(cx, str)) {
126 return nullptr;
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);
138 if (!copiedChars) {
139 return nullptr;
142 return NewString<CanGC>(cx, std::move(copiedChars), len);
145 UniqueTwoByteChars copiedChars =
146 str->asRope().copyTwoByteChars(cx, js::StringBufferArena);
147 if (!copiedChars) {
148 return nullptr;
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()) {
160 return true;
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.
167 if (str->isAtom()) {
168 cx->markAtom(&str->asAtom());
169 return true;
172 /* Check the cache. */
173 if (StringWrapperMap::Ptr p = lookupWrapper(str)) {
174 strp.set(p->value().get());
175 return true;
178 /* No dice. Make a copy, and cache it. */
179 JSString* copy = CopyStringPure(cx, str);
180 if (!copy) {
181 return false;
183 if (!putWrapper(cx, strp, copy)) {
184 return false;
187 strp.set(copy);
188 return true;
191 bool Compartment::wrap(JSContext* cx, MutableHandleBigInt bi) {
192 MOZ_ASSERT(cx->compartment() == this);
194 if (bi->zone() == cx->zone()) {
195 return true;
198 BigInt* copy = BigInt::copy(cx, bi);
199 if (!copy) {
200 return false;
202 bi.set(copy);
203 return true;
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));
216 return true;
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));
228 return true;
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));
235 return !!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.
240 if (IsWindow(obj)) {
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));
249 return !!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));
263 return !!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 -
271 // see bug 809295.
272 auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap;
273 AutoCheckRecursionLimit recursion(cx);
274 if (!recursion.checkSystem(cx)) {
275 return false;
277 if (preWrap) {
278 preWrap(cx, cx->global(), origObj, obj, objectPassedToWrap, obj);
279 if (!obj) {
280 return false;
283 MOZ_ASSERT(!IsWindow(obj));
285 return true;
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>());
297 return true;
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));
307 if (!wrapper) {
308 return false;
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
320 // reference to it.
321 if (wrapper->is<CrossCompartmentWrapperObject>()) {
322 NukeCrossCompartmentWrapper(cx, wrapper);
324 return false;
327 obj.set(wrapper);
328 return true;
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) {
338 return true;
341 JSObject* copy = CopyExtendedPrimitive(cx, obj);
342 if (!copy) {
343 return false;
346 obj.set(copy);
347 return true;
349 #endif
351 bool Compartment::wrap(JSContext* cx, MutableHandleObject obj) {
352 MOZ_ASSERT(cx->compartment() == this);
354 if (!obj) {
355 return true;
358 #ifdef ENABLE_RECORD_TUPLE
359 MOZ_ASSERT(!IsExtendedPrimitive(*obj));
360 #endif
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,
371 obj)) {
372 return false;
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)) {
379 return false;
383 // Ensure that the wrapper is also exposed.
384 ExposeObjectToActiveJS(obj);
385 return true;
388 bool Compartment::rewrap(JSContext* cx, MutableHandleObject obj,
389 HandleObject existingArg) {
390 MOZ_ASSERT(cx->compartment() == this);
391 MOZ_ASSERT(obj);
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
412 // to reuse.
413 if (!getNonWrapperObjectForCurrentCompartment(cx, existingArg, obj)) {
414 return false;
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) {
420 return true;
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())) {
430 return false;
433 if (desc.hasSetter()) {
434 if (!wrap(cx, desc.setter())) {
435 return false;
438 if (desc.hasValue()) {
439 if (!wrap(cx, desc.value())) {
440 return false;
443 return true;
446 bool Compartment::wrap(JSContext* cx,
447 MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
448 if (desc.isNothing()) {
449 return true;
452 Rooted<PropertyDescriptor> desc_(cx, *desc);
453 if (!wrap(cx, &desc_)) {
454 return false;
456 desc.set(mozilla::Some(desc_.get()));
457 return true;
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])) {
463 return false;
466 return true;
469 static inline bool ShouldTraceWrapper(JSObject* wrapper,
470 Compartment::EdgeSelector whichEdges) {
471 if (whichEdges == Compartment::AllEdges) {
472 return true;
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()) {
493 continue;
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);
506 /* static */
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()) {
513 continue;
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(
541 JSTracer* trc) {
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()) {
581 continue;
583 GlobalObject* global = realm->maybeGlobal();
584 ExposeObjectToActiveJS(global);
585 return *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) {
595 MOZ_ASSERT(comp);
596 for (Realm* r : comp->realms()) {
597 if (r->hasLiveGlobal()) {
598 return true;
601 return false;
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_")) {
612 ni->unlink();
614 MOZ_ASSERT(ni->objectBeingIterated()->compartment() == this);