brintos

brintos / llvm-project-archived public Read only

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