blob: 451b146bde798e4b4c81b62c39d0adcb1b158630 [file] [log] [blame]
yangguo@chromium.org5a11aaf2012-06-20 11:29:00 +00001// Copyright 2012 the V8 project authors. All rights reserved.
ager@chromium.orgce5e87b2010-03-10 10:24:18 +00002// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28// LiveEdit feature implementation. The script should be executed after
kmillikin@chromium.org5d8f0e62010-03-24 08:21:20 +000029// debug-debugger.js.
ager@chromium.orgce5e87b2010-03-10 10:24:18 +000030
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000031// A LiveEdit namespace. It contains functions that modifies JavaScript code
32// according to changes of script source (if possible).
33//
34// When new script source is put in, the difference is calculated textually,
35// in form of list of delete/add/change chunks. The functions that include
36// change chunk(s) get recompiled, or their enclosing functions are
37// recompiled instead.
38// If the function may not be recompiled (e.g. it was completely erased in new
39// version of the script) it remains unchanged, but the code that could
40// create a new instance of this function goes away. An old version of script
41// is created to back up this obsolete function.
42// All unchanged functions have their positions updated accordingly.
43//
44// LiveEdit namespace is declared inside a single function constructor.
lrn@chromium.org25156de2010-04-06 13:10:27 +000045Debug.LiveEdit = new function() {
lrn@chromium.org25156de2010-04-06 13:10:27 +000046
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000047 // Forward declaration for minifier.
48 var FunctionStatus;
vegorov@chromium.org42841962010-10-18 11:18:59 +000049
yangguo@chromium.org5a11aaf2012-06-20 11:29:00 +000050 var NEEDS_STEP_IN_PROPERTY_NAME = "stack_update_needs_step_in";
51
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000052 // Applies the change to the script.
53 // The change is in form of list of chunks encoded in a single array as
54 // a series of triplets (pos1_start, pos1_end, pos2_end)
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +000055 function ApplyPatchMultiChunk(script, diff_array, new_source, preview_only,
56 change_log) {
ricow@chromium.orgc9c80822010-04-21 08:22:37 +000057
ricow@chromium.orgc9c80822010-04-21 08:22:37 +000058 var old_source = script.source;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000059
kmillikin@chromium.org4111b802010-05-03 10:34:42 +000060 // Gather compile information about old version of script.
61 var old_compile_info = GatherCompileInfo(old_source, script);
vegorov@chromium.org42841962010-10-18 11:18:59 +000062
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000063 // Build tree structures for old and new versions of the script.
64 var root_old_node = BuildCodeInfoTree(old_compile_info);
kmillikin@chromium.org4111b802010-05-03 10:34:42 +000065
66 var pos_translator = new PosTranslator(diff_array);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000067
68 // Analyze changes.
69 MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
kmillikin@chromium.org4111b802010-05-03 10:34:42 +000070
71 // Find all SharedFunctionInfo's that were compiled from this script.
72 FindLiveSharedInfos(root_old_node, script);
vegorov@chromium.org42841962010-10-18 11:18:59 +000073
kmillikin@chromium.org4111b802010-05-03 10:34:42 +000074 // Gather compile information about new version of script.
75 var new_compile_info;
76 try {
77 new_compile_info = GatherCompileInfo(new_source, script);
78 } catch (e) {
mmassi@chromium.org49a44672012-12-04 13:52:03 +000079 var failure =
80 new Failure("Failed to compile new version of script: " + e);
81 if (e instanceof SyntaxError) {
82 var details = {
83 type: "liveedit_compile_error",
84 syntaxErrorMessage: e.message
85 };
86 CopyErrorPositionToDetails(e, details);
87 failure.details = details;
88 }
89 throw failure;
kmillikin@chromium.org4111b802010-05-03 10:34:42 +000090 }
91 var root_new_node = BuildCodeInfoTree(new_compile_info);
92
93 // Link recompiled script data with other data.
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000094 FindCorrespondingFunctions(root_old_node, root_new_node);
vegorov@chromium.org42841962010-10-18 11:18:59 +000095
fschneider@chromium.org013f3e12010-04-26 13:27:52 +000096 // Prepare to-do lists.
97 var replace_code_list = new Array();
98 var link_to_old_script_list = new Array();
kmillikin@chromium.org4111b802010-05-03 10:34:42 +000099 var link_to_original_script_list = new Array();
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000100 var update_positions_list = new Array();
101
102 function HarvestTodo(old_node) {
103 function CollectDamaged(node) {
104 link_to_old_script_list.push(node);
105 for (var i = 0; i < node.children.length; i++) {
106 CollectDamaged(node.children[i]);
107 }
108 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000109
110 // Recursively collects all newly compiled functions that are going into
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000111 // business and should have link to the actual script updated.
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000112 function CollectNew(node_list) {
113 for (var i = 0; i < node_list.length; i++) {
114 link_to_original_script_list.push(node_list[i]);
115 CollectNew(node_list[i].children);
116 }
117 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000118
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000119 if (old_node.status == FunctionStatus.DAMAGED) {
120 CollectDamaged(old_node);
121 return;
122 }
123 if (old_node.status == FunctionStatus.UNCHANGED) {
124 update_positions_list.push(old_node);
125 } else if (old_node.status == FunctionStatus.SOURCE_CHANGED) {
126 update_positions_list.push(old_node);
127 } else if (old_node.status == FunctionStatus.CHANGED) {
128 replace_code_list.push(old_node);
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000129 CollectNew(old_node.unmatched_new_nodes);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000130 }
131 for (var i = 0; i < old_node.children.length; i++) {
132 HarvestTodo(old_node.children[i]);
133 }
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000134 }
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000135
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000136 var preview_description = {
137 change_tree: DescribeChangeTree(root_old_node),
138 textual_diff: {
139 old_len: old_source.length,
140 new_len: new_source.length,
141 chunks: diff_array
142 },
143 updated: false
144 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000145
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000146 if (preview_only) {
147 return preview_description;
148 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000149
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000150 HarvestTodo(root_old_node);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000151
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000152 // Collect shared infos for functions whose code need to be patched.
153 var replaced_function_infos = new Array();
154 for (var i = 0; i < replace_code_list.length; i++) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000155 var live_shared_function_infos =
156 replace_code_list[i].live_shared_function_infos;
157
158 if (live_shared_function_infos) {
fschneider@chromium.org9e3e0b62011-01-03 10:16:46 +0000159 for (var j = 0; j < live_shared_function_infos.length; j++) {
160 replaced_function_infos.push(live_shared_function_infos[j]);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000161 }
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000162 }
163 }
164
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000165 // We haven't changed anything before this line yet.
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000166 // Committing all changes.
vegorov@chromium.org42841962010-10-18 11:18:59 +0000167
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000168 // Check that function being patched is not currently on stack or drop them.
169 var dropped_functions_number =
170 CheckStackActivations(replaced_function_infos, change_log);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000171
172 preview_description.stack_modified = dropped_functions_number != 0;
173
yangguo@chromium.orgefdb9d72012-04-26 08:21:05 +0000174 // Our current implementation requires client to manually issue "step in"
175 // command for correct stack state.
yangguo@chromium.org5a11aaf2012-06-20 11:29:00 +0000176 preview_description[NEEDS_STEP_IN_PROPERTY_NAME] =
yangguo@chromium.orgefdb9d72012-04-26 08:21:05 +0000177 preview_description.stack_modified;
178
vegorov@chromium.org42841962010-10-18 11:18:59 +0000179 // Start with breakpoints. Convert their line/column positions and
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000180 // temporary remove.
181 var break_points_restorer = TemporaryRemoveBreakPoints(script, change_log);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000182
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000183 var old_script;
184
185 // Create an old script only if there are function that should be linked
186 // to old version.
187 if (link_to_old_script_list.length == 0) {
188 %LiveEditReplaceScript(script, new_source, null);
189 old_script = void 0;
190 } else {
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000191 var old_script_name = CreateNameForOldScript(script);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000192
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000193 // Update the script text and create a new script representing an old
194 // version of the script.
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000195 old_script = %LiveEditReplaceScript(script, new_source,
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000196 old_script_name);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000197
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000198 var link_to_old_script_report = new Array();
199 change_log.push( { linked_to_old_script: link_to_old_script_report } );
vegorov@chromium.org42841962010-10-18 11:18:59 +0000200
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000201 // We need to link to old script all former nested functions.
202 for (var i = 0; i < link_to_old_script_list.length; i++) {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000203 LinkToOldScript(link_to_old_script_list[i], old_script,
204 link_to_old_script_report);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000205 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000206
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000207 preview_description.created_script_name = old_script_name;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000208 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000209
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000210 // Link to an actual script all the functions that we are going to use.
211 for (var i = 0; i < link_to_original_script_list.length; i++) {
212 %LiveEditFunctionSetScript(
213 link_to_original_script_list[i].info.shared_function_info, script);
214 }
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000215
216 for (var i = 0; i < replace_code_list.length; i++) {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000217 PatchFunctionCode(replace_code_list[i], change_log);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000218 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000219
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000220 var position_patch_report = new Array();
221 change_log.push( {position_patched: position_patch_report} );
vegorov@chromium.org42841962010-10-18 11:18:59 +0000222
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000223 for (var i = 0; i < update_positions_list.length; i++) {
224 // TODO(LiveEdit): take into account wether it's source_changed or
225 // unchanged and whether positions changed at all.
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000226 PatchPositions(update_positions_list[i], diff_array,
227 position_patch_report);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000228
229 if (update_positions_list[i].live_shared_function_infos) {
230 update_positions_list[i].live_shared_function_infos.
231 forEach(function (info) {
232 %LiveEditFunctionSourceUpdated(info.raw_array);
233 });
234 }
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000235 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000236
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000237 break_points_restorer(pos_translator, old_script);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000238
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000239 preview_description.updated = true;
240 return preview_description;
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000241 }
242 // Function is public.
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000243 this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
244
vegorov@chromium.org42841962010-10-18 11:18:59 +0000245
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000246 // Fully compiles source string as a script. Returns Array of
247 // FunctionCompileInfo -- a descriptions of all functions of the script.
248 // Elements of array are ordered by start positions of functions (from top
249 // to bottom) in the source. Fields outer_index and next_sibling_index help
250 // to navigate the nesting structure of functions.
251 //
252 // All functions get compiled linked to script provided as parameter script.
253 // TODO(LiveEdit): consider not using actual scripts as script, because
vegorov@chromium.org42841962010-10-18 11:18:59 +0000254 // we have to manually erase all links right after compile.
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000255 function GatherCompileInfo(source, script) {
256 // Get function info, elements are partially sorted (it is a tree of
257 // nested functions serialized as parent followed by serialized children.
258 var raw_compile_info = %LiveEditGatherCompileInfo(script, source);
259
260 // Sort function infos by start position field.
261 var compile_info = new Array();
262 var old_index_map = new Array();
263 for (var i = 0; i < raw_compile_info.length; i++) {
264 var info = new FunctionCompileInfo(raw_compile_info[i]);
265 // Remove all links to the actual script. Breakpoints system and
266 // LiveEdit itself believe that any function in heap that points to a
267 // particular script is a regular function.
268 // For some functions we will restore this link later.
269 %LiveEditFunctionSetScript(info.shared_function_info, void 0);
270 compile_info.push(info);
271 old_index_map.push(i);
272 }
273
274 for (var i = 0; i < compile_info.length; i++) {
275 var k = i;
276 for (var j = i + 1; j < compile_info.length; j++) {
277 if (compile_info[k].start_position > compile_info[j].start_position) {
278 k = j;
279 }
280 }
281 if (k != i) {
282 var temp_info = compile_info[k];
283 var temp_index = old_index_map[k];
284 compile_info[k] = compile_info[i];
285 old_index_map[k] = old_index_map[i];
286 compile_info[i] = temp_info;
287 old_index_map[i] = temp_index;
288 }
289 }
290
291 // After sorting update outer_inder field using old_index_map. Also
292 // set next_sibling_index field.
293 var current_index = 0;
294
295 // The recursive function, that goes over all children of a particular
296 // node (i.e. function info).
297 function ResetIndexes(new_parent_index, old_parent_index) {
298 var previous_sibling = -1;
299 while (current_index < compile_info.length &&
300 compile_info[current_index].outer_index == old_parent_index) {
301 var saved_index = current_index;
302 compile_info[saved_index].outer_index = new_parent_index;
303 if (previous_sibling != -1) {
304 compile_info[previous_sibling].next_sibling_index = saved_index;
305 }
306 previous_sibling = saved_index;
307 current_index++;
308 ResetIndexes(saved_index, old_index_map[saved_index]);
309 }
310 if (previous_sibling != -1) {
311 compile_info[previous_sibling].next_sibling_index = -1;
312 }
313 }
314
315 ResetIndexes(-1, -1);
316 Assert(current_index == compile_info.length);
317
318 return compile_info;
319 }
320
vegorov@chromium.org42841962010-10-18 11:18:59 +0000321
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000322 // Replaces function's Code.
323 function PatchFunctionCode(old_node, change_log) {
324 var new_info = old_node.corresponding_node.info;
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000325 if (old_node.live_shared_function_infos) {
326 old_node.live_shared_function_infos.forEach(function (old_info) {
327 %LiveEditReplaceFunctionCode(new_info.raw_array,
328 old_info.raw_array);
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000329
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000330 // The function got a new code. However, this new code brings all new
331 // instances of SharedFunctionInfo for nested functions. However,
332 // we want the original instances to be used wherever possible.
333 // (This is because old instances and new instances will be both
334 // linked to a script and breakpoints subsystem does not really
335 // expects this; neither does LiveEdit subsystem on next call).
336 for (var i = 0; i < old_node.children.length; i++) {
337 if (old_node.children[i].corresponding_node) {
338 var corresponding_child_info =
339 old_node.children[i].corresponding_node.info.
340 shared_function_info;
341
342 if (old_node.children[i].live_shared_function_infos) {
343 old_node.children[i].live_shared_function_infos.
344 forEach(function (old_child_info) {
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000345 %LiveEditReplaceRefToNestedFunction(
346 old_info.info,
347 corresponding_child_info,
348 old_child_info.info);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000349 });
350 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000351 }
352 }
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000353 });
vegorov@chromium.org42841962010-10-18 11:18:59 +0000354
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000355 change_log.push( {function_patched: new_info.function_name} );
356 } else {
357 change_log.push( {function_patched: new_info.function_name,
358 function_info_not_found: true} );
359 }
360 }
361
vegorov@chromium.org42841962010-10-18 11:18:59 +0000362
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000363 // Makes a function associated with another instance of a script (the
364 // one representing its old version). This way the function still
365 // may access its own text.
366 function LinkToOldScript(old_info_node, old_script, report_array) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000367 if (old_info_node.live_shared_function_infos) {
368 old_info_node.live_shared_function_infos.
369 forEach(function (info) {
370 %LiveEditFunctionSetScript(info.info, old_script);
371 });
372
373 report_array.push( { name: old_info_node.info.function_name } );
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000374 } else {
375 report_array.push(
376 { name: old_info_node.info.function_name, not_found: true } );
377 }
378 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000379
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000380
381 // Returns function that restores breakpoints.
382 function TemporaryRemoveBreakPoints(original_script, change_log) {
383 var script_break_points = GetScriptBreakPoints(original_script);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000384
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000385 var break_points_update_report = [];
386 change_log.push( { break_points_update: break_points_update_report } );
387
388 var break_point_old_positions = [];
389 for (var i = 0; i < script_break_points.length; i++) {
390 var break_point = script_break_points[i];
391
392 break_point.clear();
vegorov@chromium.org42841962010-10-18 11:18:59 +0000393
394 // TODO(LiveEdit): be careful with resource offset here.
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000395 var break_point_position = Debug.findScriptSourcePosition(original_script,
396 break_point.line(), break_point.column());
vegorov@chromium.org42841962010-10-18 11:18:59 +0000397
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000398 var old_position_description = {
399 position: break_point_position,
400 line: break_point.line(),
401 column: break_point.column()
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000402 };
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000403 break_point_old_positions.push(old_position_description);
404 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000405
406
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000407 // Restores breakpoints and creates their copies in the "old" copy of
408 // the script.
409 return function (pos_translator, old_script_copy_opt) {
410 // Update breakpoints (change positions and restore them in old version
411 // of script.
412 for (var i = 0; i < script_break_points.length; i++) {
413 var break_point = script_break_points[i];
414 if (old_script_copy_opt) {
415 var clone = break_point.cloneForOtherScript(old_script_copy_opt);
416 clone.set(old_script_copy_opt);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000417
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000418 break_points_update_report.push( {
419 type: "copied_to_old",
420 id: break_point.number(),
vegorov@chromium.org42841962010-10-18 11:18:59 +0000421 new_id: clone.number(),
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000422 positions: break_point_old_positions[i]
423 } );
424 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000425
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000426 var updated_position = pos_translator.Translate(
427 break_point_old_positions[i].position,
428 PosTranslator.ShiftWithTopInsideChunkHandler);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000429
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000430 var new_location =
431 original_script.locationFromPosition(updated_position, false);
432
433 break_point.update_positions(new_location.line, new_location.column);
434
435 var new_position_description = {
436 position: updated_position,
437 line: new_location.line,
438 column: new_location.column
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000439 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000440
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000441 break_point.set(original_script);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000442
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000443 break_points_update_report.push( { type: "position_changed",
444 id: break_point.number(),
445 old_positions: break_point_old_positions[i],
446 new_positions: new_position_description
447 } );
448 }
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000449 };
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000450 }
451
vegorov@chromium.org42841962010-10-18 11:18:59 +0000452
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000453 function Assert(condition, message) {
454 if (!condition) {
455 if (message) {
456 throw "Assert " + message;
457 } else {
458 throw "Assert";
459 }
460 }
461 }
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000462
463 function DiffChunk(pos1, pos2, len1, len2) {
464 this.pos1 = pos1;
465 this.pos2 = pos2;
466 this.len1 = len1;
467 this.len2 = len2;
468 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000469
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000470 function PosTranslator(diff_array) {
471 var chunks = new Array();
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000472 var current_diff = 0;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000473 for (var i = 0; i < diff_array.length; i += 3) {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000474 var pos1_begin = diff_array[i];
475 var pos2_begin = pos1_begin + current_diff;
476 var pos1_end = diff_array[i + 1];
477 var pos2_end = diff_array[i + 2];
478 chunks.push(new DiffChunk(pos1_begin, pos2_begin, pos1_end - pos1_begin,
479 pos2_end - pos2_begin));
vegorov@chromium.org42841962010-10-18 11:18:59 +0000480 current_diff = pos2_end - pos1_end;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000481 }
482 this.chunks = chunks;
483 }
484 PosTranslator.prototype.GetChunks = function() {
485 return this.chunks;
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000486 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000487
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000488 PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
vegorov@chromium.org42841962010-10-18 11:18:59 +0000489 var array = this.chunks;
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000490 if (array.length == 0 || pos < array[0].pos1) {
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000491 return pos;
492 }
493 var chunk_index1 = 0;
494 var chunk_index2 = array.length - 1;
495
496 while (chunk_index1 < chunk_index2) {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000497 var middle_index = Math.floor((chunk_index1 + chunk_index2) / 2);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000498 if (pos < array[middle_index + 1].pos1) {
499 chunk_index2 = middle_index;
500 } else {
501 chunk_index1 = middle_index + 1;
502 }
503 }
504 var chunk = array[chunk_index1];
505 if (pos >= chunk.pos1 + chunk.len1) {
vegorov@chromium.org42841962010-10-18 11:18:59 +0000506 return pos + chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000507 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000508
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000509 if (!inside_chunk_handler) {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000510 inside_chunk_handler = PosTranslator.DefaultInsideChunkHandler;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000511 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000512 return inside_chunk_handler(pos, chunk);
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000513 };
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000514
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000515 PosTranslator.DefaultInsideChunkHandler = function(pos, diff_chunk) {
516 Assert(false, "Cannot translate position in changed area");
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000517 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000518
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000519 PosTranslator.ShiftWithTopInsideChunkHandler =
520 function(pos, diff_chunk) {
521 // We carelessly do not check whether we stay inside the chunk after
522 // translation.
vegorov@chromium.org42841962010-10-18 11:18:59 +0000523 return pos - diff_chunk.pos1 + diff_chunk.pos2;
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000524 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000525
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000526 var FunctionStatus = {
527 // No change to function or its inner functions; however its positions
vegorov@chromium.org42841962010-10-18 11:18:59 +0000528 // in script may have been shifted.
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000529 UNCHANGED: "unchanged",
530 // The code of a function remains unchanged, but something happened inside
531 // some inner functions.
532 SOURCE_CHANGED: "source changed",
533 // The code of a function is changed or some nested function cannot be
534 // properly patched so this function must be recompiled.
535 CHANGED: "changed",
536 // Function is changed but cannot be patched.
537 DAMAGED: "damaged"
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000538 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000539
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000540 function CodeInfoTreeNode(code_info, children, array_index) {
541 this.info = code_info;
542 this.children = children;
543 // an index in array of compile_info
vegorov@chromium.org42841962010-10-18 11:18:59 +0000544 this.array_index = array_index;
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000545 this.parent = void 0;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000546
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000547 this.status = FunctionStatus.UNCHANGED;
548 // Status explanation is used for debugging purposes and will be shown
549 // in user UI if some explanations are needed.
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000550 this.status_explanation = void 0;
551 this.new_start_pos = void 0;
552 this.new_end_pos = void 0;
553 this.corresponding_node = void 0;
554 this.unmatched_new_nodes = void 0;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000555
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000556 // 'Textual' correspondence/matching is weaker than 'pure'
557 // correspondence/matching. We need 'textual' level for visual presentation
558 // in UI, we use 'pure' level for actual code manipulation.
559 // Sometimes only function body is changed (functions in old and new script
560 // textually correspond), but we cannot patch the code, so we see them
vegorov@chromium.org42841962010-10-18 11:18:59 +0000561 // as an old function deleted and new function created.
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000562 this.textual_corresponding_node = void 0;
563 this.textually_unmatched_new_nodes = void 0;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000564
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000565 this.live_shared_function_infos = void 0;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000566 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000567
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000568 // From array of function infos that is implicitly a tree creates
569 // an actual tree of functions in script.
570 function BuildCodeInfoTree(code_info_array) {
571 // Throughtout all function we iterate over input array.
572 var index = 0;
573
vegorov@chromium.org42841962010-10-18 11:18:59 +0000574 // Recursive function that builds a branch of tree.
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000575 function BuildNode() {
576 var my_index = index;
577 index++;
578 var child_array = new Array();
579 while (index < code_info_array.length &&
580 code_info_array[index].outer_index == my_index) {
581 child_array.push(BuildNode());
582 }
583 var node = new CodeInfoTreeNode(code_info_array[my_index], child_array,
584 my_index);
585 for (var i = 0; i < child_array.length; i++) {
586 child_array[i].parent = node;
587 }
588 return node;
589 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000590
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000591 var root = BuildNode();
592 Assert(index == code_info_array.length);
593 return root;
594 }
595
596 // Applies a list of the textual diff chunks onto the tree of functions.
597 // Determines status of each function (from unchanged to damaged). However
598 // children of unchanged functions are ignored.
599 function MarkChangedFunctions(code_info_tree, chunks) {
600
ulan@chromium.org2efb9002012-01-19 15:36:35 +0000601 // A convenient iterator over diff chunks that also translates
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000602 // positions from old to new in a current non-changed part of script.
603 var chunk_it = new function() {
604 var chunk_index = 0;
605 var pos_diff = 0;
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000606 this.current = function() { return chunks[chunk_index]; };
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000607 this.next = function() {
608 var chunk = chunks[chunk_index];
vegorov@chromium.org42841962010-10-18 11:18:59 +0000609 pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000610 chunk_index++;
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000611 };
612 this.done = function() { return chunk_index >= chunks.length; };
613 this.TranslatePos = function(pos) { return pos + pos_diff; };
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000614 };
615
616 // A recursive function that processes internals of a function and all its
617 // inner functions. Iterator chunk_it initially points to a chunk that is
618 // below function start.
619 function ProcessInternals(info_node) {
620 info_node.new_start_pos = chunk_it.TranslatePos(
vegorov@chromium.org42841962010-10-18 11:18:59 +0000621 info_node.info.start_position);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000622 var child_index = 0;
623 var code_changed = false;
624 var source_changed = false;
625 // Simultaneously iterates over child functions and over chunks.
626 while (!chunk_it.done() &&
627 chunk_it.current().pos1 < info_node.info.end_position) {
628 if (child_index < info_node.children.length) {
629 var child = info_node.children[child_index];
vegorov@chromium.org42841962010-10-18 11:18:59 +0000630
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000631 if (child.info.end_position <= chunk_it.current().pos1) {
632 ProcessUnchangedChild(child);
633 child_index++;
634 continue;
635 } else if (child.info.start_position >=
636 chunk_it.current().pos1 + chunk_it.current().len1) {
637 code_changed = true;
638 chunk_it.next();
639 continue;
640 } else if (child.info.start_position <= chunk_it.current().pos1 &&
641 child.info.end_position >= chunk_it.current().pos1 +
642 chunk_it.current().len1) {
643 ProcessInternals(child);
644 source_changed = source_changed ||
645 ( child.status != FunctionStatus.UNCHANGED );
646 code_changed = code_changed ||
647 ( child.status == FunctionStatus.DAMAGED );
648 child_index++;
649 continue;
650 } else {
651 code_changed = true;
652 child.status = FunctionStatus.DAMAGED;
653 child.status_explanation =
654 "Text diff overlaps with function boundary";
655 child_index++;
656 continue;
657 }
658 } else {
vegorov@chromium.org42841962010-10-18 11:18:59 +0000659 if (chunk_it.current().pos1 + chunk_it.current().len1 <=
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000660 info_node.info.end_position) {
661 info_node.status = FunctionStatus.CHANGED;
662 chunk_it.next();
663 continue;
664 } else {
665 info_node.status = FunctionStatus.DAMAGED;
666 info_node.status_explanation =
667 "Text diff overlaps with function boundary";
668 return;
669 }
670 }
671 Assert("Unreachable", false);
672 }
673 while (child_index < info_node.children.length) {
674 var child = info_node.children[child_index];
675 ProcessUnchangedChild(child);
676 child_index++;
677 }
678 if (code_changed) {
679 info_node.status = FunctionStatus.CHANGED;
680 } else if (source_changed) {
681 info_node.status = FunctionStatus.SOURCE_CHANGED;
682 }
683 info_node.new_end_pos =
vegorov@chromium.org42841962010-10-18 11:18:59 +0000684 chunk_it.TranslatePos(info_node.info.end_position);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000685 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000686
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000687 function ProcessUnchangedChild(node) {
688 node.new_start_pos = chunk_it.TranslatePos(node.info.start_position);
689 node.new_end_pos = chunk_it.TranslatePos(node.info.end_position);
690 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000691
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000692 ProcessInternals(code_info_tree);
693 }
694
695 // For ecah old function (if it is not damaged) tries to find a corresponding
696 // function in new script. Typically it should succeed (non-damaged functions
697 // by definition may only have changes inside their bodies). However there are
698 // reasons for corresponence not to be found; function with unmodified text
699 // in new script may become enclosed into other function; the innocent change
700 // inside function body may in fact be something like "} function B() {" that
701 // splits a function into 2 functions.
702 function FindCorrespondingFunctions(old_code_tree, new_code_tree) {
703
704 // A recursive function that tries to find a correspondence for all
705 // child functions and for their inner functions.
706 function ProcessChildren(old_node, new_node) {
707 var old_children = old_node.children;
708 var new_children = new_node.children;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000709
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000710 var unmatched_new_nodes_list = [];
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000711 var textually_unmatched_new_nodes_list = [];
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000712
713 var old_index = 0;
714 var new_index = 0;
715 while (old_index < old_children.length) {
716 if (old_children[old_index].status == FunctionStatus.DAMAGED) {
717 old_index++;
718 } else if (new_index < new_children.length) {
719 if (new_children[new_index].info.start_position <
720 old_children[old_index].new_start_pos) {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000721 unmatched_new_nodes_list.push(new_children[new_index]);
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000722 textually_unmatched_new_nodes_list.push(new_children[new_index]);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000723 new_index++;
724 } else if (new_children[new_index].info.start_position ==
725 old_children[old_index].new_start_pos) {
726 if (new_children[new_index].info.end_position ==
727 old_children[old_index].new_end_pos) {
728 old_children[old_index].corresponding_node =
729 new_children[new_index];
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000730 old_children[old_index].textual_corresponding_node =
731 new_children[new_index];
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000732 if (old_children[old_index].status != FunctionStatus.UNCHANGED) {
733 ProcessChildren(old_children[old_index],
734 new_children[new_index]);
735 if (old_children[old_index].status == FunctionStatus.DAMAGED) {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000736 unmatched_new_nodes_list.push(
737 old_children[old_index].corresponding_node);
738 old_children[old_index].corresponding_node = void 0;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000739 old_node.status = FunctionStatus.CHANGED;
740 }
741 }
742 } else {
743 old_children[old_index].status = FunctionStatus.DAMAGED;
744 old_children[old_index].status_explanation =
745 "No corresponding function in new script found";
746 old_node.status = FunctionStatus.CHANGED;
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000747 unmatched_new_nodes_list.push(new_children[new_index]);
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000748 textually_unmatched_new_nodes_list.push(new_children[new_index]);
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000749 }
750 new_index++;
751 old_index++;
752 } else {
753 old_children[old_index].status = FunctionStatus.DAMAGED;
754 old_children[old_index].status_explanation =
755 "No corresponding function in new script found";
756 old_node.status = FunctionStatus.CHANGED;
757 old_index++;
758 }
759 } else {
760 old_children[old_index].status = FunctionStatus.DAMAGED;
761 old_children[old_index].status_explanation =
762 "No corresponding function in new script found";
763 old_node.status = FunctionStatus.CHANGED;
764 old_index++;
765 }
766 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000767
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000768 while (new_index < new_children.length) {
769 unmatched_new_nodes_list.push(new_children[new_index]);
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000770 textually_unmatched_new_nodes_list.push(new_children[new_index]);
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000771 new_index++;
772 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000773
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000774 if (old_node.status == FunctionStatus.CHANGED) {
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000775 var why_wrong_expectations =
776 WhyFunctionExpectationsDiffer(old_node.info, new_node.info);
777 if (why_wrong_expectations) {
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000778 old_node.status = FunctionStatus.DAMAGED;
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000779 old_node.status_explanation = why_wrong_expectations;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000780 }
781 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000782 old_node.unmatched_new_nodes = unmatched_new_nodes_list;
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000783 old_node.textually_unmatched_new_nodes =
784 textually_unmatched_new_nodes_list;
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000785 }
786
787 ProcessChildren(old_code_tree, new_code_tree);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000788
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000789 old_code_tree.corresponding_node = new_code_tree;
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000790 old_code_tree.textual_corresponding_node = new_code_tree;
791
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000792 Assert(old_code_tree.status != FunctionStatus.DAMAGED,
793 "Script became damaged");
794 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000795
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000796 function FindLiveSharedInfos(old_code_tree, script) {
797 var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
vegorov@chromium.org42841962010-10-18 11:18:59 +0000798
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000799 var shared_infos = new Array();
vegorov@chromium.org42841962010-10-18 11:18:59 +0000800
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000801 for (var i = 0; i < shared_raw_list.length; i++) {
802 shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
803 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000804
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000805 // Finds all SharedFunctionInfos that corresponds to compile info
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000806 // in old version of the script.
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000807 function FindFunctionInfos(compile_info) {
808 var wrappers = [];
809
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000810 for (var i = 0; i < shared_infos.length; i++) {
811 var wrapper = shared_infos[i];
812 if (wrapper.start_position == compile_info.start_position &&
813 wrapper.end_position == compile_info.end_position) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000814 wrappers.push(wrapper);
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000815 }
816 }
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000817
818 if (wrappers.length > 0) {
819 return wrappers;
820 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000821 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000822
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000823 function TraverseTree(node) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000824 node.live_shared_function_infos = FindFunctionInfos(node.info);
825
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000826 for (var i = 0; i < node.children.length; i++) {
827 TraverseTree(node.children[i]);
828 }
829 }
830
831 TraverseTree(old_code_tree);
832 }
fschneider@chromium.org013f3e12010-04-26 13:27:52 +0000833
vegorov@chromium.org42841962010-10-18 11:18:59 +0000834
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000835 // An object describing function compilation details. Its index fields
836 // apply to indexes inside array that stores these objects.
837 function FunctionCompileInfo(raw_array) {
838 this.function_name = raw_array[0];
839 this.start_position = raw_array[1];
840 this.end_position = raw_array[2];
841 this.param_num = raw_array[3];
842 this.code = raw_array[4];
ager@chromium.orgea4f62e2010-08-16 16:28:43 +0000843 this.code_scope_info = raw_array[5];
844 this.scope_info = raw_array[6];
845 this.outer_index = raw_array[7];
846 this.shared_function_info = raw_array[8];
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000847 this.next_sibling_index = null;
848 this.raw_array = raw_array;
849 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000850
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000851 function SharedInfoWrapper(raw_array) {
852 this.function_name = raw_array[0];
853 this.start_position = raw_array[1];
854 this.end_position = raw_array[2];
855 this.info = raw_array[3];
856 this.raw_array = raw_array;
857 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000858
859 // Changes positions (including all statments) in function.
860 function PatchPositions(old_info_node, diff_array, report_array) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000861 if (old_info_node.live_shared_function_infos) {
862 old_info_node.live_shared_function_infos.forEach(function (info) {
863 %LiveEditPatchFunctionPositions(info.raw_array,
864 diff_array);
865 });
866
867 report_array.push( { name: old_info_node.info.function_name } );
868 } else {
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000869 // TODO(LiveEdit): function is not compiled yet or is already collected.
vegorov@chromium.org42841962010-10-18 11:18:59 +0000870 report_array.push(
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000871 { name: old_info_node.info.function_name, info_not_found: true } );
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000872 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +0000873 }
874
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000875 // Adds a suffix to script name to mark that it is old version.
876 function CreateNameForOldScript(script) {
877 // TODO(635): try better than this; support several changes.
878 return script.name + " (old)";
879 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000880
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000881 // Compares a function interface old and new version, whether it
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000882 // changed or not. Returns explanation if they differ.
883 function WhyFunctionExpectationsDiffer(function_info1, function_info2) {
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000884 // Check that function has the same number of parameters (there may exist
885 // an adapter, that won't survive function parameter number change).
886 if (function_info1.param_num != function_info2.param_num) {
vegorov@chromium.org42841962010-10-18 11:18:59 +0000887 return "Changed parameter number: " + function_info1.param_num +
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000888 " and " + function_info2.param_num;
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000889 }
890 var scope_info1 = function_info1.scope_info;
891 var scope_info2 = function_info2.scope_info;
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000892
vegorov@chromium.org42841962010-10-18 11:18:59 +0000893 var scope_info1_text;
894 var scope_info2_text;
895
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000896 if (scope_info1) {
vegorov@chromium.org42841962010-10-18 11:18:59 +0000897 scope_info1_text = scope_info1.toString();
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000898 } else {
899 scope_info1_text = "";
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000900 }
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000901 if (scope_info2) {
vegorov@chromium.org42841962010-10-18 11:18:59 +0000902 scope_info2_text = scope_info2.toString();
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000903 } else {
904 scope_info2_text = "";
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000905 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000906
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000907 if (scope_info1_text != scope_info2_text) {
908 return "Incompatible variable maps: [" + scope_info1_text +
vegorov@chromium.org42841962010-10-18 11:18:59 +0000909 "] and [" + scope_info2_text + "]";
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000910 }
911 // No differences. Return undefined.
912 return;
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000913 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000914
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000915 // Minifier forward declaration.
916 var FunctionPatchabilityStatus;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000917
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000918 // For array of wrapped shared function infos checks that none of them
919 // have activations on stack (of any thread). Throws a Failure exception
920 // if this proves to be false.
921 function CheckStackActivations(shared_wrapper_list, change_log) {
922 var shared_list = new Array();
923 for (var i = 0; i < shared_wrapper_list.length; i++) {
924 shared_list[i] = shared_wrapper_list[i].info;
925 }
926 var result = %LiveEditCheckAndDropActivations(shared_list, true);
927 if (result[shared_list.length]) {
928 // Extra array element may contain error message.
929 throw new Failure(result[shared_list.length]);
930 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000931
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000932 var problems = new Array();
933 var dropped = new Array();
934 for (var i = 0; i < shared_list.length; i++) {
935 var shared = shared_wrapper_list[i];
936 if (result[i] == FunctionPatchabilityStatus.REPLACED_ON_ACTIVE_STACK) {
937 dropped.push({ name: shared.function_name } );
938 } else if (result[i] != FunctionPatchabilityStatus.AVAILABLE_FOR_PATCH) {
939 var description = {
940 name: shared.function_name,
vegorov@chromium.org42841962010-10-18 11:18:59 +0000941 start_pos: shared.start_position,
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000942 end_pos: shared.end_position,
943 replace_problem:
944 FunctionPatchabilityStatus.SymbolName(result[i])
945 };
946 problems.push(description);
947 }
948 }
949 if (dropped.length > 0) {
950 change_log.push({ dropped_from_stack: dropped });
951 }
952 if (problems.length > 0) {
953 change_log.push( { functions_on_stack: problems } );
954 throw new Failure("Blocked by functions on stack");
955 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000956
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +0000957 return dropped.length;
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000958 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000959
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000960 // A copy of the FunctionPatchabilityStatus enum from liveedit.h
961 var FunctionPatchabilityStatus = {
962 AVAILABLE_FOR_PATCH: 1,
963 BLOCKED_ON_ACTIVE_STACK: 2,
964 BLOCKED_ON_OTHER_STACK: 3,
965 BLOCKED_UNDER_NATIVE_CODE: 4,
966 REPLACED_ON_ACTIVE_STACK: 5
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000967 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000968
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000969 FunctionPatchabilityStatus.SymbolName = function(code) {
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000970 var enumeration = FunctionPatchabilityStatus;
971 for (name in enumeration) {
972 if (enumeration[name] == code) {
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000973 return name;
974 }
vegorov@chromium.org42841962010-10-18 11:18:59 +0000975 }
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000976 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000977
978
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000979 // A logical failure in liveedit process. This means that change_log
980 // is valid and consistent description of what happened.
981 function Failure(message) {
982 this.message = message;
983 }
984 // Function (constructor) is public.
985 this.Failure = Failure;
vegorov@chromium.org42841962010-10-18 11:18:59 +0000986
ricow@chromium.orgc9c80822010-04-21 08:22:37 +0000987 Failure.prototype.toString = function() {
988 return "LiveEdit Failure: " + this.message;
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +0000989 };
vegorov@chromium.org42841962010-10-18 11:18:59 +0000990
mmassi@chromium.org49a44672012-12-04 13:52:03 +0000991 function CopyErrorPositionToDetails(e, details) {
992 function createPositionStruct(script, position) {
993 if (position == -1) return;
994 var location = script.locationFromPosition(position, true);
995 if (location == null) return;
996 return {
997 line: location.line + 1,
998 column: location.column + 1,
999 position: position
1000 };
1001 }
1002
1003 if (!("scriptObject" in e) || !("startPosition" in e)) {
1004 return;
1005 }
1006
1007 var script = e.scriptObject;
1008
1009 var position_struct = {
1010 start: createPositionStruct(script, e.startPosition),
1011 end: createPositionStruct(script, e.endPosition)
1012 };
1013 details.position = position_struct;
1014 }
1015
ricow@chromium.orgc9c80822010-04-21 08:22:37 +00001016 // A testing entry.
1017 function GetPcFromSourcePos(func, source_pos) {
1018 return %GetFunctionCodePositionFromSource(func, source_pos);
1019 }
1020 // Function is public.
1021 this.GetPcFromSourcePos = GetPcFromSourcePos;
1022
lrn@chromium.org25156de2010-04-06 13:10:27 +00001023 // LiveEdit main entry point: changes a script text to a new string.
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001024 function SetScriptSource(script, new_source, preview_only, change_log) {
lrn@chromium.org25156de2010-04-06 13:10:27 +00001025 var old_source = script.source;
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +00001026 var diff = CompareStrings(old_source, new_source);
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001027 return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
1028 change_log);
lrn@chromium.org25156de2010-04-06 13:10:27 +00001029 }
ricow@chromium.orgc9c80822010-04-21 08:22:37 +00001030 // Function is public.
1031 this.SetScriptSource = SetScriptSource;
vegorov@chromium.org42841962010-10-18 11:18:59 +00001032
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +00001033 function CompareStrings(s1, s2) {
1034 return %LiveEditCompareStrings(s1, s2);
ricow@chromium.orgc9c80822010-04-21 08:22:37 +00001035 }
kmillikin@chromium.org4111b802010-05-03 10:34:42 +00001036
1037 // Applies the change to the script.
1038 // The change is always a substring (change_pos, change_pos + change_len)
1039 // being replaced with a completely different string new_str.
1040 // This API is a legacy and is obsolete.
1041 //
1042 // @param {Script} script that is being changed
1043 // @param {Array} change_log a list that collects engineer-readable
1044 // description of what happened.
1045 function ApplySingleChunkPatch(script, change_pos, change_len, new_str,
1046 change_log) {
1047 var old_source = script.source;
vegorov@chromium.org42841962010-10-18 11:18:59 +00001048
kmillikin@chromium.org4111b802010-05-03 10:34:42 +00001049 // Prepare new source string.
1050 var new_source = old_source.substring(0, change_pos) +
1051 new_str + old_source.substring(change_pos + change_len);
vegorov@chromium.org42841962010-10-18 11:18:59 +00001052
kmillikin@chromium.org4111b802010-05-03 10:34:42 +00001053 return ApplyPatchMultiChunk(script,
1054 [ change_pos, change_pos + change_len, change_pos + new_str.length],
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001055 new_source, false, change_log);
1056 }
vegorov@chromium.org42841962010-10-18 11:18:59 +00001057
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001058 // Creates JSON description for a change tree.
1059 function DescribeChangeTree(old_code_tree) {
vegorov@chromium.org42841962010-10-18 11:18:59 +00001060
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001061 function ProcessOldNode(node) {
1062 var child_infos = [];
1063 for (var i = 0; i < node.children.length; i++) {
1064 var child = node.children[i];
1065 if (child.status != FunctionStatus.UNCHANGED) {
1066 child_infos.push(ProcessOldNode(child));
1067 }
1068 }
1069 var new_child_infos = [];
1070 if (node.textually_unmatched_new_nodes) {
1071 for (var i = 0; i < node.textually_unmatched_new_nodes.length; i++) {
1072 var child = node.textually_unmatched_new_nodes[i];
1073 new_child_infos.push(ProcessNewNode(child));
1074 }
1075 }
1076 var res = {
1077 name: node.info.function_name,
1078 positions: DescribePositions(node),
1079 status: node.status,
1080 children: child_infos,
vegorov@chromium.org42841962010-10-18 11:18:59 +00001081 new_children: new_child_infos
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001082 };
1083 if (node.status_explanation) {
1084 res.status_explanation = node.status_explanation;
1085 }
1086 if (node.textual_corresponding_node) {
1087 res.new_positions = DescribePositions(node.textual_corresponding_node);
1088 }
1089 return res;
1090 }
vegorov@chromium.org42841962010-10-18 11:18:59 +00001091
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001092 function ProcessNewNode(node) {
1093 var child_infos = [];
1094 // Do not list ancestors.
1095 if (false) {
1096 for (var i = 0; i < node.children.length; i++) {
1097 child_infos.push(ProcessNewNode(node.children[i]));
1098 }
1099 }
1100 var res = {
1101 name: node.info.function_name,
1102 positions: DescribePositions(node),
1103 children: child_infos,
1104 };
1105 return res;
1106 }
vegorov@chromium.org42841962010-10-18 11:18:59 +00001107
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001108 function DescribePositions(node) {
1109 return {
1110 start_position: node.info.start_position,
1111 end_position: node.info.end_position
1112 };
1113 }
vegorov@chromium.org42841962010-10-18 11:18:59 +00001114
kmillikin@chromium.org69ea3962010-07-05 11:01:40 +00001115 return ProcessOldNode(old_code_tree);
kmillikin@chromium.org4111b802010-05-03 10:34:42 +00001116 }
lrn@chromium.org25156de2010-04-06 13:10:27 +00001117
yangguo@chromium.org5a11aaf2012-06-20 11:29:00 +00001118 // Restarts call frame and returns value similar to what LiveEdit returns.
1119 function RestartFrame(frame_mirror) {
1120 var result = frame_mirror.restart();
1121 if (IS_STRING(result)) {
1122 throw new Failure("Failed to restart frame: " + result);
1123 }
1124 var result = {};
1125 result[NEEDS_STEP_IN_PROPERTY_NAME] = true;
1126 return result;
1127 }
1128 // Function is public.
1129 this.RestartFrame = RestartFrame;
vegorov@chromium.org42841962010-10-18 11:18:59 +00001130
kmillikin@chromium.org4111b802010-05-03 10:34:42 +00001131 // Functions are public for tests.
1132 this.TestApi = {
1133 PosTranslator: PosTranslator,
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +00001134 CompareStrings: CompareStrings,
kmillikin@chromium.org4111b802010-05-03 10:34:42 +00001135 ApplySingleChunkPatch: ApplySingleChunkPatch
mstarzinger@chromium.org1b3afd12011-11-29 14:28:56 +00001136 };
1137};