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.
buildTree(llvm::StringRef 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.
addFile(llvm::StringRef Path,llvm::StringRef Contents)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 
TEST_F(SyntaxTreeTest,Basic)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