aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/clang/lib/Testing/TestAST.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/clang/lib/Testing/TestAST.cpp')
-rw-r--r--contrib/llvm-project/clang/lib/Testing/TestAST.cpp169
1 files changed, 169 insertions, 0 deletions
diff --git a/contrib/llvm-project/clang/lib/Testing/TestAST.cpp b/contrib/llvm-project/clang/lib/Testing/TestAST.cpp
new file mode 100644
index 000000000000..fe8b93851613
--- /dev/null
+++ b/contrib/llvm-project/clang/lib/Testing/TestAST.cpp
@@ -0,0 +1,169 @@
+//===--- 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/Error.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>();
+ if (auto Err = VFS->setCurrentWorkingDirectory(In.WorkingDir))
+ ADD_FAILURE() << "Failed to setWD: " << Err.message();
+ 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