1 //===--- TestAST.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 #include "clang/Testing/TestAST.h"
10 #include "clang/Basic/Diagnostic.h"
11 #include "clang/Basic/LangOptions.h"
12 #include "clang/Frontend/FrontendActions.h"
13 #include "clang/Frontend/TextDiagnostic.h"
14 #include "clang/Testing/CommandLineArgs.h"
15 #include "llvm/ADT/ScopeExit.h"
16 #include "llvm/Support/VirtualFileSystem.h"
18 #include "gtest/gtest.h"
24 // Captures diagnostics into a vector, optionally reporting errors to gtest.
25 class StoreDiagnostics
: public DiagnosticConsumer
{
26 std::vector
<StoredDiagnostic
> &Out
;
31 StoreDiagnostics(std::vector
<StoredDiagnostic
> &Out
, bool ReportErrors
)
32 : Out(Out
), ReportErrors(ReportErrors
) {}
34 void BeginSourceFile(const LangOptions
&LangOpts
,
35 const Preprocessor
*) override
{
36 this->LangOpts
= LangOpts
;
39 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel
,
40 const Diagnostic
&Info
) override
{
41 Out
.emplace_back(DiagLevel
, Info
);
42 if (ReportErrors
&& DiagLevel
>= DiagnosticsEngine::Error
) {
44 llvm::raw_string_ostream
OS(Text
);
45 TextDiagnostic
Renderer(OS
, LangOpts
,
46 &Info
.getDiags()->getDiagnosticOptions());
47 Renderer
.emitStoredDiagnostic(Out
.back());
48 ADD_FAILURE() << Text
;
53 // Fills in the bits of a CompilerInstance that weren't initialized yet.
54 // Provides "empty" ASTContext etc if we fail before parsing gets started.
55 void createMissingComponents(CompilerInstance
&Clang
) {
56 if (!Clang
.hasDiagnostics())
57 Clang
.createDiagnostics();
58 if (!Clang
.hasFileManager())
59 Clang
.createFileManager();
60 if (!Clang
.hasSourceManager())
61 Clang
.createSourceManager(Clang
.getFileManager());
62 if (!Clang
.hasTarget())
64 if (!Clang
.hasPreprocessor())
65 Clang
.createPreprocessor(TU_Complete
);
66 if (!Clang
.hasASTConsumer())
67 Clang
.setASTConsumer(std::make_unique
<ASTConsumer
>());
68 if (!Clang
.hasASTContext())
69 Clang
.createASTContext();
71 Clang
.createSema(TU_Complete
, /*CodeCompleteConsumer=*/nullptr);
76 TestAST::TestAST(const TestInputs
&In
) {
77 Clang
= std::make_unique
<CompilerInstance
>(
78 std::make_shared
<PCHContainerOperations
>());
79 // If we don't manage to finish parsing, create CompilerInstance components
80 // anyway so that the test will see an empty AST instead of crashing.
81 auto RecoverFromEarlyExit
=
82 llvm::make_scope_exit([&] { createMissingComponents(*Clang
); });
84 // Extra error conditions are reported through diagnostics, set that up first.
85 bool ErrorOK
= In
.ErrorOK
|| llvm::StringRef(In
.Code
).contains("error-ok");
86 Clang
->createDiagnostics(new StoreDiagnostics(Diagnostics
, !ErrorOK
));
88 // Parse cc1 argv, (typically [-std=c++20 input.cc]) into CompilerInvocation.
89 std::vector
<const char *> Argv
;
90 std::vector
<std::string
> LangArgs
= getCC1ArgsForTesting(In
.Language
);
91 for (const auto &S
: LangArgs
)
92 Argv
.push_back(S
.c_str());
93 for (const auto &S
: In
.ExtraArgs
)
94 Argv
.push_back(S
.c_str());
95 std::string Filename
= In
.FileName
;
97 Filename
= getFilenameForTesting(In
.Language
).str();
98 Argv
.push_back(Filename
.c_str());
99 Clang
->setInvocation(std::make_unique
<CompilerInvocation
>());
100 if (!CompilerInvocation::CreateFromArgs(Clang
->getInvocation(), Argv
,
101 Clang
->getDiagnostics(), "clang")) {
102 ADD_FAILURE() << "Failed to create invocation";
105 assert(!Clang
->getInvocation().getFrontendOpts().DisableFree
);
107 // Set up a VFS with only the virtual file visible.
108 auto VFS
= llvm::makeIntrusiveRefCnt
<llvm::vfs::InMemoryFileSystem
>();
109 VFS
->addFile(Filename
, /*ModificationTime=*/0,
110 llvm::MemoryBuffer::getMemBufferCopy(In
.Code
, Filename
));
111 for (const auto &Extra
: In
.ExtraFiles
)
113 Extra
.getKey(), /*ModificationTime=*/0,
114 llvm::MemoryBuffer::getMemBufferCopy(Extra
.getValue(), Extra
.getKey()));
115 Clang
->createFileManager(VFS
);
117 // Running the FrontendAction creates the other components: SourceManager,
118 // Preprocessor, ASTContext, Sema. Preprocessor needs TargetInfo to be set.
119 EXPECT_TRUE(Clang
->createTarget());
121 In
.MakeAction
? In
.MakeAction() : std::make_unique
<SyntaxOnlyAction
>();
122 const FrontendInputFile
&Main
= Clang
->getFrontendOpts().Inputs
.front();
123 if (!Action
->BeginSourceFile(*Clang
, Main
)) {
124 ADD_FAILURE() << "Failed to BeginSourceFile()";
125 Action
.reset(); // Don't call EndSourceFile if BeginSourceFile failed.
128 if (auto Err
= Action
->Execute())
129 ADD_FAILURE() << "Failed to Execute(): " << llvm::toString(std::move(Err
));
131 // Action->EndSourceFile() would destroy the ASTContext, we want to keep it.
132 // But notify the preprocessor we're done now.
133 Clang
->getPreprocessor().EndSourceFile();
134 // We're done gathering diagnostics, detach the consumer so we can destroy it.
135 Clang
->getDiagnosticClient().EndSourceFile();
136 Clang
->getDiagnostics().setClient(new DiagnosticConsumer(),
137 /*ShouldOwnClient=*/true);
140 void TestAST::clear() {
142 // We notified the preprocessor of EOF already, so detach it first.
143 // Sema needs the PP alive until after EndSourceFile() though.
144 auto PP
= Clang
->getPreprocessorPtr(); // Keep PP alive for now.
145 Clang
->setPreprocessor(nullptr); // Detach so we don't send EOF twice.
146 Action
->EndSourceFile(); // Destroy ASTContext and Sema.
147 // Now Sema is gone, PP can safely be destroyed.
154 TestAST
&TestAST::operator=(TestAST
&&M
) {
156 Action
= std::move(M
.Action
);
157 Clang
= std::move(M
.Clang
);
158 Diagnostics
= std::move(M
.Diagnostics
);
162 TestAST::TestAST(TestAST
&&M
) { *this = std::move(M
); }
164 TestAST::~TestAST() { clear(); }
166 } // end namespace clang