1// Copyright 2017 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 4package dash 5 6import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "reflect" 11 "sort" 12 "strings" 13 "time" 14 15 "github.com/google/syzkaller/dashboard/dashapi" 16 "github.com/google/syzkaller/pkg/email" 17 "golang.org/x/net/context" 18 "google.golang.org/appengine/datastore" 19 "google.golang.org/appengine/log" 20) 21 22// Backend-independent reporting logic. 23// Two main entry points: 24// - reportingPoll is called by backends to get list of bugs that need to be reported. 25// - incomingCommand is called by backends to update bug statuses. 26 27const ( 28 maxMailLogLen = 1 << 20 29 maxMailReportLen = 64 << 10 30 maxInlineError = 16 << 10 31 internalError = "internal error" 32 // This is embedded as first line of syzkaller reproducer files. 33 syzReproPrefix = "# See https://goo.gl/kgGztJ for information about syzkaller reproducers.\n" 34) 35 36// reportingPoll is called by backends to get list of bugs that need to be reported. 37func reportingPollBugs(c context.Context, typ string) []*dashapi.BugReport { 38 state, err := loadReportingState(c) 39 if err != nil { 40 log.Errorf(c, "%v", err) 41 return nil 42 } 43 var bugs []*Bug 44 _, err = datastore.NewQuery("Bug"). 45 Filter("Status<", BugStatusFixed). 46 GetAll(c, &bugs) 47 if err != nil { 48 log.Errorf(c, "%v", err) 49 return nil 50 } 51 log.Infof(c, "fetched %v bugs", len(bugs)) 52 sort.Sort(bugReportSorter(bugs)) 53 var reports []*dashapi.BugReport 54 for _, bug := range bugs { 55 rep, err := handleReportBug(c, typ, state, bug) 56 if err != nil { 57 log.Errorf(c, "%v: failed to report bug %v: %v", bug.Namespace, bug.Title, err) 58 continue 59 } 60 if rep == nil { 61 continue 62 } 63 reports = append(reports, rep) 64 if len(reports) > 50 { 65 break // temp measure during the jam 66 } 67 } 68 return reports 69} 70 71func handleReportBug(c context.Context, typ string, state *ReportingState, bug *Bug) (*dashapi.BugReport, error) { 72 reporting, bugReporting, crash, crashKey, _, _, _, err := needReport(c, typ, state, bug) 73 if err != nil || reporting == nil { 74 return nil, err 75 } 76 rep, err := createBugReport(c, bug, crash, crashKey, bugReporting, reporting.Config) 77 if err != nil { 78 return nil, err 79 } 80 log.Infof(c, "bug %q: reporting to %v", bug.Title, reporting.Name) 81 return rep, nil 82} 83 84func needReport(c context.Context, typ string, state *ReportingState, bug *Bug) ( 85 reporting *Reporting, bugReporting *BugReporting, crash *Crash, 86 crashKey *datastore.Key, reportingIdx int, status, link string, err error) { 87 reporting, bugReporting, reportingIdx, status, err = currentReporting(c, bug) 88 if err != nil || reporting == nil { 89 return 90 } 91 if typ != "" && typ != reporting.Config.Type() { 92 status = "on a different reporting" 93 reporting, bugReporting = nil, nil 94 return 95 } 96 link = bugReporting.Link 97 if !bugReporting.Reported.IsZero() && bugReporting.ReproLevel >= bug.ReproLevel { 98 status = fmt.Sprintf("%v: reported%v on %v", 99 reporting.DisplayTitle, reproStr(bugReporting.ReproLevel), 100 formatTime(bugReporting.Reported)) 101 reporting, bugReporting = nil, nil 102 return 103 } 104 ent := state.getEntry(timeNow(c), bug.Namespace, reporting.Name) 105 cfg := config.Namespaces[bug.Namespace] 106 if timeSince(c, bug.FirstTime) < cfg.ReportingDelay { 107 status = fmt.Sprintf("%v: initial reporting delay", reporting.DisplayTitle) 108 reporting, bugReporting = nil, nil 109 return 110 } 111 if bug.ReproLevel < ReproLevelC && timeSince(c, bug.FirstTime) < cfg.WaitForRepro { 112 status = fmt.Sprintf("%v: waiting for C repro", reporting.DisplayTitle) 113 reporting, bugReporting = nil, nil 114 return 115 } 116 if !cfg.MailWithoutReport && !bug.HasReport { 117 status = fmt.Sprintf("%v: no report", reporting.DisplayTitle) 118 reporting, bugReporting = nil, nil 119 return 120 } 121 122 crash, crashKey, err = findCrashForBug(c, bug) 123 if err != nil { 124 status = fmt.Sprintf("%v: no crashes!", reporting.DisplayTitle) 125 reporting, bugReporting = nil, nil 126 return 127 } 128 if reporting.Config.NeedMaintainers() && len(crash.Maintainers) == 0 { 129 status = fmt.Sprintf("%v: no maintainers", reporting.DisplayTitle) 130 reporting, bugReporting = nil, nil 131 return 132 } 133 134 // Limit number of reports sent per day, 135 // but don't limit sending repros to already reported bugs. 136 if bugReporting.Reported.IsZero() && reporting.DailyLimit != 0 && 137 ent.Sent >= reporting.DailyLimit { 138 status = fmt.Sprintf("%v: out of quota for today", reporting.DisplayTitle) 139 reporting, bugReporting = nil, nil 140 return 141 } 142 143 // Ready to be reported. 144 if bugReporting.Reported.IsZero() { 145 // This update won't be committed, but it will prevent us from 146 // reporting too many bugs in a single poll. 147 ent.Sent++ 148 } 149 status = fmt.Sprintf("%v: ready to report", reporting.DisplayTitle) 150 if !bugReporting.Reported.IsZero() { 151 status += fmt.Sprintf(" (reported%v on %v)", 152 reproStr(bugReporting.ReproLevel), formatTime(bugReporting.Reported)) 153 } 154 return 155} 156 157func currentReporting(c context.Context, bug *Bug) (*Reporting, *BugReporting, int, string, error) { 158 for i := range bug.Reporting { 159 bugReporting := &bug.Reporting[i] 160 if !bugReporting.Closed.IsZero() { 161 continue 162 } 163 reporting := config.Namespaces[bug.Namespace].ReportingByName(bugReporting.Name) 164 if reporting == nil { 165 return nil, nil, 0, "", fmt.Errorf("%v: missing in config", bugReporting.Name) 166 } 167 switch reporting.Filter(bug) { 168 case FilterSkip: 169 if bugReporting.Reported.IsZero() { 170 continue 171 } 172 fallthrough 173 case FilterReport: 174 return reporting, bugReporting, i, "", nil 175 case FilterHold: 176 return nil, nil, 0, fmt.Sprintf("%v: reporting suspended", reporting.DisplayTitle), nil 177 } 178 } 179 return nil, nil, 0, "", fmt.Errorf("no reporting left") 180} 181 182func reproStr(level dashapi.ReproLevel) string { 183 switch level { 184 case ReproLevelSyz: 185 return " syz repro" 186 case ReproLevelC: 187 return " C repro" 188 default: 189 return "" 190 } 191} 192 193func createBugReport(c context.Context, bug *Bug, crash *Crash, crashKey *datastore.Key, 194 bugReporting *BugReporting, config interface{}) (*dashapi.BugReport, error) { 195 reportingConfig, err := json.Marshal(config) 196 if err != nil { 197 return nil, err 198 } 199 crashLog, _, err := getText(c, textCrashLog, crash.Log) 200 if err != nil { 201 return nil, err 202 } 203 if len(crashLog) > maxMailLogLen { 204 crashLog = crashLog[len(crashLog)-maxMailLogLen:] 205 } 206 report, _, err := getText(c, textCrashReport, crash.Report) 207 if err != nil { 208 return nil, err 209 } 210 if len(report) > maxMailReportLen { 211 report = report[:maxMailReportLen] 212 } 213 reproC, _, err := getText(c, textReproC, crash.ReproC) 214 if err != nil { 215 return nil, err 216 } 217 reproSyz, _, err := getText(c, textReproSyz, crash.ReproSyz) 218 if err != nil { 219 return nil, err 220 } 221 if len(reproSyz) != 0 { 222 buf := new(bytes.Buffer) 223 buf.WriteString(syzReproPrefix) 224 if len(crash.ReproOpts) != 0 { 225 fmt.Fprintf(buf, "#%s\n", crash.ReproOpts) 226 } 227 buf.Write(reproSyz) 228 reproSyz = buf.Bytes() 229 } 230 build, err := loadBuild(c, bug.Namespace, crash.BuildID) 231 if err != nil { 232 return nil, err 233 } 234 kernelConfig, _, err := getText(c, textKernelConfig, build.KernelConfig) 235 if err != nil { 236 return nil, err 237 } 238 239 rep := &dashapi.BugReport{ 240 Namespace: bug.Namespace, 241 Config: reportingConfig, 242 ID: bugReporting.ID, 243 ExtID: bugReporting.ExtID, 244 First: bugReporting.Reported.IsZero(), 245 Title: bug.displayTitle(), 246 Log: crashLog, 247 LogLink: externalLink(c, textCrashLog, crash.Log), 248 Report: report, 249 ReportLink: externalLink(c, textCrashReport, crash.Report), 250 Maintainers: crash.Maintainers, 251 OS: build.OS, 252 Arch: build.Arch, 253 VMArch: build.VMArch, 254 CompilerID: build.CompilerID, 255 KernelRepo: build.KernelRepo, 256 KernelRepoAlias: kernelRepoInfo(build).Alias, 257 KernelBranch: build.KernelBranch, 258 KernelCommit: build.KernelCommit, 259 KernelCommitTitle: build.KernelCommitTitle, 260 KernelCommitDate: build.KernelCommitDate, 261 KernelConfig: kernelConfig, 262 KernelConfigLink: externalLink(c, textKernelConfig, build.KernelConfig), 263 ReproC: reproC, 264 ReproCLink: externalLink(c, textReproC, crash.ReproC), 265 ReproSyz: reproSyz, 266 ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz), 267 CrashID: crashKey.IntID(), 268 NumCrashes: bug.NumCrashes, 269 HappenedOn: managersToRepos(c, bug.Namespace, bug.HappenedOn), 270 } 271 if bugReporting.CC != "" { 272 rep.CC = strings.Split(bugReporting.CC, "|") 273 } 274 return rep, nil 275} 276 277func managersToRepos(c context.Context, ns string, managers []string) []string { 278 var repos []string 279 dedup := make(map[string]bool) 280 for _, manager := range managers { 281 build, err := lastManagerBuild(c, ns, manager) 282 if err != nil { 283 log.Errorf(c, "failed to get manager %q build: %v", manager, err) 284 continue 285 } 286 repo := kernelRepoInfo(build).Alias 287 if dedup[repo] { 288 continue 289 } 290 dedup[repo] = true 291 repos = append(repos, repo) 292 } 293 sort.Strings(repos) 294 return repos 295} 296 297// reportingPollClosed is called by backends to get list of closed bugs. 298func reportingPollClosed(c context.Context, ids []string) ([]string, error) { 299 var bugs []*Bug 300 _, err := datastore.NewQuery("Bug"). 301 GetAll(c, &bugs) 302 if err != nil { 303 log.Errorf(c, "%v", err) 304 return nil, nil 305 } 306 bugMap := make(map[string]*Bug) 307 for _, bug := range bugs { 308 for i := range bug.Reporting { 309 bugMap[bug.Reporting[i].ID] = bug 310 } 311 } 312 var closed []string 313 for _, id := range ids { 314 bug := bugMap[id] 315 if bug == nil { 316 continue 317 } 318 bugReporting, _ := bugReportingByID(bug, id) 319 bug, err = canonicalBug(c, bug) 320 if err != nil { 321 log.Errorf(c, "%v", err) 322 continue 323 } 324 if bug.Status >= BugStatusFixed || !bugReporting.Closed.IsZero() { 325 closed = append(closed, id) 326 } 327 } 328 return closed, nil 329} 330 331// incomingCommand is entry point to bug status updates. 332func incomingCommand(c context.Context, cmd *dashapi.BugUpdate) (bool, string, error) { 333 log.Infof(c, "got command: %+v", cmd) 334 ok, reason, err := incomingCommandImpl(c, cmd) 335 if err != nil { 336 log.Errorf(c, "%v (%v)", reason, err) 337 } else if !ok && reason != "" { 338 log.Errorf(c, "invalid update: %v", reason) 339 } 340 return ok, reason, err 341} 342 343func incomingCommandImpl(c context.Context, cmd *dashapi.BugUpdate) (bool, string, error) { 344 for i, com := range cmd.FixCommits { 345 if len(com) >= 2 && com[0] == '"' && com[len(com)-1] == '"' { 346 com = com[1 : len(com)-1] 347 cmd.FixCommits[i] = com 348 } 349 if len(com) < 3 { 350 return false, fmt.Sprintf("bad commit title: %q", com), nil 351 } 352 } 353 bug, bugKey, err := findBugByReportingID(c, cmd.ID) 354 if err != nil { 355 return false, internalError, err 356 } 357 now := timeNow(c) 358 dupHash := "" 359 if cmd.Status == dashapi.BugStatusDup { 360 bugReporting, _ := bugReportingByID(bug, cmd.ID) 361 dup, dupKey, err := findBugByReportingID(c, cmd.DupOf) 362 if err != nil { 363 // Email reporting passes bug title in cmd.DupOf, try to find bug by title. 364 dup, dupKey, err = findDupByTitle(c, bug.Namespace, cmd.DupOf) 365 if err != nil { 366 return false, "can't find the dup bug", err 367 } 368 dupReporting := bugReportingByName(dup, bugReporting.Name) 369 if dupReporting == nil { 370 return false, "can't find the dup bug", 371 fmt.Errorf("dup does not have reporting %q", bugReporting.Name) 372 } 373 cmd.DupOf = dupReporting.ID 374 } 375 dupReporting, _ := bugReportingByID(dup, cmd.DupOf) 376 if bugReporting == nil || dupReporting == nil { 377 return false, internalError, fmt.Errorf("can't find bug reporting") 378 } 379 if bugKey.StringID() == dupKey.StringID() { 380 if bugReporting.Name == dupReporting.Name { 381 return false, "Can't dup bug to itself.", nil 382 } 383 return false, fmt.Sprintf("Can't dup bug to itself in different reporting (%v->%v).\n"+ 384 "Please dup syzbot bugs only onto syzbot bugs for the same kernel/reporting.", 385 bugReporting.Name, dupReporting.Name), nil 386 } 387 if bug.Namespace != dup.Namespace { 388 return false, fmt.Sprintf("Duplicate bug corresponds to a different kernel (%v->%v).\n"+ 389 "Please dup syzbot bugs only onto syzbot bugs for the same kernel.", 390 bug.Namespace, dup.Namespace), nil 391 } 392 if bugReporting.Name != dupReporting.Name { 393 return false, fmt.Sprintf("Can't dup bug to a bug in different reporting (%v->%v)."+ 394 "Please dup syzbot bugs only onto syzbot bugs for the same kernel/reporting.", 395 bugReporting.Name, dupReporting.Name), nil 396 } 397 dupCanon, err := canonicalBug(c, dup) 398 if err != nil { 399 return false, internalError, fmt.Errorf("failed to get canonical bug for dup: %v", err) 400 } 401 if !dupReporting.Closed.IsZero() && dupCanon.Status == BugStatusOpen { 402 return false, "Dup bug is already upstreamed.", nil 403 } 404 dupHash = bugKeyHash(dup.Namespace, dup.Title, dup.Seq) 405 } 406 407 ok, reply := false, "" 408 tx := func(c context.Context) error { 409 var err error 410 ok, reply, err = incomingCommandTx(c, now, cmd, bugKey, dupHash) 411 return err 412 } 413 err = datastore.RunInTransaction(c, tx, &datastore.TransactionOptions{ 414 XG: true, 415 // Default is 3 which fails sometimes. 416 // We don't want incoming bug updates to fail, 417 // because for e.g. email we won't have an external retry. 418 Attempts: 30, 419 }) 420 if err != nil { 421 return false, internalError, err 422 } 423 return ok, reply, nil 424} 425 426func incomingCommandTx(c context.Context, now time.Time, cmd *dashapi.BugUpdate, 427 bugKey *datastore.Key, dupHash string) (bool, string, error) { 428 bug := new(Bug) 429 if err := datastore.Get(c, bugKey, bug); err != nil { 430 return false, internalError, fmt.Errorf("can't find the corresponding bug: %v", err) 431 } 432 bugReporting, final := bugReportingByID(bug, cmd.ID) 433 if bugReporting == nil { 434 return false, internalError, fmt.Errorf("can't find bug reporting") 435 } 436 if ok, reply, err := checkBugStatus(c, cmd, bug, bugReporting); !ok { 437 return false, reply, err 438 } 439 state, err := loadReportingState(c) 440 if err != nil { 441 return false, internalError, err 442 } 443 stateEnt := state.getEntry(now, bug.Namespace, bugReporting.Name) 444 if ok, reply, err := incomingCommandCmd(c, now, cmd, bug, bugReporting, final, dupHash, stateEnt); !ok { 445 return false, reply, err 446 } 447 if len(cmd.FixCommits) != 0 && (bug.Status == BugStatusOpen || bug.Status == BugStatusDup) { 448 sort.Strings(cmd.FixCommits) 449 if !reflect.DeepEqual(bug.Commits, cmd.FixCommits) { 450 bug.Commits = cmd.FixCommits 451 bug.PatchedOn = nil 452 } 453 } 454 if cmd.CrashID != 0 { 455 // Rememeber that we've reported this crash. 456 crash := new(Crash) 457 crashKey := datastore.NewKey(c, "Crash", "", cmd.CrashID, bugKey) 458 if err := datastore.Get(c, crashKey, crash); err != nil { 459 return false, internalError, fmt.Errorf("failed to get reported crash %v: %v", 460 cmd.CrashID, err) 461 } 462 crash.Reported = now 463 if _, err := datastore.Put(c, crashKey, crash); err != nil { 464 return false, internalError, fmt.Errorf("failed to put reported crash %v: %v", 465 cmd.CrashID, err) 466 } 467 bugReporting.CrashID = cmd.CrashID 468 } 469 if bugReporting.ExtID == "" { 470 bugReporting.ExtID = cmd.ExtID 471 } 472 if bugReporting.Link == "" { 473 bugReporting.Link = cmd.Link 474 } 475 if len(cmd.CC) != 0 { 476 merged := email.MergeEmailLists(strings.Split(bugReporting.CC, "|"), cmd.CC) 477 bugReporting.CC = strings.Join(merged, "|") 478 } 479 if bugReporting.ReproLevel < cmd.ReproLevel { 480 bugReporting.ReproLevel = cmd.ReproLevel 481 } 482 if bug.Status != BugStatusDup { 483 bug.DupOf = "" 484 } 485 if _, err := datastore.Put(c, bugKey, bug); err != nil { 486 return false, internalError, fmt.Errorf("failed to put bug: %v", err) 487 } 488 if err := saveReportingState(c, state); err != nil { 489 return false, internalError, err 490 } 491 return true, "", nil 492} 493 494func incomingCommandCmd(c context.Context, now time.Time, cmd *dashapi.BugUpdate, 495 bug *Bug, bugReporting *BugReporting, final bool, dupHash string, 496 stateEnt *ReportingStateEntry) (bool, string, error) { 497 switch cmd.Status { 498 case dashapi.BugStatusOpen: 499 bug.Status = BugStatusOpen 500 bug.Closed = time.Time{} 501 if bugReporting.Reported.IsZero() { 502 bugReporting.Reported = now 503 stateEnt.Sent++ // sending repro does not count against the quota 504 } 505 // Close all previous reporting if they are not closed yet 506 // (can happen due to Status == ReportingDisabled). 507 for i := range bug.Reporting { 508 if bugReporting == &bug.Reporting[i] { 509 break 510 } 511 if bug.Reporting[i].Closed.IsZero() { 512 bug.Reporting[i].Closed = now 513 } 514 } 515 if bug.ReproLevel < cmd.ReproLevel { 516 return false, internalError, 517 fmt.Errorf("bug update with invalid repro level: %v/%v", 518 bug.ReproLevel, cmd.ReproLevel) 519 } 520 case dashapi.BugStatusUpstream: 521 if final { 522 return false, "Can't upstream, this is final destination.", nil 523 } 524 if len(bug.Commits) != 0 { 525 // We could handle this case, but how/when it will occur 526 // in real life is unclear now. 527 return false, "Can't upstream this bug, the bug has fixing commits.", nil 528 } 529 bug.Status = BugStatusOpen 530 bug.Closed = time.Time{} 531 bugReporting.Closed = now 532 case dashapi.BugStatusInvalid: 533 bugReporting.Closed = now 534 bug.Closed = now 535 bug.Status = BugStatusInvalid 536 case dashapi.BugStatusDup: 537 bug.Status = BugStatusDup 538 bug.Closed = now 539 bug.DupOf = dupHash 540 case dashapi.BugStatusUpdate: 541 // Just update Link, Commits, etc below. 542 default: 543 return false, internalError, fmt.Errorf("unknown bug status %v", cmd.Status) 544 } 545 return true, "", nil 546} 547 548func checkBugStatus(c context.Context, cmd *dashapi.BugUpdate, bug *Bug, bugReporting *BugReporting) ( 549 bool, string, error) { 550 switch bug.Status { 551 case BugStatusOpen: 552 case BugStatusDup: 553 canon, err := canonicalBug(c, bug) 554 if err != nil { 555 return false, internalError, err 556 } 557 if canon.Status != BugStatusOpen { 558 // We used to reject updates to closed bugs, 559 // but this is confusing and non-actionable for users. 560 // So now we fail the update, but give empty reason, 561 // which means "don't notify user". 562 if cmd.Status == dashapi.BugStatusUpdate { 563 // This happens when people discuss old bugs. 564 log.Infof(c, "Dup bug is already closed") 565 } else { 566 log.Errorf(c, "Dup bug is already closed") 567 } 568 return false, "", nil 569 } 570 case BugStatusFixed, BugStatusInvalid: 571 if cmd.Status != dashapi.BugStatusUpdate { 572 log.Errorf(c, "This bug is already closed") 573 } 574 return false, "", nil 575 default: 576 return false, internalError, fmt.Errorf("unknown bug status %v", bug.Status) 577 } 578 if !bugReporting.Closed.IsZero() { 579 if cmd.Status != dashapi.BugStatusUpdate { 580 log.Errorf(c, "This bug reporting is already closed") 581 } 582 return false, "", nil 583 } 584 return true, "", nil 585} 586 587func findBugByReportingID(c context.Context, id string) (*Bug, *datastore.Key, error) { 588 var bugs []*Bug 589 keys, err := datastore.NewQuery("Bug"). 590 Filter("Reporting.ID=", id). 591 Limit(2). 592 GetAll(c, &bugs) 593 if err != nil { 594 return nil, nil, fmt.Errorf("failed to fetch bugs: %v", err) 595 } 596 if len(bugs) == 0 { 597 return nil, nil, fmt.Errorf("failed to find bug by reporting id %q", id) 598 } 599 if len(bugs) > 1 { 600 return nil, nil, fmt.Errorf("multiple bugs for reporting id %q", id) 601 } 602 return bugs[0], keys[0], nil 603} 604 605func findDupByTitle(c context.Context, ns, title string) (*Bug, *datastore.Key, error) { 606 title, seq, err := splitDisplayTitle(title) 607 if err != nil { 608 return nil, nil, err 609 } 610 bugHash := bugKeyHash(ns, title, seq) 611 bugKey := datastore.NewKey(c, "Bug", bugHash, 0, nil) 612 bug := new(Bug) 613 if err := datastore.Get(c, bugKey, bug); err != nil { 614 return nil, nil, fmt.Errorf("failed to get dup: %v", err) 615 } 616 return bug, bugKey, nil 617} 618 619func bugReportingByID(bug *Bug, id string) (*BugReporting, bool) { 620 for i := range bug.Reporting { 621 if bug.Reporting[i].ID == id { 622 return &bug.Reporting[i], i == len(bug.Reporting)-1 623 } 624 } 625 return nil, false 626} 627 628func bugReportingByName(bug *Bug, name string) *BugReporting { 629 for i := range bug.Reporting { 630 if bug.Reporting[i].Name == name { 631 return &bug.Reporting[i] 632 } 633 } 634 return nil 635} 636 637func queryCrashesForBug(c context.Context, bugKey *datastore.Key, limit int) ( 638 []*Crash, []*datastore.Key, error) { 639 var crashes []*Crash 640 keys, err := datastore.NewQuery("Crash"). 641 Ancestor(bugKey). 642 Order("-ReportLen"). 643 Order("-Reported"). 644 Order("-Time"). 645 Limit(limit). 646 GetAll(c, &crashes) 647 if err != nil { 648 return nil, nil, fmt.Errorf("failed to fetch crashes: %v", err) 649 } 650 return crashes, keys, nil 651} 652 653func findCrashForBug(c context.Context, bug *Bug) (*Crash, *datastore.Key, error) { 654 bugKey := datastore.NewKey(c, "Bug", bugKeyHash(bug.Namespace, bug.Title, bug.Seq), 0, nil) 655 crashes, keys, err := queryCrashesForBug(c, bugKey, 1) 656 if err != nil { 657 return nil, nil, err 658 } 659 if len(crashes) < 1 { 660 return nil, nil, fmt.Errorf("no crashes") 661 } 662 crash, key := crashes[0], keys[0] 663 if bug.ReproLevel == ReproLevelC { 664 if crash.ReproC == 0 { 665 log.Errorf(c, "bug '%v': has C repro, but crash without C repro", bug.Title) 666 } 667 } else if bug.ReproLevel == ReproLevelSyz { 668 if crash.ReproSyz == 0 { 669 log.Errorf(c, "bug '%v': has syz repro, but crash without syz repro", bug.Title) 670 } 671 } else if bug.HasReport { 672 if crash.Report == 0 { 673 log.Errorf(c, "bug '%v': has report, but crash without report", bug.Title) 674 } 675 } 676 return crash, key, nil 677} 678 679func loadReportingState(c context.Context) (*ReportingState, error) { 680 state := new(ReportingState) 681 key := datastore.NewKey(c, "ReportingState", "", 1, nil) 682 if err := datastore.Get(c, key, state); err != nil && err != datastore.ErrNoSuchEntity { 683 return nil, fmt.Errorf("failed to get reporting state: %v", err) 684 } 685 return state, nil 686} 687 688func saveReportingState(c context.Context, state *ReportingState) error { 689 key := datastore.NewKey(c, "ReportingState", "", 1, nil) 690 if _, err := datastore.Put(c, key, state); err != nil { 691 return fmt.Errorf("failed to put reporting state: %v", err) 692 } 693 return nil 694} 695 696func (state *ReportingState) getEntry(now time.Time, namespace, name string) *ReportingStateEntry { 697 if namespace == "" || name == "" { 698 panic(fmt.Sprintf("requesting reporting state for %v/%v", namespace, name)) 699 } 700 // Convert time to date of the form 20170125. 701 date := timeDate(now) 702 for i := range state.Entries { 703 ent := &state.Entries[i] 704 if ent.Namespace == namespace && ent.Name == name { 705 if ent.Date != date { 706 ent.Date = date 707 ent.Sent = 0 708 } 709 return ent 710 } 711 } 712 state.Entries = append(state.Entries, ReportingStateEntry{ 713 Namespace: namespace, 714 Name: name, 715 Date: date, 716 Sent: 0, 717 }) 718 return &state.Entries[len(state.Entries)-1] 719} 720 721// bugReportSorter sorts bugs by priority we want to report them. 722// E.g. we want to report bugs with reproducers before bugs without reproducers. 723type bugReportSorter []*Bug 724 725func (a bugReportSorter) Len() int { return len(a) } 726func (a bugReportSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 727func (a bugReportSorter) Less(i, j int) bool { 728 if a[i].ReproLevel != a[j].ReproLevel { 729 return a[i].ReproLevel > a[j].ReproLevel 730 } 731 if a[i].HasReport != a[j].HasReport { 732 return a[i].HasReport 733 } 734 if a[i].NumCrashes != a[j].NumCrashes { 735 return a[i].NumCrashes > a[j].NumCrashes 736 } 737 return a[i].FirstTime.Before(a[j].FirstTime) 738} 739