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

--HG--
extra : rebase_source : 10f930852e39b0b1ef917b18b6a1332a9a815d5d
This commit is contained in:
Jeff Walden 2010-08-02 23:52:12 -07:00
parent 957aa77aaa
commit 359a93c61a
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_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_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_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
* with "the original Foo.prototype value" as their [[Prototype]] (__proto__)
@ -1675,11 +1681,9 @@ struct JSClass {
* with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
* prevously allowed, but is now an ES5 violation and thus unsupported.
*/
#define JSCLASS_GLOBAL_FLAGS \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSProto_LIMIT * 3 + 2))
#define JSRESERVED_GLOBAL_COMPARTMENT (JSProto_LIMIT * 3)
#define JSRESERVED_GLOBAL_THIS (JSRESERVED_GLOBAL_COMPARTMENT + 1)
#define JSCLASS_GLOBAL_FLAGS \
(JSCLASS_IS_GLOBAL | \
JSCLASS_HAS_RESERVED_SLOTS(JSProto_LIMIT * 3 + JSRESERVED_GLOBAL_SLOTS_COUNT))
/* Fast access to the original value of each standard class's prototype. */
#define JSCLASS_CACHED_PROTO_SHIFT (JSCLASS_HIGH_FLAGS_SHIFT + 8)

View File

@ -87,6 +87,12 @@
using namespace js;
inline JSObject *
JSObject::getThrowTypeError() const
{
return &getGlobal()->getReservedSlot(JSRESERVED_GLOBAL_THROWTYPEERROR).toObject();
}
JSBool
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
* to make it appear so).
*
* This code couples tightly to the attributes for lazy_function_props[]
* initializers above, and to js_SetProperty and js_HasOwnProperty.
* This code couples tightly to the attributes for lazyFunctionDataProps[]
* and poisonPillProps[] initializers below, and to js_SetProperty and
* js_HasOwnProperty.
*
* It's important to allow delegating objects, even though they inherit
* 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;
}
struct LazyFunctionProp {
namespace {
struct LazyFunctionDataProp {
uint16 atomOffset;
int8 tinyid;
uint8 attrs;
};
/* NB: no sentinel at the end -- use JS_ARRAY_LENGTH to bound loops. */
static LazyFunctionProp lazy_function_props[] = {
{ATOM_OFFSET(arguments), FUN_ARGUMENTS, JSPROP_PERMANENT},
struct PoisonPillProp {
uint16 atomOffset;
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(caller), FUN_CALLER, 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
fun_enumerate(JSContext *cx, JSObject *obj)
{
@ -1493,13 +1514,20 @@ fun_enumerate(JSContext *cx, JSObject *obj)
if (!JS_LookupPropertyById(cx, obj, id, &v))
return false;
for (uintN i = 0; i < JS_ARRAY_LENGTH(lazy_function_props); i++) {
LazyFunctionProp &lfp = lazy_function_props[i];
for (uintN i = 0; i < JS_ARRAY_LENGTH(lazyFunctionDataProps); i++) {
const LazyFunctionDataProp &lfp = lazyFunctionDataProps[i];
id = ATOM_TO_JSID(OFFSET_TO_ATOM(cx->runtime, lfp.atomOffset));
if (!JS_LookupPropertyById(cx, obj, id, &v))
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;
}
@ -1575,8 +1603,8 @@ fun_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
return JS_TRUE;
}
for (uintN i = 0; i < JS_ARRAY_LENGTH(lazy_function_props); i++) {
LazyFunctionProp *lfp = &lazy_function_props[i];
for (uintN i = 0; i < JS_ARRAY_LENGTH(lazyFunctionDataProps); i++) {
const LazyFunctionDataProp *lfp = &lazyFunctionDataProps[i];
atom = OFFSET_TO_ATOM(cx->runtime, lfp->atomOffset);
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;
}
@ -2393,20 +2452,43 @@ Function(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval)
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 *
js_InitFunctionClass(JSContext *cx, JSObject *obj)
{
JSObject *proto;
JSFunction *fun;
proto = js_InitClass(cx, obj, NULL, &js_FunctionClass, Function, 1,
NULL, function_methods, NULL, NULL);
JSObject *proto = js_InitClass(cx, obj, NULL, &js_FunctionClass, Function, 1,
NULL, function_methods, NULL, NULL);
if (!proto)
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)
return NULL;
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;
}

View File

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

View File

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

View File

@ -418,7 +418,7 @@ struct JSObject {
parent = newParent;
}
JSObject *getGlobal();
JSObject *getGlobal() const;
void *getPrivate() const {
JS_ASSERT(getClass()->flags & JSCLASS_HAS_PRIVATE);
@ -739,6 +739,8 @@ struct JSObject {
JS_FRIEND_API(JSCompartment *) getCompartment(JSContext *cx);
inline JSObject *getThrowTypeError() const;
void swap(JSObject *obj);
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
* 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();
if (cg->flags & TCF_IN_FUNCTION) {
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) {
/*
* 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/
script 15.3.4.3-01.js
script function-caller.js