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