author | Terrence Cole <terrence@mozilla.com> |
Mon, 14 Dec 2015 13:28:26 -0800 | |
changeset 298166 | 0fcaad4111c43ef3bbab08f66f5c3511a2af67a5 |
parent 298165 | ca34a07172f67508bd75964c0b617e8a344d8397 |
child 298167 | 2b227a22287677ac7af098166a632e768e70d022 |
push id | 77052 |
push user | tcole@mozilla.com |
push date | Thu, 19 May 2016 17:59:13 +0000 |
treeherder | mozilla-inbound@0fcaad4111c4 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jonco |
bugs | 1232417 |
milestone | 49.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -121,62 +121,48 @@ struct TraceChildrenFunctor { void js::TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind) { MOZ_ASSERT(thing); TraceChildrenFunctor f; DispatchTraceKindTyped(f, kind, trc, thing); } +namespace { +struct TraceIncomingFunctor { + JSTracer* trc_; + const JS::CompartmentSet& compartments_; + TraceIncomingFunctor(JSTracer* trc, const JS::CompartmentSet& compartments) + : trc_(trc), compartments_(compartments) + {} + using ReturnType = void; + template <typename T> + ReturnType operator()(T tp) { + if (!compartments_.has((*tp)->compartment())) + return; + TraceManuallyBarrieredEdge(trc_, tp, "cross-compartment wrapper"); + } + // StringWrappers are just used to avoid copying strings + // across zones multiple times, and don't hold a strong + // reference. + ReturnType operator()(JSString** tp) {} +}; +} // namespace (anonymous) + JS_PUBLIC_API(void) JS::TraceIncomingCCWs(JSTracer* trc, const JS::CompartmentSet& compartments) { for (js::CompartmentsIter comp(trc->runtime(), SkipAtoms); !comp.done(); comp.next()) { if (compartments.has(comp)) continue; for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) { - const CrossCompartmentKey& key = e.front().key(); - JSObject* obj; - JSScript* script; - - switch (key.kind) { - case CrossCompartmentKey::StringWrapper: - // StringWrappers are just used to avoid copying strings - // across zones multiple times, and don't hold a strong - // reference. - continue; - - case CrossCompartmentKey::ObjectWrapper: - case CrossCompartmentKey::DebuggerObject: - case CrossCompartmentKey::DebuggerSource: - case CrossCompartmentKey::DebuggerEnvironment: - case CrossCompartmentKey::DebuggerWasmScript: - case CrossCompartmentKey::DebuggerWasmSource: - obj = static_cast<JSObject*>(key.wrapped); - // Ignore CCWs whose wrapped value doesn't live in our given - // set of zones. - if (!compartments.has(obj->compartment())) - continue; - - TraceManuallyBarrieredEdge(trc, &obj, "cross-compartment wrapper"); - MOZ_ASSERT(obj == key.wrapped); - break; - - case CrossCompartmentKey::DebuggerScript: - script = static_cast<JSScript*>(key.wrapped); - // Ignore CCWs whose wrapped value doesn't live in our given - // set of compartments. - if (!compartments.has(script->compartment())) - continue; - - TraceManuallyBarrieredEdge(trc, &script, "cross-compartment wrapper"); - MOZ_ASSERT(script == key.wrapped); - break; - } + mozilla::DebugOnly<const CrossCompartmentKey> prior = e.front().key(); + e.front().mutableKey().applyToWrapped(TraceIncomingFunctor(trc, compartments)); + MOZ_ASSERT(e.front().key() == prior); } } } /*** Cycle Collector Helpers **********************************************************************/ // This function is used by the Cycle Collector (CC) to trace through -- or in
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -214,83 +214,92 @@ class WrapperMapRef : public BufferableR { WrapperMap* map; CrossCompartmentKey key; public: WrapperMapRef(WrapperMap* map, const CrossCompartmentKey& key) : map(map), key(key) {} + struct TraceFunctor { + JSTracer* trc_; + const char* name_; + TraceFunctor(JSTracer *trc, const char* name) : trc_(trc), name_(name) {} + + using ReturnType = void; + template <class T> void operator()(T* t) { TraceManuallyBarrieredEdge(trc_, t, name_); } + }; void trace(JSTracer* trc) override { CrossCompartmentKey prior = key; - if (key.debugger) - TraceManuallyBarrieredEdge(trc, &key.debugger, "CCW debugger"); - if (key.kind == CrossCompartmentKey::ObjectWrapper || - key.kind == CrossCompartmentKey::DebuggerObject || - key.kind == CrossCompartmentKey::DebuggerEnvironment || - key.kind == CrossCompartmentKey::DebuggerSource || - key.kind == CrossCompartmentKey::DebuggerWasmScript || - key.kind == CrossCompartmentKey::DebuggerWasmSource) - { - MOZ_ASSERT(IsInsideNursery(key.wrapped) || - key.wrapped->asTenured().getTraceKind() == JS::TraceKind::Object); - TraceManuallyBarrieredEdge(trc, reinterpret_cast<JSObject**>(&key.wrapped), - "CCW wrapped object"); - } - if (key.debugger == prior.debugger && key.wrapped == prior.wrapped) + key.applyToWrapped(TraceFunctor(trc, "ccw wrapped")); + key.applyToDebugger(TraceFunctor(trc, "ccw debugger")); + if (key == prior) return; /* Look for the original entry, which might have been removed. */ WrapperMap::Ptr p = map->lookup(prior); if (!p) return; /* Rekey the entry. */ map->rekeyAs(prior, key, key); } }; #ifdef JSGC_HASH_TABLE_CHECKS +namespace { +struct CheckGCThingAfterMovingGCFunctor { + using ReturnType = void; + template <class T> void operator()(T* t) { CheckGCThingAfterMovingGC(*t); } +}; +} // namespace (anonymous) + void JSCompartment::checkWrapperMapAfterMovingGC() { /* * Assert that the postbarriers have worked and that nothing is left in * wrapperMap that points into the nursery, and that the hash table entries * are discoverable. */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { - CrossCompartmentKey key = e.front().key(); - CheckGCThingAfterMovingGC(key.debugger); - CheckGCThingAfterMovingGC(key.wrapped); - CheckGCThingAfterMovingGC( - static_cast<Cell*>(e.front().value().unbarrieredGet().toGCThing())); + e.front().mutableKey().applyToWrapped(CheckGCThingAfterMovingGCFunctor()); + e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor()); - WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(key); + WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key()); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); } } #endif +namespace { +struct IsInsideNurseryFunctor { + using ReturnType = bool; + template <class T> bool operator()(T tp) { return IsInsideNursery(*tp); } +}; +} // namespace (anonymous) + bool -JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, const js::Value& wrapper) +JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, + const js::Value& wrapper) { - MOZ_ASSERT(wrapped.wrapped); - MOZ_ASSERT_IF(wrapped.kind == CrossCompartmentKey::StringWrapper, wrapper.isString()); - MOZ_ASSERT_IF(wrapped.kind != CrossCompartmentKey::StringWrapper, wrapper.isObject()); + MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString()); + MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject()); /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */ MOZ_ASSERT(!IsInsideNursery(static_cast<gc::Cell*>(wrapper.toGCThing()))); if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) { ReportOutOfMemory(cx); return false; } - if (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger)) { + if (const_cast<CrossCompartmentKey&>(wrapped).applyToWrapped(IsInsideNurseryFunctor()) || + const_cast<CrossCompartmentKey&>(wrapped).applyToDebugger(IsInsideNurseryFunctor())) + { WrapperMapRef ref(&crossCompartmentWrappers, wrapped); cx->runtime()->gc.storeBuffer.putGeneric(ref); } return true; } static JSString* @@ -560,17 +569,17 @@ JSCompartment::getNonSyntacticLexicalSco void JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc) { MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting()); MOZ_ASSERT(!zone()->isCollecting() || trc->runtime()->gc.isHeapCompacting()); for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { Value v = e.front().value().unbarrieredGet(); - if (e.front().key().kind == CrossCompartmentKey::ObjectWrapper) { + if (e.front().key().is<JSObject*>()) { ProxyObject* wrapper = &v.toObject().as<ProxyObject>(); /* * We have a cross-compartment wrapper. Its private pointer may * point into the compartment being collected, so we should mark it. */ TraceEdge(trc, wrapper->slotOfPrivate(), "cross-compartment wrapper"); } @@ -753,46 +762,42 @@ JSCompartment::sweepNativeIterators() * markCrossCompartmentWrappers. */ void JSCompartment::sweepCrossCompartmentWrappers() { crossCompartmentWrappers.sweep(); } +namespace { +struct TraceRootFunctor { + JSTracer* trc; + const char* name; + TraceRootFunctor(JSTracer* trc, const char* name) : trc(trc), name(name) {} + using ReturnType = void; + template <class T> ReturnType operator()(T* t) { return TraceRoot(trc, t, name); } +}; +struct NeedsSweepUnbarrieredFunctor { + using ReturnType = bool; + template <class T> bool operator()(T* t) const { return IsAboutToBeFinalizedUnbarriered(t); } +}; +} // namespace (anonymous) + +void +CrossCompartmentKey::trace(JSTracer* trc) +{ + applyToWrapped(TraceRootFunctor(trc, "CrossCompartmentKey::wrapped")); + applyToDebugger(TraceRootFunctor(trc, "CrossCompartmentKey::debugger")); +} + bool CrossCompartmentKey::needsSweep() { - bool keyDying; - switch (kind) { - case CrossCompartmentKey::ObjectWrapper: - case CrossCompartmentKey::DebuggerObject: - case CrossCompartmentKey::DebuggerEnvironment: - case CrossCompartmentKey::DebuggerSource: - case CrossCompartmentKey::DebuggerWasmScript: - case CrossCompartmentKey::DebuggerWasmSource: - MOZ_ASSERT(IsInsideNursery(wrapped) || - wrapped->asTenured().getTraceKind() == JS::TraceKind::Object); - keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast<JSObject**>(&wrapped)); - break; - case CrossCompartmentKey::StringWrapper: - MOZ_ASSERT(wrapped->asTenured().getTraceKind() == JS::TraceKind::String); - keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast<JSString**>(&wrapped)); - break; - case CrossCompartmentKey::DebuggerScript: - MOZ_ASSERT(wrapped->asTenured().getTraceKind() == JS::TraceKind::Script); - keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast<JSScript**>(&wrapped)); - break; - default: - MOZ_CRASH("Unknown key kind"); - } - - bool dbgDying = debugger && IsAboutToBeFinalizedUnbarriered(&debugger); - MOZ_ASSERT_IF(keyDying || dbgDying, kind != CrossCompartmentKey::StringWrapper); - return keyDying || dbgDying; + return applyToWrapped(NeedsSweepUnbarrieredFunctor()) || + applyToDebugger(NeedsSweepUnbarrieredFunctor()); } void JSCompartment::sweepTemplateObjects() { if (mappedArgumentsTemplate_ && IsAboutToBeFinalized(&mappedArgumentsTemplate_)) mappedArgumentsTemplate_.set(nullptr);
--- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef jscompartment_h #define jscompartment_h #include "mozilla/LinkedList.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/Tuple.h" #include "mozilla/Variant.h" #include "mozilla/XorShift128PlusRNG.h" #include "builtin/RegExp.h" #include "gc/Barrier.h" #include "gc/Zone.h" #include "vm/GlobalObject.h" #include "vm/PIC.h" @@ -30,18 +31,20 @@ class JitCompartment; namespace gc { template<class Node> class ComponentFinder; } // namespace gc namespace wasm { class Module; } // namespace wasm +class ClonedBlockObject; +class ScriptSourceObject; +class WasmModuleObject; struct NativeIterator; -class ClonedBlockObject; /* * A single-entry cache for some base-10 double-to-string conversions. This * helps date-format-xparb.js. It also avoids skewing the results for * v8-splay.js when measured by the SunSpider harness, where the splay tree * initialization (which includes many repeated double-to-string conversions) * is erroneously included in the measurement; see bug 562553. */ @@ -66,85 +69,127 @@ class DtoaCache { #ifdef JSGC_HASH_TABLE_CHECKS void checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); } #endif }; struct CrossCompartmentKey { - enum Kind { - ObjectWrapper, - StringWrapper, - DebuggerScript, - DebuggerSource, - DebuggerObject, - DebuggerEnvironment, - DebuggerWasmScript, - DebuggerWasmSource - }; + public: + enum DebuggerObjectKind : uint8_t { DebuggerSource, DebuggerEnvironment, DebuggerObject, + DebuggerWasmScript, DebuggerWasmSource }; + using DebuggerAndObject = mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>; + using DebuggerAndScript = mozilla::Tuple<NativeObject*, JSScript*>; + using WrappedType = mozilla::Variant< + JSObject*, + JSString*, + DebuggerAndScript, + DebuggerAndObject>; - Kind kind; - JSObject* debugger; - js::gc::Cell* wrapped; - - explicit CrossCompartmentKey(JSObject* wrapped) - : kind(ObjectWrapper), debugger(nullptr), wrapped(wrapped) - { - MOZ_RELEASE_ASSERT(wrapped); - } - explicit CrossCompartmentKey(JSString* wrapped) - : kind(StringWrapper), debugger(nullptr), wrapped(wrapped) + explicit CrossCompartmentKey(JSObject* obj) : wrapped(obj) { MOZ_RELEASE_ASSERT(obj); } + explicit CrossCompartmentKey(JSString* str) : wrapped(str) { MOZ_RELEASE_ASSERT(str); } + explicit CrossCompartmentKey(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(wrapped); + MOZ_RELEASE_ASSERT(debugger); + MOZ_RELEASE_ASSERT(obj); } - explicit CrossCompartmentKey(Value wrappedArg) - : kind(wrappedArg.isString() ? StringWrapper : ObjectWrapper), - debugger(nullptr), - wrapped((js::gc::Cell*)wrappedArg.toGCThing()) + explicit CrossCompartmentKey(NativeObject* debugger, JSScript* script) + : wrapped(DebuggerAndScript(debugger, script)) { - MOZ_RELEASE_ASSERT(wrappedArg.isString() || wrappedArg.isObject()); - MOZ_RELEASE_ASSERT(wrapped); + MOZ_RELEASE_ASSERT(debugger); + MOZ_RELEASE_ASSERT(script); } - explicit CrossCompartmentKey(const RootedValue& wrappedArg) - : kind(wrappedArg.get().isString() ? StringWrapper : ObjectWrapper), - debugger(nullptr), - wrapped((js::gc::Cell*)wrappedArg.get().toGCThing()) - { - MOZ_RELEASE_ASSERT(wrappedArg.isString() || wrappedArg.isObject()); - MOZ_RELEASE_ASSERT(wrapped); - } - CrossCompartmentKey(Kind kind, JSObject* dbg, js::gc::Cell* wrapped) - : kind(kind), debugger(dbg), wrapped(wrapped) - { - MOZ_RELEASE_ASSERT(dbg); - MOZ_RELEASE_ASSERT(wrapped); + + 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) -> typename F::ReturnType { + struct WrappedMatcher { + using ReturnType = typename F::ReturnType; + F f_; + explicit WrappedMatcher(F f) : f_(f) {} + ReturnType match(JSObject*& obj) { return f_(&obj); } + ReturnType match(JSString*& str) { return f_(&str); } + ReturnType match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<1>(tpl)); } + ReturnType match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<1>(tpl)); } + } matcher(f); + return wrapped.match(matcher); } + template <typename F> + auto applyToDebugger(F f) -> typename F::ReturnType { + struct DebuggerMatcher { + using ReturnType = typename F::ReturnType; + 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(DebuggerAndObject& tpl) { return f_(&mozilla::Get<0>(tpl)); } + } matcher(f); + return wrapped.match(matcher); + } + + // Valid for JSObject* and Debugger keys. Crashes immediately if used on a + // JSString* key. + JSCompartment* compartment() { + struct GetCompartmentFunctor { + using ReturnType = JSCompartment*; + ReturnType operator()(JSObject** tp) const { return (*tp)->compartment(); } + ReturnType operator()(JSScript** tp) const { return (*tp)->compartment(); } + ReturnType operator()(JSString** tp) const { + MOZ_CRASH("invalid ccw key"); return nullptr; + } + }; + return applyToWrapped(GetCompartmentFunctor()); + } + + struct Hasher : public DefaultHasher<CrossCompartmentKey> + { + struct HashFunctor { + using ReturnType = HashNumber; + ReturnType match(JSObject* obj) { return DefaultHasher<JSObject*>::hash(obj); } + ReturnType match(JSString* str) { return DefaultHasher<JSString*>::hash(str); } + ReturnType match(const DebuggerAndScript& tpl) { + return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^ + DefaultHasher<JSScript*>::hash(mozilla::Get<1>(tpl)); + } + ReturnType 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; + } + }; + void trace(JSTracer* trc); bool needsSweep(); private: CrossCompartmentKey() = delete; -}; - -struct WrapperHasher : public DefaultHasher<CrossCompartmentKey> -{ - static HashNumber hash(const CrossCompartmentKey& key) { - static_assert(sizeof(HashNumber) == sizeof(uint32_t), - "subsequent code assumes a four-byte hash"); - return uint32_t(uintptr_t(key.wrapped)) | uint32_t(key.kind); - } - - static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) { - return l.kind == k.kind && l.debugger == k.debugger && l.wrapped == k.wrapped; - } + WrappedType wrapped; }; using WrapperMap = GCRekeyableHashMap<CrossCompartmentKey, ReadBarrieredValue, - WrapperHasher, SystemAllocPolicy>; + CrossCompartmentKey::Hasher, SystemAllocPolicy>; // We must ensure that all newly allocated JSObjects get their metadata // set. However, metadata builders may require the new object be in a sane // state (eg, have its reserved slots initialized so they can get the // sizeOfExcludingThis of the object). Therefore, for objects of certain // JSClasses (those marked with JSCLASS_DELAY_METADATA_BUILDER), it is not safe // for the allocation paths to call the object metadata builder // immediately. Instead, the JSClass-specific "constructor" C++ function up the
--- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -592,25 +592,39 @@ js::ZoneGlobalsAreAllGray(JS::Zone* zone for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) { JSObject* obj = comp->maybeGlobal(); if (!obj || !JS::ObjectIsMarkedGray(obj)) return false; } return true; } +namespace { +struct VisitGrayCallbackFunctor { + GCThingCallback callback_; + void* closure_; + VisitGrayCallbackFunctor(GCThingCallback callback, void* closure) + : callback_(callback), closure_(closure) + {} + + using ReturnType = void; + template <class T> + ReturnType operator()(T tp) const { + if ((*tp)->isTenured() && (*tp)->asTenured().isMarked(gc::GRAY)) + callback_(closure_, JS::GCCellPtr(*tp)); + } +}; +} // namespace (anonymous) + JS_FRIEND_API(void) js::VisitGrayWrapperTargets(Zone* zone, GCThingCallback callback, void* closure) { for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) { - for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) { - gc::Cell* thing = e.front().key().wrapped; - if (thing->isTenured() && thing->asTenured().isMarked(gc::GRAY)) - callback(closure, JS::GCCellPtr(thing, thing->asTenured().getTraceKind())); - } + for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) + e.front().mutableKey().applyToWrapped(VisitGrayCallbackFunctor(callback, closure)); } } JS_FRIEND_API(JSObject*) js::GetWeakmapKeyDelegate(JSObject* key) { if (JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp()) return op(key);
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -234,16 +234,17 @@ #include "vm/Stack-inl.h" #include "vm/String-inl.h" using namespace js; using namespace js::gc; using mozilla::ArrayLength; +using mozilla::Get; using mozilla::Maybe; using mozilla::Swap; using JS::AutoGCRooter; /* Perform a Full GC every 20 seconds if MaybeGC is called */ static const uint64_t GC_IDLE_FULL_SPAN = 20 * 1000 * 1000; @@ -3902,56 +3903,68 @@ class CompartmentCheckTracer : public JS {} Cell* src; JS::TraceKind srcKind; Zone* zone; JSCompartment* compartment; }; +namespace { +struct IsDestComparatorFunctor { + JS::GCCellPtr dst_; + IsDestComparatorFunctor(JS::GCCellPtr dst) : dst_(dst) {} + + using ReturnType = bool; + template <typename T> bool operator()(T* t) { return (*t) == dst_.asCell(); } +}; +} // namespace (anonymous) + static bool -InCrossCompartmentMap(JSObject* src, Cell* dst, JS::TraceKind dstKind) +InCrossCompartmentMap(JSObject* src, JS::GCCellPtr dst) { JSCompartment* srccomp = src->compartment(); - if (dstKind == JS::TraceKind::Object) { - Value key = ObjectValue(*static_cast<JSObject*>(dst)); + if (dst.is<JSObject>()) { + Value key = ObjectValue(dst.as<JSObject>()); if (WrapperMap::Ptr p = srccomp->lookupWrapper(key)) { if (*p->value().unsafeGet() == ObjectValue(*src)) return true; } } /* * If the cross-compartment edge is caused by the debugger, then we don't * know the right hashtable key, so we have to iterate. */ for (JSCompartment::WrapperEnum e(srccomp); !e.empty(); e.popFront()) { - if (e.front().key().wrapped == dst && ToMarkable(e.front().value()) == src) + if (e.front().mutableKey().applyToWrapped(IsDestComparatorFunctor(dst)) && + ToMarkable(e.front().value()) == src) + { return true; + } } return false; } struct MaybeCompartmentFunctor { template <typename T> JSCompartment* operator()(T* t) { return t->maybeCompartment(); } }; void CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing) { - TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell()); - JSCompartment* comp = DispatchTyped(MaybeCompartmentFunctor(), thing); if (comp && compartment) { MOZ_ASSERT(comp == compartment || runtime()->isAtomsCompartment(comp) || (srcKind == JS::TraceKind::Object && - InCrossCompartmentMap(static_cast<JSObject*>(src), tenured, thing.kind()))); + InCrossCompartmentMap(static_cast<JSObject*>(src), thing))); } else { + TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell()); MOZ_ASSERT(tenured->zone() == zone || tenured->zone()->isAtomsZone()); } } void GCRuntime::checkForCompartmentMismatches() { if (disableStrictProxyCheckingCount) @@ -4176,44 +4189,30 @@ GCRuntime::markCompartments() * compartments, live or dead, and operates on their objects. See bug 803376 * for details on this problem. To avoid the problem, we try to avoid * allocation and read barriers during JS_TransplantObject and the like. */ /* Set the maybeAlive flag based on cross-compartment edges. */ for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - const CrossCompartmentKey& key = e.front().key(); - JSCompartment* dest; - switch (key.kind) { - case CrossCompartmentKey::ObjectWrapper: - case CrossCompartmentKey::DebuggerObject: - case CrossCompartmentKey::DebuggerSource: - case CrossCompartmentKey::DebuggerEnvironment: - case CrossCompartmentKey::DebuggerWasmScript: - case CrossCompartmentKey::DebuggerWasmSource: - dest = static_cast<JSObject*>(key.wrapped)->compartment(); - break; - case CrossCompartmentKey::DebuggerScript: - dest = static_cast<JSScript*>(key.wrapped)->compartment(); - break; - default: - dest = nullptr; - break; - } + if (e.front().key().is<JSString*>()) + continue; + JSCompartment* dest = e.front().mutableKey().compartment(); if (dest) dest->maybeAlive = true; } } /* * For black roots, code in gc/Marking.cpp will already have set maybeAlive * during MarkRuntime. */ + /* Propogate maybeAlive to scheduleForDestruction. */ for (GCCompartmentsIter c(rt); !c.done(); c.next()) { if (!c->maybeAlive && !rt->isAtomsCompartment(c)) c->scheduledForDestruction = true; } } template <class ZoneIterT> void @@ -4585,17 +4584,17 @@ DropStringWrappers(JSRuntime* rt) { /* * String "wrappers" are dropped on GC because their presence would require * us to sweep the wrappers in all compartments every time we sweep a * compartment group. */ for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - if (e.front().key().kind == CrossCompartmentKey::StringWrapper) + if (e.front().key().is<JSString*>()) e.removeFront(); } } } /* * Group zones that must be swept at the same time. * @@ -4605,51 +4604,56 @@ DropStringWrappers(JSRuntime* rt) * marked. However, if we had already swept that object, we would be in trouble. * * If we consider these dependencies as a graph, then all the compartments in * any strongly-connected component of this graph must be swept in the same * slice. * * Tarjan's algorithm is used to calculate the components. */ +namespace { +struct AddOutgoingEdgeFunctor { + bool needsEdge_; + ComponentFinder<JS::Zone>& finder_; + + AddOutgoingEdgeFunctor(bool needsEdge, ComponentFinder<JS::Zone>& finder) + : needsEdge_(needsEdge), finder_(finder) + {} + + using ReturnType = void; + template <typename T> + ReturnType operator()(T tp) { + TenuredCell& other = (*tp)->asTenured(); + + /* + * Add edge to wrapped object compartment if wrapped object is not + * marked black to indicate that wrapper compartment not be swept + * after wrapped compartment. + */ + if (needsEdge_) { + JS::Zone* zone = other.zone(); + if (zone->isGCMarking()) + finder_.addEdgeTo(zone); + } + } +}; +} // namespace (anonymous) void JSCompartment::findOutgoingEdges(ComponentFinder<JS::Zone>& finder) { for (js::WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { - CrossCompartmentKey::Kind kind = e.front().key().kind; - MOZ_ASSERT(kind != CrossCompartmentKey::StringWrapper); - TenuredCell& other = e.front().key().wrapped->asTenured(); - if (kind == CrossCompartmentKey::ObjectWrapper) { - /* - * Add edge to wrapped object compartment if wrapped object is not - * marked black to indicate that wrapper compartment not be swept - * after wrapped compartment. - */ - if (!other.isMarked(BLACK) || other.isMarked(GRAY)) { - JS::Zone* w = other.zone(); - if (w->isGCMarking()) - finder.addEdgeTo(w); - } - } else { - MOZ_ASSERT(kind == CrossCompartmentKey::DebuggerScript || - kind == CrossCompartmentKey::DebuggerSource || - kind == CrossCompartmentKey::DebuggerObject || - kind == CrossCompartmentKey::DebuggerEnvironment || - kind == CrossCompartmentKey::DebuggerWasmScript || - kind == CrossCompartmentKey::DebuggerWasmSource); - /* - * Add edge for debugger object wrappers, to ensure (in conjuction - * with call to Debugger::findCompartmentEdges below) that debugger - * and debuggee objects are always swept in the same group. - */ - JS::Zone* w = other.zone(); - if (w->isGCMarking()) - finder.addEdgeTo(w); + CrossCompartmentKey& key = e.front().mutableKey(); + MOZ_ASSERT(!key.is<JSString*>()); + bool needsEdge = true; + if (key.is<JSObject*>()) { + TenuredCell& other = key.as<JSObject*>()->asTenured(); + needsEdge = !other.isMarked(BLACK) || other.isMarked(GRAY); } + key.applyToWrapped(AddOutgoingEdgeFunctor(needsEdge, finder)); } } void Zone::findOutgoingEdges(ComponentFinder<JS::Zone>& finder) { /* * Any compartment may have a pointer to an atom in the atoms @@ -4798,16 +4802,30 @@ ProxyObject::grayLinkExtraSlot(JSObject* static void AssertNotOnGrayList(JSObject* obj) { MOZ_ASSERT_IF(IsGrayListObject(obj), GetProxyExtra(obj, ProxyObject::grayLinkExtraSlot(obj)).isUndefined()); } #endif +static void +AssertNoWrappersInGrayList(JSRuntime* rt) +{ +#ifdef DEBUG + for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { + MOZ_ASSERT(!c->gcIncomingGrayPointers); + for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { + if (!e.front().key().is<JSString*>()) + AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject()); + } + } +#endif +} + static JSObject* CrossCompartmentPointerReferent(JSObject* obj) { MOZ_ASSERT(IsGrayListObject(obj)); return &obj->as<ProxyObject>().private_().toObject(); } static JSObject* @@ -5402,26 +5420,17 @@ GCRuntime::beginSweepPhase(bool destroyi gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP); sweepOnBackgroundThread = !destroyingRuntime && !TraceEnabled() && CanUseExtraThreads(); releaseObservedTypes = shouldReleaseObservedTypes(); -#ifdef DEBUG - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - MOZ_ASSERT(!c->gcIncomingGrayPointers); - for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - if (e.front().key().kind != CrossCompartmentKey::StringWrapper) - AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject()); - } - } -#endif - + AssertNoWrappersInGrayList(rt); DropStringWrappers(rt); findZoneGroups(); endMarkingZoneGroup(); beginSweepingZoneGroup(); } bool @@ -5691,26 +5700,19 @@ GCRuntime::endSweepPhase(bool destroying #ifdef DEBUG for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { for (auto i : AllAllocKinds()) { MOZ_ASSERT_IF(!IsBackgroundFinalized(i) || !sweepOnBackgroundThread, !zone->arenas.arenaListsToSweep[i]); } } - - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - MOZ_ASSERT(!c->gcIncomingGrayPointers); - - for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - if (e.front().key().kind != CrossCompartmentKey::StringWrapper) - AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject()); - } - } #endif + + AssertNoWrappersInGrayList(rt); } void GCRuntime::beginCompactPhase() { MOZ_ASSERT(!isBackgroundSweeping()); gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
--- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -482,17 +482,17 @@ js::NukeCrossCompartmentWrappers(JSConte if (!sourceFilter.match(c)) continue; // Iterate the wrappers looking for anything interesting. for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { // Some cross-compartment wrappers are for strings. We're not // interested in those. const CrossCompartmentKey& k = e.front().key(); - if (k.kind != CrossCompartmentKey::ObjectWrapper) + if (!k.is<JSObject*>()) continue; AutoWrapperRooter wobj(cx, WrapperValue(e)); JSObject* wrapped = UncheckedUnwrap(wobj); if (nukeReferencesToWindow == DontNukeWindowReferences && IsWindowProxy(wrapped)) { @@ -611,22 +611,22 @@ js::RecomputeWrappers(JSContext* cx, con for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) { // Filter by source compartment. if (!sourceFilter.match(c)) continue; // Iterate over the wrappers, filtering appropriately. for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { // Filter out non-objects. - const CrossCompartmentKey& k = e.front().key(); - if (k.kind != CrossCompartmentKey::ObjectWrapper) + CrossCompartmentKey& k = e.front().mutableKey(); + if (!k.is<JSObject*>()) continue; // Filter by target compartment. - if (!targetFilter.match(static_cast<JSObject*>(k.wrapped)->compartment())) + if (!targetFilter.match(k.compartment())) continue; // Add it to the list. if (!toRecompute.append(WrapperValue(e))) return false; } }
--- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -943,17 +943,17 @@ Debugger::wrapEnvironment(JSContext* cx, envobj->setPrivateGCThing(env); envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object)); if (!p.add(cx, environments, env, envobj)) { NukeDebuggerWrapper(envobj); return false; } - CrossCompartmentKey key(CrossCompartmentKey::DebuggerEnvironment, object, env); + CrossCompartmentKey key(object, env, CrossCompartmentKey::DebuggerEnvironment); if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) { NukeDebuggerWrapper(envobj); environments.remove(env); return false; } } rval.setObject(*envobj); @@ -987,17 +987,17 @@ Debugger::wrapDebuggeeValue(JSContext* c return false; if (!p.add(cx, objects, obj, dobj)) { NukeDebuggerWrapper(dobj); return false; } if (obj->compartment() != object->compartment()) { - CrossCompartmentKey key(CrossCompartmentKey::DebuggerObject, object, obj); + CrossCompartmentKey key(object, obj, CrossCompartmentKey::DebuggerObject); if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) { NukeDebuggerWrapper(dobj); objects.remove(obj); ReportOutOfMemory(cx); return false; } } @@ -5058,17 +5058,17 @@ Debugger::newDebuggerScript(JSContext* c DebuggerScriptSetPrivateMatcher matcher(scriptobj); referent.match(matcher); return scriptobj; } template <typename ReferentVariant, typename Referent, typename Map> JSObject* -Debugger::wrapVariantReferent(JSContext* cx, Map& map, CrossCompartmentKey::Kind keyKind, +Debugger::wrapVariantReferent(JSContext* cx, Map& map, Handle<CrossCompartmentKey> key, Handle<ReferentVariant> referent) { assertSameCompartment(cx, object); Handle<Referent> untaggedReferent = referent.template as<Referent>(); MOZ_ASSERT(cx->compartment() != untaggedReferent->compartment()); DependentAddPtr<Map> p(cx, map, untaggedReferent); @@ -5077,17 +5077,16 @@ Debugger::wrapVariantReferent(JSContext* if (!wrapper) return nullptr; if (!p.add(cx, map, untaggedReferent, wrapper)) { NukeDebuggerWrapper(wrapper); return nullptr; } - CrossCompartmentKey key(keyKind, object, untaggedReferent); if (!object->compartment()->putWrapper(cx, key, ObjectValue(*wrapper))) { NukeDebuggerWrapper(wrapper); map.remove(untaggedReferent); ReportOutOfMemory(cx); return nullptr; } } @@ -5095,21 +5094,26 @@ Debugger::wrapVariantReferent(JSContext* return p->value(); } JSObject* Debugger::wrapVariantReferent(JSContext* cx, Handle<DebuggerScriptReferent> referent) { JSObject* obj; if (referent.is<JSScript*>()) { + Handle<JSScript*> untaggedReferent = referent.template as<JSScript*>(); + Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent)); obj = wrapVariantReferent<DebuggerScriptReferent, JSScript*, ScriptWeakMap>( - cx, scripts, CrossCompartmentKey::DebuggerScript, referent); + cx, scripts, key, referent); } else { + Handle<WasmModuleObject*> untaggedReferent = referent.template as<WasmModuleObject*>(); + Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent, + CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmScript)); obj = wrapVariantReferent<DebuggerScriptReferent, WasmModuleObject*, WasmModuleWeakMap>( - cx, wasmModuleScripts, CrossCompartmentKey::DebuggerWasmScript, referent); + cx, wasmModuleScripts, key, referent); } MOZ_ASSERT_IF(obj, GetScriptReferent(obj) == referent); return obj; } JSObject* Debugger::wrapScript(JSContext* cx, HandleScript script) { @@ -6405,21 +6409,27 @@ Debugger::newDebuggerSource(JSContext* c return sourceobj; } JSObject* Debugger::wrapVariantReferent(JSContext* cx, Handle<DebuggerSourceReferent> referent) { JSObject* obj; if (referent.is<ScriptSourceObject*>()) { + Handle<ScriptSourceObject*> untaggedReferent = referent.template as<ScriptSourceObject*>(); + Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent, + CrossCompartmentKey::DebuggerObjectKind::DebuggerSource)); obj = wrapVariantReferent<DebuggerSourceReferent, ScriptSourceObject*, SourceWeakMap>( - cx, sources, CrossCompartmentKey::DebuggerSource, referent); + cx, sources, key, referent); } else { + Handle<WasmModuleObject*> untaggedReferent = referent.template as<WasmModuleObject*>(); + Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent, + CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmSource)); obj = wrapVariantReferent<DebuggerSourceReferent, WasmModuleObject*, WasmModuleWeakMap>( - cx, wasmModuleSources, CrossCompartmentKey::DebuggerWasmSource, referent); + cx, wasmModuleSources, key, referent); } MOZ_ASSERT_IF(obj, GetSourceReferent(obj) == referent); return obj; } JSObject* Debugger::wrapSource(JSContext* cx, HandleScriptSource source) {
--- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -701,17 +701,17 @@ class Debugger : private mozilla::Linked * Helper function to help wrap Debugger objects whose referents may be * variants. Currently Debugger.Script and Debugger.Source referents may * be variants. * * Prefer using wrapScript, wrapWasmScript, wrapSource, and wrapWasmSource * whenever possible. */ template <typename ReferentVariant, typename Referent, typename Map> - JSObject* wrapVariantReferent(JSContext* cx, Map& map, CrossCompartmentKey::Kind keyKind, + JSObject* wrapVariantReferent(JSContext* cx, Map& map, Handle<CrossCompartmentKey> key, Handle<ReferentVariant> referent); JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerScriptReferent> referent); JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerSourceReferent> referent); /* * Allocate and initialize a Debugger.Script instance whose referent is * |referent|. */