clang 20.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
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
22namespace clang {
23namespace {
24
25// Captures diagnostics into a vector, optionally reporting errors to gtest.
26class StoreDiagnostics : public DiagnosticConsumer {
27 std::vector<StoredDiagnostic> &Out;
28 bool ReportErrors;
29 LangOptions LangOpts;
30
31public:
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.
56void 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
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
143void 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
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
165TestAST::TestAST(TestAST &&M) { *this = std::move(M); }
166
167TestAST::~TestAST() { clear(); }
168
169} // end namespace clang
Defines the Diagnostic-related interfaces.
StringRef Text
Definition: Format.cpp:3002
StringRef Filename
Definition: Format.cpp:3001
Defines the clang::LangOptions interface.
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...
Definition: Diagnostic.h:1745
Level
The level of the diagnostic, after it has been through mapping.
Definition: Diagnostic.h:195
An input file for the front end.
The result of parsing a file specified by TestInputs.
Definition: TestAST.h:72
TestAST(const TestInputs &)
Constructing a TestAST parses the virtual file.
Definition: TestAST.cpp:77
TestAST & operator=(TestAST &&)
Definition: TestAST.cpp:157
The JSON file list parser is used to communicate input to InstallAPI.
std::vector< std::string > getCC1ArgsForTesting(TestLanguage Lang)
StringRef getFilenameForTesting(TestLanguage Lang)
@ TU_Complete
The translation unit is a complete translation unit.
Definition: LangOptions.h:1039
Specifies a virtual source file to be parsed as part of a test.
Definition: TestAST.h:34