Bug 650161 - Update plugin wrapper tracing to work with compacting GC r=terrence r=johns

This commit is contained in:
Jon Coppeard 2014-11-05 16:37:16 +00:00
parent 8525319352
commit cbc7a10921
3 changed files with 205 additions and 62 deletions

View File

@ -3602,9 +3602,9 @@ nsObjectLoadingContent::TeardownProtoChain()
if (!proto) {
break;
}
// Unwrap while checking the jsclass - if the prototype is a wrapper for
// Unwrap while checking the class - if the prototype is a wrapper for
// an NP object, that counts too.
if (JS_GetClass(js::UncheckedUnwrap(proto)) == &sNPObjectJSWrapperClass) {
if (nsNPObjWrapper::IsWrapper(js::UncheckedUnwrap(proto))) {
// We found an NPObject on the proto chain, get its prototype...
if (!::JS_GetPrototype(cx, proto, &proto)) {
return;

View File

@ -54,6 +54,14 @@ struct JSObjWrapperHasher : public js::DefaultHasher<nsJSObjWrapperKey>
}
};
class NPObjWrapperHashEntry : public PLDHashEntryHdr
{
public:
NPObject *mNPObj; // Must be the first member for the PLDHash stubs to work
JS::TenuredHeap<JSObject*> mJSObj;
NPP mNpp;
};
// Hash of JSObject wrappers that wraps JSObjects as NPObjects. There
// will be one wrapper per JSObject per plugin instance, i.e. if two
// plugins access the JSObject x, two wrappers for x will be
@ -79,9 +87,8 @@ static PLDHashTable sNPObjWrappers;
// wrappers and we can kill off hash tables etc.
static int32_t sWrapperCount;
// The JSRuntime. Used to unroot JSObjects when no JSContext is
// reachable.
static JSRuntime *sJSRuntime;
// The runtime service used to register/unregister GC callbacks.
nsCOMPtr<nsIJSRuntimeService> sCallbackRuntime;
static nsTArray<NPObject*>* sDelayedReleases;
@ -158,7 +165,10 @@ static bool
NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp);
static void
NPObjWrapper_Finalize(JSFreeOp *fop, JSObject *obj);
NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj);
static void
NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old);
static bool
NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp);
@ -171,7 +181,7 @@ CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
JS::Handle<jsid> id, NPVariant* getPropertyResult,
JS::MutableHandle<JS::Value> vp);
const JSClass sNPObjectJSWrapperClass =
const static js::Class sNPObjectJSWrapperClass =
{
NPRUNTIME_JSCLASS_NAME,
JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE,
@ -185,7 +195,17 @@ const JSClass sNPObjectJSWrapperClass =
NPObjWrapper_Finalize,
NPObjWrapper_Call,
nullptr, /* hasInstance */
NPObjWrapper_Construct
NPObjWrapper_Construct,
nullptr, /* trace */
JS_NULL_CLASS_SPEC,
{
nullptr, /* outerObject */
nullptr, /* innerObject */
nullptr, /* iteratorObject */
false, /* isWrappedNative */
nullptr, /* weakmapKeyDelegateOp */
NPObjWrapper_ObjectMoved
}
};
typedef struct NPObjectMemberPrivate {
@ -220,6 +240,28 @@ static const JSClass sNPObjectMemberClass =
static void
OnWrapperDestroyed();
static void
TraceJSObjWrappers(JSTracer *trc, void *data)
{
if (!sJSObjWrappers.initialized()) {
return;
}
// Trace all JSObjects in the sJSObjWrappers table and rekey the entries if
// any of them moved.
for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) {
nsJSObjWrapperKey key = e.front().key();
JS_CallUnbarrieredObjectTracer(trc, &key.mJSObj, "sJSObjWrappers key object");
nsJSObjWrapper *wrapper = e.front().value();
if (wrapper->mJSObj) {
JS_CallObjectTracer(trc, &wrapper->mJSObj, "sJSObjWrappers wrapper object");
}
if (key != e.front().key()) {
e.rekeyFront(key);
}
}
}
static void
DelayedReleaseGCCallback(JSGCStatus status)
{
@ -240,22 +282,116 @@ DelayedReleaseGCCallback(JSGCStatus status)
}
}
static bool
RegisterGCCallbacks()
{
if (sCallbackRuntime) {
return true;
}
static const char rtsvc_id[] = "@mozilla.org/js/xpc/RuntimeService;1";
nsCOMPtr<nsIJSRuntimeService> rtsvc(do_GetService(rtsvc_id));
if (!rtsvc) {
return false;
}
JSRuntime *jsRuntime = nullptr;
rtsvc->GetRuntime(&jsRuntime);
MOZ_ASSERT(jsRuntime != nullptr);
// Register a callback to trace wrapped JSObjects.
if (!JS_AddExtraGCRootsTracer(jsRuntime, TraceJSObjWrappers, nullptr)) {
return false;
}
// Register our GC callback to perform delayed destruction of finalized
// NPObjects.
rtsvc->RegisterGCCallback(DelayedReleaseGCCallback);
// Set runtime pointer to indicate that callbacks have been registered.
sCallbackRuntime = rtsvc;
return true;
}
static void
UnregisterGCCallbacks()
{
MOZ_ASSERT(sCallbackRuntime);
JSRuntime *jsRuntime = nullptr;
sCallbackRuntime->GetRuntime(&jsRuntime);
MOZ_ASSERT(jsRuntime != nullptr);
// Remove tracing callback.
JS_RemoveExtraGCRootsTracer(jsRuntime, TraceJSObjWrappers, nullptr);
// Remove delayed destruction callback.
sCallbackRuntime->UnregisterGCCallback(DelayedReleaseGCCallback);
// Unset runtime pointer to indicate callbacks are no longer registered.
sCallbackRuntime = nullptr;
}
static bool
CreateJSObjWrapperTable()
{
MOZ_ASSERT(!sJSObjWrappersAccessible);
MOZ_ASSERT(!sJSObjWrappers.initialized());
if (!RegisterGCCallbacks()) {
return false;
}
if (!sJSObjWrappers.init(16)) {
NS_ERROR("Error initializing PLDHashTable sJSObjWrappers!");
return false;
}
sJSObjWrappersAccessible = true;
return true;
}
static void
DestroyJSObjWrapperTable()
{
MOZ_ASSERT(sJSObjWrappersAccessible);
MOZ_ASSERT(sJSObjWrappers.initialized());
MOZ_ASSERT(sJSObjWrappers.count() == 0);
// No more wrappers, and our hash was initialized. Finish the
// hash to prevent leaking it.
sJSObjWrappers.finish();
sJSObjWrappersAccessible = false;
}
static bool
CreateNPObjWrapperTable()
{
MOZ_ASSERT(!sNPObjWrappers.ops);
if (!RegisterGCCallbacks()) {
return false;
}
PL_DHashTableInit(&sNPObjWrappers, PL_DHashGetStubOps(), nullptr,
sizeof(NPObjWrapperHashEntry));
return true;
}
static void
DestroyNPObjWrapperTable()
{
MOZ_ASSERT(sNPObjWrappers.EntryCount() == 0);
PL_DHashTableFinish(&sNPObjWrappers);
sNPObjWrappers.ops = nullptr;
}
static void
OnWrapperCreated()
{
if (sWrapperCount++ == 0) {
static const char rtsvc_id[] = "@mozilla.org/js/xpc/RuntimeService;1";
nsCOMPtr<nsIJSRuntimeService> rtsvc(do_GetService(rtsvc_id));
if (!rtsvc)
return;
rtsvc->GetRuntime(&sJSRuntime);
NS_ASSERTION(sJSRuntime != nullptr, "no JSRuntime?!");
// Register our GC callback to perform delayed destruction of finalized
// NPObjects. Leave this callback around and don't ever unregister it.
rtsvc->RegisterGCCallback(DelayedReleaseGCCallback);
}
++sWrapperCount;
}
static void
@ -265,26 +401,16 @@ OnWrapperDestroyed()
if (--sWrapperCount == 0) {
if (sJSObjWrappersAccessible) {
MOZ_ASSERT(sJSObjWrappers.count() == 0);
// No more wrappers, and our hash was initialized. Finish the
// hash to prevent leaking it.
sJSObjWrappers.finish();
sJSObjWrappersAccessible = false;
DestroyJSObjWrapperTable();
}
if (sNPObjWrappers.ops) {
MOZ_ASSERT(sNPObjWrappers.EntryCount() == 0);
// No more wrappers, and our hash was initialized. Finish the
// hash to prevent leaking it.
PL_DHashTableFinish(&sNPObjWrappers);
sNPObjWrappers.ops = nullptr;
DestroyNPObjWrapperTable();
}
// No more need for this.
sJSRuntime = nullptr;
UnregisterGCCallbacks();
}
}
@ -507,7 +633,7 @@ ReportExceptionIfPending(JSContext *cx)
}
nsJSObjWrapper::nsJSObjWrapper(NPP npp)
: mJSObj(GetJSContext(npp), nullptr), mNpp(npp)
: mJSObj(nullptr), mNpp(npp)
{
MOZ_COUNT_CTOR(nsJSObjWrapper);
OnWrapperCreated();
@ -517,7 +643,7 @@ nsJSObjWrapper::~nsJSObjWrapper()
{
MOZ_COUNT_DTOR(nsJSObjWrapper);
// Invalidate first, since it relies on sJSRuntime and sJSObjWrappers.
// Invalidate first, since it relies on sJSObjWrappers.
NP_Invalidate(this);
OnWrapperDestroyed();
@ -982,9 +1108,7 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle<JSObject*> obj)
// class and private from the JSObject, neither of which cares about
// compartments.
const JSClass *clazz = JS_GetClass(obj);
if (clazz == &sNPObjectJSWrapperClass) {
if (nsNPObjWrapper::IsWrapper(obj)) {
// obj is one of our own, its private data is the NPObject we're
// looking for.
@ -1005,12 +1129,8 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle<JSObject*> obj)
if (!sJSObjWrappers.initialized()) {
// No hash yet (or any more), initialize it.
if (!sJSObjWrappers.init(16)) {
NS_ERROR("Error initializing PLDHashTable!");
if (!CreateJSObjWrapperTable())
return nullptr;
}
sJSObjWrappersAccessible = true;
}
MOZ_ASSERT(sJSObjWrappersAccessible);
@ -1032,10 +1152,10 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle<JSObject*> obj)
return nullptr;
}
// Assign mJSObj, rooting the JSObject. Its lifetime is now tied to that of
// the NPObject.
wrapper->mJSObj = obj;
// Insert the new wrapper into the hashtable, rooting the JSObject. Its
// lifetime is now tied to that of the NPObject.
nsJSObjWrapperKey key(obj, npp);
if (!sJSObjWrappers.putNew(key, wrapper)) {
// Out of memory, free the wrapper we created.
@ -1060,7 +1180,7 @@ GetNPObjectWrapper(JSContext *cx, JSObject *aObj, bool wrapResult = true)
{
JS::Rooted<JSObject*> obj(cx, aObj);
while (obj && (obj = js::CheckedUnwrap(obj))) {
if (JS_GetClass(obj) == &sNPObjectJSWrapperClass) {
if (nsNPObjWrapper::IsWrapper(obj)) {
if (wrapResult && !JS_WrapObject(cx, &obj)) {
return nullptr;
}
@ -1627,7 +1747,7 @@ NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType hint, JS::
}
static void
NPObjWrapper_Finalize(JSFreeOp *fop, JSObject *obj)
NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj)
{
NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
if (npobj) {
@ -1641,6 +1761,32 @@ NPObjWrapper_Finalize(JSFreeOp *fop, JSObject *obj)
sDelayedReleases->AppendElement(npobj);
}
static void
NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old)
{
// The wrapper JSObject has been moved, so we need to update the entry in the
// sNPObjWrappers hash table, if present.
if (!sNPObjWrappers.ops) {
return;
}
NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
if (!npobj) {
return;
}
// The hazard analysis thinks that PL_DHashTableOperate() can GC but this is
// not possible if we pass PL_DHASH_LOOKUP.
JS::AutoSuppressGCAnalysis nogc;
NPObjWrapperHashEntry *entry = static_cast<NPObjWrapperHashEntry *>
(PL_DHashTableOperate(&sNPObjWrappers, npobj, PL_DHASH_LOOKUP));
MOZ_ASSERT(PL_DHASH_ENTRY_IS_BUSY(entry) && entry->mJSObj);
MOZ_ASSERT(entry->mJSObj == old);
entry->mJSObj = obj;
}
static bool
NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp)
{
@ -1657,14 +1803,11 @@ NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp)
return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true);
}
class NPObjWrapperHashEntry : public PLDHashEntryHdr
bool
nsNPObjWrapper::IsWrapper(JSObject *obj)
{
public:
NPObject *mNPObj; // Must be the first member for the PLDHash stubs to work
JSObject *mJSObj;
NPP mNpp;
};
return js::GetObjectClass(obj) == &sNPObjectJSWrapperClass;
}
// An NPObject is going away, make sure we null out the JS object's
// private data in case this is an NPObject that came from a plugin
@ -1736,8 +1879,9 @@ nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj)
if (!sNPObjWrappers.ops) {
// No hash yet (or any more), initialize it.
PL_DHashTableInit(&sNPObjWrappers, PL_DHashGetStubOps(), nullptr,
sizeof(NPObjWrapperHashEntry));
if (!CreateNPObjWrapperTable()) {
return nullptr;
}
}
NPObjWrapperHashEntry *entry = static_cast<NPObjWrapperHashEntry *>
@ -1767,8 +1911,8 @@ nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj)
// No existing JSObject, create one.
JS::Rooted<JSObject*> obj(cx, ::JS_NewObject(cx, &sNPObjectJSWrapperClass, JS::NullPtr(),
JS::NullPtr()));
JS::Rooted<JSObject*> obj(cx, ::JS_NewObject(cx, js::Jsvalify(&sNPObjectJSWrapperClass),
JS::NullPtr(), JS::NullPtr()));
if (generation != sNPObjWrappers.Generation()) {
// Reload entry if the JS_NewObject call caused a GC and reallocated

View File

@ -36,12 +36,10 @@ public:
const NPP mNpp;
};
extern const JSClass sNPObjectJSWrapperClass;
class nsJSObjWrapper : public NPObject
{
public:
JS::PersistentRooted<JSObject *> mJSObj;
JS::Heap<JSObject *> mJSObj;
const NPP mNpp;
static NPObject *GetNewOrUsed(NPP npp, JSContext *cx,
@ -78,6 +76,7 @@ public:
class nsNPObjWrapper
{
public:
static bool IsWrapper(JSObject *obj);
static void OnDestroy(NPObject *npobj);
static JSObject *GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj);
};