| //===- unittests/Frontend/FrontendActionTest.cpp - FrontendAction tests ---===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Frontend/FrontendAction.h" |
| #include "clang/AST/ASTConsumer.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/Basic/LangStandard.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Frontend/CompilerInvocation.h" |
| #include "clang/Frontend/FrontendActions.h" |
| #include "clang/Lex/Preprocessor.h" |
| #include "clang/Lex/PreprocessorOptions.h" |
| #include "clang/Sema/Sema.h" |
| #include "clang/Serialization/InMemoryModuleCache.h" |
| #include "llvm/ADT/Triple.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/ToolOutputFile.h" |
| #include "gtest/gtest.h" |
| |
| using namespace llvm; |
| using namespace clang; |
| |
| namespace { |
| |
| class TestASTFrontendAction : public ASTFrontendAction { |
| public: |
| TestASTFrontendAction(bool enableIncrementalProcessing = false, |
| bool actOnEndOfTranslationUnit = false) |
| : EnableIncrementalProcessing(enableIncrementalProcessing), |
| ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { } |
| |
| bool EnableIncrementalProcessing; |
| bool ActOnEndOfTranslationUnit; |
| std::vector<std::string> decl_names; |
| |
| bool BeginSourceFileAction(CompilerInstance &ci) override { |
| if (EnableIncrementalProcessing) |
| ci.getPreprocessor().enableIncrementalProcessing(); |
| |
| return ASTFrontendAction::BeginSourceFileAction(ci); |
| } |
| |
| std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, |
| StringRef InFile) override { |
| return std::make_unique<Visitor>(CI, ActOnEndOfTranslationUnit, |
| decl_names); |
| } |
| |
| private: |
| class Visitor : public ASTConsumer, public RecursiveASTVisitor<Visitor> { |
| public: |
| Visitor(CompilerInstance &CI, bool ActOnEndOfTranslationUnit, |
| std::vector<std::string> &decl_names) : |
| CI(CI), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit), |
| decl_names_(decl_names) {} |
| |
| void HandleTranslationUnit(ASTContext &context) override { |
| if (ActOnEndOfTranslationUnit) { |
| CI.getSema().ActOnEndOfTranslationUnit(); |
| } |
| TraverseDecl(context.getTranslationUnitDecl()); |
| } |
| |
| virtual bool VisitNamedDecl(NamedDecl *Decl) { |
| decl_names_.push_back(Decl->getQualifiedNameAsString()); |
| return true; |
| } |
| |
| private: |
| CompilerInstance &CI; |
| bool ActOnEndOfTranslationUnit; |
| std::vector<std::string> &decl_names_; |
| }; |
| }; |
| |
| TEST(ASTFrontendAction, Sanity) { |
| auto invocation = std::make_shared<CompilerInvocation>(); |
| invocation->getPreprocessorOpts().addRemappedFile( |
| "test.cc", |
| MemoryBuffer::getMemBuffer("int main() { float x; }").release()); |
| invocation->getFrontendOpts().Inputs.push_back( |
| FrontendInputFile("test.cc", Language::CXX)); |
| invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; |
| invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; |
| CompilerInstance compiler; |
| compiler.setInvocation(std::move(invocation)); |
| compiler.createDiagnostics(); |
| |
| TestASTFrontendAction test_action; |
| ASSERT_TRUE(compiler.ExecuteAction(test_action)); |
| ASSERT_EQ(2U, test_action.decl_names.size()); |
| EXPECT_EQ("main", test_action.decl_names[0]); |
| EXPECT_EQ("x", test_action.decl_names[1]); |
| } |
| |
| TEST(ASTFrontendAction, IncrementalParsing) { |
| auto invocation = std::make_shared<CompilerInvocation>(); |
| invocation->getPreprocessorOpts().addRemappedFile( |
| "test.cc", |
| MemoryBuffer::getMemBuffer("int main() { float x; }").release()); |
| invocation->getFrontendOpts().Inputs.push_back( |
| FrontendInputFile("test.cc", Language::CXX)); |
| invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; |
| invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; |
| CompilerInstance compiler; |
| compiler.setInvocation(std::move(invocation)); |
| compiler.createDiagnostics(); |
| |
| TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true); |
| ASSERT_TRUE(compiler.ExecuteAction(test_action)); |
| ASSERT_EQ(2U, test_action.decl_names.size()); |
| EXPECT_EQ("main", test_action.decl_names[0]); |
| EXPECT_EQ("x", test_action.decl_names[1]); |
| } |
| |
| TEST(ASTFrontendAction, LateTemplateIncrementalParsing) { |
| auto invocation = std::make_shared<CompilerInvocation>(); |
| invocation->getLangOpts()->CPlusPlus = true; |
| invocation->getLangOpts()->DelayedTemplateParsing = true; |
| invocation->getPreprocessorOpts().addRemappedFile( |
| "test.cc", MemoryBuffer::getMemBuffer( |
| "template<typename T> struct A { A(T); T data; };\n" |
| "template<typename T> struct B: public A<T> {\n" |
| " B();\n" |
| " B(B const& b): A<T>(b.data) {}\n" |
| "};\n" |
| "B<char> c() { return B<char>(); }\n").release()); |
| invocation->getFrontendOpts().Inputs.push_back( |
| FrontendInputFile("test.cc", Language::CXX)); |
| invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; |
| invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; |
| CompilerInstance compiler; |
| compiler.setInvocation(std::move(invocation)); |
| compiler.createDiagnostics(); |
| |
| TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true, |
| /*actOnEndOfTranslationUnit=*/true); |
| ASSERT_TRUE(compiler.ExecuteAction(test_action)); |
| ASSERT_EQ(13U, test_action.decl_names.size()); |
| EXPECT_EQ("A", test_action.decl_names[0]); |
| EXPECT_EQ("c", test_action.decl_names[12]); |
| } |
| |
| struct TestPPCallbacks : public PPCallbacks { |
| TestPPCallbacks() : SeenEnd(false) {} |
| |
| void EndOfMainFile() override { SeenEnd = true; } |
| |
| bool SeenEnd; |
| }; |
| |
| class TestPPCallbacksFrontendAction : public PreprocessorFrontendAction { |
| TestPPCallbacks *Callbacks; |
| |
| public: |
| TestPPCallbacksFrontendAction(TestPPCallbacks *C) |
| : Callbacks(C), SeenEnd(false) {} |
| |
| void ExecuteAction() override { |
| Preprocessor &PP = getCompilerInstance().getPreprocessor(); |
| PP.addPPCallbacks(std::unique_ptr<TestPPCallbacks>(Callbacks)); |
| PP.EnterMainSourceFile(); |
| } |
| void EndSourceFileAction() override { SeenEnd = Callbacks->SeenEnd; } |
| |
| bool SeenEnd; |
| }; |
| |
| TEST(PreprocessorFrontendAction, EndSourceFile) { |
| auto Invocation = std::make_shared<CompilerInvocation>(); |
| Invocation->getPreprocessorOpts().addRemappedFile( |
| "test.cc", |
| MemoryBuffer::getMemBuffer("int main() { float x; }").release()); |
| Invocation->getFrontendOpts().Inputs.push_back( |
| FrontendInputFile("test.cc", Language::CXX)); |
| Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; |
| Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; |
| CompilerInstance Compiler; |
| Compiler.setInvocation(std::move(Invocation)); |
| Compiler.createDiagnostics(); |
| |
| TestPPCallbacks *Callbacks = new TestPPCallbacks; |
| TestPPCallbacksFrontendAction TestAction(Callbacks); |
| ASSERT_FALSE(Callbacks->SeenEnd); |
| ASSERT_FALSE(TestAction.SeenEnd); |
| ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); |
| // Check that EndOfMainFile was called before EndSourceFileAction. |
| ASSERT_TRUE(TestAction.SeenEnd); |
| } |
| |
| class TypoExternalSemaSource : public ExternalSemaSource { |
| CompilerInstance &CI; |
| |
| public: |
| TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {} |
| |
| TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, |
| Scope *S, CXXScopeSpec *SS, |
| CorrectionCandidateCallback &CCC, |
| DeclContext *MemberContext, bool EnteringContext, |
| const ObjCObjectPointerType *OPT) override { |
| // Generate a fake typo correction with one attached note. |
| ASTContext &Ctx = CI.getASTContext(); |
| TypoCorrection TC(DeclarationName(&Ctx.Idents.get("moo"))); |
| unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID( |
| DiagnosticsEngine::Note, "This is a note"); |
| TC.addExtraDiagnostic(PartialDiagnostic(DiagID, Ctx.getDiagAllocator())); |
| return TC; |
| } |
| }; |
| |
| struct TypoDiagnosticConsumer : public DiagnosticConsumer { |
| void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, |
| const Diagnostic &Info) override { |
| // Capture errors and notes. There should be one of each. |
| if (DiagLevel == DiagnosticsEngine::Error) { |
| assert(Error.empty()); |
| Info.FormatDiagnostic(Error); |
| } else { |
| assert(Note.empty()); |
| Info.FormatDiagnostic(Note); |
| } |
| } |
| SmallString<32> Error; |
| SmallString<32> Note; |
| }; |
| |
| TEST(ASTFrontendAction, ExternalSemaSource) { |
| auto Invocation = std::make_shared<CompilerInvocation>(); |
| Invocation->getLangOpts()->CPlusPlus = true; |
| Invocation->getPreprocessorOpts().addRemappedFile( |
| "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n" |
| "int main() { foo(); }") |
| .release()); |
| Invocation->getFrontendOpts().Inputs.push_back( |
| FrontendInputFile("test.cc", Language::CXX)); |
| Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; |
| Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; |
| CompilerInstance Compiler; |
| Compiler.setInvocation(std::move(Invocation)); |
| auto *TDC = new TypoDiagnosticConsumer; |
| Compiler.createDiagnostics(TDC, /*ShouldOwnClient=*/true); |
| Compiler.setExternalSemaSource(new TypoExternalSemaSource(Compiler)); |
| |
| SyntaxOnlyAction TestAction; |
| ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); |
| // There should be one error correcting to 'moo' and a note attached to it. |
| EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?", |
| std::string(TDC->Error)); |
| EXPECT_EQ("This is a note", std::string(TDC->Note)); |
| } |
| |
| TEST(GeneratePCHFrontendAction, CacheGeneratedPCH) { |
| // Create a temporary file for writing out the PCH that will be cleaned up. |
| int PCHFD; |
| llvm::SmallString<128> PCHFilename; |
| ASSERT_FALSE( |
| llvm::sys::fs::createTemporaryFile("test.h", "pch", PCHFD, PCHFilename)); |
| llvm::ToolOutputFile PCHFile(PCHFilename, PCHFD); |
| |
| for (bool ShouldCache : {false, true}) { |
| auto Invocation = std::make_shared<CompilerInvocation>(); |
| Invocation->getLangOpts()->CacheGeneratedPCH = ShouldCache; |
| Invocation->getPreprocessorOpts().addRemappedFile( |
| "test.h", |
| MemoryBuffer::getMemBuffer("int foo(void) { return 1; }\n").release()); |
| Invocation->getFrontendOpts().Inputs.push_back( |
| FrontendInputFile("test.h", Language::C)); |
| Invocation->getFrontendOpts().OutputFile = PCHFilename.str().str(); |
| Invocation->getFrontendOpts().ProgramAction = frontend::GeneratePCH; |
| Invocation->getTargetOpts().Triple = "x86_64-apple-darwin19.0.0"; |
| CompilerInstance Compiler; |
| Compiler.setInvocation(std::move(Invocation)); |
| Compiler.createDiagnostics(); |
| |
| GeneratePCHAction TestAction; |
| ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); |
| |
| // Check whether the PCH was cached. |
| if (ShouldCache) |
| EXPECT_EQ(InMemoryModuleCache::Final, |
| Compiler.getModuleCache().getPCMState(PCHFilename)); |
| else |
| EXPECT_EQ(InMemoryModuleCache::Unknown, |
| Compiler.getModuleCache().getPCMState(PCHFilename)); |
| } |
| } |
| |
| } // anonymous namespace |