xref: /llvm-project/clang/lib/Testing/TestAST.cpp (revision df9a14d7bbf1180e4f1474254c9d7ed6bcb4ce55)
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(*llvm::vfs::getRealFileSystem());
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   std::string Filename = In.FileName;
86   if (Filename.empty())
87     Filename = getFilenameForTesting(In.Language).str();
88 
89   // Set up a VFS with only the virtual file visible.
90   auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
91   if (auto Err = VFS->setCurrentWorkingDirectory(In.WorkingDir))
92     ADD_FAILURE() << "Failed to setWD: " << Err.message();
93   VFS->addFile(Filename, /*ModificationTime=*/0,
94                llvm::MemoryBuffer::getMemBufferCopy(In.Code, Filename));
95   for (const auto &Extra : In.ExtraFiles)
96     VFS->addFile(
97         Extra.getKey(), /*ModificationTime=*/0,
98         llvm::MemoryBuffer::getMemBufferCopy(Extra.getValue(), Extra.getKey()));
99 
100   // Extra error conditions are reported through diagnostics, set that up first.
101   bool ErrorOK = In.ErrorOK || llvm::StringRef(In.Code).contains("error-ok");
102   Clang->createDiagnostics(*VFS, new StoreDiagnostics(Diagnostics, !ErrorOK));
103 
104   // Parse cc1 argv, (typically [-std=c++20 input.cc]) into CompilerInvocation.
105   std::vector<const char *> Argv;
106   std::vector<std::string> LangArgs = getCC1ArgsForTesting(In.Language);
107   for (const auto &S : LangArgs)
108     Argv.push_back(S.c_str());
109   for (const auto &S : In.ExtraArgs)
110     Argv.push_back(S.c_str());
111   Argv.push_back(Filename.c_str());
112   Clang->setInvocation(std::make_unique<CompilerInvocation>());
113   if (!CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv,
114                                           Clang->getDiagnostics(), "clang")) {
115     ADD_FAILURE() << "Failed to create invocation";
116     return;
117   }
118   assert(!Clang->getInvocation().getFrontendOpts().DisableFree);
119 
120   Clang->createFileManager(VFS);
121 
122   // Running the FrontendAction creates the other components: SourceManager,
123   // Preprocessor, ASTContext, Sema. Preprocessor needs TargetInfo to be set.
124   EXPECT_TRUE(Clang->createTarget());
125   Action =
126       In.MakeAction ? In.MakeAction() : std::make_unique<SyntaxOnlyAction>();
127   const FrontendInputFile &Main = Clang->getFrontendOpts().Inputs.front();
128   if (!Action->BeginSourceFile(*Clang, Main)) {
129     ADD_FAILURE() << "Failed to BeginSourceFile()";
130     Action.reset(); // Don't call EndSourceFile if BeginSourceFile failed.
131     return;
132   }
133   if (auto Err = Action->Execute())
134     ADD_FAILURE() << "Failed to Execute(): " << llvm::toString(std::move(Err));
135 
136   // Action->EndSourceFile() would destroy the ASTContext, we want to keep it.
137   // But notify the preprocessor we're done now.
138   Clang->getPreprocessor().EndSourceFile();
139   // We're done gathering diagnostics, detach the consumer so we can destroy it.
140   Clang->getDiagnosticClient().EndSourceFile();
141   Clang->getDiagnostics().setClient(new DiagnosticConsumer(),
142                                     /*ShouldOwnClient=*/true);
143 }
144 
145 void TestAST::clear() {
146   if (Action) {
147     // We notified the preprocessor of EOF already, so detach it first.
148     // Sema needs the PP alive until after EndSourceFile() though.
149     auto PP = Clang->getPreprocessorPtr(); // Keep PP alive for now.
150     Clang->setPreprocessor(nullptr);       // Detach so we don't send EOF twice.
151     Action->EndSourceFile();               // Destroy ASTContext and Sema.
152     // Now Sema is gone, PP can safely be destroyed.
153   }
154   Action.reset();
155   Clang.reset();
156   Diagnostics.clear();
157 }
158 
159 TestAST &TestAST::operator=(TestAST &&M) {
160   clear();
161   Action = std::move(M.Action);
162   Clang = std::move(M.Clang);
163   Diagnostics = std::move(M.Diagnostics);
164   return *this;
165 }
166 
167 TestAST::TestAST(TestAST &&M) { *this = std::move(M); }
168 
169 TestAST::~TestAST() { clear(); }
170 
171 } // end namespace clang
172