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 }