diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2020-12-12 13:52:51 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2020-12-13 18:56:36 +0100 |
| commit | e5cf022016fc2e488a9a7c20e8736e6c47cc1593 (patch) | |
| tree | d6e2afb8a5a11ef4a7a317a4d004803e2a511077 /pkg/cover/html.go | |
| parent | 3a7cbae43a7c07056c6f5219bc68613087287333 (diff) | |
pkg/cover: support compiler frontend coverage
Currently we only support compiler middle/backenend coverage
where we can map coverage points to source line.
Support better frontend coverage where coverage points map
to source code ranges start line:col - end line:col.
Diffstat (limited to 'pkg/cover/html.go')
| -rw-r--r-- | pkg/cover/html.go | 137 |
1 files changed, 111 insertions, 26 deletions
diff --git a/pkg/cover/html.go b/pkg/cover/html.go index fb4f2ccf0..3b4d5ec43 100644 --- a/pkg/cover/html.go +++ b/pkg/cover/html.go @@ -16,6 +16,8 @@ import ( "sort" "strconv" "strings" + + "github.com/google/syzkaller/pkg/cover/backend" ) func (rg *ReportGenerator) DoHTML(w io.Writer, progs []Prog) error { @@ -59,12 +61,12 @@ func (rg *ReportGenerator) DoHTML(w io.Writer, progs []Prog) error { templateBase: templateBase{ Path: path, Name: fname, - Total: file.pcs, - Covered: file.covered, + Total: file.totalPCs, + Covered: file.coveredPCs, }, } pos.Files = append(pos.Files, f) - if file.covered == 0 { + if file.coveredPCs == 0 { continue } addFunctionCoverage(file, d) @@ -138,35 +140,119 @@ func (rg *ReportGenerator) DoCSV(w io.Writer, progs []Prog) error { func fileContents(file *file, lines [][]byte) string { var buf bytes.Buffer + lineCover := perLineCoverage(file.covered, file.uncovered) + htmlReplacer := strings.NewReplacer(">", ">", "<", "<", "&", "&", "\t", " ") for i, ln := range lines { - cov, ok := file.lines[i+1] - prog, class, count := "", "", " " - if ok { - if len(cov.count) != 0 { - if cov.prog != -1 { - prog = fmt.Sprintf("onclick='onProgClick(%v)'", cov.prog) - } - count = fmt.Sprintf("% 5v", len(cov.count)) + prog, count := "", " " + if line := file.lines[i+1]; len(line.progCount) != 0 { + prog = fmt.Sprintf("onclick='onProgClick(%v)'", line.progIndex) + count = fmt.Sprintf("% 5v", len(line.progCount)) + } + buf.WriteString(fmt.Sprintf("<span class='count' %v>%v</span> ", prog, count)) + + start := 0 + cover := append(lineCover[i+1], lineCoverChunk{End: backend.LineEnd}) + for _, cov := range cover { + end := cov.End - 1 + if end > len(ln) { + end = len(ln) + } + if end == start { + continue + } + chunk := htmlReplacer.Replace(string(ln[start:end])) + start = end + class := "" + if cov.Covered && cov.Uncovered { + class = "both" + } else if cov.Covered { class = "covered" - if cov.uncovered { - class = "both" - } - } else { + } else if cov.Uncovered { class = "uncovered" + } else { + buf.WriteString(chunk) + continue } + buf.WriteString(fmt.Sprintf("<span class='%v'>%v</span>", class, chunk)) } - buf.WriteString(fmt.Sprintf("<span class='count' %v>%v</span>", prog, count)) - if class == "" { - buf.WriteByte(' ') - buf.Write(ln) - buf.WriteByte('\n') + buf.WriteByte('\n') + } + return buf.String() +} + +type lineCoverChunk struct { + End int + Covered bool + Uncovered bool +} + +func perLineCoverage(covered, uncovered []backend.Range) map[int][]lineCoverChunk { + lines := make(map[int][]lineCoverChunk) + for _, r := range covered { + mergeRange(lines, r, true) + } + for _, r := range uncovered { + mergeRange(lines, r, false) + } + return lines +} + +func mergeRange(lines map[int][]lineCoverChunk, r backend.Range, covered bool) { + // Don't panic on broken debug info, it is frequently broken. + if r.EndLine < r.StartLine { + r.EndLine = r.StartLine + } + if r.EndLine == r.StartLine && r.EndCol <= r.StartCol { + r.EndCol = backend.LineEnd + } + for line := r.StartLine; line <= r.EndLine; line++ { + start := 0 + if line == r.StartLine { + start = r.StartCol + } + end := backend.LineEnd + if line == r.EndLine { + end = r.EndCol + } + ln := lines[line] + if ln == nil { + ln = append(ln, lineCoverChunk{End: backend.LineEnd}) + } + lines[line] = mergeLine(ln, start, end, covered) + } +} + +func mergeLine(chunks []lineCoverChunk, start, end int, covered bool) []lineCoverChunk { + var res []lineCoverChunk + chunkStart := 0 + for _, chunk := range chunks { + if chunkStart >= end || chunk.End <= start { + res = append(res, chunk) + } else if covered && chunk.Covered || !covered && chunk.Uncovered { + res = append(res, chunk) + } else if chunkStart >= start && chunk.End <= end { + if covered { + chunk.Covered = true + } else { + chunk.Uncovered = true + } + res = append(res, chunk) } else { - buf.WriteString(fmt.Sprintf("<span class='%v'> ", class)) - buf.Write(ln) - buf.WriteString("</span>\n") + if chunkStart < start { + res = append(res, lineCoverChunk{start, chunk.Covered, chunk.Uncovered}) + } + mid := end + if mid > chunk.End { + mid = chunk.End + } + res = append(res, lineCoverChunk{mid, chunk.Covered || covered, chunk.Uncovered || !covered}) + if chunk.End > end { + res = append(res, lineCoverChunk{chunk.End, chunk.Covered, chunk.Uncovered}) + } } + chunkStart = chunk.End } - return buf.String() + return res } func addFunctionCoverage(file *file, data *templateData) { @@ -227,14 +313,13 @@ func parseFile(fn string) ([][]byte, error) { if err != nil { return nil, err } - htmlReplacer := strings.NewReplacer(">", ">", "<", "<", "&", "&", "\t", " ") var lines [][]byte for { idx := bytes.IndexByte(data, '\n') if idx == -1 { break } - lines = append(lines, []byte(htmlReplacer.Replace(string(data[:idx])))) + lines = append(lines, data[:idx]) data = data[idx+1:] } if len(data) != 0 { |
