[clang] Handle __declspec() attributes in using
[llvm-project.git] / clang / lib / Testing / TestAST.cpp
blob8c79fcd7d63635adfcb3885d7b7be4c96bf9fc24
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 //===----------------------------------------------------------------------===//
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"
18 #include "gtest/gtest.h"
20 namespace clang {
21 namespace {
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;
29 public:
30 StoreDiagnostics(std::vector<StoredDiagnostic> &Out, bool ReportErrors)
31 : Out(Out), ReportErrors(ReportErrors) {}
33 void BeginSourceFile(const LangOptions &LangOpts,
34 const Preprocessor *) override {
35 this->LangOpts = LangOpts;
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;
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);
73 } // namespace
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); });
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));
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;
102 assert(!Clang->getInvocation().getFrontendOpts().DisableFree);
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);
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;
125 if (auto Err = Action->Execute())
126 ADD_FAILURE() << "Failed to Execute(): " << llvm::toString(std::move(Err));
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);
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.
146 Action.reset();
147 Clang.reset();
148 Diagnostics.clear();
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;
159 TestAST::TestAST(TestAST &&M) { *this = std::move(M); }
161 TestAST::~TestAST() { clear(); }
163 } // end namespace clang