[debugger] tuned StepNext and StepOut at return position

Proposed behaviour:
- StepNext at return position go into next function call (no changes with current behavior, but implemented in v8::Debug instead of hack on inspector side);
- StepOut at return position go into next non-current function call.

We need this to have better stepping in cases with native functions, blackboxed functions and/or different embedder calls (e.g. event listeners).

New behavior could be illustrated with two examples (for more see stepping-with-natives-and-frameworks test):
- let's assume that we've blackboxed callAll function, this function just takes its arguments and call one after another:
var foo = () => 1;
callAll(foo, foo, () => 2);
If we break inside of first call of function foo. Then on..
..StepNext - we're able to reach second call of function foo,
..StepOut - we're able to reach () => 2 call.

- let's consider case with native function:
[1,2,3].map(x => x * 2)
If we break inside of first callback call, then with StepNext we can iterate through all calls of callback, with StepOut we go to next statement after .map call.

Implementation details:
- when we request break we schedule step-in function call for any step action at return position and for step-in at any position,
- when we request StepOut at return position - we mark current function as needed-to-be-ignored inside of PrepareStepIn(function) call,
- when we request StepOut at not return position - we set break at return position and ask debugger to just repeat last step action on next stepping-related break.

Design doc: https://docs.google.com/document/d/1ihXHOIhP_q-fJCA0e2EiXz_Zr3B08KMjaPifcaqZ60Q/edit

BUG=v8:6118,chromium:583193
R=dgozman@chromium.org,yangguo@chromium.org

Review-Url: https://codereview.chromium.org/2758483002
Cr-Commit-Position: refs/heads/master@{#44028}
This commit is contained in:
kozyatinskiy 2017-03-22 07:16:18 -07:00 committed by Commit bot
parent ee64674811
commit e27d18c943
12 changed files with 1003 additions and 8 deletions

View File

@ -389,6 +389,8 @@ void Debug::ThreadInit() {
thread_local_.last_step_action_ = StepNone;
thread_local_.last_statement_position_ = kNoSourcePosition;
thread_local_.last_frame_count_ = -1;
thread_local_.fast_forward_to_return_ = false;
thread_local_.ignore_step_into_function_ = Smi::kZero;
thread_local_.target_frame_count_ = -1;
thread_local_.return_value_ = Smi::kZero;
thread_local_.async_task_count_ = 0;
@ -418,6 +420,7 @@ int Debug::ArchiveSpacePerThread() { return 0; }
void Debug::Iterate(ObjectVisitor* v) {
v->VisitPointer(&thread_local_.return_value_);
v->VisitPointer(&thread_local_.suspended_generator_);
v->VisitPointer(&thread_local_.ignore_step_into_function_);
}
DebugInfoListNode::DebugInfoListNode(DebugInfo* debug_info): next_(NULL) {
@ -527,6 +530,17 @@ void Debug::Break(JavaScriptFrame* frame) {
int target_frame_count = thread_local_.target_frame_count_;
int last_frame_count = thread_local_.last_frame_count_;
// StepOut at not return position was requested and return break locations
// were flooded with one shots.
if (thread_local_.fast_forward_to_return_) {
DCHECK(location.IsReturn());
// We have to ignore recursive calls to function.
if (current_frame_count > target_frame_count) return;
ClearStepping();
PrepareStep(StepOut);
return;
}
bool step_break = false;
switch (step_action) {
case StepNone:
@ -812,7 +826,8 @@ void Debug::ClearAllBreakPoints() {
}
}
void Debug::FloodWithOneShot(Handle<SharedFunctionInfo> shared) {
void Debug::FloodWithOneShot(Handle<SharedFunctionInfo> shared,
bool returns_only) {
if (IsBlackboxed(shared)) return;
// Make sure the function is compiled and has set up the debug info.
if (!EnsureDebugInfo(shared)) return;
@ -820,11 +835,13 @@ void Debug::FloodWithOneShot(Handle<SharedFunctionInfo> shared) {
// Flood the function with break points.
if (debug_info->HasDebugCode()) {
for (CodeBreakIterator it(debug_info); !it.Done(); it.Next()) {
if (returns_only && !it.GetBreakLocation().IsReturn()) continue;
it.SetDebugBreak();
}
}
if (debug_info->HasDebugBytecodeArray()) {
for (BytecodeArrayBreakIterator it(debug_info); !it.Done(); it.Next()) {
if (returns_only && !it.GetBreakLocation().IsReturn()) continue;
it.SetDebugBreak();
}
}
@ -878,6 +895,10 @@ void Debug::PrepareStepIn(Handle<JSFunction> function) {
if (ignore_events()) return;
if (in_debug_scope()) return;
if (break_disabled()) return;
Handle<SharedFunctionInfo> shared(function->shared());
if (IsBlackboxed(shared)) return;
if (*function == thread_local_.ignore_step_into_function_) return;
thread_local_.ignore_step_into_function_ = Smi::kZero;
FloodWithOneShot(Handle<SharedFunctionInfo>(function->shared(), isolate_));
}
@ -983,7 +1004,6 @@ void Debug::PrepareStep(StepAction step_action) {
feature_tracker()->Track(DebugFeatureTracker::kStepping);
thread_local_.last_step_action_ = step_action;
UpdateHookOnFunctionCall();
StackTraceFrameIterator frames_it(isolate_, frame_id);
StandardFrame* frame = frames_it.frame();
@ -1010,8 +1030,19 @@ void Debug::PrepareStep(StepAction step_action) {
BreakLocation location = BreakLocation::FromFrame(debug_info, js_frame);
// Any step at a return is a step-out.
if (location.IsReturn()) step_action = StepOut;
// Any step at a return is a step-out and we need to schedule DebugOnFunction
// call callback.
if (location.IsReturn()) {
// On StepOut we'll ignore our further calls to current function in
// PrepareStepIn callback.
if (last_step_action() == StepOut) {
thread_local_.ignore_step_into_function_ = *function;
}
step_action = StepOut;
thread_local_.last_step_action_ = StepIn;
}
UpdateHookOnFunctionCall();
// A step-next at a tail call is a step-out.
if (location.IsTailCall() && step_action == StepNext) step_action = StepOut;
// A step-next in blackboxed function is a step-out.
@ -1032,6 +1063,14 @@ void Debug::PrepareStep(StepAction step_action) {
// Clear last position info. For stepping out it does not matter.
thread_local_.last_statement_position_ = kNoSourcePosition;
thread_local_.last_frame_count_ = -1;
if (!location.IsReturn() && !IsBlackboxed(shared)) {
// At not return position we flood return positions with one shots and
// will repeat StepOut automatically at next break.
thread_local_.target_frame_count_ = current_frame_count;
thread_local_.fast_forward_to_return_ = true;
FloodWithOneShot(shared, true);
return;
}
// Skip the current frame, find the first frame we want to step out to
// and deoptimize every frame along the way.
bool in_current_frame = true;
@ -1122,6 +1161,8 @@ void Debug::ClearStepping() {
thread_local_.last_step_action_ = StepNone;
thread_local_.last_statement_position_ = kNoSourcePosition;
thread_local_.ignore_step_into_function_ = Smi::kZero;
thread_local_.fast_forward_to_return_ = false;
thread_local_.last_frame_count_ = -1;
thread_local_.target_frame_count_ = -1;
UpdateHookOnFunctionCall();

View File

@ -490,7 +490,8 @@ class Debug {
// Clear all code from instrumentation.
void ClearAllBreakPoints();
// Instrument a function with one-shots.
void FloodWithOneShot(Handle<SharedFunctionInfo> function);
void FloodWithOneShot(Handle<SharedFunctionInfo> function,
bool returns_only = false);
// Clear all one-shot instrumentations, but restore break points.
void ClearOneShot();
@ -565,6 +566,12 @@ class Debug {
// Step action for last step performed.
StepAction last_step_action_;
// If set then this function will be ignored in PrepareStepIn call.
Object* ignore_step_into_function_;
// If set then we need to repeat StepOut action at return.
bool fast_forward_to_return_;
// Source statement position from last step next action.
int last_statement_position_;

View File

@ -6679,3 +6679,37 @@ TEST(BuiltinsExceptionPrediction) {
}
CHECK(!fail);
}
TEST(DebugGetPossibleBreakpointsReturnLocations) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::String> source = v8_str(
"function fib(x) {\n"
" if (x < 0) return;\n"
" if (x === 0) return 1;\n"
" if (x === 1) return fib(0);\n"
" return x > 2 ? fib(x - 1) + fib(x - 2) : fib(1) + fib(0);\n"
"}");
CompileRun(source);
v8::PersistentValueVector<v8::debug::Script> scripts(isolate);
v8::debug::GetLoadedScripts(isolate, scripts);
CHECK(scripts.Size() == 1);
std::vector<v8::debug::BreakLocation> locations;
CHECK(scripts.Get(0)->GetPossibleBreakpoints(
v8::debug::Location(0, 17), v8::debug::Location(), true, &locations));
int returns_count = 0;
for (size_t i = 0; i < locations.size(); ++i) {
if (locations[i].type() == v8::debug::kReturnBreakLocation) {
++returns_count;
}
}
if (i::FLAG_turbo) {
// With turbofan we generate one return location per return statement,
// each has line = 5, column = 0 as statement position.
CHECK(returns_count == 4);
} else {
// Without turbofan we generate one return location.
CHECK(returns_count == 1);
}
}

View File

@ -6,7 +6,7 @@
Debug = debug.Debug
var exception = null;
var break_count = 0;
const expected_breaks = 9;
const expected_breaks = 10;
function listener(event, exec_state, event_data, data) {
try {
@ -65,7 +65,7 @@ function promise4() {
function finalize() {
Promise.resolve().then(function() {
if (expected_breaks !== break_count) {
if (expected_breaks !== break_count) { // Break 9. StepOut.
%AbortJS("FAIL: expected <" + expected_breaks + "> breaks instead of <" +
break_count + ">");
}

View File

@ -59,7 +59,9 @@ testStepFromUser (user.js:31:2)
(anonymous) (expr.js:0:0)
Executing stepOut...
testStepFromUser (user.js:32:0)
userBoo (user.js:27:2)
frameworkCall (framework.js:10:23)
testStepFromUser (user.js:31:2)
(anonymous) (expr.js:0:0)
Executing resume...

View File

@ -0,0 +1,11 @@
Return break locations within function
Running test: testTailCall
[
[0] : {
columnNumber : 0
lineNumber : 6
scriptId : <scriptId>
type : return
}
]

View File

@ -0,0 +1,26 @@
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest.log('Return break locations within function');
InspectorTest.addScript(`
function fib(x) {
if (x < 0) return;
if (x === 0) return 1;
if (x === 1) return fib(0);
return x > 2 ? fib(x - 1) + fib(x - 2) : fib(1) + fib(0);
}
`);
InspectorTest.runAsyncTestSuite([
async function testTailCall() {
var scriptPromise = Protocol.Debugger.onceScriptParsed();
Protocol.Debugger.enable();
var scriptId = (await scriptPromise).params.scriptId;
var locations = (await Protocol.Debugger.getPossibleBreakpoints({
start: { lineNumber: 0, columnNumber: 0, scriptId }
})).result.locations;
InspectorTest.logMessage(locations.filter(location => location.type === 'return'));
}
]);

View File

@ -0,0 +1,177 @@
Checks stepping over tail calls.
Running test: testStepOver
f (:2:14)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
function f(x) {
if (x == 2) #debugger;
if (x-- > 0) return f(x);
Debugger.stepOver called
f (:3:2)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x == 2) debugger;
#if (x-- > 0) return f(x);
}
Debugger.stepOver called
f (:3:15)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x == 2) debugger;
if (x-- > 0) #return f(x);
}
Debugger.stepOver called
f (:4:0)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOver called
f (:4:0)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOver called
f (:4:0)
f (:3:22)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOver called
f (:4:0)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOver called
(anonymous) (:5:5)
}
f(5);#
Debugger.resume called
Running test: testStepOut
f (:2:14)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
function f(x) {
if (x == 2) #debugger;
if (x-- > 0) return f(x);
Debugger.stepOut called
f (:4:0)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOut called
f (:4:0)
f (:3:22)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOut called
f (:4:0)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOut called
(anonymous) (:5:5)
}
f(5);#
Debugger.resume called
Running test: testStepOutFromReturn
f (:2:14)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
function f(x) {
if (x == 2) #debugger;
if (x-- > 0) return f(x);
Debugger.stepOver called
f (:3:2)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x == 2) debugger;
#if (x-- > 0) return f(x);
}
Debugger.stepOver called
f (:3:15)
f (:3:22)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x == 2) debugger;
if (x-- > 0) #return f(x);
}
Debugger.stepOut called
f (:4:0)
f (:3:22)
f (:3:22)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOut called
f (:4:0)
f (:3:22)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOut called
f (:4:0)
(anonymous) (:5:0)
if (x-- > 0) return f(x);
#}
f(5);
Debugger.stepOut called
(anonymous) (:5:5)
}
f(5);#
Debugger.resume called

View File

@ -0,0 +1,81 @@
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest.log('Checks stepping over tail calls.');
InspectorTest.setupScriptMap();
InspectorTest.dumpProtocolCommand('Debugger.pause');
InspectorTest.dumpProtocolCommand('Debugger.stepInto');
InspectorTest.dumpProtocolCommand('Debugger.stepOver');
InspectorTest.dumpProtocolCommand('Debugger.stepOut');
InspectorTest.dumpProtocolCommand('Debugger.resume');
let source = `
function f(x) {
if (x == 2) debugger;
if (x-- > 0) return f(x);
}
f(5);
`;
Protocol.Debugger.enable();
Protocol.Debugger.setBlackboxPatterns({patterns: ['framework\.js']});
InspectorTest.runAsyncTestSuite([
async function testStepOver() {
Protocol.Runtime.evaluate({expression: source});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testStepOut() {
Protocol.Runtime.evaluate({expression: source});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testStepOutFromReturn() {
Protocol.Runtime.evaluate({expression: source});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
}
]);
function logPauseLocation(message) {
InspectorTest.logCallFrames(message.params.callFrames);
return InspectorTest.logSourceLocation(message.params.callFrames[0].location);
}

View File

@ -0,0 +1,310 @@
Stepping with natives and frameworks.
Running test: testNativeCodeStepOut
Debugger.pause called
#[1,2].map(v => v);
Debugger.stepInto called
[1,2].map(v => #v);
Debugger.stepOut called
[1,2].map(v => v)#;
Debugger.resume called
Running test: testNativeCodeStepOver
Debugger.pause called
#[1,2].map(v => v);
Debugger.stepInto called
[1,2].map(v => #v);
Debugger.stepOver called
[1,2].map(v => v#);
Debugger.stepOver called
[1,2].map(v => #v);
Debugger.stepOver called
[1,2].map(v => v#);
Debugger.stepOver called
[1,2].map(v => v)#;
Debugger.resume called
Running test: testNativeCodeStepInto
Debugger.pause called
#[1,2].map(v => v);
Debugger.stepInto called
[1,2].map(v => #v);
Debugger.stepInto called
[1,2].map(v => v#);
Debugger.stepInto called
[1,2].map(v => #v);
Debugger.stepInto called
[1,2].map(v => v#);
Debugger.stepInto called
[1,2].map(v => v)#;
Debugger.resume called
Running test: testFrameworkCodeStepInto
Debugger.pause called
#callAll(() => 1, () => 2);
Debugger.stepInto called
callAll(() => #1, () => 2);
Debugger.stepInto called
callAll(() => 1#, () => 2);
Debugger.stepInto called
callAll(() => 1, () => #2);
Debugger.stepInto called
callAll(() => 1, () => 2#);
Debugger.stepInto called
callAll(() => 1, () => 2)#;
Debugger.resume called
Running test: testFrameworkCodeStepOver
Debugger.pause called
#callAll(() => 1, () => 2);
Debugger.stepInto called
callAll(() => #1, () => 2);
Debugger.stepOver called
callAll(() => 1#, () => 2);
Debugger.stepOver called
callAll(() => 1, () => #2);
Debugger.stepOver called
callAll(() => 1, () => 2#);
Debugger.stepOver called
callAll(() => 1, () => 2)#;
Debugger.resume called
Running test: testFrameworkCodeStepOut
Debugger.pause called
#callAll(() => 1, () => 2);
Debugger.stepInto called
callAll(() => #1, () => 2);
Debugger.stepOut called
callAll(() => 1, () => #2);
Debugger.stepOut called
callAll(() => 1, () => 2)#;
Debugger.resume called
Running test: testFrameworkNextCallDeeperStepOut
Debugger.pause called
#callAll(() => 1, callAll.bind(null, () => 2));
Debugger.stepInto called
callAll(() => #1, callAll.bind(null, () => 2));
Debugger.stepOut called
callAll(() => 1, callAll.bind(null, () => #2));
Debugger.stepOut called
callAll(() => 1, callAll.bind(null, () => 2))#;
Debugger.resume called
Running test: testFrameworkNextCallDeeperStepOutSameFunction
Debugger.pause called
#callAll(foo, callAll.bind(null, foo));
Debugger.stepInto called
foo = () => #1
Debugger.stepOut called
callAll(foo, callAll.bind(null, foo))#;
Debugger.resume called
Running test: testFrameworkNextCallDeeperStepInto
Debugger.pause called
#callAll(() => 1, callAll.bind(null, () => 2));
Debugger.stepInto called
callAll(() => #1, callAll.bind(null, () => 2));
Debugger.stepOver called
callAll(() => 1#, callAll.bind(null, () => 2));
Debugger.stepOver called
callAll(() => 1, callAll.bind(null, () => #2));
Debugger.stepOver called
callAll(() => 1, callAll.bind(null, () => 2#));
Debugger.stepOver called
callAll(() => 1, callAll.bind(null, () => 2))#;
Debugger.resume called
Running test: testFrameworkNextCallDeeperStepOver
Debugger.pause called
#callAll(() => 1, callAll.bind(null, () => 2));
Debugger.stepInto called
callAll(() => #1, callAll.bind(null, () => 2));
Debugger.stepOver called
callAll(() => 1#, callAll.bind(null, () => 2));
Debugger.stepOver called
callAll(() => 1, callAll.bind(null, () => #2));
Debugger.stepOver called
callAll(() => 1, callAll.bind(null, () => 2#));
Debugger.stepOver called
callAll(() => 1, callAll.bind(null, () => 2))#;
Debugger.resume called
Running test: testFrameworkCurrentCallDeeperStepOut
Debugger.pause called
#callAll(callAll.bind(null, () => 1), () => 2);
Debugger.stepInto called
callAll(callAll.bind(null, () => #1), () => 2);
Debugger.stepOut called
callAll(callAll.bind(null, () => 1), () => #2);
Debugger.stepOut called
callAll(callAll.bind(null, () => 1), () => 2)#;
Debugger.resume called
Running test: testFrameworkCurrentCallDeeperStepOutSameFunction
Debugger.pause called
#callAll(callAll.bind(null, foo), foo);
Debugger.stepInto called
foo = () => #1
Debugger.stepOut called
callAll(callAll.bind(null, foo), foo)#;
Debugger.resume called
Running test: testFrameworkCurrentCallDeeperStepOver
Debugger.pause called
#callAll(callAll.bind(null, () => 1), () => 2);
Debugger.stepInto called
callAll(callAll.bind(null, () => #1), () => 2);
Debugger.stepOver called
callAll(callAll.bind(null, () => 1#), () => 2);
Debugger.stepOver called
callAll(callAll.bind(null, () => 1), () => #2);
Debugger.stepOver called
callAll(callAll.bind(null, () => 1), () => 2#);
Debugger.stepOver called
callAll(callAll.bind(null, () => 1), () => 2)#;
Debugger.resume called
Running test: testFrameworkCurrentCallDeeperStepInto
Debugger.pause called
#callAll(callAll.bind(null, () => 1), () => 2);
Debugger.stepInto called
callAll(callAll.bind(null, () => #1), () => 2);
Debugger.stepInto called
callAll(callAll.bind(null, () => 1#), () => 2);
Debugger.stepInto called
callAll(callAll.bind(null, () => 1), () => #2);
Debugger.stepInto called
callAll(callAll.bind(null, () => 1), () => 2#);
Debugger.stepInto called
callAll(callAll.bind(null, () => 1), () => 2)#;
Debugger.resume called
Running test: testFrameworkStepOverMixed
Debugger.pause called
#callAll(foo, foo, () => 2);
Debugger.stepInto called
foo = () => #1
Debugger.stepOver called
foo = () => 1#
Debugger.stepOver called
foo = () => #1
Debugger.stepOver called
foo = () => 1#
Debugger.stepOver called
callAll(foo, foo, () => #2);
Debugger.stepOver called
callAll(foo, foo, () => 2#);
Debugger.stepOver called
callAll(foo, foo, () => 2)#;
Debugger.resume called
Running test: testFrameworkStepOutMixed
Debugger.pause called
#callAll(foo, foo, () => 2);
Debugger.stepInto called
foo = () => #1
Debugger.stepOut called
callAll(foo, foo, () => #2);
Debugger.stepOut called
callAll(foo, foo, () => 2)#;
Debugger.resume called
Running test: testStepOutFrameworkSameFunctionAtReturn
Debugger.pause called
#callAll(foo, foo, () => 2);
Debugger.stepInto called
foo = () => #1
Debugger.stepOver called
foo = () => 1#
Debugger.stepOut called
callAll(foo, foo, () => #2);
Debugger.stepOut called
callAll(foo, foo, () => 2)#;
Debugger.resume called

View File

@ -0,0 +1,300 @@
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest.log('Stepping with natives and frameworks.');
InspectorTest.addScript(`
function callAll() {
for (var f of arguments)
f();
}
//# sourceURL=framework.js`);
InspectorTest.setupScriptMap();
InspectorTest.dumpProtocolCommand('Debugger.pause');
InspectorTest.dumpProtocolCommand('Debugger.stepInto');
InspectorTest.dumpProtocolCommand('Debugger.stepOver');
InspectorTest.dumpProtocolCommand('Debugger.stepOut');
InspectorTest.dumpProtocolCommand('Debugger.resume');
Protocol.Debugger.enable();
Protocol.Debugger.setBlackboxPatterns({patterns: ['framework\.js']});
InspectorTest.runAsyncTestSuite([
async function testNativeCodeStepOut() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({expression: '[1,2].map(v => v);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testNativeCodeStepOver() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({expression: '[1,2].map(v => v);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testNativeCodeStepInto() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({expression: '[1,2].map(v => v);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkCodeStepInto() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({expression: 'callAll(() => 1, () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkCodeStepOver() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({expression: 'callAll(() => 1, () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkCodeStepOut() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({expression: 'callAll(() => 1, () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkNextCallDeeperStepOut() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(() => 1, callAll.bind(null, () => 2));'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkNextCallDeeperStepOutSameFunction() {
await Protocol.Runtime.evaluate({expression: 'foo = () => 1'});
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(foo, callAll.bind(null, foo));'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkNextCallDeeperStepInto() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(() => 1, callAll.bind(null, () => 2));'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkNextCallDeeperStepOver() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(() => 1, callAll.bind(null, () => 2));'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkCurrentCallDeeperStepOut() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(callAll.bind(null, () => 1), () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkCurrentCallDeeperStepOutSameFunction() {
await Protocol.Runtime.evaluate({expression: 'foo = () => 1'});
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(callAll.bind(null, foo), foo);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkCurrentCallDeeperStepOver() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(callAll.bind(null, () => 1), () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkCurrentCallDeeperStepInto() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(callAll.bind(null, () => 1), () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkStepOverMixed() {
await Protocol.Runtime.evaluate({expression: 'foo = () => 1'});
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(foo, foo, () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testFrameworkStepOutMixed() {
await Protocol.Runtime.evaluate({expression: 'foo = () => 1'});
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(foo, foo, () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
},
async function testStepOutFrameworkSameFunctionAtReturn() {
await Protocol.Runtime.evaluate({expression: 'foo = () => 1'});
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({
expression: 'callAll(foo, foo, () => 2);'});
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepInto();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOver();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
Protocol.Debugger.stepOut();
await logPauseLocation(await Protocol.Debugger.oncePaused());
await Protocol.Debugger.resume();
}
]);
function logPauseLocation(message) {
return InspectorTest.logSourceLocation(message.params.callFrames[0].location);
}

View File

@ -7,6 +7,7 @@ InspectorTest._dispatchTable = new Map();
InspectorTest._requestId = 0;
InspectorTest._dumpInspectorProtocolMessages = false;
InspectorTest._eventHandler = {};
InspectorTest._commandToLog = new Set();
Protocol = new Proxy({}, {
get: function(target, agentName, receiver) {
@ -30,6 +31,8 @@ Protocol = new Proxy({}, {
}
});
InspectorTest.dumpProtocolCommand = (command) => InspectorTest._commandToLog.add(command);
var utils = {};
(function setupUtils() {
utils.load = load;
@ -265,6 +268,9 @@ InspectorTest._sendCommandPromise = function(method, params, contextGroupId)
var messageObject = { "id": requestId, "method": method, "params": params };
var fulfillCallback;
var promise = new Promise(fulfill => fulfillCallback = fulfill);
if (InspectorTest._commandToLog.has(method)) {
utils.print(method + ' called');
}
InspectorTest.sendRawCommand(requestId, JSON.stringify(messageObject), fulfillCallback, contextGroupId);
return promise;
}