diff --git a/llvm/docs/CommandGuide/llvm-cov.rst b/llvm/docs/CommandGuide/llvm-cov.rst --- a/llvm/docs/CommandGuide/llvm-cov.rst +++ b/llvm/docs/CommandGuide/llvm-cov.rst @@ -321,6 +321,14 @@ to generate the coverage data on one machine, and then use llvm-cov on a different machine where you have the same files on a different path. +.. option:: -path-equivalence-regex=, + + Map the paths in the coverage data to local source file paths. Unlike + `-path-equivalence`, this takes as a regular expression. Any string + matched by the regular expression in file paths will be replaced to the + given string. If there is a captured string in the regular expression, + the captured string will be used as a prefix of the new remapped path. + .. program:: llvm-cov report .. _llvm-cov-report: diff --git a/llvm/test/tools/llvm-cov/sources-specified.test b/llvm/test/tools/llvm-cov/sources-specified.test --- a/llvm/test/tools/llvm-cov/sources-specified.test +++ b/llvm/test/tools/llvm-cov/sources-specified.test @@ -10,6 +10,12 @@ RUN: %S/Inputs/sources_specified/main.cc %S/Inputs/sources_specified/extra \ RUN: | FileCheck -check-prefix=SHOW %s +RUN: llvm-cov show -instr-profile %S/Inputs/sources_specified/main.profdata \ +RUN: -path-equivalence-regex=/tmp,%S/Inputs \ +RUN: %S/Inputs/sources_specified/main.covmapping \ +RUN: %S/Inputs/sources_specified/main.cc %S/Inputs/sources_specified/extra \ +RUN: | FileCheck -check-prefix=SHOWREGEX %s + # Don't include all source files when provided source files are filtered out. RUN: llvm-cov show -instr-profile %S/Inputs/sources_specified/main.profdata \ RUN: -path-equivalence=/tmp,%S/Inputs \ @@ -32,6 +38,12 @@ SHOW: {{.*}}sources_specified{{.*}} SHOW: {{.*}}sources_specified{{.*}} +# Order of files may differ, check that there are 3 files and not abs.h. +SHOWREGEX-NOT: {{.*}}abs.h{{.*}} +SHOWREGEX: {{.*}}sources_specified{{.*}} +SHOWREGEX: {{.*}}sources_specified{{.*}} +SHOWREGEX: {{.*}}sources_specified{{.*}} + # Test "export" command. Use a temp .json file as output is a single line. RUN: llvm-cov export -instr-profile %S/Inputs/sources_specified/main.profdata \ 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 @@ -32,6 +32,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" +#include "llvm/Support/Regex.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/SpecialCaseList.h" #include "llvm/Support/ThreadPool.h" @@ -80,6 +81,9 @@ /// directory, recursively collect all of the paths within the directory. void collectPaths(const std::string &Path); + /// Return the actual file path of a given source file + StringRef getSourceFilePath(StringRef SourceFile); + /// Return a memory buffer for the given source file. ErrorOr getSourceFile(StringRef SourceFile); @@ -106,6 +110,17 @@ /// Load the coverage mapping data. Return nullptr if an error occurred. std::unique_ptr load(); + // Convert remapping paths to native paths with trailing seperators. + std::string nativeWithTrailing(StringRef Path, bool RemoveDots); + + // Initialize RemappedFilenames by a regular expression of the coverage data + // path (path-equivalence-regex). + void remapPathsFromRegex(const CoverageMapping &Coverage); + + // Initialize RemappedFilenames by a string of the coverage data path + // (path-equivalence). + void remapPathsFromString(const CoverageMapping &Coverage); + /// Create a mapping from files in the Coverage data to local copies /// (path-equivalence). void remapPathNames(const CoverageMapping &Coverage); @@ -153,6 +168,9 @@ /// remapped to, when using -path-equivalence. Optional> PathRemapping; + // True if the first value of PathRemapping is a regular expression. + bool PathRemappingRegex = false; + /// The architecture the coverage mapping data targets. std::vector CoverageArches; @@ -239,17 +257,24 @@ } } +StringRef CodeCoverageTool::getSourceFilePath(StringRef SourceFile) { + if (!RemappedFilenames.empty()) { + auto LocalFile = RemappedFilenames.find(SourceFile); + if (LocalFile != RemappedFilenames.end()) + SourceFile = LocalFile->second; + } + + return SourceFile; +} + ErrorOr CodeCoverageTool::getSourceFile(StringRef SourceFile) { // If we've remapped filenames, look up the real location for this file. std::unique_lock Guard{LoadedSourceFilesLock}; - if (!RemappedFilenames.empty()) { - auto Loc = RemappedFilenames.find(SourceFile); - if (Loc != RemappedFilenames.end()) - SourceFile = Loc->second; - } + SourceFile = getSourceFilePath(SourceFile); for (const auto &Files : LoadedSourceFiles) - if (sys::fs::equivalent(SourceFile, Files.first)) + if ((!PathRemappingRegex && sys::fs::equivalent(SourceFile, Files.first)) || + (PathRemappingRegex && SourceFile == Files.first)) return *Files.second; auto Buffer = MemoryBuffer::getFile(SourceFile); if (auto EC = Buffer.getError()) { @@ -338,6 +363,10 @@ std::unique_ptr CodeCoverageTool::createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage) { + auto RemappedSourceFile = SourceFile; + if (PathRemappingRegex) + RemappedSourceFile = getSourceFilePath(SourceFile); + auto SourceBuffer = getSourceFile(SourceFile); if (!SourceBuffer) return nullptr; @@ -347,10 +376,10 @@ auto Branches = FileCoverage.getBranches(); auto Expansions = FileCoverage.getExpansions(); - auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(), + auto View = SourceCoverageView::create(RemappedSourceFile, SourceBuffer.get(), ViewOpts, std::move(FileCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); - attachBranchSubViews(*View, SourceFile, Branches, SourceBuffer.get(), + attachBranchSubViews(*View, RemappedSourceFile, Branches, SourceBuffer.get(), FileCoverage); if (!ViewOpts.ShowFunctionInstantiations) return View; @@ -434,23 +463,46 @@ return Coverage; } -void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) { - if (!PathRemapping) - return; - - // Convert remapping paths to native paths with trailing seperators. - auto nativeWithTrailing = [](StringRef Path) -> std::string { - if (Path.empty()) - return ""; - SmallString<128> NativePath; - sys::path::native(Path, NativePath); +std::string CodeCoverageTool::nativeWithTrailing(StringRef Path, + bool RemoveDots) { + if (Path.empty()) + return ""; + SmallString<256> NativePath; + sys::path::native(Path, NativePath); + if (RemoveDots) sys::path::remove_dots(NativePath, true); - if (!sys::path::is_separator(NativePath.back())) - NativePath += sys::path::get_separator(); - return NativePath.c_str(); - }; - std::string RemapFrom = nativeWithTrailing(PathRemapping->first); - std::string RemapTo = nativeWithTrailing(PathRemapping->second); + if (!sys::path::is_separator(NativePath.back())) + NativePath += sys::path::get_separator(); + return NativePath.c_str(); +}; + +void CodeCoverageTool::remapPathsFromRegex(const CoverageMapping &Coverage) { + llvm::Regex RemapFrom = llvm::Regex(PathRemapping->first); + std::string RemapTo = + llvm::Regex::escape(nativeWithTrailing(PathRemapping->second, false)); + + // Create a mapping from coverage data file paths to local paths. + for (StringRef Filename : Coverage.getUniqueSourceFiles()) { + SmallVector Matches; + if (RemapFrom.match(Filename.str(), &Matches)) { + SmallString<256> RemappedNativeFilename; + if (Matches.size() == 2) + sys::path::native(Matches[1].str() + + RemapFrom.sub(RemapTo, Filename.str()), + RemappedNativeFilename); + else + sys::path::native(RemapFrom.sub(RemapTo, Filename.str()), + RemappedNativeFilename); + + sys::path::remove_dots(RemappedNativeFilename, true); + RemappedFilenames[Filename] = std::string(RemappedNativeFilename.str()); + } + } +} + +void CodeCoverageTool::remapPathsFromString(const CoverageMapping &Coverage) { + std::string RemapFrom = nativeWithTrailing(PathRemapping->first, true); + std::string RemapTo = nativeWithTrailing(PathRemapping->second, true); // Create a mapping from coverage data file paths to local paths. for (StringRef Filename : Coverage.getUniqueSourceFiles()) { @@ -462,6 +514,16 @@ RemapTo + NativeFilename.substr(RemapFrom.size()).str(); } } +} + +void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) { + if (!PathRemapping) + return; + + if (PathRemappingRegex) + remapPathsFromRegex(Coverage); + else + remapPathsFromString(Coverage); // Convert input files from local paths to coverage data file paths. StringMap InvRemappedFilenames; @@ -470,7 +532,7 @@ std::string(RemappedFilename.getKey()); for (std::string &Filename : SourceFiles) { - SmallString<128> NativeFilename; + SmallString<256> NativeFilename; sys::path::native(Filename, NativeFilename); auto CovFileName = InvRemappedFilenames.find(NativeFilename); if (CovFileName != InvRemappedFilenames.end()) @@ -572,6 +634,8 @@ return; } + if (PathRemappingRegex) + SourceFile = getSourceFilePath(SourceFile); auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false); if (Error E = OSOrErr.takeError()) { error("Could not create view file!", toString(std::move(E))); @@ -629,6 +693,11 @@ cl::desc(", Map coverage data paths to local source file " "paths")); + cl::opt PathRemapRegex( + "path-equivalence-regex", cl::Optional, + cl::desc(", Map regular expression of coverage data paths " + "to local source file paths.")); + cl::OptionCategory FilteringCategory("Function filtering options"); cl::list NameFilters( @@ -754,6 +823,13 @@ PathRemapping = {std::string(EquivPair.first), std::string(EquivPair.second)}; + auto EquivRegexPair = StringRef(PathRemapRegex).split(','); + if (!(EquivRegexPair.first.empty() && EquivRegexPair.second.empty())) { + PathRemapping = {std::string(EquivRegexPair.first), + std::string(EquivRegexPair.second)}; + PathRemappingRegex = true; + } + // If a demangler is supplied, check if it exists and register it. if (!DemanglerOpts.empty()) { auto DemanglerPathOrErr = sys::findProgramByName(DemanglerOpts[0]);