Bug 1432885 - Step in to the first valid step target, r=loganfsmyth.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 14 May 2019 07:11:04 -1000
changeset 533284 4478ea184906f92ad82672ca5e91ae96a3a34107
parent 533283 4a74609752d2e77e4be401e92978c9c32a842b40
child 533285 45fb04e542c521dcdbf361091b06f4de6979789f
push id11276
push userrgurzau@mozilla.com
push dateMon, 20 May 2019 13:11:24 +0000
treeherdermozilla-beta@847755a7c325 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersloganfsmyth
bugs1432885
milestone68.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 1432885 - Step in to the first valid step target, r=loganfsmyth.
devtools/client/debugger/test/mochitest/browser.ini
devtools/client/debugger/test/mochitest/browser_dbg-step-in-uninitialized.js
devtools/client/debugger/test/mochitest/examples/doc-step-in-uninitialized.html
devtools/server/actors/thread.js
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -661,16 +661,17 @@ support-files =
   examples/doc-windowless-workers.html
   examples/doc-windowless-workers-early-breakpoint.html
   examples/simple-worker.js
   examples/doc-worker-scopes.html
   examples/scopes-worker.js
   examples/doc-event-handler.html
   examples/doc-eval-throw.html
   examples/doc-sourceURL-breakpoint.html
+  examples/doc-step-in-uninitialized.html
 
 [browser_dbg-asm.js]
 [browser_dbg-audiocontext.js]
 [browser_dbg-async-stepping.js]
 [browser_dbg-sourcemapped-breakpoint-console.js]
 skip-if = (os == "win" && ccov) # Bug 1453549
 [browser_dbg-xhr-breakpoints.js]
 [browser_dbg-xhr-run-to-completion.js]
@@ -774,16 +775,17 @@ skip-if = os == "win" || (verify) # Bug 
 skip-if = os == 'linux' && !asan # bug 1447118
 [browser_dbg-sources.js]
 [browser_dbg-sources-arrow-keys.js]
 [browser_dbg-sources-named-eval.js]
 [browser_dbg-sources-querystring.js]
 skip-if = true
 [browser_dbg-stepping.js]
 skip-if = debug || (verify && (os == 'win')) || (os == "win" && os_version == "6.1")
+[browser_dbg-step-in-uninitialized.js]
 [browser_dbg-tabs.js]
 [browser_dbg-tabs-keyboard.js]
 skip-if = os == "win"
 [browser_dbg-tabs-pretty-print.js]
 [browser_dbg-tabs-without-urls.js]
 [browser_dbg-toggling-tools.js]
 [browser_dbg-react-app.js]
 skip-if = os == "win"
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-step-in-uninitialized.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// When stepping into a function, 'let' variables should show as uninitialized
+// instead of undefined.
+
+function findNodeValue(dbg, text) {
+  for (let index = 0;; index++) {
+    var elem = findElement(dbg, "scopeNode", index);
+    if (elem && elem.innerText == text) {
+      return findElement(dbg, "scopeValue", index).innerText;
+    }
+  }
+}
+
+add_task(async function test() {
+  const dbg = await initDebugger("doc-step-in-uninitialized.html");
+  invokeInTab("main");
+  await waitForPaused(dbg, "doc-step-in-uninitialized.html");
+
+  await stepOver(dbg);
+  await stepIn(dbg);
+
+  assertDebugLine(dbg, 8);
+  assertPausedLocation(dbg);
+
+  // We step past the 'let x' at the start of the function because it is not
+  // a breakpoint position.
+  ok(findNodeValue(dbg, "x") == "undefined", "x undefined");
+  ok(findNodeValue(dbg, "y") == "(uninitialized)", "y uninitialized");
+
+  await stepOver(dbg);
+
+  assertDebugLine(dbg, 9);
+
+  ok(findNodeValue(dbg, "y") == "3", "y initialized");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/examples/doc-step-in-uninitialized.html
@@ -0,0 +1,11 @@
+<script>
+function main() {
+  debugger;
+  foo();
+}
+function foo() {
+  let x;
+  let y = 3;
+  return 0;
+}
+</script>
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -552,26 +552,46 @@ const ThreadActor = ActorClassWithSpec(t
     }
 
     // If the parent actor has been closed, terminate the debuggee script
     // instead of continuing. Executing JS after the content window is gone is
     // a bad idea.
     return this._parentClosed ? null : undefined;
   },
 
-  _makeOnEnterFrame: function({ pauseAndRespond }) {
+  _makeOnEnterFrame: function({ thread, pauseAndRespond }) {
     return frame => {
       const { generatedSourceActor } = this.sources.getFrameLocation(frame);
 
       const url = generatedSourceActor.url;
       if (this.sources.isBlackBoxed(url)) {
         return undefined;
       }
 
-      return pauseAndRespond(frame);
+      // If the initial frame offset is a step target, we are done.
+      if (frame.script.getOffsetMetadata(frame.offset).isStepStart) {
+        return pauseAndRespond(frame);
+      }
+
+      // Continue forward until we get to a valid step target.
+      const { onStep, onPop } = thread._makeSteppingHooks(
+        null, "next", false, null
+      );
+
+      if (thread.dbg.replaying) {
+        const offsets =
+          thread._findReplayingStepOffsets(null, frame,
+                                           /* rewinding = */ false);
+        frame.setReplayingOnStep(onStep, offsets);
+      } else {
+        frame.onStep = onStep;
+      }
+
+      frame.onPop = onPop;
+      return undefined;
     };
   },
 
   _makeOnPop: function({ thread, pauseAndRespond, startLocation, steppingType }) {
     const result = function(completion) {
       // onPop is called with 'this' set to the current frame.
       const generatedLocation = thread.sources.getFrameLocation(this);
 
@@ -641,17 +661,17 @@ const ThreadActor = ActorClassWithSpec(t
   _intraFrameLocationIsStepTarget: function(startLocation, script, offset) {
     // Only allow stepping stops at entry points for the line.
     if (!script.getOffsetMetadata(offset).isBreakpoint) {
       return false;
     }
 
     const generatedLocation = this.sources.getScriptOffsetLocation(script, offset);
 
-    if (startLocation.generatedUrl !== generatedLocation.generatedUrl) {
+    if (!startLocation || startLocation.generatedUrl !== generatedLocation.generatedUrl) {
       return true;
     }
 
     // TODO(logan): When we remove points points, this can be removed too as
     // we assert that we're at a different frame offset from the last time
     // we paused.
     const lineChanged = startLocation.generatedLine !== generatedLocation.generatedLine;
     const columnChanged =
@@ -670,22 +690,16 @@ const ThreadActor = ActorClassWithSpec(t
       return pausePoint.step;
     }
 
     return script.getOffsetMetadata(offset).isStepStart;
   },
 
   _makeOnStep: function({ thread, pauseAndRespond, startFrame,
                           startLocation, steppingType, completion, rewinding }) {
-    // Breaking in place: we should always pause.
-    if (steppingType === "break") {
-      return () => pauseAndRespond(this);
-    }
-
-    // Otherwise take what a "step" means into consideration.
     return function() {
       // onStep is called with 'this' set to the current frame.
 
       const generatedLocation = thread.sources.getFrameLocation(this);
 
       // Always continue execution if either:
       //
       // 1. We are in a source mapped region, but inside a null mapping