From 5ec6a2cf11c0f9137e0bd4ef1d0ba38c5221fcc2 Mon Sep 17 00:00:00 2001 From: "mrbkap@gmail.com" Date: Sat, 29 Dec 2007 15:40:50 -0800 Subject: [PATCH] Make enumeration over SJOWs walk the prototype chain. Also make SJOWs unwrap same-origin XOWs. bug 410090, r+sr=jst --- .../xpconnect/src/XPCCrossOriginWrapper.cpp | 129 +----------------- .../xpconnect/src/XPCSafeJSObjectWrapper.cpp | 46 ++++++- js/src/xpconnect/src/XPCWrapper.cpp | 127 +++++++++++++++++ js/src/xpconnect/src/XPCWrapper.h | 15 +- .../tests/mochitest/test_wrappers.html | 18 ++- 5 files changed, 205 insertions(+), 130 deletions(-) diff --git a/js/src/xpconnect/src/XPCCrossOriginWrapper.cpp b/js/src/xpconnect/src/XPCCrossOriginWrapper.cpp index 9374550141f3..f30b7664be32 100644 --- a/js/src/xpconnect/src/XPCCrossOriginWrapper.cpp +++ b/js/src/xpconnect/src/XPCCrossOriginWrapper.cpp @@ -1013,118 +1013,16 @@ XPC_XOW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) equality(cx, obj, OBJECT_TO_JSVAL(test), bp); } -JS_STATIC_DLL_CALLBACK(void) -IteratorFinalize(JSContext *cx, JSObject *obj) -{ - jsval v; - JS_GetReservedSlot(cx, obj, 0, &v); - - JSIdArray *ida = reinterpret_cast(JSVAL_TO_PRIVATE(v)); - if (ida) { - JS_DestroyIdArray(cx, ida); - } -} - -JS_STATIC_DLL_CALLBACK(JSBool) -IteratorNext(JSContext *cx, uintN argc, jsval *vp) -{ - JSObject *obj = JSVAL_TO_OBJECT(vp[1]); - jsval v; - - JS_GetReservedSlot(cx, obj, 0, &v); - JSIdArray *ida = reinterpret_cast(JSVAL_TO_PRIVATE(v)); - - JS_GetReservedSlot(cx, obj, 1, &v); - jsint idx = JSVAL_TO_INT(v); - - if (idx == ida->length) { - return JS_ThrowStopIteration(cx); - } - - JS_GetReservedSlot(cx, obj, 2, &v); - jsid id = ida->vector[idx++]; - if (JSVAL_TO_BOOLEAN(v)) { - if (!JS_IdToValue(cx, id, &v)) { - return JS_FALSE; - } - - *vp = v; - } else { - // We need to return an [id, value] pair. - if (!OBJ_GET_PROPERTY(cx, JS_GetParent(cx, obj), id, &v)) { - return JS_FALSE; - } - - jsval name; - if (!JS_IdToValue(cx, id, &name)) { - return JS_FALSE; - } - - jsval vec[2] = { name, v }; - JSAutoTempValueRooter tvr(cx, 2, vec); - JSObject *array = JS_NewArrayObject(cx, 2, vec); - if (!array) { - return JS_FALSE; - } - - *vp = OBJECT_TO_JSVAL(array); - } - - JS_SetReservedSlot(cx, obj, 1, INT_TO_JSVAL(idx)); - return JS_TRUE; -} - -static JSClass IteratorClass = { - "XOW iterator", JSCLASS_HAS_RESERVED_SLOTS(3), - JS_PropertyStub, JS_PropertyStub, - JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, IteratorFinalize, - - JSCLASS_NO_OPTIONAL_MEMBERS -}; - - JS_STATIC_DLL_CALLBACK(JSObject *) XPC_XOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly) { - // This is rather ugly: we want to use the trick seen in Enumerate, - // where we use our wrapper's resolve hook to determine if we should - // enumerate a given property. However, we don't want to pollute the - // identifiers with a next method, so we create an object that - // delegates (via the __proto__ link) to a XOW. - - jsval root = JSVAL_NULL; - - // Root v's address so we can set it and have the right value rooted. - JSAutoTempValueRooter tvr(cx, 1, &root); - JSObject *wrapperIter = JS_NewObject(cx, &sXPC_XOW_JSClass.base, nsnull, JS_GetGlobalForObject(cx, obj)); if (!wrapperIter) { return nsnull; } - root = OBJECT_TO_JSVAL(wrapperIter); - - JSObject *iterObj = JS_NewObject(cx, &IteratorClass, wrapperIter, obj); - if (!iterObj) { - return nsnull; - } - - root = OBJECT_TO_JSVAL(iterObj); - - // Do this sooner rather than later to avoid complications in - // IteratorFinalize. - if (!JS_SetReservedSlot(cx, iterObj, 0, PRIVATE_TO_JSVAL(nsnull))) { - return nsnull; - } - - // Initialize iterObj. - if (!JS_DefineFunction(cx, iterObj, "next", (JSNative)IteratorNext, 0, - JSFUN_FAST_NATIVE)) { - return nsnull; - } + JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(wrapperIter)); // Initialize our XOW. JSObject *innerObj = GetWrappedObject(cx, obj); @@ -1142,29 +1040,8 @@ XPC_XOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly) return nsnull; } - // Start enumerating over all of our properties. - do { - if (!XPCWrapper::Enumerate(cx, iterObj, innerObj)) { - return nsnull; - } - } while ((innerObj = JS_GetPrototype(cx, innerObj)) != nsnull); - - JSIdArray *ida = JS_Enumerate(cx, iterObj); - if (!ida) { - return nsnull; - } - - if (!JS_SetReservedSlot(cx, iterObj, 0, PRIVATE_TO_JSVAL(ida)) || - !JS_SetReservedSlot(cx, iterObj, 1, JSVAL_ZERO) || - !JS_SetReservedSlot(cx, iterObj, 2, BOOLEAN_TO_JSVAL(keysonly))) { - return nsnull; - } - - if (!JS_SetPrototype(cx, iterObj, nsnull)) { - return nsnull; - } - - return iterObj; + return XPCWrapper::CreateIteratorObj(cx, wrapperIter, obj, innerObj, + keysonly); } JS_STATIC_DLL_CALLBACK(JSObject *) diff --git a/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp b/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp index fe2f38454045..ba4668a79d71 100644 --- a/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp +++ b/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp @@ -82,6 +82,9 @@ XPC_SJOW_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, JS_STATIC_DLL_CALLBACK(JSBool) XPC_SJOW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp); +JS_STATIC_DLL_CALLBACK(JSObject *) +XPC_SJOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly); + JS_STATIC_DLL_CALLBACK(JSObject *) XPC_SJOW_WrappedObject(JSContext *cx, JSObject *obj); @@ -203,7 +206,7 @@ JSExtendedClass sXPC_SJOW_JSClass = { XPC_SJOW_Equality, nsnull, // outerObject nsnull, // innerObject - nsnull, // iteratorObject + XPC_SJOW_Iterator, XPC_SJOW_WrappedObject, JSCLASS_NO_RESERVED_MEMBERS }; @@ -898,6 +901,17 @@ XPC_SJOW_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, return ThrowException(NS_ERROR_INVALID_ARG, cx); } + if (JS_GET_CLASS(cx, objToWrap) == &sXPC_XOW_JSClass.base) { + // We're being asked to wrap a XOW. By using XPCWrapper::Unwrap, + // we guarantee that the wrapped object is same-origin to us. If + // it isn't, then just wrap the XOW for an added layer of wrapping. + + JSObject *maybeInner = XPCWrapper::Unwrap(cx, objToWrap); + if (maybeInner) { + objToWrap = maybeInner; + } + } + // Check that the caller can access the unsafe object. if (!CanCallerAccess(cx, objToWrap)) { // CanCallerAccess() already threw for us. @@ -954,6 +968,36 @@ XPC_SJOW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) return JS_TRUE; } +JS_STATIC_DLL_CALLBACK(JSObject *) +XPC_SJOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly) +{ + JSObject *innerObj = GetUnsafeObject(cx, obj); + if (!innerObj) { + ThrowException(NS_ERROR_INVALID_ARG, cx); + return nsnull; + } + + // Create our dummy SJOW. + JSObject *wrapperIter = ::JS_NewObject(cx, &sXPC_SJOW_JSClass.base, nsnull, + nsnull); + if (!wrapperIter || + !::JS_SetParent(cx, wrapperIter, innerObj) || + !::JS_SetPrototype(cx, wrapperIter, nsnull)) { + return nsnull; + } + + if (!::JS_SetReservedSlot(cx, wrapperIter, XPC_SJOW_SLOT_IS_RESOLVING, + BOOLEAN_TO_JSVAL(JS_FALSE))) { + return JS_FALSE; + } + + JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(wrapperIter)); + + // Initialize the wrapper. + return XPCWrapper::CreateIteratorObj(cx, wrapperIter, obj, innerObj, + keysonly); +} + JS_STATIC_DLL_CALLBACK(JSObject *) XPC_SJOW_WrappedObject(JSContext *cx, JSObject *obj) { diff --git a/js/src/xpconnect/src/XPCWrapper.cpp b/js/src/xpconnect/src/XPCWrapper.cpp index efff4d1bc2c7..c219c77ba409 100644 --- a/js/src/xpconnect/src/XPCWrapper.cpp +++ b/js/src/xpconnect/src/XPCWrapper.cpp @@ -54,6 +54,133 @@ XPCWrapper::sNumSlots = 2; JSNative XPCWrapper::sEvalNative = nsnull; +JS_STATIC_DLL_CALLBACK(void) +IteratorFinalize(JSContext *cx, JSObject *obj) +{ + jsval v; + JS_GetReservedSlot(cx, obj, 0, &v); + + JSIdArray *ida = reinterpret_cast(JSVAL_TO_PRIVATE(v)); + if (ida) { + JS_DestroyIdArray(cx, ida); + } +} + +JS_STATIC_DLL_CALLBACK(JSBool) +IteratorNext(JSContext *cx, uintN argc, jsval *vp) +{ + JSObject *obj = JSVAL_TO_OBJECT(vp[1]); + jsval v; + + JS_GetReservedSlot(cx, obj, 0, &v); + JSIdArray *ida = reinterpret_cast(JSVAL_TO_PRIVATE(v)); + + JS_GetReservedSlot(cx, obj, 1, &v); + jsint idx = JSVAL_TO_INT(v); + + if (idx == ida->length) { + return JS_ThrowStopIteration(cx); + } + + JS_GetReservedSlot(cx, obj, 2, &v); + jsid id = ida->vector[idx++]; + if (JSVAL_TO_BOOLEAN(v)) { + if (!JS_IdToValue(cx, id, &v)) { + return JS_FALSE; + } + + *vp = v; + } else { + // We need to return an [id, value] pair. + if (!OBJ_GET_PROPERTY(cx, JS_GetParent(cx, obj), id, &v)) { + return JS_FALSE; + } + + jsval name; + if (!JS_IdToValue(cx, id, &name)) { + return JS_FALSE; + } + + jsval vec[2] = { name, v }; + JSAutoTempValueRooter tvr(cx, 2, vec); + JSObject *array = JS_NewArrayObject(cx, 2, vec); + if (!array) { + return JS_FALSE; + } + + *vp = OBJECT_TO_JSVAL(array); + } + + JS_SetReservedSlot(cx, obj, 1, INT_TO_JSVAL(idx)); + return JS_TRUE; +} + +static JSClass IteratorClass = { + "XOW iterator", JSCLASS_HAS_RESERVED_SLOTS(3), + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, + JS_ConvertStub, IteratorFinalize, + + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +// static +JSObject * +XPCWrapper::CreateIteratorObj(JSContext *cx, JSObject *tempWrapper, + JSObject *wrapperObj, JSObject *innerObj, + JSBool keysonly) +{ + // This is rather ugly: we want to use the trick seen in Enumerate, + // where we use our wrapper's resolve hook to determine if we should + // enumerate a given property. However, we don't want to pollute the + // identifiers with a next method, so we create an object that + // delegates (via the __proto__ link) to the wrapper. + + JSObject *iterObj = JS_NewObject(cx, &IteratorClass, tempWrapper, wrapperObj); + if (!iterObj) { + return nsnull; + } + + JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(iterObj)); + + // Do this sooner rather than later to avoid complications in + // IteratorFinalize. + if (!JS_SetReservedSlot(cx, iterObj, 0, PRIVATE_TO_JSVAL(nsnull))) { + return nsnull; + } + + // Initialize iterObj. + if (!JS_DefineFunction(cx, iterObj, "next", (JSNative)IteratorNext, 0, + JSFUN_FAST_NATIVE)) { + return nsnull; + } + + // Start enumerating over all of our properties. + do { + if (!XPCWrapper::Enumerate(cx, iterObj, innerObj)) { + return nsnull; + } + } while ((innerObj = JS_GetPrototype(cx, innerObj)) != nsnull); + + JSIdArray *ida = JS_Enumerate(cx, iterObj); + if (!ida) { + return nsnull; + } + + if (!JS_SetReservedSlot(cx, iterObj, 0, PRIVATE_TO_JSVAL(ida)) || + !JS_SetReservedSlot(cx, iterObj, 1, JSVAL_ZERO) || + !JS_SetReservedSlot(cx, iterObj, 2, BOOLEAN_TO_JSVAL(keysonly))) { + return nsnull; + } + + if (!JS_SetPrototype(cx, iterObj, nsnull)) { + return nsnull; + } + + return iterObj; +} + // static JSBool XPCWrapper::AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) diff --git a/js/src/xpconnect/src/XPCWrapper.h b/js/src/xpconnect/src/XPCWrapper.h index 6600ae5beb65..0e78f23d4ddd 100644 --- a/js/src/xpconnect/src/XPCWrapper.h +++ b/js/src/xpconnect/src/XPCWrapper.h @@ -177,8 +177,7 @@ public: } /** - * Unwraps an XPCSafeJSObjectWrapper or an XPCCrossOriginWrapper into its - * wrapped native. + * Unwraps a XPCCrossOriginWrapper into its wrapped native. */ static JSObject *Unwrap(JSContext *cx, JSObject *wrapper) { if (JS_GET_CLASS(cx, wrapper) != &sXPC_XOW_JSClass.base) { @@ -231,6 +230,18 @@ public: : XPC_XOW_WrapFunction(cx, wrapperObj, funobj, v); } + /** + * Creates an iterator object that walks up the prototype of + * wrappedObj. This is suitable for for-in loops over a wrapper. If + * a property is not supposed to be reflected, the resolve hook + * is expected to censor it. tempWrapper must be rooted already. + */ + static JSObject *CreateIteratorObj(JSContext *cx, + JSObject *tempWrapper, + JSObject *wrapperObj, + JSObject *innerObj, + JSBool keysonly); + /** * Called for the common part of adding a property to obj. */ diff --git a/js/src/xpconnect/tests/mochitest/test_wrappers.html b/js/src/xpconnect/tests/mochitest/test_wrappers.html index 4a357b25c702..3637fba98e5f 100644 --- a/js/src/xpconnect/tests/mochitest/test_wrappers.html +++ b/js/src/xpconnect/tests/mochitest/test_wrappers.html @@ -26,7 +26,23 @@ is(answer.sort().toString(), expected.sort().toString(), - 'enumeration does not work'); + 'enumeration over XOWs walks the prototype chain'); + + answer = []; + let (obj = { a: 3 }) { + for (let i in XPCSafeJSObjectWrapper({ __proto__: obj})) + answer.push(i); + is(answer.toString(), 'a', + 'enumeration over SJOWs walks the prototype chain'); + } + + answer = []; + for (let i in XPCSafeJSObjectWrapper(location)) + answer.push(i); + + is(answer.sort().toString(), + expected.sort().toString(), + 'enumeration over SJOWs walks the prototype chain and works over XOWs'); var origProto = window.__proto__; try {