Bug 1515447 - Add receiver parameter to Debugger.Object's getProperty and setProperty. r=jimb, a=RyanVM
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Fri, 28 Dec 2018 19:07:15 +0000
changeset 506662 7bfd0ddce88e35eb5be95af2f62d7a50208b0741
parent 506661 5b52f9f8300439f6f58beead39f28ee22faf4863
child 506663 d5ed432f8da4733c3ab2b8f4ed0a139a4b637d6d
push id10502
push userryanvm@gmail.com
push dateSat, 12 Jan 2019 23:41:05 +0000
treeherdermozilla-beta@8acdc547561d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb, RyanVM
bugs1515447
milestone65.0
Bug 1515447 - Add receiver parameter to Debugger.Object's getProperty and setProperty. r=jimb, a=RyanVM 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
@@ -10243,17 +10243,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) {
@@ -10261,17 +10264,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) {
@@ -11200,65 +11207,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
@@ -1640,20 +1640,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(