// 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 #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/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(Bytecode::kIllegal))); } // Return the handler from the original bytecode array. DCHECK(it.frame()->is_interpreted()); InterpretedFrame* interpreted_frame = reinterpret_cast(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()->GetBytecodeHandler(bytecode, operand_scale); if (side_effect_check_failed) { return MakePair(ReadOnlyRoots(isolate).exception(), Smi::FromInt(static_cast(bytecode))); } Object interrupt_object = isolate->stack_guard()->HandleInterrupts(); if (interrupt_object.IsException(isolate)) { return MakePair(interrupt_object, Smi::FromInt(static_cast(bytecode))); } return MakePair(isolate->debug()->return_value(), Smi::FromInt(static_cast(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 static MaybeHandle GetIteratorInternalProperties( Isolate* isolate, Handle object) { Factory* factory = isolate->factory(); Handle iterator = Handle::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 result = factory->NewFixedArray(2 * 3); Handle has_more = factory->NewStringFromAsciiChecked("[[IteratorHasMore]]"); result->set(0, *has_more); result->set(1, isolate->heap()->ToBoolean(iterator->HasMore())); Handle index = factory->NewStringFromAsciiChecked("[[IteratorIndex]]"); result->set(2, *index); result->set(3, iterator->index()); Handle iterator_kind = factory->NewStringFromAsciiChecked("[[IteratorKind]]"); result->set(4, *iterator_kind); Handle kind_str = factory->NewStringFromAsciiChecked(kind); result->set(5, *kind_str); return factory->NewJSArrayWithElements(result); } MaybeHandle Runtime::GetInternalProperties(Isolate* isolate, Handle object) { Factory* factory = isolate->factory(); if (object->IsJSBoundFunction()) { Handle function = Handle::cast(object); Handle result = factory->NewFixedArray(2 * 3); Handle target = factory->NewStringFromAsciiChecked("[[TargetFunction]]"); result->set(0, *target); result->set(1, function->bound_target_function()); Handle bound_this = factory->NewStringFromAsciiChecked("[[BoundThis]]"); result->set(2, *bound_this); result->set(3, function->bound_this()); Handle bound_args = factory->NewStringFromAsciiChecked("[[BoundArgs]]"); result->set(4, *bound_args); Handle bound_arguments = factory->CopyFixedArray(handle(function->bound_arguments(), isolate)); Handle arguments_array = factory->NewJSArrayWithElements(bound_arguments); result->set(5, *arguments_array); return factory->NewJSArrayWithElements(result); } else if (object->IsJSMapIterator()) { Handle iterator = Handle::cast(object); return GetIteratorInternalProperties(isolate, iterator); } else if (object->IsJSSetIterator()) { Handle iterator = Handle::cast(object); return GetIteratorInternalProperties(isolate, iterator); } else if (object->IsJSGeneratorObject()) { Handle generator = Handle::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 result = factory->NewFixedArray(2 * 3); Handle generator_status = factory->NewStringFromAsciiChecked("[[GeneratorStatus]]"); result->set(0, *generator_status); Handle status_str = factory->NewStringFromAsciiChecked(status); result->set(1, *status_str); Handle function = factory->NewStringFromAsciiChecked("[[GeneratorFunction]]"); result->set(2, *function); result->set(3, generator->function()); Handle receiver = factory->NewStringFromAsciiChecked("[[GeneratorReceiver]]"); result->set(4, *receiver); result->set(5, generator->receiver()); return factory->NewJSArrayWithElements(result); } else if (object->IsJSPromise()) { Handle promise = Handle::cast(object); const char* status = JSPromise::Status(promise->status()); Handle result = factory->NewFixedArray(2 * 2); Handle promise_status = factory->NewStringFromAsciiChecked("[[PromiseStatus]]"); result->set(0, *promise_status); Handle status_str = factory->NewStringFromAsciiChecked(status); result->set(1, *status_str); Handle value_obj(promise->status() == Promise::kPending ? ReadOnlyRoots(isolate).undefined_value() : promise->result(), isolate); Handle promise_value = factory->NewStringFromAsciiChecked("[[PromiseValue]]"); result->set(2, *promise_value); result->set(3, *value_obj); return factory->NewJSArrayWithElements(result); } else if (object->IsJSProxy()) { Handle js_proxy = Handle::cast(object); Handle result = factory->NewFixedArray(3 * 2); Handle handler_str = factory->NewStringFromAsciiChecked("[[Handler]]"); result->set(0, *handler_str); result->set(1, js_proxy->handler()); Handle target_str = factory->NewStringFromAsciiChecked("[[Target]]"); result->set(2, *target_str); result->set(3, js_proxy->target()); Handle 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 js_value = Handle::cast(object); Handle result = factory->NewFixedArray(2); Handle 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 variable_name, Handle 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 shared(fun->shared(), isolate); // Find the number of break points Handle 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::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(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 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