1 //===- TreeTestBase.cpp ---------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // This file provides the test infrastructure for syntax trees.
11 //===----------------------------------------------------------------------===//
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"
34 using namespace clang
;
35 using namespace clang::syntax
;
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);
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(config
);
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(config
);
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
70 class BuildSyntaxTree
: public ASTConsumer
{
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
)) {
81 void HandleTranslationUnit(ASTContext
&Ctx
) override
{
82 TB
= std::make_unique
<syntax::TokenBuffer
>(std::move(*Tokens
).consume());
83 Tokens
= nullptr; // make sure we fail if this gets called twice.
84 TM
= std::make_unique
<syntax::TokenBufferTokenManager
>(
85 *TB
, Ctx
.getLangOpts(), Ctx
.getSourceManager());
86 Arena
= std::make_unique
<syntax::Arena
>();
87 Root
= syntax::buildSyntaxTree(*Arena
, *TM
, Ctx
);
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
;
98 class BuildSyntaxTreeAction
: public ASTFrontendAction
{
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
) {}
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.
110 std::make_unique
<syntax::TokenCollector
>(CI
.getPreprocessor());
111 return std::make_unique
<BuildSyntaxTree
>(Root
, TB
, TM
, Arena
,
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
;
122 constexpr const char *FileName
= "./input.cpp";
123 FS
->addFile(FileName
, time_t(), llvm::MemoryBuffer::getMemBufferCopy(""));
125 if (!Diags
->getClient())
126 Diags
->setClient(new TextDiagnosticPrinter(llvm::errs(), DiagOpts
.get()));
127 Diags
->setSeverityForGroup(diag::Flavor::WarningOrError
, "unused-value",
128 diag::Severity::Ignored
, SourceLocation());
130 // Prepare to run a compiler.
131 std::vector
<std::string
> Args
= {
135 llvm::copy(ClangConfig
.getCommandLineArgs(), std::back_inserter(Args
));
136 Args
.push_back(FileName
);
138 std::vector
<const char *> ArgsCStr
;
139 for (const std::string
&arg
: Args
) {
140 ArgsCStr
.push_back(arg
.c_str());
143 CreateInvocationOptions CIOpts
;
144 CIOpts
.Diags
= Diags
;
146 Invocation
= createInvocation(ArgsCStr
, std::move(CIOpts
));
148 Invocation
->getFrontendOpts().DisableFree
= false;
149 Invocation
->getPreprocessorOpts().addRemappedFile(
150 FileName
, llvm::MemoryBuffer::getMemBufferCopy(Code
).release());
151 CompilerInstance Compiler
;
152 Compiler
.setInvocation(Invocation
);
153 Compiler
.setDiagnostics(Diags
.get());
154 Compiler
.setFileManager(FileMgr
.get());
155 Compiler
.setSourceManager(SourceMgr
.get());
157 syntax::TranslationUnit
*Root
= nullptr;
158 BuildSyntaxTreeAction
Recorder(Root
, this->TM
, this->TB
, this->Arena
);
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";
170 syntax::Node
*SyntaxTreeTest::nodeByRange(llvm::Annotations::Range R
,
171 syntax::Node
*Root
) {
172 ArrayRef
<syntax::Token
> Toks
= tokens(Root
, *TM
);
174 if (Toks
.front().location().isFileID() && Toks
.back().location().isFileID() &&
175 syntax::Token::range(*SourceMgr
, Toks
.front(), Toks
.back()) ==
176 syntax::FileRange(SourceMgr
->getMainFileID(), R
.Begin
, R
.End
))
179 auto *T
= dyn_cast
<syntax::Tree
>(Root
);
182 for (auto *C
= T
->getFirstChild(); C
!= nullptr; C
= C
->getNextSibling()) {
183 if (auto *Result
= nodeByRange(R
, C
))