1 // Copyright 2014 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/runtime/runtime-utils.h"
6 
7 #include "src/arguments.h"
8 #include "src/debug/debug.h"
9 #include "src/debug/debug-frames.h"
10 #include "src/debug/liveedit.h"
11 #include "src/frames-inl.h"
12 #include "src/isolate-inl.h"
13 #include "src/runtime/runtime.h"
14 
15 namespace v8 {
16 namespace internal {
17 
18 // For a script finds all SharedFunctionInfo's in the heap that points
19 // to this script. Returns JSArray of SharedFunctionInfo wrapped
20 // in OpaqueReferences.
RUNTIME_FUNCTION(Runtime_LiveEditFindSharedFunctionInfosForScript)21 RUNTIME_FUNCTION(Runtime_LiveEditFindSharedFunctionInfosForScript) {
22   HandleScope scope(isolate);
23   CHECK(isolate->debug()->live_edit_enabled());
24   DCHECK(args.length() == 1);
25   CONVERT_ARG_CHECKED(JSValue, script_value, 0);
26 
27   CHECK(script_value->value()->IsScript());
28   Handle<Script> script = Handle<Script>(Script::cast(script_value->value()));
29 
30   List<Handle<SharedFunctionInfo> > found;
31   Heap* heap = isolate->heap();
32   {
33     HeapIterator iterator(heap);
34     HeapObject* heap_obj;
35     while ((heap_obj = iterator.next())) {
36       if (!heap_obj->IsSharedFunctionInfo()) continue;
37       SharedFunctionInfo* shared = SharedFunctionInfo::cast(heap_obj);
38       if (shared->script() != *script) continue;
39       found.Add(Handle<SharedFunctionInfo>(shared));
40     }
41   }
42 
43   Handle<FixedArray> result = isolate->factory()->NewFixedArray(found.length());
44   for (int i = 0; i < found.length(); ++i) {
45     Handle<SharedFunctionInfo> shared = found[i];
46     SharedInfoWrapper info_wrapper = SharedInfoWrapper::Create(isolate);
47     Handle<String> name(String::cast(shared->name()));
48     info_wrapper.SetProperties(name, shared->start_position(),
49                                shared->end_position(), shared);
50     result->set(i, *info_wrapper.GetJSArray());
51   }
52   return *isolate->factory()->NewJSArrayWithElements(result);
53 }
54 
55 
56 // For a script calculates compilation information about all its functions.
57 // The script source is explicitly specified by the second argument.
58 // The source of the actual script is not used, however it is important that
59 // all generated code keeps references to this particular instance of script.
60 // Returns a JSArray of compilation infos. The array is ordered so that
61 // each function with all its descendant is always stored in a continues range
62 // with the function itself going first. The root function is a script function.
RUNTIME_FUNCTION(Runtime_LiveEditGatherCompileInfo)63 RUNTIME_FUNCTION(Runtime_LiveEditGatherCompileInfo) {
64   HandleScope scope(isolate);
65   CHECK(isolate->debug()->live_edit_enabled());
66   DCHECK(args.length() == 2);
67   CONVERT_ARG_CHECKED(JSValue, script, 0);
68   CONVERT_ARG_HANDLE_CHECKED(String, source, 1);
69 
70   CHECK(script->value()->IsScript());
71   Handle<Script> script_handle = Handle<Script>(Script::cast(script->value()));
72 
73   RETURN_RESULT_OR_FAILURE(isolate,
74                            LiveEdit::GatherCompileInfo(script_handle, source));
75 }
76 
77 
78 // Changes the source of the script to a new_source.
79 // If old_script_name is provided (i.e. is a String), also creates a copy of
80 // the script with its original source and sends notification to debugger.
RUNTIME_FUNCTION(Runtime_LiveEditReplaceScript)81 RUNTIME_FUNCTION(Runtime_LiveEditReplaceScript) {
82   HandleScope scope(isolate);
83   CHECK(isolate->debug()->live_edit_enabled());
84   DCHECK(args.length() == 3);
85   CONVERT_ARG_CHECKED(JSValue, original_script_value, 0);
86   CONVERT_ARG_HANDLE_CHECKED(String, new_source, 1);
87   CONVERT_ARG_HANDLE_CHECKED(Object, old_script_name, 2);
88 
89   CHECK(original_script_value->value()->IsScript());
90   Handle<Script> original_script(Script::cast(original_script_value->value()));
91 
92   Handle<Object> old_script = LiveEdit::ChangeScriptSource(
93       original_script, new_source, old_script_name);
94 
95   if (old_script->IsScript()) {
96     Handle<Script> script_handle = Handle<Script>::cast(old_script);
97     return *Script::GetWrapper(script_handle);
98   } else {
99     return isolate->heap()->null_value();
100   }
101 }
102 
103 
RUNTIME_FUNCTION(Runtime_LiveEditFunctionSourceUpdated)104 RUNTIME_FUNCTION(Runtime_LiveEditFunctionSourceUpdated) {
105   HandleScope scope(isolate);
106   CHECK(isolate->debug()->live_edit_enabled());
107   DCHECK(args.length() == 1);
108   CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_info, 0);
109   CHECK(SharedInfoWrapper::IsInstance(shared_info));
110 
111   LiveEdit::FunctionSourceUpdated(shared_info);
112   return isolate->heap()->undefined_value();
113 }
114 
115 
116 // Replaces code of SharedFunctionInfo with a new one.
RUNTIME_FUNCTION(Runtime_LiveEditReplaceFunctionCode)117 RUNTIME_FUNCTION(Runtime_LiveEditReplaceFunctionCode) {
118   HandleScope scope(isolate);
119   CHECK(isolate->debug()->live_edit_enabled());
120   DCHECK(args.length() == 2);
121   CONVERT_ARG_HANDLE_CHECKED(JSArray, new_compile_info, 0);
122   CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_info, 1);
123   CHECK(SharedInfoWrapper::IsInstance(shared_info));
124 
125   LiveEdit::ReplaceFunctionCode(new_compile_info, shared_info);
126   return isolate->heap()->undefined_value();
127 }
128 
129 
130 // Connects SharedFunctionInfo to another script.
RUNTIME_FUNCTION(Runtime_LiveEditFunctionSetScript)131 RUNTIME_FUNCTION(Runtime_LiveEditFunctionSetScript) {
132   HandleScope scope(isolate);
133   CHECK(isolate->debug()->live_edit_enabled());
134   DCHECK(args.length() == 2);
135   CONVERT_ARG_HANDLE_CHECKED(Object, function_object, 0);
136   CONVERT_ARG_HANDLE_CHECKED(Object, script_object, 1);
137 
138   if (function_object->IsJSValue()) {
139     Handle<JSValue> function_wrapper = Handle<JSValue>::cast(function_object);
140     if (script_object->IsJSValue()) {
141       CHECK(JSValue::cast(*script_object)->value()->IsScript());
142       Script* script = Script::cast(JSValue::cast(*script_object)->value());
143       script_object = Handle<Object>(script, isolate);
144     }
145     CHECK(function_wrapper->value()->IsSharedFunctionInfo());
146     LiveEdit::SetFunctionScript(function_wrapper, script_object);
147   } else {
148     // Just ignore this. We may not have a SharedFunctionInfo for some functions
149     // and we check it in this function.
150   }
151 
152   return isolate->heap()->undefined_value();
153 }
154 
155 
156 // In a code of a parent function replaces original function as embedded object
157 // with a substitution one.
RUNTIME_FUNCTION(Runtime_LiveEditReplaceRefToNestedFunction)158 RUNTIME_FUNCTION(Runtime_LiveEditReplaceRefToNestedFunction) {
159   HandleScope scope(isolate);
160   CHECK(isolate->debug()->live_edit_enabled());
161   DCHECK(args.length() == 3);
162 
163   CONVERT_ARG_HANDLE_CHECKED(JSValue, parent_wrapper, 0);
164   CONVERT_ARG_HANDLE_CHECKED(JSValue, orig_wrapper, 1);
165   CONVERT_ARG_HANDLE_CHECKED(JSValue, subst_wrapper, 2);
166   CHECK(parent_wrapper->value()->IsSharedFunctionInfo());
167   CHECK(orig_wrapper->value()->IsSharedFunctionInfo());
168   CHECK(subst_wrapper->value()->IsSharedFunctionInfo());
169 
170   LiveEdit::ReplaceRefToNestedFunction(parent_wrapper, orig_wrapper,
171                                        subst_wrapper);
172   return isolate->heap()->undefined_value();
173 }
174 
175 
176 // Updates positions of a shared function info (first parameter) according
177 // to script source change. Text change is described in second parameter as
178 // array of groups of 3 numbers:
179 // (change_begin, change_end, change_end_new_position).
180 // Each group describes a change in text; groups are sorted by change_begin.
RUNTIME_FUNCTION(Runtime_LiveEditPatchFunctionPositions)181 RUNTIME_FUNCTION(Runtime_LiveEditPatchFunctionPositions) {
182   HandleScope scope(isolate);
183   CHECK(isolate->debug()->live_edit_enabled());
184   DCHECK(args.length() == 2);
185   CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_array, 0);
186   CONVERT_ARG_HANDLE_CHECKED(JSArray, position_change_array, 1);
187   CHECK(SharedInfoWrapper::IsInstance(shared_array));
188 
189   LiveEdit::PatchFunctionPositions(shared_array, position_change_array);
190   return isolate->heap()->undefined_value();
191 }
192 
193 
194 // For array of SharedFunctionInfo's (each wrapped in JSValue)
195 // checks that none of them have activations on stacks (of any thread).
196 // Returns array of the same length with corresponding results of
197 // LiveEdit::FunctionPatchabilityStatus type.
RUNTIME_FUNCTION(Runtime_LiveEditCheckAndDropActivations)198 RUNTIME_FUNCTION(Runtime_LiveEditCheckAndDropActivations) {
199   HandleScope scope(isolate);
200   CHECK(isolate->debug()->live_edit_enabled());
201   DCHECK(args.length() == 3);
202   CONVERT_ARG_HANDLE_CHECKED(JSArray, old_shared_array, 0);
203   CONVERT_ARG_HANDLE_CHECKED(JSArray, new_shared_array, 1);
204   CONVERT_BOOLEAN_ARG_CHECKED(do_drop, 2);
205   USE(new_shared_array);
206   CHECK(old_shared_array->length()->IsSmi());
207   CHECK(new_shared_array->length() == old_shared_array->length());
208   CHECK(old_shared_array->HasFastElements());
209   CHECK(new_shared_array->HasFastElements());
210   int array_length = Smi::cast(old_shared_array->length())->value();
211   for (int i = 0; i < array_length; i++) {
212     Handle<Object> old_element;
213     Handle<Object> new_element;
214     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
215         isolate, old_element,
216         JSReceiver::GetElement(isolate, old_shared_array, i));
217     CHECK(old_element->IsJSValue() &&
218           Handle<JSValue>::cast(old_element)->value()->IsSharedFunctionInfo());
219     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
220         isolate, new_element,
221         JSReceiver::GetElement(isolate, new_shared_array, i));
222     CHECK(
223         new_element->IsUndefined(isolate) ||
224         (new_element->IsJSValue() &&
225          Handle<JSValue>::cast(new_element)->value()->IsSharedFunctionInfo()));
226   }
227 
228   return *LiveEdit::CheckAndDropActivations(old_shared_array, new_shared_array,
229                                             do_drop);
230 }
231 
232 
233 // Compares 2 strings line-by-line, then token-wise and returns diff in form
234 // of JSArray of triplets (pos1, pos1_end, pos2_end) describing list
235 // of diff chunks.
RUNTIME_FUNCTION(Runtime_LiveEditCompareStrings)236 RUNTIME_FUNCTION(Runtime_LiveEditCompareStrings) {
237   HandleScope scope(isolate);
238   CHECK(isolate->debug()->live_edit_enabled());
239   DCHECK(args.length() == 2);
240   CONVERT_ARG_HANDLE_CHECKED(String, s1, 0);
241   CONVERT_ARG_HANDLE_CHECKED(String, s2, 1);
242 
243   Handle<JSArray> result = LiveEdit::CompareStrings(s1, s2);
244   uint32_t array_length = 0;
245   CHECK(result->length()->ToArrayLength(&array_length));
246   if (array_length > 0) {
247     isolate->debug()->feature_tracker()->Track(DebugFeatureTracker::kLiveEdit);
248   }
249 
250   return *result;
251 }
252 
253 
254 // Restarts a call frame and completely drops all frames above.
255 // Returns true if successful. Otherwise returns undefined or an error message.
RUNTIME_FUNCTION(Runtime_LiveEditRestartFrame)256 RUNTIME_FUNCTION(Runtime_LiveEditRestartFrame) {
257   HandleScope scope(isolate);
258   CHECK(isolate->debug()->live_edit_enabled());
259   DCHECK(args.length() == 2);
260   CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
261   CHECK(isolate->debug()->CheckExecutionState(break_id));
262 
263   CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
264   Heap* heap = isolate->heap();
265 
266   // Find the relevant frame with the requested index.
267   StackFrame::Id id = isolate->debug()->break_frame_id();
268   if (id == StackFrame::NO_ID) {
269     // If there are no JavaScript stack frames return undefined.
270     return heap->undefined_value();
271   }
272 
273   StackTraceFrameIterator it(isolate, id);
274   int inlined_jsframe_index =
275       DebugFrameHelper::FindIndexedNonNativeFrame(&it, index);
276   // Liveedit is not supported on Wasm.
277   if (inlined_jsframe_index == -1 || it.is_wasm()) {
278     return heap->undefined_value();
279   }
280   // We don't really care what the inlined frame index is, since we are
281   // throwing away the entire frame anyways.
282   const char* error_message = LiveEdit::RestartFrame(it.javascript_frame());
283   if (error_message) {
284     return *(isolate->factory()->InternalizeUtf8String(error_message));
285   }
286   return heap->true_value();
287 }
288 }  // namespace internal
289 }  // namespace v8
290