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