Add Debugger.Object.prototype.defineProperties and a few defineProperty tests.

This commit is contained in:
Jason Orendorff 2011-07-13 06:46:23 -05:00
parent 01f08a3395
commit ef3cb9f6dc
11 changed files with 281 additions and 55 deletions

View File

@ -0,0 +1,46 @@
// Debug.Object.prototype.defineProperties.
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
var descProps = ['configurable', 'enumerable', 'writable', 'value', 'get', 'set'];
function test(objexpr, descs) {
g.eval("obj = (" + objexpr + ");");
var gobjw = gw.getOwnPropertyDescriptor("obj").value;
gobjw.defineProperties(descs);
var indirectEval = eval;
var obj = indirectEval("(" + objexpr + ");");
Object.defineProperties(obj, descs);
var ids = Object.keys(descs);
for (var i = 0; i < ids.length; i++) {
var actual = gobjw.getOwnPropertyDescriptor(ids[i]);
var expected = Object.getOwnPropertyDescriptor(obj, ids[i]);
assertEq(Object.getPrototypeOf(actual), Object.prototype);
assertEq(actual.configurable, expected.configurable);
assertEq(actual.enumerable, expected.enumerable);
for (var j = 0; j < descProps; j++) {
var prop = descProps[j];
assertEq(prop in actual, prop in expected);
assertEq(actual[prop], expected[prop]);
}
}
}
test("{}", {});
test("/abc/", {});
g.eval("var aglobal = newGlobal('same-compartment');");
var aglobal = newGlobal('same-compartment');
test("aglobal", {});
var adescs = {a: {enumerable: true, writable: true, value: 0}};
test("{}", adescs);
test("{a: 1}", adescs);
var arrdescs = [{value: 'a'}, {value: 'b'}, , {value: 'd'}];
test("{}", arrdescs);
test("[]", arrdescs);
test("[0, 1, 2, 3]", arrdescs);

View File

@ -0,0 +1,32 @@
// Exceptions thrown by obj.defineProperties are copied into the debugger compartment.
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
function test(objexpr, descs) {
var exca, excb;
g.eval("obj = (" + objexpr + ");");
var gobjw = gw.getOwnPropertyDescriptor("obj").value;
try {
gobjw.defineProperties(descs);
} catch (exc) {
exca = exc;
}
var indirectEval = eval;
var obj = indirectEval("(" + objexpr + ");");
try {
Object.defineProperties(obj, descs);
} catch (exc) {
excb = exc;
}
assertEq(Object.getPrototypeOf(exca), Object.getPrototypeOf(excb));
assertEq(exca.message, excb.message);
assertEq(typeof exca.fileName, "string");
assertEq(typeof exca.stack, "string");
}
test("Object.create(null, {p: {value: 1}})", {p: {value: 2}});

View File

@ -0,0 +1,20 @@
// obj.defineProperties can define accessor properties.
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
g.value = undefined;
g.eval("function gf() { return 12; }\n" +
"function sf(v) { value = v; }\n");
var gfw = gw.getOwnPropertyDescriptor("gf").value;
var sfw = gw.getOwnPropertyDescriptor("sf").value;
gw.defineProperties({x: {configurable: true, get: gfw, set: sfw}});
assertEq(g.x, 12);
g.x = 'ok';
assertEq(g.value, 'ok');
var desc = g.Object.getOwnPropertyDescriptor(g, "x");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.get, g.gf);
assertEq(desc.set, g.sf);

View File

@ -2,8 +2,6 @@
// Also: when defineProperty throws, the exception is native to the debugger
// compartment, not a wrapper.
load(libdir + "asserts.js");
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);

View File

@ -0,0 +1,16 @@
// obj.defineProperty works when obj's referent is a wrapper.
var x = {};
var g = newGlobal('new-compartment');
g.x = x;
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
var xw = gw.getOwnPropertyDescriptor("x").value;
xw.defineProperty("p", {configurable: true, enumerable: true, writable: true, value: gw});
assertEq(x.p, g);
var desc = Object.getOwnPropertyDescriptor(x, "p");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, true);
assertEq(desc.writable, true);
assertEq(desc.value, g);

View File

@ -0,0 +1,18 @@
// obj.defineProperty redefining an existing property leaves unspecified attributes unchanged.
var g = newGlobal('new-compartment');
g.p = 1;
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
gw.defineProperty("p", {value: 2});
assertEq(g.p, 2);
var desc = Object.getOwnPropertyDescriptor(g, "p");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, true);
assertEq(desc.writable, true);
assertEq(desc.value, 2);
g.p = 3;
assertEq(g.p, 3);

View File

@ -43,7 +43,6 @@
#include "jsapi.h"
#include "jscntxt.h"
#include "jsemit.h"
#include "jsexn.h"
#include "jsgcmark.h"
#include "jsobj.h"
#include "jstl.h"
@ -2929,6 +2928,36 @@ CheckArgCompartment(JSContext *cx, JSObject *obj, const Value &v,
return true;
}
// Convert Debugger.Objects in decs to debuggee values.
// Reject non-callable getters and setters.
static bool
UnwrapPropDesc(JSContext *cx, Debugger *dbg, JSObject *obj, PropDesc *desc)
{
return (!desc->hasValue || (dbg->unwrapDebuggeeValue(cx, &desc->value) &&
CheckArgCompartment(cx, obj, desc->value, "defineProperty",
"value"))) &&
(!desc->hasGet || (dbg->unwrapDebuggeeValue(cx, &desc->get) &&
CheckArgCompartment(cx, obj, desc->get, "defineProperty", "get") &&
desc->checkGetter(cx))) &&
(!desc->hasSet || (dbg->unwrapDebuggeeValue(cx, &desc->set) &&
CheckArgCompartment(cx, obj, desc->set, "defineProperty", "set") &&
desc->checkSetter(cx)));
}
// Rewrap *idp and the fields of *desc for the current compartment. Also:
// defining a property on a proxy requiers the pd field to contain a descriptor
// object, so reconstitute desc->pd if needed.
static bool
WrapIdAndPropDesc(JSContext *cx, JSObject *obj, jsid *idp, PropDesc *desc)
{
JSCompartment *comp = cx->compartment;
return comp->wrapId(cx, idp) &&
comp->wrap(cx, &desc->value) &&
comp->wrap(cx, &desc->get) &&
comp->wrap(cx, &desc->set) &&
(!obj->isProxy() || desc->makeObject(cx));
}
static JSBool
DebuggerObject_defineProperty(JSContext *cx, uintN argc, Value *vp)
{
@ -2938,55 +2967,65 @@ DebuggerObject_defineProperty(JSContext *cx, uintN argc, Value *vp)
if (!ValueToId(cx, argc >= 1 ? vp[2] : UndefinedValue(), &id))
return JS_FALSE;
const Value &descval = argc >= 2 ? vp[3] : UndefinedValue();
const Value &descval = argc >= 1 ? vp[3] : UndefinedValue();
AutoPropDescArrayRooter descs(cx);
PropDesc *desc = descs.append();
if (!desc || !desc->initialize(cx, descval, false))
return false;
desc->pd.setUndefined();
if ((desc->hasValue && (!dbg->unwrapDebuggeeValue(cx, &desc->value) ||
!CheckArgCompartment(cx, obj, desc->value, "defineProperty",
"value"))) ||
(desc->hasGet && (!dbg->unwrapDebuggeeValue(cx, &desc->get) ||
!CheckArgCompartment(cx, obj, desc->get, "defineProperty", "get") ||
!desc->checkGetter(cx))) ||
(desc->hasSet && (!dbg->unwrapDebuggeeValue(cx, &desc->set) ||
!CheckArgCompartment(cx, obj, desc->set, "defineProperty", "set") ||
!desc->checkSetter(cx))))
{
if (!UnwrapPropDesc(cx, dbg, obj, desc))
return false;
{
AutoCompartment ac(cx, obj);
if (!ac.enter() || !WrapIdAndPropDesc(cx, obj, &id, desc))
return false;
ErrorCopier ec(ac, dbg->toJSObject());
bool dummy;
if (!DefineProperty(cx, obj, id, *desc, true, &dummy))
return false;
}
vp->setUndefined();
return true;
}
static JSBool
DebuggerObject_defineProperties(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, vp, "get script", dbg, obj);
REQUIRE_ARGC("Debugger.Object.defineProperties", 1);
JSObject *props = ToObject(cx, &vp[2]);
if (!props)
return false;
AutoIdVector ids(cx);
AutoPropDescArrayRooter descs(cx);
if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs))
return false;
size_t n = ids.length();
for (size_t i = 0; i < n; i++) {
if (!UnwrapPropDesc(cx, dbg, obj, &descs[i]))
return false;
}
{
AutoCompartment ac(cx, obj);
if (!ac.enter() ||
!ac.destination->wrapId(cx, &id) ||
!ac.destination->wrap(cx, &desc->value) ||
!ac.destination->wrap(cx, &desc->get) ||
!ac.destination->wrap(cx, &desc->set))
{
if (!ac.enter())
return false;
for (size_t i = 0; i < n; i++) {
if (!WrapIdAndPropDesc(cx, obj, &ids[i], &descs[i]))
return false;
}
// Defining a property on a proxy requiers the pd field to contain
// a descriptor object. Reconstitute it.
if (obj->isProxy() && !desc->makeObject(cx))
return false;
bool ignored;
if (!DefineProperty(cx, obj, id, *desc, true, &ignored)) {
if (cx->isExceptionPending()) {
Value exc = cx->getPendingException();
if (exc.isObject() && exc.toObject().isError()) {
cx->clearPendingException();
ac.leave();
JSObject *copyobj = js_CopyErrorObject(cx, &exc.toObject(), dbg->toJSObject());
if (copyobj)
cx->setPendingException(ObjectValue(*copyobj));
}
}
return false;
ErrorCopier ec(ac, dbg->toJSObject());
for (size_t i = 0; i < n; i++) {
bool dummy;
if (!DefineProperty(cx, obj, ids[i], descs[i], true, &dummy))
return false;
}
}
@ -2994,6 +3033,7 @@ DebuggerObject_defineProperty(JSContext *cx, uintN argc, Value *vp)
return true;
}
enum ApplyOrCallMode { ApplyMode, CallMode };
static JSBool
@ -3085,6 +3125,7 @@ static JSFunctionSpec DebuggerObject_methods[] = {
JS_FN("getOwnPropertyDescriptor", DebuggerObject_getOwnPropertyDescriptor, 1, 0),
JS_FN("getOwnPropertyNames", DebuggerObject_getOwnPropertyNames, 0, 0),
JS_FN("defineProperty", DebuggerObject_defineProperty, 2, 0),
JS_FN("defineProperties", DebuggerObject_defineProperties, 1, 0),
JS_FN("apply", DebuggerObject_apply, 0, 0),
JS_FN("call", DebuggerObject_call, 0, 0),
JS_FS_END

View File

@ -2499,30 +2499,42 @@ obj_defineProperty(JSContext* cx, uintN argc, Value* vp)
return true;
}
namespace js {
bool
ReadPropertyDescriptors(JSContext *cx, JSObject *props, bool checkAccessors,
AutoIdVector *ids, AutoPropDescArrayRooter *descs)
{
if (!GetPropertyNames(cx, props, JSITER_OWNONLY, ids))
return false;
for (size_t i = 0, len = ids->length(); i < len; i++) {
jsid id = (*ids)[i];
PropDesc* desc = descs->append();
Value v;
if (!desc || !props->getProperty(cx, id, &v) || !desc->initialize(cx, v, checkAccessors))
return false;
}
return true;
}
} /* namespace js */
static bool
DefineProperties(JSContext *cx, JSObject *obj, JSObject *props)
{
AutoIdVector ids(cx);
if (!GetPropertyNames(cx, props, JSITER_OWNONLY, &ids))
AutoPropDescArrayRooter descs(cx);
if (!ReadPropertyDescriptors(cx, props, true, &ids, &descs))
return false;
AutoPropDescArrayRooter descs(cx);
size_t len = ids.length();
for (size_t i = 0; i < len; i++) {
jsid id = ids[i];
PropDesc* desc = descs.append();
Value v;
if (!desc || !props->getProperty(cx, id, &v) || !desc->initialize(cx, v))
return false;
}
bool dummy;
for (size_t i = 0, len = ids.length(); i < len; i++) {
if (!DefineProperty(cx, obj, ids[i], descs[i], true, &dummy))
return false;
}
bool dummy;
for (size_t i = 0; i < len; i++) {
if (!DefineProperty(cx, obj, ids[i], descs[i], true, &dummy))
return false;
}
return true;
return true;
}
extern JSBool

View File

@ -1646,6 +1646,14 @@ extern bool
DefineProperty(JSContext *cx, JSObject *obj, const jsid &id, const PropDesc &desc, bool throwError,
bool *rval);
/*
* Read property descriptors from props, as for Object.defineProperties. See
* ES5 15.2.3.7 steps 3-5.
*/
extern bool
ReadPropertyDescriptors(JSContext *cx, JSObject *props, bool checkAccessors,
AutoIdVector *ids, AutoPropDescArrayRooter *descs);
/*
* Constant to pass to js_LookupPropertyWithFlags to infer bits from current
* bytecode.

View File

@ -41,6 +41,7 @@
#include "jsapi.h"
#include "jscntxt.h"
#include "jsexn.h"
#include "jsgc.h"
#include "jsgcmark.h"
#include "jsiter.h"
@ -475,6 +476,24 @@ AutoCompartment::leave()
entered = false;
}
ErrorCopier::~ErrorCopier()
{
JSContext *cx = ac.context;
if (cx->compartment == ac.destination &&
ac.origin != ac.destination &&
cx->isExceptionPending())
{
Value exc = cx->getPendingException();
if (exc.isObject() && exc.toObject().isError()) {
cx->clearPendingException();
ac.leave();
JSObject *copyobj = js_CopyErrorObject(cx, &exc.toObject(), scope);
if (copyobj)
cx->setPendingException(ObjectValue(*copyobj));
}
}
}
/* Cross compartment wrappers. */
JSCrossCompartmentWrapper::JSCrossCompartmentWrapper(uintN flags)

View File

@ -200,6 +200,22 @@ class AutoCompartment
AutoCompartment & operator=(const AutoCompartment &);
};
/*
* Use this to change the behavior of an AutoCompartment slightly on error. If
* the exception happens to be an Error object, copy it to the origin compartment
* instead of wrapping it.
*/
class ErrorCopier {
AutoCompartment &ac;
JSObject *scope;
public:
ErrorCopier(AutoCompartment &ac, JSObject *scope) : ac(ac), scope(scope) {
JS_ASSERT(scope->compartment() == ac.origin);
}
~ErrorCopier();
};
extern JSObject *
TransparentObjectWrapper(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSObject *parent,
uintN flags);