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

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

Issue 1265923002: Debugger: move implementation to a separate folder. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: rebase Created 5 years, 4 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/mips/assembler-mips-inl.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // LiveEdit feature implementation. The script should be executed after
6 // debug-debugger.js.
7
8 // A LiveEdit namespace. It contains functions that modifies JavaScript code
9 // according to changes of script source (if possible).
10 //
11 // When new script source is put in, the difference is calculated textually,
12 // in form of list of delete/add/change chunks. The functions that include
13 // change chunk(s) get recompiled, or their enclosing functions are
14 // recompiled instead.
15 // If the function may not be recompiled (e.g. it was completely erased in new
16 // version of the script) it remains unchanged, but the code that could
17 // create a new instance of this function goes away. An old version of script
18 // is created to back up this obsolete function.
19 // All unchanged functions have their positions updated accordingly.
20 //
21 // LiveEdit namespace is declared inside a single function constructor.
22
23 "use strict";
24
25 Debug.LiveEdit = new function() {
26
27 // Forward declaration for minifier.
28 var FunctionStatus;
29
30 // Applies the change to the script.
31 // The change is in form of list of chunks encoded in a single array as
32 // a series of triplets (pos1_start, pos1_end, pos2_end)
33 function ApplyPatchMultiChunk(script, diff_array, new_source, preview_only,
34 change_log) {
35
36 var old_source = script.source;
37
38 // Gather compile information about old version of script.
39 var old_compile_info = GatherCompileInfo(old_source, script);
40
41 // Build tree structures for old and new versions of the script.
42 var root_old_node = BuildCodeInfoTree(old_compile_info);
43
44 var pos_translator = new PosTranslator(diff_array);
45
46 // Analyze changes.
47 MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
48
49 // Find all SharedFunctionInfo's that were compiled from this script.
50 FindLiveSharedInfos(root_old_node, script);
51
52 // Gather compile information about new version of script.
53 var new_compile_info;
54 try {
55 new_compile_info = GatherCompileInfo(new_source, script);
56 } catch (e) {
57 var failure =
58 new Failure("Failed to compile new version of script: " + e);
59 if (e instanceof SyntaxError) {
60 var details = {
61 type: "liveedit_compile_error",
62 syntaxErrorMessage: e.message
63 };
64 CopyErrorPositionToDetails(e, details);
65 failure.details = details;
66 }
67 throw failure;
68 }
69 var root_new_node = BuildCodeInfoTree(new_compile_info);
70
71 // Link recompiled script data with other data.
72 FindCorrespondingFunctions(root_old_node, root_new_node);
73
74 // Prepare to-do lists.
75 var replace_code_list = new Array();
76 var link_to_old_script_list = new Array();
77 var link_to_original_script_list = new Array();
78 var update_positions_list = new Array();
79
80 function HarvestTodo(old_node) {
81 function CollectDamaged(node) {
82 link_to_old_script_list.push(node);
83 for (var i = 0; i < node.children.length; i++) {
84 CollectDamaged(node.children[i]);
85 }
86 }
87
88 // Recursively collects all newly compiled functions that are going into
89 // business and should have link to the actual script updated.
90 function CollectNew(node_list) {
91 for (var i = 0; i < node_list.length; i++) {
92 link_to_original_script_list.push(node_list[i]);
93 CollectNew(node_list[i].children);
94 }
95 }
96
97 if (old_node.status == FunctionStatus.DAMAGED) {
98 CollectDamaged(old_node);
99 return;
100 }
101 if (old_node.status == FunctionStatus.UNCHANGED) {
102 update_positions_list.push(old_node);
103 } else if (old_node.status == FunctionStatus.SOURCE_CHANGED) {
104 update_positions_list.push(old_node);
105 } else if (old_node.status == FunctionStatus.CHANGED) {
106 replace_code_list.push(old_node);
107 CollectNew(old_node.unmatched_new_nodes);
108 }
109 for (var i = 0; i < old_node.children.length; i++) {
110 HarvestTodo(old_node.children[i]);
111 }
112 }
113
114 var preview_description = {
115 change_tree: DescribeChangeTree(root_old_node),
116 textual_diff: {
117 old_len: old_source.length,
118 new_len: new_source.length,
119 chunks: diff_array
120 },
121 updated: false
122 };
123
124 if (preview_only) {
125 return preview_description;
126 }
127
128 HarvestTodo(root_old_node);
129
130 // Collect shared infos for functions whose code need to be patched.
131 var replaced_function_infos = new Array();
132 for (var i = 0; i < replace_code_list.length; i++) {
133 var live_shared_function_infos =
134 replace_code_list[i].live_shared_function_infos;
135
136 if (live_shared_function_infos) {
137 for (var j = 0; j < live_shared_function_infos.length; j++) {
138 replaced_function_infos.push(live_shared_function_infos[j]);
139 }
140 }
141 }
142
143 // We haven't changed anything before this line yet.
144 // Committing all changes.
145
146 // Check that function being patched is not currently on stack or drop them.
147 var dropped_functions_number =
148 CheckStackActivations(replaced_function_infos, change_log);
149
150 // Our current implementation requires client to manually issue "step in"
151 // command for correct stack state if the stack was modified.
152 preview_description.stack_modified = dropped_functions_number != 0;
153
154 // Start with breakpoints. Convert their line/column positions and
155 // temporary remove.
156 var break_points_restorer = TemporaryRemoveBreakPoints(script, change_log);
157
158 var old_script;
159
160 // Create an old script only if there are function that should be linked
161 // to old version.
162 if (link_to_old_script_list.length == 0) {
163 %LiveEditReplaceScript(script, new_source, null);
164 old_script = UNDEFINED;
165 } else {
166 var old_script_name = CreateNameForOldScript(script);
167
168 // Update the script text and create a new script representing an old
169 // version of the script.
170 old_script = %LiveEditReplaceScript(script, new_source,
171 old_script_name);
172
173 var link_to_old_script_report = new Array();
174 change_log.push( { linked_to_old_script: link_to_old_script_report } );
175
176 // We need to link to old script all former nested functions.
177 for (var i = 0; i < link_to_old_script_list.length; i++) {
178 LinkToOldScript(link_to_old_script_list[i], old_script,
179 link_to_old_script_report);
180 }
181
182 preview_description.created_script_name = old_script_name;
183 }
184
185 // Link to an actual script all the functions that we are going to use.
186 for (var i = 0; i < link_to_original_script_list.length; i++) {
187 %LiveEditFunctionSetScript(
188 link_to_original_script_list[i].info.shared_function_info, script);
189 }
190
191 for (var i = 0; i < replace_code_list.length; i++) {
192 PatchFunctionCode(replace_code_list[i], change_log);
193 }
194
195 var position_patch_report = new Array();
196 change_log.push( {position_patched: position_patch_report} );
197
198 for (var i = 0; i < update_positions_list.length; i++) {
199 // TODO(LiveEdit): take into account whether it's source_changed or
200 // unchanged and whether positions changed at all.
201 PatchPositions(update_positions_list[i], diff_array,
202 position_patch_report);
203
204 if (update_positions_list[i].live_shared_function_infos) {
205 update_positions_list[i].live_shared_function_infos.
206 forEach(function (info) {
207 %LiveEditFunctionSourceUpdated(info.raw_array);
208 });
209 }
210 }
211
212 break_points_restorer(pos_translator, old_script);
213
214 preview_description.updated = true;
215 return preview_description;
216 }
217 // Function is public.
218 this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
219
220
221 // Fully compiles source string as a script. Returns Array of
222 // FunctionCompileInfo -- a descriptions of all functions of the script.
223 // Elements of array are ordered by start positions of functions (from top
224 // to bottom) in the source. Fields outer_index and next_sibling_index help
225 // to navigate the nesting structure of functions.
226 //
227 // All functions get compiled linked to script provided as parameter script.
228 // TODO(LiveEdit): consider not using actual scripts as script, because
229 // we have to manually erase all links right after compile.
230 function GatherCompileInfo(source, script) {
231 // Get function info, elements are partially sorted (it is a tree of
232 // nested functions serialized as parent followed by serialized children.
233 var raw_compile_info = %LiveEditGatherCompileInfo(script, source);
234
235 // Sort function infos by start position field.
236 var compile_info = new Array();
237 var old_index_map = new Array();
238 for (var i = 0; i < raw_compile_info.length; i++) {
239 var info = new FunctionCompileInfo(raw_compile_info[i]);
240 // Remove all links to the actual script. Breakpoints system and
241 // LiveEdit itself believe that any function in heap that points to a
242 // particular script is a regular function.
243 // For some functions we will restore this link later.
244 %LiveEditFunctionSetScript(info.shared_function_info, UNDEFINED);
245 compile_info.push(info);
246 old_index_map.push(i);
247 }
248
249 for (var i = 0; i < compile_info.length; i++) {
250 var k = i;
251 for (var j = i + 1; j < compile_info.length; j++) {
252 if (compile_info[k].start_position > compile_info[j].start_position) {
253 k = j;
254 }
255 }
256 if (k != i) {
257 var temp_info = compile_info[k];
258 var temp_index = old_index_map[k];
259 compile_info[k] = compile_info[i];
260 old_index_map[k] = old_index_map[i];
261 compile_info[i] = temp_info;
262 old_index_map[i] = temp_index;
263 }
264 }
265
266 // After sorting update outer_index field using old_index_map. Also
267 // set next_sibling_index field.
268 var current_index = 0;
269
270 // The recursive function, that goes over all children of a particular
271 // node (i.e. function info).
272 function ResetIndexes(new_parent_index, old_parent_index) {
273 var previous_sibling = -1;
274 while (current_index < compile_info.length &&
275 compile_info[current_index].outer_index == old_parent_index) {
276 var saved_index = current_index;
277 compile_info[saved_index].outer_index = new_parent_index;
278 if (previous_sibling != -1) {
279 compile_info[previous_sibling].next_sibling_index = saved_index;
280 }
281 previous_sibling = saved_index;
282 current_index++;
283 ResetIndexes(saved_index, old_index_map[saved_index]);
284 }
285 if (previous_sibling != -1) {
286 compile_info[previous_sibling].next_sibling_index = -1;
287 }
288 }
289
290 ResetIndexes(-1, -1);
291 Assert(current_index == compile_info.length);
292
293 return compile_info;
294 }
295
296
297 // Replaces function's Code.
298 function PatchFunctionCode(old_node, change_log) {
299 var new_info = old_node.corresponding_node.info;
300 if (old_node.live_shared_function_infos) {
301 old_node.live_shared_function_infos.forEach(function (old_info) {
302 %LiveEditReplaceFunctionCode(new_info.raw_array,
303 old_info.raw_array);
304
305 // The function got a new code. However, this new code brings all new
306 // instances of SharedFunctionInfo for nested functions. However,
307 // we want the original instances to be used wherever possible.
308 // (This is because old instances and new instances will be both
309 // linked to a script and breakpoints subsystem does not really
310 // expects this; neither does LiveEdit subsystem on next call).
311 for (var i = 0; i < old_node.children.length; i++) {
312 if (old_node.children[i].corresponding_node) {
313 var corresponding_child_info =
314 old_node.children[i].corresponding_node.info.
315 shared_function_info;
316
317 if (old_node.children[i].live_shared_function_infos) {
318 old_node.children[i].live_shared_function_infos.
319 forEach(function (old_child_info) {
320 %LiveEditReplaceRefToNestedFunction(
321 old_info.info,
322 corresponding_child_info,
323 old_child_info.info);
324 });
325 }
326 }
327 }
328 });
329
330 change_log.push( {function_patched: new_info.function_name} );
331 } else {
332 change_log.push( {function_patched: new_info.function_name,
333 function_info_not_found: true} );
334 }
335 }
336
337
338 // Makes a function associated with another instance of a script (the
339 // one representing its old version). This way the function still
340 // may access its own text.
341 function LinkToOldScript(old_info_node, old_script, report_array) {
342 if (old_info_node.live_shared_function_infos) {
343 old_info_node.live_shared_function_infos.
344 forEach(function (info) {
345 %LiveEditFunctionSetScript(info.info, old_script);
346 });
347
348 report_array.push( { name: old_info_node.info.function_name } );
349 } else {
350 report_array.push(
351 { name: old_info_node.info.function_name, not_found: true } );
352 }
353 }
354
355
356 // Returns function that restores breakpoints.
357 function TemporaryRemoveBreakPoints(original_script, change_log) {
358 var script_break_points = GetScriptBreakPoints(original_script);
359
360 var break_points_update_report = [];
361 change_log.push( { break_points_update: break_points_update_report } );
362
363 var break_point_old_positions = [];
364 for (var i = 0; i < script_break_points.length; i++) {
365 var break_point = script_break_points[i];
366
367 break_point.clear();
368
369 // TODO(LiveEdit): be careful with resource offset here.
370 var break_point_position = Debug.findScriptSourcePosition(original_script,
371 break_point.line(), break_point.column());
372
373 var old_position_description = {
374 position: break_point_position,
375 line: break_point.line(),
376 column: break_point.column()
377 };
378 break_point_old_positions.push(old_position_description);
379 }
380
381
382 // Restores breakpoints and creates their copies in the "old" copy of
383 // the script.
384 return function (pos_translator, old_script_copy_opt) {
385 // Update breakpoints (change positions and restore them in old version
386 // of script.
387 for (var i = 0; i < script_break_points.length; i++) {
388 var break_point = script_break_points[i];
389 if (old_script_copy_opt) {
390 var clone = break_point.cloneForOtherScript(old_script_copy_opt);
391 clone.set(old_script_copy_opt);
392
393 break_points_update_report.push( {
394 type: "copied_to_old",
395 id: break_point.number(),
396 new_id: clone.number(),
397 positions: break_point_old_positions[i]
398 } );
399 }
400
401 var updated_position = pos_translator.Translate(
402 break_point_old_positions[i].position,
403 PosTranslator.ShiftWithTopInsideChunkHandler);
404
405 var new_location =
406 original_script.locationFromPosition(updated_position, false);
407
408 break_point.update_positions(new_location.line, new_location.column);
409
410 var new_position_description = {
411 position: updated_position,
412 line: new_location.line,
413 column: new_location.column
414 };
415
416 break_point.set(original_script);
417
418 break_points_update_report.push( { type: "position_changed",
419 id: break_point.number(),
420 old_positions: break_point_old_positions[i],
421 new_positions: new_position_description
422 } );
423 }
424 };
425 }
426
427
428 function Assert(condition, message) {
429 if (!condition) {
430 if (message) {
431 throw "Assert " + message;
432 } else {
433 throw "Assert";
434 }
435 }
436 }
437
438 function DiffChunk(pos1, pos2, len1, len2) {
439 this.pos1 = pos1;
440 this.pos2 = pos2;
441 this.len1 = len1;
442 this.len2 = len2;
443 }
444
445 function PosTranslator(diff_array) {
446 var chunks = new Array();
447 var current_diff = 0;
448 for (var i = 0; i < diff_array.length; i += 3) {
449 var pos1_begin = diff_array[i];
450 var pos2_begin = pos1_begin + current_diff;
451 var pos1_end = diff_array[i + 1];
452 var pos2_end = diff_array[i + 2];
453 chunks.push(new DiffChunk(pos1_begin, pos2_begin, pos1_end - pos1_begin,
454 pos2_end - pos2_begin));
455 current_diff = pos2_end - pos1_end;
456 }
457 this.chunks = chunks;
458 }
459 PosTranslator.prototype.GetChunks = function() {
460 return this.chunks;
461 };
462
463 PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
464 var array = this.chunks;
465 if (array.length == 0 || pos < array[0].pos1) {
466 return pos;
467 }
468 var chunk_index1 = 0;
469 var chunk_index2 = array.length - 1;
470
471 while (chunk_index1 < chunk_index2) {
472 var middle_index = Math.floor((chunk_index1 + chunk_index2) / 2);
473 if (pos < array[middle_index + 1].pos1) {
474 chunk_index2 = middle_index;
475 } else {
476 chunk_index1 = middle_index + 1;
477 }
478 }
479 var chunk = array[chunk_index1];
480 if (pos >= chunk.pos1 + chunk.len1) {
481 return pos + chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
482 }
483
484 if (!inside_chunk_handler) {
485 inside_chunk_handler = PosTranslator.DefaultInsideChunkHandler;
486 }
487 return inside_chunk_handler(pos, chunk);
488 };
489
490 PosTranslator.DefaultInsideChunkHandler = function(pos, diff_chunk) {
491 Assert(false, "Cannot translate position in changed area");
492 };
493
494 PosTranslator.ShiftWithTopInsideChunkHandler =
495 function(pos, diff_chunk) {
496 // We carelessly do not check whether we stay inside the chunk after
497 // translation.
498 return pos - diff_chunk.pos1 + diff_chunk.pos2;
499 };
500
501 var FunctionStatus = {
502 // No change to function or its inner functions; however its positions
503 // in script may have been shifted.
504 UNCHANGED: "unchanged",
505 // The code of a function remains unchanged, but something happened inside
506 // some inner functions.
507 SOURCE_CHANGED: "source changed",
508 // The code of a function is changed or some nested function cannot be
509 // properly patched so this function must be recompiled.
510 CHANGED: "changed",
511 // Function is changed but cannot be patched.
512 DAMAGED: "damaged"
513 };
514
515 function CodeInfoTreeNode(code_info, children, array_index) {
516 this.info = code_info;
517 this.children = children;
518 // an index in array of compile_info
519 this.array_index = array_index;
520 this.parent = UNDEFINED;
521
522 this.status = FunctionStatus.UNCHANGED;
523 // Status explanation is used for debugging purposes and will be shown
524 // in user UI if some explanations are needed.
525 this.status_explanation = UNDEFINED;
526 this.new_start_pos = UNDEFINED;
527 this.new_end_pos = UNDEFINED;
528 this.corresponding_node = UNDEFINED;
529 this.unmatched_new_nodes = UNDEFINED;
530
531 // 'Textual' correspondence/matching is weaker than 'pure'
532 // correspondence/matching. We need 'textual' level for visual presentation
533 // in UI, we use 'pure' level for actual code manipulation.
534 // Sometimes only function body is changed (functions in old and new script
535 // textually correspond), but we cannot patch the code, so we see them
536 // as an old function deleted and new function created.
537 this.textual_corresponding_node = UNDEFINED;
538 this.textually_unmatched_new_nodes = UNDEFINED;
539
540 this.live_shared_function_infos = UNDEFINED;
541 }
542
543 // From array of function infos that is implicitly a tree creates
544 // an actual tree of functions in script.
545 function BuildCodeInfoTree(code_info_array) {
546 // Throughtout all function we iterate over input array.
547 var index = 0;
548
549 // Recursive function that builds a branch of tree.
550 function BuildNode() {
551 var my_index = index;
552 index++;
553 var child_array = new Array();
554 while (index < code_info_array.length &&
555 code_info_array[index].outer_index == my_index) {
556 child_array.push(BuildNode());
557 }
558 var node = new CodeInfoTreeNode(code_info_array[my_index], child_array,
559 my_index);
560 for (var i = 0; i < child_array.length; i++) {
561 child_array[i].parent = node;
562 }
563 return node;
564 }
565
566 var root = BuildNode();
567 Assert(index == code_info_array.length);
568 return root;
569 }
570
571 // Applies a list of the textual diff chunks onto the tree of functions.
572 // Determines status of each function (from unchanged to damaged). However
573 // children of unchanged functions are ignored.
574 function MarkChangedFunctions(code_info_tree, chunks) {
575
576 // A convenient iterator over diff chunks that also translates
577 // positions from old to new in a current non-changed part of script.
578 var chunk_it = new function() {
579 var chunk_index = 0;
580 var pos_diff = 0;
581 this.current = function() { return chunks[chunk_index]; };
582 this.next = function() {
583 var chunk = chunks[chunk_index];
584 pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1);
585 chunk_index++;
586 };
587 this.done = function() { return chunk_index >= chunks.length; };
588 this.TranslatePos = function(pos) { return pos + pos_diff; };
589 };
590
591 // A recursive function that processes internals of a function and all its
592 // inner functions. Iterator chunk_it initially points to a chunk that is
593 // below function start.
594 function ProcessInternals(info_node) {
595 info_node.new_start_pos = chunk_it.TranslatePos(
596 info_node.info.start_position);
597 var child_index = 0;
598 var code_changed = false;
599 var source_changed = false;
600 // Simultaneously iterates over child functions and over chunks.
601 while (!chunk_it.done() &&
602 chunk_it.current().pos1 < info_node.info.end_position) {
603 if (child_index < info_node.children.length) {
604 var child = info_node.children[child_index];
605
606 if (child.info.end_position <= chunk_it.current().pos1) {
607 ProcessUnchangedChild(child);
608 child_index++;
609 continue;
610 } else if (child.info.start_position >=
611 chunk_it.current().pos1 + chunk_it.current().len1) {
612 code_changed = true;
613 chunk_it.next();
614 continue;
615 } else if (child.info.start_position <= chunk_it.current().pos1 &&
616 child.info.end_position >= chunk_it.current().pos1 +
617 chunk_it.current().len1) {
618 ProcessInternals(child);
619 source_changed = source_changed ||
620 ( child.status != FunctionStatus.UNCHANGED );
621 code_changed = code_changed ||
622 ( child.status == FunctionStatus.DAMAGED );
623 child_index++;
624 continue;
625 } else {
626 code_changed = true;
627 child.status = FunctionStatus.DAMAGED;
628 child.status_explanation =
629 "Text diff overlaps with function boundary";
630 child_index++;
631 continue;
632 }
633 } else {
634 if (chunk_it.current().pos1 + chunk_it.current().len1 <=
635 info_node.info.end_position) {
636 info_node.status = FunctionStatus.CHANGED;
637 chunk_it.next();
638 continue;
639 } else {
640 info_node.status = FunctionStatus.DAMAGED;
641 info_node.status_explanation =
642 "Text diff overlaps with function boundary";
643 return;
644 }
645 }
646 Assert("Unreachable", false);
647 }
648 while (child_index < info_node.children.length) {
649 var child = info_node.children[child_index];
650 ProcessUnchangedChild(child);
651 child_index++;
652 }
653 if (code_changed) {
654 info_node.status = FunctionStatus.CHANGED;
655 } else if (source_changed) {
656 info_node.status = FunctionStatus.SOURCE_CHANGED;
657 }
658 info_node.new_end_pos =
659 chunk_it.TranslatePos(info_node.info.end_position);
660 }
661
662 function ProcessUnchangedChild(node) {
663 node.new_start_pos = chunk_it.TranslatePos(node.info.start_position);
664 node.new_end_pos = chunk_it.TranslatePos(node.info.end_position);
665 }
666
667 ProcessInternals(code_info_tree);
668 }
669
670 // For each old function (if it is not damaged) tries to find a corresponding
671 // function in new script. Typically it should succeed (non-damaged functions
672 // by definition may only have changes inside their bodies). However there are
673 // reasons for correspondence not to be found; function with unmodified text
674 // in new script may become enclosed into other function; the innocent change
675 // inside function body may in fact be something like "} function B() {" that
676 // splits a function into 2 functions.
677 function FindCorrespondingFunctions(old_code_tree, new_code_tree) {
678
679 // A recursive function that tries to find a correspondence for all
680 // child functions and for their inner functions.
681 function ProcessNode(old_node, new_node) {
682 var scope_change_description =
683 IsFunctionContextLocalsChanged(old_node.info, new_node.info);
684 if (scope_change_description) {
685 old_node.status = FunctionStatus.CHANGED;
686 }
687
688 var old_children = old_node.children;
689 var new_children = new_node.children;
690
691 var unmatched_new_nodes_list = [];
692 var textually_unmatched_new_nodes_list = [];
693
694 var old_index = 0;
695 var new_index = 0;
696 while (old_index < old_children.length) {
697 if (old_children[old_index].status == FunctionStatus.DAMAGED) {
698 old_index++;
699 } else if (new_index < new_children.length) {
700 if (new_children[new_index].info.start_position <
701 old_children[old_index].new_start_pos) {
702 unmatched_new_nodes_list.push(new_children[new_index]);
703 textually_unmatched_new_nodes_list.push(new_children[new_index]);
704 new_index++;
705 } else if (new_children[new_index].info.start_position ==
706 old_children[old_index].new_start_pos) {
707 if (new_children[new_index].info.end_position ==
708 old_children[old_index].new_end_pos) {
709 old_children[old_index].corresponding_node =
710 new_children[new_index];
711 old_children[old_index].textual_corresponding_node =
712 new_children[new_index];
713 if (scope_change_description) {
714 old_children[old_index].status = FunctionStatus.DAMAGED;
715 old_children[old_index].status_explanation =
716 "Enclosing function is now incompatible. " +
717 scope_change_description;
718 old_children[old_index].corresponding_node = UNDEFINED;
719 } else if (old_children[old_index].status !=
720 FunctionStatus.UNCHANGED) {
721 ProcessNode(old_children[old_index],
722 new_children[new_index]);
723 if (old_children[old_index].status == FunctionStatus.DAMAGED) {
724 unmatched_new_nodes_list.push(
725 old_children[old_index].corresponding_node);
726 old_children[old_index].corresponding_node = UNDEFINED;
727 old_node.status = FunctionStatus.CHANGED;
728 }
729 }
730 } else {
731 old_children[old_index].status = FunctionStatus.DAMAGED;
732 old_children[old_index].status_explanation =
733 "No corresponding function in new script found";
734 old_node.status = FunctionStatus.CHANGED;
735 unmatched_new_nodes_list.push(new_children[new_index]);
736 textually_unmatched_new_nodes_list.push(new_children[new_index]);
737 }
738 new_index++;
739 old_index++;
740 } else {
741 old_children[old_index].status = FunctionStatus.DAMAGED;
742 old_children[old_index].status_explanation =
743 "No corresponding function in new script found";
744 old_node.status = FunctionStatus.CHANGED;
745 old_index++;
746 }
747 } else {
748 old_children[old_index].status = FunctionStatus.DAMAGED;
749 old_children[old_index].status_explanation =
750 "No corresponding function in new script found";
751 old_node.status = FunctionStatus.CHANGED;
752 old_index++;
753 }
754 }
755
756 while (new_index < new_children.length) {
757 unmatched_new_nodes_list.push(new_children[new_index]);
758 textually_unmatched_new_nodes_list.push(new_children[new_index]);
759 new_index++;
760 }
761
762 if (old_node.status == FunctionStatus.CHANGED) {
763 if (old_node.info.param_num != new_node.info.param_num) {
764 old_node.status = FunctionStatus.DAMAGED;
765 old_node.status_explanation = "Changed parameter number: " +
766 old_node.info.param_num + " and " + new_node.info.param_num;
767 }
768 }
769 old_node.unmatched_new_nodes = unmatched_new_nodes_list;
770 old_node.textually_unmatched_new_nodes =
771 textually_unmatched_new_nodes_list;
772 }
773
774 ProcessNode(old_code_tree, new_code_tree);
775
776 old_code_tree.corresponding_node = new_code_tree;
777 old_code_tree.textual_corresponding_node = new_code_tree;
778
779 Assert(old_code_tree.status != FunctionStatus.DAMAGED,
780 "Script became damaged");
781 }
782
783 function FindLiveSharedInfos(old_code_tree, script) {
784 var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
785
786 var shared_infos = new Array();
787
788 for (var i = 0; i < shared_raw_list.length; i++) {
789 shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
790 }
791
792 // Finds all SharedFunctionInfos that corresponds to compile info
793 // in old version of the script.
794 function FindFunctionInfos(compile_info) {
795 var wrappers = [];
796
797 for (var i = 0; i < shared_infos.length; i++) {
798 var wrapper = shared_infos[i];
799 if (wrapper.start_position == compile_info.start_position &&
800 wrapper.end_position == compile_info.end_position) {
801 wrappers.push(wrapper);
802 }
803 }
804
805 if (wrappers.length > 0) {
806 return wrappers;
807 }
808 }
809
810 function TraverseTree(node) {
811 node.live_shared_function_infos = FindFunctionInfos(node.info);
812
813 for (var i = 0; i < node.children.length; i++) {
814 TraverseTree(node.children[i]);
815 }
816 }
817
818 TraverseTree(old_code_tree);
819 }
820
821
822 // An object describing function compilation details. Its index fields
823 // apply to indexes inside array that stores these objects.
824 function FunctionCompileInfo(raw_array) {
825 this.function_name = raw_array[0];
826 this.start_position = raw_array[1];
827 this.end_position = raw_array[2];
828 this.param_num = raw_array[3];
829 this.code = raw_array[4];
830 this.code_scope_info = raw_array[5];
831 this.scope_info = raw_array[6];
832 this.outer_index = raw_array[7];
833 this.shared_function_info = raw_array[8];
834 this.next_sibling_index = null;
835 this.raw_array = raw_array;
836 }
837
838 function SharedInfoWrapper(raw_array) {
839 this.function_name = raw_array[0];
840 this.start_position = raw_array[1];
841 this.end_position = raw_array[2];
842 this.info = raw_array[3];
843 this.raw_array = raw_array;
844 }
845
846 // Changes positions (including all statements) in function.
847 function PatchPositions(old_info_node, diff_array, report_array) {
848 if (old_info_node.live_shared_function_infos) {
849 old_info_node.live_shared_function_infos.forEach(function (info) {
850 %LiveEditPatchFunctionPositions(info.raw_array,
851 diff_array);
852 });
853
854 report_array.push( { name: old_info_node.info.function_name } );
855 } else {
856 // TODO(LiveEdit): function is not compiled yet or is already collected.
857 report_array.push(
858 { name: old_info_node.info.function_name, info_not_found: true } );
859 }
860 }
861
862 // Adds a suffix to script name to mark that it is old version.
863 function CreateNameForOldScript(script) {
864 // TODO(635): try better than this; support several changes.
865 return script.name + " (old)";
866 }
867
868 // Compares a function scope heap structure, old and new version, whether it
869 // changed or not. Returns explanation if they differ.
870 function IsFunctionContextLocalsChanged(function_info1, function_info2) {
871 var scope_info1 = function_info1.scope_info;
872 var scope_info2 = function_info2.scope_info;
873
874 var scope_info1_text;
875 var scope_info2_text;
876
877 if (scope_info1) {
878 scope_info1_text = scope_info1.toString();
879 } else {
880 scope_info1_text = "";
881 }
882 if (scope_info2) {
883 scope_info2_text = scope_info2.toString();
884 } else {
885 scope_info2_text = "";
886 }
887
888 if (scope_info1_text != scope_info2_text) {
889 return "Variable map changed: [" + scope_info1_text +
890 "] => [" + scope_info2_text + "]";
891 }
892 // No differences. Return undefined.
893 return;
894 }
895
896 // Minifier forward declaration.
897 var FunctionPatchabilityStatus;
898
899 // For array of wrapped shared function infos checks that none of them
900 // have activations on stack (of any thread). Throws a Failure exception
901 // if this proves to be false.
902 function CheckStackActivations(shared_wrapper_list, change_log) {
903 var shared_list = new Array();
904 for (var i = 0; i < shared_wrapper_list.length; i++) {
905 shared_list[i] = shared_wrapper_list[i].info;
906 }
907 var result = %LiveEditCheckAndDropActivations(shared_list, true);
908 if (result[shared_list.length]) {
909 // Extra array element may contain error message.
910 throw new Failure(result[shared_list.length]);
911 }
912
913 var problems = new Array();
914 var dropped = new Array();
915 for (var i = 0; i < shared_list.length; i++) {
916 var shared = shared_wrapper_list[i];
917 if (result[i] == FunctionPatchabilityStatus.REPLACED_ON_ACTIVE_STACK) {
918 dropped.push({ name: shared.function_name } );
919 } else if (result[i] != FunctionPatchabilityStatus.AVAILABLE_FOR_PATCH) {
920 var description = {
921 name: shared.function_name,
922 start_pos: shared.start_position,
923 end_pos: shared.end_position,
924 replace_problem:
925 FunctionPatchabilityStatus.SymbolName(result[i])
926 };
927 problems.push(description);
928 }
929 }
930 if (dropped.length > 0) {
931 change_log.push({ dropped_from_stack: dropped });
932 }
933 if (problems.length > 0) {
934 change_log.push( { functions_on_stack: problems } );
935 throw new Failure("Blocked by functions on stack");
936 }
937
938 return dropped.length;
939 }
940
941 // A copy of the FunctionPatchabilityStatus enum from liveedit.h
942 var FunctionPatchabilityStatus = {
943 AVAILABLE_FOR_PATCH: 1,
944 BLOCKED_ON_ACTIVE_STACK: 2,
945 BLOCKED_ON_OTHER_STACK: 3,
946 BLOCKED_UNDER_NATIVE_CODE: 4,
947 REPLACED_ON_ACTIVE_STACK: 5,
948 BLOCKED_UNDER_GENERATOR: 6,
949 BLOCKED_ACTIVE_GENERATOR: 7
950 };
951
952 FunctionPatchabilityStatus.SymbolName = function(code) {
953 var enumeration = FunctionPatchabilityStatus;
954 for (var name in enumeration) {
955 if (enumeration[name] == code) {
956 return name;
957 }
958 }
959 };
960
961
962 // A logical failure in liveedit process. This means that change_log
963 // is valid and consistent description of what happened.
964 function Failure(message) {
965 this.message = message;
966 }
967 // Function (constructor) is public.
968 this.Failure = Failure;
969
970 Failure.prototype.toString = function() {
971 return "LiveEdit Failure: " + this.message;
972 };
973
974 function CopyErrorPositionToDetails(e, details) {
975 function createPositionStruct(script, position) {
976 if (position == -1) return;
977 var location = script.locationFromPosition(position, true);
978 if (location == null) return;
979 return {
980 line: location.line + 1,
981 column: location.column + 1,
982 position: position
983 };
984 }
985
986 if (!("scriptObject" in e) || !("startPosition" in e)) {
987 return;
988 }
989
990 var script = e.scriptObject;
991
992 var position_struct = {
993 start: createPositionStruct(script, e.startPosition),
994 end: createPositionStruct(script, e.endPosition)
995 };
996 details.position = position_struct;
997 }
998
999 // A testing entry.
1000 function GetPcFromSourcePos(func, source_pos) {
1001 return %GetFunctionCodePositionFromSource(func, source_pos);
1002 }
1003 // Function is public.
1004 this.GetPcFromSourcePos = GetPcFromSourcePos;
1005
1006 // LiveEdit main entry point: changes a script text to a new string.
1007 function SetScriptSource(script, new_source, preview_only, change_log) {
1008 var old_source = script.source;
1009 var diff = CompareStrings(old_source, new_source);
1010 return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
1011 change_log);
1012 }
1013 // Function is public.
1014 this.SetScriptSource = SetScriptSource;
1015
1016 function CompareStrings(s1, s2) {
1017 return %LiveEditCompareStrings(s1, s2);
1018 }
1019
1020 // Applies the change to the script.
1021 // The change is always a substring (change_pos, change_pos + change_len)
1022 // being replaced with a completely different string new_str.
1023 // This API is a legacy and is obsolete.
1024 //
1025 // @param {Script} script that is being changed
1026 // @param {Array} change_log a list that collects engineer-readable
1027 // description of what happened.
1028 function ApplySingleChunkPatch(script, change_pos, change_len, new_str,
1029 change_log) {
1030 var old_source = script.source;
1031
1032 // Prepare new source string.
1033 var new_source = old_source.substring(0, change_pos) +
1034 new_str + old_source.substring(change_pos + change_len);
1035
1036 return ApplyPatchMultiChunk(script,
1037 [ change_pos, change_pos + change_len, change_pos + new_str.length],
1038 new_source, false, change_log);
1039 }
1040
1041 // Creates JSON description for a change tree.
1042 function DescribeChangeTree(old_code_tree) {
1043
1044 function ProcessOldNode(node) {
1045 var child_infos = [];
1046 for (var i = 0; i < node.children.length; i++) {
1047 var child = node.children[i];
1048 if (child.status != FunctionStatus.UNCHANGED) {
1049 child_infos.push(ProcessOldNode(child));
1050 }
1051 }
1052 var new_child_infos = [];
1053 if (node.textually_unmatched_new_nodes) {
1054 for (var i = 0; i < node.textually_unmatched_new_nodes.length; i++) {
1055 var child = node.textually_unmatched_new_nodes[i];
1056 new_child_infos.push(ProcessNewNode(child));
1057 }
1058 }
1059 var res = {
1060 name: node.info.function_name,
1061 positions: DescribePositions(node),
1062 status: node.status,
1063 children: child_infos,
1064 new_children: new_child_infos
1065 };
1066 if (node.status_explanation) {
1067 res.status_explanation = node.status_explanation;
1068 }
1069 if (node.textual_corresponding_node) {
1070 res.new_positions = DescribePositions(node.textual_corresponding_node);
1071 }
1072 return res;
1073 }
1074
1075 function ProcessNewNode(node) {
1076 var child_infos = [];
1077 // Do not list ancestors.
1078 if (false) {
1079 for (var i = 0; i < node.children.length; i++) {
1080 child_infos.push(ProcessNewNode(node.children[i]));
1081 }
1082 }
1083 var res = {
1084 name: node.info.function_name,
1085 positions: DescribePositions(node),
1086 children: child_infos,
1087 };
1088 return res;
1089 }
1090
1091 function DescribePositions(node) {
1092 return {
1093 start_position: node.info.start_position,
1094 end_position: node.info.end_position
1095 };
1096 }
1097
1098 return ProcessOldNode(old_code_tree);
1099 }
1100
1101 // Functions are public for tests.
1102 this.TestApi = {
1103 PosTranslator: PosTranslator,
1104 CompareStrings: CompareStrings,
1105 ApplySingleChunkPatch: ApplySingleChunkPatch
1106 };
1107 };
OLDNEW
« no previous file with comments | « src/liveedit.cc ('k') | src/mips/assembler-mips-inl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698