630 changes: 630 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp

Large diffs are not rendered by default.

179 changes: 179 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//===-- CPlusPlusNameParser.h -----------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_CPlusPlusNameParser_h_
#define liblldb_CPlusPlusNameParser_h_

// C Includes
// C++ Includes

// Other libraries and framework includes
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"

// Project includes
#include "lldb/Utility/ConstString.h"
#include "lldb/lldb-private.h"

namespace lldb_private {

// Helps to validate and obtain various parts of C++ definitions.
class CPlusPlusNameParser {
public:
CPlusPlusNameParser(llvm::StringRef text) : m_text(text) { ExtractTokens(); }

struct ParsedName {
llvm::StringRef basename;
llvm::StringRef context;
};

struct ParsedFunction {
ParsedName name;
llvm::StringRef arguments;
llvm::StringRef qualifiers;
};

// Treats given text as a function definition and parses it.
// Function definition might or might not have a return type and this should
// change parsing result.
// Examples:
// main(int, chat const*)
// T fun(int, bool)
// std::vector<int>::push_back(int)
// int& map<int, pair<short, int>>::operator[](short) const
// int (*get_function(const chat *))()
llvm::Optional<ParsedFunction> ParseAsFunctionDefinition();

// Treats given text as a potentially nested name of C++ entity (function,
// class, field) and parses it.
// Examples:
// main
// fun
// std::vector<int>::push_back
// map<int, pair<short, int>>::operator[]
// func<C>(int, C&)::nested_class::method
llvm::Optional<ParsedName> ParseAsFullName();

private:
// A C++ definition to parse.
llvm::StringRef m_text;
// Tokens extracted from m_text.
llvm::SmallVector<clang::Token, 30> m_tokens;
// Index of the next token to look at from m_tokens.
size_t m_next_token_index = 0;

// Range of tokens saved in m_next_token_index.
struct Range {
size_t begin_index = 0;
size_t end_index = 0;

Range() {}
Range(size_t begin, size_t end) : begin_index(begin), end_index(end) {
assert(end >= begin);
}

size_t size() const { return end_index - begin_index; }

bool empty() const { return size() == 0; }
};

struct ParsedNameRanges {
Range basename_range;
Range context_range;
};

// Bookmark automatically restores parsing position (m_next_token_index)
// when destructed unless it's manually removed with Remove().
class Bookmark {
public:
Bookmark(size_t &position)
: m_position(position), m_position_value(position) {}
Bookmark(const Bookmark &) = delete;
Bookmark(Bookmark &&b)
: m_position(b.m_position), m_position_value(b.m_position_value),
m_restore(b.m_restore) {
b.Remove();
}
Bookmark &operator=(Bookmark &&) = delete;
Bookmark &operator=(const Bookmark &) = delete;

void Remove() { m_restore = false; }
size_t GetSavedPosition() { return m_position_value; }
~Bookmark() {
if (m_restore) {
m_position = m_position_value;
}
}

private:
size_t &m_position;
size_t m_position_value;
bool m_restore = true;
};

bool HasMoreTokens();
void Advance();
void TakeBack();
bool ConsumeToken(clang::tok::TokenKind kind);
template <typename... Ts> bool ConsumeToken(Ts... kinds);
Bookmark SetBookmark();
size_t GetCurrentPosition();
clang::Token &Peek();
bool ConsumeBrackets(clang::tok::TokenKind left, clang::tok::TokenKind right);

llvm::Optional<ParsedFunction> ParseFunctionImpl(bool expect_return_type);

// Parses functions returning function pointers 'string (*f(int x))(float y)'
llvm::Optional<ParsedFunction> ParseFuncPtr(bool expect_return_type);

// Consumes function arguments enclosed within '(' ... ')'
bool ConsumeArguments();

// Consumes template arguments enclosed within '<' ... '>'
bool ConsumeTemplateArgs();

// Consumes '(anonymous namespace)'
bool ConsumeAnonymousNamespace();

// Consumes operator declaration like 'operator *' or 'operator delete []'
bool ConsumeOperator();

// Skips 'const' and 'volatile'
void SkipTypeQualifiers();

// Skips 'const', 'volatile', '&', '&&' in the end of the function.
void SkipFunctionQualifiers();

// Consumes built-in types like 'int' or 'unsigned long long int'
bool ConsumeBuiltinType();

// Consumes types defined via decltype keyword.
bool ConsumeDecltype();

// Skips 'const' and 'volatile'
void SkipPtrsAndRefs();

// Consumes things like 'const * const &'
bool ConsumePtrsAndRefs();

// Consumes full type name like 'Namespace::Class<int>::Method()::InnerClass'
bool ConsumeTypename();

llvm::Optional<ParsedNameRanges> ParseFullNameImpl();
llvm::StringRef GetTextForRange(const Range &range);

// Populate m_tokens by calling clang lexer on m_text.
void ExtractTokens();
};

} // namespace lldb_private

#endif // liblldb_CPlusPlusNameParser_h_
133 changes: 123 additions & 10 deletions lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,148 @@
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "gtest/gtest.h"

#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h"

using namespace lldb_private;

TEST(CPlusPlusLanguage, MethodName) {
TEST(CPlusPlusLanguage, MethodNameParsing) {
struct TestCase {
std::string input;
std::string context, basename, arguments, qualifiers, scope_qualified_name;
};

TestCase test_cases[] = {
{"foo::bar(baz)", "foo", "bar", "(baz)", "", "foo::bar"},
{"main(int, char *[]) ", "", "main", "(int, char *[])", "", "main"},
{"foo::bar(baz) const", "foo", "bar", "(baz)", "const", "foo::bar"},
{"foo::~bar(baz)", "foo", "~bar", "(baz)", "", "foo::~bar"},
{"a::b::c::d(e,f)", "a::b::c", "d", "(e,f)", "", "a::b::c::d"},
{"void f(int)", "", "f", "(int)", "", "f"},

// Operators
{"std::basic_ostream<char, std::char_traits<char> >& "
"std::operator<<<std::char_traits<char> >"
"(std::basic_ostream<char, std::char_traits<char> >&, char const*)",
"std", "operator<<<std::char_traits<char> >",
"(std::basic_ostream<char, std::char_traits<char> >&, char const*)", "",
"std::operator<<<std::char_traits<char> >"}};
"std::operator<<<std::char_traits<char> >"},
{"operator delete[](void*, clang::ASTContext const&, unsigned long)", "",
"operator delete[]", "(void*, clang::ASTContext const&, unsigned long)",
"", "operator delete[]"},
{"llvm::Optional<clang::PostInitializer>::operator bool() const",
"llvm::Optional<clang::PostInitializer>", "operator bool", "()", "const",
"llvm::Optional<clang::PostInitializer>::operator bool"},
{"(anonymous namespace)::FactManager::operator[](unsigned short)",
"(anonymous namespace)::FactManager", "operator[]", "(unsigned short)",
"", "(anonymous namespace)::FactManager::operator[]"},
{"const int& std::map<int, pair<short, int>>::operator[](short) const",
"std::map<int, pair<short, int>>", "operator[]", "(short)", "const",
"std::map<int, pair<short, int>>::operator[]"},
{"CompareInsn::operator()(llvm::StringRef, InsnMatchEntry const&)",
"CompareInsn", "operator()", "(llvm::StringRef, InsnMatchEntry const&)",
"", "CompareInsn::operator()"},
{"llvm::Optional<llvm::MCFixupKind>::operator*() const &",
"llvm::Optional<llvm::MCFixupKind>", "operator*", "()", "const &",
"llvm::Optional<llvm::MCFixupKind>::operator*"},
// Internal classes
{"operator<<(Cls, Cls)::Subclass::function()",
"operator<<(Cls, Cls)::Subclass", "function", "()", "",
"operator<<(Cls, Cls)::Subclass::function"},
{"SAEC::checkFunction(context&) const::CallBack::CallBack(int)",
"SAEC::checkFunction(context&) const::CallBack", "CallBack", "(int)", "",
"SAEC::checkFunction(context&) const::CallBack::CallBack"},
// Anonymous namespace
{"XX::(anonymous namespace)::anon_class::anon_func() const",
"XX::(anonymous namespace)::anon_class", "anon_func", "()", "const",
"XX::(anonymous namespace)::anon_class::anon_func"},

// Function pointers
{"string (*f(vector<int>&&))(float)", "", "f", "(vector<int>&&)", "",
"f"},
{"void (*&std::_Any_data::_M_access<void (*)()>())()", "std::_Any_data",
"_M_access<void (*)()>", "()", "",
"std::_Any_data::_M_access<void (*)()>"},
{"void (*(*(*(*(*(*(*(* const&func1(int))())())())())())())())()", "",
"func1", "(int)", "", "func1"},

// Decltype
{"decltype(nullptr)&& std::forward<decltype(nullptr)>"
"(std::remove_reference<decltype(nullptr)>::type&)",
"std", "forward<decltype(nullptr)>",
"(std::remove_reference<decltype(nullptr)>::type&)", "",
"std::forward<decltype(nullptr)>"},

// Templates
{"void llvm::PM<llvm::Module, llvm::AM<llvm::Module>>::"
"addPass<llvm::VP>(llvm::VP)",
"llvm::PM<llvm::Module, llvm::AM<llvm::Module>>", "addPass<llvm::VP>",
"(llvm::VP)", "",
"llvm::PM<llvm::Module, llvm::AM<llvm::Module>>::"
"addPass<llvm::VP>"},
{"void std::vector<Class, std::allocator<Class> >"
"::_M_emplace_back_aux<Class const&>(Class const&)",
"std::vector<Class, std::allocator<Class> >",
"_M_emplace_back_aux<Class const&>", "(Class const&)", "",
"std::vector<Class, std::allocator<Class> >::"
"_M_emplace_back_aux<Class const&>"},
{"unsigned long llvm::countTrailingOnes<unsigned int>"
"(unsigned int, llvm::ZeroBehavior)",
"llvm", "countTrailingOnes<unsigned int>",
"(unsigned int, llvm::ZeroBehavior)", "",
"llvm::countTrailingOnes<unsigned int>"},
{"std::enable_if<(10u)<(64), bool>::type llvm::isUInt<10u>(unsigned "
"long)",
"llvm", "isUInt<10u>", "(unsigned long)", "", "llvm::isUInt<10u>"},
{"f<A<operator<(X,Y)::Subclass>, sizeof(B)<sizeof(C)>()", "",
"f<A<operator<(X,Y)::Subclass>, sizeof(B)<sizeof(C)>", "()", "",
"f<A<operator<(X,Y)::Subclass>, sizeof(B)<sizeof(C)>"}};

for (const auto &test : test_cases) {
CPlusPlusLanguage::MethodName method(ConstString(test.input));
EXPECT_TRUE(method.IsValid());
EXPECT_EQ(test.context, method.GetContext());
EXPECT_EQ(test.basename, method.GetBasename());
EXPECT_EQ(test.arguments, method.GetArguments());
EXPECT_EQ(test.qualifiers, method.GetQualifiers());
EXPECT_EQ(test.scope_qualified_name, method.GetScopeQualifiedName());
EXPECT_TRUE(method.IsValid()) << test.input;
if (method.IsValid()) {
EXPECT_EQ(test.context, method.GetContext().str());
EXPECT_EQ(test.basename, method.GetBasename().str());
EXPECT_EQ(test.arguments, method.GetArguments().str());
EXPECT_EQ(test.qualifiers, method.GetQualifiers().str());
EXPECT_EQ(test.scope_qualified_name, method.GetScopeQualifiedName());
}
}
}

TEST(CPlusPlusLanguage, ExtractContextAndIdentifier) {
struct TestCase {
std::string input;
std::string context, basename;
};

TestCase test_cases[] = {
{"main", "", "main"},
{"foo01::bar", "foo01", "bar"},
{"foo::~bar", "foo", "~bar"},
{"std::vector<int>::push_back", "std::vector<int>", "push_back"},
{"operator<<(Cls, Cls)::Subclass::function",
"operator<<(Cls, Cls)::Subclass", "function"},
{"std::vector<Class, std::allocator<Class>>"
"::_M_emplace_back_aux<Class const&>",
"std::vector<Class, std::allocator<Class>>",
"_M_emplace_back_aux<Class const&>"}};

llvm::StringRef context, basename;
for (const auto &test : test_cases) {
EXPECT_TRUE(CPlusPlusLanguage::ExtractContextAndIdentifier(
test.input.c_str(), context, basename));
EXPECT_EQ(test.context, context.str());
EXPECT_EQ(test.basename, basename.str());
}

EXPECT_FALSE(CPlusPlusLanguage::ExtractContextAndIdentifier("void", context,
basename));
EXPECT_FALSE(
CPlusPlusLanguage::ExtractContextAndIdentifier("321", context, basename));
EXPECT_FALSE(
CPlusPlusLanguage::ExtractContextAndIdentifier("", context, basename));
EXPECT_FALSE(CPlusPlusLanguage::ExtractContextAndIdentifier(
"selector:", context, basename));
}