#ifndef vm_Compartment_h
#define vm_Compartment_h
#include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Tuple.h"
#include "mozilla/Variant.h"
#include <stddef.h>
#include "gc/Barrier.h"
#include "gc/NurseryAwareHashMap.h"
#include "js/UniquePtr.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
namespace js {
class CrossCompartmentKey {
public:
enum DebuggerObjectKind : uint8_t {
DebuggerSource,
DebuggerEnvironment,
DebuggerObject,
DebuggerWasmScript,
DebuggerWasmSource
};
using DebuggerAndObject =
mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>;
using DebuggerAndScript = mozilla::Tuple<NativeObject*, JSScript*>;
using DebuggerAndLazyScript = mozilla::Tuple<NativeObject*, LazyScript*>;
using WrappedType =
mozilla::Variant<JSObject*, JSString*, DebuggerAndScript,
DebuggerAndLazyScript, DebuggerAndObject>;
explicit CrossCompartmentKey(JSObject* obj) : wrapped(obj) {
MOZ_RELEASE_ASSERT(obj);
}
explicit CrossCompartmentKey(JSString* str) : wrapped(str) {
MOZ_RELEASE_ASSERT(str);
}
explicit CrossCompartmentKey(const JS::Value& v)
: wrapped(v.isString() ? WrappedType(v.toString())
: WrappedType(&v.toObject())) {}
explicit CrossCompartmentKey(NativeObject* debugger, JSObject* obj,
DebuggerObjectKind kind)
: wrapped(DebuggerAndObject(debugger, obj, kind)) {
MOZ_RELEASE_ASSERT(debugger);
MOZ_RELEASE_ASSERT(obj);
}
explicit CrossCompartmentKey(NativeObject* debugger, JSScript* script)
: wrapped(DebuggerAndScript(debugger, script)) {
MOZ_RELEASE_ASSERT(debugger);
MOZ_RELEASE_ASSERT(script);
}
explicit CrossCompartmentKey(NativeObject* debugger, LazyScript* lazyScript)
: wrapped(DebuggerAndLazyScript(debugger, lazyScript)) {
MOZ_RELEASE_ASSERT(debugger);
MOZ_RELEASE_ASSERT(lazyScript);
}
bool operator==(const CrossCompartmentKey& other) const {
return wrapped == other.wrapped;
}
bool operator!=(const CrossCompartmentKey& other) const {
return wrapped != other.wrapped;
}
template <typename T>
bool is() const {
return wrapped.is<T>();
}
template <typename T>
const T& as() const {
return wrapped.as<T>();
}
template <typename F>
auto applyToWrapped(F f) {
struct WrappedMatcher {
F f_;
explicit WrappedMatcher(F f) : f_(f) {}
auto match(JSObject*& obj) { return f_(&obj); }
auto match(JSString*& str) { return f_(&str); }
auto match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<1>(tpl)); }
auto match(DebuggerAndLazyScript& tpl) {
return f_(&mozilla::Get<1>(tpl));
}
auto match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<1>(tpl)); }
} matcher(f);
return wrapped.match(matcher);
}
template <typename F>
auto applyToDebugger(F f) {
using ReturnType = decltype(f(static_cast<NativeObject**>(nullptr)));
struct DebuggerMatcher {
F f_;
explicit DebuggerMatcher(F f) : f_(f) {}
ReturnType match(JSObject*& obj) { return ReturnType(); }
ReturnType match(JSString*& str) { return ReturnType(); }
ReturnType match(DebuggerAndScript& tpl) {
return f_(&mozilla::Get<0>(tpl));
}
ReturnType match(DebuggerAndLazyScript& tpl) {
return f_(&mozilla::Get<0>(tpl));
}
ReturnType match(DebuggerAndObject& tpl) {
return f_(&mozilla::Get<0>(tpl));
}
} matcher(f);
return wrapped.match(matcher);
}
JS::Compartment* compartment() {
return applyToWrapped([](auto tp) { return (*tp)->maybeCompartment(); });
}
struct Hasher : public DefaultHasher<CrossCompartmentKey> {
struct HashFunctor {
HashNumber match(JSObject* obj) {
return DefaultHasher<JSObject*>::hash(obj);
}
HashNumber match(JSString* str) {
return DefaultHasher<JSString*>::hash(str);
}
HashNumber match(const DebuggerAndScript& tpl) {
return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
DefaultHasher<JSScript*>::hash(mozilla::Get<1>(tpl));
}
HashNumber match(const DebuggerAndLazyScript& tpl) {
return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
DefaultHasher<LazyScript*>::hash(mozilla::Get<1>(tpl));
}
HashNumber match(const DebuggerAndObject& tpl) {
return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
DefaultHasher<JSObject*>::hash(mozilla::Get<1>(tpl)) ^
(mozilla::Get<2>(tpl) << 5);
}
};
static HashNumber hash(const CrossCompartmentKey& key) {
return key.wrapped.match(HashFunctor());
}
static bool match(const CrossCompartmentKey& l,
const CrossCompartmentKey& k) {
return l.wrapped == k.wrapped;
}
};
bool isTenured() const {
auto self = const_cast<CrossCompartmentKey*>(this);
return self->applyToWrapped([](auto tp) { return (*tp)->isTenured(); });
}
void trace(JSTracer* trc);
bool needsSweep();
private:
CrossCompartmentKey() = delete;
WrappedType wrapped;
};
class WrapperMap {
static const size_t InitialInnerMapSize = 4;
using InnerMap =
NurseryAwareHashMap<CrossCompartmentKey, JS::Value,
CrossCompartmentKey::Hasher, SystemAllocPolicy>;
using OuterMap =
GCHashMap<JS::Compartment*, InnerMap, DefaultHasher<JS::Compartment*>,
SystemAllocPolicy>;
OuterMap map;
public:
class Enum {
public:
enum SkipStrings : bool { WithStrings = false, WithoutStrings = true };
private:
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();
if (!c && skipStrings) {
continue;
}
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;
SkipStrings skipStrings;
public:
explicit Enum(WrapperMap& m, SkipStrings s = WithStrings)
: filter(nullptr), skipStrings(s) {
outer.emplace(m.map);
goToNext();
}
Enum(WrapperMap& m, const CompartmentFilter& f, SkipStrings s = WithStrings)
: filter(&f), skipStrings(s) {
outer.emplace(m.map);
goToNext();
}
Enum(WrapperMap& m, JS::Compartment* target) {
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 WrapperMap;
InnerMap* map;
Ptr() : InnerMap::Ptr(), map(nullptr) {}
Ptr(const InnerMap::Ptr& p, InnerMap& m) : InnerMap::Ptr(p), map(&m) {}
};
WrapperMap() {}
explicit WrapperMap(size_t aLen) : map(aLen) {}
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(const CrossCompartmentKey& k) const {
auto op = map.lookup(const_cast<CrossCompartmentKey&>(k).compartment());
if (op) {
auto ip = op->value().lookup(k);
if (ip) {
return Ptr(ip, op->value());
}
}
return Ptr();
}
void remove(Ptr p) {
if (p) {
p.map->remove(p);
}
}
MOZ_MUST_USE bool put(const CrossCompartmentKey& k, const JS::Value& v) {
JS::Compartment* c = const_cast<CrossCompartmentKey&>(k).compartment();
MOZ_ASSERT(k.is<JSString*>() == !c);
auto p = map.lookupForAdd(c);
if (!p) {
InnerMap m(InitialInnerMapSize);
if (!map.add(p, c, std::move(m))) {
return false;
}
}
return p->value().put(k, v);
}
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 sweep() {
for (OuterMap::Enum e(map); !e.empty(); e.popFront()) {
InnerMap& m = e.front().value();
m.sweep();
if (m.empty()) {
e.removeFront();
}
}
}
};
}
class JS::Compartment {
JS::Zone* zone_;
JSRuntime* runtime_;
bool invisibleToDebugger_;
js::WrapperMap crossCompartmentWrappers;
using RealmVector = js::Vector<JS::Realm*, 1, js::SystemAllocPolicy>;
RealmVector realms_;
public:
JSObject* gcIncomingGrayPointers = nullptr;
void* data = nullptr;
struct {
bool scheduledForDestruction = false;
bool maybeAlive = true;
bool hasEnteredRealm = false;
} gcState;
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_;
}
JSRuntime* runtimeFromAnyThread() const { return runtime_; }
bool invisibleToDebugger() const { return invisibleToDebugger_; }
RealmVector& realms() { return realms_; }
js::GlobalObject& firstGlobal() const;
js::GlobalObject& globalForNewCCW() const { return firstGlobal(); }
void assertNoCrossCompartmentWrappers() {
MOZ_ASSERT(crossCompartmentWrappers.empty());
}
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
size_t* compartmentObjects,
size_t* crossCompartmentWrappersTables,
size_t* compartmentsPrivateData);
#ifdef JSGC_HASH_TABLE_CHECKS
void checkWrapperMapAfterMovingGC();
#endif
private:
bool getNonWrapperObjectForCurrentCompartment(JSContext* cx,
js::MutableHandleObject obj);
bool getOrCreateWrapper(JSContext* cx, js::HandleObject existing,
js::MutableHandleObject obj);
public:
explicit Compartment(JS::Zone* zone, bool invisibleToDebugger);
void destroy(js::FreeOp* fop);
MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp);
MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp);
MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi);
MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj);
MOZ_MUST_USE bool wrap(JSContext* cx,
JS::MutableHandle<JS::PropertyDescriptor> desc);
MOZ_MUST_USE bool wrap(JSContext* cx,
JS::MutableHandle<JS::GCVector<JS::Value>> vec);
MOZ_MUST_USE bool rewrap(JSContext* cx, JS::MutableHandleObject obj,
JS::HandleObject existing);
MOZ_MUST_USE bool putWrapper(JSContext* cx,
const js::CrossCompartmentKey& wrapped,
const js::Value& wrapper);
js::WrapperMap::Ptr lookupWrapper(const js::Value& wrapped) const {
return crossCompartmentWrappers.lookup(js::CrossCompartmentKey(wrapped));
}
js::WrapperMap::Ptr lookupWrapper(JSObject* obj) const {
return crossCompartmentWrappers.lookup(js::CrossCompartmentKey(obj));
}
void removeWrapper(js::WrapperMap::Ptr p) {
crossCompartmentWrappers.remove(p);
}
bool hasNurseryAllocatedWrapperEntries(const js::CompartmentFilter& f) {
return crossCompartmentWrappers.hasNurseryAllocatedWrapperEntries(f);
}
struct WrapperEnum : public js::WrapperMap::Enum {
explicit WrapperEnum(JS::Compartment* c)
: js::WrapperMap::Enum(c->crossCompartmentWrappers) {}
};
struct NonStringWrapperEnum : public js::WrapperMap::Enum {
explicit NonStringWrapperEnum(JS::Compartment* c)
: js::WrapperMap::Enum(c->crossCompartmentWrappers, WithoutStrings) {}
explicit NonStringWrapperEnum(JS::Compartment* c,
const js::CompartmentFilter& f)
: js::WrapperMap::Enum(c->crossCompartmentWrappers, f, WithoutStrings) {
}
explicit NonStringWrapperEnum(JS::Compartment* c, JS::Compartment* target)
: js::WrapperMap::Enum(c->crossCompartmentWrappers, target) {
MOZ_ASSERT(target);
}
};
struct StringWrapperEnum : public js::WrapperMap::Enum {
explicit StringWrapperEnum(JS::Compartment* c)
: js::WrapperMap::Enum(c->crossCompartmentWrappers, nullptr) {}
};
void traceOutgoingCrossCompartmentWrappers(JSTracer* trc);
static void traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc);
void sweepRealms(js::FreeOp* fop, bool keepAtleastOne,
bool destroyingRuntime);
void sweepAfterMinorGC(JSTracer* trc);
void sweepCrossCompartmentWrappers();
static void fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc);
void fixupAfterMovingGC();
MOZ_MUST_USE bool findSweepGroupEdges();
};
namespace js {
template <typename T>
inline void SetMaybeAliveFlag(T* thing) {}
template <>
inline void SetMaybeAliveFlag(JSObject* thing) {
thing->compartment()->gcState.maybeAlive = true;
}
template <>
inline void SetMaybeAliveFlag(JSScript* thing) {
thing->compartment()->gcState.maybeAlive = true;
}
struct WrapperValue {
explicit WrapperValue(const WrapperMap::Ptr& ptr)
: value(*ptr->value().unsafeGet()) {}
explicit WrapperValue(const WrapperMap::Enum& e)
: value(*e.front().value().unsafeGet()) {}
Value& get() { return value; }
Value get() const { return value; }
operator const Value&() const { return value; }
JSObject& toObject() const { return value.toObject(); }
private:
Value value;
};
class MOZ_RAII AutoWrapperVector : public JS::GCVector<WrapperValue, 8>,
private JS::AutoGCRooter {
public:
explicit AutoWrapperVector(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::GCVector<WrapperValue, 8>(cx),
JS::AutoGCRooter(cx, JS::AutoGCRooter::Tag::WrapperVector) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
friend void AutoGCRooter::trace(JSTracer* trc);
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
class MOZ_RAII AutoWrapperRooter : private JS::AutoGCRooter {
public:
AutoWrapperRooter(JSContext* cx,
const WrapperValue& v MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::AutoGCRooter(cx, JS::AutoGCRooter::Tag::Wrapper), value(v) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
operator JSObject*() const { return value.get().toObjectOrNull(); }
friend void JS::AutoGCRooter::trace(JSTracer* trc);
private:
WrapperValue value;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
}
namespace JS {
template <>
struct GCPolicy<js::CrossCompartmentKey>
: public StructGCPolicy<js::CrossCompartmentKey> {
static bool isTenured(const js::CrossCompartmentKey& key) {
return key.isTenured();
}
};
}
#endif