diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 723471ebb21c..c5fb8eecec98 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -3178,12 +3178,20 @@ nsJSContext::ClearScope(void *aGlobalObj, PRBool aClearFromProtoChain) JS_ClearPendingException(mContext); } - if (!JS_ClearScope(mContext, obj)) - JS_ClearPendingException(mContext); + // Hack fix for bug 611653. Originally, this always called JS_ClearScope, + // which was required to avoid leaks. But for native objects, the JS + // 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 (!JS_ClearScope(mContext, &obj->getProxyExtra().toObject())) - JS_ClearPendingException(mContext); + JS_ClearScope(mContext, &obj->getProxyExtra().toObject()); } if (window != JSVAL_VOID) { @@ -3221,8 +3229,7 @@ nsJSContext::ClearScope(void *aGlobalObj, PRBool aClearFromProtoChain) // Clear up obj's prototype chain, but not Object.prototype. for (JSObject *o = ::JS_GetPrototype(mContext, obj), *next; o && (next = ::JS_GetPrototype(mContext, o)); o = next) - if (!::JS_ClearScope(mContext, o)) - JS_ClearPendingException(mContext); + ::JS_ClearScope(mContext, o); } } diff --git a/ipc/testshell/XPCShellEnvironment.cpp b/ipc/testshell/XPCShellEnvironment.cpp index db6b071beaf5..de0d2c1a4bec 100644 --- a/ipc/testshell/XPCShellEnvironment.cpp +++ b/ipc/testshell/XPCShellEnvironment.cpp @@ -524,8 +524,7 @@ Clear(JSContext *cx, { jsval *argv = JS_ARGV(cx, vp); if (argc > 0 && !JSVAL_IS_PRIMITIVE(argv[0])) { - if (!JS_ClearScope(cx, JSVAL_TO_OBJECT(argv[0]))) - return JS_FALSE; + JS_ClearScope(cx, JSVAL_TO_OBJECT(argv[0])); } else { JS_ReportError(cx, "'clear' requires an object"); return JS_FALSE; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index b7813e11e1a3..b843178cd3cb 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3922,14 +3922,12 @@ JS_DeleteProperty(JSContext *cx, JSObject *obj, const char *name) return JS_DeleteProperty2(cx, obj, name, &junk); } -JS_PUBLIC_API(JSBool) +JS_PUBLIC_API(void) JS_ClearScope(JSContext *cx, JSObject *obj) { CHECK_REQUEST(cx); assertSameCompartment(cx, obj); - uint32 span = obj->slotSpan(); - JSFinalizeOp clearOp = obj->getOps()->clear; if (clearOp) clearOp(cx, obj); @@ -3937,36 +3935,16 @@ JS_ClearScope(JSContext *cx, JSObject *obj) if (obj->isNative()) js_ClearNative(cx, obj); - js_InitRandom(cx); - /* Clear cached class objects on the global object. */ if (obj->isGlobal()) { - if (!obj->unbrand(cx)) - return false; - for (int key = JSProto_Null; key < JSProto_LIMIT * 3; key++) JS_SetReservedSlot(cx, obj, key, JSVAL_VOID); - /* Clear regexp statics. */ - RegExpStatics::extractFrom(obj)->clear(); - /* Clear the CSP eval-is-allowed cache. */ 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; - } } - return true; + js_InitRandom(cx); } JS_PUBLIC_API(JSIdArray *) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 6586e859505b..91af9067ccf8 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2469,7 +2469,7 @@ JS_DeleteElement(JSContext *cx, JSObject *obj, jsint index); extern JS_PUBLIC_API(JSBool) JS_DeleteElement2(JSContext *cx, JSObject *obj, jsint index, jsval *rval); -extern JS_PUBLIC_API(JSBool) +extern JS_PUBLIC_API(void) JS_ClearScope(JSContext *cx, JSObject *obj); extern JS_PUBLIC_API(JSIdArray *) diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 24c239f25ea0..972117f3e4a6 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -270,14 +270,8 @@ JSCompartment::wrap(JSContext *cx, Value *vp) /* If we already have a wrapper for this value, use it. */ if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(*vp)) { *vp = p->value; - if (vp->isObject()) { - JSObject *obj = &vp->toObject(); - do { - JS_ASSERT(obj->isWrapper()); - obj->setParent(global); - obj = obj->getProto(); - } while (obj); - } + if (vp->isObject()) + vp->toObject().setParent(global); return true; } diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 292e1f397851..68c30377387c 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -4450,6 +4450,40 @@ JSObject::freeSlot(JSContext *cx, uint32 slot) 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 */ #define JSBOXEDWORD_INT_MAX_STRING "1073741823" diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 9bdd4f9008f2..55190c8d1357 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1768,6 +1768,15 @@ extern JSBool js_SetNativeAttributes(JSContext *cx, JSObject *obj, js::Shape *shape, 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 { /* diff --git a/js/src/jsscope.h b/js/src/jsscope.h index 99aefa13a336..1c297367c525 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -297,6 +297,7 @@ struct Shape : public JSObjectMap friend class js::PropertyTree; friend class js::Bindings; 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 diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 72bd14578058..f2e156485588 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2733,11 +2733,10 @@ Clear(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj; if (argc != 0 && !JS_ValueToObject(cx, JS_ARGV(cx, vp)[0], &obj)) - return false; - if (!JS_ClearScope(cx, obj)) - return false; + return JS_FALSE; + JS_ClearScope(cx, obj); JS_SET_RVAL(cx, vp, JSVAL_VOID); - return true; + return JS_TRUE; } static JSBool @@ -4685,8 +4684,7 @@ split_setup(JSContext *cx, JSBool evalcx) } } - if (!JS_ClearScope(cx, outer)) - return NULL; + JS_ClearScope(cx, outer); #ifndef LAZY_STANDARD_CLASSES if (!JS_InitStandardClasses(cx, inner)) diff --git a/js/src/xpconnect/loader/mozJSComponentLoader.h b/js/src/xpconnect/loader/mozJSComponentLoader.h index adfe1d1b3e2b..120a951add88 100644 --- a/js/src/xpconnect/loader/mozJSComponentLoader.h +++ b/js/src/xpconnect/loader/mozJSComponentLoader.h @@ -178,8 +178,7 @@ class mozJSComponentLoader : public mozilla::ModuleLoader, JSAutoEnterCompartment ac; ac.enterAndIgnoreErrors(sSelf->mContext, global); - if (!JS_ClearScope(sSelf->mContext, global)) - JS_ClearPendingException(sSelf->mContext); + JS_ClearScope(sSelf->mContext, global); JS_RemoveObjectRoot(sSelf->mContext, &global); } diff --git a/js/src/xpconnect/shell/xpcshell.cpp b/js/src/xpconnect/shell/xpcshell.cpp index 22466bb493b5..334a396d98f9 100644 --- a/js/src/xpconnect/shell/xpcshell.cpp +++ b/js/src/xpconnect/shell/xpcshell.cpp @@ -679,8 +679,7 @@ static JSBool Clear(JSContext *cx, uintN argc, jsval *vp) { if (argc > 0 && !JSVAL_IS_PRIMITIVE(JS_ARGV(cx, vp)[0])) { - if (!JS_ClearScope(cx, JSVAL_TO_OBJECT(JS_ARGV(cx, vp)[0]))) - return JS_FALSE; + JS_ClearScope(cx, JSVAL_TO_OBJECT(JS_ARGV(cx, vp)[0])); } else { JS_ReportError(cx, "'clear' requires an object"); return JS_FALSE; @@ -2020,8 +2019,7 @@ main(int argc, char **argv) (void**) getter_AddRefs(bogus)); #endif JSPRINCIPALS_DROP(cx, gJSPrincipals); - if (!JS_ClearScope(cx, glob)) - NS_ERROR("clearing scope failed"); + JS_ClearScope(cx, glob); JS_GC(cx); JSContext *oldcx; cxstack->Pop(&oldcx); diff --git a/js/src/xpconnect/src/dom_quickstubs.qsconf b/js/src/xpconnect/src/dom_quickstubs.qsconf index 08e9f0d8df74..1dd1728a3d11 100644 --- a/js/src/xpconnect/src/dom_quickstubs.qsconf +++ b/js/src/xpconnect/src/dom_quickstubs.qsconf @@ -558,9 +558,8 @@ nsIDOMHTMLDocument_Write_customMethodCallCode = """ nsIDOMStorage_Clear_customMethodCallCode = """ rv = self->Clear(); - if (NS_SUCCEEDED(rv)) { - rv = JS_ClearScope(cx, obj) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; - } + if (NS_SUCCEEDED(rv)) + JS_ClearScope(cx, obj); """ CUSTOM_QS = { diff --git a/js/src/xpconnect/tests/TestXPC.cpp b/js/src/xpconnect/tests/TestXPC.cpp index 45fc3b661a71..a088de45e106 100644 --- a/js/src/xpconnect/tests/TestXPC.cpp +++ b/js/src/xpconnect/tests/TestXPC.cpp @@ -856,8 +856,7 @@ int main() { JSAutoRequest ar(jscontext); - if (!JS_ClearScope(jscontext, glob)) - DIE("FAILED to clear scope"); + JS_ClearScope(jscontext, glob); JS_GC(jscontext); JS_GC(jscontext); }