| // 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 <vector> | |
| #include "src/arguments-inl.h" | |
| #include "src/compiler.h" | |
| #include "src/debug/debug-coverage.h" | |
| #include "src/debug/debug-evaluate.h" | |
| #include "src/debug/debug-frames.h" | |
| #include "src/debug/debug-scopes.h" | |
| #include "src/debug/debug.h" | |
| #include "src/debug/liveedit.h" | |
| #include "src/frames-inl.h" | |
| #include "src/globals.h" | |
| #include "src/interpreter/bytecode-array-accessor.h" | |
| #include "src/interpreter/bytecodes.h" | |
| #include "src/interpreter/interpreter.h" | |
| #include "src/isolate-inl.h" | |
| #include "src/objects/debug-objects-inl.h" | |
| #include "src/objects/js-collection-inl.h" | |
| #include "src/objects/js-generator-inl.h" | |
| #include "src/objects/js-promise-inl.h" | |
| #include "src/runtime/runtime-utils.h" | |
| #include "src/runtime/runtime.h" | |
| #include "src/snapshot/snapshot.h" | |
| #include "src/wasm/wasm-objects-inl.h" | |
| namespace v8 { | |
| namespace internal { | |
| RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) { | |
| using interpreter::Bytecode; | |
| using interpreter::Bytecodes; | |
| using interpreter::OperandScale; | |
| SealHandleScope shs(isolate); | |
| DCHECK_EQ(1, args.length()); | |
| CONVERT_ARG_HANDLE_CHECKED(Object, value, 0); | |
| HandleScope scope(isolate); | |
| // Return value can be changed by debugger. Last set value will be used as | |
| // return value. | |
| ReturnValueScope result_scope(isolate->debug()); | |
| isolate->debug()->set_return_value(*value); | |
| // Get the top-most JavaScript frame. | |
| JavaScriptFrameIterator it(isolate); | |
| if (isolate->debug_execution_mode() == DebugInfo::kBreakpoints) { | |
| isolate->debug()->Break(it.frame(), | |
| handle(it.frame()->function(), isolate)); | |
| } | |
| // Return the handler from the original bytecode array. | |
| DCHECK(it.frame()->is_interpreted()); | |
| InterpretedFrame* interpreted_frame = | |
| reinterpret_cast<InterpretedFrame*>(it.frame()); | |
| SharedFunctionInfo* shared = interpreted_frame->function()->shared(); | |
| BytecodeArray* bytecode_array = shared->GetBytecodeArray(); | |
| int bytecode_offset = interpreted_frame->GetBytecodeOffset(); | |
| Bytecode bytecode = Bytecodes::FromByte(bytecode_array->get(bytecode_offset)); | |
| bool side_effect_check_failed = false; | |
| if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) { | |
| side_effect_check_failed = | |
| !isolate->debug()->PerformSideEffectCheckAtBytecode(interpreted_frame); | |
| } | |
| if (Bytecodes::Returns(bytecode)) { | |
| // If we are returning (or suspending), reset the bytecode array on the | |
| // interpreted stack frame to the non-debug variant so that the interpreter | |
| // entry trampoline sees the return/suspend bytecode rather than the | |
| // DebugBreak. | |
| interpreted_frame->PatchBytecodeArray(bytecode_array); | |
| } | |
| // We do not have to deal with operand scale here. If the bytecode at the | |
| // break is prefixed by operand scaling, we would have patched over the | |
| // scaling prefix. We now simply dispatch to the handler for the prefix. | |
| // We need to deserialize now to ensure we don't hit the debug break again | |
| // after deserializing. | |
| OperandScale operand_scale = OperandScale::kSingle; | |
| isolate->interpreter()->GetAndMaybeDeserializeBytecodeHandler(bytecode, | |
| operand_scale); | |
| if (side_effect_check_failed) { | |
| return MakePair(ReadOnlyRoots(isolate).exception(), | |
| Smi::FromInt(static_cast<uint8_t>(bytecode))); | |
| } | |
| Object* interrupt_object = isolate->stack_guard()->HandleInterrupts(); | |
| if (interrupt_object->IsException(isolate)) { | |
| return MakePair(interrupt_object, | |
| Smi::FromInt(static_cast<uint8_t>(bytecode))); | |
| } | |
| return MakePair(isolate->debug()->return_value(), | |
| Smi::FromInt(static_cast<uint8_t>(bytecode))); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugBreakAtEntry) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(1, args.length()); | |
| CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0); | |
| USE(function); | |
| DCHECK(function->shared()->HasDebugInfo()); | |
| DCHECK(function->shared()->GetDebugInfo()->BreakAtEntry()); | |
| // Get the top-most JavaScript frame. | |
| JavaScriptFrameIterator it(isolate); | |
| DCHECK_EQ(*function, it.frame()->function()); | |
| isolate->debug()->Break(it.frame(), function); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_HandleDebuggerStatement) { | |
| SealHandleScope shs(isolate); | |
| DCHECK_EQ(0, args.length()); | |
| if (isolate->debug()->break_points_active()) { | |
| isolate->debug()->HandleDebugBreak(kIgnoreIfTopFrameBlackboxed); | |
| } | |
| return isolate->stack_guard()->HandleInterrupts(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_ScheduleBreak) { | |
| SealHandleScope shs(isolate); | |
| DCHECK_EQ(0, args.length()); | |
| isolate->RequestInterrupt( | |
| [](v8::Isolate* isolate, void*) { v8::debug::BreakRightNow(isolate); }, | |
| nullptr); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| template <class IteratorType> | |
| static MaybeHandle<JSArray> GetIteratorInternalProperties( | |
| Isolate* isolate, Handle<IteratorType> object) { | |
| Factory* factory = isolate->factory(); | |
| Handle<IteratorType> iterator = Handle<IteratorType>::cast(object); | |
| const char* kind = nullptr; | |
| switch (iterator->map()->instance_type()) { | |
| case JS_MAP_KEY_ITERATOR_TYPE: | |
| kind = "keys"; | |
| break; | |
| case JS_MAP_KEY_VALUE_ITERATOR_TYPE: | |
| case JS_SET_KEY_VALUE_ITERATOR_TYPE: | |
| kind = "entries"; | |
| break; | |
| case JS_MAP_VALUE_ITERATOR_TYPE: | |
| case JS_SET_VALUE_ITERATOR_TYPE: | |
| kind = "values"; | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| Handle<FixedArray> result = factory->NewFixedArray(2 * 3); | |
| Handle<String> has_more = | |
| factory->NewStringFromAsciiChecked("[[IteratorHasMore]]"); | |
| result->set(0, *has_more); | |
| result->set(1, isolate->heap()->ToBoolean(iterator->HasMore())); | |
| Handle<String> index = | |
| factory->NewStringFromAsciiChecked("[[IteratorIndex]]"); | |
| result->set(2, *index); | |
| result->set(3, iterator->index()); | |
| Handle<String> iterator_kind = | |
| factory->NewStringFromAsciiChecked("[[IteratorKind]]"); | |
| result->set(4, *iterator_kind); | |
| Handle<String> kind_str = factory->NewStringFromAsciiChecked(kind); | |
| result->set(5, *kind_str); | |
| return factory->NewJSArrayWithElements(result); | |
| } | |
| MaybeHandle<JSArray> Runtime::GetInternalProperties(Isolate* isolate, | |
| Handle<Object> object) { | |
| Factory* factory = isolate->factory(); | |
| if (object->IsJSBoundFunction()) { | |
| Handle<JSBoundFunction> function = Handle<JSBoundFunction>::cast(object); | |
| Handle<FixedArray> result = factory->NewFixedArray(2 * 3); | |
| Handle<String> target = | |
| factory->NewStringFromAsciiChecked("[[TargetFunction]]"); | |
| result->set(0, *target); | |
| result->set(1, function->bound_target_function()); | |
| Handle<String> bound_this = | |
| factory->NewStringFromAsciiChecked("[[BoundThis]]"); | |
| result->set(2, *bound_this); | |
| result->set(3, function->bound_this()); | |
| Handle<String> bound_args = | |
| factory->NewStringFromAsciiChecked("[[BoundArgs]]"); | |
| result->set(4, *bound_args); | |
| Handle<FixedArray> bound_arguments = | |
| factory->CopyFixedArray(handle(function->bound_arguments(), isolate)); | |
| Handle<JSArray> arguments_array = | |
| factory->NewJSArrayWithElements(bound_arguments); | |
| result->set(5, *arguments_array); | |
| return factory->NewJSArrayWithElements(result); | |
| } else if (object->IsJSMapIterator()) { | |
| Handle<JSMapIterator> iterator = Handle<JSMapIterator>::cast(object); | |
| return GetIteratorInternalProperties(isolate, iterator); | |
| } else if (object->IsJSSetIterator()) { | |
| Handle<JSSetIterator> iterator = Handle<JSSetIterator>::cast(object); | |
| return GetIteratorInternalProperties(isolate, iterator); | |
| } else if (object->IsJSGeneratorObject()) { | |
| Handle<JSGeneratorObject> generator = | |
| Handle<JSGeneratorObject>::cast(object); | |
| const char* status = "suspended"; | |
| if (generator->is_closed()) { | |
| status = "closed"; | |
| } else if (generator->is_executing()) { | |
| status = "running"; | |
| } else { | |
| DCHECK(generator->is_suspended()); | |
| } | |
| Handle<FixedArray> result = factory->NewFixedArray(2 * 3); | |
| Handle<String> generator_status = | |
| factory->NewStringFromAsciiChecked("[[GeneratorStatus]]"); | |
| result->set(0, *generator_status); | |
| Handle<String> status_str = factory->NewStringFromAsciiChecked(status); | |
| result->set(1, *status_str); | |
| Handle<String> function = | |
| factory->NewStringFromAsciiChecked("[[GeneratorFunction]]"); | |
| result->set(2, *function); | |
| result->set(3, generator->function()); | |
| Handle<String> receiver = | |
| factory->NewStringFromAsciiChecked("[[GeneratorReceiver]]"); | |
| result->set(4, *receiver); | |
| result->set(5, generator->receiver()); | |
| return factory->NewJSArrayWithElements(result); | |
| } else if (object->IsJSPromise()) { | |
| Handle<JSPromise> promise = Handle<JSPromise>::cast(object); | |
| const char* status = JSPromise::Status(promise->status()); | |
| Handle<FixedArray> result = factory->NewFixedArray(2 * 2); | |
| Handle<String> promise_status = | |
| factory->NewStringFromAsciiChecked("[[PromiseStatus]]"); | |
| result->set(0, *promise_status); | |
| Handle<String> status_str = factory->NewStringFromAsciiChecked(status); | |
| result->set(1, *status_str); | |
| Handle<Object> value_obj(promise->status() == Promise::kPending | |
| ? ReadOnlyRoots(isolate).undefined_value() | |
| : promise->result(), | |
| isolate); | |
| Handle<String> promise_value = | |
| factory->NewStringFromAsciiChecked("[[PromiseValue]]"); | |
| result->set(2, *promise_value); | |
| result->set(3, *value_obj); | |
| return factory->NewJSArrayWithElements(result); | |
| } else if (object->IsJSProxy()) { | |
| Handle<JSProxy> js_proxy = Handle<JSProxy>::cast(object); | |
| Handle<FixedArray> result = factory->NewFixedArray(3 * 2); | |
| Handle<String> handler_str = | |
| factory->NewStringFromAsciiChecked("[[Handler]]"); | |
| result->set(0, *handler_str); | |
| result->set(1, js_proxy->handler()); | |
| Handle<String> target_str = | |
| factory->NewStringFromAsciiChecked("[[Target]]"); | |
| result->set(2, *target_str); | |
| result->set(3, js_proxy->target()); | |
| Handle<String> is_revoked_str = | |
| factory->NewStringFromAsciiChecked("[[IsRevoked]]"); | |
| result->set(4, *is_revoked_str); | |
| result->set(5, isolate->heap()->ToBoolean(js_proxy->IsRevoked())); | |
| return factory->NewJSArrayWithElements(result); | |
| } else if (object->IsJSValue()) { | |
| Handle<JSValue> js_value = Handle<JSValue>::cast(object); | |
| Handle<FixedArray> result = factory->NewFixedArray(2); | |
| Handle<String> primitive_value = | |
| factory->NewStringFromAsciiChecked("[[PrimitiveValue]]"); | |
| result->set(0, *primitive_value); | |
| result->set(1, js_value->value()); | |
| return factory->NewJSArrayWithElements(result); | |
| } | |
| return factory->NewJSArray(0); | |
| } | |
| RUNTIME_FUNCTION(Runtime_GetGeneratorScopeCount) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(1, args.length()); | |
| if (!args[0]->IsJSGeneratorObject()) return Smi::kZero; | |
| // Check arguments. | |
| CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, gen, 0); | |
| // Only inspect suspended generator scopes. | |
| if (!gen->is_suspended()) { | |
| return Smi::kZero; | |
| } | |
| // Count the visible scopes. | |
| int n = 0; | |
| for (ScopeIterator it(isolate, gen); !it.Done(); it.Next()) { | |
| n++; | |
| } | |
| return Smi::FromInt(n); | |
| } | |
| RUNTIME_FUNCTION(Runtime_GetGeneratorScopeDetails) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(2, args.length()); | |
| if (!args[0]->IsJSGeneratorObject()) { | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| // Check arguments. | |
| CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, gen, 0); | |
| CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]); | |
| // Only inspect suspended generator scopes. | |
| if (!gen->is_suspended()) { | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| // Find the requested scope. | |
| int n = 0; | |
| ScopeIterator it(isolate, gen); | |
| for (; !it.Done() && n < index; it.Next()) { | |
| n++; | |
| } | |
| if (it.Done()) { | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| return *it.MaterializeScopeDetails(); | |
| } | |
| static bool SetScopeVariableValue(ScopeIterator* it, int index, | |
| Handle<String> variable_name, | |
| Handle<Object> new_value) { | |
| for (int n = 0; !it->Done() && n < index; it->Next()) { | |
| n++; | |
| } | |
| if (it->Done()) { | |
| return false; | |
| } | |
| return it->SetVariableValue(variable_name, new_value); | |
| } | |
| // Change variable value in closure or local scope | |
| // args[0]: number or JsFunction: break id or function | |
| // args[1]: number: scope index | |
| // args[2]: string: variable name | |
| // args[3]: object: new value | |
| // | |
| // Return true if success and false otherwise | |
| RUNTIME_FUNCTION(Runtime_SetGeneratorScopeVariableValue) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(4, args.length()); | |
| CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, gen, 0); | |
| CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]); | |
| CONVERT_ARG_HANDLE_CHECKED(String, variable_name, 2); | |
| CONVERT_ARG_HANDLE_CHECKED(Object, new_value, 3); | |
| ScopeIterator it(isolate, gen); | |
| bool res = SetScopeVariableValue(&it, index, variable_name, new_value); | |
| return isolate->heap()->ToBoolean(res); | |
| } | |
| RUNTIME_FUNCTION(Runtime_GetBreakLocations) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(1, args.length()); | |
| CHECK(isolate->debug()->is_active()); | |
| CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0); | |
| Handle<SharedFunctionInfo> shared(fun->shared(), isolate); | |
| // Find the number of break points | |
| Handle<Object> break_locations = | |
| Debug::GetSourceBreakLocations(isolate, shared); | |
| if (break_locations->IsUndefined(isolate)) { | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| // Return array as JS array | |
| return *isolate->factory()->NewJSArrayWithElements( | |
| Handle<FixedArray>::cast(break_locations)); | |
| } | |
| // Returns the state of break on exceptions | |
| // args[0]: boolean indicating uncaught exceptions | |
| RUNTIME_FUNCTION(Runtime_IsBreakOnException) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(1, args.length()); | |
| CONVERT_NUMBER_CHECKED(uint32_t, type_arg, Uint32, args[0]); | |
| ExceptionBreakType type = static_cast<ExceptionBreakType>(type_arg); | |
| bool result = isolate->debug()->IsBreakOnException(type); | |
| return Smi::FromInt(result); | |
| } | |
| // Clear all stepping set by PrepareStep. | |
| RUNTIME_FUNCTION(Runtime_ClearStepping) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(0, args.length()); | |
| CHECK(isolate->debug()->is_active()); | |
| isolate->debug()->ClearStepping(); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugGetLoadedScriptIds) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(0, args.length()); | |
| Handle<FixedArray> instances; | |
| { | |
| DebugScope debug_scope(isolate->debug()); | |
| // Fill the script objects. | |
| instances = isolate->debug()->GetLoadedScripts(); | |
| } | |
| // Convert the script objects to proper JS objects. | |
| for (int i = 0; i < instances->length(); i++) { | |
| Handle<Script> script(Script::cast(instances->get(i)), isolate); | |
| instances->set(i, Smi::FromInt(script->id())); | |
| } | |
| // Return result as a JS array. | |
| return *isolate->factory()->NewJSArrayWithElements(instances); | |
| } | |
| RUNTIME_FUNCTION(Runtime_FunctionGetInferredName) { | |
| SealHandleScope shs(isolate); | |
| DCHECK_EQ(1, args.length()); | |
| CONVERT_ARG_CHECKED(Object, f, 0); | |
| if (f->IsJSFunction()) { | |
| return JSFunction::cast(f)->shared()->inferred_name(); | |
| } | |
| return ReadOnlyRoots(isolate).empty_string(); | |
| } | |
| // Performs a GC. | |
| // Presently, it only does a full GC. | |
| RUNTIME_FUNCTION(Runtime_CollectGarbage) { | |
| SealHandleScope shs(isolate); | |
| DCHECK_EQ(1, args.length()); | |
| isolate->heap()->CollectAllGarbage(Heap::kAbortIncrementalMarkingMask, | |
| GarbageCollectionReason::kRuntime); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| // Gets the current heap usage. | |
| RUNTIME_FUNCTION(Runtime_GetHeapUsage) { | |
| SealHandleScope shs(isolate); | |
| DCHECK_EQ(0, args.length()); | |
| int usage = static_cast<int>(isolate->heap()->SizeOfObjects()); | |
| if (!Smi::IsValid(usage)) { | |
| return *isolate->factory()->NewNumberFromInt(usage); | |
| } | |
| return Smi::FromInt(usage); | |
| } | |
| namespace { | |
| int ScriptLinePosition(Handle<Script> script, int line) { | |
| if (line < 0) return -1; | |
| if (script->type() == Script::TYPE_WASM) { | |
| return WasmModuleObject::cast(script->wasm_module_object()) | |
| ->GetFunctionOffset(line); | |
| } | |
| Script::InitLineEnds(script); | |
| FixedArray* line_ends_array = FixedArray::cast(script->line_ends()); | |
| const int line_count = line_ends_array->length(); | |
| DCHECK_LT(0, line_count); | |
| if (line == 0) return 0; | |
| // If line == line_count, we return the first position beyond the last line. | |
| if (line > line_count) return -1; | |
| return Smi::ToInt(line_ends_array->get(line - 1)) + 1; | |
| } | |
| int ScriptLinePositionWithOffset(Handle<Script> script, int line, int offset) { | |
| if (line < 0 || offset < 0) return -1; | |
| if (line == 0 || offset == 0) | |
| return ScriptLinePosition(script, line) + offset; | |
| Script::PositionInfo info; | |
| if (!Script::GetPositionInfo(script, offset, &info, Script::NO_OFFSET)) { | |
| return -1; | |
| } | |
| const int total_line = info.line + line; | |
| return ScriptLinePosition(script, total_line); | |
| } | |
| Handle<Object> GetJSPositionInfo(Handle<Script> script, int position, | |
| Script::OffsetFlag offset_flag, | |
| Isolate* isolate) { | |
| Script::PositionInfo info; | |
| if (!Script::GetPositionInfo(script, position, &info, offset_flag)) { | |
| return isolate->factory()->null_value(); | |
| } | |
| Handle<String> source = handle(String::cast(script->source()), isolate); | |
| Handle<String> sourceText = script->type() == Script::TYPE_WASM | |
| ? isolate->factory()->empty_string() | |
| : isolate->factory()->NewSubString( | |
| source, info.line_start, info.line_end); | |
| Handle<JSObject> jsinfo = | |
| isolate->factory()->NewJSObject(isolate->object_function()); | |
| JSObject::AddProperty(isolate, jsinfo, isolate->factory()->script_string(), | |
| script, NONE); | |
| JSObject::AddProperty(isolate, jsinfo, isolate->factory()->position_string(), | |
| handle(Smi::FromInt(position), isolate), NONE); | |
| JSObject::AddProperty(isolate, jsinfo, isolate->factory()->line_string(), | |
| handle(Smi::FromInt(info.line), isolate), NONE); | |
| JSObject::AddProperty(isolate, jsinfo, isolate->factory()->column_string(), | |
| handle(Smi::FromInt(info.column), isolate), NONE); | |
| JSObject::AddProperty(isolate, jsinfo, | |
| isolate->factory()->sourceText_string(), sourceText, | |
| NONE); | |
| return jsinfo; | |
| } | |
| Handle<Object> ScriptLocationFromLine(Isolate* isolate, Handle<Script> script, | |
| Handle<Object> opt_line, | |
| Handle<Object> opt_column, | |
| int32_t offset) { | |
| // Line and column are possibly undefined and we need to handle these cases, | |
| // additionally subtracting corresponding offsets. | |
| int32_t line = 0; | |
| if (!opt_line->IsNullOrUndefined(isolate)) { | |
| CHECK(opt_line->IsNumber()); | |
| line = NumberToInt32(*opt_line) - script->line_offset(); | |
| } | |
| int32_t column = 0; | |
| if (!opt_column->IsNullOrUndefined(isolate)) { | |
| CHECK(opt_column->IsNumber()); | |
| column = NumberToInt32(*opt_column); | |
| if (line == 0) column -= script->column_offset(); | |
| } | |
| int line_position = ScriptLinePositionWithOffset(script, line, offset); | |
| if (line_position < 0 || column < 0) return isolate->factory()->null_value(); | |
| return GetJSPositionInfo(script, line_position + column, Script::NO_OFFSET, | |
| isolate); | |
| } | |
| // Slow traversal over all scripts on the heap. | |
| bool GetScriptById(Isolate* isolate, int needle, Handle<Script>* result) { | |
| Script::Iterator iterator(isolate); | |
| Script* script = nullptr; | |
| while ((script = iterator.Next()) != nullptr) { | |
| if (script->id() == needle) { | |
| *result = handle(script, isolate); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| } // namespace | |
| // TODO(5530): Rename once conflicting function has been deleted. | |
| RUNTIME_FUNCTION(Runtime_ScriptLocationFromLine2) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(4, args.length()); | |
| CONVERT_NUMBER_CHECKED(int32_t, scriptid, Int32, args[0]); | |
| CONVERT_ARG_HANDLE_CHECKED(Object, opt_line, 1); | |
| CONVERT_ARG_HANDLE_CHECKED(Object, opt_column, 2); | |
| CONVERT_NUMBER_CHECKED(int32_t, offset, Int32, args[3]); | |
| Handle<Script> script; | |
| CHECK(GetScriptById(isolate, scriptid, &script)); | |
| return *ScriptLocationFromLine(isolate, script, opt_line, opt_column, offset); | |
| } | |
| // On function call, depending on circumstances, prepare for stepping in, | |
| // or perform a side effect check. | |
| RUNTIME_FUNCTION(Runtime_DebugOnFunctionCall) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(2, args.length()); | |
| CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0); | |
| CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 1); | |
| if (isolate->debug()->needs_check_on_function_call()) { | |
| // Ensure that the callee will perform debug check on function call too. | |
| Deoptimizer::DeoptimizeFunction(*fun); | |
| if (isolate->debug()->last_step_action() >= StepIn || | |
| isolate->debug()->break_on_next_function_call()) { | |
| DCHECK_EQ(isolate->debug_execution_mode(), DebugInfo::kBreakpoints); | |
| isolate->debug()->PrepareStepIn(fun); | |
| } | |
| if (isolate->debug_execution_mode() == DebugInfo::kSideEffects && | |
| !isolate->debug()->PerformSideEffectCheck(fun, receiver)) { | |
| return ReadOnlyRoots(isolate).exception(); | |
| } | |
| } | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| // Set one shot breakpoints for the suspended generator object. | |
| RUNTIME_FUNCTION(Runtime_DebugPrepareStepInSuspendedGenerator) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(0, args.length()); | |
| isolate->debug()->PrepareStepInSuspendedGenerator(); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugPushPromise) { | |
| DCHECK_EQ(1, args.length()); | |
| HandleScope scope(isolate); | |
| CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0); | |
| isolate->PushPromise(promise); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugPopPromise) { | |
| DCHECK_EQ(0, args.length()); | |
| SealHandleScope shs(isolate); | |
| isolate->PopPromise(); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugIsActive) { | |
| SealHandleScope shs(isolate); | |
| return Smi::FromInt(isolate->debug()->is_active()); | |
| } | |
| namespace { | |
| Handle<JSObject> MakeRangeObject(Isolate* isolate, const CoverageBlock& range) { | |
| Factory* factory = isolate->factory(); | |
| Handle<String> start_string = factory->InternalizeUtf8String("start"); | |
| Handle<String> end_string = factory->InternalizeUtf8String("end"); | |
| Handle<String> count_string = factory->InternalizeUtf8String("count"); | |
| Handle<JSObject> range_obj = factory->NewJSObjectWithNullProto(); | |
| JSObject::AddProperty(isolate, range_obj, start_string, | |
| factory->NewNumberFromInt(range.start), NONE); | |
| JSObject::AddProperty(isolate, range_obj, end_string, | |
| factory->NewNumberFromInt(range.end), NONE); | |
| JSObject::AddProperty(isolate, range_obj, count_string, | |
| factory->NewNumberFromUint(range.count), NONE); | |
| return range_obj; | |
| } | |
| } // namespace | |
| RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(0, args.length()); | |
| // Collect coverage data. | |
| std::unique_ptr<Coverage> coverage; | |
| if (isolate->is_best_effort_code_coverage()) { | |
| coverage = Coverage::CollectBestEffort(isolate); | |
| } else { | |
| coverage = Coverage::CollectPrecise(isolate); | |
| } | |
| Factory* factory = isolate->factory(); | |
| // Turn the returned data structure into JavaScript. | |
| // Create an array of scripts. | |
| int num_scripts = static_cast<int>(coverage->size()); | |
| // Prepare property keys. | |
| Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts); | |
| Handle<String> script_string = factory->NewStringFromStaticChars("script"); | |
| for (int i = 0; i < num_scripts; i++) { | |
| const auto& script_data = coverage->at(i); | |
| HandleScope inner_scope(isolate); | |
| std::vector<CoverageBlock> ranges; | |
| int num_functions = static_cast<int>(script_data.functions.size()); | |
| for (int j = 0; j < num_functions; j++) { | |
| const auto& function_data = script_data.functions[j]; | |
| ranges.emplace_back(function_data.start, function_data.end, | |
| function_data.count); | |
| for (size_t k = 0; k < function_data.blocks.size(); k++) { | |
| const auto& block_data = function_data.blocks[k]; | |
| ranges.emplace_back(block_data.start, block_data.end, block_data.count); | |
| } | |
| } | |
| int num_ranges = static_cast<int>(ranges.size()); | |
| Handle<FixedArray> ranges_array = factory->NewFixedArray(num_ranges); | |
| for (int j = 0; j < num_ranges; j++) { | |
| Handle<JSObject> range_object = MakeRangeObject(isolate, ranges[j]); | |
| ranges_array->set(j, *range_object); | |
| } | |
| Handle<JSArray> script_obj = | |
| factory->NewJSArrayWithElements(ranges_array, PACKED_ELEMENTS); | |
| JSObject::AddProperty(isolate, script_obj, script_string, | |
| handle(script_data.script->source(), isolate), NONE); | |
| scripts_array->set(i, *script_obj); | |
| } | |
| return *factory->NewJSArrayWithElements(scripts_array, PACKED_ELEMENTS); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugTogglePreciseCoverage) { | |
| SealHandleScope shs(isolate); | |
| CONVERT_BOOLEAN_ARG_CHECKED(enable, 0); | |
| Coverage::SelectMode(isolate, enable ? debug::Coverage::kPreciseCount | |
| : debug::Coverage::kBestEffort); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugToggleBlockCoverage) { | |
| SealHandleScope shs(isolate); | |
| CONVERT_BOOLEAN_ARG_CHECKED(enable, 0); | |
| Coverage::SelectMode(isolate, enable ? debug::Coverage::kBlockCount | |
| : debug::Coverage::kBestEffort); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_IncBlockCounter) { | |
| SealHandleScope scope(isolate); | |
| DCHECK_EQ(2, args.length()); | |
| CONVERT_ARG_CHECKED(JSFunction, function, 0); | |
| CONVERT_SMI_ARG_CHECKED(coverage_array_slot_index, 1); | |
| // It's quite possible that a function contains IncBlockCounter bytecodes, but | |
| // no coverage info exists. This happens e.g. by selecting the best-effort | |
| // coverage collection mode, which triggers deletion of all coverage infos in | |
| // order to avoid memory leaks. | |
| SharedFunctionInfo* shared = function->shared(); | |
| if (shared->HasCoverageInfo()) { | |
| CoverageInfo* coverage_info = shared->GetCoverageInfo(); | |
| coverage_info->IncrementBlockCount(coverage_array_slot_index); | |
| } | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionSuspended) { | |
| DCHECK_EQ(1, args.length()); | |
| HandleScope scope(isolate); | |
| CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); | |
| isolate->OnAsyncFunctionStateChanged(promise, debug::kAsyncFunctionSuspended); | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionFinished) { | |
| DCHECK_EQ(2, args.length()); | |
| HandleScope scope(isolate); | |
| CONVERT_BOOLEAN_ARG_CHECKED(has_suspend, 0); | |
| CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 1); | |
| isolate->PopPromise(); | |
| if (has_suspend) { | |
| isolate->OnAsyncFunctionStateChanged(promise, | |
| debug::kAsyncFunctionFinished); | |
| } | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| RUNTIME_FUNCTION(Runtime_LiveEditPatchScript) { | |
| HandleScope scope(isolate); | |
| DCHECK_EQ(2, args.length()); | |
| CONVERT_ARG_HANDLE_CHECKED(JSFunction, script_function, 0); | |
| CONVERT_ARG_HANDLE_CHECKED(String, new_source, 1); | |
| Handle<Script> script(Script::cast(script_function->shared()->script()), | |
| isolate); | |
| v8::debug::LiveEditResult result; | |
| LiveEdit::PatchScript(isolate, script, new_source, false, &result); | |
| switch (result.status) { | |
| case v8::debug::LiveEditResult::COMPILE_ERROR: | |
| return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( | |
| "LiveEdit failed: COMPILE_ERROR")); | |
| case v8::debug::LiveEditResult::BLOCKED_BY_RUNNING_GENERATOR: | |
| return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( | |
| "LiveEdit failed: BLOCKED_BY_RUNNING_GENERATOR")); | |
| case v8::debug::LiveEditResult::BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME: | |
| return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( | |
| "LiveEdit failed: BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME")); | |
| case v8::debug::LiveEditResult:: | |
| BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME: | |
| return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( | |
| "LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME")); | |
| case v8::debug::LiveEditResult::BLOCKED_BY_ACTIVE_FUNCTION: | |
| return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( | |
| "LiveEdit failed: BLOCKED_BY_ACTIVE_FUNCTION")); | |
| case v8::debug::LiveEditResult::BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME: | |
| return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( | |
| "LiveEdit failed: BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME")); | |
| case v8::debug::LiveEditResult::FRAME_RESTART_IS_NOT_SUPPORTED: | |
| return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( | |
| "LiveEdit failed: FRAME_RESTART_IS_NOT_SUPPORTED")); | |
| case v8::debug::LiveEditResult::OK: | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| return ReadOnlyRoots(isolate).undefined_value(); | |
| } | |
| } // namespace internal | |
| } // namespace v8 |