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

source code of clang/lib/Testing/TestAST.cpp