Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Coverage-x86/Coverage-x86.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<LanguageStandard>stdcpp20</LanguageStandard>
<PreprocessorDefinitions>NDEBUG;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
Expand Down
1 change: 1 addition & 0 deletions Coverage/Coverage.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
<SDLCheck>true</SDLCheck>
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
<LanguageStandard>stdcpp20</LanguageStandard>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
Expand Down
113 changes: 113 additions & 0 deletions Coverage/CoverageRunner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "CoverageRunner.h"

#include <string>
//---------------------------------------------------------------------------------------

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;
}
70 changes: 53 additions & 17 deletions Coverage/CoverageRunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
#include "Disassembler/ReachabilityAnalysis.h"

#include <algorithm>
#include <format>
#include <iostream>
#include <filesystem>
#include <regex>
#include <string>
#include <unordered_map>
#include <unordered_set>
Expand All @@ -24,40 +24,67 @@
#include <DbgHelp.h>
#pragma warning(default : 4091)

/// <summary>
/// Allow to keep real path between PDB info and real path.
/// </summary>
struct SourceManager
{
struct SearchResult
{
bool isNew = false;
bool isFound = false;
bool isExcluded = false;
};

std::vector<std::regex> _excludeFilter;
std::unordered_map<std::filesystem::path, std::filesystem::path> _conversion;

SourceManager() noexcept = default;

void setupExcludeFilter(const RuntimeOptions& opts);
bool isExcluded(const std::filesystem::path& originalPath) const;

/// <summary>
/// Search if PDB file exists (search into pdb path and inside CodePath).
/// Then save it into map to not compute it again.
/// </summary>
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
{
/// <summary>
/// Keep met source file (can be remap if PDB have another path)
/// </summary>
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<CallbackInfo*>(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<PVOID>(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.
Expand All @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions Coverage/Disassembler/ReachabilityAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct Helper
SIZE_T numberBytesRead;
if (!ReadProcessMemory(ptr, reinterpret_cast<LPVOID>(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;
Expand Down Expand Up @@ -178,7 +178,7 @@ ReachabilityAnalysis::ReachabilityAnalysis(HANDLE processHandle, DWORD64 methodS
SIZE_T numberBytesRead;
if (!ReadProcessMemory(processHandle, reinterpret_cast<LPVOID>(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;
Expand Down
28 changes: 15 additions & 13 deletions Coverage/FileCallbackInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -66,15 +66,15 @@ 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;
}
}
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();
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 << "<package name=\"" << RuntimeOptions::Instance().PackageName << "\">" << std::endl;
stream << "<package name=\"" << RuntimeOptionsSingleton::Instance().PackageName << "\">" << std::endl;
for (auto& it : lineData)
{
auto ptr = it.second.get();
Expand Down Expand Up @@ -229,7 +229,7 @@ struct FileCallbackInfo
stream << "<coverage line-rate=\"" << lineRate << "\"" << " " << "version=\"\">" << std::endl;
stream << "\t" << "<packages>" << std::endl;

stream << "\t\t" << "<package name=\"" << RuntimeOptions::Instance().PackageName << "\" line-rate=\"" << lineRate << "\">" << std::endl;
stream << "\t\t" << "<package name=\"" << RuntimeOptionsSingleton::Instance().PackageName << "\" line-rate=\"" << lineRate << "\">" << std::endl;
stream << "\t\t\t" << "<classes>" << std::endl;
for (auto& it : lineData)
{
Expand Down Expand Up @@ -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;

Expand All @@ -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());
Expand All @@ -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;

Expand All @@ -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;
}
}

Expand Down
Loading