Bug 514581 - ES5: fun.caller and fun.arguments must throw when fun is strict-mode code. r=jimb

This commit is contained in:
Jeff Walden
2010-08-02 23:52:12 -07:00
parent fdcd258697
commit cb4843d90a
9 changed files with 212 additions and 38 deletions

View File

@@ -331,3 +331,4 @@ MSG_DEF(JSMSG_BAD_GET_SET_FIELD, 248, 1, JSEXN_TYPEERR, "property descripto
MSG_DEF(JSMSG_BAD_PROXY_FIX, 249, 0, JSEXN_TYPEERR, "proxy was fixed while executing the handler") MSG_DEF(JSMSG_BAD_PROXY_FIX, 249, 0, JSEXN_TYPEERR, "proxy was fixed while executing the handler")
MSG_DEF(JSMSG_INVALID_EVAL_SCOPE_ARG, 250, 0, JSEXN_EVALERR, "invalid eval scope argument") MSG_DEF(JSMSG_INVALID_EVAL_SCOPE_ARG, 250, 0, JSEXN_EVALERR, "invalid eval scope argument")
MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS, 251, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}") MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS, 251, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}")
MSG_DEF(JSMSG_THROW_TYPE_ERROR, 252, 0, JSEXN_TYPEERR, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")

View File

@@ -1664,6 +1664,12 @@ struct JSClass {
#define JSCLASS_MARK_IS_TRACE (1<<(JSCLASS_HIGH_FLAGS_SHIFT+3)) #define JSCLASS_MARK_IS_TRACE (1<<(JSCLASS_HIGH_FLAGS_SHIFT+3))
#define JSCLASS_INTERNAL_FLAG2 (1<<(JSCLASS_HIGH_FLAGS_SHIFT+4)) #define JSCLASS_INTERNAL_FLAG2 (1<<(JSCLASS_HIGH_FLAGS_SHIFT+4))
/* Additional global reserved slots, beyond those for standard prototypes. */
#define JSRESERVED_GLOBAL_SLOTS_COUNT 3
#define JSRESERVED_GLOBAL_COMPARTMENT (JSProto_LIMIT * 3)
#define JSRESERVED_GLOBAL_THIS (JSRESERVED_GLOBAL_COMPARTMENT + 1)
#define JSRESERVED_GLOBAL_THROWTYPEERROR (JSRESERVED_GLOBAL_THIS + 1)
/* /*
* ECMA-262 requires that most constructors used internally create objects * ECMA-262 requires that most constructors used internally create objects
* with "the original Foo.prototype value" as their [[Prototype]] (__proto__) * with "the original Foo.prototype value" as their [[Prototype]] (__proto__)
@@ -1676,10 +1682,8 @@ struct JSClass {
* prevously allowed, but is now an ES5 violation and thus unsupported. * prevously allowed, but is now an ES5 violation and thus unsupported.
*/ */
#define JSCLASS_GLOBAL_FLAGS \ #define JSCLASS_GLOBAL_FLAGS \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSProto_LIMIT * 3 + 2)) (JSCLASS_IS_GLOBAL | \
JSCLASS_HAS_RESERVED_SLOTS(JSProto_LIMIT * 3 + JSRESERVED_GLOBAL_SLOTS_COUNT))
#define JSRESERVED_GLOBAL_COMPARTMENT (JSProto_LIMIT * 3)
#define JSRESERVED_GLOBAL_THIS (JSRESERVED_GLOBAL_COMPARTMENT + 1)
/* Fast access to the original value of each standard class's prototype. */ /* Fast access to the original value of each standard class's prototype. */
#define JSCLASS_CACHED_PROTO_SHIFT (JSCLASS_HIGH_FLAGS_SHIFT + 8) #define JSCLASS_CACHED_PROTO_SHIFT (JSCLASS_HIGH_FLAGS_SHIFT + 8)

View File

@@ -87,6 +87,12 @@
using namespace js; using namespace js;
inline JSObject *
JSObject::getThrowTypeError() const
{
return &getGlobal()->getReservedSlot(JSRESERVED_GLOBAL_THROWTYPEERROR).toObject();
}
JSBool JSBool
js_GetArgsValue(JSContext *cx, JSStackFrame *fp, Value *vp) js_GetArgsValue(JSContext *cx, JSStackFrame *fp, Value *vp)
{ {
@@ -1385,8 +1391,9 @@ fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
* Function.prototype object (we use JSPROP_PERMANENT with JSPROP_SHARED * Function.prototype object (we use JSPROP_PERMANENT with JSPROP_SHARED
* to make it appear so). * to make it appear so).
* *
* This code couples tightly to the attributes for lazy_function_props[] * This code couples tightly to the attributes for lazyFunctionDataProps[]
* initializers above, and to js_SetProperty and js_HasOwnProperty. * and poisonPillProps[] initializers below, and to js_SetProperty and
* js_HasOwnProperty.
* *
* It's important to allow delegating objects, even though they inherit * It's important to allow delegating objects, even though they inherit
* this getter (fun_getProperty), to override arguments, arity, caller, * this getter (fun_getProperty), to override arguments, arity, caller,
@@ -1466,20 +1473,34 @@ fun_getProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
return true; return true;
} }
struct LazyFunctionProp { namespace {
struct LazyFunctionDataProp {
uint16 atomOffset; uint16 atomOffset;
int8 tinyid; int8 tinyid;
uint8 attrs; uint8 attrs;
}; };
/* NB: no sentinel at the end -- use JS_ARRAY_LENGTH to bound loops. */ struct PoisonPillProp {
static LazyFunctionProp lazy_function_props[] = { uint16 atomOffset;
{ATOM_OFFSET(arguments), FUN_ARGUMENTS, JSPROP_PERMANENT}, int8 tinyid;
};
/* NB: no sentinels at ends -- use JS_ARRAY_LENGTH to bound loops. */
const LazyFunctionDataProp lazyFunctionDataProps[] = {
{ATOM_OFFSET(arity), FUN_ARITY, JSPROP_PERMANENT}, {ATOM_OFFSET(arity), FUN_ARITY, JSPROP_PERMANENT},
{ATOM_OFFSET(caller), FUN_CALLER, JSPROP_PERMANENT},
{ATOM_OFFSET(name), FUN_NAME, JSPROP_PERMANENT}, {ATOM_OFFSET(name), FUN_NAME, JSPROP_PERMANENT},
}; };
/* Properties censored into [[ThrowTypeError]] in strict mode. */
const PoisonPillProp poisonPillProps[] = {
{ATOM_OFFSET(arguments), FUN_ARGUMENTS },
{ATOM_OFFSET(caller), FUN_CALLER },
};
}
static JSBool static JSBool
fun_enumerate(JSContext *cx, JSObject *obj) fun_enumerate(JSContext *cx, JSObject *obj)
{ {
@@ -1493,13 +1514,20 @@ fun_enumerate(JSContext *cx, JSObject *obj)
if (!JS_LookupPropertyById(cx, obj, id, &v)) if (!JS_LookupPropertyById(cx, obj, id, &v))
return false; return false;
for (uintN i = 0; i < JS_ARRAY_LENGTH(lazy_function_props); i++) { for (uintN i = 0; i < JS_ARRAY_LENGTH(lazyFunctionDataProps); i++) {
LazyFunctionProp &lfp = lazy_function_props[i]; const LazyFunctionDataProp &lfp = lazyFunctionDataProps[i];
id = ATOM_TO_JSID(OFFSET_TO_ATOM(cx->runtime, lfp.atomOffset)); id = ATOM_TO_JSID(OFFSET_TO_ATOM(cx->runtime, lfp.atomOffset));
if (!JS_LookupPropertyById(cx, obj, id, &v)) if (!JS_LookupPropertyById(cx, obj, id, &v))
return false; return false;
} }
for (uintN i = 0; i < JS_ARRAY_LENGTH(poisonPillProps); i++) {
const PoisonPillProp &p = poisonPillProps[i];
id = ATOM_TO_JSID(OFFSET_TO_ATOM(cx->runtime, p.atomOffset));
if (!JS_LookupPropertyById(cx, obj, id, &v))
return false;
}
return true; return true;
} }
@@ -1575,8 +1603,8 @@ fun_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
return JS_TRUE; return JS_TRUE;
} }
for (uintN i = 0; i < JS_ARRAY_LENGTH(lazy_function_props); i++) { for (uintN i = 0; i < JS_ARRAY_LENGTH(lazyFunctionDataProps); i++) {
LazyFunctionProp *lfp = &lazy_function_props[i]; const LazyFunctionDataProp *lfp = &lazyFunctionDataProps[i];
atom = OFFSET_TO_ATOM(cx->runtime, lfp->atomOffset); atom = OFFSET_TO_ATOM(cx->runtime, lfp->atomOffset);
if (id == ATOM_TO_JSID(atom)) { if (id == ATOM_TO_JSID(atom)) {
@@ -1594,6 +1622,37 @@ fun_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
} }
} }
for (uintN i = 0; i < JS_ARRAY_LENGTH(poisonPillProps); i++) {
const PoisonPillProp &p = poisonPillProps[i];
atom = OFFSET_TO_ATOM(cx->runtime, p.atomOffset);
if (id == ATOM_TO_JSID(atom)) {
JS_ASSERT(!IsInternalFunctionObject(obj));
PropertyOp getter, setter;
uintN attrs = JSPROP_PERMANENT;
if (fun->isInterpreted() && fun->u.i.script->strictModeCode) {
JSObject *throwTypeError = obj->getThrowTypeError();
getter = CastAsPropertyOp(throwTypeError);
setter = CastAsPropertyOp(throwTypeError);
attrs |= JSPROP_GETTER | JSPROP_SETTER;
} else {
getter = fun_getProperty;
setter = PropertyStub;
}
if (!js_DefineNativeProperty(cx, obj, ATOM_TO_JSID(atom), UndefinedValue(),
getter, setter,
attrs, JSScopeProperty::HAS_SHORTID,
p.tinyid, NULL)) {
return JS_FALSE;
}
*objp = obj;
return JS_TRUE;
}
}
return JS_TRUE; return JS_TRUE;
} }
@@ -2393,20 +2452,43 @@ Function(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval)
filename, lineno); filename, lineno);
} }
namespace {
JSBool
ThrowTypeError(JSContext *cx, uintN argc, Value *vp)
{
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
JSMSG_THROW_TYPE_ERROR);
return false;
}
}
JSObject * JSObject *
js_InitFunctionClass(JSContext *cx, JSObject *obj) js_InitFunctionClass(JSContext *cx, JSObject *obj)
{ {
JSObject *proto; JSObject *proto = js_InitClass(cx, obj, NULL, &js_FunctionClass, Function, 1,
JSFunction *fun;
proto = js_InitClass(cx, obj, NULL, &js_FunctionClass, Function, 1,
NULL, function_methods, NULL, NULL); NULL, function_methods, NULL, NULL);
if (!proto) if (!proto)
return NULL; return NULL;
fun = js_NewFunction(cx, proto, NULL, 0, JSFUN_INTERPRETED, obj, NULL);
JSFunction *fun = js_NewFunction(cx, proto, NULL, 0, JSFUN_INTERPRETED, obj, NULL);
if (!fun) if (!fun)
return NULL; return NULL;
fun->u.i.script = JSScript::emptyScript(); fun->u.i.script = JSScript::emptyScript();
if (obj->getClass()->flags & JSCLASS_IS_GLOBAL) {
/* ES5 13.2.3: Construct the unique [[ThrowTypeError]] function object. */
JSObject *throwTypeError =
js_NewFunction(cx, NULL, reinterpret_cast<Native>(ThrowTypeError), 0,
JSFUN_FAST_NATIVE, obj, NULL);
if (!throwTypeError)
return NULL;
JS_ALWAYS_TRUE(js_SetReservedSlot(cx, obj, JSRESERVED_GLOBAL_THROWTYPEERROR,
ObjectValue(*throwTypeError)));
}
return proto; return proto;
} }

View File

@@ -349,6 +349,10 @@ IsFunctionObject(const js::Value &v, JSObject **funobj)
(JS_ASSERT((funobj)->isFunction()), \ (JS_ASSERT((funobj)->isFunction()), \
(JSFunction *) (funobj)->getPrivate()) (JSFunction *) (funobj)->getPrivate())
extern JSFunction *
js_NewFunction(JSContext *cx, JSObject *funobj, js::Native native, uintN nargs,
uintN flags, JSObject *parent, JSAtom *atom);
namespace js { namespace js {
/* /*
@@ -364,6 +368,9 @@ IsInternalFunctionObject(JSObject *funobj)
return funobj == fun && (fun->flags & JSFUN_LAMBDA) && !funobj->getParent(); return funobj == fun && (fun->flags & JSFUN_LAMBDA) && !funobj->getParent();
} }
extern JSString *
fun_toStringHelper(JSContext *cx, JSObject *obj, uintN indent);
} /* namespace js */ } /* namespace js */
extern JSObject * extern JSObject *
@@ -372,10 +379,6 @@ js_InitFunctionClass(JSContext *cx, JSObject *obj);
extern JSObject * extern JSObject *
js_InitArgumentsClass(JSContext *cx, JSObject *obj); js_InitArgumentsClass(JSContext *cx, JSObject *obj);
extern JSFunction *
js_NewFunction(JSContext *cx, JSObject *funobj, js::Native native, uintN nargs,
uintN flags, JSObject *parent, JSAtom *atom);
extern void extern void
js_TraceFunction(JSTracer *trc, JSFunction *fun); js_TraceFunction(JSTracer *trc, JSFunction *fun);
@@ -555,11 +558,4 @@ js_fun_apply(JSContext *cx, uintN argc, js::Value *vp);
extern JSBool extern JSBool
js_fun_call(JSContext *cx, uintN argc, js::Value *vp); js_fun_call(JSContext *cx, uintN argc, js::Value *vp);
namespace js {
extern JSString *
fun_toStringHelper(JSContext *cx, JSObject *obj, uintN indent);
}
#endif /* jsfun_h___ */ #endif /* jsfun_h___ */

View File

@@ -6046,9 +6046,9 @@ JSObject::wrappedObject(JSContext *cx) const
} }
JSObject * JSObject *
JSObject::getGlobal() JSObject::getGlobal() const
{ {
JSObject *obj = this; JSObject *obj = const_cast<JSObject *>(this);
while (JSObject *parent = obj->getParent()) while (JSObject *parent = obj->getParent())
obj = parent; obj = parent;
return obj; return obj;

View File

@@ -418,7 +418,7 @@ struct JSObject {
parent = newParent; parent = newParent;
} }
JSObject *getGlobal(); JSObject *getGlobal() const;
void *getPrivate() const { void *getPrivate() const {
JS_ASSERT(getClass()->flags & JSCLASS_HAS_PRIVATE); JS_ASSERT(getClass()->flags & JSCLASS_HAS_PRIVATE);
@@ -739,6 +739,8 @@ struct JSObject {
JS_FRIEND_API(JSCompartment *) getCompartment(JSContext *cx); JS_FRIEND_API(JSCompartment *) getCompartment(JSContext *cx);
inline JSObject *getThrowTypeError() const;
void swap(JSObject *obj); void swap(JSObject *obj);
inline bool canHaveMethodBarrier() const; inline bool canHaveMethodBarrier() const;

View File

@@ -1000,13 +1000,25 @@ js_NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg)
{ {
/* /*
* We can probably use the immutable empty script singleton, just * We can probably use the immutable empty script singleton, just
* one hard case (nupvars != 0) may stand in our way. * two hard cases (nupvars != 0, strict mode code) may stand in our
* way.
*/ */
JSScript *empty = JSScript::emptyScript(); JSScript *empty = JSScript::emptyScript();
if (cg->flags & TCF_IN_FUNCTION) { if (cg->flags & TCF_IN_FUNCTION) {
fun = cg->fun; fun = cg->fun;
JS_ASSERT(FUN_INTERPRETED(fun) && !FUN_SCRIPT(fun)); JS_ASSERT(fun->isInterpreted() && !FUN_SCRIPT(fun));
if (cg->flags & TCF_STRICT_MODE_CODE) {
/*
* We can't use a script singleton for empty strict mode
* functions because they have poison-pill caller and
* arguments properties:
*
* function strict() { "use strict"; }
* strict.caller; // calls [[ThrowTypeError]] function
*/
goto skip_empty;
}
if (fun->u.i.nupvars != 0) { if (fun->u.i.nupvars != 0) {
/* /*
* FIXME: upvar uses that were all optimized away may leave * FIXME: upvar uses that were all optimized away may leave

View File

@@ -0,0 +1,76 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var gTestfile = 'function-caller.js';
var BUGNUMBER = 514581;
var summary = "Function.prototype.caller should throw a TypeError for " +
"strict-mode functions";
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
// behavior
function expectTypeError(fun)
{
try
{
fun();
throw new Error("didn't throw");
}
catch (e)
{
assertEq(e instanceof TypeError, true,
"expected TypeError calling function" +
("name" in fun ? " " + fun.name : "") + ", instead got: " + e);
}
}
function bar() { "use strict"; }
expectTypeError(function barCaller() { bar.caller; });
function baz() { "use strict"; return 17; }
expectTypeError(function bazCaller() { baz.caller; });
// accessor identity
function strictMode() { "use strict"; return 42; }
var canonicalTTE = Object.getOwnPropertyDescriptor(strictMode, "caller").get;
var barCaller = Object.getOwnPropertyDescriptor(bar, "caller");
assertEq("get" in barCaller, true);
assertEq("set" in barCaller, true);
assertEq(barCaller.get, canonicalTTE);
assertEq(barCaller.set, canonicalTTE);
var barArguments = Object.getOwnPropertyDescriptor(bar, "arguments");
assertEq("get" in barArguments, true);
assertEq("set" in barArguments, true);
assertEq(barArguments.get, canonicalTTE);
assertEq(barArguments.set, canonicalTTE);
var bazCaller = Object.getOwnPropertyDescriptor(baz, "caller");
assertEq("get" in bazCaller, true);
assertEq("set" in bazCaller, true);
assertEq(bazCaller.get, canonicalTTE);
assertEq(bazCaller.set, canonicalTTE);
var bazArguments = Object.getOwnPropertyDescriptor(baz, "arguments");
assertEq("get" in bazArguments, true);
assertEq("set" in bazArguments, true);
assertEq(bazArguments.get, canonicalTTE);
assertEq(bazArguments.set, canonicalTTE);
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("All tests passed!");

View File

@@ -1,2 +1,3 @@
url-prefix ../../jsreftest.html?test=ecma_5/Function/ url-prefix ../../jsreftest.html?test=ecma_5/Function/
script 15.3.4.3-01.js script 15.3.4.3-01.js
script function-caller.js