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/Frontend/CompilerInstance.h" 14 #include "clang/Frontend/CompilerInvocation.h" 15 #include "clang/Frontend/FrontendActions.h" 16 #include "clang/Lex/Preprocessor.h" 17 #include "clang/Lex/PreprocessorOptions.h" 18 #include "clang/Sema/Sema.h" 19 #include "clang/Serialization/InMemoryModuleCache.h" 20 #include "llvm/ADT/Triple.h" 21 #include "llvm/Support/MemoryBuffer.h" 22 #include "llvm/Support/ToolOutputFile.h" 23 #include "gtest/gtest.h" 24 25 using namespace llvm; 26 using namespace clang; 27 28 namespace { 29 30 class TestASTFrontendAction : public ASTFrontendAction { 31 public: 32 TestASTFrontendAction(bool enableIncrementalProcessing = false, 33 bool actOnEndOfTranslationUnit = false) 34 : EnableIncrementalProcessing(enableIncrementalProcessing), 35 ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { } 36 37 bool EnableIncrementalProcessing; 38 bool ActOnEndOfTranslationUnit; 39 std::vector<std::string> decl_names; 40 41 bool BeginSourceFileAction(CompilerInstance &ci) override { 42 if (EnableIncrementalProcessing) 43 ci.getPreprocessor().enableIncrementalProcessing(); 44 45 return ASTFrontendAction::BeginSourceFileAction(ci); 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 auto invocation = std::make_shared<CompilerInvocation>(); 83 invocation->getPreprocessorOpts().addRemappedFile( 84 "test.cc", 85 MemoryBuffer::getMemBuffer("int main() { float x; }").release()); 86 invocation->getFrontendOpts().Inputs.push_back( 87 FrontendInputFile("test.cc", InputKind::CXX)); 88 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 89 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 90 CompilerInstance compiler; 91 compiler.setInvocation(std::move(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 auto invocation = std::make_shared<CompilerInvocation>(); 103 invocation->getPreprocessorOpts().addRemappedFile( 104 "test.cc", 105 MemoryBuffer::getMemBuffer("int main() { float x; }").release()); 106 invocation->getFrontendOpts().Inputs.push_back( 107 FrontendInputFile("test.cc", InputKind::CXX)); 108 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 109 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 110 CompilerInstance compiler; 111 compiler.setInvocation(std::move(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 auto invocation = std::make_shared<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( 134 FrontendInputFile("test.cc", InputKind::CXX)); 135 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 136 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 137 CompilerInstance compiler; 138 compiler.setInvocation(std::move(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 auto Invocation = std::make_shared<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", InputKind::CXX)); 181 Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 182 Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 183 CompilerInstance Compiler; 184 Compiler.setInvocation(std::move(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 = std::make_shared<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", InputKind::CXX)); 242 Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 243 Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 244 CompilerInstance Compiler; 245 Compiler.setInvocation(std::move(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 TEST(GeneratePCHFrontendAction, CacheGeneratedPCH) { 259 // Create a temporary file for writing out the PCH that will be cleaned up. 260 int PCHFD; 261 llvm::SmallString<128> PCHFilename; 262 ASSERT_FALSE( 263 llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD, PCHFilename)); 264 llvm::ToolOutputFile PCHFile(PCHFilename, PCHFD); 265 266 for (bool ShouldCache : {false, true}) { 267 auto Invocation = std::make_shared<CompilerInvocation>(); 268 Invocation->getLangOpts()->CacheGeneratedPCH = ShouldCache; 269 Invocation->getPreprocessorOpts().addRemappedFile( 270 "test.h", 271 MemoryBuffer::getMemBuffer("int foo(void) { return 1; }\n").release()); 272 Invocation->getFrontendOpts().Inputs.push_back( 273 FrontendInputFile("test.h", InputKind::C)); 274 Invocation->getFrontendOpts().OutputFile = StringRef(PCHFilename); 275 Invocation->getFrontendOpts().ProgramAction = frontend::GeneratePCH; 276 Invocation->getTargetOpts().Triple = "x86_64-apple-darwin19.0.0"; 277 CompilerInstance Compiler; 278 Compiler.setInvocation(std::move(Invocation)); 279 Compiler.createDiagnostics(); 280 281 GeneratePCHAction TestAction; 282 ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); 283 284 // Check whether the PCH was cached. 285 if (ShouldCache) 286 EXPECT_EQ(InMemoryModuleCache::Final, 287 Compiler.getModuleCache().getPCMState(PCHFilename)); 288 else 289 EXPECT_EQ(InMemoryModuleCache::Unknown, 290 Compiler.getModuleCache().getPCMState(PCHFilename)); 291 } 292 } 293 294 } // anonymous namespace 295