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