1 //===- unittests/Frontend/FrontendActionTest.cpp - FrontendAction tests ---===//
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
7 //===----------------------------------------------------------------------===//
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 "llvm/Support/MemoryBuffer.h"
22 #include "llvm/Support/ToolOutputFile.h"
23 #include "llvm/Support/VirtualFileSystem.h"
24 #include "llvm/TargetParser/Triple.h"
25 #include "gtest/gtest.h"
28 using namespace clang
;
32 class TestASTFrontendAction
: public ASTFrontendAction
{
34 TestASTFrontendAction(bool enableIncrementalProcessing
= false,
35 bool actOnEndOfTranslationUnit
= false)
36 : EnableIncrementalProcessing(enableIncrementalProcessing
),
37 ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit
) { }
39 bool EnableIncrementalProcessing
;
40 bool ActOnEndOfTranslationUnit
;
41 std::vector
<std::string
> decl_names
;
43 bool BeginSourceFileAction(CompilerInstance
&ci
) override
{
44 if (EnableIncrementalProcessing
)
45 ci
.getPreprocessor().enableIncrementalProcessing();
47 return ASTFrontendAction::BeginSourceFileAction(ci
);
50 std::unique_ptr
<ASTConsumer
> CreateASTConsumer(CompilerInstance
&CI
,
51 StringRef InFile
) override
{
52 return std::make_unique
<Visitor
>(CI
, ActOnEndOfTranslationUnit
,
57 class Visitor
: public ASTConsumer
, public DynamicRecursiveASTVisitor
{
59 Visitor(CompilerInstance
&CI
, bool ActOnEndOfTranslationUnit
,
60 std::vector
<std::string
> &decl_names
) :
61 CI(CI
), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit
),
62 decl_names_(decl_names
) {}
64 void HandleTranslationUnit(ASTContext
&context
) override
{
65 if (ActOnEndOfTranslationUnit
) {
66 CI
.getSema().ActOnEndOfTranslationUnit();
68 TraverseDecl(context
.getTranslationUnitDecl());
71 bool VisitNamedDecl(NamedDecl
*Decl
) override
{
72 decl_names_
.push_back(Decl
->getQualifiedNameAsString());
78 bool ActOnEndOfTranslationUnit
;
79 std::vector
<std::string
> &decl_names_
;
83 TEST(ASTFrontendAction
, Sanity
) {
84 auto invocation
= std::make_shared
<CompilerInvocation
>();
85 invocation
->getPreprocessorOpts().addRemappedFile(
87 MemoryBuffer::getMemBuffer("int main() { float x; }").release());
88 invocation
->getFrontendOpts().Inputs
.push_back(
89 FrontendInputFile("test.cc", Language::CXX
));
90 invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
91 invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
92 CompilerInstance compiler
;
93 compiler
.setInvocation(std::move(invocation
));
94 compiler
.createDiagnostics(*llvm::vfs::getRealFileSystem());
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]);
103 TEST(ASTFrontendAction
, IncrementalParsing
) {
104 auto invocation
= std::make_shared
<CompilerInvocation
>();
105 invocation
->getPreprocessorOpts().addRemappedFile(
107 MemoryBuffer::getMemBuffer("int main() { float x; }").release());
108 invocation
->getFrontendOpts().Inputs
.push_back(
109 FrontendInputFile("test.cc", Language::CXX
));
110 invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
111 invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
112 CompilerInstance compiler
;
113 compiler
.setInvocation(std::move(invocation
));
114 compiler
.createDiagnostics(*llvm::vfs::getRealFileSystem());
116 TestASTFrontendAction
test_action(/*enableIncrementalProcessing=*/true);
117 ASSERT_TRUE(compiler
.ExecuteAction(test_action
));
118 ASSERT_EQ(2U, test_action
.decl_names
.size());
119 EXPECT_EQ("main", test_action
.decl_names
[0]);
120 EXPECT_EQ("x", test_action
.decl_names
[1]);
123 TEST(ASTFrontendAction
, LateTemplateIncrementalParsing
) {
124 auto invocation
= std::make_shared
<CompilerInvocation
>();
125 invocation
->getLangOpts().CPlusPlus
= true;
126 invocation
->getLangOpts().DelayedTemplateParsing
= true;
127 invocation
->getPreprocessorOpts().addRemappedFile(
128 "test.cc", MemoryBuffer::getMemBuffer(
129 "template<typename T> struct A { A(T); T data; };\n"
130 "template<typename T> struct B: public A<T> {\n"
132 " B(B const& b): A<T>(b.data) {}\n"
134 "B<char> c() { return B<char>(); }\n").release());
135 invocation
->getFrontendOpts().Inputs
.push_back(
136 FrontendInputFile("test.cc", Language::CXX
));
137 invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
138 invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
139 CompilerInstance compiler
;
140 compiler
.setInvocation(std::move(invocation
));
141 compiler
.createDiagnostics(*llvm::vfs::getRealFileSystem());
143 TestASTFrontendAction
test_action(/*enableIncrementalProcessing=*/true,
144 /*actOnEndOfTranslationUnit=*/true);
145 ASSERT_TRUE(compiler
.ExecuteAction(test_action
));
146 ASSERT_EQ(13U, test_action
.decl_names
.size());
147 EXPECT_EQ("A", test_action
.decl_names
[0]);
148 EXPECT_EQ("c", test_action
.decl_names
[12]);
151 struct TestPPCallbacks
: public PPCallbacks
{
152 TestPPCallbacks() : SeenEnd(false) {}
154 void EndOfMainFile() override
{ SeenEnd
= true; }
159 class TestPPCallbacksFrontendAction
: public PreprocessorFrontendAction
{
160 TestPPCallbacks
*Callbacks
;
163 TestPPCallbacksFrontendAction(TestPPCallbacks
*C
)
164 : Callbacks(C
), SeenEnd(false) {}
166 void ExecuteAction() override
{
167 Preprocessor
&PP
= getCompilerInstance().getPreprocessor();
168 PP
.addPPCallbacks(std::unique_ptr
<TestPPCallbacks
>(Callbacks
));
169 PP
.EnterMainSourceFile();
171 void EndSourceFileAction() override
{ SeenEnd
= Callbacks
->SeenEnd
; }
176 TEST(PreprocessorFrontendAction
, EndSourceFile
) {
177 auto Invocation
= std::make_shared
<CompilerInvocation
>();
178 Invocation
->getPreprocessorOpts().addRemappedFile(
180 MemoryBuffer::getMemBuffer("int main() { float x; }").release());
181 Invocation
->getFrontendOpts().Inputs
.push_back(
182 FrontendInputFile("test.cc", Language::CXX
));
183 Invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
184 Invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
185 CompilerInstance Compiler
;
186 Compiler
.setInvocation(std::move(Invocation
));
187 Compiler
.createDiagnostics(*llvm::vfs::getRealFileSystem());
189 TestPPCallbacks
*Callbacks
= new TestPPCallbacks
;
190 TestPPCallbacksFrontendAction
TestAction(Callbacks
);
191 ASSERT_FALSE(Callbacks
->SeenEnd
);
192 ASSERT_FALSE(TestAction
.SeenEnd
);
193 ASSERT_TRUE(Compiler
.ExecuteAction(TestAction
));
194 // Check that EndOfMainFile was called before EndSourceFileAction.
195 ASSERT_TRUE(TestAction
.SeenEnd
);
198 class TypoExternalSemaSource
: public ExternalSemaSource
{
199 CompilerInstance
&CI
;
202 TypoExternalSemaSource(CompilerInstance
&CI
) : CI(CI
) {}
204 TypoCorrection
CorrectTypo(const DeclarationNameInfo
&Typo
, int LookupKind
,
205 Scope
*S
, CXXScopeSpec
*SS
,
206 CorrectionCandidateCallback
&CCC
,
207 DeclContext
*MemberContext
, bool EnteringContext
,
208 const ObjCObjectPointerType
*OPT
) override
{
209 // Generate a fake typo correction with one attached note.
210 ASTContext
&Ctx
= CI
.getASTContext();
211 TypoCorrection
TC(DeclarationName(&Ctx
.Idents
.get("moo")));
212 unsigned DiagID
= Ctx
.getDiagnostics().getCustomDiagID(
213 DiagnosticsEngine::Note
, "This is a note");
214 TC
.addExtraDiagnostic(PartialDiagnostic(DiagID
, Ctx
.getDiagAllocator()));
219 struct TypoDiagnosticConsumer
: public DiagnosticConsumer
{
220 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel
,
221 const Diagnostic
&Info
) override
{
222 // Capture errors and notes. There should be one of each.
223 if (DiagLevel
== DiagnosticsEngine::Error
) {
224 assert(Error
.empty());
225 Info
.FormatDiagnostic(Error
);
227 assert(Note
.empty());
228 Info
.FormatDiagnostic(Note
);
231 SmallString
<32> Error
;
232 SmallString
<32> Note
;
235 TEST(ASTFrontendAction
, ExternalSemaSource
) {
236 auto Invocation
= std::make_shared
<CompilerInvocation
>();
237 Invocation
->getLangOpts().CPlusPlus
= true;
238 Invocation
->getPreprocessorOpts().addRemappedFile(
239 "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n"
240 "int main() { foo(); }")
242 Invocation
->getFrontendOpts().Inputs
.push_back(
243 FrontendInputFile("test.cc", Language::CXX
));
244 Invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
245 Invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
246 CompilerInstance Compiler
;
247 Compiler
.setInvocation(std::move(Invocation
));
248 auto *TDC
= new TypoDiagnosticConsumer
;
249 Compiler
.createDiagnostics(*llvm::vfs::getRealFileSystem(), TDC
,
250 /*ShouldOwnClient=*/true);
251 Compiler
.setExternalSemaSource(new TypoExternalSemaSource(Compiler
));
253 SyntaxOnlyAction TestAction
;
254 ASSERT_TRUE(Compiler
.ExecuteAction(TestAction
));
255 // There should be one error correcting to 'moo' and a note attached to it.
256 EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?",
257 std::string(TDC
->Error
));
258 EXPECT_EQ("This is a note", std::string(TDC
->Note
));
261 TEST(GeneratePCHFrontendAction
, CacheGeneratedPCH
) {
262 // Create a temporary file for writing out the PCH that will be cleaned up.
264 llvm::SmallString
<128> PCHFilename
;
266 llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD
, PCHFilename
));
267 llvm::ToolOutputFile
PCHFile(PCHFilename
, PCHFD
);
269 for (bool ShouldCache
: {false, true}) {
270 auto Invocation
= std::make_shared
<CompilerInvocation
>();
271 Invocation
->getLangOpts().CacheGeneratedPCH
= ShouldCache
;
272 Invocation
->getPreprocessorOpts().addRemappedFile(
274 MemoryBuffer::getMemBuffer("int foo(void) { return 1; }\n").release());
275 Invocation
->getFrontendOpts().Inputs
.push_back(
276 FrontendInputFile("test.h", Language::C
));
277 Invocation
->getFrontendOpts().OutputFile
= PCHFilename
.str().str();
278 Invocation
->getFrontendOpts().ProgramAction
= frontend::GeneratePCH
;
279 Invocation
->getTargetOpts().Triple
= "x86_64-apple-darwin19.0.0";
280 CompilerInstance Compiler
;
281 Compiler
.setInvocation(std::move(Invocation
));
282 Compiler
.createDiagnostics(*llvm::vfs::getRealFileSystem());
284 GeneratePCHAction TestAction
;
285 ASSERT_TRUE(Compiler
.ExecuteAction(TestAction
));
287 // Check whether the PCH was cached.
289 EXPECT_EQ(InMemoryModuleCache::Final
,
290 Compiler
.getModuleCache().getPCMState(PCHFilename
));
292 EXPECT_EQ(InMemoryModuleCache::Unknown
,
293 Compiler
.getModuleCache().getPCMState(PCHFilename
));
297 } // anonymous namespace