xref: /llvm-project/clang/unittests/Tooling/Syntax/TreeTestBase.cpp (revision 7dfdca1961aadc75ca397818bfb9bd32f1879248)
1 //===- TreeTestBase.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 // This file provides the test infrastructure for syntax trees.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "TreeTestBase.h"
14 #include "clang/AST/ASTConsumer.h"
15 #include "clang/Basic/LLVM.h"
16 #include "clang/Frontend/CompilerInstance.h"
17 #include "clang/Frontend/CompilerInvocation.h"
18 #include "clang/Frontend/FrontendAction.h"
19 #include "clang/Frontend/TextDiagnosticPrinter.h"
20 #include "clang/Lex/PreprocessorOptions.h"
21 #include "clang/Testing/CommandLineArgs.h"
22 #include "clang/Testing/TestClangConfig.h"
23 #include "clang/Tooling/Syntax/BuildTree.h"
24 #include "clang/Tooling/Syntax/Nodes.h"
25 #include "clang/Tooling/Syntax/Tokens.h"
26 #include "clang/Tooling/Syntax/Tree.h"
27 #include "llvm/ADT/ArrayRef.h"
28 #include "llvm/ADT/StringRef.h"
29 #include "llvm/Support/Casting.h"
30 #include "llvm/Support/Error.h"
31 #include "llvm/Testing/Annotations/Annotations.h"
32 #include "gtest/gtest.h"
33 
34 using namespace clang;
35 using namespace clang::syntax;
36 
37 namespace {
38 ArrayRef<syntax::Token> tokens(syntax::Node *N,
39                                const TokenBufferTokenManager &STM) {
40   assert(N->isOriginal() && "tokens of modified nodes are not well-defined");
41   if (auto *L = dyn_cast<syntax::Leaf>(N))
42     return llvm::ArrayRef(STM.getToken(L->getTokenKey()), 1);
43   auto *T = cast<syntax::Tree>(N);
44   return llvm::ArrayRef(STM.getToken(T->findFirstLeaf()->getTokenKey()),
45                         STM.getToken(T->findLastLeaf()->getTokenKey()) + 1);
46 }
47 } // namespace
48 
49 std::vector<TestClangConfig> clang::syntax::allTestClangConfigs() {
50   std::vector<TestClangConfig> all_configs;
51   for (TestLanguage lang : {
52 #define TESTLANGUAGE(lang, version, std_flag, version_index)                   \
53   Lang_##lang##version,
54 #include "clang/Testing/TestLanguage.def"
55        }) {
56     TestClangConfig config;
57     config.Language = lang;
58     config.Target = "x86_64-pc-linux-gnu";
59     all_configs.push_back(config);
60 
61     // Windows target is interesting to test because it enables
62     // `-fdelayed-template-parsing`.
63     config.Target = "x86_64-pc-win32-msvc";
64     all_configs.push_back(config);
65   }
66   return all_configs;
67 }
68 
69 syntax::TranslationUnit *
70 SyntaxTreeTest::buildTree(StringRef Code, const TestClangConfig &ClangConfig) {
71   // FIXME: this code is almost the identical to the one in TokensTest. Share
72   //        it.
73   class BuildSyntaxTree : public ASTConsumer {
74   public:
75     BuildSyntaxTree(syntax::TranslationUnit *&Root,
76                     std::unique_ptr<syntax::TokenBuffer> &TB,
77                     std::unique_ptr<syntax::TokenBufferTokenManager> &TM,
78                     std::unique_ptr<syntax::Arena> &Arena,
79                     std::unique_ptr<syntax::TokenCollector> Tokens)
80         : Root(Root), TB(TB), TM(TM), Arena(Arena), Tokens(std::move(Tokens)) {
81       assert(this->Tokens);
82     }
83 
84     void HandleTranslationUnit(ASTContext &Ctx) override {
85       TB = std::make_unique<syntax::TokenBuffer>(std::move(*Tokens).consume());
86       Tokens = nullptr; // make sure we fail if this gets called twice.
87       TM = std::make_unique<syntax::TokenBufferTokenManager>(
88           *TB, Ctx.getLangOpts(), Ctx.getSourceManager());
89       Arena = std::make_unique<syntax::Arena>();
90       Root = syntax::buildSyntaxTree(*Arena, *TM, Ctx);
91     }
92 
93   private:
94     syntax::TranslationUnit *&Root;
95     std::unique_ptr<syntax::TokenBuffer> &TB;
96     std::unique_ptr<syntax::TokenBufferTokenManager> &TM;
97     std::unique_ptr<syntax::Arena> &Arena;
98     std::unique_ptr<syntax::TokenCollector> Tokens;
99   };
100 
101   class BuildSyntaxTreeAction : public ASTFrontendAction {
102   public:
103     BuildSyntaxTreeAction(syntax::TranslationUnit *&Root,
104                           std::unique_ptr<syntax::TokenBufferTokenManager> &TM,
105                           std::unique_ptr<syntax::TokenBuffer> &TB,
106                           std::unique_ptr<syntax::Arena> &Arena)
107         : Root(Root), TM(TM), TB(TB), Arena(Arena) {}
108 
109     std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
110                                                    StringRef InFile) override {
111       // We start recording the tokens, ast consumer will take on the result.
112       auto Tokens =
113           std::make_unique<syntax::TokenCollector>(CI.getPreprocessor());
114       return std::make_unique<BuildSyntaxTree>(Root, TB, TM, Arena,
115                                                std::move(Tokens));
116     }
117 
118   private:
119     syntax::TranslationUnit *&Root;
120     std::unique_ptr<syntax::TokenBufferTokenManager> &TM;
121     std::unique_ptr<syntax::TokenBuffer> &TB;
122     std::unique_ptr<syntax::Arena> &Arena;
123   };
124 
125   constexpr const char *FileName = "./input.cpp";
126   FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy(""));
127 
128   if (!Diags->getClient())
129     Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), DiagOpts.get()));
130   Diags->setSeverityForGroup(diag::Flavor::WarningOrError, "unused-value",
131                              diag::Severity::Ignored, SourceLocation());
132 
133   // Prepare to run a compiler.
134   std::vector<std::string> Args = {
135       "syntax-test",
136       "-fsyntax-only",
137   };
138   llvm::copy(ClangConfig.getCommandLineArgs(), std::back_inserter(Args));
139   Args.push_back(FileName);
140 
141   std::vector<const char *> ArgsCStr;
142   for (const std::string &arg : Args) {
143     ArgsCStr.push_back(arg.c_str());
144   }
145 
146   CreateInvocationOptions CIOpts;
147   CIOpts.Diags = Diags;
148   CIOpts.VFS = FS;
149   Invocation = createInvocation(ArgsCStr, std::move(CIOpts));
150   assert(Invocation);
151   Invocation->getFrontendOpts().DisableFree = false;
152   Invocation->getPreprocessorOpts().addRemappedFile(
153       FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release());
154   CompilerInstance Compiler;
155   Compiler.setInvocation(Invocation);
156   Compiler.setDiagnostics(Diags.get());
157   Compiler.setFileManager(FileMgr.get());
158   Compiler.setSourceManager(SourceMgr.get());
159 
160   syntax::TranslationUnit *Root = nullptr;
161   BuildSyntaxTreeAction Recorder(Root, this->TM, this->TB, this->Arena);
162 
163   // Action could not be executed but the frontend didn't identify any errors
164   // in the code ==> problem in setting up the action.
165   if (!Compiler.ExecuteAction(Recorder) &&
166       Diags->getClient()->getNumErrors() == 0) {
167     ADD_FAILURE() << "failed to run the frontend";
168     std::abort();
169   }
170   return Root;
171 }
172 
173 syntax::Node *SyntaxTreeTest::nodeByRange(llvm::Annotations::Range R,
174                                           syntax::Node *Root) {
175   ArrayRef<syntax::Token> Toks = tokens(Root, *TM);
176 
177   if (Toks.front().location().isFileID() && Toks.back().location().isFileID() &&
178       syntax::Token::range(*SourceMgr, Toks.front(), Toks.back()) ==
179           syntax::FileRange(SourceMgr->getMainFileID(), R.Begin, R.End))
180     return Root;
181 
182   auto *T = dyn_cast<syntax::Tree>(Root);
183   if (!T)
184     return nullptr;
185   for (auto *C = T->getFirstChild(); C != nullptr; C = C->getNextSibling()) {
186     if (auto *Result = nodeByRange(R, C))
187       return Result;
188   }
189   return nullptr;
190 }
191