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/RecursiveASTVisitor.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/ADT/Triple.h"
22 #include "llvm/Support/MemoryBuffer.h"
23 #include "llvm/Support/ToolOutputFile.h"
24 #include "gtest/gtest.h"
27 using namespace clang
;
31 class TestASTFrontendAction
: public ASTFrontendAction
{
33 TestASTFrontendAction(bool enableIncrementalProcessing
= false,
34 bool actOnEndOfTranslationUnit
= false)
35 : EnableIncrementalProcessing(enableIncrementalProcessing
),
36 ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit
) { }
38 bool EnableIncrementalProcessing
;
39 bool ActOnEndOfTranslationUnit
;
40 std::vector
<std::string
> decl_names
;
42 bool BeginSourceFileAction(CompilerInstance
&ci
) override
{
43 if (EnableIncrementalProcessing
)
44 ci
.getPreprocessor().enableIncrementalProcessing();
46 return ASTFrontendAction::BeginSourceFileAction(ci
);
49 std::unique_ptr
<ASTConsumer
> CreateASTConsumer(CompilerInstance
&CI
,
50 StringRef InFile
) override
{
51 return std::make_unique
<Visitor
>(CI
, ActOnEndOfTranslationUnit
,
56 class Visitor
: public ASTConsumer
, public RecursiveASTVisitor
<Visitor
> {
58 Visitor(CompilerInstance
&CI
, bool ActOnEndOfTranslationUnit
,
59 std::vector
<std::string
> &decl_names
) :
60 CI(CI
), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit
),
61 decl_names_(decl_names
) {}
63 void HandleTranslationUnit(ASTContext
&context
) override
{
64 if (ActOnEndOfTranslationUnit
) {
65 CI
.getSema().ActOnEndOfTranslationUnit();
67 TraverseDecl(context
.getTranslationUnitDecl());
70 virtual bool VisitNamedDecl(NamedDecl
*Decl
) {
71 decl_names_
.push_back(Decl
->getQualifiedNameAsString());
77 bool ActOnEndOfTranslationUnit
;
78 std::vector
<std::string
> &decl_names_
;
82 TEST(ASTFrontendAction
, Sanity
) {
83 auto invocation
= std::make_shared
<CompilerInvocation
>();
84 invocation
->getPreprocessorOpts().addRemappedFile(
86 MemoryBuffer::getMemBuffer("int main() { float x; }").release());
87 invocation
->getFrontendOpts().Inputs
.push_back(
88 FrontendInputFile("test.cc", Language::CXX
));
89 invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
90 invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
91 CompilerInstance compiler
;
92 compiler
.setInvocation(std::move(invocation
));
93 compiler
.createDiagnostics();
95 TestASTFrontendAction test_action
;
96 ASSERT_TRUE(compiler
.ExecuteAction(test_action
));
97 ASSERT_EQ(2U, test_action
.decl_names
.size());
98 EXPECT_EQ("main", test_action
.decl_names
[0]);
99 EXPECT_EQ("x", test_action
.decl_names
[1]);
102 TEST(ASTFrontendAction
, IncrementalParsing
) {
103 auto invocation
= std::make_shared
<CompilerInvocation
>();
104 invocation
->getPreprocessorOpts().addRemappedFile(
106 MemoryBuffer::getMemBuffer("int main() { float x; }").release());
107 invocation
->getFrontendOpts().Inputs
.push_back(
108 FrontendInputFile("test.cc", Language::CXX
));
109 invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
110 invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
111 CompilerInstance compiler
;
112 compiler
.setInvocation(std::move(invocation
));
113 compiler
.createDiagnostics();
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]);
122 TEST(ASTFrontendAction
, LateTemplateIncrementalParsing
) {
123 auto invocation
= std::make_shared
<CompilerInvocation
>();
124 invocation
->getLangOpts()->CPlusPlus
= true;
125 invocation
->getLangOpts()->DelayedTemplateParsing
= true;
126 invocation
->getPreprocessorOpts().addRemappedFile(
127 "test.cc", MemoryBuffer::getMemBuffer(
128 "template<typename T> struct A { A(T); T data; };\n"
129 "template<typename T> struct B: public A<T> {\n"
131 " B(B const& b): A<T>(b.data) {}\n"
133 "B<char> c() { return B<char>(); }\n").release());
134 invocation
->getFrontendOpts().Inputs
.push_back(
135 FrontendInputFile("test.cc", Language::CXX
));
136 invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
137 invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
138 CompilerInstance compiler
;
139 compiler
.setInvocation(std::move(invocation
));
140 compiler
.createDiagnostics();
142 TestASTFrontendAction
test_action(/*enableIncrementalProcessing=*/true,
143 /*actOnEndOfTranslationUnit=*/true);
144 ASSERT_TRUE(compiler
.ExecuteAction(test_action
));
145 ASSERT_EQ(13U, test_action
.decl_names
.size());
146 EXPECT_EQ("A", test_action
.decl_names
[0]);
147 EXPECT_EQ("c", test_action
.decl_names
[12]);
150 struct TestPPCallbacks
: public PPCallbacks
{
151 TestPPCallbacks() : SeenEnd(false) {}
153 void EndOfMainFile() override
{ SeenEnd
= true; }
158 class TestPPCallbacksFrontendAction
: public PreprocessorFrontendAction
{
159 TestPPCallbacks
*Callbacks
;
162 TestPPCallbacksFrontendAction(TestPPCallbacks
*C
)
163 : Callbacks(C
), SeenEnd(false) {}
165 void ExecuteAction() override
{
166 Preprocessor
&PP
= getCompilerInstance().getPreprocessor();
167 PP
.addPPCallbacks(std::unique_ptr
<TestPPCallbacks
>(Callbacks
));
168 PP
.EnterMainSourceFile();
170 void EndSourceFileAction() override
{ SeenEnd
= Callbacks
->SeenEnd
; }
175 TEST(PreprocessorFrontendAction
, EndSourceFile
) {
176 auto Invocation
= std::make_shared
<CompilerInvocation
>();
177 Invocation
->getPreprocessorOpts().addRemappedFile(
179 MemoryBuffer::getMemBuffer("int main() { float x; }").release());
180 Invocation
->getFrontendOpts().Inputs
.push_back(
181 FrontendInputFile("test.cc", Language::CXX
));
182 Invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
183 Invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
184 CompilerInstance Compiler
;
185 Compiler
.setInvocation(std::move(Invocation
));
186 Compiler
.createDiagnostics();
188 TestPPCallbacks
*Callbacks
= new TestPPCallbacks
;
189 TestPPCallbacksFrontendAction
TestAction(Callbacks
);
190 ASSERT_FALSE(Callbacks
->SeenEnd
);
191 ASSERT_FALSE(TestAction
.SeenEnd
);
192 ASSERT_TRUE(Compiler
.ExecuteAction(TestAction
));
193 // Check that EndOfMainFile was called before EndSourceFileAction.
194 ASSERT_TRUE(TestAction
.SeenEnd
);
197 class TypoExternalSemaSource
: public ExternalSemaSource
{
198 CompilerInstance
&CI
;
201 TypoExternalSemaSource(CompilerInstance
&CI
) : CI(CI
) {}
203 TypoCorrection
CorrectTypo(const DeclarationNameInfo
&Typo
, int LookupKind
,
204 Scope
*S
, CXXScopeSpec
*SS
,
205 CorrectionCandidateCallback
&CCC
,
206 DeclContext
*MemberContext
, bool EnteringContext
,
207 const ObjCObjectPointerType
*OPT
) override
{
208 // Generate a fake typo correction with one attached note.
209 ASTContext
&Ctx
= CI
.getASTContext();
210 TypoCorrection
TC(DeclarationName(&Ctx
.Idents
.get("moo")));
211 unsigned DiagID
= Ctx
.getDiagnostics().getCustomDiagID(
212 DiagnosticsEngine::Note
, "This is a note");
213 TC
.addExtraDiagnostic(PartialDiagnostic(DiagID
, Ctx
.getDiagAllocator()));
218 struct TypoDiagnosticConsumer
: public DiagnosticConsumer
{
219 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel
,
220 const Diagnostic
&Info
) override
{
221 // Capture errors and notes. There should be one of each.
222 if (DiagLevel
== DiagnosticsEngine::Error
) {
223 assert(Error
.empty());
224 Info
.FormatDiagnostic(Error
);
226 assert(Note
.empty());
227 Info
.FormatDiagnostic(Note
);
230 SmallString
<32> Error
;
231 SmallString
<32> Note
;
234 TEST(ASTFrontendAction
, ExternalSemaSource
) {
235 auto Invocation
= std::make_shared
<CompilerInvocation
>();
236 Invocation
->getLangOpts()->CPlusPlus
= true;
237 Invocation
->getPreprocessorOpts().addRemappedFile(
238 "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n"
239 "int main() { foo(); }")
241 Invocation
->getFrontendOpts().Inputs
.push_back(
242 FrontendInputFile("test.cc", Language::CXX
));
243 Invocation
->getFrontendOpts().ProgramAction
= frontend::ParseSyntaxOnly
;
244 Invocation
->getTargetOpts().Triple
= "i386-unknown-linux-gnu";
245 CompilerInstance Compiler
;
246 Compiler
.setInvocation(std::move(Invocation
));
247 auto *TDC
= new TypoDiagnosticConsumer
;
248 Compiler
.createDiagnostics(TDC
, /*ShouldOwnClient=*/true);
249 Compiler
.setExternalSemaSource(new TypoExternalSemaSource(Compiler
));
251 SyntaxOnlyAction TestAction
;
252 ASSERT_TRUE(Compiler
.ExecuteAction(TestAction
));
253 // There should be one error correcting to 'moo' and a note attached to it.
254 EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?",
255 std::string(TDC
->Error
));
256 EXPECT_EQ("This is a note", std::string(TDC
->Note
));
259 TEST(GeneratePCHFrontendAction
, CacheGeneratedPCH
) {
260 // Create a temporary file for writing out the PCH that will be cleaned up.
262 llvm::SmallString
<128> PCHFilename
;
264 llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD
, PCHFilename
));
265 llvm::ToolOutputFile
PCHFile(PCHFilename
, PCHFD
);
267 for (bool ShouldCache
: {false, true}) {
268 auto Invocation
= std::make_shared
<CompilerInvocation
>();
269 Invocation
->getLangOpts()->CacheGeneratedPCH
= ShouldCache
;
270 Invocation
->getPreprocessorOpts().addRemappedFile(
272 MemoryBuffer::getMemBuffer("int foo(void) { return 1; }\n").release());
273 Invocation
->getFrontendOpts().Inputs
.push_back(
274 FrontendInputFile("test.h", Language::C
));
275 Invocation
->getFrontendOpts().OutputFile
=
276 std::string(StringRef(PCHFilename
));
277 Invocation
->getFrontendOpts().ProgramAction
= frontend::GeneratePCH
;
278 Invocation
->getTargetOpts().Triple
= "x86_64-apple-darwin19.0.0";
279 CompilerInstance Compiler
;
280 Compiler
.setInvocation(std::move(Invocation
));
281 Compiler
.createDiagnostics();
283 GeneratePCHAction TestAction
;
284 ASSERT_TRUE(Compiler
.ExecuteAction(TestAction
));
286 // Check whether the PCH was cached.
288 EXPECT_EQ(InMemoryModuleCache::Final
,
289 Compiler
.getModuleCache().getPCMState(PCHFilename
));
291 EXPECT_EQ(InMemoryModuleCache::Unknown
,
292 Compiler
.getModuleCache().getPCMState(PCHFilename
));
296 } // anonymous namespace