//===- unittest/Tooling/ToolingTest.cpp - Tooling unit 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/Tooling/Tooling.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclGroup.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/Driver.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticBuffer.h" #include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Support/Host.h" #include "llvm/Support/Path.h" #include "llvm/Support/TargetSelect.h" #include "gtest/gtest.h" #include #include #include namespace clang { namespace tooling { namespace { /// Takes an ast consumer and returns it from CreateASTConsumer. This only /// works with single translation unit compilations. class TestAction : public clang::ASTFrontendAction { public: /// Takes ownership of TestConsumer. explicit TestAction(std::unique_ptr TestConsumer) : TestConsumer(std::move(TestConsumer)) {} protected: std::unique_ptr CreateASTConsumer(clang::CompilerInstance &compiler, StringRef dummy) override { /// TestConsumer will be deleted by the framework calling us. return std::move(TestConsumer); } private: std::unique_ptr TestConsumer; }; class FindTopLevelDeclConsumer : public clang::ASTConsumer { public: explicit FindTopLevelDeclConsumer(bool *FoundTopLevelDecl) : FoundTopLevelDecl(FoundTopLevelDecl) {} bool HandleTopLevelDecl(clang::DeclGroupRef DeclGroup) override { *FoundTopLevelDecl = true; return true; } private: bool * const FoundTopLevelDecl; }; } // end namespace TEST(runToolOnCode, FindsNoTopLevelDeclOnEmptyCode) { bool FoundTopLevelDecl = false; EXPECT_TRUE(runToolOnCode( std::make_unique( std::make_unique(&FoundTopLevelDecl)), "")); EXPECT_FALSE(FoundTopLevelDecl); } namespace { class FindClassDeclXConsumer : public clang::ASTConsumer { public: FindClassDeclXConsumer(bool *FoundClassDeclX) : FoundClassDeclX(FoundClassDeclX) {} bool HandleTopLevelDecl(clang::DeclGroupRef GroupRef) override { if (CXXRecordDecl* Record = dyn_cast( *GroupRef.begin())) { if (Record->getName() == "X") { *FoundClassDeclX = true; } } return true; } private: bool *FoundClassDeclX; }; bool FindClassDeclX(ASTUnit *AST) { for (std::vector::iterator i = AST->top_level_begin(), e = AST->top_level_end(); i != e; ++i) { if (CXXRecordDecl* Record = dyn_cast(*i)) { if (Record->getName() == "X") { return true; } } } return false; } struct TestDiagnosticConsumer : public DiagnosticConsumer { TestDiagnosticConsumer() : NumDiagnosticsSeen(0) {} void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override { ++NumDiagnosticsSeen; } unsigned NumDiagnosticsSeen; }; } // end namespace TEST(runToolOnCode, FindsClassDecl) { bool FoundClassDeclX = false; EXPECT_TRUE(runToolOnCode( std::make_unique( std::make_unique(&FoundClassDeclX)), "class X;")); EXPECT_TRUE(FoundClassDeclX); FoundClassDeclX = false; EXPECT_TRUE(runToolOnCode( std::make_unique( std::make_unique(&FoundClassDeclX)), "class Y;")); EXPECT_FALSE(FoundClassDeclX); } TEST(buildASTFromCode, FindsClassDecl) { std::unique_ptr AST = buildASTFromCode("class X;"); ASSERT_TRUE(AST.get()); EXPECT_TRUE(FindClassDeclX(AST.get())); AST = buildASTFromCode("class Y;"); ASSERT_TRUE(AST.get()); EXPECT_FALSE(FindClassDeclX(AST.get())); } TEST(buildASTFromCode, ReportsErrors) { TestDiagnosticConsumer Consumer; std::unique_ptr AST = buildASTFromCodeWithArgs( "int x = \"A\";", {}, "input.cc", "clang-tool", std::make_shared(), getClangStripDependencyFileAdjuster(), FileContentMappings(), &Consumer); EXPECT_TRUE(AST.get()); EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen); } TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) { std::unique_ptr Factory( newFrontendActionFactory()); std::unique_ptr Action(Factory->create()); EXPECT_TRUE(Action.get() != nullptr); } struct IndependentFrontendActionCreator { std::unique_ptr newASTConsumer() { return std::make_unique(nullptr); } }; TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) { IndependentFrontendActionCreator Creator; std::unique_ptr Factory( newFrontendActionFactory(&Creator)); std::unique_ptr Action(Factory->create()); EXPECT_TRUE(Action.get() != nullptr); } TEST(ToolInvocation, TestMapVirtualFile) { llvm::IntrusiveRefCntPtr OverlayFileSystem( new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), OverlayFileSystem)); std::vector Args; Args.push_back("tool-executable"); Args.push_back("-Idef"); Args.push_back("-fsyntax-only"); Args.push_back("test.cpp"); clang::tooling::ToolInvocation Invocation( Args, std::make_unique(), Files.get()); InMemoryFileSystem->addFile( "test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include \n")); InMemoryFileSystem->addFile("def/abc", 0, llvm::MemoryBuffer::getMemBuffer("\n")); EXPECT_TRUE(Invocation.run()); } TEST(ToolInvocation, TestVirtualModulesCompilation) { // FIXME: Currently, this only tests that we don't exit with an error if a // mapped module.map is found on the include path. In the future, expand this // test to run a full modules enabled compilation, so we make sure we can // rerun modules compilations with a virtual file system. llvm::IntrusiveRefCntPtr OverlayFileSystem( new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), OverlayFileSystem)); std::vector Args; Args.push_back("tool-executable"); Args.push_back("-Idef"); Args.push_back("-fsyntax-only"); Args.push_back("test.cpp"); clang::tooling::ToolInvocation Invocation( Args, std::make_unique(), Files.get()); InMemoryFileSystem->addFile( "test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include \n")); InMemoryFileSystem->addFile("def/abc", 0, llvm::MemoryBuffer::getMemBuffer("\n")); // Add a module.map file in the include directory of our header, so we trigger // the module.map header search logic. InMemoryFileSystem->addFile("def/module.map", 0, llvm::MemoryBuffer::getMemBuffer("\n")); EXPECT_TRUE(Invocation.run()); } TEST(ToolInvocation, DiagnosticsEngineProperlyInitializedForCC1Construction) { llvm::IntrusiveRefCntPtr OverlayFileSystem( new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), OverlayFileSystem)); std::vector Args; Args.push_back("tool-executable"); // Unknown warning option will result in a warning. Args.push_back("-fexpensive-optimizations"); // Argument that will suppress the warning above. Args.push_back("-Wno-ignored-optimization-argument"); Args.push_back("-E"); Args.push_back("test.cpp"); clang::tooling::ToolInvocation Invocation( Args, std::make_unique(), Files.get()); InMemoryFileSystem->addFile("test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("")); TextDiagnosticBuffer Consumer; Invocation.setDiagnosticConsumer(&Consumer); EXPECT_TRUE(Invocation.run()); // Check that the warning was ignored due to the '-Wno-xxx' argument. EXPECT_EQ(std::distance(Consumer.warn_begin(), Consumer.warn_end()), 0u); } TEST(ToolInvocation, CustomDiagnosticOptionsOverwriteParsedOnes) { llvm::IntrusiveRefCntPtr OverlayFileSystem( new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), OverlayFileSystem)); std::vector Args; Args.push_back("tool-executable"); // Unknown warning option will result in a warning. Args.push_back("-fexpensive-optimizations"); // Argument that will suppress the warning above. Args.push_back("-Wno-ignored-optimization-argument"); Args.push_back("-E"); Args.push_back("test.cpp"); clang::tooling::ToolInvocation Invocation( Args, std::make_unique(), Files.get()); InMemoryFileSystem->addFile("test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("")); TextDiagnosticBuffer Consumer; Invocation.setDiagnosticConsumer(&Consumer); // Inject custom `DiagnosticOptions` for command-line parsing. auto DiagOpts = llvm::makeIntrusiveRefCnt(); Invocation.setDiagnosticOptions(&*DiagOpts); EXPECT_TRUE(Invocation.run()); // Check that the warning was issued during command-line parsing due to the // custom `DiagnosticOptions` without '-Wno-xxx'. EXPECT_EQ(std::distance(Consumer.warn_begin(), Consumer.warn_end()), 1u); } struct DiagnosticConsumerExpectingSourceManager : public DiagnosticConsumer { bool SawSourceManager; DiagnosticConsumerExpectingSourceManager() : SawSourceManager(false) {} void HandleDiagnostic(clang::DiagnosticsEngine::Level, const clang::Diagnostic &info) override { SawSourceManager = info.hasSourceManager(); } }; TEST(ToolInvocation, DiagConsumerExpectingSourceManager) { llvm::IntrusiveRefCntPtr OverlayFileSystem( new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), OverlayFileSystem)); std::vector Args; Args.push_back("tool-executable"); // Note: intentional error; user probably meant -ferror-limit=0. Args.push_back("-ferror-limit=-1"); Args.push_back("-fsyntax-only"); Args.push_back("test.cpp"); clang::tooling::ToolInvocation Invocation( Args, std::make_unique(), Files.get()); InMemoryFileSystem->addFile( "test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("int main() {}\n")); DiagnosticConsumerExpectingSourceManager Consumer; Invocation.setDiagnosticConsumer(&Consumer); EXPECT_TRUE(Invocation.run()); EXPECT_TRUE(Consumer.SawSourceManager); } namespace { /// Overlays the real filesystem with the given VFS and returns the result. llvm::IntrusiveRefCntPtr overlayRealFS(llvm::IntrusiveRefCntPtr VFS) { auto RFS = llvm::vfs::getRealFileSystem(); auto OverlayFS = llvm::makeIntrusiveRefCnt(RFS); OverlayFS->pushOverlay(VFS); return OverlayFS; } struct CommandLineExtractorTest : public ::testing::Test { llvm::IntrusiveRefCntPtr InMemoryFS; llvm::IntrusiveRefCntPtr Diags; driver::Driver Driver; public: CommandLineExtractorTest() : InMemoryFS(new llvm::vfs::InMemoryFileSystem), Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions)), Driver("clang", llvm::sys::getDefaultTargetTriple(), *Diags, "clang LLVM compiler", overlayRealFS(InMemoryFS)) {} void addFile(StringRef Name, StringRef Content) { InMemoryFS->addFile(Name, 0, llvm::MemoryBuffer::getMemBuffer(Content)); } const llvm::opt::ArgStringList * extractCC1Arguments(llvm::ArrayRef Argv) { const std::unique_ptr Compilation( Driver.BuildCompilation(llvm::makeArrayRef(Argv))); return getCC1Arguments(Diags.get(), Compilation.get()); } }; } // namespace TEST_F(CommandLineExtractorTest, AcceptOffloading) { addFile("test.c", "int main() {}\n"); const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", "-x", "hip", "test.c", "-nogpulib", "-nogpuinc"}; EXPECT_NE(extractCC1Arguments(Args), nullptr); } TEST_F(CommandLineExtractorTest, AcceptOffloadingCompile) { addFile("test.c", "int main() {}\n"); const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", "-c", "-x", "hip", "test.c", "-nogpulib", "-nogpuinc"}; EXPECT_NE(extractCC1Arguments(Args), nullptr); } TEST_F(CommandLineExtractorTest, AcceptOffloadingSyntaxOnly) { addFile("test.c", "int main() {}\n"); const char *Args[] = { "clang", "-target", "arm64-apple-macosx11.0.0", "-fsyntax-only", "-x", "hip", "test.c", "-nogpulib", "-nogpuinc"}; EXPECT_NE(extractCC1Arguments(Args), nullptr); } TEST_F(CommandLineExtractorTest, AcceptExternalAssembler) { addFile("test.c", "int main() {}\n"); const char *Args[] = { "clang", "-target", "arm64-apple-macosx11.0.0", "-fno-integrated-as", "-c", "test.c"}; EXPECT_NE(extractCC1Arguments(Args), nullptr); } TEST_F(CommandLineExtractorTest, AcceptEmbedBitcode) { addFile("test.c", "int main() {}\n"); const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", "-c", "-fembed-bitcode", "test.c"}; EXPECT_NE(extractCC1Arguments(Args), nullptr); } TEST_F(CommandLineExtractorTest, AcceptSaveTemps) { addFile("test.c", "int main() {}\n"); const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", "-c", "-save-temps", "test.c"}; EXPECT_NE(extractCC1Arguments(Args), nullptr); } TEST_F(CommandLineExtractorTest, RejectMultipleArchitectures) { addFile("test.c", "int main() {}\n"); const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", "-arch", "x86_64", "-arch", "arm64", "-c", "test.c"}; EXPECT_EQ(extractCC1Arguments(Args), nullptr); } TEST_F(CommandLineExtractorTest, RejectMultipleInputFiles) { addFile("one.c", "void one() {}\n"); addFile("two.c", "void two() {}\n"); const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", "-c", "one.c", "two.c"}; EXPECT_EQ(extractCC1Arguments(Args), nullptr); } struct VerifyEndCallback : public SourceFileCallbacks { VerifyEndCallback() : BeginCalled(0), EndCalled(0), Matched(false) {} bool handleBeginSource(CompilerInstance &CI) override { ++BeginCalled; return true; } void handleEndSource() override { ++EndCalled; } std::unique_ptr newASTConsumer() { return std::make_unique(&Matched); } unsigned BeginCalled; unsigned EndCalled; bool Matched; }; #if !defined(_WIN32) TEST(newFrontendActionFactory, InjectsSourceFileCallbacks) { VerifyEndCallback EndCallback; FixedCompilationDatabase Compilations("/", std::vector()); std::vector Sources; Sources.push_back("/a.cc"); Sources.push_back("/b.cc"); ClangTool Tool(Compilations, Sources); Tool.mapVirtualFile("/a.cc", "void a() {}"); Tool.mapVirtualFile("/b.cc", "void b() {}"); std::unique_ptr Action( newFrontendActionFactory(&EndCallback, &EndCallback)); Tool.run(Action.get()); EXPECT_TRUE(EndCallback.Matched); EXPECT_EQ(2u, EndCallback.BeginCalled); EXPECT_EQ(2u, EndCallback.EndCalled); } #endif struct SkipBodyConsumer : public clang::ASTConsumer { /// Skip the 'skipMe' function. bool shouldSkipFunctionBody(Decl *D) override { NamedDecl *F = dyn_cast(D); return F && F->getNameAsString() == "skipMe"; } }; struct SkipBodyAction : public clang::ASTFrontendAction { std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef) override { Compiler.getFrontendOpts().SkipFunctionBodies = true; return std::make_unique(); } }; TEST(runToolOnCode, TestSkipFunctionBody) { std::vector Args = {"-std=c++11"}; std::vector Args2 = {"-fno-delayed-template-parsing"}; EXPECT_TRUE(runToolOnCode(std::make_unique(), "int skipMe() { an_error_here }")); EXPECT_FALSE(runToolOnCode(std::make_unique(), "int skipMeNot() { an_error_here }")); // Test constructors with initializers EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(), "struct skipMe { skipMe() : an_error() { more error } };", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(), "struct skipMe { skipMe(); };" "skipMe::skipMe() : an_error([](){;}) { more error }", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(), "struct skipMe { skipMe(); };" "skipMe::skipMe() : an_error{[](){;}} { more error }", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(), "struct skipMe { skipMe(); };" "skipMe::skipMe() : a(e)>>(), f{}, g() { error }", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(), "struct skipMe { skipMe() : bases()... { error } };", Args)); EXPECT_FALSE(runToolOnCodeWithArgs( std::make_unique(), "struct skipMeNot { skipMeNot() : an_error() { } };", Args)); EXPECT_FALSE(runToolOnCodeWithArgs(std::make_unique(), "struct skipMeNot { skipMeNot(); };" "skipMeNot::skipMeNot() : an_error() { }", Args)); // Try/catch EXPECT_TRUE(runToolOnCode( std::make_unique(), "void skipMe() try { an_error() } catch(error) { error };")); EXPECT_TRUE(runToolOnCode( std::make_unique(), "struct S { void skipMe() try { an_error() } catch(error) { error } };")); EXPECT_TRUE( runToolOnCode(std::make_unique(), "void skipMe() try { an_error() } catch(error) { error; }" "catch(error) { error } catch (error) { }")); EXPECT_FALSE(runToolOnCode( std::make_unique(), "void skipMe() try something;")); // don't crash while parsing // Template EXPECT_TRUE(runToolOnCode( std::make_unique(), "template int skipMe() { an_error_here }" "int x = skipMe();")); EXPECT_FALSE(runToolOnCodeWithArgs( std::make_unique(), "template int skipMeNot() { an_error_here }", Args2)); } TEST(runToolOnCodeWithArgs, TestNoDepFile) { llvm::SmallString<32> DepFilePath; ASSERT_FALSE(llvm::sys::fs::getPotentiallyUniqueTempFileName("depfile", "d", DepFilePath)); std::vector Args; Args.push_back("-MMD"); Args.push_back("-MT"); Args.push_back(std::string(DepFilePath.str())); Args.push_back("-MF"); Args.push_back(std::string(DepFilePath.str())); EXPECT_TRUE(runToolOnCodeWithArgs(std::make_unique(), "", Args)); EXPECT_FALSE(llvm::sys::fs::exists(DepFilePath.str())); EXPECT_FALSE(llvm::sys::fs::remove(DepFilePath.str())); } struct CheckColoredDiagnosticsAction : public clang::ASTFrontendAction { CheckColoredDiagnosticsAction(bool ShouldShowColor) : ShouldShowColor(ShouldShowColor) {} std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef) override { if (Compiler.getDiagnosticOpts().ShowColors != ShouldShowColor) Compiler.getDiagnostics().Report( Compiler.getDiagnostics().getCustomDiagID( DiagnosticsEngine::Fatal, "getDiagnosticOpts().ShowColors != ShouldShowColor")); return std::make_unique(); } private: bool ShouldShowColor = true; }; TEST(runToolOnCodeWithArgs, DiagnosticsColor) { EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(true), "", {"-fcolor-diagnostics"})); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(false), "", {"-fno-color-diagnostics"})); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(true), "", {"-fno-color-diagnostics", "-fcolor-diagnostics"})); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(false), "", {"-fcolor-diagnostics", "-fno-color-diagnostics"})); EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(true), "", {"-fno-color-diagnostics", "-fdiagnostics-color=always"})); // Check that this test would fail if ShowColors is not what it should. EXPECT_FALSE(runToolOnCodeWithArgs( std::make_unique(false), "", {"-fcolor-diagnostics"})); } TEST(ClangToolTest, ArgumentAdjusters) { FixedCompilationDatabase Compilations("/", std::vector()); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); bool Found = false; bool Ran = false; ArgumentsAdjuster CheckSyntaxOnlyAdjuster = [&Found, &Ran](const CommandLineArguments &Args, StringRef /*unused*/) { Ran = true; if (llvm::is_contained(Args, "-fsyntax-only")) Found = true; return Args; }; Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster); Tool.run(Action.get()); EXPECT_TRUE(Ran); EXPECT_TRUE(Found); Ran = Found = false; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster); Tool.appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); Tool.run(Action.get()); EXPECT_TRUE(Ran); EXPECT_FALSE(Found); } TEST(ClangToolTest, NoDoubleSyntaxOnly) { FixedCompilationDatabase Compilations("/", {"-fsyntax-only"}); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); size_t SyntaxOnlyCount = 0; ArgumentsAdjuster CheckSyntaxOnlyAdjuster = [&SyntaxOnlyCount](const CommandLineArguments &Args, StringRef /*unused*/) { for (llvm::StringRef Arg : Args) { if (Arg == "-fsyntax-only") ++SyntaxOnlyCount; } return Args; }; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster); Tool.run(Action.get()); EXPECT_EQ(SyntaxOnlyCount, 1U); } TEST(ClangToolTest, NoOutputCommands) { FixedCompilationDatabase Compilations("/", {"-save-temps", "-save-temps=cwd", "--save-temps", "--save-temps=somedir"}); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); const std::vector OutputCommands = {"-save-temps"}; bool Ran = false; ArgumentsAdjuster CheckSyntaxOnlyAdjuster = [&OutputCommands, &Ran](const CommandLineArguments &Args, StringRef /*unused*/) { for (llvm::StringRef Arg : Args) { for (llvm::StringRef OutputCommand : OutputCommands) EXPECT_FALSE(Arg.contains(OutputCommand)); } Ran = true; return Args; }; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster); Tool.run(Action.get()); EXPECT_TRUE(Ran); } TEST(ClangToolTest, BaseVirtualFileSystemUsage) { FixedCompilationDatabase Compilations("/", std::vector()); llvm::IntrusiveRefCntPtr OverlayFileSystem( new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); InMemoryFileSystem->addFile( "a.cpp", 0, llvm::MemoryBuffer::getMemBuffer("int main() {}")); ClangTool Tool(Compilations, std::vector(1, "a.cpp"), std::make_shared(), OverlayFileSystem); std::unique_ptr Action( newFrontendActionFactory()); EXPECT_EQ(0, Tool.run(Action.get())); } // Check getClangStripDependencyFileAdjuster doesn't strip args after -MD/-MMD. TEST(ClangToolTest, StripDependencyFileAdjuster) { FixedCompilationDatabase Compilations("/", {"-MD", "-c", "-MMD", "-w"}); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); CommandLineArguments FinalArgs; ArgumentsAdjuster CheckFlagsAdjuster = [&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) { FinalArgs = Args; return Args; }; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(getClangStripDependencyFileAdjuster()); Tool.appendArgumentsAdjuster(CheckFlagsAdjuster); Tool.run(Action.get()); auto HasFlag = [&FinalArgs](const std::string &Flag) { return llvm::find(FinalArgs, Flag) != FinalArgs.end(); }; EXPECT_FALSE(HasFlag("-MD")); EXPECT_FALSE(HasFlag("-MMD")); EXPECT_TRUE(HasFlag("-c")); EXPECT_TRUE(HasFlag("-w")); } // Check getClangStripDependencyFileAdjuster strips /showIncludes and variants TEST(ClangToolTest, StripDependencyFileAdjusterShowIncludes) { FixedCompilationDatabase Compilations( "/", {"/showIncludes", "/showIncludes:user", "-showIncludes", "-showIncludes:user", "-c"}); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); CommandLineArguments FinalArgs; ArgumentsAdjuster CheckFlagsAdjuster = [&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) { FinalArgs = Args; return Args; }; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(getClangStripDependencyFileAdjuster()); Tool.appendArgumentsAdjuster(CheckFlagsAdjuster); Tool.run(Action.get()); auto HasFlag = [&FinalArgs](const std::string &Flag) { return llvm::find(FinalArgs, Flag) != FinalArgs.end(); }; EXPECT_FALSE(HasFlag("/showIncludes")); EXPECT_FALSE(HasFlag("/showIncludes:user")); EXPECT_FALSE(HasFlag("-showIncludes")); EXPECT_FALSE(HasFlag("-showIncludes:user")); EXPECT_TRUE(HasFlag("-c")); } // Check getClangStripDependencyFileAdjuster doesn't strip args when using the // MSVC cl.exe driver TEST(ClangToolTest, StripDependencyFileAdjusterMsvc) { FixedCompilationDatabase Compilations( "/", {"--driver-mode=cl", "-MD", "-MDd", "-MT", "-O1", "-MTd", "-MP"}); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); CommandLineArguments FinalArgs; ArgumentsAdjuster CheckFlagsAdjuster = [&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) { FinalArgs = Args; return Args; }; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(getClangStripDependencyFileAdjuster()); Tool.appendArgumentsAdjuster(CheckFlagsAdjuster); Tool.run(Action.get()); auto HasFlag = [&FinalArgs](const std::string &Flag) { return llvm::find(FinalArgs, Flag) != FinalArgs.end(); }; EXPECT_TRUE(HasFlag("-MD")); EXPECT_TRUE(HasFlag("-MDd")); EXPECT_TRUE(HasFlag("-MT")); EXPECT_TRUE(HasFlag("-O1")); EXPECT_TRUE(HasFlag("-MTd")); EXPECT_TRUE(HasFlag("-MP")); } // Check getClangStripPluginsAdjuster strips plugin related args. TEST(ClangToolTest, StripPluginsAdjuster) { FixedCompilationDatabase Compilations( "/", {"-Xclang", "-add-plugin", "-Xclang", "random-plugin"}); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); CommandLineArguments FinalArgs; ArgumentsAdjuster CheckFlagsAdjuster = [&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) { FinalArgs = Args; return Args; }; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(getStripPluginsAdjuster()); Tool.appendArgumentsAdjuster(CheckFlagsAdjuster); Tool.run(Action.get()); auto HasFlag = [&FinalArgs](const std::string &Flag) { return llvm::find(FinalArgs, Flag) != FinalArgs.end(); }; EXPECT_FALSE(HasFlag("-Xclang")); EXPECT_FALSE(HasFlag("-add-plugin")); EXPECT_FALSE(HasFlag("-random-plugin")); } namespace { /// Find a target name such that looking for it in TargetRegistry by that name /// returns the same target. We expect that there is at least one target /// configured with this property. std::string getAnyTarget() { llvm::InitializeAllTargets(); for (const auto &Target : llvm::TargetRegistry::targets()) { std::string Error; StringRef TargetName(Target.getName()); if (TargetName == "x86-64") TargetName = "x86_64"; if (llvm::TargetRegistry::lookupTarget(std::string(TargetName), Error) == &Target) { return std::string(TargetName); } } return ""; } } TEST(addTargetAndModeForProgramName, AddsTargetAndMode) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); std::vector Args = {"clang", "-foo"}; addTargetAndModeForProgramName(Args, ""); EXPECT_EQ((std::vector{"clang", "-foo"}), Args); addTargetAndModeForProgramName(Args, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "--target=" + Target, "--driver-mode=g++", "-foo"}), Args); } TEST(addTargetAndModeForProgramName, PathIgnored) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); SmallString<32> ToolPath; llvm::sys::path::append(ToolPath, "foo", "bar", Target + "-g++"); std::vector Args = {"clang", "-foo"}; addTargetAndModeForProgramName(Args, ToolPath); EXPECT_EQ((std::vector{"clang", "--target=" + Target, "--driver-mode=g++", "-foo"}), Args); } TEST(addTargetAndModeForProgramName, IgnoresExistingTarget) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); std::vector Args = {"clang", "-foo", "-target", "something"}; addTargetAndModeForProgramName(Args, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "--driver-mode=g++", "-foo", "-target", "something"}), Args); std::vector ArgsAlt = {"clang", "-foo", "--target=something"}; addTargetAndModeForProgramName(ArgsAlt, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "--driver-mode=g++", "-foo", "--target=something"}), ArgsAlt); } TEST(addTargetAndModeForProgramName, IgnoresExistingMode) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); std::vector Args = {"clang", "-foo", "--driver-mode=abc"}; addTargetAndModeForProgramName(Args, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "--target=" + Target, "-foo", "--driver-mode=abc"}), Args); } #ifndef _WIN32 TEST(ClangToolTest, BuildASTs) { FixedCompilationDatabase Compilations("/", std::vector()); std::vector Sources; Sources.push_back("/a.cc"); Sources.push_back("/b.cc"); ClangTool Tool(Compilations, Sources); Tool.mapVirtualFile("/a.cc", "void a() {}"); Tool.mapVirtualFile("/b.cc", "void b() {}"); std::vector> ASTs; EXPECT_EQ(0, Tool.buildASTs(ASTs)); EXPECT_EQ(2u, ASTs.size()); } TEST(ClangToolTest, InjectDiagnosticConsumer) { FixedCompilationDatabase Compilations("/", std::vector()); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "int x = undeclared;"); TestDiagnosticConsumer Consumer; Tool.setDiagnosticConsumer(&Consumer); std::unique_ptr Action( newFrontendActionFactory()); Tool.run(Action.get()); EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen); } TEST(ClangToolTest, InjectDiagnosticConsumerInBuildASTs) { FixedCompilationDatabase Compilations("/", std::vector()); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "int x = undeclared;"); TestDiagnosticConsumer Consumer; Tool.setDiagnosticConsumer(&Consumer); std::vector> ASTs; Tool.buildASTs(ASTs); EXPECT_EQ(1u, ASTs.size()); EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen); } #endif TEST(runToolOnCode, TestResetDiagnostics) { // This is a tool that resets the diagnostic during the compilation. struct ResetDiagnosticAction : public clang::ASTFrontendAction { std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef) override { struct Consumer : public clang::ASTConsumer { bool HandleTopLevelDecl(clang::DeclGroupRef D) override { auto &Diags = (*D.begin())->getASTContext().getDiagnostics(); // Ignore any error Diags.Reset(); // Disable warnings because computing the CFG might crash. Diags.setIgnoreAllWarnings(true); return true; } }; return std::make_unique(); } }; // Should not crash EXPECT_FALSE( runToolOnCode(std::make_unique(), "struct Foo { Foo(int); ~Foo(); struct Fwd _fwd; };" "void func() { long x; Foo f(x); }")); } } // end namespace tooling } // end namespace clang