xref: /llvm-project/clang/unittests/Frontend/FrontendActionTest.cpp (revision df9a14d7bbf1180e4f1474254c9d7ed6bcb4ce55)
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 "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"
26 
27 using namespace llvm;
28 using namespace clang;
29 
30 namespace {
31 
32 class TestASTFrontendAction : public ASTFrontendAction {
33 public:
34   TestASTFrontendAction(bool enableIncrementalProcessing = false,
35                         bool actOnEndOfTranslationUnit = false)
36     : EnableIncrementalProcessing(enableIncrementalProcessing),
37       ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { }
38 
39   bool EnableIncrementalProcessing;
40   bool ActOnEndOfTranslationUnit;
41   std::vector<std::string> decl_names;
42 
43   bool BeginSourceFileAction(CompilerInstance &ci) override {
44     if (EnableIncrementalProcessing)
45       ci.getPreprocessor().enableIncrementalProcessing();
46 
47     return ASTFrontendAction::BeginSourceFileAction(ci);
48   }
49 
50   std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
51                                                  StringRef InFile) override {
52     return std::make_unique<Visitor>(CI, ActOnEndOfTranslationUnit,
53                                       decl_names);
54   }
55 
56 private:
57   class Visitor : public ASTConsumer, public DynamicRecursiveASTVisitor {
58   public:
59     Visitor(CompilerInstance &CI, bool ActOnEndOfTranslationUnit,
60             std::vector<std::string> &decl_names) :
61       CI(CI), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit),
62       decl_names_(decl_names) {}
63 
64     void HandleTranslationUnit(ASTContext &context) override {
65       if (ActOnEndOfTranslationUnit) {
66         CI.getSema().ActOnEndOfTranslationUnit();
67       }
68       TraverseDecl(context.getTranslationUnitDecl());
69     }
70 
71     bool VisitNamedDecl(NamedDecl *Decl) override {
72       decl_names_.push_back(Decl->getQualifiedNameAsString());
73       return true;
74     }
75 
76   private:
77     CompilerInstance &CI;
78     bool ActOnEndOfTranslationUnit;
79     std::vector<std::string> &decl_names_;
80   };
81 };
82 
83 TEST(ASTFrontendAction, Sanity) {
84   auto invocation = std::make_shared<CompilerInvocation>();
85   invocation->getPreprocessorOpts().addRemappedFile(
86       "test.cc",
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());
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 
103 TEST(ASTFrontendAction, IncrementalParsing) {
104   auto invocation = std::make_shared<CompilerInvocation>();
105   invocation->getPreprocessorOpts().addRemappedFile(
106       "test.cc",
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());
115 
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]);
121 }
122 
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"
131       "  B();\n"
132       "  B(B const& b): A<T>(b.data) {}\n"
133       "};\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());
142 
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]);
149 }
150 
151 struct TestPPCallbacks : public PPCallbacks {
152   TestPPCallbacks() : SeenEnd(false) {}
153 
154   void EndOfMainFile() override { SeenEnd = true; }
155 
156   bool SeenEnd;
157 };
158 
159 class TestPPCallbacksFrontendAction : public PreprocessorFrontendAction {
160   TestPPCallbacks *Callbacks;
161 
162 public:
163   TestPPCallbacksFrontendAction(TestPPCallbacks *C)
164       : Callbacks(C), SeenEnd(false) {}
165 
166   void ExecuteAction() override {
167     Preprocessor &PP = getCompilerInstance().getPreprocessor();
168     PP.addPPCallbacks(std::unique_ptr<TestPPCallbacks>(Callbacks));
169     PP.EnterMainSourceFile();
170   }
171   void EndSourceFileAction() override { SeenEnd = Callbacks->SeenEnd; }
172 
173   bool SeenEnd;
174 };
175 
176 TEST(PreprocessorFrontendAction, EndSourceFile) {
177   auto Invocation = std::make_shared<CompilerInvocation>();
178   Invocation->getPreprocessorOpts().addRemappedFile(
179       "test.cc",
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());
188 
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);
196 }
197 
198 class TypoExternalSemaSource : public ExternalSemaSource {
199   CompilerInstance &CI;
200 
201 public:
202   TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {}
203 
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()));
215     return TC;
216   }
217 };
218 
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);
226     } else {
227       assert(Note.empty());
228       Info.FormatDiagnostic(Note);
229     }
230   }
231   SmallString<32> Error;
232   SmallString<32> Note;
233 };
234 
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(); }")
241                      .release());
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));
252 
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));
259 }
260 
261 TEST(GeneratePCHFrontendAction, CacheGeneratedPCH) {
262   // Create a temporary file for writing out the PCH that will be cleaned up.
263   int PCHFD;
264   llvm::SmallString<128> PCHFilename;
265   ASSERT_FALSE(
266       llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD, PCHFilename));
267   llvm::ToolOutputFile PCHFile(PCHFilename, PCHFD);
268 
269   for (bool ShouldCache : {false, true}) {
270     auto Invocation = std::make_shared<CompilerInvocation>();
271     Invocation->getLangOpts().CacheGeneratedPCH = ShouldCache;
272     Invocation->getPreprocessorOpts().addRemappedFile(
273         "test.h",
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());
283 
284     GeneratePCHAction TestAction;
285     ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
286 
287     // Check whether the PCH was cached.
288     if (ShouldCache)
289       EXPECT_EQ(InMemoryModuleCache::Final,
290                 Compiler.getModuleCache().getPCMState(PCHFilename));
291     else
292       EXPECT_EQ(InMemoryModuleCache::Unknown,
293                 Compiler.getModuleCache().getPCMState(PCHFilename));
294   }
295 }
296 
297 } // anonymous namespace
298