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