1// Copyright 2012 the V8 project authors. All rights reserved.
2// 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
29// debug-debugger.js.
30
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.
45Debug.LiveEdit = new function() {
46
47  // Forward declaration for minifier.
48  var FunctionStatus;
49
50  var NEEDS_STEP_IN_PROPERTY_NAME = "stack_update_needs_step_in";
51
52  // 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)
55  function ApplyPatchMultiChunk(script, diff_array, new_source, preview_only,
56      change_log) {
57
58    var old_source = script.source;
59
60    // Gather compile information about old version of script.
61    var old_compile_info = GatherCompileInfo(old_source, script);
62
63    // Build tree structures for old and new versions of the script.
64    var root_old_node = BuildCodeInfoTree(old_compile_info);
65
66    var pos_translator = new PosTranslator(diff_array);
67
68    // Analyze changes.
69    MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
70
71    // Find all SharedFunctionInfo's that were compiled from this script.
72    FindLiveSharedInfos(root_old_node, script);
73
74    // 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) {
79      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;
90    }
91    var root_new_node = BuildCodeInfoTree(new_compile_info);
92
93    // Link recompiled script data with other data.
94    FindCorrespondingFunctions(root_old_node, root_new_node);
95
96    // Prepare to-do lists.
97    var replace_code_list = new Array();
98    var link_to_old_script_list = new Array();
99    var link_to_original_script_list = new Array();
100    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      }
109
110      // Recursively collects all newly compiled functions that are going into
111      // business and should have link to the actual script updated.
112      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      }
118
119      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);
129        CollectNew(old_node.unmatched_new_nodes);
130      }
131      for (var i = 0; i < old_node.children.length; i++) {
132        HarvestTodo(old_node.children[i]);
133      }
134    }
135
136    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    };
145
146    if (preview_only) {
147      return preview_description;
148    }
149
150    HarvestTodo(root_old_node);
151
152    // 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++) {
155      var live_shared_function_infos =
156          replace_code_list[i].live_shared_function_infos;
157
158      if (live_shared_function_infos) {
159        for (var j = 0; j < live_shared_function_infos.length; j++) {
160          replaced_function_infos.push(live_shared_function_infos[j]);
161        }
162      }
163    }
164
165    // We haven't changed anything before this line yet.
166    // Committing all changes.
167
168    // 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);
171
172    preview_description.stack_modified = dropped_functions_number != 0;
173
174    // Our current implementation requires client to manually issue "step in"
175    // command for correct stack state.
176    preview_description[NEEDS_STEP_IN_PROPERTY_NAME] =
177        preview_description.stack_modified;
178
179    // Start with breakpoints. Convert their line/column positions and
180    // temporary remove.
181    var break_points_restorer = TemporaryRemoveBreakPoints(script, change_log);
182
183    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 {
191      var old_script_name = CreateNameForOldScript(script);
192
193      // Update the script text and create a new script representing an old
194      // version of the script.
195      old_script = %LiveEditReplaceScript(script, new_source,
196          old_script_name);
197
198      var link_to_old_script_report = new Array();
199      change_log.push( { linked_to_old_script: link_to_old_script_report } );
200
201      // We need to link to old script all former nested functions.
202      for (var i = 0; i < link_to_old_script_list.length; i++) {
203        LinkToOldScript(link_to_old_script_list[i], old_script,
204            link_to_old_script_report);
205      }
206
207      preview_description.created_script_name = old_script_name;
208    }
209
210    // 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    }
215
216    for (var i = 0; i < replace_code_list.length; i++) {
217      PatchFunctionCode(replace_code_list[i], change_log);
218    }
219
220    var position_patch_report = new Array();
221    change_log.push( {position_patched: position_patch_report} );
222
223    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.
226      PatchPositions(update_positions_list[i], diff_array,
227          position_patch_report);
228
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      }
235    }
236
237    break_points_restorer(pos_translator, old_script);
238
239    preview_description.updated = true;
240    return preview_description;
241  }
242  // Function is public.
243  this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
244
245
246  // 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
254  // we have to manually erase all links right after compile.
255  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
321
322  // Replaces function's Code.
323  function PatchFunctionCode(old_node, change_log) {
324    var new_info = old_node.corresponding_node.info;
325    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);
329
330        // 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) {
345                    %LiveEditReplaceRefToNestedFunction(
346                        old_info.info,
347                        corresponding_child_info,
348                        old_child_info.info);
349                  });
350            }
351          }
352        }
353      });
354
355      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
362
363  // 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) {
367    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 } );
374    } else {
375      report_array.push(
376          { name: old_info_node.info.function_name, not_found: true } );
377    }
378  }
379
380
381  // Returns function that restores breakpoints.
382  function TemporaryRemoveBreakPoints(original_script, change_log) {
383    var script_break_points = GetScriptBreakPoints(original_script);
384
385    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();
393
394      // TODO(LiveEdit): be careful with resource offset here.
395      var break_point_position = Debug.findScriptSourcePosition(original_script,
396          break_point.line(), break_point.column());
397
398      var old_position_description = {
399          position: break_point_position,
400          line: break_point.line(),
401          column: break_point.column()
402      };
403      break_point_old_positions.push(old_position_description);
404    }
405
406
407    // 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);
417
418          break_points_update_report.push( {
419            type: "copied_to_old",
420            id: break_point.number(),
421            new_id: clone.number(),
422            positions: break_point_old_positions[i]
423            } );
424        }
425
426        var updated_position = pos_translator.Translate(
427            break_point_old_positions[i].position,
428            PosTranslator.ShiftWithTopInsideChunkHandler);
429
430        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
439        };
440
441        break_point.set(original_script);
442
443        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      }
449    };
450  }
451
452
453  function Assert(condition, message) {
454    if (!condition) {
455      if (message) {
456        throw "Assert " + message;
457      } else {
458        throw "Assert";
459      }
460    }
461  }
462
463  function DiffChunk(pos1, pos2, len1, len2) {
464    this.pos1 = pos1;
465    this.pos2 = pos2;
466    this.len1 = len1;
467    this.len2 = len2;
468  }
469
470  function PosTranslator(diff_array) {
471    var chunks = new Array();
472    var current_diff = 0;
473    for (var i = 0; i < diff_array.length; i += 3) {
474      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));
480      current_diff = pos2_end - pos1_end;
481    }
482    this.chunks = chunks;
483  }
484  PosTranslator.prototype.GetChunks = function() {
485    return this.chunks;
486  };
487
488  PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
489    var array = this.chunks;
490    if (array.length == 0 || pos < array[0].pos1) {
491      return pos;
492    }
493    var chunk_index1 = 0;
494    var chunk_index2 = array.length - 1;
495
496    while (chunk_index1 < chunk_index2) {
497      var middle_index = Math.floor((chunk_index1 + chunk_index2) / 2);
498      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) {
506      return pos + chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
507    }
508
509    if (!inside_chunk_handler) {
510      inside_chunk_handler = PosTranslator.DefaultInsideChunkHandler;
511    }
512    return inside_chunk_handler(pos, chunk);
513  };
514
515  PosTranslator.DefaultInsideChunkHandler = function(pos, diff_chunk) {
516    Assert(false, "Cannot translate position in changed area");
517  };
518
519  PosTranslator.ShiftWithTopInsideChunkHandler =
520      function(pos, diff_chunk) {
521    // We carelessly do not check whether we stay inside the chunk after
522    // translation.
523    return pos - diff_chunk.pos1 + diff_chunk.pos2;
524  };
525
526  var FunctionStatus = {
527      // No change to function or its inner functions; however its positions
528      // in script may have been shifted.
529      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"
538  };
539
540  function CodeInfoTreeNode(code_info, children, array_index) {
541    this.info = code_info;
542    this.children = children;
543    // an index in array of compile_info
544    this.array_index = array_index;
545    this.parent = void 0;
546
547    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.
550    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;
555
556    // '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
561    // as an old function deleted and new function created.
562    this.textual_corresponding_node = void 0;
563    this.textually_unmatched_new_nodes = void 0;
564
565    this.live_shared_function_infos = void 0;
566  }
567
568  // 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
574    // Recursive function that builds a branch of tree.
575    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    }
590
591    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
601    // A convenient iterator over diff chunks that also translates
602    // 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;
606      this.current = function() { return chunks[chunk_index]; };
607      this.next = function() {
608        var chunk = chunks[chunk_index];
609        pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1);
610        chunk_index++;
611      };
612      this.done = function() { return chunk_index >= chunks.length; };
613      this.TranslatePos = function(pos) { return pos + pos_diff; };
614    };
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(
621          info_node.info.start_position);
622      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];
630
631          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 {
659          if (chunk_it.current().pos1 + chunk_it.current().len1 <=
660              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 =
684          chunk_it.TranslatePos(info_node.info.end_position);
685    }
686
687    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    }
691
692    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;
709
710      var unmatched_new_nodes_list = [];
711      var textually_unmatched_new_nodes_list = [];
712
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) {
721            unmatched_new_nodes_list.push(new_children[new_index]);
722            textually_unmatched_new_nodes_list.push(new_children[new_index]);
723            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];
730              old_children[old_index].textual_corresponding_node =
731                  new_children[new_index];
732              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) {
736                  unmatched_new_nodes_list.push(
737                      old_children[old_index].corresponding_node);
738                  old_children[old_index].corresponding_node = void 0;
739                  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;
747              unmatched_new_nodes_list.push(new_children[new_index]);
748              textually_unmatched_new_nodes_list.push(new_children[new_index]);
749            }
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      }
767
768      while (new_index < new_children.length) {
769        unmatched_new_nodes_list.push(new_children[new_index]);
770        textually_unmatched_new_nodes_list.push(new_children[new_index]);
771        new_index++;
772      }
773
774      if (old_node.status == FunctionStatus.CHANGED) {
775        var why_wrong_expectations =
776            WhyFunctionExpectationsDiffer(old_node.info, new_node.info);
777        if (why_wrong_expectations) {
778          old_node.status = FunctionStatus.DAMAGED;
779          old_node.status_explanation = why_wrong_expectations;
780        }
781      }
782      old_node.unmatched_new_nodes = unmatched_new_nodes_list;
783      old_node.textually_unmatched_new_nodes =
784          textually_unmatched_new_nodes_list;
785    }
786
787    ProcessChildren(old_code_tree, new_code_tree);
788
789    old_code_tree.corresponding_node = new_code_tree;
790    old_code_tree.textual_corresponding_node = new_code_tree;
791
792    Assert(old_code_tree.status != FunctionStatus.DAMAGED,
793        "Script became damaged");
794  }
795
796  function FindLiveSharedInfos(old_code_tree, script) {
797    var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
798
799    var shared_infos = new Array();
800
801    for (var i = 0; i < shared_raw_list.length; i++) {
802      shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
803    }
804
805    // Finds all SharedFunctionInfos that corresponds to compile info
806    // in old version of the script.
807    function FindFunctionInfos(compile_info) {
808      var wrappers = [];
809
810      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) {
814          wrappers.push(wrapper);
815        }
816      }
817
818      if (wrappers.length > 0) {
819        return wrappers;
820      }
821    }
822
823    function TraverseTree(node) {
824      node.live_shared_function_infos = FindFunctionInfos(node.info);
825
826      for (var i = 0; i < node.children.length; i++) {
827        TraverseTree(node.children[i]);
828      }
829    }
830
831    TraverseTree(old_code_tree);
832  }
833
834
835  // 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];
843    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];
847    this.next_sibling_index = null;
848    this.raw_array = raw_array;
849  }
850
851  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  }
858
859  // Changes positions (including all statments) in function.
860  function PatchPositions(old_info_node, diff_array, report_array) {
861    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 {
869      // TODO(LiveEdit): function is not compiled yet or is already collected.
870      report_array.push(
871          { name: old_info_node.info.function_name, info_not_found: true } );
872    }
873  }
874
875  // 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  }
880
881  // Compares a function interface old and new version, whether it
882  // changed or not. Returns explanation if they differ.
883  function WhyFunctionExpectationsDiffer(function_info1, function_info2) {
884    // 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) {
887      return "Changed parameter number: " + function_info1.param_num +
888          " and " + function_info2.param_num;
889    }
890    var scope_info1 = function_info1.scope_info;
891    var scope_info2 = function_info2.scope_info;
892
893    var scope_info1_text;
894    var scope_info2_text;
895
896    if (scope_info1) {
897      scope_info1_text = scope_info1.toString();
898    } else {
899      scope_info1_text = "";
900    }
901    if (scope_info2) {
902      scope_info2_text = scope_info2.toString();
903    } else {
904      scope_info2_text = "";
905    }
906
907    if (scope_info1_text != scope_info2_text) {
908      return "Incompatible variable maps: [" + scope_info1_text +
909          "] and [" + scope_info2_text + "]";
910    }
911    // No differences. Return undefined.
912    return;
913  }
914
915  // Minifier forward declaration.
916  var FunctionPatchabilityStatus;
917
918  // 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    }
931
932    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,
941            start_pos: shared.start_position,
942            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    }
956
957    return dropped.length;
958  }
959
960  // 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
967  };
968
969  FunctionPatchabilityStatus.SymbolName = function(code) {
970    var enumeration = FunctionPatchabilityStatus;
971    for (name in enumeration) {
972      if (enumeration[name] == code) {
973        return name;
974      }
975    }
976  };
977
978
979  // 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;
986
987  Failure.prototype.toString = function() {
988    return "LiveEdit Failure: " + this.message;
989  };
990
991  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
1016  // 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
1023  // LiveEdit main entry point: changes a script text to a new string.
1024  function SetScriptSource(script, new_source, preview_only, change_log) {
1025    var old_source = script.source;
1026    var diff = CompareStrings(old_source, new_source);
1027    return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
1028        change_log);
1029  }
1030  // Function is public.
1031  this.SetScriptSource = SetScriptSource;
1032
1033  function CompareStrings(s1, s2) {
1034    return %LiveEditCompareStrings(s1, s2);
1035  }
1036
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;
1048
1049    // Prepare new source string.
1050    var new_source = old_source.substring(0, change_pos) +
1051        new_str + old_source.substring(change_pos + change_len);
1052
1053    return ApplyPatchMultiChunk(script,
1054        [ change_pos, change_pos + change_len, change_pos + new_str.length],
1055        new_source, false, change_log);
1056  }
1057
1058  // Creates JSON description for a change tree.
1059  function DescribeChangeTree(old_code_tree) {
1060
1061    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,
1081        new_children: new_child_infos
1082      };
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    }
1091
1092    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    }
1107
1108    function DescribePositions(node) {
1109      return {
1110        start_position: node.info.start_position,
1111        end_position: node.info.end_position
1112      };
1113    }
1114
1115    return ProcessOldNode(old_code_tree);
1116  }
1117
1118  // 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;
1130
1131  // Functions are public for tests.
1132  this.TestApi = {
1133    PosTranslator: PosTranslator,
1134    CompareStrings: CompareStrings,
1135    ApplySingleChunkPatch: ApplySingleChunkPatch
1136  };
1137};
1138