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/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/ADT/Triple.h" 22 #include "llvm/Support/MemoryBuffer.h" 23 #include "llvm/Support/ToolOutputFile.h" 24 #include "gtest/gtest.h" 25 26 using namespace llvm; 27 using namespace clang; 28 29 namespace { 30 31 class TestASTFrontendAction : public ASTFrontendAction { 32 public: 33 TestASTFrontendAction(bool enableIncrementalProcessing = false, 34 bool actOnEndOfTranslationUnit = false) 35 : EnableIncrementalProcessing(enableIncrementalProcessing), 36 ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { } 37 38 bool EnableIncrementalProcessing; 39 bool ActOnEndOfTranslationUnit; 40 std::vector<std::string> decl_names; 41 42 bool BeginSourceFileAction(CompilerInstance &ci) override { 43 if (EnableIncrementalProcessing) 44 ci.getPreprocessor().enableIncrementalProcessing(); 45 46 return ASTFrontendAction::BeginSourceFileAction(ci); 47 } 48 49 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, 50 StringRef InFile) override { 51 return std::make_unique<Visitor>(CI, ActOnEndOfTranslationUnit, 52 decl_names); 53 } 54 55 private: 56 class Visitor : public ASTConsumer, public RecursiveASTVisitor<Visitor> { 57 public: 58 Visitor(CompilerInstance &CI, bool ActOnEndOfTranslationUnit, 59 std::vector<std::string> &decl_names) : 60 CI(CI), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit), 61 decl_names_(decl_names) {} 62 63 void HandleTranslationUnit(ASTContext &context) override { 64 if (ActOnEndOfTranslationUnit) { 65 CI.getSema().ActOnEndOfTranslationUnit(); 66 } 67 TraverseDecl(context.getTranslationUnitDecl()); 68 } 69 70 virtual bool VisitNamedDecl(NamedDecl *Decl) { 71 decl_names_.push_back(Decl->getQualifiedNameAsString()); 72 return true; 73 } 74 75 private: 76 CompilerInstance &CI; 77 bool ActOnEndOfTranslationUnit; 78 std::vector<std::string> &decl_names_; 79 }; 80 }; 81 82 TEST(ASTFrontendAction, Sanity) { 83 auto invocation = std::make_shared<CompilerInvocation>(); 84 invocation->getPreprocessorOpts().addRemappedFile( 85 "test.cc", 86 MemoryBuffer::getMemBuffer("int main() { float x; }").release()); 87 invocation->getFrontendOpts().Inputs.push_back( 88 FrontendInputFile("test.cc", Language::CXX)); 89 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 90 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 91 CompilerInstance compiler; 92 compiler.setInvocation(std::move(invocation)); 93 compiler.createDiagnostics(); 94 95 TestASTFrontendAction test_action; 96 ASSERT_TRUE(compiler.ExecuteAction(test_action)); 97 ASSERT_EQ(2U, test_action.decl_names.size()); 98 EXPECT_EQ("main", test_action.decl_names[0]); 99 EXPECT_EQ("x", test_action.decl_names[1]); 100 } 101 102 TEST(ASTFrontendAction, IncrementalParsing) { 103 auto invocation = std::make_shared<CompilerInvocation>(); 104 invocation->getPreprocessorOpts().addRemappedFile( 105 "test.cc", 106 MemoryBuffer::getMemBuffer("int main() { float x; }").release()); 107 invocation->getFrontendOpts().Inputs.push_back( 108 FrontendInputFile("test.cc", Language::CXX)); 109 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 110 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 111 CompilerInstance compiler; 112 compiler.setInvocation(std::move(invocation)); 113 compiler.createDiagnostics(); 114 115 TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true); 116 ASSERT_TRUE(compiler.ExecuteAction(test_action)); 117 ASSERT_EQ(2U, test_action.decl_names.size()); 118 EXPECT_EQ("main", test_action.decl_names[0]); 119 EXPECT_EQ("x", test_action.decl_names[1]); 120 } 121 122 TEST(ASTFrontendAction, LateTemplateIncrementalParsing) { 123 auto invocation = std::make_shared<CompilerInvocation>(); 124 invocation->getLangOpts()->CPlusPlus = true; 125 invocation->getLangOpts()->DelayedTemplateParsing = true; 126 invocation->getPreprocessorOpts().addRemappedFile( 127 "test.cc", MemoryBuffer::getMemBuffer( 128 "template<typename T> struct A { A(T); T data; };\n" 129 "template<typename T> struct B: public A<T> {\n" 130 " B();\n" 131 " B(B const& b): A<T>(b.data) {}\n" 132 "};\n" 133 "B<char> c() { return B<char>(); }\n").release()); 134 invocation->getFrontendOpts().Inputs.push_back( 135 FrontendInputFile("test.cc", Language::CXX)); 136 invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 137 invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 138 CompilerInstance compiler; 139 compiler.setInvocation(std::move(invocation)); 140 compiler.createDiagnostics(); 141 142 TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true, 143 /*actOnEndOfTranslationUnit=*/true); 144 ASSERT_TRUE(compiler.ExecuteAction(test_action)); 145 ASSERT_EQ(13U, test_action.decl_names.size()); 146 EXPECT_EQ("A", test_action.decl_names[0]); 147 EXPECT_EQ("c", test_action.decl_names[12]); 148 } 149 150 struct TestPPCallbacks : public PPCallbacks { 151 TestPPCallbacks() : SeenEnd(false) {} 152 153 void EndOfMainFile() override { SeenEnd = true; } 154 155 bool SeenEnd; 156 }; 157 158 class TestPPCallbacksFrontendAction : public PreprocessorFrontendAction { 159 TestPPCallbacks *Callbacks; 160 161 public: 162 TestPPCallbacksFrontendAction(TestPPCallbacks *C) 163 : Callbacks(C), SeenEnd(false) {} 164 165 void ExecuteAction() override { 166 Preprocessor &PP = getCompilerInstance().getPreprocessor(); 167 PP.addPPCallbacks(std::unique_ptr<TestPPCallbacks>(Callbacks)); 168 PP.EnterMainSourceFile(); 169 } 170 void EndSourceFileAction() override { SeenEnd = Callbacks->SeenEnd; } 171 172 bool SeenEnd; 173 }; 174 175 TEST(PreprocessorFrontendAction, EndSourceFile) { 176 auto Invocation = std::make_shared<CompilerInvocation>(); 177 Invocation->getPreprocessorOpts().addRemappedFile( 178 "test.cc", 179 MemoryBuffer::getMemBuffer("int main() { float x; }").release()); 180 Invocation->getFrontendOpts().Inputs.push_back( 181 FrontendInputFile("test.cc", Language::CXX)); 182 Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 183 Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 184 CompilerInstance Compiler; 185 Compiler.setInvocation(std::move(Invocation)); 186 Compiler.createDiagnostics(); 187 188 TestPPCallbacks *Callbacks = new TestPPCallbacks; 189 TestPPCallbacksFrontendAction TestAction(Callbacks); 190 ASSERT_FALSE(Callbacks->SeenEnd); 191 ASSERT_FALSE(TestAction.SeenEnd); 192 ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); 193 // Check that EndOfMainFile was called before EndSourceFileAction. 194 ASSERT_TRUE(TestAction.SeenEnd); 195 } 196 197 class TypoExternalSemaSource : public ExternalSemaSource { 198 CompilerInstance &CI; 199 200 public: 201 TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {} 202 203 TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, 204 Scope *S, CXXScopeSpec *SS, 205 CorrectionCandidateCallback &CCC, 206 DeclContext *MemberContext, bool EnteringContext, 207 const ObjCObjectPointerType *OPT) override { 208 // Generate a fake typo correction with one attached note. 209 ASTContext &Ctx = CI.getASTContext(); 210 TypoCorrection TC(DeclarationName(&Ctx.Idents.get("moo"))); 211 unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID( 212 DiagnosticsEngine::Note, "This is a note"); 213 TC.addExtraDiagnostic(PartialDiagnostic(DiagID, Ctx.getDiagAllocator())); 214 return TC; 215 } 216 }; 217 218 struct TypoDiagnosticConsumer : public DiagnosticConsumer { 219 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 220 const Diagnostic &Info) override { 221 // Capture errors and notes. There should be one of each. 222 if (DiagLevel == DiagnosticsEngine::Error) { 223 assert(Error.empty()); 224 Info.FormatDiagnostic(Error); 225 } else { 226 assert(Note.empty()); 227 Info.FormatDiagnostic(Note); 228 } 229 } 230 SmallString<32> Error; 231 SmallString<32> Note; 232 }; 233 234 TEST(ASTFrontendAction, ExternalSemaSource) { 235 auto Invocation = std::make_shared<CompilerInvocation>(); 236 Invocation->getLangOpts()->CPlusPlus = true; 237 Invocation->getPreprocessorOpts().addRemappedFile( 238 "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n" 239 "int main() { foo(); }") 240 .release()); 241 Invocation->getFrontendOpts().Inputs.push_back( 242 FrontendInputFile("test.cc", Language::CXX)); 243 Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; 244 Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; 245 CompilerInstance Compiler; 246 Compiler.setInvocation(std::move(Invocation)); 247 auto *TDC = new TypoDiagnosticConsumer; 248 Compiler.createDiagnostics(TDC, /*ShouldOwnClient=*/true); 249 Compiler.setExternalSemaSource(new TypoExternalSemaSource(Compiler)); 250 251 SyntaxOnlyAction TestAction; 252 ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); 253 // There should be one error correcting to 'moo' and a note attached to it. 254 EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?", 255 std::string(TDC->Error)); 256 EXPECT_EQ("This is a note", std::string(TDC->Note)); 257 } 258 259 TEST(GeneratePCHFrontendAction, CacheGeneratedPCH) { 260 // Create a temporary file for writing out the PCH that will be cleaned up. 261 int PCHFD; 262 llvm::SmallString<128> PCHFilename; 263 ASSERT_FALSE( 264 llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD, PCHFilename)); 265 llvm::ToolOutputFile PCHFile(PCHFilename, PCHFD); 266 267 for (bool ShouldCache : {false, true}) { 268 auto Invocation = std::make_shared<CompilerInvocation>(); 269 Invocation->getLangOpts()->CacheGeneratedPCH = ShouldCache; 270 Invocation->getPreprocessorOpts().addRemappedFile( 271 "test.h", 272 MemoryBuffer::getMemBuffer("int foo(void) { return 1; }\n").release()); 273 Invocation->getFrontendOpts().Inputs.push_back( 274 FrontendInputFile("test.h", Language::C)); 275 Invocation->getFrontendOpts().OutputFile = 276 std::string(StringRef(PCHFilename)); 277 Invocation->getFrontendOpts().ProgramAction = frontend::GeneratePCH; 278 Invocation->getTargetOpts().Triple = "x86_64-apple-darwin19.0.0"; 279 CompilerInstance Compiler; 280 Compiler.setInvocation(std::move(Invocation)); 281 Compiler.createDiagnostics(); 282 283 GeneratePCHAction TestAction; 284 ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); 285 286 // Check whether the PCH was cached. 287 if (ShouldCache) 288 EXPECT_EQ(InMemoryModuleCache::Final, 289 Compiler.getModuleCache().getPCMState(PCHFilename)); 290 else 291 EXPECT_EQ(InMemoryModuleCache::Unknown, 292 Compiler.getModuleCache().getPCMState(PCHFilename)); 293 } 294 } 295 296 } // anonymous namespace 297