github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/public_json_api.go (about)

     1  // Copyright 2021 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 main
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"time"
    12  
    13  	"cloud.google.com/go/civil"
    14  	"github.com/google/syzkaller/dashboard/api"
    15  	"github.com/google/syzkaller/pkg/cover"
    16  	"github.com/google/syzkaller/pkg/coveragedb"
    17  )
    18  
    19  func getExtAPIDescrForBugPage(bugPage *uiBugPage) *api.Bug {
    20  	return &api.Bug{
    21  		Version: api.Version,
    22  		Title:   bugPage.Bug.Title,
    23  		ID:      bugPage.Bug.ID,
    24  		Discussions: func() []string {
    25  			if bugPage.Bug.ExternalLink == "" {
    26  				return nil
    27  			}
    28  			return []string{bugPage.Bug.ExternalLink}
    29  		}(),
    30  		FixCommits: getBugFixCommits(bugPage.Bug),
    31  		CauseCommit: func() *api.Commit {
    32  			if bugPage.BisectCause == nil || bugPage.BisectCause.Commit == nil {
    33  				return nil
    34  			}
    35  			bisectCause := bugPage.BisectCause
    36  			return &api.Commit{
    37  				Title:  bisectCause.Commit.Title,
    38  				Link:   bisectCause.Commit.Link,
    39  				Hash:   bisectCause.Commit.Hash,
    40  				Repo:   bisectCause.KernelRepo,
    41  				Branch: bisectCause.KernelBranch}
    42  		}(),
    43  		Crashes: func() []api.Crash {
    44  			var res []api.Crash
    45  			for _, crash := range bugPage.Crashes.Crashes {
    46  				res = append(res, api.Crash{
    47  					Title:              crash.Title,
    48  					SyzReproducerLink:  crash.ReproSyzLink,
    49  					CReproducerLink:    crash.ReproCLink,
    50  					KernelConfigLink:   crash.KernelConfigLink,
    51  					KernelSourceGit:    crash.KernelCommitLink,
    52  					KernelSourceCommit: crash.KernelCommit,
    53  					SyzkallerGit:       crash.SyzkallerCommitLink,
    54  					SyzkallerCommit:    crash.SyzkallerCommit,
    55  					// TODO: add the CompilerDescription
    56  					// TODO: add the Architecture
    57  					CrashReportLink: crash.ReportLink,
    58  				})
    59  			}
    60  			return res
    61  		}(),
    62  	}
    63  }
    64  
    65  func getBugFixCommits(bug *uiBug) []api.Commit {
    66  	var res []api.Commit
    67  	for _, commit := range bug.Commits {
    68  		res = append(res, api.Commit{
    69  			Title:  commit.Title,
    70  			Link:   commit.Link,
    71  			Hash:   commit.Hash,
    72  			Repo:   commit.Repo,
    73  			Branch: commit.Branch,
    74  		})
    75  	}
    76  	return res
    77  }
    78  
    79  func getExtAPIDescrForBugGroups(bugGroups []*uiBugGroup) *api.BugGroup {
    80  	var bugs []api.BugSummary
    81  	for _, group := range bugGroups {
    82  		for _, bug := range group.Bugs {
    83  			bugs = append(bugs, api.BugSummary{
    84  				Title:      bug.Title,
    85  				Link:       bug.Link,
    86  				FixCommits: getBugFixCommits(bug),
    87  			})
    88  		}
    89  	}
    90  	return &api.BugGroup{
    91  		Version: api.Version,
    92  		Bugs:    bugs,
    93  	}
    94  }
    95  
    96  type publicKernelTree struct {
    97  	Repo   string `json:"repo"`
    98  	Branch string `json:"branch"`
    99  }
   100  
   101  type publicBackportBug struct {
   102  	Namespace       string `json:"namespace"`
   103  	Title           string `json:"title"`
   104  	ConfigLink      string `json:"config_link"`
   105  	SyzReproLink    string `json:"syz_repro_link"`
   106  	CReproLink      string `json:"c_repro_link"`
   107  	SyzkallerCommit string `json:"syzkaller_commit"`
   108  }
   109  
   110  type publicMissingBackport struct {
   111  	From   publicKernelTree    `json:"from"`
   112  	To     publicKernelTree    `json:"to"`
   113  	Commit string              `json:"commit"`
   114  	Title  string              `json:"title"`
   115  	Bugs   []publicBackportBug `json:"bugs"`
   116  }
   117  
   118  type publicAPIBackports struct {
   119  	Version int                     `json:"version"`
   120  	List    []publicMissingBackport `json:"list"`
   121  }
   122  
   123  func getExtAPIDescrForBackports(groups []*uiBackportGroup) *publicAPIBackports {
   124  	return &publicAPIBackports{
   125  		Version: api.Version,
   126  		List: func() []publicMissingBackport {
   127  			var res []publicMissingBackport
   128  			for _, group := range groups {
   129  				from := publicKernelTree{
   130  					Repo:   group.From.URL,
   131  					Branch: group.From.Branch,
   132  				}
   133  				to := publicKernelTree{
   134  					Repo:   group.To.URL,
   135  					Branch: group.To.Branch,
   136  				}
   137  				for _, backport := range group.List {
   138  					record := publicMissingBackport{
   139  						From:   from,
   140  						To:     to,
   141  						Commit: backport.Commit.Hash,
   142  						Title:  backport.Commit.Title,
   143  					}
   144  					for ns, bugs := range backport.Bugs {
   145  						for _, info := range bugs {
   146  							record.Bugs = append(record.Bugs, publicBackportBug{
   147  								Namespace:       ns,
   148  								Title:           info.Bug.Title,
   149  								ConfigLink:      info.Crash.KernelConfigLink,
   150  								CReproLink:      info.Crash.ReproCLink,
   151  								SyzReproLink:    info.Crash.ReproSyzLink,
   152  								SyzkallerCommit: info.Crash.SyzkallerCommit,
   153  							})
   154  						}
   155  					}
   156  					res = append(res, record)
   157  				}
   158  			}
   159  			return res
   160  		}(),
   161  	}
   162  }
   163  
   164  func GetJSONDescrFor(page interface{}) ([]byte, error) {
   165  	var res interface{}
   166  	switch i := page.(type) {
   167  	case *uiBugPage:
   168  		res = getExtAPIDescrForBugPage(i)
   169  	case *uiTerminalPage:
   170  		res = getExtAPIDescrForBugGroups([]*uiBugGroup{i.Bugs})
   171  	case *uiMainPage:
   172  		res = getExtAPIDescrForBugGroups(i.Groups)
   173  	case *uiBackportsPage:
   174  		res = getExtAPIDescrForBackports(i.Groups)
   175  	default:
   176  		return nil, ErrClientNotFound
   177  	}
   178  	return json.MarshalIndent(res, "", "\t")
   179  }
   180  
   181  func writeExtAPICoverageFor(ctx context.Context, w io.Writer, ns, repo string, p *coverageHeatmapParams) error {
   182  	// By default, return the previous month coverage. It guarantees the good numbers.
   183  	//
   184  	// The alternative is to return the current month.
   185  	// The numbers will jump every day, on the 1st date may drop down.
   186  	tps, err := coveragedb.GenNPeriodsTill(1, civil.DateOf(time.Now()).AddDays(-31), "month")
   187  	if err != nil {
   188  		return fmt.Errorf("coveragedb.GenNPeriodsTill: %w", err)
   189  	}
   190  
   191  	covDBClient := getCoverageDBClient(ctx)
   192  	ff, err := coveragedb.MakeFuncFinder(ctx, covDBClient, ns, tps[0])
   193  	if err != nil {
   194  		return fmt.Errorf("coveragedb.MakeFuncFinder: %w", err)
   195  	}
   196  	subsystem := ""
   197  	manager := ""
   198  	if p != nil {
   199  		subsystem = p.subsystem
   200  		manager = p.manager
   201  	}
   202  	covCh, errCh := coveragedb.FilesCoverageStream(ctx, covDBClient,
   203  		&coveragedb.SelectScope{
   204  			Ns:        ns,
   205  			Subsystem: subsystem,
   206  			Manager:   manager,
   207  			Periods:   tps,
   208  		})
   209  	if err := writeFileCoverage(ctx, w, repo, ff, covCh); err != nil {
   210  		return fmt.Errorf("populateFileCoverage: %w", err)
   211  	}
   212  	if err := <-errCh; err != nil {
   213  		return fmt.Errorf("coveragedb.FilesCoverageStream: %w", err)
   214  	}
   215  	return nil
   216  }
   217  
   218  func writeFileCoverage(ctx context.Context, w io.Writer, repo string, ff *coveragedb.FunctionFinder,
   219  	covCh <-chan *coveragedb.FileCoverageWithLineInfo) error {
   220  	enc := json.NewEncoder(w)
   221  	enc.SetIndent("", "\t")
   222  	for {
   223  		select {
   224  		case fileCov := <-covCh:
   225  			if fileCov == nil {
   226  				return nil
   227  			}
   228  			funcsCov, err := genFuncsCov(fileCov, ff)
   229  			if err != nil {
   230  				return fmt.Errorf("genFuncsCov: %w", err)
   231  			}
   232  			if err := enc.Encode(&cover.FileCoverage{
   233  				Repo:      repo,
   234  				Commit:    fileCov.Commit,
   235  				FilePath:  fileCov.Filepath,
   236  				Functions: funcsCov,
   237  			}); err != nil {
   238  				return fmt.Errorf("enc.Encode: %w", err)
   239  			}
   240  		case <-ctx.Done():
   241  			return nil
   242  		}
   243  	}
   244  }
   245  
   246  func genFuncsCov(fc *coveragedb.FileCoverageWithLineInfo, ff *coveragedb.FunctionFinder,
   247  ) ([]*cover.FuncCoverage, error) {
   248  	nameToLines := map[string][]*cover.Block{}
   249  	for i, hitCount := range fc.HitCounts {
   250  		lineNum := int(fc.LinesInstrumented[i])
   251  		funcName, err := ff.FileLineToFuncName(fc.Filepath, lineNum)
   252  		if err != nil {
   253  			return nil, fmt.Errorf("ff.FileLineToFuncName: %w", err)
   254  		}
   255  		nameToLines[funcName] = append(nameToLines[funcName], &cover.Block{
   256  			HitCount: int(hitCount),
   257  			FromLine: lineNum,
   258  			FromCol:  0,
   259  			ToLine:   lineNum,
   260  			ToCol:    -1,
   261  		})
   262  	}
   263  
   264  	var res []*cover.FuncCoverage
   265  	for funcName, blocks := range nameToLines {
   266  		res = append(res, &cover.FuncCoverage{
   267  			FuncName: funcName,
   268  			Blocks:   blocks,
   269  		})
   270  	}
   271  	return res, nil
   272  }