1 //===- TreeTest.cpp -------------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/Tooling/Syntax/Tree.h" 10 #include "clang/AST/ASTConsumer.h" 11 #include "clang/AST/Decl.h" 12 #include "clang/Frontend/CompilerInstance.h" 13 #include "clang/Frontend/FrontendAction.h" 14 #include "clang/Lex/PreprocessorOptions.h" 15 #include "clang/Tooling/Syntax/BuildTree.h" 16 #include "clang/Tooling/Syntax/Nodes.h" 17 #include "clang/Tooling/Tooling.h" 18 #include "llvm/ADT/STLExtras.h" 19 #include "llvm/ADT/StringRef.h" 20 #include "gmock/gmock.h" 21 #include "gtest/gtest.h" 22 #include <cstdlib> 23 24 using namespace clang; 25 26 namespace { 27 class SyntaxTreeTest : public ::testing::Test { 28 protected: 29 // Build a syntax tree for the code. 30 syntax::TranslationUnit *buildTree(llvm::StringRef Code) { 31 // FIXME: this code is almost the identical to the one in TokensTest. Share 32 // it. 33 class BuildSyntaxTree : public ASTConsumer { 34 public: 35 BuildSyntaxTree(syntax::TranslationUnit *&Root, 36 std::unique_ptr<syntax::Arena> &Arena, 37 std::unique_ptr<syntax::TokenCollector> Tokens) 38 : Root(Root), Arena(Arena), Tokens(std::move(Tokens)) { 39 assert(this->Tokens); 40 } 41 42 void HandleTranslationUnit(ASTContext &Ctx) override { 43 Arena = llvm::make_unique<syntax::Arena>(Ctx.getSourceManager(), 44 Ctx.getLangOpts(), 45 std::move(*Tokens).consume()); 46 Tokens = nullptr; // make sure we fail if this gets called twice. 47 Root = syntax::buildSyntaxTree(*Arena, *Ctx.getTranslationUnitDecl()); 48 } 49 50 private: 51 syntax::TranslationUnit *&Root; 52 std::unique_ptr<syntax::Arena> &Arena; 53 std::unique_ptr<syntax::TokenCollector> Tokens; 54 }; 55 56 class BuildSyntaxTreeAction : public ASTFrontendAction { 57 public: 58 BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, 59 std::unique_ptr<syntax::Arena> &Arena) 60 : Root(Root), Arena(Arena) {} 61 62 std::unique_ptr<ASTConsumer> 63 CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { 64 // We start recording the tokens, ast consumer will take on the result. 65 auto Tokens = 66 llvm::make_unique<syntax::TokenCollector>(CI.getPreprocessor()); 67 return llvm::make_unique<BuildSyntaxTree>(Root, Arena, 68 std::move(Tokens)); 69 } 70 71 private: 72 syntax::TranslationUnit *&Root; 73 std::unique_ptr<syntax::Arena> &Arena; 74 }; 75 76 constexpr const char *FileName = "./input.cpp"; 77 FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy("")); 78 if (!Diags->getClient()) 79 Diags->setClient(new IgnoringDiagConsumer); 80 // Prepare to run a compiler. 81 std::vector<const char *> Args = {"syntax-test", "-std=c++11", 82 "-fsyntax-only", FileName}; 83 auto CI = createInvocationFromCommandLine(Args, Diags, FS); 84 assert(CI); 85 CI->getFrontendOpts().DisableFree = false; 86 CI->getPreprocessorOpts().addRemappedFile( 87 FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release()); 88 CompilerInstance Compiler; 89 Compiler.setInvocation(std::move(CI)); 90 Compiler.setDiagnostics(Diags.get()); 91 Compiler.setFileManager(FileMgr.get()); 92 Compiler.setSourceManager(SourceMgr.get()); 93 94 syntax::TranslationUnit *Root = nullptr; 95 BuildSyntaxTreeAction Recorder(Root, this->Arena); 96 if (!Compiler.ExecuteAction(Recorder)) { 97 ADD_FAILURE() << "failed to run the frontend"; 98 std::abort(); 99 } 100 return Root; 101 } 102 103 // Adds a file to the test VFS. 104 void addFile(llvm::StringRef Path, llvm::StringRef Contents) { 105 if (!FS->addFile(Path, time_t(), 106 llvm::MemoryBuffer::getMemBufferCopy(Contents))) { 107 ADD_FAILURE() << "could not add a file to VFS: " << Path; 108 } 109 } 110 111 // Data fields. 112 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 113 new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions); 114 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS = 115 new llvm::vfs::InMemoryFileSystem; 116 llvm::IntrusiveRefCntPtr<FileManager> FileMgr = 117 new FileManager(FileSystemOptions(), FS); 118 llvm::IntrusiveRefCntPtr<SourceManager> SourceMgr = 119 new SourceManager(*Diags, *FileMgr); 120 // Set after calling buildTree(). 121 std::unique_ptr<syntax::Arena> Arena; 122 }; 123 124 TEST_F(SyntaxTreeTest, Basic) { 125 std::pair</*Input*/ std::string, /*Expected*/ std::string> Cases[] = { 126 { 127 R"cpp( 128 int main() {} 129 void foo() {} 130 )cpp", 131 R"txt( 132 *: TranslationUnit 133 |-TopLevelDeclaration 134 | |-int 135 | |-main 136 | |-( 137 | |-) 138 | `-CompoundStatement 139 | |-2: { 140 | `-3: } 141 |-TopLevelDeclaration 142 | |-void 143 | |-foo 144 | |-( 145 | |-) 146 | `-CompoundStatement 147 | |-2: { 148 | `-3: } 149 `-<eof> 150 )txt"}, 151 }; 152 153 for (const auto &T : Cases) { 154 auto *Root = buildTree(T.first); 155 std::string Expected = llvm::StringRef(T.second).trim().str(); 156 std::string Actual = llvm::StringRef(Root->dump(*Arena)).trim(); 157 EXPECT_EQ(Expected, Actual) << "the resulting dump is:\n" << Actual; 158 } 159 } 160 } // namespace 161