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 |