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