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

Reply via email to