LiveEdit: breakpoints updates and fixes for related problems
Review URL: http://codereview.chromium.org/1800007 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4533 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
ff0775c38f
commit
80453231fe
@ -31,7 +31,6 @@
|
||||
#include "codegen-inl.h"
|
||||
#include "compiler.h"
|
||||
#include "debug.h"
|
||||
#include "liveedit.h"
|
||||
#include "oprofile-agent.h"
|
||||
#include "prettyprinter.h"
|
||||
#include "register-allocator-inl.h"
|
||||
@ -204,7 +203,6 @@ Handle<Code> CodeGenerator::MakeCodeEpilogue(MacroAssembler* masm,
|
||||
// all the pieces into a Code object. This function is only to be called by
|
||||
// the compiler.cc code.
|
||||
Handle<Code> CodeGenerator::MakeCode(CompilationInfo* info) {
|
||||
LiveEditFunctionTracker live_edit_tracker(info->function());
|
||||
Handle<Script> script = info->script();
|
||||
if (!script->IsUndefined() && !script->source()->IsUndefined()) {
|
||||
int len = String::cast(script->source())->length();
|
||||
@ -216,7 +214,6 @@ Handle<Code> CodeGenerator::MakeCode(CompilationInfo* info) {
|
||||
MacroAssembler masm(NULL, kInitialBufferSize);
|
||||
CodeGenerator cgen(&masm);
|
||||
CodeGeneratorScope scope(&cgen);
|
||||
live_edit_tracker.RecordFunctionScope(info->function()->scope());
|
||||
cgen.Generate(info);
|
||||
if (cgen.HasStackOverflow()) {
|
||||
ASSERT(!Top::has_pending_exception());
|
||||
@ -225,9 +222,7 @@ Handle<Code> CodeGenerator::MakeCode(CompilationInfo* info) {
|
||||
|
||||
InLoopFlag in_loop = (cgen.loop_nesting() != 0) ? IN_LOOP : NOT_IN_LOOP;
|
||||
Code::Flags flags = Code::ComputeFlags(Code::FUNCTION, in_loop);
|
||||
Handle<Code> result = MakeCodeEpilogue(cgen.masm(), flags, info);
|
||||
live_edit_tracker.RecordFunctionCode(result);
|
||||
return result;
|
||||
return MakeCodeEpilogue(cgen.masm(), flags, info);
|
||||
}
|
||||
|
||||
|
||||
|
@ -192,6 +192,8 @@ static Handle<SharedFunctionInfo> MakeFunctionInfo(bool is_global,
|
||||
FunctionLiteral* lit =
|
||||
MakeAST(is_global, script, extension, pre_data, is_json);
|
||||
|
||||
LiveEditFunctionTracker live_edit_tracker(lit);
|
||||
|
||||
// Check for parse errors.
|
||||
if (lit == NULL) {
|
||||
ASSERT(Top::has_pending_exception());
|
||||
@ -253,6 +255,8 @@ static Handle<SharedFunctionInfo> MakeFunctionInfo(bool is_global,
|
||||
Debugger::OnAfterCompile(script, Debugger::NO_AFTER_COMPILE_FLAGS);
|
||||
#endif
|
||||
|
||||
live_edit_tracker.RecordFunctionInfo(result, lit);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -448,6 +452,7 @@ bool Compiler::CompileLazy(CompilationInfo* info) {
|
||||
Handle<SharedFunctionInfo> Compiler::BuildFunctionInfo(FunctionLiteral* literal,
|
||||
Handle<Script> script,
|
||||
AstVisitor* caller) {
|
||||
LiveEditFunctionTracker live_edit_tracker(literal);
|
||||
#ifdef DEBUG
|
||||
// We should not try to compile the same function literal more than
|
||||
// once.
|
||||
@ -552,6 +557,7 @@ Handle<SharedFunctionInfo> Compiler::BuildFunctionInfo(FunctionLiteral* literal,
|
||||
// the resulting function.
|
||||
SetExpectedNofPropertiesFromEstimate(result,
|
||||
literal->expected_property_count());
|
||||
live_edit_tracker.RecordFunctionInfo(result, literal);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -124,12 +124,6 @@ BreakPoint.prototype.source_position = function() {
|
||||
};
|
||||
|
||||
|
||||
BreakPoint.prototype.updateSourcePosition = function(new_position, script) {
|
||||
this.source_position_ = new_position;
|
||||
// TODO(635): also update line and column.
|
||||
};
|
||||
|
||||
|
||||
BreakPoint.prototype.hit_count = function() {
|
||||
return this.hit_count_;
|
||||
};
|
||||
@ -245,6 +239,21 @@ function ScriptBreakPoint(type, script_id_or_name, opt_line, opt_column,
|
||||
}
|
||||
|
||||
|
||||
//Creates a clone of script breakpoint that is linked to another script.
|
||||
ScriptBreakPoint.prototype.cloneForOtherScript = function (other_script) {
|
||||
var copy = new ScriptBreakPoint(Debug.ScriptBreakPointType.ScriptId,
|
||||
other_script.id, this.line_, this.column_, this.groupId_);
|
||||
copy.number_ = next_break_point_number++;
|
||||
script_break_points.push(copy);
|
||||
|
||||
copy.hit_count_ = this.hit_count_;
|
||||
copy.active_ = this.active_;
|
||||
copy.condition_ = this.condition_;
|
||||
copy.ignoreCount_ = this.ignoreCount_;
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
ScriptBreakPoint.prototype.number = function() {
|
||||
return this.number_;
|
||||
};
|
||||
@ -280,6 +289,13 @@ ScriptBreakPoint.prototype.column = function() {
|
||||
};
|
||||
|
||||
|
||||
ScriptBreakPoint.prototype.update_positions = function(line, column) {
|
||||
this.line_ = line;
|
||||
this.column_ = column;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ScriptBreakPoint.prototype.hit_count = function() {
|
||||
return this.hit_count_;
|
||||
};
|
||||
@ -406,6 +422,17 @@ function UpdateScriptBreakPoints(script) {
|
||||
}
|
||||
|
||||
|
||||
function GetScriptBreakPoints(script) {
|
||||
var result = [];
|
||||
for (var i = 0; i < script_break_points.length; i++) {
|
||||
if (script_break_points[i].matchesScript(script)) {
|
||||
result.push(script_break_points[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Debug.setListener = function(listener, opt_data) {
|
||||
if (!IS_FUNCTION(listener) && !IS_UNDEFINED(listener) && !IS_NULL(listener)) {
|
||||
throw new Error('Parameters have wrong types.');
|
||||
|
@ -450,7 +450,6 @@ Handle<Code> FullCodeGenerator::MakeCode(CompilationInfo* info) {
|
||||
CodeGenerator::MakeCodePrologue(info);
|
||||
const int kInitialBufferSize = 4 * KB;
|
||||
MacroAssembler masm(NULL, kInitialBufferSize);
|
||||
LiveEditFunctionTracker live_edit_tracker(info->function());
|
||||
|
||||
FullCodeGenerator cgen(&masm);
|
||||
cgen.Generate(info, PRIMARY);
|
||||
@ -459,9 +458,7 @@ Handle<Code> FullCodeGenerator::MakeCode(CompilationInfo* info) {
|
||||
return Handle<Code>::null();
|
||||
}
|
||||
Code::Flags flags = Code::ComputeFlags(Code::FUNCTION, NOT_IN_LOOP);
|
||||
Handle<Code> result = CodeGenerator::MakeCodeEpilogue(&masm, flags, info);
|
||||
live_edit_tracker.RecordFunctionCode(result);
|
||||
return result;
|
||||
return CodeGenerator::MakeCodeEpilogue(&masm, flags, info);
|
||||
}
|
||||
|
||||
|
||||
|
@ -83,7 +83,7 @@ Debug.LiveEdit = new function() {
|
||||
//
|
||||
// The script is used for compilation, because it produces code that
|
||||
// needs to be linked with some particular script (for nested functions).
|
||||
function DebugGatherCompileInfo(source) {
|
||||
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);
|
||||
@ -92,8 +92,14 @@ Debug.LiveEdit = new function() {
|
||||
var compile_info = new Array();
|
||||
var old_index_map = new Array();
|
||||
for (var i = 0; i < raw_compile_info.length; i++) {
|
||||
compile_info.push(new FunctionCompileInfo(raw_compile_info[i]));
|
||||
old_index_map.push(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++) {
|
||||
@ -148,6 +154,8 @@ Debug.LiveEdit = new function() {
|
||||
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++) {
|
||||
@ -160,15 +168,36 @@ Debug.LiveEdit = new function() {
|
||||
}
|
||||
|
||||
// Replaces function's Code.
|
||||
function PatchCode(new_info, shared_info) {
|
||||
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} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -180,15 +209,8 @@ Debug.LiveEdit = new function() {
|
||||
{ name: old_info.function_name, info_not_found: true } );
|
||||
return;
|
||||
}
|
||||
var breakpoint_position_update = %LiveEditPatchFunctionPositions(
|
||||
%LiveEditPatchFunctionPositions(
|
||||
shared_info.raw_array, diff_array);
|
||||
for (var i = 0; i < breakpoint_position_update.length; i += 2) {
|
||||
var new_pos = breakpoint_position_update[i];
|
||||
var break_point_object = breakpoint_position_update[i + 1];
|
||||
change_log.push( { breakpoint_position_update:
|
||||
{ from: break_point_object.source_position(), to: new_pos } } );
|
||||
break_point_object.updateSourcePosition(new_pos, script);
|
||||
}
|
||||
position_patch_report.push( { name: old_info.function_name } );
|
||||
}
|
||||
|
||||
@ -199,7 +221,7 @@ Debug.LiveEdit = new function() {
|
||||
// may access its own text.
|
||||
function LinkToOldScript(shared_info, old_info_node) {
|
||||
if (shared_info) {
|
||||
%LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script);
|
||||
%LiveEditFunctionSetScript(shared_info.info, old_script);
|
||||
link_to_old_script_report.push( { name: shared_info.function_name } );
|
||||
} else {
|
||||
link_to_old_script_report.push(
|
||||
@ -220,12 +242,12 @@ Debug.LiveEdit = new function() {
|
||||
}
|
||||
|
||||
// Gather compile information about old version of script.
|
||||
var old_compile_info = DebugGatherCompileInfo(old_source);
|
||||
var old_compile_info = GatherCompileInfo(old_source);
|
||||
|
||||
// Gather compile information about new version of script.
|
||||
var new_compile_info;
|
||||
try {
|
||||
new_compile_info = DebugGatherCompileInfo(new_source);
|
||||
new_compile_info = GatherCompileInfo(new_source);
|
||||
} catch (e) {
|
||||
throw new Failure("Failed to compile new version of script: " + e);
|
||||
}
|
||||
@ -243,6 +265,7 @@ Debug.LiveEdit = new function() {
|
||||
// Prepare to-do lists.
|
||||
var replace_code_list = new Array();
|
||||
var link_to_old_script_list = new Array();
|
||||
var link_to_original_script_list = new Array();
|
||||
var update_positions_list = new Array();
|
||||
|
||||
function HarvestTodo(old_node) {
|
||||
@ -252,6 +275,15 @@ Debug.LiveEdit = new function() {
|
||||
CollectDamaged(node.children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively collects all newly compiled functions that are going into
|
||||
// business and should be 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]);
|
||||
CollectNew(node_list[i].children);
|
||||
}
|
||||
}
|
||||
|
||||
if (old_node.status == FunctionStatus.DAMAGED) {
|
||||
CollectDamaged(old_node);
|
||||
@ -263,6 +295,7 @@ Debug.LiveEdit = new function() {
|
||||
update_positions_list.push(old_node);
|
||||
} else if (old_node.status == FunctionStatus.CHANGED) {
|
||||
replace_code_list.push(old_node);
|
||||
CollectNew(old_node.unmatched_new_nodes);
|
||||
}
|
||||
for (var i = 0; i < old_node.children.length; i++) {
|
||||
HarvestTodo(old_node.children[i]);
|
||||
@ -286,14 +319,24 @@ Debug.LiveEdit = new function() {
|
||||
|
||||
// We haven't changed anything before this line yet.
|
||||
// Committing all changes.
|
||||
|
||||
// Start with breakpoints. Convert their line/column positions and
|
||||
// temporary remove.
|
||||
var break_points_restorer = TemporaryRemoveBreakPoints(script, change_log);
|
||||
|
||||
// Create old script if there are function linked to old version.
|
||||
if (link_to_old_script_list.length > 0) {
|
||||
var old_script;
|
||||
|
||||
// Create an old script only if there are function that should be linked
|
||||
// to old version.
|
||||
if (link_to_old_script_list.length == 0) {
|
||||
%LiveEditReplaceScript(script, new_source, null);
|
||||
old_script = void 0;
|
||||
} else {
|
||||
var old_script_name = CreateNameForOldScript(script);
|
||||
|
||||
// Update the script text and create a new script representing an old
|
||||
// version of the script.
|
||||
var old_script = %LiveEditReplaceScript(script, new_source,
|
||||
old_script = %LiveEditReplaceScript(script, new_source,
|
||||
old_script_name);
|
||||
|
||||
var link_to_old_script_report = new Array();
|
||||
@ -307,10 +350,14 @@ Debug.LiveEdit = new function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Link to an actual script all the functions that we are going to use.
|
||||
for (var i = 0; i < link_to_original_script_list.length; i++) {
|
||||
%LiveEditFunctionSetScript(
|
||||
link_to_original_script_list[i].info.shared_function_info, script);
|
||||
}
|
||||
|
||||
for (var i = 0; i < replace_code_list.length; i++) {
|
||||
PatchCode(replace_code_list[i].corresponding_node.info,
|
||||
FindFunctionInfo(replace_code_list[i].array_index));
|
||||
PatchCode(replace_code_list[i]);
|
||||
}
|
||||
|
||||
var position_patch_report = new Array();
|
||||
@ -322,10 +369,84 @@ Debug.LiveEdit = new function() {
|
||||
PatchPositions(update_positions_list[i].info,
|
||||
FindFunctionInfo(update_positions_list[i].array_index));
|
||||
}
|
||||
|
||||
break_points_restorer(pos_translator, old_script);
|
||||
}
|
||||
// Function is public.
|
||||
this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
|
||||
|
||||
|
||||
// Returns function that restores breakpoints.
|
||||
function TemporaryRemoveBreakPoints(original_script, change_log) {
|
||||
var script_break_points = GetScriptBreakPoints(original_script);
|
||||
|
||||
var break_points_update_report = [];
|
||||
change_log.push( { break_points_update: break_points_update_report } );
|
||||
|
||||
var break_point_old_positions = [];
|
||||
for (var i = 0; i < script_break_points.length; i++) {
|
||||
var break_point = script_break_points[i];
|
||||
|
||||
break_point.clear();
|
||||
|
||||
// TODO(LiveEdit): be careful with resource offset here.
|
||||
var break_point_position = Debug.findScriptSourcePosition(original_script,
|
||||
break_point.line(), break_point.column());
|
||||
|
||||
var old_position_description = {
|
||||
position: break_point_position,
|
||||
line: break_point.line(),
|
||||
column: break_point.column()
|
||||
}
|
||||
break_point_old_positions.push(old_position_description);
|
||||
}
|
||||
|
||||
|
||||
// Restores breakpoints and creates their copies in the "old" copy of
|
||||
// the script.
|
||||
return function (pos_translator, old_script_copy_opt) {
|
||||
// Update breakpoints (change positions and restore them in old version
|
||||
// of script.
|
||||
for (var i = 0; i < script_break_points.length; i++) {
|
||||
var break_point = script_break_points[i];
|
||||
if (old_script_copy_opt) {
|
||||
var clone = break_point.cloneForOtherScript(old_script_copy_opt);
|
||||
clone.set(old_script_copy_opt);
|
||||
|
||||
break_points_update_report.push( {
|
||||
type: "copied_to_old",
|
||||
id: break_point.number(),
|
||||
new_id: clone.number(),
|
||||
positions: break_point_old_positions[i]
|
||||
} );
|
||||
}
|
||||
|
||||
var updated_position = pos_translator.Translate(
|
||||
break_point_old_positions[i].position,
|
||||
PosTranslator.ShiftWithTopInsideChunkHandler);
|
||||
|
||||
var new_location =
|
||||
original_script.locationFromPosition(updated_position, false);
|
||||
|
||||
break_point.update_positions(new_location.line, new_location.column);
|
||||
|
||||
var new_position_description = {
|
||||
position: updated_position,
|
||||
line: new_location.line,
|
||||
column: new_location.column
|
||||
}
|
||||
|
||||
break_point.set(original_script);
|
||||
|
||||
break_points_update_report.push( { type: "position_changed",
|
||||
id: break_point.number(),
|
||||
old_positions: break_point_old_positions[i],
|
||||
new_positions: new_position_description
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Assert(condition, message) {
|
||||
if (!condition) {
|
||||
@ -346,15 +467,15 @@ Debug.LiveEdit = new function() {
|
||||
|
||||
function PosTranslator(diff_array) {
|
||||
var chunks = new Array();
|
||||
var pos1 = 0;
|
||||
var pos2 = 0;
|
||||
var current_diff = 0;
|
||||
for (var i = 0; i < diff_array.length; i += 3) {
|
||||
pos2 += diff_array[i] - pos1 + pos2;
|
||||
pos1 = diff_array[i];
|
||||
chunks.push(new DiffChunk(pos1, pos2, diff_array[i + 1] - pos1,
|
||||
diff_array[i + 2] - pos2));
|
||||
pos1 = diff_array[i + 1];
|
||||
pos2 = diff_array[i + 2];
|
||||
var pos1_begin = diff_array[i];
|
||||
var pos2_begin = pos1_begin + current_diff;
|
||||
var pos1_end = diff_array[i + 1];
|
||||
var pos2_end = diff_array[i + 2];
|
||||
chunks.push(new DiffChunk(pos1_begin, pos2_begin, pos1_end - pos1_begin,
|
||||
pos2_end - pos2_begin));
|
||||
current_diff = pos2_end - pos1_end;
|
||||
}
|
||||
this.chunks = chunks;
|
||||
}
|
||||
@ -364,14 +485,14 @@ Debug.LiveEdit = new function() {
|
||||
|
||||
PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
|
||||
var array = this.chunks;
|
||||
if (array.length == 0 || pos < array[0]) {
|
||||
if (array.length == 0 || pos < array[0].pos1) {
|
||||
return pos;
|
||||
}
|
||||
var chunk_index1 = 0;
|
||||
var chunk_index2 = array.length - 1;
|
||||
|
||||
while (chunk_index1 < chunk_index2) {
|
||||
var middle_index = (chunk_index1 + chunk_index2) / 2;
|
||||
var middle_index = Math.floor((chunk_index1 + chunk_index2) / 2);
|
||||
if (pos < array[middle_index + 1].pos1) {
|
||||
chunk_index2 = middle_index;
|
||||
} else {
|
||||
@ -380,17 +501,24 @@ Debug.LiveEdit = new function() {
|
||||
}
|
||||
var chunk = array[chunk_index1];
|
||||
if (pos >= chunk.pos1 + chunk.len1) {
|
||||
return pos += chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
|
||||
return pos + chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
|
||||
}
|
||||
|
||||
if (!inside_chunk_handler) {
|
||||
inside_chunk_handler = PosTranslator.default_inside_chunk_handler;
|
||||
inside_chunk_handler = PosTranslator.DefaultInsideChunkHandler;
|
||||
}
|
||||
inside_chunk_handler(pos, chunk);
|
||||
return inside_chunk_handler(pos, chunk);
|
||||
}
|
||||
|
||||
PosTranslator.default_inside_chunk_handler = function() {
|
||||
Assert(false, "Cannot translate position in chaged area");
|
||||
PosTranslator.DefaultInsideChunkHandler = function(pos, diff_chunk) {
|
||||
Assert(false, "Cannot translate position in changed area");
|
||||
}
|
||||
|
||||
PosTranslator.ShiftWithTopInsideChunkHandler =
|
||||
function(pos, diff_chunk) {
|
||||
// We carelessly do not check whether we stay inside the chunk after
|
||||
// translation.
|
||||
return pos - diff_chunk.pos1 + diff_chunk.pos2;
|
||||
}
|
||||
|
||||
var FunctionStatus = {
|
||||
@ -412,15 +540,16 @@ Debug.LiveEdit = new function() {
|
||||
this.children = children;
|
||||
// an index in array of compile_info
|
||||
this.array_index = array_index;
|
||||
this.parent = void(0);
|
||||
this.parent = void 0;
|
||||
|
||||
this.status = FunctionStatus.UNCHANGED;
|
||||
// Status explanation is used for debugging purposes and will be shown
|
||||
// in user UI if some explanations are needed.
|
||||
this.status_explanation = void(0);
|
||||
this.new_start_pos = void(0);
|
||||
this.new_end_pos = void(0);
|
||||
this.corresponding_node = void(0);
|
||||
this.status_explanation = void 0;
|
||||
this.new_start_pos = void 0;
|
||||
this.new_end_pos = void 0;
|
||||
this.corresponding_node = void 0;
|
||||
this.unmatched_new_nodes = void 0;
|
||||
}
|
||||
|
||||
// From array of function infos that is implicitly a tree creates
|
||||
@ -564,6 +693,8 @@ Debug.LiveEdit = new function() {
|
||||
function ProcessChildren(old_node, new_node) {
|
||||
var old_children = old_node.children;
|
||||
var new_children = new_node.children;
|
||||
|
||||
var unmatched_new_nodes_list = [];
|
||||
|
||||
var old_index = 0;
|
||||
var new_index = 0;
|
||||
@ -573,6 +704,7 @@ Debug.LiveEdit = new function() {
|
||||
} else if (new_index < new_children.length) {
|
||||
if (new_children[new_index].info.start_position <
|
||||
old_children[old_index].new_start_pos) {
|
||||
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) {
|
||||
@ -584,6 +716,9 @@ Debug.LiveEdit = new function() {
|
||||
ProcessChildren(old_children[old_index],
|
||||
new_children[new_index]);
|
||||
if (old_children[old_index].status == FunctionStatus.DAMAGED) {
|
||||
unmatched_new_nodes_list.push(
|
||||
old_children[old_index].corresponding_node);
|
||||
old_children[old_index].corresponding_node = void 0;
|
||||
old_node.status = FunctionStatus.CHANGED;
|
||||
}
|
||||
}
|
||||
@ -592,6 +727,7 @@ Debug.LiveEdit = new function() {
|
||||
old_children[old_index].status_explanation =
|
||||
"No corresponding function in new script found";
|
||||
old_node.status = FunctionStatus.CHANGED;
|
||||
unmatched_new_nodes_list.push(new_children[new_index]);
|
||||
}
|
||||
new_index++;
|
||||
old_index++;
|
||||
@ -611,12 +747,18 @@ Debug.LiveEdit = new function() {
|
||||
}
|
||||
}
|
||||
|
||||
while (new_index < new_children.length) {
|
||||
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)) {
|
||||
old_node.status = FunctionStatus.DAMAGED;
|
||||
old_node.status_explanation = "Changed code expectations";
|
||||
}
|
||||
}
|
||||
old_node.unmatched_new_nodes = unmatched_new_nodes_list;
|
||||
}
|
||||
|
||||
ProcessChildren(old_code_tree, new_code_tree);
|
||||
@ -637,6 +779,7 @@ Debug.LiveEdit = new function() {
|
||||
this.code = raw_array[4];
|
||||
this.scope_info = raw_array[5];
|
||||
this.outer_index = raw_array[6];
|
||||
this.shared_function_info = raw_array[7];
|
||||
this.next_sibling_index = null;
|
||||
this.raw_array = raw_array;
|
||||
}
|
||||
@ -776,71 +919,10 @@ Debug.LiveEdit = new function() {
|
||||
function CompareStringsLinewise(s1, s2) {
|
||||
return %LiveEditCompareStringsLinewise(s1, s2);
|
||||
}
|
||||
// Function is public (for tests).
|
||||
this.CompareStringsLinewise = CompareStringsLinewise;
|
||||
|
||||
|
||||
// Finds a difference between 2 strings in form of a single chunk.
|
||||
// This is a temporary solution. We should calculate a read diff instead.
|
||||
function FindSimpleDiff(old_source, new_source) {
|
||||
var change_pos;
|
||||
var old_len;
|
||||
var new_len;
|
||||
|
||||
// A find range block. Whenever control leaves it, it should set 3 local
|
||||
// variables declared above.
|
||||
find_range:
|
||||
{
|
||||
// First look from the beginning of strings.
|
||||
var pos1;
|
||||
{
|
||||
var next_pos;
|
||||
for (pos1 = 0; true; pos1 = next_pos) {
|
||||
if (pos1 >= old_source.length) {
|
||||
change_pos = pos1;
|
||||
old_len = 0;
|
||||
new_len = new_source.length - pos1;
|
||||
break find_range;
|
||||
}
|
||||
if (pos1 >= new_source.length) {
|
||||
change_pos = pos1;
|
||||
old_len = old_source.length - pos1;
|
||||
new_len = 0;
|
||||
break find_range;
|
||||
}
|
||||
if (old_source[pos1] != new_source[pos1]) {
|
||||
break;
|
||||
}
|
||||
next_pos = pos1 + 1;
|
||||
}
|
||||
}
|
||||
// Now compare strings from the ends.
|
||||
change_pos = pos1;
|
||||
var pos_old;
|
||||
var pos_new;
|
||||
{
|
||||
for (pos_old = old_source.length - 1, pos_new = new_source.length - 1;
|
||||
true;
|
||||
pos_old--, pos_new--) {
|
||||
if (pos_old - change_pos + 1 < 0 || pos_new - change_pos + 1 < 0) {
|
||||
old_len = pos_old - change_pos + 2;
|
||||
new_len = pos_new - change_pos + 2;
|
||||
break find_range;
|
||||
}
|
||||
if (old_source[pos_old] != new_source[pos_new]) {
|
||||
old_len = pos_old - change_pos + 1;
|
||||
new_len = pos_new - change_pos + 1;
|
||||
break find_range;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (old_len == 0 && new_len == 0) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
return { "change_pos": change_pos, "old_len": old_len, "new_len": new_len };
|
||||
// Functions are public for tests.
|
||||
this.TestApi = {
|
||||
PosTranslator: PosTranslator,
|
||||
CompareStringsLinewise: CompareStringsLinewise
|
||||
}
|
||||
}
|
||||
|
173
src/liveedit.cc
173
src/liveedit.cc
@ -417,6 +417,8 @@ static void CompileScriptForTracker(Handle<Script> script) {
|
||||
|
||||
// Compile the code.
|
||||
CompilationInfo info(lit, script, is_eval);
|
||||
|
||||
LiveEditFunctionTracker tracker(lit);
|
||||
Handle<Code> code = MakeCodeForLiveEdit(&info);
|
||||
|
||||
// Check for stack-overflow exceptions.
|
||||
@ -424,6 +426,7 @@ static void CompileScriptForTracker(Handle<Script> script) {
|
||||
Top::StackOverflow();
|
||||
return;
|
||||
}
|
||||
tracker.RecordRootFunctionInfo(code);
|
||||
}
|
||||
|
||||
// Unwraps JSValue object, returning its field "value"
|
||||
@ -501,9 +504,13 @@ class FunctionInfoWrapper : public JSArrayBasedStruct<FunctionInfoWrapper> {
|
||||
Handle<JSValue> wrapper = WrapInJSValue(*function_code);
|
||||
this->SetField(kCodeOffset_, wrapper);
|
||||
}
|
||||
void SetScopeInfo(Handle<JSArray> scope_info_array) {
|
||||
void SetScopeInfo(Handle<Object> scope_info_array) {
|
||||
this->SetField(kScopeInfoOffset_, scope_info_array);
|
||||
}
|
||||
void SetSharedFunctionInfo(Handle<SharedFunctionInfo> info) {
|
||||
Handle<JSValue> info_holder = WrapInJSValue(*info);
|
||||
this->SetField(kSharedFunctionInfoOffset_, info_holder);
|
||||
}
|
||||
int GetParentIndex() {
|
||||
return this->GetSmiValueField(kParentIndexOffset_);
|
||||
}
|
||||
@ -527,7 +534,8 @@ class FunctionInfoWrapper : public JSArrayBasedStruct<FunctionInfoWrapper> {
|
||||
static const int kCodeOffset_ = 4;
|
||||
static const int kScopeInfoOffset_ = 5;
|
||||
static const int kParentIndexOffset_ = 6;
|
||||
static const int kSize_ = 7;
|
||||
static const int kSharedFunctionInfoOffset_ = 7;
|
||||
static const int kSize_ = 8;
|
||||
|
||||
friend class JSArrayBasedStruct<FunctionInfoWrapper>;
|
||||
};
|
||||
@ -593,7 +601,11 @@ class FunctionInfoListener {
|
||||
current_parent_index_ = info.GetParentIndex();
|
||||
}
|
||||
|
||||
void FunctionScope(Scope* scope) {
|
||||
// TODO(LiveEdit): Move private method below.
|
||||
// This private section was created here to avoid moving the function
|
||||
// to keep already complex diff simpler.
|
||||
private:
|
||||
Object* SerializeFunctionScope(Scope* scope) {
|
||||
HandleScope handle_scope;
|
||||
|
||||
Handle<JSArray> scope_info_list = Factory::NewJSArray(10);
|
||||
@ -604,7 +616,7 @@ class FunctionInfoListener {
|
||||
// scopes of this chain.
|
||||
Scope* outer_scope = scope->outer_scope();
|
||||
if (outer_scope == NULL) {
|
||||
return;
|
||||
return Heap::undefined_value();
|
||||
}
|
||||
do {
|
||||
ZoneList<Variable*> list(10);
|
||||
@ -645,17 +657,33 @@ class FunctionInfoListener {
|
||||
outer_scope = outer_scope->outer_scope();
|
||||
} while (outer_scope != NULL);
|
||||
|
||||
FunctionInfoWrapper info =
|
||||
FunctionInfoWrapper::cast(result_->GetElement(current_parent_index_));
|
||||
info.SetScopeInfo(scope_info_list);
|
||||
return *scope_info_list;
|
||||
}
|
||||
|
||||
public:
|
||||
// Saves only function code, because for a script function we
|
||||
// may never create a SharedFunctionInfo object.
|
||||
void FunctionCode(Handle<Code> function_code) {
|
||||
FunctionInfoWrapper info =
|
||||
FunctionInfoWrapper::cast(result_->GetElement(current_parent_index_));
|
||||
info.SetFunctionCode(function_code);
|
||||
}
|
||||
|
||||
// Saves full information about a function: its code, its scope info
|
||||
// and a SharedFunctionInfo object.
|
||||
void FunctionInfo(Handle<SharedFunctionInfo> shared, Scope* scope) {
|
||||
if (!shared->IsSharedFunctionInfo()) {
|
||||
return;
|
||||
}
|
||||
FunctionInfoWrapper info =
|
||||
FunctionInfoWrapper::cast(result_->GetElement(current_parent_index_));
|
||||
info.SetFunctionCode(Handle<Code>(shared->code()));
|
||||
info.SetSharedFunctionInfo(shared);
|
||||
|
||||
Handle<Object> scope_info_list(SerializeFunctionScope(scope));
|
||||
info.SetScopeInfo(scope_info_list);
|
||||
}
|
||||
|
||||
Handle<JSArray> GetResult() {
|
||||
return result_;
|
||||
}
|
||||
@ -815,7 +843,6 @@ void LiveEdit::ReplaceFunctionCode(Handle<JSArray> new_compile_info_array,
|
||||
|
||||
Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo();
|
||||
|
||||
|
||||
if (IsJSFunctionCode(shared_info->code())) {
|
||||
ReplaceCodeObject(shared_info->code(),
|
||||
*(compile_info_wrapper.GetFunctionCode()));
|
||||
@ -839,11 +866,10 @@ void LiveEdit::ReplaceFunctionCode(Handle<JSArray> new_compile_info_array,
|
||||
|
||||
// TODO(635): Eval caches its scripts (same text -- same compiled info).
|
||||
// Make sure we clear such caches.
|
||||
void LiveEdit::RelinkFunctionToScript(Handle<JSArray> shared_info_array,
|
||||
Handle<Script> script_handle) {
|
||||
SharedInfoWrapper shared_info_wrapper(shared_info_array);
|
||||
Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo();
|
||||
|
||||
void LiveEdit::SetFunctionScript(Handle<JSValue> function_wrapper,
|
||||
Handle<Object> script_handle) {
|
||||
Handle<SharedFunctionInfo> shared_info =
|
||||
Handle<SharedFunctionInfo>::cast(UnwrapJSValue(function_wrapper));
|
||||
shared_info->set_script(*script_handle);
|
||||
}
|
||||
|
||||
@ -998,20 +1024,7 @@ static Handle<Code> PatchPositionsInCode(Handle<Code> code,
|
||||
}
|
||||
|
||||
|
||||
static Handle<Object> GetBreakPointObjectsForJS(
|
||||
Handle<BreakPointInfo> break_point_info) {
|
||||
if (break_point_info->break_point_objects()->IsFixedArray()) {
|
||||
Handle<FixedArray> fixed_array(
|
||||
FixedArray::cast(break_point_info->break_point_objects()));
|
||||
Handle<Object> array = Factory::NewJSArrayWithElements(fixed_array);
|
||||
return array;
|
||||
} else {
|
||||
return Handle<Object>(break_point_info->break_point_objects());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Handle<JSArray> LiveEdit::PatchFunctionPositions(
|
||||
void LiveEdit::PatchFunctionPositions(
|
||||
Handle<JSArray> shared_info_array, Handle<JSArray> position_change_array) {
|
||||
SharedInfoWrapper shared_info_wrapper(shared_info_array);
|
||||
Handle<SharedFunctionInfo> info = shared_info_wrapper.GetInfo();
|
||||
@ -1040,45 +1053,71 @@ Handle<JSArray> LiveEdit::PatchFunctionPositions(
|
||||
ReplaceCodeObject(info->code(), *patched_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Handle<JSArray> result = Factory::NewJSArray(0);
|
||||
int result_len = 0;
|
||||
static Handle<Script> CreateScriptCopy(Handle<Script> original) {
|
||||
Handle<String> original_source(String::cast(original->source()));
|
||||
|
||||
if (info->debug_info()->IsDebugInfo()) {
|
||||
Handle<DebugInfo> debug_info(DebugInfo::cast(info->debug_info()));
|
||||
Handle<Code> patched_orig_code =
|
||||
PatchPositionsInCode(Handle<Code>(debug_info->original_code()),
|
||||
position_change_array);
|
||||
if (*patched_orig_code != debug_info->original_code()) {
|
||||
// Do not use expensive ReplaceCodeObject for original_code, because we
|
||||
// do not expect any other references except this one.
|
||||
debug_info->set_original_code(*patched_orig_code);
|
||||
}
|
||||
Handle<Script> copy = Factory::NewScript(original_source);
|
||||
|
||||
Handle<FixedArray> break_point_infos(debug_info->break_points());
|
||||
for (int i = 0; i < break_point_infos->length(); i++) {
|
||||
if (!break_point_infos->get(i)->IsBreakPointInfo()) {
|
||||
continue;
|
||||
}
|
||||
Handle<BreakPointInfo> info(
|
||||
BreakPointInfo::cast(break_point_infos->get(i)));
|
||||
int old_in_script_position = info->source_position()->value() +
|
||||
old_function_start;
|
||||
int new_in_script_position = TranslatePosition(old_in_script_position,
|
||||
position_change_array);
|
||||
info->set_source_position(
|
||||
Smi::FromInt(new_in_script_position - new_function_start));
|
||||
if (old_in_script_position != new_in_script_position) {
|
||||
SetElement(result, result_len,
|
||||
Handle<Smi>(Smi::FromInt(new_in_script_position)));
|
||||
SetElement(result, result_len + 1,
|
||||
GetBreakPointObjectsForJS(info));
|
||||
result_len += 2;
|
||||
copy->set_name(original->name());
|
||||
copy->set_line_offset(original->line_offset());
|
||||
copy->set_column_offset(original->column_offset());
|
||||
copy->set_data(original->data());
|
||||
copy->set_type(original->type());
|
||||
copy->set_context_data(original->context_data());
|
||||
copy->set_compilation_type(original->compilation_type());
|
||||
copy->set_eval_from_shared(original->eval_from_shared());
|
||||
copy->set_eval_from_instructions_offset(
|
||||
original->eval_from_instructions_offset());
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
Object* LiveEdit::ChangeScriptSource(Handle<Script> original_script,
|
||||
Handle<String> new_source,
|
||||
Handle<Object> old_script_name) {
|
||||
Handle<Object> old_script_object;
|
||||
if (old_script_name->IsString()) {
|
||||
Handle<Script> old_script = CreateScriptCopy(original_script);
|
||||
old_script->set_name(String::cast(*old_script_name));
|
||||
old_script_object = old_script;
|
||||
Debugger::OnAfterCompile(old_script, Debugger::SEND_WHEN_DEBUGGING);
|
||||
} else {
|
||||
old_script_object = Handle<Object>(Heap::null_value());
|
||||
}
|
||||
|
||||
original_script->set_source(*new_source);
|
||||
|
||||
// Drop line ends so that they will be recalculated.
|
||||
original_script->set_line_ends(Heap::undefined_value());
|
||||
|
||||
return *old_script_object;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LiveEdit::ReplaceRefToNestedFunction(
|
||||
Handle<JSValue> parent_function_wrapper,
|
||||
Handle<JSValue> orig_function_wrapper,
|
||||
Handle<JSValue> subst_function_wrapper) {
|
||||
|
||||
Handle<SharedFunctionInfo> parent_shared =
|
||||
Handle<SharedFunctionInfo>::cast(UnwrapJSValue(parent_function_wrapper));
|
||||
Handle<SharedFunctionInfo> orig_shared =
|
||||
Handle<SharedFunctionInfo>::cast(UnwrapJSValue(orig_function_wrapper));
|
||||
Handle<SharedFunctionInfo> subst_shared =
|
||||
Handle<SharedFunctionInfo>::cast(UnwrapJSValue(subst_function_wrapper));
|
||||
|
||||
for (RelocIterator it(parent_shared->code()); !it.done(); it.next()) {
|
||||
if (it.rinfo()->rmode() == RelocInfo::EMBEDDED_OBJECT) {
|
||||
if (it.rinfo()->target_object() == *orig_shared) {
|
||||
it.rinfo()->set_target_object(*subst_shared);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -1362,17 +1401,16 @@ LiveEditFunctionTracker::~LiveEditFunctionTracker() {
|
||||
}
|
||||
|
||||
|
||||
void LiveEditFunctionTracker::RecordFunctionCode(Handle<Code> code) {
|
||||
void LiveEditFunctionTracker::RecordFunctionInfo(
|
||||
Handle<SharedFunctionInfo> info, FunctionLiteral* lit) {
|
||||
if (active_function_info_listener != NULL) {
|
||||
active_function_info_listener->FunctionCode(code);
|
||||
active_function_info_listener->FunctionInfo(info, lit->scope());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LiveEditFunctionTracker::RecordFunctionScope(Scope* scope) {
|
||||
if (active_function_info_listener != NULL) {
|
||||
active_function_info_listener->FunctionScope(scope);
|
||||
}
|
||||
void LiveEditFunctionTracker::RecordRootFunctionInfo(Handle<Code> code) {
|
||||
active_function_info_listener->FunctionCode(code);
|
||||
}
|
||||
|
||||
|
||||
@ -1393,11 +1431,12 @@ LiveEditFunctionTracker::~LiveEditFunctionTracker() {
|
||||
}
|
||||
|
||||
|
||||
void LiveEditFunctionTracker::RecordFunctionCode(Handle<Code> code) {
|
||||
void LiveEditFunctionTracker::RecordFunctionInfo(
|
||||
Handle<SharedFunctionInfo> info, FunctionLiteral* lit) {
|
||||
}
|
||||
|
||||
|
||||
void LiveEditFunctionTracker::RecordFunctionScope(Scope* scope) {
|
||||
void LiveEditFunctionTracker::RecordRootFunctionInfo(Handle<Code> code) {
|
||||
}
|
||||
|
||||
|
||||
|
@ -67,8 +67,9 @@ class LiveEditFunctionTracker {
|
||||
public:
|
||||
explicit LiveEditFunctionTracker(FunctionLiteral* fun);
|
||||
~LiveEditFunctionTracker();
|
||||
void RecordFunctionCode(Handle<Code> code);
|
||||
void RecordFunctionScope(Scope* scope);
|
||||
void RecordFunctionInfo(Handle<SharedFunctionInfo> info,
|
||||
FunctionLiteral* lit);
|
||||
void RecordRootFunctionInfo(Handle<Code> code);
|
||||
|
||||
static bool IsActive();
|
||||
};
|
||||
@ -85,14 +86,26 @@ class LiveEdit : AllStatic {
|
||||
static void ReplaceFunctionCode(Handle<JSArray> new_compile_info_array,
|
||||
Handle<JSArray> shared_info_array);
|
||||
|
||||
static void RelinkFunctionToScript(Handle<JSArray> shared_info_array,
|
||||
Handle<Script> script_handle);
|
||||
// Updates script field in FunctionSharedInfo.
|
||||
static void SetFunctionScript(Handle<JSValue> function_wrapper,
|
||||
Handle<Object> script_handle);
|
||||
|
||||
// Returns an array of pairs (new source position, breakpoint_object/array)
|
||||
// so that JS side could update positions in breakpoint objects.
|
||||
static Handle<JSArray> PatchFunctionPositions(
|
||||
static void PatchFunctionPositions(
|
||||
Handle<JSArray> shared_info_array, Handle<JSArray> position_change_array);
|
||||
|
||||
// For a script updates its source field. If old_script_name is provided
|
||||
// (i.e. is a String), also creates a copy of the script with its original
|
||||
// source and sends notification to debugger.
|
||||
static Object* ChangeScriptSource(Handle<Script> original_script,
|
||||
Handle<String> new_source,
|
||||
Handle<Object> old_script_name);
|
||||
|
||||
// In a code of a parent function replaces original function as embedded
|
||||
// object with a substitution one.
|
||||
static void ReplaceRefToNestedFunction(Handle<JSValue> parent_function_shared,
|
||||
Handle<JSValue> orig_function_shared,
|
||||
Handle<JSValue> subst_function_shared);
|
||||
|
||||
// Checks listed functions on stack and return array with corresponding
|
||||
// FunctionPatchabilityStatus statuses; extra array element may
|
||||
// contain general error message. Modifies the current stack and
|
||||
|
@ -9674,38 +9674,30 @@ static Object* Runtime_LiveEditGatherCompileInfo(Arguments args) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Changes the source of the script to a new_source and creates a new
|
||||
// script representing the old version of the script source.
|
||||
// Changes the source of the script to a new_source.
|
||||
// If old_script_name is provided (i.e. is a String), also creates a copy of
|
||||
// the script with its original source and sends notification to debugger.
|
||||
static Object* Runtime_LiveEditReplaceScript(Arguments args) {
|
||||
ASSERT(args.length() == 3);
|
||||
HandleScope scope;
|
||||
CONVERT_CHECKED(JSValue, original_script_value, args[0]);
|
||||
CONVERT_ARG_CHECKED(String, new_source, 1);
|
||||
CONVERT_ARG_CHECKED(String, old_script_name, 2);
|
||||
Handle<Script> original_script =
|
||||
Handle<Script>(Script::cast(original_script_value->value()));
|
||||
Handle<Object> old_script_name(args[2]);
|
||||
|
||||
Handle<String> original_source(String::cast(original_script->source()));
|
||||
CONVERT_CHECKED(Script, original_script_pointer,
|
||||
original_script_value->value());
|
||||
Handle<Script> original_script(original_script_pointer);
|
||||
|
||||
original_script->set_source(*new_source);
|
||||
Handle<Script> old_script = Factory::NewScript(original_source);
|
||||
old_script->set_name(*old_script_name);
|
||||
old_script->set_line_offset(original_script->line_offset());
|
||||
old_script->set_column_offset(original_script->column_offset());
|
||||
old_script->set_data(original_script->data());
|
||||
old_script->set_type(original_script->type());
|
||||
old_script->set_context_data(original_script->context_data());
|
||||
old_script->set_compilation_type(original_script->compilation_type());
|
||||
old_script->set_eval_from_shared(original_script->eval_from_shared());
|
||||
old_script->set_eval_from_instructions_offset(
|
||||
original_script->eval_from_instructions_offset());
|
||||
Object* old_script = LiveEdit::ChangeScriptSource(original_script,
|
||||
new_source,
|
||||
old_script_name);
|
||||
|
||||
// Drop line ends so that they will be recalculated.
|
||||
original_script->set_line_ends(Heap::undefined_value());
|
||||
|
||||
Debugger::OnAfterCompile(old_script, Debugger::SEND_WHEN_DEBUGGING);
|
||||
|
||||
return *(GetScriptWrapper(old_script));
|
||||
if (old_script->IsScript()) {
|
||||
Handle<Script> script_handle(Script::cast(old_script));
|
||||
return *(GetScriptWrapper(script_handle));
|
||||
} else {
|
||||
return Heap::null_value();
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces code of SharedFunctionInfo with a new one.
|
||||
@ -9721,35 +9713,60 @@ static Object* Runtime_LiveEditReplaceFunctionCode(Arguments args) {
|
||||
}
|
||||
|
||||
// Connects SharedFunctionInfo to another script.
|
||||
static Object* Runtime_LiveEditRelinkFunctionToScript(Arguments args) {
|
||||
static Object* Runtime_LiveEditFunctionSetScript(Arguments args) {
|
||||
ASSERT(args.length() == 2);
|
||||
HandleScope scope;
|
||||
CONVERT_ARG_CHECKED(JSArray, shared_info_array, 0);
|
||||
CONVERT_ARG_CHECKED(JSValue, script_value, 1);
|
||||
Handle<Script> script = Handle<Script>(Script::cast(script_value->value()));
|
||||
Handle<Object> function_object(args[0]);
|
||||
Handle<Object> script_object(args[1]);
|
||||
|
||||
LiveEdit::RelinkFunctionToScript(shared_info_array, script);
|
||||
if (function_object->IsJSValue()) {
|
||||
Handle<JSValue> function_wrapper = Handle<JSValue>::cast(function_object);
|
||||
if (script_object->IsJSValue()) {
|
||||
CONVERT_CHECKED(Script, script, JSValue::cast(*script_object)->value());
|
||||
script_object = Handle<Object>(script);
|
||||
}
|
||||
|
||||
LiveEdit::SetFunctionScript(function_wrapper, script_object);
|
||||
} else {
|
||||
// Just ignore this. We may not have a SharedFunctionInfo for some functions
|
||||
// and we check it in this function.
|
||||
}
|
||||
|
||||
return Heap::undefined_value();
|
||||
}
|
||||
|
||||
|
||||
// In a code of a parent function replaces original function as embedded object
|
||||
// with a substitution one.
|
||||
static Object* Runtime_LiveEditReplaceRefToNestedFunction(Arguments args) {
|
||||
ASSERT(args.length() == 3);
|
||||
HandleScope scope;
|
||||
|
||||
CONVERT_ARG_CHECKED(JSValue, parent_wrapper, 0);
|
||||
CONVERT_ARG_CHECKED(JSValue, orig_wrapper, 1);
|
||||
CONVERT_ARG_CHECKED(JSValue, subst_wrapper, 2);
|
||||
|
||||
LiveEdit::ReplaceRefToNestedFunction(parent_wrapper, orig_wrapper,
|
||||
subst_wrapper);
|
||||
|
||||
return Heap::undefined_value();
|
||||
}
|
||||
|
||||
|
||||
// Updates positions of a shared function info (first parameter) according
|
||||
// to script source change. Text change is described in second parameter as
|
||||
// array of groups of 3 numbers:
|
||||
// (change_begin, change_end, change_end_new_position).
|
||||
// Each group describes a change in text; groups are sorted by change_begin.
|
||||
// Returns an array of pairs (new source position, breakpoint_object/array)
|
||||
// so that JS side could update positions in breakpoint objects.
|
||||
static Object* Runtime_LiveEditPatchFunctionPositions(Arguments args) {
|
||||
ASSERT(args.length() == 2);
|
||||
HandleScope scope;
|
||||
CONVERT_ARG_CHECKED(JSArray, shared_array, 0);
|
||||
CONVERT_ARG_CHECKED(JSArray, position_change_array, 1);
|
||||
|
||||
Handle<Object> result =
|
||||
LiveEdit::PatchFunctionPositions(shared_array, position_change_array);
|
||||
LiveEdit::PatchFunctionPositions(shared_array, position_change_array);
|
||||
|
||||
return *result;
|
||||
return Heap::undefined_value();
|
||||
}
|
||||
|
||||
|
||||
|
@ -337,7 +337,8 @@ namespace internal {
|
||||
F(LiveEditGatherCompileInfo, 2, 1) \
|
||||
F(LiveEditReplaceScript, 3, 1) \
|
||||
F(LiveEditReplaceFunctionCode, 2, 1) \
|
||||
F(LiveEditRelinkFunctionToScript, 2, 1) \
|
||||
F(LiveEditFunctionSetScript, 2, 1) \
|
||||
F(LiveEditReplaceRefToNestedFunction, 3, 1) \
|
||||
F(LiveEditPatchFunctionPositions, 2, 1) \
|
||||
F(LiveEditCheckAndDropActivations, 2, 1) \
|
||||
F(LiveEditCompareStringsLinewise, 2, 1) \
|
||||
|
69
test/mjsunit/debug-liveedit-3.js
Normal file
69
test/mjsunit/debug-liveedit-3.js
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
// In this test case we edit a script so that techincally function text
|
||||
// hasen't been changed. However actually function became one level more nested
|
||||
// and must be recompiled because it uses variable from outer scope.
|
||||
|
||||
|
||||
Debug = debug.Debug
|
||||
|
||||
var function_z_text =
|
||||
" function Z() {\n"
|
||||
+ " return 2 + p;\n"
|
||||
+ " }\n";
|
||||
|
||||
eval(
|
||||
"function Factory(p) {\n"
|
||||
+ "return (\n"
|
||||
+ function_z_text
|
||||
+ ");\n"
|
||||
+ "}\n"
|
||||
);
|
||||
|
||||
var z6 = Factory(6);
|
||||
assertEquals(8, z6());
|
||||
|
||||
var script = Debug.findScript(Factory);
|
||||
|
||||
var new_source = script.source.replace(function_z_text, "function Intermediate() {\nreturn (\n" + function_z_text + ")\n;\n}\n");
|
||||
print("new source: " + new_source);
|
||||
|
||||
var change_log = new Array();
|
||||
Debug.LiveEdit.SetScriptSource(script, new_source, change_log);
|
||||
print("Change log: " + JSON.stringify(change_log) + "\n");
|
||||
|
||||
assertEquals(8, z6());
|
||||
|
||||
var z100 = Factory(100)();
|
||||
|
||||
assertEquals(102, z100());
|
||||
|
||||
|
97
test/mjsunit/debug-liveedit-breakpoints.js
Normal file
97
test/mjsunit/debug-liveedit-breakpoints.js
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2010 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
|
||||
|
||||
var function_z_text =
|
||||
" function Z() {\n"
|
||||
+ " return 'Z';\n" // Breakpoint line ( #6 )
|
||||
+ " }\n";
|
||||
|
||||
eval(
|
||||
"function F25() {\n"
|
||||
+ " return 25;\n" // Breakpoint line ( #1 )
|
||||
+ "}\n"
|
||||
+ "function F26 () {\n"
|
||||
+ " var x = 20;\n"
|
||||
+ function_z_text // function takes exactly 3 lines
|
||||
// // Breakpoint line ( #6 )
|
||||
//
|
||||
+ " var y = 6;\n"
|
||||
+ " return x + y;\n"
|
||||
+ "}\n"
|
||||
+ "function Nested() {\n"
|
||||
+ " var a = 30;\n"
|
||||
+ " return function F27() {\n"
|
||||
+ " var b = 3;\n" // Breakpoint line ( #14 )
|
||||
+ " return a - b;\n"
|
||||
+ " }\n"
|
||||
+ "}\n"
|
||||
);
|
||||
|
||||
|
||||
assertEquals(25, F25());
|
||||
assertEquals(26, F26());
|
||||
|
||||
var script = Debug.findScript(F25);
|
||||
|
||||
Debug.setScriptBreakPoint(Debug.ScriptBreakPointType.ScriptId, script.id, 1, 1, "true || false || false");
|
||||
Debug.setScriptBreakPoint(Debug.ScriptBreakPointType.ScriptId, script.id, 6, 1, "true || false || false");
|
||||
Debug.setScriptBreakPoint(Debug.ScriptBreakPointType.ScriptId, script.id, 14, 1, "true || false || false");
|
||||
|
||||
assertEquals(3, Debug.scriptBreakPoints().length);
|
||||
|
||||
var new_source = script.source.replace(function_z_text, "");
|
||||
print("new source: " + new_source);
|
||||
|
||||
var change_log = new Array();
|
||||
Debug.LiveEdit.SetScriptSource(script, new_source, change_log);
|
||||
print("Change log: " + JSON.stringify(change_log) + "\n");
|
||||
|
||||
var breaks = Debug.scriptBreakPoints();
|
||||
|
||||
// One breakpoint gets duplicated in a old version of script.
|
||||
assertTrue(breaks.length > 3 + 1);
|
||||
|
||||
var breakpoints_in_script = 0;
|
||||
var break_position_map = {};
|
||||
for (var i = 0; i < breaks.length; i++) {
|
||||
if (breaks[i].script_id() == script.id) {
|
||||
break_position_map[breaks[i].line()] = true;
|
||||
breakpoints_in_script++;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(3, breakpoints_in_script);
|
||||
|
||||
// Check 2 breakpoints. The one in deleted function should have been moved somewhere.
|
||||
assertTrue(break_position_map[1]);
|
||||
assertTrue(break_position_map[11]);
|
||||
|
@ -31,7 +31,7 @@
|
||||
Debug = debug.Debug
|
||||
|
||||
function CheckCompareOneWay(s1, s2) {
|
||||
var diff_array = Debug.LiveEdit.CompareStringsLinewise(s1, s2);
|
||||
var diff_array = Debug.LiveEdit.TestApi.CompareStringsLinewise(s1, s2);
|
||||
|
||||
var pos1 = 0;
|
||||
var pos2 = 0;
|
||||
|
97
test/mjsunit/debug-liveedit-utils.js
Normal file
97
test/mjsunit/debug-liveedit-utils.js
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2010 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 Return2010() {
|
||||
return 2010;
|
||||
}
|
||||
|
||||
|
||||
// Diff it trivial: zero chunks
|
||||
var NoChunkTranslator = new Debug.LiveEdit.TestApi.PosTranslator([]);
|
||||
|
||||
assertEquals(0, NoChunkTranslator.Translate(0));
|
||||
assertEquals(10, NoChunkTranslator.Translate(10));
|
||||
|
||||
|
||||
// Diff has one chunk
|
||||
var SingleChunkTranslator = new Debug.LiveEdit.TestApi.PosTranslator([20, 30, 25]);
|
||||
|
||||
assertEquals(0, SingleChunkTranslator.Translate(0));
|
||||
assertEquals(5, SingleChunkTranslator.Translate(5));
|
||||
assertEquals(10, SingleChunkTranslator.Translate(10));
|
||||
assertEquals(19, SingleChunkTranslator.Translate(19));
|
||||
assertEquals(2010, SingleChunkTranslator.Translate(20, Return2010));
|
||||
assertEquals(25, SingleChunkTranslator.Translate(30));
|
||||
assertEquals(26, SingleChunkTranslator.Translate(31));
|
||||
assertEquals(2010, SingleChunkTranslator.Translate(26, Return2010));
|
||||
|
||||
try {
|
||||
SingleChunkTranslator.Translate(21);
|
||||
assertTrue(false);
|
||||
} catch (ignore) {
|
||||
}
|
||||
try {
|
||||
SingleChunkTranslator.Translate(24);
|
||||
assertTrue(false);
|
||||
} catch (ignore) {
|
||||
}
|
||||
|
||||
|
||||
// Diff has several chunk (3). See the table below.
|
||||
|
||||
/*
|
||||
chunks: (new <- old)
|
||||
10 10
|
||||
15 20
|
||||
|
||||
35 40
|
||||
50 40
|
||||
|
||||
70 60
|
||||
70 70
|
||||
*/
|
||||
|
||||
var MultiChunkTranslator = new Debug.LiveEdit.TestApi.PosTranslator([10, 20, 15, 40, 40, 50, 60, 70, 70 ]);
|
||||
assertEquals(5, MultiChunkTranslator.Translate(5));
|
||||
assertEquals(9, MultiChunkTranslator.Translate(9));
|
||||
assertEquals(2010, MultiChunkTranslator.Translate(10, Return2010));
|
||||
assertEquals(15, MultiChunkTranslator.Translate(20));
|
||||
assertEquals(20, MultiChunkTranslator.Translate(25));
|
||||
assertEquals(34, MultiChunkTranslator.Translate(39));
|
||||
assertEquals(50, MultiChunkTranslator.Translate(40, Return2010));
|
||||
assertEquals(55, MultiChunkTranslator.Translate(45));
|
||||
assertEquals(69, MultiChunkTranslator.Translate(59));
|
||||
assertEquals(2010, MultiChunkTranslator.Translate(60, Return2010));
|
||||
assertEquals(70, MultiChunkTranslator.Translate(70));
|
||||
assertEquals(75, MultiChunkTranslator.Translate(75));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user