Bug 1121554 - Include receiver argument in setProperty hooks, r=jorendorff.

This commit is contained in:
Brian Hackett 2015-02-14 08:50:48 -07:00
parent 30b0849a33
commit 8d8840048c
13 changed files with 209 additions and 62 deletions

View File

@ -173,7 +173,7 @@ typedef bool
(* GetPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
JS::MutableHandleValue vp);
typedef bool
(* SetPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
(* SetPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
JS::MutableHandleValue vp, bool strict);
typedef bool
(* GetOwnPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id,

View File

@ -1768,7 +1768,8 @@ bool
TypedObject::obj_defineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
return ReportPropertyError(cx, JSMSG_UNDEFINED_PROP, id);
Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
return ReportTypedObjTypeError(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, typedObj);
}
bool
@ -1816,7 +1817,6 @@ bool
TypedObject::obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, MutableHandleValue vp)
{
MOZ_ASSERT(obj->is<TypedObject>());
Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
// Dispatch elements to obj_getElement:
@ -1917,7 +1917,7 @@ TypedObject::obj_getArrayElement(JSContext *cx,
}
bool
TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
MutableHandleValue vp, bool strict)
{
MOZ_ASSERT(obj->is<TypedObject>());
@ -1925,7 +1925,7 @@ TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
uint32_t index;
if (js_IdIsIndex(id, &index))
return obj_setElement(cx, obj, index, vp, strict);
return obj_setElement(cx, obj, receiver, index, vp, strict);
switch (typedObj->typeDescr().kind()) {
case type::Scalar:
@ -1937,9 +1937,12 @@ TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
case type::Array:
if (JSID_IS_ATOM(id, cx->names().length)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage,
nullptr, JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
return false;
if (obj == receiver) {
JS_ReportErrorNumber(cx, js_GetErrorMessage,
nullptr, JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
return false;
}
return SetNonWritableProperty(cx, id, strict);
}
break;
@ -1950,6 +1953,9 @@ TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
if (!descr->fieldIndex(id, &fieldIndex))
break;
if (obj != receiver)
return SetPropertyByDefining(cx, obj, receiver, id, vp, strict, false);
size_t offset = descr->fieldOffset(fieldIndex);
Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
RootedAtom fieldName(cx, &descr->fieldName(fieldIndex));
@ -1957,17 +1963,26 @@ TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
}
}
return ReportTypedObjTypeError(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, typedObj);
return SetPropertyOnProto(cx, obj, receiver, id, vp, strict);
}
bool
TypedObject::obj_setElement(JSContext *cx, HandleObject obj, uint32_t index,
MutableHandleValue vp, bool strict)
TypedObject::obj_setElement(JSContext *cx, HandleObject obj, HandleObject receiver,
uint32_t index, MutableHandleValue vp, bool strict)
{
MOZ_ASSERT(obj->is<TypedObject>());
Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
Rooted<TypeDescr *> descr(cx, &typedObj->typeDescr());
if (obj != receiver) {
RootedId id(cx);
if (!IndexToId(cx, index, &id))
return false;
if (descr->is<ArrayTypeDescr>())
return SetPropertyByDefining(cx, obj, receiver, id, vp, strict, false);
return SetPropertyOnProto(cx, obj, receiver, id, vp, strict);
}
switch (descr->kind()) {
case type::Scalar:
case type::Reference:
@ -1979,15 +1994,18 @@ TypedObject::obj_setElement(JSContext *cx, HandleObject obj, uint32_t index,
return obj_setArrayElement(cx, typedObj, descr, index, vp);
}
return ReportTypedObjTypeError(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, typedObj);
RootedId id(cx);
if (!IndexToId(cx, index, &id))
return false;
return SetPropertyOnProto(cx, obj, receiver, id, vp, strict);
}
/*static*/ bool
TypedObject::obj_setArrayElement(JSContext *cx,
Handle<TypedObject*> typedObj,
Handle<TypeDescr*> descr,
uint32_t index,
MutableHandleValue vp)
Handle<TypedObject*> typedObj,
Handle<TypeDescr*> descr,
uint32_t index,
MutableHandleValue vp)
{
if (index >= (size_t) typedObj->length()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage,

View File

@ -543,11 +543,10 @@ class TypedObject : public JSObject
static bool obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver,
uint32_t index, MutableHandleValue vp);
static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
MutableHandleValue vp, bool strict);
static bool obj_setElement(JSContext *cx, HandleObject obj, uint32_t index,
MutableHandleValue vp, bool strict);
static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, MutableHandleValue vp, bool strict);
static bool obj_setElement(JSContext *cx, HandleObject obj, HandleObject receiver,
uint32_t index, MutableHandleValue vp, bool strict);
static bool obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
MutableHandle<JSPropertyDescriptor> desc);

View File

@ -0,0 +1,72 @@
if (typeof TypedObject === "undefined")
quit();
// Test the behavior of property sets on typed objects when they are a
// prototype or their prototype has a setter.
var TO = TypedObject;
function assertThrows(fun, errorType) {
try {
fun();
assertEq(true, false, "Expected error, but none was thrown");
} catch (e) {
assertEq(e instanceof errorType, true, "Wrong error type thrown");
}
}
var PointType = new TO.StructType({x: TO.int32, y: TO.int32 });
function testPoint() {
var p = new PointType();
var sub = Object.create(p);
var found;
Object.defineProperty(PointType.prototype, "z", {set: function(a) { this.d = a; }});
Object.defineProperty(PointType.prototype, "innocuous", {set: function(a) { found = a; }});
sub.x = 5;
assertEq(sub.x, 5);
assertEq(p.x, 0);
sub.z = 5;
assertEq(sub.d, 5);
assertEq(sub.z, undefined);
sub[3] = 5;
assertEq(sub[3], 5);
p.innocuous = 10;
assertEq(found, 10);
assertThrows(function() {
p.z = 10;
assertEq(true, false);
}, TypeError);
}
testPoint();
var IntArrayType = new TO.ArrayType(TO.int32, 3);
function testArray() {
var arr = new IntArrayType();
var found;
Object.defineProperty(IntArrayType.prototype, 5, {set: function(a) { found = a; }});
assertThrows(function() {
arr[5] = 5;
}, RangeError);
assertThrows(function() {
arr[4] = 5;
}, RangeError);
var p = Object.create(arr);
p.length = 100;
assertEq(p.length, 3);
assertThrows(function() {
"use strict";
p.length = 100;
}, TypeError);
}
testArray();

View File

@ -0,0 +1,31 @@
// Use the correct receiver when non-native objects are prototypes of other objects.
function Thing(a, b) {
this.a = a;
this.b = b;
}
function foo() {
var array = [];
for (var i = 0; i < 10000; i++)
array.push(new Thing(i, i + 1));
var proto = new Thing(1, 2);
var obj = Object.create(proto);
Object.defineProperty(Thing.prototype, "c", {set:function() { this.d = 0; }});
obj.c = 3;
assertEq(obj.c, undefined);
assertEq(obj.d, 0);
assertEq(obj.hasOwnProperty("d"), true);
assertEq(proto.d, undefined);
assertEq(proto.hasOwnProperty("d"), false);
obj.a = 3;
assertEq(obj.a, 3);
assertEq(proto.a, 1);
assertEq(obj.hasOwnProperty("a"), true);
}
foo();

View File

@ -338,7 +338,7 @@ extern JS_FRIEND_API(bool)
proxy_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
JS::MutableHandleValue vp);
extern JS_FRIEND_API(bool)
proxy_SetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
proxy_SetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
JS::MutableHandleValue bp, bool strict);
extern JS_FRIEND_API(bool)
proxy_GetOwnPropertyDescriptor(JSContext *cx, JS::HandleObject obj, JS::HandleId id,

View File

@ -1675,9 +1675,7 @@ JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj, HandleObject rec
if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp))
return false;
}
if (obj->is<ProxyObject>())
return Proxy::set(cx, obj, receiver, id, strict, vp);
return obj->getOps()->setProperty(cx, obj, id, vp, strict);
return obj->getOps()->setProperty(cx, obj, receiver, id, vp, strict);
}
/* static */ bool

View File

@ -572,10 +572,10 @@ js::proxy_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, Ha
}
bool
js::proxy_SetProperty(JSContext *cx, HandleObject obj, HandleId id,
js::proxy_SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
MutableHandleValue vp, bool strict)
{
return Proxy::set(cx, obj, obj, id, strict, vp);
return Proxy::set(cx, obj, receiver, id, strict, vp);
}
bool

View File

@ -1948,9 +1948,9 @@ MaybeReportUndeclaredVarAssignment(JSContext *cx, JSString *propname)
* This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.c-f, but it
* is really old code and there are a few barnacles.
*/
static bool
SetPropertyByDefining(JSContext *cx, HandleNativeObject obj, HandleObject receiver,
HandleId id, HandleValue v, bool strict, bool objHasOwn)
bool
js::SetPropertyByDefining(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, HandleValue v, bool strict, bool objHasOwn)
{
// Step 5.c-d: Test whether receiver has an existing own property
// receiver[id]. The spec calls [[GetOwnProperty]]; js::HasOwnProperty is
@ -2015,6 +2015,33 @@ SetPropertyByDefining(JSContext *cx, HandleNativeObject obj, HandleObject receiv
return DefinePropertyOrElement(cx, nativeReceiver, id, getter, setter, attrs, v, true, strict);
}
// When setting |id| for |receiver| and |obj| has no property for id, continue
// the search up the prototype chain.
bool
js::SetPropertyOnProto(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, MutableHandleValue vp, bool strict)
{
MOZ_ASSERT(!obj->is<ProxyObject>());
RootedObject proto(cx, obj->getProto());
if (proto)
return SetProperty(cx, proto, receiver, id, vp, strict);
return SetPropertyByDefining(cx, obj, receiver, id, vp, strict, false);
}
bool
js::SetNonWritableProperty(JSContext *cx, HandleId id, bool strict)
{
// Setting a non-writable property is an error in strict mode code, a
// warning with the extra warnings option, and otherwise does nothing.
if (strict)
return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR);
if (cx->compartment()->options().extraWarnings(cx))
return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING);
return true;
}
/*
* Implement "the rest of" assignment to a property when no property receiver[id]
* was found anywhere on the prototype chain.
@ -2154,18 +2181,8 @@ SetExistingProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver
} else {
MOZ_ASSERT(shape->isDataDescriptor());
if (!shape->writable()) {
/*
* Error in strict mode code, warn with extra warnings
* options, otherwise do nothing.
*/
if (strict)
return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR);
if (cx->compartment()->options().extraWarnings(cx))
return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING);
return true;
}
if (!shape->writable())
return SetNonWritableProperty(cx, id, strict);
}
if (pobj == receiver) {

View File

@ -1282,6 +1282,18 @@ NativeGetElement(JSContext *cx, HandleNativeObject obj, uint32_t index, MutableH
return NativeGetElement(cx, obj, obj, index, vp);
}
bool
SetPropertyByDefining(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, HandleValue v, bool strict, bool objHasOwn);
bool
SetPropertyOnProto(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, MutableHandleValue vp, bool strict);
// Report any error or warning when writing to a non-writable property.
bool
SetNonWritableProperty(JSContext *cx, HandleId id, bool strict);
/*
* Indicates whether an assignment operation is qualified (`x.y = 0`) or
* unqualified (`y = 0`). In strict mode, the latter is an error if no such

View File

@ -492,11 +492,14 @@ with_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleI
}
static bool
with_SetProperty(JSContext *cx, HandleObject obj, HandleId id,
with_SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
MutableHandleValue vp, bool strict)
{
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
return SetProperty(cx, actual, actual, id, vp, strict);
RootedObject actualReceiver(cx, receiver);
if (receiver == obj)
actualReceiver = actual;
return SetProperty(cx, actual, actualReceiver, id, vp, strict);
}
static bool
@ -935,7 +938,7 @@ uninitialized_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver
}
static bool
uninitialized_SetProperty(JSContext *cx, HandleObject obj, HandleId id,
uninitialized_SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
MutableHandleValue vp, bool strict)
{
ReportUninitializedLexicalId(cx, id);

View File

@ -309,28 +309,25 @@ UnboxedPlainObject::obj_getProperty(JSContext *cx, HandleObject obj, HandleObjec
}
/* static */ bool
UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
MutableHandleValue vp, bool strict)
UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, MutableHandleValue vp, bool strict)
{
const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property *property = layout.lookup(id)) {
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, vp))
return true;
if (obj == receiver) {
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, vp))
return true;
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
return false;
return SetProperty(cx, obj, obj, id, vp, strict);
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
return false;
return SetProperty(cx, obj, receiver, id, vp, strict);
}
return SetPropertyByDefining(cx, obj, receiver, id, vp, strict, false);
}
RootedObject proto(cx, obj->getProto());
if (!proto) {
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
return false;
return SetProperty(cx, obj, obj, id, vp, strict);
}
return SetProperty(cx, proto, obj, id, vp, strict);
return SetPropertyOnProto(cx, obj, receiver, id, vp, strict);
}
/* static */ bool

View File

@ -144,8 +144,8 @@ class UnboxedPlainObject : public JSObject
static bool obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, MutableHandleValue vp);
static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleId id,
MutableHandleValue vp, bool strict);
static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
HandleId id, MutableHandleValue vp, bool strict);
static bool obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
MutableHandle<JSPropertyDescriptor> desc);