Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: src/liveedit-delay.js

Issue 652027: Basic implementation of liveedit feature (Closed)
Patch Set: add proper source headers Created 10 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/liveedit.cc ('k') | src/runtime.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 function Assert(condition, message) {
51 if (!condition) {
52 if (message) {
53 throw "Assert " + message;
54 } else {
55 throw "Assert";
56 }
57 }
58 }
59
60 // An object describing function compilation details. Its index fields
61 // apply to indexes inside array that stores these objects.
62 function FunctionCompileInfo(raw_array) {
63 this.function_name = raw_array[0];
64 this.start_position = raw_array[1];
65 this.end_position = raw_array[2];
66 this.param_num = raw_array[3];
67 this.code = raw_array[4];
68 this.scope_info = raw_array[5];
69 this.outer_index = raw_array[6];
70 this.next_sibling_index = null;
71 this.raw_array = raw_array;
72 }
73
74 // A structure describing SharedFunctionInfo.
75 function SharedInfoWrapper(raw_array) {
76 this.function_name = raw_array[0];
77 this.start_position = raw_array[1];
78 this.end_position = raw_array[2];
79 this.info = raw_array[3];
80 this.raw_array = raw_array;
81 }
82
83
84 // Fully compiles source string as a script. Returns Array of
85 // FunctionCompileInfo -- a descriptions of all functions of the script.
86 // Elements of array are ordered by start positions of functions (from top
87 // to bottom) in the source. Fields outer_index and next_sibling_index help
88 // to navigate the nesting structure of functions.
89 //
90 // The script is used for compilation, because it produces code that
91 // needs to be linked with some particular script (for nested functions).
92 function DebugGatherCompileInfo(source) {
93 // Get function info, elements are partially sorted (it is a tree
94 // of nested functions serialized as parent followed by serialized children.
95 var raw_compile_info = %LiveEditGatherCompileInfo(script, source);
96
97 // Sort function infos by start position field.
98 var compile_info = new Array();
99 var old_index_map = new Array();
100 for (var i = 0; i < raw_compile_info.length; i++) {
101 compile_info.push(new FunctionCompileInfo(raw_compile_info[i]));
102 old_index_map.push(i);
103 }
104
105 for (var i = 0; i < compile_info.length; i++) {
106 var k = i;
107 for (var j = i + 1; j < compile_info.length; j++) {
108 if (compile_info[k].start_position > compile_info[j].start_position) {
109 k = j;
110 }
111 }
112 if (k != i) {
113 var temp_info = compile_info[k];
114 var temp_index = old_index_map[k];
115 compile_info[k] = compile_info[i];
116 old_index_map[k] = old_index_map[i];
117 compile_info[i] = temp_info;
118 old_index_map[i] = temp_index;
119 }
120 }
121
122 // After sorting update outer_inder field using old_index_map. Also
123 // set next_sibling_index field.
124 var current_index = 0;
125
126 // The recursive function, that goes over all children of a particular
127 // node (i.e. function info).
128 function ResetIndexes(new_parent_index, old_parent_index) {
129 var previous_sibling = -1;
130 while (current_index < compile_info.length &&
131 compile_info[current_index].outer_index == old_parent_index) {
132 var saved_index = current_index;
133 compile_info[saved_index].outer_index = new_parent_index;
134 if (previous_sibling != -1) {
135 compile_info[previous_sibling].next_sibling_index = saved_index;
136 }
137 previous_sibling = saved_index;
138 current_index++;
139 ResetIndexes(saved_index, old_index_map[saved_index]);
140 }
141 if (previous_sibling != -1) {
142 compile_info[previous_sibling].next_sibling_index = -1;
143 }
144 }
145
146 ResetIndexes(-1, -1);
147 Assert(current_index == compile_info.length);
148
149 return compile_info;
150 }
151
152 // Given a positions, finds a function that fully includes the entire change.
153 function FindChangedFunction(compile_info, offset, len) {
154 // First condition: function should start before the change region.
155 // Function #0 (whole-script function) always does, but we want
156 // one, that is later in this list.
157 var index = 0;
158 while (index + 1 < compile_info.length &&
159 compile_info[index + 1].start_position <= offset) {
160 index++;
161 }
162 // Now we are at the last function that begins before the change
163 // region. The function that covers entire change region is either
164 // this function or the enclosing one.
165 for (; compile_info[index].end_position < offset + len;
166 index = compile_info[index].outer_index) {
167 Assert(index != -1);
168 }
169 return index;
170 }
171
172 // Compares a function interface old and new version, whether it
173 // changed or not.
174 function CompareFunctionExpectations(function_info1, function_info2) {
175 // Check that function has the same number of parameters (there may exist
176 // an adapter, that won't survive function parameter number change).
177 if (function_info1.param_num != function_info2.param_num) {
178 return false;
179 }
180 var scope_info1 = function_info1.scope_info;
181 var scope_info2 = function_info2.scope_info;
182
183 if (!scope_info1) {
184 return !scope_info2;
185 }
186
187 if (scope_info1.length != scope_info2.length) {
188 return false;
189 }
190
191 // Check that outer scope structure is not changed. Otherwise the function
192 // will not properly work with existing scopes.
193 return scope_info1.toString() == scope_info2.toString();
194 }
195
196 // Variable forward declarations. Preprocessor "Minifier" needs them.
197 var old_compile_info;
198 var shared_infos;
199 // Finds SharedFunctionInfo that corresponds compile info with index
200 // in old version of the script.
201 function FindFunctionInfo(index) {
202 var old_info = old_compile_info[index];
203 for (var i = 0; i < shared_infos.length; i++) {
204 var info = shared_infos[i];
205 if (info.start_position == old_info.start_position &&
206 info.end_position == old_info.end_position) {
207 return info;
208 }
209 }
210 }
211
212 // Replaces function's Code.
213 function PatchCode(new_info, shared_info) {
214 %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array);
215
216 change_log.push( {function_patched: new_info.function_name} );
217 }
218
219 var change_len_old;
220 var change_len_new;
221 // Translate position in old version of script into position in new
222 // version of script.
223 function PosTranslator(old_pos) {
224 if (old_pos <= change_pos) {
225 return old_pos;
226 }
227 if (old_pos >= change_pos + change_len_old) {
228 return old_pos + change_len_new - change_len_old;
229 }
230 return -1;
231 }
232
233 var position_change_array;
234 var position_patch_report;
235 function PatchPositions(new_info, shared_info) {
236 if (!shared_info) {
237 // TODO: explain what is happening.
238 return;
239 }
240 %LiveEditPatchFunctionPositions(shared_info.raw_array,
241 position_change_array);
242 position_patch_report.push( { name: new_info.function_name } );
243 }
244
245 var link_to_old_script_report;
246 var old_script;
247 // Makes a function associated with another instance of a script (the
248 // one representing its old version). This way the function still
249 // may access its own text.
250 function LinkToOldScript(shared_info) {
251 %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script);
252
253 link_to_old_script_report.push( { name: shared_info.function_name } );
254 }
255
256
257
258 var old_source = script.source;
259 var change_len_old = change_len;
260 var change_len_new = new_str.length;
261
262 // Prepare new source string.
263 var new_source = old_source.substring(0, change_pos) +
264 new_str + old_source.substring(change_pos + change_len);
265
266 // Find all SharedFunctionInfo's that are compiled from this script.
267 var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
268
269 // Gather compile information about old version of script.
270 var old_compile_info = DebugGatherCompileInfo(old_source);
271
272 // Gather compile information about new version of script.
273 var new_compile_info;
274 try {
275 new_compile_info = DebugGatherCompileInfo(new_source);
276 } catch (e) {
277 throw "Failed to compile new version of script: " + e;
278 }
279
280 // An index of a single function, that is going to have its code replaced.
281 var function_being_patched =
282 FindChangedFunction(old_compile_info, change_pos, change_len_old);
283 // In old and new script versions function with a change should have the
284 // same indexes.
285 var function_being_patched2 =
286 FindChangedFunction(new_compile_info, change_pos, change_len_new);
287 Assert(function_being_patched == function_being_patched2,
288 "inconsistent old/new compile info");
289
290 // Check that function being patched has the same expectations in a new
291 // version. Otherwise we cannot safely patch its behavior and should
292 // choose the outer function instead.
293 while (!CompareFunctionExpectations(old_compile_info[function_being_patched],
294 new_compile_info[function_being_patched])) {
295
296 Assert(old_compile_info[function_being_patched].outer_index ==
297 new_compile_info[function_being_patched].outer_index);
298 function_being_patched =
299 old_compile_info[function_being_patched].outer_index;
300 Assert(function_being_patched != -1);
301 }
302
303 // TODO: Need to check here that there are no activations of the function
304 // being patched on stack.
305
306 // Committing all changes.
307 var old_script_name = script.name + " (old)";
308
309 // Update the script text and create a new script representing an old
310 // version of the script.
311 var old_script = %LiveEditReplaceScript(script, new_source, old_script_name);
312
313 var shared_infos = new Array();
314
315 for (var i = 0; i < shared_raw_list.length; i++) {
316 shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
317 }
318
319 PatchCode(new_compile_info[function_being_patched],
320 FindFunctionInfo(function_being_patched));
321
322 var position_patch_report = new Array();
323 change_log.push( {position_patched: position_patch_report} );
324
325 var position_change_array = [ change_pos,
326 change_pos + change_len_old,
327 change_pos + change_len_new ];
328
329 // Update positions of all outer functions (i.e. all functions, that
330 // are partially below the function being patched).
331 for (var i = new_compile_info[function_being_patched].outer_index;
332 i != -1;
333 i = new_compile_info[i].outer_index) {
334 PatchPositions(new_compile_info[i], FindFunctionInfo(i));
335 }
336
337 // Update positions of all functions that are fully below the function
338 // being patched.
339 var old_next_sibling =
340 old_compile_info[function_being_patched].next_sibling_index;
341 var new_next_sibling =
342 new_compile_info[function_being_patched].next_sibling_index;
343
344 // We simply go over the tail of both old and new lists. Their tails should
345 // have an identical structure.
346 if (old_next_sibling == -1) {
347 Assert(new_next_sibling == -1);
348 } else {
349 Assert(old_compile_info.length - old_next_sibling ==
350 new_compile_info.length - new_next_sibling);
351
352 for (var i = old_next_sibling, j = new_next_sibling;
353 i < old_compile_info.length; i++, j++) {
354 PatchPositions(new_compile_info[j], FindFunctionInfo(i));
355 }
356 }
357
358 var link_to_old_script_report = new Array();
359 change_log.push( { linked_to_old_script: link_to_old_script_report } );
360
361 // We need to link to old script all former nested functions.
362 for (var i = function_being_patched + 1; i < old_next_sibling; i++) {
363 LinkToOldScript(FindFunctionInfo(i), old_script);
364 }
365 }
366
OLDNEW
« no previous file with comments | « src/liveedit.cc ('k') | src/runtime.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698