diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index a3624accf9b5a..6985325b3008a 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -16,6 +16,7 @@ #include #include +#include "llvm/ADT/Sequence.h" #include "llvm/ADT/StringRef.h" #include "llvm/Demangle/ItaniumDemangle.h" @@ -538,15 +539,173 @@ 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 = ']'; + 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 == '<' && ma_char != '<') { + 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 == '<') + depth++; + else if (cur == '>') { + depth--; + + if (depth == 0) { + found_end = true; + break; + } + } + } + + if (found_end) { + f_idx = tmp_idx + 1; + continue; + } + } + + // 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(); +} + +/// Extracts the next context component from a C++ scope resolution string. +/// +/// This function parses a C++ qualified name (e.g., "ns::Class::method") +/// from right to left, extracting one scope context at a time. It handles +/// nested templates, abi_tags and array brackets while searching +/// for scope resolution operators (::). +/// \param context The full context string to parse (e.g., +/// "std::vector::size") +/// \param end_pos [in,out] The position to start searching backwards from. On +/// return, contains the position of the previous scope +/// separator (::), or llvm::StringRef::npos if no more +/// components exist. +/// +/// Example: +/// llvm::StringRef scope = "ns::inner::Class"; +/// size_t pos = scope.size(); +/// +/// ctx1 = NextContext(context, pos); // returns "Class", pos = 9 +/// ctx2 = NextContext(context, pos); // returns "inner", pos = 2 +/// ctx3 = NextContext(context, pos); // returns "ns", pos = StringRef::npos +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(end_pos) - 1; + int depth = 0; + + if (end >= static_cast(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); + } - return basename; + // 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 pos (consume the + // current full_name context) and reset the pat_pos to the beginning. + full_pos = next_full_pos; + pat_pos = 0; + } + + return false; } bool CPlusPlusLanguage::CxxMethodName::ContainsPath(llvm::StringRef path) { @@ -564,21 +723,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 - // 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 - // identifier : func - // - // 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 +735,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() | func | - // | void func>() | 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() | vector | skip_template | true | + // | foo[abi:aTag]() | foo | skip_template_and_tag | true | + // | MyClass::foo() | OClass::foo | | false | + // | bar::foo | 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..be858fbbfdc76 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/cpp/abi_tag/TestCPPBreakpointLocationsAbiTag.py @@ -0,0 +1,89 @@ +""" +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]::foo[abi:FOO]()", + "void TemplateStruct[abi:TEMPLATE_STRUCT]::foo[abi:FOO_TEMPLATE](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]::foo[abi:FOO]()", + "void TemplateStruct[abi:TEMPLATE_STRUCT]::foo[abi:FOO_TEMPLATE](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]::operator<[abi:OPERATOR](int)", + }, + ), + Case( + name="TemplateStruct::operator<<", + matches={ + "bool TemplateStruct[abi:TEMPLATE_STRUCT]::operator<<[abi:operator](int)" + }, + ), + Case( + name="operator<<", + matches={ + "bool TemplateStruct[abi:TEMPLATE_STRUCT]::operator<<[abi:operator](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 +class [[gnu::abi_tag("TEMPLATE_STRUCT")]] TemplateStruct { + + [[gnu::abi_tag("FOO")]] + void foo() { + int something = 32; + } + +public: + void foo_pub() { this->foo(); } + + template + [[gnu::abi_tag("FOO_TEMPLATE")]] + void foo(ArgType val) { + val = 20; + } + + template + [[gnu::abi_tag("OPERATOR")]] + bool operator<(Ty val) { + return false; + } + + template + [[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 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; + using NoMatch = std::initializer_list; + + 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 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]()", + Match{ + "func", + "func[abi:FUNC_TAG]", + "func[abi:FUNC_TAG]", + "func", + }, + NoMatch{"nn::func"}}, + TestCase{"Ball[abi:STRUCT_TAG] " + "first::func[abi:FUNC_TAG]>()", + 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]::func[abi:FUNC_TAG]()", + Match{"first::func", "first[abi:CTX_TAG]::func", + "first::func[abi:FUNC_TAG]", "first::func", + "first[abi:CTX_TAG]::func", + "first[abi:CTX_TAG]::func[abi:FUNC_TAG]", + "second::first::func"}, + NoMatch{"first::func[abi:CTX_TAG]", "first[abi:FUNC_TAG]::func", + "first::func", + "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", "first::func::first::func"}, + NoMatch{"first[abi:CTX_TAG1]", "func[abi:FUNC_TAG]", + "func::first", "first::func::first"}}, + // operator overload + TestCase{"Ball[abi:CTX_TAG]::operator<[abi:OPERATOR](int)", + Match{"operator<", "operator<[abi:OPERATOR]", "Ball::operator<"}, + NoMatch{"operator<<", "Ball::operator<<"}}, + TestCase{ + "Ball[abi:CTX_TAG]::operator[][abi:OPERATOR](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]::operator<<[abi:OPERATOR](int)", + Match{"Ball::operator<<", "operator<<", + "operator<<[abi:OPERATOR]", "operator<<", + "Ball[abi:CTX_TAG]::operator<<[abi:OPERATOR]"}, + NoMatch{"operator<", "operator<<", + "Ball::operator<<", + "operator<<[abi:operator]"}}, + }; + + 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 +383,9 @@ TEST(CPlusPlusLanguage, ContainsPath) { CPlusPlusLanguage::CxxMethodName reference_6(ConstString( "bar::baz::operator<<, Type>>()")); + 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 +395,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 +479,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>", context, basename));