Support 'restart call frame' debug command
Review URL: https://chromiumcodereview.appspot.com/10544151 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@11836 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
b072e6156b
commit
bdfc48a3fd
@ -1449,6 +1449,8 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(
|
||||
this.profileRequest_(request, response);
|
||||
} else if (request.command == 'changelive') {
|
||||
this.changeLiveRequest_(request, response);
|
||||
} else if (request.command == 'restartframe') {
|
||||
this.restartFrameRequest_(request, response);
|
||||
} else if (request.command == 'flags') {
|
||||
this.debuggerFlagsRequest_(request, response);
|
||||
} else if (request.command == 'v8flags') {
|
||||
@ -2358,9 +2360,6 @@ DebugCommandProcessor.prototype.profileRequest_ = function(request, response) {
|
||||
|
||||
DebugCommandProcessor.prototype.changeLiveRequest_ = function(
|
||||
request, response) {
|
||||
if (!Debug.LiveEdit) {
|
||||
return response.failed('LiveEdit feature is not supported');
|
||||
}
|
||||
if (!request.arguments) {
|
||||
return response.failed('Missing arguments');
|
||||
}
|
||||
@ -2398,6 +2397,37 @@ DebugCommandProcessor.prototype.changeLiveRequest_ = function(
|
||||
};
|
||||
|
||||
|
||||
DebugCommandProcessor.prototype.restartFrameRequest_ = function(
|
||||
request, response) {
|
||||
if (!request.arguments) {
|
||||
return response.failed('Missing arguments');
|
||||
}
|
||||
var frame = request.arguments.frame;
|
||||
|
||||
// No frames to evaluate in frame.
|
||||
if (this.exec_state_.frameCount() == 0) {
|
||||
return response.failed('No frames');
|
||||
}
|
||||
|
||||
var frame_mirror;
|
||||
// Check whether a frame was specified.
|
||||
if (!IS_UNDEFINED(frame)) {
|
||||
var frame_number = %ToNumber(frame);
|
||||
if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
|
||||
return response.failed('Invalid frame "' + frame + '"');
|
||||
}
|
||||
// Restart specified frame.
|
||||
frame_mirror = this.exec_state_.frame(frame_number);
|
||||
} else {
|
||||
// Restart selected frame.
|
||||
frame_mirror = this.exec_state_.frame();
|
||||
}
|
||||
|
||||
var result_description = Debug.LiveEdit.RestartFrame(frame_mirror);
|
||||
response.body = {result: result_description};
|
||||
};
|
||||
|
||||
|
||||
DebugCommandProcessor.prototype.debuggerFlagsRequest_ = function(request,
|
||||
response) {
|
||||
// Check for legal request.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@ -47,6 +47,8 @@ Debug.LiveEdit = new function() {
|
||||
// Forward declaration for minifier.
|
||||
var FunctionStatus;
|
||||
|
||||
var NEEDS_STEP_IN_PROPERTY_NAME = "stack_update_needs_step_in";
|
||||
|
||||
// Applies the change to the script.
|
||||
// The change is in form of list of chunks encoded in a single array as
|
||||
// a series of triplets (pos1_start, pos1_end, pos2_end)
|
||||
@ -161,7 +163,7 @@ Debug.LiveEdit = new function() {
|
||||
|
||||
// Our current implementation requires client to manually issue "step in"
|
||||
// command for correct stack state.
|
||||
preview_description.stack_update_needs_step_in =
|
||||
preview_description[NEEDS_STEP_IN_PROPERTY_NAME] =
|
||||
preview_description.stack_modified;
|
||||
|
||||
// Start with breakpoints. Convert their line/column positions and
|
||||
@ -1078,6 +1080,18 @@ Debug.LiveEdit = new function() {
|
||||
return ProcessOldNode(old_code_tree);
|
||||
}
|
||||
|
||||
// Restarts call frame and returns value similar to what LiveEdit returns.
|
||||
function RestartFrame(frame_mirror) {
|
||||
var result = frame_mirror.restart();
|
||||
if (IS_STRING(result)) {
|
||||
throw new Failure("Failed to restart frame: " + result);
|
||||
}
|
||||
var result = {};
|
||||
result[NEEDS_STEP_IN_PROPERTY_NAME] = true;
|
||||
return result;
|
||||
}
|
||||
// Function is public.
|
||||
this.RestartFrame = RestartFrame;
|
||||
|
||||
// Functions are public for tests.
|
||||
this.TestApi = {
|
||||
|
@ -1595,17 +1595,36 @@ static bool IsDropableFrame(StackFrame* frame) {
|
||||
return !frame->is_exit();
|
||||
}
|
||||
|
||||
// Fills result array with statuses of functions. Modifies the stack
|
||||
// removing all listed function if possible and if do_drop is true.
|
||||
static const char* DropActivationsInActiveThread(
|
||||
Handle<JSArray> shared_info_array, Handle<JSArray> result, bool do_drop,
|
||||
Zone* zone) {
|
||||
|
||||
// Describes a set of call frames that execute any of listed functions.
|
||||
// Finding no such frames does not mean error.
|
||||
class MultipleFunctionTarget {
|
||||
public:
|
||||
MultipleFunctionTarget(Handle<JSArray> shared_info_array,
|
||||
Handle<JSArray> result)
|
||||
: m_shared_info_array(shared_info_array),
|
||||
m_result(result) {}
|
||||
bool MatchActivation(StackFrame* frame,
|
||||
LiveEdit::FunctionPatchabilityStatus status) {
|
||||
return CheckActivation(m_shared_info_array, m_result, frame, status);
|
||||
}
|
||||
const char* GetNotFoundMessage() {
|
||||
return NULL;
|
||||
}
|
||||
private:
|
||||
Handle<JSArray> m_shared_info_array;
|
||||
Handle<JSArray> m_result;
|
||||
};
|
||||
|
||||
// Drops all call frame matched by target and all frames above them.
|
||||
template<typename TARGET>
|
||||
static const char* DropActivationsInActiveThreadImpl(
|
||||
TARGET& target, bool do_drop, Zone* zone) {
|
||||
Isolate* isolate = Isolate::Current();
|
||||
Debug* debug = isolate->debug();
|
||||
ZoneScope scope(isolate, DELETE_ON_EXIT);
|
||||
Vector<StackFrame*> frames = CreateStackMap(zone);
|
||||
|
||||
int array_len = Smi::cast(shared_info_array->length())->value();
|
||||
|
||||
int top_frame_index = -1;
|
||||
int frame_index = 0;
|
||||
@ -1615,8 +1634,8 @@ static const char* DropActivationsInActiveThread(
|
||||
top_frame_index = frame_index;
|
||||
break;
|
||||
}
|
||||
if (CheckActivation(shared_info_array, result, frame,
|
||||
LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
|
||||
if (target.MatchActivation(
|
||||
frame, LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
|
||||
// We are still above break_frame. It is not a target frame,
|
||||
// it is a problem.
|
||||
return "Debugger mark-up on stack is not found";
|
||||
@ -1625,7 +1644,7 @@ static const char* DropActivationsInActiveThread(
|
||||
|
||||
if (top_frame_index == -1) {
|
||||
// We haven't found break frame, but no function is blocking us anyway.
|
||||
return NULL;
|
||||
return target.GetNotFoundMessage();
|
||||
}
|
||||
|
||||
bool target_frame_found = false;
|
||||
@ -1638,8 +1657,8 @@ static const char* DropActivationsInActiveThread(
|
||||
c_code_found = true;
|
||||
break;
|
||||
}
|
||||
if (CheckActivation(shared_info_array, result, frame,
|
||||
LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
|
||||
if (target.MatchActivation(
|
||||
frame, LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
|
||||
target_frame_found = true;
|
||||
bottom_js_frame_index = frame_index;
|
||||
}
|
||||
@ -1651,8 +1670,8 @@ static const char* DropActivationsInActiveThread(
|
||||
for (; frame_index < frames.length(); frame_index++) {
|
||||
StackFrame* frame = frames[frame_index];
|
||||
if (frame->is_java_script()) {
|
||||
if (CheckActivation(shared_info_array, result, frame,
|
||||
LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
|
||||
if (target.MatchActivation(
|
||||
frame, LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
|
||||
// Cannot drop frame under C frames.
|
||||
return NULL;
|
||||
}
|
||||
@ -1667,7 +1686,7 @@ static const char* DropActivationsInActiveThread(
|
||||
|
||||
if (!target_frame_found) {
|
||||
// Nothing to drop.
|
||||
return NULL;
|
||||
return target.GetNotFoundMessage();
|
||||
}
|
||||
|
||||
Debug::FrameDropMode drop_mode = Debug::FRAMES_UNTOUCHED;
|
||||
@ -1690,6 +1709,23 @@ static const char* DropActivationsInActiveThread(
|
||||
}
|
||||
debug->FramesHaveBeenDropped(new_id, drop_mode,
|
||||
restarter_frame_function_pointer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Fills result array with statuses of functions. Modifies the stack
|
||||
// removing all listed function if possible and if do_drop is true.
|
||||
static const char* DropActivationsInActiveThread(
|
||||
Handle<JSArray> shared_info_array, Handle<JSArray> result, bool do_drop,
|
||||
Zone* zone) {
|
||||
MultipleFunctionTarget target(shared_info_array, result);
|
||||
|
||||
const char* message =
|
||||
DropActivationsInActiveThreadImpl(target, do_drop, zone);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
int array_len = Smi::cast(shared_info_array->length())->value();
|
||||
|
||||
// Replace "blocked on active" with "replaced on active" status.
|
||||
for (int i = 0; i < array_len; i++) {
|
||||
@ -1766,6 +1802,41 @@ Handle<JSArray> LiveEdit::CheckAndDropActivations(
|
||||
}
|
||||
|
||||
|
||||
// Describes a single callframe a target. Not finding this frame
|
||||
// means an error.
|
||||
class SingleFrameTarget {
|
||||
public:
|
||||
explicit SingleFrameTarget(JavaScriptFrame* frame) : m_frame(frame) {}
|
||||
|
||||
bool MatchActivation(StackFrame* frame,
|
||||
LiveEdit::FunctionPatchabilityStatus status) {
|
||||
if (frame->fp() == m_frame->fp()) {
|
||||
m_saved_status = status;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const char* GetNotFoundMessage() {
|
||||
return "Failed to found requested frame";
|
||||
}
|
||||
LiveEdit::FunctionPatchabilityStatus saved_status() {
|
||||
return m_saved_status;
|
||||
}
|
||||
private:
|
||||
JavaScriptFrame* m_frame;
|
||||
LiveEdit::FunctionPatchabilityStatus m_saved_status;
|
||||
};
|
||||
|
||||
|
||||
// Finds a drops required frame and all frames above.
|
||||
// Returns error message or NULL.
|
||||
const char* LiveEdit::RestartFrame(JavaScriptFrame* frame, Zone* zone) {
|
||||
SingleFrameTarget target(frame);
|
||||
|
||||
return DropActivationsInActiveThreadImpl(target, true, zone);
|
||||
}
|
||||
|
||||
|
||||
LiveEditFunctionTracker::LiveEditFunctionTracker(Isolate* isolate,
|
||||
FunctionLiteral* fun)
|
||||
: isolate_(isolate) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@ -123,6 +123,10 @@ class LiveEdit : AllStatic {
|
||||
static Handle<JSArray> CheckAndDropActivations(
|
||||
Handle<JSArray> shared_info_array, bool do_drop, Zone* zone);
|
||||
|
||||
// Restarts the call frame and completely drops all frames above it.
|
||||
// Return error message or NULL.
|
||||
static const char* RestartFrame(JavaScriptFrame* frame, Zone* zone);
|
||||
|
||||
// A copy of this is in liveedit-debugger.js.
|
||||
enum FunctionPatchabilityStatus {
|
||||
FUNCTION_AVAILABLE_FOR_PATCH = 1,
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
||||
// Copyright 2006-2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@ -1750,6 +1750,15 @@ FrameMirror.prototype.localsText = function() {
|
||||
};
|
||||
|
||||
|
||||
FrameMirror.prototype.restart = function() {
|
||||
var result = %LiveEditRestartFrame(this.break_id_, this.index_);
|
||||
if (IS_UNDEFINED(result)) {
|
||||
result = "Failed to find requested frame";
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
FrameMirror.prototype.toText = function(opt_locals) {
|
||||
var result = '';
|
||||
result += '#' + (this.index() <= 9 ? '0' : '') + this.index();
|
||||
|
@ -12789,6 +12789,45 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_LiveEditCompareStrings) {
|
||||
}
|
||||
|
||||
|
||||
// Restarts a call frame and completely drops all frames above.
|
||||
// Returns true if successful. Otherwise returns undefined or an error message.
|
||||
RUNTIME_FUNCTION(MaybeObject*, Runtime_LiveEditRestartFrame) {
|
||||
HandleScope scope(isolate);
|
||||
ASSERT(args.length() == 2);
|
||||
|
||||
// Check arguments.
|
||||
Object* check;
|
||||
{ MaybeObject* maybe_check = Runtime_CheckExecutionState(
|
||||
RUNTIME_ARGUMENTS(isolate, args));
|
||||
if (!maybe_check->ToObject(&check)) return maybe_check;
|
||||
}
|
||||
CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
|
||||
Heap* heap = isolate->heap();
|
||||
|
||||
// Find the relevant frame with the requested index.
|
||||
StackFrame::Id id = isolate->debug()->break_frame_id();
|
||||
if (id == StackFrame::NO_ID) {
|
||||
// If there are no JavaScript stack frames return undefined.
|
||||
return heap->undefined_value();
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
JavaScriptFrameIterator it(isolate, id);
|
||||
for (; !it.done(); it.Advance()) {
|
||||
if (index < count + it.frame()->GetInlineCount()) break;
|
||||
count += it.frame()->GetInlineCount();
|
||||
}
|
||||
if (it.done()) return heap->undefined_value();
|
||||
|
||||
const char* error_message =
|
||||
LiveEdit::RestartFrame(it.frame(), isolate->zone());
|
||||
if (error_message) {
|
||||
return *(isolate->factory()->LookupAsciiSymbol(error_message));
|
||||
}
|
||||
return heap->true_value();
|
||||
}
|
||||
|
||||
|
||||
// A testing entry. Returns statement position which is the closest to
|
||||
// source_position.
|
||||
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetFunctionCodePositionFromSource) {
|
||||
|
@ -442,6 +442,7 @@ namespace internal {
|
||||
F(LiveEditPatchFunctionPositions, 2, 1) \
|
||||
F(LiveEditCheckAndDropActivations, 2, 1) \
|
||||
F(LiveEditCompareStrings, 2, 1) \
|
||||
F(LiveEditRestartFrame, 2, 1) \
|
||||
F(GetFunctionCodePositionFromSource, 2, 1) \
|
||||
F(ExecuteInDebugContext, 2, 1) \
|
||||
\
|
||||
|
153
test/mjsunit/debug-liveedit-restart-frame.js
Normal file
153
test/mjsunit/debug-liveedit-restart-frame.js
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Flags: --expose-debug-as debug
|
||||
// Get the Debug object exposed from the debug context global object.
|
||||
|
||||
Debug = debug.Debug
|
||||
|
||||
function FindCallFrame(exec_state, frame_code) {
|
||||
var number = Number(frame_code);
|
||||
if (number >= 0) {
|
||||
return exec_state.frame(number);
|
||||
} else {
|
||||
for (var i = 0; i < exec_state.frameCount(); i++) {
|
||||
var frame = exec_state.frame(i);
|
||||
var func_mirror = frame.func();
|
||||
if (frame_code == func_mirror.name()) {
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to find function name " + function_name);
|
||||
}
|
||||
|
||||
function TestCase(test_scenario, expected_output) {
|
||||
// Global variable, accessed from eval'd script.
|
||||
test_output = "";
|
||||
|
||||
function TestCode() {
|
||||
function A() {
|
||||
// Extra stack variable. To make function not slim.
|
||||
// Restarter doesn't work on slim function when stopped on 'debugger'
|
||||
// statement. (There is no padding for 'debugger' statement).
|
||||
var o = {};
|
||||
test_output += 'A';
|
||||
test_output += '=';
|
||||
debugger;
|
||||
return 'Capybara';
|
||||
}
|
||||
function B(p1, p2) {
|
||||
test_output += 'B';
|
||||
return A();
|
||||
}
|
||||
function C() {
|
||||
test_output += 'C';
|
||||
// Function call with argument adaptor is intentional.
|
||||
return B();
|
||||
}
|
||||
function D() {
|
||||
test_output += 'D';
|
||||
// Function call with argument adaptor is intentional.
|
||||
return C(1, 2);
|
||||
}
|
||||
function E() {
|
||||
test_output += 'E';
|
||||
return D();
|
||||
}
|
||||
function F() {
|
||||
test_output += 'F';
|
||||
return E();
|
||||
}
|
||||
return F();
|
||||
}
|
||||
|
||||
var scenario_pos = 0;
|
||||
|
||||
function DebuggerStatementHandler(exec_state) {
|
||||
while (true) {
|
||||
assertTrue(scenario_pos < test_scenario.length);
|
||||
var change_code = test_scenario[scenario_pos++];
|
||||
if (change_code == '=') {
|
||||
// Continue.
|
||||
return;
|
||||
}
|
||||
var frame = FindCallFrame(exec_state, change_code);
|
||||
// Throws if fails.
|
||||
Debug.LiveEdit.RestartFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
var saved_exception = null;
|
||||
|
||||
function listener(event, exec_state, event_data, data) {
|
||||
if (saved_exception != null) {
|
||||
return;
|
||||
}
|
||||
if (event == Debug.DebugEvent.Break) {
|
||||
try {
|
||||
DebuggerStatementHandler(exec_state);
|
||||
} catch (e) {
|
||||
saved_exception = e;
|
||||
}
|
||||
} else {
|
||||
print("Other: " + event);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.setListener(listener);
|
||||
assertEquals("Capybara", TestCode());
|
||||
Debug.setListener(null);
|
||||
|
||||
if (saved_exception) {
|
||||
print("Exception: " + saved_exception);
|
||||
print("Stack: " + saved_exception.stack);
|
||||
assertUnreachable();
|
||||
}
|
||||
|
||||
print(test_output);
|
||||
|
||||
assertEquals(expected_output, test_output);
|
||||
}
|
||||
|
||||
TestCase('0==', "FEDCBA=A=");
|
||||
TestCase('1==', "FEDCBA=BA=");
|
||||
TestCase('2==', "FEDCBA=CBA=");
|
||||
TestCase('3==', "FEDCBA=DCBA=");
|
||||
TestCase('4==', "FEDCBA=EDCBA=");
|
||||
TestCase('5==', "FEDCBA=FEDCBA=");
|
||||
|
||||
TestCase('=', "FEDCBA=");
|
||||
|
||||
TestCase('C==', "FEDCBA=CBA=");
|
||||
|
||||
TestCase('B=C=A=D==', "FEDCBA=BA=CBA=A=DCBA=");
|
||||
|
||||
// Successive restarts don't work now and require additional fix.
|
||||
//TestCase('BCDE==', "FEDCBA=EDCBA=");
|
||||
//TestCase('BC=BCDE==', "FEDCBA=CBA=EDCBA=");
|
||||
//TestCase('EF==', "FEDCBA=FEDCBA=");
|
@ -63,6 +63,7 @@ unicode-case-overoptimization: PASS, TIMEOUT if ($arch == arm || $arch == mips)
|
||||
debug-liveedit-check-stack: SKIP
|
||||
debug-liveedit-patch-positions-replace: SKIP
|
||||
debug-liveedit-stack-padding: SKIP
|
||||
debug-liveedit-restart-frame: SKIP
|
||||
|
||||
# Test Crankshaft compilation time. Expected to take too long in debug mode.
|
||||
regress/regress-1969: PASS, SKIP if $mode == debug
|
||||
|
Loading…
Reference in New Issue
Block a user