OLD | NEW |
| (Empty) |
1 // Copyright 2010 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-delay.js. | |
30 | |
31 | |
32 // Changes script text and recompiles all relevant functions if possible. | |
33 // The change is always a substring (change_pos, change_pos + change_len) | |
34 // being replaced with a completely different string new_str. | |
35 // | |
36 // Only one function will have its Code changed in result of this function. | |
37 // All nested functions (should they have any instances at the moment) are left | |
38 // unchanged and re-linked to a newly created script instance representing old | |
39 // version of the source. (Generally speaking, | |
40 // during the change all nested functions are erased and completely different | |
41 // set of nested functions are introduced.) All other functions just have | |
42 // their positions updated. | |
43 // | |
44 // @param {Script} script that is being changed | |
45 // @param {Array} change_log a list that collects engineer-readable description | |
46 // of what happened. | |
47 Debug.LiveEditChangeScript = function(script, change_pos, change_len, new_str, | |
48 change_log) { | |
49 | |
50 // So far the function works as namespace. | |
51 var liveedit = Debug.LiveEditChangeScript; | |
52 var Assert = liveedit.Assert; | |
53 | |
54 // Fully compiles source string as a script. Returns Array of | |
55 // FunctionCompileInfo -- a descriptions of all functions of the script. | |
56 // Elements of array are ordered by start positions of functions (from top | |
57 // to bottom) in the source. Fields outer_index and next_sibling_index help | |
58 // to navigate the nesting structure of functions. | |
59 // | |
60 // The script is used for compilation, because it produces code that | |
61 // needs to be linked with some particular script (for nested functions). | |
62 function DebugGatherCompileInfo(source) { | |
63 // Get function info, elements are partially sorted (it is a tree | |
64 // of nested functions serialized as parent followed by serialized children. | |
65 var raw_compile_info = %LiveEditGatherCompileInfo(script, source); | |
66 | |
67 // Sort function infos by start position field. | |
68 var compile_info = new Array(); | |
69 var old_index_map = new Array(); | |
70 for (var i = 0; i < raw_compile_info.length; i++) { | |
71 compile_info.push(new liveedit.FunctionCompileInfo(raw_compile_info[i]))
; | |
72 old_index_map.push(i); | |
73 } | |
74 | |
75 for (var i = 0; i < compile_info.length; i++) { | |
76 var k = i; | |
77 for (var j = i + 1; j < compile_info.length; j++) { | |
78 if (compile_info[k].start_position > compile_info[j].start_position) { | |
79 k = j; | |
80 } | |
81 } | |
82 if (k != i) { | |
83 var temp_info = compile_info[k]; | |
84 var temp_index = old_index_map[k]; | |
85 compile_info[k] = compile_info[i]; | |
86 old_index_map[k] = old_index_map[i]; | |
87 compile_info[i] = temp_info; | |
88 old_index_map[i] = temp_index; | |
89 } | |
90 } | |
91 | |
92 // After sorting update outer_inder field using old_index_map. Also | |
93 // set next_sibling_index field. | |
94 var current_index = 0; | |
95 | |
96 // The recursive function, that goes over all children of a particular | |
97 // node (i.e. function info). | |
98 function ResetIndexes(new_parent_index, old_parent_index) { | |
99 var previous_sibling = -1; | |
100 while (current_index < compile_info.length && | |
101 compile_info[current_index].outer_index == old_parent_index) { | |
102 var saved_index = current_index; | |
103 compile_info[saved_index].outer_index = new_parent_index; | |
104 if (previous_sibling != -1) { | |
105 compile_info[previous_sibling].next_sibling_index = saved_index; | |
106 } | |
107 previous_sibling = saved_index; | |
108 current_index++; | |
109 ResetIndexes(saved_index, old_index_map[saved_index]); | |
110 } | |
111 if (previous_sibling != -1) { | |
112 compile_info[previous_sibling].next_sibling_index = -1; | |
113 } | |
114 } | |
115 | |
116 ResetIndexes(-1, -1); | |
117 Assert(current_index == compile_info.length); | |
118 | |
119 return compile_info; | |
120 } | |
121 | |
122 // Given a positions, finds a function that fully includes the entire change. | |
123 function FindChangedFunction(compile_info, offset, len) { | |
124 // First condition: function should start before the change region. | |
125 // Function #0 (whole-script function) always does, but we want | |
126 // one, that is later in this list. | |
127 var index = 0; | |
128 while (index + 1 < compile_info.length && | |
129 compile_info[index + 1].start_position <= offset) { | |
130 index++; | |
131 } | |
132 // Now we are at the last function that begins before the change | |
133 // region. The function that covers entire change region is either | |
134 // this function or the enclosing one. | |
135 for (; compile_info[index].end_position < offset + len; | |
136 index = compile_info[index].outer_index) { | |
137 Assert(index != -1); | |
138 } | |
139 return index; | |
140 } | |
141 | |
142 // Variable forward declarations. Preprocessor "Minifier" needs them. | |
143 var old_compile_info; | |
144 var shared_infos; | |
145 // Finds SharedFunctionInfo that corresponds compile info with index | |
146 // in old version of the script. | |
147 function FindFunctionInfo(index) { | |
148 var old_info = old_compile_info[index]; | |
149 for (var i = 0; i < shared_infos.length; i++) { | |
150 var info = shared_infos[i]; | |
151 if (info.start_position == old_info.start_position && | |
152 info.end_position == old_info.end_position) { | |
153 return info; | |
154 } | |
155 } | |
156 } | |
157 | |
158 // Replaces function's Code. | |
159 function PatchCode(new_info, shared_info) { | |
160 %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array); | |
161 | |
162 change_log.push( {function_patched: new_info.function_name} ); | |
163 } | |
164 | |
165 var change_len_old; | |
166 var change_len_new; | |
167 // Translate position in old version of script into position in new | |
168 // version of script. | |
169 function PosTranslator(old_pos) { | |
170 if (old_pos <= change_pos) { | |
171 return old_pos; | |
172 } | |
173 if (old_pos >= change_pos + change_len_old) { | |
174 return old_pos + change_len_new - change_len_old; | |
175 } | |
176 return -1; | |
177 } | |
178 | |
179 var position_change_array; | |
180 var position_patch_report; | |
181 function PatchPositions(new_info, shared_info) { | |
182 if (!shared_info) { | |
183 // TODO: explain what is happening. | |
184 return; | |
185 } | |
186 %LiveEditPatchFunctionPositions(shared_info.raw_array, | |
187 position_change_array); | |
188 position_patch_report.push( { name: new_info.function_name } ); | |
189 } | |
190 | |
191 var link_to_old_script_report; | |
192 var old_script; | |
193 // Makes a function associated with another instance of a script (the | |
194 // one representing its old version). This way the function still | |
195 // may access its own text. | |
196 function LinkToOldScript(shared_info) { | |
197 %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script); | |
198 | |
199 link_to_old_script_report.push( { name: shared_info.function_name } ); | |
200 } | |
201 | |
202 | |
203 | |
204 var old_source = script.source; | |
205 var change_len_old = change_len; | |
206 var change_len_new = new_str.length; | |
207 | |
208 // Prepare new source string. | |
209 var new_source = old_source.substring(0, change_pos) + | |
210 new_str + old_source.substring(change_pos + change_len); | |
211 | |
212 // Find all SharedFunctionInfo's that are compiled from this script. | |
213 var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script); | |
214 | |
215 var shared_infos = new Array(); | |
216 | |
217 for (var i = 0; i < shared_raw_list.length; i++) { | |
218 shared_infos.push(new liveedit.SharedInfoWrapper(shared_raw_list[i])); | |
219 } | |
220 | |
221 // Gather compile information about old version of script. | |
222 var old_compile_info = DebugGatherCompileInfo(old_source); | |
223 | |
224 // Gather compile information about new version of script. | |
225 var new_compile_info; | |
226 try { | |
227 new_compile_info = DebugGatherCompileInfo(new_source); | |
228 } catch (e) { | |
229 throw new liveedit.Failure("Failed to compile new version of script: " + e); | |
230 } | |
231 | |
232 // An index of a single function, that is going to have its code replaced. | |
233 var function_being_patched = | |
234 FindChangedFunction(old_compile_info, change_pos, change_len_old); | |
235 | |
236 // In old and new script versions function with a change should have the | |
237 // same indexes. | |
238 var function_being_patched2 = | |
239 FindChangedFunction(new_compile_info, change_pos, change_len_new); | |
240 Assert(function_being_patched == function_being_patched2, | |
241 "inconsistent old/new compile info"); | |
242 | |
243 // Check that function being patched has the same expectations in a new | |
244 // version. Otherwise we cannot safely patch its behavior and should | |
245 // choose the outer function instead. | |
246 while (!liveedit.CompareFunctionExpectations( | |
247 old_compile_info[function_being_patched], | |
248 new_compile_info[function_being_patched])) { | |
249 | |
250 Assert(old_compile_info[function_being_patched].outer_index == | |
251 new_compile_info[function_being_patched].outer_index); | |
252 function_being_patched = | |
253 old_compile_info[function_being_patched].outer_index; | |
254 Assert(function_being_patched != -1); | |
255 } | |
256 | |
257 // Check that function being patched is not currently on stack. | |
258 liveedit.CheckStackActivations( | |
259 [ FindFunctionInfo(function_being_patched) ], change_log ); | |
260 | |
261 | |
262 // Committing all changes. | |
263 var old_script_name = liveedit.CreateNameForOldScript(script); | |
264 | |
265 // Update the script text and create a new script representing an old | |
266 // version of the script. | |
267 var old_script = %LiveEditReplaceScript(script, new_source, old_script_name); | |
268 | |
269 PatchCode(new_compile_info[function_being_patched], | |
270 FindFunctionInfo(function_being_patched)); | |
271 | |
272 var position_patch_report = new Array(); | |
273 change_log.push( {position_patched: position_patch_report} ); | |
274 | |
275 var position_change_array = [ change_pos, | |
276 change_pos + change_len_old, | |
277 change_pos + change_len_new ]; | |
278 | |
279 // Update positions of all outer functions (i.e. all functions, that | |
280 // are partially below the function being patched). | |
281 for (var i = new_compile_info[function_being_patched].outer_index; | |
282 i != -1; | |
283 i = new_compile_info[i].outer_index) { | |
284 PatchPositions(new_compile_info[i], FindFunctionInfo(i)); | |
285 } | |
286 | |
287 // Update positions of all functions that are fully below the function | |
288 // being patched. | |
289 var old_next_sibling = | |
290 old_compile_info[function_being_patched].next_sibling_index; | |
291 var new_next_sibling = | |
292 new_compile_info[function_being_patched].next_sibling_index; | |
293 | |
294 // We simply go over the tail of both old and new lists. Their tails should | |
295 // have an identical structure. | |
296 if (old_next_sibling == -1) { | |
297 Assert(new_next_sibling == -1); | |
298 } else { | |
299 Assert(old_compile_info.length - old_next_sibling == | |
300 new_compile_info.length - new_next_sibling); | |
301 | |
302 for (var i = old_next_sibling, j = new_next_sibling; | |
303 i < old_compile_info.length; i++, j++) { | |
304 PatchPositions(new_compile_info[j], FindFunctionInfo(i)); | |
305 } | |
306 } | |
307 | |
308 var link_to_old_script_report = new Array(); | |
309 change_log.push( { linked_to_old_script: link_to_old_script_report } ); | |
310 | |
311 // We need to link to old script all former nested functions. | |
312 for (var i = function_being_patched + 1; i < old_next_sibling; i++) { | |
313 LinkToOldScript(FindFunctionInfo(i), old_script); | |
314 } | |
315 } | |
316 | |
317 Debug.LiveEditChangeScript.Assert = function(condition, message) { | |
318 if (!condition) { | |
319 if (message) { | |
320 throw "Assert " + message; | |
321 } else { | |
322 throw "Assert"; | |
323 } | |
324 } | |
325 } | |
326 | |
327 // An object describing function compilation details. Its index fields | |
328 // apply to indexes inside array that stores these objects. | |
329 Debug.LiveEditChangeScript.FunctionCompileInfo = function(raw_array) { | |
330 this.function_name = raw_array[0]; | |
331 this.start_position = raw_array[1]; | |
332 this.end_position = raw_array[2]; | |
333 this.param_num = raw_array[3]; | |
334 this.code = raw_array[4]; | |
335 this.scope_info = raw_array[5]; | |
336 this.outer_index = raw_array[6]; | |
337 this.next_sibling_index = null; | |
338 this.raw_array = raw_array; | |
339 } | |
340 | |
341 // A structure describing SharedFunctionInfo. | |
342 Debug.LiveEditChangeScript.SharedInfoWrapper = function(raw_array) { | |
343 this.function_name = raw_array[0]; | |
344 this.start_position = raw_array[1]; | |
345 this.end_position = raw_array[2]; | |
346 this.info = raw_array[3]; | |
347 this.raw_array = raw_array; | |
348 } | |
349 | |
350 // Adds a suffix to script name to mark that it is old version. | |
351 Debug.LiveEditChangeScript.CreateNameForOldScript = function(script) { | |
352 // TODO(635): try better than this; support several changes. | |
353 return script.name + " (old)"; | |
354 } | |
355 | |
356 // Compares a function interface old and new version, whether it | |
357 // changed or not. | |
358 Debug.LiveEditChangeScript.CompareFunctionExpectations = | |
359 function(function_info1, function_info2) { | |
360 // Check that function has the same number of parameters (there may exist | |
361 // an adapter, that won't survive function parameter number change). | |
362 if (function_info1.param_num != function_info2.param_num) { | |
363 return false; | |
364 } | |
365 var scope_info1 = function_info1.scope_info; | |
366 var scope_info2 = function_info2.scope_info; | |
367 | |
368 if (!scope_info1) { | |
369 return !scope_info2; | |
370 } | |
371 | |
372 if (scope_info1.length != scope_info2.length) { | |
373 return false; | |
374 } | |
375 | |
376 // Check that outer scope structure is not changed. Otherwise the function | |
377 // will not properly work with existing scopes. | |
378 return scope_info1.toString() == scope_info2.toString(); | |
379 } | |
380 | |
381 // For array of wrapped shared function infos checks that none of them | |
382 // have activations on stack (of any thread). Throws a Failure exception | |
383 // if this proves to be false. | |
384 Debug.LiveEditChangeScript.CheckStackActivations = function(shared_wrapper_list, | |
385 change_log) { | |
386 var liveedit = Debug.LiveEditChangeScript; | |
387 | |
388 var shared_list = new Array(); | |
389 for (var i = 0; i < shared_wrapper_list.length; i++) { | |
390 shared_list[i] = shared_wrapper_list[i].info; | |
391 } | |
392 var result = %LiveEditCheckStackActivations(shared_list); | |
393 var problems = new Array(); | |
394 for (var i = 0; i < shared_list.length; i++) { | |
395 if (result[i] == liveedit.FunctionPatchabilityStatus.FUNCTION_BLOCKED_ON_STA
CK) { | |
396 var shared = shared_list[i]; | |
397 var description = { | |
398 name: shared.function_name, | |
399 start_pos: shared.start_position, | |
400 end_pos: shared.end_position | |
401 }; | |
402 problems.push(description); | |
403 } | |
404 } | |
405 if (problems.length > 0) { | |
406 change_log.push( { functions_on_stack: problems } ); | |
407 throw new liveedit.Failure("Blocked by functions on stack"); | |
408 } | |
409 } | |
410 | |
411 // A copy of the FunctionPatchabilityStatus enum from liveedit.h | |
412 Debug.LiveEditChangeScript.FunctionPatchabilityStatus = { | |
413 FUNCTION_AVAILABLE_FOR_PATCH: 0, | |
414 FUNCTION_BLOCKED_ON_STACK: 1 | |
415 } | |
416 | |
417 | |
418 // A logical failure in liveedit process. This means that change_log | |
419 // is valid and consistent description of what happened. | |
420 Debug.LiveEditChangeScript.Failure = function(message) { | |
421 this.message = message; | |
422 } | |
423 | |
424 Debug.LiveEditChangeScript.Failure.prototype.toString = function() { | |
425 return "LiveEdit Failure: " + this.message; | |
426 } | |
427 | |
428 // A testing entry. | |
429 Debug.LiveEditChangeScript.GetPcFromSourcePos = function(func, source_pos) { | |
430 return %GetFunctionCodePositionFromSource(func, source_pos); | |
431 } | |
OLD | NEW |