/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef vm_Compartment_h #define vm_Compartment_h #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include #include #include "gc/NurseryAwareHashMap.h" #include "gc/ZoneAllocator.h" #include "vm/Iteration.h" #include "vm/JSObject.h" #include "vm/JSScript.h" namespace js { JSString* CopyStringPure(JSContext* cx, JSString* str); // The data structure use to storing JSObject CCWs for a given source // compartment. These are partitioned by target compartment so that we can // easily select wrappers by source and target compartment. String CCWs are // stored in a per-zone separate map. class ObjectWrapperMap { static const size_t InitialInnerMapSize = 4; using InnerMap = NurseryAwareHashMap; using OuterMap = GCHashMap, ZoneAllocPolicy>; OuterMap map; Zone* zone; public: class Enum { Enum(const Enum&) = delete; void operator=(const Enum&) = delete; void goToNext() { if (outer.isNothing()) { return; } for (; !outer->empty(); outer->popFront()) { JS::Compartment* c = outer->front().key(); MOZ_ASSERT(c); if (filter && !filter->match(c)) { continue; } InnerMap& m = outer->front().value(); if (!m.empty()) { if (inner.isSome()) { inner.reset(); } inner.emplace(m); outer->popFront(); return; } } } mozilla::Maybe outer; mozilla::Maybe inner; const CompartmentFilter* filter; public: explicit Enum(ObjectWrapperMap& m) : filter(nullptr) { outer.emplace(m.map); goToNext(); } Enum(ObjectWrapperMap& m, const CompartmentFilter& f) : filter(&f) { outer.emplace(m.map); goToNext(); } Enum(ObjectWrapperMap& m, JS::Compartment* target) { // Leave the outer map as nothing and only iterate the inner map we // find here. auto p = m.map.lookup(target); if (p) { inner.emplace(p->value()); } } bool empty() const { return (outer.isNothing() || outer->empty()) && (inner.isNothing() || inner->empty()); } InnerMap::Entry& front() const { MOZ_ASSERT(inner.isSome() && !inner->empty()); return inner->front(); } void popFront() { MOZ_ASSERT(!empty()); if (!inner->empty()) { inner->popFront(); if (!inner->empty()) { return; } } goToNext(); } void removeFront() { MOZ_ASSERT(inner.isSome()); inner->removeFront(); } }; class Ptr : public InnerMap::Ptr { friend class ObjectWrapperMap; InnerMap* map; Ptr() : InnerMap::Ptr(), map(nullptr) {} Ptr(const InnerMap::Ptr& p, InnerMap& m) : InnerMap::Ptr(p), map(&m) {} }; // Iterator over compartments that the ObjectWrapperMap has wrappers for. class WrappedCompartmentEnum { OuterMap::Enum iter; void settle() { // It's possible for InnerMap to be empty after wrappers have been // removed, e.g. by being nuked. while (!iter.empty() && iter.front().value().empty()) { iter.popFront(); } } public: explicit WrappedCompartmentEnum(ObjectWrapperMap& map) : iter(map.map) { settle(); } bool empty() const { return iter.empty(); } JS::Compartment* front() const { return iter.front().key(); } operator JS::Compartment*() const { return front(); } void popFront() { iter.popFront(); settle(); } }; explicit ObjectWrapperMap(Zone* zone) : map(zone), zone(zone) {} ObjectWrapperMap(Zone* zone, size_t aLen) : map(zone, aLen), zone(zone) {} bool empty() { if (map.empty()) { return true; } for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { if (!e.front().value().empty()) { return false; } } return true; } Ptr lookup(JSObject* obj) const { auto op = map.lookup(obj->compartment()); if (op) { auto ip = op->value().lookup(obj); if (ip) { return Ptr(ip, op->value()); } } return Ptr(); } void remove(Ptr p) { if (p) { p.map->remove(p); } } [[nodiscard]] bool put(JSObject* key, JSObject* value) { JS::Compartment* comp = key->compartment(); auto ptr = map.lookupForAdd(comp); if (!ptr) { InnerMap m(zone, InitialInnerMapSize); if (!map.add(ptr, comp, std::move(m))) { return false; } } return ptr->value().put(key, value); } size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { size_t size = map.shallowSizeOfExcludingThis(mallocSizeOf); for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { size += e.front().value().sizeOfExcludingThis(mallocSizeOf); } return size; } size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { size_t size = map.shallowSizeOfIncludingThis(mallocSizeOf); for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { size += e.front().value().sizeOfIncludingThis(mallocSizeOf); } return size; } bool hasNurseryAllocatedWrapperEntries(const CompartmentFilter& f) { for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { JS::Compartment* c = e.front().key(); if (c && !f.match(c)) { continue; } InnerMap& m = e.front().value(); if (m.hasNurseryEntries()) { return true; } } return false; } void sweepAfterMinorGC(JSTracer* trc) { for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { InnerMap& m = e.front().value(); m.sweepAfterMinorGC(trc); if (m.empty()) { e.removeFront(); } } } void traceWeak(JSTracer* trc) { for (OuterMap::Enum e(map); !e.empty(); e.popFront()) { InnerMap& m = e.front().value(); m.traceWeak(trc); if (m.empty()) { e.removeFront(); } } } }; using StringWrapperMap = NurseryAwareHashMap; } // namespace js class JS::Compartment { JS::Zone* zone_; JSRuntime* runtime_; bool invisibleToDebugger_; js::ObjectWrapperMap crossCompartmentObjectWrappers; using RealmVector = js::Vector; RealmVector realms_; public: /* * During GC, stores the head of a list of incoming pointers from gray cells. * * The objects in the list are either cross-compartment wrappers, or * debugger wrapper objects. The list link is either in the second extra * slot for the former, or a special slot for the latter. */ JSObject* gcIncomingGrayPointers = nullptr; void* data = nullptr; // Fields set and used by the GC. Be careful, may be stale after we return // to the mutator. struct { // These flags help us to discover if a compartment that shouldn't be // alive manages to outlive a GC. Note that these flags have to be on // the compartment, not the realm, because same-compartment realms can // have cross-realm pointers without wrappers. bool scheduledForDestruction = false; bool hasMarkedCells = false; bool maybeAlive = true; // During GC, we may set this to |true| if we entered a realm in this // compartment. Note that (without a stack walk) we don't know exactly // *which* realms, because Realm::enterRealmDepthIgnoringJit_ does not // account for cross-Realm calls in JIT code updating cx->realm_. See // also the enterRealmDepthIgnoringJit_ comment. bool hasEnteredRealm = false; } gcState; // True if all outgoing wrappers have been nuked. This happens when all realms // have been nuked and NukeCrossCompartmentWrappers is called with the // NukeAllReferences option. This prevents us from creating new wrappers for // the compartment. bool nukedOutgoingWrappers = false; JS::Zone* zone() { return zone_; } const JS::Zone* zone() const { return zone_; } JSRuntime* runtimeFromMainThread() const { MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_)); return runtime_; } // Note: Unrestricted access to the zone's runtime from an arbitrary // thread can easily lead to races. Use this method very carefully. JSRuntime* runtimeFromAnyThread() const { return runtime_; } // Certain compartments are implementation details of the embedding, and // references to them should never leak out to script. For realms belonging to // this compartment, onNewGlobalObject does not fire, and addDebuggee is a // no-op. bool invisibleToDebugger() const { return invisibleToDebugger_; } RealmVector& realms() { return realms_; } // Cross-compartment wrappers are shared by all realms in the compartment, but // they still have a per-realm ObjectGroup etc. To prevent us from having // multiple realms, each with some cross-compartment wrappers potentially // keeping the realm alive longer than necessary, we always allocate CCWs in // the first realm. js::GlobalObject& firstGlobal() const; js::GlobalObject& globalForNewCCW() const { return firstGlobal(); } void assertNoCrossCompartmentWrappers() { MOZ_ASSERT(crossCompartmentObjectWrappers.empty()); } void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* compartmentObjects, size_t* crossCompartmentWrappersTables, size_t* compartmentsPrivateData); #ifdef JSGC_HASH_TABLE_CHECKS void checkObjectWrappersAfterMovingGC(); #endif private: bool getNonWrapperObjectForCurrentCompartment(JSContext* cx, js::HandleObject origObj, js::MutableHandleObject obj); bool getOrCreateWrapper(JSContext* cx, js::HandleObject existing, js::MutableHandleObject obj); public: explicit Compartment(JS::Zone* zone, bool invisibleToDebugger); void destroy(JS::GCContext* gcx); [[nodiscard]] inline bool wrap(JSContext* cx, JS::MutableHandleValue vp); [[nodiscard]] inline bool wrap(JSContext* cx, MutableHandle> vp); [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandleString strp); [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandle bi); [[nodiscard]] bool wrap(JSContext* cx, JS::MutableHandleObject obj); [[nodiscard]] bool wrap(JSContext* cx, JS::MutableHandle desc); [[nodiscard]] bool wrap( JSContext* cx, JS::MutableHandle> desc); [[nodiscard]] bool wrap(JSContext* cx, JS::MutableHandle> vec); #ifdef ENABLE_RECORD_TUPLE [[nodiscard]] bool wrapExtendedPrimitive(JSContext* cx, JS::MutableHandleObject obj); #endif [[nodiscard]] bool rewrap(JSContext* cx, JS::MutableHandleObject obj, JS::HandleObject existing); [[nodiscard]] bool putWrapper(JSContext* cx, JSObject* wrapped, JSObject* wrapper); [[nodiscard]] bool putWrapper(JSContext* cx, JSString* wrapped, JSString* wrapper); js::ObjectWrapperMap::Ptr lookupWrapper(JSObject* obj) const { return crossCompartmentObjectWrappers.lookup(obj); } inline js::StringWrapperMap::Ptr lookupWrapper(JSString* str) const; void removeWrapper(js::ObjectWrapperMap::Ptr p); bool hasNurseryAllocatedObjectWrapperEntries(const js::CompartmentFilter& f) { return crossCompartmentObjectWrappers.hasNurseryAllocatedWrapperEntries(f); } // Iterator over |wrapped -> wrapper| entries for object CCWs in a given // compartment. Can be optionally restricted by target compartment. struct ObjectWrapperEnum : public js::ObjectWrapperMap::Enum { explicit ObjectWrapperEnum(Compartment* c) : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers) {} explicit ObjectWrapperEnum(Compartment* c, const js::CompartmentFilter& f) : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers, f) {} explicit ObjectWrapperEnum(Compartment* c, Compartment* target) : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers, target) { MOZ_ASSERT(target); } }; // Iterator over compartments that this compartment has CCWs for. struct WrappedObjectCompartmentEnum : public js::ObjectWrapperMap::WrappedCompartmentEnum { explicit WrappedObjectCompartmentEnum(Compartment* c) : js::ObjectWrapperMap::WrappedCompartmentEnum( c->crossCompartmentObjectWrappers) {} }; /* * These methods mark pointers that cross compartment boundaries. They are * called in per-zone GCs to prevent the wrappers' outgoing edges from * dangling (full GCs naturally follow pointers across compartments) and * when compacting to update cross-compartment pointers. */ enum EdgeSelector { AllEdges, NonGrayEdges, GrayEdges }; void traceWrapperTargetsInCollectedZones(JSTracer* trc, EdgeSelector whichEdges); static void traceIncomingCrossCompartmentEdgesForZoneGC( JSTracer* trc, EdgeSelector whichEdges); void sweepRealms(JS::GCContext* gcx, bool keepAtleastOne, bool destroyingRuntime); void sweepAfterMinorGC(JSTracer* trc); void traceCrossCompartmentObjectWrapperEdges(JSTracer* trc); void fixupCrossCompartmentObjectWrappersAfterMovingGC(JSTracer* trc); void fixupAfterMovingGC(JSTracer* trc); [[nodiscard]] bool findSweepGroupEdges(); private: // Head node of list of active iterators that may need deleted property // suppression. js::NativeIteratorListHead enumerators_; public: js::NativeIteratorListHead* enumeratorsAddr() { return &enumerators_; } MOZ_ALWAYS_INLINE bool objectMaybeInIteration(JSObject* obj); void traceWeakNativeIterators(JSTracer* trc); }; namespace js { // We only set the hasMarkedCells flag for objects and scripts. It's assumed // that, if a compartment is alive, then it will have at least some live object // or script it in. Even if we get this wrong, the worst that will happen is // that scheduledForDestruction will be set on the compartment, which will cause // some extra GC activity to try to free the compartment. template inline void SetCompartmentHasMarkedCells(T* thing) {} template <> inline void SetCompartmentHasMarkedCells(JSObject* thing) { thing->compartment()->gcState.hasMarkedCells = true; } template <> inline void SetCompartmentHasMarkedCells(JSScript* thing) { thing->compartment()->gcState.hasMarkedCells = true; } /* * AutoWrapperVector and AutoWrapperRooter can be used to store wrappers that * are obtained from the cross-compartment map. However, these classes should * not be used if the wrapper will escape. For example, it should not be stored * in the heap. * * The AutoWrapper rooters are different from other autorooters because their * wrappers are marked on every GC slice rather than just the first one. If * there's some wrapper that we want to use temporarily without causing it to be * marked, we can use these AutoWrapper classes. If we get unlucky and a GC * slice runs during the code using the wrapper, the GC will mark the wrapper so * that it doesn't get swept out from under us. Otherwise, the wrapper needn't * be marked. This is useful in functions like JS_TransplantObject that * manipulate wrappers in compartments that may no longer be alive. */ /* * This class stores the data for AutoWrapperVector and AutoWrapperRooter. It * should not be used in any other situations. */ struct WrapperValue { /* * We use unsafeGet() in the constructors to avoid invoking a read barrier * on the wrapper, which may be dead (see the comment about bug 803376 in * gc/GC.cpp regarding this). If there is an incremental GC while the * wrapper is in use, the AutoWrapper rooter will ensure the wrapper gets * marked. */ explicit WrapperValue(const ObjectWrapperMap::Ptr& ptr) : value(*ptr->value().unsafeGet()) {} explicit WrapperValue(const ObjectWrapperMap::Enum& e) : value(*e.front().value().unsafeGet()) {} JSObject*& get() { return value; } JSObject* get() const { return value; } operator JSObject*() const { return value; } private: JSObject* value; }; class MOZ_RAII AutoWrapperVector : public JS::GCVector, public JS::AutoGCRooter { public: explicit AutoWrapperVector(JSContext* cx) : JS::GCVector(cx), JS::AutoGCRooter(cx, JS::AutoGCRooter::Kind::WrapperVector) {} void trace(JSTracer* trc); private: }; class MOZ_RAII AutoWrapperRooter : public JS::AutoGCRooter { public: AutoWrapperRooter(JSContext* cx, const WrapperValue& v) : JS::AutoGCRooter(cx, JS::AutoGCRooter::Kind::Wrapper), value(v) {} operator JSObject*() const { return value; } void trace(JSTracer* trc); private: WrapperValue value; }; } /* namespace js */ #endif /* vm_Compartment_h */