https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/176465
>From 0b50dabc60648c3211787d534dd14cea5279f9ac Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Fri, 16 Jan 2026 11:31:17 -0800 Subject: [PATCH 1/9] [lldb-dap] Adding more details to 'exceptionInfo'. --- .../test/tools/lldb-dap/lldbdap_testcase.py | 48 ++++++---- .../TestDAP_setExceptionBreakpoints.py | 8 +- .../lldb-dap/exception/TestDAP_exception.py | 2 +- .../exception/cpp/TestDAP_exception_cpp.py | 5 +- .../exception/objc/TestDAP_exception_objc.py | 10 +-- .../exception/runtime-instruments/Makefile | 4 + .../TestDAP_runtime_instruments.py | 25 ++++++ .../exception/runtime-instruments/categories | 1 + .../exception/runtime-instruments/main.c | 5 ++ .../Handler/ExceptionInfoRequestHandler.cpp | 90 +++++++++++++------ 10 files changed, 144 insertions(+), 54 deletions(-) create mode 100644 lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile create mode 100644 lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py create mode 100644 lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories create mode 100644 lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index e4a3e1c786ffe..b2cac4dfb0e5b 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -208,25 +208,40 @@ def verify_all_breakpoints_hit(self, breakpoint_ids): return self.assertTrue(False, f"breakpoints not hit, stopped_events={stopped_events}") - def verify_stop_exception_info(self, expected_description): + def verify_stop_exception_info( + self, expected_description: str, expected_text: Optional[str] = None + ) -> None: """Wait for the process we are debugging to stop, and verify the stop reason is 'exception' and that the description matches 'expected_description' """ stopped_events = self.dap_server.wait_for_stopped() + self.assertIsNotNone(stopped_events, "No stopped events detected") for stopped_event in stopped_events: - if "body" in stopped_event: - body = stopped_event["body"] - if "reason" not in body: - continue - if body["reason"] != "exception": - continue - if "description" not in body: - continue - description = body["description"] - if expected_description == description: - return True - return False + if ( + "body" not in stopped_event + or stopped_event["body"]["reason"] != "exception" + ): + continue + self.assertIn( + "description", + stopped_event["body"], + f"stopped event missing description {stopped_event}", + ) + description = stopped_event["body"]["description"] + self.assertRegex( + description, + expected_description, + f"for 'stopped' event {stopped_event!r}", + ) + if expected_text: + self.assertRegex( + stopped_event["body"]["text"], + expected_text, + f"for stopped event {stopped_event!r}", + ) + return + self.fail(f"No valid stop exception info detected in {stopped_events}") def verify_stop_on_entry(self) -> None: """Waits for the process to be stopped and then verifies at least one @@ -437,12 +452,9 @@ def continue_to_breakpoints(self, breakpoint_ids): self.do_continue() self.verify_breakpoint_hit(breakpoint_ids) - def continue_to_exception_breakpoint(self, filter_label): + def continue_to_exception_breakpoint(self, description, text=None): self.do_continue() - self.assertTrue( - self.verify_stop_exception_info(filter_label), - 'verify we got "%s"' % (filter_label), - ) + self.verify_stop_exception_info(description, text) def continue_to_exit(self, exitCode=0): self.do_continue() diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py index 4ca733a9a59ca..684726c927bc1 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py @@ -35,5 +35,9 @@ def test_functionality(self): if response: self.assertTrue(response["success"]) - self.continue_to_exception_breakpoint("C++ Throw") - self.continue_to_exception_breakpoint("C++ Catch") + self.continue_to_exception_breakpoint( + r"breakpoint \d+\.\d+", text=r"C\+\+ Throw" + ) + self.continue_to_exception_breakpoint( + r"breakpoint \d+\.\d+", text=r"C\+\+ Catch" + ) diff --git a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py index f044bcae41892..b92c3290ceb4c 100644 --- a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py +++ b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py @@ -18,7 +18,7 @@ def test_stopped_description(self): self.build_and_launch(program) self.do_continue() - self.assertTrue(self.verify_stop_exception_info("signal SIGABRT")) + self.verify_stop_exception_info("signal SIGABRT") exceptionInfo = self.get_exceptionInfo() self.assertEqual(exceptionInfo["breakMode"], "always") self.assertEqual(exceptionInfo["description"], "signal SIGABRT") diff --git a/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py b/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py index 6471e2b87251a..4729cbef00c11 100644 --- a/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py +++ b/lldb/test/API/tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py @@ -2,7 +2,6 @@ Test exception behavior in DAP with c++ throw. """ - from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * import lldbdap_testcase @@ -18,9 +17,9 @@ def test_stopped_description(self): program = self.getBuildArtifact("a.out") self.build_and_launch(program) self.dap_server.request_continue() - self.assertTrue(self.verify_stop_exception_info("signal SIGABRT")) + self.verify_stop_exception_info("signal SIGABRT") exceptionInfo = self.get_exceptionInfo() self.assertEqual(exceptionInfo["breakMode"], "always") - self.assertEqual(exceptionInfo["description"], "signal SIGABRT") + self.assertIn("signal SIGABRT", exceptionInfo["description"]) self.assertEqual(exceptionInfo["exceptionId"], "signal") self.assertIsNotNone(exceptionInfo["details"]) diff --git a/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py index ddedf7a6de8c6..40233af4b2bd6 100644 --- a/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py +++ b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py @@ -16,10 +16,10 @@ def test_stopped_description(self): program = self.getBuildArtifact("a.out") self.build_and_launch(program) self.dap_server.request_continue() - self.assertTrue(self.verify_stop_exception_info("signal SIGABRT")) + self.verify_stop_exception_info("signal SIGABRT") exception_info = self.get_exceptionInfo() self.assertEqual(exception_info["breakMode"], "always") - self.assertEqual(exception_info["description"], "signal SIGABRT") + self.assertIn("signal SIGABRT", exception_info["description"]) self.assertEqual(exception_info["exceptionId"], "signal") exception_details = exception_info["details"] self.assertRegex(exception_details["message"], "SomeReason") @@ -44,7 +44,7 @@ def test_break_on_throw_and_catch(self): if response: self.assertTrue(response["success"]) - self.continue_to_exception_breakpoint("Objective-C Throw") + self.continue_to_exception_breakpoint("hit Objective-C exception") # FIXME: Catching objc exceptions do not appear to be working. # Xcode appears to set a breakpoint on '__cxa_begin_catch' for objc @@ -54,10 +54,10 @@ def test_break_on_throw_and_catch(self): self.do_continue() - self.assertTrue(self.verify_stop_exception_info("signal SIGABRT")) + self.verify_stop_exception_info("signal SIGABRT") exception_info = self.get_exceptionInfo() self.assertEqual(exception_info["breakMode"], "always") - self.assertEqual(exception_info["description"], "signal SIGABRT") + self.assertIn("signal SIGABRT", exception_info["description"]) self.assertEqual(exception_info["exceptionId"], "signal") exception_details = exception_info["details"] self.assertRegex(exception_details["message"], "SomeReason") diff --git a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile new file mode 100644 index 0000000000000..b27db90a40de2 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c +CFLAGS_EXTRAS := -fsanitize=undefined -g + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py new file mode 100644 index 0000000000000..caff7dd1bedcd --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py @@ -0,0 +1,25 @@ +""" +Test that we stop at runtime instrumentation locations. +""" + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbdap_testcase + + +class TestDAP_runtime_instruments(lldbdap_testcase.DAPTestCaseBase): + @skipUnlessUndefinedBehaviorSanitizer + def test_ubsan(self): + """ + Test that we stop at ubsan. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + self.do_continue() + + self.verify_stop_exception_info("Out of bounds index") + exceptionInfo = self.get_exceptionInfo() + self.assertEqual(exceptionInfo["breakMode"], "always") + self.assertEqual("Out of bounds index", exceptionInfo["description"]) + self.assertEqual(exceptionInfo["exceptionId"], "runtime-instrumentation") + self.assertIn("main.c", exceptionInfo["details"]["stackTrace"]) diff --git a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories new file mode 100644 index 0000000000000..c756cb1241945 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/categories @@ -0,0 +1 @@ +instrumentation-runtime diff --git a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c new file mode 100644 index 0000000000000..9434b8e2d7583 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/main.c @@ -0,0 +1,5 @@ +int main(int argc, char const *argv[]) { + int data[4] = {0}; + int *p = data + 5; // ubsan + return *p; +} diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index ddf55e6fb382d..c76fc83fa8cbe 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -8,15 +8,30 @@ #include "DAP.h" #include "DAPError.h" +#include "DAPLog.h" #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" #include "lldb/API/SBStream.h" +#include "lldb/API/SBStructuredData.h" +#include "lldb/API/SBThreadCollection.h" +#include "lldb/lldb-enumerations.h" +#include <utility> using namespace lldb_dap::protocol; namespace lldb_dap { +static std::string ThreadSummary(lldb::SBThread &thread) { + lldb::SBStream stream; + thread.GetDescription(stream); + for (uint32_t idx = 0; idx < thread.GetNumFrames(); idx++) { + lldb::SBFrame frame = thread.GetFrameAtIndex(idx); + frame.GetDescription(stream); + } + return {stream.GetData(), stream.GetSize()}; +} + /// Retrieves the details of the exception that caused this event to be raised. /// /// Clients should only call this request if the corresponding capability @@ -29,53 +44,78 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { return llvm::make_error<DAPError>( llvm::formatv("Invalid thread id: {}", args.threadId).str()); - ExceptionInfoResponseBody response; - response.breakMode = eExceptionBreakModeAlways; + ExceptionInfoResponseBody body; + body.breakMode = eExceptionBreakModeAlways; const lldb::StopReason stop_reason = thread.GetStopReason(); switch (stop_reason) { + case lldb::eStopReasonInstrumentation: + body.exceptionId = "runtime-instrumentation"; + break; case lldb::eStopReasonSignal: - response.exceptionId = "signal"; + body.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(); + body.exceptionId = exc_bp->GetFilter(); + body.description = exc_bp->GetLabel().str() + "\n"; } else { - response.exceptionId = "exception"; + body.exceptionId = "exception"; } } break; default: - response.exceptionId = "exception"; + body.exceptionId = "exception"; } lldb::SBStream stream; - if (response.description.empty()) { - if (thread.GetStopDescription(stream)) { - response.description = {stream.GetData(), stream.GetSize()}; - } - } + if (thread.GetStopDescription(stream)) + body.description += {stream.GetData(), stream.GetSize()}; if (lldb::SBValue exception = thread.GetCurrentException()) { + body.details = ExceptionDetails{}; + if (const char *name = exception.GetName()) + body.details->evaluateName = name; + if (const char *typeName = exception.GetDisplayTypeName()) + body.details->typeName = typeName; + stream.Clear(); - response.details = ExceptionDetails{}; - if (exception.GetDescription(stream)) { - response.details->message = {stream.GetData(), stream.GetSize()}; - } + if (exception.GetDescription(stream)) + body.details->message = {stream.GetData(), stream.GetSize()}; if (lldb::SBThread exception_backtrace = - thread.GetCurrentExceptionBacktrace()) { - stream.Clear(); - exception_backtrace.GetDescription(stream); + thread.GetCurrentExceptionBacktrace()) + body.details->stackTrace = ThreadSummary(exception_backtrace); + } - for (uint32_t idx = 0; idx < exception_backtrace.GetNumFrames(); idx++) { - lldb::SBFrame frame = exception_backtrace.GetFrameAtIndex(idx); - frame.GetDescription(stream); - } - response.details->stackTrace = {stream.GetData(), stream.GetSize()}; + lldb::SBStructuredData crash_info = + dap.target.GetProcess().GetExtendedCrashInformation(); + stream.Clear(); + if (crash_info.IsValid() && crash_info.GetDescription(stream)) + body.description += "\n\nExtended Crash Information:\n" + + std::string(stream.GetData(), stream.GetSize()); + + for (uint32_t idx = 0; idx < lldb::eNumInstrumentationRuntimeTypes; idx++) { + lldb::InstrumentationRuntimeType type = + static_cast<lldb::InstrumentationRuntimeType>(idx); + if (!dap.target.GetProcess().IsInstrumentationRuntimePresent(type)) + continue; + lldb::SBThreadCollection threads = + thread.GetStopReasonExtendedBacktraces(type); + for (uint32_t tidx = 0; tidx < threads.GetSize(); tidx++) { + auto thread = threads.GetThreadAtIndex(tidx); + if (!thread) + continue; + ExceptionDetails details; + details.stackTrace = ThreadSummary(thread); + if (!body.details) + body.details = std::move(details); + else + body.details->innerException.emplace_back(std::move(details)); } } - return response; + + return body; } + } // namespace lldb_dap >From 4b209449608a5e394af5ee603c81f3539f756e8d Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Fri, 16 Jan 2026 11:53:39 -0800 Subject: [PATCH 2/9] Fixing tests after splitting up the PR. --- .../breakpoint/TestDAP_setExceptionBreakpoints.py | 8 ++------ .../lldb-dap/exception/objc/TestDAP_exception_objc.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py index 684726c927bc1..5ed7e13fd0b44 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setExceptionBreakpoints.py @@ -35,9 +35,5 @@ def test_functionality(self): if response: self.assertTrue(response["success"]) - self.continue_to_exception_breakpoint( - r"breakpoint \d+\.\d+", text=r"C\+\+ Throw" - ) - self.continue_to_exception_breakpoint( - r"breakpoint \d+\.\d+", text=r"C\+\+ Catch" - ) + self.continue_to_exception_breakpoint(r"C\+\+ Throw") + self.continue_to_exception_breakpoint(r"C\+\+ Catch") diff --git a/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py index 40233af4b2bd6..694cadb6ed2fe 100644 --- a/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py +++ b/lldb/test/API/tools/lldb-dap/exception/objc/TestDAP_exception_objc.py @@ -44,7 +44,7 @@ def test_break_on_throw_and_catch(self): if response: self.assertTrue(response["success"]) - self.continue_to_exception_breakpoint("hit Objective-C exception") + self.continue_to_exception_breakpoint("Objective-C Throw") # FIXME: Catching objc exceptions do not appear to be working. # Xcode appears to set a breakpoint on '__cxa_begin_catch' for objc >From 967c61afb52495e34e484eab4ae45bf0f82d1c40 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Tue, 20 Jan 2026 14:05:04 -0800 Subject: [PATCH 3/9] Using a variable to hold the SBProcess. --- .../lldb-dap/Handler/ExceptionInfoRequestHandler.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index c76fc83fa8cbe..f4168ade75448 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -88,8 +88,11 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { body.details->stackTrace = ThreadSummary(exception_backtrace); } - lldb::SBStructuredData crash_info = - dap.target.GetProcess().GetExtendedCrashInformation(); + lldb::SBProcess process = dap.target.GetProcess(); + if (!process) + return body; + + lldb::SBStructuredData crash_info = process.GetExtendedCrashInformation(); stream.Clear(); if (crash_info.IsValid() && crash_info.GetDescription(stream)) body.description += "\n\nExtended Crash Information:\n" + @@ -98,8 +101,9 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { for (uint32_t idx = 0; idx < lldb::eNumInstrumentationRuntimeTypes; idx++) { lldb::InstrumentationRuntimeType type = static_cast<lldb::InstrumentationRuntimeType>(idx); - if (!dap.target.GetProcess().IsInstrumentationRuntimePresent(type)) + if (!process.IsInstrumentationRuntimePresent(type)) continue; + lldb::SBThreadCollection threads = thread.GetStopReasonExtendedBacktraces(type); for (uint32_t tidx = 0; tidx < threads.GetSize(); tidx++) { >From 48886ce8ebc79af47e446e388d925f6cc34359c6 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Wed, 21 Jan 2026 15:53:57 -0800 Subject: [PATCH 4/9] Improving formatting by parsing extended stack trace json. --- .../TestDAP_runtime_instruments.py | 2 +- .../Handler/ExceptionInfoRequestHandler.cpp | 92 +++++++++++++++++-- lldb/tools/lldb-dap/JSONUtils.cpp | 2 +- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py index caff7dd1bedcd..3fa7aca91a926 100644 --- a/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py +++ b/lldb/test/API/tools/lldb-dap/exception/runtime-instruments/TestDAP_runtime_instruments.py @@ -20,6 +20,6 @@ def test_ubsan(self): self.verify_stop_exception_info("Out of bounds index") exceptionInfo = self.get_exceptionInfo() self.assertEqual(exceptionInfo["breakMode"], "always") - self.assertEqual("Out of bounds index", exceptionInfo["description"]) + self.assertRegex(exceptionInfo["description"], r"Out of bounds index") self.assertEqual(exceptionInfo["exceptionId"], "runtime-instrumentation") self.assertIn("main.c", exceptionInfo["details"]["stackTrace"]) diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index f4168ade75448..5ebe62d7d389c 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -8,14 +8,18 @@ #include "DAP.h" #include "DAPError.h" -#include "DAPLog.h" #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBThreadCollection.h" +#include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" +#include <string> #include <utility> using namespace lldb_dap::protocol; @@ -32,19 +36,56 @@ static std::string ThreadSummary(lldb::SBThread &thread) { return {stream.GetData(), stream.GetSize()}; } +struct RuntimeInstrumentReport { + std::string description; + std::string instrument; + std::string summary; + + std::string filename; + uint32_t column = LLDB_INVALID_COLUMN_NUMBER; + uint32_t line = LLDB_INVALID_LINE_NUMBER; + + // keys found on UBSan + lldb::addr_t memory = LLDB_INVALID_ADDRESS; + lldb::tid_t tid = LLDB_INVALID_THREAD_ID; + std::vector<lldb::user_id_t> trace; + + // keys found on MainThreadChecker + std::string api_name; + std::string class_name; + std::string selector; + + // FIXME: TSan, ASan, BoundsSafety +}; + +static bool fromJSON(const llvm::json::Value &Params, + RuntimeInstrumentReport &RIR, llvm::json::Path Path) { + llvm::json::ObjectMapper O(Params, Path); + return O && O.mapOptional("description", RIR.description) && + O.mapOptional("instrumentation_class", RIR.instrument) && + O.mapOptional("summary", RIR.summary) && + O.mapOptional("filename", RIR.filename) && + O.mapOptional("col", RIR.column) && O.mapOptional("line", RIR.line) && + O.mapOptional("memory", RIR.memory) && O.mapOptional("tid", RIR.tid) && + O.mapOptional("trace", RIR.trace) && + O.mapOptional("api_name", RIR.api_name) && + O.mapOptional("class_name", RIR.class_name) && + O.mapOptional("selector", RIR.selector); +} + /// 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<ExceptionInfoResponseBody> ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { - lldb::SBThread thread = dap.GetLLDBThread(args.threadId); if (!thread.IsValid()) return llvm::make_error<DAPError>( llvm::formatv("Invalid thread id: {}", args.threadId).str()); ExceptionInfoResponseBody body; + llvm::raw_string_ostream OS(body.description); body.breakMode = eExceptionBreakModeAlways; const lldb::StopReason stop_reason = thread.GetStopReason(); switch (stop_reason) { @@ -59,7 +100,7 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { dap.GetExceptionBPFromStopReason(thread); if (exc_bp) { body.exceptionId = exc_bp->GetFilter(); - body.description = exc_bp->GetLabel().str() + "\n"; + OS << exc_bp->GetLabel().str(); } else { body.exceptionId = "exception"; } @@ -70,7 +111,46 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { lldb::SBStream stream; if (thread.GetStopDescription(stream)) - body.description += {stream.GetData(), stream.GetSize()}; + OS << std::string{stream.GetData(), stream.GetSize()}; + + stream.Clear(); + if (thread.GetStopReasonExtendedInfoAsJSON(stream)) { + OS << "\n"; + + llvm::Expected<RuntimeInstrumentReport> report = + llvm::json::parse<RuntimeInstrumentReport>( + {stream.GetData(), stream.GetSize()}); + // If we failed to parse the extended stop reason info, attach it + // unmodified. + if (!report) { + llvm::consumeError(report.takeError()); + OS << std::string(stream.GetData(), stream.GetSize()); + } else { + if (!report->filename.empty()) { + OS << report->filename; + if (report->line != LLDB_INVALID_LINE_NUMBER) { + OS << ":" << report->line; + if (report->column != LLDB_INVALID_COLUMN_NUMBER) + OS << ":" << report->column; + } + OS << " "; + } + + OS << report->instrument; + if (!report->description.empty()) + OS << ": " << report->description; + OS << "\n"; + if (!report->summary.empty()) + OS << report->summary << "\n"; + + if (!report->api_name.empty()) + OS << "API Name: " << report->api_name << "\n"; + if (!report->class_name.empty()) + OS << "Class Name: " << report->class_name << "\n"; + if (!report->selector.empty()) + OS << "Selector: " << report->selector << "\n"; + } + } if (lldb::SBValue exception = thread.GetCurrentException()) { body.details = ExceptionDetails{}; @@ -95,8 +175,8 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { lldb::SBStructuredData crash_info = process.GetExtendedCrashInformation(); stream.Clear(); if (crash_info.IsValid() && crash_info.GetDescription(stream)) - body.description += "\n\nExtended Crash Information:\n" + - std::string(stream.GetData(), stream.GetSize()); + OS << "\nExtended Crash Information:\n" + + std::string(stream.GetData(), stream.GetSize()); for (uint32_t idx = 0; idx < lldb::eNumInstrumentationRuntimeTypes; idx++) { lldb::InstrumentationRuntimeType type = diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 5c33c6aa591a6..f41f3e030e6a0 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -532,7 +532,7 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, llvm::formatv("data breakpoint {0}", bp_id).str()); } break; case lldb::eStopReasonInstrumentation: - body.try_emplace("reason", "breakpoint"); + body.try_emplace("reason", "exception"); break; case lldb::eStopReasonProcessorTrace: body.try_emplace("reason", "processor trace"); >From 902f3ae8885c70f26bd579d221299821c14d7352 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Wed, 21 Jan 2026 16:39:11 -0800 Subject: [PATCH 5/9] Update lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp Co-authored-by: Ebuka Ezike <[email protected]> --- lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index 5ebe62d7d389c..cc0b8bd88bef4 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -100,7 +100,7 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { dap.GetExceptionBPFromStopReason(thread); if (exc_bp) { body.exceptionId = exc_bp->GetFilter(); - OS << exc_bp->GetLabel().str(); + OS << exc_bp->GetLabel(); } else { body.exceptionId = "exception"; } >From d24fee2a31d92c82944d625037e07ad2bac55f31 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Wed, 21 Jan 2026 17:00:20 -0800 Subject: [PATCH 6/9] Using some static helpers to isolate different formatting operations. --- .../Handler/ExceptionInfoRequestHandler.cpp | 243 ++++++++++-------- 1 file changed, 141 insertions(+), 102 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index cc0b8bd88bef4..156842adfe66c 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -13,12 +13,14 @@ #include "RequestHandler.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStructuredData.h" +#include "lldb/API/SBThread.h" #include "lldb/API/SBThreadCollection.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-types.h" #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" #include <string> #include <utility> @@ -26,16 +28,6 @@ using namespace lldb_dap::protocol; namespace lldb_dap { -static std::string ThreadSummary(lldb::SBThread &thread) { - lldb::SBStream stream; - thread.GetDescription(stream); - for (uint32_t idx = 0; idx < thread.GetNumFrames(); idx++) { - lldb::SBFrame frame = thread.GetFrameAtIndex(idx); - frame.GetDescription(stream); - } - return {stream.GetData(), stream.GetSize()}; -} - struct RuntimeInstrumentReport { std::string description; std::string instrument; @@ -73,130 +65,177 @@ static bool fromJSON(const llvm::json::Value &Params, O.mapOptional("selector", RIR.selector); } -/// 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<ExceptionInfoResponseBody> -ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { - lldb::SBThread thread = dap.GetLLDBThread(args.threadId); - if (!thread.IsValid()) - return llvm::make_error<DAPError>( - llvm::formatv("Invalid thread id: {}", args.threadId).str()); - - ExceptionInfoResponseBody body; - llvm::raw_string_ostream OS(body.description); - body.breakMode = eExceptionBreakModeAlways; +static std::string FormatExceptionId(DAP &dap, llvm::raw_ostream &OS, + lldb::SBThread &thread) { const lldb::StopReason stop_reason = thread.GetStopReason(); switch (stop_reason) { case lldb::eStopReasonInstrumentation: - body.exceptionId = "runtime-instrumentation"; - break; + return "runtime-instrumentation"; case lldb::eStopReasonSignal: - body.exceptionId = "signal"; - break; + return "signal"; case lldb::eStopReasonBreakpoint: { const ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); if (exc_bp) { - body.exceptionId = exc_bp->GetFilter(); OS << exc_bp->GetLabel(); - } else { - body.exceptionId = "exception"; + return exc_bp->GetFilter().str(); } - } break; + } + LLVM_FALLTHROUGH; default: - body.exceptionId = "exception"; + return "exception"; } +} +static void FormatDescription(llvm::raw_ostream &OS, lldb::SBThread &thread) { lldb::SBStream stream; if (thread.GetStopDescription(stream)) OS << std::string{stream.GetData(), stream.GetSize()}; +} - stream.Clear(); - if (thread.GetStopReasonExtendedInfoAsJSON(stream)) { - OS << "\n"; - - llvm::Expected<RuntimeInstrumentReport> report = - llvm::json::parse<RuntimeInstrumentReport>( - {stream.GetData(), stream.GetSize()}); - // If we failed to parse the extended stop reason info, attach it - // unmodified. - if (!report) { - llvm::consumeError(report.takeError()); - OS << std::string(stream.GetData(), stream.GetSize()); - } else { - if (!report->filename.empty()) { - OS << report->filename; - if (report->line != LLDB_INVALID_LINE_NUMBER) { - OS << ":" << report->line; - if (report->column != LLDB_INVALID_COLUMN_NUMBER) - OS << ":" << report->column; - } - OS << " "; - } - - OS << report->instrument; - if (!report->description.empty()) - OS << ": " << report->description; - OS << "\n"; - if (!report->summary.empty()) - OS << report->summary << "\n"; - - if (!report->api_name.empty()) - OS << "API Name: " << report->api_name << "\n"; - if (!report->class_name.empty()) - OS << "Class Name: " << report->class_name << "\n"; - if (!report->selector.empty()) - OS << "Selector: " << report->selector << "\n"; +static void FormatExtendedStopInfo(llvm::raw_ostream &OS, + lldb::SBThread &thread) { + lldb::SBStream stream; + if (!thread.GetStopReasonExtendedInfoAsJSON(stream)) + return; + + OS << "\n"; + + llvm::Expected<RuntimeInstrumentReport> report = + llvm::json::parse<RuntimeInstrumentReport>( + {stream.GetData(), stream.GetSize()}); + // If we failed to parse the extended stop reason info, attach it unmodified. + if (!report) { + llvm::consumeError(report.takeError()); + OS << std::string(stream.GetData(), stream.GetSize()); + return; + } + + if (!report->filename.empty()) { + OS << report->filename; + if (report->line != LLDB_INVALID_LINE_NUMBER) { + OS << ":" << report->line; + if (report->column != LLDB_INVALID_COLUMN_NUMBER) + OS << ":" << report->column; } + OS << " "; + } + + OS << report->instrument; + if (!report->description.empty()) + OS << ": " << report->description; + OS << "\n"; + if (!report->summary.empty()) + OS << report->summary << "\n"; + + // MainThreadChecker instrument details + if (!report->api_name.empty()) + OS << "API Name: " << report->api_name << "\n"; + if (!report->class_name.empty()) + OS << "Class Name: " << report->class_name << "\n"; + if (!report->selector.empty()) + OS << "Selector: " << report->selector << "\n"; +} + +static void FormatCrashReport(llvm::raw_ostream &OS, lldb::SBThread &thread) { + lldb::SBProcess process = thread.GetProcess(); + if (!process) + return; + + lldb::SBStructuredData crash_info = process.GetExtendedCrashInformation(); + if (!crash_info) + return; + + lldb::SBStream stream; + if (!crash_info.GetDescription(stream)) + return; + + OS << "\nExtended Crash Information:\n" + << std::string(stream.GetData(), stream.GetSize()); +} + +static std::string ThreadSummary(lldb::SBThread &thread) { + lldb::SBStream stream; + thread.GetDescription(stream); + for (uint32_t idx = 0; idx < thread.GetNumFrames(); idx++) { + lldb::SBFrame frame = thread.GetFrameAtIndex(idx); + frame.GetDescription(stream); } + return {stream.GetData(), stream.GetSize()}; +} + +static std::optional<ExceptionDetails> FormatException(lldb::SBThread &thread) { + lldb::SBValue exception = thread.GetCurrentException(); + if (!exception) + return {}; + + ExceptionDetails details; + if (const char *name = exception.GetName()) + details.evaluateName = name; + if (const char *typeName = exception.GetDisplayTypeName()) + details.typeName = typeName; + + lldb::SBStream stream; + if (exception.GetDescription(stream)) + details.message = {stream.GetData(), stream.GetSize()}; + + if (lldb::SBThread exception_backtrace = + thread.GetCurrentExceptionBacktrace()) + details.stackTrace = ThreadSummary(exception_backtrace); + + return details; +} - if (lldb::SBValue exception = thread.GetCurrentException()) { - body.details = ExceptionDetails{}; - if (const char *name = exception.GetName()) - body.details->evaluateName = name; - if (const char *typeName = exception.GetDisplayTypeName()) - body.details->typeName = typeName; +static void +FormatRuntimeInstrumentStackTrace(lldb::SBThread &thread, + lldb::InstrumentationRuntimeType type, + std::optional<ExceptionDetails> &details) { + lldb::SBThreadCollection threads = + thread.GetStopReasonExtendedBacktraces(type); + for (uint32_t tidx = 0; tidx < threads.GetSize(); tidx++) { + auto thread = threads.GetThreadAtIndex(tidx); + if (!thread) + continue; - stream.Clear(); - if (exception.GetDescription(stream)) - body.details->message = {stream.GetData(), stream.GetSize()}; + ExceptionDetails current_details; + current_details.stackTrace = ThreadSummary(thread); - if (lldb::SBThread exception_backtrace = - thread.GetCurrentExceptionBacktrace()) - body.details->stackTrace = ThreadSummary(exception_backtrace); + if (!details) + details = std::move(current_details); + else + details->innerException.emplace_back(std::move(current_details)); } +} +/// 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<ExceptionInfoResponseBody> +ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { + lldb::SBThread thread = dap.GetLLDBThread(args.threadId); + if (!thread.IsValid()) + return llvm::make_error<DAPError>( + llvm::formatv("Invalid thread id: {}", args.threadId).str()); - lldb::SBProcess process = dap.target.GetProcess(); - if (!process) - return body; + ExceptionInfoResponseBody body; + llvm::raw_string_ostream OS(body.description); - lldb::SBStructuredData crash_info = process.GetExtendedCrashInformation(); - stream.Clear(); - if (crash_info.IsValid() && crash_info.GetDescription(stream)) - OS << "\nExtended Crash Information:\n" + - std::string(stream.GetData(), stream.GetSize()); + body.exceptionId = FormatExceptionId(dap, OS, thread); + body.breakMode = eExceptionBreakModeAlways; + body.details = FormatException(thread); + FormatDescription(OS, thread); + FormatExtendedStopInfo(OS, thread); + FormatCrashReport(OS, thread); + + lldb::SBProcess process = thread.GetProcess(); for (uint32_t idx = 0; idx < lldb::eNumInstrumentationRuntimeTypes; idx++) { lldb::InstrumentationRuntimeType type = static_cast<lldb::InstrumentationRuntimeType>(idx); if (!process.IsInstrumentationRuntimePresent(type)) continue; - lldb::SBThreadCollection threads = - thread.GetStopReasonExtendedBacktraces(type); - for (uint32_t tidx = 0; tidx < threads.GetSize(); tidx++) { - auto thread = threads.GetThreadAtIndex(tidx); - if (!thread) - continue; - ExceptionDetails details; - details.stackTrace = ThreadSummary(thread); - if (!body.details) - body.details = std::move(details); - else - body.details->innerException.emplace_back(std::move(details)); - } + FormatRuntimeInstrumentStackTrace(thread, type, body.details); } return body; >From 9b9de19a0f67106cd871af402c526c7dcec596a1 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Wed, 21 Jan 2026 17:26:48 -0800 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Jonas Devlieghere <[email protected]> --- lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index 156842adfe66c..da80ed56842f3 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -37,17 +37,17 @@ struct RuntimeInstrumentReport { uint32_t column = LLDB_INVALID_COLUMN_NUMBER; uint32_t line = LLDB_INVALID_LINE_NUMBER; - // keys found on UBSan + // Keys used by UBSan. lldb::addr_t memory = LLDB_INVALID_ADDRESS; lldb::tid_t tid = LLDB_INVALID_THREAD_ID; std::vector<lldb::user_id_t> trace; - // keys found on MainThreadChecker + // Keys used by MainThreadChecker. std::string api_name; std::string class_name; std::string selector; - // FIXME: TSan, ASan, BoundsSafety + // FIXME: Support TSan, ASan, BoundsSafety. }; static bool fromJSON(const llvm::json::Value &Params, >From 130bc5c1fd3a601159233936faf5e3d909727233 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Thu, 22 Jan 2026 12:41:53 -0800 Subject: [PATCH 8/9] Refactoring to have more specific formatters and creating a set of SBAPI helpers. --- .../Handler/ExceptionInfoRequestHandler.cpp | 269 +++++++++++------- lldb/tools/lldb-dap/SBAPIExtras.h | 78 +++++ 2 files changed, 240 insertions(+), 107 deletions(-) create mode 100644 lldb/tools/lldb-dap/SBAPIExtras.h diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index da80ed56842f3..2a49248e42f45 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -11,62 +11,139 @@ #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" +#include "SBAPIExtras.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBThread.h" #include "lldb/API/SBThreadCollection.h" +#include "lldb/API/SBValue.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-types.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/BranchProbability.h" #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include <string> -#include <utility> +using namespace llvm; +using namespace lldb_dap; using namespace lldb_dap::protocol; -namespace lldb_dap { +namespace { -struct RuntimeInstrumentReport { +// See `InstrumentationRuntimeUBSan::RetrieveReportData`. +struct UBSanReport { std::string description; - std::string instrument; std::string summary; - std::string filename; uint32_t column = LLDB_INVALID_COLUMN_NUMBER; uint32_t line = LLDB_INVALID_LINE_NUMBER; - - // Keys used by UBSan. lldb::addr_t memory = LLDB_INVALID_ADDRESS; lldb::tid_t tid = LLDB_INVALID_THREAD_ID; std::vector<lldb::user_id_t> trace; +}; - // Keys used by MainThreadChecker. +// See `InstrumentationRuntimeMainThreadChecker::RetrieveReportData`. +struct MainThreadCheckerReport { + std::string description; std::string api_name; std::string class_name; std::string selector; - - // FIXME: Support TSan, ASan, BoundsSafety. + lldb::tid_t tid = LLDB_INVALID_THREAD_ID; + std::vector<lldb::addr_t> trace; }; -static bool fromJSON(const llvm::json::Value &Params, - RuntimeInstrumentReport &RIR, llvm::json::Path Path) { - llvm::json::ObjectMapper O(Params, Path); - return O && O.mapOptional("description", RIR.description) && - O.mapOptional("instrumentation_class", RIR.instrument) && - O.mapOptional("summary", RIR.summary) && - O.mapOptional("filename", RIR.filename) && - O.mapOptional("col", RIR.column) && O.mapOptional("line", RIR.line) && - O.mapOptional("memory", RIR.memory) && O.mapOptional("tid", RIR.tid) && - O.mapOptional("trace", RIR.trace) && - O.mapOptional("api_name", RIR.api_name) && - O.mapOptional("class_name", RIR.class_name) && - O.mapOptional("selector", RIR.selector); +// FIXME: Support TSan, ASan, BoundsSafety formatting. + +using RuntimeInstrumentReport = + std::variant<UBSanReport, MainThreadCheckerReport>; + +static bool fromJSON(const json::Value ¶ms, UBSanReport &report, + json::Path path) { + json::ObjectMapper O(params, path); + return O.mapOptional("description", report.description) && + O.mapOptional("summary", report.summary) && + O.mapOptional("filename", report.filename) && + O.mapOptional("col", report.column) && + O.mapOptional("line", report.line) && + O.mapOptional("memory_address", report.memory); +} + +static bool fromJSON(const json::Value ¶ms, MainThreadCheckerReport &report, + json::Path path) { + json::ObjectMapper O(params, path); + return O.mapOptional("description", report.description) && + O.mapOptional("api_name", report.api_name) && + O.mapOptional("class_name", report.class_name) && + O.mapOptional("selector", report.selector); +} + +static bool fromJSON(const json::Value ¶ms, RuntimeInstrumentReport &report, + json::Path path) { + json::ObjectMapper O(params, path); + std::string instrumentation_class; + if (!O || !O.map("instrumentation_class", instrumentation_class)) + return false; + + if (instrumentation_class == "UndefinedBehaviorSanitizer") { + UBSanReport inner_report; + if (fromJSON(params, inner_report, path)) + report = inner_report; + return true; + } + if (instrumentation_class == "MainThreadChecker") { + MainThreadCheckerReport inner_report; + if (fromJSON(params, inner_report, path)) + report = inner_report; + return true; + } + + // FIXME: Support additional runtime instruments with specific formatters. + return false; +} + +static raw_ostream &operator<<(raw_ostream &OS, UBSanReport &report) { + if (!report.filename.empty()) { + OS << report.filename; + if (report.line != LLDB_INVALID_LINE_NUMBER) { + OS << ":" << report.line; + if (report.column != LLDB_INVALID_COLUMN_NUMBER) + OS << ":" << report.column; + } + OS << " "; + } + + if (!report.description.empty()) + OS << report.description << "\n"; + + if (!report.summary.empty()) + OS << report.summary; + + return OS; +} + +static raw_ostream &operator<<(raw_ostream &OS, + MainThreadCheckerReport &report) { + if (!report.description.empty()) + OS << report.description << "\n"; + + if (!report.class_name.empty()) + OS << "Class Name: " << report.class_name << "\n"; + if (!report.selector.empty()) + OS << "Selector: " << report.selector << "\n"; + + return OS; } -static std::string FormatExceptionId(DAP &dap, llvm::raw_ostream &OS, - lldb::SBThread &thread) { +static raw_ostream &operator<<(raw_ostream &OS, + RuntimeInstrumentReport &report) { + std::visit([&](auto &r) { OS << r; }, report); + return OS; +} + +static std::string FormatExceptionId(DAP &dap, lldb::SBThread &thread) { const lldb::StopReason stop_reason = thread.GetStopReason(); switch (stop_reason) { case lldb::eStopReasonInstrumentation: @@ -76,10 +153,8 @@ static std::string FormatExceptionId(DAP &dap, llvm::raw_ostream &OS, case lldb::eStopReasonBreakpoint: { const ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); - if (exc_bp) { - OS << exc_bp->GetLabel(); + if (exc_bp) return exc_bp->GetFilter().str(); - } } LLVM_FALLTHROUGH; default: @@ -87,81 +162,59 @@ static std::string FormatExceptionId(DAP &dap, llvm::raw_ostream &OS, } } -static void FormatDescription(llvm::raw_ostream &OS, lldb::SBThread &thread) { +static std::string FormatStopDescription(lldb::SBThread &thread) { lldb::SBStream stream; - if (thread.GetStopDescription(stream)) - OS << std::string{stream.GetData(), stream.GetSize()}; + if (!thread.GetStopDescription(stream)) + return ""; + std::string desc; + raw_string_ostream OS(desc); + OS << stream; + return desc; } -static void FormatExtendedStopInfo(llvm::raw_ostream &OS, - lldb::SBThread &thread) { +static std::string FormatExtendedStopInfo(lldb::SBThread &thread) { lldb::SBStream stream; if (!thread.GetStopReasonExtendedInfoAsJSON(stream)) - return; - - OS << "\n"; + return ""; - llvm::Expected<RuntimeInstrumentReport> report = - llvm::json::parse<RuntimeInstrumentReport>( + std::string stop_info; + raw_string_ostream OS(stop_info); + Expected<RuntimeInstrumentReport> report = + json::parse<RuntimeInstrumentReport>( {stream.GetData(), stream.GetSize()}); - // If we failed to parse the extended stop reason info, attach it unmodified. - if (!report) { - llvm::consumeError(report.takeError()); - OS << std::string(stream.GetData(), stream.GetSize()); - return; - } - if (!report->filename.empty()) { - OS << report->filename; - if (report->line != LLDB_INVALID_LINE_NUMBER) { - OS << ":" << report->line; - if (report->column != LLDB_INVALID_COLUMN_NUMBER) - OS << ":" << report->column; - } - OS << " "; + // Check if we can improve the formatting of the raw JSON report. + if (report) { + OS << *report; + } else { + consumeError(report.takeError()); + OS << stream; } - OS << report->instrument; - if (!report->description.empty()) - OS << ": " << report->description; - OS << "\n"; - if (!report->summary.empty()) - OS << report->summary << "\n"; - - // MainThreadChecker instrument details - if (!report->api_name.empty()) - OS << "API Name: " << report->api_name << "\n"; - if (!report->class_name.empty()) - OS << "Class Name: " << report->class_name << "\n"; - if (!report->selector.empty()) - OS << "Selector: " << report->selector << "\n"; + return stop_info; } -static void FormatCrashReport(llvm::raw_ostream &OS, lldb::SBThread &thread) { - lldb::SBProcess process = thread.GetProcess(); - if (!process) - return; - - lldb::SBStructuredData crash_info = process.GetExtendedCrashInformation(); +static std::string FormatCrashReport(lldb::SBThread &thread) { + lldb::SBStructuredData crash_info = + thread.GetProcess().GetExtendedCrashInformation(); if (!crash_info) - return; + return ""; - lldb::SBStream stream; - if (!crash_info.GetDescription(stream)) - return; + std::string report; + raw_string_ostream OS(report); + OS << "Extended Crash Information:\n" << crash_info; - OS << "\nExtended Crash Information:\n" - << std::string(stream.GetData(), stream.GetSize()); + return report; } -static std::string ThreadSummary(lldb::SBThread &thread) { - lldb::SBStream stream; - thread.GetDescription(stream); - for (uint32_t idx = 0; idx < thread.GetNumFrames(); idx++) { - lldb::SBFrame frame = thread.GetFrameAtIndex(idx); - frame.GetDescription(stream); - } - return {stream.GetData(), stream.GetSize()}; +static std::string FormatStackTrace(lldb::SBThread &thread) { + std::string stack_trace; + raw_string_ostream OS(stack_trace); + + for (auto frame : thread) + OS << frame; + + return stack_trace; } static std::optional<ExceptionDetails> FormatException(lldb::SBThread &thread) { @@ -170,18 +223,18 @@ static std::optional<ExceptionDetails> FormatException(lldb::SBThread &thread) { return {}; ExceptionDetails details; + raw_string_ostream OS(details.message); + if (const char *name = exception.GetName()) details.evaluateName = name; if (const char *typeName = exception.GetDisplayTypeName()) details.typeName = typeName; - lldb::SBStream stream; - if (exception.GetDescription(stream)) - details.message = {stream.GetData(), stream.GetSize()}; + OS << exception; if (lldb::SBThread exception_backtrace = thread.GetCurrentExceptionBacktrace()) - details.stackTrace = ThreadSummary(exception_backtrace); + details.stackTrace = FormatStackTrace(exception_backtrace); return details; } @@ -192,13 +245,9 @@ FormatRuntimeInstrumentStackTrace(lldb::SBThread &thread, std::optional<ExceptionDetails> &details) { lldb::SBThreadCollection threads = thread.GetStopReasonExtendedBacktraces(type); - for (uint32_t tidx = 0; tidx < threads.GetSize(); tidx++) { - auto thread = threads.GetThreadAtIndex(tidx); - if (!thread) - continue; - + for (auto thread : threads) { ExceptionDetails current_details; - current_details.stackTrace = ThreadSummary(thread); + current_details.stackTrace = FormatStackTrace(thread); if (!details) details = std::move(current_details); @@ -206,27 +255,35 @@ FormatRuntimeInstrumentStackTrace(lldb::SBThread &thread, details->innerException.emplace_back(std::move(current_details)); } } + +} // end namespace + /// 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<ExceptionInfoResponseBody> +Expected<ExceptionInfoResponseBody> ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { lldb::SBThread thread = dap.GetLLDBThread(args.threadId); if (!thread.IsValid()) - return llvm::make_error<DAPError>( - llvm::formatv("Invalid thread id: {}", args.threadId).str()); + return make_error<DAPError>( + formatv("Invalid thread id: {}", args.threadId).str()); ExceptionInfoResponseBody body; - llvm::raw_string_ostream OS(body.description); - - body.exceptionId = FormatExceptionId(dap, OS, thread); body.breakMode = eExceptionBreakModeAlways; + body.exceptionId = FormatExceptionId(dap, thread); body.details = FormatException(thread); - FormatDescription(OS, thread); - FormatExtendedStopInfo(OS, thread); - FormatCrashReport(OS, thread); + llvm::raw_string_ostream OS(body.description); + OS << FormatStopDescription(thread); + + if (std::string stop_info = FormatExtendedStopInfo(thread); + !stop_info.empty()) + OS << "\n\n" << stop_info; + + if (std::string crash_report = FormatCrashReport(thread); + !crash_report.empty()) + OS << "\n\n" << crash_report; lldb::SBProcess process = thread.GetProcess(); for (uint32_t idx = 0; idx < lldb::eNumInstrumentationRuntimeTypes; idx++) { @@ -240,5 +297,3 @@ ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { return body; } - -} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/SBAPIExtras.h b/lldb/tools/lldb-dap/SBAPIExtras.h new file mode 100644 index 0000000000000..0745b2e043c21 --- /dev/null +++ b/lldb/tools/lldb-dap/SBAPIExtras.h @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// Extensions on SB API. +//===----------------------------------------------------------------------===// + +#include "lldb/API/SBStream.h" +#include "lldb/API/SBStructuredData.h" +#include "lldb/API/SBThread.h" +#include "lldb/API/SBThreadCollection.h" +#include "lldb/API/SBValue.h" +#include "llvm/Support/raw_ostream.h" +#include <cstdint> +#include <functional> + +namespace lldb { + +/// An iterator helper for iterating over various SB API containers. +template <typename Container, typename Item, typename Index, + Item (Container::*Get)(Index)> +struct iter { + using difference_type = Index; + using value_type = Item; + + Container container; + Index index; + + Item operator*() { return std::invoke(Get, container, index); } + void operator++() { index++; } + bool operator!=(const iter &other) { return index != other.index; } +}; + +/// SBThreadCollection thread iterator. +using thread_iter = iter<SBThreadCollection, SBThread, size_t, + &SBThreadCollection::GetThreadAtIndex>; +inline thread_iter begin(SBThreadCollection TC) { return {TC, 0}; } +inline thread_iter end(SBThreadCollection TC) { return {TC, TC.GetSize()}; } + +/// SBThread frame iterator. +using frame_iter = + iter<SBThread, SBFrame, uint32_t, &SBThread::GetFrameAtIndex>; +inline frame_iter begin(SBThread T) { return {T, 0}; } +inline frame_iter end(SBThread T) { return {T, T.GetNumFrames()}; } + +// llvm::raw_ostream print helpers. + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, SBStream &stream) { + OS << llvm::StringRef{stream.GetData(), stream.GetSize()}; + return OS; +} + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, SBFrame &frame) { + SBStream stream; + if (frame.GetDescription(stream)) + OS << stream; + return OS; +} + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, SBValue &value) { + SBStream stream; + if (value.GetDescription(stream)) + OS << stream; + return OS; +} + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const SBStructuredData &data) { + SBStream stream; + if (data.GetDescription(stream)) + OS << stream; + return OS; +} + +} // namespace lldb >From 6c19be9e3fd332362ee9cf3dcac2aaaf35dce938 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Fri, 23 Jan 2026 10:38:48 -0800 Subject: [PATCH 9/9] Addressing reviewer feedback. --- .../test/tools/lldb-dap/dap_server.py | 10 ++++---- .../test/tools/lldb-dap/lldbdap_testcase.py | 20 +++++++--------- .../Handler/ExceptionInfoRequestHandler.cpp | 23 ++++++++----------- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index d1b91f06f84aa..9ccc32f952c8d 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -41,13 +41,13 @@ # timeout by a factor of 10 if ASAN is enabled. DEFAULT_TIMEOUT: Final[float] = 50 * (10 if ("ASAN_OPTIONS" in os.environ) else 1) + # See lldbtest.Base.spawnSubprocess, which should help ensure any processes # created by the DAP client are terminated correctly when the test ends. class SpawnHelperCallback(Protocol): def __call__( self, executable: str, args: List[str], extra_env: List[str], **kwargs - ) -> subprocess.Popen: - ... + ) -> subprocess.Popen: ... ## DAP type references @@ -633,7 +633,7 @@ def predicate(p: ProtocolMessage): self._recv_packet(predicate=predicate), ) - def wait_for_stopped(self) -> Optional[List[Event]]: + def wait_for_stopped(self) -> List[Event]: stopped_events = [] stopped_event = self.wait_for_event(filter=["stopped", "exited"]) while stopped_event: @@ -2063,9 +2063,7 @@ def main(): level=( logging.DEBUG if opts.verbose > 1 - else logging.INFO - if opts.verbose > 0 - else logging.WARNING + else logging.INFO if opts.verbose > 0 else logging.WARNING ), ) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index b2cac4dfb0e5b..05df045be2e3e 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -211,24 +211,20 @@ def verify_all_breakpoints_hit(self, breakpoint_ids): def verify_stop_exception_info( self, expected_description: str, expected_text: Optional[str] = None ) -> None: - """Wait for the process we are debugging to stop, and verify the stop - reason is 'exception' and that the description matches - 'expected_description' - """ + """Wait for the debuggee to stop, and verify the stop reason is + 'exception' with the description matching 'expected_description' and + text match 'expected_text', if specified.""" stopped_events = self.dap_server.wait_for_stopped() - self.assertIsNotNone(stopped_events, "No stopped events detected") for stopped_event in stopped_events: - if ( - "body" not in stopped_event - or stopped_event["body"]["reason"] != "exception" - ): + body = stopped_event["body"] + if body["reason"] != "exception": continue self.assertIn( "description", - stopped_event["body"], + body, f"stopped event missing description {stopped_event}", ) - description = stopped_event["body"]["description"] + description = body["description"] self.assertRegex( description, expected_description, @@ -236,7 +232,7 @@ def verify_stop_exception_info( ) if expected_text: self.assertRegex( - stopped_event["body"]["text"], + body["text"], expected_text, f"for stopped event {stopped_event!r}", ) diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index 2a49248e42f45..ab171ed44f590 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -19,7 +19,6 @@ #include "lldb/API/SBValue.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" -#include "lldb/lldb-types.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/BranchProbability.h" #include "llvm/Support/Error.h" @@ -40,9 +39,6 @@ struct UBSanReport { std::string filename; uint32_t column = LLDB_INVALID_COLUMN_NUMBER; uint32_t line = LLDB_INVALID_LINE_NUMBER; - lldb::addr_t memory = LLDB_INVALID_ADDRESS; - lldb::tid_t tid = LLDB_INVALID_THREAD_ID; - std::vector<lldb::user_id_t> trace; }; // See `InstrumentationRuntimeMainThreadChecker::RetrieveReportData`. @@ -51,8 +47,6 @@ struct MainThreadCheckerReport { std::string api_name; std::string class_name; std::string selector; - lldb::tid_t tid = LLDB_INVALID_THREAD_ID; - std::vector<lldb::addr_t> trace; }; // FIXME: Support TSan, ASan, BoundsSafety formatting. @@ -67,8 +61,7 @@ static bool fromJSON(const json::Value ¶ms, UBSanReport &report, O.mapOptional("summary", report.summary) && O.mapOptional("filename", report.filename) && O.mapOptional("col", report.column) && - O.mapOptional("line", report.line) && - O.mapOptional("memory_address", report.memory); + O.mapOptional("line", report.line); } static bool fromJSON(const json::Value ¶ms, MainThreadCheckerReport &report, @@ -89,15 +82,17 @@ static bool fromJSON(const json::Value ¶ms, RuntimeInstrumentReport &report, if (instrumentation_class == "UndefinedBehaviorSanitizer") { UBSanReport inner_report; - if (fromJSON(params, inner_report, path)) - report = inner_report; - return true; + bool success = fromJSON(params, inner_report, path); + if (success) + report = std::move(inner_report); + return success; } if (instrumentation_class == "MainThreadChecker") { MainThreadCheckerReport inner_report; - if (fromJSON(params, inner_report, path)) - report = inner_report; - return true; + bool success = fromJSON(params, inner_report, path); + if (success) + report = std::move(inner_report); + return success; } // FIXME: Support additional runtime instruments with specific formatters. _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
