runtime-liveedit.cc 11.6 KB
Newer Older
1 2 3 4
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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 14 15 16 17 18 19 20 21 22 23
#include "src/runtime/runtime.h"

namespace v8 {
namespace internal {

// For a script finds all SharedFunctionInfo's in the heap that points
// to this script. Returns JSArray of SharedFunctionInfo wrapped
// in OpaqueReferences.
RUNTIME_FUNCTION(Runtime_LiveEditFindSharedFunctionInfosForScript) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
24
  DCHECK_EQ(1, args.length());
25 26
  CONVERT_ARG_CHECKED(JSValue, script_value, 0);

27
  CHECK(script_value->value()->IsScript());
28 29
  Handle<Script> script = Handle<Script>(Script::cast(script_value->value()));

30
  List<Handle<SharedFunctionInfo> > found;
31 32
  Heap* heap = isolate->heap();
  {
33 34
    HeapIterator iterator(heap);
    HeapObject* heap_obj;
35
    while ((heap_obj = iterator.next()) != nullptr) {
36 37 38 39 40
      if (!heap_obj->IsSharedFunctionInfo()) continue;
      SharedFunctionInfo* shared = SharedFunctionInfo::cast(heap_obj);
      if (shared->script() != *script) continue;
      found.Add(Handle<SharedFunctionInfo>(shared));
    }
41 42
  }

43 44 45 46
  Handle<FixedArray> result = isolate->factory()->NewFixedArray(found.length());
  for (int i = 0; i < found.length(); ++i) {
    Handle<SharedFunctionInfo> shared = found[i];
    SharedInfoWrapper info_wrapper = SharedInfoWrapper::Create(isolate);
47
    Handle<String> name(shared->name(), isolate);
48 49 50 51 52
    info_wrapper.SetProperties(name, shared->start_position(),
                               shared->end_position(), shared);
    result->set(i, *info_wrapper.GetJSArray());
  }
  return *isolate->factory()->NewJSArrayWithElements(result);
53 54 55 56 57 58 59 60 61 62 63 64 65
}


// For a script calculates compilation information about all its functions.
// The script source is explicitly specified by the second argument.
// The source of the actual script is not used, however it is important that
// all generated code keeps references to this particular instance of script.
// Returns a JSArray of compilation infos. The array is ordered so that
// each function with all its descendant is always stored in a continues range
// with the function itself going first. The root function is a script function.
RUNTIME_FUNCTION(Runtime_LiveEditGatherCompileInfo) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
66
  DCHECK_EQ(2, args.length());
67 68 69
  CONVERT_ARG_CHECKED(JSValue, script, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, source, 1);

70
  CHECK(script->value()->IsScript());
71 72
  Handle<Script> script_handle = Handle<Script>(Script::cast(script->value()));

73 74
  RETURN_RESULT_OR_FAILURE(isolate,
                           LiveEdit::GatherCompileInfo(script_handle, source));
75 76 77 78 79 80 81 82 83
}


// Changes the source of the script to a new_source.
// If old_script_name is provided (i.e. is a String), also creates a copy of
// the script with its original source and sends notification to debugger.
RUNTIME_FUNCTION(Runtime_LiveEditReplaceScript) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
84
  DCHECK_EQ(3, args.length());
85 86 87 88
  CONVERT_ARG_CHECKED(JSValue, original_script_value, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, new_source, 1);
  CONVERT_ARG_HANDLE_CHECKED(Object, old_script_name, 2);

89
  CHECK(original_script_value->value()->IsScript());
90 91 92 93 94 95 96 97 98 99 100 101 102
  Handle<Script> original_script(Script::cast(original_script_value->value()));

  Handle<Object> old_script = LiveEdit::ChangeScriptSource(
      original_script, new_source, old_script_name);

  if (old_script->IsScript()) {
    Handle<Script> script_handle = Handle<Script>::cast(old_script);
    return *Script::GetWrapper(script_handle);
  } else {
    return isolate->heap()->null_value();
  }
}

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
// Recreate the shared function infos array after changing the IDs of all
// SharedFunctionInfos.
RUNTIME_FUNCTION(Runtime_LiveEditFixupScript) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
  DCHECK_EQ(args.length(), 2);
  CONVERT_ARG_CHECKED(JSValue, script_value, 0);
  CONVERT_INT32_ARG_CHECKED(max_function_literal_id, 1);

  CHECK(script_value->value()->IsScript());
  Handle<Script> script(Script::cast(script_value->value()));

  LiveEdit::FixupScript(script, max_function_literal_id);
  return isolate->heap()->undefined_value();
}
118 119 120 121

RUNTIME_FUNCTION(Runtime_LiveEditFunctionSourceUpdated) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
122
  DCHECK_EQ(args.length(), 2);
123
  CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_info, 0);
124
  CONVERT_INT32_ARG_CHECKED(new_function_literal_id, 1);
125
  CHECK(SharedInfoWrapper::IsInstance(shared_info));
126

127
  LiveEdit::FunctionSourceUpdated(shared_info, new_function_literal_id);
128 129 130 131 132 133 134 135
  return isolate->heap()->undefined_value();
}


// Replaces code of SharedFunctionInfo with a new one.
RUNTIME_FUNCTION(Runtime_LiveEditReplaceFunctionCode) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
136
  DCHECK_EQ(2, args.length());
137 138
  CONVERT_ARG_HANDLE_CHECKED(JSArray, new_compile_info, 0);
  CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_info, 1);
139
  CHECK(SharedInfoWrapper::IsInstance(shared_info));
140 141 142 143 144 145 146 147 148 149

  LiveEdit::ReplaceFunctionCode(new_compile_info, shared_info);
  return isolate->heap()->undefined_value();
}


// Connects SharedFunctionInfo to another script.
RUNTIME_FUNCTION(Runtime_LiveEditFunctionSetScript) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
150
  DCHECK_EQ(2, args.length());
151 152 153 154 155 156
  CONVERT_ARG_HANDLE_CHECKED(Object, function_object, 0);
  CONVERT_ARG_HANDLE_CHECKED(Object, script_object, 1);

  if (function_object->IsJSValue()) {
    Handle<JSValue> function_wrapper = Handle<JSValue>::cast(function_object);
    if (script_object->IsJSValue()) {
157
      CHECK(JSValue::cast(*script_object)->value()->IsScript());
158 159 160
      Script* script = Script::cast(JSValue::cast(*script_object)->value());
      script_object = Handle<Object>(script, isolate);
    }
161
    CHECK(function_wrapper->value()->IsSharedFunctionInfo());
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    LiveEdit::SetFunctionScript(function_wrapper, script_object);
  } else {
    // Just ignore this. We may not have a SharedFunctionInfo for some functions
    // and we check it in this function.
  }

  return isolate->heap()->undefined_value();
}


// In a code of a parent function replaces original function as embedded object
// with a substitution one.
RUNTIME_FUNCTION(Runtime_LiveEditReplaceRefToNestedFunction) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
177
  DCHECK_EQ(3, args.length());
178 179 180 181

  CONVERT_ARG_HANDLE_CHECKED(JSValue, parent_wrapper, 0);
  CONVERT_ARG_HANDLE_CHECKED(JSValue, orig_wrapper, 1);
  CONVERT_ARG_HANDLE_CHECKED(JSValue, subst_wrapper, 2);
182 183 184
  CHECK(parent_wrapper->value()->IsSharedFunctionInfo());
  CHECK(orig_wrapper->value()->IsSharedFunctionInfo());
  CHECK(subst_wrapper->value()->IsSharedFunctionInfo());
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

  LiveEdit::ReplaceRefToNestedFunction(parent_wrapper, orig_wrapper,
                                       subst_wrapper);
  return isolate->heap()->undefined_value();
}


// Updates positions of a shared function info (first parameter) according
// to script source change. Text change is described in second parameter as
// array of groups of 3 numbers:
// (change_begin, change_end, change_end_new_position).
// Each group describes a change in text; groups are sorted by change_begin.
RUNTIME_FUNCTION(Runtime_LiveEditPatchFunctionPositions) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
200
  DCHECK_EQ(2, args.length());
201 202
  CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_array, 0);
  CONVERT_ARG_HANDLE_CHECKED(JSArray, position_change_array, 1);
203
  CHECK(SharedInfoWrapper::IsInstance(shared_array));
204 205 206 207 208 209 210 211 212 213 214 215 216

  LiveEdit::PatchFunctionPositions(shared_array, position_change_array);
  return isolate->heap()->undefined_value();
}


// For array of SharedFunctionInfo's (each wrapped in JSValue)
// checks that none of them have activations on stacks (of any thread).
// Returns array of the same length with corresponding results of
// LiveEdit::FunctionPatchabilityStatus type.
RUNTIME_FUNCTION(Runtime_LiveEditCheckAndDropActivations) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
217
  DCHECK_EQ(3, args.length());
218 219 220 221
  CONVERT_ARG_HANDLE_CHECKED(JSArray, old_shared_array, 0);
  CONVERT_ARG_HANDLE_CHECKED(JSArray, new_shared_array, 1);
  CONVERT_BOOLEAN_ARG_CHECKED(do_drop, 2);
  USE(new_shared_array);
222 223 224 225
  CHECK(old_shared_array->length()->IsSmi());
  CHECK(new_shared_array->length() == old_shared_array->length());
  CHECK(old_shared_array->HasFastElements());
  CHECK(new_shared_array->HasFastElements());
226
  int array_length = Smi::cast(old_shared_array->length())->value();
227
  for (int i = 0; i < array_length; i++) {
228 229 230
    Handle<Object> old_element;
    Handle<Object> new_element;
    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
231 232
        isolate, old_element,
        JSReceiver::GetElement(isolate, old_shared_array, i));
233 234
    CHECK(old_element->IsJSValue() &&
          Handle<JSValue>::cast(old_element)->value()->IsSharedFunctionInfo());
235
    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
236 237
        isolate, new_element,
        JSReceiver::GetElement(isolate, new_shared_array, i));
238
    CHECK(
239
        new_element->IsUndefined(isolate) ||
240 241
        (new_element->IsJSValue() &&
         Handle<JSValue>::cast(new_element)->value()->IsSharedFunctionInfo()));
242 243
  }

244 245
  return *LiveEdit::CheckAndDropActivations(old_shared_array, new_shared_array,
                                            do_drop);
246 247 248 249 250 251 252 253 254
}


// Compares 2 strings line-by-line, then token-wise and returns diff in form
// of JSArray of triplets (pos1, pos1_end, pos2_end) describing list
// of diff chunks.
RUNTIME_FUNCTION(Runtime_LiveEditCompareStrings) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
255
  DCHECK_EQ(2, args.length());
256 257 258
  CONVERT_ARG_HANDLE_CHECKED(String, s1, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, s2, 1);

259
  Handle<JSArray> result = LiveEdit::CompareStrings(s1, s2);
260
  uint32_t array_length = 0;
261 262 263 264
  CHECK(result->length()->ToArrayLength(&array_length));
  if (array_length > 0) {
    isolate->debug()->feature_tracker()->Track(DebugFeatureTracker::kLiveEdit);
  }
265

266
  return *result;
267 268 269 270 271 272 273 274
}


// Restarts a call frame and completely drops all frames above.
// Returns true if successful. Otherwise returns undefined or an error message.
RUNTIME_FUNCTION(Runtime_LiveEditRestartFrame) {
  HandleScope scope(isolate);
  CHECK(isolate->debug()->live_edit_enabled());
275
  DCHECK_EQ(2, args.length());
276
  CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
277
  CHECK(isolate->debug()->CheckExecutionState(break_id));
278 279 280 281 282 283 284 285 286 287 288

  CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
  Heap* heap = isolate->heap();

  // Find the relevant frame with the requested index.
  StackFrame::Id id = isolate->debug()->break_frame_id();
  if (id == StackFrame::NO_ID) {
    // If there are no JavaScript stack frames return undefined.
    return heap->undefined_value();
  }

289
  StackTraceFrameIterator it(isolate, id);
290 291
  int inlined_jsframe_index =
      DebugFrameHelper::FindIndexedNonNativeFrame(&it, index);
292 293 294 295
  // Liveedit is not supported on Wasm.
  if (inlined_jsframe_index == -1 || it.is_wasm()) {
    return heap->undefined_value();
  }
296 297
  // We don't really care what the inlined frame index is, since we are
  // throwing away the entire frame anyways.
298
  const char* error_message = LiveEdit::RestartFrame(it.javascript_frame());
299 300 301 302 303
  if (error_message) {
    return *(isolate->factory()->InternalizeUtf8String(error_message));
  }
  return heap->true_value();
}
304 305
}  // namespace internal
}  // namespace v8