Revision: 5015
Author: [email protected]
Date: Fri Jul 2 13:46:04 2010
Log: Describe LiveEdit changes and support preview mode
Review URL: http://codereview.chromium.org/2883020
http://code.google.com/p/v8/source/detail?r=5015
Modified:
/branches/bleeding_edge/src/debug-debugger.js
/branches/bleeding_edge/src/liveedit-debugger.js
/branches/bleeding_edge/test/mjsunit/debug-liveedit-3.js
/branches/bleeding_edge/test/mjsunit/debug-liveedit-breakpoints.js
/branches/bleeding_edge/test/mjsunit/debug-liveedit-newsource.js
=======================================
--- /branches/bleeding_edge/src/debug-debugger.js Mon Jun 28 05:09:29 2010
+++ /branches/bleeding_edge/src/debug-debugger.js Fri Jul 2 13:46:04 2010
@@ -2070,6 +2070,7 @@
return response.failed('Missing arguments');
}
var script_id = request.arguments.script_id;
+ var preview_only = !!request.arguments.preview_only;
var scripts = %DebugGetLoadedScripts();
@@ -2092,18 +2093,9 @@
var new_source = request.arguments.new_source;
- try {
- Debug.LiveEdit.SetScriptSource(the_script, new_source, change_log);
- } catch (e) {
- if (e instanceof Debug.LiveEdit.Failure) {
- // Let's treat it as a "success" so that body with change_log will be
- // sent back. "change_log" will have "failure" field set.
- change_log.push( { failure: true, message: e.toString() } );
- } else {
- throw e;
- }
- }
- response.body = {change_log: change_log};
+ var result_description = Debug.LiveEdit.SetScriptSource(the_script,
+ new_source, preview_only, change_log);
+ response.body = {change_log: change_log, result: result_description};
};
=======================================
--- /branches/bleeding_edge/src/liveedit-debugger.js Wed Apr 28 06:29:07
2010
+++ /branches/bleeding_edge/src/liveedit-debugger.js Fri Jul 2 13:46:04
2010
@@ -51,7 +51,8 @@
// 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)
- function ApplyPatchMultiChunk(script, diff_array, new_source,
change_log) {
+ function ApplyPatchMultiChunk(script, diff_array, new_source,
preview_only,
+ change_log) {
var old_source = script.source;
@@ -96,7 +97,7 @@
}
// Recursively collects all newly compiled functions that are going
into
- // business and should be have link to the actual script updated.
+ // business and should have link to the actual script updated.
function CollectNew(node_list) {
for (var i = 0; i < node_list.length; i++) {
link_to_original_script_list.push(node_list[i]);
@@ -121,6 +122,20 @@
}
}
+ var preview_description = {
+ change_tree: DescribeChangeTree(root_old_node),
+ textual_diff: {
+ old_len: old_source.length,
+ new_len: new_source.length,
+ chunks: diff_array
+ },
+ updated: false
+ };
+
+ if (preview_only) {
+ return preview_description;
+ }
+
HarvestTodo(root_old_node);
// Collect shared infos for functions whose code need to be patched.
@@ -132,13 +147,15 @@
}
}
- // Check that function being patched is not currently on stack.
- CheckStackActivations(replaced_function_infos, change_log);
-
-
// We haven't changed anything before this line yet.
// Committing all changes.
+ // Check that function being patched is not currently on stack or drop
them.
+ var dropped_functions_number =
+ CheckStackActivations(replaced_function_infos, change_log);
+
+ preview_description.stack_modified = dropped_functions_number != 0;
+
// Start with breakpoints. Convert their line/column positions and
// temporary remove.
var break_points_restorer = TemporaryRemoveBreakPoints(script,
change_log);
@@ -166,6 +183,8 @@
LinkToOldScript(link_to_old_script_list[i], old_script,
link_to_old_script_report);
}
+
+ preview_description.created_script_name = old_script_name;
}
// Link to an actual script all the functions that we are going to use.
@@ -189,6 +208,9 @@
}
break_points_restorer(pos_translator, old_script);
+
+ preview_description.updated = true;
+ return preview_description;
}
// Function is public.
this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
@@ -494,6 +516,16 @@
this.new_end_pos = void 0;
this.corresponding_node = void 0;
this.unmatched_new_nodes = void 0;
+
+ // 'Textual' correspondence/matching is weaker than 'pure'
+ // correspondence/matching. We need 'textual' level for visual
presentation
+ // in UI, we use 'pure' level for actual code manipulation.
+ // Sometimes only function body is changed (functions in old and new
script
+ // textually correspond), but we cannot patch the code, so we see them
+ // as an old function deleted and new function created.
+ this.textual_corresponding_node = void 0;
+ this.textually_unmatched_new_nodes = void 0;
+
this.live_shared_info_wrapper = void 0;
}
@@ -640,6 +672,7 @@
var new_children = new_node.children;
var unmatched_new_nodes_list = [];
+ var textually_unmatched_new_nodes_list = [];
var old_index = 0;
var new_index = 0;
@@ -650,6 +683,7 @@
if (new_children[new_index].info.start_position <
old_children[old_index].new_start_pos) {
unmatched_new_nodes_list.push(new_children[new_index]);
+
textually_unmatched_new_nodes_list.push(new_children[new_index]);
new_index++;
} else if (new_children[new_index].info.start_position ==
old_children[old_index].new_start_pos) {
@@ -657,6 +691,8 @@
old_children[old_index].new_end_pos) {
old_children[old_index].corresponding_node =
new_children[new_index];
+ old_children[old_index].textual_corresponding_node =
+ new_children[new_index];
if (old_children[old_index].status !=
FunctionStatus.UNCHANGED) {
ProcessChildren(old_children[old_index],
new_children[new_index]);
@@ -673,6 +709,7 @@
"No corresponding function in new script found";
old_node.status = FunctionStatus.CHANGED;
unmatched_new_nodes_list.push(new_children[new_index]);
+
textually_unmatched_new_nodes_list.push(new_children[new_index]);
}
new_index++;
old_index++;
@@ -694,21 +731,28 @@
while (new_index < new_children.length) {
unmatched_new_nodes_list.push(new_children[new_index]);
+ textually_unmatched_new_nodes_list.push(new_children[new_index]);
new_index++;
}
if (old_node.status == FunctionStatus.CHANGED) {
- if (!CompareFunctionExpectations(old_node.info, new_node.info)) {
+ var why_wrong_expectations =
+ WhyFunctionExpectationsDiffer(old_node.info, new_node.info);
+ if (why_wrong_expectations) {
old_node.status = FunctionStatus.DAMAGED;
- old_node.status_explanation = "Changed code expectations";
+ old_node.status_explanation = why_wrong_expectations;
}
}
old_node.unmatched_new_nodes = unmatched_new_nodes_list;
+ old_node.textually_unmatched_new_nodes =
+ textually_unmatched_new_nodes_list;
}
ProcessChildren(old_code_tree, new_code_tree);
old_code_tree.corresponding_node = new_code_tree;
+ old_code_tree.textual_corresponding_node = new_code_tree;
+
Assert(old_code_tree.status != FunctionStatus.DAMAGED,
"Script became damaged");
}
@@ -792,27 +836,37 @@
}
// Compares a function interface old and new version, whether it
- // changed or not.
- function CompareFunctionExpectations(function_info1, function_info2) {
+ // changed or not. Returns explanation if they differ.
+ function WhyFunctionExpectationsDiffer(function_info1, function_info2) {
// Check that function has the same number of parameters (there may
exist
// an adapter, that won't survive function parameter number change).
if (function_info1.param_num != function_info2.param_num) {
- return false;
+ return "Changed parameter number: " + function_info1.param_num +
+ " and " + function_info2.param_num;
}
var scope_info1 = function_info1.scope_info;
var scope_info2 = function_info2.scope_info;
-
- if (!scope_info1) {
- return !scope_info2;
- }
-
- if (scope_info1.length != scope_info2.length) {
- return false;
- }
-
- // Check that outer scope structure is not changed. Otherwise the
function
- // will not properly work with existing scopes.
- return scope_info1.toString() == scope_info2.toString();
+
+ var scope_info1_text;
+ var scope_info2_text;
+
+ if (scope_info1) {
+ scope_info1_text = scope_info1.toString();
+ } else {
+ scope_info1_text = "";
+ }
+ if (scope_info2) {
+ scope_info2_text = scope_info2.toString();
+ } else {
+ scope_info2_text = "";
+ }
+
+ if (scope_info1_text != scope_info2_text) {
+ return "Incompatible variable maps: [" + scope_info1_text +
+ "] and [" + scope_info2_text + "]";
+ }
+ // No differences. Return undefined.
+ return;
}
// Minifier forward declaration.
@@ -856,6 +910,8 @@
change_log.push( { functions_on_stack: problems } );
throw new Failure("Blocked by functions on stack");
}
+
+ return dropped.length;
}
// A copy of the FunctionPatchabilityStatus enum from liveedit.h
@@ -897,14 +953,11 @@
this.GetPcFromSourcePos = GetPcFromSourcePos;
// LiveEdit main entry point: changes a script text to a new string.
- function SetScriptSource(script, new_source, change_log) {
+ function SetScriptSource(script, new_source, preview_only, change_log) {
var old_source = script.source;
var diff = CompareStringsLinewise(old_source, new_source);
- if (diff.length == 0) {
- change_log.push( { empty_diff: true } );
- return;
- }
- ApplyPatchMultiChunk(script, diff, new_source, change_log);
+ return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
+ change_log);
}
// Function is public.
this.SetScriptSource = SetScriptSource;
@@ -931,7 +984,67 @@
return ApplyPatchMultiChunk(script,
[ change_pos, change_pos + change_len, change_pos +
new_str.length],
- new_source, change_log);
+ new_source, false, change_log);
+ }
+
+ // Creates JSON description for a change tree.
+ function DescribeChangeTree(old_code_tree) {
+
+ function ProcessOldNode(node) {
+ var child_infos = [];
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+ if (child.status != FunctionStatus.UNCHANGED) {
+ child_infos.push(ProcessOldNode(child));
+ }
+ }
+ var new_child_infos = [];
+ if (node.textually_unmatched_new_nodes) {
+ for (var i = 0; i < node.textually_unmatched_new_nodes.length;
i++) {
+ var child = node.textually_unmatched_new_nodes[i];
+ new_child_infos.push(ProcessNewNode(child));
+ }
+ }
+ var res = {
+ name: node.info.function_name,
+ positions: DescribePositions(node),
+ status: node.status,
+ children: child_infos,
+ new_children: new_child_infos
+ };
+ if (node.status_explanation) {
+ res.status_explanation = node.status_explanation;
+ }
+ if (node.textual_corresponding_node) {
+ res.new_positions =
DescribePositions(node.textual_corresponding_node);
+ }
+ return res;
+ }
+
+ function ProcessNewNode(node) {
+ var child_infos = [];
+ // Do not list ancestors.
+ if (false) {
+ for (var i = 0; i < node.children.length; i++) {
+ child_infos.push(ProcessNewNode(node.children[i]));
+ }
+ }
+ var res = {
+ name: node.info.function_name,
+ positions: DescribePositions(node),
+ children: child_infos,
+ };
+ return res;
+ }
+
+ function DescribePositions(node) {
+ return {
+ start_position: node.info.start_position,
+ end_position: node.info.end_position
+ };
+ }
+
+ return ProcessOldNode(old_code_tree);
}
=======================================
--- /branches/bleeding_edge/test/mjsunit/debug-liveedit-3.js Wed Apr 28
04:38:43 2010
+++ /branches/bleeding_edge/test/mjsunit/debug-liveedit-3.js Fri Jul 2
13:46:04 2010
@@ -57,7 +57,8 @@
print("new source: " + new_source);
var change_log = new Array();
-Debug.LiveEdit.SetScriptSource(script, new_source, change_log);
+var result = Debug.LiveEdit.SetScriptSource(script, new_source, false,
change_log);
+print("Result: " + JSON.stringify(result) + "\n");
print("Change log: " + JSON.stringify(change_log) + "\n");
assertEquals(8, z6());
=======================================
--- /branches/bleeding_edge/test/mjsunit/debug-liveedit-breakpoints.js Wed
Apr 28 04:38:43 2010
+++ /branches/bleeding_edge/test/mjsunit/debug-liveedit-breakpoints.js Fri
Jul 2 13:46:04 2010
@@ -72,7 +72,8 @@
print("new source: " + new_source);
var change_log = new Array();
-Debug.LiveEdit.SetScriptSource(script, new_source, change_log);
+var result = Debug.LiveEdit.SetScriptSource(script, new_source, false,
change_log);
+print("Result: " + JSON.stringify(result) + "\n");
print("Change log: " + JSON.stringify(change_log) + "\n");
var breaks = Debug.scriptBreakPoints();
=======================================
--- /branches/bleeding_edge/test/mjsunit/debug-liveedit-newsource.js Wed
Apr 21 09:59:58 2010
+++ /branches/bleeding_edge/test/mjsunit/debug-liveedit-newsource.js Fri
Jul 2 13:46:04 2010
@@ -57,7 +57,8 @@
print("new source: " + new_source);
var change_log = new Array();
-Debug.LiveEdit.SetScriptSource(script, new_source, change_log);
+var result = Debug.LiveEdit.SetScriptSource(script, new_source, false,
change_log);
+print("Result: " + JSON.stringify(result) + "\n");
print("Change log: " + JSON.stringify(change_log) + "\n");
assertEquals("Capybara", ChooseAnimal());
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev