Commit 6f099e4c authored by peter.rybin@gmail.com's avatar peter.rybin@gmail.com

LiveEdit: clean JS sources a bit

Review URL: http://codereview.chromium.org/1697016

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4539 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 8fee5e81
......@@ -2018,32 +2018,16 @@ DebugCommandProcessor.prototype.changeLiveRequest_ = function(request, response)
return;
}
// A function that calls a proper signature of LiveEdit API.
var invocation;
var change_log = new Array();
if (IS_STRING(request.arguments.new_source)) {
var new_source = request.arguments.new_source;
invocation = function() {
return Debug.LiveEdit.SetScriptSource(the_script, new_source, change_log);
}
} else {
var change_pos = parseInt(request.arguments.change_pos);
var change_len = parseInt(request.arguments.change_len);
var new_string = request.arguments.new_string;
if (!IS_STRING(new_string)) {
response.failed('Argument "new_string" is not a string value');
return;
}
invocation = function() {
return Debug.LiveEdit.ApplyPatch(the_script, change_pos, change_len,
new_string, change_log);
}
if (!IS_STRING(request.arguments.new_source)) {
throw "new_source argument expected";
}
var new_source = request.arguments.new_source;
try {
invocation();
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
......
......@@ -44,222 +44,41 @@
// LiveEdit namespace is declared inside a single function constructor.
Debug.LiveEdit = new function() {
// Applies the change to the script.
// The change is always a substring (change_pos, change_pos + change_len)
// being replaced with a completely different string new_str.
// This API is a legacy and is obsolete.
//
// @param {Script} script that is being changed
// @param {Array} change_log a list that collects engineer-readable
// description of what happened.
function ApplyPatch(script, change_pos, change_len, new_str,
change_log) {
var old_source = script.source;
// Prepare new source string.
var new_source = old_source.substring(0, change_pos) +
new_str + old_source.substring(change_pos + change_len);
return ApplyPatchMultiChunk(script,
[ change_pos, change_pos + change_len, change_pos + new_str.length],
new_source, change_log);
}
// Function is public.
this.ApplyPatch = ApplyPatch;
// Forward declaration for minifier.
var FunctionStatus;
// 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) {
// Fully compiles source string as a script. Returns Array of
// FunctionCompileInfo -- a descriptions of all functions of the script.
// Elements of array are ordered by start positions of functions (from top
// to bottom) in the source. Fields outer_index and next_sibling_index help
// to navigate the nesting structure of functions.
//
// The script is used for compilation, because it produces code that
// needs to be linked with some particular script (for nested functions).
function GatherCompileInfo(source) {
// Get function info, elements are partially sorted (it is a tree of
// nested functions serialized as parent followed by serialized children.
var raw_compile_info = %LiveEditGatherCompileInfo(script, source);
// Sort function infos by start position field.
var compile_info = new Array();
var old_index_map = new Array();
for (var i = 0; i < raw_compile_info.length; i++) {
var info = new FunctionCompileInfo(raw_compile_info[i]);
// Remove all links to the actual script. Breakpoints system and
// LiveEdit itself believe that any function in heap that points to a
// particular script is a regular function.
// For some functions we will restore this link later.
%LiveEditFunctionSetScript(info.shared_function_info, void 0);
compile_info.push(info);
old_index_map.push(i);
}
for (var i = 0; i < compile_info.length; i++) {
var k = i;
for (var j = i + 1; j < compile_info.length; j++) {
if (compile_info[k].start_position > compile_info[j].start_position) {
k = j;
}
}
if (k != i) {
var temp_info = compile_info[k];
var temp_index = old_index_map[k];
compile_info[k] = compile_info[i];
old_index_map[k] = old_index_map[i];
compile_info[i] = temp_info;
old_index_map[i] = temp_index;
}
}
// After sorting update outer_inder field using old_index_map. Also
// set next_sibling_index field.
var current_index = 0;
// The recursive function, that goes over all children of a particular
// node (i.e. function info).
function ResetIndexes(new_parent_index, old_parent_index) {
var previous_sibling = -1;
while (current_index < compile_info.length &&
compile_info[current_index].outer_index == old_parent_index) {
var saved_index = current_index;
compile_info[saved_index].outer_index = new_parent_index;
if (previous_sibling != -1) {
compile_info[previous_sibling].next_sibling_index = saved_index;
}
previous_sibling = saved_index;
current_index++;
ResetIndexes(saved_index, old_index_map[saved_index]);
}
if (previous_sibling != -1) {
compile_info[previous_sibling].next_sibling_index = -1;
}
}
ResetIndexes(-1, -1);
Assert(current_index == compile_info.length);
return compile_info;
}
// Variable forward declarations. Preprocessor "Minifier" needs them.
var old_compile_info;
var shared_infos;
// Finds SharedFunctionInfo that corresponds compile info with index
// in old version of the script.
// TODO(LiveEdit): Redesign this part. Find live SharedFunctionInfo
// about the time that FindCorrespondingFunctions is being run.
function FindFunctionInfo(index) {
var old_info = old_compile_info[index];
for (var i = 0; i < shared_infos.length; i++) {
var info = shared_infos[i];
if (info.start_position == old_info.start_position &&
info.end_position == old_info.end_position) {
return info;
}
}
}
// Replaces function's Code.
function PatchCode(old_node) {
var new_info = old_node.corresponding_node.info;
var shared_info = FindFunctionInfo(old_node.array_index);
if (shared_info) {
%LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array);
// The function got a new code. However, this new code brings all new
// instances of SharedFunctionInfo for nested functions. However,
// we want the original instances to be used wherever possible.
// (This is because old instances and new instances will be both
// linked to a script and breakpoints subsystem does not really
// expects this; neither does LiveEdit subsystem on next call).
for (var i = 0; i < old_node.children.length; i++) {
if (old_node.children[i].corresponding_node) {
var corresponding_child = old_node.children[i].corresponding_node;
var child_shared_info =
FindFunctionInfo(old_node.children[i].array_index);
if (child_shared_info) {
%LiveEditReplaceRefToNestedFunction(shared_info.info,
corresponding_child.info.shared_function_info,
child_shared_info.info);
}
}
}
change_log.push( {function_patched: new_info.function_name} );
} else {
change_log.push( {function_patched: new_info.function_name,
function_info_not_found: true} );
}
}
var position_patch_report;
function PatchPositions(old_info, shared_info) {
if (!shared_info) {
// TODO(LiveEdit): function is not compiled yet or is already collected.
position_patch_report.push(
{ name: old_info.function_name, info_not_found: true } );
return;
}
%LiveEditPatchFunctionPositions(
shared_info.raw_array, diff_array);
position_patch_report.push( { name: old_info.function_name } );
}
var link_to_old_script_report;
var old_script;
// Makes a function associated with another instance of a script (the
// one representing its old version). This way the function still
// may access its own text.
function LinkToOldScript(shared_info, old_info_node) {
if (shared_info) {
%LiveEditFunctionSetScript(shared_info.info, old_script);
link_to_old_script_report.push( { name: shared_info.function_name } );
} else {
link_to_old_script_report.push(
{ name: old_info_node.info.function_name, not_found: true } );
}
}
var old_source = script.source;
// Find all SharedFunctionInfo's that are compiled from this script.
var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
var shared_infos = new Array();
for (var i = 0; i < shared_raw_list.length; i++) {
shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
}
// Gather compile information about old version of script.
var old_compile_info = GatherCompileInfo(old_source);
var old_compile_info = GatherCompileInfo(old_source, script);
// Build tree structures for old and new versions of the script.
var root_old_node = BuildCodeInfoTree(old_compile_info);
var pos_translator = new PosTranslator(diff_array);
// Analyze changes.
MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
// Find all SharedFunctionInfo's that were compiled from this script.
FindLiveSharedInfos(root_old_node, script);
// Gather compile information about new version of script.
var new_compile_info;
try {
new_compile_info = GatherCompileInfo(new_source);
new_compile_info = GatherCompileInfo(new_source, script);
} catch (e) {
throw new Failure("Failed to compile new version of script: " + e);
}
var pos_translator = new PosTranslator(diff_array);
// Build tree structures for old and new versions of the script.
var root_old_node = BuildCodeInfoTree(old_compile_info);
var root_new_node = BuildCodeInfoTree(new_compile_info);
// Analyze changes.
MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
// Link recompiled script data with other data.
FindCorrespondingFunctions(root_old_node, root_new_node);
// Prepare to-do lists.
......@@ -307,9 +126,9 @@ Debug.LiveEdit = new function() {
// Collect shared infos for functions whose code need to be patched.
var replaced_function_infos = new Array();
for (var i = 0; i < replace_code_list.length; i++) {
var info = FindFunctionInfo(replace_code_list[i].array_index);
if (info) {
replaced_function_infos.push(info);
var info_wrapper = replace_code_list[i].live_shared_info_wrapper;
if (info_wrapper) {
replaced_function_infos.push(info_wrapper);
}
}
......@@ -344,9 +163,8 @@ Debug.LiveEdit = new function() {
// We need to link to old script all former nested functions.
for (var i = 0; i < link_to_old_script_list.length; i++) {
LinkToOldScript(
FindFunctionInfo(link_to_old_script_list[i].array_index),
link_to_old_script_list[i]);
LinkToOldScript(link_to_old_script_list[i], old_script,
link_to_old_script_report);
}
}
......@@ -357,7 +175,7 @@ Debug.LiveEdit = new function() {
}
for (var i = 0; i < replace_code_list.length; i++) {
PatchCode(replace_code_list[i]);
PatchFunctionCode(replace_code_list[i], change_log);
}
var position_patch_report = new Array();
......@@ -366,8 +184,8 @@ Debug.LiveEdit = new function() {
for (var i = 0; i < update_positions_list.length; i++) {
// TODO(LiveEdit): take into account wether it's source_changed or
// unchanged and whether positions changed at all.
PatchPositions(update_positions_list[i].info,
FindFunctionInfo(update_positions_list[i].array_index));
PatchPositions(update_positions_list[i], diff_array,
position_patch_report);
}
break_points_restorer(pos_translator, old_script);
......@@ -375,6 +193,132 @@ Debug.LiveEdit = new function() {
// Function is public.
this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
// Fully compiles source string as a script. Returns Array of
// FunctionCompileInfo -- a descriptions of all functions of the script.
// Elements of array are ordered by start positions of functions (from top
// to bottom) in the source. Fields outer_index and next_sibling_index help
// to navigate the nesting structure of functions.
//
// All functions get compiled linked to script provided as parameter script.
// TODO(LiveEdit): consider not using actual scripts as script, because
// we have to manually erase all links right after compile.
function GatherCompileInfo(source, script) {
// Get function info, elements are partially sorted (it is a tree of
// nested functions serialized as parent followed by serialized children.
var raw_compile_info = %LiveEditGatherCompileInfo(script, source);
// Sort function infos by start position field.
var compile_info = new Array();
var old_index_map = new Array();
for (var i = 0; i < raw_compile_info.length; i++) {
var info = new FunctionCompileInfo(raw_compile_info[i]);
// Remove all links to the actual script. Breakpoints system and
// LiveEdit itself believe that any function in heap that points to a
// particular script is a regular function.
// For some functions we will restore this link later.
%LiveEditFunctionSetScript(info.shared_function_info, void 0);
compile_info.push(info);
old_index_map.push(i);
}
for (var i = 0; i < compile_info.length; i++) {
var k = i;
for (var j = i + 1; j < compile_info.length; j++) {
if (compile_info[k].start_position > compile_info[j].start_position) {
k = j;
}
}
if (k != i) {
var temp_info = compile_info[k];
var temp_index = old_index_map[k];
compile_info[k] = compile_info[i];
old_index_map[k] = old_index_map[i];
compile_info[i] = temp_info;
old_index_map[i] = temp_index;
}
}
// After sorting update outer_inder field using old_index_map. Also
// set next_sibling_index field.
var current_index = 0;
// The recursive function, that goes over all children of a particular
// node (i.e. function info).
function ResetIndexes(new_parent_index, old_parent_index) {
var previous_sibling = -1;
while (current_index < compile_info.length &&
compile_info[current_index].outer_index == old_parent_index) {
var saved_index = current_index;
compile_info[saved_index].outer_index = new_parent_index;
if (previous_sibling != -1) {
compile_info[previous_sibling].next_sibling_index = saved_index;
}
previous_sibling = saved_index;
current_index++;
ResetIndexes(saved_index, old_index_map[saved_index]);
}
if (previous_sibling != -1) {
compile_info[previous_sibling].next_sibling_index = -1;
}
}
ResetIndexes(-1, -1);
Assert(current_index == compile_info.length);
return compile_info;
}
// Replaces function's Code.
function PatchFunctionCode(old_node, change_log) {
var new_info = old_node.corresponding_node.info;
var shared_info_wrapper = old_node.live_shared_info_wrapper;
if (shared_info_wrapper) {
%LiveEditReplaceFunctionCode(new_info.raw_array,
shared_info_wrapper.raw_array);
// The function got a new code. However, this new code brings all new
// instances of SharedFunctionInfo for nested functions. However,
// we want the original instances to be used wherever possible.
// (This is because old instances and new instances will be both
// linked to a script and breakpoints subsystem does not really
// expects this; neither does LiveEdit subsystem on next call).
for (var i = 0; i < old_node.children.length; i++) {
if (old_node.children[i].corresponding_node) {
var corresponding_child = old_node.children[i].corresponding_node;
var child_shared_info_wrapper =
old_node.children[i].live_shared_info_wrapper;
if (child_shared_info_wrapper) {
%LiveEditReplaceRefToNestedFunction(shared_info_wrapper.info,
corresponding_child.info.shared_function_info,
child_shared_info_wrapper.info);
}
}
}
change_log.push( {function_patched: new_info.function_name} );
} else {
change_log.push( {function_patched: new_info.function_name,
function_info_not_found: true} );
}
}
// Makes a function associated with another instance of a script (the
// one representing its old version). This way the function still
// may access its own text.
function LinkToOldScript(old_info_node, old_script, report_array) {
var shared_info = old_info_node.live_shared_info_wrapper;
if (shared_info) {
%LiveEditFunctionSetScript(shared_info.info, old_script);
report_array.push( { name: shared_info.function_name } );
} else {
report_array.push(
{ name: old_info_node.info.function_name, not_found: true } );
}
}
// Returns function that restores breakpoints.
function TemporaryRemoveBreakPoints(original_script, change_log) {
......@@ -550,6 +494,7 @@ Debug.LiveEdit = new function() {
this.new_end_pos = void 0;
this.corresponding_node = void 0;
this.unmatched_new_nodes = void 0;
this.live_shared_info_wrapper = void 0;
}
// From array of function infos that is implicitly a tree creates
......@@ -767,6 +712,40 @@ Debug.LiveEdit = new function() {
Assert(old_code_tree.status != FunctionStatus.DAMAGED,
"Script became damaged");
}
function FindLiveSharedInfos(old_code_tree, script) {
var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
var shared_infos = new Array();
for (var i = 0; i < shared_raw_list.length; i++) {
shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
}
// Finds SharedFunctionInfo that corresponds compile info with index
// in old version of the script.
function FindFunctionInfo(compile_info) {
for (var i = 0; i < shared_infos.length; i++) {
var wrapper = shared_infos[i];
if (wrapper.start_position == compile_info.start_position &&
wrapper.end_position == compile_info.end_position) {
return wrapper;
}
}
}
function TraverseTree(node) {
var info_wrapper = FindFunctionInfo(node.info);
if (info_wrapper) {
node.live_shared_info_wrapper = info_wrapper;
}
for (var i = 0; i < node.children.length; i++) {
TraverseTree(node.children[i]);
}
}
TraverseTree(old_code_tree);
}
// An object describing function compilation details. Its index fields
......@@ -791,7 +770,21 @@ Debug.LiveEdit = new function() {
this.info = raw_array[3];
this.raw_array = raw_array;
}
// Changes positions (including all statments) in function.
function PatchPositions(old_info_node, diff_array, report_array) {
var shared_info_wrapper = old_info_node.live_shared_info_wrapper;
if (!shared_info_wrapper) {
// TODO(LiveEdit): function is not compiled yet or is already collected.
report_array.push(
{ name: old_info_node.info.function_name, info_not_found: true } );
return;
}
%LiveEditPatchFunctionPositions(shared_info_wrapper.raw_array,
diff_array);
report_array.push( { name: old_info_node.info.function_name } );
}
// Adds a suffix to script name to mark that it is old version.
function CreateNameForOldScript(script) {
// TODO(635): try better than this; support several changes.
......@@ -919,10 +912,33 @@ Debug.LiveEdit = new function() {
function CompareStringsLinewise(s1, s2) {
return %LiveEditCompareStringsLinewise(s1, s2);
}
// Applies the change to the script.
// The change is always a substring (change_pos, change_pos + change_len)
// being replaced with a completely different string new_str.
// This API is a legacy and is obsolete.
//
// @param {Script} script that is being changed
// @param {Array} change_log a list that collects engineer-readable
// description of what happened.
function ApplySingleChunkPatch(script, change_pos, change_len, new_str,
change_log) {
var old_source = script.source;
// Prepare new source string.
var new_source = old_source.substring(0, change_pos) +
new_str + old_source.substring(change_pos + change_len);
return ApplyPatchMultiChunk(script,
[ change_pos, change_pos + change_len, change_pos + new_str.length],
new_source, change_log);
}
// Functions are public for tests.
this.TestApi = {
PosTranslator: PosTranslator,
CompareStringsLinewise: CompareStringsLinewise
CompareStringsLinewise: CompareStringsLinewise,
ApplySingleChunkPatch: ApplySingleChunkPatch
}
}
......@@ -43,6 +43,6 @@ var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "Cap' + 'y' + 'bara";
var change_log = new Array();
Debug.LiveEdit.ApplyPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
assertEquals("Capybara", ChooseAnimal());
......@@ -58,7 +58,7 @@ var new_animal_patch = "'Capybara' + p";
// because old value of parameter "p" was not saved.
// Instead it patches ChooseAnimal.
var change_log = new Array();
Debug.LiveEdit.ApplyPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
print("Change log: " + JSON.stringify(change_log) + "\n");
var new_closure = ChooseAnimal(19);
......
......@@ -60,7 +60,7 @@ function TestBase(name) {
// Runs in debugger context.
var change_log = new Array();
try {
Debug.LiveEdit.ApplyPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
} finally {
print("Change log: " + JSON.stringify(change_log) + "\n");
}
......
......@@ -59,7 +59,7 @@ var new_body_patch = "{return 'Capybara';" +
var change_log = new Array();
function Changer() {
Debug.LiveEdit.ApplyPatch(script, patch_pos, orig_body.length, new_body_patch, change_log);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_body.length, new_body_patch, change_log);
print("Change log: " + JSON.stringify(change_log) + "\n");
}
......
......@@ -80,7 +80,7 @@ var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "'Capybara'";
var change_log = new Array();
Debug.LiveEdit.ApplyPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
print("Change log: " + JSON.stringify(change_log) + "\n");
var res = ChooseAnimal();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment