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