Fix for bug 367779 (Make cycle collection through JS objects more reliable). r=jst, sr=brendan.

This commit is contained in:
peterv%propagandism.org 2007-02-18 20:05:32 +00:00
Родитель e991cbf0ba
Коммит 3e4c261c01
10 изменённых файлов: 371 добавлений и 31 удалений

Просмотреть файл

@ -108,18 +108,42 @@ nsXPConnect::nsXPConnect()
}
typedef nsBaseHashtable<nsClearingVoidPtrHashKey, PRUint32, PRUint32> PointerSet;
#ifndef XPCONNECT_STANDALONE
typedef nsBaseHashtable<nsClearingVoidPtrHashKey, nsISupports*, nsISupports*> ScopeSet;
#endif
struct JSObjectRefcounts
{
PointerSet mRefCounts;
JSObjectRefcounts()
#ifndef XPCONNECT_STANDALONE
ScopeSet mScopes;
#endif
PRBool mMarkEnded;
JSObjectRefcounts() : mMarkEnded(PR_FALSE)
{
mRefCounts.Init();
#ifndef XPCONNECT_STANDALONE
mScopes.Init();
#endif
}
void Clear()
{
mRefCounts.Clear();
#ifndef XPCONNECT_STANDALONE
mScopes.Clear();
#endif
}
void MarkStart()
{
Clear();
mMarkEnded = PR_FALSE;
}
void MarkEnd()
{
mMarkEnded = PR_TRUE;
}
void Ref(void *obj, uint8 flags)
@ -430,6 +454,23 @@ nsXPConnect::GetInfoForName(const char * name, nsIInterfaceInfo** info)
return FindInfo(NameTester, name, mInterfaceInfoManager, info);
}
static JSGCCallback gOldJSGCCallback;
JS_STATIC_DLL_CALLBACK(JSBool)
XPCCycleGCCallback(JSContext *cx, JSGCStatus status)
{
// Chain to old GCCallback first, we want to get all the mark notifications
// before recording the end of the mark phase.
JSBool ok = gOldJSGCCallback ? gOldJSGCCallback(cx, status) : JS_TRUE;
// Record the end of a mark phase. If we get more mark notifications then
// the GC has restarted and we'll need to clear the refcounts first.
if(status == JSGC_MARK_END)
nsXPConnect::GetXPConnect()->GetJSObjectRefcounts()->MarkEnd();
return ok;
}
void XPCMarkNotification(void *thing, uint8 flags, void *closure)
{
uint8 ty = flags & GCF_TYPEMASK;
@ -439,6 +480,12 @@ void XPCMarkNotification(void *thing, uint8 flags, void *closure)
return;
JSObjectRefcounts* jsr = NS_STATIC_CAST(JSObjectRefcounts*, closure);
// We're marking after a mark phase ended, so the GC restarted itself and
// we want to clear the refcounts first.
// XXX With GC_MARK_DEBUG defined we end up too many times in
// XPCMarkNotification, even while taking JSGC_MARK_END into account.
if(jsr->mMarkEnded)
jsr->MarkStart();
jsr->Ref(thing, flags);
}
@ -448,17 +495,32 @@ nsXPConnect::BeginCycleCollection()
if (!mObjRefcounts)
mObjRefcounts = new JSObjectRefcounts;
mObjRefcounts->Clear();
mObjRefcounts->MarkStart();
XPCCallContext cx(NATIVE_CALLER);
if (cx.IsValid()) {
gOldJSGCCallback = JS_SetGCCallback(cx, XPCCycleGCCallback);
JS_SetGCThingCallback(cx, XPCMarkNotification, mObjRefcounts);
JS_GC(cx);
JS_SetGCThingCallback(cx, nsnull, nsnull);
JS_SetGCCallback(cx, gOldJSGCCallback);
gOldJSGCCallback = nsnull;
#ifndef XPCONNECT_STANDALONE
XPCWrappedNativeScope::TraverseScopes(cx);
#endif
}
return NS_OK;
}
#ifndef XPCONNECT_STANDALONE
void
nsXPConnect::RecordTraversal(void *p, nsISupports *s)
{
mObjRefcounts->mScopes.Put(p, s);
}
#endif
nsresult
nsXPConnect::FinishCycleCollection()
{
@ -524,23 +586,90 @@ nsXPConnect::Traverse(void *p, nsCycleCollectionTraversalCallback &cb)
if (!mObjRefcounts->Get(p, refcount))
return NS_OK;
cb.DescribeNode(refcount, sizeof(JSObject), "JS Object");
JSObject *obj = NS_STATIC_CAST(JSObject*, p);
JSClass* clazz = OBJ_GET_CLASS(cx, obj);
#ifdef DEBUG
char name[72];
if(XPCNativeWrapper::IsNativeWrapperClass(clazz))
{
XPCWrappedNative* wn = XPCNativeWrapper::GetWrappedNative(cx, obj);
XPCNativeScriptableInfo* si = wn ? wn->GetScriptableInfo() : nsnull;
if(si)
snprintf(name, sizeof(name), "XPCNativeWrapper (%s)",
si->GetJSClass()->name);
else
snprintf(name, sizeof(name), "XPCNativeWrapper");
}
else if(obj)
{
XPCNativeScriptableInfo* si = nsnull;
if(IS_PROTO_CLASS(clazz))
{
XPCWrappedNativeProto* p =
(XPCWrappedNativeProto*) JS_GetPrivate(cx, obj);
si = p->GetScriptableInfo();
}
if(si)
snprintf(name, sizeof(name), "JS Object (%s - %s)", clazz->name,
si->GetJSClass()->name);
else
snprintf(name, sizeof(name), "JS Object (%s)", clazz->name);
}
else
{
snprintf(name, sizeof(name), "JS Object");
}
cb.DescribeNode(refcount, sizeof(JSObject), name);
#else
cb.DescribeNode(refcount, sizeof(JSObject), "JS Object");
#endif
if (!p)
return NS_OK;
JSObject *obj = NS_STATIC_CAST(JSObject*, p);
if (OBJ_GET_CLASS(cx, obj)->flags & JSCLASS_HAS_PRIVATE
&& OBJ_GET_CLASS(cx, obj)->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS)
if(XPCNativeWrapper::IsNativeWrapperClass(clazz))
{
// XPCNativeWrapper keeps its wrapped native alive (see XPC_NW_Mark).
XPCWrappedNative* wn = XPCNativeWrapper::GetWrappedNative(cx, obj);
if(wn)
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT,
wn->GetFlatJSObject());
}
else if(clazz == &XPC_WN_Tearoff_JSClass)
{
// A tearoff holds a strong reference to its native object
// (see XPCWrappedNative::FlatJSObjectFinalized). Its XPCWrappedNative
// will be held alive through the parent of the JSObject of the tearoff.
XPCWrappedNativeTearOff *to =
(XPCWrappedNativeTearOff*) JS_GetPrivate(cx, obj);
cb.NoteXPCOMChild(to->GetNative());
}
else if(IS_PROTO_CLASS(clazz))
{
// See XPC_WN_Shared_Proto_Mark.
XPCWrappedNativeProto* p =
(XPCWrappedNativeProto*) JS_GetPrivate(cx, obj);
if(p)
// Mark scope.
p->GetScope()->Traverse(cb);
}
else if(clazz->flags & JSCLASS_HAS_PRIVATE &&
clazz->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS)
{
void *v = JS_GetPrivate(cx, obj);
if (v)
cb.NoteXPCOMChild(NS_STATIC_CAST(nsISupports*, v));
}
JSObject *parent = OBJ_GET_PARENT(cx, obj);
if(parent)
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT, parent);
for (uint32 i = JSSLOT_START(OBJ_GET_CLASS(cx, obj));
i < STOBJ_NSLOTS(obj); ++i)
JSObject *proto = OBJ_GET_PROTO(cx, obj);
if(proto)
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT, proto);
for(uint32 i = JSSLOT_START(clazz); i < STOBJ_NSLOTS(obj); ++i)
{
jsval val = STOBJ_GET_SLOT(obj, i);
if (!JSVAL_IS_NULL(val)
@ -551,7 +680,17 @@ nsXPConnect::Traverse(void *p, nsCycleCollectionTraversalCallback &cb)
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT, child);
}
}
#ifndef XPCONNECT_STANDALONE
if(clazz->flags & JSCLASS_IS_GLOBAL)
{
nsISupports *principal = nsnull;
mObjRefcounts->mScopes.Get(obj, &principal);
if(principal)
cb.NoteXPCOMChild(principal);
}
#endif
return NS_OK;
}

Просмотреть файл

@ -481,6 +481,10 @@ public:
nsresult Unroot(const nsDeque &nodes);
nsresult Traverse(void *p, nsCycleCollectionTraversalCallback &cb);
nsresult FinishCycleCollection();
JSObjectRefcounts* GetJSObjectRefcounts() {return mObjRefcounts;}
#ifndef XPCONNECT_STANDALONE
void RecordTraversal(void *p, nsISupports *s);
#endif
#ifdef XPC_IDISPATCH_SUPPORT
public:
@ -1119,6 +1123,15 @@ public:
static void InitStatics() { gScopes = nsnull; gDyingScopes = nsnull; }
void Traverse(nsCycleCollectionTraversalCallback &cb);
#ifndef XPCONNECT_STANDALONE
/**
* Fills the hash mapping global object to principal.
*/
static void TraverseScopes(XPCCallContext& ccx);
#endif
protected:
XPCWrappedNativeScope(XPCCallContext& ccx, JSObject* aGlobal);
virtual ~XPCWrappedNativeScope();
@ -3551,8 +3564,10 @@ extern char * xpc_CheckAccessList(const PRUnichar* wideName, const char* list[])
class XPCVariant : public nsIVariant
{
public:
NS_DECL_ISUPPORTS
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIVARIANT
NS_DECL_CYCLE_COLLECTION_CLASS(XPCVariant)
// If this class ever implements nsIWritableVariant, take special care with
// the case when mJSVal is JSVAL_STRING, since we don't own the data in
// that case.
@ -3593,6 +3608,10 @@ protected:
// For faster GC-thing locking and unlocking
JSRuntime* mJSRuntime;
#ifdef GC_MARK_DEBUG
char *mGCRootName;
#endif
};
NS_DEFINE_STATIC_IID_ACCESSOR(XPCVariant, XPCVARIANT_IID)

Просмотреть файл

@ -42,7 +42,19 @@
#include "xpcprivate.h"
NS_IMPL_ISUPPORTS2_CI(XPCVariant, XPCVariant, nsIVariant)
NS_IMPL_CYCLE_COLLECTION_CLASS(XPCVariant)
NS_INTERFACE_MAP_BEGIN(XPCVariant)
NS_INTERFACE_MAP_ENTRY(XPCVariant)
NS_INTERFACE_MAP_ENTRY(nsIVariant)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_IMPL_QUERY_CLASSINFO(XPCVariant)
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(XPCVariant)
NS_INTERFACE_MAP_END
NS_IMPL_CI_INTERFACE_GETTER2(XPCVariant, XPCVariant, nsIVariant)
NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCVariant)
NS_IMPL_CYCLE_COLLECTING_RELEASE(XPCVariant)
XPCVariant::XPCVariant(JSRuntime* aJSRuntime)
: mJSVal(JSVAL_VOID),
@ -61,10 +73,30 @@ XPCVariant::~XPCVariant()
if(JSVAL_IS_GCTHING(mJSVal))
{
NS_ASSERTION(mJSRuntime, "Must have a runtime!");
#ifdef GC_MARK_DEBUG
JS_RemoveRootRT(mJSRuntime, &mJSVal);
JS_smprintf_free(mGCRootName);
mGCRootName = nsnull;
#else
JS_UnlockGCThingRT(mJSRuntime, JSVAL_TO_GCTHING(mJSVal));
#endif
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XPCVariant)
if(JSVAL_IS_OBJECT(tmp->mJSVal))
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT,
JSVAL_TO_OBJECT(tmp->mJSVal));
nsVariant::Traverse(tmp->mData, cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
// NB: We might unlink our outgoing references in the future; for now we do
// nothing. This is a harmless conservative behavior; it just means that we rely
// on the cycle being broken by some of the external XPCOM objects' unlink()
// methods, not our own. Typically *any* unlinking will break the cycle.
NS_IMPL_CYCLE_COLLECTION_UNLINK_0(XPCVariant)
// static
XPCVariant* XPCVariant::newVariant(XPCCallContext& ccx, jsval aJSVal)
{
@ -78,9 +110,16 @@ XPCVariant* XPCVariant::newVariant(XPCCallContext& ccx, jsval aJSVal)
if(JSVAL_IS_GCTHING(variant->mJSVal))
{
PRBool ok;
#ifdef GC_MARK_DEBUG
variant->mGCRootName = JS_smprintf("XPCVariant::mJSVal[0x%p]", variant);
ok = JS_AddNamedRoot(ccx, &variant->mJSVal, variant->mGCRootName);
#else
// use JS_LockGCThingRT, because we get better performance in a lot of
// cases than with adding a named GC root.
if(!JS_LockGCThing(ccx, JSVAL_TO_GCTHING(variant->mJSVal)))
ok = JS_LockGCThing(ccx, JSVAL_TO_GCTHING(variant->mJSVal));
#endif
if(!ok)
{
NS_RELEASE(variant); // Also sets variant to nsnull.
}

Просмотреть файл

@ -63,24 +63,57 @@ NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Traverse
// knows for how long.
nsresult rv;
nsCOMPtr<nsIXPConnectWrappedJS> owner = do_QueryInterface(s, &rv);
if (NS_FAILED(rv))
return rv;
nsIXPConnectWrappedJS *base;
nsXPCWrappedJS *tmp;
{
// Put the nsCOMPtr in a local scope, to avoid messing up the refcount
// below.
nsCOMPtr<nsIXPConnectWrappedJS> owner = do_QueryInterface(s, &rv);
if (NS_FAILED(rv))
return rv;
nsIXPConnectWrappedJS *base = owner.get();
nsXPCWrappedJS *tmp = NS_STATIC_CAST(nsXPCWrappedJS*, base);
base = owner.get();
tmp = NS_STATIC_CAST(nsXPCWrappedJS*, base);
NS_ASSERTION(tmp->mRefCnt.get() > 2,
"How can this be, no one else holds a strong ref?");
}
// REVIEW ME PLEASE:
//
// I am not sure when this represents the true refcount.
NS_ASSERTION(tmp->IsValid(), "How did we get here?");
cb.DescribeNode(tmp->mRefCnt.get(), sizeof(nsXPCWrappedJS),
"nsXPCWrappedJS");
nsrefcnt refcnt = tmp->mRefCnt.get();
#ifdef DEBUG
char name[72];
snprintf(name, sizeof(name), "nsXPCWrappedJS (%s)",
tmp->GetClass()->GetInterfaceName());
cb.DescribeNode(refcnt, sizeof(nsXPCWrappedJS), name);
#else
cb.DescribeNode(refcnt, sizeof(nsXPCWrappedJS), "nsXPCWrappedJS");
#endif
if (tmp->IsValid())
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT,
// nsXPCWrappedJS keeps its own refcount artificially at or above 1, see the
// comment above nsXPCWrappedJS::AddRef.
cb.NoteXPCOMChild(base);
if(refcnt > 1)
// nsXPCWrappedJS roots its mJSObj when its refcount is > 1, see
// the comment above nsXPCWrappedJS::AddRef.
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT,
tmp->GetJSObject());
nsXPCWrappedJS* root = tmp->GetRootWrapper();
if(root == tmp)
{
// The root wrapper keeps the aggregated native object alive.
nsISupports* outer = tmp->GetAggregatedNativeObject();
if (outer)
cb.NoteXPCOMChild(outer);
}
else
{
// Non-root wrappers keep their root alive.
cb.NoteXPCOMChild(NS_STATIC_CAST(nsIXPConnectWrappedJS*, root));
}
return NS_OK;
}
@ -134,6 +167,14 @@ nsXPCWrappedJS::QueryInterface(REFNSIID aIID, void** aInstancePtr)
return NS_OK;
}
if(aIID.Equals(NS_GET_IID(nsCycleCollectionISupports)))
{
NS_ADDREF(this);
*aInstancePtr =
NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this);
return NS_OK;
}
// Always check for this first so that our 'outer' can get this interface
// from us without recurring into a call to the outer's QI!
if(aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS)))

Просмотреть файл

@ -54,7 +54,22 @@ NS_CYCLE_COLLECTION_CLASSNAME(XPCWrappedNative)::Traverse(nsISupports *s,
nsCycleCollectionTraversalCallback &cb)
{
XPCWrappedNative *tmp = NS_STATIC_CAST(XPCWrappedNative*, s);
if(!tmp->IsValid())
return NS_OK;
#ifdef DEBUG
char name[72];
XPCNativeScriptableInfo* si = tmp->GetScriptableInfo();
if(si)
snprintf(name, sizeof(name), "XPCWrappedNative (%s)",
si->GetJSClass()->name);
else
snprintf(name, sizeof(name), "XPCWrappedNative");
cb.DescribeNode(tmp->mRefCnt.get(), sizeof(XPCWrappedNative), name);
#else
cb.DescribeNode(tmp->mRefCnt.get(), sizeof(XPCWrappedNative), "XPCWrappedNative");
#endif
if (tmp->mRefCnt.get() > 1) {
@ -75,6 +90,27 @@ NS_CYCLE_COLLECTION_CLASSNAME(XPCWrappedNative)::Traverse(nsISupports *s,
}
}
// XXX If there is a scriptable helper we will not be able to find out what
// it marked.
// xpc_MarkForValidWrapper calls MarkBeforeJSFinalize and
// MarkScopeJSObjects.
// XPCWrappedNative marks its proto (see MarkBeforeJSFinalize).
if(tmp->HasProto())
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT,
tmp->GetProto()->GetJSProtoObject());
// XPCWrappedNative marks its mNativeWrapper (see MarkBeforeJSFinalize).
if(tmp->mNativeWrapper)
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT,
tmp->mNativeWrapper);
// XPCWrappedNative marks its scope.
tmp->GetScope()->Traverse(cb);
// XPCWrappedNative keeps its native object alive.
if (tmp->GetIdentityObject()) {
cb.NoteXPCOMChild(tmp->GetIdentityObject());
}

Просмотреть файл

@ -698,12 +698,6 @@ xpc_MarkForValidWrapper(JSContext *cx, XPCWrappedNative* wrapper, void *arg)
wrapper->MarkBeforeJSFinalize(cx);
if(wrapper->HasProto())
{
JSObject* obj = wrapper->GetProto()->GetJSProtoObject();
NS_ASSERTION(obj, "bad proto");
JS_MarkGCThing(cx, obj, "XPCWrappedNativeProto::mJSProtoObject", arg);
}
MarkScopeJSObjects(cx, wrapper->GetScope(), arg);
}

Просмотреть файл

@ -793,3 +793,32 @@ XPCWrappedNativeScope::DebugDump(PRInt16 depth)
#endif
}
void
XPCWrappedNativeScope::Traverse(nsCycleCollectionTraversalCallback &cb)
{
// See MarkScopeJSObjects.
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT, mGlobalJSObject);
JSObject *obj = mPrototypeJSObject;
if(obj)
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT, obj);
obj = mPrototypeJSFunction;
if(obj)
cb.NoteScriptChild(nsIProgrammingLanguage::JAVASCRIPT, obj);
}
#ifndef XPCONNECT_STANDALONE
// static
void
XPCWrappedNativeScope::TraverseScopes(XPCCallContext& ccx)
{
// Hold the lock throughout.
XPCAutoLock lock(ccx.GetRuntime()->GetMapLock());
for(XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext)
if(cur->mGlobalJSObject && cur->mScriptObjectPrincipal)
{
ccx.GetXPConnect()->RecordTraversal(cur->mGlobalJSObject,
cur->mScriptObjectPrincipal);
}
}
#endif

Просмотреть файл

@ -169,6 +169,15 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsCycleCollectionParticipant,
return NS_OK; \
}
#define NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_class) \
NS_IMETHODIMP \
NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unlink(nsISupports *s) \
{ \
NS_ASSERTION(CheckForRightISupports(s), \
"not the nsISupports pointer we expect"); \
return NS_OK; \
}
///////////////////////////////////////////////////////////////////////////////
// Helpers for implementing nsCycleCollectionParticipant::Traverse

Просмотреть файл

@ -45,6 +45,7 @@
#include "prdtoa.h"
#include <math.h>
#include "nsCRT.h"
#include "nsCycleCollectionParticipant.h"
/***************************************************************************/
// Helpers for static convert functions...
@ -1657,6 +1658,36 @@ nsVariant::Cleanup(nsDiscriminatedUnion* data)
return NS_OK;
}
/* static */ void
nsVariant::Traverse(const nsDiscriminatedUnion& data,
nsCycleCollectionTraversalCallback &cb)
{
switch(data.mType)
{
case nsIDataType::VTYPE_INTERFACE:
case nsIDataType::VTYPE_INTERFACE_IS:
if (data.u.iface.mInterfaceValue) {
cb.NoteXPCOMChild(data.u.iface.mInterfaceValue);
}
break;
case nsIDataType::VTYPE_ARRAY:
switch(data.u.array.mArrayType) {
case nsIDataType::VTYPE_INTERFACE:
case nsIDataType::VTYPE_INTERFACE_IS:
{
nsISupports** p = (nsISupports**) data.u.array.mArrayValue;
for(PRUint32 i = data.u.array.mArrayCount; i > 0; p++, i--)
if(*p)
cb.NoteXPCOMChild(*p);
}
default:
break;
}
default:
break;
}
}
/***************************************************************************/
/***************************************************************************/
// members...

Просмотреть файл

@ -179,6 +179,9 @@ public:
static nsresult SetToEmpty(nsDiscriminatedUnion* data);
static nsresult SetToEmptyArray(nsDiscriminatedUnion* data);
static void Traverse(const nsDiscriminatedUnion& data,
nsCycleCollectionTraversalCallback &cb);
private:
~nsVariant();