Bug 630072. Fix issue with wrappers holding objects from old scopes alive. r=mrbkap@gmail.com, a=blocker

This commit is contained in:
Andreas Gal 2011-02-11 16:36:48 -08:00
parent ec5802cfe2
commit cabd660fbb
13 changed files with 58 additions and 73 deletions

View File

@ -3178,20 +3178,12 @@ nsJSContext::ClearScope(void *aGlobalObj, PRBool aClearFromProtoChain)
JS_ClearPendingException(mContext); JS_ClearPendingException(mContext);
} }
// Hack fix for bug 611653. Originally, this always called JS_ClearScope, if (!JS_ClearScope(mContext, obj))
// which was required to avoid leaks. But for native objects, the JS JS_ClearPendingException(mContext);
// engine has an optimization that requires that permanent properties of
// the global object are never deleted. So instead, we call a new special
// API that clears the values of the global, thus avoiding leaks without
// deleting any properties.
if (obj->isNative()) {
js_UnbrandAndClearSlots(mContext, obj);
} else {
JS_ClearScope(mContext, obj);
}
if (xpc::WrapperFactory::IsXrayWrapper(obj)) { if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
JS_ClearScope(mContext, &obj->getProxyExtra().toObject()); if (!JS_ClearScope(mContext, &obj->getProxyExtra().toObject()))
JS_ClearPendingException(mContext);
} }
if (window != JSVAL_VOID) { if (window != JSVAL_VOID) {
@ -3229,7 +3221,8 @@ nsJSContext::ClearScope(void *aGlobalObj, PRBool aClearFromProtoChain)
// Clear up obj's prototype chain, but not Object.prototype. // Clear up obj's prototype chain, but not Object.prototype.
for (JSObject *o = ::JS_GetPrototype(mContext, obj), *next; for (JSObject *o = ::JS_GetPrototype(mContext, obj), *next;
o && (next = ::JS_GetPrototype(mContext, o)); o = next) o && (next = ::JS_GetPrototype(mContext, o)); o = next)
::JS_ClearScope(mContext, o); if (!::JS_ClearScope(mContext, o))
JS_ClearPendingException(mContext);
} }
} }

View File

@ -524,7 +524,8 @@ Clear(JSContext *cx,
{ {
jsval *argv = JS_ARGV(cx, vp); jsval *argv = JS_ARGV(cx, vp);
if (argc > 0 && !JSVAL_IS_PRIMITIVE(argv[0])) { if (argc > 0 && !JSVAL_IS_PRIMITIVE(argv[0])) {
JS_ClearScope(cx, JSVAL_TO_OBJECT(argv[0])); if (!JS_ClearScope(cx, JSVAL_TO_OBJECT(argv[0])))
return JS_FALSE;
} else { } else {
JS_ReportError(cx, "'clear' requires an object"); JS_ReportError(cx, "'clear' requires an object");
return JS_FALSE; return JS_FALSE;

View File

@ -3922,12 +3922,14 @@ JS_DeleteProperty(JSContext *cx, JSObject *obj, const char *name)
return JS_DeleteProperty2(cx, obj, name, &junk); return JS_DeleteProperty2(cx, obj, name, &junk);
} }
JS_PUBLIC_API(void) JS_PUBLIC_API(JSBool)
JS_ClearScope(JSContext *cx, JSObject *obj) JS_ClearScope(JSContext *cx, JSObject *obj)
{ {
CHECK_REQUEST(cx); CHECK_REQUEST(cx);
assertSameCompartment(cx, obj); assertSameCompartment(cx, obj);
uint32 span = obj->slotSpan();
JSFinalizeOp clearOp = obj->getOps()->clear; JSFinalizeOp clearOp = obj->getOps()->clear;
if (clearOp) if (clearOp)
clearOp(cx, obj); clearOp(cx, obj);
@ -3935,16 +3937,36 @@ JS_ClearScope(JSContext *cx, JSObject *obj)
if (obj->isNative()) if (obj->isNative())
js_ClearNative(cx, obj); js_ClearNative(cx, obj);
js_InitRandom(cx);
/* Clear cached class objects on the global object. */ /* Clear cached class objects on the global object. */
if (obj->isGlobal()) { if (obj->isGlobal()) {
if (!obj->unbrand(cx))
return false;
for (int key = JSProto_Null; key < JSProto_LIMIT * 3; key++) for (int key = JSProto_Null; key < JSProto_LIMIT * 3; key++)
JS_SetReservedSlot(cx, obj, key, JSVAL_VOID); JS_SetReservedSlot(cx, obj, key, JSVAL_VOID);
/* Clear regexp statics. */
RegExpStatics::extractFrom(obj)->clear();
/* Clear the CSP eval-is-allowed cache. */ /* Clear the CSP eval-is-allowed cache. */
JS_SetReservedSlot(cx, obj, JSRESERVED_GLOBAL_EVAL_ALLOWED, JSVAL_VOID); JS_SetReservedSlot(cx, obj, JSRESERVED_GLOBAL_EVAL_ALLOWED, JSVAL_VOID);
/*
* Compile-and-go scripts might rely on these slots to be present,
* so set a bunch of dummy properties to make sure compiled code
* doesn't reach into empty space.
*/
uint32 n = 0;
while (obj->slotSpan() < span) {
if (!JS_DefinePropertyById(cx, obj, INT_TO_JSID(n), JSVAL_VOID, NULL, NULL, 0))
return false;
++n;
}
} }
js_InitRandom(cx); return true;
} }
JS_PUBLIC_API(JSIdArray *) JS_PUBLIC_API(JSIdArray *)

View File

@ -2469,7 +2469,7 @@ JS_DeleteElement(JSContext *cx, JSObject *obj, jsint index);
extern JS_PUBLIC_API(JSBool) extern JS_PUBLIC_API(JSBool)
JS_DeleteElement2(JSContext *cx, JSObject *obj, jsint index, jsval *rval); JS_DeleteElement2(JSContext *cx, JSObject *obj, jsint index, jsval *rval);
extern JS_PUBLIC_API(void) extern JS_PUBLIC_API(JSBool)
JS_ClearScope(JSContext *cx, JSObject *obj); JS_ClearScope(JSContext *cx, JSObject *obj);
extern JS_PUBLIC_API(JSIdArray *) extern JS_PUBLIC_API(JSIdArray *)

View File

@ -270,8 +270,14 @@ JSCompartment::wrap(JSContext *cx, Value *vp)
/* If we already have a wrapper for this value, use it. */ /* If we already have a wrapper for this value, use it. */
if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(*vp)) { if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(*vp)) {
*vp = p->value; *vp = p->value;
if (vp->isObject()) if (vp->isObject()) {
vp->toObject().setParent(global); JSObject *obj = &vp->toObject();
do {
JS_ASSERT(obj->isWrapper());
obj->setParent(global);
obj = obj->getProto();
} while (obj);
}
return true; return true;
} }

View File

@ -4450,40 +4450,6 @@ JSObject::freeSlot(JSContext *cx, uint32 slot)
return false; return false;
} }
JS_FRIEND_API(void)
js_UnbrandAndClearSlots(JSContext *cx, JSObject *obj)
{
JS_ASSERT(obj->isNative());
JS_ASSERT(obj->isGlobal());
/* This can return false but that doesn't mean it failed. */
obj->unbrand(cx);
/*
* Clear the prototype cache. We must not clear the other global
* reserved slots, as other code will crash if they are arbitrarily
* reset (e.g., regexp statics).
*/
for (int key = JSProto_Null; key < JSRESERVED_GLOBAL_THIS; key++)
JS_SetReservedSlot(cx, obj, key, JSVAL_VOID);
/*
* Clear the non-reserved slots.
*/
ClearValueRange(obj->slots + JSCLASS_RESERVED_SLOTS(obj->clasp),
obj->capacity - JSCLASS_RESERVED_SLOTS(obj->clasp),
obj->clasp == &js_ArrayClass);
/*
* We just overwrote all slots to undefined, so the freelist has
* been trashed. We need to clear the head pointer or else we will
* crash later. This leaks slots but the object is all but dead
* anyway.
*/
if (obj->hasPropertyTable())
obj->lastProperty()->getTable()->freelist = SHAPE_INVALID_SLOT;
}
/* JSBOXEDWORD_INT_MAX as a string */ /* JSBOXEDWORD_INT_MAX as a string */
#define JSBOXEDWORD_INT_MAX_STRING "1073741823" #define JSBOXEDWORD_INT_MAX_STRING "1073741823"

View File

@ -1768,15 +1768,6 @@ extern JSBool
js_SetNativeAttributes(JSContext *cx, JSObject *obj, js::Shape *shape, js_SetNativeAttributes(JSContext *cx, JSObject *obj, js::Shape *shape,
uintN attrs); uintN attrs);
/*
* Hack fix for bug 611653: Do not use for any other purpose.
*
* Unbrand and set all slot values to undefined (except reserved slots that
* are not used for cached prototypes).
*/
JS_FRIEND_API(void)
js_UnbrandAndClearSlots(JSContext *cx, JSObject *obj);
namespace js { namespace js {
/* /*

View File

@ -297,7 +297,6 @@ struct Shape : public JSObjectMap
friend class js::PropertyTree; friend class js::PropertyTree;
friend class js::Bindings; friend class js::Bindings;
friend bool IsShapeAboutToBeFinalized(JSContext *cx, const js::Shape *shape); friend bool IsShapeAboutToBeFinalized(JSContext *cx, const js::Shape *shape);
friend JS_FRIEND_API(void) ::js_UnbrandAndClearSlots(JSContext *cx, JSObject *obj);
/* /*
* numLinearSearches starts at zero and is incremented initially on each * numLinearSearches starts at zero and is incremented initially on each

View File

@ -2733,10 +2733,11 @@ Clear(JSContext *cx, uintN argc, jsval *vp)
{ {
JSObject *obj; JSObject *obj;
if (argc != 0 && !JS_ValueToObject(cx, JS_ARGV(cx, vp)[0], &obj)) if (argc != 0 && !JS_ValueToObject(cx, JS_ARGV(cx, vp)[0], &obj))
return JS_FALSE; return false;
JS_ClearScope(cx, obj); if (!JS_ClearScope(cx, obj))
return false;
JS_SET_RVAL(cx, vp, JSVAL_VOID); JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE; return true;
} }
static JSBool static JSBool
@ -4684,7 +4685,8 @@ split_setup(JSContext *cx, JSBool evalcx)
} }
} }
JS_ClearScope(cx, outer); if (!JS_ClearScope(cx, outer))
return NULL;
#ifndef LAZY_STANDARD_CLASSES #ifndef LAZY_STANDARD_CLASSES
if (!JS_InitStandardClasses(cx, inner)) if (!JS_InitStandardClasses(cx, inner))

View File

@ -178,7 +178,8 @@ class mozJSComponentLoader : public mozilla::ModuleLoader,
JSAutoEnterCompartment ac; JSAutoEnterCompartment ac;
ac.enterAndIgnoreErrors(sSelf->mContext, global); ac.enterAndIgnoreErrors(sSelf->mContext, global);
JS_ClearScope(sSelf->mContext, global); if (!JS_ClearScope(sSelf->mContext, global))
JS_ClearPendingException(sSelf->mContext);
JS_RemoveObjectRoot(sSelf->mContext, &global); JS_RemoveObjectRoot(sSelf->mContext, &global);
} }

View File

@ -679,7 +679,8 @@ static JSBool
Clear(JSContext *cx, uintN argc, jsval *vp) Clear(JSContext *cx, uintN argc, jsval *vp)
{ {
if (argc > 0 && !JSVAL_IS_PRIMITIVE(JS_ARGV(cx, vp)[0])) { if (argc > 0 && !JSVAL_IS_PRIMITIVE(JS_ARGV(cx, vp)[0])) {
JS_ClearScope(cx, JSVAL_TO_OBJECT(JS_ARGV(cx, vp)[0])); if (!JS_ClearScope(cx, JSVAL_TO_OBJECT(JS_ARGV(cx, vp)[0])))
return JS_FALSE;
} else { } else {
JS_ReportError(cx, "'clear' requires an object"); JS_ReportError(cx, "'clear' requires an object");
return JS_FALSE; return JS_FALSE;
@ -2019,7 +2020,8 @@ main(int argc, char **argv)
(void**) getter_AddRefs(bogus)); (void**) getter_AddRefs(bogus));
#endif #endif
JSPRINCIPALS_DROP(cx, gJSPrincipals); JSPRINCIPALS_DROP(cx, gJSPrincipals);
JS_ClearScope(cx, glob); if (!JS_ClearScope(cx, glob))
NS_ERROR("clearing scope failed");
JS_GC(cx); JS_GC(cx);
JSContext *oldcx; JSContext *oldcx;
cxstack->Pop(&oldcx); cxstack->Pop(&oldcx);

View File

@ -558,8 +558,9 @@ nsIDOMHTMLDocument_Write_customMethodCallCode = """
nsIDOMStorage_Clear_customMethodCallCode = """ nsIDOMStorage_Clear_customMethodCallCode = """
rv = self->Clear(); rv = self->Clear();
if (NS_SUCCEEDED(rv)) if (NS_SUCCEEDED(rv)) {
JS_ClearScope(cx, obj); rv = JS_ClearScope(cx, obj) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
""" """
CUSTOM_QS = { CUSTOM_QS = {

View File

@ -856,7 +856,8 @@ int main()
{ {
JSAutoRequest ar(jscontext); JSAutoRequest ar(jscontext);
JS_ClearScope(jscontext, glob); if (!JS_ClearScope(jscontext, glob))
DIE("FAILED to clear scope");
JS_GC(jscontext); JS_GC(jscontext);
JS_GC(jscontext); JS_GC(jscontext);
} }