Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: src/liveedit-debugger.js

Issue 1672006: Support multi-chunk differences (Closed)
Patch Set: merge Created 10 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/liveedit.cc ('k') | src/runtime.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2010 the V8 project authors. All rights reserved. 1 // Copyright 2010 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without 2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are 3 // modification, are permitted provided that the following conditions are
4 // met: 4 // met:
5 // 5 //
6 // * Redistributions of source code must retain the above copyright 6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer. 7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above 8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following 9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided 10 // disclaimer in the documentation and/or other materials provided
(...skipping 10 matching lines...) Expand all
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 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. 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 27
28 // LiveEdit feature implementation. The script should be executed after 28 // LiveEdit feature implementation. The script should be executed after
29 // debug-debugger.js. 29 // debug-debugger.js.
30 30
31 // A LiveEdit namespace is declared inside a single function constructor. 31 // 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.
32 Debug.LiveEdit = new function() { 45 Debug.LiveEdit = new function() {
33 46
34 // Changes script text and recompiles all relevant functions if possible. 47 // Applies the change to the script.
35 // The change is always a substring (change_pos, change_pos + change_len) 48 // The change is always a substring (change_pos, change_pos + change_len)
36 // being replaced with a completely different string new_str. 49 // being replaced with a completely different string new_str.
37 // 50 // This API is a legacy and is obsolete.
38 // Only one function will have its Code changed in result of this function.
39 // All nested functions (should they have any instances at the moment) are
40 // left unchanged and re-linked to a newly created script instance
41 // representing old version of the source. (Generally speaking,
42 // during the change all nested functions are erased and completely different
43 // set of nested functions are introduced.) All other functions just have
44 // their positions updated.
45 // 51 //
46 // @param {Script} script that is being changed 52 // @param {Script} script that is being changed
47 // @param {Array} change_log a list that collects engineer-readable 53 // @param {Array} change_log a list that collects engineer-readable
48 // description of what happened. 54 // description of what happened.
49 function ApplyPatch(script, change_pos, change_len, new_str, 55 function ApplyPatch(script, change_pos, change_len, new_str,
50 change_log) { 56 change_log) {
57 var old_source = script.source;
58
59 // Prepare new source string.
60 var new_source = old_source.substring(0, change_pos) +
61 new_str + old_source.substring(change_pos + change_len);
62
63 return ApplyPatchMultiChunk(script,
64 [ change_pos, change_pos + change_len, change_pos + new_str.length],
65 new_source, change_log);
66 }
67 // Function is public.
68 this.ApplyPatch = ApplyPatch;
69
70 // Forward declaration for minifier.
71 var FunctionStatus;
72
73 // Applies the change to the script.
74 // The change is in form of list of chunks encoded in a single array as
75 // a series of triplets (pos1_start, pos1_end, pos2_end)
76 function ApplyPatchMultiChunk(script, diff_array, new_source, change_log) {
51 77
52 // Fully compiles source string as a script. Returns Array of 78 // Fully compiles source string as a script. Returns Array of
53 // FunctionCompileInfo -- a descriptions of all functions of the script. 79 // FunctionCompileInfo -- a descriptions of all functions of the script.
54 // Elements of array are ordered by start positions of functions (from top 80 // Elements of array are ordered by start positions of functions (from top
55 // to bottom) in the source. Fields outer_index and next_sibling_index help 81 // to bottom) in the source. Fields outer_index and next_sibling_index help
56 // to navigate the nesting structure of functions. 82 // to navigate the nesting structure of functions.
57 // 83 //
58 // The script is used for compilation, because it produces code that 84 // The script is used for compilation, because it produces code that
59 // needs to be linked with some particular script (for nested functions). 85 // needs to be linked with some particular script (for nested functions).
60 function DebugGatherCompileInfo(source) { 86 function DebugGatherCompileInfo(source) {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
110 compile_info[previous_sibling].next_sibling_index = -1; 136 compile_info[previous_sibling].next_sibling_index = -1;
111 } 137 }
112 } 138 }
113 139
114 ResetIndexes(-1, -1); 140 ResetIndexes(-1, -1);
115 Assert(current_index == compile_info.length); 141 Assert(current_index == compile_info.length);
116 142
117 return compile_info; 143 return compile_info;
118 } 144 }
119 145
120 // Given a positions, finds a function that fully includes the entire
121 // change.
122 function FindChangedFunction(compile_info, offset, len) {
123 // First condition: function should start before the change region.
124 // Function #0 (whole-script function) always does, but we want
125 // one, that is later in this list.
126 var index = 0;
127 while (index + 1 < compile_info.length &&
128 compile_info[index + 1].start_position <= offset) {
129 index++;
130 }
131 // Now we are at the last function that begins before the change
132 // region. The function that covers entire change region is either
133 // this function or the enclosing one.
134 for (; compile_info[index].end_position < offset + len;
135 index = compile_info[index].outer_index) {
136 Assert(index != -1);
137 }
138 return index;
139 }
140
141 // Variable forward declarations. Preprocessor "Minifier" needs them. 146 // Variable forward declarations. Preprocessor "Minifier" needs them.
142 var old_compile_info; 147 var old_compile_info;
143 var shared_infos; 148 var shared_infos;
144 // Finds SharedFunctionInfo that corresponds compile info with index 149 // Finds SharedFunctionInfo that corresponds compile info with index
145 // in old version of the script. 150 // in old version of the script.
146 function FindFunctionInfo(index) { 151 function FindFunctionInfo(index) {
147 var old_info = old_compile_info[index]; 152 var old_info = old_compile_info[index];
148 for (var i = 0; i < shared_infos.length; i++) { 153 for (var i = 0; i < shared_infos.length; i++) {
149 var info = shared_infos[i]; 154 var info = shared_infos[i];
150 if (info.start_position == old_info.start_position && 155 if (info.start_position == old_info.start_position &&
151 info.end_position == old_info.end_position) { 156 info.end_position == old_info.end_position) {
152 return info; 157 return info;
153 } 158 }
154 } 159 }
155 } 160 }
156 161
157 // Replaces function's Code. 162 // Replaces function's Code.
158 function PatchCode(new_info, shared_info) { 163 function PatchCode(new_info, shared_info) {
159 %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array); 164 if (shared_info) {
165 %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array);
166 change_log.push( {function_patched: new_info.function_name} );
167 } else {
168 change_log.push( {function_patched: new_info.function_name,
169 function_info_not_found: true} );
170 }
160 171
161 change_log.push( {function_patched: new_info.function_name} );
162 } 172 }
163 173
164 var change_len_old; 174
165 var change_len_new;
166 // Translate position in old version of script into position in new
167 // version of script.
168 function PosTranslator(old_pos) {
169 if (old_pos <= change_pos) {
170 return old_pos;
171 }
172 if (old_pos >= change_pos + change_len_old) {
173 return old_pos + change_len_new - change_len_old;
174 }
175 return -1;
176 }
177
178 var position_change_array;
179 var position_patch_report; 175 var position_patch_report;
180 function PatchPositions(new_info, shared_info) { 176 function PatchPositions(old_info, shared_info) {
181 if (!shared_info) { 177 if (!shared_info) {
182 // TODO(LiveEdit): explain what is happening. 178 // TODO(LiveEdit): function is not compiled yet or is already collected.
179 position_patch_report.push(
180 { name: old_info.function_name, info_not_found: true } );
183 return; 181 return;
184 } 182 }
185 var breakpoint_position_update = %LiveEditPatchFunctionPositions( 183 var breakpoint_position_update = %LiveEditPatchFunctionPositions(
186 shared_info.raw_array, position_change_array); 184 shared_info.raw_array, diff_array);
187 for (var i = 0; i < breakpoint_position_update.length; i += 2) { 185 for (var i = 0; i < breakpoint_position_update.length; i += 2) {
188 var new_pos = breakpoint_position_update[i]; 186 var new_pos = breakpoint_position_update[i];
189 var break_point_object = breakpoint_position_update[i + 1]; 187 var break_point_object = breakpoint_position_update[i + 1];
190 change_log.push( { breakpoint_position_update: 188 change_log.push( { breakpoint_position_update:
191 { from: break_point_object.source_position(), to: new_pos } } ); 189 { from: break_point_object.source_position(), to: new_pos } } );
192 break_point_object.updateSourcePosition(new_pos, script); 190 break_point_object.updateSourcePosition(new_pos, script);
193 } 191 }
194 position_patch_report.push( { name: new_info.function_name } ); 192 position_patch_report.push( { name: old_info.function_name } );
195 } 193 }
196 194
197 var link_to_old_script_report; 195 var link_to_old_script_report;
198 var old_script; 196 var old_script;
199 // Makes a function associated with another instance of a script (the 197 // Makes a function associated with another instance of a script (the
200 // one representing its old version). This way the function still 198 // one representing its old version). This way the function still
201 // may access its own text. 199 // may access its own text.
202 function LinkToOldScript(shared_info) { 200 function LinkToOldScript(shared_info, old_info_node) {
203 %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script); 201 if (shared_info) {
204 202 %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script);
205 link_to_old_script_report.push( { name: shared_info.function_name } ); 203 link_to_old_script_report.push( { name: shared_info.function_name } );
204 } else {
205 link_to_old_script_report.push(
206 { name: old_info_node.info.function_name, not_found: true } );
207 }
206 } 208 }
207 209
208
209 210
210 var old_source = script.source; 211 var old_source = script.source;
211 var change_len_old = change_len; 212
212 var change_len_new = new_str.length;
213
214 // Prepare new source string.
215 var new_source = old_source.substring(0, change_pos) +
216 new_str + old_source.substring(change_pos + change_len);
217
218 // Find all SharedFunctionInfo's that are compiled from this script. 213 // Find all SharedFunctionInfo's that are compiled from this script.
219 var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script); 214 var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
220 215
221 var shared_infos = new Array(); 216 var shared_infos = new Array();
222 217
223 for (var i = 0; i < shared_raw_list.length; i++) { 218 for (var i = 0; i < shared_raw_list.length; i++) {
224 shared_infos.push(new SharedInfoWrapper(shared_raw_list[i])); 219 shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
225 } 220 }
226 221
227 // Gather compile information about old version of script. 222 // Gather compile information about old version of script.
228 var old_compile_info = DebugGatherCompileInfo(old_source); 223 var old_compile_info = DebugGatherCompileInfo(old_source);
229 224
230 // Gather compile information about new version of script. 225 // Gather compile information about new version of script.
231 var new_compile_info; 226 var new_compile_info;
232 try { 227 try {
233 new_compile_info = DebugGatherCompileInfo(new_source); 228 new_compile_info = DebugGatherCompileInfo(new_source);
234 } catch (e) { 229 } catch (e) {
235 throw new Failure("Failed to compile new version of script: " + e); 230 throw new Failure("Failed to compile new version of script: " + e);
236 } 231 }
237 232
238 // An index of a single function, that is going to have its code replaced. 233 var pos_translator = new PosTranslator(diff_array);
239 var function_being_patched = 234
240 FindChangedFunction(old_compile_info, change_pos, change_len_old); 235 // Build tree structures for old and new versions of the script.
241 236 var root_old_node = BuildCodeInfoTree(old_compile_info);
242 // In old and new script versions function with a change should have the 237 var root_new_node = BuildCodeInfoTree(new_compile_info);
243 // same indexes. 238
244 var function_being_patched2 = 239 // Analyze changes.
245 FindChangedFunction(new_compile_info, change_pos, change_len_new); 240 MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
246 Assert(function_being_patched == function_being_patched2, 241 FindCorrespondingFunctions(root_old_node, root_new_node);
247 "inconsistent old/new compile info"); 242
248 243 // Prepare to-do lists.
249 // Check that function being patched has the same expectations in a new 244 var replace_code_list = new Array();
250 // version. Otherwise we cannot safely patch its behavior and should 245 var link_to_old_script_list = new Array();
251 // choose the outer function instead. 246 var update_positions_list = new Array();
252 while (!CompareFunctionExpectations( 247
253 old_compile_info[function_being_patched], 248 function HarvestTodo(old_node) {
254 new_compile_info[function_being_patched])) { 249 function CollectDamaged(node) {
255 250 link_to_old_script_list.push(node);
256 Assert(old_compile_info[function_being_patched].outer_index == 251 for (var i = 0; i < node.children.length; i++) {
257 new_compile_info[function_being_patched].outer_index); 252 CollectDamaged(node.children[i]);
258 function_being_patched = 253 }
259 old_compile_info[function_being_patched].outer_index; 254 }
260 Assert(function_being_patched != -1); 255
256 if (old_node.status == FunctionStatus.DAMAGED) {
257 CollectDamaged(old_node);
258 return;
259 }
260 if (old_node.status == FunctionStatus.UNCHANGED) {
261 update_positions_list.push(old_node);
262 } else if (old_node.status == FunctionStatus.SOURCE_CHANGED) {
263 update_positions_list.push(old_node);
264 } else if (old_node.status == FunctionStatus.CHANGED) {
265 replace_code_list.push(old_node);
266 }
267 for (var i = 0; i < old_node.children.length; i++) {
268 HarvestTodo(old_node.children[i]);
269 }
261 } 270 }
262 271
272 HarvestTodo(root_old_node);
273
274 // Collect shared infos for functions whose code need to be patched.
275 var replaced_function_infos = new Array();
276 for (var i = 0; i < replace_code_list.length; i++) {
277 var info = FindFunctionInfo(replace_code_list[i].array_index);
278 if (info) {
279 replaced_function_infos.push(info);
280 }
281 }
282
263 // Check that function being patched is not currently on stack. 283 // Check that function being patched is not currently on stack.
264 CheckStackActivations( 284 CheckStackActivations(replaced_function_infos, change_log);
265 [ FindFunctionInfo(function_being_patched) ], change_log );
266 285
267 286
287 // We haven't changed anything before this line yet.
268 // Committing all changes. 288 // Committing all changes.
269 var old_script_name = CreateNameForOldScript(script); 289
270 290 // Create old script if there are function linked to old version.
271 // Update the script text and create a new script representing an old 291 if (link_to_old_script_list.length > 0) {
272 // version of the script. 292 var old_script_name = CreateNameForOldScript(script);
273 var old_script = %LiveEditReplaceScript(script, new_source, 293
274 old_script_name); 294 // Update the script text and create a new script representing an old
275 295 // version of the script.
276 PatchCode(new_compile_info[function_being_patched], 296 var old_script = %LiveEditReplaceScript(script, new_source,
277 FindFunctionInfo(function_being_patched)); 297 old_script_name);
298
299 var link_to_old_script_report = new Array();
300 change_log.push( { linked_to_old_script: link_to_old_script_report } );
301
302 // We need to link to old script all former nested functions.
303 for (var i = 0; i < link_to_old_script_list.length; i++) {
304 LinkToOldScript(
305 FindFunctionInfo(link_to_old_script_list[i].array_index),
306 link_to_old_script_list[i]);
307 }
308 }
309
310
311 for (var i = 0; i < replace_code_list.length; i++) {
312 PatchCode(replace_code_list[i].corresponding_node.info,
313 FindFunctionInfo(replace_code_list[i].array_index));
314 }
278 315
279 var position_patch_report = new Array(); 316 var position_patch_report = new Array();
280 change_log.push( {position_patched: position_patch_report} ); 317 change_log.push( {position_patched: position_patch_report} );
281 318
282 var position_change_array = [ change_pos, 319 for (var i = 0; i < update_positions_list.length; i++) {
283 change_pos + change_len_old, 320 // TODO(LiveEdit): take into account wether it's source_changed or
284 change_pos + change_len_new ]; 321 // unchanged and whether positions changed at all.
285 322 PatchPositions(update_positions_list[i].info,
286 // Update positions of all outer functions (i.e. all functions, that 323 FindFunctionInfo(update_positions_list[i].array_index));
287 // are partially below the function being patched).
288 for (var i = new_compile_info[function_being_patched].outer_index;
289 i != -1;
290 i = new_compile_info[i].outer_index) {
291 PatchPositions(new_compile_info[i], FindFunctionInfo(i));
292 }
293
294 // Update positions of all functions that are fully below the function
295 // being patched.
296 var old_next_sibling =
297 old_compile_info[function_being_patched].next_sibling_index;
298 var new_next_sibling =
299 new_compile_info[function_being_patched].next_sibling_index;
300
301 // We simply go over the tail of both old and new lists. Their tails should
302 // have an identical structure.
303 if (old_next_sibling == -1) {
304 Assert(new_next_sibling == -1);
305 } else {
306 Assert(old_compile_info.length - old_next_sibling ==
307 new_compile_info.length - new_next_sibling);
308
309 for (var i = old_next_sibling, j = new_next_sibling;
310 i < old_compile_info.length; i++, j++) {
311 PatchPositions(new_compile_info[j], FindFunctionInfo(i));
312 }
313 }
314
315 var link_to_old_script_report = new Array();
316 change_log.push( { linked_to_old_script: link_to_old_script_report } );
317
318 // We need to link to old script all former nested functions.
319 for (var i = function_being_patched + 1; i < old_next_sibling; i++) {
320 LinkToOldScript(FindFunctionInfo(i), old_script);
321 } 324 }
322 } 325 }
323 // Function is public. 326 // Function is public.
324 this.ApplyPatch = ApplyPatch; 327 this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
328
325 329
326 function Assert(condition, message) { 330 function Assert(condition, message) {
327 if (!condition) { 331 if (!condition) {
328 if (message) { 332 if (message) {
329 throw "Assert " + message; 333 throw "Assert " + message;
330 } else { 334 } else {
331 throw "Assert"; 335 throw "Assert";
332 } 336 }
333 } 337 }
334 } 338 }
339
340 function DiffChunk(pos1, pos2, len1, len2) {
341 this.pos1 = pos1;
342 this.pos2 = pos2;
343 this.len1 = len1;
344 this.len2 = len2;
345 }
346
347 function PosTranslator(diff_array) {
348 var chunks = new Array();
349 var pos1 = 0;
350 var pos2 = 0;
351 for (var i = 0; i < diff_array.length; i += 3) {
352 pos2 += diff_array[i] - pos1 + pos2;
353 pos1 = diff_array[i];
354 chunks.push(new DiffChunk(pos1, pos2, diff_array[i + 1] - pos1,
355 diff_array[i + 2] - pos2));
356 pos1 = diff_array[i + 1];
357 pos2 = diff_array[i + 2];
358 }
359 this.chunks = chunks;
360 }
361 PosTranslator.prototype.GetChunks = function() {
362 return this.chunks;
363 }
364
365 PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
366 var array = this.chunks;
367 if (array.length == 0 || pos < array[0]) {
368 return pos;
369 }
370 var chunk_index1 = 0;
371 var chunk_index2 = array.length - 1;
372
373 while (chunk_index1 < chunk_index2) {
374 var middle_index = (chunk_index1 + chunk_index2) / 2;
375 if (pos < array[middle_index + 1].pos1) {
376 chunk_index2 = middle_index;
377 } else {
378 chunk_index1 = middle_index + 1;
379 }
380 }
381 var chunk = array[chunk_index1];
382 if (pos >= chunk.pos1 + chunk.len1) {
383 return pos += chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
384 }
385
386 if (!inside_chunk_handler) {
387 inside_chunk_handler = PosTranslator.default_inside_chunk_handler;
388 }
389 inside_chunk_handler(pos, chunk);
390 }
391
392 PosTranslator.default_inside_chunk_handler = function() {
393 Assert(false, "Cannot translate position in chaged area");
394 }
395
396 var FunctionStatus = {
397 // No change to function or its inner functions; however its positions
398 // in script may have been shifted.
399 UNCHANGED: "unchanged",
400 // The code of a function remains unchanged, but something happened inside
401 // some inner functions.
402 SOURCE_CHANGED: "source changed",
403 // The code of a function is changed or some nested function cannot be
404 // properly patched so this function must be recompiled.
405 CHANGED: "changed",
406 // Function is changed but cannot be patched.
407 DAMAGED: "damaged"
408 }
409
410 function CodeInfoTreeNode(code_info, children, array_index) {
411 this.info = code_info;
412 this.children = children;
413 // an index in array of compile_info
414 this.array_index = array_index;
415 this.parent = void(0);
416
417 this.status = FunctionStatus.UNCHANGED;
418 // Status explanation is used for debugging purposes and will be shown
419 // in user UI if some explanations are needed.
420 this.status_explanation = void(0);
421 this.new_start_pos = void(0);
422 this.new_end_pos = void(0);
423 this.corresponding_node = void(0);
424 }
425
426 // From array of function infos that is implicitly a tree creates
427 // an actual tree of functions in script.
428 function BuildCodeInfoTree(code_info_array) {
429 // Throughtout all function we iterate over input array.
430 var index = 0;
431
432 // Recursive function that builds a branch of tree.
433 function BuildNode() {
434 var my_index = index;
435 index++;
436 var child_array = new Array();
437 while (index < code_info_array.length &&
438 code_info_array[index].outer_index == my_index) {
439 child_array.push(BuildNode());
440 }
441 var node = new CodeInfoTreeNode(code_info_array[my_index], child_array,
442 my_index);
443 for (var i = 0; i < child_array.length; i++) {
444 child_array[i].parent = node;
445 }
446 return node;
447 }
448
449 var root = BuildNode();
450 Assert(index == code_info_array.length);
451 return root;
452 }
453
454 // Applies a list of the textual diff chunks onto the tree of functions.
455 // Determines status of each function (from unchanged to damaged). However
456 // children of unchanged functions are ignored.
457 function MarkChangedFunctions(code_info_tree, chunks) {
458
459 // A convenient interator over diff chunks that also translates
460 // positions from old to new in a current non-changed part of script.
461 var chunk_it = new function() {
462 var chunk_index = 0;
463 var pos_diff = 0;
464 this.current = function() { return chunks[chunk_index]; }
465 this.next = function() {
466 var chunk = chunks[chunk_index];
467 pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1);
468 chunk_index++;
469 }
470 this.done = function() { return chunk_index >= chunks.length; }
471 this.TranslatePos = function(pos) { return pos + pos_diff; }
472 };
473
474 // A recursive function that processes internals of a function and all its
475 // inner functions. Iterator chunk_it initially points to a chunk that is
476 // below function start.
477 function ProcessInternals(info_node) {
478 info_node.new_start_pos = chunk_it.TranslatePos(
479 info_node.info.start_position);
480 var child_index = 0;
481 var code_changed = false;
482 var source_changed = false;
483 // Simultaneously iterates over child functions and over chunks.
484 while (!chunk_it.done() &&
485 chunk_it.current().pos1 < info_node.info.end_position) {
486 if (child_index < info_node.children.length) {
487 var child = info_node.children[child_index];
488
489 if (child.info.end_position <= chunk_it.current().pos1) {
490 ProcessUnchangedChild(child);
491 child_index++;
492 continue;
493 } else if (child.info.start_position >=
494 chunk_it.current().pos1 + chunk_it.current().len1) {
495 code_changed = true;
496 chunk_it.next();
497 continue;
498 } else if (child.info.start_position <= chunk_it.current().pos1 &&
499 child.info.end_position >= chunk_it.current().pos1 +
500 chunk_it.current().len1) {
501 ProcessInternals(child);
502 source_changed = source_changed ||
503 ( child.status != FunctionStatus.UNCHANGED );
504 code_changed = code_changed ||
505 ( child.status == FunctionStatus.DAMAGED );
506 child_index++;
507 continue;
508 } else {
509 code_changed = true;
510 child.status = FunctionStatus.DAMAGED;
511 child.status_explanation =
512 "Text diff overlaps with function boundary";
513 child_index++;
514 continue;
515 }
516 } else {
517 if (chunk_it.current().pos1 + chunk_it.current().len1 <=
518 info_node.info.end_position) {
519 info_node.status = FunctionStatus.CHANGED;
520 chunk_it.next();
521 continue;
522 } else {
523 info_node.status = FunctionStatus.DAMAGED;
524 info_node.status_explanation =
525 "Text diff overlaps with function boundary";
526 return;
527 }
528 }
529 Assert("Unreachable", false);
530 }
531 while (child_index < info_node.children.length) {
532 var child = info_node.children[child_index];
533 ProcessUnchangedChild(child);
534 child_index++;
535 }
536 if (code_changed) {
537 info_node.status = FunctionStatus.CHANGED;
538 } else if (source_changed) {
539 info_node.status = FunctionStatus.SOURCE_CHANGED;
540 }
541 info_node.new_end_pos =
542 chunk_it.TranslatePos(info_node.info.end_position);
543 }
544
545 function ProcessUnchangedChild(node) {
546 node.new_start_pos = chunk_it.TranslatePos(node.info.start_position);
547 node.new_end_pos = chunk_it.TranslatePos(node.info.end_position);
548 }
549
550 ProcessInternals(code_info_tree);
551 }
552
553 // For ecah old function (if it is not damaged) tries to find a corresponding
554 // function in new script. Typically it should succeed (non-damaged functions
555 // by definition may only have changes inside their bodies). However there are
556 // reasons for corresponence not to be found; function with unmodified text
557 // in new script may become enclosed into other function; the innocent change
558 // inside function body may in fact be something like "} function B() {" that
559 // splits a function into 2 functions.
560 function FindCorrespondingFunctions(old_code_tree, new_code_tree) {
561
562 // A recursive function that tries to find a correspondence for all
563 // child functions and for their inner functions.
564 function ProcessChildren(old_node, new_node) {
565 var old_children = old_node.children;
566 var new_children = new_node.children;
567
568 var old_index = 0;
569 var new_index = 0;
570 while (old_index < old_children.length) {
571 if (old_children[old_index].status == FunctionStatus.DAMAGED) {
572 old_index++;
573 } else if (new_index < new_children.length) {
574 if (new_children[new_index].info.start_position <
575 old_children[old_index].new_start_pos) {
576 new_index++;
577 } else if (new_children[new_index].info.start_position ==
578 old_children[old_index].new_start_pos) {
579 if (new_children[new_index].info.end_position ==
580 old_children[old_index].new_end_pos) {
581 old_children[old_index].corresponding_node =
582 new_children[new_index];
583 if (old_children[old_index].status != FunctionStatus.UNCHANGED) {
584 ProcessChildren(old_children[old_index],
585 new_children[new_index]);
586 if (old_children[old_index].status == FunctionStatus.DAMAGED) {
587 old_node.status = FunctionStatus.CHANGED;
588 }
589 }
590 } else {
591 old_children[old_index].status = FunctionStatus.DAMAGED;
592 old_children[old_index].status_explanation =
593 "No corresponding function in new script found";
594 old_node.status = FunctionStatus.CHANGED;
595 }
596 new_index++;
597 old_index++;
598 } else {
599 old_children[old_index].status = FunctionStatus.DAMAGED;
600 old_children[old_index].status_explanation =
601 "No corresponding function in new script found";
602 old_node.status = FunctionStatus.CHANGED;
603 old_index++;
604 }
605 } else {
606 old_children[old_index].status = FunctionStatus.DAMAGED;
607 old_children[old_index].status_explanation =
608 "No corresponding function in new script found";
609 old_node.status = FunctionStatus.CHANGED;
610 old_index++;
611 }
612 }
613
614 if (old_node.status == FunctionStatus.CHANGED) {
615 if (!CompareFunctionExpectations(old_node.info, new_node.info)) {
616 old_node.status = FunctionStatus.DAMAGED;
617 old_node.status_explanation = "Changed code expectations";
618 }
619 }
620 }
621
622 ProcessChildren(old_code_tree, new_code_tree);
623
624 old_code_tree.corresponding_node = new_code_tree;
625 Assert(old_code_tree.status != FunctionStatus.DAMAGED,
626 "Script became damaged");
627 }
628
335 629
336 // An object describing function compilation details. Its index fields 630 // An object describing function compilation details. Its index fields
337 // apply to indexes inside array that stores these objects. 631 // apply to indexes inside array that stores these objects.
338 function FunctionCompileInfo(raw_array) { 632 function FunctionCompileInfo(raw_array) {
339 this.function_name = raw_array[0]; 633 this.function_name = raw_array[0];
340 this.start_position = raw_array[1]; 634 this.start_position = raw_array[1];
341 this.end_position = raw_array[2]; 635 this.end_position = raw_array[2];
342 this.param_num = raw_array[3]; 636 this.param_num = raw_array[3];
343 this.code = raw_array[4]; 637 this.code = raw_array[4];
344 this.scope_info = raw_array[5]; 638 this.scope_info = raw_array[5];
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
462 // A testing entry. 756 // A testing entry.
463 function GetPcFromSourcePos(func, source_pos) { 757 function GetPcFromSourcePos(func, source_pos) {
464 return %GetFunctionCodePositionFromSource(func, source_pos); 758 return %GetFunctionCodePositionFromSource(func, source_pos);
465 } 759 }
466 // Function is public. 760 // Function is public.
467 this.GetPcFromSourcePos = GetPcFromSourcePos; 761 this.GetPcFromSourcePos = GetPcFromSourcePos;
468 762
469 // LiveEdit main entry point: changes a script text to a new string. 763 // LiveEdit main entry point: changes a script text to a new string.
470 function SetScriptSource(script, new_source, change_log) { 764 function SetScriptSource(script, new_source, change_log) {
471 var old_source = script.source; 765 var old_source = script.source;
472 var diff = FindSimpleDiff(old_source, new_source); 766 var diff = CompareStringsLinewise(old_source, new_source);
473 if (!diff) { 767 if (diff.length == 0) {
768 change_log.push( { empty_diff: true } );
474 return; 769 return;
475 } 770 }
476 ApplyPatch(script, diff.change_pos, diff.old_len, 771 ApplyPatchMultiChunk(script, diff, new_source, change_log);
477 new_source.substring(diff.change_pos, diff.change_pos + diff.new_len),
478 change_log);
479 } 772 }
480 // Function is public. 773 // Function is public.
481 this.SetScriptSource = SetScriptSource; 774 this.SetScriptSource = SetScriptSource;
482 775
483 function CompareStringsLinewise(s1, s2) { 776 function CompareStringsLinewise(s1, s2) {
484 return %LiveEditCompareStringsLinewise(s1, s2); 777 return %LiveEditCompareStringsLinewise(s1, s2);
485 } 778 }
486 // Function is public (for tests). 779 // Function is public (for tests).
487 this.CompareStringsLinewise = CompareStringsLinewise; 780 this.CompareStringsLinewise = CompareStringsLinewise;
488 781
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
544 } 837 }
545 838
546 if (old_len == 0 && new_len == 0) { 839 if (old_len == 0 && new_len == 0) {
547 // no change 840 // no change
548 return; 841 return;
549 } 842 }
550 843
551 return { "change_pos": change_pos, "old_len": old_len, "new_len": new_len }; 844 return { "change_pos": change_pos, "old_len": old_len, "new_len": new_len };
552 } 845 }
553 } 846 }
OLDNEW
« no previous file with comments | « src/liveedit.cc ('k') | src/runtime.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698