[Lldb-commits] [lldb] [lldb-dap] Adding more details to 'exceptionInfo'. (PR #176465)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Fri Jan 23 10:39:13 PST 2026
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 <harjohn at google.com>
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 <harjohn at google.com>
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 <harjohn at google.com>
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 <harjohn at google.com>
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 <ash at greaterthaninfinity.com>
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 <yerimyah1 at gmail.com>
---
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 <harjohn at google.com>
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 <ash at greaterthaninfinity.com>
Date: Wed, 21 Jan 2026 17:26:48 -0800
Subject: [PATCH 7/9] Apply suggestions from code review
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
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 <harjohn at google.com>
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 <harjohn at google.com>
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.
More information about the lldb-commits
mailing list