#include "vm/Compartment-inl.h"
#include "mozilla/MemoryReporting.h"
#include <stddef.h>
#include "jsfriendapi.h"
#include "gc/Policy.h"
#include "gc/PublicIterators.h"
#include "js/Date.h"
#include "js/Proxy.h"
#include "js/RootingAPI.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "proxy/DeadObjectProxy.h"
#include "vm/Debugger.h"
#include "vm/Iteration.h"
#include "vm/JSContext.h"
#include "vm/WrapperObject.h"
#include "gc/GC-inl.h"
#include "gc/Marking-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSFunction-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/UnboxedObject-inl.h"
using namespace js;
using JS::AutoStableStringChars;
Compartment::Compartment(Zone* zone, bool invisibleToDebugger)
: zone_(zone),
runtime_(zone->runtimeFromAnyThread()),
invisibleToDebugger_(invisibleToDebugger),
crossCompartmentWrappers(0) {}
#ifdef JSGC_HASH_TABLE_CHECKS
void Compartment::checkWrapperMapAfterMovingGC() {
for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
auto checkGCThing = [](auto tp) { CheckGCThingAfterMovingGC(*tp); };
e.front().mutableKey().applyToWrapped(checkGCThing);
e.front().mutableKey().applyToDebugger(checkGCThing);
WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key());
MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
}
}
#endif
bool Compartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
const js::Value& wrapper) {
MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
if (!crossCompartmentWrappers.put(wrapped, wrapper)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
static JSString* CopyStringPure(JSContext* cx, JSString* str) {
size_t len = str->length();
JSString* copy;
if (str->isLinear()) {
if (str->hasLatin1Chars()) {
JS::AutoCheckCannotGC nogc;
copy = NewStringCopyN<NoGC>(cx, str->asLinear().latin1Chars(nogc), len);
} else {
JS::AutoCheckCannotGC nogc;
copy = NewStringCopyNDontDeflate<NoGC>(
cx, str->asLinear().twoByteChars(nogc), len);
}
if (copy) {
return copy;
}
AutoStableStringChars chars(cx);
if (!chars.init(cx, str)) {
return nullptr;
}
return chars.isLatin1() ? NewStringCopyN<CanGC>(
cx, chars.latin1Range().begin().get(), len)
: NewStringCopyNDontDeflate<CanGC>(
cx, chars.twoByteRange().begin().get(), len);
}
if (str->hasLatin1Chars()) {
UniquePtr<Latin1Char[], JS::FreePolicy> copiedChars =
str->asRope().copyLatin1CharsZ(cx);
if (!copiedChars) {
return nullptr;
}
return NewString<CanGC>(cx, std::move(copiedChars), len);
}
UniqueTwoByteChars copiedChars = str->asRope().copyTwoByteCharsZ(cx);
if (!copiedChars) {
return nullptr;
}
return NewStringDontDeflate<CanGC>(cx, std::move(copiedChars), len);
}
bool Compartment::wrap(JSContext* cx, MutableHandleString strp) {
MOZ_ASSERT(cx->compartment() == this);
JSString* str = strp;
if (str->zoneFromAnyThread() == zone()) {
return true;
}
if (str->isAtom()) {
cx->markAtom(&str->asAtom());
return true;
}
RootedValue key(cx, StringValue(str));
if (WrapperMap::Ptr p =
crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
strp.set(p->value().get().toString());
return true;
}
JSString* copy = CopyStringPure(cx, str);
if (!copy) {
return false;
}
if (!putWrapper(cx, CrossCompartmentKey(key), StringValue(copy))) {
return false;
}
strp.set(copy);
return true;
}
bool Compartment::wrap(JSContext* cx, MutableHandleBigInt bi) {
MOZ_ASSERT(cx->compartment() == this);
if (bi->zone() == cx->zone()) {
return true;
}
BigInt* copy = BigInt::copy(cx, bi);
if (!copy) {
return false;
}
bi.set(copy);
return true;
}
bool Compartment::getNonWrapperObjectForCurrentCompartment(
JSContext* cx, MutableHandleObject obj) {
MOZ_ASSERT(cx->global());
MOZ_ASSERT(!cx->runtime()->isSelfHostingZone(cx->zone()));
MOZ_ASSERT(!cx->runtime()->isSelfHostingZone(obj->zone()));
if (obj->compartment() == this) {
obj.set(ToWindowProxyIfWindow(obj));
return true;
}
RootedObject objectPassedToWrap(cx, obj);
obj.set(UncheckedUnwrap(obj, true));
if (obj->compartment() == this) {
MOZ_ASSERT(!IsWindow(obj));
return true;
}
if (!AllowNewWrapper(this, obj)) {
JSObject* res = NewDeadProxyObject(cx, IsCallableFlag(obj->isCallable()),
IsConstructorFlag(obj->isConstructor()));
if (!res) {
return false;
}
obj.set(res);
return true;
}
auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap;
if (!CheckSystemRecursionLimit(cx)) {
return false;
}
if (preWrap) {
preWrap(cx, cx->global(), obj, objectPassedToWrap, obj);
if (!obj) {
return false;
}
}
MOZ_ASSERT(!IsWindow(obj));
return true;
}
bool Compartment::getOrCreateWrapper(JSContext* cx, HandleObject existing,
MutableHandleObject obj) {
RootedValue key(cx, ObjectValue(*obj));
if (WrapperMap::Ptr p =
crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
obj.set(&p->value().get().toObject());
MOZ_ASSERT(obj->is<CrossCompartmentWrapperObject>());
return true;
}
ExposeObjectToActiveJS(obj);
auto wrap = cx->runtime()->wrapObjectCallbacks->wrap;
RootedObject wrapper(cx, wrap(cx, existing, obj));
if (!wrapper) {
return false;
}
MOZ_ASSERT(Wrapper::wrappedObject(wrapper) == &key.get().toObject());
if (!putWrapper(cx, CrossCompartmentKey(key), ObjectValue(*wrapper))) {
if (wrapper->is<CrossCompartmentWrapperObject>()) {
NukeCrossCompartmentWrapper(cx, wrapper);
}
return false;
}
obj.set(wrapper);
return true;
}
bool Compartment::wrap(JSContext* cx, MutableHandleObject obj) {
MOZ_ASSERT(cx->compartment() == this);
if (!obj) {
return true;
}
AutoDisableProxyCheck adpc;
JS::AssertObjectIsNotGray(obj);
if (!getNonWrapperObjectForCurrentCompartment(cx, obj)) {
return false;
}
if (obj->compartment() != this) {
if (!getOrCreateWrapper(cx, nullptr, obj)) {
return false;
}
}
ExposeObjectToActiveJS(obj);
return true;
}
bool Compartment::rewrap(JSContext* cx, MutableHandleObject obj,
HandleObject existingArg) {
MOZ_ASSERT(cx->compartment() == this);
MOZ_ASSERT(obj);
MOZ_ASSERT(existingArg);
MOZ_ASSERT(existingArg->compartment() == cx->compartment());
MOZ_ASSERT(IsDeadProxyObject(existingArg));
AutoDisableProxyCheck adpc;
RootedObject existing(cx, existingArg);
if (existing->hasStaticPrototype() ||
existing->isCallable() || obj->isCallable()) {
existing.set(nullptr);
}
if (!getNonWrapperObjectForCurrentCompartment(cx, obj)) {
return false;
}
if (obj->compartment() == this) {
return true;
}
return getOrCreateWrapper(cx, existing, obj);
}
bool Compartment::wrap(JSContext* cx,
MutableHandle<JS::PropertyDescriptor> desc) {
if (!wrap(cx, desc.object())) {
return false;
}
if (desc.hasGetterObject()) {
if (!wrap(cx, desc.getterObject())) {
return false;
}
}
if (desc.hasSetterObject()) {
if (!wrap(cx, desc.setterObject())) {
return false;
}
}
return wrap(cx, desc.value());
}
bool Compartment::wrap(JSContext* cx, MutableHandle<GCVector<Value>> vec) {
for (size_t i = 0; i < vec.length(); ++i) {
if (!wrap(cx, vec[i])) {
return false;
}
}
return true;
}
void Compartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc) {
MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
MOZ_ASSERT(!zone()->isCollectingFromAnyThread() ||
trc->runtime()->gc.isHeapCompacting());
for (NonStringWrapperEnum e(this); !e.empty(); e.popFront()) {
if (e.front().key().is<JSObject*>()) {
Value v = e.front().value().unbarrieredGet();
ProxyObject* wrapper = &v.toObject().as<ProxyObject>();
ProxyObject::traceEdgeToTarget(trc, wrapper);
}
}
}
void Compartment::traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc) {
gcstats::AutoPhase ap(trc->runtime()->gc.stats(),
gcstats::PhaseKind::MARK_CCWS);
MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
for (CompartmentsIter c(trc->runtime()); !c.done(); c.next()) {
if (!c->zone()->isCollecting()) {
c->traceOutgoingCrossCompartmentWrappers(trc);
}
}
Debugger::traceIncomingCrossCompartmentEdges(trc);
}
void Compartment::sweepAfterMinorGC(JSTracer* trc) {
crossCompartmentWrappers.sweepAfterMinorGC(trc);
for (RealmsInCompartmentIter r(this); !r.done(); r.next()) {
r->sweepAfterMinorGC();
}
}
void Compartment::sweepCrossCompartmentWrappers() {
crossCompartmentWrappers.sweep();
}
void CrossCompartmentKey::trace(JSTracer* trc) {
applyToWrapped(
[trc](auto tp) { TraceRoot(trc, tp, "CrossCompartmentKey::wrapped"); });
applyToDebugger(
[trc](auto tp) { TraceRoot(trc, tp, "CrossCompartmentKey::debugger"); });
}
bool CrossCompartmentKey::needsSweep() {
auto needsSweep = [](auto tp) { return IsAboutToBeFinalizedUnbarriered(tp); };
return applyToWrapped(needsSweep) || applyToDebugger(needsSweep);
}
void Compartment::fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc) {
MOZ_ASSERT(trc->runtime()->gc.isHeapCompacting());
for (CompartmentsIter comp(trc->runtime()); !comp.done(); comp.next()) {
comp->sweepCrossCompartmentWrappers();
comp->traceOutgoingCrossCompartmentWrappers(trc);
}
}
void Compartment::fixupAfterMovingGC() {
MOZ_ASSERT(zone()->isGCCompacting());
for (RealmsInCompartmentIter r(this); !r.done(); r.next()) {
r->fixupAfterMovingGC();
}
sweepCrossCompartmentWrappers();
}
void Compartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
size_t* compartmentObjects,
size_t* crossCompartmentWrappersTables,
size_t* compartmentsPrivateData) {
*compartmentObjects += mallocSizeOf(this);
*crossCompartmentWrappersTables +=
crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
if (auto callback = runtime_->sizeOfIncludingThisCompartmentCallback) {
*compartmentsPrivateData += callback(mallocSizeOf, this);
}
}
GlobalObject& Compartment::firstGlobal() const {
for (Realm* realm : realms_) {
if (!realm->hasLiveGlobal()) {
continue;
}
GlobalObject* global = realm->maybeGlobal();
ExposeObjectToActiveJS(global);
return *global;
}
MOZ_CRASH("If all our globals are dead, why is someone expecting a global?");
}
JS_FRIEND_API JSObject* js::GetFirstGlobalInCompartment(JS::Compartment* comp) {
return &comp->firstGlobal();
}
JS_FRIEND_API bool js::CompartmentHasLiveGlobal(JS::Compartment* comp) {
MOZ_ASSERT(comp);
for (Realm* r : comp->realms()) {
if (r->hasLiveGlobal()) {
return true;
}
}
return false;
}