llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Ebuka Ezike (da-viper)

<details>
<summary>Changes</summary>

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. 

---

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
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]

``````````

</details>


https://github.com/llvm/llvm-project/pull/170527
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to