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