1//===- unittests/Frontend/FrontendActionTest.cpp - FrontendAction tests ---===//
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/Frontend/FrontendAction.h"
10#include "clang/AST/ASTConsumer.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/DynamicRecursiveASTVisitor.h"
13#include "clang/Basic/LangStandard.h"
14#include "clang/Frontend/CompilerInstance.h"
15#include "clang/Frontend/CompilerInvocation.h"
16#include "clang/Frontend/FrontendActions.h"
17#include "clang/Lex/Preprocessor.h"
18#include "clang/Lex/PreprocessorOptions.h"
19#include "clang/Sema/Sema.h"
20#include "clang/Serialization/InMemoryModuleCache.h"
21#include "clang/Serialization/ModuleCache.h"
22#include "llvm/Support/MemoryBuffer.h"
23#include "llvm/Support/ToolOutputFile.h"
24#include "llvm/Support/VirtualFileSystem.h"
25#include "llvm/TargetParser/Triple.h"
26#include "gtest/gtest.h"
27
28using namespace llvm;
29using namespace clang;
30
31namespace {
32
33class TestASTFrontendAction : public ASTFrontendAction {
34public:
35 TestASTFrontendAction(bool enableIncrementalProcessing = false,
36 bool actOnEndOfTranslationUnit = false)
37 : EnableIncrementalProcessing(enableIncrementalProcessing),
38 ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { }
39
40 bool EnableIncrementalProcessing;
41 bool ActOnEndOfTranslationUnit;
42 std::vector<std::string> decl_names;
43
44 bool BeginSourceFileAction(CompilerInstance &ci) override {
45 if (EnableIncrementalProcessing)
46 ci.getPreprocessor().enableIncrementalProcessing();
47
48 return ASTFrontendAction::BeginSourceFileAction(CI&: ci);
49 }
50
51 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
52 StringRef InFile) override {
53 return std::make_unique<Visitor>(args&: CI, args&: ActOnEndOfTranslationUnit,
54 args&: decl_names);
55 }
56
57private:
58 class Visitor : public ASTConsumer, public DynamicRecursiveASTVisitor {
59 public:
60 Visitor(CompilerInstance &CI, bool ActOnEndOfTranslationUnit,
61 std::vector<std::string> &decl_names) :
62 CI(CI), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit),
63 decl_names_(decl_names) {}
64
65 void HandleTranslationUnit(ASTContext &context) override {
66 if (ActOnEndOfTranslationUnit) {
67 CI.getSema().ActOnEndOfTranslationUnit();
68 }
69 TraverseDecl(context.getTranslationUnitDecl());
70 }
71
72 bool VisitNamedDecl(NamedDecl *Decl) override {
73 decl_names_.push_back(x: Decl->getQualifiedNameAsString());
74 return true;
75 }
76
77 private:
78 CompilerInstance &CI;
79 bool ActOnEndOfTranslationUnit;
80 std::vector<std::string> &decl_names_;
81 };
82};
83
84TEST(ASTFrontendAction, Sanity) {
85 auto invocation = std::make_shared<CompilerInvocation>();
86 invocation->getPreprocessorOpts().addRemappedFile(
87 From: "test.cc",
88 To: MemoryBuffer::getMemBuffer(InputData: "int main() { float x; }").release());
89 invocation->getFrontendOpts().Inputs.push_back(
90 Elt: FrontendInputFile("test.cc", Language::CXX));
91 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
92 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
93 CompilerInstance compiler(std::move(invocation));
94 compiler.createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem());
95
96 TestASTFrontendAction test_action;
97 ASSERT_TRUE(compiler.ExecuteAction(test_action));
98 ASSERT_EQ(2U, test_action.decl_names.size());
99 EXPECT_EQ("main", test_action.decl_names[0]);
100 EXPECT_EQ("x", test_action.decl_names[1]);
101}
102
103TEST(ASTFrontendAction, IncrementalParsing) {
104 auto invocation = std::make_shared<CompilerInvocation>();
105 invocation->getPreprocessorOpts().addRemappedFile(
106 From: "test.cc",
107 To: MemoryBuffer::getMemBuffer(InputData: "int main() { float x; }").release());
108 invocation->getFrontendOpts().Inputs.push_back(
109 Elt: FrontendInputFile("test.cc", Language::CXX));
110 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
111 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
112 CompilerInstance compiler(std::move(invocation));
113 compiler.createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem());
114
115 TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true);
116 ASSERT_TRUE(compiler.ExecuteAction(test_action));
117 ASSERT_EQ(2U, test_action.decl_names.size());
118 EXPECT_EQ("main", test_action.decl_names[0]);
119 EXPECT_EQ("x", test_action.decl_names[1]);
120}
121
122TEST(ASTFrontendAction, LateTemplateIncrementalParsing) {
123 auto invocation = std::make_shared<CompilerInvocation>();
124 invocation->getLangOpts().CPlusPlus = true;
125 invocation->getLangOpts().DelayedTemplateParsing = true;
126 invocation->getPreprocessorOpts().addRemappedFile(
127 From: "test.cc", To: MemoryBuffer::getMemBuffer(
128 InputData: "template<typename T> struct A { A(T); T data; };\n"
129 "template<typename T> struct B: public A<T> {\n"
130 " B();\n"
131 " B(B const& b): A<T>(b.data) {}\n"
132 "};\n"
133 "B<char> c() { return B<char>(); }\n").release());
134 invocation->getFrontendOpts().Inputs.push_back(
135 Elt: FrontendInputFile("test.cc", Language::CXX));
136 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
137 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
138 CompilerInstance compiler(std::move(invocation));
139 compiler.createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem());
140
141 TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true,
142 /*actOnEndOfTranslationUnit=*/true);
143 ASSERT_TRUE(compiler.ExecuteAction(test_action));
144 ASSERT_EQ(13U, test_action.decl_names.size());
145 EXPECT_EQ("A", test_action.decl_names[0]);
146 EXPECT_EQ("c", test_action.decl_names[12]);
147}
148
149struct TestPPCallbacks : public PPCallbacks {
150 TestPPCallbacks() : SeenEnd(false) {}
151
152 void EndOfMainFile() override { SeenEnd = true; }
153
154 bool SeenEnd;
155};
156
157class TestPPCallbacksFrontendAction : public PreprocessorFrontendAction {
158 TestPPCallbacks *Callbacks;
159
160public:
161 TestPPCallbacksFrontendAction(TestPPCallbacks *C)
162 : Callbacks(C), SeenEnd(false) {}
163
164 void ExecuteAction() override {
165 Preprocessor &PP = getCompilerInstance().getPreprocessor();
166 PP.addPPCallbacks(C: std::unique_ptr<TestPPCallbacks>(Callbacks));
167 PP.EnterMainSourceFile();
168 }
169 void EndSourceFileAction() override { SeenEnd = Callbacks->SeenEnd; }
170
171 bool SeenEnd;
172};
173
174TEST(PreprocessorFrontendAction, EndSourceFile) {
175 auto Invocation = std::make_shared<CompilerInvocation>();
176 Invocation->getPreprocessorOpts().addRemappedFile(
177 From: "test.cc",
178 To: MemoryBuffer::getMemBuffer(InputData: "int main() { float x; }").release());
179 Invocation->getFrontendOpts().Inputs.push_back(
180 Elt: FrontendInputFile("test.cc", Language::CXX));
181 Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
182 Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
183 CompilerInstance Compiler(std::move(Invocation));
184 Compiler.createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem());
185
186 TestPPCallbacks *Callbacks = new TestPPCallbacks;
187 TestPPCallbacksFrontendAction TestAction(Callbacks);
188 ASSERT_FALSE(Callbacks->SeenEnd);
189 ASSERT_FALSE(TestAction.SeenEnd);
190 ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
191 // Check that EndOfMainFile was called before EndSourceFileAction.
192 ASSERT_TRUE(TestAction.SeenEnd);
193}
194
195class TypoExternalSemaSource : public ExternalSemaSource {
196 CompilerInstance &CI;
197
198public:
199 TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {}
200
201 TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
202 Scope *S, CXXScopeSpec *SS,
203 CorrectionCandidateCallback &CCC,
204 DeclContext *MemberContext, bool EnteringContext,
205 const ObjCObjectPointerType *OPT) override {
206 // Generate a fake typo correction with one attached note.
207 ASTContext &Ctx = CI.getASTContext();
208 TypoCorrection TC(DeclarationName(&Ctx.Idents.get(Name: "moo")));
209 unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID(
210 L: DiagnosticsEngine::Note, FormatString: "This is a note");
211 TC.addExtraDiagnostic(PD: PartialDiagnostic(DiagID, Ctx.getDiagAllocator()));
212 return TC;
213 }
214};
215
216struct TypoDiagnosticConsumer : public DiagnosticConsumer {
217 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
218 const Diagnostic &Info) override {
219 // Capture errors and notes. There should be one of each.
220 if (DiagLevel == DiagnosticsEngine::Error) {
221 assert(Error.empty());
222 Info.FormatDiagnostic(OutStr&: Error);
223 } else {
224 assert(Note.empty());
225 Info.FormatDiagnostic(OutStr&: Note);
226 }
227 }
228 SmallString<32> Error;
229 SmallString<32> Note;
230};
231
232TEST(ASTFrontendAction, ExternalSemaSource) {
233 auto Invocation = std::make_shared<CompilerInvocation>();
234 Invocation->getLangOpts().CPlusPlus = true;
235 Invocation->getPreprocessorOpts().addRemappedFile(
236 From: "test.cc", To: MemoryBuffer::getMemBuffer(InputData: "void fooo();\n"
237 "int main() { foo(); }")
238 .release());
239 Invocation->getFrontendOpts().Inputs.push_back(
240 Elt: FrontendInputFile("test.cc", Language::CXX));
241 Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
242 Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
243 CompilerInstance Compiler(std::move(Invocation));
244 auto *TDC = new TypoDiagnosticConsumer;
245 Compiler.createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem(), Client: TDC,
246 /*ShouldOwnClient=*/true);
247 Compiler.setExternalSemaSource(new TypoExternalSemaSource(Compiler));
248
249 SyntaxOnlyAction TestAction;
250 ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
251 // There should be one error correcting to 'moo' and a note attached to it.
252 EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?",
253 std::string(TDC->Error));
254 EXPECT_EQ("This is a note", std::string(TDC->Note));
255}
256
257TEST(GeneratePCHFrontendAction, CacheGeneratedPCH) {
258 // Create a temporary file for writing out the PCH that will be cleaned up.
259 int PCHFD;
260 llvm::SmallString<128> PCHFilename;
261 ASSERT_FALSE(
262 llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD, PCHFilename));
263 llvm::ToolOutputFile PCHFile(PCHFilename, PCHFD);
264
265 for (bool ShouldCache : {false, true}) {
266 auto Invocation = std::make_shared<CompilerInvocation>();
267 Invocation->getLangOpts().CacheGeneratedPCH = ShouldCache;
268 Invocation->getPreprocessorOpts().addRemappedFile(
269 From: "test.h",
270 To: MemoryBuffer::getMemBuffer(InputData: "int foo(void) { return 1; }\n").release());
271 Invocation->getFrontendOpts().Inputs.push_back(
272 Elt: FrontendInputFile("test.h", Language::C));
273 Invocation->getFrontendOpts().OutputFile = PCHFilename.str().str();
274 Invocation->getFrontendOpts().ProgramAction = frontend::GeneratePCH;
275 Invocation->getTargetOpts().Triple = "x86_64-apple-darwin19.0.0";
276 CompilerInstance Compiler(std::move(Invocation));
277 Compiler.createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem());
278
279 GeneratePCHAction TestAction;
280 ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
281
282 // Check whether the PCH was cached.
283 if (ShouldCache)
284 EXPECT_EQ(InMemoryModuleCache::Final,
285 Compiler.getModuleCache().getInMemoryModuleCache().getPCMState(
286 PCHFilename));
287 else
288 EXPECT_EQ(InMemoryModuleCache::Unknown,
289 Compiler.getModuleCache().getInMemoryModuleCache().getPCMState(
290 PCHFilename));
291 }
292}
293
294} // anonymous namespace
295

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/unittests/Frontend/FrontendActionTest.cpp