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