Path: blob/main/contrib/llvm-project/clang/lib/Testing/TestAST.cpp
35232 views
//===--- TestAST.cpp ------------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Testing/TestAST.h"9#include "clang/Basic/Diagnostic.h"10#include "clang/Basic/LangOptions.h"11#include "clang/Frontend/FrontendActions.h"12#include "clang/Frontend/TextDiagnostic.h"13#include "clang/Testing/CommandLineArgs.h"14#include "llvm/ADT/ScopeExit.h"15#include "llvm/Support/Error.h"16#include "llvm/Support/VirtualFileSystem.h"1718#include "gtest/gtest.h"19#include <string>2021namespace clang {22namespace {2324// Captures diagnostics into a vector, optionally reporting errors to gtest.25class StoreDiagnostics : public DiagnosticConsumer {26std::vector<StoredDiagnostic> &Out;27bool ReportErrors;28LangOptions LangOpts;2930public:31StoreDiagnostics(std::vector<StoredDiagnostic> &Out, bool ReportErrors)32: Out(Out), ReportErrors(ReportErrors) {}3334void BeginSourceFile(const LangOptions &LangOpts,35const Preprocessor *) override {36this->LangOpts = LangOpts;37}3839void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,40const Diagnostic &Info) override {41Out.emplace_back(DiagLevel, Info);42if (ReportErrors && DiagLevel >= DiagnosticsEngine::Error) {43std::string Text;44llvm::raw_string_ostream OS(Text);45TextDiagnostic Renderer(OS, LangOpts,46&Info.getDiags()->getDiagnosticOptions());47Renderer.emitStoredDiagnostic(Out.back());48ADD_FAILURE() << Text;49}50}51};5253// Fills in the bits of a CompilerInstance that weren't initialized yet.54// Provides "empty" ASTContext etc if we fail before parsing gets started.55void createMissingComponents(CompilerInstance &Clang) {56if (!Clang.hasDiagnostics())57Clang.createDiagnostics();58if (!Clang.hasFileManager())59Clang.createFileManager();60if (!Clang.hasSourceManager())61Clang.createSourceManager(Clang.getFileManager());62if (!Clang.hasTarget())63Clang.createTarget();64if (!Clang.hasPreprocessor())65Clang.createPreprocessor(TU_Complete);66if (!Clang.hasASTConsumer())67Clang.setASTConsumer(std::make_unique<ASTConsumer>());68if (!Clang.hasASTContext())69Clang.createASTContext();70if (!Clang.hasSema())71Clang.createSema(TU_Complete, /*CodeCompleteConsumer=*/nullptr);72}7374} // namespace7576TestAST::TestAST(const TestInputs &In) {77Clang = std::make_unique<CompilerInstance>(78std::make_shared<PCHContainerOperations>());79// If we don't manage to finish parsing, create CompilerInstance components80// anyway so that the test will see an empty AST instead of crashing.81auto RecoverFromEarlyExit =82llvm::make_scope_exit([&] { createMissingComponents(*Clang); });8384// Extra error conditions are reported through diagnostics, set that up first.85bool ErrorOK = In.ErrorOK || llvm::StringRef(In.Code).contains("error-ok");86Clang->createDiagnostics(new StoreDiagnostics(Diagnostics, !ErrorOK));8788// Parse cc1 argv, (typically [-std=c++20 input.cc]) into CompilerInvocation.89std::vector<const char *> Argv;90std::vector<std::string> LangArgs = getCC1ArgsForTesting(In.Language);91for (const auto &S : LangArgs)92Argv.push_back(S.c_str());93for (const auto &S : In.ExtraArgs)94Argv.push_back(S.c_str());95std::string Filename = In.FileName;96if (Filename.empty())97Filename = getFilenameForTesting(In.Language).str();98Argv.push_back(Filename.c_str());99Clang->setInvocation(std::make_unique<CompilerInvocation>());100if (!CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv,101Clang->getDiagnostics(), "clang")) {102ADD_FAILURE() << "Failed to create invocation";103return;104}105assert(!Clang->getInvocation().getFrontendOpts().DisableFree);106107// Set up a VFS with only the virtual file visible.108auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();109if (auto Err = VFS->setCurrentWorkingDirectory(In.WorkingDir))110ADD_FAILURE() << "Failed to setWD: " << Err.message();111VFS->addFile(Filename, /*ModificationTime=*/0,112llvm::MemoryBuffer::getMemBufferCopy(In.Code, Filename));113for (const auto &Extra : In.ExtraFiles)114VFS->addFile(115Extra.getKey(), /*ModificationTime=*/0,116llvm::MemoryBuffer::getMemBufferCopy(Extra.getValue(), Extra.getKey()));117Clang->createFileManager(VFS);118119// Running the FrontendAction creates the other components: SourceManager,120// Preprocessor, ASTContext, Sema. Preprocessor needs TargetInfo to be set.121EXPECT_TRUE(Clang->createTarget());122Action =123In.MakeAction ? In.MakeAction() : std::make_unique<SyntaxOnlyAction>();124const FrontendInputFile &Main = Clang->getFrontendOpts().Inputs.front();125if (!Action->BeginSourceFile(*Clang, Main)) {126ADD_FAILURE() << "Failed to BeginSourceFile()";127Action.reset(); // Don't call EndSourceFile if BeginSourceFile failed.128return;129}130if (auto Err = Action->Execute())131ADD_FAILURE() << "Failed to Execute(): " << llvm::toString(std::move(Err));132133// Action->EndSourceFile() would destroy the ASTContext, we want to keep it.134// But notify the preprocessor we're done now.135Clang->getPreprocessor().EndSourceFile();136// We're done gathering diagnostics, detach the consumer so we can destroy it.137Clang->getDiagnosticClient().EndSourceFile();138Clang->getDiagnostics().setClient(new DiagnosticConsumer(),139/*ShouldOwnClient=*/true);140}141142void TestAST::clear() {143if (Action) {144// We notified the preprocessor of EOF already, so detach it first.145// Sema needs the PP alive until after EndSourceFile() though.146auto PP = Clang->getPreprocessorPtr(); // Keep PP alive for now.147Clang->setPreprocessor(nullptr); // Detach so we don't send EOF twice.148Action->EndSourceFile(); // Destroy ASTContext and Sema.149// Now Sema is gone, PP can safely be destroyed.150}151Action.reset();152Clang.reset();153Diagnostics.clear();154}155156TestAST &TestAST::operator=(TestAST &&M) {157clear();158Action = std::move(M.Action);159Clang = std::move(M.Clang);160Diagnostics = std::move(M.Diagnostics);161return *this;162}163164TestAST::TestAST(TestAST &&M) { *this = std::move(M); }165166TestAST::~TestAST() { clear(); }167168} // end namespace clang169170171