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