Bug 1214961 - Sweep XPConnect incrementally; r=mccr8, r=jonco

This patch splits up the "wrapped-js" table by compartment in order to allow us
to visit the miminal number of wrapped-js in each GC sweeping slice, instead of
visiting the entire table repeatedly. This dramatically reduces our sweeping
overhead, reducing the number of GC slices, and making them more likely to fall
within budget.
This commit is contained in:
Terrence Cole 2015-10-15 13:43:28 -07:00
parent 330afb2752
commit 774fb050ef
13 changed files with 311 additions and 89 deletions

View File

@ -21,7 +21,7 @@ using namespace mozilla::jsipc;
using mozilla::AutoSafeJSContext;
static void
UpdateChildWeakPointersAfterGC(JSRuntime* rt, void* data)
UpdateChildWeakPointersBeforeSweepingZoneGroup(JSRuntime* rt, void* data)
{
static_cast<JavaScriptChild*>(data)->updateWeakPointers();
}
@ -34,7 +34,7 @@ JavaScriptChild::JavaScriptChild(JSRuntime* rt)
JavaScriptChild::~JavaScriptChild()
{
JS_RemoveWeakPointerCallback(rt_, UpdateChildWeakPointersAfterGC);
JS_RemoveWeakPointerZoneGroupCallback(rt_, UpdateChildWeakPointersBeforeSweepingZoneGroup);
}
bool
@ -45,7 +45,7 @@ JavaScriptChild::init()
if (!WrapperAnswer::init())
return false;
JS_AddWeakPointerCallback(rt_, UpdateChildWeakPointersAfterGC, this);
JS_AddWeakPointerZoneGroupCallback(rt_, UpdateChildWeakPointersBeforeSweepingZoneGroup, this);
return true;
}

View File

@ -757,8 +757,10 @@ class GCRuntime
void setGCCallback(JSGCCallback callback, void* data);
bool addFinalizeCallback(JSFinalizeCallback callback, void* data);
void removeFinalizeCallback(JSFinalizeCallback func);
bool addWeakPointerCallback(JSWeakPointerCallback callback, void* data);
void removeWeakPointerCallback(JSWeakPointerCallback func);
bool addWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback, void* data);
void removeWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback);
bool addWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback, void* data);
void removeWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback);
JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback);
void setValidate(bool enable);
@ -969,7 +971,8 @@ class GCRuntime
#endif
void callFinalizeCallbacks(FreeOp* fop, JSFinalizeStatus status) const;
void callWeakPointerCallbacks() const;
void callWeakPointerZoneGroupCallbacks() const;
void callWeakPointerCompartmentCallbacks(JSCompartment* comp) const;
public:
JSRuntime* rt;
@ -1269,7 +1272,8 @@ class GCRuntime
Callback<JSGCCallback> gcCallback;
CallbackVector<JSFinalizeCallback> finalizeCallbacks;
CallbackVector<JSWeakPointerCallback> updateWeakPointerCallbacks;
CallbackVector<JSWeakPointerZoneGroupCallback> updateWeakPointerZoneGroupCallbacks;
CallbackVector<JSWeakPointerCompartmentCallback> updateWeakPointerCompartmentCallbacks;
/*
* Malloc counter to measure memory pressure for GC scheduling. It runs

View File

@ -138,7 +138,9 @@ static const PhaseInfo phases[] = {
{ PHASE_SWEEP_MARK_INCOMING_GRAY, "Mark Incoming Gray Pointers", PHASE_SWEEP_MARK, 14 },
{ PHASE_SWEEP_MARK_GRAY, "Mark Gray", PHASE_SWEEP_MARK, 15 },
{ PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak", PHASE_SWEEP_MARK, 16 },
{ PHASE_FINALIZE_START, "Finalize Start Callback", PHASE_SWEEP, 17 },
{ PHASE_FINALIZE_START, "Finalize Start Callbacks", PHASE_SWEEP, 17 },
{ PHASE_WEAK_ZONEGROUP_CALLBACK, "Per-Slice Weak Callback", PHASE_FINALIZE_START, 56 },
{ PHASE_WEAK_COMPARTMENT_CALLBACK, "Per-Compartment Weak Callback", PHASE_FINALIZE_START, 57 },
{ PHASE_SWEEP_ATOMS, "Sweep Atoms", PHASE_SWEEP, 18 },
{ PHASE_SWEEP_SYMBOL_REGISTRY, "Sweep Symbol Registry", PHASE_SWEEP, 19 },
{ PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments", PHASE_SWEEP, 20 },

View File

@ -44,6 +44,8 @@ enum Phase : uint8_t {
PHASE_SWEEP_MARK_GRAY,
PHASE_SWEEP_MARK_GRAY_WEAK,
PHASE_FINALIZE_START,
PHASE_WEAK_ZONEGROUP_CALLBACK,
PHASE_WEAK_COMPARTMENT_CALLBACK,
PHASE_SWEEP_ATOMS,
PHASE_SWEEP_SYMBOL_REGISTRY,
PHASE_SWEEP_COMPARTMENTS,

View File

@ -1539,18 +1539,33 @@ JS_RemoveFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb)
}
JS_PUBLIC_API(bool)
JS_AddWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb, void* data)
JS_AddWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb, void* data)
{
AssertHeapIsIdle(rt);
return rt->gc.addWeakPointerCallback(cb, data);
return rt->gc.addWeakPointerZoneGroupCallback(cb, data);
}
JS_PUBLIC_API(void)
JS_RemoveWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb)
JS_RemoveWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb)
{
rt->gc.removeWeakPointerCallback(cb);
rt->gc.removeWeakPointerZoneGroupCallback(cb);
}
JS_PUBLIC_API(bool)
JS_AddWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb,
void* data)
{
AssertHeapIsIdle(rt);
return rt->gc.addWeakPointerCompartmentCallback(cb, data);
}
JS_PUBLIC_API(void)
JS_RemoveWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb)
{
rt->gc.removeWeakPointerCompartmentCallback(cb);
}
JS_PUBLIC_API(void)
JS_UpdateWeakPointerAfterGC(JS::Heap<JSObject*>* objp)
{

View File

@ -601,7 +601,10 @@ typedef void
(* JSFinalizeCallback)(JSFreeOp* fop, JSFinalizeStatus status, bool isCompartment, void* data);
typedef void
(* JSWeakPointerCallback)(JSRuntime* rt, void* data);
(* JSWeakPointerZoneGroupCallback)(JSRuntime* rt, void* data);
typedef void
(* JSWeakPointerCompartmentCallback)(JSRuntime* rt, JSCompartment* comp, void* data);
typedef bool
(* JSInterruptCallback)(JSContext* cx);
@ -1721,11 +1724,20 @@ JS_RemoveFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb);
*
* To handle this, any part of the system that maintain weak pointers to
* JavaScript GC things must register a callback with
* JS_(Add,Remove)WeakPointerCallback(). This callback must then call
* JS_UpdateWeakPointerAfterGC() on all weak pointers it knows about.
* JS_(Add,Remove)WeakPointer{ZoneGroup,Compartment}Callback(). This callback
* must then call JS_UpdateWeakPointerAfterGC() on all weak pointers it knows
* about.
*
* The argument to JS_UpdateWeakPointerAfterGC() is an in-out param. If the
* referent is about to be finalized the pointer will be set to null. If the
* Since sweeping is incremental, we have several callbacks to avoid repeatedly
* having to visit all embedder structures. The WeakPointerZoneGroupCallback is
* called once for each strongly connected group of zones, whereas the
* WeakPointerCompartmentCallback is called once for each compartment that is
* visited while sweeping. Structures that cannot contain references in more
* than one compartment should sweep the relevant per-compartment structures
* using the latter callback to minimizer per-slice overhead.
*
* The argument to JS_UpdateWeakPointerAfterGC() is an in-out param. If the
* referent is about to be finalized the pointer will be set to null. If the
* referent has been moved then the pointer will be updated to point to the new
* location.
*
@ -1736,10 +1748,17 @@ JS_RemoveFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb);
*/
extern JS_PUBLIC_API(bool)
JS_AddWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb, void* data);
JS_AddWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb, void* data);
extern JS_PUBLIC_API(void)
JS_RemoveWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb);
JS_RemoveWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb);
extern JS_PUBLIC_API(bool)
JS_AddWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb,
void* data);
extern JS_PUBLIC_API(void)
JS_RemoveWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb);
extern JS_PUBLIC_API(void)
JS_UpdateWeakPointerAfterGC(JS::Heap<JSObject*>* objp);

View File

@ -1619,31 +1619,54 @@ GCRuntime::callFinalizeCallbacks(FreeOp* fop, JSFinalizeStatus status) const
}
bool
GCRuntime::addWeakPointerCallback(JSWeakPointerCallback callback, void* data)
GCRuntime::addWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback, void* data)
{
return updateWeakPointerCallbacks.append(Callback<JSWeakPointerCallback>(callback, data));
return updateWeakPointerZoneGroupCallbacks.append(
Callback<JSWeakPointerZoneGroupCallback>(callback, data));
}
void
GCRuntime::removeWeakPointerCallback(JSWeakPointerCallback callback)
GCRuntime::removeWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback)
{
for (Callback<JSWeakPointerCallback>* p = updateWeakPointerCallbacks.begin();
p < updateWeakPointerCallbacks.end(); p++)
{
if (p->op == callback) {
updateWeakPointerCallbacks.erase(p);
for (auto& p : updateWeakPointerZoneGroupCallbacks) {
if (p.op == callback) {
updateWeakPointerZoneGroupCallbacks.erase(&p);
break;
}
}
}
void
GCRuntime::callWeakPointerCallbacks() const
GCRuntime::callWeakPointerZoneGroupCallbacks() const
{
for (const Callback<JSWeakPointerCallback>* p = updateWeakPointerCallbacks.begin();
p < updateWeakPointerCallbacks.end(); p++)
{
p->op(rt, p->data);
for (auto const& p : updateWeakPointerZoneGroupCallbacks) {
p.op(rt, p.data);
}
}
bool
GCRuntime::addWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback, void* data)
{
return updateWeakPointerCompartmentCallbacks.append(
Callback<JSWeakPointerCompartmentCallback>(callback, data));
}
void
GCRuntime::removeWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback)
{
for (auto& p : updateWeakPointerCompartmentCallbacks) {
if (p.op == callback) {
updateWeakPointerCompartmentCallbacks.erase(&p);
break;
}
}
}
void
GCRuntime::callWeakPointerCompartmentCallbacks(JSCompartment* comp) const
{
for (auto const& p : updateWeakPointerCompartmentCallbacks) {
p.op(rt, comp, p.data);
}
}
@ -2665,7 +2688,9 @@ GCRuntime::updatePointersToRelocatedCells(Zone* zone)
rt->nativeIterCache.purge();
// Call callbacks to get the rest of the system to fixup other untraced pointers.
callWeakPointerCallbacks();
callWeakPointerZoneGroupCallbacks();
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
callWeakPointerCompartmentCallbacks(comp);
// Finally, iterate through all cells that can contain JSObject pointers to
// update them. Since updating each cell is independent we try to
@ -5020,7 +5045,17 @@ GCRuntime::beginSweepingZoneGroup()
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
callWeakPointerCallbacks();
{
gcstats::AutoPhase ap2(stats, gcstats::PHASE_WEAK_ZONEGROUP_CALLBACK);
callWeakPointerZoneGroupCallbacks();
}
{
gcstats::AutoPhase ap2(stats, gcstats::PHASE_WEAK_COMPARTMENT_CALLBACK);
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
callWeakPointerCompartmentCallbacks(comp);
}
}
}
if (sweepingAtoms) {

View File

@ -188,9 +188,27 @@ public:
namespace xpc {
CompartmentPrivate::CompartmentPrivate(JSCompartment* c)
: wantXrays(false)
, allowWaivers(true)
, writeToGlobalPrototype(false)
, skipWriteToGlobalPrototype(false)
, isWebExtensionContentScript(false)
, universalXPConnectEnabled(false)
, forcePermissiveCOWs(false)
, scriptability(c)
, scope(nullptr)
, mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH))
{
MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
mozilla::PodArrayZero(wrapperDenialWarnings);
}
CompartmentPrivate::~CompartmentPrivate()
{
MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
mWrappedJSMap->ShutdownMarker();
delete mWrappedJSMap;
}
static bool
@ -919,18 +937,36 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp* fop,
}
/* static */ void
XPCJSRuntime::WeakPointerCallback(JSRuntime* rt, void* data)
XPCJSRuntime::WeakPointerZoneGroupCallback(JSRuntime* rt, void* data)
{
// Called to remove any weak pointers to GC things that are about to be
// finalized and fixup any pointers that may have been moved.
// Called before each sweeping slice -- after processing any final marking
// triggered by barriers -- to clear out any references to things that are
// about to be finalized and update any pointers to moved GC things.
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
MOZ_ASSERT(self->WrappedJSToReleaseArray().IsEmpty());
self->mWrappedJSMap->UpdateWeakPointersAfterGC(self);
XPCWrappedNativeScope::UpdateWeakPointersAfterGC(self);
}
/* static */ void
XPCJSRuntime::WeakPointerCompartmentCallback(JSRuntime* rt, JSCompartment* comp, void* data)
{
// Called immediately after the ZoneGroup weak pointer callback, but only
// once for each compartment that is being swept.
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp);
if (xpcComp)
xpcComp->UpdateWeakPointersAfterGC(self);
}
void
CompartmentPrivate::UpdateWeakPointersAfterGC(XPCJSRuntime* runtime)
{
mWrappedJSMap->UpdateWeakPointersAfterGC(runtime);
}
static void WatchdogMain(void* arg);
class Watchdog;
class WatchdogManager;
@ -1479,6 +1515,12 @@ XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf)
return n;
}
size_t
CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf)
{
return mallocSizeOf(this) + mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf);
}
/***************************************************************************/
void XPCJSRuntime::DestroyJSContextStack()
@ -1560,7 +1602,8 @@ XPCJSRuntime::~XPCJSRuntime()
// callback if we aren't careful. Null out the relevant callbacks.
js::SetActivityCallback(Runtime(), nullptr, nullptr);
JS_RemoveFinalizeCallback(Runtime(), FinalizeCallback);
JS_RemoveWeakPointerCallback(Runtime(), WeakPointerCallback);
JS_RemoveWeakPointerZoneGroupCallback(Runtime(), WeakPointerZoneGroupCallback);
JS_RemoveWeakPointerCompartmentCallback(Runtime(), WeakPointerCompartmentCallback);
// Clear any pending exception. It might be an XPCWrappedJS, and if we try
// to destroy it later we will crash.
@ -2252,6 +2295,11 @@ ReportCompartmentStats(const JS::CompartmentStats& cStats,
"Orphan DOM nodes, i.e. those that are only reachable from JavaScript "
"objects.");
ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("private-data"),
extras.sizeOfXPCPrivate,
"Extra data attached to the compartment by XPConnect, including "\
"wrapped-js that is local to a single compartment.");
ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/gc-heap"),
cStats.scriptsGCHeap,
"JSScript instances. There is one per user-defined function in a "
@ -2758,14 +2806,16 @@ class XPCJSRuntimeStats : public JS::RuntimeStats
xpc::CompartmentStatsExtras* extras = new xpc::CompartmentStatsExtras;
nsCString cName;
GetCompartmentName(c, cName, &mAnonymizeID, /* replaceSlashes = */ true);
if (mGetLocations) {
CompartmentPrivate* cp = CompartmentPrivate::Get(c);
if (cp)
cp->GetLocationURI(CompartmentPrivate::LocationHintAddon,
getter_AddRefs(extras->location));
CompartmentPrivate* cp = CompartmentPrivate::Get(c);
if (cp) {
if (mGetLocations) {
cp->GetLocationURI(CompartmentPrivate::LocationHintAddon,
getter_AddRefs(extras->location));
}
// Note: cannot use amIAddonManager implementation at this point,
// as it is a JS service and the JS heap is currently not idle.
// Otherwise, we could have computed the add-on id at this point.
extras->sizeOfXPCPrivate = cp->SizeOfIncludingThis(mallocSizeOf_);
}
// Get the compartment's global.
@ -2856,7 +2906,7 @@ JSReporter::CollectReports(WindowPaths* windowPaths,
size_t xpcJSRuntimeSize = xpcrt->SizeOfIncludingThis(JSMallocSizeOf);
size_t wrappedJSSize = xpcrt->GetWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf);
size_t wrappedJSSize = xpcrt->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf);
XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf);
XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(&sizeInfo);
@ -3398,7 +3448,8 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
JS_SetCompartmentNameCallback(runtime, CompartmentNameCallback);
mPrevGCSliceCallback = JS::SetGCSliceCallback(runtime, GCSliceCallback);
JS_AddFinalizeCallback(runtime, FinalizeCallback, nullptr);
JS_AddWeakPointerCallback(runtime, WeakPointerCallback, this);
JS_AddWeakPointerZoneGroupCallback(runtime, WeakPointerZoneGroupCallback, this);
JS_AddWeakPointerCompartmentCallback(runtime, WeakPointerCompartmentCallback, this);
JS_SetWrapObjectCallbacks(runtime, &WrapObjectCallbacks);
js::SetPreserveWrapperCallback(runtime, PreserveWrapper);
#ifdef MOZ_ENABLE_PROFILER_SPS
@ -3459,7 +3510,7 @@ XPCJSRuntime::newXPCJSRuntime(nsXPConnect* aXPConnect)
if (self &&
self->Runtime() &&
self->GetWrappedJSMap() &&
self->GetMultiCompartmentWrappedJSMap() &&
self->GetWrappedJSClassMap() &&
self->GetIID2NativeInterfaceMap() &&
self->GetClassInfo2NativeSetMap() &&
@ -3651,9 +3702,10 @@ XPCJSRuntime::DebugDump(int16_t depth)
}
XPC_LOG_OUTDENT();
}
// iterate wrappers...
XPC_LOG_ALWAYS(("mWrappedJSMap @ %x with %d wrappers(s)",
mWrappedJSMap, mWrappedJSMap->Count()));
// iterate wrappers...
if (depth && mWrappedJSMap->Count()) {
XPC_LOG_INDENT();
mWrappedJSMap->Dump(depth);

View File

@ -95,7 +95,6 @@ JSObject2WrappedJSMap::UpdateWeakPointersAfterGC(XPCJSRuntime* runtime)
// the posibility of doing any JS GCThing allocations during the gc cycle.
nsTArray<nsXPCWrappedJS*>& dying = runtime->WrappedJSToReleaseArray();
MOZ_ASSERT(dying.IsEmpty());
for (Map::Enum e(mTable); !e.empty(); e.popFront()) {
nsXPCWrappedJS* wrapper = e.front().value();

View File

@ -47,6 +47,16 @@ public:
return p ? p->value() : nullptr;
}
#ifdef DEBUG
inline bool HasWrapper(nsXPCWrappedJS* wrapper) {
for (auto r = mTable.all(); !r.empty(); r.popFront()) {
if (r.front().value() == wrapper)
return true;
}
return false;
}
#endif
inline nsXPCWrappedJS* Add(JSContext* cx, nsXPCWrappedJS* wrapper) {
NS_PRECONDITION(wrapper,"bad param");
JSObject* obj = wrapper->GetJSObjectPreserveColor();

View File

@ -323,16 +323,10 @@ nsXPCWrappedJS::GetNewOrUsed(JS::HandleObject jsObj,
MOZ_CRASH();
AutoJSContext cx;
XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
if (!map) {
MOZ_ASSERT(map,"bad map");
return NS_ERROR_FAILURE;
}
bool allowNonScriptable = mozilla::jsipc::IsWrappedCPOW(jsObj);
RefPtr<nsXPCWrappedJSClass> clasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, aIID,
allowNonScriptable);
allowNonScriptable);
if (!clasp)
return NS_ERROR_FAILURE;
@ -340,8 +334,19 @@ nsXPCWrappedJS::GetNewOrUsed(JS::HandleObject jsObj,
if (!rootJSObj)
return NS_ERROR_FAILURE;
xpc::CompartmentPrivate* rootComp = xpc::CompartmentPrivate::Get(rootJSObj);
MOZ_ASSERT(rootComp);
// Find any existing wrapper.
RefPtr<nsXPCWrappedJS> root = rootComp->GetWrappedJSMap()->Find(rootJSObj);
MOZ_ASSERT_IF(root, !nsXPConnect::GetRuntimeInstance()->GetMultiCompartmentWrappedJSMap()->
Find(rootJSObj));
if (!root) {
root = nsXPConnect::GetRuntimeInstance()->GetMultiCompartmentWrappedJSMap()->
Find(rootJSObj);
}
nsresult rv = NS_ERROR_FAILURE;
RefPtr<nsXPCWrappedJS> root = map->Find(rootJSObj);
if (root) {
RefPtr<nsXPCWrappedJS> wrapper = root->FindOrFindInherited(aIID);
if (wrapper) {
@ -353,7 +358,8 @@ nsXPCWrappedJS::GetNewOrUsed(JS::HandleObject jsObj,
// Make a new root wrapper, because there is no existing
// root wrapper, and the wrapper we are trying to make isn't
// a root.
RefPtr<nsXPCWrappedJSClass> rootClasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, NS_GET_IID(nsISupports));
RefPtr<nsXPCWrappedJSClass> rootClasp =
nsXPCWrappedJSClass::GetNewOrUsed(cx, NS_GET_IID(nsISupports));
if (!rootClasp)
return NS_ERROR_FAILURE;
@ -391,11 +397,21 @@ nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx,
NS_ADDREF_THIS();
if (IsRootWrapper()) {
nsXPConnect::GetRuntimeInstance()->GetWrappedJSMap()->Add(cx, this);
MOZ_ASSERT(!IsMultiCompartment(), "mNext is always nullptr here");
xpc::CompartmentPrivate::Get(mJSObj)->GetWrappedJSMap()->Add(cx, this);
} else {
NS_ADDREF(mRoot);
mNext = mRoot->mNext;
mRoot->mNext = this;
// We always start wrappers in the per-compartment table. If adding
// this wrapper to the chain causes it to cross compartments, we need
// to migrate the chain to the global table on the XPCJSRuntime.
if (mRoot->IsMultiCompartment()) {
xpc::CompartmentPrivate::Get(mRoot->mJSObj)->GetWrappedJSMap()->Remove(mRoot);
MOZ_RELEASE_ASSERT(nsXPConnect::GetRuntimeInstance()->
GetMultiCompartmentWrappedJSMap()->Add(cx, mRoot));
}
}
}
@ -404,31 +420,71 @@ nsXPCWrappedJS::~nsXPCWrappedJS()
Destroy();
}
void
XPCJSRuntime::RemoveWrappedJS(nsXPCWrappedJS* wrapper)
{
AssertInvalidWrappedJSNotInTable(wrapper);
if (!wrapper->IsValid())
return;
// It is possible for the same JS XPCOM implementation object to be wrapped
// with a different interface in multiple JSCompartments. In this case, the
// wrapper chain will contain references to multiple compartments. While we
// always store single-compartment chains in the per-compartment wrapped-js
// table, chains in the multi-compartment wrapped-js table may contain
// single-compartment chains, if they have ever contained a wrapper in a
// different compartment. Since removal requires a lookup anyway, we just do
// the remove on both tables unconditionally.
MOZ_ASSERT_IF(wrapper->IsMultiCompartment(),
!xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())->
GetWrappedJSMap()->HasWrapper(wrapper));
GetMultiCompartmentWrappedJSMap()->Remove(wrapper);
xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())->GetWrappedJSMap()->
Remove(wrapper);
}
#ifdef DEBUG
static void
NotHasWrapperAssertionCallback(JSRuntime* rt, void* data, JSCompartment* comp)
{
auto wrapper = static_cast<nsXPCWrappedJS*>(data);
auto xpcComp = xpc::CompartmentPrivate::Get(comp);
MOZ_ASSERT_IF(xpcComp, !xpcComp->GetWrappedJSMap()->HasWrapper(wrapper));
}
#endif
void
XPCJSRuntime::AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const
{
#ifdef DEBUG
if (!wrapper->IsValid()) {
MOZ_ASSERT(!GetMultiCompartmentWrappedJSMap()->HasWrapper(wrapper));
if (!mGCIsRunning)
JS_IterateCompartments(Runtime(), wrapper, NotHasWrapperAssertionCallback);
}
#endif
}
void
nsXPCWrappedJS::Destroy()
{
MOZ_ASSERT(1 == int32_t(mRefCnt), "should be stabilized for deletion");
if (IsRootWrapper()) {
XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
if (map)
map->Remove(this);
}
if (IsRootWrapper())
nsXPConnect::GetRuntimeInstance()->RemoveWrappedJS(this);
Unlink();
}
void
nsXPCWrappedJS::Unlink()
{
nsXPConnect::GetRuntimeInstance()->AssertInvalidWrappedJSNotInTable(this);
if (IsValid()) {
XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
if (rt) {
if (IsRootWrapper()) {
JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
if (map)
map->Remove(this);
}
if (IsRootWrapper())
rt->RemoveWrappedJS(this);
if (mRefCnt > 1)
RemoveFromRootSet();
@ -450,6 +506,13 @@ nsXPCWrappedJS::Unlink()
cur = cur->mNext;
MOZ_ASSERT(cur, "failed to find wrapper in its own chain");
}
// Note: unlinking this wrapper may have changed us from a multi-
// compartment wrapper chain to a single-compartment wrapper chain. We
// leave the wrapper in the multi-compartment table as it is likely to
// need to be multi-compartment again in the future and, moreover, we
// cannot get a JSContext here.
// let the root go
NS_RELEASE(mRoot);
}
@ -465,6 +528,20 @@ nsXPCWrappedJS::Unlink()
}
}
bool
nsXPCWrappedJS::IsMultiCompartment() const
{
MOZ_ASSERT(IsRootWrapper());
JSCompartment* compartment = js::GetObjectCompartment(mJSObj);
nsXPCWrappedJS* next = mNext;
while (next) {
if (js::GetObjectCompartment(next->mJSObj) != compartment)
return true;
next = next->mNext;
}
return false;
}
nsXPCWrappedJS*
nsXPCWrappedJS::Find(REFNSIID aIID)
{

View File

@ -430,6 +430,9 @@ public:
XPCJSContextStack* GetJSContextStack() {return mJSContextStack;}
void DestroyJSContextStack();
void RemoveWrappedJS(nsXPCWrappedJS* wrapper);
void AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const;
XPCCallContext* GetCallContext() const {return mCallContext;}
XPCCallContext* SetCallContext(XPCCallContext* ccx)
{XPCCallContext* old = mCallContext; mCallContext = ccx; return old;}
@ -443,10 +446,10 @@ public:
{XPCWrappedNative* old = mResolvingWrapper;
mResolvingWrapper = w; return old;}
JSObject2WrappedJSMap* GetWrappedJSMap() const
JSObject2WrappedJSMap* GetMultiCompartmentWrappedJSMap() const
{return mWrappedJSMap;}
IID2WrappedJSClassMap* GetWrappedJSClassMap() const
IID2WrappedJSClassMap* GetWrappedJSClassMap() const
{return mWrappedJSClassMap;}
IID2NativeInterfaceMap* GetIID2NativeInterfaceMap() const
@ -567,7 +570,8 @@ public:
JSFinalizeStatus status,
bool isCompartmentGC,
void* data);
static void WeakPointerCallback(JSRuntime* rt, void* data);
static void WeakPointerZoneGroupCallback(JSRuntime* rt, void* data);
static void WeakPointerCompartmentCallback(JSRuntime* rt, JSCompartment* comp, void* data);
inline void AddVariantRoot(XPCTraceableVariant* variant);
inline void AddWrappedJSRoot(nsXPCWrappedJS* wrappedJS);
@ -611,7 +615,7 @@ public:
nsTArray<nsXPCWrappedJS*>& WrappedJSToReleaseArray() { return mWrappedJSToReleaseArray; }
private:
XPCJSRuntime(); // no implementation
XPCJSRuntime() = delete;
explicit XPCJSRuntime(nsXPConnect* aXPConnect);
void ReleaseIncrementally(nsTArray<nsISupports*>& array);
@ -2449,6 +2453,14 @@ public:
*/
JSObject* GetJSObjectPreserveColor() const {return mJSObj;}
// Returns true if the wrapper chain contains references to multiple
// compartments. If the wrapper chain contains references to multiple
// compartments, then it must be registered on the XPCJSRuntime. Otherwise,
// it should be registered in the CompartmentPrivate for the compartment of
// the root's JS object. This will only return correct results when called
// on the root wrapper and will assert if not called on a root wrapper.
bool IsMultiCompartment() const;
nsXPCWrappedJSClass* GetClass() const {return mClass;}
REFNSIID GetIID() const {return GetClass()->GetIID();}
nsXPCWrappedJS* GetRootWrapper() const {return mRoot;}
@ -2491,7 +2503,7 @@ public:
virtual ~nsXPCWrappedJS();
protected:
nsXPCWrappedJS(); // not implemented
nsXPCWrappedJS() = delete;
nsXPCWrappedJS(JSContext* cx,
JSObject* aJSObj,
nsXPCWrappedJSClass* aClass,
@ -3677,20 +3689,7 @@ public:
LocationHintAddon
};
explicit CompartmentPrivate(JSCompartment* c)
: wantXrays(false)
, allowWaivers(true)
, writeToGlobalPrototype(false)
, skipWriteToGlobalPrototype(false)
, isWebExtensionContentScript(false)
, universalXPConnectEnabled(false)
, forcePermissiveCOWs(false)
, scriptability(c)
, scope(nullptr)
{
MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
mozilla::PodArrayZero(wrapperDenialWarnings);
}
explicit CompartmentPrivate(JSCompartment* c);
~CompartmentPrivate();
@ -3802,9 +3801,15 @@ public:
locationURI = aLocationURI;
}
JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap; }
void UpdateWeakPointersAfterGC(XPCJSRuntime* runtime);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
private:
nsCString location;
nsCOMPtr<nsIURI> locationURI;
JSObject2WrappedJSMap* mWrappedJSMap;
bool TryParseLocationURI(LocationHint aType, nsIURI** aURI);
};

View File

@ -383,11 +383,13 @@ private:
class CompartmentStatsExtras {
public:
CompartmentStatsExtras()
: sizeOfXPCPrivate(0)
{}
nsAutoCString jsPathPrefix;
nsAutoCString domPathPrefix;
nsCOMPtr<nsIURI> location;
size_t sizeOfXPCPrivate;
private:
CompartmentStatsExtras(const CompartmentStatsExtras& other) = delete;