mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-27 13:36:07 +00:00

Private headers inside umbrella files shouldn't be marked as unused. Differential Revision: https://reviews.llvm.org/D146406
167 lines
6.2 KiB
C++
167 lines
6.2 KiB
C++
//===--- TestAST.cpp ------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Testing/TestAST.h"
|
|
#include "clang/Basic/Diagnostic.h"
|
|
#include "clang/Basic/LangOptions.h"
|
|
#include "clang/Frontend/FrontendActions.h"
|
|
#include "clang/Frontend/TextDiagnostic.h"
|
|
#include "clang/Testing/CommandLineArgs.h"
|
|
#include "llvm/ADT/ScopeExit.h"
|
|
#include "llvm/Support/VirtualFileSystem.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
#include <string>
|
|
|
|
namespace clang {
|
|
namespace {
|
|
|
|
// Captures diagnostics into a vector, optionally reporting errors to gtest.
|
|
class StoreDiagnostics : public DiagnosticConsumer {
|
|
std::vector<StoredDiagnostic> &Out;
|
|
bool ReportErrors;
|
|
LangOptions LangOpts;
|
|
|
|
public:
|
|
StoreDiagnostics(std::vector<StoredDiagnostic> &Out, bool ReportErrors)
|
|
: Out(Out), ReportErrors(ReportErrors) {}
|
|
|
|
void BeginSourceFile(const LangOptions &LangOpts,
|
|
const Preprocessor *) override {
|
|
this->LangOpts = LangOpts;
|
|
}
|
|
|
|
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
|
|
const Diagnostic &Info) override {
|
|
Out.emplace_back(DiagLevel, Info);
|
|
if (ReportErrors && DiagLevel >= DiagnosticsEngine::Error) {
|
|
std::string Text;
|
|
llvm::raw_string_ostream OS(Text);
|
|
TextDiagnostic Renderer(OS, LangOpts,
|
|
&Info.getDiags()->getDiagnosticOptions());
|
|
Renderer.emitStoredDiagnostic(Out.back());
|
|
ADD_FAILURE() << Text;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Fills in the bits of a CompilerInstance that weren't initialized yet.
|
|
// Provides "empty" ASTContext etc if we fail before parsing gets started.
|
|
void createMissingComponents(CompilerInstance &Clang) {
|
|
if (!Clang.hasDiagnostics())
|
|
Clang.createDiagnostics();
|
|
if (!Clang.hasFileManager())
|
|
Clang.createFileManager();
|
|
if (!Clang.hasSourceManager())
|
|
Clang.createSourceManager(Clang.getFileManager());
|
|
if (!Clang.hasTarget())
|
|
Clang.createTarget();
|
|
if (!Clang.hasPreprocessor())
|
|
Clang.createPreprocessor(TU_Complete);
|
|
if (!Clang.hasASTConsumer())
|
|
Clang.setASTConsumer(std::make_unique<ASTConsumer>());
|
|
if (!Clang.hasASTContext())
|
|
Clang.createASTContext();
|
|
if (!Clang.hasSema())
|
|
Clang.createSema(TU_Complete, /*CodeCompleteConsumer=*/nullptr);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TestAST::TestAST(const TestInputs &In) {
|
|
Clang = std::make_unique<CompilerInstance>(
|
|
std::make_shared<PCHContainerOperations>());
|
|
// If we don't manage to finish parsing, create CompilerInstance components
|
|
// anyway so that the test will see an empty AST instead of crashing.
|
|
auto RecoverFromEarlyExit =
|
|
llvm::make_scope_exit([&] { createMissingComponents(*Clang); });
|
|
|
|
// Extra error conditions are reported through diagnostics, set that up first.
|
|
bool ErrorOK = In.ErrorOK || llvm::StringRef(In.Code).contains("error-ok");
|
|
Clang->createDiagnostics(new StoreDiagnostics(Diagnostics, !ErrorOK));
|
|
|
|
// Parse cc1 argv, (typically [-std=c++20 input.cc]) into CompilerInvocation.
|
|
std::vector<const char *> Argv;
|
|
std::vector<std::string> LangArgs = getCC1ArgsForTesting(In.Language);
|
|
for (const auto &S : LangArgs)
|
|
Argv.push_back(S.c_str());
|
|
for (const auto &S : In.ExtraArgs)
|
|
Argv.push_back(S.c_str());
|
|
std::string Filename = In.FileName;
|
|
if (Filename.empty())
|
|
Filename = getFilenameForTesting(In.Language).str();
|
|
Argv.push_back(Filename.c_str());
|
|
Clang->setInvocation(std::make_unique<CompilerInvocation>());
|
|
if (!CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv,
|
|
Clang->getDiagnostics(), "clang")) {
|
|
ADD_FAILURE() << "Failed to create invocation";
|
|
return;
|
|
}
|
|
assert(!Clang->getInvocation().getFrontendOpts().DisableFree);
|
|
|
|
// Set up a VFS with only the virtual file visible.
|
|
auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
|
|
VFS->addFile(Filename, /*ModificationTime=*/0,
|
|
llvm::MemoryBuffer::getMemBufferCopy(In.Code, Filename));
|
|
for (const auto &Extra : In.ExtraFiles)
|
|
VFS->addFile(
|
|
Extra.getKey(), /*ModificationTime=*/0,
|
|
llvm::MemoryBuffer::getMemBufferCopy(Extra.getValue(), Extra.getKey()));
|
|
Clang->createFileManager(VFS);
|
|
|
|
// Running the FrontendAction creates the other components: SourceManager,
|
|
// Preprocessor, ASTContext, Sema. Preprocessor needs TargetInfo to be set.
|
|
EXPECT_TRUE(Clang->createTarget());
|
|
Action =
|
|
In.MakeAction ? In.MakeAction() : std::make_unique<SyntaxOnlyAction>();
|
|
const FrontendInputFile &Main = Clang->getFrontendOpts().Inputs.front();
|
|
if (!Action->BeginSourceFile(*Clang, Main)) {
|
|
ADD_FAILURE() << "Failed to BeginSourceFile()";
|
|
Action.reset(); // Don't call EndSourceFile if BeginSourceFile failed.
|
|
return;
|
|
}
|
|
if (auto Err = Action->Execute())
|
|
ADD_FAILURE() << "Failed to Execute(): " << llvm::toString(std::move(Err));
|
|
|
|
// Action->EndSourceFile() would destroy the ASTContext, we want to keep it.
|
|
// But notify the preprocessor we're done now.
|
|
Clang->getPreprocessor().EndSourceFile();
|
|
// We're done gathering diagnostics, detach the consumer so we can destroy it.
|
|
Clang->getDiagnosticClient().EndSourceFile();
|
|
Clang->getDiagnostics().setClient(new DiagnosticConsumer(),
|
|
/*ShouldOwnClient=*/true);
|
|
}
|
|
|
|
void TestAST::clear() {
|
|
if (Action) {
|
|
// We notified the preprocessor of EOF already, so detach it first.
|
|
// Sema needs the PP alive until after EndSourceFile() though.
|
|
auto PP = Clang->getPreprocessorPtr(); // Keep PP alive for now.
|
|
Clang->setPreprocessor(nullptr); // Detach so we don't send EOF twice.
|
|
Action->EndSourceFile(); // Destroy ASTContext and Sema.
|
|
// Now Sema is gone, PP can safely be destroyed.
|
|
}
|
|
Action.reset();
|
|
Clang.reset();
|
|
Diagnostics.clear();
|
|
}
|
|
|
|
TestAST &TestAST::operator=(TestAST &&M) {
|
|
clear();
|
|
Action = std::move(M.Action);
|
|
Clang = std::move(M.Clang);
|
|
Diagnostics = std::move(M.Diagnostics);
|
|
return *this;
|
|
}
|
|
|
|
TestAST::TestAST(TestAST &&M) { *this = std::move(M); }
|
|
|
|
TestAST::~TestAST() { clear(); }
|
|
|
|
} // end namespace clang
|