Bug 376957 - Prevent data leaks from cross-site JSON loads (JavaScript literals), by making the global name bindings ReadOnly/DontDelete and making [] and {} use the global bindings. Still more that can be done here, but this covers a lot of the fix. r+a=brendan

git-svn-id: svn://10.0.0.236/trunk@240378 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
jwalden%mit.edu 2007-12-04 03:05:24 +00:00
parent 97e3ce1112
commit 680af8da29
19 changed files with 124 additions and 80 deletions

View File

@ -2672,7 +2672,9 @@ JS_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto,
named = OBJ_DEFINE_PROPERTY(cx, obj, ATOM_TO_JSID(atom),
OBJECT_TO_JSVAL(proto),
NULL, NULL,
(clasp->flags & JSCLASS_IS_ANONYMOUS)
(clasp->flags &
(JSCLASS_IS_ANONYMOUS |
JSCLASS_FIXED_BINDING))
? JSPROP_READONLY | JSPROP_PERMANENT
: 0,
NULL);
@ -2683,7 +2685,10 @@ JS_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto,
ctor = proto;
} else {
/* Define the constructor function in obj's scope. */
fun = js_DefineFunction(cx, obj, atom, constructor, nargs, 0);
fun = js_DefineFunction(cx, obj, atom, constructor, nargs,
(clasp->flags & JSCLASS_FIXED_BINDING)
? JSPROP_READONLY | JSPROP_PERMANENT
: 0);
named = (fun != NULL);
if (!fun)
goto bad;

View File

@ -1256,6 +1256,12 @@ struct JSExtendedClass {
/* Indicates that JSClass.mark is a tracer with JSTraceOp type. */
#define JSCLASS_MARK_IS_TRACE (1<<(JSCLASS_HIGH_FLAGS_SHIFT+3))
/*
* Indicates that the class object defined on the object provided to
* JS_InitClass should be readonly and permanent.
*/
#define JSCLASS_FIXED_BINDING (1<<(JSCLASS_HIGH_FLAGS_SHIFT+4))
/*
* ECMA-262 requires that most constructors used internally create objects
* with "the original Foo.prototype value" as their [[Prototype]] (__proto__)

View File

@ -494,7 +494,8 @@ array_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
JSClass js_ArrayClass = {
"Array",
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array) |
JSCLASS_FIXED_BINDING,
array_addProperty, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, array_convert, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS

View File

@ -56,7 +56,8 @@
JSClass js_BooleanClass = {
"Boolean",
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Boolean),
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Boolean) |
JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS

View File

@ -483,7 +483,8 @@ msFromTime(jsdouble t)
JSClass js_DateClass = {
js_Date_str,
JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_HAS_CACHED_PROTO(JSProto_Date) |
JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS

View File

@ -6035,11 +6035,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* but use a stack slot for t and avoid dup'ing and popping it via
* the JSOP_NEWINIT and JSOP_INITELEM bytecodes.
*/
ale = js_IndexAtom(cx, CLASS_ATOM(cx, Array), &cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_INDEX_OP(JSOP_CALLNAME, ALE_INDEX(ale));
if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0)
if (js_Emit2(cx, cg, JSOP_NEWINIT, (jsbytecode) JSProto_Array) < 0)
return JS_FALSE;
pn2 = pn->pn_head;
@ -6108,11 +6104,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* but use a stack slot for t and avoid dup'ing and popping it via
* the JSOP_NEWINIT and JSOP_INITELEM bytecodes.
*/
ale = js_IndexAtom(cx, CLASS_ATOM(cx, Object), &cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_INDEX_OP(JSOP_CALLNAME, ALE_INDEX(ale));
if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0)
if (js_Emit2(cx, cg, JSOP_NEWINIT, (jsbytecode) JSProto_Object) < 0)
return JS_FALSE;
pn2 = pn->pn_head;

View File

@ -1409,7 +1409,8 @@ fun_reserveSlots(JSContext *cx, JSObject *obj)
JS_FRIEND_DATA(JSClass) js_FunctionClass = {
js_Function_str,
JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(2) |
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Function),
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Function) |
JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub,
fun_getProperty, JS_PropertyStub,
fun_enumerate, (JSResolveOp)fun_resolve,

View File

@ -3478,8 +3478,6 @@ interrupt:
BEGIN_CASE(JSOP_NEW)
/* Get immediate argc and find the constructor function. */
argc = GET_ARGC(pc);
do_new:
SAVE_SP_AND_PC(fp);
vp = sp - (2 + argc);
JS_ASSERT(vp >= fp->spbase);
@ -3547,7 +3545,7 @@ interrupt:
BEGIN_CASE(JSOP_ELEMDEC)
/*
* Delay fetching of id until we have the object to ensure
* the proper evaluation oder. See bug 372331.
* the proper evaluation order. See bug 372331.
*/
id = 0;
i = -2;
@ -4690,6 +4688,7 @@ interrupt:
ok = ComputeGlobalThis(cx, sp); \
if (!ok) \
goto out; \
JS_ASSERT(!JSVAL_IS_NULL(sp[-1]) && !JSVAL_IS_VOID(sp[-1])); \
JS_END_MACRO
BEGIN_CASE(JSOP_GLOBALTHIS)
@ -5292,9 +5291,16 @@ interrupt:
#endif /* JS_HAS_GETTER_SETTER */
BEGIN_CASE(JSOP_NEWINIT)
argc = 0;
i = GET_INT8(pc);
JS_ASSERT(i == JSProto_Array || i == JSProto_Object);
obj = (i == JSProto_Array)
? js_NewArrayObject(cx, 0, NULL)
: js_NewObject(cx, &js_ObjectClass, NULL, NULL);
if (!obj)
goto out;
PUSH_OPND(OBJECT_TO_JSVAL(obj));
fp->sharpDepth++;
goto do_new;
END_CASE(JSOP_NEWINIT)
BEGIN_CASE(JSOP_ENDINIT)
if (--fp->sharpDepth == 0)

View File

@ -119,7 +119,7 @@ js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
JSClass js_IteratorClass = {
"Iterator",
JSCLASS_HAS_RESERVED_SLOTS(2) | /* slots for state and flags */
JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) | JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS

View File

@ -506,7 +506,8 @@ js_InitMathClass(JSContext *cx, JSObject *obj)
{
JSObject *Math;
Math = JS_DefineObject(cx, obj, js_Math_str, &js_MathClass, NULL, 0);
Math = JS_DefineObject(cx, obj, js_Math_str, &js_MathClass, NULL,
JSPROP_READONLY | JSPROP_PERMANENT);
if (!Math)
return NULL;
if (!JS_DefineFunctions(cx, Math, math_static_methods))

View File

@ -157,7 +157,8 @@ static JSFunctionSpec number_functions[] = {
JSClass js_NumberClass = {
js_Number_str,
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Number),
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Number) |
JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS

View File

@ -114,7 +114,7 @@ JS_FRIEND_DATA(JSObjectOps) js_ObjectOps = {
JSClass js_ObjectClass = {
js_Object_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS

View File

@ -368,7 +368,6 @@ js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc,
goto print_int;
case JOF_INT8:
JS_ASSERT(op == JSOP_INT8);
i = GET_INT8(pc);
goto print_int;
@ -4239,12 +4238,9 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
case JSOP_NEWINIT:
{
JSBool isArray;
i = GET_INT8(pc);
JS_ASSERT(i == JSProto_Array || i == JSProto_Object);
LOCAL_ASSERT(ss->top >= 2);
(void) PopOff(ss, op);
lval = POP_STR();
isArray = (*lval == 'A');
todo = ss->sprinter.offset;
#if JS_HAS_SHARP_VARS
op = (JSOp)pc[len];
@ -4252,12 +4248,12 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
pc += len;
cs = &js_CodeSpec[op];
len = cs->length;
i = (jsint) GET_UINT16(pc);
if (Sprint(&ss->sprinter, "#%u=", (unsigned) i) < 0)
if (Sprint(&ss->sprinter, "#%u=",
(unsigned) (jsint) GET_UINT16(pc)) < 0)
return NULL;
}
#endif /* JS_HAS_SHARP_VARS */
if (isArray) {
if (i == JSProto_Array) {
++ss->inArrayInit;
if (SprintCString(&ss->sprinter, "[") < 0)
return NULL;
@ -4269,6 +4265,9 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
}
case JSOP_ENDINIT:
{
JSBool inArray;
op = JSOP_NOP; /* turn off parens */
rval = POP_STR();
sn = js_GetSrcNote(jp->script, pc);
@ -4276,35 +4275,53 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
/* Skip any #n= prefix to find the opening bracket. */
for (xval = rval; *xval != '[' && *xval != '{'; xval++)
continue;
if (*xval == '[')
inArray = (*xval == '[');
if (inArray)
--ss->inArrayInit;
todo = Sprint(&ss->sprinter, "%s%s%c",
rval,
(sn && SN_TYPE(sn) == SRC_CONTINUE) ? ", " : "",
(*xval == '[') ? ']' : '}');
inArray ? ']' : '}');
break;
}
case JSOP_INITPROP:
{
JSBool isFirst;
LOAD_ATOM(0);
xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
(jschar)
(ATOM_IS_IDENTIFIER(atom) ? 0 : '\''));
if (!xval)
return NULL;
isFirst = (ss->opcodes[ss->top - 2] == JSOP_NEWINIT);
rval = POP_STR();
lval = POP_STR();
do_initprop:
#ifdef OLD_GETTER_SETTER
todo = Sprint(&ss->sprinter, "%s%s%s%s%s:%s",
todo = Sprint(&ss->sprinter, "%s%s",
lval,
(lval[1] != '\0') ? ", " : "",
xval,
(lastop == JSOP_GETTER || lastop == JSOP_SETTER)
? " " : "",
(lastop == JSOP_GETTER) ? js_getter_str :
(lastop == JSOP_SETTER) ? js_setter_str :
"",
rval);
isFirst ? "" : ", ");
}
do_initprop:
/*
* NB: From here onward we must not overwrite todo, which must
* be the offset just before we print the [ or { which starts
* this literal. This is important both for JSOP_INITPROP and
* for JSOP_INITELEM (whose control flow can extend through
* here via a goto).
*/
#ifdef OLD_GETTER_SETTER
if (Sprint(&ss->sprinter, "%s%s%s%s%s:%s",
xval,
(lastop == JSOP_GETTER || lastop == JSOP_SETTER)
? " " : "",
(lastop == JSOP_GETTER) ? js_getter_str :
(lastop == JSOP_SETTER) ? js_setter_str :
"",
rval) < 0) {
return NULL;
}
#else
if (lastop == JSOP_GETTER || lastop == JSOP_SETTER) {
if (!atom ||
@ -4313,12 +4330,13 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
ATOM_IS_KEYWORD(atom) ||
(ss->opcodes[ss->top+1] != JSOP_ANONFUNOBJ &&
ss->opcodes[ss->top+1] != JSOP_NAMEDFUNOBJ)) {
todo = Sprint(&ss->sprinter, "%s%s%s %s: %s", lval,
(lval[1] != '\0') ? ", " : "", xval,
(lastop == JSOP_GETTER) ? js_getter_str :
(lastop == JSOP_SETTER) ? js_setter_str :
"",
rval);
if (Sprint(&ss->sprinter, "%s %s: %s", xval,
(lastop == JSOP_GETTER) ? js_getter_str :
(lastop == JSOP_SETTER) ? js_setter_str :
"",
rval) < 0) {
return NULL;
}
} else {
const char *end = rval + strlen(rval);
@ -4328,29 +4346,30 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
LOCAL_ASSERT(rval[8] == ' ');
rval += 8 + 1;
LOCAL_ASSERT(*end ? *end == ')' : end[-1] == '}');
todo = Sprint(&ss->sprinter, "%s%s%s %s%s%.*s",
lval,
(lval[1] != '\0') ? ", " : "",
(lastop == JSOP_GETTER)
? js_get_str : js_set_str,
xval,
(rval[0] != '(') ? " " : "",
end - rval, rval);
if (Sprint(&ss->sprinter, "%s %s%s%.*s",
(lastop == JSOP_GETTER)
? js_get_str : js_set_str,
xval,
(rval[0] != '(') ? " " : "",
end - rval, rval) < 0) {
return NULL;
}
}
} else {
todo = Sprint(&ss->sprinter, "%s%s%s: %s",
lval,
(lval[1] != '\0') ? ", " : "",
xval,
rval);
if (Sprint(&ss->sprinter, "%s: %s", xval, rval) < 0)
return NULL;
}
#endif
break;
case JSOP_INITELEM:
{
JSBool isFirst;
/* Turn off most parens (all if there's only one initialiser). */
LOCAL_ASSERT(pc + len < endpc);
op = (GetStr(ss, ss->top - 3)[1] == '\0' &&
isFirst = (ss->opcodes[ss->top - 3] == JSOP_NEWINIT);
op = (isFirst &&
GetStr(ss, ss->top - 2)[0] == '0' &&
(JSOp) pc[len] == JSOP_ENDINIT)
? JSOP_NOP
@ -4362,15 +4381,21 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
xval = POP_STR();
lval = POP_STR();
sn = js_GetSrcNote(jp->script, pc);
/* NB: must not overwrite todo after this! */
todo = Sprint(&ss->sprinter, "%s%s",
lval,
isFirst ? "" : ", ");
if (todo < 0)
break;
if (sn && SN_TYPE(sn) == SRC_INITPROP) {
atom = NULL;
goto do_initprop;
}
todo = Sprint(&ss->sprinter, "%s%s%s",
lval,
(lval[1] != '\0' || *xval != '0') ? ", " : "",
rval);
if (SprintCString(&ss->sprinter, rval) < 0)
return NULL;
break;
}
#if JS_HAS_SHARP_VARS
case JSOP_DEFSHARP:

View File

@ -120,7 +120,7 @@ typedef enum JSOpLength {
#define JOF_PARENHEAD (1U<<21) /* opcode consumes value of expression in
parenthesized statement head */
#define JOF_INVOKE (1U<<22) /* JSOP_CALL, JSOP_NEW, JSOP_EVAL */
#define JOF_TMPSLOT (1U<<23) /* interpreter uses extra temporray slot
#define JOF_TMPSLOT (1U<<23) /* interpreter uses extra temporary slot
to root intermediate objects */
#define JOF_TMPSLOT_SHIFT 23

View File

@ -214,7 +214,7 @@ OPDEF(JSOP_SETVAR, 87, "setvar", NULL, 3, 1, 1, 3, JOF_QVAR |
OPDEF(JSOP_UINT16, 88, "uint16", NULL, 3, 0, 1, 16, JOF_UINT16)
/* Object and array literal support. */
OPDEF(JSOP_NEWINIT, 89, "newinit", NULL, 1, 2, 1, 0, JOF_BYTE)
OPDEF(JSOP_NEWINIT, 89, "newinit", NULL, 2, 0, 1, 19, JOF_INT8)
OPDEF(JSOP_ENDINIT, 90, "endinit", NULL, 1, 0, 0, 19, JOF_BYTE)
OPDEF(JSOP_INITPROP, 91, "initprop", NULL, 3, 1, 0, 3, JOF_ATOM|JOF_PROP|JOF_DETECTING)
OPDEF(JSOP_INITELEM, 92, "initelem", NULL, 1, 2, 0, 3, JOF_BYTE |JOF_ELEM|JOF_DETECTING)

View File

@ -3866,7 +3866,8 @@ regexp_trace(JSTracer *trc, JSObject *obj)
JSClass js_RegExpClass = {
js_RegExp_str,
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1) |
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp) |
JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub,
regexp_getProperty, regexp_setProperty,
JS_EnumerateStub, JS_ResolveStub,

View File

@ -597,7 +597,7 @@ str_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
JSClass js_StringClass = {
js_String_str,
JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE |
JSCLASS_HAS_CACHED_PROTO(JSProto_String),
JSCLASS_HAS_CACHED_PROTO(JSProto_String) | JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, str_getProperty, JS_PropertyStub,
str_enumerate, (JSResolveOp)str_resolve, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS

View File

@ -202,7 +202,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32 id);
* before deserialization of bytecode. If the saved version does not match
* the current version, abort deserialization and invalidate the file.
*/
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 16)
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 17)
/*
* Library-private functions.

View File

@ -237,7 +237,8 @@ namespace_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
JS_FRIEND_DATA(JSExtendedClass) js_NamespaceClass = {
{ "Namespace",
JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED |
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace),
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace) |
JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, namespace_getProperty, NULL,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, namespace_finalize,
NULL, NULL, NULL, NULL,
@ -445,7 +446,8 @@ qname_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
JS_FRIEND_DATA(JSExtendedClass) js_QNameClass = {
{ "QName",
JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED |
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_QName),
JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_QName) |
JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, qname_getProperty, NULL,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, qname_finalize,
NULL, NULL, NULL, NULL,
@ -5559,7 +5561,7 @@ xml_getObjectOps(JSContext *cx, JSClass *clasp)
JS_FRIEND_DATA(JSClass) js_XMLClass = {
js_XML_str,
JSCLASS_HAS_PRIVATE | JSCLASS_MARK_IS_TRACE |
JSCLASS_HAS_CACHED_PROTO(JSProto_XML),
JSCLASS_HAS_CACHED_PROTO(JSProto_XML) | JSCLASS_FIXED_BINDING,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, xml_finalize,
xml_getObjectOps, NULL, NULL, NULL,
@ -7676,7 +7678,8 @@ js_InitXMLClass(JSContext *cx, JSObject *obj)
return NULL;
/* Define the XMLList function and give it the same prototype as XML. */
fun = JS_DefineFunction(cx, obj, js_XMLList_str, XMLList, 1, 0);
fun = JS_DefineFunction(cx, obj, js_XMLList_str, XMLList, 1,
JSPROP_READONLY | JSPROP_PERMANENT);
if (!fun)
return NULL;
if (!js_SetClassPrototype(cx, fun->object, proto,