mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 09:45:41 +00:00
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:
parent
957aa77aaa
commit
359a93c61a
@ -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")
|
||||
|
@ -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)
|
||||
|
116
js/src/jsfun.cpp
116
js/src/jsfun.cpp
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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___ */
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
76
js/src/tests/ecma_5/Function/function-caller.js
Normal file
76
js/src/tests/ecma_5/Function/function-caller.js
Normal 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!");
|
@ -1,2 +1,3 @@
|
||||
url-prefix ../../jsreftest.html?test=ecma_5/Function/
|
||||
script 15.3.4.3-01.js
|
||||
script function-caller.js
|
||||
|
Loading…
Reference in New Issue
Block a user