// 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. #include "src/runtime/runtime-utils.h" #include "src/arguments.h" #include "src/debug/debug.h" #include "src/debug/debug-frames.h" #include "src/debug/liveedit.h" #include "src/frames-inl.h" #include "src/isolate-inl.h" #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()); DCHECK(args.length() == 1); CONVERT_ARG_CHECKED(JSValue, script_value, 0); CHECK(script_value->value()->IsScript()); Handle<Script> script = Handle<Script>(Script::cast(script_value->value())); List<Handle<SharedFunctionInfo> > found; Heap* heap = isolate->heap(); { HeapIterator iterator(heap); HeapObject* heap_obj; while ((heap_obj = iterator.next())) { if (!heap_obj->IsSharedFunctionInfo()) continue; SharedFunctionInfo* shared = SharedFunctionInfo::cast(heap_obj); if (shared->script() != *script) continue; found.Add(Handle<SharedFunctionInfo>(shared)); } } 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); Handle<String> name(String::cast(shared->name())); info_wrapper.SetProperties(name, shared->start_position(), shared->end_position(), shared); result->set(i, *info_wrapper.GetJSArray()); } return *isolate->factory()->NewJSArrayWithElements(result); } // 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()); DCHECK(args.length() == 2); CONVERT_ARG_CHECKED(JSValue, script, 0); CONVERT_ARG_HANDLE_CHECKED(String, source, 1); CHECK(script->value()->IsScript()); Handle<Script> script_handle = Handle<Script>(Script::cast(script->value())); RETURN_RESULT_OR_FAILURE(isolate, LiveEdit::GatherCompileInfo(script_handle, source)); } // 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()); DCHECK(args.length() == 3); 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); CHECK(original_script_value->value()->IsScript()); 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(); } } RUNTIME_FUNCTION(Runtime_LiveEditFunctionSourceUpdated) { HandleScope scope(isolate); CHECK(isolate->debug()->live_edit_enabled()); DCHECK(args.length() == 1); CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_info, 0); CHECK(SharedInfoWrapper::IsInstance(shared_info)); LiveEdit::FunctionSourceUpdated(shared_info); 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()); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(JSArray, new_compile_info, 0); CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_info, 1); CHECK(SharedInfoWrapper::IsInstance(shared_info)); 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()); DCHECK(args.length() == 2); 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()) { CHECK(JSValue::cast(*script_object)->value()->IsScript()); Script* script = Script::cast(JSValue::cast(*script_object)->value()); script_object = Handle<Object>(script, isolate); } CHECK(function_wrapper->value()->IsSharedFunctionInfo()); 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()); DCHECK(args.length() == 3); CONVERT_ARG_HANDLE_CHECKED(JSValue, parent_wrapper, 0); CONVERT_ARG_HANDLE_CHECKED(JSValue, orig_wrapper, 1); CONVERT_ARG_HANDLE_CHECKED(JSValue, subst_wrapper, 2); CHECK(parent_wrapper->value()->IsSharedFunctionInfo()); CHECK(orig_wrapper->value()->IsSharedFunctionInfo()); CHECK(subst_wrapper->value()->IsSharedFunctionInfo()); 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()); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(JSArray, shared_array, 0); CONVERT_ARG_HANDLE_CHECKED(JSArray, position_change_array, 1); CHECK(SharedInfoWrapper::IsInstance(shared_array)); 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()); DCHECK(args.length() == 3); 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); 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()); int array_length = Smi::cast(old_shared_array->length())->value(); for (int i = 0; i < array_length; i++) { Handle<Object> old_element; Handle<Object> new_element; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, old_element, JSReceiver::GetElement(isolate, old_shared_array, i)); CHECK(old_element->IsJSValue() && Handle<JSValue>::cast(old_element)->value()->IsSharedFunctionInfo()); ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, new_element, JSReceiver::GetElement(isolate, new_shared_array, i)); CHECK( new_element->IsUndefined(isolate) || (new_element->IsJSValue() && Handle<JSValue>::cast(new_element)->value()->IsSharedFunctionInfo())); } return *LiveEdit::CheckAndDropActivations(old_shared_array, new_shared_array, do_drop); } // 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()); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(String, s1, 0); CONVERT_ARG_HANDLE_CHECKED(String, s2, 1); Handle<JSArray> result = LiveEdit::CompareStrings(s1, s2); uint32_t array_length = 0; CHECK(result->length()->ToArrayLength(&array_length)); if (array_length > 0) { isolate->debug()->feature_tracker()->Track(DebugFeatureTracker::kLiveEdit); } return *result; } // 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()); DCHECK(args.length() == 2); CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]); CHECK(isolate->debug()->CheckExecutionState(break_id)); 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(); } StackTraceFrameIterator it(isolate, id); int inlined_jsframe_index = DebugFrameHelper::FindIndexedNonNativeFrame(&it, index); // Liveedit is not supported on Wasm. if (inlined_jsframe_index == -1 || it.is_wasm()) { return heap->undefined_value(); } // We don't really care what the inlined frame index is, since we are // throwing away the entire frame anyways. const char* error_message = LiveEdit::RestartFrame(it.javascript_frame()); if (error_message) { return *(isolate->factory()->InternalizeUtf8String(error_message)); } return heap->true_value(); } } // namespace internal } // namespace v8