Skip to content

[lldb-dap] Creating protocol types for setExceptionBreakpoints. #144153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1050,8 +1050,12 @@ def request_setBreakpoints(self, source: Source, line_array, data=None):
self._update_verified_breakpoints(response["body"]["breakpoints"])
return response

def request_setExceptionBreakpoints(self, filters):
def request_setExceptionBreakpoints(
self, *, filters: list[str] = [], filter_options: list[dict] = []
):
args_dict = {"filters": filters}
if filter_options:
args_dict["filterOptions"] = filter_options
command_dict = {
"command": "setExceptionBreakpoints",
"type": "request",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
"""
Test lldb-dap setBreakpoints request
Test lldb-dap setExceptionBreakpoints request
"""


import dap_server
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbdap_testcase


@skip("Temporarily disable the breakpoint tests")
class TestDAP_setExceptionBreakpoints(lldbdap_testcase.DAPTestCaseBase):
@skipIfWindows
def test_functionality(self):
Expand All @@ -33,8 +29,9 @@ def test_functionality(self):
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)

filters = ["cpp_throw", "cpp_catch"]
response = self.dap_server.request_setExceptionBreakpoints(filters)
response = self.dap_server.request_setExceptionBreakpoints(
filters=["cpp_throw", "cpp_catch"],
)
if response:
self.assertTrue(response["success"])

Expand Down
2 changes: 1 addition & 1 deletion lldb/test/API/tools/lldb-dap/exception/objc/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
OBJC_SOURCES := main.m

CFLAGS_EXTRAS := -w
CFLAGS_EXTRAS := -w -fobjc-exceptions

USE_SYSTEM_STDLIB := 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
Test exception behavior in DAP with obj-c throw.
"""


from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase
Expand All @@ -25,3 +24,41 @@ def test_stopped_description(self):
exception_details = exception_info["details"]
self.assertRegex(exception_details["message"], "SomeReason")
self.assertRegex(exception_details["stackTrace"], "main.m")

@skipUnlessDarwin
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)

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("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
# catch, which is different than
# SBTarget::BreakpointCreateForException(eLanguageObjectiveC, /*catch_bp=*/true, /*throw_bp=*/false);
# self.continue_to_exception_breakpoint("Objective-C Catch")

self.do_continue()

self.assertTrue(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.assertEqual(exception_info["exceptionId"], "signal")
exception_details = exception_info["details"]
self.assertRegex(exception_details["message"], "SomeReason")
self.assertRegex(exception_details["stackTrace"], "main.m")
12 changes: 9 additions & 3 deletions lldb/test/API/tools/lldb-dap/exception/objc/main.m
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
#import <Foundation/Foundation.h>

int main(int argc, char const *argv[]) {
@throw [[NSException alloc] initWithName:@"ThrownException"
reason:@"SomeReason"
userInfo:nil];
@try {
NSException *e = [[NSException alloc] initWithName:@"ThrownException"
reason:@"SomeReason"
userInfo:nil];
@throw e;
} @catch (NSException *e) {
NSLog(@"Caught %@", e);
@throw; // let the process crash...
}
return 0;
}
158 changes: 73 additions & 85 deletions lldb/tools/lldb-dap/DAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "DAP.h"
#include "DAPLog.h"
#include "EventHelper.h"
#include "ExceptionBreakpoint.h"
#include "Handler/RequestHandler.h"
#include "Handler/ResponseHandler.h"
#include "JSONUtils.h"
Expand All @@ -17,6 +18,7 @@
#include "Protocol/ProtocolBase.h"
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "ProtocolUtils.h"
#include "Transport.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBCommandInterpreter.h"
Expand Down Expand Up @@ -129,104 +131,89 @@ DAP::DAP(Log *log, const ReplMode default_repl_mode,
DAP::~DAP() = default;

void DAP::PopulateExceptionBreakpoints() {
llvm::call_once(init_exception_breakpoints_flag, [this]() {
exception_breakpoints = std::vector<ExceptionBreakpoint>{};

if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
exception_breakpoints->emplace_back(*this, "cpp_catch", "C++ Catch",
lldb::eLanguageTypeC_plus_plus);
exception_breakpoints->emplace_back(*this, "cpp_throw", "C++ Throw",
lldb::eLanguageTypeC_plus_plus);
}
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) {
exception_breakpoints->emplace_back(
*this, "objc_catch", "Objective-C Catch", lldb::eLanguageTypeObjC);
exception_breakpoints->emplace_back(
*this, "objc_throw", "Objective-C Throw", lldb::eLanguageTypeObjC);
}
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) {
exception_breakpoints->emplace_back(*this, "swift_catch", "Swift Catch",
lldb::eLanguageTypeSwift);
exception_breakpoints->emplace_back(*this, "swift_throw", "Swift Throw",
lldb::eLanguageTypeSwift);
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
exception_breakpoints.emplace_back(*this, "cpp_catch", "C++ Catch",
lldb::eLanguageTypeC_plus_plus,
eExceptionKindCatch);
exception_breakpoints.emplace_back(*this, "cpp_throw", "C++ Throw",
lldb::eLanguageTypeC_plus_plus,
eExceptionKindThrow);
}

if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) {
exception_breakpoints.emplace_back(*this, "objc_catch", "Objective-C Catch",
lldb::eLanguageTypeObjC,
eExceptionKindCatch);
exception_breakpoints.emplace_back(*this, "objc_throw", "Objective-C Throw",
lldb::eLanguageTypeObjC,
eExceptionKindThrow);
}

if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) {
exception_breakpoints.emplace_back(*this, "swift_catch", "Swift Catch",
lldb::eLanguageTypeSwift,
eExceptionKindCatch);
exception_breakpoints.emplace_back(*this, "swift_throw", "Swift Throw",
lldb::eLanguageTypeSwift,
eExceptionKindThrow);
}

// Besides handling the hardcoded list of languages from above, we try to find
// any other languages that support exception breakpoints using the SB API.
for (int raw_lang = lldb::eLanguageTypeUnknown;
raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);

// We first discard any languages already handled above.
if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
lang == lldb::eLanguageTypeSwift)
continue;

if (!lldb::SBDebugger::SupportsLanguage(lang))
continue;

const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
if (!name)
continue;
std::string raw_lang_name = name;
std::string capitalized_lang_name = capitalize(name);

if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
const char *raw_throw_keyword =
lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
std::string throw_keyword =
raw_throw_keyword ? raw_throw_keyword : "throw";

exception_breakpoints.emplace_back(
*this, raw_lang_name + "_" + throw_keyword,
capitalized_lang_name + " " + capitalize(throw_keyword), lang,
eExceptionKindThrow);
}
// Besides handling the hardcoded list of languages from above, we try to
// find any other languages that support exception breakpoints using the
// SB API.
for (int raw_lang = lldb::eLanguageTypeUnknown;
raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);

// We first discard any languages already handled above.
if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
lang == lldb::eLanguageTypeSwift)
continue;

if (!lldb::SBDebugger::SupportsLanguage(lang))
continue;

const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
if (!name)
continue;
std::string raw_lang_name = name;
std::string capitalized_lang_name = capitalize(name);

if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
const char *raw_throw_keyword =
lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
std::string throw_keyword =
raw_throw_keyword ? raw_throw_keyword : "throw";

exception_breakpoints->emplace_back(
*this, raw_lang_name + "_" + throw_keyword,
capitalized_lang_name + " " + capitalize(throw_keyword), lang);
}

if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
const char *raw_catch_keyword =
lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
std::string catch_keyword =
raw_catch_keyword ? raw_catch_keyword : "catch";
if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
const char *raw_catch_keyword =
lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
std::string catch_keyword =
raw_catch_keyword ? raw_catch_keyword : "catch";

exception_breakpoints->emplace_back(
*this, raw_lang_name + "_" + catch_keyword,
capitalized_lang_name + " " + capitalize(catch_keyword), lang);
}
exception_breakpoints.emplace_back(
*this, raw_lang_name + "_" + catch_keyword,
capitalized_lang_name + " " + capitalize(catch_keyword), lang,
eExceptionKindCatch);
}
assert(!exception_breakpoints->empty() && "should not be empty");
});
}
}

ExceptionBreakpoint *DAP::GetExceptionBreakpoint(llvm::StringRef filter) {
// PopulateExceptionBreakpoints() is called after g_dap.debugger is created
// in a request-initialize.
//
// But this GetExceptionBreakpoint() method may be called before attaching, in
// which case, we may not have populated the filter yet.
//
// We also cannot call PopulateExceptionBreakpoints() in DAP::DAP() because
// we need SBDebugger::Initialize() to have been called before this.
//
// So just calling PopulateExceptionBreakoints(),which does lazy-populating
// seems easiest. Two other options include:
// + call g_dap.PopulateExceptionBreakpoints() in lldb-dap.cpp::main()
// right after the call to SBDebugger::Initialize()
// + Just call PopulateExceptionBreakpoints() to get a fresh list everytime
// we query (a bit overkill since it's not likely to change?)
PopulateExceptionBreakpoints();

for (auto &bp : *exception_breakpoints) {
for (auto &bp : exception_breakpoints) {
if (bp.GetFilter() == filter)
return &bp;
}
return nullptr;
}

ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
// See comment in the other GetExceptionBreakpoint().
PopulateExceptionBreakpoints();

for (auto &bp : *exception_breakpoints) {
for (auto &bp : exception_breakpoints) {
if (bp.GetID() == bp_id)
return &bp;
}
Expand Down Expand Up @@ -1118,8 +1105,9 @@ protocol::Capabilities DAP::GetCapabilities() {
}

// Available filters or options for the setExceptionBreakpoints request.
PopulateExceptionBreakpoints();
std::vector<protocol::ExceptionBreakpointsFilter> filters;
for (const auto &exc_bp : *exception_breakpoints)
for (const auto &exc_bp : exception_breakpoints)
filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
capabilities.exceptionBreakpointFilters = std::move(filters);

Expand Down
4 changes: 2 additions & 2 deletions lldb/tools/lldb-dap/DAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct DAP {
lldb::SBBroadcaster broadcaster;
FunctionBreakpointMap function_breakpoints;
InstructionBreakpointMap instruction_breakpoints;
std::optional<std::vector<ExceptionBreakpoint>> exception_breakpoints;
std::vector<ExceptionBreakpoint> exception_breakpoints;
llvm::once_flag init_exception_breakpoints_flag;

/// Map step in target id to list of function targets that user can choose.
Expand Down Expand Up @@ -320,7 +320,7 @@ struct DAP {
});
}

/// The set of capablities supported by this adapter.
/// The set of capabilities supported by this adapter.
protocol::Capabilities GetCapabilities();

/// Debuggee will continue from stopped state.
Expand Down
26 changes: 18 additions & 8 deletions lldb/tools/lldb-dap/ExceptionBreakpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,33 @@
#include "ExceptionBreakpoint.h"
#include "BreakpointBase.h"
#include "DAP.h"
#include "Protocol/ProtocolTypes.h"
#include "lldb/API/SBMutex.h"
#include "lldb/API/SBTarget.h"
#include <mutex>

using namespace llvm;
using namespace lldb_dap::protocol;

namespace lldb_dap {

void ExceptionBreakpoint::SetBreakpoint() {
protocol::Breakpoint ExceptionBreakpoint::SetBreakpoint(StringRef condition) {
lldb::SBMutex lock = m_dap.GetAPIMutex();
std::lock_guard<lldb::SBMutex> guard(lock);

if (m_bp.IsValid())
return;
bool catch_value = m_filter.find("_catch") != std::string::npos;
bool throw_value = m_filter.find("_throw") != std::string::npos;
m_bp = m_dap.target.BreakpointCreateForException(m_language, catch_value,
throw_value);
m_bp.AddName(BreakpointBase::kDAPBreakpointLabel);
if (!m_bp.IsValid()) {
m_bp = m_dap.target.BreakpointCreateForException(
m_language, m_kind == eExceptionKindCatch,
m_kind == eExceptionKindThrow);
m_bp.AddName(BreakpointBase::kDAPBreakpointLabel);
}

m_bp.SetCondition(condition.data());

protocol::Breakpoint breakpoint;
breakpoint.id = m_bp.GetID();
breakpoint.verified = m_bp.IsValid();
return breakpoint;
}

void ExceptionBreakpoint::ClearBreakpoint() {
Expand Down
Loading
Loading