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:
peter.rybin@gmail.com 2010-04-28 11:38:43 +00:00
parent ff0775c38f
commit 80453231fe
13 changed files with 672 additions and 232 deletions

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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.');

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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) {
}

View File

@ -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

View File

@ -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();
}

View File

@ -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) \

View 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());

View 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]);

View File

@ -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;

View 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));