Index: llvm/trunk/test/tools/sancov/html-report.test =================================================================== --- llvm/trunk/test/tools/sancov/html-report.test +++ llvm/trunk/test/tools/sancov/html-report.test @@ -0,0 +1,6 @@ +REQUIRES: x86_64-linux +RUN: sancov -obj %p/Inputs/test-linux_x86_64 -html-report %p/Inputs/test-linux_x86_64.sancov | FileCheck %s + +// It's very difficult to test html report. Do basic smoke check. +CHECK: {{}} + Index: llvm/trunk/tools/sancov/sancov.cc =================================================================== --- llvm/trunk/tools/sancov/sancov.cc +++ llvm/trunk/tools/sancov/sancov.cc @@ -55,7 +55,8 @@ enum ActionType { PrintAction, CoveredFunctionsAction, - NotCoveredFunctionsAction + NotCoveredFunctionsAction, + HtmlReportAction }; cl::opt Action( @@ -65,6 +66,8 @@ "Print all covered funcions."), clEnumValN(NotCoveredFunctionsAction, "not-covered-functions", "Print all not covered funcions."), + clEnumValN(HtmlReportAction, "html-report", + "Print HTML coverage report."), clEnumValEnd)); static cl::list ClInputFiles(cl::Positional, cl::OneOrMore, @@ -167,19 +170,24 @@ 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)); +} + // Compute [FileLoc -> FunctionName] map for given addresses. static std::map computeFunctionsMap(const std::set &Addrs) { std::map Fns; - symbolize::LLVMSymbolizer::Options SymbolizerOptions; - SymbolizerOptions.Demangle = ClDemangle; - SymbolizerOptions.UseSymbolTable = true; - symbolize::LLVMSymbolizer Symbolizer(SymbolizerOptions); + auto Symbolizer(createSymbolizer()); // Fill in Fns map. for (auto Addr : Addrs) { - auto InliningInfo = Symbolizer.symbolizeInlinedCode(ClBinaryName, Addr); + auto InliningInfo = Symbolizer->symbolizeInlinedCode(ClBinaryName, Addr); FailIfError(InliningInfo); for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) { auto FrameInfo = InliningInfo->getFrame(I); @@ -398,6 +406,56 @@ } } +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; +} + +// Computes a map file_name->{line_number} +static std::map> +getFileLines(const std::set &Addrs) { + std::map> FileLines; + + auto Symbolizer(createSymbolizer()); + + // Fill in FileLines map. + for (auto Addr : Addrs) { + auto InliningInfo = Symbolizer->symbolizeInlinedCode(ClBinaryName, Addr); + FailIfError(InliningInfo); + for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) { + auto FrameInfo = InliningInfo->getFrame(I); + SmallString<256> FileName(FrameInfo.FileName); + sys::path::remove_dots(FileName, /* remove_dot_dot */ true); + FileLines[FileName.str()].insert(FrameInfo.Line); + } + } + + return FileLines; +} + class CoverageData { public: // Read single file coverage data. @@ -471,6 +529,82 @@ } } + void printReport(raw_ostream &OS) { + // file_name -> set of covered lines; + std::map> CoveredFileLines = + getFileLines(*Addrs); + std::map> CoveragePoints = + getFileLines(getCoveragePoints(ClBinaryName)); + + std::string Title = stripPathPrefix(ClBinaryName) + " Coverage Report"; + + OS << "\n"; + OS << "\n"; + + // Stylesheet + OS << "\n"; + OS << "" << Title << "\n"; + OS << "\n"; + OS << "\n"; + + // Title + OS << "

" << Title << "

\n"; + OS << "

Coverage files: "; + for (auto InputFile : ClInputFiles) { + llvm::sys::fs::file_status Status; + llvm::sys::fs::status(InputFile, Status); + OS << stripPathPrefix(InputFile) << " (" + << Status.getLastModificationTime().str() << ")"; + } + OS << "

\n"; + + // TOC + OS << "
\n"; + + // Source + for (auto It : CoveredFileLines) { + auto FileName = It.first; + auto Lines = It.second; + auto CovLines = CoveragePoints[FileName]; + + OS << " \n"; + OS << "

" << stripPathPrefix(FileName) << "

\n"; + ErrorOr> BufOrErr = + MemoryBuffer::getFile(FileName); + if (!BufOrErr) { + OS << "Error reading file: " << FileName << " : " + << BufOrErr.getError().message() << "(" + << BufOrErr.getError().value() << ")\n"; + continue; + } + + OS << "
\n";
+      for (line_iterator I = line_iterator(*BufOrErr.get(), false);
+           !I.is_at_eof(); ++I) {
+        OS << "";
+        OS << escapeHtml(*I) << "\n";
+      }
+      OS << "
\n"; + } + + OS << "\n"; + OS << "\n"; + } + // Print list of covered functions. // Line format: : void printCoveredFunctions(raw_ostream &OS) { @@ -527,6 +661,10 @@ CovData.get()->printNotCoveredFunctions(outs()); return 0; } + case HtmlReportAction: { + CovData.get()->printReport(outs()); + return 0; + } } llvm_unreachable("unsupported action");