Skip to content

Conversation

@da-viper
Copy link
Contributor

@da-viper da-viper commented Dec 3, 2025

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:

[[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.

@llvmbot
Copy link
Member

llvmbot commented Dec 3, 2025

@llvm/pr-subscribers-lldb

Author: Ebuka Ezike (da-viper)

Changes

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:

[[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.


Patch is 23.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/170527.diff

6 Files Affected:

  • (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+154-30)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h (+36-12)
  • (added) lldb/test/API/functionalities/breakpoint/cpp/abi_tag/Makefile (+3)
  • (added) lldb/test/API/functionalities/breakpoint/cpp/abi_tag/TestCPPBreakpointLocationsAbiTag.py (+90)
  • (added) lldb/test/API/functionalities/breakpoint/cpp/abi_tag/main.cpp (+118)
  • (modified) lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp (+115-4)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index a3624accf9b5a..fb5d082090296 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"
 
@@ -538,15 +539,156 @@ 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);
+    }
+
+    // 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 == ']')
+      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 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);
+
+    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 full_name cursor (consume the
+    // current full_namecontext) 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 +706,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 +718,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..a394205e6ec15 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   |
+    ///
+    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.
+    ///
+    /// \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
+    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 41df35f67a790..5b4f36b6e548d 100644
--- a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
+++ b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
@@ -262,6 +262,114 @@ 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: 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"}},
+      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:...
[truncated]

@github-actions
Copy link

github-actions bot commented Dec 3, 2025

✅ With the latest revision this PR passed the Python code formatter.


// skip template_tags.
if (options.skip_templates && in_char == open_angle &&
ma_char != open_angle) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this becomes more readable if you just replace open_angle with '<'

bool CPlusPlusLanguage::CxxMethodName::NameMatches(llvm::StringRef full_name,
llvm::StringRef pattern,
MatchOptions options) {
constexpr llvm::StringRef abi_prefix = "[abi:";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we simplify the changes to this code by splitting the "full_name" like:

// Remove any encoded ABI tags from the C++ name before looking for matches
full_name = full_name.split("[abi::").first;

Then nothing else in this function needs to change right? Or are we trying to do something with the ABI name later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the abi_tag may be in the template i.e Module<SomeType[abi:TAG]>::find and will fail.
and we also compare the tag names in case in the future we add support for breakpoint set --name foo[abi:TAG]

Copy link
Member

@Michael137 Michael137 Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remind me why we didnt modify the CPlusPlusNameParser? Not saying we should, but it does parse/skip ABI tags to some extent already

Alternatively, could we search and replace all ABI tags in the name with a "simple" regex? (ignoring the question of whether we want to support setting breakpoint by specific tags, which i have some doubts about). We might be able to get away with a regex because it isnt ever valid c++ syntax. If it does clash, we could change the demangler to output something even less likely to clash with real c++. Just some ideas to avoid hand-parsing these tags.

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bigger discussion (out of scope of this PR) is whether we want to match breakpoints by using demangled names in the first place. The ABI tags are only an issue because the demangler prints them, but they aren't part of the DWARF names. There are other attributes that affect mangling in the same way (but much less wide-spread). So what if we matched against debug-info names (when they are available). Maybe we are already matching by debug-info names, but then why is the demangled name blocking the match here. I'd have to remind myself of that detail

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants