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