| //===- 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 <algorithm> |
| #include <string> |
| #include <vector> |
| |
| 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<clang::ASTConsumer> TestConsumer) |
| : TestConsumer(std::move(TestConsumer)) {} |
| |
| protected: |
| std::unique_ptr<clang::ASTConsumer> |
| CreateASTConsumer(clang::CompilerInstance &compiler, |
| StringRef dummy) override { |
| /// TestConsumer will be deleted by the framework calling us. |
| return std::move(TestConsumer); |
| } |
| |
| private: |
| std::unique_ptr<clang::ASTConsumer> 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<TestAction>( |
| std::make_unique<FindTopLevelDeclConsumer>(&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<clang::CXXRecordDecl>( |
| *GroupRef.begin())) { |
| if (Record->getName() == "X") { |
| *FoundClassDeclX = true; |
| } |
| } |
| return true; |
| } |
| private: |
| bool *FoundClassDeclX; |
| }; |
| bool FindClassDeclX(ASTUnit *AST) { |
| for (std::vector<Decl *>::iterator i = AST->top_level_begin(), |
| e = AST->top_level_end(); |
| i != e; ++i) { |
| if (CXXRecordDecl* Record = dyn_cast<clang::CXXRecordDecl>(*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<TestAction>( |
| std::make_unique<FindClassDeclXConsumer>(&FoundClassDeclX)), |
| "class X;")); |
| EXPECT_TRUE(FoundClassDeclX); |
| |
| FoundClassDeclX = false; |
| EXPECT_TRUE(runToolOnCode( |
| std::make_unique<TestAction>( |
| std::make_unique<FindClassDeclXConsumer>(&FoundClassDeclX)), |
| "class Y;")); |
| EXPECT_FALSE(FoundClassDeclX); |
| } |
| |
| TEST(buildASTFromCode, FindsClassDecl) { |
| std::unique_ptr<ASTUnit> 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<ASTUnit> AST = buildASTFromCodeWithArgs( |
| "int x = \"A\";", {}, "input.cc", "clang-tool", |
| std::make_shared<PCHContainerOperations>(), |
| getClangStripDependencyFileAdjuster(), FileContentMappings(), &Consumer); |
| EXPECT_TRUE(AST.get()); |
| EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen); |
| } |
| |
| TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) { |
| std::unique_ptr<FrontendActionFactory> Factory( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| std::unique_ptr<FrontendAction> Action(Factory->create()); |
| EXPECT_TRUE(Action.get() != nullptr); |
| } |
| |
| struct IndependentFrontendActionCreator { |
| std::unique_ptr<ASTConsumer> newASTConsumer() { |
| return std::make_unique<FindTopLevelDeclConsumer>(nullptr); |
| } |
| }; |
| |
| TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) { |
| IndependentFrontendActionCreator Creator; |
| std::unique_ptr<FrontendActionFactory> Factory( |
| newFrontendActionFactory(&Creator)); |
| std::unique_ptr<FrontendAction> Action(Factory->create()); |
| EXPECT_TRUE(Action.get() != nullptr); |
| } |
| |
| TEST(ToolInvocation, TestMapVirtualFile) { |
| llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
| new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
| new llvm::vfs::InMemoryFileSystem); |
| OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
| llvm::IntrusiveRefCntPtr<FileManager> Files( |
| new FileManager(FileSystemOptions(), OverlayFileSystem)); |
| std::vector<std::string> 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<SyntaxOnlyAction>(), Files.get()); |
| InMemoryFileSystem->addFile( |
| "test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include <abc>\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<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
| new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
| new llvm::vfs::InMemoryFileSystem); |
| OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
| llvm::IntrusiveRefCntPtr<FileManager> Files( |
| new FileManager(FileSystemOptions(), OverlayFileSystem)); |
| std::vector<std::string> 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<SyntaxOnlyAction>(), Files.get()); |
| InMemoryFileSystem->addFile( |
| "test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include <abc>\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<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
| new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
| new llvm::vfs::InMemoryFileSystem); |
| OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
| llvm::IntrusiveRefCntPtr<FileManager> Files( |
| new FileManager(FileSystemOptions(), OverlayFileSystem)); |
| |
| std::vector<std::string> 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<SyntaxOnlyAction>(), 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<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
| new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
| new llvm::vfs::InMemoryFileSystem); |
| OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
| llvm::IntrusiveRefCntPtr<FileManager> Files( |
| new FileManager(FileSystemOptions(), OverlayFileSystem)); |
| |
| std::vector<std::string> 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<SyntaxOnlyAction>(), 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<DiagnosticOptions>(); |
| 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<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
| new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
| new llvm::vfs::InMemoryFileSystem); |
| OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
| llvm::IntrusiveRefCntPtr<FileManager> Files( |
| new FileManager(FileSystemOptions(), OverlayFileSystem)); |
| std::vector<std::string> 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<SyntaxOnlyAction>(), 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<llvm::vfs::OverlayFileSystem> |
| overlayRealFS(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) { |
| auto RFS = llvm::vfs::getRealFileSystem(); |
| auto OverlayFS = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(RFS); |
| OverlayFS->pushOverlay(VFS); |
| return OverlayFS; |
| } |
| |
| struct CommandLineExtractorTest : public ::testing::Test { |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFS; |
| llvm::IntrusiveRefCntPtr<DiagnosticsEngine> 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<const char *> Argv) { |
| const std::unique_ptr<driver::Compilation> 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<ASTConsumer> newASTConsumer() { |
| return std::make_unique<FindTopLevelDeclConsumer>(&Matched); |
| } |
| unsigned BeginCalled; |
| unsigned EndCalled; |
| bool Matched; |
| }; |
| |
| #if !defined(_WIN32) |
| TEST(newFrontendActionFactory, InjectsSourceFileCallbacks) { |
| VerifyEndCallback EndCallback; |
| |
| FixedCompilationDatabase Compilations("/", std::vector<std::string>()); |
| std::vector<std::string> 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<FrontendActionFactory> 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<NamedDecl>(D); |
| return F && F->getNameAsString() == "skipMe"; |
| } |
| }; |
| |
| struct SkipBodyAction : public clang::ASTFrontendAction { |
| std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, |
| StringRef) override { |
| Compiler.getFrontendOpts().SkipFunctionBodies = true; |
| return std::make_unique<SkipBodyConsumer>(); |
| } |
| }; |
| |
| TEST(runToolOnCode, TestSkipFunctionBody) { |
| std::vector<std::string> Args = {"-std=c++11"}; |
| std::vector<std::string> Args2 = {"-fno-delayed-template-parsing"}; |
| |
| EXPECT_TRUE(runToolOnCode(std::make_unique<SkipBodyAction>(), |
| "int skipMe() { an_error_here }")); |
| EXPECT_FALSE(runToolOnCode(std::make_unique<SkipBodyAction>(), |
| "int skipMeNot() { an_error_here }")); |
| |
| // Test constructors with initializers |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<SkipBodyAction>(), |
| "struct skipMe { skipMe() : an_error() { more error } };", Args)); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<SkipBodyAction>(), "struct skipMe { skipMe(); };" |
| "skipMe::skipMe() : an_error([](){;}) { more error }", |
| Args)); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<SkipBodyAction>(), "struct skipMe { skipMe(); };" |
| "skipMe::skipMe() : an_error{[](){;}} { more error }", |
| Args)); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<SkipBodyAction>(), |
| "struct skipMe { skipMe(); };" |
| "skipMe::skipMe() : a<b<c>(e)>>(), f{}, g() { error }", |
| Args)); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<SkipBodyAction>(), "struct skipMe { skipMe() : bases()... { error } };", |
| Args)); |
| |
| EXPECT_FALSE(runToolOnCodeWithArgs( |
| std::make_unique<SkipBodyAction>(), "struct skipMeNot { skipMeNot() : an_error() { } };", |
| Args)); |
| EXPECT_FALSE(runToolOnCodeWithArgs(std::make_unique<SkipBodyAction>(), |
| "struct skipMeNot { skipMeNot(); };" |
| "skipMeNot::skipMeNot() : an_error() { }", |
| Args)); |
| |
| // Try/catch |
| EXPECT_TRUE(runToolOnCode( |
| std::make_unique<SkipBodyAction>(), |
| "void skipMe() try { an_error() } catch(error) { error };")); |
| EXPECT_TRUE(runToolOnCode( |
| std::make_unique<SkipBodyAction>(), |
| "struct S { void skipMe() try { an_error() } catch(error) { error } };")); |
| EXPECT_TRUE( |
| runToolOnCode(std::make_unique<SkipBodyAction>(), |
| "void skipMe() try { an_error() } catch(error) { error; }" |
| "catch(error) { error } catch (error) { }")); |
| EXPECT_FALSE(runToolOnCode( |
| std::make_unique<SkipBodyAction>(), |
| "void skipMe() try something;")); // don't crash while parsing |
| |
| // Template |
| EXPECT_TRUE(runToolOnCode( |
| std::make_unique<SkipBodyAction>(), "template<typename T> int skipMe() { an_error_here }" |
| "int x = skipMe<int>();")); |
| EXPECT_FALSE(runToolOnCodeWithArgs( |
| std::make_unique<SkipBodyAction>(), |
| "template<typename T> int skipMeNot() { an_error_here }", Args2)); |
| } |
| |
| TEST(runToolOnCodeWithArgs, TestNoDepFile) { |
| llvm::SmallString<32> DepFilePath; |
| ASSERT_FALSE(llvm::sys::fs::getPotentiallyUniqueTempFileName("depfile", "d", |
| DepFilePath)); |
| std::vector<std::string> 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<SkipBodyAction>(), "", 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<ASTConsumer> 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<ASTConsumer>(); |
| } |
| |
| private: |
| bool ShouldShowColor = true; |
| }; |
| |
| TEST(runToolOnCodeWithArgs, DiagnosticsColor) { |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<CheckColoredDiagnosticsAction>(true), "", |
| {"-fcolor-diagnostics"})); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<CheckColoredDiagnosticsAction>(false), "", |
| {"-fno-color-diagnostics"})); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<CheckColoredDiagnosticsAction>(true), "", |
| {"-fno-color-diagnostics", "-fcolor-diagnostics"})); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<CheckColoredDiagnosticsAction>(false), "", |
| {"-fcolor-diagnostics", "-fno-color-diagnostics"})); |
| EXPECT_TRUE(runToolOnCodeWithArgs( |
| std::make_unique<CheckColoredDiagnosticsAction>(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<CheckColoredDiagnosticsAction>(false), "", |
| {"-fcolor-diagnostics"})); |
| } |
| |
| TEST(ClangToolTest, ArgumentAdjusters) { |
| FixedCompilationDatabase Compilations("/", std::vector<std::string>()); |
| |
| ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "void a() {}"); |
| |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| |
| 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<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "void a() {}"); |
| |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| |
| 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<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "void a() {}"); |
| |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| |
| const std::vector<llvm::StringRef> 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<std::string>()); |
| llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
| new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
| llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
| new llvm::vfs::InMemoryFileSystem); |
| OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
| |
| InMemoryFileSystem->addFile( |
| "a.cpp", 0, llvm::MemoryBuffer::getMemBuffer("int main() {}")); |
| |
| ClangTool Tool(Compilations, std::vector<std::string>(1, "a.cpp"), |
| std::make_shared<PCHContainerOperations>(), OverlayFileSystem); |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| 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<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "void a() {}"); |
| |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| |
| 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<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "void a() {}"); |
| |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| |
| 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<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "void a() {}"); |
| |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| |
| 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<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "void a() {}"); |
| |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| |
| 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<std::string> Args = {"clang", "-foo"}; |
| addTargetAndModeForProgramName(Args, ""); |
| EXPECT_EQ((std::vector<std::string>{"clang", "-foo"}), Args); |
| addTargetAndModeForProgramName(Args, Target + "-g++"); |
| EXPECT_EQ((std::vector<std::string>{"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<std::string> Args = {"clang", "-foo"}; |
| addTargetAndModeForProgramName(Args, ToolPath); |
| EXPECT_EQ((std::vector<std::string>{"clang", "--target=" + Target, |
| "--driver-mode=g++", "-foo"}), |
| Args); |
| } |
| |
| TEST(addTargetAndModeForProgramName, IgnoresExistingTarget) { |
| std::string Target = getAnyTarget(); |
| ASSERT_FALSE(Target.empty()); |
| |
| std::vector<std::string> Args = {"clang", "-foo", "-target", "something"}; |
| addTargetAndModeForProgramName(Args, Target + "-g++"); |
| EXPECT_EQ((std::vector<std::string>{"clang", "--driver-mode=g++", "-foo", |
| "-target", "something"}), |
| Args); |
| |
| std::vector<std::string> ArgsAlt = {"clang", "-foo", "--target=something"}; |
| addTargetAndModeForProgramName(ArgsAlt, Target + "-g++"); |
| EXPECT_EQ((std::vector<std::string>{"clang", "--driver-mode=g++", "-foo", |
| "--target=something"}), |
| ArgsAlt); |
| } |
| |
| TEST(addTargetAndModeForProgramName, IgnoresExistingMode) { |
| std::string Target = getAnyTarget(); |
| ASSERT_FALSE(Target.empty()); |
| |
| std::vector<std::string> Args = {"clang", "-foo", "--driver-mode=abc"}; |
| addTargetAndModeForProgramName(Args, Target + "-g++"); |
| EXPECT_EQ((std::vector<std::string>{"clang", "--target=" + Target, "-foo", |
| "--driver-mode=abc"}), |
| Args); |
| } |
| |
| #ifndef _WIN32 |
| TEST(ClangToolTest, BuildASTs) { |
| FixedCompilationDatabase Compilations("/", std::vector<std::string>()); |
| |
| std::vector<std::string> 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<std::unique_ptr<ASTUnit>> ASTs; |
| EXPECT_EQ(0, Tool.buildASTs(ASTs)); |
| EXPECT_EQ(2u, ASTs.size()); |
| } |
| |
| TEST(ClangToolTest, InjectDiagnosticConsumer) { |
| FixedCompilationDatabase Compilations("/", std::vector<std::string>()); |
| ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "int x = undeclared;"); |
| TestDiagnosticConsumer Consumer; |
| Tool.setDiagnosticConsumer(&Consumer); |
| std::unique_ptr<FrontendActionFactory> Action( |
| newFrontendActionFactory<SyntaxOnlyAction>()); |
| Tool.run(Action.get()); |
| EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen); |
| } |
| |
| TEST(ClangToolTest, InjectDiagnosticConsumerInBuildASTs) { |
| FixedCompilationDatabase Compilations("/", std::vector<std::string>()); |
| ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc")); |
| Tool.mapVirtualFile("/a.cc", "int x = undeclared;"); |
| TestDiagnosticConsumer Consumer; |
| Tool.setDiagnosticConsumer(&Consumer); |
| std::vector<std::unique_ptr<ASTUnit>> 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<ASTConsumer> 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<Consumer>(); |
| } |
| }; |
| |
| // Should not crash |
| EXPECT_FALSE( |
| runToolOnCode(std::make_unique<ResetDiagnosticAction>(), |
| "struct Foo { Foo(int); ~Foo(); struct Fwd _fwd; };" |
| "void func() { long x; Foo f(x); }")); |
| } |
| |
| } // end namespace tooling |
| } // end namespace clang |