| //===- unittests/AST/ASTTraverserTest.h------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/ASTNodeTraverser.h" |
| #include "clang/AST/TextNodeDumper.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| using namespace clang::tooling; |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| |
| class NodeTreePrinter : public TextTreeStructure { |
| llvm::raw_ostream &OS; |
| |
| public: |
| NodeTreePrinter(llvm::raw_ostream &OS) |
| : TextTreeStructure(OS, /* showColors */ false), OS(OS) {} |
| |
| void Visit(const Decl *D) { |
| OS << D->getDeclKindName() << "Decl"; |
| if (auto *ND = dyn_cast<NamedDecl>(D)) { |
| OS << " '" << ND->getDeclName() << "'"; |
| } |
| } |
| |
| void Visit(const Stmt *S) { |
| OS << S->getStmtClassName(); |
| if (auto *E = dyn_cast<DeclRefExpr>(S)) { |
| OS << " '" << E->getDecl()->getDeclName() << "'"; |
| } |
| } |
| |
| void Visit(QualType QT) { |
| OS << "QualType " << QT.split().Quals.getAsString(); |
| } |
| |
| void Visit(const Type *T) { OS << T->getTypeClassName() << "Type"; } |
| |
| void Visit(const comments::Comment *C, const comments::FullComment *FC) { |
| OS << C->getCommentKindName(); |
| } |
| |
| void Visit(const CXXCtorInitializer *Init) { OS << "CXXCtorInitializer"; } |
| |
| void Visit(const Attr *A) { |
| switch (A->getKind()) { |
| #define ATTR(X) \ |
| case attr::X: \ |
| OS << #X; \ |
| break; |
| #include "clang/Basic/AttrList.inc" |
| } |
| OS << "Attr"; |
| } |
| |
| void Visit(const OMPClause *C) { OS << "OMPClause"; } |
| void Visit(const TemplateArgument &A, SourceRange R = {}, |
| const Decl *From = nullptr, const char *Label = nullptr) { |
| OS << "TemplateArgument"; |
| } |
| |
| template <typename... T> void Visit(T...) {} |
| }; |
| |
| class TestASTDumper : public ASTNodeTraverser<TestASTDumper, NodeTreePrinter> { |
| |
| NodeTreePrinter MyNodeRecorder; |
| |
| public: |
| TestASTDumper(llvm::raw_ostream &OS) : MyNodeRecorder(OS) {} |
| NodeTreePrinter &doGetNodeDelegate() { return MyNodeRecorder; } |
| }; |
| |
| template <typename... NodeType> std::string dumpASTString(NodeType &&... N) { |
| std::string Buffer; |
| llvm::raw_string_ostream OS(Buffer); |
| |
| TestASTDumper Dumper(OS); |
| |
| OS << "\n"; |
| |
| Dumper.Visit(std::forward<NodeType &&>(N)...); |
| |
| return OS.str(); |
| } |
| |
| template <typename... NodeType> |
| std::string dumpASTString(TraversalKind TK, NodeType &&... N) { |
| std::string Buffer; |
| llvm::raw_string_ostream OS(Buffer); |
| |
| TestASTDumper Dumper(OS); |
| Dumper.SetTraversalKind(TK); |
| |
| OS << "\n"; |
| |
| Dumper.Visit(std::forward<NodeType &&>(N)...); |
| |
| return OS.str(); |
| } |
| |
| const FunctionDecl *getFunctionNode(clang::ASTUnit *AST, |
| const std::string &Name) { |
| auto Result = ast_matchers::match(functionDecl(hasName(Name)).bind("fn"), |
| AST->getASTContext()); |
| EXPECT_EQ(Result.size(), 1u); |
| return Result[0].getNodeAs<FunctionDecl>("fn"); |
| } |
| |
| template <typename T> struct Verifier { |
| static void withDynNode(T Node, const std::string &DumpString) { |
| EXPECT_EQ(dumpASTString(DynTypedNode::create(Node)), DumpString); |
| } |
| }; |
| |
| template <typename T> struct Verifier<T *> { |
| static void withDynNode(T *Node, const std::string &DumpString) { |
| EXPECT_EQ(dumpASTString(DynTypedNode::create(*Node)), DumpString); |
| } |
| }; |
| |
| template <typename T> |
| void verifyWithDynNode(T Node, const std::string &DumpString) { |
| EXPECT_EQ(dumpASTString(Node), DumpString); |
| |
| Verifier<T>::withDynNode(Node, DumpString); |
| } |
| |
| TEST(Traverse, Dump) { |
| |
| auto AST = buildASTFromCode(R"cpp( |
| struct A { |
| int m_number; |
| |
| /// CTor |
| A() : m_number(42) {} |
| |
| [[nodiscard]] const int func() { |
| return 42; |
| } |
| |
| }; |
| |
| template<typename T> |
| struct templ |
| { |
| }; |
| |
| template<> |
| struct templ<int> |
| { |
| }; |
| |
| void parmvardecl_attr(struct A __attribute__((address_space(19)))*); |
| |
| )cpp"); |
| |
| const FunctionDecl *Func = getFunctionNode(AST.get(), "func"); |
| |
| verifyWithDynNode(Func, |
| R"cpp( |
| CXXMethodDecl 'func' |
| |-CompoundStmt |
| | `-ReturnStmt |
| | `-IntegerLiteral |
| `-WarnUnusedResultAttr |
| )cpp"); |
| |
| Stmt *Body = Func->getBody(); |
| |
| verifyWithDynNode(Body, |
| R"cpp( |
| CompoundStmt |
| `-ReturnStmt |
| `-IntegerLiteral |
| )cpp"); |
| |
| QualType QT = Func->getType(); |
| |
| verifyWithDynNode(QT, |
| R"cpp( |
| FunctionProtoType |
| `-QualType const |
| `-BuiltinType |
| )cpp"); |
| |
| const FunctionDecl *CTorFunc = getFunctionNode(AST.get(), "A"); |
| |
| verifyWithDynNode(CTorFunc->getType(), |
| R"cpp( |
| FunctionProtoType |
| `-BuiltinType |
| )cpp"); |
| |
| Attr *A = *Func->attr_begin(); |
| |
| { |
| std::string expectedString = R"cpp( |
| WarnUnusedResultAttr |
| )cpp"; |
| |
| EXPECT_EQ(dumpASTString(A), expectedString); |
| } |
| |
| auto *CTor = dyn_cast<CXXConstructorDecl>(CTorFunc); |
| const CXXCtorInitializer *Init = *CTor->init_begin(); |
| |
| verifyWithDynNode(Init, |
| R"cpp( |
| CXXCtorInitializer |
| `-IntegerLiteral |
| )cpp"); |
| |
| const comments::FullComment *Comment = |
| AST->getASTContext().getLocalCommentForDeclUncached(CTorFunc); |
| { |
| std::string expectedString = R"cpp( |
| FullComment |
| `-ParagraphComment |
| `-TextComment |
| )cpp"; |
| EXPECT_EQ(dumpASTString(Comment, Comment), expectedString); |
| } |
| |
| auto Result = ast_matchers::match( |
| classTemplateSpecializationDecl(hasName("templ")).bind("fn"), |
| AST->getASTContext()); |
| EXPECT_EQ(Result.size(), 1u); |
| auto Templ = Result[0].getNodeAs<ClassTemplateSpecializationDecl>("fn"); |
| |
| TemplateArgument TA = Templ->getTemplateArgs()[0]; |
| |
| verifyWithDynNode(TA, |
| R"cpp( |
| TemplateArgument |
| )cpp"); |
| |
| Func = getFunctionNode(AST.get(), "parmvardecl_attr"); |
| |
| const auto *Parm = Func->getParamDecl(0); |
| const auto TL = Parm->getTypeSourceInfo()->getTypeLoc(); |
| ASSERT_TRUE(TL.getType()->isPointerType()); |
| |
| const auto ATL = TL.getNextTypeLoc().getAs<AttributedTypeLoc>(); |
| const auto *AS = cast<AddressSpaceAttr>(ATL.getAttr()); |
| EXPECT_EQ(toTargetAddressSpace(static_cast<LangAS>(AS->getAddressSpace())), |
| 19u); |
| } |
| |
| TEST(Traverse, IgnoreUnlessSpelledInSourceStructs) { |
| auto AST = buildASTFromCode(R"cpp( |
| |
| struct MyStruct { |
| MyStruct(); |
| MyStruct(int i) { |
| MyStruct(); |
| } |
| ~MyStruct(); |
| }; |
| |
| )cpp"); |
| |
| auto BN = ast_matchers::match( |
| cxxConstructorDecl(hasName("MyStruct"), |
| hasParameter(0, parmVarDecl(hasType(isInteger())))) |
| .bind("ctor"), |
| AST->getASTContext()); |
| EXPECT_EQ(BN.size(), 1u); |
| |
| EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, |
| BN[0].getNodeAs<Decl>("ctor")), |
| R"cpp( |
| CXXConstructorDecl 'MyStruct' |
| |-ParmVarDecl 'i' |
| `-CompoundStmt |
| `-CXXTemporaryObjectExpr |
| )cpp"); |
| |
| EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs<Decl>("ctor")), |
| R"cpp( |
| CXXConstructorDecl 'MyStruct' |
| |-ParmVarDecl 'i' |
| `-CompoundStmt |
| `-ExprWithCleanups |
| `-CXXBindTemporaryExpr |
| `-CXXTemporaryObjectExpr |
| )cpp"); |
| } |
| |
| TEST(Traverse, IgnoreUnlessSpelledInSourceReturnStruct) { |
| |
| auto AST = buildASTFromCode(R"cpp( |
| struct Retval { |
| Retval() {} |
| ~Retval() {} |
| }; |
| |
| Retval someFun(); |
| |
| void foo() |
| { |
| someFun(); |
| } |
| )cpp"); |
| |
| auto BN = ast_matchers::match(functionDecl(hasName("foo")).bind("fn"), |
| AST->getASTContext()); |
| EXPECT_EQ(BN.size(), 1u); |
| |
| EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, |
| BN[0].getNodeAs<Decl>("fn")), |
| R"cpp( |
| FunctionDecl 'foo' |
| `-CompoundStmt |
| `-CallExpr |
| `-DeclRefExpr 'someFun' |
| )cpp"); |
| |
| EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs<Decl>("fn")), |
| R"cpp( |
| FunctionDecl 'foo' |
| `-CompoundStmt |
| `-ExprWithCleanups |
| `-CXXBindTemporaryExpr |
| `-CallExpr |
| `-ImplicitCastExpr |
| `-DeclRefExpr 'someFun' |
| )cpp"); |
| } |
| |
| TEST(Traverse, IgnoreUnlessSpelledInSourceReturns) { |
| |
| auto AST = buildASTFromCode(R"cpp( |
| |
| struct A |
| { |
| }; |
| |
| struct B |
| { |
| B(int); |
| B(A const& a); |
| B(); |
| }; |
| |
| struct C |
| { |
| operator B(); |
| }; |
| |
| B func1() { |
| return 42; |
| } |
| |
| B func2() { |
| return B{42}; |
| } |
| |
| B func3() { |
| return B(42); |
| } |
| |
| B func4() { |
| return B(); |
| } |
| |
| B func5() { |
| return B{}; |
| } |
| |
| B func6() { |
| return C(); |
| } |
| |
| B func7() { |
| return A(); |
| } |
| |
| B func8() { |
| return C{}; |
| } |
| |
| B func9() { |
| return A{}; |
| } |
| |
| B func10() { |
| A a; |
| return a; |
| } |
| |
| B func11() { |
| B b; |
| return b; |
| } |
| |
| B func12() { |
| C c; |
| return c; |
| } |
| |
| )cpp"); |
| |
| auto getFunctionNode = [&AST](const std::string &name) { |
| auto BN = ast_matchers::match(functionDecl(hasName(name)).bind("fn"), |
| AST->getASTContext()); |
| EXPECT_EQ(BN.size(), 1u); |
| return BN[0].getNodeAs<Decl>("fn"); |
| }; |
| |
| { |
| auto FN = getFunctionNode("func1"); |
| llvm::StringRef Expected = R"cpp( |
| FunctionDecl 'func1' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-ExprWithCleanups |
| `-CXXConstructExpr |
| `-MaterializeTemporaryExpr |
| `-ImplicitCastExpr |
| `-CXXConstructExpr |
| `-IntegerLiteral |
| )cpp"; |
| |
| EXPECT_EQ(dumpASTString(TK_AsIs, FN), Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func1' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-IntegerLiteral |
| )cpp"; |
| EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, FN), Expected); |
| } |
| |
| llvm::StringRef Expected = R"cpp( |
| FunctionDecl 'func2' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXTemporaryObjectExpr |
| `-IntegerLiteral |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func2")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func3' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXFunctionalCastExpr |
| `-IntegerLiteral |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func3")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func4' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXTemporaryObjectExpr |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func4")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func5' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXTemporaryObjectExpr |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func5")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func6' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXTemporaryObjectExpr |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func6")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func7' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXTemporaryObjectExpr |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func7")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func8' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXFunctionalCastExpr |
| `-InitListExpr |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func8")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func9' |
| `-CompoundStmt |
| `-ReturnStmt |
| `-CXXFunctionalCastExpr |
| `-InitListExpr |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func9")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func10' |
| `-CompoundStmt |
| |-DeclStmt |
| | `-VarDecl 'a' |
| | `-CXXConstructExpr |
| `-ReturnStmt |
| `-DeclRefExpr 'a' |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func10")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func11' |
| `-CompoundStmt |
| |-DeclStmt |
| | `-VarDecl 'b' |
| | `-CXXConstructExpr |
| `-ReturnStmt |
| `-DeclRefExpr 'b' |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func11")), |
| Expected); |
| |
| Expected = R"cpp( |
| FunctionDecl 'func12' |
| `-CompoundStmt |
| |-DeclStmt |
| | `-VarDecl 'c' |
| | `-CXXConstructExpr |
| `-ReturnStmt |
| `-DeclRefExpr 'c' |
| )cpp"; |
| EXPECT_EQ( |
| dumpASTString(TK_IgnoreUnlessSpelledInSource, getFunctionNode("func12")), |
| Expected); |
| } |
| |
| TEST(Traverse, LambdaUnlessSpelledInSource) { |
| |
| auto AST = |
| buildASTFromCodeWithArgs(R"cpp( |
| |
| void captures() { |
| int a = 0; |
| int b = 0; |
| int d = 0; |
| int f = 0; |
| |
| [a, &b, c = d, &e = f](int g, int h = 42) {}; |
| } |
| |
| void templated() { |
| int a = 0; |
| [a]<typename T>(T t) {}; |
| } |
| |
| struct SomeStruct { |
| int a = 0; |
| void capture_this() { |
| [this]() {}; |
| } |
| void capture_this_copy() { |
| [self = *this]() {}; |
| } |
| }; |
| )cpp", |
| {"-Wno-unused-value", "-Wno-c++2a-extensions"}); |
| |
| auto getLambdaNode = [&AST](const std::string &name) { |
| auto BN = ast_matchers::match( |
| lambdaExpr(hasAncestor(functionDecl(hasName(name)))).bind("lambda"), |
| AST->getASTContext()); |
| EXPECT_EQ(BN.size(), 1u); |
| return BN[0].getNodeAs<LambdaExpr>("lambda"); |
| }; |
| |
| { |
| auto L = getLambdaNode("captures"); |
| |
| llvm::StringRef Expected = R"cpp( |
| LambdaExpr |
| |-DeclRefExpr 'a' |
| |-DeclRefExpr 'b' |
| |-VarDecl 'c' |
| | `-DeclRefExpr 'd' |
| |-VarDecl 'e' |
| | `-DeclRefExpr 'f' |
| |-ParmVarDecl 'g' |
| |-ParmVarDecl 'h' |
| | `-IntegerLiteral |
| `-CompoundStmt |
| )cpp"; |
| EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, L), Expected); |
| |
| Expected = R"cpp( |
| LambdaExpr |
| |-CXXRecordDecl '' |
| | |-CXXMethodDecl 'operator()' |
| | | |-ParmVarDecl 'g' |
| | | |-ParmVarDecl 'h' |
| | | | `-IntegerLiteral |
| | | `-CompoundStmt |
| | |-FieldDecl '' |
| | |-FieldDecl '' |
| | |-FieldDecl '' |
| | |-FieldDecl '' |
| | `-CXXDestructorDecl '~' |
| |-ImplicitCastExpr |
| | `-DeclRefExpr 'a' |
| |-DeclRefExpr 'b' |
| |-ImplicitCastExpr |
| | `-DeclRefExpr 'd' |
| |-DeclRefExpr 'f' |
| `-CompoundStmt |
| )cpp"; |
| EXPECT_EQ(dumpASTString(TK_AsIs, L), Expected); |
| } |
| |
| { |
| auto L = getLambdaNode("templated"); |
| |
| llvm::StringRef Expected = R"cpp( |
| LambdaExpr |
| |-DeclRefExpr 'a' |
| |-TemplateTypeParmDecl 'T' |
| |-ParmVarDecl 't' |
| `-CompoundStmt |
| )cpp"; |
| EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, L), Expected); |
| } |
| |
| { |
| auto L = getLambdaNode("capture_this"); |
| |
| llvm::StringRef Expected = R"cpp( |
| LambdaExpr |
| |-CXXThisExpr |
| `-CompoundStmt |
| )cpp"; |
| EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, L), Expected); |
| } |
| |
| { |
| auto L = getLambdaNode("capture_this_copy"); |
| |
| llvm::StringRef Expected = R"cpp( |
| LambdaExpr |
| |-VarDecl 'self' |
| | `-UnaryOperator |
| | `-CXXThisExpr |
| `-CompoundStmt |
| )cpp"; |
| EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, L), Expected); |
| } |
| } |
| |
| } // namespace clang |