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/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"
25 
26 using namespace llvm;
27 using namespace clang;
28 
29 namespace {
30 
31 class TestASTFrontendAction : public ASTFrontendAction {
32 public:
TestASTFrontendAction(bool enableIncrementalProcessing=false,bool actOnEndOfTranslationUnit=false)33   TestASTFrontendAction(bool enableIncrementalProcessing = false,
34                         bool actOnEndOfTranslationUnit = false)
35     : EnableIncrementalProcessing(enableIncrementalProcessing),
36       ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { }
37 
38   bool EnableIncrementalProcessing;
39   bool ActOnEndOfTranslationUnit;
40   std::vector<std::string> decl_names;
41 
BeginSourceFileAction(CompilerInstance & ci)42   bool BeginSourceFileAction(CompilerInstance &ci) override {
43     if (EnableIncrementalProcessing)
44       ci.getPreprocessor().enableIncrementalProcessing();
45 
46     return ASTFrontendAction::BeginSourceFileAction(ci);
47   }
48 
CreateASTConsumer(CompilerInstance & CI,StringRef InFile)49   std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
50                                                  StringRef InFile) override {
51     return std::make_unique<Visitor>(CI, ActOnEndOfTranslationUnit,
52                                       decl_names);
53   }
54 
55 private:
56   class Visitor : public ASTConsumer, public RecursiveASTVisitor<Visitor> {
57   public:
Visitor(CompilerInstance & CI,bool ActOnEndOfTranslationUnit,std::vector<std::string> & decl_names)58     Visitor(CompilerInstance &CI, bool ActOnEndOfTranslationUnit,
59             std::vector<std::string> &decl_names) :
60       CI(CI), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit),
61       decl_names_(decl_names) {}
62 
HandleTranslationUnit(ASTContext & context)63     void HandleTranslationUnit(ASTContext &context) override {
64       if (ActOnEndOfTranslationUnit) {
65         CI.getSema().ActOnEndOfTranslationUnit();
66       }
67       TraverseDecl(context.getTranslationUnitDecl());
68     }
69 
VisitNamedDecl(NamedDecl * Decl)70     virtual bool VisitNamedDecl(NamedDecl *Decl) {
71       decl_names_.push_back(Decl->getQualifiedNameAsString());
72       return true;
73     }
74 
75   private:
76     CompilerInstance &CI;
77     bool ActOnEndOfTranslationUnit;
78     std::vector<std::string> &decl_names_;
79   };
80 };
81 
TEST(ASTFrontendAction,Sanity)82 TEST(ASTFrontendAction, Sanity) {
83   auto invocation = std::make_shared<CompilerInvocation>();
84   invocation->getPreprocessorOpts().addRemappedFile(
85       "test.cc",
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();
94 
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]);
100 }
101 
TEST(ASTFrontendAction,IncrementalParsing)102 TEST(ASTFrontendAction, IncrementalParsing) {
103   auto invocation = std::make_shared<CompilerInvocation>();
104   invocation->getPreprocessorOpts().addRemappedFile(
105       "test.cc",
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();
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 
TEST(ASTFrontendAction,LateTemplateIncrementalParsing)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"
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       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();
141 
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]);
148 }
149 
150 struct TestPPCallbacks : public PPCallbacks {
TestPPCallbacks__anone0b07fbc0111::TestPPCallbacks151   TestPPCallbacks() : SeenEnd(false) {}
152 
EndOfMainFile__anone0b07fbc0111::TestPPCallbacks153   void EndOfMainFile() override { SeenEnd = true; }
154 
155   bool SeenEnd;
156 };
157 
158 class TestPPCallbacksFrontendAction : public PreprocessorFrontendAction {
159   TestPPCallbacks *Callbacks;
160 
161 public:
TestPPCallbacksFrontendAction(TestPPCallbacks * C)162   TestPPCallbacksFrontendAction(TestPPCallbacks *C)
163       : Callbacks(C), SeenEnd(false) {}
164 
ExecuteAction()165   void ExecuteAction() override {
166     Preprocessor &PP = getCompilerInstance().getPreprocessor();
167     PP.addPPCallbacks(std::unique_ptr<TestPPCallbacks>(Callbacks));
168     PP.EnterMainSourceFile();
169   }
EndSourceFileAction()170   void EndSourceFileAction() override { SeenEnd = Callbacks->SeenEnd; }
171 
172   bool SeenEnd;
173 };
174 
TEST(PreprocessorFrontendAction,EndSourceFile)175 TEST(PreprocessorFrontendAction, EndSourceFile) {
176   auto Invocation = std::make_shared<CompilerInvocation>();
177   Invocation->getPreprocessorOpts().addRemappedFile(
178       "test.cc",
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();
187 
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);
195 }
196 
197 class TypoExternalSemaSource : public ExternalSemaSource {
198   CompilerInstance &CI;
199 
200 public:
TypoExternalSemaSource(CompilerInstance & CI)201   TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {}
202 
CorrectTypo(const DeclarationNameInfo & Typo,int LookupKind,Scope * S,CXXScopeSpec * SS,CorrectionCandidateCallback & CCC,DeclContext * MemberContext,bool EnteringContext,const ObjCObjectPointerType * OPT)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()));
214     return TC;
215   }
216 };
217 
218 struct TypoDiagnosticConsumer : public DiagnosticConsumer {
HandleDiagnostic__anone0b07fbc0111::TypoDiagnosticConsumer219   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);
225     } else {
226       assert(Note.empty());
227       Info.FormatDiagnostic(Note);
228     }
229   }
230   SmallString<32> Error;
231   SmallString<32> Note;
232 };
233 
TEST(ASTFrontendAction,ExternalSemaSource)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(); }")
240                      .release());
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));
250 
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));
257 }
258 
TEST(GeneratePCHFrontendAction,CacheGeneratedPCH)259 TEST(GeneratePCHFrontendAction, CacheGeneratedPCH) {
260   // Create a temporary file for writing out the PCH that will be cleaned up.
261   int PCHFD;
262   llvm::SmallString<128> PCHFilename;
263   ASSERT_FALSE(
264       llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD, PCHFilename));
265   llvm::ToolOutputFile PCHFile(PCHFilename, PCHFD);
266 
267   for (bool ShouldCache : {false, true}) {
268     auto Invocation = std::make_shared<CompilerInvocation>();
269     Invocation->getLangOpts()->CacheGeneratedPCH = ShouldCache;
270     Invocation->getPreprocessorOpts().addRemappedFile(
271         "test.h",
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 = PCHFilename.str().str();
276     Invocation->getFrontendOpts().ProgramAction = frontend::GeneratePCH;
277     Invocation->getTargetOpts().Triple = "x86_64-apple-darwin19.0.0";
278     CompilerInstance Compiler;
279     Compiler.setInvocation(std::move(Invocation));
280     Compiler.createDiagnostics();
281 
282     GeneratePCHAction TestAction;
283     ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
284 
285     // Check whether the PCH was cached.
286     if (ShouldCache)
287       EXPECT_EQ(InMemoryModuleCache::Final,
288                 Compiler.getModuleCache().getPCMState(PCHFilename));
289     else
290       EXPECT_EQ(InMemoryModuleCache::Unknown,
291                 Compiler.getModuleCache().getPCMState(PCHFilename));
292   }
293 }
294 
295 } // anonymous namespace
296