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
15namespace v8 {
16namespace 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.
21RUNTIME_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.
63RUNTIME_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.
81RUNTIME_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
104RUNTIME_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.
117RUNTIME_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.
131RUNTIME_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.
158RUNTIME_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.
181RUNTIME_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.
198RUNTIME_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.
236RUNTIME_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.
256RUNTIME_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