diff --git a/disassembler/disasm_backed_debug_info_extractor.cpp b/disassembler/disasm_backed_debug_info_extractor.cpp index 26ffac4d71f871b6d3c610e36946bf8cb130737d..f36c68e7a6c9b456405fdc1c5686a092b0ba7f2d 100644 --- a/disassembler/disasm_backed_debug_info_extractor.cpp +++ b/disassembler/disasm_backed_debug_info_extractor.cpp @@ -43,6 +43,26 @@ const panda_file::ColumnNumberTable &DisasmBackedDebugInfoExtractor::GetColumnNu return empty; } +const panda_file::LocalVariableTable &DisasmBackedDebugInfoExtractor::GetLocalVariableTable( + panda_file::File::EntityId method_id) const +{ + if (GetDisassemblySourceName(method_id)) { + return GetDisassembly(method_id).local_variable_table_; + } + + return DebugInfoExtractor::GetLocalVariableTable(method_id); +} + +const std::vector &DisasmBackedDebugInfoExtractor::GetParameterInfo( + panda_file::File::EntityId method_id) const +{ + if (GetDisassemblySourceName(method_id)) { + return GetDisassembly(method_id).parameter_info_; + } + + return DebugInfoExtractor::GetParameterInfo(method_id); +} + const char *DisasmBackedDebugInfoExtractor::GetSourceFile(panda_file::File::EntityId method_id) const { if (auto &disassembly_source_name = GetDisassemblySourceName(method_id)) { @@ -102,6 +122,27 @@ const DisasmBackedDebugInfoExtractor::Disassembly &DisasmBackedDebugInfoExtracto panda_file::LineNumberTable line_table; disassembler_.Serialize(method, source_code, true, &line_table); - return disassemblies_.emplace(method_id, Disassembly {source_code.str(), std::move(line_table)}).first->second; + auto params_num = method.GetParamsNum(); + std::vector parameter_info(params_num); + for (auto argument_num = 0U; argument_num < params_num; ++argument_num) { + parameter_info[argument_num].name = "a" + std::to_string(argument_num); + parameter_info[argument_num].signature = method.params[argument_num].type.GetDescriptor(); + } + + // We use -1 here as a number for Accumulator, because there is no other way + // to specify Accumulator as a register for local variable + auto total_reg_num = method.GetTotalRegs(); + panda_file::LocalVariableTable local_variable_table(total_reg_num + 1, panda_file::LocalVariableInfo {}); + for (auto vreg_num = -1; vreg_num < static_cast(total_reg_num); ++vreg_num) { + local_variable_table[vreg_num + 1].reg_number = vreg_num; + local_variable_table[vreg_num + 1].name = vreg_num == -1 ? "acc" : "v" + std::to_string(vreg_num); + local_variable_table[vreg_num + 1].start_offset = 0; + local_variable_table[vreg_num + 1].end_offset = UINT32_MAX; + } + + return disassemblies_ + .emplace(method_id, Disassembly {source_code.str(), std::move(line_table), std::move(parameter_info), + std::move(local_variable_table)}) + .first->second; } } // namespace panda::disasm \ No newline at end of file diff --git a/disassembler/disasm_backed_debug_info_extractor.h b/disassembler/disasm_backed_debug_info_extractor.h index fea0eb240ba64a9f32592e008b1e1c5c57f61823..5f6288abdfe51ba7feb1cd3731a515e6da3ea8ad 100644 --- a/disassembler/disasm_backed_debug_info_extractor.h +++ b/disassembler/disasm_backed_debug_info_extractor.h @@ -19,14 +19,16 @@ #include "disassembler.h" namespace panda::disasm { -class DisasmBackedDebugInfoExtractor : public panda_file::DebugInfoExtractor { +class PANDA_PUBLIC_API DisasmBackedDebugInfoExtractor : public panda_file::DebugInfoExtractor { public: - PANDA_PUBLIC_API explicit DisasmBackedDebugInfoExtractor( + explicit DisasmBackedDebugInfoExtractor( const panda_file::File &file, std::function &&on_disasm_source_name = [](auto, auto) {}); const panda_file::LineNumberTable &GetLineNumberTable(panda_file::File::EntityId method_id) const override; const panda_file::ColumnNumberTable &GetColumnNumberTable(panda_file::File::EntityId method_id) const override; + const panda_file::LocalVariableTable &GetLocalVariableTable(panda_file::File::EntityId method_id) const override; + const std::vector &GetParameterInfo(panda_file::File::EntityId method_id) const override; const char *GetSourceFile(panda_file::File::EntityId method_id) const override; const char *GetSourceCode(panda_file::File::EntityId method_id) const override; @@ -34,6 +36,8 @@ private: struct Disassembly { std::string source_code_; panda_file::LineNumberTable line_table_; + std::vector parameter_info_; + panda_file::LocalVariableTable local_variable_table_; }; const std::optional &GetDisassemblySourceName(panda_file::File::EntityId method_id) const; diff --git a/libpandafile/templates/type.h.erb b/libpandafile/templates/type.h.erb index 952f1b28b63122915f24f037fe2e3d02d50380bb..bb0f36412d74941078c1ee9a5e75a50babd47f50 100644 --- a/libpandafile/templates/type.h.erb +++ b/libpandafile/templates/type.h.erb @@ -46,6 +46,10 @@ public: constexpr explicit Type(TypeId id) : type_(id) {} + constexpr bool IsValid() const { + return type_ != TypeId::INVALID; + } + constexpr bool IsPrimitive() const { return type_ != TypeId::REFERENCE; } diff --git a/runtime/include/console_call_type.h b/runtime/include/console_call_type.h index d19ef7eea5e297c3713d36ea783ce81e263bcb13..edf04ff436afa87ef3f292e2182334853eecb8c2 100644 --- a/runtime/include/console_call_type.h +++ b/runtime/include/console_call_type.h @@ -12,11 +12,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #ifndef PANDA_RUNTIME_CONSOLE_CALL_TYPE_H #define PANDA_RUNTIME_CONSOLE_CALL_TYPE_H namespace panda { - enum class ConsoleCallType { LOG, DEBUG, @@ -24,7 +24,6 @@ enum class ConsoleCallType { ERROR, WARNING, }; - } // namespace panda #endif // PANDA_RUNTIME_CONSOLE_CALL_TYPE_H diff --git a/runtime/include/language_context.h b/runtime/include/language_context.h index 300517d27e5ff9eef10750d049e2948ed0b55487..63a06933aff73dc2cdc1a5e1077c3c797dfa4f24 100644 --- a/runtime/include/language_context.h +++ b/runtime/include/language_context.h @@ -27,7 +27,7 @@ #include "runtime/include/itable_builder.h" #include "runtime/include/language_config.h" #include "runtime/include/vtable_builder.h" -#include "runtime/include/tooling/inspector/inspector_extension.h" +#include "runtime/include/tooling/inspector_extension.h" #include "runtime/include/tooling/pt_lang_extension.h" #include "runtime/mem/gc/gc_types.h" @@ -150,7 +150,7 @@ public: virtual std::unique_ptr CreateClassLinkerExtension() const; - virtual std::unique_ptr CreateInspectorExtension() const; + virtual std::unique_ptr CreateInspectorExtension() const; virtual PandaUniquePtr CreatePtLangExt() const; @@ -339,7 +339,7 @@ public: return base_->CreateClassLinkerExtension(); } - std::unique_ptr CreateInspectorExtension() + std::unique_ptr CreateInspectorExtension() { return base_->CreateInspectorExtension(); } diff --git a/runtime/include/runtime_notification.h b/runtime/include/runtime_notification.h index 982a92a617b7fa7a1e6fc7c27766b4ece51b12a1..0b73ad207961c1ff07753171789d3ac531c90edf 100644 --- a/runtime/include/runtime_notification.h +++ b/runtime/include/runtime_notification.h @@ -66,7 +66,7 @@ public: virtual void ConsoleCall([[maybe_unused]] ManagedThread *thread, [[maybe_unused]] ConsoleCallType type, [[maybe_unused]] uint64_t timestamp, - [[maybe_unused]] const PandaVector &arguments) + [[maybe_unused]] const PandaVector &arguments) { } @@ -469,7 +469,7 @@ public: } void ConsoleCallEvent(ManagedThread *thread, ConsoleCallType type, uint64_t timestamp, - const PandaVector &arguments) const + const PandaVector &arguments) const { if (UNLIKELY(has_console_listeners_)) { for (auto listener : console_listeners_) { diff --git a/runtime/include/tooling/debug_interface.h b/runtime/include/tooling/debug_interface.h index 34d23153e2c398103f072c5abe80964264a81c27..cfdffc2ebe7066d28060d63ac37ea1a5b00946f3 100644 --- a/runtime/include/tooling/debug_interface.h +++ b/runtime/include/tooling/debug_interface.h @@ -38,6 +38,7 @@ #include "runtime/include/tooling/vreg_value.h" #include "runtime/interpreter/frame.h" #include "runtime/include/tooling/pt_lang_extension.h" +#include "runtime/include/typed_value.h" namespace panda::tooling { class PtLangExt; @@ -332,7 +333,7 @@ public: } virtual void ConsoleCall(PtThread /* thread */, ConsoleCallType /* type */, uint64_t /* timestamp */, - const PandaVector & /* arguments */) + const PandaVector & /* arguments */) { } diff --git a/runtime/include/tooling/inspector/inspector_extension.h b/runtime/include/tooling/inspector/inspector_extension.h deleted file mode 100644 index 492b727e540916e5a13266aa88547fd7ea19a5d1..0000000000000000000000000000000000000000 --- a/runtime/include/tooling/inspector/inspector_extension.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef PANDA_TOOLING_INSPECTOR_INSPECTOR_EXTENSION_H -#define PANDA_TOOLING_INSPECTOR_INSPECTOR_EXTENSION_H - -#include "property_descriptor.h" -#include "remote_object.h" - -#include "include/coretypes/tagged_value.h" -#include "include/tooling/vreg_value.h" -#include "macros.h" - -#include -#include - -namespace panda { -class ManagedThread; -class ObjectHeader; -} // namespace panda - -namespace panda::tooling::inspector { -class ObjectRepository; - -class InspectorExtension { -public: - InspectorExtension() = default; - virtual ~InspectorExtension() = default; - - NO_COPY_SEMANTIC(InspectorExtension); - NO_MOVE_SEMANTIC(InspectorExtension); - - RemoteObject GetRemoteObject(ManagedThread *thread, ObjectRepository &object_repository, - coretypes::TaggedValue value) - { - return value.IsHeapObject() ? GetRemoteObject(thread, object_repository, value.GetHeapObject()) - : GetRemoteObjectForPrimitive(value); - } - - virtual RemoteObject GetRemoteObject(ManagedThread *thread, ObjectRepository &object_repository, - ObjectHeader *object) = 0; - - virtual std::optional GetVariableDescriptor(ManagedThread *thread, - ObjectRepository &object_repository, - const std::string &name, - const std::string &signature, VRegValue value) = 0; - -protected: - static RemoteObject GetRemoteObjectForPrimitive(coretypes::TaggedValue value) - { - if (value.IsUndefined() || value.IsHole()) { - return RemoteObject::Undefined(); - } - if (value.IsNull()) { - return RemoteObject::Null(); - } - if (value.IsBoolean()) { - return RemoteObject::Boolean(value.IsTrue()); - } - if (value.IsInt()) { - return RemoteObject::Number(value.GetInt()); - } - if (value.IsDouble()) { - return RemoteObject::Number(value.GetDouble()); - } - UNREACHABLE(); - } -}; -} // namespace panda::tooling::inspector - -#endif // PANDA_TOOLING_INSPECTOR_INSPECTOR_EXTENSION_H diff --git a/runtime/tooling/inspector/tests/test_server.h b/runtime/include/tooling/inspector_extension.h similarity index 37% rename from runtime/tooling/inspector/tests/test_server.h rename to runtime/include/tooling/inspector_extension.h index c1a7b558727f844db3fe121ff6c23fa6a6924550..b81abfe1c4b61c4d7269658d2f3452e9616ff769 100644 --- a/runtime/tooling/inspector/tests/test_server.h +++ b/runtime/include/tooling/inspector_extension.h @@ -13,35 +13,36 @@ * limitations under the License. */ -#ifndef PANDA_TOOLING_INSPECTOR_TEST_TEST_SERVER_H -#define PANDA_TOOLING_INSPECTOR_TEST_TEST_SERVER_H +#ifndef PANDA_TOOLING_INSPECTOR_EXTENSION_H +#define PANDA_TOOLING_INSPECTOR_EXTENSION_H -#include "../server_endpoint.h" -#include "test_config.h" -#include "test_event_loop.h" - -#include "websocketpp/connection.hpp" +#include "libpandabase/macros.h" +#include "libpandafile/file_items.h" +#include "runtime/include/typed_value.h" +#include #include -#include -#include +#include -namespace panda::tooling::inspector::test { -class TestLogger; +namespace panda::tooling { +// TODO(a.urakov): move to PtLangExt after the refactoring will be done -// NOLINTNEXTLINE(fuchsia-multiple-inheritance) -class TestServer : public ServerEndpoint, public TestEventLoop { +class InspectorExtension { public: - TestServer(std::string_view name, TestLogger &logger); - - void Connect(const websocketpp::connection::ptr &client_connection, TestEventLoop &client_event_loop); - - void OnConnect(std::function &&handler) - { - // NOLINTNEXTLINE(modernize-avoid-bind) - endpoint_.set_open_handler(std::bind(std::move(handler))); - } + InspectorExtension() = default; + virtual ~InspectorExtension() = default; + + NO_COPY_SEMANTIC(InspectorExtension); + NO_MOVE_SEMANTIC(InspectorExtension); + + virtual std::string GetClassName(const ObjectHeader *object) = 0; + virtual std::optional GetAsString(const ObjectHeader *object) = 0; + virtual std::optional GetLengthIfArray(const ObjectHeader *object) = 0; + virtual void EnumerateProperties( + const ObjectHeader *object, + const std::function &handler) = 0; }; -} // namespace panda::tooling::inspector::test +} // namespace panda::tooling -#endif // PANDA_TOOLING_INSPECTOR_TEST_TEST_SERVER_H +#endif // PANDA_TOOLING_INSPECTOR_EXTENSION_H diff --git a/runtime/include/tooling/pt_lang_extension.h b/runtime/include/tooling/pt_lang_extension.h index 3b013c7280c21900bb7338e66a526464010bda37..5678ddd670d691b5ec746ef125fe0bb9905ea212 100644 --- a/runtime/include/tooling/pt_lang_extension.h +++ b/runtime/include/tooling/pt_lang_extension.h @@ -20,6 +20,7 @@ #include "runtime/include/tooling/pt_value.h" // TODO(maksenov): remove this file after refactoring js_runtime +// TODO(a.urakov): move here current InspectorExtension instead namespace panda::tooling { class PtMethod { public: diff --git a/runtime/include/typed_value.h b/runtime/include/typed_value.h new file mode 100644 index 0000000000000000000000000000000000000000..e5c6b54e2a55f45230a6892a4f230a169eb0e043 --- /dev/null +++ b/runtime/include/typed_value.h @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_TYPED_VALUE_H +#define PANDA_RUNTIME_TYPED_VALUE_H + +#include +#include +#include + +#include "libpandabase/macros.h" +#include "libpandafile/file_items.h" +#include "runtime/include/coretypes/tagged_value.h" + +namespace panda { +class ObjectHeader; + +class TypedValue { + using Value = std::variant; + +public: + ~TypedValue() = default; + + static TypedValue Invalid() + { + return TypedValue {Value(std::in_place_index<0>)}; + } + + static TypedValue Void() + { + return TypedValue {Value(std::in_place_index<1>)}; + } + + static TypedValue U1(bool value) + { + return TypedValue {value}; + } + + static TypedValue I8(int8_t value) + { + return TypedValue {value}; + } + + static TypedValue U8(uint8_t value) + { + return TypedValue {value}; + } + + static TypedValue I16(int16_t value) + { + return TypedValue {value}; + } + + static TypedValue U16(uint16_t value) + { + return TypedValue {value}; + } + + static TypedValue I32(int32_t value) + { + return TypedValue {value}; + } + + static TypedValue U32(uint32_t value) + { + return TypedValue {value}; + } + + static TypedValue F32(float value) + { + return TypedValue {value}; + } + + static TypedValue F64(double value) + { + return TypedValue {value}; + } + + static TypedValue I64(int64_t value) + { + return TypedValue {value}; + } + + static TypedValue U64(uint64_t value) + { + return TypedValue {value}; + } + + static TypedValue Reference(ObjectHeader *value) + { + return TypedValue {value}; + } + + static TypedValue Tagged(coretypes::TaggedValue value) + { + return TypedValue {value}; + } + + DEFAULT_COPY_SEMANTIC(TypedValue); + DEFAULT_MOVE_SEMANTIC(TypedValue); + + panda_file::Type::TypeId GetType() const + { + return static_cast(value_.index()); + } + + bool IsInvalid() const + { + return GetType() == panda_file::Type::TypeId::INVALID; + } + + bool IsVoid() const + { + return GetType() == panda_file::Type::TypeId::VOID; + } + + bool IsU1() const + { + return GetType() == panda_file::Type::TypeId::U1; + } + + bool IsI8() const + { + return GetType() == panda_file::Type::TypeId::I8; + } + + bool IsU8() const + { + return GetType() == panda_file::Type::TypeId::U8; + } + + bool IsI16() const + { + return GetType() == panda_file::Type::TypeId::I16; + } + + bool IsU16() const + { + return GetType() == panda_file::Type::TypeId::U16; + } + + bool IsI32() const + { + return GetType() == panda_file::Type::TypeId::I32; + } + + bool IsU32() const + { + return GetType() == panda_file::Type::TypeId::U32; + } + + bool IsF32() const + { + return GetType() == panda_file::Type::TypeId::F32; + } + + bool IsF64() const + { + return GetType() == panda_file::Type::TypeId::F64; + } + + bool IsI64() const + { + return GetType() == panda_file::Type::TypeId::I64; + } + + bool IsU64() const + { + return GetType() == panda_file::Type::TypeId::U64; + } + + bool IsReference() const + { + return GetType() == panda_file::Type::TypeId::REFERENCE; + } + + bool IsTagged() const + { + return GetType() == panda_file::Type::TypeId::TAGGED; + } + + bool GetAsU1() const + { + ASSERT(IsU1()); + return std::get(value_); + } + + int8_t GetAsI8() const + { + ASSERT(IsI8()); + return std::get(value_); + } + + uint8_t GetAsU8() const + { + ASSERT(IsU8()); + return std::get(value_); + } + + int16_t GetAsI16() const + { + ASSERT(IsI16()); + return std::get(value_); + } + + uint16_t GetAsU16() const + { + ASSERT(IsU16()); + return std::get(value_); + } + + int32_t GetAsI32() const + { + ASSERT(IsI32()); + return std::get(value_); + } + + uint32_t GetAsU32() const + { + ASSERT(IsU32()); + return std::get(value_); + } + + float GetAsF32() const + { + ASSERT(IsF32()); + return std::get(value_); + } + + double GetAsF64() const + { + ASSERT(IsF64()); + return std::get(value_); + } + + int64_t GetAsI64() const + { + ASSERT(IsI64()); + return std::get(value_); + } + + uint64_t GetAsU64() const + { + ASSERT(IsU64()); + return std::get(value_); + } + + ObjectHeader *GetAsReference() const + { + ASSERT(IsReference()); + return std::get(value_); + } + + coretypes::TaggedValue GetAsTagged() const + { + ASSERT(IsTagged()); + return std::get(value_); + } + +private: + explicit TypedValue(Value value) : value_(value) {} + + Value value_; +}; +} // namespace panda + +#endif // PANDA_RUNTIME_TYPED_VALUE_H diff --git a/runtime/language_context.cpp b/runtime/language_context.cpp index 7713dcc459c2d607352c58b738ec503c62dc8c57..11145313541f3bccf2ec4d73947a3a695257a216 100644 --- a/runtime/language_context.cpp +++ b/runtime/language_context.cpp @@ -61,7 +61,7 @@ std::unique_ptr LanguageContextBase::CreateClassLinkerExte return nullptr; } -std::unique_ptr LanguageContextBase::CreateInspectorExtension() const +std::unique_ptr LanguageContextBase::CreateInspectorExtension() const { return nullptr; } diff --git a/runtime/tooling/debugger.h b/runtime/tooling/debugger.h index 9b79278a81a7dc1fbbe7f7177dbd668807719c0e..5ded00d35f864805f48a94ddb2411beb8f4a2c0d 100644 --- a/runtime/tooling/debugger.h +++ b/runtime/tooling/debugger.h @@ -243,7 +243,7 @@ public: uint32_t bc_offset) override; void ConsoleCall(ManagedThread *thread, ConsoleCallType type, uint64_t timestamp, - const PandaVector &arguments) override + const PandaVector &arguments) override { hooks_.ConsoleCall(PtThread(thread), type, timestamp, arguments); } diff --git a/runtime/tooling/inspector/BUILD.gn b/runtime/tooling/inspector/BUILD.gn index f03a90b21be3d3f155f32c24d139774610652e86..1c595951f01c9fd83b7fa6aff7f3184c17a6a13f 100644 --- a/runtime/tooling/inspector/BUILD.gn +++ b/runtime/tooling/inspector/BUILD.gn @@ -16,15 +16,19 @@ import("//ark/runtime_core/ark_config.gni") libarkinspector_sources = [ "asio_server.cpp", "debug_info_cache.cpp", + "debuggable_thread.cpp", + "default_inspector_extension.cpp", "endpoint.cpp", "error.cpp", "event_loop.cpp", "init.cpp", "inspector.cpp", "inspector_server.cpp", + "object_repository.cpp", "session_manager.cpp", "source_manager.cpp", "thread_state.cpp", + "types/call_frame.cpp", "types/location.cpp", "types/remote_object.cpp", "ws_logger.cpp", diff --git a/runtime/tooling/inspector/CMakeLists.txt b/runtime/tooling/inspector/CMakeLists.txt index 7c812e09719708fb985128e9601b1d5d5a7d54ab..2c66dfeca6223ec5751e86c4767b5a0f2d6ef877 100644 --- a/runtime/tooling/inspector/CMakeLists.txt +++ b/runtime/tooling/inspector/CMakeLists.txt @@ -22,15 +22,19 @@ add_definitions(-DASIO_NO_TYPEID -DASIO_STANDALONE) add_library(arkinspector SHARED asio_server.cpp debug_info_cache.cpp + debuggable_thread.cpp + default_inspector_extension.cpp endpoint.cpp error.cpp event_loop.cpp init.cpp inspector.cpp inspector_server.cpp + object_repository.cpp session_manager.cpp source_manager.cpp thread_state.cpp + types/call_frame.cpp types/location.cpp types/remote_object.cpp ws_logger.cpp @@ -62,26 +66,11 @@ target_compile_options(arkinspector PUBLIC "-Wno-deprecated-declarations") panda_add_gtest(NAME arkinspector_tests NO_CORES SOURCES - tests/breakpoint_test.cpp - tests/client.cpp - tests/combined_event_loop.cpp - tests/inspector_test_base.cpp - tests/inspector_test.cpp - tests/instruction_pointer.cpp - tests/scope_test.cpp - tests/server_test.cpp - tests/step_test.cpp - tests/test_debugger.cpp - tests/test_event_loop.cpp tests/test_logger.cpp - tests/test_method.cpp - tests/test_server.cpp tests/types_test.cpp tests/ws_logger_test.cpp LIBRARIES - arkassembler arkinspector - arkruntime gmock SANITIZERS ${PANDA_SANITIZERS_LIST} diff --git a/runtime/tooling/inspector/debug_info_cache.cpp b/runtime/tooling/inspector/debug_info_cache.cpp index 4178e5ada91865985aebf4ab98677ad68d4c6830..6ca2db758c03155198c2995367d0c91f966e994d 100644 --- a/runtime/tooling/inspector/debug_info_cache.cpp +++ b/runtime/tooling/inspector/debug_info_cache.cpp @@ -14,54 +14,60 @@ */ #include "debug_info_cache.h" +#include "source_manager.h" #include "debug_info_extractor.h" -#include "disassembler/disasm_backed_debug_info_extractor.h" +#include "macros.h" #include "optimizer/ir_builder/inst_builder.h" #include "tooling/pt_location.h" -#include "utils/logger.h" -#include "utils/utf.h" -namespace panda::tooling::inspector { -DebugInfoCache::DebugInfoCache(bool back_debug_info_with_disasm) - : back_debug_info_with_disasm_(back_debug_info_with_disasm) -{ -} +#include +#include +namespace panda::tooling::inspector { void DebugInfoCache::AddPandaFile(const panda_file::File &file) { os::memory::LockHolder lock(debug_infos_mutex_); - debug_infos_.emplace(&file, back_debug_info_with_disasm_ - ? std::make_unique( - file, - [this, &file](auto method_id, auto source_name) { - os::memory::LockHolder l(disassemblies_mutex_); - disassemblies_.emplace(std::piecewise_construct, - std::forward_as_tuple(source_name), - std::forward_as_tuple(file, method_id)); - }) - : std::make_unique(&file)); + debug_infos_.emplace(std::piecewise_construct, std::forward_as_tuple(&file), + std::forward_as_tuple(file, [this, &file](auto method_id, auto source_name) { + os::memory::LockHolder l(disassemblies_mutex_); + disassemblies_.emplace(std::piecewise_construct, std::forward_as_tuple(source_name), + std::forward_as_tuple(file, method_id)); + })); } -void DebugInfoCache::GetSourceLocation(const PtFrame &frame, std::string_view &source_file, - std::string_view &method_name, size_t &line_number) +CallFrame DebugInfoCache::GetCallFrame(PtThread thread, const PtFrame &frame, SourceManager &source_manager) { auto method = frame.GetMethod(); auto panda_file = method->GetPandaFile(); auto &debug_info = GetDebugInfo(panda_file); - source_file = debug_info.GetSourceFile(method->GetFileId()); - - method_name = utf::Mutf8AsCString(method->GetName().data); - - // Line number entry corresponding to the bytecode location is - // the last such entry for which the bytecode offset is not greater than - // the given offset. Use `std::upper_bound` to find the first entry - // for which the condition is not true, and then step back. - auto &table = debug_info.GetLineNumberTable(method->GetFileId()); - auto line_number_iter = std::upper_bound(table.begin(), table.end(), frame.GetBytecodeOffset(), - [](auto offset, auto &entry) { return offset < entry.offset; }); - ASSERT(line_number_iter != table.begin()); - line_number = std::prev(line_number_iter)->line; + auto source_file = debug_info.GetSourceFile(method->GetFileId()); + + auto method_name = utf::Mutf8AsCString(method->GetName().data); + auto script_id = source_manager.GetScriptId(thread, source_file); + + auto find_table_entry = [&](const auto &table) -> auto & + { + // Line/column number entry corresponding to the bytecode location is + // the last such entry for which the bytecode offset is not greater than + // the given offset. Use `std::upper_bound` to find the first entry + // for which the condition is not true, and then step back. + auto iter = std::upper_bound(table.begin(), table.end(), frame.GetBytecodeOffset(), + [](auto offset, auto &entry) { return offset < entry.offset; }); + ASSERT(iter != table.begin()); + return *std::prev(iter); + }; + + auto line_number = find_table_entry(debug_info.GetLineNumberTable(method->GetFileId())).line; + + // DevEco Studio requires the locations it receives to have a column + // number, even though it is technically optional. + auto column_number = 1U; + if (auto table = debug_info.GetColumnNumberTable(method->GetFileId()); !table.empty()) { + column_number = find_table_entry(table).column; + } + + return {frame.GetFrameId(), method_name, {script_id, line_number, column_number}, source_file}; } std::unordered_set DebugInfoCache::GetCurrentLineLocations(const PtFrame &frame) @@ -100,7 +106,7 @@ std::unordered_set DebugInfoCache::GetContinueToLocati std::unordered_set locations; EnumerateLineEntries([](auto, auto &) { return true; }, [source_file](auto, auto &debug_info, auto method_id) { - return debug_info->GetSourceFile(method_id) == source_file; + return debug_info.GetSourceFile(method_id) == source_file; }, [line_number, &locations](auto panda_file, auto &, auto method_id, auto &entry, auto next) { if (entry.line == line_number) { @@ -126,42 +132,37 @@ std::unordered_set DebugInfoCache::GetContinueToLocati return locations; } -std::vector DebugInfoCache::GetBreakpointLocations( - const std::function &source_file_filter, size_t line_number, - std::set &source_files) +void DebugInfoCache::EnumerateBreakpointLocations(const std::function &source_file_filter, + size_t line_number, + const std::function &handler) { - std::vector locations; - source_files.clear(); EnumerateLineEntries([](auto, auto &) { return true; }, [&source_file_filter](auto, auto &debug_info, auto method_id) { - return source_file_filter(debug_info->GetSourceFile(method_id)); + return source_file_filter(debug_info.GetSourceFile(method_id)); }, - [line_number, &source_files, &locations](auto panda_file, auto &debug_info, auto method_id, - auto &entry, auto /* next */) { + [&](auto panda_file, auto &debug_info, auto method_id, auto &entry, auto /* next */) { if (entry.line == line_number) { - source_files.insert(debug_info->GetSourceFile(method_id)); - locations.emplace_back(panda_file->GetFilename().data(), method_id, entry.offset); + handler(debug_info.GetSourceFile(method_id), + PtLocation(panda_file->GetFilename().data(), method_id, entry.offset)); } return true; }); - return locations; } -std::set DebugInfoCache::GetValidLineNumbers(std::string_view source_file, size_t start_line, size_t end_line, - bool restrict_to_function) +void DebugInfoCache::EnumerateValidLineNumbers(std::string_view source_file, size_t start_line, size_t end_line, + bool restrict_to_function, const std::function &handler) { - std::set line_numbers; EnumerateLineEntries([](auto, auto &) { return true; }, [source_file, start_line, restrict_to_function](auto, auto &debug_info, auto method_id) { - if (debug_info->GetSourceFile(method_id) != source_file) { + if (debug_info.GetSourceFile(method_id) != source_file) { return false; } bool has_less = false; bool has_greater = false; if (restrict_to_function) { - for (auto &entry : debug_info->GetLineNumberTable(method_id)) { + for (auto &entry : debug_info.GetLineNumberTable(method_id)) { if (entry.line <= start_line) { has_less = true; } @@ -178,32 +179,88 @@ std::set DebugInfoCache::GetValidLineNumbers(std::string_view source_fil return !restrict_to_function || (has_less && has_greater); }, - [start_line, end_line, &line_numbers](auto, auto &, auto, auto &entry, auto /* next */) { + [start_line, end_line, &handler](auto, auto &, auto, auto &entry, auto /* next */) { if (entry.line >= start_line && entry.line < end_line) { - line_numbers.insert(entry.line); + handler(entry.line); } return true; }); - return line_numbers; } -std::vector DebugInfoCache::GetParameterInfo(Method *method) +// NOLINTBEGIN(readability-magic-numbers) +static TypedValue CreateTypedValueFromReg(uint64_t reg, panda_file::Type::TypeId type) +{ + switch (type) { + case panda_file::Type::TypeId::INVALID: + return TypedValue::Invalid(); + case panda_file::Type::TypeId::VOID: + return TypedValue::Void(); + case panda_file::Type::TypeId::U1: + return TypedValue::U1((reg & 0x1ULL) != 0); + case panda_file::Type::TypeId::I8: + return TypedValue::I8(reg & 0xffULL); + case panda_file::Type::TypeId::U8: + return TypedValue::U8(reg & 0xffULL); + case panda_file::Type::TypeId::I16: + return TypedValue::I16(reg & 0xffffULL); + case panda_file::Type::TypeId::U16: + return TypedValue::U16(reg & 0xffffULL); + case panda_file::Type::TypeId::I32: + return TypedValue::I32(reg & 0xffffffffULL); + case panda_file::Type::TypeId::U32: + return TypedValue::U32(reg & 0xffffffffULL); + case panda_file::Type::TypeId::F32: + return TypedValue::F32(bit_cast(static_cast(reg & 0xffffffffULL))); + case panda_file::Type::TypeId::F64: + return TypedValue::F64(bit_cast(reg & 0xffffffffULL)); + case panda_file::Type::TypeId::I64: + return TypedValue::I64(reg); + case panda_file::Type::TypeId::U64: + return TypedValue::U64(reg); + case panda_file::Type::TypeId::REFERENCE: + return TypedValue::Reference(reinterpret_cast(reg)); + case panda_file::Type::TypeId::TAGGED: + return TypedValue::Tagged(coretypes::TaggedValue(static_cast(reg))); + } + UNREACHABLE(); +} +// NOLINTEND(readability-magic-numbers) + +std::map DebugInfoCache::GetLocals(const PtFrame &frame) { - auto ¶meter_info = GetDebugInfo(method->GetPandaFile()).GetParameterInfo(method->GetFileId()); - - // If the number of parameters in debug info doesn't match the number of - // declared method arguments, treat parameter info as invalid and don't - // use it. - if (parameter_info.size() != method->GetNumArgs()) { - auto name = utf::Mutf8AsCString(method->GetName().data); - LOG(WARNING, DEBUGGER) << "The number of parameters in debug info for method '" << name - << "' doesn't match the number of method arguments: " << parameter_info.size() - << " != " << method->GetNumArgs(); - return {}; + std::map result; + + auto local_handler = [&result](const std::string &name, const std::string &signature, uint64_t reg) { + auto type = signature.empty() ? panda_file::Type(panda_file::Type::TypeId::INVALID) + : panda_file::Type::GetTypeIdBySignature(signature[0]); + if (!type.IsValid()) { + type = panda_file::Type(panda_file::Type::TypeId::I64); + } + + result.emplace(name, CreateTypedValueFromReg(reg, type.GetId())); + }; + + auto method = frame.GetMethod(); + auto method_id = method->GetFileId(); + auto &debug_info = GetDebugInfo(method->GetPandaFile()); + auto ¶meters = debug_info.GetParameterInfo(method_id); + for (auto i = 0U; i < parameters.size(); i++) { + auto ¶meter = parameters[i]; + local_handler(parameter.name, parameter.signature, frame.GetArgument(i)); + } + + auto &variables = debug_info.GetLocalVariableTable(method_id); + for (auto &variable : variables) { + auto frame_offset = frame.GetBytecodeOffset(); + if (variable.start_offset <= frame_offset && frame_offset < variable.end_offset) { + local_handler(variable.name, variable.type_signature, + // We introduced a hack in DisasmBackedDebugInfoExtractor, assigning -1 to Accumulator + variable.reg_number == -1 ? frame.GetAccumulator() : frame.GetVReg(variable.reg_number)); + } } - return parameter_info; + return result; } std::string DebugInfoCache::GetSourceCode(std::string_view source_file) @@ -235,6 +292,6 @@ const panda_file::DebugInfoExtractor &DebugInfoCache::GetDebugInfo(const panda_f os::memory::LockHolder lock(debug_infos_mutex_); auto it = debug_infos_.find(file); ASSERT(it != debug_infos_.end()); - return *it->second; + return it->second; } } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/debug_info_cache.h b/runtime/tooling/inspector/debug_info_cache.h index 0df02febe560a4c172a0700a8c42890e3e875cbd..6845b7d18960f97a93827210d367bbb7b6860900 100644 --- a/runtime/tooling/inspector/debug_info_cache.h +++ b/runtime/tooling/inspector/debug_info_cache.h @@ -16,43 +16,47 @@ #ifndef PANDA_TOOLING_INSPECTOR_DEBUG_INFO_CACHE_H #define PANDA_TOOLING_INSPECTOR_DEBUG_INFO_CACHE_H -#include "debug_info_extractor.h" +#include "types/call_frame.h" +#include "debug_info_extractor.h" +#include "disassembler/disasm_backed_debug_info_extractor.h" #include "method.h" #include "tooling/debugger.h" +#include "tooling/pt_location.h" +#include "tooling/pt_thread.h" +#include "typed_value.h" +#include +#include #include -#include +#include #include #include #include namespace panda::tooling::inspector { +class SourceManager; + class DebugInfoCache final { public: - explicit DebugInfoCache(bool back_debug_info_with_disasm); + DebugInfoCache() = default; ~DebugInfoCache() = default; NO_COPY_SEMANTIC(DebugInfoCache); NO_MOVE_SEMANTIC(DebugInfoCache); void AddPandaFile(const panda_file::File &file); - void GetSourceLocation(const PtFrame &frame, std::string_view &source_file, std::string_view &method_name, - size_t &line_number); + CallFrame GetCallFrame(PtThread thread, const PtFrame &frame, SourceManager &source_manager); std::unordered_set GetCurrentLineLocations(const PtFrame &frame); std::unordered_set GetContinueToLocations(std::string_view source_file, size_t line_number); - std::vector GetBreakpointLocations(const std::function &source_file_filter, - size_t line_number, std::set &source_files); - std::set GetValidLineNumbers(std::string_view source_file, size_t start_line, size_t end_line, - bool restrict_to_function); + void EnumerateBreakpointLocations(const std::function &source_file_filter, + size_t line_number, + const std::function &handler); + void EnumerateValidLineNumbers(std::string_view source_file, size_t start_line, size_t end_line, + bool restrict_to_function, const std::function &handler); - panda_file::LocalVariableTable GetLocalVariableTable(Method *method) - { - return GetDebugInfo(method->GetPandaFile()).GetLocalVariableTable(method->GetFileId()); - } - - std::vector GetParameterInfo(Method *method); + std::map GetLocals(const PtFrame &frame); std::string GetSourceCode(std::string_view source_file); @@ -69,12 +73,12 @@ private: continue; } - for (auto method_id : debug_info->GetMethodIdList()) { + for (auto method_id : debug_info.GetMethodIdList()) { if (!method_filter(file, debug_info, method_id)) { continue; } - auto &table = debug_info->GetLineNumberTable(method_id); + auto &table = debug_info.GetLineNumberTable(method_id); for (auto it = table.begin(); it != table.end(); ++it) { auto next = it + 1; if (!handler(file, debug_info, method_id, *it, next != table.end() ? &*next : nullptr)) { @@ -85,10 +89,8 @@ private: } } - bool back_debug_info_with_disasm_; - os::memory::Mutex debug_infos_mutex_; - std::unordered_map> debug_infos_ + std::unordered_map debug_infos_ GUARDED_BY(debug_infos_mutex_); os::memory::Mutex disassemblies_mutex_; diff --git a/runtime/tooling/inspector/debuggable_thread.cpp b/runtime/tooling/inspector/debuggable_thread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0ef5e8c56db62eed1d152ee738c4444b24dd2dd3 --- /dev/null +++ b/runtime/tooling/inspector/debuggable_thread.cpp @@ -0,0 +1,244 @@ +/** + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "runtime/tooling/inspector/debuggable_thread.h" + +namespace panda::tooling::inspector { +DebuggableThread::DebuggableThread( + ManagedThread *thread, std::function &)> &&pre_suspend, + std::function &)> &&post_suspend, + std::function &)> &&pre_wait_suspension, + std::function &)> &&post_wait_suspension, + std::function &&pre_resume, std::function &&post_resume) + : thread_(thread), + pre_suspend_(std::move(pre_suspend)), + post_suspend_(std::move(post_suspend)), + pre_wait_suspension_(std::move(pre_wait_suspension)), + post_wait_suspension_(std::move(post_wait_suspension)), + pre_resume_(std::move(pre_resume)), + post_resume_(std::move(post_resume)) +{ + ASSERT(thread); +} + +void DebuggableThread::Reset() +{ + os::memory::LockHolder lock(mutex_); + state_.Reset(); +} + +void DebuggableThread::BreakOnStart() +{ + os::memory::LockHolder lock(mutex_); + state_.BreakOnStart(); +} + +void DebuggableThread::Continue() +{ + os::memory::LockHolder lock(mutex_); + if (state_.Continue()) { + Resume(); + } +} + +void DebuggableThread::ContinueTo(std::unordered_set locations) +{ + os::memory::LockHolder lock(mutex_); + if (state_.ContinueTo(std::move(locations))) { + Resume(); + } +} + +void DebuggableThread::StepInto(std::unordered_set locations) +{ + os::memory::LockHolder lock(mutex_); + if (state_.StepInto(std::move(locations))) { + Resume(); + } +} + +void DebuggableThread::StepOver(std::unordered_set locations) +{ + os::memory::LockHolder lock(mutex_); + if (state_.StepOver(std::move(locations))) { + Resume(); + } +} + +void DebuggableThread::StepOut() +{ + os::memory::LockHolder lock(mutex_); + if (state_.StepOut()) { + Resume(); + } +} + +void DebuggableThread::Touch() +{ + os::memory::LockHolder lock(mutex_); + if (state_.IsPaused()) { + Resume(); + } +} + +void DebuggableThread::Pause() +{ + os::memory::LockHolder lock(mutex_); + state_.Pause(); +} + +void DebuggableThread::SetBreakpointsActive(bool active) +{ + os::memory::LockHolder lock(mutex_); + state_.SetBreakpointsActive(active); +} + +BreakpointId DebuggableThread::SetBreakpoint(const std::vector &locations) +{ + os::memory::LockHolder lock(mutex_); + return state_.SetBreakpoint(locations); +} + +void DebuggableThread::RemoveBreakpoint(BreakpointId id) +{ + os::memory::LockHolder lock(mutex_); + state_.RemoveBreakpoint(id); +} + +bool DebuggableThread::RequestToObjectRepository(std::function request) +{ + os::memory::LockHolder lock(mutex_); + + ASSERT(!request_.has_value()); + + if (!suspended_) { + return false; + } + + ASSERT(thread_->IsSuspended()); + + request_ = std::move(request); + thread_->Resume(); + + while (request_) { + request_done_.Wait(&mutex_); + } + + ASSERT(suspended_); + ASSERT(thread_->IsSuspended()); + + return true; +} + +void DebuggableThread::OnFramePop() +{ + os::memory::LockHolder lock(mutex_); + state_.OnFramePop(); +} + +bool DebuggableThread::OnMethodEntry() +{ + os::memory::LockHolder lock(mutex_); + return state_.OnMethodEntry(); +} + +void DebuggableThread::OnSingleStep(const PtLocation &location) +{ + os::memory::LockHolder lock(mutex_); + state_.OnSingleStep(location); + while (state_.IsPaused()) { + ObjectRepository object_repository; + auto hit_breakpoints = state_.GetBreakpointsByLocation(location); + Suspend(object_repository, hit_breakpoints); + } +} + +std::vector DebuggableThread::OnConsoleCall(const PandaVector &arguments) +{ + std::vector result; + + ObjectRepository object_repository; + std::transform(arguments.begin(), arguments.end(), std::back_inserter(result), + [&object_repository](auto value) { return object_repository.CreateObject(value); }); + + return result; +} + +void DebuggableThread::Suspend(ObjectRepository &object_repository, const std::vector &hit_breakpoints) +{ + ASSERT(ManagedThread::GetCurrent() == thread_); + + ASSERT(!suspended_); + ASSERT(!request_.has_value()); + + pre_suspend_(object_repository, hit_breakpoints); + + suspended_ = true; + thread_->Suspend(); + + post_suspend_(object_repository, hit_breakpoints); + + while (suspended_) { + mutex_.Unlock(); + + pre_wait_suspension_(object_repository, hit_breakpoints); + thread_->WaitSuspension(); + post_wait_suspension_(object_repository, hit_breakpoints); + + mutex_.Lock(); + + // We have three levels of suspension: + // - state_.IsPaused() - tells if the thread is paused on breakpoint or step; + // - suspended_ - tells if the thread generally sleeps (but could execute object repository requests); + // - thread_->IsSuspended() - tells if the thread is actually sleeping + // + // If we are here, then the latter is false (thread waked up). The following variants are possible: + // - not paused and not suspended - e.g. continue / stepping action was performed; + // - not paused and suspended - invalid; + // - paused and not suspended - touch was performed, resume and sleep back + // (used to notify about the state on `runIfWaitingForDebugger`); + // - paused and suspended - object repository request was made. + + ASSERT(suspended_ == request_.has_value()); + + if (request_) { + (*request_)(object_repository); + + request_.reset(); + thread_->Suspend(); + + request_done_.Signal(); + } + } +} + +void DebuggableThread::Resume() +{ + ASSERT(!request_.has_value()); + + if (!suspended_) { + return; + } + + ASSERT(thread_->IsSuspended()); + + pre_resume_(); + + suspended_ = false; + thread_->Resume(); + + post_resume_(); +} +} // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/debuggable_thread.h b/runtime/tooling/inspector/debuggable_thread.h new file mode 100644 index 0000000000000000000000000000000000000000..8e565e08957de26b1f33564e2b1988d7295d6893 --- /dev/null +++ b/runtime/tooling/inspector/debuggable_thread.h @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_TOOLING_INSPECTOR_DEBUGGABLE_THREAD_H +#define PANDA_TOOLING_INSPECTOR_DEBUGGABLE_THREAD_H + +#include "runtime/tooling/debugger.h" +#include "runtime/tooling/inspector/object_repository.h" +#include "runtime/tooling/inspector/thread_state.h" +#include "runtime/tooling/inspector/types/numeric_id.h" + +namespace panda::tooling::inspector { +class DebuggableThread { +public: + DebuggableThread(ManagedThread *thread, + std::function &)> &&pre_suspend, + std::function &)> &&post_suspend, + std::function &)> &&pre_wait_suspension, + std::function &)> &&post_wait_suspension, + std::function &&pre_resume, std::function &&post_resume); + ~DebuggableThread() = default; + + NO_COPY_SEMANTIC(DebuggableThread); + NO_MOVE_SEMANTIC(DebuggableThread); + + //////////////////////////////////////////////////////////////////////////// + // + // The following methods should be called on the server thread + // + //////////////////////////////////////////////////////////////////////////// + + // Resets the state on a new connection + void Reset(); + + // Tells a newly created thread to pause on the next step + void BreakOnStart(); + + // Continues execution of a paused thread + void Continue(); + + // Continues execution of a paused thread until it reaches one of the locations + void ContinueTo(std::unordered_set locations); + + // Tells a paused thread to perform a step into + void StepInto(std::unordered_set locations); + + // Tells a paused thread to perform a step over + void StepOver(std::unordered_set locations); + + // Tells a paused thread to perform a step out + void StepOut(); + + // Makes a paused thread to resume and suspend back (does nothing for running threads). + // Used to notify about thread's state when processing `runIfWaitingForDebugger` + void Touch(); + + // Tells a running thread to pause on the next step + void Pause(); + + // Enables or disables stops on breakpoints + void SetBreakpointsActive(bool active); + + // Sets a breakpoint on the locations. Returns the breakpoint ID + BreakpointId SetBreakpoint(const std::vector &locations); + + // Removes the breakpoint by ID + void RemoveBreakpoint(BreakpointId id); + + // Executes a request to object repository on a paused thread (does nothing for running threads) + bool RequestToObjectRepository(std::function request); + + //////////////////////////////////////////////////////////////////////////// + // + // The following methods should be called on an application thread + // + //////////////////////////////////////////////////////////////////////////// + + // Notification that an "interesting" frame was popped + void OnFramePop(); + + // Notification that a new frame was pushed. Returns true if we want to be notified about the frame is popped + // (i.e. the frame is "interesting"), false otherwise + bool OnMethodEntry(); + + // Notification that a next step will be performed. Pauses the thread if necessary + void OnSingleStep(const PtLocation &location); + + // Notification that a call to console was performed. Returns its arguments presented as remote objects + std::vector OnConsoleCall(const PandaVector &arguments); + +private: + // Suspends a paused thread. Should be called on an application thread + void Suspend(ObjectRepository &object_repository, const std::vector &hit_breakpoints) + REQUIRES(mutex_); + + // Marks a paused thread as not suspended. Should be called on the server thread + void Resume() REQUIRES(mutex_); + + ManagedThread *thread_; + std::function &)> pre_suspend_; + std::function &)> post_suspend_; + std::function &)> pre_wait_suspension_; + std::function &)> post_wait_suspension_; + std::function pre_resume_; + std::function post_resume_; + + os::memory::Mutex mutex_; + ThreadState state_ GUARDED_BY(mutex_); + bool suspended_ GUARDED_BY(mutex_) {false}; + std::optional> request_ GUARDED_BY(mutex_); + os::memory::ConditionVariable request_done_ GUARDED_BY(mutex_); +}; +} // namespace panda::tooling::inspector + +#endif // PANDA_TOOLING_INSPECTOR_DEBUGGABLE_THREAD_H diff --git a/runtime/include/tooling/inspector/remote_object_id.h b/runtime/tooling/inspector/default_inspector_extension.cpp similarity index 40% rename from runtime/include/tooling/inspector/remote_object_id.h rename to runtime/tooling/inspector/default_inspector_extension.cpp index 798b3b8e345a5ff2cce30b4d5a52347d85df5992..df2cbfd5cd0fdd4b0acdb208d677b72ec9c5a970 100644 --- a/runtime/include/tooling/inspector/remote_object_id.h +++ b/runtime/tooling/inspector/default_inspector_extension.cpp @@ -12,13 +12,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef PANDA_TOOLING_INSPECTOR_REMOTE_OBJECT_ID_H -#define PANDA_TOOLING_INSPECTOR_REMOTE_OBJECT_ID_H -#include +#include "runtime/tooling/inspector/default_inspector_extension.h" namespace panda::tooling::inspector { -using RemoteObjectId = size_t; -} // namespace panda::tooling::inspector +std::string DefaultInspectorExtension::GetClassName(const ObjectHeader *object) +{ + (void)object; + return {}; +} + +std::optional DefaultInspectorExtension::GetAsString(const ObjectHeader *object) +{ + (void)object; + return {}; +} + +std::optional DefaultInspectorExtension::GetLengthIfArray(const ObjectHeader *object) +{ + (void)object; + return {}; +} + +void DefaultInspectorExtension::EnumerateProperties( + const ObjectHeader *object, const std::function &handler) +{ + (void)object; + (void)handler; +} -#endif // PANDA_TOOLING_INSPECTOR_REMOTE_OBJECT_ID_H +std::unique_ptr CreateInspectorExtension(ManagedThread *thread) +{ + std::unique_ptr result; + + if (thread != nullptr) { + result = thread->GetLanguageContext().CreateInspectorExtension(); + } + if (!result) { + result = std::make_unique(); + } + + return result; +} +} // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/tests/combined_event_loop.h b/runtime/tooling/inspector/default_inspector_extension.h similarity index 35% rename from runtime/tooling/inspector/tests/combined_event_loop.h rename to runtime/tooling/inspector/default_inspector_extension.h index 6bd939d7073722c0448c571e439296315eda853e..9046d3fa064113cf1a1e972e8e6fd3cd07118705 100644 --- a/runtime/tooling/inspector/tests/combined_event_loop.h +++ b/runtime/tooling/inspector/default_inspector_extension.h @@ -13,36 +13,30 @@ * limitations under the License. */ -#ifndef PANDA_TOOLING_INSPECTOR_TEST_COMBINED_EVENT_LOOP_H -#define PANDA_TOOLING_INSPECTOR_TEST_COMBINED_EVENT_LOOP_H +#ifndef PANDA_RUNTIME_TOOLING_INSPECTOR_DEFAULT_INSPECTOR_EXTENSION_H +#define PANDA_RUNTIME_TOOLING_INSPECTOR_DEFAULT_INSPECTOR_EXTENSION_H -#include "../event_loop.h" +#include "runtime/include/managed_thread.h" +#include "runtime/include/tooling/inspector_extension.h" -namespace panda::tooling::inspector::test { -class CombinedEventLoop : public EventLoop { +namespace panda::tooling::inspector { +class DefaultInspectorExtension : public InspectorExtension { public: - CombinedEventLoop(EventLoop &left, EventLoop &right) : left_(left), right_(right) {} - - bool Poll() override; - bool RunOne() override; - -private: - enum State { LEFT, RIGHT }; - - State state_ {LEFT}; - EventLoop &left_; - EventLoop &right_; + DefaultInspectorExtension() = default; + ~DefaultInspectorExtension() override = default; + + NO_COPY_SEMANTIC(DefaultInspectorExtension); + NO_MOVE_SEMANTIC(DefaultInspectorExtension); + + std::string GetClassName(const ObjectHeader *object) override; + std::optional GetAsString(const ObjectHeader *object) override; + std::optional GetLengthIfArray(const ObjectHeader *object) override; + void EnumerateProperties( + const ObjectHeader *object, + const std::function &handler) override; }; -inline CombinedEventLoop operator+(EventLoop &left, EventLoop &right) -{ - return {left, right}; -} - -inline CombinedEventLoop operator+(CombinedEventLoop &&left, EventLoop &right) -{ - return {left, right}; -} -} // namespace panda::tooling::inspector::test +std::unique_ptr CreateInspectorExtension(ManagedThread *thread); +} // namespace panda::tooling::inspector -#endif // PANDA_TOOLING_INSPECTOR_TEST_COMBINED_EVENT_LOOP_H +#endif // PANDA_RUNTIME_TOOLING_INSPECTOR_DEFAULT_INSPECTOR_EXTENSION_H diff --git a/runtime/tooling/inspector/init.cpp b/runtime/tooling/inspector/init.cpp index ba264cf56baf059c3866ebb937a3ea49592a867a..a2d073e40b1516d96cf116bbcfe9105202f76cd4 100644 --- a/runtime/tooling/inspector/init.cpp +++ b/runtime/tooling/inspector/init.cpp @@ -30,7 +30,7 @@ static panda::Runtime::DebugSessionHandle G_DEBUG_SESSION; static panda::tooling::inspector::AsioServer G_SERVER; // NOLINTNEXTLINE(fuchsia-statically-constructed-objects) -static std::optional G_INSPECTOR; +static std::optional G_INSPECTOR; extern "C" int StartDebugger(uint32_t port, bool break_on_start) { diff --git a/runtime/tooling/inspector/inspector.cpp b/runtime/tooling/inspector/inspector.cpp index af9c49888696f2a0dc287dd5fb31726e61226de8..a400a98d4e708acaf672d44d5cce80524c84331a 100644 --- a/runtime/tooling/inspector/inspector.cpp +++ b/runtime/tooling/inspector/inspector.cpp @@ -15,20 +15,19 @@ #include "inspector.h" +#include "inspector_server.h" #include "error.h" +#include "types/location.h" +#include "types/remote_object.h" #include "macros.h" #include "plugins.h" #include "runtime.h" -#include "source_lang_enum.h" #include "thread.h" -#include "tooling/inspector/remote_object.h" -#include "tooling/vreg_value.h" +#include "tooling/pt_location.h" #include "utils/logger.h" #include -#include -#include #include #include #include @@ -38,12 +37,11 @@ using namespace std::placeholders; // NOLINT(google-build-using-namespace) namespace panda::tooling::inspector { -Inspector::Inspector(Server &server, DebugInterface &debugger, std::unique_ptr session_manager, - bool break_on_start, bool back_debug_info_with_disasm) - : inspector_server_(server, std::move(session_manager)), - break_on_start_(break_on_start), +Inspector::Inspector(Server &server, DebugInterface &debugger, bool break_on_start) + : break_on_start_(break_on_start), + inspector_server_(server), debugger_(debugger), - debug_info_cache_(back_debug_info_with_disasm) + source_manager_(std::bind(&InspectorServer::CallDebuggerScriptParsed, &inspector_server_, _1, _2, _3)) { if (!HandleError(debugger_.RegisterHooks(this))) { return; @@ -57,9 +55,9 @@ Inspector::Inspector(Server &server, DebugInterface &debugger, std::unique_ptr &arguments) + const PandaVector &arguments) { os::memory::ReadLockHolder lock(debugger_events_lock_); - auto source_lang = thread.GetManagedThread()->GetThreadLang(); - auto extension = GetExtension(source_lang); - if (extension == nullptr) { - LOG(WARNING, DEBUGGER) << "Could not convert console call arguments, please define InspectorExtension for " - << plugins::LangToRuntimeType(source_lang); - return; - } + auto it = threads_.find(thread); + ASSERT(it != threads_.end()); - std::vector argument_objects; - std::transform(arguments.begin(), arguments.end(), std::back_inserter(argument_objects), [&](auto tagged_value) { - return extension->GetRemoteObject(thread.GetManagedThread(), object_repository_, tagged_value); - }); - - inspector_server_.CallRuntimeConsoleApiCalled(thread, type, timestamp, argument_objects); + inspector_server_.CallRuntimeConsoleApiCalled(thread, type, timestamp, it->second.OnConsoleCall(arguments)); } void Inspector::FramePop(PtThread thread, Method * /* method */, bool /* was_popped_by_exception */) { os::memory::ReadLockHolder lock(debugger_events_lock_); - auto it = states_.find(thread); - ASSERT(it != states_.end()); + auto it = threads_.find(thread); + ASSERT(it != threads_.end()); it->second.OnFramePop(); } @@ -133,8 +124,8 @@ void Inspector::MethodEntry(PtThread thread, Method * /* method */) { os::memory::ReadLockHolder lock(debugger_events_lock_); - auto it = states_.find(thread); - ASSERT(it != states_.end()); + auto it = threads_.find(thread); + ASSERT(it != threads_.end()); if (it->second.OnMethodEntry()) { HandleError(debugger_.NotifyFramePop(thread, 0)); } @@ -159,8 +150,8 @@ void Inspector::SingleStep(PtThread thread, Method * /* method */, const PtLocat { os::memory::ReadLockHolder lock(debugger_events_lock_); - auto it = states_.find(thread); - ASSERT(it != states_.end()); + auto it = threads_.find(thread); + ASSERT(it != threads_.end()); it->second.OnSingleStep(location); } @@ -172,44 +163,25 @@ void Inspector::ThreadStart(PtThread thread) inspector_server_.CallTargetAttachedToTarget(thread); } - auto [it, inserted] = states_.emplace( + auto [it, inserted] = threads_.emplace( std::piecewise_construct, std::forward_as_tuple(thread), std::forward_as_tuple( - [this, thread](auto &hit_breakpoints) { - Suspend(thread); - inspector_server_.CallDebuggerPaused(thread, hit_breakpoints, [this, thread](auto &handler) { - std::deque scope_chain; - - auto res = HandleError(debugger_.EnumerateFrames(thread, [&](auto &frame) { - scope_chain.push_back(GetFrameObject(thread, frame)); - return true; - })); - if (!res) { - return; - } - - HandleError(debugger_.EnumerateFrames(thread, [this, &handler, &scope_chain](const PtFrame &frame) { - std::string_view source_file; - std::string_view method_name; - size_t line_number; - debug_info_cache_.GetSourceLocation(frame, source_file, method_name, line_number); - - handler(frame.GetFrameId(), method_name, source_file, line_number, scope_chain); - - scope_chain.pop_front(); - return true; - })); - }); - }, - [this, thread]() NO_THREAD_SAFETY_ANALYSIS { - debugger_events_lock_.Unlock(); - WaitSuspension(thread); - debugger_events_lock_.ReadLock(); + thread.GetManagedThread(), [](auto &, auto &) {}, + [this, thread](auto &object_repository, auto &hit_breakpoints) { + std::vector call_frames; + + HandleError(debugger_.EnumerateFrames(thread, [&](const auto &pt_frame) { + call_frames.push_back(debug_info_cache_.GetCallFrame(thread, pt_frame, source_manager_)); + call_frames.back().SetScope( + object_repository.CreateFrameObject(pt_frame, debug_info_cache_.GetLocals(pt_frame))); + return true; + })); + + inspector_server_.CallDebuggerPaused(thread, hit_breakpoints, call_frames); }, - [this, thread]() { - Resume(thread); - inspector_server_.CallDebuggerResumed(thread); - })); + [this](auto &, auto &) NO_THREAD_SAFETY_ANALYSIS { debugger_events_lock_.Unlock(); }, + [this](auto &, auto &) NO_THREAD_SAFETY_ANALYSIS { debugger_events_lock_.ReadLock(); }, []() {}, + [this, thread]() { inspector_server_.CallDebuggerResumed(thread); })); (void)inserted; ASSERT(inserted); @@ -224,9 +196,10 @@ void Inspector::ThreadEnd(PtThread thread) if (thread != PtThread::NONE) { inspector_server_.CallTargetDetachedFromTarget(thread); + source_manager_.RemoveThread(thread); } - states_.erase(thread); + threads_.erase(thread); } void Inspector::RuntimeEnable(PtThread thread) @@ -236,49 +209,69 @@ void Inspector::RuntimeEnable(PtThread thread) void Inspector::RunIfWaitingForDebugger(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { + auto it = threads_.find(thread); + if (it != threads_.end()) { it->second.Touch(); } } void Inspector::Pause(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { + auto it = threads_.find(thread); + if (it != threads_.end()) { it->second.Pause(); } } void Inspector::Continue(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { + auto it = threads_.find(thread); + if (it != threads_.end()) { it->second.Continue(); } } void Inspector::SetBreakpointsActive(PtThread thread, bool active) { - auto it = states_.find(thread); - if (it != states_.end()) { + auto it = threads_.find(thread); + if (it != threads_.end()) { it->second.SetBreakpointsActive(active); } } -std::set Inspector::GetPossibleBreakpoints(std::string_view source_file, size_t start_line, size_t end_line, - bool restrict_to_function) +std::vector Inspector::GetPossibleBreakpoints(const Location &start, const Location &end, + bool restrict_to_function) { - return debug_info_cache_.GetValidLineNumbers(source_file, start_line, end_line, restrict_to_function); + ASSERT(start.GetScriptId() == end.GetScriptId()); + auto script_id = start.GetScriptId(); + + std::vector locations; + debug_info_cache_.EnumerateValidLineNumbers( + source_manager_.GetSourceFileName(script_id), start.GetLineNumber(), end.GetLineNumber(), restrict_to_function, + [&](auto line_number) { locations.emplace_back(script_id, line_number); }); + + return locations; } -std::optional Inspector::SetBreakpoint(PtThread thread, - const std::function &source_files_filter, - size_t line_number, std::set &source_files) +std::optional>> Inspector::SetBreakpoint( + PtThread thread, const std::function &source_file_filter, size_t line_number) { - if (auto it = states_.find(thread); it != states_.end()) { - auto locations = debug_info_cache_.GetBreakpointLocations(source_files_filter, line_number, source_files); - return it->second.SetBreakpoint(locations); + if (auto it = threads_.find(thread); it != threads_.end()) { + std::vector pt_locations; + std::vector locations; + + debug_info_cache_.EnumerateBreakpointLocations( + [&](auto file_name) { + return source_file_filter(source_manager_.GetScriptId(thread, file_name), file_name); + }, + line_number, + [&](auto file_name, auto pt_location) { + pt_locations.emplace_back(std::move(pt_location)); + locations.emplace_back(source_manager_.GetScriptId(thread, file_name), line_number); + }); + + auto id = it->second.SetBreakpoint(pt_locations); + return {{id, std::move(locations)}}; } return {}; @@ -286,176 +279,80 @@ std::optional Inspector::SetBreakpoint(PtThread thread, void Inspector::RemoveBreakpoint(PtThread thread, BreakpointId id) { - auto it = states_.find(thread); - if (it != states_.end()) { + auto it = threads_.find(thread); + if (it != threads_.end()) { it->second.RemoveBreakpoint(id); } } -void Inspector::StepIntoOver(PtThread thread, ThreadState::StepKind step_kind) +void Inspector::StepInto(PtThread thread) { - // NOLINTNEXTLINE(readability-simplify-boolean-expr) - ASSERT(step_kind == ThreadState::StepKind::STEP_INTO || step_kind == ThreadState::StepKind::STEP_OVER); + auto it = threads_.find(thread); + if (it != threads_.end()) { + auto frame = debugger_.GetCurrentFrame(thread); + if (!frame) { + HandleError(frame.Error()); + return; + } + + it->second.StepInto(debug_info_cache_.GetCurrentLineLocations(*frame.Value())); + } +} - auto it = states_.find(thread); - if (it != states_.end()) { +void Inspector::StepOver(PtThread thread) +{ + auto it = threads_.find(thread); + if (it != threads_.end()) { auto frame = debugger_.GetCurrentFrame(thread); if (!frame) { HandleError(frame.Error()); return; } - it->second.StepIntoOver(step_kind, debug_info_cache_.GetCurrentLineLocations(*frame.Value())); + it->second.StepOver(debug_info_cache_.GetCurrentLineLocations(*frame.Value())); } } void Inspector::StepOut(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { + auto it = threads_.find(thread); + if (it != threads_.end()) { HandleError(debugger_.NotifyFramePop(thread, 0)); it->second.StepOut(); } } -void Inspector::ContinueToLocation(PtThread thread, std::string_view source_file, size_t line_number) +void Inspector::ContinueToLocation(PtThread thread, const Location &location) { - auto it = states_.find(thread); - if (it != states_.end()) { - it->second.ContinueTo(debug_info_cache_.GetContinueToLocations(source_file, line_number)); + auto it = threads_.find(thread); + if (it != threads_.end()) { + it->second.ContinueTo(debug_info_cache_.GetContinueToLocations( + source_manager_.GetSourceFileName(location.GetScriptId()), location.GetLineNumber())); } } -InspectorExtension *Inspector::GetExtension(panda_file::SourceLang source_lang) +std::vector Inspector::GetProperties(PtThread thread, RemoteObjectId object_id, + bool generate_preview) { - auto &extension = extensions_[panda_file::GetLangArrIndex(source_lang)]; + std::optional> properties; - if (!extension) { - extension = Runtime::GetCurrent()->GetLanguageContext(source_lang).CreateInspectorExtension(); + auto it = threads_.find(thread); + if (it != threads_.end()) { + it->second.RequestToObjectRepository([object_id, generate_preview, &properties](auto &object_repository) { + properties = object_repository.GetProperties(object_id, generate_preview); + }); } - return extension->get(); -} - -RemoteObject Inspector::GetFrameObject(PtThread thread, const PtFrame &frame) -{ - auto object_id = object_repository_.CreateObjectId(); - - auto method = frame.GetMethod(); - auto source_lang = method->GetClass()->GetSourceLang(); - - if (source_lang == panda_file::SourceLang::PANDA_ASSEMBLY) { - object_repository_.AddProperty(object_id, {"acc", RemoteObject::Number(frame.GetAccumulator())}); - - for (auto argument_num = 0U; argument_num < frame.GetArgumentNum(); ++argument_num) { - object_repository_.AddProperty( - object_id, {"a" + std::to_string(argument_num), RemoteObject::Number(frame.GetArgument(argument_num))}); - } - - for (auto vreg_num = 0U; vreg_num < frame.GetVRegNum(); ++vreg_num) { - object_repository_.AddProperty( - object_id, {"v" + std::to_string(vreg_num), RemoteObject::Number(frame.GetVReg(vreg_num))}); - } - } else if (auto extension = GetExtension(source_lang)) { - auto parameters = debug_info_cache_.GetParameterInfo(method); - ASSERT(parameters.empty() || parameters.size() == frame.GetArgumentNum()); - - for (auto parameter_num = 0U; parameter_num < parameters.size(); ++parameter_num) { - auto parameter = parameters[parameter_num]; - VRegValue value(frame.GetArgument(parameter_num)); - - if (auto descriptor = extension->GetVariableDescriptor(thread.GetManagedThread(), object_repository_, - parameter.name, parameter.signature, value)) { - object_repository_.AddProperty(object_id, *std::move(descriptor)); - } - } - - for (auto &variable : debug_info_cache_.GetLocalVariableTable(method)) { - if (frame.GetBytecodeOffset() < variable.start_offset || variable.end_offset <= frame.GetBytecodeOffset()) { - continue; - } - - VRegValue value(frame.GetVReg(variable.reg_number)); - - if (auto descriptor = extension->GetVariableDescriptor(thread.GetManagedThread(), object_repository_, - variable.name, variable.type_signature, value)) { - object_repository_.AddProperty(object_id, *std::move(descriptor)); - } - } - } - - return RemoteObject::Object("", object_id, "Frame #" + std::to_string(frame.GetFrameId())); -} - -std::vector Inspector::GetProperties(RemoteObjectId object_id, bool generate_preview) -{ - auto properties = object_repository_.GetProperties(object_id); if (!properties) { LOG(INFO, DEBUGGER) << "Failed to resolve object id: " << object_id; return {}; } - if (generate_preview) { - for (auto &property : *properties) { - property.GeneratePreview(object_repository_); - } - } - return *properties; } -std::string Inspector::GetSourceCode(std::string_view source_file) -{ - return debug_info_cache_.GetSourceCode(source_file); -} - -InspectorST::InspectorST(Server &server, DebugInterface &debugger) - : Inspector(server, debugger, std::make_unique(), true, false) -{ -} - -void InspectorST::WaitSuspension(panda::tooling::PtThread /* thread */) -{ - inspector_server_.Run(); -} - -void InspectorST::Resume(PtThread /* thread */) -{ - inspector_server_.Kill(); -} - -InspectorMT::InspectorMT(Server &server, DebugInterface &debugger, bool break_on_start) - : Inspector(server, debugger, std::make_unique(), break_on_start, true) -{ - server_thread_ = std::thread(&InspectorServer::Run, &inspector_server_); - os::thread::SetThreadName(server_thread_.native_handle(), "InspectorServer"); -} - -InspectorMT::~InspectorMT() -{ - inspector_server_.Kill(); - server_thread_.join(); -} - -void InspectorMT::Suspend(PtThread thread) -{ - auto mt = thread.GetManagedThread(); - ASSERT(mt); - mt->Suspend(); -} - -void InspectorMT::WaitSuspension(PtThread thread) -{ - auto mt = thread.GetManagedThread(); - ASSERT(mt); - ASSERT(ManagedThread::GetCurrent() == mt); - mt->WaitSuspension(); -} - -void InspectorMT::Resume(PtThread thread) +std::string Inspector::GetSourceCode(ScriptId script_id) { - auto mt = thread.GetManagedThread(); - ASSERT(mt); - mt->Resume(); + return debug_info_cache_.GetSourceCode(source_manager_.GetSourceFileName(script_id)); } } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/inspector.h b/runtime/tooling/inspector/inspector.h index cf41a29328ff699f609ad0498d4cf3255c4b676e..124180c644f863ebd61d9e64bf8a3d2e057fb229 100644 --- a/runtime/tooling/inspector/inspector.h +++ b/runtime/tooling/inspector/inspector.h @@ -17,17 +17,16 @@ #define PANDA_TOOLING_INSPECTOR_INSPECTOR_H #include "debug_info_cache.h" +#include "debuggable_thread.h" #include "inspector_server.h" -#include "thread_state.h" +#include "source_manager.h" +#include "types/location.h" #include "types/numeric_id.h" -#include "source_lang_enum.h" #include "tooling/debug_interface.h" -#include "tooling/inspector/inspector_extension.h" #include "tooling/inspector/object_repository.h" -#include "tooling/inspector/property_descriptor.h" -#include "tooling/inspector/remote_object.h" -#include "tooling/inspector/remote_object_id.h" +#include "tooling/inspector/types/property_descriptor.h" +#include "tooling/inspector/types/remote_object.h" #include "tooling/pt_thread.h" #include @@ -35,29 +34,27 @@ #include #include #include -#include #include #include +#include #include namespace panda::tooling { class DebugInterface; namespace inspector { -// NOLINTNEXTLINE(fuchsia-virtual-inheritance) -class Server; +class Server; // NOLINT(fuchsia-virtual-inheritance) class Inspector : public PtHooks { public: - Inspector(Server &server, DebugInterface &debugger, std::unique_ptr session_manager, - bool break_on_start, bool back_debug_info_with_disasm); + Inspector(Server &server, DebugInterface &debugger, bool break_on_start); ~Inspector() override; NO_COPY_SEMANTIC(Inspector); NO_MOVE_SEMANTIC(Inspector); void ConsoleCall(PtThread thread, ConsoleCallType type, uint64_t timestamp, - const PandaVector &arguments) override; + const PandaVector &arguments) override; void FramePop(PtThread thread, Method *method, bool was_popped_by_exception) override; void MethodEntry(PtThread thread, Method *method) override; void LoadModule(std::string_view file_name) override; @@ -74,28 +71,18 @@ private: void Continue(PtThread thread); void SetBreakpointsActive(PtThread thread, bool active); - std::set GetPossibleBreakpoints(std::string_view source_file, size_t start_line, size_t end_line, - bool restrict_to_function); - std::optional SetBreakpoint(PtThread thread, - const std::function &source_files_filter, - size_t line_number, std::set &source_files); + std::vector GetPossibleBreakpoints(const Location &start, const Location &end, bool restrict_to_function); + std::optional>> SetBreakpoint( + PtThread thread, const std::function &source_file_filter, size_t line_number); void RemoveBreakpoint(PtThread thread, BreakpointId breakpoint_id); - void StepIntoOver(PtThread thread, ThreadState::StepKind step_kind); + void StepInto(PtThread thread); + void StepOver(PtThread thread); void StepOut(PtThread thread); - void ContinueToLocation(PtThread thread, std::string_view source_file, size_t line_number); + void ContinueToLocation(PtThread thread, const Location &location); - InspectorExtension *GetExtension(panda_file::SourceLang source_lang); - RemoteObject GetFrameObject(PtThread thread, const PtFrame &frame); - std::vector GetProperties(RemoteObjectId object_id, bool generate_preview); - std::string GetSourceCode(std::string_view source_file); - - virtual void Suspend(PtThread thread) = 0; - virtual void WaitSuspension(PtThread thread) = 0; - virtual void Resume(PtThread thread) = 0; - -protected: - InspectorServer inspector_server_; // NOLINT(misc-non-private-member-variables-in-classes) + std::vector GetProperties(PtThread thread, RemoteObjectId object_id, bool generate_preview); + std::string GetSourceCode(ScriptId script_id); private: bool break_on_start_; @@ -103,40 +90,11 @@ private: os::memory::RWLock debugger_events_lock_; bool connecting_ {false}; // Should be accessed only from the server thread + InspectorServer inspector_server_; // NOLINT(misc-non-private-member-variables-in-classes) DebugInterface &debugger_; DebugInfoCache debug_info_cache_; - std::map states_; - ObjectRepository object_repository_; - - std::array>, panda_file::LANG_COUNT> extensions_; -}; - -class InspectorST final : public Inspector { -public: - InspectorST(Server &server, DebugInterface &debugger); - ~InspectorST() override = default; - - NO_COPY_SEMANTIC(InspectorST); - NO_MOVE_SEMANTIC(InspectorST); - -private: - void Suspend(PtThread /* thread */) override {} - void WaitSuspension(PtThread thread) override; - void Resume(PtThread thread) override; -}; - -class InspectorMT final : public Inspector { -public: - InspectorMT(Server &server, DebugInterface &debugger, bool break_on_start); - ~InspectorMT() override; - - NO_COPY_SEMANTIC(InspectorMT); - NO_MOVE_SEMANTIC(InspectorMT); - -private: - void Suspend(PtThread thread) override; - void WaitSuspension(PtThread thread) override; - void Resume(PtThread thread) override; + std::map threads_; + SourceManager source_manager_; std::thread server_thread_; }; diff --git a/runtime/tooling/inspector/inspector_server.cpp b/runtime/tooling/inspector/inspector_server.cpp index bde6aa4ce5f9be15a6a620e8af718283e5686f64..43ec0140fbdd5b4e1a90dd47b7185b900e5fc2b8 100644 --- a/runtime/tooling/inspector/inspector_server.cpp +++ b/runtime/tooling/inspector/inspector_server.cpp @@ -16,27 +16,34 @@ #include "inspector_server.h" #include "server.h" +#include "types/call_frame.h" #include "types/location.h" #include "types/numeric_id.h" #include "console_call_type.h" #include "macros.h" -#include "tooling/inspector/remote_object_id.h" #include "tooling/pt_thread.h" #include "utils/json_builder.h" #include "utils/json_parser.h" #include "utils/logger.h" +#include #include #include #include +#include #include namespace panda::tooling::inspector { -InspectorServer::InspectorServer(Server &server, std::unique_ptr session_manager) - : server_(server), session_manager_(std::move(session_manager)) +InspectorServer::InspectorServer(Server &server) : server_(server) { server_.OnCall("Debugger.enable", [](auto, auto &result, auto &) { result.AddProperty("debuggerId", "debugger"); }); + + // Required by DevEco Studio IDE + server_.OnCall("Console.enable", [](auto, auto &, auto &) {}); + server_.OnCall("Debugger.setAsyncCallStackDepth", [](auto, auto &, auto &) {}); + server_.OnCall("Debugger.setBlackboxPatterns", [](auto, auto &, auto &) {}); + server_.OnCall("Profiler.enable", [](auto, auto &, auto &) {}); } void InspectorServer::Kill() @@ -65,8 +72,7 @@ void InspectorServer::OnOpen(std::function &&handler) { server_.OnOpen([this, handler = std::move(handler)]() { // A new connection is open, reinitialize the state - session_manager_->EnumerateSessions([this](auto &id, auto thread) { - source_manager_.RemoveThread(thread); + session_manager_.EnumerateSessions([this](auto &id, auto) { if (!id.empty()) { SendTargetAttachedToTarget(id); } @@ -85,39 +91,16 @@ void InspectorServer::OnFail(std::function &&handler) }); } -void InspectorServer::CallDebuggerPaused( - PtThread thread, const std::vector &hit_breakpoints, - const std::function &)> &)> &enumerate_frames) +void InspectorServer::CallDebuggerPaused(PtThread thread, const std::vector &hit_breakpoints, + const std::vector &call_frames) { - auto session_id = session_manager_->GetSessionIdByThread(thread); + auto session_id = session_manager_.GetSessionIdByThread(thread); server_.Call(session_id, "Debugger.paused", [&](auto ¶ms) { - params.AddProperty("callFrames", [this, thread, &enumerate_frames](JsonArrayBuilder &call_frames) { - enumerate_frames([this, thread, &call_frames](auto frame_id, auto method_name, auto source_file, - auto line_number, auto &scope_chain) { - call_frames.Add([&](JsonObjectBuilder &call_frame) { - auto [script_id, is_new] = source_manager_.GetScriptId(thread, source_file); - - if (is_new) { - CallDebuggerScriptParsed(thread, script_id, source_file); - } - - call_frame.AddProperty("callFrameId", std::to_string(frame_id)); - call_frame.AddProperty("functionName", method_name.data()); - call_frame.AddProperty("location", Location(script_id, line_number).ToJson()); - call_frame.AddProperty("url", source_file.data()); - - call_frame.AddProperty("scopeChain", [&](JsonArrayBuilder &scope_chain_builder) { - for (auto &scope : scope_chain) { - scope_chain_builder.Add([&](JsonObjectBuilder &scope_builder) { - scope_builder.AddProperty("type", "local"); - scope_builder.AddProperty("object", scope.ToJson()); - }); - } - }); - }); - }); + params.AddProperty("callFrames", [&](JsonArrayBuilder &array) { + for (auto &call_frame : call_frames) { + array.Add(call_frame.ToJson()); + } }); params.AddProperty("hitBreakpoints", [&hit_breakpoints](JsonArrayBuilder &hit_breakpoints_builder) { @@ -132,12 +115,12 @@ void InspectorServer::CallDebuggerPaused( void InspectorServer::CallDebuggerResumed(PtThread thread) { - server_.Call(session_manager_->GetSessionIdByThread(thread), "Debugger.resumed"); + server_.Call(session_manager_.GetSessionIdByThread(thread), "Debugger.resumed"); } void InspectorServer::CallDebuggerScriptParsed(PtThread thread, ScriptId script_id, std::string_view source_file) { - auto session_id = session_manager_->GetSessionIdByThread(thread); + auto session_id = session_manager_.GetSessionIdByThread(thread); server_.Call(session_id, "Debugger.scriptParsed", [&](auto ¶ms) { params.AddProperty("executionContextId", thread.GetId()); params.AddProperty("scriptId", std::to_string(script_id)); @@ -153,7 +136,7 @@ void InspectorServer::CallDebuggerScriptParsed(PtThread thread, ScriptId script_ void InspectorServer::CallRuntimeConsoleApiCalled(PtThread thread, ConsoleCallType type, uint64_t timestamp, const std::vector &arguments) { - auto session_id = session_manager_->GetSessionIdByThread(thread); + auto session_id = session_manager_.GetSessionIdByThread(thread); server_.Call(session_id, "Runtime.consoleAPICalled", [&](auto ¶ms) { params.AddProperty("executionContextId", thread.GetId()); @@ -189,7 +172,7 @@ void InspectorServer::CallRuntimeConsoleApiCalled(PtThread thread, ConsoleCallTy void InspectorServer::CallRuntimeExecutionContextCreated(PtThread thread) { - auto session_id = session_manager_->GetSessionIdByThread(thread); + auto session_id = session_manager_.GetSessionIdByThread(thread); std::string name; if (thread != PtThread::NONE) { @@ -207,7 +190,7 @@ void InspectorServer::CallRuntimeExecutionContextCreated(PtThread thread) void InspectorServer::CallTargetAttachedToTarget(PtThread thread) { - auto &session_id = session_manager_->AddSession(thread); + auto &session_id = session_manager_.AddSession(thread); if (!session_id.empty()) { SendTargetAttachedToTarget(session_id); } @@ -215,13 +198,12 @@ void InspectorServer::CallTargetAttachedToTarget(PtThread thread) void InspectorServer::CallTargetDetachedFromTarget(PtThread thread) { - auto session_id = session_manager_->GetSessionIdByThread(thread); + auto session_id = session_manager_.GetSessionIdByThread(thread); // Pause the server thread to ensure that there will be no dangling PtThreads server_.Pause(); - session_manager_->RemoveSession(session_id); - source_manager_.RemoveThread(thread); + session_manager_.RemoveSession(session_id); // Now no one will retrieve the detached thread from the sessions manager server_.Continue(); @@ -232,8 +214,7 @@ void InspectorServer::CallTargetDetachedFromTarget(PtThread thread) } } -void InspectorServer::OnCallDebuggerContinueToLocation( - std::function &&handler) +void InspectorServer::OnCallDebuggerContinueToLocation(std::function &&handler) { server_.OnCall( "Debugger.continueToLocation", [this, handler = std::move(handler)](auto &session_id, auto &, auto ¶ms) { @@ -243,33 +224,27 @@ void InspectorServer::OnCallDebuggerContinueToLocation( return; } - auto thread = session_manager_->GetThreadBySessionId(session_id); - - handler(thread, source_manager_.GetSourceFileName(location->GetScriptId()), location->GetLineNumber()); + handler(session_manager_.GetThreadBySessionId(session_id), *location); }); } void InspectorServer::OnCallDebuggerGetPossibleBreakpoints( - std::function(std::string_view, size_t, size_t, bool)> &&handler) + std::function(const Location &, const Location &, bool)> &&handler) { server_.OnCall("Debugger.getPossibleBreakpoints", - [this, handler = std::move(handler)](auto &, auto &result, const JsonObject ¶ms) { + [handler = std::move(handler)](auto &, auto &result, const JsonObject ¶ms) { auto start = Location::FromJsonProperty(params, "start"); if (!start) { LOG(INFO, DEBUGGER) << start.Error(); return; } - auto script_id = start->GetScriptId(); - - size_t end_line = ~0U; - if (auto end = Location::FromJsonProperty(params, "end")) { - if (end->GetScriptId() != script_id) { - LOG(INFO, DEBUGGER) << "Script ids don't match"; - return; - } - - end_line = end->GetLineNumber(); + auto end = Location::FromJsonProperty(params, "end"); + if (!end) { + end = Location(start->GetScriptId(), ~0U); + } else if (start->GetScriptId() != end->GetScriptId()) { + LOG(INFO, DEBUGGER) << "Script ids don't match"; + return; } bool restrict_to_function = false; @@ -277,34 +252,31 @@ void InspectorServer::OnCallDebuggerGetPossibleBreakpoints( restrict_to_function = *prop; } - auto line_numbers = handler(source_manager_.GetSourceFileName(script_id), start->GetLineNumber(), - end_line, restrict_to_function); + auto locations = handler(*start, *end, restrict_to_function); - result.AddProperty("locations", [script_id, &line_numbers](JsonArrayBuilder &array) { - for (auto line_number : line_numbers) { - array.Add(Location(script_id, line_number).ToJson()); + result.AddProperty("locations", [&locations](JsonArrayBuilder &array) { + for (auto &location : locations) { + array.Add(location.ToJson()); } }); }); } -void InspectorServer::OnCallDebuggerGetScriptSource(std::function &&handler) +void InspectorServer::OnCallDebuggerGetScriptSource(std::function &&handler) { - server_.OnCall("Debugger.getScriptSource", - [this, handler = std::move(handler)](auto &, auto &result, auto ¶ms) { - if (auto script_id = ParseNumericId(params, "scriptId")) { - auto source_file = source_manager_.GetSourceFileName(*script_id); - result.AddProperty("scriptSource", handler(source_file)); - } else { - LOG(INFO, DEBUGGER) << script_id.Error(); - } - }); + server_.OnCall("Debugger.getScriptSource", [handler = std::move(handler)](auto &, auto &result, auto ¶ms) { + if (auto script_id = ParseNumericId(params, "scriptId")) { + result.AddProperty("scriptSource", handler(*script_id)); + } else { + LOG(INFO, DEBUGGER) << script_id.Error(); + } + }); } void InspectorServer::OnCallDebuggerPause(std::function &&handler) { server_.OnCall("Debugger.pause", [this, handler = std::move(handler)](auto &session_id, auto &, auto &) { - auto thread = session_manager_->GetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread); }); } @@ -314,7 +286,7 @@ void InspectorServer::OnCallDebuggerRemoveBreakpoint(std::function(params, "breakpointId")) { - handler(session_manager_->GetThreadBySessionId(session_id), *breakpoint_id); + handler(session_manager_.GetThreadBySessionId(session_id), *breakpoint_id); } else { LOG(INFO, DEBUGGER) << breakpoint_id.Error(); } @@ -324,44 +296,42 @@ void InspectorServer::OnCallDebuggerRemoveBreakpoint(std::function &&handler) { server_.OnCall("Debugger.resume", [this, handler = std::move(handler)](auto &session_id, auto &, auto &) { - auto thread = session_manager_->GetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread); }); } void InspectorServer::OnCallDebuggerSetBreakpoint( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler) + std::function>>( + PtThread, const std::function &, size_t)> &&handler) { - server_.OnCall("Debugger.setBreakpoint", - [this, handler = std::move(handler)](auto &session_id, auto &result, auto ¶ms) { - auto location = Location::FromJsonProperty(params, "location"); - if (!location) { - LOG(INFO, DEBUGGER) << location.Error(); - return; - } - - auto thread = session_manager_->GetThreadBySessionId(session_id); + server_.OnCall( + "Debugger.setBreakpoint", [this, handler = std::move(handler)](auto &session_id, auto &result, auto ¶ms) { + auto location = Location::FromJsonProperty(params, "location"); + if (!location) { + LOG(INFO, DEBUGGER) << location.Error(); + return; + } - auto source_file = source_manager_.GetSourceFileName(location->GetScriptId()); - std::set source_files; + auto thread = session_manager_.GetThreadBySessionId(session_id); + auto source_file_filter = [&](auto script_id, auto) { return script_id == location->GetScriptId(); }; - auto id = handler( - thread, [source_file](auto file_name) { return file_name == source_file; }, - location->GetLineNumber(), source_files); - if (!id) { - LOG(INFO, DEBUGGER) << "Failed to set breakpoint"; - return; - } + BreakpointId id; + if (auto breakpoint = handler(thread, source_file_filter, location->GetLineNumber())) { + id = breakpoint->first; + } else { + LOG(INFO, DEBUGGER) << "Failed to set breakpoint"; + return; + } - result.AddProperty("breakpointId", std::to_string(*id)); - result.AddProperty("actualLocation", location->ToJson()); - }); + result.AddProperty("breakpointId", std::to_string(id)); + result.AddProperty("actualLocation", location->ToJson()); + }); } void InspectorServer::OnCallDebuggerSetBreakpointByUrl( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler) + std::function>>( + PtThread, const std::function &, size_t)> &&handler) { server_.OnCall("Debugger.setBreakpointByUrl", [this, handler = std::move(handler)](auto &session_id, auto &result, const JsonObject ¶ms) { @@ -373,12 +343,16 @@ void InspectorServer::OnCallDebuggerSetBreakpointByUrl( return; } - std::function source_file_filter; + std::function source_file_filter; if (auto url = params.GetValue("url")) { - source_file_filter = [source_file = url->find("file://") == 0 ? url->substr(std::strlen("file://")) : *url]( - auto file_name) { return file_name == source_file; }; + std::string_view source_file(*url); + if (url->find("file://") == 0) { + source_file = source_file.substr(std::strlen("file://")); + } + + source_file_filter = [source_file](auto, auto file_name) { return file_name == source_file; }; } else if (auto url_regex = params.GetValue("urlRegex")) { - source_file_filter = [regex = std::regex(*url_regex)](auto file_name) { + source_file_filter = [regex = std::regex(*url_regex)](auto, auto file_name) { return std::regex_match(file_name.data(), regex); }; } else { @@ -386,27 +360,18 @@ void InspectorServer::OnCallDebuggerSetBreakpointByUrl( return; } - std::set source_files; - auto thread = session_manager_->GetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); - auto id = handler(thread, source_file_filter, line_number, source_files); - if (!id) { + auto breakpoint = handler(thread, source_file_filter, line_number); + if (!breakpoint) { LOG(INFO, DEBUGGER) << "Failed to set breakpoint"; return; } - result.AddProperty("breakpointId", std::to_string(*id)); - result.AddProperty("locations", [this, line_number, &source_files, thread](JsonArrayBuilder &locations) { - for (auto source_file : source_files) { - locations.Add([this, line_number, thread, source_file](JsonObjectBuilder &location) { - auto [script_id, is_new] = source_manager_.GetScriptId(thread, source_file); - - if (is_new) { - CallDebuggerScriptParsed(thread, script_id, source_file); - } - - Location(script_id, line_number).ToJson()(location); - }); + result.AddProperty("breakpointId", std::to_string(breakpoint->first)); + result.AddProperty("locations", [&](JsonArrayBuilder &locations) { + for (auto &location : breakpoint->second) { + locations.Add(location.ToJson()); } }); }); @@ -424,7 +389,7 @@ void InspectorServer::OnCallDebuggerSetBreakpointsActive(std::functionGetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread, active); }); } @@ -432,7 +397,7 @@ void InspectorServer::OnCallDebuggerSetBreakpointsActive(std::function &&handler) { server_.OnCall("Debugger.stepInto", [this, handler = std::move(handler)](auto &session_id, auto &, auto &) { - auto thread = session_manager_->GetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread); }); } @@ -440,7 +405,7 @@ void InspectorServer::OnCallDebuggerStepInto(std::function &&han void InspectorServer::OnCallDebuggerStepOut(std::function &&handler) { server_.OnCall("Debugger.stepOut", [this, handler = std::move(handler)](auto &session_id, auto &, auto &) { - auto thread = session_manager_->GetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread); }); } @@ -448,7 +413,7 @@ void InspectorServer::OnCallDebuggerStepOut(std::function &&hand void InspectorServer::OnCallDebuggerStepOver(std::function &&handler) { server_.OnCall("Debugger.stepOver", [this, handler = std::move(handler)](auto &session_id, auto &, auto &) { - auto thread = session_manager_->GetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread); }); } @@ -456,16 +421,18 @@ void InspectorServer::OnCallDebuggerStepOver(std::function &&han void InspectorServer::OnCallRuntimeEnable(std::function &&handler) { server_.OnCall("Runtime.enable", [this, handler = std::move(handler)](auto &session_id, auto &, auto &) { - auto thread = session_manager_->GetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread); }); } void InspectorServer::OnCallRuntimeGetProperties( - std::function(RemoteObjectId, bool)> &&handler) + std::function(PtThread, RemoteObjectId, bool)> &&handler) { server_.OnCall("Runtime.getProperties", - [handler = std::move(handler)](auto, auto &result, const JsonObject ¶ms) { + [this, handler = std::move(handler)](auto &session_id, auto &result, const JsonObject ¶ms) { + auto thread = session_manager_.GetThreadBySessionId(session_id); + auto object_id = ParseNumericId(params, "objectId"); if (!object_id) { LOG(INFO, DEBUGGER) << object_id.Error(); @@ -478,7 +445,7 @@ void InspectorServer::OnCallRuntimeGetProperties( } result.AddProperty("result", [&](JsonArrayBuilder &array) { - for (auto &descriptor : handler(*object_id, generate_preview)) { + for (auto &descriptor : handler(thread, *object_id, generate_preview)) { array.Add(descriptor.ToJson()); } }); @@ -489,7 +456,7 @@ void InspectorServer::OnCallRuntimeRunIfWaitingForDebugger(std::functionGetThreadBySessionId(session_id); + auto thread = session_manager_.GetThreadBySessionId(session_id); handler(thread); }); } diff --git a/runtime/tooling/inspector/inspector_server.h b/runtime/tooling/inspector/inspector_server.h index 9d4a8cc8ea7e8f87a52207b4ee7a64cecd9d3a19..9c6ec1eaec4e7c2347a225f795bd934fd1e9951d 100644 --- a/runtime/tooling/inspector/inspector_server.h +++ b/runtime/tooling/inspector/inspector_server.h @@ -17,30 +17,29 @@ #define PANDA_TOOLING_INSPECTOR_INSPECTOR_SERVER_H #include "session_manager.h" -#include "source_manager.h" #include "types/numeric_id.h" #include "console_call_type.h" -#include "tooling/inspector/property_descriptor.h" -#include "tooling/inspector/remote_object.h" -#include "tooling/inspector/remote_object_id.h" +#include "tooling/inspector/types/property_descriptor.h" +#include "tooling/inspector/types/remote_object.h" #include "tooling/pt_thread.h" #include #include -#include #include #include -#include #include +#include #include namespace panda::tooling::inspector { +class CallFrame; +class Location; class Server; // NOLINT(fuchsia-virtual-inheritance) class InspectorServer final { public: - InspectorServer(Server &server, std::unique_ptr session_manager); + explicit InspectorServer(Server &server); ~InspectorServer() = default; NO_COPY_SEMANTIC(InspectorServer); @@ -53,10 +52,8 @@ public: void OnOpen(std::function &&handler); void OnFail(std::function &&handler); - void CallDebuggerPaused( - PtThread thread, const std::vector &hit_breakpoints, - const std::function &)> &)> &enumerate_frames); + void CallDebuggerPaused(PtThread thread, const std::vector &hit_breakpoints, + const std::vector &call_frames); void CallDebuggerResumed(PtThread thread); void CallDebuggerScriptParsed(PtThread thread, ScriptId id, std::string_view source_file); void CallRuntimeConsoleApiCalled(PtThread thread, ConsoleCallType type, uint64_t timestamp, @@ -65,34 +62,34 @@ public: void CallTargetAttachedToTarget(PtThread thread); void CallTargetDetachedFromTarget(PtThread thread); - void OnCallDebuggerContinueToLocation(std::function &&handler); + void OnCallDebuggerContinueToLocation(std::function &&handler); void OnCallDebuggerGetPossibleBreakpoints( - std::function(std::string_view, size_t, size_t, bool)> &&handler); - void OnCallDebuggerGetScriptSource(std::function &&handler); + std::function(const Location &, const Location &, bool)> &&handler); + void OnCallDebuggerGetScriptSource(std::function &&handler); void OnCallDebuggerPause(std::function &&handler); void OnCallDebuggerRemoveBreakpoint(std::function &&handler); void OnCallDebuggerResume(std::function &&handler); void OnCallDebuggerSetBreakpoint( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler); + std::function>>( + PtThread, const std::function &, size_t)> &&handler); void OnCallDebuggerSetBreakpointByUrl( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler); + std::function>>( + PtThread, const std::function &, size_t)> &&handler); void OnCallDebuggerSetBreakpointsActive(std::function &&handler); void OnCallDebuggerStepInto(std::function &&handler); void OnCallDebuggerStepOut(std::function &&handler); void OnCallDebuggerStepOver(std::function &&handler); void OnCallRuntimeEnable(std::function &&handler); - void OnCallRuntimeGetProperties(std::function(RemoteObjectId, bool)> &&handler); + void OnCallRuntimeGetProperties( + std::function(PtThread, RemoteObjectId, bool)> &&handler); void OnCallRuntimeRunIfWaitingForDebugger(std::function &&handler); private: void SendTargetAttachedToTarget(const std::string &session_id); Server &server_; - std::unique_ptr session_manager_; - SourceManager source_manager_; + SessionManager session_manager_; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/object_repository.cpp b/runtime/tooling/inspector/object_repository.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d03998a741773950e4d077fd4e88e38d0de1fc46 --- /dev/null +++ b/runtime/tooling/inspector/object_repository.cpp @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "runtime/tooling/inspector/object_repository.h" + +#include "runtime/handle_scope-inl.h" +#include "runtime/tooling/inspector/default_inspector_extension.h" + +namespace panda::tooling::inspector { +ObjectRepository::ObjectRepository() + : extension_(CreateInspectorExtension(ManagedThread::GetCurrent())), + scope_(std::make_unique>(ManagedThread::GetCurrent())) +{ +} + +RemoteObject ObjectRepository::CreateFrameObject(const PtFrame &frame, const std::map &locals) +{ + ASSERT(ManagedThread::GetCurrent()->GetMutatorLock()->HasLock()); + + std::vector properties; + properties.reserve(locals.size()); + for (auto &local : locals) { + properties.emplace_back(local.first, CreateObject(local.second)); + } + + auto id = counter_++; + frames_.emplace(id, std::move(properties)); + + return RemoteObject::Object("", id, "Frame #" + std::to_string(frame.GetFrameId())); +} + +RemoteObject ObjectRepository::CreateObject(TypedValue value) +{ + ASSERT(ManagedThread::GetCurrent()->GetMutatorLock()->HasLock()); + + switch (value.GetType()) { + case panda_file::Type::TypeId::INVALID: + case panda_file::Type::TypeId::VOID: + return RemoteObject::Undefined(); + case panda_file::Type::TypeId::U1: + return RemoteObject::Boolean(value.GetAsU1()); + case panda_file::Type::TypeId::I8: + return RemoteObject::Number(value.GetAsI8()); + case panda_file::Type::TypeId::U8: + return RemoteObject::Number(value.GetAsU8()); + case panda_file::Type::TypeId::I16: + return RemoteObject::Number(value.GetAsI16()); + case panda_file::Type::TypeId::U16: + return RemoteObject::Number(value.GetAsU16()); + case panda_file::Type::TypeId::I32: + return RemoteObject::Number(value.GetAsI32()); + case panda_file::Type::TypeId::U32: + return RemoteObject::Number(value.GetAsU32()); + case panda_file::Type::TypeId::F32: + return RemoteObject::Number(value.GetAsF32()); + case panda_file::Type::TypeId::F64: + return RemoteObject::Number(value.GetAsF64()); + case panda_file::Type::TypeId::I64: + return RemoteObject::Number(value.GetAsI64()); + case panda_file::Type::TypeId::U64: + return RemoteObject::Number(value.GetAsI64()); + case panda_file::Type::TypeId::REFERENCE: + return CreateObject(value.GetAsReference()); + case panda_file::Type::TypeId::TAGGED: + return CreateObject(value.GetAsTagged()); + } + UNREACHABLE(); +} + +std::vector ObjectRepository::GetProperties(RemoteObjectId id, bool generate_preview) +{ + ASSERT(ManagedThread::GetCurrent()->GetMutatorLock()->HasLock()); + + auto properties = GetProperties(id); + + if (generate_preview) { + for (auto &property : properties) { + if (property.IsAccessor()) { + continue; + } + + auto &value = property.GetValue(); + if (auto value_id = value.GetObjectId()) { + value.GeneratePreview(GetProperties(*value_id)); + } + } + } + + return properties; +} + +RemoteObject ObjectRepository::CreateObject(coretypes::TaggedValue value) +{ + if (value.IsHeapObject()) { + return CreateObject(value.GetHeapObject()); + } + if (value.IsUndefined() || value.IsHole()) { + return RemoteObject::Undefined(); + } + if (value.IsNull()) { + return RemoteObject::Null(); + } + if (value.IsBoolean()) { + return RemoteObject::Boolean(value.IsTrue()); + } + if (value.IsInt()) { + return RemoteObject::Number(value.GetInt()); + } + if (value.IsDouble()) { + return RemoteObject::Number(value.GetDouble()); + } + UNREACHABLE(); +} + +RemoteObject ObjectRepository::CreateObject(ObjectHeader *object) +{ + ASSERT(ManagedThread::GetCurrent()->GetMutatorLock()->HasLock()); + + if (object == nullptr) { + return RemoteObject::Null(); + } + + if (auto str = extension_->GetAsString(object)) { + return RemoteObject::String(*str); + } + + RemoteObjectId id; + + auto it = std::find_if(objects_.begin(), objects_.end(), [object](auto &p) { return p.second.GetPtr() == object; }); + if (it == objects_.end()) { + id = counter_++; + + objects_.emplace(std::piecewise_construct, std::forward_as_tuple(id), + std::forward_as_tuple(ManagedThread::GetCurrent(), object)); + } else { + id = it->first; + } + + if (auto array_len = extension_->GetLengthIfArray(object)) { + return RemoteObject::Array(extension_->GetClassName(object), *array_len, id); + } + + return RemoteObject::Object(extension_->GetClassName(object), id); +} + +std::vector ObjectRepository::GetProperties(RemoteObjectId id) +{ + ASSERT(ManagedThread::GetCurrent()->GetMutatorLock()->HasLock()); + + auto f_it = frames_.find(id); + if (f_it != frames_.end()) { + ASSERT(objects_.find(id) == objects_.end()); + return f_it->second; + } + + auto o_it = objects_.find(id); + if (o_it == objects_.end()) { + LOG(INFO, DEBUGGER) << "Unknown object ID " << id; + return {}; + } + + std::vector properties; + extension_->EnumerateProperties( + o_it->second.GetPtr(), + [this, &properties](auto &name, auto value, auto is_array_element, auto is_final, auto is_accessor) { + auto property = is_accessor ? PropertyDescriptor::Accessor(name, CreateObject(value)) + : PropertyDescriptor(name, CreateObject(value), is_array_element); + if (!is_accessor && is_final) { + property.SetWritable(false); + } + properties.emplace_back(std::move(property)); + }); + + return properties; +} +} // namespace panda::tooling::inspector diff --git a/runtime/include/tooling/inspector/object_repository.h b/runtime/tooling/inspector/object_repository.h similarity index 40% rename from runtime/include/tooling/inspector/object_repository.h rename to runtime/tooling/inspector/object_repository.h index 301c4f03337078e95ce8ff1e59a4faffbec2d2d6..8ea7817b286965765bcf10eb493d2856a8e6b533 100644 --- a/runtime/include/tooling/inspector/object_repository.h +++ b/runtime/tooling/inspector/object_repository.h @@ -12,48 +12,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #ifndef PANDA_TOOLING_INSPECTOR_OBJECT_REPOSITORY_H #define PANDA_TOOLING_INSPECTOR_OBJECT_REPOSITORY_H -#include "property_descriptor.h" -#include "remote_object_id.h" - -#include "libpandabase/macros.h" #include "libpandabase/os/mutex.h" - -#include -#include -#include +#include "runtime/handle_scope.h" +#include "runtime/include/tooling/debug_interface.h" +#include "runtime/include/typed_value.h" +#include "runtime/tooling/inspector/types/numeric_id.h" +#include "runtime/tooling/inspector/types/property_descriptor.h" namespace panda::tooling::inspector { +//////////////////////////////////////////////////////////////////////////// +// +// All manipulations with an object repository should be made +// on the corresponding application thread with mutator lock held +// +//////////////////////////////////////////////////////////////////////////// + class ObjectRepository { public: - RemoteObjectId CreateObjectId() - { - os::memory::LockHolder lock(mutex_); - objects_.emplace_back(); - return objects_.size() - 1; - } - - void AddProperty(RemoteObjectId object_id, PropertyDescriptor property) - { - os::memory::LockHolder lock(mutex_); - ASSERT(object_id < objects_.size()); - objects_[object_id].push_back(std::move(property)); - } - - std::optional> GetProperties(RemoteObjectId object_id) const - { - os::memory::LockHolder lock(mutex_); - if (objects_.size() <= object_id) { - return {}; - } - return objects_[object_id]; - } + explicit ObjectRepository(); + ~ObjectRepository() = default; + + NO_COPY_SEMANTIC(ObjectRepository); + NO_MOVE_SEMANTIC(ObjectRepository); + + RemoteObject CreateFrameObject(const PtFrame &frame, const std::map &locals); + RemoteObject CreateObject(TypedValue value); + + std::vector GetProperties(RemoteObjectId id, bool generate_preview); private: - mutable os::memory::Mutex mutex_; - std::vector> objects_ GUARDED_BY(mutex_); + RemoteObject CreateObject(coretypes::TaggedValue value); + RemoteObject CreateObject(ObjectHeader *object); + std::vector GetProperties(RemoteObjectId id); + + std::unique_ptr extension_; + std::unique_ptr> scope_; + RemoteObjectId counter_ {0}; + std::map> frames_; + std::map> objects_; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/session_manager.cpp b/runtime/tooling/inspector/session_manager.cpp index 74cbf198bbca803460e9654b5ea12d0bdabbf375..adf8f4dd1ac840d841ee20269da8647725131ed0 100644 --- a/runtime/tooling/inspector/session_manager.cpp +++ b/runtime/tooling/inspector/session_manager.cpp @@ -16,7 +16,7 @@ #include "session_manager.h" namespace panda::tooling::inspector { -const std::string &SessionManagerMT::AddSession(PtThread thread) +const std::string &SessionManager::AddSession(PtThread thread) { ASSERT(thread.GetManagedThread()); @@ -24,13 +24,13 @@ const std::string &SessionManagerMT::AddSession(PtThread thread) return sessions_.insert({!sessions_.empty() ? std::to_string(thread.GetId()) : "", thread}).first->first; } -void SessionManagerMT::RemoveSession(const std::string &id) +void SessionManager::RemoveSession(const std::string &id) { os::memory::LockHolder lock(mutex_); sessions_.erase(id); } -std::string SessionManagerMT::GetSessionIdByThread(PtThread thread) const +std::string SessionManager::GetSessionIdByThread(PtThread thread) const { ASSERT(thread.GetManagedThread()); @@ -44,7 +44,7 @@ std::string SessionManagerMT::GetSessionIdByThread(PtThread thread) const return std::to_string(thread.GetId()); } -PtThread SessionManagerMT::GetThreadBySessionId(const std::string &id) const +PtThread SessionManager::GetThreadBySessionId(const std::string &id) const { os::memory::LockHolder lock(mutex_); @@ -56,7 +56,7 @@ PtThread SessionManagerMT::GetThreadBySessionId(const std::string &id) const return it->second; } -void SessionManagerMT::EnumerateSessions(const std::function &handler) const +void SessionManager::EnumerateSessions(const std::function &handler) const { os::memory::LockHolder lock(mutex_); for (auto &[id, thread] : sessions_) { diff --git a/runtime/tooling/inspector/session_manager.h b/runtime/tooling/inspector/session_manager.h index dc30dfd9dd1478acd793d6793ce4c39682795e2e..0134bec8462a32b482d2e2996df2798a425761f4 100644 --- a/runtime/tooling/inspector/session_manager.h +++ b/runtime/tooling/inspector/session_manager.h @@ -22,69 +22,18 @@ namespace panda::tooling::inspector { class SessionManager { public: SessionManager() = default; - virtual ~SessionManager() = default; + ~SessionManager() = default; NO_COPY_SEMANTIC(SessionManager); NO_MOVE_SEMANTIC(SessionManager); - virtual const std::string &AddSession(PtThread thread) = 0; - virtual void RemoveSession(const std::string &id) = 0; + const std::string &AddSession(PtThread thread); + void RemoveSession(const std::string &id); - [[nodiscard]] virtual std::string GetSessionIdByThread(PtThread thread) const = 0; - [[nodiscard]] virtual PtThread GetThreadBySessionId(const std::string &id) const = 0; + [[nodiscard]] std::string GetSessionIdByThread(PtThread thread) const; + [[nodiscard]] PtThread GetThreadBySessionId(const std::string &id) const; - virtual void EnumerateSessions(const std::function &handler) const = 0; -}; - -class SessionManagerST final : public SessionManager { -public: - SessionManagerST() = default; - ~SessionManagerST() override = default; - - NO_COPY_SEMANTIC(SessionManagerST); - NO_MOVE_SEMANTIC(SessionManagerST); - - const std::string &AddSession(PtThread /* thread */) override - { - UNREACHABLE(); - } - - void RemoveSession(const std::string & /* id */) override - { - UNREACHABLE(); - } - - [[nodiscard]] std::string GetSessionIdByThread(PtThread /* thread */) const override - { - return {}; - } - - [[nodiscard]] PtThread GetThreadBySessionId(const std::string & /* id */) const override - { - return PtThread::NONE; - } - - void EnumerateSessions(const std::function &handler) const override - { - handler({}, PtThread::NONE); - } -}; - -class SessionManagerMT final : public SessionManager { -public: - SessionManagerMT() = default; - ~SessionManagerMT() override = default; - - NO_COPY_SEMANTIC(SessionManagerMT); - NO_MOVE_SEMANTIC(SessionManagerMT); - - const std::string &AddSession(PtThread thread) override; - void RemoveSession(const std::string &id) override; - - [[nodiscard]] std::string GetSessionIdByThread(PtThread thread) const override; - [[nodiscard]] PtThread GetThreadBySessionId(const std::string &id) const override; - - void EnumerateSessions(const std::function &handler) const override; + void EnumerateSessions(const std::function &handler) const; private: mutable os::memory::Mutex mutex_; diff --git a/runtime/tooling/inspector/source_manager.cpp b/runtime/tooling/inspector/source_manager.cpp index 36b6986e3698e4755213f01a4b662174b6a6ae76..0c6e9204f831b4cc9babb911b31884810a742f6e 100644 --- a/runtime/tooling/inspector/source_manager.cpp +++ b/runtime/tooling/inspector/source_manager.cpp @@ -20,7 +20,7 @@ #include namespace panda::tooling::inspector { -std::pair SourceManager::GetScriptId(PtThread thread, std::string_view file_name) +ScriptId SourceManager::GetScriptId(PtThread thread, std::string_view file_name) { os::memory::LockHolder lock(mutex_); @@ -34,7 +34,11 @@ std::pair SourceManager::GetScriptId(PtThread thread, std::strin id_to_file_name_.emplace(id, name); } - return {id, is_new_for_thread}; + if (is_new_for_thread) { + on_new_(thread, id, file_name); + } + + return id; } std::string_view SourceManager::GetSourceFileName(ScriptId id) const diff --git a/runtime/tooling/inspector/source_manager.h b/runtime/tooling/inspector/source_manager.h index 9f2f1915f8edb8989fec26cf74baf7786861b524..c30c6e6cf384b6453b369a4e6ad512d8a7be648d 100644 --- a/runtime/tooling/inspector/source_manager.h +++ b/runtime/tooling/inspector/source_manager.h @@ -21,6 +21,7 @@ #include "os/mutex.h" #include "tooling/pt_thread.h" +#include #include #include #include @@ -31,13 +32,13 @@ namespace panda::tooling::inspector { class SourceManager final { public: - SourceManager() = default; + SourceManager(std::function on_new) : on_new_(std::move(on_new)) {} ~SourceManager() = default; NO_COPY_SEMANTIC(SourceManager); NO_MOVE_SEMANTIC(SourceManager); - std::pair GetScriptId(PtThread thread, std::string_view file_name); + ScriptId GetScriptId(PtThread thread, std::string_view file_name); [[nodiscard]] std::string_view GetSourceFileName(ScriptId id) const; void RemoveThread(PtThread thread); @@ -47,6 +48,7 @@ private: std::unordered_map file_name_to_id_ GUARDED_BY(mutex_); std::unordered_map id_to_file_name_ GUARDED_BY(mutex_); std::map> known_sources_ GUARDED_BY(mutex_); + std::function on_new_; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/tests/breakpoint_test.cpp b/runtime/tooling/inspector/tests/breakpoint_test.cpp deleted file mode 100644 index be55ae05e5bce7b71b14e677b6f6f5637ef95e03..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/breakpoint_test.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "../types/location.h" -#include "../types/numeric_id.h" -#include "combined_event_loop.h" -#include "common.h" -#include "inspector_test_base.h" -#include "instruction_pointer.h" -#include "json_object_matcher.h" -#include "test_frame.h" -#include "test_method.h" - -#include "utils/json_builder.h" -#include "utils/json_parser.h" -#include "utils/utf.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include -#include -#include -#include -#include - -using namespace std::placeholders; // NOLINT(google-build-using-namespace) -using testing::_; -using testing::Matcher; - -namespace panda::tooling::inspector::test { -class BreakpointTest : public InspectorTestBase { -protected: - void SetUpSourceFiles() override - { - auto klass = LoadSourceFile(R"( - .function void func() { - nop - - return - } - - .function void main() { - call func - call func - return - } - )"); - - main_frame_.SetMethod(klass->GetDirectMethod(utf::CStringAsMutf8("main"))); - func_.Set(klass->GetDirectMethod(utf::CStringAsMutf8("func"))); - } - - void SetUp() override - { - InspectorTestBase::SetUp(); - main_.Resume(); - (server_ + client_).Poll(); - } - - void TearDown() override - { - main_.Finish(); - InspectorTestBase::TearDown(); - } - - template - void CheckPossibleBreakpoints(size_t start_line, std::optional end_line, - std::optional restrict_to_function, - std::index_sequence /* breakpoints */); - - TestFrame main_frame_ {debugger_}; - InstructionPointer main_ {main_frame_, client_, debugger_}; - TestMethod func_ {debugger_, client_}; -}; - -template -void BreakpointTest::CheckPossibleBreakpoints(size_t start_line, std::optional end_line, - std::optional restrict_to_function, - std::index_sequence /* breakpoints */) -{ - CallSync( - "Debugger.getPossibleBreakpoints", - [this, start_line, end_line, restrict_to_function](JsonObjectBuilder ¶ms) { - params.AddProperty("start", Location(script_id_, start_line).ToJson()); - if (end_line) { - params.AddProperty("end", Location(script_id_, *end_line).ToJson()); - } - if (restrict_to_function) { - params.AddProperty("restrictToFunction", *restrict_to_function); - } - }, - [this](const JsonObject &result) { - (void)this; - auto result_matcher = JsonProperties(JsonProperty { - "locations", JsonElements(Matcher { - Pointee(LocationEq(Location(script_id_, BREAKPOINT)))}...)}); - EXPECT_THAT(result, result_matcher); - }); // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) -} - -TEST_F(BreakpointTest, GetsPossibleBreakpoints) -{ - CheckPossibleBreakpoints(5, 11, std::nullopt, std::index_sequence<5, 9, 10> {}); - CheckPossibleBreakpoints(10, std::nullopt, std::nullopt, std::index_sequence<10, 11> {}); - CheckPossibleBreakpoints(5, 10, true, std::index_sequence<5> {}); - CheckPossibleBreakpoints(4, std::nullopt, true, std::index_sequence<5> {}); - CheckPossibleBreakpoints(7, 11, true, std::index_sequence<> {}); - CheckPossibleBreakpoints(1, std::nullopt, true, std::index_sequence<> {}); -} - -// When stopped at a breakpoint while doing a step and resumed, -// the unfinished step command should be canceled. -TEST_F(BreakpointTest, InterruptsStep) -{ - auto id1 = CallSync( - "Debugger.setBreakpoint", - [&](auto ¶ms) { params.AddProperty("location", Location(script_id_, 3).ToJson()); }, - std::bind(&ParseNumericId, _1, "breakpointId")); // NOLINT(modernize-avoid-bind) - ASSERT_TRUE(id1); - - auto id2 = CallSync( - "Debugger.setBreakpoint", - [&](auto ¶ms) { params.AddProperty("location", Location(script_id_, 10).ToJson()); }, - std::bind(&ParseNumericId, _1, "breakpointId")); // NOLINT(modernize-avoid-bind) - ASSERT_TRUE(id2); - - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 9)}, {*id1}); - }); - - main_.StepOver(); - ExpectPause({CallFrame(main_.GetMethod(), 10)}, {*id2}); - - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 10)}, {*id1}); - }); -} - -TEST_F(BreakpointTest, RemovesBreakpoint) -{ - auto breakpoint_id = CallSync( - "Debugger.setBreakpoint", - [&](auto ¶ms) { params.AddProperty("location", Location(script_id_, 3).ToJson()); }, - std::bind(&ParseNumericId, _1, "breakpointId")); // NOLINT(modernize-avoid-bind) - ASSERT_TRUE(breakpoint_id); - - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 9)}, {*breakpoint_id}); - }); - - main_.Step(); - - CallSync( - "Debugger.removeBreakpoint", - [&](auto ¶ms) { params.AddProperty("breakpointId", std::to_string(*breakpoint_id)); }, - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - [](auto &result) { EXPECT_THAT(result, JsonProperties()); }); - - func_.Call([](auto &func) { func.Resume(); }); -} - -TEST_F(BreakpointTest, SetsBreakpoint) -{ - auto id1 = CallSync( - "Debugger.setBreakpoint", - [&](auto ¶ms) { params.AddProperty("location", Location(script_id_, 3).ToJson()); }, - [&](auto &result) { - EXPECT_THAT(result, JsonProperties(JsonProperty {"breakpointId", _}, - JsonProperty { - "actualLocation", Pointee(LocationEq(Location(script_id_, 3)))})); - return ParseNumericId(result, "breakpointId"); - }); - ASSERT_TRUE(id1); - - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 9)}, {*id1}); - }); - main_.Step(); - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 10)}, {*id1}); - }); - main_.Finish(); -} - -TEST_F(BreakpointTest, SetsBreakpointByUrl) -{ - auto id1 = CallSync( - "Debugger.setBreakpointByUrl", - [&](auto ¶ms) { - params.AddProperty("lineNumber", 2); - params.AddProperty("url", url_); - }, - [&](auto &result) { - EXPECT_THAT(result, JsonProperties(JsonProperty {"breakpointId", _}, - JsonProperty { - "locations", JsonElements(Matcher { - Pointee(LocationEq(Location(script_id_, 3)))})})); - return ParseNumericId(result, "breakpointId"); - }); - ASSERT_TRUE(id1); - - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 9)}, {*id1}); - }); - main_.Step(); - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 10)}, {*id1}); - }); - main_.Finish(); -} - -TEST_F(BreakpointTest, SetsBreakpointByUrlRegex) -{ - auto id1 = CallSync( - "Debugger.setBreakpointByUrl", - [](auto ¶ms) { - params.AddProperty("lineNumber", 2); - params.AddProperty("urlRegex", ".*"); - }, - [&](auto &result) { - EXPECT_THAT(result, JsonProperties(JsonProperty {"breakpointId", _}, - JsonProperty { - "locations", JsonElements(Matcher { - Pointee(LocationEq(Location(script_id_, 3)))})})); - return ParseNumericId(result, "breakpointId"); - }); - ASSERT_TRUE(id1); - - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 9)}, {*id1}); - }); - main_.Step(); - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 10)}, {*id1}); - }); - main_.Finish(); -} - -TEST_F(BreakpointTest, SetsBreakpointsActive) -{ - auto id1 = CallSync( - "Debugger.setBreakpoint", - [&](auto ¶ms) { params.AddProperty("location", Location(script_id_, 3).ToJson()); }, - std::bind(&ParseNumericId, _1, "breakpointId")); // NOLINT(modernize-avoid-bind) - ASSERT_TRUE(id1); - - CallSync( - "Debugger.setBreakpointsActive", [](auto ¶ms) { params.AddProperty("active", false); }, - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - [](auto &result) { EXPECT_THAT(result, JsonProperties()); }); - - func_.Call([](auto &func) { func.Resume(); }); - main_.Step(); - - CallSync( - "Debugger.setBreakpointsActive", [](auto ¶ms) { params.AddProperty("active", true); }, - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - [](auto &result) { EXPECT_THAT(result, JsonProperties()); }); - - func_.Call([&](auto &func) { - func.Resume(); - ExpectPause({CallFrame(func_, 3), CallFrame(main_.GetMethod(), 10)}, {*id1}); - }); -} - -TEST_F(BreakpointTest, StepOntoBreakpoint) -{ - auto id1 = CallSync( - "Debugger.setBreakpoint", - [&](auto ¶ms) { params.AddProperty("location", Location(script_id_, 10).ToJson()); }, - std::bind(&ParseNumericId, _1, "breakpointId")); // NOLINT(modernize-avoid-bind) - ASSERT_TRUE(id1); - - auto id2 = CallSync( - "Debugger.setBreakpoint", - [&](auto ¶ms) { params.AddProperty("location", Location(script_id_, 11).ToJson()); }, - std::bind(&ParseNumericId, _1, "breakpointId")); // NOLINT(modernize-avoid-bind) - ASSERT_TRUE(id2); - - main_.StepOver(); - ExpectPause({CallFrame(main_.GetMethod(), 10)}, {*id1}); - func_.Call(); - main_.Resume(); - ExpectPause({CallFrame(main_.GetMethod(), 11)}, {*id2}); - func_.Call(); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/client.cpp b/runtime/tooling/inspector/tests/client.cpp deleted file mode 100644 index 361a127161fb3461101e1d63d0511f9940f7bdc8..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/client.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "client.h" -#include "test_server.h" - -#include "utils/logger.h" -#include "websocketpp/close.hpp" -#include "websocketpp/uri.hpp" - -#include -#include -#include - -namespace panda::tooling::inspector::test { -class ClientCategory : public std::error_category { -public: - const char *name() const noexcept override - { - return "client"; - } - - std::string message(int code) const override - { - switch (code) { - case Client::Error::CONNECTION_ALREADY_PINNED: - return "Connection already pinned"; - case Client::Error::CONNECTION_FAILED: - return "Connection failed"; - default: - return "Unknown"; - } - } - - static std::error_code Encode(Client::Error error) - { - static ClientCategory category; - return {error, category}; - } -}; - -void Client::Call(const char *method, std::function &¶ms, - std::function &&handler) -{ - Endpoint::Call({}, ++id_, method, std::move(params)); - OnResult(id_, std::move(handler)); -} - -std::error_code Client::Close() -{ - std::error_code ec; - GetPinnedConnection()->close(websocketpp::close::status::normal, "", ec); - return ec; -} - -void Client::Connect(TestServer &server, std::function &&cb) -{ - std::error_code ec; - - auto connection = endpoint_.get_connection(std::make_shared(false, server.GetName(), ""), ec); - - if (ec) { - return cb(ec); - } - - connection->set_open_handler([this, cb](auto hdl) { - if (!Pin(hdl)) { - return cb(ClientCategory::Encode(CONNECTION_ALREADY_PINNED)); - } - - cb(std::error_code()); - }); - - connection->set_fail_handler([this, cb](auto hdl) { - LOG(INFO, DEBUGGER) << "Failed to connect: " << endpoint_.get_con_from_hdl(hdl)->get_response().get_body(); - - cb(ClientCategory::Encode(CONNECTION_FAILED)); - }); - - server.Connect(connection, *this); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/client.h b/runtime/tooling/inspector/tests/client.h deleted file mode 100644 index 79ac5dad3bae36c8840946f2f0d95838d9d7d615..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/client.h +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_CLIENT_H -#define PANDA_TOOLING_INSPECTOR_TEST_CLIENT_H - -#include "../endpoint.h" -#include "test_config.h" -#include "test_event_loop.h" - -#include "websocketpp/client.hpp" - -#include -#include -#include -#include - -namespace panda { -class JsonObject; -class JsonObjectBuilder; -} // namespace panda - -namespace panda::tooling::inspector::test { -class TestLogger; -class TestServer; - -// NOLINTNEXTLINE(fuchsia-multiple-inheritance) -class Client : public Endpoint>, public TestEventLoop { -public: - enum Error { - CONNECTION_ALREADY_PINNED = 1, - CONNECTION_FAILED, - }; - - Client(std::string_view name, TestLogger &logger) : TestEventLoop(name, logger) {} - - void Call( - const char *method, std::function &¶ms = [](auto & /* builder */) {}, - std::function &&handler = [](auto & /* result */) {}); - - void Call(const char *method, std::function &&handler) - { - Call( - method, [](auto & /* builder */) {}, std::move(handler)); - } - - std::error_code Close(); - - void Connect(TestServer &server, std::function &&cb); - - template - void OnCall(const char *method, MethodHandler &&handler) - { - // NOLINTNEXTLINE(modernize-avoid-bind) - Endpoint::OnCall(method, std::bind(std::forward(handler), std::placeholders::_3)); - } - -private: - unsigned id_ {0}; -}; -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_CLIENT_H diff --git a/runtime/tooling/inspector/tests/common.h b/runtime/tooling/inspector/tests/common.h deleted file mode 100644 index 3484b99ac0914ffd177fd6498c1166a650cc6497..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/common.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_COMMON_H -#define PANDA_TOOLING_INSPECTOR_TEST_COMMON_H - -#include "../types/location.h" -#include "json_object_matcher.h" - -#include "utils/json_parser.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include -#include -#include -#include -#include - -namespace panda::tooling::inspector::test { - -MATCHER(IsInteger, "") -{ - auto abs = std::abs(arg); - return abs - std::trunc(abs) < std::max(abs, 1.) * std::numeric_limits::epsilon(); -} - -inline testing::AssertionResult IsOk(std::error_code ec) -{ - return (ec == std::error_code()) ? testing::AssertionSuccess() - : testing::AssertionFailure() << ec << ": " << ec.message(); -} - -inline testing::Matcher LocationEq(const Location &location) -{ - return JsonProperties(JsonProperty {"scriptId", std::to_string(location.GetScriptId())}, - JsonProperty {"lineNumber", location.GetLineNumber() - 1}); -} -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_COMMON_H diff --git a/runtime/tooling/inspector/tests/inspector_test.cpp b/runtime/tooling/inspector/tests/inspector_test.cpp deleted file mode 100644 index a56f77ab06f8dd6a05db64a3e4f83d85e3e02b69..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/inspector_test.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "combined_event_loop.h" -#include "common.h" -#include "inspector_test_base.h" -#include "json_object_matcher.h" - -#include "tooling/pt_location.h" -#include "tooling/pt_thread.h" -#include "utils/json_builder.h" -#include "utils/json_parser.h" -#include "utils/logger.h" -#include "utils/utf.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include - -using testing::_; -using testing::HasSubstr; -using testing::InSequence; -using testing::Matcher; -using testing::MockFunction; - -namespace panda::tooling::inspector::test { -class InspectorTest : public InspectorTestBase { -protected: - bool AttachDebugger() const override - { - return false; - } - - template - void Call(const char *method, Arg &&...arg) - { - auto saved_prefix = logger_.SetPrefix(client_.GetName()); - client_.Call(method, std::forward(arg)...); - logger_.SetPrefix(saved_prefix); - } - - template - void OnCall(const char *method, Arg &&...arg) - { - client_.OnCall(method, std::forward(arg)...); - } - - template - void ExpectUnsupportedMethods(MethodName... method) - { - EXPECT_CALL(logger_, LogLineInternal(Logger::Level::WARNING, Logger::Component::DEBUGGER, - HasSubstr("Unsupported method"))) - .Times(0); - - (EXPECT_CALL(logger_, LogLineInternal(Logger::Level::WARNING, Logger::Component::DEBUGGER, - AllOf(HasSubstr("Unsupported method"), HasSubstr(method)))), - ...); - } -}; - -namespace { -class Callback { -public: - template - explicit Callback(MockFunction &checkpoint, JsonProperty... property) - : checkpoint_(&checkpoint), result_matcher_(JsonProperties(std::move(property)...)) - { - } - - template - explicit Callback(JsonProperty... property) : result_matcher_(JsonProperties(std::move(property)...)) - { - } - - void operator()(const JsonObject &result) - { - EXPECT_THAT(result, result_matcher_); - - if (checkpoint_ != nullptr) { - checkpoint_->Call(); - } - } - -private: - MockFunction *checkpoint_ {nullptr}; - Matcher result_matcher_; -}; -} // namespace - -TEST_F(InspectorTest, InitialSequence) -{ - ExpectUnsupportedMethods("Profiler.enable", "Debugger.setPauseOnExceptions", "Debugger.setAsyncCallStackDepth", - "Runtime.getIsolateId", "Debugger.setBlackboxPatterns"); - - // NOLINTNEXTLINE(readability-isolate-declaration) - MockFunction execution_context_created, runtime_enabled, debugger_enabled, debugger_paused, - debugger_resumed, run_if_waiting; - InSequence seq; - EXPECT_CALL(execution_context_created, Call); - EXPECT_CALL(runtime_enabled, Call); - EXPECT_CALL(debugger_enabled, Call); - EXPECT_CALL(run_if_waiting, Call); - EXPECT_CALL(debugger_paused, Call); - EXPECT_CALL(debugger_resumed, Call); - - debugger_.GetHooks().ThreadStart(PtThread::NONE); - - Call("Profiler.enable", Callback()); - Call("Runtime.enable", Callback(runtime_enabled)); - OnCall("Runtime.executionContextCreated", - Callback(execution_context_created, - JsonProperty { - "context", Pointee(JsonProperties(JsonProperty {"id", IsInteger()}, - JsonProperty {"origin", _}, - JsonProperty {"name", _}))})); - Call( - "Debugger.enable", [](auto ¶ms) { params.AddProperty("maxScriptsCacheSize", 10000000); }, - Callback(debugger_enabled, JsonProperty {"debuggerId", _})); - Call( - "Debugger.setPauseOnExceptions", [](auto ¶ms) { params.AddProperty("state", "none"); }, Callback()); - Call( - "Debugger.setAsyncCallStackDepth", [](auto ¶ms) { params.AddProperty("maxDepth", 32); }, Callback()); - Call("Runtime.getIsolateId", Callback(JsonProperty {"id", _})); - Call( - "Debugger.setBlackboxPatterns", - [](auto ¶ms) { params.AddProperty("patterns", [](JsonArrayBuilder & /* patterns */) {}); }, Callback()); - Call("Runtime.runIfWaitingForDebugger", Callback(run_if_waiting)); - OnCall("Debugger.paused", Callback(debugger_paused, JsonProperty {"callFrames", _}, - JsonProperty {"hitBreakpoints", _}, - JsonProperty {"reason", "other"})); - OnCall("Debugger.resumed", Callback(debugger_resumed)); - - (server_ + client_).Poll(); - - Call("Debugger.resume"); - debugger_.GetHooks().SingleStep(PtThread::NONE, nullptr, PtLocation("", {}, 0)); - (server_ + client_).Poll(); -} - -TEST_F(InspectorTest, BreaksOnStart) -{ - // NOLINTNEXTLINE(readability-isolate-declaration) - MockFunction debugger_paused, debugger_resumed, debugger_resume_replied_to; - InSequence seq; - EXPECT_CALL(debugger_paused, Call); - EXPECT_CALL(debugger_resumed, Call); - EXPECT_CALL(debugger_resume_replied_to, Call); - - debugger_.GetHooks().ThreadStart(PtThread::NONE); - Call("Runtime.runIfWaitingForDebugger"); - (server_ + client_).Poll(); - - OnCall("Debugger.resumed", Callback(debugger_resumed)); - - Call( - "Debugger.resume", [](auto & /* params */) {}, Callback(debugger_resume_replied_to)); - OnCall("Debugger.paused", Callback(debugger_paused, JsonProperty {"callFrames", _}, - JsonProperty {"hitBreakpoints", _}, - JsonProperty {"reason", "other"})); - debugger_.GetHooks().SingleStep( - PtThread::NONE, LoadSourceFile(".function void foo() {}")->GetDirectMethod(utf::CStringAsMutf8("foo")), - PtLocation("", {}, 0)); - - (server_ + client_).Poll(); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/inspector_test_base.cpp b/runtime/tooling/inspector/tests/inspector_test_base.cpp deleted file mode 100644 index e971f973c6ec929497d398142f7c51c625a7cb8c..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/inspector_test_base.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "inspector_test_base.h" - -#include "../types/location.h" -#include "../types/numeric_id.h" -#include "combined_event_loop.h" -#include "common.h" -#include "json_object_matcher.h" - -#include "assembly-emitter.h" -#include "assembly-parser.h" -#include "class_helper.h" -#include "compiler.h" -#include "mem/panda_string.h" -#include "runtime.h" -#include "runtime_options.h" -#include "source_lang_enum.h" -#include "tooling/inspector/remote_object_id.h" -#include "tooling/pt_thread.h" -#include "utils/json_parser.h" -#include "utils/logger.h" -#include "utils/utf.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using testing::_; -using testing::Eq; -using testing::HasSubstr; -using testing::IsEmpty; -using testing::Matcher; -using testing::NotNull; - -namespace panda::tooling::inspector::test { -void InspectorTestBase::SetUp() -{ - // Check for unsupported methods on the server, i.e. with id. - EXPECT_CALL(logger_, LogLineInternal(Logger::Level::WARNING, Logger::Component::DEBUGGER, - HasSubstr(R"(Unsupported method: {"id")"))) - .Times(0); - - // Set up runtime. - RuntimeOptions options; - options.SetShouldInitializeIntrinsics(false); - options.SetShouldLoadBootPandaFiles(false); - Runtime::Create(options); - - // Set up connection. - client_.Connect(server_, [](std::error_code ec) { ASSERT_TRUE(IsOk(ec)); }); - (server_ + client_).Poll(); - - client_.OnCall("Debugger.scriptParsed", [this](const JsonObject &script) { - auto script_id = ParseNumericId(script, "scriptId"); - ASSERT_TRUE(script_id) << script_id.Error(); - script_id_ = *script_id; - - auto url = script.GetValue("url"); - ASSERT_NE(url, nullptr) << "No 'url' property"; - url_ = *url; - }); - - SetUpSourceFiles(); - - if (AttachDebugger()) { - debugger_.GetHooks().ThreadStart(PtThread::NONE); - client_.Call("Runtime.runIfWaitingForDebugger"); - (server_ + client_).Poll(); - } -} - -void InspectorTestBase::TearDown() -{ - (server_ + client_).Poll(); - Runtime::Destroy(); -} - -Class *InspectorTestBase::LoadSourceFile(const std::string &source) -{ - auto program = pandasm::Parser().Parse(source); - if (!program) { - return nullptr; - } - - auto *class_linker = Runtime::GetCurrent()->GetClassLinker(); - - if (auto pf = pandasm::AsmEmitter::Emit(program.Value())) { - std::string_view file_name = pf->GetFilename(); - class_linker->AddPandaFile(std::move(pf)); - debugger_.GetHooks().LoadModule(file_name); - } else { - return nullptr; - } - - PandaString storage; - auto descriptor = ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &storage); - - ScopedMutatorLock lock; - return class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY)->GetClass(descriptor); -} - -void InspectorTestBase::ExpectPause(std::initializer_list call_frames, - std::initializer_list hit_breakpoints) -{ - size_t pause_count = 0; - - client_.OnCall("Debugger.paused", [&](const JsonObject &result) { - ASSERT_EQ(++pause_count, 1) << "Unexpected pause"; - - std::deque frame_object_ids; - { - auto result_call_frames = result.GetValue("callFrames"); - ASSERT_THAT(result_call_frames, Pointee(Not(IsEmpty()))); - - auto result_call_frame = result_call_frames->front().Get(); - ASSERT_THAT(result_call_frame, Pointee(NotNull())); - - auto result_scope_chain = (**result_call_frame).GetValue("scopeChain"); - ASSERT_NE(result_scope_chain, nullptr); - - for (auto &result_scope : *result_scope_chain) { - auto result_scope_ptr = result_scope.Get(); - ASSERT_THAT(result_scope_ptr, Pointee(NotNull())); - - auto result_scope_object = (**result_scope_ptr).GetValue("object"); - ASSERT_THAT(result_scope_object, Pointee(NotNull())); - - auto result_scope_object_id = ParseNumericId(**result_scope_object, "objectId"); - ASSERT_TRUE(result_scope_object_id) << result_scope_object_id.Error(); - - frame_object_ids.push_back(*result_scope_object_id); - } - } - ASSERT_EQ(frame_object_ids.size(), call_frames.size()); - - for (auto frame_number = 0U; frame_number < call_frames.size(); ++frame_number) { - auto scope = &call_frames.begin()[frame_number].scope_; - - client_.Call( - "Runtime.getProperties", - [&](auto ¶ms) { params.AddProperty("objectId", std::to_string(frame_object_ids[frame_number])); }, - [scope](auto &result_properties) { - std::vector> descriptors; - - auto add_descriptor = [&](auto name, auto value) { - descriptors.push_back(Pointee(JsonProperties( - JsonProperty {"name", std::move(name)}, - JsonProperty { - "value", Pointee(JsonProperties(JsonProperty {"type", "number"}, - JsonProperty {"value", value}))}, - JsonProperty {"configurable", _}, - JsonProperty {"enumerable", _}, - JsonProperty {"writable", _}))); - }; - - add_descriptor("acc", scope->acc); - - for (auto index = 0U; index < scope->args.size(); ++index) { - add_descriptor("a" + std::to_string(index), scope->args.begin()[index]); - } - - for (auto index = 0U; index < scope->vregs.size(); ++index) { - add_descriptor("v" + std::to_string(index), scope->vregs.begin()[index]); - } - - EXPECT_THAT(result_properties, JsonProperties(JsonProperty { - "result", JsonElementsAreArray(descriptors)})); - }); - } - - std::deque> scope_chain; - std::transform( - frame_object_ids.begin(), frame_object_ids.end(), std::back_inserter(scope_chain), - [](auto frame_object_id) { - return Pointee(JsonProperties( - JsonProperty {"type", "local"}, - JsonProperty { - "object", Pointee(JsonProperties( - JsonProperty {"type", "object"}, - JsonProperty {"className", _}, - JsonProperty {"objectId", std::to_string(frame_object_id)}, - JsonProperty {"description", _}))})); - }); - - std::vector> call_frame_matchers; - std::transform( - call_frames.begin(), call_frames.end(), std::back_inserter(call_frame_matchers), [&](auto &call_frame) { - auto matcher = Pointee( - JsonProperties(JsonProperty {"callFrameId", _}, - JsonProperty {"functionName", call_frame.function_name_}, - JsonProperty { - "location", Pointee(LocationEq(Location(script_id_, call_frame.line_number_)))}, - JsonProperty {"url", _}, - JsonProperty {"scopeChain", JsonElementsAreArray(scope_chain)})); - - scope_chain.pop_front(); - return matcher; - }); - - std::vector> hit_breakpoint_matchers; - std::transform(hit_breakpoints.begin(), hit_breakpoints.end(), std::back_inserter(hit_breakpoint_matchers), - [](auto hit_breakpoint) { return Eq(std::to_string(hit_breakpoint)); }); - - EXPECT_THAT(result, JsonProperties(JsonProperty {"reason", "other"}, - JsonProperty {"callFrames", - JsonElementsAreArray(call_frame_matchers)}, - JsonProperty { - "hitBreakpoints", JsonElementsAreArray(hit_breakpoint_matchers)})); - }); - - (server_ + client_).Poll(); - EXPECT_EQ(pause_count, 1) << "Expected a pause"; -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/inspector_test_base.h b/runtime/tooling/inspector/tests/inspector_test_base.h deleted file mode 100644 index 341be5a6bb5d64e38d7d1538af05faed4c67d6c6..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/inspector_test_base.h +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_INSPECTOR_TEST_BASE_H -#define PANDA_TOOLING_INSPECTOR_TEST_INSPECTOR_TEST_BASE_H - -#include "../inspector.h" -#include "../types/numeric_id.h" -#include "client.h" -#include "combined_event_loop.h" -#include "test_debugger.h" -#include "test_logger.h" -#include "test_method.h" -#include "test_server.h" - -#include "method.h" -#include "utils/utf.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace panda { -class Class; -class JsonObject; -class JsonObjectBuilder; -} // namespace panda - -namespace panda::tooling::inspector::test { -class InspectorTestBase : public testing::Test { -protected: - virtual bool AttachDebugger() const - { - return true; - } - - void SetUp() override; - void TearDown() override; - - Class *LoadSourceFile(const std::string &source); - - template , - typename = std::enable_if_t>> - void CallSync(const char *method, std::function &¶ms, Function &&handler) - { - client_.Call(method, std::move(params), - [&](auto &object) { std::invoke(std::forward(handler), object); }); - (server_ + client_).Poll(); - } - - void CallSync(const char *method, std::function &¶ms) - { - CallSync(method, std::move(params), [](auto & /* result */) {}); - } - - template , - typename = std::enable_if_t>> - Result CallSync(const char *method, std::function &¶ms, Function &&handler) - { - Result result; - CallSync(method, std::move(params), - [&](auto &object) { result = std::invoke(std::forward(handler), object); }); - return result; - } - - class CallFrame { - friend class InspectorTestBase; - - public: - struct Scope { - uint64_t acc; - std::initializer_list args; - std::initializer_list vregs; - }; - - CallFrame(Method *method, size_t line_number, Scope scope = {}) - : function_name_(utf::Mutf8AsCString(method->GetName().data)), line_number_(line_number), scope_(scope) - { - } - - CallFrame(const TestMethod &method, size_t line_number, Scope scope = {}) - : CallFrame(method.Get(), line_number, scope) - { - } - - private: - const char *function_name_; - size_t line_number_; - Scope scope_; - }; - - void ExpectPause(std::initializer_list call_frames, - std::initializer_list hit_breakpoints = {}); - - // NOLINTBEGIN(misc-non-private-member-variables-in-classes) - TestLogger logger_ {TestLogger::FAIL_ON_ERROR | TestLogger::OUTPUT_ON_FAIL}; - testing::StrictMock debugger_; - Client client_ {"client", logger_}; - TestServer server_ {"server", logger_}; - InspectorST inspector_ {server_, debugger_}; - ScriptId script_id_ {~0U}; - std::string url_; - // NOLINTEND(misc-non-private-member-variables-in-classes) - -private: - virtual void SetUpSourceFiles() {} -}; -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_INSPECTOR_TEST_BASE_H diff --git a/runtime/tooling/inspector/tests/instruction_pointer.cpp b/runtime/tooling/inspector/tests/instruction_pointer.cpp deleted file mode 100644 index cd92663bd1bae392a0ad307ef7c921bb1f435e42..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/instruction_pointer.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "instruction_pointer.h" - -#include "../inspector.h" -#include "../types/location.h" -#include "client.h" -#include "json_object_matcher.h" - -#include "bytecode_instruction.h" -#include "macros.h" -#include "tooling/pt_location.h" -#include "tooling/pt_thread.h" -#include "utils/json_parser.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace panda::tooling::inspector::test { -InstructionPointer::~InstructionPointer() -{ - EXPECT_EQ(instruction_.GetAddress(), instruction_.GetFrom()) << "InstructionPointer not finished stepping"; -} - -void InstructionPointer::ContinueTo(const Location &location) -{ - client_.Call( - "Debugger.continueToLocation", [&](auto ¶ms) { params.AddProperty("location", location.ToJson()); }, - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - [](auto &result) { EXPECT_THAT(result, JsonProperties()); }); - - Step(); -} - -void InstructionPointer::Finish() -{ - if (unset_) { - Reset(); - } - - while (instruction_.IsValid()) { - Step(); - } -} - -void InstructionPointer::Resume() -{ - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - client_.Call("Debugger.resume", [](const JsonObject &result) { EXPECT_THAT(result, JsonProperties()); }); - Step(); -} - -void InstructionPointer::Step() -{ - if (unset_) { - Reset(); - } - - ASSERT(instruction_.IsValid()); - - PtLocation location(GetMethod()->GetPandaFile()->GetFilename().c_str(), GetMethod()->GetFileId(), - instruction_.GetOffset()); - - frame_.SetBytecodeOffset(instruction_.GetOffset()); - - debugger_.GetHooks().SingleStep(PtThread::NONE, GetMethod(), location); - debugger_.HandleBreakpoint(location); - - instruction_ = instruction_.GetNext(); -} - -void InstructionPointer::StepInto() -{ - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - client_.Call("Debugger.stepInto", [](const JsonObject &result) { EXPECT_THAT(result, JsonProperties()); }); - Step(); -} - -void InstructionPointer::StepOut() -{ - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - client_.Call("Debugger.stepOut", [](const JsonObject &result) { EXPECT_THAT(result, JsonProperties()); }); - Step(); -} - -void InstructionPointer::StepOver() -{ - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - client_.Call("Debugger.stepOver", [](const JsonObject &result) { EXPECT_THAT(result, JsonProperties()); }); - Step(); -} - -void InstructionPointer::Reset() -{ - ASSERT(GetMethod()); - instruction_ = BytecodeInstructionSafe(GetMethod()->GetInstructions(), GetMethod()->GetInstructions(), - GetMethod()->GetInstructions() + GetMethod()->GetCodeSize() - 1); - frame_.SetBytecodeOffset(0); - unset_ = false; -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/instruction_pointer.h b/runtime/tooling/inspector/tests/instruction_pointer.h deleted file mode 100644 index 788cc5e18c87c8d45b333ead65f1533340c2fb9b..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/instruction_pointer.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_INSTRUCTION_POINTER_H -#define PANDA_TOOLING_INSPECTOR_TEST_INSTRUCTION_POINTER_H - -#include "test_frame.h" - -#include "bytecode_instruction.h" -#include "macros.h" - -#include -#include - -namespace panda::tooling::inspector { -class Location; -} // namespace panda::tooling::inspector - -namespace panda::tooling::inspector::test { -class Client; -class TestDebugger; - -class InstructionPointer { -public: - InstructionPointer(TestFrame &frame, Client &client, TestDebugger &debugger) - : frame_(frame), client_(client), debugger_(debugger) - { - } - - NO_COPY_SEMANTIC(InstructionPointer); - DEFAULT_MOVE_CTOR(InstructionPointer) - NO_MOVE_OPERATOR(InstructionPointer); - - ~InstructionPointer(); - - Method *GetMethod() const - { - return frame_.GetMethod(); - } - - void ContinueTo(const Location &location); - void Finish(); - void Resume(); - void Step(); - void StepInto(); - void StepOut(); - void StepOver(); - - void SetAccumulator(uint64_t acc) - { - frame_.SetAccumulator(acc); - } - - template - void SetArguments(Value... value) - { - size_t index = 0; - (frame_.SetArgument(index++, value), ...); - } - - template - void SetVRegs(Value... value) - { - size_t index = 0; - (frame_.SetVReg(index++, value), ...); - } - -private: - void Reset(); - - TestFrame &frame_; - Client &client_; - TestDebugger &debugger_; - BytecodeInstructionSafe instruction_; - bool unset_ {true}; -}; -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_INSTRUCTION_POINTER_H diff --git a/runtime/tooling/inspector/tests/scope_test.cpp b/runtime/tooling/inspector/tests/scope_test.cpp deleted file mode 100644 index 521d2dbcd70807c2fba2a16e0df5da9856ae2c25..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/scope_test.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "inspector_test_base.h" -#include "instruction_pointer.h" -#include "test_method.h" - -#include "utils/utf.h" - -#include "gtest/gtest.h" - -namespace panda::tooling::inspector::test { -class ScopeTest : public InspectorTestBase { -protected: - void SetUpSourceFiles() override - { - auto klass = LoadSourceFile(R"( - .function void main() { - call foo - return - } - - .function void foo() { - return - } - )"); - - main_.Set(klass->GetDirectMethod(utf::CStringAsMutf8("main"))); - foo_.Set(klass->GetDirectMethod(utf::CStringAsMutf8("foo"))); - } - - // NOLINTBEGIN(misc-non-private-member-variables-in-classes) - TestMethod main_ {debugger_, client_}; - TestMethod foo_ {debugger_, client_}; - // NOLINTEND(misc-non-private-member-variables-in-classes) -}; - -TEST_F(ScopeTest, SetsScopeChain) -{ - main_.Call([&](auto &main) { - main.SetArguments(1, 2, 3); - main.SetVRegs(4, 5); - main.StepInto(); - ExpectPause({CallFrame(main_, 3, {0, {1, 2, 3}, {4, 5}})}); - - foo_.Call([&](auto &foo) { - foo.SetAccumulator(1); - foo.SetArguments(2, 2); - foo.StepInto(); - ExpectPause({CallFrame(foo_, 8, {1, {2, 2}, {}}), CallFrame(main_, 3, {0, {1, 2, 3}, {4, 5}})}); - }); - - main.SetAccumulator(1); - main.SetVRegs(5, 6); - main.Resume(); - ExpectPause({CallFrame(main_, 4, {1, {1, 2, 3}, {5, 6}})}); - }); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/server_test.cpp b/runtime/tooling/inspector/tests/server_test.cpp deleted file mode 100644 index b2b1033c102f0acf70398ea7046b16d88485f9eb..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/server_test.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "client.h" -#include "combined_event_loop.h" -#include "common.h" -#include "json_object_matcher.h" -#include "test_logger.h" -#include "test_server.h" - -#include "utils/json_builder.h" -#include "utils/json_parser.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include -#include -#include - -using namespace std::literals::string_view_literals; -using testing::Test; - -namespace panda::tooling::inspector::test { -class ServerTest : public Test { -protected: - TestLogger logger_ {TestLogger::FAIL_ON_ERROR | TestLogger::OUTPUT_ON_FAIL}; - TestServer server_ {"server", logger_}; - Client client_ {"client", logger_}; -}; - -TEST_F(ServerTest, AcceptsOneConnectionAtATime) -{ - logger_.SetFlag(TestLogger::FAIL_ON_ERROR, false); - - unsigned server_connected = 0; - unsigned client_connected = 0; - unsigned client_failed = 0; - - auto check = [&server_connected, &client_connected, &client_failed]() { - if (server_connected < 1 || client_connected + client_failed < 3) { - return; - } - - EXPECT_EQ(server_connected, 1); - EXPECT_EQ(client_connected, 1); - EXPECT_EQ(client_failed, 2); - }; - - server_.OnConnect([&]() { - ++server_connected; - check(); - }); - - auto on_client_connect = [&client_connected, &client_failed, &check](std::error_code ec) { - if (ec) { - ++client_failed; - } else { - ++client_connected; - } - check(); - }; - - Client another_client("another client", logger_); - Client yet_another_client("yet another client", logger_); - - client_.Connect(server_, on_client_connect); - another_client.Connect(server_, on_client_connect); - yet_another_client.Connect(server_, on_client_connect); - - (server_ + client_ + another_client + yet_another_client).Poll(); -} - -TEST_F(ServerTest, AcceptsSubsequentConnections) -{ - unsigned connected = 0; - - server_.OnConnect([&]() { ++connected; }); - - for (auto client_name : {"first client"sv, "second client"sv, "third client"sv}) { - Client client {client_name, logger_}; - - client.Connect(server_, [&](std::error_code ec) { - ASSERT_TRUE(IsOk(ec)); - ASSERT_TRUE(IsOk(client.Close())); - }); - - (server_ + client).Poll(); - } - - EXPECT_EQ(connected, 3); -} - -TEST_F(ServerTest, Calls) -{ - server_.OnConnect([&]() { - server_.Call("foo", [](JsonObjectBuilder ¶ms) { params.AddProperty("x", "bar"); }); - server_.Call("bar"); - }); - - client_.OnCall("foo", [](auto ¶ms) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - EXPECT_THAT(params, JsonProperties(JsonProperty {"x", "bar"})); - }); - - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - client_.OnCall("bar", [](auto ¶ms) { EXPECT_THAT(params, JsonProperties()); }); - - client_.Connect(server_, [&](std::error_code ec) { ASSERT_TRUE(IsOk(ec)); }); - - (server_ + client_).Poll(); -} -TEST_F(ServerTest, Replies) -{ - server_.OnCall("next", [value = 0](auto /* session_id */, auto &result, auto & /* params */) mutable { - result.AddProperty("value", value++); - }); - - std::function check_next = [&, value = 0]() mutable { - client_.Call("next", [&](const JsonObject &next) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - EXPECT_THAT(next, JsonProperties(JsonProperty {"value", value})); - - if (value == 5) { - return; - } - - value += 1; - check_next(); - }); - }; - - client_.Connect(server_, [&](std::error_code ec) { - ASSERT_TRUE(IsOk(ec)); - check_next(); - }); - - (server_ + client_).Poll(); -} - -TEST_F(ServerTest, Pong) -{ - server_.OnCall("ping", [&, ball = 0](auto /* session_id */, auto & /* result */, auto &ping) mutable { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - EXPECT_THAT(ping, JsonProperties(JsonProperty {"ball", ball})); - - server_.Call("pong", [ball](JsonObjectBuilder &pong) { pong.AddProperty("ball", ball + 1); }); - - ball += 2; - }); - - client_.OnCall("pong", [&, ball = 1](auto &pong) mutable { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo) - EXPECT_THAT(pong, JsonProperties(JsonProperty {"ball", ball})); - - if (ball == 5) { - return; - } - - client_.Call("ping", [ball](JsonObjectBuilder &ping) { ping.AddProperty("ball", ball + 1); }); - - ball += 2; - }); - - client_.Connect(server_, [&](std::error_code ec) { - ASSERT_TRUE(IsOk(ec)); - client_.Call("ping", [](JsonObjectBuilder &ping) { ping.AddProperty("ball", 0); }); - }); - - (server_ + client_).Poll(); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/step_test.cpp b/runtime/tooling/inspector/tests/step_test.cpp deleted file mode 100644 index 24404e6545ff5117c7828d13964d15415845af94..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/step_test.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "../types/location.h" -#include "combined_event_loop.h" -#include "inspector_test_base.h" -#include "instruction_pointer.h" -#include "test_method.h" - -#include "utils/utf.h" - -#include "gtest/gtest.h" - -namespace panda::tooling::inspector::test { -class StepTest : public InspectorTestBase { -protected: - void SetUpSourceFiles() override - { - auto klass = LoadSourceFile(R"( - .function void main() { - call foo - call foo - return - } - - .function void foo() { - nop - call bar - return - } - - .function void bar() { - return - } - )"); - - main_.Set(klass->GetDirectMethod(utf::CStringAsMutf8("main"))); - foo_.Set(klass->GetDirectMethod(utf::CStringAsMutf8("foo"))); - bar_.Set(klass->GetDirectMethod(utf::CStringAsMutf8("bar"))); - } - - TestMethod main_ {debugger_, client_}; - TestMethod foo_ {debugger_, client_}; - TestMethod bar_ {debugger_, client_}; -}; - -TEST_F(StepTest, ContinuesToLocation) -{ - main_.Call([&](auto &main) { - main.StepInto(); - ExpectPause({CallFrame(main_, 3)}); - - foo_.Call([&](auto &foo) { - foo.ContinueTo({script_id_, 15}); - ExpectPause({CallFrame(foo_, 9), CallFrame(main_, 3)}); - - foo.Step(); - - bar_.Call([&](auto &bar) { - bar.ContinueTo({script_id_, 9}); - ExpectPause({CallFrame(bar_, 15), CallFrame(foo_, 10), CallFrame(main_, 3)}); - }); - }); - - main.Step(); - foo_.Call([&](auto &foo) { - foo.Resume(); - - foo.Step(); - ExpectPause({CallFrame(foo_, 9), CallFrame(main_, 4)}); - - bar_.Call(); - }); - }); -} - -TEST_F(StepTest, StepsInto) -{ - main_.Call([&](auto &main) { - main.StepInto(); - ExpectPause({CallFrame(main_, 3)}); - - foo_.Call([&](auto &foo) { - foo.StepInto(); - ExpectPause({CallFrame(foo_, 9), CallFrame(main_, 3)}); - - foo.StepInto(); - ExpectPause({CallFrame(foo_, 10), CallFrame(main_, 3)}); - - bar_.Call([&](auto &bar) { - bar.StepInto(); - ExpectPause({CallFrame(bar_, 15), CallFrame(foo_, 10), CallFrame(main_, 3)}); - }); - - foo.StepInto(); - ExpectPause({CallFrame(foo_, 11), CallFrame(main_, 3)}); - }); - - main.StepInto(); - ExpectPause({CallFrame(main_, 4)}); - - foo_.Call([&](auto &foo) { - foo.StepInto(); - ExpectPause({CallFrame(foo_, 9), CallFrame(main_, 4)}); - - foo.StepInto(); - ExpectPause({CallFrame(foo_, 10), CallFrame(main_, 4)}); - - bar_.Call([&](auto &bar) { - bar.StepInto(); - ExpectPause({CallFrame(bar_, 15), CallFrame(foo_, 10), CallFrame(main_, 4)}); - }); - - foo.StepInto(); - ExpectPause({CallFrame(foo_, 11), CallFrame(main_, 4)}); - }); - - main.StepInto(); - ExpectPause({CallFrame(main_, 5)}); - }); -} - -TEST_F(StepTest, StepsOut) -{ - main_.Call([&](auto &main) { - main.StepInto(); - ExpectPause({CallFrame(main_, 3)}); - - foo_.Call([&](auto &foo) { - foo.StepInto(); - ExpectPause({CallFrame(foo_, 9), CallFrame(main_, 3)}); - - foo.StepOut(); - ExpectPause({CallFrame(foo_, 10), CallFrame(main_, 3)}); - - bar_.Call(); - }); - - main.StepOut(); - ExpectPause({CallFrame(main_, 4)}); - - foo_.Call([&](auto &foo) { - foo.Step(); - foo.Step(); - bar_.Call(); - }); - }); -} - -TEST_F(StepTest, StepsOver) -{ - main_.Call([&](auto &main) { - main.StepOver(); - ExpectPause({CallFrame(main_, 3)}); - - foo_.Call(); - main.StepInto(); - ExpectPause({CallFrame(main_, 4)}); - - foo_.Call([&](auto &foo) { - foo.StepOver(); - ExpectPause({CallFrame(foo_, 9), CallFrame(main_, 4)}); - - foo.StepOver(); - ExpectPause({CallFrame(foo_, 10), CallFrame(main_, 4)}); - - bar_.Call(); - foo.StepOver(); - ExpectPause({CallFrame(foo_, 11), CallFrame(main_, 4)}); - }); - - main.StepOver(); - ExpectPause({CallFrame(main_, 5)}); - }); -} - -TEST_F(StepTest, Pause) -{ - main_.Call([&](auto &main) { - main.Resume(); - ExpectPause({CallFrame(main_, 3)}); - - client_.Call("Debugger.pause"); - (server_ + client_).Poll(); - - main.Resume(); - ExpectPause({CallFrame(main_, 4)}); - }); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/test_config.h b/runtime/tooling/inspector/tests/test_config.h deleted file mode 100644 index 6137849731d9a50de519e1a1322eeb34b99d1f50..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_config.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_TEST_CONFIG_H -#define PANDA_TOOLING_INSPECTOR_TEST_TEST_CONFIG_H - -#include "../asio_config.h" - -#include "websocketpp/transport/iostream/endpoint.hpp" - -namespace panda::tooling::inspector::test { -struct TestConfig : AsioConfig { - // NOLINTNEXTLINE(readability-identifier-naming) - using transport_type = websocketpp::transport::iostream::endpoint; -}; -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_TEST_CONFIG_H diff --git a/runtime/tooling/inspector/tests/test_debugger.cpp b/runtime/tooling/inspector/tests/test_debugger.cpp deleted file mode 100644 index be0d9269750010d59e1ff8e8fc45aeb96da34104..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_debugger.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "test_debugger.h" -#include "test_frame.h" - -#include "macros.h" -#include "tooling/debug_interface.h" -#include "tooling/pt_thread.h" -#include "utils/expected.h" - -#include -#include -#include - -namespace panda::tooling::inspector::test { -void TestDebugger::PushFrame(TestFrame &frame) -{ - return call_stack_.PushFront(frame); -} - -Expected, Error> TestDebugger::GetCurrentFrame(PtThread /* thread */) const -{ - if (call_stack_.Empty()) { - return Unexpected(Error(Error::Type::NO_MORE_FRAMES, "No current frame")); - } - - return {std::make_unique(call_stack_.Front())}; -} - -std::optional TestDebugger::EnumerateFrames(PtThread /* thread */, - std::function function) const -{ - std::all_of(call_stack_.begin(), call_stack_.end(), function); - return {}; -} - -std::optional TestDebugger::NotifyFramePop(PtThread /* thread */, uint32_t frame_depth) const -{ - std::next(call_stack_.begin(), frame_depth)->SetNotifyPop(); - return {}; -} - -void TestDebugger::HandleBreakpoint(const PtLocation &location) const -{ - if (breakpoints_.count(location) == 0) { - return; - } - - ASSERT(hooks_ != nullptr && !call_stack_.Empty()); - hooks_->Breakpoint(PtThread::NONE, call_stack_.Front().GetMethod(), location); -} - -std::optional TestDebugger::SetBreakpoint(const PtLocation &location) -{ - if (!breakpoints_.insert(location).second) { - return Error(Error::Type::BREAKPOINT_ALREADY_EXISTS, "Breakpoint already exists"); - } - - return {}; -} - -std::optional TestDebugger::RemoveBreakpoint(const PtLocation &location) -{ - if (breakpoints_.erase(location) == 0) { - return Error(Error::Type::BREAKPOINT_NOT_FOUND, "Breakpoint not found"); - } - - return {}; -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/test_debugger.h b/runtime/tooling/inspector/tests/test_debugger.h deleted file mode 100644 index 91d60dc6702484d7ea84e66224a77309b18552c7..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_debugger.h +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_TEST_DEBUGGER_H -#define PANDA_TOOLING_INSPECTOR_TEST_TEST_DEBUGGER_H - -#include "macros.h" -#include "tooling/debugger.h" -#include "tooling/debug_interface.h" -#include "tooling/pt_location.h" -#include "utils/list.h" - -#include "gmock/gmock.h" - -#include - -namespace panda::tooling::inspector::test { -class TestFrame; - -class TestDebugger : public DebugInterface { -public: - std::optional RegisterHooks(PtHooks *hooks) override - { - hooks_ = hooks; - return {}; - } - std::optional UnregisterHooks() override - { - hooks_ = nullptr; - return {}; - } - PtHooks &GetHooks() - { - ASSERT(hooks_ != nullptr); - return *hooks_; - } - - void PushFrame(TestFrame &frame); - void PopFrame() - { - call_stack_.PopFront(); - } - - Expected, Error> GetCurrentFrame(PtThread thread) const override; - std::optional EnumerateFrames(PtThread thread, std::function function) const override; - std::optional NotifyFramePop(PtThread thread, uint32_t frame_depth) const override; - - void HandleBreakpoint(const PtLocation &location) const; - std::optional SetBreakpoint(const PtLocation &location) override; - std::optional RemoveBreakpoint(const PtLocation &location) override; - - MOCK_METHOD(std::optional, EnableAllGlobalHook, (), (override)); - MOCK_METHOD(std::optional, DisableAllGlobalHook, (), (override)); - MOCK_METHOD(std::optional, SetNotification, (PtThread, bool, PtHookType), (override)); - MOCK_METHOD(std::optional, SuspendThread, (PtThread), (const override)); - MOCK_METHOD(std::optional, ResumeThread, (PtThread), (const override)); - MOCK_METHOD(PtLangExt *, GetLangExtension, (), (const override)); - MOCK_METHOD((Expected), GetPtMethod, (const PtLocation &), (const override)); - MOCK_METHOD(std::optional, GetThreadList, (PandaVector *), (const override)); - MOCK_METHOD(std::optional, SetVariable, (PtThread, uint32_t, int32_t, const PtValue &), (const override)); - MOCK_METHOD(std::optional, GetVariable, (PtThread, uint32_t, int32_t, PtValue *), (const override)); - MOCK_METHOD(std::optional, GetProperty, (PtObject, PtProperty, PtValue *), (const override)); - MOCK_METHOD(std::optional, SetProperty, (PtObject, PtProperty, const PtValue &), (const override)); - MOCK_METHOD(std::optional, EvaluateExpression, (PtThread, uint32_t, ExpressionWrapper, PtValue *), - (const override)); - MOCK_METHOD(std::optional, RetransformClasses, (int, const PtClass *), (const override)); - MOCK_METHOD(std::optional, RedefineClasses, (int, const PandaClassDefinition *), (const override)); - MOCK_METHOD(std::optional, GetThreadInfo, (PtThread, ThreadInfo *), (const override)); - MOCK_METHOD(std::optional, RestartFrame, (PtThread, uint32_t), (const override)); - MOCK_METHOD(std::optional, SetAsyncCallStackDepth, (uint32_t), (const override)); - MOCK_METHOD(std::optional, AwaitPromise, (PtObject, PtValue *), (const override)); - MOCK_METHOD(std::optional, CallFunctionOn, (PtObject, PtMethod, const PandaVector &, PtValue *), - (const override)); - MOCK_METHOD(std::optional, GetProperties, (uint32_t *, char ***), (const override)); - MOCK_METHOD(std::optional, GetThisVariableByFrame, (PtThread, uint32_t, ObjectHeader **), (override)); - MOCK_METHOD(std::optional, SetPropertyAccessWatch, (BaseClass *, PtProperty), (override)); - MOCK_METHOD(std::optional, ClearPropertyAccessWatch, (BaseClass *, PtProperty), (override)); - MOCK_METHOD(std::optional, SetPropertyModificationWatch, (BaseClass *, PtProperty), (override)); - MOCK_METHOD(std::optional, ClearPropertyModificationWatch, (BaseClass *, PtProperty), (override)); - MOCK_METHOD(std::optional, SetPropertyAccessWatch, (PtClass, PtProperty), (override)); - MOCK_METHOD(std::optional, ClearPropertyAccessWatch, (PtClass, PtProperty), (override)); - MOCK_METHOD(std::optional, SetPropertyModificationWatch, (PtClass, PtProperty), (override)); - MOCK_METHOD(std::optional, GetThisVariableByFrame, (PtThread, uint32_t, PtValue *), (override)); - MOCK_METHOD(std::optional, ClearPropertyModificationWatch, (PtClass, PtProperty), (override)); - -private: - PtHooks *hooks_ {nullptr}; - mutable List call_stack_; - std::unordered_set breakpoints_; -}; -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_TEST_DEBUGGER_H diff --git a/runtime/tooling/inspector/tests/test_event_loop.cpp b/runtime/tooling/inspector/tests/test_event_loop.cpp deleted file mode 100644 index 9ebf16c8566c423f9914700364ce78993a76941a..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_event_loop.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "test_event_loop.h" -#include "test_logger.h" - -namespace panda::tooling::inspector::test { -bool TestEventLoop::Poll() -{ - bool result = !messages_.empty(); - while (RunOne()) { - } - return result; -} - -bool TestEventLoop::RunOne() -{ - if (messages_.empty()) { - return false; - } - - const auto &[connection, buffer] = messages_.front(); - - auto saved_prefix = logger_.SetPrefix(name_); - connection->read_all(buffer.data(), buffer.size()); - logger_.SetPrefix(saved_prefix); - - messages_.pop_front(); - return true; -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/test_event_loop.h b/runtime/tooling/inspector/tests/test_event_loop.h deleted file mode 100644 index d5e5458b03bdd0db62cfb9cf300147c61c36319b..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_event_loop.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_TEST_EVENT_LOOP_H -#define PANDA_TOOLING_INSPECTOR_TEST_TEST_EVENT_LOOP_H - -#include "../event_loop.h" -#include "test_config.h" - -#include "websocketpp/connection.hpp" - -#include -#include -#include -#include -#include -#include - -namespace panda::tooling::inspector::test { -class TestLogger; - -// NOLINTNEXTLINE(fuchsia-virtual-inheritance) -class TestEventLoop : public virtual EventLoop { -public: - TestEventLoop(std::string_view name, TestLogger &logger) : logger_(logger), name_(name) {} - - const std::string &GetName() const - { - return name_; - } - - bool Poll() override; - - void Push(const websocketpp::connection::ptr &connection, std::vector &&buffer) - { - messages_.emplace_back(connection, std::move(buffer)); - } - - bool RunOne() override; - -private: - using Message = std::tuple::ptr, std::vector>; - - TestLogger &logger_; - std::string name_; - std::deque messages_; -}; -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_TEST_EVENT_LOOP_H diff --git a/runtime/tooling/inspector/tests/test_frame.h b/runtime/tooling/inspector/tests/test_frame.h deleted file mode 100644 index f382ac98e5b6e352ea4da3c9f401c5693c3d6de9..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_frame.h +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_TEST_TEST_FRAME_H -#define PANDA_TOOLING_INSPECTOR_TEST_TEST_FRAME_H - -#include "test_debugger.h" - -#include "macros.h" -#include "method.h" -#include "tooling/debug_interface.h" -#include "tooling/pt_thread.h" -#include "utils/list.h" - -#include -#include -#include - -namespace panda::tooling::inspector::test { -class TestFrame : public PtFrame, public ListNode { -public: - explicit TestFrame(TestDebugger &debugger) : debugger_(&debugger) - { - debugger.PushFrame(*this); - } - - TestFrame(const TestFrame &other) // NOLINT(bugprone-copy-constructor-init) - : PtFrame(), // NOLINT(readability-redundant-member-init) - ListNode(), // NOLINT(readability-redundant-member-init) - method_(other.method_), - bytecode_offset_(other.bytecode_offset_), - acc_(other.acc_), - args_(other.args_), - vregs_(other.vregs_) - { - } - - TestFrame &operator=(const TestFrame &other) - { - if (&other == this) { - return *this; - } - - debugger_ = nullptr; - method_ = other.method_; - bytecode_offset_ = other.bytecode_offset_; - notify_pop_ = false; - acc_ = other.acc_; - args_ = other.args_; - vregs_ = other.vregs_; - return *this; - } - - NO_MOVE_SEMANTIC(TestFrame); - - ~TestFrame() override - { - if (notify_pop_) { - ASSERT(debugger_ != nullptr); - debugger_->GetHooks().FramePop(PtThread::NONE, method_, false); - } - - if (debugger_ != nullptr) { - debugger_->PopFrame(); - } - } - - bool IsInterpreterFrame() const override - { - return true; - } - - void SetMethod(Method *method) - { - method_ = method; - } - - Method *GetMethod() const override - { - return method_; - } - - void SetVReg(size_t index, uint64_t value) - { - if (vregs_.size() <= index) { - vregs_.resize(index + 1); - } - vregs_[index] = value; - } - - uint64_t GetVReg(size_t index) const override - { - return vregs_[index]; - } - - size_t GetVRegNum() const override - { - return vregs_.size(); - } - - void SetArgument(size_t index, uint64_t value) - { - if (args_.size() <= index) { - args_.resize(index + 1); - } - args_[index] = value; - } - - uint64_t GetArgument(size_t index) const override - { - return args_[index]; - } - - size_t GetArgumentNum() const override - { - return args_.size(); - } - - void SetAccumulator(uint64_t value) - { - acc_ = value; - } - - uint64_t GetAccumulator() const override - { - return acc_; - } - - panda_file::File::EntityId GetMethodId() const override - { - return method_->GetFileId(); - } - - void SetBytecodeOffset(uint32_t bytecode_offset) - { - bytecode_offset_ = bytecode_offset; - } - - uint32_t GetBytecodeOffset() const override - { - return bytecode_offset_; - } - - std::string GetPandaFile() const override - { - return method_->GetPandaFile()->GetFilename(); - } - - uint32_t GetFrameId() const override - { - return reinterpret_cast(this); - } - - void SetNotifyPop() - { - ASSERT(debugger_ != nullptr); - notify_pop_ = true; - } - -private: - TestDebugger *debugger_ {nullptr}; - Method *method_ {nullptr}; - uint32_t bytecode_offset_ {0}; - bool notify_pop_ {false}; - - uint64_t acc_ {0}; - std::vector args_; - std::vector vregs_; -}; -} // namespace panda::tooling::inspector::test - -#endif // PANDA_TOOLING_INSPECTOR_TEST_TEST_FRAME_H diff --git a/runtime/tooling/inspector/tests/test_method.cpp b/runtime/tooling/inspector/tests/test_method.cpp deleted file mode 100644 index 823da37f42ff42d62b4cb7d8d42d92d280d4ae9a..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_method.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "test_method.h" -#include "instruction_pointer.h" -#include "test_frame.h" - -#include "macros.h" -#include "tooling/pt_thread.h" -#include "tooling/vreg_value.h" - -namespace panda::tooling::inspector::test { -void TestMethod::Call(const std::function &body) -{ - ASSERT(method_); - - TestFrame frame(debugger_); - frame.SetMethod(method_); - debugger_.GetHooks().MethodEntry(PtThread::NONE, method_); - - InstructionPointer inst(frame, client_, debugger_); - body(inst); - inst.Finish(); - - debugger_.GetHooks().MethodExit(PtThread::NONE, method_, false, VRegValue()); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/test_server.cpp b/runtime/tooling/inspector/tests/test_server.cpp deleted file mode 100644 index 6e2f40d48aca37fb8dd93ec754f762128f74f1d3..0000000000000000000000000000000000000000 --- a/runtime/tooling/inspector/tests/test_server.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "test_server.h" -#include "test_config.h" - -#include "websocketpp/connection.hpp" - -#include -#include - -#define CONFIG test::TestConfig -#include "../server_endpoint-inl.h" -#undef CONFIG - -namespace panda::tooling::inspector::test { -TestServer::TestServer(std::string_view name, TestLogger &logger) : TestEventLoop(name, logger) {} - -void TestServer::Connect(const websocketpp::connection::ptr &client_connection, - TestEventLoop &client_event_loop) -{ - auto server_connection = endpoint_.get_connection(); - - using Weak = websocketpp::connection::weak_ptr; - - server_connection->set_write_handler( - [connection = Weak(client_connection), &client_event_loop](auto /* hdl */, const char *buffer, size_t size) { - client_event_loop.Push(connection.lock(), {buffer, buffer + size}); - return std::error_code(); - }); - - client_connection->set_write_handler( - [this, connection = Weak(server_connection)](auto /* hdl */, const char *buffer, size_t size) { - Push(connection.lock(), {buffer, buffer + size}); - return std::error_code(); - }); - - server_connection->start(); - client_connection->start(); -} -} // namespace panda::tooling::inspector::test diff --git a/runtime/tooling/inspector/tests/types_test.cpp b/runtime/tooling/inspector/tests/types_test.cpp index f0ac09b683b16d0230027d7fb61b34daf72d88e0..85cbfa5fd129eefb2f10b2ddb4d666ff9701eec4 100644 --- a/runtime/tooling/inspector/tests/types_test.cpp +++ b/runtime/tooling/inspector/tests/types_test.cpp @@ -13,14 +13,13 @@ * limitations under the License. */ +#include "../types/call_frame.h" #include "../types/location.h" #include "../types/numeric_id.h" -#include "common.h" +#include "../types/property_descriptor.h" +#include "../types/remote_object.h" #include "json_object_matcher.h" -#include "tooling/inspector/object_repository.h" -#include "tooling/inspector/property_descriptor.h" -#include "tooling/inspector/remote_object.h" #include "utils/json_builder.h" #include "utils/json_parser.h" @@ -30,6 +29,7 @@ #include #include #include +#include #include #include @@ -38,6 +38,19 @@ using testing::IsNull; using testing::Matcher; namespace panda::tooling::inspector::test { +static Matcher LocationEq(const Location &location) +{ + auto script_id = JsonProperty {"scriptId", std::to_string(location.GetScriptId())}; + auto line_number = JsonProperty {"lineNumber", location.GetLineNumber() - 1}; + + if (auto column_number = location.GetColumnNumber()) { + return JsonProperties(script_id, line_number, + JsonProperty {"columnNumber", *column_number - 1}); + } + + return JsonProperties(script_id, line_number); +} + template static JsonObject ToJson(const T &object) { @@ -46,17 +59,47 @@ static JsonObject ToJson(const T &object) return JsonObject(std::move(json_builder).Build()); } +TEST(CallFrameTest, StringifiesToJson) +{ + Location location(2, 3); + CallFrame call_frame(1, "foo", location, "bar"); + + EXPECT_THAT(ToJson(call_frame), + JsonProperties(JsonProperty {"callFrameId", _}, + JsonProperty {"functionName", "foo"}, + JsonProperty {"location", Pointee(LocationEq(location))}, + JsonProperty {"url", "bar"}, + JsonProperty {"scopeChain", JsonElements()})); + + call_frame.SetScope(RemoteObject::Undefined()); + EXPECT_THAT( + ToJson(call_frame), + JsonProperties(JsonProperty {"callFrameId", _}, + JsonProperty {"functionName", "foo"}, + JsonProperty {"location", Pointee(LocationEq(location))}, + JsonProperty {"url", "bar"}, + JsonProperty { + "scopeChain", JsonElements(Matcher(Pointee(JsonProperties( + JsonProperty {"type", "local"}, + JsonProperty { + "object", Pointee(JsonProperties(JsonProperty { + "type", "undefined"}))}))))})); +} + TEST(LocationTest, ConstructsFromJsonProperty) { JsonObject object(R"({ "no scriptId": {"lineNumber": 4}, "no lineNumber": {"scriptId": "1"}, "invalid scriptId": {"scriptId": "foo", "lineNumber": 4}, - "negative scriptId": {"scriptId": "-1", "lineNumber": 4}, + "negative scriptId": {"scriptId": "-1", "lineNumber": 4, "columnNumber": 8}, "fractional scriptId": {"scriptId": "1.1", "lineNumber": 4}, "negative lineNumber": {"scriptId": "1", "lineNumber": -4}, "fractional lineNumber": {"scriptId": "1", "lineNumber": 4.1}, - "ok": {"scriptId": "1", "lineNumber": 4} + "negative columnNumber": {"scriptId": "1", "lineNumber": 4, "columnNumber": -8}, + "fractional columnNumber": {"scriptId": "1", "lineNumber": 4, "columnNumber": 8.1}, + "without columnNumber": {"scriptId": "1", "lineNumber": 4}, + "with columnNumber": {"scriptId": "1", "lineNumber": 4, "columnNumber": 8} })"); EXPECT_FALSE(Location::FromJsonProperty(object, "no scriptId")); @@ -66,17 +109,29 @@ TEST(LocationTest, ConstructsFromJsonProperty) EXPECT_FALSE(Location::FromJsonProperty(object, "fractional scriptId")); EXPECT_FALSE(Location::FromJsonProperty(object, "negative lineNumber")); EXPECT_FALSE(Location::FromJsonProperty(object, "fractional lineNumber")); - - auto location = Location::FromJsonProperty(object, "ok"); - ASSERT_TRUE(location); - EXPECT_EQ(location->GetScriptId(), 1); - EXPECT_EQ(location->GetLineNumber(), 5); + EXPECT_FALSE(Location::FromJsonProperty(object, "negative columnNumber")); + EXPECT_FALSE(Location::FromJsonProperty(object, "fractional columnNumber")); + + auto line_only = Location::FromJsonProperty(object, "without columnNumber"); + ASSERT_TRUE(line_only); + EXPECT_EQ(line_only->GetScriptId(), 1); + EXPECT_EQ(line_only->GetLineNumber(), 5); + EXPECT_FALSE(line_only->GetColumnNumber().has_value()); + + auto line_and_column = Location::FromJsonProperty(object, "with columnNumber"); + ASSERT_TRUE(line_and_column); + EXPECT_EQ(line_and_column->GetScriptId(), 1); + EXPECT_EQ(line_and_column->GetLineNumber(), 5); + EXPECT_EQ(line_and_column->GetColumnNumber(), 9); } TEST(LocationTest, StringifiesToJson) { - Location location(1, 4); - EXPECT_THAT(ToJson(location), LocationEq(location)); + Location line_only(1, 4); + EXPECT_THAT(ToJson(line_only), LocationEq(line_only)); + + Location line_and_column(1, 4, 8); + EXPECT_THAT(ToJson(line_and_column), LocationEq(line_and_column)); } TEST(NumericIdTest, ConstructsFromJsonProperty) @@ -322,13 +377,11 @@ TEST(RemoteObjectTest, StringifiesToJson) template static void ExpectPreview(F &&func) { - ObjectRepository object_repository; - auto object_id = object_repository.CreateObjectId(); - + std::vector properties; std::vector> expected_properties; std::vector> expected_entries; - func([&](PropertyDescriptor descriptor) { object_repository.AddProperty(object_id, std::move(descriptor)); }, + func([&](PropertyDescriptor descriptor) { properties.emplace_back(std::move(descriptor)); }, [&](auto &&...json_property) { expected_properties.push_back( Pointee(JsonProperties(std::forward(json_property)...))); @@ -338,8 +391,8 @@ static void ExpectPreview(F &&func) "value", Pointee(JsonProperties(std::forward(json_property)...))}))); }); - auto object = RemoteObject::Object("Object", object_id); - object.GeneratePreview(object_repository); + auto object = RemoteObject::Object("Object"); + object.GeneratePreview(properties); if (expected_entries.empty()) { EXPECT_THAT(ToJson(object).GetValue("preview"), diff --git a/runtime/tooling/inspector/thread_state.cpp b/runtime/tooling/inspector/thread_state.cpp index 15c7047b462a6be907235a578aa1ae9a7cf5d804..c2d4ebee02ad2e1de9134e91ce88860bfd3e8bb7 100644 --- a/runtime/tooling/inspector/thread_state.cpp +++ b/runtime/tooling/inspector/thread_state.cpp @@ -16,15 +16,18 @@ #include "thread_state.h" namespace panda::tooling::inspector { -ThreadState::ThreadState(std::function &)> &&suspend, - std::function &&wait_suspension, std::function &&resume) - : suspend_(std::move(suspend)), wait_suspension_(std::move(wait_suspension)), resume_(std::move(resume)) +std::vector ThreadState::GetBreakpointsByLocation(const PtLocation &location) const { + std::vector hit_breakpoints; + + auto range = breakpoint_locations_.equal_range(location); + std::transform(range.first, range.second, std::back_inserter(hit_breakpoints), [](auto &p) { return p.second; }); + + return hit_breakpoints; } void ThreadState::Reset() { - os::memory::LockHolder lock(mutex_); if (step_kind_ != StepKind::BREAK_ON_START) { step_kind_ = StepKind::NONE; } @@ -37,70 +40,69 @@ void ThreadState::Reset() void ThreadState::BreakOnStart() { - os::memory::LockHolder lock(mutex_); if (!paused_) { step_kind_ = StepKind::BREAK_ON_START; } } -void ThreadState::Continue() +bool ThreadState::Continue() { - os::memory::LockHolder lock(mutex_); if (paused_) { step_kind_ = StepKind::NONE; paused_ = false; - resume_(); + return true; } + return false; } -void ThreadState::ContinueTo(std::unordered_set locations) +bool ThreadState::ContinueTo(std::unordered_set locations) { - os::memory::LockHolder lock(mutex_); if (paused_) { step_kind_ = StepKind::CONTINUE_TO; step_locations_ = std::move(locations); paused_ = false; - resume_(); + return true; } + return false; } -void ThreadState::StepIntoOver(ThreadState::StepKind step_kind, std::unordered_set locations) +bool ThreadState::StepInto(std::unordered_set locations) { - // NOLINTNEXTLINE(readability-simplify-boolean-expr) - ASSERT(step_kind == StepKind::STEP_INTO || step_kind == StepKind::STEP_OVER); - - os::memory::LockHolder lock(mutex_); if (paused_) { - step_kind_ = step_kind; + step_kind_ = StepKind::STEP_INTO; method_entered_ = false; step_locations_ = std::move(locations); paused_ = false; - resume_(); + return true; } + return false; } -void ThreadState::StepOut() +bool ThreadState::StepOver(std::unordered_set locations) { - os::memory::LockHolder lock(mutex_); if (paused_) { - step_kind_ = StepKind::STEP_OUT; - method_entered_ = true; + step_kind_ = StepKind::STEP_OVER; + method_entered_ = false; + step_locations_ = std::move(locations); paused_ = false; - resume_(); + return true; } + return false; } -void ThreadState::Touch() +bool ThreadState::StepOut() { - os::memory::LockHolder lock(mutex_); if (paused_) { - resume_(); + step_kind_ = StepKind::STEP_OUT; + method_entered_ = true; + paused_ = false; + return true; } + return false; } void ThreadState::Pause() { - os::memory::LockHolder lock(mutex_); if (!paused_) { step_kind_ = StepKind::PAUSE; } @@ -108,14 +110,11 @@ void ThreadState::Pause() void ThreadState::SetBreakpointsActive(bool active) { - os::memory::LockHolder lock(mutex_); breakpoints_active_ = active; } BreakpointId ThreadState::SetBreakpoint(const std::vector &locations) { - os::memory::LockHolder lock(mutex_); - auto id = next_breakpoint_id_++; for (auto &location : locations) { breakpoint_locations_.emplace(location, id); @@ -126,8 +125,6 @@ BreakpointId ThreadState::SetBreakpoint(const std::vector &locations void ThreadState::RemoveBreakpoint(BreakpointId id) { - os::memory::LockHolder lock(mutex_); - for (auto it = breakpoint_locations_.begin(); it != breakpoint_locations_.end();) { if (it->second == id) { it = breakpoint_locations_.erase(it); @@ -139,7 +136,6 @@ void ThreadState::RemoveBreakpoint(BreakpointId id) void ThreadState::OnFramePop() { - os::memory::LockHolder lock(mutex_); ASSERT(!paused_); switch (step_kind_) { case StepKind::NONE: @@ -160,7 +156,6 @@ void ThreadState::OnFramePop() bool ThreadState::OnMethodEntry() { - os::memory::LockHolder lock(mutex_); ASSERT(!paused_); switch (step_kind_) { case StepKind::NONE: @@ -182,7 +177,6 @@ bool ThreadState::OnMethodEntry() void ThreadState::OnSingleStep(const PtLocation &location) { - os::memory::LockHolder lock(mutex_); ASSERT(!paused_); if (!breakpoints_active_ || breakpoint_locations_.find(location) == breakpoint_locations_.end()) { switch (step_kind_) { @@ -224,18 +218,5 @@ void ThreadState::OnSingleStep(const PtLocation &location) } else { paused_ = true; } - - while (paused_) { - std::vector hit_breakpoints; - auto range = breakpoint_locations_.equal_range(location); - std::transform(range.first, range.second, std::back_inserter(hit_breakpoints), - [](auto &p) { return p.second; }); - - suspend_(hit_breakpoints); - - mutex_.Unlock(); - wait_suspension_(); - mutex_.Lock(); - } } } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/thread_state.h b/runtime/tooling/inspector/thread_state.h index 78c8c8dac0ed9d46254d0c6f341ed0dae314ebee..bf65718630edaa744f863c1e44a4b388203bdd76 100644 --- a/runtime/tooling/inspector/thread_state.h +++ b/runtime/tooling/inspector/thread_state.h @@ -26,6 +26,36 @@ namespace panda::tooling::inspector { class ThreadState final { public: + ThreadState() = default; + ~ThreadState() = default; + + NO_COPY_SEMANTIC(ThreadState); + NO_MOVE_SEMANTIC(ThreadState); + + bool IsPaused() const + { + return paused_; + } + + std::vector GetBreakpointsByLocation(const PtLocation &location) const; + + void Reset(); + void BreakOnStart(); + bool Continue(); + bool ContinueTo(std::unordered_set locations); + bool StepInto(std::unordered_set locations); + bool StepOver(std::unordered_set locations); + bool StepOut(); + void Pause(); + void SetBreakpointsActive(bool active); + BreakpointId SetBreakpoint(const std::vector &locations); + void RemoveBreakpoint(BreakpointId id); + + void OnFramePop(); + bool OnMethodEntry(); + void OnSingleStep(const PtLocation &location); + +private: enum class StepKind { // Just continue execution NONE, @@ -51,96 +81,21 @@ public: STEP_OVER }; - ThreadState(std::function &)> &&suspend, - std::function &&wait_suspension, std::function &&resume); - ~ThreadState() = default; - - NO_COPY_SEMANTIC(ThreadState); - NO_MOVE_SEMANTIC(ThreadState); - - //////////////////////////////////////////////////////////////////////////// - // - // The following methods should be called on the server thread - // - //////////////////////////////////////////////////////////////////////////// - - // Resets the state on a new connection - void Reset(); - - // Tells a newly created thread to pause on the next step - void BreakOnStart(); - - // Continues execution of a paused thread - void Continue(); - - // Continues execution of a paused thread until it reaches one of the locations - void ContinueTo(std::unordered_set locations); - - // Tells a paused thread to perform a step into / over - void StepIntoOver(StepKind step_kind, std::unordered_set locations); - - // Tells a paused thread to perform a step out - void StepOut(); - - // Makes a paused thread to resume and suspend back (does nothing for running threads). - // Used to notify about thread's state when processing `runIfWaitingForDebugger` - void Touch(); - - // Tells a running thread to pause on the next step - void Pause(); - - // Enables or disables stops on breakpoints - void SetBreakpointsActive(bool active); - - // Sets a breakpoint on the locations. Returns the breakpoint ID - BreakpointId SetBreakpoint(const std::vector &locations); - - // Removes the breakpoint by ID - void RemoveBreakpoint(BreakpointId id); - - //////////////////////////////////////////////////////////////////////////// - // - // The following methods should be called on an application thread - // - //////////////////////////////////////////////////////////////////////////// - - // Notification that an "interesting" frame was popped - void OnFramePop(); - - // Notification that a new frame was pushed. Returns true if we want to be notified about the frame is popped - // (i.e. the frame is "interesting"), false otherwise - bool OnMethodEntry(); - - // Notification that a next step will be performed. Pauses the thread if necessary - void OnSingleStep(const PtLocation &location); - -private: - os::memory::Mutex mutex_; - - // Marks a paused thread as suspended. Should be called on an application thread - std::function &)> suspend_ GUARDED_BY(mutex_); - - // Waits until the suspension of a paused thread ends. Should be called on an application thread - std::function wait_suspension_; - - // Marks a paused thread as not suspended. Should be called on the server thread - std::function resume_ GUARDED_BY(mutex_); - - StepKind step_kind_ GUARDED_BY(mutex_) {StepKind::NONE}; + StepKind step_kind_ {StepKind::NONE}; // The set of locations has different semantics for various kinds of stepping: // - for CONTINUE_TO it contains the set of locations we should reach to end the step; // - for STEP_INTO and STEP_OVER it contains the set of locations corresponding to the current line // (then, the locations we should leave to end the step). - std::unordered_set step_locations_ GUARDED_BY(mutex_); + std::unordered_set step_locations_; - bool method_entered_ GUARDED_BY(mutex_) {false}; + bool method_entered_ {false}; - bool breakpoints_active_ GUARDED_BY(mutex_) {true}; - BreakpointId next_breakpoint_id_ GUARDED_BY(mutex_) {0}; - std::unordered_multimap breakpoint_locations_ GUARDED_BY(mutex_); + bool breakpoints_active_ {true}; + BreakpointId next_breakpoint_id_ {0}; + std::unordered_multimap breakpoint_locations_; - bool paused_ GUARDED_BY(mutex_) {false}; + bool paused_ {false}; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/tests/combined_event_loop.cpp b/runtime/tooling/inspector/types/call_frame.cpp similarity index 35% rename from runtime/tooling/inspector/tests/combined_event_loop.cpp rename to runtime/tooling/inspector/types/call_frame.cpp index edfcf825dbc73ebc3f46abfac3a1cd5db9ebf111..22e677a4696df9a742cc6a79a5ed23e21cba4013 100644 --- a/runtime/tooling/inspector/tests/combined_event_loop.cpp +++ b/runtime/tooling/inspector/types/call_frame.cpp @@ -13,40 +13,31 @@ * limitations under the License. */ -#include "combined_event_loop.h" +#include "call_frame.h" -namespace panda::tooling::inspector::test { -bool CombinedEventLoop::Poll() -{ - if (!RunOne()) { - return false; - } - - while (RunOne()) { - } - return true; -} +#include "utils/json_builder.h" -bool CombinedEventLoop::RunOne() +namespace panda::tooling::inspector { +std::function CallFrame::ToJson() const { - for (unsigned count = 2; count-- != 0;) { - switch (state_) { - case LEFT: - state_ = RIGHT; - if (left_.RunOne()) { - return true; - } - break; + return [this](auto &json_builder) { + json_builder.AddProperty("callFrameId", JsonObjectBuilder() + .AddProperty("ordinal", id_) + .AddProperty("injectedScriptId", location_.GetScriptId()) + .Build()); - case RIGHT: - state_ = LEFT; - if (right_.RunOne()) { - return true; - } - break; - } - } + json_builder.AddProperty("functionName", function_name_); + json_builder.AddProperty("location", location_.ToJson()); + json_builder.AddProperty("url", url_); - return false; + json_builder.AddProperty("scopeChain", [&](JsonArrayBuilder &scope_chain_builder) { + if (scope_) { + scope_chain_builder.Add([&](JsonObjectBuilder &scope_builder) { + scope_builder.AddProperty("type", "local"); + scope_builder.AddProperty("object", scope_->ToJson()); + }); + } + }); + }; } -} // namespace panda::tooling::inspector::test +} // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/tests/test_method.h b/runtime/tooling/inspector/types/call_frame.h similarity index 44% rename from runtime/tooling/inspector/tests/test_method.h rename to runtime/tooling/inspector/types/call_frame.h index 0bef5323ee19fcfd63e64c2f7b88a8de6e900c65..217dc3084a51752c5d440c0789ae95bba872a1eb 100644 --- a/runtime/tooling/inspector/tests/test_method.h +++ b/runtime/tooling/inspector/types/call_frame.h @@ -12,42 +12,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#ifndef PANDA_TOOLING_INSPECTOR_TYPES_CALL_FRAME_H +#define PANDA_TOOLING_INSPECTOR_TYPES_CALL_FRAME_H -#ifndef PANDA_TOOLING_INSPECTOR_TEST_TEST_METHOD_H -#define PANDA_TOOLING_INSPECTOR_TEST_TEST_METHOD_H +#include "location.h" +#include "numeric_id.h" +#include "remote_object.h" #include +#include +#include +#include namespace panda { -class Method; +class JsonObjectBuilder; } // namespace panda -namespace panda::tooling::inspector::test { -class Client; -class InstructionPointer; -class TestDebugger; - -class TestMethod { +namespace panda::tooling::inspector { +class CallFrame { public: - TestMethod(TestDebugger &debugger, Client &client) : debugger_(debugger), client_(client) {} - - Method *Get() const + CallFrame(CallFrameId id, std::string_view function_name, Location location, std::string_view url) + : id_(id), function_name_(function_name), location_(location), url_(url) { - return method_; } - void Set(Method *method) + void SetScope(RemoteObject scope) { - method_ = method; + scope_ = std::move(scope); } - void Call(const std::function &body = [](auto & /* ip */) {}); + std::function ToJson() const; private: - TestDebugger &debugger_; - Client &client_; - Method *method_ {nullptr}; + CallFrameId id_; + std::string_view function_name_; + Location location_; + std::string_view url_; + std::optional scope_; }; -} // namespace panda::tooling::inspector::test +} // namespace panda::tooling::inspector -#endif // PANDA_TOOLING_INSPECTOR_TEST_TEST_METHOD_H +#endif // PANDA_TOOLING_INSPECTOR_TYPES_CALL_FRAME_H diff --git a/runtime/tooling/inspector/types/location.cpp b/runtime/tooling/inspector/types/location.cpp index 905c8c163353836da4cae275ccf537459b8cf88d..263fc3002dcb8100c92a2ec6303daa9cd5f39dd6 100644 --- a/runtime/tooling/inspector/types/location.cpp +++ b/runtime/tooling/inspector/types/location.cpp @@ -21,35 +21,58 @@ #include "utils/json_parser.h" #include +#include #include +#include #include using namespace std::literals::string_literals; // NOLINT(google-build-using-namespace) namespace panda::tooling::inspector { -Expected Location::FromJsonProperty(const JsonObject &object, const char *property_name) +static Expected, std::string> GetIndexValue(const JsonObject &object, const char *property_name) { - auto property = object.GetValue(property_name); + auto property = object.GetValue(property_name); if (property == nullptr) { + return {}; + } + + auto value = std::trunc(*property); + if (*property < 0 || *property - value > value * DBL_EPSILON) { + return Unexpected("Invalid "s + property_name + ": " + std::to_string(*property)); + } + + return {static_cast(value) + 1}; +} + +Expected Location::FromJsonProperty(const JsonObject &object, const char *property_name) +{ + auto location = object.GetValue(property_name); + if (location == nullptr) { return Unexpected("No such property: "s + property_name); } - auto script_id = ParseNumericId(**property, "scriptId"); + auto script_id = ParseNumericId(**location, "scriptId"); if (!script_id) { return Unexpected(script_id.Error()); } - auto line_number = property->get()->GetValue("lineNumber"); - if (line_number == nullptr) { + auto line_number = GetIndexValue(**location, "lineNumber"); + if (!line_number) { + return Unexpected(line_number.Error()); + } + if (!*line_number) { return Unexpected("Invalid Location: No 'lineNumber' property"s); } - auto line_number_trunc = std::trunc(*line_number); - if (*line_number < 0 || *line_number - line_number_trunc > line_number_trunc * DBL_EPSILON) { - return Unexpected("Invalid line number: " + std::to_string(*line_number)); + auto column_number = GetIndexValue(**location, "columnNumber"); + if (!column_number) { + return Unexpected(column_number.Error()); } - return Location(*script_id, line_number_trunc + 1); + if (*column_number) { + return Location(*script_id, **line_number, **column_number); + } + return Location(*script_id, **line_number); } std::function Location::ToJson() const @@ -57,6 +80,10 @@ std::function Location::ToJson() const return [this](JsonObjectBuilder &json_builder) { json_builder.AddProperty("scriptId", std::to_string(script_id_)); json_builder.AddProperty("lineNumber", line_number_ - 1); + + if (column_number_) { + json_builder.AddProperty("columnNumber", *column_number_ - 1); + } }; } } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/types/location.h b/runtime/tooling/inspector/types/location.h index 43ad10df088dd5439f3d88848a9a4b756b33415a..a5a559de6d736a4e1b72a5b1fb21aaddec7475ba 100644 --- a/runtime/tooling/inspector/types/location.h +++ b/runtime/tooling/inspector/types/location.h @@ -22,6 +22,7 @@ #include #include +#include #include namespace panda { @@ -34,6 +35,11 @@ class Location { public: Location(ScriptId script_id, size_t line_number) : script_id_(script_id), line_number_(line_number) {} + Location(ScriptId script_id, size_t line_number, size_t column_number) + : script_id_(script_id), line_number_(line_number), column_number_(column_number) + { + } + static Expected FromJsonProperty(const JsonObject &object, const char *property_name); ScriptId GetScriptId() const @@ -46,11 +52,17 @@ public: return line_number_; } + const std::optional &GetColumnNumber() const + { + return column_number_; + } + std::function ToJson() const; private: ScriptId script_id_; size_t line_number_; + std::optional column_number_; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/types/numeric_id.h b/runtime/tooling/inspector/types/numeric_id.h index 6dc4826e7a9da92bbff2b47283c5fe5aa39d039b..2d129a9c278a246ab62e66d58f0477a554f040f7 100644 --- a/runtime/tooling/inspector/types/numeric_id.h +++ b/runtime/tooling/inspector/types/numeric_id.h @@ -28,7 +28,9 @@ namespace panda::tooling::inspector { using BreakpointId = size_t; +using CallFrameId = uint32_t; using FrameId = uint32_t; +using RemoteObjectId = size_t; using ScriptId = size_t; template >> diff --git a/runtime/include/tooling/inspector/property_descriptor.h b/runtime/tooling/inspector/types/property_descriptor.h similarity index 90% rename from runtime/include/tooling/inspector/property_descriptor.h rename to runtime/tooling/inspector/types/property_descriptor.h index 69a7ef5edfb92219b69d79a7e2e886ee86a72fa7..6608db91479378b8c772eae4a7990e459426e4c0 100644 --- a/runtime/include/tooling/inspector/property_descriptor.h +++ b/runtime/tooling/inspector/types/property_descriptor.h @@ -12,8 +12,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef PANDA_TOOLING_INSPECTOR_PROPERTY_DESCRIPTOR_H -#define PANDA_TOOLING_INSPECTOR_PROPERTY_DESCRIPTOR_H + +#ifndef PANDA_TOOLING_INSPECTOR_TYPES_PROPERTY_DESCRIPTOR_H +#define PANDA_TOOLING_INSPECTOR_TYPES_PROPERTY_DESCRIPTOR_H #include "remote_object.h" @@ -25,8 +26,6 @@ #include namespace panda::tooling::inspector { -class ObjectRepository; - class PropertyDescriptor { public: PropertyDescriptor(std::string name, RemoteObject value, bool is_entry = false) @@ -110,13 +109,6 @@ public: writable_ = writable; } - void GeneratePreview(ObjectRepository &object_repository) - { - if (!IsAccessor()) { - value_.GeneratePreview(object_repository); - } - } - std::function ToJson() const { return [this](JsonObjectBuilder &json_builder) { @@ -150,4 +142,4 @@ private: }; } // namespace panda::tooling::inspector -#endif // PANDA_TOOLING_INSPECTOR_PROPERTY_DESCRIPTOR_H +#endif // PANDA_TOOLING_INSPECTOR_TYPES_PROPERTY_DESCRIPTOR_H diff --git a/runtime/tooling/inspector/types/remote_object.cpp b/runtime/tooling/inspector/types/remote_object.cpp index 6fe24a983917511bebed4726b7e95b5896584a40..24690f82309da49d573b750f6369aa774513888f 100644 --- a/runtime/tooling/inspector/types/remote_object.cpp +++ b/runtime/tooling/inspector/types/remote_object.cpp @@ -13,10 +13,10 @@ * limitations under the License. */ -#include "tooling/inspector/remote_object.h" +#include "tooling/inspector/types/remote_object.h" #include "macros.h" -#include "tooling/inspector/object_repository.h" +#include "tooling/inspector/types/property_descriptor.h" #include "utils/json_builder.h" #include "utils/string_helpers.h" @@ -32,19 +32,11 @@ using panda::helpers::string::Format; namespace panda::tooling::inspector { -void RemoteObject::GeneratePreview(const ObjectRepository &object_repository) +void RemoteObject::GeneratePreview(const std::vector &properties) const { - auto object_id = GetObjectId(); - if (!object_id) { - return; - } - - auto properties = object_repository.GetProperties(*object_id); - ASSERT(properties); - preview_.clear(); - for (auto &property : *properties) { + for (auto &property : properties) { if (!property.IsEnumerable()) { continue; } diff --git a/runtime/include/tooling/inspector/remote_object.h b/runtime/tooling/inspector/types/remote_object.h similarity index 96% rename from runtime/include/tooling/inspector/remote_object.h rename to runtime/tooling/inspector/types/remote_object.h index c88f506d3ad2589d02b2884965a53b5213c538cd..c954f5577a2c7976afb7d49858c9d0c1faf7c4ea 100644 --- a/runtime/include/tooling/inspector/remote_object.h +++ b/runtime/tooling/inspector/types/remote_object.h @@ -15,7 +15,7 @@ #ifndef PANDA_TOOLING_INSPECTOR_REMOTE_OBJECT_H #define PANDA_TOOLING_INSPECTOR_REMOTE_OBJECT_H -#include "remote_object_id.h" +#include "numeric_id.h" #include #include @@ -32,7 +32,7 @@ class JsonObjectBuilder; } // namespace panda namespace panda::tooling::inspector { -class ObjectRepository; +class PropertyDescriptor; class RemoteObject { public: @@ -112,7 +112,9 @@ public: return RemoteObject(FunctionT {std::move(class_name), object_id, std::move(name), length}); } - void GeneratePreview(const ObjectRepository &object_repository); + std::optional GetObjectId() const; + + void GeneratePreview(const std::vector &properties) const; std::function ToJson() const; @@ -179,12 +181,11 @@ private: static std::string GetDescription(const ObjectT &object); static std::string GetDescription(const ArrayT &array); static std::string GetDescription(const FunctionT &function); - std::optional GetObjectId() const; Type GetType() const; std::function PreviewToJson() const; Value value_; - std::vector preview_; + mutable std::vector preview_; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/pt_hooks_wrapper.h b/runtime/tooling/pt_hooks_wrapper.h index 32846c6a73b6f40c2e086d1ba1fa9c7cf3d47fd8..18ae06f91dc9a1110586e1600bed56acb6ee16d4 100644 --- a/runtime/tooling/pt_hooks_wrapper.h +++ b/runtime/tooling/pt_hooks_wrapper.h @@ -144,7 +144,7 @@ public: } void ConsoleCall(PtThread thread, ConsoleCallType type, uint64_t timestamp, - const PandaVector &arguments) override + const PandaVector &arguments) override { // Atomic with acquire order reason: data race with hooks_ auto *loaded_hooks = hooks_.load(std::memory_order_acquire);