//===-- sancov.cc --------------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file is a command-line tool for reading and analyzing sanitizer // coverage. //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Twine.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCDisassembler/MCDisassembler.h" #include "llvm/MC/MCInst.h" #include "llvm/MC/MCInstPrinter.h" #include "llvm/MC/MCInstrAnalysis.h" #include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCObjectFileInfo.h" #include "llvm/MC/MCRegisterInfo.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/Object/Archive.h" #include "llvm/Object/Binary.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/MD5.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Regex.h" #include "llvm/Support/Signals.h" #include "llvm/Support/SpecialCaseList.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include using namespace llvm; namespace { // --------- COMMAND LINE FLAGS --------- enum ActionType { PrintAction, PrintCovPointsAction, CoveredFunctionsAction, NotCoveredFunctionsAction, HtmlReportAction, StatsAction }; cl::opt Action( cl::desc("Action (required)"), cl::Required, cl::values(clEnumValN(PrintAction, "print", "Print coverage addresses"), clEnumValN(PrintCovPointsAction, "print-coverage-pcs", "Print coverage instrumentation points addresses."), clEnumValN(CoveredFunctionsAction, "covered-functions", "Print all covered funcions."), clEnumValN(NotCoveredFunctionsAction, "not-covered-functions", "Print all not covered funcions."), clEnumValN(HtmlReportAction, "html-report", "Print HTML coverage report."), clEnumValN(StatsAction, "print-coverage-stats", "Print coverage statistics."), clEnumValEnd)); static cl::list ClInputFiles(cl::Positional, cl::OneOrMore, cl::desc("(|<.sancov file>)...")); static cl::opt ClDemangle("demangle", cl::init(true), cl::desc("Print demangled function name.")); static cl::opt ClStripPathPrefix( "strip_path_prefix", cl::init(""), cl::desc("Strip this prefix from file paths in reports.")); static cl::opt ClBlacklist("blacklist", cl::init(""), cl::desc("Blacklist file (sanitizer blacklist format).")); static cl::opt ClUseDefaultBlacklist( "use_default_blacklist", cl::init(true), cl::Hidden, cl::desc("Controls if default blacklist should be used.")); static const char *const DefaultBlacklistStr = "fun:__sanitizer_.*\n" "src:/usr/include/.*\n" "src:.*/libc\\+\\+/.*\n"; // --------- FORMAT SPECIFICATION --------- struct FileHeader { uint32_t Bitness; uint32_t Magic; }; static const uint32_t BinCoverageMagic = 0xC0BFFFFF; static const uint32_t Bitness32 = 0xFFFFFF32; static const uint32_t Bitness64 = 0xFFFFFF64; // --------- ERROR HANDLING --------- static void Fail(const llvm::Twine &E) { errs() << "Error: " << E << "\n"; exit(1); } static void FailIfError(std::error_code Error) { if (!Error) return; errs() << "Error: " << Error.message() << "(" << Error.value() << ")\n"; exit(1); } template static void FailIfError(const ErrorOr &E) { FailIfError(E.getError()); } static void FailIfError(Error Err) { if (Err) { logAllUnhandledErrors(std::move(Err), errs(), "Error: "); exit(1); } } template static void FailIfError(Expected &E) { FailIfError(E.takeError()); } static void FailIfNotEmpty(const llvm::Twine &E) { if (E.str().empty()) return; Fail(E); } template static void FailIfEmpty(const std::unique_ptr &Ptr, const std::string &Message) { if (Ptr.get()) return; Fail(Message); } // --------- // Produces std::map> grouping input // elements by FuncTy result. template static inline auto group_by(const RangeTy &R, FuncTy F) -> std::map::type, std::vector::type>> { std::map::type, std::vector::type>> Result; for (const auto &E : R) { Result[F(E)].push_back(E); } return Result; } template static void readInts(const char *Start, const char *End, std::set *Ints) { const T *S = reinterpret_cast(Start); const T *E = reinterpret_cast(End); std::copy(S, E, std::inserter(*Ints, Ints->end())); } struct FileLoc { bool operator<(const FileLoc &RHS) const { return std::tie(FileName, Line) < std::tie(RHS.FileName, RHS.Line); } std::string FileName; uint32_t Line; }; struct FileFn { bool operator<(const FileFn &RHS) const { return std::tie(FileName, FunctionName) < std::tie(RHS.FileName, RHS.FunctionName); } std::string FileName; std::string FunctionName; }; struct FnLoc { bool operator<(const FnLoc &RHS) const { return std::tie(Loc, FunctionName) < std::tie(RHS.Loc, RHS.FunctionName); } FileLoc Loc; std::string FunctionName; }; std::string stripPathPrefix(std::string Path) { if (ClStripPathPrefix.empty()) return Path; size_t Pos = Path.find(ClStripPathPrefix); if (Pos == std::string::npos) return Path; return Path.substr(Pos + ClStripPathPrefix.size()); } static std::unique_ptr createSymbolizer() { symbolize::LLVMSymbolizer::Options SymbolizerOptions; SymbolizerOptions.Demangle = ClDemangle; SymbolizerOptions.UseSymbolTable = true; return std::unique_ptr( new symbolize::LLVMSymbolizer(SymbolizerOptions)); } // A DILineInfo with address. struct AddrInfo : public DILineInfo { uint64_t Addr; AddrInfo(const DILineInfo &DI, uint64_t Addr) : DILineInfo(DI), Addr(Addr) { FileName = normalizeFilename(FileName); } private: static std::string normalizeFilename(const std::string &FileName) { SmallString<256> S(FileName); sys::path::remove_dots(S, /* remove_dot_dot */ true); return S.str().str(); } }; class Blacklists { public: Blacklists() : DefaultBlacklist(createDefaultBlacklist()), UserBlacklist(createUserBlacklist()) {} // AddrInfo contains normalized filename. It is important to check it rather // than DILineInfo. bool isBlacklisted(const AddrInfo &AI) { if (DefaultBlacklist && DefaultBlacklist->inSection("fun", AI.FunctionName)) return true; if (DefaultBlacklist && DefaultBlacklist->inSection("src", AI.FileName)) return true; if (UserBlacklist && UserBlacklist->inSection("fun", AI.FunctionName)) return true; if (UserBlacklist && UserBlacklist->inSection("src", AI.FileName)) return true; return false; } private: static std::unique_ptr createDefaultBlacklist() { if (!ClUseDefaultBlacklist) return std::unique_ptr(); std::unique_ptr MB = MemoryBuffer::getMemBuffer(DefaultBlacklistStr); std::string Error; auto Blacklist = SpecialCaseList::create(MB.get(), Error); FailIfNotEmpty(Error); return Blacklist; } static std::unique_ptr createUserBlacklist() { if (ClBlacklist.empty()) return std::unique_ptr(); return SpecialCaseList::createOrDie({{ClBlacklist}}); } std::unique_ptr DefaultBlacklist; std::unique_ptr UserBlacklist; }; // Collect all debug info for given addresses. static std::vector getAddrInfo(const std::string &ObjectFile, const std::set &Addrs, bool InlinedCode) { std::vector Result; auto Symbolizer(createSymbolizer()); Blacklists B; for (auto Addr : Addrs) { auto LineInfo = Symbolizer->symbolizeCode(ObjectFile, Addr); FailIfError(LineInfo); auto LineAddrInfo = AddrInfo(*LineInfo, Addr); if (B.isBlacklisted(LineAddrInfo)) continue; Result.push_back(LineAddrInfo); if (InlinedCode) { auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr); FailIfError(InliningInfo); for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) { auto FrameInfo = InliningInfo->getFrame(I); auto FrameAddrInfo = AddrInfo(FrameInfo, Addr); if (B.isBlacklisted(FrameAddrInfo)) continue; Result.push_back(FrameAddrInfo); } } } return Result; } // Locate __sanitizer_cov* function addresses that are used for coverage // reporting. static std::set findSanitizerCovFunctions(const object::ObjectFile &O) { std::set Result; for (const object::SymbolRef &Symbol : O.symbols()) { Expected AddressOrErr = Symbol.getAddress(); FailIfError(errorToErrorCode(AddressOrErr.takeError())); Expected NameOrErr = Symbol.getName(); FailIfError(errorToErrorCode(NameOrErr.takeError())); StringRef Name = NameOrErr.get(); if (Name == "__sanitizer_cov" || Name == "__sanitizer_cov_with_check" || Name == "__sanitizer_cov_trace_func_enter") { if (!(Symbol.getFlags() & object::BasicSymbolRef::SF_Undefined)) Result.insert(AddressOrErr.get()); } } return Result; } // Locate addresses of all coverage points in a file. Coverage point // is defined as the 'address of instruction following __sanitizer_cov // call - 1'. static void getObjectCoveragePoints(const object::ObjectFile &O, std::set *Addrs) { Triple TheTriple("unknown-unknown-unknown"); TheTriple.setArch(Triple::ArchType(O.getArch())); auto TripleName = TheTriple.getTriple(); std::string Error; const Target *TheTarget = TargetRegistry::lookupTarget(TripleName, Error); FailIfNotEmpty(Error); std::unique_ptr STI( TheTarget->createMCSubtargetInfo(TripleName, "", "")); FailIfEmpty(STI, "no subtarget info for target " + TripleName); std::unique_ptr MRI( TheTarget->createMCRegInfo(TripleName)); FailIfEmpty(MRI, "no register info for target " + TripleName); std::unique_ptr AsmInfo( TheTarget->createMCAsmInfo(*MRI, TripleName)); FailIfEmpty(AsmInfo, "no asm info for target " + TripleName); std::unique_ptr MOFI(new MCObjectFileInfo); MCContext Ctx(AsmInfo.get(), MRI.get(), MOFI.get()); std::unique_ptr DisAsm( TheTarget->createMCDisassembler(*STI, Ctx)); FailIfEmpty(DisAsm, "no disassembler info for target " + TripleName); std::unique_ptr MII(TheTarget->createMCInstrInfo()); FailIfEmpty(MII, "no instruction info for target " + TripleName); std::unique_ptr MIA( TheTarget->createMCInstrAnalysis(MII.get())); FailIfEmpty(MIA, "no instruction analysis info for target " + TripleName); auto SanCovAddrs = findSanitizerCovFunctions(O); if (SanCovAddrs.empty()) Fail("__sanitizer_cov* functions not found"); for (object::SectionRef Section : O.sections()) { if (Section.isVirtual() || !Section.isText()) // llvm-objdump does the same. continue; uint64_t SectionAddr = Section.getAddress(); uint64_t SectSize = Section.getSize(); if (!SectSize) continue; StringRef BytesStr; FailIfError(Section.getContents(BytesStr)); ArrayRef Bytes(reinterpret_cast(BytesStr.data()), BytesStr.size()); for (uint64_t Index = 0, Size = 0; Index < Section.getSize(); Index += Size) { MCInst Inst; if (!DisAsm->getInstruction(Inst, Size, Bytes.slice(Index), SectionAddr + Index, nulls(), nulls())) { if (Size == 0) Size = 1; continue; } uint64_t Addr = Index + SectionAddr; // Sanitizer coverage uses the address of the next instruction - 1. uint64_t CovPoint = Addr + Size - 1; uint64_t Target; if (MIA->isCall(Inst) && MIA->evaluateBranch(Inst, SectionAddr + Index, Size, Target) && SanCovAddrs.find(Target) != SanCovAddrs.end()) Addrs->insert(CovPoint); } } } static void visitObjectFiles(const object::Archive &A, function_ref Fn) { Error Err; for (auto &C : A.children(Err)) { Expected> ChildOrErr = C.getAsBinary(); FailIfError(errorToErrorCode(ChildOrErr.takeError())); if (auto *O = dyn_cast(&*ChildOrErr.get())) Fn(*O); else FailIfError(object::object_error::invalid_file_type); } FailIfError(std::move(Err)); } static void visitObjectFiles(const std::string &FileName, function_ref Fn) { Expected> BinaryOrErr = object::createBinary(FileName); if (!BinaryOrErr) FailIfError(errorToErrorCode(BinaryOrErr.takeError())); object::Binary &Binary = *BinaryOrErr.get().getBinary(); if (object::Archive *A = dyn_cast(&Binary)) visitObjectFiles(*A, Fn); else if (object::ObjectFile *O = dyn_cast(&Binary)) Fn(*O); else FailIfError(object::object_error::invalid_file_type); } std::set findSanitizerCovFunctions(const std::string &FileName) { std::set Result; visitObjectFiles(FileName, [&](const object::ObjectFile &O) { auto Addrs = findSanitizerCovFunctions(O); Result.insert(Addrs.begin(), Addrs.end()); }); return Result; } // Locate addresses of all coverage points in a file. Coverage point // is defined as the 'address of instruction following __sanitizer_cov // call - 1'. std::set getCoveragePoints(const std::string &FileName) { std::set Result; visitObjectFiles(FileName, [&](const object::ObjectFile &O) { getObjectCoveragePoints(O, &Result); }); return Result; } static void printCovPoints(const std::string &ObjFile, raw_ostream &OS) { for (uint64_t Addr : getCoveragePoints(ObjFile)) { OS << "0x"; OS.write_hex(Addr); OS << "\n"; } } static std::string escapeHtml(const std::string &S) { std::string Result; Result.reserve(S.size()); for (char Ch : S) { switch (Ch) { case '&': Result.append("&"); break; case '\'': Result.append("'"); break; case '"': Result.append("""); break; case '<': Result.append("<"); break; case '>': Result.append(">"); break; default: Result.push_back(Ch); break; } } return Result; } // Adds leading zeroes wrapped in 'lz' style. // Leading zeroes help locate 000% coverage. static std::string formatHtmlPct(size_t Pct) { Pct = std::max(std::size_t{0}, std::min(std::size_t{100}, Pct)); std::string Num = std::to_string(Pct); std::string Zeroes(3 - Num.size(), '0'); if (!Zeroes.empty()) Zeroes = "" + Zeroes + ""; return Zeroes + Num; } static std::string anchorName(const std::string &Anchor) { llvm::MD5 Hasher; llvm::MD5::MD5Result Hash; Hasher.update(Anchor); Hasher.final(Hash); SmallString<32> HexString; llvm::MD5::stringifyResult(Hash, HexString); return HexString.str().str(); } static ErrorOr isCoverageFile(const std::string &FileName) { ErrorOr> BufOrErr = MemoryBuffer::getFile(FileName); if (!BufOrErr) { errs() << "Warning: " << BufOrErr.getError().message() << "(" << BufOrErr.getError().value() << "), filename: " << llvm::sys::path::filename(FileName) << "\n"; return BufOrErr.getError(); } std::unique_ptr Buf = std::move(BufOrErr.get()); if (Buf->getBufferSize() < 8) { return false; } const FileHeader *Header = reinterpret_cast(Buf->getBufferStart()); return Header->Magic == BinCoverageMagic; } struct CoverageStats { CoverageStats() : AllPoints(0), CovPoints(0), AllFns(0), CovFns(0) {} size_t AllPoints; size_t CovPoints; size_t AllFns; size_t CovFns; }; static raw_ostream &operator<<(raw_ostream &OS, const CoverageStats &Stats) { OS << "all-edges: " << Stats.AllPoints << "\n"; OS << "cov-edges: " << Stats.CovPoints << "\n"; OS << "all-functions: " << Stats.AllFns << "\n"; OS << "cov-functions: " << Stats.CovFns << "\n"; return OS; } class CoverageData { public: // Read single file coverage data. static ErrorOr> read(const std::string &FileName) { ErrorOr> BufOrErr = MemoryBuffer::getFile(FileName); if (!BufOrErr) return BufOrErr.getError(); std::unique_ptr Buf = std::move(BufOrErr.get()); if (Buf->getBufferSize() < 8) { errs() << "File too small (<8): " << Buf->getBufferSize(); return make_error_code(errc::illegal_byte_sequence); } const FileHeader *Header = reinterpret_cast(Buf->getBufferStart()); if (Header->Magic != BinCoverageMagic) { errs() << "Wrong magic: " << Header->Magic; return make_error_code(errc::illegal_byte_sequence); } auto Addrs = llvm::make_unique>(); switch (Header->Bitness) { case Bitness64: readInts(Buf->getBufferStart() + 8, Buf->getBufferEnd(), Addrs.get()); break; case Bitness32: readInts(Buf->getBufferStart() + 8, Buf->getBufferEnd(), Addrs.get()); break; default: errs() << "Unsupported bitness: " << Header->Bitness; return make_error_code(errc::illegal_byte_sequence); } return std::unique_ptr(new CoverageData(std::move(Addrs))); } // Merge multiple coverage data together. static std::unique_ptr merge(const std::vector> &Covs) { auto Addrs = llvm::make_unique>(); for (const auto &Cov : Covs) Addrs->insert(Cov->Addrs->begin(), Cov->Addrs->end()); return std::unique_ptr(new CoverageData(std::move(Addrs))); } // Read list of files and merges their coverage info. static ErrorOr> readAndMerge(const std::vector &FileNames) { std::vector> Covs; for (const auto &FileName : FileNames) { auto Cov = read(FileName); if (!Cov) return Cov.getError(); Covs.push_back(std::move(Cov.get())); } return merge(Covs); } // Print coverage addresses. void printAddrs(raw_ostream &OS) { for (auto Addr : *Addrs) { OS << "0x"; OS.write_hex(Addr); OS << "\n"; } } protected: explicit CoverageData(std::unique_ptr> Addrs) : Addrs(std::move(Addrs)) {} friend class CoverageDataWithObjectFile; std::unique_ptr> Addrs; }; // Coverage data translated into source code line-level information. // Fetches debug info in constructor and calculates various information per // request. class SourceCoverageData { public: enum LineStatus { // coverage information for the line is not available. // default value in maps. UNKNOWN = 0, // the line is fully covered. COVERED = 1, // the line is fully uncovered. NOT_COVERED = 2, // some points in the line a covered, some are not. MIXED = 3 }; SourceCoverageData(std::string ObjectFile, const std::set &Addrs) : AllCovPoints(getCoveragePoints(ObjectFile)) { if (!std::includes(AllCovPoints.begin(), AllCovPoints.end(), Addrs.begin(), Addrs.end())) { Fail("Coverage points in binary and .sancov file do not match."); } AllAddrInfo = getAddrInfo(ObjectFile, AllCovPoints, true); CovAddrInfo = getAddrInfo(ObjectFile, Addrs, true); } // Compute number of coverage points hit/total in a file. // file_name -> std::map> computeFileCoverage() { std::map> FileCoverage; auto AllCovPointsByFile = group_by(AllAddrInfo, [](const AddrInfo &AI) { return AI.FileName; }); auto CovPointsByFile = group_by(CovAddrInfo, [](const AddrInfo &AI) { return AI.FileName; }); for (const auto &P : AllCovPointsByFile) { const std::string &FileName = P.first; FileCoverage[FileName] = std::make_pair(CovPointsByFile[FileName].size(), AllCovPointsByFile[FileName].size()); } return FileCoverage; } // line_number -> line_status. typedef std::map LineStatusMap; // file_name -> LineStatusMap typedef std::map FileLineStatusMap; // fills in the {file_name -> {line_no -> status}} map. FileLineStatusMap computeLineStatusMap() { FileLineStatusMap StatusMap; auto AllLocs = group_by(AllAddrInfo, [](const AddrInfo &AI) { return FileLoc{AI.FileName, AI.Line}; }); auto CovLocs = group_by(CovAddrInfo, [](const AddrInfo &AI) { return FileLoc{AI.FileName, AI.Line}; }); for (const auto &P : AllLocs) { const FileLoc &Loc = P.first; auto I = CovLocs.find(Loc); if (I == CovLocs.end()) { StatusMap[Loc.FileName][Loc.Line] = NOT_COVERED; } else { StatusMap[Loc.FileName][Loc.Line] = (I->second.size() == P.second.size()) ? COVERED : MIXED; } } return StatusMap; } std::set computeAllFunctions() const { std::set Fns; for (const auto &AI : AllAddrInfo) { Fns.insert(FileFn{AI.FileName, AI.FunctionName}); } return Fns; } std::set computeCoveredFunctions() const { std::set Fns; auto CovFns = group_by(CovAddrInfo, [](const AddrInfo &AI) { return FileFn{AI.FileName, AI.FunctionName}; }); for (const auto &P : CovFns) { Fns.insert(P.first); } return Fns; } std::set computeNotCoveredFunctions() const { std::set Fns; auto AllFns = group_by(AllAddrInfo, [](const AddrInfo &AI) { return FileFn{AI.FileName, AI.FunctionName}; }); auto CovFns = group_by(CovAddrInfo, [](const AddrInfo &AI) { return FileFn{AI.FileName, AI.FunctionName}; }); for (const auto &P : AllFns) { if (CovFns.find(P.first) == CovFns.end()) { Fns.insert(P.first); } } return Fns; } // Compute % coverage for each function. std::map computeFunctionsCoverage() const { std::map FnCoverage; auto AllFns = group_by(AllAddrInfo, [](const AddrInfo &AI) { return FileFn{AI.FileName, AI.FunctionName}; }); auto CovFns = group_by(CovAddrInfo, [](const AddrInfo &AI) { return FileFn{AI.FileName, AI.FunctionName}; }); for (const auto &P : AllFns) { FileFn F = P.first; FnCoverage[F] = CovFns[F].size() * 100 / P.second.size(); } return FnCoverage; } typedef std::map> FunctionLocs; // finds first line number in a file for each function. FunctionLocs resolveFunctions(const std::set &Fns) const { std::vector FnAddrs; for (const auto &AI : AllAddrInfo) { if (Fns.find(FileFn{AI.FileName, AI.FunctionName}) != Fns.end()) FnAddrs.push_back(AI); } auto GroupedAddrs = group_by(FnAddrs, [](const AddrInfo &AI) { return FnLoc{FileLoc{AI.FileName, AI.Line}, AI.FunctionName}; }); FunctionLocs Result; std::string LastFileName; std::set ProcessedFunctions; for (const auto &P : GroupedAddrs) { const FnLoc &Loc = P.first; std::string FileName = Loc.Loc.FileName; std::string FunctionName = Loc.FunctionName; if (LastFileName != FileName) ProcessedFunctions.clear(); LastFileName = FileName; if (!ProcessedFunctions.insert(FunctionName).second) continue; auto FLoc = FileLoc{FileName, Loc.Loc.Line}; Result[FLoc].insert(FunctionName); } return Result; } std::set files() const { std::set Files; for (const auto &AI : AllAddrInfo) { Files.insert(AI.FileName); } return Files; } void collectStats(CoverageStats *Stats) const { Stats->AllPoints += AllCovPoints.size(); Stats->AllFns += computeAllFunctions().size(); Stats->CovFns += computeCoveredFunctions().size(); } private: const std::set AllCovPoints; std::vector AllAddrInfo; std::vector CovAddrInfo; }; static void printFunctionLocs(const SourceCoverageData::FunctionLocs &FnLocs, raw_ostream &OS) { for (const auto &Fns : FnLocs) { for (const auto &Fn : Fns.second) { OS << stripPathPrefix(Fns.first.FileName) << ":" << Fns.first.Line << " " << Fn << "\n"; } } } // Holder for coverage data + filename of corresponding object file. class CoverageDataWithObjectFile : public CoverageData { public: static ErrorOr> readAndMerge(const std::string &ObjectFile, const std::vector &FileNames) { auto MergedDataOrError = CoverageData::readAndMerge(FileNames); if (!MergedDataOrError) return MergedDataOrError.getError(); return std::unique_ptr( new CoverageDataWithObjectFile(ObjectFile, std::move(MergedDataOrError.get()))); } std::string object_file() const { return ObjectFile; } // Print list of covered functions. // Line format: : void printCoveredFunctions(raw_ostream &OS) const { SourceCoverageData SCovData(ObjectFile, *Addrs); auto CoveredFns = SCovData.computeCoveredFunctions(); printFunctionLocs(SCovData.resolveFunctions(CoveredFns), OS); } // Print list of not covered functions. // Line format: : void printNotCoveredFunctions(raw_ostream &OS) const { SourceCoverageData SCovData(ObjectFile, *Addrs); auto NotCoveredFns = SCovData.computeNotCoveredFunctions(); printFunctionLocs(SCovData.resolveFunctions(NotCoveredFns), OS); } void printReport(raw_ostream &OS) const { SourceCoverageData SCovData(ObjectFile, *Addrs); auto LineStatusMap = SCovData.computeLineStatusMap(); std::set AllFns = SCovData.computeAllFunctions(); // file_loc -> set[function_name] auto AllFnsByLoc = SCovData.resolveFunctions(AllFns); auto FileCoverage = SCovData.computeFileCoverage(); auto FnCoverage = SCovData.computeFunctionsCoverage(); auto FnCoverageByFile = group_by(FnCoverage, [](const std::pair &FileFn) { return FileFn.first.FileName; }); // TOC size_t NotCoveredFilesCount = 0; std::set Files = SCovData.files(); // Covered Files. OS << "
Touched Files\n"; OS << "\n"; OS << ""; OS << "\n"; for (const auto &FileName : Files) { std::pair FC = FileCoverage[FileName]; if (FC.first == 0) { NotCoveredFilesCount++; continue; } size_t CovPct = FC.second == 0 ? 100 : 100 * FC.first / FC.second; OS << "" << "" << "\n"; } OS << "
FileCoverage %Hit (Total) Fns
" << stripPathPrefix(FileName) << "" << formatHtmlPct(CovPct) << "%" << FC.first << " (" << FC.second << ")" << "
\n"; OS << "
\n"; // Not covered files. if (NotCoveredFilesCount) { OS << "
Not Touched Files\n"; OS << "\n"; for (const auto &FileName : Files) { std::pair FC = FileCoverage[FileName]; if (FC.first == 0) OS << "\n"; } OS << "
" << stripPathPrefix(FileName) << "
\n"; OS << "
\n"; } else { OS << "

Congratulations! All source files are touched.

\n"; } // Source for (const auto &FileName : Files) { std::pair FC = FileCoverage[FileName]; if (FC.first == 0) continue; OS << "\n"; OS << "

" << stripPathPrefix(FileName) << "

\n"; OS << "
Function Coverage"; OS << "
\n"; auto &FileFnCoverage = FnCoverageByFile[FileName]; for (const auto &P : FileFnCoverage) { std::string FunctionName = P.first.FunctionName; OS << "
"; OS << "" << formatHtmlPct(P.second) << "% "; OS << ""; OS << escapeHtml(FunctionName) << ""; OS << "
\n"; } OS << "
\n"; ErrorOr> BufOrErr = MemoryBuffer::getFile(FileName); if (!BufOrErr) { OS << "Error reading file: " << FileName << " : " << BufOrErr.getError().message() << "(" << BufOrErr.getError().value() << ")\n"; continue; } OS << "
\n";
      const auto &LineStatuses = LineStatusMap[FileName];
      for (line_iterator I = line_iterator(*BufOrErr.get(), false);
           !I.is_at_eof(); ++I) {
        uint32_t Line = I.line_number();
        { // generate anchors (if any);
          FileLoc Loc = FileLoc{FileName, Line};
          auto It = AllFnsByLoc.find(Loc);
          if (It != AllFnsByLoc.end()) {
            for (const std::string &Fn : It->second) {
              OS << "";
            };
          }
        }

        OS << "second
                                                  : SourceCoverageData::UNKNOWN;
        switch (Status) {
        case SourceCoverageData::UNKNOWN:
          OS << "class=unknown";
          break;
        case SourceCoverageData::COVERED:
          OS << "class=covered";
          break;
        case SourceCoverageData::NOT_COVERED:
          OS << "class=notcovered";
          break;
        case SourceCoverageData::MIXED:
          OS << "class=mixed";
          break;
        }
        OS << ">";
        OS << escapeHtml(*I) << "\n";
      }
      OS << "
\n"; } } void collectStats(CoverageStats *Stats) const { Stats->CovPoints += Addrs->size(); SourceCoverageData SCovData(ObjectFile, *Addrs); SCovData.collectStats(Stats); } private: CoverageDataWithObjectFile(std::string ObjectFile, std::unique_ptr Coverage) : CoverageData(std::move(Coverage->Addrs)), ObjectFile(std::move(ObjectFile)) {} const std::string ObjectFile; }; // Multiple coverage files data organized by object file. class CoverageDataSet { public: static ErrorOr> readCmdArguments(std::vector FileNames) { // Short name => file name. std::map ObjFiles; std::string FirstObjFile; std::set CovFiles; // Partition input values into coverage/object files. for (const auto &FileName : FileNames) { auto ErrorOrIsCoverage = isCoverageFile(FileName); if (!ErrorOrIsCoverage) continue; if (ErrorOrIsCoverage.get()) { CovFiles.insert(FileName); } else { auto ShortFileName = llvm::sys::path::filename(FileName); if (ObjFiles.find(ShortFileName) != ObjFiles.end()) { Fail("Duplicate binary file with a short name: " + ShortFileName); } ObjFiles[ShortFileName] = FileName; if (FirstObjFile.empty()) FirstObjFile = FileName; } } Regex SancovRegex("(.*)\\.[0-9]+\\.sancov"); SmallVector Components; // Object file => list of corresponding coverage file names. auto CoverageByObjFile = group_by(CovFiles, [&](std::string FileName) { auto ShortFileName = llvm::sys::path::filename(FileName); auto Ok = SancovRegex.match(ShortFileName, &Components); if (!Ok) { Fail("Can't match coverage file name against " "..sancov pattern: " + FileName); } auto Iter = ObjFiles.find(Components[1]); if (Iter == ObjFiles.end()) { Fail("Object file for coverage not found: " + FileName); } return Iter->second; }); // Read coverage. std::vector> MergedCoverage; for (const auto &Pair : CoverageByObjFile) { if (findSanitizerCovFunctions(Pair.first).empty()) { for (const auto &FileName : Pair.second) { CovFiles.erase(FileName); } errs() << "Ignoring " << Pair.first << " and its coverage because __sanitizer_cov* functions were not " "found.\n"; continue; } auto DataOrError = CoverageDataWithObjectFile::readAndMerge(Pair.first, Pair.second); FailIfError(DataOrError); MergedCoverage.push_back(std::move(DataOrError.get())); } return std::unique_ptr( new CoverageDataSet(FirstObjFile, &MergedCoverage, CovFiles)); } void printCoveredFunctions(raw_ostream &OS) const { for (const auto &Cov : Coverage) { Cov->printCoveredFunctions(OS); } } void printNotCoveredFunctions(raw_ostream &OS) const { for (const auto &Cov : Coverage) { Cov->printNotCoveredFunctions(OS); } } void printStats(raw_ostream &OS) const { CoverageStats Stats; for (const auto &Cov : Coverage) { Cov->collectStats(&Stats); } OS << Stats; } void printReport(raw_ostream &OS) const { auto Title = (llvm::sys::path::filename(MainObjFile) + " Coverage Report").str(); OS << "\n"; OS << "\n"; // Stylesheet OS << "\n"; OS << "" << Title << "\n"; OS << "\n"; OS << "\n"; // Title OS << "

" << Title << "

\n"; // Modules TOC. if (Coverage.size() > 1) { for (const auto &CovData : Coverage) { OS << "
  • object_file()) << "\">" << llvm::sys::path::filename(CovData->object_file()) << "
  • \n"; } } for (const auto &CovData : Coverage) { if (Coverage.size() > 1) { OS << "

    " << llvm::sys::path::filename(CovData->object_file()) << "

    \n"; } OS << "object_file()) << "\">\n"; CovData->printReport(OS); } // About OS << "
    About\n"; OS << "Coverage files:
      "; for (const auto &InputFile : CoverageFiles) { llvm::sys::fs::file_status Status; llvm::sys::fs::status(InputFile, Status); OS << "
    • " << stripPathPrefix(InputFile) << " (" << Status.getLastModificationTime().str() << ")
    • \n"; } OS << "
    \n"; OS << "\n"; OS << "\n"; } bool empty() const { return Coverage.empty(); } private: explicit CoverageDataSet( const std::string &MainObjFile, std::vector> *Data, const std::set &CoverageFiles) : MainObjFile(MainObjFile), CoverageFiles(CoverageFiles) { Data->swap(this->Coverage); } const std::string MainObjFile; std::vector> Coverage; const std::set CoverageFiles; }; } // namespace int main(int argc, char **argv) { // Print stack trace if we signal out. sys::PrintStackTraceOnErrorSignal(argv[0]); PrettyStackTraceProgram X(argc, argv); llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllDisassemblers(); cl::ParseCommandLineOptions(argc, argv, "Sanitizer Coverage Processing Tool"); // -print doesn't need object files. if (Action == PrintAction) { auto CovData = CoverageData::readAndMerge(ClInputFiles); FailIfError(CovData); CovData.get()->printAddrs(outs()); return 0; } else if (Action == PrintCovPointsAction) { // -print-coverage-points doesn't need coverage files. for (const std::string &ObjFile : ClInputFiles) { printCovPoints(ObjFile, outs()); } return 0; } auto CovDataSet = CoverageDataSet::readCmdArguments(ClInputFiles); FailIfError(CovDataSet); if (CovDataSet.get()->empty()) { Fail("No coverage files specified."); } switch (Action) { case CoveredFunctionsAction: { CovDataSet.get()->printCoveredFunctions(outs()); return 0; } case NotCoveredFunctionsAction: { CovDataSet.get()->printNotCoveredFunctions(outs()); return 0; } case HtmlReportAction: { CovDataSet.get()->printReport(outs()); return 0; } case StatsAction: { CovDataSet.get()->printStats(outs()); return 0; } case PrintAction: case PrintCovPointsAction: llvm_unreachable("unsupported action"); } }