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