Bug 1013219 - set the line number of the terminating retrval; r=jimb,ejpbruel,fitzgen

This commit is contained in:
Tom Tromey 2014-12-11 13:22:44 -07:00
parent 0bb6956f02
commit 7c4e941f79
22 changed files with 318 additions and 142 deletions

View File

@ -136,11 +136,11 @@ function test() {
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).");
}

View File

@ -131,11 +131,11 @@ function test() {
"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();

View File

@ -372,10 +372,13 @@ function attachTestThread(aClient, aTitle, aCallback) {
// 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]);
});
});
});
}
@ -724,6 +727,20 @@ function stepIn(client, threadClient) {
.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.

View File

@ -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();
}

View File

@ -4,6 +4,7 @@ function f() {
function g() {
var a = 1;
var b = 2;
return;
}

View File

@ -39,69 +39,68 @@ function test_simple_breakpoint()
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");
},
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 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");
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)));
});
// 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.
for (let callback of testCallbacks) {
let waiter = waitForPause(gThreadClient);
gThreadClient.stepIn();
});
// Step into the next line with the function call.
let packet = yield waiter;
callback(packet);
}
// 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" +

View File

@ -39,67 +39,66 @@ function test_simple_breakpoint()
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");
},
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) {
// 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");
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)));
});
// 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.
for (let callback of testCallbacks) {
let waiter = waitForPause(gThreadClient);
gThreadClient.stepOver();
});
// Step over to the next line with the function call.
let packet = yield waiter;
callback(packet);
}
// 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" +

View File

@ -38,7 +38,7 @@ function test_executable_lines() {
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);
});
});

View File

@ -26,12 +26,12 @@ function run_test() {
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);

View File

@ -38,7 +38,7 @@ function test_simple_stepping()
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);

View File

@ -51,7 +51,7 @@ function test_simple_stepping()
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");

View File

@ -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
);
}

View File

@ -205,6 +205,7 @@ reason = bug 820380
[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]

View File

@ -3592,6 +3592,9 @@ BytecodeEmitter::emitFunctionScript(ParseNode* body)
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.

View File

@ -3188,6 +3188,8 @@ Parser<ParseHandler>::functionArgsAndBodyGeneric(InHandling inHandling,
return false;
}
handler.setEndPosition(body, pos().begin);
return finishFunctionDefinition(pn, funbox, body);
}

View File

@ -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);

View File

@ -33,4 +33,4 @@ dbg.onDebuggerStatement = function(frame) {
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");

View File

@ -65,7 +65,7 @@ testOne("testTryFinally",
} finally { // +6
} // +7
nothing(); // +8
`, "168");
`, "1689");
// The same but without a finally clause.
testOne("testTryCatch",
@ -74,7 +74,7 @@ testOne("testTryCatch",
} catch (e) { // +6
} // +7
nothing(); // +8
`, "18");
`, "189");
// Test the instructions at the end of a "catch".
testOne("testCatchFinally",
@ -85,7 +85,7 @@ testOne("testCatchFinally",
} 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
@ -98,7 +98,7 @@ testOne("testCatch",
} catch (e) { // +6
} // +7
nothing(); // +8
`, "18");
`, "189");
// Test the instruction at the end of a "finally" clause.
testOne("testFinally",
@ -107,7 +107,7 @@ testOne("testFinally",
${bitOfCode}
} // +6
nothing(); // +7
`, "17");
`, "178");
// Test the instruction at the end of a "then" clause.
testOne("testThen",
@ -116,7 +116,7 @@ testOne("testThen",
} else { // +6
} // +7
nothing(); // +8
`, "18");
`, "189");
// Test the instructions leaving a switch block.
testOne("testSwitch",
@ -126,4 +126,4 @@ testOne("testSwitch",
${bitOfCode}
} // +6
nothing(); // +7
`, "17");
`, "178");

View File

@ -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");

View File

@ -16,4 +16,4 @@ Debugger(global).onDebuggerStatement = function (frame) {
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 ");

View File

@ -16,7 +16,7 @@ Debugger(global).onDebuggerStatement = function (frame) {
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();
@ -24,5 +24,5 @@ global.f();
// 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 ");

View File

@ -55,7 +55,9 @@ g.eval("/* Any copyright is dedicated to the Public Domain.\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);