xref: /llvm-project/clang/unittests/Frontend/FrontendActionTest.cpp (revision 7de9969bb0853f3331a29825ae5a959515d59d87)
1 //===- unittests/Frontend/FrontendActionTest.cpp - FrontendAction tests ---===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "clang/AST/ASTConsumer.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/RecursiveASTVisitor.h"
13 #include "clang/Frontend/CompilerInstance.h"
14 #include "clang/Frontend/CompilerInvocation.h"
15 #include "clang/Frontend/FrontendAction.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 "llvm/ADT/Triple.h"
21 #include "llvm/Support/MemoryBuffer.h"
22 #include "gtest/gtest.h"
23 
24 using namespace llvm;
25 using namespace clang;
26 
27 namespace {
28 
29 class TestASTFrontendAction : public ASTFrontendAction {
30 public:
31   TestASTFrontendAction(bool enableIncrementalProcessing = false,
32                         bool actOnEndOfTranslationUnit = false)
33     : EnableIncrementalProcessing(enableIncrementalProcessing),
34       ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { }
35 
36   bool EnableIncrementalProcessing;
37   bool ActOnEndOfTranslationUnit;
38   std::vector<std::string> decl_names;
39 
40   bool BeginSourceFileAction(CompilerInstance &ci,
41                              StringRef filename) override {
42     if (EnableIncrementalProcessing)
43       ci.getPreprocessor().enableIncrementalProcessing();
44 
45     return ASTFrontendAction::BeginSourceFileAction(ci, filename);
46   }
47 
48   std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
49                                                  StringRef InFile) override {
50     return llvm::make_unique<Visitor>(CI, ActOnEndOfTranslationUnit,
51                                       decl_names);
52   }
53 
54 private:
55   class Visitor : public ASTConsumer, public RecursiveASTVisitor<Visitor> {
56   public:
57     Visitor(CompilerInstance &CI, bool ActOnEndOfTranslationUnit,
58             std::vector<std::string> &decl_names) :
59       CI(CI), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit),
60       decl_names_(decl_names) {}
61 
62     void HandleTranslationUnit(ASTContext &context) override {
63       if (ActOnEndOfTranslationUnit) {
64         CI.getSema().ActOnEndOfTranslationUnit();
65       }
66       TraverseDecl(context.getTranslationUnitDecl());
67     }
68 
69     virtual bool VisitNamedDecl(NamedDecl *Decl) {
70       decl_names_.push_back(Decl->getQualifiedNameAsString());
71       return true;
72     }
73 
74   private:
75     CompilerInstance &CI;
76     bool ActOnEndOfTranslationUnit;
77     std::vector<std::string> &decl_names_;
78   };
79 };
80 
81 TEST(ASTFrontendAction, Sanity) {
82   CompilerInvocation *invocation = new CompilerInvocation;
83   invocation->getPreprocessorOpts().addRemappedFile(
84       "test.cc",
85       MemoryBuffer::getMemBuffer("int main() { float x; }").release());
86   invocation->getFrontendOpts().Inputs.push_back(FrontendInputFile("test.cc",
87                                                                    IK_CXX));
88   invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
89   invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
90   CompilerInstance compiler;
91   compiler.setInvocation(invocation);
92   compiler.createDiagnostics();
93 
94   TestASTFrontendAction test_action;
95   ASSERT_TRUE(compiler.ExecuteAction(test_action));
96   ASSERT_EQ(2U, test_action.decl_names.size());
97   EXPECT_EQ("main", test_action.decl_names[0]);
98   EXPECT_EQ("x", test_action.decl_names[1]);
99 }
100 
101 TEST(ASTFrontendAction, IncrementalParsing) {
102   CompilerInvocation *invocation = new CompilerInvocation;
103   invocation->getPreprocessorOpts().addRemappedFile(
104       "test.cc",
105       MemoryBuffer::getMemBuffer("int main() { float x; }").release());
106   invocation->getFrontendOpts().Inputs.push_back(FrontendInputFile("test.cc",
107                                                                    IK_CXX));
108   invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
109   invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
110   CompilerInstance compiler;
111   compiler.setInvocation(invocation);
112   compiler.createDiagnostics();
113 
114   TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true);
115   ASSERT_TRUE(compiler.ExecuteAction(test_action));
116   ASSERT_EQ(2U, test_action.decl_names.size());
117   EXPECT_EQ("main", test_action.decl_names[0]);
118   EXPECT_EQ("x", test_action.decl_names[1]);
119 }
120 
121 TEST(ASTFrontendAction, LateTemplateIncrementalParsing) {
122   CompilerInvocation *invocation = new CompilerInvocation;
123   invocation->getLangOpts()->CPlusPlus = true;
124   invocation->getLangOpts()->DelayedTemplateParsing = true;
125   invocation->getPreprocessorOpts().addRemappedFile(
126     "test.cc", MemoryBuffer::getMemBuffer(
127       "template<typename T> struct A { A(T); T data; };\n"
128       "template<typename T> struct B: public A<T> {\n"
129       "  B();\n"
130       "  B(B const& b): A<T>(b.data) {}\n"
131       "};\n"
132       "B<char> c() { return B<char>(); }\n").release());
133   invocation->getFrontendOpts().Inputs.push_back(FrontendInputFile("test.cc",
134                                                                    IK_CXX));
135   invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
136   invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
137   CompilerInstance compiler;
138   compiler.setInvocation(invocation);
139   compiler.createDiagnostics();
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 
149 struct TestPPCallbacks : public PPCallbacks {
150   TestPPCallbacks() : SeenEnd(false) {}
151 
152   void EndOfMainFile() override { SeenEnd = true; }
153 
154   bool SeenEnd;
155 };
156 
157 class TestPPCallbacksFrontendAction : public PreprocessorFrontendAction {
158   TestPPCallbacks *Callbacks;
159 
160 public:
161   TestPPCallbacksFrontendAction(TestPPCallbacks *C)
162       : Callbacks(C), SeenEnd(false) {}
163 
164   void ExecuteAction() override {
165     Preprocessor &PP = getCompilerInstance().getPreprocessor();
166     PP.addPPCallbacks(std::unique_ptr<TestPPCallbacks>(Callbacks));
167     PP.EnterMainSourceFile();
168   }
169   void EndSourceFileAction() override { SeenEnd = Callbacks->SeenEnd; }
170 
171   bool SeenEnd;
172 };
173 
174 TEST(PreprocessorFrontendAction, EndSourceFile) {
175   CompilerInvocation *Invocation = new CompilerInvocation;
176   Invocation->getPreprocessorOpts().addRemappedFile(
177       "test.cc",
178       MemoryBuffer::getMemBuffer("int main() { float x; }").release());
179   Invocation->getFrontendOpts().Inputs.push_back(
180       FrontendInputFile("test.cc", IK_CXX));
181   Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
182   Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
183   CompilerInstance Compiler;
184   Compiler.setInvocation(Invocation);
185   Compiler.createDiagnostics();
186 
187   TestPPCallbacks *Callbacks = new TestPPCallbacks;
188   TestPPCallbacksFrontendAction TestAction(Callbacks);
189   ASSERT_FALSE(Callbacks->SeenEnd);
190   ASSERT_FALSE(TestAction.SeenEnd);
191   ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
192   // Check that EndOfMainFile was called before EndSourceFileAction.
193   ASSERT_TRUE(TestAction.SeenEnd);
194 }
195 
196 class TypoExternalSemaSource : public ExternalSemaSource {
197   CompilerInstance &CI;
198 
199 public:
200   TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {}
201 
202   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
203                              Scope *S, CXXScopeSpec *SS,
204                              CorrectionCandidateCallback &CCC,
205                              DeclContext *MemberContext, bool EnteringContext,
206                              const ObjCObjectPointerType *OPT) override {
207     // Generate a fake typo correction with one attached note.
208     ASTContext &Ctx = CI.getASTContext();
209     TypoCorrection TC(DeclarationName(&Ctx.Idents.get("moo")));
210     unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID(
211         DiagnosticsEngine::Note, "This is a note");
212     TC.addExtraDiagnostic(PartialDiagnostic(DiagID, Ctx.getDiagAllocator()));
213     return TC;
214   }
215 };
216 
217 struct TypoDiagnosticConsumer : public DiagnosticConsumer {
218   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
219                         const Diagnostic &Info) override {
220     // Capture errors and notes. There should be one of each.
221     if (DiagLevel == DiagnosticsEngine::Error) {
222       assert(Error.empty());
223       Info.FormatDiagnostic(Error);
224     } else {
225       assert(Note.empty());
226       Info.FormatDiagnostic(Note);
227     }
228   }
229   SmallString<32> Error;
230   SmallString<32> Note;
231 };
232 
233 TEST(ASTFrontendAction, ExternalSemaSource) {
234   auto *Invocation = new CompilerInvocation;
235   Invocation->getLangOpts()->CPlusPlus = true;
236   Invocation->getPreprocessorOpts().addRemappedFile(
237       "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n"
238                                             "int main() { foo(); }")
239                      .release());
240   Invocation->getFrontendOpts().Inputs.push_back(
241       FrontendInputFile("test.cc", IK_CXX));
242   Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
243   Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
244   CompilerInstance Compiler;
245   Compiler.setInvocation(Invocation);
246   auto *TDC = new TypoDiagnosticConsumer;
247   Compiler.createDiagnostics(TDC, /*ShouldOwnClient=*/true);
248   Compiler.setExternalSemaSource(new TypoExternalSemaSource(Compiler));
249 
250   SyntaxOnlyAction TestAction;
251   ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
252   // There should be one error correcting to 'moo' and a note attached to it.
253   EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?",
254             TDC->Error.str().str());
255   EXPECT_EQ("This is a note", TDC->Note.str().str());
256 }
257 
258 } // anonymous namespace
259