diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.h b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.h --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.h +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.h @@ -117,6 +117,7 @@ void Advance(); void TakeBack(); bool ConsumeToken(clang::tok::TokenKind kind); + template bool ConsumeToken(Ts... kinds); Bookmark SetBookmark(); size_t GetCurrentPosition(); @@ -164,6 +165,16 @@ // Consumes full type name like 'Namespace::Class::Method()::InnerClass' bool ConsumeTypename(); + /// Consumes ABI tags enclosed within '[abi:' ... ']' + /// + /// Since there is no restriction on what the ABI tag + /// string may contain, this API supports parsing a small + /// set of special characters. + /// + /// The following regex describes the set of supported characters: + /// [A-Za-z,.\s\d]+ + bool ConsumeAbiTag(); + llvm::Optional ParseFullNameImpl(); llvm::StringRef GetTextForRange(const Range &range); diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp @@ -226,9 +226,15 @@ Advance(); break; case tok::l_square: - if (!ConsumeBrackets(tok::l_square, tok::r_square)) + // Handle templates tagged with an ABI tag. + // An example demangled/prettified version is: + // func[abi:tag1][abi:tag2](int) + if (ConsumeAbiTag()) + can_open_template = true; + else if (ConsumeBrackets(tok::l_square, tok::r_square)) + can_open_template = false; + else return false; - can_open_template = false; break; case tok::l_paren: if (!ConsumeArguments()) @@ -249,6 +255,32 @@ return true; } +bool CPlusPlusNameParser::ConsumeAbiTag() { + Bookmark start_position = SetBookmark(); + if (!ConsumeToken(tok::l_square)) + return false; + + if (HasMoreTokens() && Peek().is(tok::raw_identifier) && + Peek().getRawIdentifier() == "abi") + Advance(); + else + return false; + + if (!ConsumeToken(tok::colon)) + return false; + + // Consume the actual tag string (and allow some special characters) + while (ConsumeToken(tok::raw_identifier, tok::comma, tok::period, + tok::numeric_constant)) + ; + + if (!ConsumeToken(tok::r_square)) + return false; + + start_position.Remove(); + return true; +} + bool CPlusPlusNameParser::ConsumeAnonymousNamespace() { Bookmark start_position = SetBookmark(); if (!ConsumeToken(tok::l_paren)) { @@ -519,6 +551,22 @@ Advance(); state = State::AfterIdentifier; break; + case tok::l_square: { + // Handles types or functions that were tagged + // with, e.g., + // [[gnu::abi_tag("tag1","tag2")]] func() + // and demangled/prettified into: + // func[abi:tag1][abi:tag2]() + + // ABI tags only appear after a method or type name + const bool valid_state = + state == State::AfterIdentifier || state == State::AfterOperator; + if (!valid_state || !ConsumeAbiTag()) { + continue_parsing = false; + } + + break; + } case tok::l_paren: { if (state == State::Beginning || state == State::AfterTwoColons) { // (anonymous namespace) diff --git a/lldb/test/API/functionalities/step-avoids-regexp/TestStepAvoidsRegexp.py b/lldb/test/API/functionalities/step-avoids-regexp/TestStepAvoidsRegexp.py --- a/lldb/test/API/functionalities/step-avoids-regexp/TestStepAvoidsRegexp.py +++ b/lldb/test/API/functionalities/step-avoids-regexp/TestStepAvoidsRegexp.py @@ -38,14 +38,11 @@ self.thread.StepInto() self.hit_correct_function("main") - @skipIfWindows - @skipIf(compiler="clang", compiler_version=['<', '11.0']) - @expectedFailureAll(bugnumber="rdar://100645742") - def test_step_avoid_regex_abi_tagged_template(self): - """Tests stepping into an ABI tagged function that matches the avoid regex""" - self.build() - (_, _, self.thread, _) = lldbutil.run_to_source_breakpoint(self, "with_tag_template", lldb.SBFileSpec('main.cpp')) - # Try to step into ignore::with_tag_template self.thread.StepInto() self.hit_correct_function("main") + + # Step into with_tag_template_returns_ignore which is outside the 'ignore::' + # namespace but returns a type from 'ignore::' + self.thread.StepInto() + self.hit_correct_function("with_tag_template_returns_ignore") diff --git a/lldb/test/API/functionalities/step-avoids-regexp/main.cpp b/lldb/test/API/functionalities/step-avoids-regexp/main.cpp --- a/lldb/test/API/functionalities/step-avoids-regexp/main.cpp +++ b/lldb/test/API/functionalities/step-avoids-regexp/main.cpp @@ -1,4 +1,6 @@ namespace ignore { +struct Dummy {}; + template auto auto_ret(T x) { return 0; } [[gnu::abi_tag("test")]] int with_tag() { return 0; } template [[gnu::abi_tag("test")]] int with_tag_template() { @@ -8,9 +10,15 @@ template decltype(auto) decltype_auto_ret(T x) { return 0; } } // namespace ignore +template +[[gnu::abi_tag("test")]] ignore::Dummy with_tag_template_returns_ignore(T x) { + return {}; +} + int main() { auto v1 = ignore::auto_ret(5); auto v2 = ignore::with_tag(); auto v3 = ignore::decltype_auto_ret(5); auto v4 = ignore::with_tag_template(); + auto v5 = with_tag_template_returns_ignore(5); } diff --git a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp --- a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp +++ b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp @@ -114,13 +114,74 @@ {"llvm::Optional::operator*() const volatile &&", "llvm::Optional", "operator*", "()", "const volatile &&", "llvm::Optional::operator*"}, + {"void foo>()", "", "foo>", "()", "", + "foo>"}, // auto return type {"auto std::test_return_auto() const", "std", "test_return_auto", "()", "const", "std::test_return_auto"}, {"decltype(auto) std::test_return_auto(int) const", "std", - "test_return_auto", "(int)", "const", - "std::test_return_auto"}}; + "test_return_auto", "(int)", "const", "std::test_return_auto"}, + + // abi_tag on class method + {"v1::v2::Dummy[abi:c1][abi:c2]> " + "v1::v2::Dummy[abi:c1][abi:c2]>" + "::method2>>(int, v1::v2::Dummy) const &&", + // Context + "v1::v2::Dummy[abi:c1][abi:c2]>", + // Basename + "method2>>", + // Args, qualifiers + "(int, v1::v2::Dummy)", "const &&", + // Full scope-qualified name without args + "v1::v2::Dummy[abi:c1][abi:c2]>" + "::method2>>"}, + + // abi_tag on free function and template argument + {"v1::v2::Dummy[abi:c1][abi:c2]> " + "v1::v2::with_tag_in_ns[abi:f1][abi:f2]>>(int, v1::v2::Dummy) const " + "&&", + // Context + "v1::v2", + // Basename + "with_tag_in_ns[abi:f1][abi:f2]>>", + // Args, qualifiers + "(int, v1::v2::Dummy)", "const &&", + // Full scope-qualified name without args + "v1::v2::with_tag_in_ns[abi:f1][abi:f2]>>"}, + + // abi_tag with special characters + {"auto ns::with_tag_in_ns[abi:special tag,0.0][abi:special " + "tag,1.0]>" + "(float) const &&", + // Context + "ns", + // Basename + "with_tag_in_ns[abi:special tag,0.0][abi:special tag,1.0]>", + // Args, qualifiers + "(float)", "const &&", + // Full scope-qualified name without args + "ns::with_tag_in_ns[abi:special tag,0.0][abi:special " + "tag,1.0]>"}, + + // abi_tag on operator overloads + {"std::__1::error_code::operator bool[abi:v160000]() const", + "std::__1::error_code", "operator bool[abi:v160000]", "()", "const", + "std::__1::error_code::operator bool[abi:v160000]"}, + + {"auto ns::foo::operator[][abi:v160000](size_t) const", "ns::foo", + "operator[][abi:v160000]", "(size_t)", "const", + "ns::foo::operator[][abi:v160000]"}, + + {"auto Foo[abi:abc]::operator<<>(int) &", + "Foo[abi:abc]", "operator<<>", "(int)", "&", + "Foo[abi:abc]::operator<<>"}}; for (const auto &test : test_cases) { CPlusPlusLanguage::MethodName method(ConstString(test.input)); @@ -135,6 +196,30 @@ } } +TEST(CPlusPlusLanguage, InvalidMethodNameParsing) { + // Tests that we correctly reject malformed function names + + std::string test_cases[] = { + "int Foo::operator[]<[10>()", + "Foo::operator bool[10]()", + "auto A::operator<=>[abi:tag]()", + "auto A::operator<<<(int)", + "auto A::operator>>>(int)", + "auto A::operator<<(int)", + "auto A::operator<<>(int)", + "auto A::foo[(int)", + "auto A::foo[](int)", + "auto A::foo[bar](int)", + "auto A::foo[abi](int)", + "auto A::foo[abi:(int)", + }; + + for (const auto &name : test_cases) { + CPlusPlusLanguage::MethodName method{ConstString(name)}; + EXPECT_FALSE(method.IsValid()) << name; + } +} + TEST(CPlusPlusLanguage, ContainsPath) { CPlusPlusLanguage::MethodName reference_1(ConstString("int foo::bar::func01(int a, double b)"));