diff --git a/Coverage-x86/Coverage-x86.vcxproj b/Coverage-x86/Coverage-x86.vcxproj
index a7a6fbd..c322a08 100644
--- a/Coverage-x86/Coverage-x86.vcxproj
+++ b/Coverage-x86/Coverage-x86.vcxproj
@@ -106,6 +106,7 @@
true
true
stdcpp20
+ NDEBUG;_MBCS;%(PreprocessorDefinitions)
true
diff --git a/Coverage/Coverage.vcxproj b/Coverage/Coverage.vcxproj
index e64799f..f7a315e 100644
--- a/Coverage/Coverage.vcxproj
+++ b/Coverage/Coverage.vcxproj
@@ -130,6 +130,7 @@
true
$(IntDir)vc$(PlatformToolsetVersion).pdb
stdcpp20
+ NDEBUG;%(PreprocessorDefinitions)
true
diff --git a/Coverage/CoverageRunner.cpp b/Coverage/CoverageRunner.cpp
new file mode 100644
index 0000000..bca9392
--- /dev/null
+++ b/Coverage/CoverageRunner.cpp
@@ -0,0 +1,113 @@
+#include "CoverageRunner.h"
+
+#include
+//---------------------------------------------------------------------------------------
+
+SourceManager CoverageRunner::_sources;
+
+//---------------------------------------------------------------------------------------
+
+void SourceManager::setupExcludeFilter(const RuntimeOptions& opts)
+{
+ _excludeFilter.clear();
+ for (const auto& filter : opts.excludeFilter)
+ {
+ _excludeFilter.emplace_back(std::regex(filter, std::regex_constants::ECMAScript /*| std::regex_constants::icase*/));
+ }
+}
+
+bool SourceManager::isExcluded(const std::filesystem::path& originalPath) const
+{
+ for (const auto& filter : _excludeFilter)
+ {
+ if (std::regex_search(originalPath.string(), filter))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void SourceManager::searchRealPath(std::filesystem::path& finalPath, const std::filesystem::path& original, bool& exclude) const
+{
+ // Search if original path exist into valid filter
+ bool isInGoodPath = false;
+ for (const auto& codepath : RuntimeOptionsSingleton::Instance().CodePaths)
+ {
+ if (original.string().starts_with(codepath.string()))
+ {
+ isInGoodPath = true;
+ break;
+ }
+ }
+
+ // If file not exists on disk but path look like good, return missing file
+ if ( isInGoodPath && !std::filesystem::exists(original) )
+ {
+ finalPath = std::filesystem::path();
+ exclude = false;
+ return;
+ }
+
+ // Try to remap path based on CodePath (happens when CI run coverage on different disk or path )
+ for (const auto& codepath : RuntimeOptionsSingleton::Instance().CodePaths)
+ {
+ // Try to reinterpret path (file from another server ?)
+ finalPath = original;
+ auto allFolders = finalPath.parent_path();
+ // Start with filename
+ finalPath = finalPath.filename();
+ const auto source = codepath;
+
+ while (!allFolders.filename().string().empty())
+ {
+ auto testPath = source / finalPath;
+ if (std::filesystem::exists(testPath))
+ {
+ finalPath = testPath;
+ exclude = false;
+ return;
+ }
+ else
+ {
+ finalPath = allFolders.filename() / finalPath;
+ allFolders = allFolders.parent_path();
+ }
+ }
+ // If found nothing, reset path and consider exclude file
+ finalPath = std::filesystem::path();
+ exclude = true;
+ }
+}
+
+SourceManager::SearchResult SourceManager::searchFromCodePath(const PSRCCODEINFO& lineInfo, const FileCallbackInfo& fileInfo, std::filesystem::path& finalPath)
+{
+ SearchResult result;
+ const auto originalPath = std::filesystem::path(lineInfo->FileName);
+ const auto itPath = _conversion.find(originalPath);
+ if (itPath != _conversion.cend())
+ {
+ finalPath = itPath->second;
+ }
+ else
+ {
+ result.isNew = true;
+ finalPath = std::filesystem::path();
+
+ // Search file is not inside exclude list and into CodePaths range
+ if ( !isExcluded(originalPath) )
+ {
+ searchRealPath(finalPath, originalPath, result.isExcluded);
+ }
+ else
+ {
+ result.isExcluded = true;
+ }
+
+ // Save already meet path
+ _conversion.emplace( originalPath, finalPath );
+ }
+ result.isFound = !finalPath.empty();
+
+ return result;
+}
\ No newline at end of file
diff --git a/Coverage/CoverageRunner.h b/Coverage/CoverageRunner.h
index be1b462..1fb1612 100644
--- a/Coverage/CoverageRunner.h
+++ b/Coverage/CoverageRunner.h
@@ -10,9 +10,9 @@
#include "Disassembler/ReachabilityAnalysis.h"
#include
-#include
#include
#include
+#include
#include
#include
#include
@@ -24,40 +24,67 @@
#include
#pragma warning(default : 4091)
+///
+/// Allow to keep real path between PDB info and real path.
+///
+struct SourceManager
+{
+ struct SearchResult
+ {
+ bool isNew = false;
+ bool isFound = false;
+ bool isExcluded = false;
+ };
+
+ std::vector _excludeFilter;
+ std::unordered_map _conversion;
+
+ SourceManager() noexcept = default;
+
+ void setupExcludeFilter(const RuntimeOptions& opts);
+ bool isExcluded(const std::filesystem::path& originalPath) const;
+
+ ///
+ /// Search if PDB file exists (search into pdb path and inside CodePath).
+ /// Then save it into map to not compute it again.
+ ///
+ SearchResult searchFromCodePath(const PSRCCODEINFO& lineInfo, const FileCallbackInfo& fileInfo, std::filesystem::path& finalPath);
+
+private:
+ void searchRealPath(std::filesystem::path& finalPath, const std::filesystem::path& original, bool& exclude) const;
+};
+
struct CoverageRunner
{
+ ///
+ /// Keep met source file (can be remap if PDB have another path)
+ ///
+ static SourceManager _sources;
+
CoverageRunner(const RuntimeOptions& opts) : options(opts),
debugInfoAvailable(false),
debuggerPresentPatched(false),
coverageContext(opts.Executable),
profileInfo()
- {}
+ {
+ // Initialize static data
+ _sources.setupExcludeFilter(opts);
+ }
static BOOL CALLBACK SymEnumLinesCallback(PSRCCODEINFO lineInfo, PVOID userContext)
{
CallbackInfo* info = reinterpret_cast(userContext);
- if (info->fileInfo->PathMatches(lineInfo->FileName))
+ std::filesystem::path filepath;
+ const auto result = _sources.searchFromCodePath(lineInfo, *info->fileInfo, filepath);
+ if (result.isFound)
{
- // Try to find if file exists (and can be covered)
- auto file = std::string(lineInfo->FileName);
- if (!std::filesystem::exists(file))
- {
-#ifndef NDEBUG
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Error))
- {
- std::cerr << std::format("Impossible to find file : {0}", file) << std::endl;
- }
-#endif
- return FALSE;
- }
-
PVOID addr = reinterpret_cast(lineInfo->Address);
auto it = info->breakpointsToSet.find(addr);
if (it == info->breakpointsToSet.end())
{
// Find line info
- auto fileLineInfo = info->fileInfo->LineInfo(file, lineInfo->LineNumber);
+ auto fileLineInfo = info->fileInfo->LineInfo(filepath.string(), lineInfo->LineNumber);
if (fileLineInfo)
{
// Only create breakpoint if we haven't already.
@@ -73,6 +100,15 @@ struct CoverageRunner
}
}
}
+ else
+ {
+ // Show one time into log
+ if (result.isNew && !result.isExcluded && RuntimeOptionsSingleton::Instance().isAtLeastLevel(VerboseLevel::Error))
+ {
+ std::cerr << std::format("Impossible to find file : {0}", lineInfo->FileName) << std::endl;
+ }
+ return FALSE;
+ }
return TRUE;
}
diff --git a/Coverage/Disassembler/ReachabilityAnalysis.cpp b/Coverage/Disassembler/ReachabilityAnalysis.cpp
index f89a94e..d2326a9 100644
--- a/Coverage/Disassembler/ReachabilityAnalysis.cpp
+++ b/Coverage/Disassembler/ReachabilityAnalysis.cpp
@@ -29,7 +29,7 @@ struct Helper
SIZE_T numberBytesRead;
if (!ReadProcessMemory(ptr, reinterpret_cast(address + arg->offset), &result, 1, &numberBytesRead))
{
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Error))
+ if (RuntimeOptionsSingleton::Instance().isAtLeastLevel(VerboseLevel::Error))
{
auto err = Util::GetLastErrorAsString();
std::cout << "Error reading memory from target process: " << err << std::endl;
@@ -178,7 +178,7 @@ ReachabilityAnalysis::ReachabilityAnalysis(HANDLE processHandle, DWORD64 methodS
SIZE_T numberBytesRead;
if (!ReadProcessMemory(processHandle, reinterpret_cast(methodStart), data, numberBytes, &numberBytesRead))
{
- if( RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Error) )
+ if(RuntimeOptionsSingleton::Instance().isAtLeastLevel(VerboseLevel::Error) )
{
auto err = Util::GetLastErrorAsString();
std::cout << "Error while reading symbol: " << err << std::endl;
diff --git a/Coverage/FileCallbackInfo.h b/Coverage/FileCallbackInfo.h
index adb287a..7bafcb0 100644
--- a/Coverage/FileCallbackInfo.h
+++ b/Coverage/FileCallbackInfo.h
@@ -24,7 +24,7 @@ struct FileCallbackInfo
FileCallbackInfo(const std::string& filename) :
filename(filename)
{
- if (RuntimeOptions::Instance().CodePaths.empty())
+ if (RuntimeOptionsSingleton::Instance().CodePaths.empty())
{
auto idx = filename.find("x64");
if (idx == std::string::npos)
@@ -66,7 +66,7 @@ struct FileCallbackInfo
std::swap(tmp, it.second);
newLineData[it.first] = std::move(tmp);
}
- else if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Trace))
+ else if (RuntimeOptionsSingleton::Instance().isAtLeastLevel(VerboseLevel::Trace))
{
std::cout << "Removing file " << it.first << std::endl;
}
@@ -74,7 +74,7 @@ struct FileCallbackInfo
std::swap(lineData, newLineData);
}
- bool PathMatches(const char* first, const std::string& second)
+ bool PathMatches(const char* first, const std::string& second) const
{
const char* ptr = first;
const char* gt = second.data();
@@ -90,17 +90,17 @@ struct FileCallbackInfo
return true;
}
- bool PathMatches(const char* filename)
+ bool PathMatches(const char* filename) const
{
if (!sourcePath.empty())
{
return PathMatches(filename, sourcePath);
}
- const auto& codePaths = RuntimeOptions::Instance().CodePaths;
+ const auto& codePaths = RuntimeOptionsSingleton::Instance().CodePaths;
for (const auto& codePath : codePaths)
{
- if (PathMatches(filename, codePath))
+ if (PathMatches(filename, codePath.string()))
{
return true;
}
@@ -170,7 +170,7 @@ struct FileCallbackInfo
// stream << "coveredstatements=\"300\" statements=\"500\" coveredmethods=\"50\" methods=\"80\" ";
// stream << "coveredconditionals=\"100\" conditionals=\"120\" coveredelements=\"900\" elements=\"1000\" ";
stream << "complexity=\"0\" />" << std::endl;
- stream << "" << std::endl;
+ stream << "" << std::endl;
for (auto& it : lineData)
{
auto ptr = it.second.get();
@@ -229,7 +229,7 @@ struct FileCallbackInfo
stream << "" << std::endl;
stream << "\t" << "" << std::endl;
- stream << "\t\t" << "" << std::endl;
+ stream << "\t\t" << "" << std::endl;
stream << "\t\t\t" << "" << std::endl;
for (auto& it : lineData)
{
@@ -379,7 +379,9 @@ struct FileCallbackInfo
filepaths.push_back(item.first);
}
- for (const auto& dirPath : RuntimeOptions::Instance().CodePaths)
+ const auto& options = RuntimeOptionsSingleton::Instance();
+
+ for (const auto& dirPath : options.CodePaths)
{
bool dirPartAdded = false;
@@ -401,7 +403,7 @@ struct FileCallbackInfo
if (!dirPartAdded && !dirPath.empty())
{
dirPartAdded = true;
- FileCoverageV2::openDirectory(stream, dirPath);
+ FileCoverageV2::openDirectory(stream, dirPath == options.SolutionPath, dirPath);
}
auto coverage = encodeCoverage(*it.second.get());
@@ -417,7 +419,7 @@ struct FileCallbackInfo
}
}
- if (!filepaths.empty() && RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Warning))
+ if (!filepaths.empty() && options.isAtLeastLevel(VerboseLevel::Warning))
{
std::cerr << "List of refuse coverage files (because not relative to any code path):" << std::endl;
@@ -428,9 +430,9 @@ struct FileCallbackInfo
std::cerr << std::endl << "List of code paths:" << std::endl;
- for (const auto& dirPath : RuntimeOptions::Instance().CodePaths)
+ for (const auto& dirPath : RuntimeOptionsSingleton::Instance().CodePaths)
{
- std::cerr << std::format("- {0}", dirPath) << std::endl;
+ std::cerr << std::format("- {0}", dirPath.string()) << std::endl;
}
}
diff --git a/Coverage/FileCoverageV2.h b/Coverage/FileCoverageV2.h
index 8f20416..e5ad3ec 100644
--- a/Coverage/FileCoverageV2.h
+++ b/Coverage/FileCoverageV2.h
@@ -3,6 +3,8 @@
#include "base64.h"
#include "FileInfo.h"
+#include
+#include
#include
#include
@@ -50,19 +52,27 @@ struct FileCoverageV2
return code;
}
+ ///
+ /// Compute from scratch the line stats.
+ /// Warning: Nb code lines can variate too if template (don't produce code line if not used)
+ /// Maybe compilation option are badly choose by user ?
+ ///
void updateStats()
{
_nbLinesCovered = 0;
+ _nbLinesCode = 0;
for (const auto& line : _code)
{
if ((line & maskIsCode) == maskIsCode)
{
+ _nbLinesCode += 1;
if ((line & maskCount) > 0)
{
_nbLinesCovered += 1;
}
}
}
+ assert(_nbLinesCovered <= _nbLinesCode);
}
bool merge(const FileCoverageV2& other)
@@ -76,7 +86,7 @@ struct FileCoverageV2
{
const size_t count = (size_t) (line & maskCount) + (size_t) (*src & maskCount);
- const bool isCode = (line & maskIsCode) == maskIsCode;
+ const bool isCode = ((line & maskIsCode) | (*src & maskIsCode)) == maskIsCode || count > 0;
const bool isPartial = (line & maskIsPartial) == maskIsPartial && (*src & maskIsPartial) == maskIsPartial;
line = (uint16_t) std::min(count, maskCount);
@@ -97,9 +107,9 @@ struct FileCoverageV2
ofs << std::format(R"()", version) << std::endl;
}
- static void openDirectory(std::ostream& ofs, const std::string& aDir)
+ static void openDirectory(std::ostream& ofs, const bool isSolutionPath, const std::filesystem::path& aDir)
{
- ofs << std::format(R"( )", aDir) << std::endl;
+ ofs << std::format(R"( )", isSolutionPath ? "true" : "false", aDir.string()) << std::endl;
}
static void closeDirectory(std::ostream& ofs)
diff --git a/Coverage/FileInfo.cpp b/Coverage/FileInfo.cpp
index cb93d12..f489804 100644
--- a/Coverage/FileInfo.cpp
+++ b/Coverage/FileInfo.cpp
@@ -111,7 +111,7 @@ FileLineInfo* FileInfo::LineInfo(size_t lineNumber)
if (lineNumber < 0xf00000 - 1 &&
lineNumber != numberLines)
{
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Warning))
+ if (RuntimeOptionsSingleton::Instance().isAtLeastLevel(VerboseLevel::Warning))
{
std::cout << "Warning: line number out of bounds: " << lineNumber << " >= " << numberLines << std::endl;
}
diff --git a/Coverage/Main.cpp b/Coverage/Main.cpp
index 5a8e101..3b4c4db 100644
--- a/Coverage/Main.cpp
+++ b/Coverage/Main.cpp
@@ -38,6 +38,7 @@ void ShowHelp()
std::cout << " helper processes into the main -o output file and delete them," << std::endl;
std::cout << " so that you end up with a single coverage file containing both" << std::endl;
std::cout << " bitnesses. Only supported for -format native / nativeV2." << std::endl;
+ std::cout << " -excludeFile: Regexp to exclude file of coverage (sometime you can have template fake file)" << std::endl;
std::cout << " -- [name]: Run coverage on the given executable filename" << std::endl;
std::cout << "Return code:" << std::endl;
std::cout << " 0: Success run" << std::endl;
@@ -55,7 +56,7 @@ void ShowHelp()
void ParseCommandLine(int argc, const char** argv)
{
- RuntimeOptions& opts = RuntimeOptions::Instance();
+ RuntimeOptions& opts = RuntimeOptionsSingleton::Instance();
LPTSTR cmd = GetCommandLine();
std::string cmdLine = cmd;
@@ -146,6 +147,7 @@ void ParseCommandLine(int argc, const char** argv)
opts.SolutionPath = t;
if (!std::filesystem::exists(opts.SolutionPath))
throw std::exception("The solution path provide is not existing.");
+ opts.CodePaths.emplace(opts.SolutionPath);
}
else if (s == "-format")
{
@@ -197,7 +199,7 @@ void ParseCommandLine(int argc, const char** argv)
}
std::string t(argv[i]);
- opts.CodePaths.push_back(t);
+ opts.CodePaths.emplace(t);
}
else if (s == "-w")
{
@@ -244,6 +246,16 @@ void ParseCommandLine(int argc, const char** argv)
opts.Executable = t;
break;
}
+ else if( s == "-excludeFile")
+ {
+ ++i;
+ if (i == argc)
+ {
+ throw std::exception("Unexpected end of parameters. Expected filter.");
+ }
+
+ opts.excludeFilter.emplace_back( std::string(argv[i]) );
+ }
else if (s == "-help")
{
ShowHelp();
@@ -324,12 +336,13 @@ void ParseCommandLine(int argc, const char** argv)
opts.ExecutableArguments = opts.ExecutableArguments.substr(1);
*/
#ifdef _DEBUG
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Trace))
+ if (opts.isAtLeastLevel(VerboseLevel::Trace))
{
std::cout << "Executable: " << opts.Executable << std::endl;
std::cout << "Arguments: " << opts.ExecutableArguments << std::endl;
}
#endif
+ return -1;
}
class UTF8CodePage {
@@ -361,7 +374,7 @@ int main(int argc, const char** argv)
}
#endif
- RuntimeOptions& opts = RuntimeOptions::Instance();
+ RuntimeOptions& opts = RuntimeOptionsSingleton::Instance();
try
{
@@ -369,7 +382,7 @@ int main(int argc, const char** argv)
}
catch (const std::exception& e)
{
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Error))
+ if (opts.isAtLeastLevel(VerboseLevel::Error))
{
std::cerr << "Error: " << e.what() << std::endl;
}
@@ -388,7 +401,7 @@ int main(int argc, const char** argv)
{
if (opts.Executable.empty())
{
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Error))
+ if (opts.isAtLeastLevel(VerboseLevel::Error))
{
std::cerr << "Error: Missing executable file" << std::endl;
}
@@ -498,7 +511,7 @@ int main(int argc, const char** argv)
{
if (!opts.MergedOutput.empty())
{
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Info))
+ if (opts.isAtLeastLevel(VerboseLevel::Info))
{
std::cout << "Merge into " << opts.MergedOutput << std::endl;
}
@@ -533,7 +546,7 @@ int main(int argc, const char** argv)
}
catch (const std::exception& e)
{
- if (RuntimeOptions::Instance().isAtLeastLevel(VerboseLevel::Error))
+ if (opts.isAtLeastLevel(VerboseLevel::Error))
{
std::cerr << "Error: " << e.what() << std::endl;
}
diff --git a/Coverage/MergeRunner.h b/Coverage/MergeRunner.h
index 2de0bb4..0cd5a17 100644
--- a/Coverage/MergeRunner.h
+++ b/Coverage/MergeRunner.h
@@ -3,12 +3,24 @@
#include "RuntimeOptions.h"
#include
+#include
#include
+struct CoverageResult
+{
+ virtual ~CoverageResult() = default;
+
+ virtual size_t nbCoveredFile() const = 0;
+
+ virtual size_t nbLineCovered(const std::filesystem::path& path) const = 0;
+
+ virtual size_t nbFolders() const = 0;
+};
+
class MergeRunner
{
protected:
- RuntimeOptions _options; ///< Copy local of option.
+ const RuntimeOptions _options; ///< Copy local of option.
// Avoid copy constructor
MergeRunner(const MergeRunner&) = delete;
@@ -28,6 +40,9 @@ class MergeRunner
/// Run merge
virtual void execute() = 0;
+ /// Read dict on disk
+ virtual std::unique_ptr read( const std::filesystem::path& ) const = 0;
+
// Allow to build good merge runner
static std::unique_ptr createMergeRunner(const RuntimeOptions& opts);
};
\ No newline at end of file
diff --git a/Coverage/MergeRunnerV1.h b/Coverage/MergeRunnerV1.h
index 5bd5370..5d981b8 100644
--- a/Coverage/MergeRunnerV1.h
+++ b/Coverage/MergeRunnerV1.h
@@ -18,7 +18,32 @@ class MergeRunnerV1 : public MergeRunner
using DictCoverage = std::map;
- DictCoverage makeDictionary(const std::string& filename)
+ struct Result : public CoverageResult
+ {
+ DictCoverage _dict;
+
+ size_t nbCoveredFile() const override
+ {
+ return _dict.size();
+ }
+
+ size_t nbLineCovered(const std::filesystem::path& path) const override
+ {
+ const auto search = _dict.find(path.string());
+ if (search != _dict.cend())
+ {
+ return count(search->second.res.begin(), search->second.res.end(), 'c');
+ }
+ return 0ull;
+ }
+
+ size_t nbFolders() const override
+ {
+ return _dict.size();
+ }
+ };
+
+ DictCoverage makeDictionary(const std::string& filename) const
{
DictCoverage dictOutput;
@@ -99,6 +124,13 @@ class MergeRunnerV1 : public MergeRunner
assert(_options.ExportFormat == RuntimeOptions::Native); // Support only this !
}
+ std::unique_ptr read(const std::filesystem::path& path) const override
+ {
+ auto r = std::make_unique();
+ r->_dict = makeDictionary(path.string());
+ return r;
+ }
+
/// Run merge
void execute() override
{
diff --git a/Coverage/MergeRunnerV2.h b/Coverage/MergeRunnerV2.h
index acf1bf6..8de21f0 100644
--- a/Coverage/MergeRunnerV2.h
+++ b/Coverage/MergeRunnerV2.h
@@ -5,8 +5,7 @@
#include "MergeRunner.h"
#include
-#include
-#include