Bug 1515447 - Add receiver parameter to Debugger.Object's getProperty and setProperty. r=jimb
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Fri, 28 Dec 2018 19:07:15 +0000
changeset 452082 8bf9415e1de84e1d4050d3cf2d56d065e94adf5d
parent 452081 c68ba2c62949ea7bb210666a1e67466205a75603
child 452083 ec8108d8f489fcef03679ce0f01b11e4dcaf14a0
push id35283
push userdvarga@mozilla.com
push dateSat, 29 Dec 2018 09:34:06 +0000
treeherdermozilla-central@f2227be9432b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs1515447
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1515447 - Add receiver parameter to Debugger.Object's getProperty and setProperty. r=jimb Differential Revision: https://phabricator.services.mozilla.com/D15018
js/src/doc/Debugger/Debugger.Object.md
js/src/jit-test/tests/debug/Object-getProperty-03.js
js/src/jit-test/tests/debug/Object-setProperty-03.js
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
--- a/js/src/doc/Debugger/Debugger.Object.md
+++ b/js/src/doc/Debugger/Debugger.Object.md
@@ -330,30 +330,35 @@ Unless otherwise specified, these method
 (say, because it gets or sets an accessor property whose handler is
 debuggee code, or because the referent is a proxy whose traps are debuggee
 code), the call throws a [`Debugger.DebuggeeWouldRun`][wouldrun] exception.
 
 These methods may throw if the referent is not a native object. Even simple
 accessors like `isExtensible` may throw if the referent is a proxy or some sort
 of exotic object like an opaque wrapper.
 
-<code>getProperty(<i>key</i>)</code>
+<code>getProperty(<i>key</i>, [<i>receiver</i>])</code>
 :   Return a [completion value][cv] with "return" being the value of the
     referent's property named <i>key</i>, or `undefined` if it has no such
-    property. All extant handler methods, breakpoints, and so on remain active
-    during the call.
+    property. <i>key</i> must be a string or symbol; <i>receiver</i> must be
+    a debuggee value. If the property is a getter, it will be evaluated with
+    <i>receiver</i> as the receiver, defaulting to the `Debugger.Object`
+    if omitted. All extant handler methods, breakpoints, and so on remain
+    active during the call.
 
-<code>setProperty(<i>key</i>, <i>value</i>)</code>
+<code>setProperty(<i>key</i>, <i>value</i>, [<i>receiver</i>])</code>
 :   Store <i>value</i> as the value of the referent's property named
-    <i>name</i>, creating the property if it does not exist. <i>Name</i>
-    must be a string; <i>value</i> must be a debuggee value. Return a
-    [completion value][cv] with "return" being a success/failure boolean,
-    or else "throw" being the exception thrown during property assignment.
-    All extant handler methods, breakpoints, and so on remain active
-    during the call.
+    <i>key</i>, creating the property if it does not exist. <i>key</i>
+    must be a string or symbol; <i>value</i> and <i>receiver</i> must be
+    debuggee values. If the property is a setter, it will be evaluated with
+    <i>receiver</i> as the receiver, defaulting to the `Debugger.Object`
+    if omitted. Return a [completion value][cv] with "return" being a
+    success/failure boolean, or else "throw" being the exception thrown
+    during property assignment. All extant handler methods, breakpoints,
+    and so on remain active during the call.
 
 <code>getOwnPropertyDescriptor(<i>key</i>)</code>
 :   Return a property descriptor for the property named <i>key</i> of the
     referent. If the referent has no such property, return `undefined`.
     (This function behaves like the standard
     `Object.getOwnPropertyDescriptor` function, except that the object being
     inspected is implicit; the property descriptor returned is allocated as
     if by code scoped to the debugger's global object (and is thus in the
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getProperty-03.js
@@ -0,0 +1,45 @@
+// tests calling script functions via Debugger.Object.prototype.getProperty
+// with different receiver objects.
+"use strict";
+load(libdir + "/asserts.js");
+
+var global = newGlobal();
+var dbg = new Debugger();
+var globalDO = dbg.addDebuggee(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+const sloppy = {
+  get getter() { return this; },
+};
+const strict = {
+  get getter() { "use strict"; return this; },
+};
+debugger;
+`);
+
+function onDebuggerStatement(frame) {
+    const { environment } = frame;
+    const sloppy = environment.getVariable("sloppy");
+    const strict = environment.getVariable("strict");
+
+    assertEq(sloppy.getProperty("getter").return, sloppy);
+    assertEq(sloppy.getProperty("getter", sloppy).return, sloppy);
+    assertEq(sloppy.getProperty("getter", strict).return, strict);
+    assertEq(sloppy.getProperty("getter", 1).return.class, "Number");
+    assertEq(sloppy.getProperty("getter", true).return.class, "Boolean");
+    assertEq(sloppy.getProperty("getter", null).return, globalDO);
+    assertEq(sloppy.getProperty("getter", undefined).return, globalDO);
+    assertErrorMessage(() => sloppy.getProperty("getter", {}), TypeError,
+                       "Debugger: expected Debugger.Object, got Object");
+
+    assertEq(strict.getProperty("getter").return, strict);
+    assertEq(strict.getProperty("getter", sloppy).return, sloppy);
+    assertEq(strict.getProperty("getter", strict).return, strict);
+    assertEq(strict.getProperty("getter", 1).return, 1);
+    assertEq(strict.getProperty("getter", true).return, true);
+    assertEq(strict.getProperty("getter", null).return, null);
+    assertEq(strict.getProperty("getter", undefined).return, undefined);
+    assertErrorMessage(() => strict.getProperty("getter", {}), TypeError,
+                       "Debugger: expected Debugger.Object, got Object");
+};
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-setProperty-03.js
@@ -0,0 +1,66 @@
+// tests calling script functions via Debugger.Object.prototype.setProperty
+// with different receiver objects.
+"use strict";
+load(libdir + "/asserts.js");
+
+var global = newGlobal();
+var dbg = new Debugger();
+var globalDO = dbg.addDebuggee(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+let receiver;
+function check(value, thisVal) {
+  receiver = thisVal;
+  if (value !== "value") throw "Unexpected value";
+}
+const sloppy = {
+  set setter(value) { check(value, this); },
+};
+const strict = {
+  set setter(value) { "use strict"; check(value, this); },
+};
+debugger;
+`);
+
+function onDebuggerStatement(frame) {
+    const { environment } = frame;
+    const sloppy = environment.getVariable("sloppy");
+    const strict = environment.getVariable("strict");
+    const receiver = () => environment.getVariable("receiver");
+    const value = "value";
+
+    assertEq(sloppy.setProperty("setter", value).return, true);
+    assertEq(receiver(), sloppy);
+    assertEq(sloppy.setProperty("setter", value, sloppy).return, true);
+    assertEq(receiver(), sloppy);
+    assertEq(sloppy.setProperty("setter", value, strict).return, true);
+    assertEq(receiver(), strict);
+    assertEq(sloppy.setProperty("setter", value, 1).return, true);
+    assertEq(receiver().class, "Number");
+    assertEq(sloppy.setProperty("setter", value, true).return, true);
+    assertEq(receiver().class, "Boolean");
+    assertEq(sloppy.setProperty("setter", value, null).return, true);
+    assertEq(receiver(), globalDO);
+    assertEq(sloppy.setProperty("setter", value, undefined).return, true);
+    assertEq(receiver(), globalDO);
+    assertErrorMessage(() => sloppy.setProperty("setter", value, {}), TypeError,
+                       "Debugger: expected Debugger.Object, got Object");
+
+    assertEq(strict.setProperty("setter", value).return, true);
+    assertEq(receiver(), strict);
+    assertEq(strict.setProperty("setter", value, sloppy).return, true);
+    assertEq(receiver(), sloppy);
+    assertEq(strict.setProperty("setter", value, strict).return, true);
+    assertEq(receiver(), strict);
+    assertEq(strict.setProperty("setter", value, 1).return, true);
+    assertEq(receiver(), 1);
+    assertEq(strict.setProperty("setter", value, true).return, true);
+    assertEq(receiver(), true);
+    assertEq(strict.setProperty("setter", value, null).return, true);
+    assertEq(receiver(), null);
+    assertEq(strict.setProperty("setter", value, undefined).return, true);
+    assertEq(receiver(), undefined);
+    assertErrorMessage(() => strict.setProperty("setter", value, {}), TypeError,
+                       "Debugger: expected Debugger.Object, got Object");
+};
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -10235,17 +10235,20 @@ static JSObject* IdVectorToArray(JSConte
                                                     unsigned argc, Value* vp) {
   THIS_DEBUGOBJECT(cx, argc, vp, "getProperty", args, object)
 
   RootedId id(cx);
   if (!ValueToId<CanGC>(cx, args.get(0), &id)) {
     return false;
   }
 
-  if (!DebuggerObject::getProperty(cx, object, id, args.rval())) {
+  RootedValue receiver(cx,
+                       args.length() < 2 ? ObjectValue(*object) : args.get(1));
+
+  if (!DebuggerObject::getProperty(cx, object, id, receiver, args.rval())) {
     return false;
   }
 
   return true;
 }
 
 /* static */ bool DebuggerObject::setPropertyMethod(JSContext* cx,
                                                     unsigned argc, Value* vp) {
@@ -10253,17 +10256,21 @@ static JSObject* IdVectorToArray(JSConte
 
   RootedId id(cx);
   if (!ValueToId<CanGC>(cx, args.get(0), &id)) {
     return false;
   }
 
   RootedValue value(cx, args.get(1));
 
-  if (!DebuggerObject::setProperty(cx, object, id, value, args.rval())) {
+  RootedValue receiver(cx,
+                       args.length() < 3 ? ObjectValue(*object) : args.get(2));
+
+  if (!DebuggerObject::setProperty(cx, object, id, value, receiver,
+                                   args.rval())) {
     return false;
   }
 
   return true;
 }
 
 /* static */ bool DebuggerObject::applyMethod(JSContext* cx, unsigned argc,
                                               Value* vp) {
@@ -11192,65 +11199,77 @@ double DebuggerObject::promiseTimeToReso
 
   ErrorCopier ec(ar);
   return DeleteProperty(cx, referent, id, result);
 }
 
 /* static */ bool DebuggerObject::getProperty(JSContext* cx,
                                               HandleDebuggerObject object,
                                               HandleId id,
-                                              MutableHandleValue result) {
-  RootedObject referent(cx, object->referent());
-  Debugger* dbg = object->owner();
-
-  // Enter the debuggee compartment and rewrap all input value for that
-  // compartment. (Rewrapping always takes place in the destination
-  // compartment.)
-  Maybe<AutoRealm> ar;
-  EnterDebuggeeObjectRealm(cx, ar, referent);
-  if (!cx->compartment()->wrap(cx, &referent)) {
-    return false;
-  }
-  cx->markId(id);
-
-  LeaveDebuggeeNoExecute nnx(cx);
-
-  bool ok = GetProperty(cx, referent, referent, id, result);
-
-  return dbg->receiveCompletionValue(ar, ok, result, result);
-}
-
-/* static */ bool DebuggerObject::setProperty(JSContext* cx,
-                                              HandleDebuggerObject object,
-                                              HandleId id, HandleValue value_,
+                                              HandleValue receiver_,
                                               MutableHandleValue result) {
   RootedObject referent(cx, object->referent());
   Debugger* dbg = object->owner();
 
   // Unwrap Debugger.Objects. This happens in the debugger's compartment since
   // that is where any exceptions must be reported.
-  RootedValue value(cx, value_);
-  if (!dbg->unwrapDebuggeeValue(cx, &value)) {
+  RootedValue receiver(cx, receiver_);
+  if (!dbg->unwrapDebuggeeValue(cx, &receiver)) {
     return false;
   }
 
   // Enter the debuggee compartment and rewrap all input value for that
   // compartment. (Rewrapping always takes place in the destination
   // compartment.)
   Maybe<AutoRealm> ar;
   EnterDebuggeeObjectRealm(cx, ar, referent);
   if (!cx->compartment()->wrap(cx, &referent) ||
-      !cx->compartment()->wrap(cx, &value)) {
+      !cx->compartment()->wrap(cx, &receiver)) {
     return false;
   }
   cx->markId(id);
 
   LeaveDebuggeeNoExecute nnx(cx);
 
-  RootedValue receiver(cx, ObjectValue(*referent));
+  bool ok = GetProperty(cx, referent, receiver, id, result);
+
+  return dbg->receiveCompletionValue(ar, ok, result, result);
+}
+
+/* static */ bool DebuggerObject::setProperty(JSContext* cx,
+                                              HandleDebuggerObject object,
+                                              HandleId id, HandleValue value_,
+                                              HandleValue receiver_,
+                                              MutableHandleValue result) {
+  RootedObject referent(cx, object->referent());
+  Debugger* dbg = object->owner();
+
+  // Unwrap Debugger.Objects. This happens in the debugger's compartment since
+  // that is where any exceptions must be reported.
+  RootedValue value(cx, value_);
+  RootedValue receiver(cx, receiver_);
+  if (!dbg->unwrapDebuggeeValue(cx, &value) ||
+      !dbg->unwrapDebuggeeValue(cx, &receiver)) {
+    return false;
+  }
+
+  // Enter the debuggee compartment and rewrap all input value for that
+  // compartment. (Rewrapping always takes place in the destination
+  // compartment.)
+  Maybe<AutoRealm> ar;
+  EnterDebuggeeObjectRealm(cx, ar, referent);
+  if (!cx->compartment()->wrap(cx, &referent) ||
+      !cx->compartment()->wrap(cx, &value) ||
+      !cx->compartment()->wrap(cx, &receiver)) {
+    return false;
+  }
+  cx->markId(id);
+
+  LeaveDebuggeeNoExecute nnx(cx);
+
   ObjectOpResult opResult;
   bool ok = SetProperty(cx, referent, id, value, receiver, opResult);
 
   result.setBoolean(ok && opResult.reallyOk());
   return dbg->receiveCompletionValue(ar, ok, result, result);
 }
 
 /* static */ bool DebuggerObject::call(JSContext* cx,
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -1646,20 +1646,21 @@ class DebuggerObject : public NativeObje
                                         HandleDebuggerObject object,
                                         bool& result);
   static MOZ_MUST_USE bool isSealed(JSContext* cx, HandleDebuggerObject object,
                                     bool& result);
   static MOZ_MUST_USE bool isFrozen(JSContext* cx, HandleDebuggerObject object,
                                     bool& result);
   static MOZ_MUST_USE bool getProperty(JSContext* cx,
                                        HandleDebuggerObject object, HandleId id,
+                                       HandleValue receiver,
                                        MutableHandleValue result);
   static MOZ_MUST_USE bool setProperty(JSContext* cx,
                                        HandleDebuggerObject object, HandleId id,
-                                       HandleValue value,
+                                       HandleValue value, HandleValue receiver,
                                        MutableHandleValue result);
   static MOZ_MUST_USE bool getPrototypeOf(JSContext* cx,
                                           HandleDebuggerObject object,
                                           MutableHandleDebuggerObject result);
   static MOZ_MUST_USE bool getOwnPropertyNames(JSContext* cx,
                                                HandleDebuggerObject object,
                                                MutableHandle<IdVector> result);
   static MOZ_MUST_USE bool getOwnPropertySymbols(