Implement an iterator hook for cross origin wrappers to avoid wrongly walking up the prototype chain during enumeration. bug 390947, r+a=brendan sr=jst

This commit is contained in:
mrbkap@gmail.com 2007-09-30 17:31:22 -07:00
parent 20e5bb075c
commit 3b183e4e87
8 changed files with 220 additions and 35 deletions

View File

@ -65,6 +65,7 @@
#include "jsfun.h"
#include "jsgc.h"
#include "jsinterp.h"
#include "jsiter.h"
#include "jslock.h"
#include "jsmath.h"
#include "jsnum.h"
@ -86,10 +87,6 @@
#include "jsxml.h"
#endif
#if JS_HAS_GENERATORS
#include "jsiter.h"
#endif
#ifdef HAVE_VA_LIST_AS_ARRAY
#define JS_ADDRESSOF_VA_LIST(ap) ((va_list *)(ap))
#else
@ -5549,6 +5546,12 @@ JS_ThrowReportedError(JSContext *cx, const char *message,
return js_ErrorToException(cx, message, reportp);
}
JS_PUBLIC_API(JSBool)
JS_ThrowStopIteration(JSContext *cx)
{
return js_ThrowStopIteration(cx);
}
#ifdef JS_THREADSAFE
/*
* Get the owning thread id of a context. Returns 0 if the context is not

View File

@ -2465,6 +2465,12 @@ extern JS_PUBLIC_API(JSBool)
JS_ThrowReportedError(JSContext *cx, const char *message,
JSErrorReport *reportp);
/*
* Throws a StopIteration exception on cx.
*/
extern JS_PUBLIC_API(JSBool)
JS_ThrowStopIteration(JSContext *cx);
#ifdef JS_THREADSAFE
/*

View File

@ -270,8 +270,8 @@ IteratorNextImpl(JSContext *cx, JSObject *obj, jsval *rval)
return JS_TRUE;
}
static JSBool
js_ThrowStopIteration(JSContext *cx, JSObject *obj)
JSBool
js_ThrowStopIteration(JSContext *cx)
{
jsval v;
@ -295,7 +295,7 @@ iterator_next(JSContext *cx, uintN argc, jsval *vp)
if (*vp == JSVAL_HOLE) {
*vp = JSVAL_NULL;
js_ThrowStopIteration(cx, obj);
js_ThrowStopIteration(cx);
return JS_FALSE;
}
return JS_TRUE;
@ -916,7 +916,7 @@ SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
*rval = JSVAL_VOID;
return JS_TRUE;
}
return js_ThrowStopIteration(cx, obj);
return js_ThrowStopIteration(cx);
}
/*
@ -990,7 +990,7 @@ generator_op(JSContext *cx, JSGeneratorOp op, jsval *vp)
switch (op) {
case JSGENOP_NEXT:
case JSGENOP_SEND:
return js_ThrowStopIteration(cx, obj);
return js_ThrowStopIteration(cx);
case JSGENOP_THROW:
JS_SetPendingException(cx, vp[2]);
return JS_FALSE;

View File

@ -77,6 +77,9 @@ js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval);
extern void
js_CloseNativeIterator(JSContext *cx, JSObject *iterobj);
extern JSBool
js_ThrowStopIteration(JSContext *cx);
#if JS_HAS_GENERATORS
/*

View File

@ -41,6 +41,7 @@
#include "nsDOMError.h"
#include "jsdbgapi.h"
#include "jsobj.h" // For OBJ_GET_PROPERTY.
#include "jscntxt.h" // For JSAutoTempValueRooter.
#include "XPCWrapper.h"
#include "nsIDOMWindow.h"
#include "nsIDOMWindowCollection.h"
@ -87,6 +88,9 @@ XPC_XOW_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
JS_STATIC_DLL_CALLBACK(JSBool)
XPC_XOW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
JS_STATIC_DLL_CALLBACK(JSObject *)
XPC_XOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly);
JSExtendedClass sXPC_XOW_JSClass = {
// JSClass (JSExtendedClass.base) initialization
{ "XPCCrossOriginWrapper",
@ -101,8 +105,13 @@ JSExtendedClass sXPC_XOW_JSClass = {
nsnull, nsnull,
nsnull, nsnull
},
// JSExtendedClass initialization
XPC_XOW_Equality
XPC_XOW_Equality,
nsnull, // outerObject
nsnull, // innerObject
XPC_XOW_Iterator,
JSCLASS_NO_RESERVED_MEMBERS
};
// The slot that we stick our scope into.
@ -813,10 +822,10 @@ XPC_XOW_Finalize(JSContext *cx, JSObject *obj)
// Now that we have our scope, see if it's going away. If it is,
// then our work here is going to be done when we destroy the scope
// entirely.
// entirely. Scope can be null if we're an enumerating XOW.
XPCWrappedNativeScope *scope = reinterpret_cast<XPCWrappedNativeScope *>
(JSVAL_TO_PRIVATE(scopeVal));
if (XPCWrappedNativeScope::IsDyingScope(scope)) {
if (!scope || XPCWrappedNativeScope::IsDyingScope(scope)) {
return;
}
@ -937,6 +946,160 @@ 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<JSIdArray *>(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<JSIdArray *>(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;
}
// Initialize our XOW.
JSObject *innerObj = GetWrappedObject(cx, obj);
if (!innerObj) {
ThrowException(NS_ERROR_INVALID_ARG, cx);
return nsnull;
}
jsval v = OBJECT_TO_JSVAL(innerObj);
if (!JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sWrappedObjSlot, v) ||
!JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sResolvingSlot,
JSVAL_FALSE) ||
!JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sNumSlots,
PRIVATE_TO_JSVAL(nsnull))) {
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;
}
JS_STATIC_DLL_CALLBACK(JSBool)
XPC_XOW_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)

View File

@ -106,30 +106,38 @@ XPCWrapper::Enumerate(JSContext *cx, JSObject *wrapperObj, JSObject *innerObj)
JSBool ok = JS_TRUE;
do {
JSIdArray *ida = JS_Enumerate(cx, innerObj);
if (!ida) {
return JS_FALSE;
JSIdArray *ida = JS_Enumerate(cx, innerObj);
if (!ida) {
return JS_FALSE;
}
for (jsint i = 0, n = ida->length; i < n; i++) {
JSObject *pobj;
JSProperty *prop;
// Let OBJ_LOOKUP_PROPERTY, in particular our NewResolve hook,
// figure out whether this id should be reflected.
ok = OBJ_LOOKUP_PROPERTY(cx, wrapperObj, ida->vector[i], &pobj, &prop);
if (!ok) {
break;
}
for (jsint i = 0, n = ida->length; i < n; i++) {
JSObject *pobj;
JSProperty *prop;
// Let OBJ_LOOKUP_PROPERTY, in particular our NewResolve hook,
// figure out whether this id should be reflected.
ok = OBJ_LOOKUP_PROPERTY(cx, wrapperObj, ida->vector[i], &pobj, &prop);
if (!ok) {
break;
}
if (prop) {
OBJ_DROP_PROPERTY(cx, pobj, prop);
}
if (prop) {
OBJ_DROP_PROPERTY(cx, pobj, prop);
}
JS_DestroyIdArray(cx, ida);
} while (ok && (innerObj = JS_GetPrototype(cx, innerObj)) != nsnull);
if (pobj != wrapperObj) {
ok = OBJ_DEFINE_PROPERTY(cx, wrapperObj, ida->vector[i], JSVAL_VOID,
nsnull, nsnull, JSPROP_ENUMERATE | JSPROP_SHARED,
nsnull);
}
if (!ok) {
break;
}
}
JS_DestroyIdArray(cx, ida);
return ok;
}

View File

@ -58,13 +58,14 @@ const char* XPCJSRuntime::mStrings[] = {
"Function", // IDX_FUNCTION
"prototype", // IDX_PROTOTYPE
"createInstance", // IDX_CREATE_INSTANCE
"item" // IDX_ITEM
"item", // IDX_ITEM
"__proto__", // IDX_PROTO
"__iterator__" // IDX_ITERATOR
#ifdef XPC_IDISPATCH_SUPPORT
, "GeckoActiveXObject" // IDX_ACTIVEX_OBJECT
, "COMObject" // IDX_COMOBJECT
, "supports" // IDX_ACTIVEX_SUPPORTS
#endif
, "__proto__" // IDX_PROTO
};
/***************************************************************************/

View File

@ -649,12 +649,13 @@ public:
IDX_PROTOTYPE ,
IDX_CREATE_INSTANCE ,
IDX_ITEM ,
IDX_PROTO ,
IDX_ITERATOR ,
#ifdef XPC_IDISPATCH_SUPPORT
IDX_ACTIVEX_OBJECT ,
IDX_COM_OBJECT ,
IDX_ACTIVEX_SUPPORTS ,
#endif
IDX_PROTO ,
IDX_TOTAL_COUNT // just a count of the above
};