xref: /src/contrib/llvm-project/clang/lib/Testing/TestAST.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1145449b1SDimitry Andric //===--- TestAST.cpp ------------------------------------------------------===//
2145449b1SDimitry Andric //
3145449b1SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4145449b1SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5145449b1SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6145449b1SDimitry Andric //
7145449b1SDimitry Andric //===----------------------------------------------------------------------===//
8145449b1SDimitry Andric 
9145449b1SDimitry Andric #include "clang/Testing/TestAST.h"
10145449b1SDimitry Andric #include "clang/Basic/Diagnostic.h"
11145449b1SDimitry Andric #include "clang/Basic/LangOptions.h"
12145449b1SDimitry Andric #include "clang/Frontend/FrontendActions.h"
13145449b1SDimitry Andric #include "clang/Frontend/TextDiagnostic.h"
14145449b1SDimitry Andric #include "clang/Testing/CommandLineArgs.h"
15145449b1SDimitry Andric #include "llvm/ADT/ScopeExit.h"
16ac9a064cSDimitry Andric #include "llvm/Support/Error.h"
17145449b1SDimitry Andric #include "llvm/Support/VirtualFileSystem.h"
18145449b1SDimitry Andric 
19145449b1SDimitry Andric #include "gtest/gtest.h"
207fa27ce4SDimitry Andric #include <string>
21145449b1SDimitry Andric 
22145449b1SDimitry Andric namespace clang {
23145449b1SDimitry Andric namespace {
24145449b1SDimitry Andric 
25145449b1SDimitry Andric // Captures diagnostics into a vector, optionally reporting errors to gtest.
26145449b1SDimitry Andric class StoreDiagnostics : public DiagnosticConsumer {
27145449b1SDimitry Andric   std::vector<StoredDiagnostic> &Out;
28145449b1SDimitry Andric   bool ReportErrors;
29145449b1SDimitry Andric   LangOptions LangOpts;
30145449b1SDimitry Andric 
31145449b1SDimitry Andric public:
StoreDiagnostics(std::vector<StoredDiagnostic> & Out,bool ReportErrors)32145449b1SDimitry Andric   StoreDiagnostics(std::vector<StoredDiagnostic> &Out, bool ReportErrors)
33145449b1SDimitry Andric       : Out(Out), ReportErrors(ReportErrors) {}
34145449b1SDimitry Andric 
BeginSourceFile(const LangOptions & LangOpts,const Preprocessor *)35145449b1SDimitry Andric   void BeginSourceFile(const LangOptions &LangOpts,
36145449b1SDimitry Andric                        const Preprocessor *) override {
37145449b1SDimitry Andric     this->LangOpts = LangOpts;
38145449b1SDimitry Andric   }
39145449b1SDimitry Andric 
HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info)40145449b1SDimitry Andric   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
41145449b1SDimitry Andric                         const Diagnostic &Info) override {
42145449b1SDimitry Andric     Out.emplace_back(DiagLevel, Info);
43145449b1SDimitry Andric     if (ReportErrors && DiagLevel >= DiagnosticsEngine::Error) {
44145449b1SDimitry Andric       std::string Text;
45145449b1SDimitry Andric       llvm::raw_string_ostream OS(Text);
46145449b1SDimitry Andric       TextDiagnostic Renderer(OS, LangOpts,
47145449b1SDimitry Andric                               &Info.getDiags()->getDiagnosticOptions());
48145449b1SDimitry Andric       Renderer.emitStoredDiagnostic(Out.back());
49145449b1SDimitry Andric       ADD_FAILURE() << Text;
50145449b1SDimitry Andric     }
51145449b1SDimitry Andric   }
52145449b1SDimitry Andric };
53145449b1SDimitry Andric 
54145449b1SDimitry Andric // Fills in the bits of a CompilerInstance that weren't initialized yet.
55145449b1SDimitry Andric // Provides "empty" ASTContext etc if we fail before parsing gets started.
createMissingComponents(CompilerInstance & Clang)56145449b1SDimitry Andric void createMissingComponents(CompilerInstance &Clang) {
57145449b1SDimitry Andric   if (!Clang.hasDiagnostics())
58145449b1SDimitry Andric     Clang.createDiagnostics();
59145449b1SDimitry Andric   if (!Clang.hasFileManager())
60145449b1SDimitry Andric     Clang.createFileManager();
61145449b1SDimitry Andric   if (!Clang.hasSourceManager())
62145449b1SDimitry Andric     Clang.createSourceManager(Clang.getFileManager());
63145449b1SDimitry Andric   if (!Clang.hasTarget())
64145449b1SDimitry Andric     Clang.createTarget();
65145449b1SDimitry Andric   if (!Clang.hasPreprocessor())
66145449b1SDimitry Andric     Clang.createPreprocessor(TU_Complete);
67145449b1SDimitry Andric   if (!Clang.hasASTConsumer())
68145449b1SDimitry Andric     Clang.setASTConsumer(std::make_unique<ASTConsumer>());
69145449b1SDimitry Andric   if (!Clang.hasASTContext())
70145449b1SDimitry Andric     Clang.createASTContext();
71145449b1SDimitry Andric   if (!Clang.hasSema())
72145449b1SDimitry Andric     Clang.createSema(TU_Complete, /*CodeCompleteConsumer=*/nullptr);
73145449b1SDimitry Andric }
74145449b1SDimitry Andric 
75145449b1SDimitry Andric } // namespace
76145449b1SDimitry Andric 
TestAST(const TestInputs & In)77145449b1SDimitry Andric TestAST::TestAST(const TestInputs &In) {
78145449b1SDimitry Andric   Clang = std::make_unique<CompilerInstance>(
79145449b1SDimitry Andric       std::make_shared<PCHContainerOperations>());
80145449b1SDimitry Andric   // If we don't manage to finish parsing, create CompilerInstance components
81145449b1SDimitry Andric   // anyway so that the test will see an empty AST instead of crashing.
82145449b1SDimitry Andric   auto RecoverFromEarlyExit =
83145449b1SDimitry Andric       llvm::make_scope_exit([&] { createMissingComponents(*Clang); });
84145449b1SDimitry Andric 
85145449b1SDimitry Andric   // Extra error conditions are reported through diagnostics, set that up first.
86145449b1SDimitry Andric   bool ErrorOK = In.ErrorOK || llvm::StringRef(In.Code).contains("error-ok");
87145449b1SDimitry Andric   Clang->createDiagnostics(new StoreDiagnostics(Diagnostics, !ErrorOK));
88145449b1SDimitry Andric 
89145449b1SDimitry Andric   // Parse cc1 argv, (typically [-std=c++20 input.cc]) into CompilerInvocation.
90145449b1SDimitry Andric   std::vector<const char *> Argv;
91145449b1SDimitry Andric   std::vector<std::string> LangArgs = getCC1ArgsForTesting(In.Language);
92145449b1SDimitry Andric   for (const auto &S : LangArgs)
93145449b1SDimitry Andric     Argv.push_back(S.c_str());
94145449b1SDimitry Andric   for (const auto &S : In.ExtraArgs)
95145449b1SDimitry Andric     Argv.push_back(S.c_str());
967fa27ce4SDimitry Andric   std::string Filename = In.FileName;
977fa27ce4SDimitry Andric   if (Filename.empty())
987fa27ce4SDimitry Andric     Filename = getFilenameForTesting(In.Language).str();
99145449b1SDimitry Andric   Argv.push_back(Filename.c_str());
100145449b1SDimitry Andric   Clang->setInvocation(std::make_unique<CompilerInvocation>());
101145449b1SDimitry Andric   if (!CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv,
102145449b1SDimitry Andric                                           Clang->getDiagnostics(), "clang")) {
103145449b1SDimitry Andric     ADD_FAILURE() << "Failed to create invocation";
104145449b1SDimitry Andric     return;
105145449b1SDimitry Andric   }
106145449b1SDimitry Andric   assert(!Clang->getInvocation().getFrontendOpts().DisableFree);
107145449b1SDimitry Andric 
108145449b1SDimitry Andric   // Set up a VFS with only the virtual file visible.
109145449b1SDimitry Andric   auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
110ac9a064cSDimitry Andric   if (auto Err = VFS->setCurrentWorkingDirectory(In.WorkingDir))
111ac9a064cSDimitry Andric     ADD_FAILURE() << "Failed to setWD: " << Err.message();
112145449b1SDimitry Andric   VFS->addFile(Filename, /*ModificationTime=*/0,
113145449b1SDimitry Andric                llvm::MemoryBuffer::getMemBufferCopy(In.Code, Filename));
114145449b1SDimitry Andric   for (const auto &Extra : In.ExtraFiles)
115145449b1SDimitry Andric     VFS->addFile(
116145449b1SDimitry Andric         Extra.getKey(), /*ModificationTime=*/0,
117145449b1SDimitry Andric         llvm::MemoryBuffer::getMemBufferCopy(Extra.getValue(), Extra.getKey()));
118145449b1SDimitry Andric   Clang->createFileManager(VFS);
119145449b1SDimitry Andric 
120145449b1SDimitry Andric   // Running the FrontendAction creates the other components: SourceManager,
121145449b1SDimitry Andric   // Preprocessor, ASTContext, Sema. Preprocessor needs TargetInfo to be set.
122145449b1SDimitry Andric   EXPECT_TRUE(Clang->createTarget());
123e3b55780SDimitry Andric   Action =
124e3b55780SDimitry Andric       In.MakeAction ? In.MakeAction() : std::make_unique<SyntaxOnlyAction>();
125145449b1SDimitry Andric   const FrontendInputFile &Main = Clang->getFrontendOpts().Inputs.front();
126145449b1SDimitry Andric   if (!Action->BeginSourceFile(*Clang, Main)) {
127145449b1SDimitry Andric     ADD_FAILURE() << "Failed to BeginSourceFile()";
128145449b1SDimitry Andric     Action.reset(); // Don't call EndSourceFile if BeginSourceFile failed.
129145449b1SDimitry Andric     return;
130145449b1SDimitry Andric   }
131145449b1SDimitry Andric   if (auto Err = Action->Execute())
132145449b1SDimitry Andric     ADD_FAILURE() << "Failed to Execute(): " << llvm::toString(std::move(Err));
133145449b1SDimitry Andric 
134145449b1SDimitry Andric   // Action->EndSourceFile() would destroy the ASTContext, we want to keep it.
135145449b1SDimitry Andric   // But notify the preprocessor we're done now.
136145449b1SDimitry Andric   Clang->getPreprocessor().EndSourceFile();
137145449b1SDimitry Andric   // We're done gathering diagnostics, detach the consumer so we can destroy it.
138145449b1SDimitry Andric   Clang->getDiagnosticClient().EndSourceFile();
139145449b1SDimitry Andric   Clang->getDiagnostics().setClient(new DiagnosticConsumer(),
140145449b1SDimitry Andric                                     /*ShouldOwnClient=*/true);
141145449b1SDimitry Andric }
142145449b1SDimitry Andric 
clear()143145449b1SDimitry Andric void TestAST::clear() {
144145449b1SDimitry Andric   if (Action) {
145145449b1SDimitry Andric     // We notified the preprocessor of EOF already, so detach it first.
146145449b1SDimitry Andric     // Sema needs the PP alive until after EndSourceFile() though.
147145449b1SDimitry Andric     auto PP = Clang->getPreprocessorPtr(); // Keep PP alive for now.
148145449b1SDimitry Andric     Clang->setPreprocessor(nullptr);       // Detach so we don't send EOF twice.
149145449b1SDimitry Andric     Action->EndSourceFile();               // Destroy ASTContext and Sema.
150145449b1SDimitry Andric     // Now Sema is gone, PP can safely be destroyed.
151145449b1SDimitry Andric   }
152145449b1SDimitry Andric   Action.reset();
153145449b1SDimitry Andric   Clang.reset();
154145449b1SDimitry Andric   Diagnostics.clear();
155145449b1SDimitry Andric }
156145449b1SDimitry Andric 
operator =(TestAST && M)157145449b1SDimitry Andric TestAST &TestAST::operator=(TestAST &&M) {
158145449b1SDimitry Andric   clear();
159145449b1SDimitry Andric   Action = std::move(M.Action);
160145449b1SDimitry Andric   Clang = std::move(M.Clang);
161145449b1SDimitry Andric   Diagnostics = std::move(M.Diagnostics);
162145449b1SDimitry Andric   return *this;
163145449b1SDimitry Andric }
164145449b1SDimitry Andric 
TestAST(TestAST && M)165145449b1SDimitry Andric TestAST::TestAST(TestAST &&M) { *this = std::move(M); }
166145449b1SDimitry Andric 
~TestAST()167145449b1SDimitry Andric TestAST::~TestAST() { clear(); }
168145449b1SDimitry Andric 
169145449b1SDimitry Andric } // end namespace clang
170