https://github.com/da-viper created
https://github.com/llvm/llvm-project/pull/170527
This PR adds support for setting breakpoints on functions annotated with the
gnu::abi_tag attribute.
Functions decorated with gnu::abi_tag can now be matched by their base name.
For example:
```cpp
[[gnu::abi_tag("cxx11")]]
int foo() { }
```
The command `breakpoint set --name foo` will now successfully match and set a
breakpoint on the above function.
Current Limitation
This PR does not include support for explicitly specifying the ABI tag in the
breakpoint name. The following syntax is not currently supported: `breakpoint
set --name foo[abi:cxx11]`. This will require changes on how we currently
lookup and match names in the debug info.
>From ffc8a12e882945f3a59d91a332f912e2bf35460e Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <[email protected]>
Date: Tue, 11 Nov 2025 12:46:24 +0000
Subject: [PATCH 1/2] [lldb] Support breakpoint on functions with abi_tags
---
.../Language/CPlusPlus/CPlusPlusLanguage.cpp | 183 +++++++++++++++---
.../Language/CPlusPlus/CPlusPlusLanguage.h | 48 +++--
.../CPlusPlus/CPlusPlusLanguageTest.cpp | 129 +++++++++++-
3 files changed, 314 insertions(+), 46 deletions(-)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index a3624accf9b5a..0df32e069f90c 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -16,6 +16,7 @@
#include <mutex>
#include <set>
+#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Demangle/ItaniumDemangle.h"
@@ -34,6 +35,7 @@
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
+#include "lldb/Utility/NameMatches.h"
#include "lldb/Utility/RegularExpression.h"
#include "lldb/ValueObject/ValueObjectVariable.h"
@@ -538,15 +540,154 @@ void CPlusPlusLanguage::CxxMethodName::Parse() {
}
}
-llvm::StringRef
-CPlusPlusLanguage::CxxMethodName::GetBasenameNoTemplateParameters() {
- llvm::StringRef basename = GetBasename();
- size_t arg_start, arg_end;
- llvm::StringRef parens("<>", 2);
- if (ReverseFindMatchingChars(basename, parens, arg_start, arg_end))
- return basename.substr(0, arg_start);
+bool CPlusPlusLanguage::CxxMethodName::NameMatches(llvm::StringRef full_name,
+ llvm::StringRef pattern,
+ MatchOptions options) {
+ constexpr llvm::StringRef abi_prefix = "[abi:";
+ constexpr char abi_end = ']';
+ constexpr char open_angle = '<';
+ constexpr char close_angle = '>';
+ size_t f_idx = 0;
+ size_t p_idx = 0;
+
+ while (f_idx < full_name.size()) {
+ const char in_char = full_name[f_idx];
+ // input may have extra abi_tag / template so we still loop
+ const bool match_empty = p_idx >= pattern.size();
+ const char ma_char = match_empty ? '\0' : pattern[p_idx];
+
+ // skip abi_tags.
+ if (options.skip_tags && in_char == '[' &&
+ full_name.substr(f_idx).starts_with(abi_prefix)) {
+
+ const size_t tag_end = full_name.find(abi_end, f_idx);
+ if (tag_end != llvm::StringRef::npos) {
+ const size_t in_tag_len = tag_end - f_idx + 1;
+
+ if (!match_empty && pattern.substr(p_idx).starts_with(abi_prefix)) {
+ const size_t match_tag_end = pattern.find(abi_end, p_idx);
+ if (match_tag_end != llvm::StringRef::npos) {
+ const size_t ma_tag_len = match_tag_end - p_idx + 1;
+
+ // match may only have only one of the input's abi_tags.
+ // we only skip if the abi_tag matches.
+ if ((in_tag_len == ma_tag_len) &&
+ full_name.substr(f_idx, in_tag_len) ==
+ pattern.substr(p_idx, ma_tag_len)) {
+ p_idx += ma_tag_len;
+ }
+ }
+ }
+
+ f_idx += in_tag_len;
+ continue;
+ }
+ }
+
+ // skip template_tags.
+ if (options.skip_templates && in_char == open_angle &&
+ ma_char != open_angle) {
+ size_t depth = 1;
+ size_t tmp_idx = f_idx + 1;
+ bool found_end = false;
+ for (; tmp_idx < full_name.size(); ++tmp_idx) {
+ const char cur = full_name[tmp_idx];
+ if (cur == open_angle)
+ depth++;
+ else if (cur == close_angle) {
+ depth--;
+
+ if (depth == 0) {
+ found_end = true;
+ break;
+ }
+ }
+ }
+
+ if (found_end) {
+ f_idx = tmp_idx + 1;
+ continue;
+ }
+ }
- return basename;
+ // input contains characters that are not in match.
+ if (match_empty || in_char != ma_char)
+ return false;
+
+ f_idx++;
+ p_idx++;
+ }
+
+ // Ensure we fully consumed the match string.
+ return p_idx == pattern.size();
+}
+
+static llvm::StringRef NextContext(llvm::StringRef context, size_t &end_pos) {
+ if (end_pos == llvm::StringRef::npos)
+ return {};
+
+ const int start = 0;
+ const int end = static_cast<int>(end_pos) - 1;
+ int depth = 0;
+
+ if (end >= static_cast<int>(context.size())) {
+ end_pos = llvm::StringRef::npos;
+ return {};
+ }
+
+ for (int idx = end; idx >= start; --idx) {
+ const char val = context[idx];
+
+ if (depth == 0 && val == ':' && (idx != 0) && (idx - 1 >= 0) &&
+ context[idx - 1] == ':') {
+ end_pos = idx - 1;
+ return context.substr(idx + 1, end_pos - idx);
+ }
+
+ if (val == '<' || val == '(' || val == '[')
+ depth++;
+ else if (val == '>' || val == ')' || val == ']')
+ depth--;
+ }
+
+ end_pos = llvm::StringRef::npos;
+ return context.substr(start, end_pos - start);
+}
+
+bool CPlusPlusLanguage::CxxMethodName::ContainsContext(
+ llvm::StringRef full_name, llvm::StringRef pattern, MatchOptions options) {
+ size_t full_pos = full_name.size();
+ size_t pat_pos = pattern.size();
+
+ // We loop as long as there are contexts left in the identifier.
+ while (full_pos != llvm::StringRef::npos) {
+ size_t next_full_pos = full_pos;
+ const llvm::StringRef full_ctx = NextContext(full_name, next_full_pos);
+
+ size_t next_pat_pos = pat_pos;
+ const llvm::StringRef pat_ctx = NextContext(pattern, next_pat_pos);
+
+ if (NameMatches(full_ctx, pat_ctx, options)) {
+ // we matched all characters in part_str.
+ if (next_pat_pos == llvm::StringRef::npos)
+ return true;
+
+ // context matches: advance both cursors.
+ full_pos = next_full_pos;
+ pat_pos = next_pat_pos;
+ continue;
+ }
+
+ if (next_pat_pos == llvm::StringRef::npos)
+ return false;
+
+ // context does not match. advance the identifier cursor (consume the
+ // current identifier context) and resest the path cursor to the beginning.
+ full_pos = next_full_pos;
+ pat_pos = 0;
+ }
+
+ return false;
}
bool CPlusPlusLanguage::CxxMethodName::ContainsPath(llvm::StringRef path) {
@@ -564,21 +705,9 @@ bool
CPlusPlusLanguage::CxxMethodName::ContainsPath(llvm::StringRef path) {
if (!success)
return m_full.GetStringRef().contains(path);
- // Basename may include template arguments.
- // E.g.,
- // GetBaseName(): func<int>
- // identifier : func
- //
- // ...but we still want to account for identifiers with template parameter
- // lists, e.g., when users set breakpoints on template specializations.
- //
- // E.g.,
- // GetBaseName(): func<uint32_t>
- // identifier : func<int32_t*>
- //
- // Try to match the basename with or without template parameters.
- if (GetBasename() != identifier &&
- GetBasenameNoTemplateParameters() != identifier)
+ const MatchOptions options{/*skip_templates*/ true, /*skip_tags*/ true};
+ const llvm::StringRef basename = GetBasename();
+ if (!NameMatches(basename, identifier, options))
return false;
// Incoming path only had an identifier, so we match.
@@ -588,13 +717,7 @@ bool
CPlusPlusLanguage::CxxMethodName::ContainsPath(llvm::StringRef path) {
if (m_context.empty())
return false;
- llvm::StringRef haystack = m_context;
- if (!haystack.consume_back(context))
- return false;
- if (haystack.empty() || !isalnum(haystack.back()))
- return true;
-
- return false;
+ return ContainsContext(m_context, context, options);
}
bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path,
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
index b5472340bd913..bbcb0fcb19b9b 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -32,19 +32,43 @@ class CPlusPlusLanguage : public Language {
bool ContainsPath(llvm::StringRef path);
private:
- /// Returns the Basename of this method without a template parameter
- /// list, if any.
+ struct MatchOptions {
+ bool skip_templates;
+ bool skip_tags;
+ };
+
+ /// Compare method name with the pattern with the option to skip over ABI
+ /// tags and template parameters in the full_name when they don't appear in
+ /// pattern.
///
- // Examples:
- //
- // +--------------------------------+---------+
- // | MethodName | Returns |
- // +--------------------------------+---------+
- // | void func() | func |
- // | void func<int>() | func |
- // | void func<std::vector<int>>() | func |
- // +--------------------------------+---------+
- llvm::StringRef GetBasenameNoTemplateParameters();
+ /// \param full_name The complete method name that may contain ABI tags and
+ /// templates
+ /// \param pattern The name pattern to match against
+ /// \param options Configuration for what to skip during matching
+ /// \return true if the names match (ignoring skipped parts), false
+ /// otherwise
+ ///
+ /// Examples:
+ // | MethodName | Pattern | Option | Returns |
+ // |----------------------|-------------|-----------------------|---------|
+ // | vector<int>() | vector | skip_template | true |
+ // | foo[abi:aTag]<int>() | foo | skip_template_and_tag | true |
+ // | MyClass::foo() | OClass::foo | | false |
+ // | bar::foo<int> | foo | no_skip_template | false |
+ ///
+ bool NameMatches(llvm::StringRef full_name, llvm::StringRef pattern,
+ MatchOptions options);
+
+ /// Checks if a pattern appears as a suffix of contexts within a full C++
+ /// name, uses the same \a MatchOption as \a NameMatches.
+ ///
+ /// \param full_name The fully qualified C++ name to search within
+ /// \param pattern The pattern to search for (can be partial scope path)
+ /// \param options Configuration for name matching (passed to NameMatches)
+ /// \return true if the pattern is found as a suffix context or the whole
+ /// context, false otherwise
+ bool ContainsContext(llvm::StringRef full_name, llvm::StringRef pattern,
+ MatchOptions options);
protected:
void Parse() override;
diff --git a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
index 41df35f67a790..c60a90f3e98ee 100644
--- a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
+++ b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
@@ -262,6 +262,124 @@ TEST(CPlusPlusLanguage, InvalidMethodNameParsing) {
}
}
+TEST(CPlusPlusLanguage, AbiContainsPath) {
+
+ using Match = std::initializer_list<llvm::StringRef>;
+ using NoMatch = std::initializer_list<llvm::StringRef>;
+
+ struct TestCase {
+ std::string input;
+ Match matches;
+ NoMatch no_matches;
+ };
+ // NOTE: for future reference to prevent testing unreachable states. or
making
+ // the matcher match unreachable states.
+ // see specification for completeness. TODO: add specification
+ // - abi tags can are ordered. i.e func[abi:TAG1][abi:TAG2] is valid but
+ // func[abi:TAG2][abi:TAG1] is not
+ // - we do not set invalid function as this get filtered out during the
module
+ // lookup.
+
+ const std::initializer_list<TestCase> test_cases = {
+ TestCase{"func[abi:TAG1][abi:TAG2]()",
+ Match{"func", "func[abi:TAG1]", "func[abi:TAG2]"},
+ NoMatch{"func[abi:WRONG_TAG]"}},
+ TestCase{"Foo::Bar::baz::func(Ball[abi:CTX_TAG]::Val )",
+ Match{"func", "baz::func", "Bar::baz::func"},
+ NoMatch{"baz", "baz::fun"}},
+ // function with abi_tag
+ TestCase{"second::first::func[abi:FUNC_TAG]()",
+ Match{"func", "func[abi:FUNC_TAG]", "first::func",
+ "first::func[abi:FUNC_TAG]"},
+ NoMatch{"func[abi:WRONG_TAG]", "funcc[abi:FUNC_TAG]",
+ "fun[abi:FUNC_TAG]", "func[abi:FUNC_TA]",
+ "func[abi:UNC_TAG]", "func[abi::FUNC_TAG]",
+ "second::func"}},
+ // function with abi_tag and template
+ TestCase{"ns::Animal func[abi:FUNC_TAG]<ns::Animal>()",
+ Match{
+ "func<ns::Animal>",
+ "func[abi:FUNC_TAG]",
+ "func[abi:FUNC_TAG]<ns::Animal>",
+ "func",
+ },
+ NoMatch{"nn::func<ns::Plane>"}},
+ TestCase{"Ball[abi:STRUCT_TAG]<int> "
+ "first::func[abi:FUNC_TAG]<Ball[abi:STRUCT_TAG]<int>>()",
+ Match{"func", "func[abi:FUNC_TAG]", "first::func"},
+ NoMatch{"func[abi:STRUCT_TAG]"}},
+ // first context with abi_tag
+ TestCase{"second::first[abi:CTX_TAG]::func()",
+ Match{"func", "first::func", "first[abi:CTX_TAG]::func"},
+ NoMatch{"first[abi:CTX_TAG]", "first[abi:WRONG_CTX_TAG]::func",
+ "first::func[abi:WRONG_FUNC_TAG]",
+ "first:func[abi:CTX_TAG]", "second::func"}},
+ // templated context
+ TestCase{"second::first[abi:CTX_TAG]<SomeClass>::func[abi:FUNC_TAG]()",
+ Match{"first::func", "first[abi:CTX_TAG]::func",
+ "first::func[abi:FUNC_TAG]", "first<SomeClass>::func",
+ "first[abi:CTX_TAG]<SomeClass>::func",
+ "first[abi:CTX_TAG]<SomeClass>::func[abi:FUNC_TAG]",
+ "second::first::func"},
+ NoMatch{"first::func[abi:CTX_TAG]", "first[abi:FUNC_TAG]::func",
+ "first::func<SomeClass>",
+ "second[abi:CTX_TAG]::first::func"}},
+ // multiple abi tag
+ TestCase{"func[abi:TAG1][abi:TAG2]()",
+ Match{"func", "func[abi:TAG1]", "func[abi:TAG2]"},
+ NoMatch{"func[abi:WRONG_TAG]"}},
+ // multiple two context twice with abi_tag
+ TestCase{"first[abi:CTX_TAG1][abi:CTX_TAG2]::func[abi:FUNC_TAG]::first::"
+ "func(int)",
+ Match{"first::func", "func"},
+ NoMatch{"first[abi:CTX_TAG1]", "func[abi:FUNC_TAG]"}},
+ // multiple abi tag in context and template
+ //
TestCase{"second::first[abi:CTX_TAG]<SomeClass>::func[abi:FUNC_TAG]()",
+
+ // operator overload
+ // aa::bb::wrap_int
+ TestCase{"Ball[abi:CTX_TAG]<int>::operator<[abi:OPERATOR]<int>(int)",
+ Match{"operator<", "operator<[abi:OPERATOR]",
"Ball::operator<"},
+ NoMatch{"operator<<", "Ball::operator<<"}},
+ TestCase{
+ "Ball[abi:CTX_TAG]<int>::operator[][abi:OPERATOR]<int>(int)",
+ Match{"Ball[abi:CTX_TAG]::operator[][abi:OPERATOR]", "operator[]",
+ "Ball::operator[]", "Ball::operator[][abi:OPERATOR]",
+ "Ball[abi:CTX_TAG]::operator[]"},
+ NoMatch{"operator_", "operator>>",
"Ball[abi:OPERATOR]::operator[]"}},
+ TestCase{"Ball[abi:CTX_TAG]<int>::operator<<[abi:OPERATOR]<int>(int)",
+ Match{"Ball::operator<<<int>", "operator<<",
+ "operator<<[abi:OPERATOR]", "operator<<<int>",
+ "Ball[abi:CTX_TAG]<int>::operator<<[abi:OPERATOR]<int>"},
+ NoMatch{"operator<", "operator<<<long>",
+ "Ball<long>::operator<<<int>",
+ "operator<<[abi:operator]"}},
+
+ // double context before matching path
+ // TODO: context in anonymous namespace
+
+ // no abi tag
+ // with return value.
+ // with templated return value.
+ };
+
+ for (const auto &[input, matches, no_matches] : test_cases) {
+ CPlusPlusLanguage::CxxMethodName method{ConstString(input)};
+ EXPECT_TRUE(method.IsValid()) << input;
+ if (!method.IsValid())
+ continue;
+
+ for (const auto &path : matches) {
+ EXPECT_TRUE(method.ContainsPath(path))
+ << llvm::formatv("`{0}` should match `{1}`", path, input);
+ }
+
+ for (const auto &path : no_matches)
+ EXPECT_FALSE(method.ContainsPath(path))
+ << llvm::formatv("`{0}` should not match `{1}`", path, input);
+ }
+}
+
TEST(CPlusPlusLanguage, ContainsPath) {
CPlusPlusLanguage::CxxMethodName reference_1(
ConstString("int foo::bar::func01(int a, double b)"));
@@ -275,6 +393,9 @@ TEST(CPlusPlusLanguage, ContainsPath) {
CPlusPlusLanguage::CxxMethodName reference_6(ConstString(
"bar::baz::operator<<<Type<double>, Type<std::vector<double>>>()"));
+ CPlusPlusLanguage::CxxMethodName ref(
+ ConstString("Foo::Bar::Baz::Function()"));
+ EXPECT_TRUE(ref.ContainsPath("Bar::Baz::Function"));
EXPECT_TRUE(reference_1.ContainsPath(""));
EXPECT_TRUE(reference_1.ContainsPath("func01"));
EXPECT_TRUE(reference_1.ContainsPath("bar::func01"));
@@ -284,11 +405,11 @@ TEST(CPlusPlusLanguage, ContainsPath) {
EXPECT_FALSE(reference_1.ContainsPath("::bar::func01"));
EXPECT_FALSE(reference_1.ContainsPath("::foo::baz::func01"));
EXPECT_FALSE(reference_1.ContainsPath("foo::bar::baz::func01"));
-
+
EXPECT_TRUE(reference_2.ContainsPath(""));
EXPECT_TRUE(reference_2.ContainsPath("foofoo::bar::func01"));
EXPECT_FALSE(reference_2.ContainsPath("foo::bar::func01"));
-
+
EXPECT_TRUE(reference_3.ContainsPath(""));
EXPECT_TRUE(reference_3.ContainsPath("func01"));
EXPECT_FALSE(reference_3.ContainsPath("func"));
@@ -368,8 +489,8 @@ TEST(CPlusPlusLanguage, ExtractContextAndIdentifier) {
"selector:", context, basename));
EXPECT_FALSE(CPlusPlusLanguage::ExtractContextAndIdentifier(
"selector:otherField:", context, basename));
- EXPECT_FALSE(CPlusPlusLanguage::ExtractContextAndIdentifier(
- "abc::", context, basename));
+ EXPECT_FALSE(CPlusPlusLanguage::ExtractContextAndIdentifier("abc::", context,
+ basename));
EXPECT_FALSE(CPlusPlusLanguage::ExtractContextAndIdentifier(
"f<A<B><C>>", context, basename));
>From b01a29f121a6004ae136b0aafbabcda5e4e5bc92 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <[email protected]>
Date: Wed, 3 Dec 2025 15:22:54 +0000
Subject: [PATCH 2/2] [lldb] add testcases
---
.../Language/CPlusPlus/CPlusPlusLanguage.cpp | 9 +-
.../Language/CPlusPlus/CPlusPlusLanguage.h | 8 +-
.../breakpoint/cpp/abi_tag/Makefile | 3 +
.../TestCPPBreakpointLocationsAbiTag.py | 90 +++++++++++++
.../breakpoint/cpp/abi_tag/main.cpp | 118 ++++++++++++++++++
.../CPlusPlus/CPlusPlusLanguageTest.cpp | 32 ++---
6 files changed, 231 insertions(+), 29 deletions(-)
create mode 100644
lldb/test/API/functionalities/breakpoint/cpp/abi_tag/Makefile
create mode 100644
lldb/test/API/functionalities/breakpoint/cpp/abi_tag/TestCPPBreakpointLocationsAbiTag.py
create mode 100644
lldb/test/API/functionalities/breakpoint/cpp/abi_tag/main.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 0df32e069f90c..fb5d082090296 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -35,7 +35,6 @@
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
-#include "lldb/Utility/NameMatches.h"
#include "lldb/Utility/RegularExpression.h"
#include "lldb/ValueObject/ValueObjectVariable.h"
@@ -644,6 +643,8 @@ static llvm::StringRef NextContext(llvm::StringRef context,
size_t &end_pos) {
return context.substr(idx + 1, end_pos - idx);
}
+ // in contexts you cannot have a standlone bracket such
+ // as `operator<` use only one variable to track depth.
if (val == '<' || val == '(' || val == '[')
depth++;
else if (val == '>' || val == ')' || val == ']')
@@ -659,7 +660,7 @@ bool CPlusPlusLanguage::CxxMethodName::ContainsContext(
size_t full_pos = full_name.size();
size_t pat_pos = pattern.size();
- // We loop as long as there are contexts left in the identifier.
+ // We loop as long as there are contexts left in the full_name.
while (full_pos != llvm::StringRef::npos) {
size_t next_full_pos = full_pos;
const llvm::StringRef full_ctx = NextContext(full_name, next_full_pos);
@@ -681,8 +682,8 @@ bool CPlusPlusLanguage::CxxMethodName::ContainsContext(
if (next_pat_pos == llvm::StringRef::npos)
return false;
- // context does not match. advance the identifier cursor (consume the
- // current identifier context) and resest the path cursor to the beginning.
+ // context does not match. advance the full_name cursor (consume the
+ // current full_namecontext) and resest the path cursor to the beginning.
full_pos = next_full_pos;
pat_pos = 0;
}
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
index bbcb0fcb19b9b..a394205e6ec15 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -56,8 +56,8 @@ class CPlusPlusLanguage : public Language {
// | MyClass::foo() | OClass::foo | | false |
// | bar::foo<int> | foo | no_skip_template | false |
///
- bool NameMatches(llvm::StringRef full_name, llvm::StringRef pattern,
- MatchOptions options);
+ static bool NameMatches(llvm::StringRef full_name, llvm::StringRef pattern,
+ MatchOptions options);
/// Checks if a pattern appears as a suffix of contexts within a full C++
/// name, uses the same \a MatchOption as \a NameMatches.
@@ -67,8 +67,8 @@ class CPlusPlusLanguage : public Language {
/// \param options Configuration for name matching (passed to NameMatches)
/// \return true if the pattern is found as a suffix context or the whole
/// context, false otherwise
- bool ContainsContext(llvm::StringRef full_name, llvm::StringRef pattern,
- MatchOptions options);
+ static bool ContainsContext(llvm::StringRef full_name,
+ llvm::StringRef pattern, MatchOptions options);
protected:
void Parse() override;
diff --git a/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/Makefile
b/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git
a/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/TestCPPBreakpointLocationsAbiTag.py
b/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/TestCPPBreakpointLocationsAbiTag.py
new file mode 100644
index 0000000000000..a46f8b597fd48
--- /dev/null
+++
b/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/TestCPPBreakpointLocationsAbiTag.py
@@ -0,0 +1,90 @@
+"""
+Test breakpoint on function with abi_tags.
+"""
+
+import lldb
+from typing import List, Set, TypedDict
+from lldbsuite.test.decorators import skipIfWindows
+from lldbsuite.test.lldbtest import VALID_TARGET, TestBase
+
+
+class Case(TypedDict, total=True):
+ name: str
+ matches: Set[str]
+
+
+@skipIfWindows # abi_tags is not supported
+class TestCPPBreakpointLocationsAbiTag(TestBase):
+
+ def verify_breakpoint_names(self, target: lldb.SBTarget, bp_dict: Case):
+ name = bp_dict["name"]
+ matches = bp_dict["matches"]
+ bp: lldb.SBBreakpoint = target.BreakpointCreateByName(name)
+
+ for location in bp:
+ self.assertTrue(location.IsValid(), f"Expected valid location
{location}")
+
+ expected_matches = set(location.addr.function.name for location in bp)
+
+ self.assertSetEqual(expected_matches, matches)
+
+ def test_breakpoint_name_with_abi_tag(self):
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target: lldb.SBTarget = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+
+ test_cases: List[Case] = [
+ Case(
+ name="foo",
+ matches={
+ "foo[abi:FOO]()",
+
"StaticStruct[abi:STATIC_STRUCT]::foo[abi:FOO][abi:FOO2]()",
+ "Struct[abi:STRUCT]::foo[abi:FOO]()",
+
"ns::NamespaceStruct[abi:NAMESPACE_STRUCT]::foo[abi:FOO]()",
+ "ns::foo[abi:NAMESPACE_FOO]()",
+ "TemplateStruct[abi:TEMPLATE_STRUCT]<int>::foo[abi:FOO]()",
+ "void
TemplateStruct[abi:TEMPLATE_STRUCT]<int>::foo[abi:FOO_TEMPLATE]<long>(long)",
+ },
+ ),
+ Case(
+ name="StaticStruct::foo",
+
matches={"StaticStruct[abi:STATIC_STRUCT]::foo[abi:FOO][abi:FOO2]()"},
+ ),
+ Case(name="Struct::foo",
matches={"Struct[abi:STRUCT]::foo[abi:FOO]()"}),
+ Case(
+ name="TemplateStruct::foo",
+ matches={
+ "TemplateStruct[abi:TEMPLATE_STRUCT]<int>::foo[abi:FOO]()",
+ "void
TemplateStruct[abi:TEMPLATE_STRUCT]<int>::foo[abi:FOO_TEMPLATE]<long>(long)",
+ },
+ ),
+ Case(name="ns::foo", matches={"ns::foo[abi:NAMESPACE_FOO]()"}),
+ # operators
+ Case(
+ name="operator<",
+ matches={
+ "Struct[abi:STRUCT]::operator<(int)",
+ "bool
TemplateStruct[abi:TEMPLATE_STRUCT]<int>::operator<[abi:OPERATOR]<int>(int)",
+ },
+ ),
+ Case(
+ name="TemplateStruct::operator<<",
+ matches={
+ "bool
TemplateStruct[abi:TEMPLATE_STRUCT]<int>::operator<<[abi:operator]<int>(int)"
+ },
+ ),
+ Case(
+ name="operator<<",
+ matches={
+ "bool
TemplateStruct[abi:TEMPLATE_STRUCT]<int>::operator<<[abi:operator]<int>(int)"
+ },
+ ),
+ Case(
+ name="operator==",
+ matches={"operator==[abi:OPERATOR](wrap_int const&, wrap_int
const&)"},
+ ),
+ ]
+
+ for case in test_cases:
+ self.verify_breakpoint_names(target, case)
diff --git a/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/main.cpp
b/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/main.cpp
new file mode 100644
index 0000000000000..34671d94fba15
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/main.cpp
@@ -0,0 +1,118 @@
+
+struct wrap_int {
+ int inner{};
+};
+[[gnu::abi_tag("OPERATOR")]]
+bool operator==(const wrap_int & /*unused*/, const wrap_int & /*unused*/) {
+ return true;
+}
+
+[[gnu::abi_tag("FOO")]]
+static int foo() {
+ return 0;
+}
+
+struct [[gnu::abi_tag("STATIC_STRUCT")]] StaticStruct {
+ [[gnu::abi_tag("FOO", "FOO2")]]
+ static int foo() {
+ return 10;
+ };
+};
+
+struct [[gnu::abi_tag("STRUCT")]] Struct {
+ [[gnu::abi_tag("FOO")]]
+ int foo() {
+ return 10;
+ };
+
+ bool operator<(int val) { return false; }
+
+ [[gnu::abi_tag("ops")]]
+ unsigned int operator[](int val) {
+ return val;
+ }
+
+ [[gnu::abi_tag("FOO")]]
+ ~Struct() {}
+};
+
+namespace ns {
+struct [[gnu::abi_tag("NAMESPACE_STRUCT")]] NamespaceStruct {
+ [[gnu::abi_tag("FOO")]]
+ int foo() {
+ return 10;
+ }
+};
+
+[[gnu::abi_tag("NAMESPACE_FOO")]]
+void end_with_foo() {}
+
+[[gnu::abi_tag("NAMESPACE_FOO")]]
+void foo() {}
+} // namespace ns
+
+template <typename Type>
+class [[gnu::abi_tag("TEMPLATE_STRUCT")]] TemplateStruct {
+
+ [[gnu::abi_tag("FOO")]]
+ void foo() {
+ int something = 32;
+ }
+
+public:
+ void foo_pub() { this->foo(); }
+
+ template <typename ArgType>
+ [[gnu::abi_tag("FOO_TEMPLATE")]]
+ void foo(ArgType val) {
+ val = 20;
+ }
+
+ template <typename Ty>
+ [[gnu::abi_tag("OPERATOR")]]
+ bool operator<(Ty val) {
+ return false;
+ }
+
+ template <typename Ty>
+ [[gnu::abi_tag("operator")]]
+ bool operator<<(Ty val) {
+ return false;
+ }
+};
+
+int main() {
+ // standalone
+ const int res1 = foo();
+
+ // static
+ const int res2 = StaticStruct::foo();
+
+ // normal
+ {
+ Struct normal;
+ const int res3 = normal.foo();
+ const bool operator_lessthan_res = normal < 10;
+ }
+
+ // namespace
+ ns::NamespaceStruct ns_struct;
+ const int res4 = ns_struct.foo();
+
+ ns::foo();
+
+ // template struct
+ TemplateStruct<int> t_struct;
+ t_struct.foo_pub();
+ const long into_param = 0;
+ t_struct.foo(into_param);
+
+ const bool t_ops_lessthan = t_struct < 20;
+ const bool t_ops_leftshift = t_struct << 30;
+
+ // standalone operator
+ wrap_int lhs;
+ wrap_int rhs;
+ const bool res6 = lhs == rhs;
+ return 0;
+}
diff --git a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
index c60a90f3e98ee..5b4f36b6e548d 100644
--- a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
+++ b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
@@ -272,22 +272,22 @@ TEST(CPlusPlusLanguage, AbiContainsPath) {
Match matches;
NoMatch no_matches;
};
- // NOTE: for future reference to prevent testing unreachable states. or
making
- // the matcher match unreachable states.
- // see specification for completeness. TODO: add specification
- // - abi tags can are ordered. i.e func[abi:TAG1][abi:TAG2] is valid but
- // func[abi:TAG2][abi:TAG1] is not
- // - we do not set invalid function as this get filtered out during the
module
- // lookup.
+
+ // NOTE: Constraints to avoid testing/matching unreachable states:
+ // (see specification for more information
+ // https://clang.llvm.org/docs/ItaniumMangleAbiTags.html)
+ // - ABI tags must appear in a specific order: func[abi:TAG1][abi:TAG2] is
+ // valid, but func[abi:TAG2][abi:TAG1] is not.
+ // - Invalid functions are filtered during module lookup and not set here.
const std::initializer_list<TestCase> test_cases = {
+ // function with abi_tag
TestCase{"func[abi:TAG1][abi:TAG2]()",
Match{"func", "func[abi:TAG1]", "func[abi:TAG2]"},
NoMatch{"func[abi:WRONG_TAG]"}},
TestCase{"Foo::Bar::baz::func(Ball[abi:CTX_TAG]::Val )",
Match{"func", "baz::func", "Bar::baz::func"},
NoMatch{"baz", "baz::fun"}},
- // function with abi_tag
TestCase{"second::first::func[abi:FUNC_TAG]()",
Match{"func", "func[abi:FUNC_TAG]", "first::func",
"first::func[abi:FUNC_TAG]"},
@@ -331,13 +331,10 @@ TEST(CPlusPlusLanguage, AbiContainsPath) {
// multiple two context twice with abi_tag
TestCase{"first[abi:CTX_TAG1][abi:CTX_TAG2]::func[abi:FUNC_TAG]::first::"
"func(int)",
- Match{"first::func", "func"},
- NoMatch{"first[abi:CTX_TAG1]", "func[abi:FUNC_TAG]"}},
- // multiple abi tag in context and template
- //
TestCase{"second::first[abi:CTX_TAG]<SomeClass>::func[abi:FUNC_TAG]()",
-
+ Match{"first::func", "func", "first::func::first::func"},
+ NoMatch{"first[abi:CTX_TAG1]", "func[abi:FUNC_TAG]",
+ "func::first", "first::func::first"}},
// operator overload
- // aa::bb::wrap_int
TestCase{"Ball[abi:CTX_TAG]<int>::operator<[abi:OPERATOR]<int>(int)",
Match{"operator<", "operator<[abi:OPERATOR]",
"Ball::operator<"},
NoMatch{"operator<<", "Ball::operator<<"}},
@@ -354,13 +351,6 @@ TEST(CPlusPlusLanguage, AbiContainsPath) {
NoMatch{"operator<", "operator<<<long>",
"Ball<long>::operator<<<int>",
"operator<<[abi:operator]"}},
-
- // double context before matching path
- // TODO: context in anonymous namespace
-
- // no abi tag
- // with return value.
- // with templated return value.
};
for (const auto &[input, matches, no_matches] : test_cases) {
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits