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/Support/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   assert(N->isOriginal() && "tokens of modified nodes are not well-defined");
40   if (auto *L = dyn_cast<syntax::Leaf>(N))
41     return llvm::makeArrayRef(L->token(), 1);
42   auto *T = cast<syntax::Tree>(N);
43   return llvm::makeArrayRef(T->firstLeaf()->token(),
44                             T->lastLeaf()->token() + 1);
45 }
46 
47 std::vector<TestClangConfig> allTestClangConfigs() {
48   std::vector<TestClangConfig> all_configs;
49   for (TestLanguage lang : {Lang_C89, Lang_C99, Lang_CXX03, Lang_CXX11,
50                             Lang_CXX14, Lang_CXX17, Lang_CXX20}) {
51     TestClangConfig config;
52     config.Language = lang;
53     config.Target = "x86_64-pc-linux-gnu";
54     all_configs.push_back(config);
55 
56     // Windows target is interesting to test because it enables
57     // `-fdelayed-template-parsing`.
58     config.Target = "x86_64-pc-win32-msvc";
59     all_configs.push_back(config);
60   }
61   return all_configs;
62 }
63 
64 INSTANTIATE_TEST_CASE_P(SyntaxTreeTests, SyntaxTreeTest,
65                         testing::ValuesIn(allTestClangConfigs()), );
66 } // namespace
67 
68 syntax::TranslationUnit *
69 SyntaxTreeTest::buildTree(StringRef Code, const TestClangConfig &ClangConfig) {
70   // FIXME: this code is almost the identical to the one in TokensTest. Share
71   //        it.
72   class BuildSyntaxTree : public ASTConsumer {
73   public:
74     BuildSyntaxTree(syntax::TranslationUnit *&Root,
75                     std::unique_ptr<syntax::TokenBuffer> &TB,
76                     std::unique_ptr<syntax::Arena> &Arena,
77                     std::unique_ptr<syntax::TokenCollector> Tokens)
78         : Root(Root), TB(TB), Arena(Arena), Tokens(std::move(Tokens)) {
79       assert(this->Tokens);
80     }
81 
82     void HandleTranslationUnit(ASTContext &Ctx) override {
83       TB = std::make_unique<syntax::TokenBuffer>(std::move(*Tokens).consume());
84       Tokens = nullptr; // make sure we fail if this gets called twice.
85       Arena = std::make_unique<syntax::Arena>(Ctx.getSourceManager(),
86                                               Ctx.getLangOpts(), *TB);
87       Root = syntax::buildSyntaxTree(*Arena, *Ctx.getTranslationUnitDecl());
88     }
89 
90   private:
91     syntax::TranslationUnit *&Root;
92     std::unique_ptr<syntax::TokenBuffer> &TB;
93     std::unique_ptr<syntax::Arena> &Arena;
94     std::unique_ptr<syntax::TokenCollector> Tokens;
95   };
96 
97   class BuildSyntaxTreeAction : public ASTFrontendAction {
98   public:
99     BuildSyntaxTreeAction(syntax::TranslationUnit *&Root,
100                           std::unique_ptr<syntax::TokenBuffer> &TB,
101                           std::unique_ptr<syntax::Arena> &Arena)
102         : Root(Root), TB(TB), Arena(Arena) {}
103 
104     std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
105                                                    StringRef InFile) override {
106       // We start recording the tokens, ast consumer will take on the result.
107       auto Tokens =
108           std::make_unique<syntax::TokenCollector>(CI.getPreprocessor());
109       return std::make_unique<BuildSyntaxTree>(Root, TB, Arena,
110                                                std::move(Tokens));
111     }
112 
113   private:
114     syntax::TranslationUnit *&Root;
115     std::unique_ptr<syntax::TokenBuffer> &TB;
116     std::unique_ptr<syntax::Arena> &Arena;
117   };
118 
119   constexpr const char *FileName = "./input.cpp";
120   FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy(""));
121 
122   if (!Diags->getClient())
123     Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), DiagOpts.get()));
124   Diags->setSeverityForGroup(diag::Flavor::WarningOrError, "unused-value",
125                              diag::Severity::Ignored, SourceLocation());
126 
127   // Prepare to run a compiler.
128   std::vector<std::string> Args = {
129       "syntax-test",
130       "-fsyntax-only",
131   };
132   llvm::copy(ClangConfig.getCommandLineArgs(), std::back_inserter(Args));
133   Args.push_back(FileName);
134 
135   std::vector<const char *> ArgsCStr;
136   for (const std::string &arg : Args) {
137     ArgsCStr.push_back(arg.c_str());
138   }
139 
140   Invocation = createInvocationFromCommandLine(ArgsCStr, Diags, FS);
141   assert(Invocation);
142   Invocation->getFrontendOpts().DisableFree = false;
143   Invocation->getPreprocessorOpts().addRemappedFile(
144       FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release());
145   CompilerInstance Compiler;
146   Compiler.setInvocation(Invocation);
147   Compiler.setDiagnostics(Diags.get());
148   Compiler.setFileManager(FileMgr.get());
149   Compiler.setSourceManager(SourceMgr.get());
150 
151   syntax::TranslationUnit *Root = nullptr;
152   BuildSyntaxTreeAction Recorder(Root, this->TB, this->Arena);
153 
154   // Action could not be executed but the frontend didn't identify any errors
155   // in the code ==> problem in setting up the action.
156   if (!Compiler.ExecuteAction(Recorder) &&
157       Diags->getClient()->getNumErrors() == 0) {
158     ADD_FAILURE() << "failed to run the frontend";
159     std::abort();
160   }
161   return Root;
162 }
163 
164 ::testing::AssertionResult SyntaxTreeTest::treeDumpEqual(StringRef Code,
165                                                          StringRef Tree) {
166   SCOPED_TRACE(llvm::join(GetParam().getCommandLineArgs(), " "));
167 
168   auto *Root = buildTree(Code, GetParam());
169   if (Diags->getClient()->getNumErrors() != 0) {
170     return ::testing::AssertionFailure()
171            << "Source file has syntax errors, they were printed to the test "
172               "log";
173   }
174   std::string Actual = std::string(StringRef(Root->dump(*Arena)).trim());
175   // EXPECT_EQ shows the diff between the two strings if they are different.
176   EXPECT_EQ(Tree.trim().str(), Actual);
177   if (Actual != Tree.trim().str()) {
178     return ::testing::AssertionFailure();
179   }
180   return ::testing::AssertionSuccess();
181 }
182 
183 syntax::Node *SyntaxTreeTest::nodeByRange(llvm::Annotations::Range R,
184                                           syntax::Node *Root) {
185   ArrayRef<syntax::Token> Toks = tokens(Root);
186 
187   if (Toks.front().location().isFileID() && Toks.back().location().isFileID() &&
188       syntax::Token::range(*SourceMgr, Toks.front(), Toks.back()) ==
189           syntax::FileRange(SourceMgr->getMainFileID(), R.Begin, R.End))
190     return Root;
191 
192   auto *T = dyn_cast<syntax::Tree>(Root);
193   if (!T)
194     return nullptr;
195   for (auto *C = T->firstChild(); C != nullptr; C = C->nextSibling()) {
196     if (auto *Result = nodeByRange(R, C))
197       return Result;
198   }
199   return nullptr;
200 }
201