Bug 1587497 - Ensure debugger advances to next line when encountering breakpoint and watchpoint.
authorMiriam <bmiriam1230@gmail.com>
Wed, 09 Oct 2019 18:22:41 +0000
changeset 497012 50c93e91923f1f4db59c8f8f1a160edae6e728a8
parent 497011 b40d7c3da6f9a99285901732fc720ef4355dc117
child 497013 96d48afc3a98fddc37833da5db50d39b2fb25af7
push id36674
push userccoroiu@mozilla.com
push dateThu, 10 Oct 2019 09:26:37 +0000
treeherdermozilla-central@f20fa8068ec2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1587497
milestone71.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 1587497 - Ensure debugger advances to next line when encountering breakpoint and watchpoint. Differential Revision: https://phabricator.services.mozilla.com/D48721
devtools/server/actors/object.js
devtools/server/tests/unit/test_watchpoint-02.js
devtools/server/tests/unit/xpcshell.ini
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -134,27 +134,39 @@ const proto = {
     if (watchpointType === "get") {
       this.obj.defineProperty(property, {
         configurable: desc.configurable,
         enumerable: desc.enumerable,
         set: this.obj.makeDebuggeeValue(v => {
           desc.value = v;
         }),
         get: this.obj.makeDebuggeeValue(() => {
+          const frame = this.thread.dbg.getNewestFrame();
+
+          if (!this.thread.hasMoved(frame, "getWatchpoint")) {
+            return false;
+          }
+
           pauseAndRespond("getWatchpoint");
           return desc.value;
         }),
       });
     }
 
     if (watchpointType === "set") {
       this.obj.defineProperty(property, {
         configurable: desc.configurable,
         enumerable: desc.enumerable,
         set: this.obj.makeDebuggeeValue(v => {
+          const frame = this.thread.dbg.getNewestFrame();
+
+          if (!this.thread.hasMoved(frame, "setWatchpoint")) {
+            return;
+          }
+
           pauseAndRespond("setWatchpoint");
           desc.value = v;
         }),
         get: this.obj.makeDebuggeeValue(() => {
           return desc.value;
         }),
       });
     }
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_watchpoint-02.js
@@ -0,0 +1,223 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-shadow */
+
+"use strict";
+
+/*
+Test that debugger advances instead of pausing twice on the
+same line when encountering both a watchpoint and a breakpoint.
+*/
+
+add_task(
+  threadFrontTest(async args => {
+    await testBreakpointAndSetWatchpoint(args);
+    await testBreakpointAndGetWatchpoint(args);
+    await testLoops(args);
+  })
+);
+
+// Test that we advance to the next line when a location
+// has both a breakpoint and set watchpoint.
+async function testBreakpointAndSetWatchpoint({ threadFront, debuggee }) {
+  function evaluateTestCode(debuggee) {
+    /* eslint-disable */
+    Cu.evalInSandbox(
+      `                                   // 1
+      function stopMe(obj) {              // 2
+        debugger;                         // 3
+        obj.a = 2;                        // 4
+        debugger;                         // 5
+      }                                   //
+      stopMe({a: 1})`,
+      debuggee,
+      "1.8",
+      "test_watchpoint-02.js",
+    );
+    /* eslint-disable */
+  }
+
+  const packet = await executeOnNextTickAndWaitForPause(
+    () => evaluateTestCode(debuggee),
+    threadFront
+  );
+
+  info("Test that we pause on the debugger statement.");
+  Assert.equal(packet.frame.where.line, 3);
+  Assert.equal(packet.why.type, "debuggerStatement");
+
+  info("Add set watchpoint.");
+  const args = packet.frame.arguments;
+  const obj = args[0];
+  const objClient = threadFront.pauseGrip(obj);
+  await objClient.addWatchpoint("a", "obj.a", "set");
+
+  info("Add breakpoint.");
+  const source = await getSourceById(threadFront, packet.frame.where.actor);
+
+  const location = {
+    sourceUrl: source.url,
+    line: 4,
+  };
+
+  threadFront.setBreakpoint(
+    location,
+    {},
+  );
+
+  info("Test that pause occurs on breakpoint.");
+  const packet2 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet2.frame.where.line, 4);
+  Assert.equal(packet2.why.type, "breakpoint");
+
+  const packet3 = await resumeAndWaitForPause(threadFront);
+
+  info("Test that we pause on the second debugger statement.");
+  Assert.equal(packet3.frame.where.line, 5);
+  Assert.equal(packet3.why.type, "debuggerStatement");
+
+  info("Remove breakpoint and finish.");
+   threadFront.removeBreakpoint(
+    location,
+    {}
+  );
+
+  await resume(threadFront);
+}
+
+// Test that we advance to the next line when a location 
+// has both a breakpoint and get watchpoint.
+async function testBreakpointAndGetWatchpoint({ threadFront, debuggee }) {
+  function evaluateTestCode(debuggee) {
+    /* eslint-disable */
+    Cu.evalInSandbox(
+      `                                   // 1
+      function stopMe(obj) {              // 2
+        debugger;                         // 3
+        obj.a + 4;                        // 4
+        debugger;                         // 5
+      }                                   // 
+      stopMe({a: 1})`,
+      debuggee,
+      "1.8",
+      "test_watchpoint-02.js",
+    );
+    /* eslint-disable */
+  }
+
+  const packet = await executeOnNextTickAndWaitForPause(
+    () => evaluateTestCode(debuggee),
+    threadFront
+  );
+
+  info("Test that we pause on the debugger statement.");
+  Assert.equal(packet.frame.where.line, 3);
+
+  info("Add get watchpoint.");
+  const args = packet.frame.arguments;
+  const obj = args[0];
+  const objClient = threadFront.pauseGrip(obj);
+  await objClient.addWatchpoint("a", "obj.a", "get");
+
+  info("Add breakpoint.");
+  const source = await getSourceById(threadFront, packet.frame.where.actor);
+
+  const location = {
+    sourceUrl: source.url,
+    line: 4,
+  };
+
+  threadFront.setBreakpoint(
+    location,
+    {},
+  );
+
+  info("Test that pause occurs on breakpoint.");
+  const packet2 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet2.frame.where.line, 4);
+  Assert.equal(packet2.why.type, "breakpoint");
+
+  const packet3 = await resumeAndWaitForPause(threadFront);
+
+  info("Test that we pause on the second debugger statement.");
+  Assert.equal(packet3.frame.where.line, 5);
+  Assert.equal(packet3.why.type, "debuggerStatement");
+
+  info("Remove breakpoint and finish.");
+  threadFront.removeBreakpoint(
+    location,
+    {}
+  );
+
+  await resume(threadFront);
+}
+
+// Test that we can pause multiple times 
+// on the same line for a watchpoint.
+async function testLoops({ threadFront, debuggee, targetFront }) {
+  async function evaluateJS(input) {
+    const consoleFront = await targetFront.getFront("console");
+    const { result } = await consoleFront.evaluateJSAsync(input, {
+      thread: threadFront.actor,
+      frameActor: packet.frame.actor,
+    });
+    return result;
+  }
+
+  function evaluateTestCode(debuggee) {
+    /* eslint-disable */
+    Cu.evalInSandbox(
+      `                                   // 1
+      function stopMe(obj) {              // 2
+        let i = 0;                        // 3  
+        debugger;                         // 4
+        while (i++ < 2) {                 // 5
+          obj.a = 2;                      // 6
+        }                                 // 7
+        debugger;                         // 8
+      }                                   // 
+      stopMe({a: 1})`,
+      debuggee,
+      "1.8",
+      "test_watchpoint-02.js",
+    );
+    /* eslint-disable */
+  }
+
+  const packet = await executeOnNextTickAndWaitForPause(
+    () => evaluateTestCode(debuggee),
+    threadFront
+  );
+
+  info("Test that we pause on the debugger statement.");
+  Assert.equal(packet.frame.where.line, 4);
+  Assert.equal(packet.why.type, "debuggerStatement");
+
+  info("Add set watchpoint.");
+  const args = packet.frame.arguments;
+  const obj = args[0];
+  const objClient = threadFront.pauseGrip(obj);
+  await objClient.addWatchpoint("a", "obj.a", "set");
+
+  info("Test that watchpoint triggers pause on set.");
+  const packet2 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet2.frame.where.line, 6);
+  Assert.equal(packet2.why.type, "setWatchpoint");
+  let result =  await evaluateJS("obj.a");
+  Assert.equal(result, 1);
+
+  info("Test that watchpoint triggers pause on set (2nd time).");
+  const packet3 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet3.frame.where.line, 6);
+  Assert.equal(packet3.why.type, "setWatchpoint");
+  let result2 =  await evaluateJS("obj.a");
+  Assert.equal(result2, 2);
+
+  info("Test that we pause on second debugger statement.");
+  const packet4 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet4.frame.where.line, 8);
+  Assert.equal(packet4.why.type, "debuggerStatement");
+
+  await resume(threadFront);
+}
+
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -212,16 +212,17 @@ skip-if = true # breakpoint sliding is n
 [test_longstringactor.js]
 [test_longstringgrips-01.js]
 [test_source-01.js]
 [test_source-02.js]
 [test_source-03.js]
 [test_source-04.js]
 [test_wasm_source-01.js]
 [test_watchpoint-01.js]
+[test_watchpoint-02.js]
 [test_breakpoint-actor-map.js]
 skip-if = true # tests for breakpoint actors are obsolete bug 1524374
 [test_unsafeDereference.js]
 [test_add_actors.js]
 [test_ignore_caught_exceptions.js]
 [test_ignore_no_interface_exceptions.js]
 [test_requestTypes.js]
 reason = bug 937197