diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index ddf55e6fb382d..c1c2adb32a510 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -7,75 +7,168 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "DAPError.h" -#include "Protocol/ProtocolRequests.h" -#include "Protocol/ProtocolTypes.h" +#include "EventHelper.h" +#include "JSONUtils.h" #include "RequestHandler.h" #include "lldb/API/SBStream.h" -using namespace lldb_dap::protocol; - namespace lldb_dap { -/// Retrieves the details of the exception that caused this event to be raised. -/// -/// Clients should only call this request if the corresponding capability -/// `supportsExceptionInfoRequest` is true. -llvm::Expected -ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { - - lldb::SBThread thread = dap.GetLLDBThread(args.threadId); - if (!thread.IsValid()) - return llvm::make_error( - llvm::formatv("Invalid thread id: {}", args.threadId).str()); - - ExceptionInfoResponseBody response; - response.breakMode = eExceptionBreakModeAlways; - const lldb::StopReason stop_reason = thread.GetStopReason(); - switch (stop_reason) { - case lldb::eStopReasonSignal: - response.exceptionId = "signal"; - break; - case lldb::eStopReasonBreakpoint: { - const ExceptionBreakpoint *exc_bp = - dap.GetExceptionBPFromStopReason(thread); - if (exc_bp) { - response.exceptionId = exc_bp->GetFilter(); - response.description = exc_bp->GetLabel(); +// "ExceptionInfoRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Retrieves the details of the exception that +// caused this event to be raised. Clients should only call this request if +// the corresponding capability `supportsExceptionInfoRequest` is true.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "exceptionInfo" ] +// }, +// "arguments": { +// "$ref": "#/definitions/ExceptionInfoArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "ExceptionInfoArguments": { +// "type": "object", +// "description": "Arguments for `exceptionInfo` request.", +// "properties": { +// "threadId": { +// "type": "integer", +// "description": "Thread for which exception information should be +// retrieved." +// } +// }, +// "required": [ "threadId" ] +// }, +// "ExceptionInfoResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to `exceptionInfo` request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "exceptionId": { +// "type": "string", +// "description": "ID of the exception that was thrown." +// }, +// "description": { +// "type": "string", +// "description": "Descriptive text for the exception." +// }, +// "breakMode": { +// "$ref": "#/definitions/ExceptionBreakMode", +// "description": "Mode that caused the exception notification to +// be raised." +// }, +// "details": { +// "$ref": "#/definitions/ExceptionDetails", +// "description": "Detailed information about the exception." +// } +// }, +// "required": [ "exceptionId", "breakMode" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +// "ExceptionDetails": { +// "type": "object", +// "description": "Detailed information about an exception that has +// occurred.", "properties": { +// "message": { +// "type": "string", +// "description": "Message contained in the exception." +// }, +// "typeName": { +// "type": "string", +// "description": "Short type name of the exception object." +// }, +// "fullTypeName": { +// "type": "string", +// "description": "Fully-qualified type name of the exception object." +// }, +// "evaluateName": { +// "type": "string", +// "description": "An expression that can be evaluated in the current +// scope to obtain the exception object." +// }, +// "stackTrace": { +// "type": "string", +// "description": "Stack trace at the time the exception was thrown." +// }, +// "innerException": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/ExceptionDetails" +// }, +// "description": "Details of the exception contained by this exception, +// if any." +// } +// } +// }, +void ExceptionInfoRequestHandler::operator()( + const llvm::json::Object &request) const { + llvm::json::Object response; + FillResponse(request, response); + const auto *arguments = request.getObject("arguments"); + llvm::json::Object body; + lldb::SBThread thread = dap.GetLLDBThread(*arguments); + if (thread.IsValid()) { + auto stopReason = thread.GetStopReason(); + if (stopReason == lldb::eStopReasonSignal) + body.try_emplace("exceptionId", "signal"); + else if (stopReason == lldb::eStopReasonBreakpoint) { + ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); + if (exc_bp) { + EmplaceSafeString(body, "exceptionId", exc_bp->GetFilter()); + EmplaceSafeString(body, "description", exc_bp->GetLabel()); + } else { + body.try_emplace("exceptionId", "exception"); + } } else { - response.exceptionId = "exception"; - } - } break; - default: - response.exceptionId = "exception"; - } - - lldb::SBStream stream; - if (response.description.empty()) { - if (thread.GetStopDescription(stream)) { - response.description = {stream.GetData(), stream.GetSize()}; + body.try_emplace("exceptionId", "exception"); } - } - - if (lldb::SBValue exception = thread.GetCurrentException()) { - stream.Clear(); - response.details = ExceptionDetails{}; - if (exception.GetDescription(stream)) { - response.details->message = {stream.GetData(), stream.GetSize()}; + if (!ObjectContainsKey(body, "description")) { + char description[1024]; + if (thread.GetStopDescription(description, sizeof(description))) { + EmplaceSafeString(body, "description", description); + } } + body.try_emplace("breakMode", "always"); + auto exception = thread.GetCurrentException(); + if (exception.IsValid()) { + llvm::json::Object details; + lldb::SBStream stream; + if (exception.GetDescription(stream)) { + EmplaceSafeString(details, "message", stream.GetData()); + } - if (lldb::SBThread exception_backtrace = - thread.GetCurrentExceptionBacktrace()) { - stream.Clear(); - exception_backtrace.GetDescription(stream); - - for (uint32_t idx = 0; idx < exception_backtrace.GetNumFrames(); idx++) { - lldb::SBFrame frame = exception_backtrace.GetFrameAtIndex(idx); - frame.GetDescription(stream); + auto exceptionBacktrace = thread.GetCurrentExceptionBacktrace(); + if (exceptionBacktrace.IsValid()) { + lldb::SBStream stream; + exceptionBacktrace.GetDescription(stream); + for (uint32_t i = 0; i < exceptionBacktrace.GetNumFrames(); i++) { + lldb::SBFrame frame = exceptionBacktrace.GetFrameAtIndex(i); + frame.GetDescription(stream); + } + EmplaceSafeString(details, "stackTrace", stream.GetData()); } - response.details->stackTrace = {stream.GetData(), stream.GetSize()}; + + body.try_emplace("details", std::move(details)); } + // auto excInfoCount = thread.GetStopReasonDataCount(); + // for (auto i=0; i> { +class ExceptionInfoRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral GetCommand() { return "exceptionInfo"; } FeatureSet GetSupportedFeatures() const override { return {protocol::eAdapterFeatureExceptionInfoRequest}; } - llvm::Expected - Run(const protocol::ExceptionInfoArguments &args) const override; + void operator()(const llvm::json::Object &request) const override; }; class InitializeRequestHandler diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index e207aad2167d6..b9393356b4e01 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -625,22 +625,4 @@ llvm::json::Value toJSON(const ModuleSymbolsResponseBody &DGMSR) { return result; } -bool fromJSON(const json::Value &Params, ExceptionInfoArguments &Args, - json::Path Path) { - json::ObjectMapper O(Params, Path); - return O && O.map("threadId", Args.threadId); -} - -json::Value toJSON(const ExceptionInfoResponseBody &ERB) { - json::Object result{{"exceptionId", ERB.exceptionId}, - {"breakMode", ERB.breakMode}}; - - if (!ERB.description.empty()) - result.insert({"description", ERB.description.c_str()}); - if (ERB.details.has_value()) - result.insert({"details", *ERB.details}); - - return result; -} - } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index b894f2b4ed44d..a85a68b87014c 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -1039,28 +1039,6 @@ struct ModuleSymbolsResponseBody { }; llvm::json::Value toJSON(const ModuleSymbolsResponseBody &); -struct ExceptionInfoArguments { - /// Thread for which exception information should be retrieved. - lldb::tid_t threadId = LLDB_INVALID_THREAD_ID; -}; -bool fromJSON(const llvm::json::Value &, ExceptionInfoArguments &, - llvm::json::Path); - -struct ExceptionInfoResponseBody { - /// ID of the exception that was thrown. - std::string exceptionId; - - /// Descriptive text for the exception. - std::string description; - - /// Mode that caused the exception notification to be raised. - ExceptionBreakMode breakMode = eExceptionBreakModeNever; - - /// Detailed information about the exception. - std::optional details; -}; -llvm::json::Value toJSON(const ExceptionInfoResponseBody &); - } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index 95007013742a0..dc8edaadcd9bb 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -1136,37 +1136,4 @@ bool fromJSON(const json::Value &Param, Variable &V, json::Path Path) { Path, /*required=*/false); } -json::Value toJSON(const ExceptionBreakMode Mode) { - switch (Mode) { - case eExceptionBreakModeNever: - return "never"; - case eExceptionBreakModeAlways: - return "always"; - case eExceptionBreakModeUnhandled: - return "unhandled"; - case eExceptionBreakModeUserUnhandled: - return "userUnhandled"; - } - llvm_unreachable("unhandled exception breakMode."); -} - -json::Value toJSON(const ExceptionDetails &ED) { - json::Object result; - - if (!ED.message.empty()) - result.insert({"message", ED.message}); - if (!ED.typeName.empty()) - result.insert({"typeName", ED.typeName}); - if (!ED.fullTypeName.empty()) - result.insert({"fullTypeName", ED.fullTypeName}); - if (!ED.evaluateName.empty()) - result.insert({"evaluateName", ED.evaluateName}); - if (!ED.stackTrace.empty()) - result.insert({"stackTrace", ED.stackTrace}); - if (!ED.innerException.empty()) - result.insert({"innerException", ED.innerException}); - - return result; -} - } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index 6d85c74377bd3..7077df90a85b5 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -1007,36 +1007,6 @@ struct Variable { llvm::json::Value toJSON(const Variable &); bool fromJSON(const llvm::json::Value &, Variable &, llvm::json::Path); -enum ExceptionBreakMode : unsigned { - eExceptionBreakModeNever, - eExceptionBreakModeAlways, - eExceptionBreakModeUnhandled, - eExceptionBreakModeUserUnhandled, -}; -llvm::json::Value toJSON(ExceptionBreakMode); - -struct ExceptionDetails { - /// Message contained in the exception. - std::string message; - - /// Short type name of the exception object. - std::string typeName; - - /// Fully-qualified type name of the exception object. - std::string fullTypeName; - - /// An expression that can be evaluated in the current scope to obtain the - /// exception object. - std::string evaluateName; - - /// Stack trace at the time the exception was thrown. - std::string stackTrace; - - /// Details of the exception contained by this exception, if any. - std::vector innerException; -}; -llvm::json::Value toJSON(const ExceptionDetails &); - } // namespace lldb_dap::protocol #endif diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt index 434f5280a97a0..a08414c30e6cd 100644 --- a/lldb/unittests/DAP/CMakeLists.txt +++ b/lldb/unittests/DAP/CMakeLists.txt @@ -7,7 +7,6 @@ add_lldb_unittest(DAPTests Handler/ContinueTest.cpp JSONUtilsTest.cpp LLDBUtilsTest.cpp - ProtocolRequestsTest.cpp ProtocolTypesTest.cpp ProtocolUtilsTest.cpp TestBase.cpp diff --git a/lldb/unittests/DAP/ProtocolRequestsTest.cpp b/lldb/unittests/DAP/ProtocolRequestsTest.cpp deleted file mode 100644 index 498195dc09325..0000000000000 --- a/lldb/unittests/DAP/ProtocolRequestsTest.cpp +++ /dev/null @@ -1,69 +0,0 @@ -//===-- ProtocolRequestsTest.cpp ------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Protocol/ProtocolRequests.h" -#include "Protocol/ProtocolTypes.h" -#include "TestingSupport/TestUtilities.h" -#include "llvm/Testing/Support/Error.h" -#include - -using namespace llvm; -using namespace lldb_dap::protocol; -using lldb_private::PrettyPrint; -using llvm::json::parse; - -TEST(ProtocolRequestsTest, ExceptionInfoArguments) { - llvm::Expected expected = - parse(R"({ - "threadId": 3434 - })"); - ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); - EXPECT_EQ(expected->threadId, 3434U); - - // Check required keys; - EXPECT_THAT_EXPECTED(parse(R"({})"), - FailedWithMessage("missing value at (root).threadId")); - - EXPECT_THAT_EXPECTED(parse(R"({"id": 10})"), - FailedWithMessage("missing value at (root).threadId")); -} - -TEST(ProtocolRequestsTest, ExceptionInfoResponseBody) { - ExceptionInfoResponseBody body; - body.exceptionId = "signal"; - body.breakMode = eExceptionBreakModeAlways; - - // Check required keys. - Expected expected = parse( - R"({ - "exceptionId": "signal", - "breakMode": "always" - })"); - - ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); - EXPECT_EQ(PrettyPrint(*expected), PrettyPrint(body)); - - // Check optional keys. - body.description = "SIGNAL SIGWINCH"; - body.breakMode = eExceptionBreakModeNever; - body.details = ExceptionDetails{}; - body.details->message = "some message"; - - Expected expected_opt = parse( - R"({ - "exceptionId": "signal", - "description": "SIGNAL SIGWINCH", - "breakMode": "never", - "details": { - "message": "some message" - } - })"); - - ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); - EXPECT_EQ(PrettyPrint(*expected_opt), PrettyPrint(body)); -} diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp index 6a4620a3f1e59..8170abdd25bc6 100644 --- a/lldb/unittests/DAP/ProtocolTypesTest.cpp +++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp @@ -1129,50 +1129,3 @@ TEST(ProtocolTypesTest, DataBreakpointInfoArguments) { EXPECT_THAT_EXPECTED(parse(R"({"name":"data"})"), llvm::Succeeded()); } - -TEST(ProtocolTypesTest, ExceptionBreakMode) { - const std::vector> test_cases = - {{ExceptionBreakMode::eExceptionBreakModeAlways, "always"}, - {ExceptionBreakMode::eExceptionBreakModeNever, "never"}, - {ExceptionBreakMode::eExceptionBreakModeUnhandled, "unhandled"}, - {ExceptionBreakMode::eExceptionBreakModeUserUnhandled, "userUnhandled"}}; - - for (const auto [value, expected] : test_cases) { - json::Value const serialized = toJSON(value); - ASSERT_EQ(serialized.kind(), llvm::json::Value::Kind::String); - EXPECT_EQ(serialized.getAsString(), expected); - } -} - -TEST(ProtocolTypesTest, ExceptionDetails) { - ExceptionDetails details; - - // Check required keys. - Expected expected = parse(R"({})"); - ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); - EXPECT_EQ(pp(*expected), pp(details)); - - // Check optional keys. - details.message = "SIGABRT exception"; - details.typeName = "signal"; - details.fullTypeName = "SIGABRT"; - details.evaluateName = "process handle SIGABRT"; - details.stackTrace = "some stacktrace"; - ExceptionDetails inner_details; - inner_details.message = "inner message"; - details.innerException = {std::move(inner_details)}; - - Expected expected_opt = parse(R"({ - "message": "SIGABRT exception", - "typeName": "signal", - "fullTypeName": "SIGABRT", - "evaluateName": "process handle SIGABRT", - "stackTrace": "some stacktrace", - "innerException": [{ - "message": "inner message" - }] - })"); - - ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); - EXPECT_EQ(pp(*expected_opt), pp(details)); -} diff --git a/lldb/unittests/TestingSupport/TestUtilities.cpp b/lldb/unittests/TestingSupport/TestUtilities.cpp index d164c227afb9e..b53822e38324b 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.cpp +++ b/lldb/unittests/TestingSupport/TestUtilities.cpp @@ -20,11 +20,6 @@ using namespace lldb_private; extern const char *TestMainArgv0; std::once_flag TestUtilities::g_debugger_initialize_flag; - -std::string lldb_private::PrettyPrint(const llvm::json::Value &value) { - return llvm::formatv("{0:2}", value).str(); -} - std::string lldb_private::GetInputFilePath(const llvm::Twine &name) { llvm::SmallString<128> result = llvm::sys::path::parent_path(TestMainArgv0); llvm::sys::fs::make_absolute(result); diff --git a/lldb/unittests/TestingSupport/TestUtilities.h b/lldb/unittests/TestingSupport/TestUtilities.h index f05d176618fa0..cc93a68a6a431 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.h +++ b/lldb/unittests/TestingSupport/TestUtilities.h @@ -30,10 +30,6 @@ } namespace lldb_private { - -/// Returns a pretty printed json string of a `llvm::json::Value`. -std::string PrettyPrint(const llvm::json::Value &E); - std::string GetInputFilePath(const llvm::Twine &name); class TestUtilities {