github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/cover/file.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package cover
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"html"
    10  	"strings"
    11  
    12  	"github.com/google/syzkaller/pkg/coveragedb"
    13  	"github.com/google/syzkaller/pkg/covermerger"
    14  )
    15  
    16  type lineRender func(string, int, *covermerger.MergeResult, *CoverageRenderConfig) string
    17  
    18  type CoverageRenderConfig struct {
    19  	RendLine                  lineRender
    20  	ShowLineCoverage          bool
    21  	ShowLineNumbers           bool
    22  	ShowLineSourceExplanation bool
    23  }
    24  
    25  func DefaultTextRenderConfig() *CoverageRenderConfig {
    26  	return &CoverageRenderConfig{
    27  		RendLine:                  RendTextLine,
    28  		ShowLineCoverage:          true,
    29  		ShowLineNumbers:           true,
    30  		ShowLineSourceExplanation: false,
    31  	}
    32  }
    33  
    34  func DefaultHTMLRenderConfig() *CoverageRenderConfig {
    35  	return &CoverageRenderConfig{
    36  		RendLine:                  RendHTMLLine,
    37  		ShowLineCoverage:          true,
    38  		ShowLineNumbers:           true,
    39  		ShowLineSourceExplanation: false,
    40  	}
    41  }
    42  
    43  func RendFileCoverage(repo, forCommit, filePath string, fileProvider covermerger.FileVersProvider,
    44  	mr *covermerger.MergeResult, renderConfig *CoverageRenderConfig) (string, error) {
    45  	repoCommit := covermerger.RepoCommit{Repo: repo, Commit: forCommit}
    46  	files, err := fileProvider.GetFileVersions(filePath, repoCommit)
    47  	if err != nil {
    48  		return "", fmt.Errorf("failed to GetFileVersions: %w", err)
    49  	}
    50  	return rendResult(files[repoCommit], mr, renderConfig), nil
    51  }
    52  
    53  // nolint:revive
    54  func GetMergeResult(c context.Context, ns, repo, forCommit, sourceCommit, filePath string,
    55  	proxy covermerger.FuncProxyURI, tp coveragedb.TimePeriod) (*covermerger.MergeResult, error) {
    56  	config := &covermerger.Config{
    57  		Jobs: 1,
    58  		Base: covermerger.RepoCommit{
    59  			Repo:   repo,
    60  			Commit: forCommit,
    61  		},
    62  		FileVersProvider: covermerger.MakeWebGit(proxy),
    63  	}
    64  
    65  	fromDate, toDate := tp.DatesFromTo()
    66  	csvReader, err := covermerger.InitNsRecords(c, ns, filePath, sourceCommit, fromDate, toDate)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("failed to covermerger.InitNsRecords: %w", err)
    69  	}
    70  	defer csvReader.Close()
    71  
    72  	ch := make(chan *covermerger.FileMergeResult, 1)
    73  	if err := covermerger.MergeCSVData(c, config, csvReader, ch); err != nil {
    74  		return nil, fmt.Errorf("error merging coverage: %w", err)
    75  	}
    76  
    77  	var mr *covermerger.MergeResult
    78  	select {
    79  	case fmr := <-ch:
    80  		if fmr != nil {
    81  			mr = fmr.MergeResult
    82  		}
    83  	default:
    84  	}
    85  
    86  	if mr != nil {
    87  		return nil, fmt.Errorf("no merge result for file %s", filePath)
    88  	}
    89  	return mr, nil
    90  }
    91  
    92  func rendResult(content string, coverage *covermerger.MergeResult, renderConfig *CoverageRenderConfig) string {
    93  	if coverage == nil {
    94  		coverage = &covermerger.MergeResult{
    95  			HitCounts:   map[int]int64{},
    96  			LineDetails: map[int][]*covermerger.FileRecord{},
    97  		}
    98  	}
    99  	srcLines := strings.Split(content, "\n")
   100  	var resLines []string
   101  	for i, srcLine := range srcLines {
   102  		resLines = append(resLines, renderConfig.RendLine(srcLine, i+1, coverage, renderConfig))
   103  	}
   104  	return strings.Join(resLines, "\n")
   105  }
   106  
   107  func RendTextLine(code string, line int, coverage *covermerger.MergeResult, config *CoverageRenderConfig) string {
   108  	res := ""
   109  	if config.ShowLineSourceExplanation {
   110  		explanation := ""
   111  		lineDetails, exist := coverage.LineDetails[line]
   112  		if exist {
   113  			explanation = fmt.Sprintf("(%d)%s ", len(lineDetails), mainSignalSource(lineDetails))
   114  		}
   115  		res += fmt.Sprintf("%50s", explanation)
   116  	}
   117  	covered, instrumented := coverage.HitCounts[line]
   118  	if config.ShowLineCoverage {
   119  		covStr := fmt.Sprintf("%6d", covered)
   120  		if !instrumented {
   121  			covStr = strings.Repeat(" ", 6)
   122  		}
   123  		res += fmt.Sprintf("%s ", covStr)
   124  	}
   125  	if config.ShowLineNumbers {
   126  		res += fmt.Sprintf("%6d ", line)
   127  	}
   128  	res += code
   129  	return res
   130  }
   131  
   132  func RendHTMLLine(code string, line int, coverage *covermerger.MergeResult, config *CoverageRenderConfig) string {
   133  	textLine := RendTextLine(code, line, coverage, config)
   134  	return `<pre style="margin: 0">` + html.EscapeString(textLine) + "</pre>"
   135  }
   136  
   137  func mainSignalSource(sources []*covermerger.FileRecord) string {
   138  	res := ""
   139  	prevMax := -1
   140  	for _, source := range sources {
   141  		if source.HitCount > prevMax {
   142  			prevMax = source.HitCount
   143  			res = fmt.Sprintf("%s:%d", source.Commit, source.StartLine)
   144  		}
   145  	}
   146  	return res
   147  }