| // 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/codegen/compiler.h" |
| #include "src/common/globals.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/execution/arguments-inl.h" |
| #include "src/execution/frames-inl.h" |
| #include "src/execution/isolate-inl.h" |
| #include "src/heap/heap-inl.h" // For ToBoolean. TODO(jkummerow): Drop. |
| #include "src/interpreter/bytecode-array-accessor.h" |
| #include "src/interpreter/bytecodes.h" |
| #include "src/interpreter/interpreter.h" |
| #include "src/logging/counters.h" |
| #include "src/objects/debug-objects-inl.h" |
| #include "src/objects/heap-object-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/embedded/embedded-data.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)); |
| } |
| |
| // If we are dropping frames, there is no need to get a return value or |
| // bytecode, since we will be restarting execution at a different frame. |
| if (isolate->debug()->will_restart()) { |
| return MakePair(ReadOnlyRoots(isolate).undefined_value(), |
| Smi::FromInt(static_cast<uint8_t>(Bytecode::kIllegal))); |
| } |
| |
| // Return the handler from the original bytecode array. |
| DCHECK(it.frame()->is_interpreted()); |
| InterpretedFrame* interpreted_frame = |
| reinterpret_cast<InterpretedFrame*>(it.frame()); |
| |
| bool side_effect_check_failed = false; |
| if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) { |
| side_effect_check_failed = |
| !isolate->debug()->PerformSideEffectCheckAtBytecode(interpreted_frame); |
| } |
| |
| // Make sure to only access these objects after the side effect check, as the |
| // check can allocate on failure. |
| 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)); |
| |
| 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()->GetBytecodeHandler(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. This is the debug target function. |
| JavaScriptFrameIterator it(isolate); |
| DCHECK_EQ(*function, it.frame()->function()); |
| // Check whether the next JS frame is closer than the last API entry. |
| // if yes, then the call to the debug target came from JavaScript. Otherwise, |
| // the call to the debug target came from API. Do not break for the latter. |
| it.Advance(); |
| if (!it.done() && |
| it.frame()->fp() < isolate->thread_local_top()->last_api_entry_) { |
| 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("[[GeneratorState]]"); |
| 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("[[PromiseState]]"); |
| 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("[[PromiseResult]]"); |
| 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->IsJSPrimitiveWrapper()) { |
| Handle<JSPrimitiveWrapper> js_value = |
| Handle<JSPrimitiveWrapper>::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); |
| } else if (object->IsJSArrayBuffer()) { |
| Handle<JSArrayBuffer> js_array_buffer = Handle<JSArrayBuffer>::cast(object); |
| Handle<FixedArray> result = factory->NewFixedArray(1 * 2); |
| |
| Handle<String> is_detached_str = |
| factory->NewStringFromAsciiChecked("[[IsDetached]]"); |
| result->set(0, *is_detached_str); |
| result->set(1, isolate->heap()->ToBoolean(js_array_buffer->was_detached())); |
| 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::zero(); |
| |
| // Check arguments. |
| CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, gen, 0); |
| |
| // Only inspect suspended generator scopes. |
| if (!gen->is_suspended()) { |
| return Smi::zero(); |
| } |
| |
| // 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()->PreciseCollectAllGarbage(Heap::kNoGCFlags, |
| 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) { |
| // Wasm positions are relative to the start of the module. |
| return 0; |
| } |
| |
| Script::InitLineEnds(script->GetIsolate(), 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); |
| for (Script script = iterator.Next(); !script.is_null(); |
| script = iterator.Next()) { |
| 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(); |
| } |
| |
| 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->script_string(); |
| 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::CoverageMode::kPreciseCount |
| : debug::CoverageMode::kBestEffort); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugToggleBlockCoverage) { |
| SealHandleScope shs(isolate); |
| CONVERT_BOOLEAN_ARG_CHECKED(enable, 0); |
| Coverage::SelectMode(isolate, enable ? debug::CoverageMode::kBlockCount |
| : debug::CoverageMode::kBestEffort); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IncBlockCounter) { |
| UNREACHABLE(); // Never called. See the IncBlockCounter builtin instead. |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionEntered) { |
| DCHECK_EQ(1, args.length()); |
| HandleScope scope(isolate); |
| CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); |
| isolate->RunPromiseHook(PromiseHookType::kInit, promise, |
| isolate->factory()->undefined_value()); |
| if (isolate->debug()->is_active()) isolate->PushPromise(promise); |
| 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->PopPromise(); |
| isolate->OnAsyncFunctionStateChanged(promise, debug::kAsyncFunctionSuspended); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionResumed) { |
| DCHECK_EQ(1, args.length()); |
| HandleScope scope(isolate); |
| CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); |
| isolate->PushPromise(promise); |
| 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 *promise; |
| } |
| |
| 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(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ProfileCreateSnapshotDataBlob) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(0, args.length()); |
| |
| // Used only by the test/memory/Memory.json benchmark. This creates a snapshot |
| // blob and outputs various statistics around it. |
| |
| DCHECK(FLAG_profile_deserialization); |
| |
| DisableEmbeddedBlobRefcounting(); |
| |
| v8::StartupData blob = CreateSnapshotDataBlobInternal( |
| v8::SnapshotCreator::FunctionCodeHandling::kClear, nullptr); |
| delete[] blob.data; |
| |
| // Track the embedded blob size as well. |
| { |
| i::EmbeddedData d = i::EmbeddedData::FromBlob(); |
| PrintF("Embedded blob is %d bytes\n", |
| static_cast<int>(d.code_size() + d.data_size())); |
| } |
| |
| FreeCurrentEmbeddedBlob(); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |