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