[Lldb-commits] [lldb] [lldb-dap] Migrate DAP exceptions test. (PR #207029)
Ebuka Ezike via lldb-commits
lldb-commits at lists.llvm.org
Thu Jul 2 06:53:48 PDT 2026
https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/207029
>From a38f3b96f47a7e674f89e5bec2862248a0f6b063 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Wed, 1 Jul 2026 17:35:20 +0100
Subject: [PATCH 1/2] [lldb-dap] Migrate DAP exceptions test.
Migrate all the tests in the `lldb-dap/exception` folder.
---
.../lldb-dap/exception/TestDAP_exception.py | 27 +++---
.../lldb-dap/exception/asan/TestDAP_asan.py | 28 +++---
.../exception/cpp/TestDAP_exception_cpp.py | 33 ++++---
.../exception/objc/TestDAP_exception_objc.py | 89 +++++++++++--------
.../lldb-dap/exception/ubsan/TestDAP_ubsan.py | 31 ++++---
5 files changed, 123 insertions(+), 85 deletions(-)
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 15b8956995432..c1657853e8c94 100644
--- a/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
+++ b/lldb/test/API/tools/lldb-dap/exception/TestDAP_exception.py
@@ -2,24 +2,29 @@
Test exception behavior in DAP with signal.
"""
-from lldbsuite.test.decorators import *
-from lldbsuite.test.lldbtest import *
-import lldbdap_testcase
+from lldbsuite.test.decorators import skipIfNoSignals
+from lldbsuite.test.tools.lldb_dap import lldb_dap_testcase
+from lldbsuite.test.tools.lldb_dap.dap_types import LaunchArgs
@skipIfNoSignals
-class TestDAP_exception(lldbdap_testcase.DAPTestCaseBase):
+class TestDAP_exception(lldb_dap_testcase.DAPTestCaseBase):
def test_stopped_description(self):
"""
Test that exception description is shown correctly in stopped
event.
"""
program = self.getBuildArtifact("a.out")
- self.build_and_launch(program)
- self.do_continue()
+ session = self.build_and_create_session()
+ process_event = session.launch(LaunchArgs(program=program))
- self.verify_stop_exception_info("signal SIGABRT")
- exceptionInfo = self.get_exceptionInfo()
- self.assertEqual(exceptionInfo["breakMode"], "always")
- self.assertEqual(exceptionInfo["description"], "signal SIGABRT")
- self.assertEqual(exceptionInfo["exceptionId"], "signal")
+ stopped_event = session.verify_stopped_on_exception(
+ expected_description="signal SIGABRT", after=process_event
+ )
+ thread_id = self.expect_not_none(stopped_event.body.threadId)
+ exception_info = session.get_exception_info(thread_id)
+
+ self.assertEqual(exception_info.breakMode, "always")
+ description = self.expect_not_none(exception_info.description)
+ self.assertEqual(description, "signal SIGABRT")
+ self.assertEqual(exception_info.exceptionId, "signal")
diff --git a/lldb/test/API/tools/lldb-dap/exception/asan/TestDAP_asan.py b/lldb/test/API/tools/lldb-dap/exception/asan/TestDAP_asan.py
index 0ce315d6e01b7..7a91c2d88f413 100644
--- a/lldb/test/API/tools/lldb-dap/exception/asan/TestDAP_asan.py
+++ b/lldb/test/API/tools/lldb-dap/exception/asan/TestDAP_asan.py
@@ -2,25 +2,27 @@
Test that we stop at runtime instrumentation locations (asan).
"""
-from lldbsuite.test.decorators import *
-from lldbsuite.test.lldbtest import *
-import lldbdap_testcase
+from lldbsuite.test.decorators import skipUnlessAddressSanitizer
+from lldbsuite.test.tools.lldb_dap import lldb_dap_testcase
+from lldbsuite.test.tools.lldb_dap.dap_types import LaunchArgs
-class TestDAP_asan(lldbdap_testcase.DAPTestCaseBase):
+class TestDAP_asan(lldb_dap_testcase.DAPTestCaseBase):
@skipUnlessAddressSanitizer
def test_asan(self):
"""
Test that we stop at asan.
"""
program = self.getBuildArtifact("a.out")
- self.build_and_launch(program)
- self.do_continue()
-
- self.verify_stop_exception_info("Use of deallocated memory")
- exceptionInfo = self.get_exceptionInfo()
- self.assertEqual(exceptionInfo["breakMode"], "always")
- self.assertRegex(
- exceptionInfo["description"], r"fatal_error: heap-use-after-free"
+ session = self.build_and_create_session()
+ process_event = session.launch(LaunchArgs(program))
+ stop_event = session.verify_stopped_on_exception(
+ after=process_event, expected_description="Use of deallocated memory"
)
- self.assertEqual(exceptionInfo["exceptionId"], "runtime-instrumentation")
+
+ thread_id = self.expect_not_none(stop_event.body.threadId)
+ exception_info = session.get_exception_info(thread_id)
+ self.assertEqual(exception_info.breakMode, "always")
+ description = self.expect_not_none(exception_info.description)
+ self.assertRegex(description, r"fatal_error: heap-use-after-free")
+ self.assertEqual(exception_info.exceptionId, "runtime-instrumentation")
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 9fcc1d4a0a8e7..bc801ca9d9912 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,13 +2,13 @@
Test exception behavior in DAP with c++ throw.
"""
-from lldbsuite.test.decorators import *
-from lldbsuite.test.lldbtest import *
-import lldbdap_testcase
+from lldbsuite.test.decorators import skipIfWasm, skipIfWindows
+from lldbsuite.test.tools.lldb_dap import lldb_dap_testcase
+from lldbsuite.test.tools.lldb_dap.dap_types import LaunchArgs
- at skipIfWasm # wasm inferiors are built with -fno-exceptions
-class TestDAP_exception_cpp(lldbdap_testcase.DAPTestCaseBase):
+ at skipIfWasm # wasm inferiors are built with -fno-exceptions.
+class TestDAP_exception_cpp(lldb_dap_testcase.DAPTestCaseBase):
@skipIfWindows
def test_stopped_description(self):
"""
@@ -16,11 +16,18 @@ def test_stopped_description(self):
event.
"""
program = self.getBuildArtifact("a.out")
- self.build_and_launch(program)
- self.dap_server.request_continue()
- self.verify_stop_exception_info("signal SIGABRT")
- exceptionInfo = self.get_exceptionInfo()
- self.assertEqual(exceptionInfo["breakMode"], "always")
- self.assertIn("signal SIGABRT", exceptionInfo["description"])
- self.assertEqual(exceptionInfo["exceptionId"], "signal")
- self.assertIsNotNone(exceptionInfo["details"])
+ session = self.build_and_create_session()
+ process_event = session.launch(LaunchArgs(program=program))
+
+ stopped_event = session.verify_stopped_on_exception(
+ expected_description="signal SIGABRT", after=process_event
+ )
+
+ thread_id = self.expect_not_none(stopped_event.body.threadId)
+ exception_info = session.get_exception_info(thread_id)
+
+ self.assertEqual(exception_info.breakMode, "always")
+ description = self.expect_not_none(exception_info.description)
+ self.assertIn("signal SIGABRT", description)
+ self.assertEqual(exception_info.exceptionId, "signal")
+ self.assertIsNotNone(exception_info.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 89f96f428ec23..438039a19dbc9 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
@@ -2,28 +2,37 @@
Test exception behavior in DAP with obj-c throw.
"""
-from lldbsuite.test.decorators import *
-from lldbsuite.test.lldbtest import *
-import lldbdap_testcase
+from lldbsuite.test.decorators import skipUnlessDarwin
+from lldbsuite.test.tools.lldb_dap import lldb_dap_testcase
+from lldbsuite.test.tools.lldb_dap.dap_types import ExceptionFilterOptions, LaunchArgs
-class TestDAP_exception_objc(lldbdap_testcase.DAPTestCaseBase):
+class TestDAP_exception_objc(lldb_dap_testcase.DAPTestCaseBase):
@skipUnlessDarwin
def test_stopped_description(self):
"""
Test that exception description is shown correctly in stopped event.
"""
program = self.getBuildArtifact("a.out")
- self.build_and_launch(program)
- self.dap_server.request_continue()
- self.verify_stop_exception_info("signal SIGABRT")
- exception_info = self.get_exceptionInfo()
- self.assertEqual(exception_info["breakMode"], "always")
- self.assertIn("signal SIGABRT", exception_info["description"])
- self.assertEqual(exception_info["exceptionId"], "signal")
- exception_details = exception_info["details"]
- self.assertRegex(exception_details["message"], "SomeReason")
- self.assertRegex(exception_details["stackTrace"], "main.m")
+ session = self.build_and_create_session()
+ process_event = session.launch(LaunchArgs(program))
+ stop_event = session.verify_stopped_on_exception(
+ after=process_event, expected_description="signal SIGABRT"
+ )
+
+ thread_id = self.expect_not_none(stop_event.body.threadId)
+ exception_info = session.get_exception_info(thread_id)
+
+ self.assertEqual(exception_info.breakMode, "always")
+ exception_description = self.expect_not_none(exception_info.description)
+ self.assertIn("signal SIGABRT", exception_description)
+ self.assertEqual(exception_info.exceptionId, "signal")
+
+ exception_details = self.expect_not_none(exception_info.details)
+ exception_message = self.expect_not_none(exception_details.message)
+ self.assertRegex(exception_message, "SomeReason")
+ stack_trace = self.expect_not_none(exception_details.stackTrace)
+ self.assertRegex(stack_trace, "main.m")
@skipUnlessDarwin
def test_break_on_throw_and_catch(self):
@@ -31,20 +40,21 @@ def test_break_on_throw_and_catch(self):
Test that breakpoints on exceptions work as expected.
"""
program = self.getBuildArtifact("a.out")
- self.build_and_launch(program)
+ session = self.build_and_create_session()
+ with session.configure(LaunchArgs(program)) as ctx:
+ response = session.set_exception_breakpoints(
+ filters=["objc_catch", "objc_throw"],
+ filterOptions=[
+ ExceptionFilterOptions(
+ filterId="objc_throw",
+ condition='[[((NSException *)$arg1) name] isEqual:@"ThrownException"]',
+ )
+ ],
+ )
+ self.assertTrue(response.success)
- response = self.dap_server.request_setExceptionBreakpoints(
- filter_options=[
- {
- "filterId": "objc_throw",
- "condition": '[[((NSException *)$arg1) name] isEqual:@"ThrownException"]',
- },
- ]
- )
- if response:
- self.assertTrue(response["success"])
-
- self.continue_to_exception_breakpoint(
+ session.verify_stopped_on_exception(
+ after=ctx.process_event,
expected_description="hit Objective-C exception",
expected_text="Objective-C Throw",
)
@@ -55,13 +65,20 @@ def test_break_on_throw_and_catch(self):
# SBTarget::BreakpointCreateForException(eLanguageObjectiveC, /*catch_bp=*/true, /*throw_bp=*/false);
# self.continue_to_exception_breakpoint("Objective-C Catch")
- self.do_continue()
+ stop_event = session.continue_to_exception_breakpoint(
+ expected_description="signal SIGABRT"
+ )
+
+ thread_id = self.expect_not_none(stop_event.body.threadId)
+ exception_info = session.get_exception_info(thread_id)
+
+ self.assertEqual(exception_info.breakMode, "always")
+ description = self.expect_not_none(exception_info.description)
+ self.assertIn("signal SIGABRT", description)
+ self.assertEqual(exception_info.exceptionId, "signal")
- self.verify_stop_exception_info("signal SIGABRT")
- exception_info = self.get_exceptionInfo()
- self.assertEqual(exception_info["breakMode"], "always")
- self.assertIn("signal SIGABRT", exception_info["description"])
- self.assertEqual(exception_info["exceptionId"], "signal")
- exception_details = exception_info["details"]
- self.assertRegex(exception_details["message"], "SomeReason")
- self.assertRegex(exception_details["stackTrace"], "main.m")
+ exception_details = self.expect_not_none(exception_info.details)
+ message = self.expect_not_none(exception_details.message)
+ self.assertRegex(message, "SomeReason")
+ stack_trace = self.expect_not_none(exception_details.stackTrace)
+ self.assertRegex(stack_trace, "main.m")
diff --git a/lldb/test/API/tools/lldb-dap/exception/ubsan/TestDAP_ubsan.py b/lldb/test/API/tools/lldb-dap/exception/ubsan/TestDAP_ubsan.py
index 270c7f0a22a89..fd2d46c730972 100644
--- a/lldb/test/API/tools/lldb-dap/exception/ubsan/TestDAP_ubsan.py
+++ b/lldb/test/API/tools/lldb-dap/exception/ubsan/TestDAP_ubsan.py
@@ -2,28 +2,35 @@
Test that we stop at runtime instrumentation locations (ubsan).
"""
-from lldbsuite.test.decorators import *
-from lldbsuite.test.lldbtest import *
-import lldbdap_testcase
+from lldbsuite.test.decorators import skipUnlessUndefinedBehaviorSanitizer
+from lldbsuite.test.tools.lldb_dap.dap_types import LaunchArgs
+from lldbsuite.test.tools.lldb_dap.lldb_dap_testcase import DAPTestCaseBase
-class TestDAP_ubsan(lldbdap_testcase.DAPTestCaseBase):
+class TestDAP_ubsan(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()
+ session = self.build_and_create_session()
+ process_event = session.launch(LaunchArgs(program))
+ stop_event = session.verify_stopped_on_exception(
+ after=process_event, expected_description=r"Out of bounds index"
+ )
- self.verify_stop_exception_info("Out of bounds index")
- exceptionInfo = self.get_exceptionInfo()
- self.assertEqual(exceptionInfo["breakMode"], "always")
- self.assertRegex(exceptionInfo["description"], r"Out of bounds index")
- self.assertEqual(exceptionInfo["exceptionId"], "runtime-instrumentation")
+ thread_id = self.expect_not_none(stop_event.body.threadId)
+ exception_info = session.get_exception_info(thread_id)
+
+ self.assertEqual(exception_info.breakMode, "always")
+ description = self.expect_not_none(exception_info.description)
+ self.assertRegex(description, r"Out of bounds index")
+ self.assertEqual(exception_info.exceptionId, "runtime-instrumentation")
# FIXME: Check on non macOS platform the stop information location heuristic
# may be wrong. enable when we have updated Ubsan stopInfo heuristic.
if self.platformIsDarwin():
- self.assertIn("main.c", exceptionInfo["details"]["stackTrace"])
+ exception_details = self.expect_not_none(exception_info.details)
+ stack_trace = self.expect_not_none(exception_details.stackTrace)
+ self.assertIn("main.c", stack_trace)
>From 85ac2a47085eb9811be15c4418f7f5f221019ef3 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Thu, 2 Jul 2026 14:52:05 +0100
Subject: [PATCH 2/2] Add test for cpp throw and catch
---
.../test/tools/lldb_dap/session_helpers.py | 5 +-
.../exception/cpp/TestDAP_exception_cpp.py | 52 +++++++++++++++++++
.../API/tools/lldb-dap/exception/cpp/main.cpp | 11 ++++
3 files changed, 67 insertions(+), 1 deletion(-)
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/session_helpers.py b/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/session_helpers.py
index 1e992883d03eb..34eb33f03c450 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/session_helpers.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb_dap/session_helpers.py
@@ -1119,7 +1119,10 @@ def continue_to_any_breakpoint(self, breakpoint_ids: list[int]):
return event
def continue_to_exception_breakpoint(
- self, *, expected_description: str, expected_text: Optional[str] = None
+ self,
+ *,
+ expected_description: Optional[str] = None,
+ expected_text: Optional[str] = None,
):
continue_response = self.do_continue()
return self.verify_stopped_on_exception(
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 bc801ca9d9912..c42ba06a3fe6b 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
@@ -3,6 +3,7 @@
"""
from lldbsuite.test.decorators import skipIfWasm, skipIfWindows
+from lldbsuite.test.lldbtest import line_number
from lldbsuite.test.tools.lldb_dap import lldb_dap_testcase
from lldbsuite.test.tools.lldb_dap.dap_types import LaunchArgs
@@ -31,3 +32,54 @@ def test_stopped_description(self):
self.assertIn("signal SIGABRT", description)
self.assertEqual(exception_info.exceptionId, "signal")
self.assertIsNotNone(exception_info.details)
+
+ @skipIfWindows # cpp exception breakpoint is not supported.
+ def test_break_on_throw_and_catch(self):
+ """Test thrown and caught cpp exception breakpoint works."""
+ program = self.getBuildArtifact("a.out")
+ session = self.build_and_create_session()
+ with session.configure(LaunchArgs(program, args=["1"])) as ctx:
+ # Check we advertise the c++ throw and catch exception filters.
+ resp = ctx.init_response
+ bp_filters = self.expect_not_none(resp.body.exceptionBreakpointFilters)
+
+ init_filters = [ft.filter for ft in bp_filters]
+ cpp_filters = ["cpp_throw", "cpp_catch"]
+ for filter in cpp_filters:
+ self.assertIn(filter, init_filters, "expect cpp_filters is advertised.")
+
+ session.set_exception_breakpoints(filters=cpp_filters)
+
+ stop_event = session.verify_stopped_on_exception(after=ctx.process_event)
+ thread_ctx = session.thread_context_from(stop_event)
+
+ def verify_stack_trace_contains(function: str, line: int):
+ frames = [ctx.frame for ctx in thread_ctx.frames()]
+ for frame in frames:
+ if frame.name.startswith(function):
+ frame_line = self.expect_not_none(frame.line)
+ self.assertEqual(frame_line, line)
+ return
+
+ msg = f"expected {function=} {line=} in stacktrace:"
+ for i, frame in enumerate(frames):
+ msg += f"\n [{i}] {frame.name}:{frame.line}"
+ self.fail(msg)
+
+ source = self.getSourcePath("main.cpp")
+ [throw_filter, caught_filter] = cpp_filters
+
+ # Check the thrown exception.
+ thrown_line = line_number(source, "// thrown_exception")
+ verify_stack_trace_contains("throw_some_string", thrown_line)
+ exc_info = session.get_exception_info(thread_ctx.thread_id)
+ self.assertEqual(throw_filter, exc_info.exceptionId)
+
+ # Check the caught exception.
+ session.continue_to_exception_breakpoint()
+ caught_line = line_number(source, "// caught_exception")
+ verify_stack_trace_contains("main", caught_line)
+ exc_info = session.get_exception_info(thread_ctx.thread_id)
+ self.assertEqual(caught_filter, exc_info.exceptionId)
+
+ session.continue_to_exit()
diff --git a/lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp b/lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
index 39d89b95319a8..589906d1a08c0 100644
--- a/lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
+++ b/lldb/test/API/tools/lldb-dap/exception/cpp/main.cpp
@@ -1,6 +1,17 @@
#include <stdexcept>
+void throw_some_string() {
+ throw "fake message"; // thrown_exception.
+}
+
int main(int argc, char const *argv[]) {
+ try {
+ throw_some_string();
+ } catch (const char *) { // caught_exception.
+ if (argc > 1)
+ return 0;
+ }
+
throw std::invalid_argument("throwing exception for testing");
return 0;
}
More information about the lldb-commits
mailing list