The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / src / liveedit-debugger.js @ f230a1cf

History | View | Annotate | Download (40.8 KB)

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.
45
Debug.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 = UNDEFINED;
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 whether 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, UNDEFINED);
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_index 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 = UNDEFINED;
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 = UNDEFINED;
551
    this.new_start_pos = UNDEFINED;
552
    this.new_end_pos = UNDEFINED;
553
    this.corresponding_node = UNDEFINED;
554
    this.unmatched_new_nodes = UNDEFINED;
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 = UNDEFINED;
563
    this.textually_unmatched_new_nodes = UNDEFINED;
564

    
565
    this.live_shared_function_infos = UNDEFINED;
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 each 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 correspondence 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 ProcessNode(old_node, new_node) {
707
      var scope_change_description =
708
          IsFunctionContextLocalsChanged(old_node.info, new_node.info);
709
      if (scope_change_description) {
710
          old_node.status = FunctionStatus.CHANGED;
711
      }
712

    
713
      var old_children = old_node.children;
714
      var new_children = new_node.children;
715

    
716
      var unmatched_new_nodes_list = [];
717
      var textually_unmatched_new_nodes_list = [];
718

    
719
      var old_index = 0;
720
      var new_index = 0;
721
      while (old_index < old_children.length) {
722
        if (old_children[old_index].status == FunctionStatus.DAMAGED) {
723
          old_index++;
724
        } else if (new_index < new_children.length) {
725
          if (new_children[new_index].info.start_position <
726
              old_children[old_index].new_start_pos) {
727
            unmatched_new_nodes_list.push(new_children[new_index]);
728
            textually_unmatched_new_nodes_list.push(new_children[new_index]);
729
            new_index++;
730
          } else if (new_children[new_index].info.start_position ==
731
              old_children[old_index].new_start_pos) {
732
            if (new_children[new_index].info.end_position ==
733
                old_children[old_index].new_end_pos) {
734
              old_children[old_index].corresponding_node =
735
                  new_children[new_index];
736
              old_children[old_index].textual_corresponding_node =
737
                  new_children[new_index];
738
              if (scope_change_description) {
739
                old_children[old_index].status = FunctionStatus.DAMAGED;
740
                old_children[old_index].status_explanation =
741
                    "Enclosing function is now incompatible. " +
742
                    scope_change_description;
743
                old_children[old_index].corresponding_node = UNDEFINED;
744
              } else if (old_children[old_index].status !=
745
                  FunctionStatus.UNCHANGED) {
746
                ProcessNode(old_children[old_index],
747
                    new_children[new_index]);
748
                if (old_children[old_index].status == FunctionStatus.DAMAGED) {
749
                  unmatched_new_nodes_list.push(
750
                      old_children[old_index].corresponding_node);
751
                  old_children[old_index].corresponding_node = UNDEFINED;
752
                  old_node.status = FunctionStatus.CHANGED;
753
                }
754
              }
755
            } else {
756
              old_children[old_index].status = FunctionStatus.DAMAGED;
757
              old_children[old_index].status_explanation =
758
                  "No corresponding function in new script found";
759
              old_node.status = FunctionStatus.CHANGED;
760
              unmatched_new_nodes_list.push(new_children[new_index]);
761
              textually_unmatched_new_nodes_list.push(new_children[new_index]);
762
            }
763
            new_index++;
764
            old_index++;
765
          } else {
766
            old_children[old_index].status = FunctionStatus.DAMAGED;
767
            old_children[old_index].status_explanation =
768
                "No corresponding function in new script found";
769
            old_node.status = FunctionStatus.CHANGED;
770
            old_index++;
771
          }
772
        } else {
773
          old_children[old_index].status = FunctionStatus.DAMAGED;
774
          old_children[old_index].status_explanation =
775
              "No corresponding function in new script found";
776
          old_node.status = FunctionStatus.CHANGED;
777
          old_index++;
778
        }
779
      }
780

    
781
      while (new_index < new_children.length) {
782
        unmatched_new_nodes_list.push(new_children[new_index]);
783
        textually_unmatched_new_nodes_list.push(new_children[new_index]);
784
        new_index++;
785
      }
786

    
787
      if (old_node.status == FunctionStatus.CHANGED) {
788
        if (old_node.info.param_num != new_node.info.param_num) {
789
          old_node.status = FunctionStatus.DAMAGED;
790
          old_node.status_explanation = "Changed parameter number: " +
791
              old_node.info.param_num + " and " + new_node.info.param_num;
792
        }
793
      }
794
      old_node.unmatched_new_nodes = unmatched_new_nodes_list;
795
      old_node.textually_unmatched_new_nodes =
796
          textually_unmatched_new_nodes_list;
797
    }
798

    
799
    ProcessNode(old_code_tree, new_code_tree);
800

    
801
    old_code_tree.corresponding_node = new_code_tree;
802
    old_code_tree.textual_corresponding_node = new_code_tree;
803

    
804
    Assert(old_code_tree.status != FunctionStatus.DAMAGED,
805
        "Script became damaged");
806
  }
807

    
808
  function FindLiveSharedInfos(old_code_tree, script) {
809
    var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
810

    
811
    var shared_infos = new Array();
812

    
813
    for (var i = 0; i < shared_raw_list.length; i++) {
814
      shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
815
    }
816

    
817
    // Finds all SharedFunctionInfos that corresponds to compile info
818
    // in old version of the script.
819
    function FindFunctionInfos(compile_info) {
820
      var wrappers = [];
821

    
822
      for (var i = 0; i < shared_infos.length; i++) {
823
        var wrapper = shared_infos[i];
824
        if (wrapper.start_position == compile_info.start_position &&
825
            wrapper.end_position == compile_info.end_position) {
826
          wrappers.push(wrapper);
827
        }
828
      }
829

    
830
      if (wrappers.length > 0) {
831
        return wrappers;
832
      }
833
    }
834

    
835
    function TraverseTree(node) {
836
      node.live_shared_function_infos = FindFunctionInfos(node.info);
837

    
838
      for (var i = 0; i < node.children.length; i++) {
839
        TraverseTree(node.children[i]);
840
      }
841
    }
842

    
843
    TraverseTree(old_code_tree);
844
  }
845

    
846

    
847
  // An object describing function compilation details. Its index fields
848
  // apply to indexes inside array that stores these objects.
849
  function FunctionCompileInfo(raw_array) {
850
    this.function_name = raw_array[0];
851
    this.start_position = raw_array[1];
852
    this.end_position = raw_array[2];
853
    this.param_num = raw_array[3];
854
    this.code = raw_array[4];
855
    this.code_scope_info = raw_array[5];
856
    this.scope_info = raw_array[6];
857
    this.outer_index = raw_array[7];
858
    this.shared_function_info = raw_array[8];
859
    this.next_sibling_index = null;
860
    this.raw_array = raw_array;
861
  }
862

    
863
  function SharedInfoWrapper(raw_array) {
864
    this.function_name = raw_array[0];
865
    this.start_position = raw_array[1];
866
    this.end_position = raw_array[2];
867
    this.info = raw_array[3];
868
    this.raw_array = raw_array;
869
  }
870

    
871
  // Changes positions (including all statements) in function.
872
  function PatchPositions(old_info_node, diff_array, report_array) {
873
    if (old_info_node.live_shared_function_infos) {
874
      old_info_node.live_shared_function_infos.forEach(function (info) {
875
          %LiveEditPatchFunctionPositions(info.raw_array,
876
                                          diff_array);
877
      });
878

    
879
      report_array.push( { name: old_info_node.info.function_name } );
880
    } else {
881
      // TODO(LiveEdit): function is not compiled yet or is already collected.
882
      report_array.push(
883
          { name: old_info_node.info.function_name, info_not_found: true } );
884
    }
885
  }
886

    
887
  // Adds a suffix to script name to mark that it is old version.
888
  function CreateNameForOldScript(script) {
889
    // TODO(635): try better than this; support several changes.
890
    return script.name + " (old)";
891
  }
892

    
893
  // Compares a function scope heap structure, old and new version, whether it
894
  // changed or not. Returns explanation if they differ.
895
  function IsFunctionContextLocalsChanged(function_info1, function_info2) {
896
    var scope_info1 = function_info1.scope_info;
897
    var scope_info2 = function_info2.scope_info;
898

    
899
    var scope_info1_text;
900
    var scope_info2_text;
901

    
902
    if (scope_info1) {
903
      scope_info1_text = scope_info1.toString();
904
    } else {
905
      scope_info1_text = "";
906
    }
907
    if (scope_info2) {
908
      scope_info2_text = scope_info2.toString();
909
    } else {
910
      scope_info2_text = "";
911
    }
912

    
913
    if (scope_info1_text != scope_info2_text) {
914
      return "Variable map changed: [" + scope_info1_text +
915
          "] => [" + scope_info2_text + "]";
916
    }
917
    // No differences. Return undefined.
918
    return;
919
  }
920

    
921
  // Minifier forward declaration.
922
  var FunctionPatchabilityStatus;
923

    
924
  // For array of wrapped shared function infos checks that none of them
925
  // have activations on stack (of any thread). Throws a Failure exception
926
  // if this proves to be false.
927
  function CheckStackActivations(shared_wrapper_list, change_log) {
928
    var shared_list = new Array();
929
    for (var i = 0; i < shared_wrapper_list.length; i++) {
930
      shared_list[i] = shared_wrapper_list[i].info;
931
    }
932
    var result = %LiveEditCheckAndDropActivations(shared_list, true);
933
    if (result[shared_list.length]) {
934
      // Extra array element may contain error message.
935
      throw new Failure(result[shared_list.length]);
936
    }
937

    
938
    var problems = new Array();
939
    var dropped = new Array();
940
    for (var i = 0; i < shared_list.length; i++) {
941
      var shared = shared_wrapper_list[i];
942
      if (result[i] == FunctionPatchabilityStatus.REPLACED_ON_ACTIVE_STACK) {
943
        dropped.push({ name: shared.function_name } );
944
      } else if (result[i] != FunctionPatchabilityStatus.AVAILABLE_FOR_PATCH) {
945
        var description = {
946
            name: shared.function_name,
947
            start_pos: shared.start_position,
948
            end_pos: shared.end_position,
949
            replace_problem:
950
                FunctionPatchabilityStatus.SymbolName(result[i])
951
        };
952
        problems.push(description);
953
      }
954
    }
955
    if (dropped.length > 0) {
956
      change_log.push({ dropped_from_stack: dropped });
957
    }
958
    if (problems.length > 0) {
959
      change_log.push( { functions_on_stack: problems } );
960
      throw new Failure("Blocked by functions on stack");
961
    }
962

    
963
    return dropped.length;
964
  }
965

    
966
  // A copy of the FunctionPatchabilityStatus enum from liveedit.h
967
  var FunctionPatchabilityStatus = {
968
      AVAILABLE_FOR_PATCH: 1,
969
      BLOCKED_ON_ACTIVE_STACK: 2,
970
      BLOCKED_ON_OTHER_STACK: 3,
971
      BLOCKED_UNDER_NATIVE_CODE: 4,
972
      REPLACED_ON_ACTIVE_STACK: 5
973
  };
974

    
975
  FunctionPatchabilityStatus.SymbolName = function(code) {
976
    var enumeration = FunctionPatchabilityStatus;
977
    for (name in enumeration) {
978
      if (enumeration[name] == code) {
979
        return name;
980
      }
981
    }
982
  };
983

    
984

    
985
  // A logical failure in liveedit process. This means that change_log
986
  // is valid and consistent description of what happened.
987
  function Failure(message) {
988
    this.message = message;
989
  }
990
  // Function (constructor) is public.
991
  this.Failure = Failure;
992

    
993
  Failure.prototype.toString = function() {
994
    return "LiveEdit Failure: " + this.message;
995
  };
996

    
997
  function CopyErrorPositionToDetails(e, details) {
998
    function createPositionStruct(script, position) {
999
      if (position == -1) return;
1000
      var location = script.locationFromPosition(position, true);
1001
      if (location == null) return;
1002
      return {
1003
        line: location.line + 1,
1004
        column: location.column + 1,
1005
        position: position
1006
      };
1007
    }
1008

    
1009
    if (!("scriptObject" in e) || !("startPosition" in e)) {
1010
      return;
1011
    }
1012

    
1013
    var script = e.scriptObject;
1014

    
1015
    var position_struct = {
1016
      start: createPositionStruct(script, e.startPosition),
1017
      end: createPositionStruct(script, e.endPosition)
1018
    };
1019
    details.position = position_struct;
1020
  }
1021

    
1022
  // A testing entry.
1023
  function GetPcFromSourcePos(func, source_pos) {
1024
    return %GetFunctionCodePositionFromSource(func, source_pos);
1025
  }
1026
  // Function is public.
1027
  this.GetPcFromSourcePos = GetPcFromSourcePos;
1028

    
1029
  // LiveEdit main entry point: changes a script text to a new string.
1030
  function SetScriptSource(script, new_source, preview_only, change_log) {
1031
    var old_source = script.source;
1032
    var diff = CompareStrings(old_source, new_source);
1033
    return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
1034
        change_log);
1035
  }
1036
  // Function is public.
1037
  this.SetScriptSource = SetScriptSource;
1038

    
1039
  function CompareStrings(s1, s2) {
1040
    return %LiveEditCompareStrings(s1, s2);
1041
  }
1042

    
1043
  // Applies the change to the script.
1044
  // The change is always a substring (change_pos, change_pos + change_len)
1045
  // being replaced with a completely different string new_str.
1046
  // This API is a legacy and is obsolete.
1047
  //
1048
  // @param {Script} script that is being changed
1049
  // @param {Array} change_log a list that collects engineer-readable
1050
  //     description of what happened.
1051
  function ApplySingleChunkPatch(script, change_pos, change_len, new_str,
1052
      change_log) {
1053
    var old_source = script.source;
1054

    
1055
    // Prepare new source string.
1056
    var new_source = old_source.substring(0, change_pos) +
1057
        new_str + old_source.substring(change_pos + change_len);
1058

    
1059
    return ApplyPatchMultiChunk(script,
1060
        [ change_pos, change_pos + change_len, change_pos + new_str.length],
1061
        new_source, false, change_log);
1062
  }
1063

    
1064
  // Creates JSON description for a change tree.
1065
  function DescribeChangeTree(old_code_tree) {
1066

    
1067
    function ProcessOldNode(node) {
1068
      var child_infos = [];
1069
      for (var i = 0; i < node.children.length; i++) {
1070
        var child = node.children[i];
1071
        if (child.status != FunctionStatus.UNCHANGED) {
1072
          child_infos.push(ProcessOldNode(child));
1073
        }
1074
      }
1075
      var new_child_infos = [];
1076
      if (node.textually_unmatched_new_nodes) {
1077
        for (var i = 0; i < node.textually_unmatched_new_nodes.length; i++) {
1078
          var child = node.textually_unmatched_new_nodes[i];
1079
          new_child_infos.push(ProcessNewNode(child));
1080
        }
1081
      }
1082
      var res = {
1083
        name: node.info.function_name,
1084
        positions: DescribePositions(node),
1085
        status: node.status,
1086
        children: child_infos,
1087
        new_children: new_child_infos
1088
      };
1089
      if (node.status_explanation) {
1090
        res.status_explanation = node.status_explanation;
1091
      }
1092
      if (node.textual_corresponding_node) {
1093
        res.new_positions = DescribePositions(node.textual_corresponding_node);
1094
      }
1095
      return res;
1096
    }
1097

    
1098
    function ProcessNewNode(node) {
1099
      var child_infos = [];
1100
      // Do not list ancestors.
1101
      if (false) {
1102
        for (var i = 0; i < node.children.length; i++) {
1103
          child_infos.push(ProcessNewNode(node.children[i]));
1104
        }
1105
      }
1106
      var res = {
1107
        name: node.info.function_name,
1108
        positions: DescribePositions(node),
1109
        children: child_infos,
1110
      };
1111
      return res;
1112
    }
1113

    
1114
    function DescribePositions(node) {
1115
      return {
1116
        start_position: node.info.start_position,
1117
        end_position: node.info.end_position
1118
      };
1119
    }
1120

    
1121
    return ProcessOldNode(old_code_tree);
1122
  }
1123

    
1124
  // Restarts call frame and returns value similar to what LiveEdit returns.
1125
  function RestartFrame(frame_mirror) {
1126
    var result = frame_mirror.restart();
1127
    if (IS_STRING(result)) {
1128
      throw new Failure("Failed to restart frame: " + result);
1129
    }
1130
    var result = {};
1131
    result[NEEDS_STEP_IN_PROPERTY_NAME] = true;
1132
    return result;
1133
  }
1134
  // Function is public.
1135
  this.RestartFrame = RestartFrame;
1136

    
1137
  // Functions are public for tests.
1138
  this.TestApi = {
1139
    PosTranslator: PosTranslator,
1140
    CompareStrings: CompareStrings,
1141
    ApplySingleChunkPatch: ApplySingleChunkPatch
1142
  };
1143
};