Bug 1013219 - set the line number of the terminating retrval; r=jimb, r=ejpbruel, r=fitzgen
authorTom Tromey <tom@tromey.com>
Mon, 28 Mar 2016 12:20:00 +0200
changeset 290851 5777c32d2b391e43d9e5c23fea0c57324e125d72
parent 290850 e1c798332d3be8aed7b934d2e6b3982d5de7d902
child 290852 1b45c030f024b195ad68b3680306e873bc11deb1
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb, ejpbruel, fitzgen
bugs1013219
milestone48.0a1
Bug 1013219 - set the line number of the terminating retrval; r=jimb, r=ejpbruel, r=fitzgen
devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js
devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js
devtools/server/tests/unit/head_dbg.js
devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-script.js
devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-at-end-of-script.js
devtools/server/tests/unit/test_breakpoint-13.js
devtools/server/tests/unit/test_breakpoint-14.js
devtools/server/tests/unit/test_get-executable-lines.js
devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-at-end-of-script.js
devtools/server/tests/unit/test_stepping-03.js
devtools/server/tests/unit/test_stepping-06.js
devtools/server/tests/unit/test_stepping-07.js
devtools/server/tests/unit/xpcshell.ini
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/jit-test/tests/debug/Frame-onPop-23.js
js/src/jit-test/tests/debug/Frame-onStep-11.js
js/src/jit-test/tests/debug/Frame-onStep-12.js
js/src/jit-test/tests/debug/Frame-onStep-13.js
js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js
js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js
js/src/jit-test/tests/debug/Script-startLine.js
--- a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js
@@ -133,21 +133,21 @@ function test() {
       is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
          "The selected value is the sources pane is incorrect.");
 
       is(gEditor.getText().search(/firstCall/), 118,
          "The first source is displayed.");
       is(gEditor.getText().search(/debugger/), -1,
          "The second source is not displayed.");
 
-      ok(isCaretPos(gPanel, 5),
+      ok(isCaretPos(gPanel, 6),
          "Editor caret location is correct.");
-      is(gEditor.getDebugLocation(), 4,
+      is(gEditor.getDebugLocation(), 5,
          "Editor debugger location is correct.");
-      ok(gEditor.hasLineClass(4, "debug-line"),
+      ok(gEditor.hasLineClass(5, "debug-line"),
          "The debugged line is highlighted appropriately (3).");
     }
 
     Task.spawn(function*() {
       yield waitForSourceShown(gPanel, "-01.js", 1);
       ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel1),
          "Title with first source is correct.");
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js
@@ -128,21 +128,21 @@ function test() {
          "The selected value is the sources pane is incorrect.");
 
       is(gEditor.getText().search(/firstCall/), 118,
          "The first source is displayed.");
       is(gEditor.getText().search(/debugger/), -1,
          "The second source is not displayed.");
 
       // The editor's debug location takes a tick to update.
-      ok(isCaretPos(gPanel, 5),
+      ok(isCaretPos(gPanel, 6),
          "Editor caret location is correct.");
-      is(gEditor.getDebugLocation(), 4,
+      is(gEditor.getDebugLocation(), 5,
          "Editor debugger location is correct.");
-      ok(gEditor.hasLineClass(4, "debug-line"),
+      ok(gEditor.hasLineClass(5, "debug-line"),
          "The debugged line is highlighted appropriately.");
 
       deferred.resolve();
 
       return deferred.promise;
     }
 
     Task.spawn(function*() {
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -363,20 +363,23 @@ function attachTestThread(aClient, aTitl
     }, onAttach);
   });
 }
 
 // Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's
 // thread, and then resume it. Pass |aCallback| the thread's response to
 // the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
 // thread.
-function attachTestTabAndResume(aClient, aTitle, aCallback) {
-  attachTestThread(aClient, aTitle, function(aResponse, aTabClient, aThreadClient) {
-    aThreadClient.resume(function (aResponse) {
-      aCallback(aResponse, aTabClient, aThreadClient);
+function attachTestTabAndResume(aClient, aTitle, aCallback = () => {}) {
+  return new Promise((resolve, reject) => {
+    attachTestThread(aClient, aTitle, function(aResponse, aTabClient, aThreadClient) {
+      aThreadClient.resume(function (aResponse) {
+        aCallback(aResponse, aTabClient, aThreadClient);
+        resolve([aResponse, aTabClient, aThreadClient]);
+      });
     });
   });
 }
 
 /**
  * Initialize the testing debugger server.
  */
 function initTestDebuggerServer(aServer = DebuggerServer)
@@ -716,16 +719,30 @@ function resumeAndWaitForPause(client, t
 function stepIn(client, threadClient) {
   dumpn("Stepping in.");
   const paused = waitForPause(client);
   return threadClient.stepIn()
     .then(() => paused);
 }
 
 /**
+ * Resume JS execution for a step over and wait for the pause after the step
+ * has been taken.
+ *
+ * @param DebuggerClient client
+ * @param ThreadClient threadClient
+ * @returns Promise
+ */
+function stepOver(client, threadClient) {
+  dumpn("Stepping over.");
+  return threadClient.stepOver()
+    .then(() => waitForPause(client));
+}
+
+/**
  * Get the list of `count` frames currently on stack, starting at the index
  * `first` for the specified thread.
  *
  * @param ThreadClient threadClient
  * @param Number first
  * @param Number count
  * @returns Promise
  */
--- a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-script.js
+++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-script.js
@@ -1,5 +1,5 @@
 "use strict";
 
 function f() {
-  function g() { var a = 1; var b = 2; } g();
+  function g() { var a = 1; var b = 2; return; } g();
 }
--- a/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-at-end-of-script.js
+++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-at-end-of-script.js
@@ -1,11 +1,12 @@
 "use strict";
 
 function f() {
   function g() {
     var a = 1;
     var b = 2;
+    return;
 
   }
 
   g();
 }
--- a/devtools/server/tests/unit/test_breakpoint-13.js
+++ b/devtools/server/tests/unit/test_breakpoint-13.js
@@ -34,79 +34,78 @@ function run_test_with_server(aServer, a
 }
 
 function test_simple_breakpoint()
 {
   gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
     let source = gThreadClient.source(aPacket.frame.where.source);
     let location = { line: gDebuggee.line0 + 2 };
 
-    source.setBreakpoint(location, function (aResponse, bpClient) {
-      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-        // Check that the stepping worked.
-        do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
-        do_check_eq(aPacket.why.type, "resumeLimit");
-
-        gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    source.setBreakpoint(location, Task.async(function*(aResponse, bpClient) {
+      const testCallbacks = [
+        function(aPacket) {
+          // Check that the stepping worked.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
           // Entered the foo function call frame.
           do_check_eq(aPacket.frame.where.line, location.line);
           do_check_neq(aPacket.why.type, "breakpoint");
           do_check_eq(aPacket.why.type, "resumeLimit");
-
-          gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-            // Check that the breakpoint wasn't the reason for this pause, but
-            // that the frame is about to be popped while stepping.
-            do_check_eq(aPacket.frame.where.line, location.line);
-            do_check_neq(aPacket.why.type, "breakpoint");
-            do_check_eq(aPacket.why.type, "resumeLimit");
-            do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
-
-            gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-              // The foo function call frame was just popped from the stack.
-              do_check_eq(gDebuggee.a, 1);
-              do_check_eq(gDebuggee.b, undefined);
-              do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
-              do_check_eq(aPacket.why.type, "resumeLimit");
-              do_check_eq(aPacket.poppedFrames.length, 1);
-
-              gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-                // Check that the debugger statement wasn't the reason for this pause.
-                do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
-                do_check_neq(aPacket.why.type, "debuggerStatement");
-                do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
+          // At the end of the foo function call frame.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+          do_check_neq(aPacket.why.type, "breakpoint");
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
+          // Check that the breakpoint wasn't the reason for this pause, but
+          // that the frame is about to be popped while stepping.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+          do_check_neq(aPacket.why.type, "breakpoint");
+          do_check_eq(aPacket.why.type, "resumeLimit");
+          do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
+        },
+        function(aPacket) {
+          // The foo function call frame was just popped from the stack.
+          do_check_eq(gDebuggee.a, 1);
+          do_check_eq(gDebuggee.b, undefined);
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+          do_check_eq(aPacket.why.type, "resumeLimit");
+          do_check_eq(aPacket.poppedFrames.length, 1);
+        },
+        function(aPacket) {
+          // Check that the debugger statement wasn't the reason for this pause.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
+          do_check_neq(aPacket.why.type, "debuggerStatement");
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
+          // Check that the debugger statement wasn't the reason for this pause.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7);
+          do_check_neq(aPacket.why.type, "debuggerStatement");
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+      ];
 
-                gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-                  // Check that the debugger statement wasn't the reason for this pause.
-                  do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7);
-                  do_check_neq(aPacket.why.type, "debuggerStatement");
-                  do_check_eq(aPacket.why.type, "resumeLimit");
-
-                  // Remove the breakpoint and finish.
-                  bpClient.remove(() => gThreadClient.resume(() => gClient.close(gCallback)));
+      for (let callback of testCallbacks) {
+        let waiter = waitForPause(gThreadClient);
+        gThreadClient.stepIn();
+        let packet = yield waiter;
+        callback(packet);
+      }
 
-                });
-                // Step past the debugger statement.
-                gThreadClient.stepIn();
-              });
-              // Step into the debugger statement.
-              gThreadClient.stepIn();
-            });
-            // Get back to the frame above.
-            gThreadClient.stepIn();
-          });
-          // Step to the end of the function call frame.
-          gThreadClient.stepIn();
-        });
-
-        // Step into the function call.
-        gThreadClient.stepIn();
-      });
-      // Step into the next line with the function call.
+      // Remove the breakpoint and finish.
+      let waiter = waitForPause(gThreadClient);
       gThreadClient.stepIn();
-    });
+      yield waiter;
+      bpClient.remove(() => gThreadClient.resume(() => gClient.close(gCallback)));
+    }));
   });
 
   Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
                    "function foo() {\n" + // line0 + 1
                    "  this.a = 1;\n" +    // line0 + 2 <-- Breakpoint is set here.
                    "}\n" +                // line0 + 3
                    "debugger;\n" +        // line0 + 4
                    "foo();\n" +           // line0 + 5
--- a/devtools/server/tests/unit/test_breakpoint-14.js
+++ b/devtools/server/tests/unit/test_breakpoint-14.js
@@ -34,77 +34,76 @@ function run_test_with_server(aServer, a
 }
 
 function test_simple_breakpoint()
 {
   gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
     let source = gThreadClient.source(aPacket.frame.where.source);
     let location = { line: gDebuggee.line0 + 2 };
 
-    source.setBreakpoint(location, function (aResponse, bpClient) {
-      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-        // Check that the stepping worked.
-        do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
-        do_check_eq(aPacket.why.type, "resumeLimit");
-
-        gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    source.setBreakpoint(location, Task.async(function*(aResponse, bpClient) {
+      const testCallbacks = [
+        function(aPacket) {
+          // Check that the stepping worked.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
           // Reached the breakpoint.
           do_check_eq(aPacket.frame.where.line, location.line);
           do_check_eq(aPacket.why.type, "breakpoint");
           do_check_neq(aPacket.why.type, "resumeLimit");
-
-          gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-            // The frame is about to be popped while stepping.
-            do_check_eq(aPacket.frame.where.line, location.line);
-            do_check_neq(aPacket.why.type, "breakpoint");
-            do_check_eq(aPacket.why.type, "resumeLimit");
-            do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
-
-            gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-              // The foo function call frame was just popped from the stack.
-              do_check_eq(gDebuggee.a, 1);
-              do_check_eq(gDebuggee.b, undefined);
-              do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
-              do_check_eq(aPacket.why.type, "resumeLimit");
-              do_check_eq(aPacket.poppedFrames.length, 1);
-
-              gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-                // Check that the debugger statement wasn't the reason for this pause.
-                do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
-                do_check_neq(aPacket.why.type, "debuggerStatement");
-                do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
+          // Stepped to the closing brace of the function.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
+          // The frame is about to be popped while stepping.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+          do_check_neq(aPacket.why.type, "breakpoint");
+          do_check_eq(aPacket.why.type, "resumeLimit");
+          do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
+        },
+        function(aPacket) {
+          // The foo function call frame was just popped from the stack.
+          do_check_eq(gDebuggee.a, 1);
+          do_check_eq(gDebuggee.b, undefined);
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+          do_check_eq(aPacket.why.type, "resumeLimit");
+          do_check_eq(aPacket.poppedFrames.length, 1);
+        },
+        function(aPacket) {
+          // Check that the debugger statement wasn't the reason for this pause.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
+          do_check_neq(aPacket.why.type, "debuggerStatement");
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+        function(aPacket) {
+          // Check that the debugger statement wasn't the reason for this pause.
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7);
+          do_check_neq(aPacket.why.type, "debuggerStatement");
+          do_check_eq(aPacket.why.type, "resumeLimit");
+        },
+      ];
 
-                gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-                  // Check that the debugger statement wasn't the reason for this pause.
-                  do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7);
-                  do_check_neq(aPacket.why.type, "debuggerStatement");
-                  do_check_eq(aPacket.why.type, "resumeLimit");
-
-                  // Remove the breakpoint and finish.
-                  bpClient.remove(() => gThreadClient.resume(() => gClient.close(gCallback)));
+      for (let callback of testCallbacks) {
+        let waiter = waitForPause(gThreadClient);
+        gThreadClient.stepOver();
+        let packet = yield waiter;
+        callback(packet);
+      }
 
-                });
-                // Step past the debugger statement.
-                gThreadClient.stepOver();
-              });
-              // Step over the debugger statement.
-              gThreadClient.stepOver();
-            });
-            // Get back to the frame above.
-            gThreadClient.stepOver();
-          });
-          // Step to the end of the function call frame.
-          gThreadClient.stepOver();
-        });
-        // Step over the function call.
-        gThreadClient.stepOver();
-      });
-      // Step over to the next line with the function call.
+      // Remove the breakpoint and finish.
+      let waiter = waitForPause(gThreadClient);
       gThreadClient.stepOver();
-    });
+      yield waiter;
+      bpClient.remove(() => gThreadClient.resume(() => gClient.close(gCallback)));
+    }));
   });
 
   Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
                    "function foo() {\n" + // line0 + 1
                    "  this.a = 1;\n" +    // line0 + 2 <-- Breakpoint is set here.
                    "}\n" +                // line0 + 3
                    "debugger;\n" +        // line0 + 4
                    "foo();\n" +           // line0 + 5
--- a/devtools/server/tests/unit/test_get-executable-lines.js
+++ b/devtools/server/tests/unit/test_get-executable-lines.js
@@ -33,17 +33,17 @@ function run_test() {
 function test_executable_lines() {
   gThreadClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) {
     do_check_eq(evt, "newSource");
 
     gThreadClient.getSources(function ({error, sources}) {
       do_check_true(!error);
       let source = gThreadClient.source(sources[0]);
       source.getExecutableLines(function(lines){
-        do_check_true(arrays_equal([2, 5, 7, 8, 12, 14, 16], lines));
+        do_check_true(arrays_equal([2, 5, 7, 8, 10, 12, 14, 16], lines));
         finishClient(gClient);
       });
     });
   });
 
   let code = readFile("sourcemapped.js");
 
   Components.utils.evalInSandbox(code, gDebuggee, "1.8",
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-at-end-of-script.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-at-end-of-script.js
@@ -21,22 +21,22 @@ function run_test() {
     let [, threadClient] = yield attachThread(tabClient);
     yield resume(threadClient);
 
     let promise = waitForNewSource(threadClient, SOURCE_URL);
     loadSubScript(SOURCE_URL, global);
     let { source } = yield promise;
     let sourceClient = threadClient.source(source);
 
-    let location = { line: 7 };
+    let location = { line: 8 };
     let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
     do_check_false(packet.isPending); // NOTE: Change this when bug 1148356 lands
     do_check_true("actualLocation" in packet);
     let actualLocation = packet.actualLocation;
-    do_check_eq(actualLocation.line, 10);
+    do_check_eq(actualLocation.line, 11);
 
     packet = yield executeOnNextTickAndWaitForPause(function () {
       Cu.evalInSandbox("f()", global);
     }, client);
     do_check_eq(packet.type, "paused");
     let why = packet.why;
     do_check_eq(why.type, "breakpoint");
     do_check_eq(why.actors.length, 1);
--- a/devtools/server/tests/unit/test_stepping-03.js
+++ b/devtools/server/tests/unit/test_stepping-03.js
@@ -33,17 +33,17 @@ function run_test_with_server(aServer, a
 }
 
 function test_simple_stepping()
 {
   gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
     gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
       // Check the return value.
       do_check_eq(aPacket.type, "paused");
-      do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 4);
+      do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
       do_check_eq(aPacket.why.type, "resumeLimit");
       // Check that stepping worked.
       do_check_eq(gDebuggee.a, 1);
       do_check_eq(gDebuggee.b, 2);
 
       gThreadClient.resume(function () {
         gClient.close(gCallback);
       });
--- a/devtools/server/tests/unit/test_stepping-06.js
+++ b/devtools/server/tests/unit/test_stepping-06.js
@@ -46,17 +46,17 @@ function test_simple_stepping()
       do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 4);
       do_check_eq(aPacket.why.type, "resumeLimit");
       do_check_eq(aPacket.why.frameFinished.return, 10);
 
       gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
         gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
           // Check that the return value is undefined.
           do_check_eq(aPacket.type, "paused");
-          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7);
+          do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 8);
           do_check_eq(aPacket.why.type, "resumeLimit");
           do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
 
           gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
             gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
               // Check that the exception was thrown.
               do_check_eq(aPacket.type, "paused");
               do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 11);
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-07.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that stepping over an implicit return makes sense. Bug 1155966.
+ */
+
+var gDebuggee;
+var gClient;
+var gCallback;
+
+function run_test() {
+  do_test_pending();
+  run_test_with_server(DebuggerServer, function () {
+    run_test_with_server(WorkerDebuggerServer, do_test_finished);
+  });
+};
+
+function run_test_with_server(aServer, aCallback) {
+  gCallback = aCallback;
+  initTestDebuggerServer(aServer);
+  gDebuggee = addTestGlobal("test-stepping", aServer);
+  gClient = new DebuggerClient(aServer.connectPipe());
+  gClient.connect(testSteppingAndReturns);
+}
+
+const testSteppingAndReturns = Task.async(function*() {
+  const [attachResponse, tabClient, threadClient] = yield attachTestTabAndResume(gClient, "test-stepping");
+  ok(!attachResponse.error, "Should not get an error attaching");
+
+  dumpn("Evaluating test code and waiting for first debugger statement");
+  const dbgStmt1 = yield executeOnNextTickAndWaitForPause(evaluateTestCode, gClient)
+  equal(dbgStmt1.frame.where.line, 3,
+        "Should be at debugger statement on line 3")
+
+  dumpn("Testing stepping with implicit return");
+  const step1 = yield stepOver(gClient, threadClient);
+  equal(step1.frame.where.line, 4, "Should step to line 4");
+  const step2 = yield stepOver(gClient, threadClient);
+  equal(step2.frame.where.line, 7,
+        "Should step to line 7, the implicit return at the last line of the function");
+  // This assertion doesn't pass yet. You would need to do *another*
+  // step at the end of this function to get the frameFinished.
+  // See bug 923975.
+  //
+  // ok(step2.why.frameFinished, "This should be the implicit function return");
+
+  dumpn("Continuing and waiting for second debugger statement");
+  const dbgStmt2 = yield resumeAndWaitForPause(gClient, threadClient);
+  equal(dbgStmt2.frame.where.line, 12,
+        "Should be at debugger statement on line 3")
+
+  dumpn("Testing stepping with explicit return");
+  const step3 = yield stepOver(gClient, threadClient);
+  equal(step3.frame.where.line, 13, "Should step to line 13");
+  const step4 = yield stepOver(gClient, threadClient);
+  equal(step4.frame.where.line, 13, "Should step out of the function from line 13");
+  ok(step4.why.frameFinished, "This should be the explicit function return");
+
+  finishClient(gClient, gCallback);
+});
+
+function evaluateTestCode() {
+  Cu.evalInSandbox(
+    `                                   //  1
+    function implicitReturn() {         //  2
+      debugger;                         //  3
+      if (this.someUndefinedProperty) { //  4
+        yikes();                        //  5
+      }                                 //  6
+    }                                   //  7
+                                        //  8
+    var yes = true;                     //  9
+    function explicitReturn() {         // 10
+      if (yes) {                        // 11
+        debugger;                       // 12
+        return 1;                       // 13
+      }                                 // 14
+    }                                   // 15
+                                        // 16
+    implicitReturn();                   // 17
+    explicitReturn();                   // 18
+    `,                                  // 19
+    gDebuggee,
+    "1.8",
+    "test_stepping-07-test-code.js",
+    1
+  );
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -203,16 +203,17 @@ reason = bug 820380
 [test_promise_state-03.js]
 [test_interrupt.js]
 [test_stepping-01.js]
 [test_stepping-02.js]
 [test_stepping-03.js]
 [test_stepping-04.js]
 [test_stepping-05.js]
 [test_stepping-06.js]
+[test_stepping-07.js]
 [test_framebindings-01.js]
 [test_framebindings-02.js]
 [test_framebindings-03.js]
 [test_framebindings-04.js]
 [test_framebindings-05.js]
 [test_framebindings-06.js]
 [test_framebindings-07.js]
 [test_pause_exceptions-01.js]
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3596,16 +3596,19 @@ BytecodeEmitter::emitFunctionScript(Pars
         if (!emit1(JSOP_RUNONCE))
             return false;
         switchToMain();
     }
 
     if (!emitTree(body))
         return false;
 
+    if (!updateSourceCoordNotes(body->pn_pos.end))
+        return false;
+
     if (sc->isFunctionBox()) {
         if (sc->asFunctionBox()->isGenerator()) {
             // If we fall off the end of a generator, do a final yield.
             if (sc->asFunctionBox()->isStarGenerator() && !emitPrepareIteratorResult())
                 return false;
 
             if (!emit1(JSOP_UNDEFINED))
                 return false;
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3205,16 +3205,18 @@ Parser<ParseHandler>::functionArgsAndBod
 #endif
         if (tokenStream.hadError())
             return false;
         funbox->bufEnd = pos().end;
         if (kind == Statement && !MatchOrInsertSemicolonAfterExpression(tokenStream))
             return false;
     }
 
+    handler.setEndPosition(body, pos().begin);
+
     return finishFunctionDefinition(pn, funbox, body);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::checkYieldNameValidity()
 {
     // In star generators and in JS >= 1.7, yield is a keyword.  Otherwise in
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-23.js
@@ -0,0 +1,34 @@
+// Check that the line number reported at an onPop stop makes sense,
+// even when it happens on an "artificial" instruction.
+
+var g = newGlobal();
+
+// This bit of code arranges for the line number of the "artificial"
+// instruction to be something nonsensical -- the middle of a loop
+// which cannot be entered.
+g.eval(`function f() {
+  debugger;                   // +0
+  if(false) {                 // +1
+    for(var b=0; b<0; b++) {  // +2
+      c = 2;                  // +3
+    }                         // +4
+  }                           // +5
+}                             // +6
+`);
+
+var dbg = Debugger(g);
+
+let debugLine;
+let foundLine;
+
+dbg.onDebuggerStatement = function(frame) {
+  debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+  frame.onPop = function(c) {
+    foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+  };
+};
+
+g.eval("f();\n");
+
+// The stop should happen on the closing brace of the function.
+assertEq(foundLine == debugLine + 6, true);
--- a/js/src/jit-test/tests/debug/Frame-onStep-11.js
+++ b/js/src/jit-test/tests/debug/Frame-onStep-11.js
@@ -28,9 +28,9 @@ dbg.onDebuggerStatement = function(frame
     if (foundLine != debugLine && this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) {
       foundLines += "," + (foundLine - debugLine);
     }
   };
 };
 
 g.f();
 
-assertEq(foundLines, ",1,2,3,4,5,6,7,8,10");
+assertEq(foundLines, ",1,2,3,4,5,6,7,8,10,11");
--- a/js/src/jit-test/tests/debug/Frame-onStep-12.js
+++ b/js/src/jit-test/tests/debug/Frame-onStep-12.js
@@ -60,70 +60,70 @@ function testOne(name, body, expected) {
 
 // Test the instructions at the end of a "try".
 testOne("testTryFinally",
         `try {
            ${bitOfCode}
          } finally {            // +6
          }                      // +7
          nothing();             // +8
-        `, "168");
+        `, "1689");
 
 // The same but without a finally clause.
 testOne("testTryCatch",
         `try {
            ${bitOfCode}
          } catch (e) {          // +6
          }                      // +7
          nothing();             // +8
-        `, "18");
+        `, "189");
 
 // Test the instructions at the end of a "catch".
 testOne("testCatchFinally",
         `try {
            throw new TypeError();
          } catch (e) {
            ${bitOfCode}
          } finally {            // +6
          }                      // +7
          nothing();             // +8
-        `, "168");
+        `, "1689");
 
 // The same but without a finally clause.  This relies on a
 // SpiderMonkey extension, because otherwise there's no way to see
 // extra instructions at the end of a catch.
 testOne("testCatch",
         `try {
            throw new TypeError();
          } catch (e if e instanceof TypeError) {
            ${bitOfCode}
          } catch (e) {          // +6
          }                      // +7
          nothing();             // +8
-        `, "18");
+        `, "189");
 
 // Test the instruction at the end of a "finally" clause.
 testOne("testFinally",
         `try {
          } finally {
            ${bitOfCode}
          }                      // +6
          nothing();             // +7
-        `, "17");
+        `, "178");
 
 // Test the instruction at the end of a "then" clause.
 testOne("testThen",
         `if (1 === 1) {
            ${bitOfCode}
          } else {               // +6
          }                      // +7
          nothing();             // +8
-        `, "18");
+        `, "189");
 
 // Test the instructions leaving a switch block.
 testOne("testSwitch",
         `var x = 5;
          switch (x) {
            case 5:
              ${bitOfCode}
          }                      // +6
          nothing();             // +7
-        `, "17");
+        `, "178");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-13.js
@@ -0,0 +1,29 @@
+// Stepping over a not-taken "if" that is at the end of the function
+// should move to the end of the function, not somewhere in the body
+// of the "if".
+
+var g = newGlobal();
+g.eval(`function f() {        // 1
+  var a,c;                    // 2
+  debugger;                   // 3
+  if(false) {                 // 4
+    for(var b=0; b<0; b++) {  // 5
+      c = 2;                  // 6
+    }                         // 7
+  }                           // 8
+}                             // 9
+`);
+
+var dbg = Debugger(g);
+var badStep = false;
+
+dbg.onDebuggerStatement = function(frame) {
+  let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+  assertEq(debugLine, 3);
+  frame.onStep = function() {
+    let foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+    assertEq(foundLine <= 4 || foundLine >= 8, true);
+  };
+};
+
+g.eval("f();\n");
--- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js
@@ -11,9 +11,9 @@ Debugger(global).onDebuggerStatement = f
             }
         });
     });
 };
 
 global.log = '';
 global.eval("function f(n) { for (var i = 0; i < n; ++i) log += '. '; log += '! '; } debugger;");
 global.f(3);
-assertEq(global.log, "25 32 44 . 39 32 44 . 39 32 44 . 39 32 57 ! 69 ");
+assertEq(global.log, "25 32 44 . 39 32 44 . 39 32 44 . 39 32 57 ! 70 ");
--- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js
@@ -11,18 +11,18 @@ Debugger(global).onDebuggerStatement = f
             }
         });
     });
 };
 
 global.log = "";
 global.eval("function ppppp() { return 1; }");
 //                     1         2         3         4
-//           0123456789012345678901234567890123456789012345678
+//           01234567890123456789012345678901234567890123456789
 global.eval("function f(){ 1 && ppppp(ppppp()) && new Error() } debugger;");
 global.f();
 
 // 14 - Enter the function body
 // 25 - Inner print()
 // 19 - Outer print()
 // 37 - new Error()
-// 48 - Exit the function body
-assertEq(global.log, "14 25 19 37 48 ");
+// 49 - Exit the function body
+assertEq(global.log, "14 25 19 37 49 ");
--- a/js/src/jit-test/tests/debug/Script-startLine.js
+++ b/js/src/jit-test/tests/debug/Script-startLine.js
@@ -50,12 +50,14 @@ g.eval("/* Any copyright is dedicated to
        " http://creativecommons.org/publicdomain/zero/1.0/ */\n" +
        "\n" +
        "function secondCall() { first = Error().lineNumber;\n" +
        "    debugger;\n" +
        "    // Comment\n" +
        "    eval(\"42;\");\n" +
        "    function foo() {}\n" +
        "    if (true) {\n" +
-       "        foo();\n" +  // <- this is +6 and must be within the extent
-       "    }\n" +
-       "}");
-test(g.secondCall, 7);
+       "        foo();\n" +
+       // The "missing" newline here is a trick to make a newline
+       // source note come at the end.  A real newline between the two
+       // closing braces causes a setline note instead.
+       "    } }");
+test(g.secondCall, 8);