// 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/deoptimizer/deoptimizer.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-iterator.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-array-buffer-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" #if V8_ENABLE_WEBASSEMBLY #include "src/debug/debug-wasm-objects.h" #include "src/wasm/wasm-objects-inl.h" #endif // V8_ENABLE_WEBASSEMBLY 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(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(isolate); 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(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(); } namespace { template static Handle AddIteratorInternalProperties( Isolate* isolate, Handle result, Handle iterator) { 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(); } result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[IteratorHasMore]]"), isolate->factory()->ToBoolean(iterator->HasMore())); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[IteratorIndex]]"), handle(iterator->index(), isolate)); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[IteratorKind]]"), isolate->factory()->NewStringFromAsciiChecked(kind)); return result; } } // namespace MaybeHandle Runtime::GetInternalProperties(Isolate* isolate, Handle object) { auto result = ArrayList::New(isolate, 8 * 2); if (object->IsJSObject()) { PrototypeIterator iter(isolate, Handle::cast(object), kStartAtReceiver); if (iter.HasAccess()) { iter.Advance(); Handle prototype = PrototypeIterator::GetCurrent(iter); if (!prototype->IsNull(isolate)) { result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromStaticChars("[[Prototype]]"), prototype); } } } if (object->IsJSBoundFunction()) { Handle function = Handle::cast(object); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[TargetFunction]]"), handle(function->bound_target_function(), isolate)); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[BoundThis]]"), handle(function->bound_this(), isolate)); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[BoundArgs]]"), isolate->factory()->NewJSArrayWithElements( isolate->factory()->CopyFixedArray( handle(function->bound_arguments(), isolate)))); } else if (object->IsJSMapIterator()) { Handle iterator = Handle::cast(object); result = AddIteratorInternalProperties(isolate, result, iterator); } else if (object->IsJSSetIterator()) { Handle iterator = Handle::cast(object); result = AddIteratorInternalProperties(isolate, result, 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()); } result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[GeneratorState]]"), isolate->factory()->NewStringFromAsciiChecked(status)); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[GeneratorFunction]]"), handle(generator->function(), isolate)); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[GeneratorReceiver]]"), handle(generator->receiver(), isolate)); } else if (object->IsJSPromise()) { Handle promise = Handle::cast(object); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[PromiseState]]"), isolate->factory()->NewStringFromAsciiChecked( JSPromise::Status(promise->status()))); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[PromiseResult]]"), promise->status() == Promise::kPending ? isolate->factory()->undefined_value() : handle(promise->result(), isolate)); } else if (object->IsJSProxy()) { Handle js_proxy = Handle::cast(object); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[Handler]]"), handle(js_proxy->handler(), isolate)); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[Target]]"), handle(js_proxy->target(), isolate)); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[IsRevoked]]"), isolate->factory()->ToBoolean(js_proxy->IsRevoked())); } else if (object->IsJSPrimitiveWrapper()) { Handle js_value = Handle::cast(object); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[PrimitiveValue]]"), handle(js_value->value(), isolate)); } else if (object->IsJSArrayBuffer()) { Handle js_array_buffer = Handle::cast(object); if (js_array_buffer->was_detached()) { // Mark a detached JSArrayBuffer and such and don't even try to // create views for it, since the TypedArray constructors will // throw a TypeError when the underlying buffer is detached. result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[IsDetached]]"), isolate->factory()->true_value()); } else { const size_t byte_length = js_array_buffer->byte_length(); static const ExternalArrayType kTypes[] = { kExternalInt8Array, kExternalUint8Array, kExternalInt16Array, kExternalInt32Array, }; for (auto type : kTypes) { switch (type) { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ case kExternal##Type##Array: { \ if ((byte_length % sizeof(ctype)) != 0) continue; \ result = ArrayList::Add( \ isolate, result, \ isolate->factory()->NewStringFromStaticChars("[[" #Type "Array]]"), \ isolate->factory()->NewJSTypedArray(kExternal##Type##Array, \ js_array_buffer, 0, \ byte_length / sizeof(ctype))); \ break; \ } TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE default: UNREACHABLE(); } } result = ArrayList::Add(isolate, result, isolate->factory()->NewStringFromAsciiChecked( "[[ArrayBufferByteLength]]"), isolate->factory()->NewNumberFromSize(byte_length)); // Use the backing store pointer as a unique ID base::EmbeddedVector buffer_data_vec; int len = SNPrintF(buffer_data_vec, V8PRIxPTR_FMT, reinterpret_cast
(js_array_buffer->backing_store())); result = ArrayList::Add( isolate, result, isolate->factory()->NewStringFromAsciiChecked("[[ArrayBufferData]]"), isolate->factory()->InternalizeUtf8String( buffer_data_vec.SubVector(0, len))); Handle memory_symbol = isolate->factory()->array_buffer_wasm_memory_symbol(); Handle memory_object = JSObject::GetDataProperty(js_array_buffer, memory_symbol); if (!memory_object->IsUndefined(isolate)) { result = ArrayList::Add(isolate, result, isolate->factory()->NewStringFromAsciiChecked( "[[WebAssemblyMemory]]"), memory_object); } } #if V8_ENABLE_WEBASSEMBLY } else if (object->IsWasmInstanceObject()) { result = AddWasmInstanceObjectInternalProperties( isolate, result, Handle::cast(object)); } else if (object->IsWasmModuleObject()) { result = AddWasmModuleObjectInternalProperties( isolate, result, Handle::cast(object)); } else if (object->IsWasmTableObject()) { result = AddWasmTableObjectInternalProperties( isolate, result, Handle::cast(object)); #endif // V8_ENABLE_WEBASSEMBLY } return isolate->factory()->NewJSArrayWithElements( ArrayList::Elements(isolate, result), PACKED_ELEMENTS); } 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 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