diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -41,6 +41,7 @@ #include #include +#include #include using namespace llvm; @@ -65,6 +66,11 @@ int run(Command Cmd, int argc, const char **argv); + CodeCoverageTool() { + FilenameFilters.push_back(IgnoreFilenameFilters); + FilenameFilters.push_back(IncludeFilenameFilters); + } + private: /// Print the error message to the error output stream. void error(const Twine &Message, StringRef Whence = ""); @@ -140,7 +146,20 @@ std::vector ObjectFilenames; CoverageViewOptions ViewOpts; CoverageFiltersMatchAll Filters; - CoverageFilters IgnoreFilenameFilters; + + std::shared_ptr IgnoreFilenameFilters = + std::make_shared(); + // The inclusion-regex-filters individually match if the inclusion regex + // does *not* match the filename (i.e., *this* regex should not cause the + // inclusion of *that* file). Thus, for a file to be included, *all* the + // inclusion filters must match (i.e., not want to include the file). + std::shared_ptr IncludeFilenameFilters = + std::make_shared(false); + + // A file is to be excluded if either the exclusion-filters match or + // the inclusion-filters match (which match if the file is *not* to be + // included, see above) + CoverageFilters FilenameFilters; /// True if InputSourceFiles are provided. bool HadSourceFiles = false; @@ -179,7 +198,7 @@ /// Whitelist from -name-whitelist to be used for filtering. std::unique_ptr NameWhitelist; }; -} +} // namespace static std::string getErrorString(const Twine &Message, StringRef Whence, bool Warning) { @@ -210,7 +229,7 @@ return; } sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true); - if (!IgnoreFilenameFilters.matchesFilename(EffectivePath)) + if (!FilenameFilters.matchesFilename(EffectivePath)) SourceFiles.emplace_back(EffectivePath.str()); HadSourceFiles = !SourceFiles.empty(); } @@ -233,8 +252,8 @@ if (llvm::sys::fs::is_directory(Status)) { std::error_code EC; - for (llvm::sys::fs::recursive_directory_iterator F(Path, EC), E; - F != E; F.increment(EC)) { + for (llvm::sys::fs::recursive_directory_iterator F(Path, EC), E; F != E; + F.increment(EC)) { auto Status = F->status(); if (!Status) { @@ -355,9 +374,9 @@ auto Branches = FunctionCoverage.getBranches(); auto Expansions = FunctionCoverage.getExpansions(); - auto View = SourceCoverageView::create(DC.demangle(Function.Name), - SourceBuffer.get(), ViewOpts, - std::move(FunctionCoverage)); + auto View = + SourceCoverageView::create(DC.demangle(Function.Name), SourceBuffer.get(), + ViewOpts, std::move(FunctionCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); attachBranchSubViews(*View, DC.demangle(Function.Name), Branches, SourceBuffer.get(), FunctionCoverage); @@ -684,6 +703,12 @@ "regular expression"), cl::ZeroOrMore, cl::cat(FilteringCategory)); + cl::list FilenameRegexFilters( + "filename-regex", cl::Optional, + cl::desc("If given, only report coverage vor source code files matching " + "the given regular expression"), + cl::ZeroOrMore, cl::cat(FilteringCategory)); + cl::opt RegionCoverageLtFilter( "region-coverage-lt", cl::Optional, cl::desc("Show code coverage only for functions with region coverage " @@ -717,8 +742,7 @@ cl::opt RegionSummary( "show-region-summary", cl::Optional, - cl::desc("Show region statistics in summary table"), - cl::init(true)); + cl::desc("Show region statistics in summary table"), cl::init(true)); cl::opt BranchSummary( "show-branch-summary", cl::Optional, @@ -807,15 +831,15 @@ // Create the function filters if (!NameFilters.empty() || NameWhitelist || !NameRegexFilters.empty()) { - auto NameFilterer = std::make_unique(); + auto NameFilterer = std::make_shared(); for (const auto &Name : NameFilters) - NameFilterer->push_back(std::make_unique(Name)); + NameFilterer->push_back(std::make_shared(Name)); if (NameWhitelist) NameFilterer->push_back( - std::make_unique(*NameWhitelist)); + std::make_shared(*NameWhitelist)); for (const auto &Regex : NameRegexFilters) NameFilterer->push_back( - std::make_unique(Regex)); + std::make_shared(Regex)); Filters.push_back(std::move(NameFilterer)); } @@ -823,26 +847,33 @@ RegionCoverageGtFilter.getNumOccurrences() || LineCoverageLtFilter.getNumOccurrences() || LineCoverageGtFilter.getNumOccurrences()) { - auto StatFilterer = std::make_unique(); + auto StatFilterer = std::make_shared(); if (RegionCoverageLtFilter.getNumOccurrences()) - StatFilterer->push_back(std::make_unique( + StatFilterer->push_back(std::make_shared( RegionCoverageFilter::LessThan, RegionCoverageLtFilter)); if (RegionCoverageGtFilter.getNumOccurrences()) - StatFilterer->push_back(std::make_unique( + StatFilterer->push_back(std::make_shared( RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter)); if (LineCoverageLtFilter.getNumOccurrences()) - StatFilterer->push_back(std::make_unique( + StatFilterer->push_back(std::make_shared( LineCoverageFilter::LessThan, LineCoverageLtFilter)); if (LineCoverageGtFilter.getNumOccurrences()) - StatFilterer->push_back(std::make_unique( + StatFilterer->push_back(std::make_shared( RegionCoverageFilter::GreaterThan, LineCoverageGtFilter)); Filters.push_back(std::move(StatFilterer)); } // Create the ignore filename filters. for (const auto &RE : IgnoreFilenameRegexFilters) - IgnoreFilenameFilters.push_back( - std::make_unique(RE)); + IgnoreFilenameFilters->push_back( + std::make_shared(RE)); + // 'Filters' do always exclude files/functions, thus an inclusion-filter + // should match if the file name does *not* match the regex. Invert the + // filter! + for (const auto &RE : FilenameRegexFilters) + IncludeFilenameFilters->push_back( + std::make_shared( + std::make_unique(RE))); if (!Arches.empty()) { for (const std::string &Arch : Arches) { @@ -993,7 +1024,7 @@ if (SourceFiles.empty() && !HadSourceFiles) // Get the source files from the function coverage mapping. for (StringRef Filename : Coverage->getUniqueSourceFiles()) { - if (!IgnoreFilenameFilters.matchesFilename(Filename)) + if (!FilenameFilters.matchesFilename(Filename)) SourceFiles.push_back(std::string(Filename)); } @@ -1097,7 +1128,7 @@ CoverageReport Report(ViewOpts, *Coverage.get()); if (!ShowFunctionSummaries) { if (SourceFiles.empty()) - Report.renderFileReports(llvm::outs(), IgnoreFilenameFilters); + Report.renderFileReports(llvm::outs(), FilenameFilters); else Report.renderFileReports(llvm::outs(), SourceFiles); } else { @@ -1149,21 +1180,21 @@ switch (ViewOpts.Format) { case CoverageViewOptions::OutputFormat::Text: - Exporter = std::make_unique(*Coverage.get(), - ViewOpts, outs()); + Exporter = std::make_unique(*Coverage.get(), ViewOpts, + outs()); break; case CoverageViewOptions::OutputFormat::HTML: // Unreachable because we should have gracefully terminated with an error // above. llvm_unreachable("Export in HTML is not supported!"); case CoverageViewOptions::OutputFormat::Lcov: - Exporter = std::make_unique(*Coverage.get(), - ViewOpts, outs()); + Exporter = std::make_unique(*Coverage.get(), ViewOpts, + outs()); break; } if (SourceFiles.empty()) - Exporter->renderRoot(IgnoreFilenameFilters); + Exporter->renderRoot(FilenameFilters); else Exporter->renderRoot(SourceFiles); diff --git a/llvm/tools/llvm-cov/CoverageFilters.h b/llvm/tools/llvm-cov/CoverageFilters.h --- a/llvm/tools/llvm-cov/CoverageFilters.h +++ b/llvm/tools/llvm-cov/CoverageFilters.h @@ -37,8 +37,26 @@ } /// Return true if the filename passes the requirements of this filter. - virtual bool matchesFilename(StringRef Filename) const { - return true; + virtual bool matchesFilename(StringRef Filename) const { return true; } +}; + +class InvertedCoverageFilter : public CoverageFilter { +private: + std::unique_ptr BaseFilter; + +public: + InvertedCoverageFilter(std::unique_ptr Base) + : BaseFilter(std::move(Base)) {} + + /// Return true if the function passes the requirements of this filter. + bool matches(const coverage::CoverageMapping &CM, + const coverage::FunctionRecord &Function) const override { + return !BaseFilter->matches(CM, Function); + } + + /// Return true if the filename passes the requirements of this filter. + bool matchesFilename(StringRef Filename) const override { + return !BaseFilter->matchesFilename(Filename); } }; @@ -133,11 +151,19 @@ /// in an instance of this class. class CoverageFilters : public CoverageFilter { protected: - std::vector> Filters; + std::vector> Filters; + const bool ValueIfEmpty; public: + /// Create a new match-any filter + /// + /// @param ValueIfEmptyIn Set to the whether this filter should match + /// anything/nothing if it is emtpy. + CoverageFilters(bool ValueIfEmptyIn = false) : ValueIfEmpty(ValueIfEmptyIn) {} + /// Append a filter to this collection. - void push_back(std::unique_ptr Filter); + // NOLINTNEXTLINE(readability-identifier-naming) + void push_back(std::shared_ptr Filter); bool empty() const { return Filters.empty(); } @@ -152,8 +178,16 @@ /// in an instance of this class. class CoverageFiltersMatchAll : public CoverageFilters { public: + /// Create a new match-all filter + /// + /// @param ValueIfEmptyIn Set to the whether this filter should match + /// anything/nothing if it is emtpy. + CoverageFiltersMatchAll(bool ValueIfEmptyIn = true) + : CoverageFilters(ValueIfEmptyIn) {} bool matches(const coverage::CoverageMapping &CM, const coverage::FunctionRecord &Function) const override; + + bool matchesFilename(StringRef Filename) const override; }; } // namespace llvm diff --git a/llvm/tools/llvm-cov/CoverageFilters.cpp b/llvm/tools/llvm-cov/CoverageFilters.cpp --- a/llvm/tools/llvm-cov/CoverageFilters.cpp +++ b/llvm/tools/llvm-cov/CoverageFilters.cpp @@ -54,12 +54,17 @@ .LineCoverage.getPercentCovered()); } -void CoverageFilters::push_back(std::unique_ptr Filter) { +// NOLINTNEXTLINE(readability-identifier-naming) +void CoverageFilters::push_back(std::shared_ptr Filter) { Filters.push_back(std::move(Filter)); } bool CoverageFilters::matches(const coverage::CoverageMapping &CM, const coverage::FunctionRecord &Function) const { + if (empty()) { + return ValueIfEmpty; + } + for (const auto &Filter : Filters) { if (Filter->matches(CM, Function)) return true; @@ -68,6 +73,10 @@ } bool CoverageFilters::matchesFilename(StringRef Filename) const { + if (empty()) { + return ValueIfEmpty; + } + for (const auto &Filter : Filters) { if (Filter->matchesFilename(Filename)) return true; @@ -78,9 +87,25 @@ bool CoverageFiltersMatchAll::matches( const coverage::CoverageMapping &CM, const coverage::FunctionRecord &Function) const { + if (empty()) { + return ValueIfEmpty; + } + for (const auto &Filter : Filters) { if (!Filter->matches(CM, Function)) return false; } return true; } + +bool CoverageFiltersMatchAll::matchesFilename(StringRef Filename) const { + if (empty()) { + return ValueIfEmpty; + } + + for (const auto &Filter : Filters) { + if (!Filter->matchesFilename(Filename)) + return false; + } + return true; +}