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