Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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() : 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) {
return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
}
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, BlackEdges };
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 */