summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Compartment.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/vm/Compartment.h
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/src/vm/Compartment.h537
1 files changed, 537 insertions, 0 deletions
diff --git a/js/src/vm/Compartment.h b/js/src/vm/Compartment.h
new file mode 100644
index 0000000000..beb883e87e
--- /dev/null
+++ b/js/src/vm/Compartment.h
@@ -0,0 +1,537 @@
+/* -*- 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 <stddef.h>
+#include <utility>
+
+#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<JSObject*, JSObject*, ZoneAllocPolicy>;
+ using OuterMap = GCHashMap<JS::Compartment*, InnerMap,
+ DefaultHasher<JS::Compartment*>, 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<OuterMap::Enum> outer;
+ mozilla::Maybe<InnerMap::Enum> 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<JSString*, JSString*, ZoneAllocPolicy,
+ DuplicatesPossible>;
+
+} // namespace js
+
+class JS::Compartment {
+ JS::Zone* zone_;
+ JSRuntime* runtime_;
+ bool invisibleToDebugger_;
+
+ js::ObjectWrapperMap crossCompartmentObjectWrappers;
+
+ using RealmVector = js::Vector<JS::Realm*, 1, js::ZoneAllocPolicy>;
+ 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<mozilla::Maybe<Value>> vp);
+
+ [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandleString strp);
+ [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi);
+ [[nodiscard]] bool wrap(JSContext* cx, JS::MutableHandleObject obj);
+ [[nodiscard]] bool wrap(JSContext* cx,
+ JS::MutableHandle<JS::PropertyDescriptor> desc);
+ [[nodiscard]] bool wrap(
+ JSContext* cx,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
+ [[nodiscard]] bool wrap(JSContext* cx,
+ JS::MutableHandle<JS::GCVector<JS::Value>> 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 <typename T>
+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<WrapperValue, 8>,
+ public JS::AutoGCRooter {
+ public:
+ explicit AutoWrapperVector(JSContext* cx)
+ : JS::GCVector<WrapperValue, 8>(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 */