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