pkg: get and store Maintainers data

Create a struct on pkg/vcs to store data of syzkaller email recipients
and update its users. The struct contains default name, email, and a
label to divide user into To and Cc when sending the emails.
This commit is contained in:
Pedro Lopes 2020-07-28 15:55:14 -05:00 committed by Dmitry Vyukov
parent 68aca71e8d
commit 242b0eb219
14 changed files with 188 additions and 74 deletions

View File

@ -180,7 +180,8 @@ func runImpl(cfg *Config, repo vcs.Repo, inst instance.Env) (*Result, error) {
}
com := res.Commits[0]
env.log("first %v commit: %v %v", what, com.Hash, com.Title)
env.log("cc: %q", com.CC)
env.log("recipients (to): %q", com.Recipients.GetEmails(vcs.To))
env.log("recipients (cc): %q", com.Recipients.GetEmails(vcs.Cc))
if res.Report != nil {
env.log("crash: %v\n%s", res.Report.Title, res.Report.Report)
}

View File

@ -15,6 +15,7 @@ import (
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/report"
"github.com/google/syzkaller/pkg/vcs"
)
// Params is input arguments for the Image function.
@ -90,10 +91,10 @@ func Clean(targetOS, targetArch, vmType, kernelDir string) error {
}
type KernelError struct {
Report []byte
Output []byte
Maintainers []string
guiltyFile string
Report []byte
Output []byte
Recipients vcs.Recipients
guiltyFile string
}
func (err *KernelError) Error() string {
@ -195,7 +196,7 @@ func extractRootCause(err error, OS, kernelSrc string) error {
if err != nil {
kernelErr.Output = append(kernelErr.Output, err.Error()...)
}
kernelErr.Maintainers = maintainers
kernelErr.Recipients = maintainers
}
return kernelErr
}

View File

@ -7,7 +7,6 @@ import (
"bufio"
"bytes"
"fmt"
"net/mail"
"path/filepath"
"regexp"
"strconv"
@ -16,6 +15,7 @@ import (
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/symbolizer"
"github.com/google/syzkaller/pkg/vcs"
)
type linux struct {
@ -351,7 +351,7 @@ func (ctx *linux) Symbolize(rep *Report) error {
if err != nil {
return err
}
rep.Maintainers = maintainers
rep.Recipients = maintainers
}
return nil
}
@ -466,14 +466,14 @@ nextFile:
return ""
}
func (ctx *linux) getMaintainers(file string) ([]string, error) {
func (ctx *linux) getMaintainers(file string) (vcs.Recipients, error) {
if ctx.kernelSrc == "" {
return nil, nil
}
return GetLinuxMaintainers(ctx.kernelSrc, file)
}
func GetLinuxMaintainers(kernelSrc, file string) ([]string, error) {
func GetLinuxMaintainers(kernelSrc, file string) (vcs.Recipients, error) {
mtrs, err := getMaintainersImpl(kernelSrc, file, false)
if err != nil {
return nil, err
@ -487,9 +487,9 @@ func GetLinuxMaintainers(kernelSrc, file string) ([]string, error) {
return mtrs, nil
}
func getMaintainersImpl(kernelSrc, file string, blame bool) ([]string, error) {
func getMaintainersImpl(kernelSrc, file string, blame bool) (vcs.Recipients, error) {
// See #1441 re --git-min-percent.
args := []string{"--no-n", "--no-rolestats", "--git-min-percent=15"}
args := []string{"--git-min-percent=15"}
if blame {
args = append(args, "--git-blame")
}
@ -499,16 +499,7 @@ func getMaintainersImpl(kernelSrc, file string, blame bool) ([]string, error) {
if err != nil {
return nil, err
}
lines := strings.Split(string(output), "\n")
var mtrs []string
for _, line := range lines {
addr, err := mail.ParseAddress(line)
if err != nil {
continue
}
mtrs = append(mtrs, addr.Address)
}
return mtrs, nil
return vcs.ParseMaintainersLinux(output), nil
}
func (ctx *linux) extractFiles(report []byte) []string {

View File

@ -13,6 +13,7 @@ import (
"strings"
"github.com/google/syzkaller/pkg/mgrconfig"
"github.com/google/syzkaller/pkg/vcs"
"github.com/google/syzkaller/sys/targets"
)
@ -50,8 +51,8 @@ type Report struct {
Corrupted bool
// CorruptedReason contains reason why the report is marked as corrupted.
CorruptedReason string
// Maintainers is list of maintainer emails (filled in by Symbolize).
Maintainers []string
// Recipients is a list of RecipientInfo with Email, Display Name, and type.
Recipients vcs.Recipients
// guiltyFile is the source file that we think is to blame for the crash (filled in by Symbolize).
guiltyFile string
// reportPrefixLen is length of additional prefix lines that we added before actual crash report.

View File

@ -196,8 +196,8 @@ func gitParseCommit(output, user, domain []byte, ignoreCC map[string]bool) (*Com
if err != nil {
return nil, fmt.Errorf("failed to parse date in git log output: %v\n%q", err, output)
}
cc := make(map[string]bool)
cc[strings.ToLower(string(lines[2]))] = true
recipients := make(map[string]bool)
recipients[strings.ToLower(string(lines[2]))] = true
var tags []string
// Use summary line + all description lines.
for _, line := range append([][]byte{lines[1]}, lines[6:]...) {
@ -235,15 +235,15 @@ func gitParseCommit(output, user, domain []byte, ignoreCC map[string]bool) (*Com
if ignoreCC[email] {
continue
}
cc[email] = true
recipients[email] = true
break
}
}
sortedCC := make([]string, 0, len(cc))
for addr := range cc {
sortedCC = append(sortedCC, addr)
sortedRecipients := make(Recipients, 0, len(recipients))
for addr := range recipients {
sortedRecipients = append(sortedRecipients, RecipientInfo{mail.Address{Address: addr}, To})
}
sort.Strings(sortedCC)
sort.Sort(sortedRecipients)
parents := strings.Split(string(lines[5]), " ")
com := &Commit{
Hash: string(lines[0]),
@ -251,7 +251,7 @@ func gitParseCommit(output, user, domain []byte, ignoreCC map[string]bool) (*Com
Author: string(lines[2]),
AuthorName: string(lines[3]),
Parents: parents,
CC: sortedCC,
Recipients: sortedRecipients,
Tags: tags,
Date: date,
}

View File

@ -167,8 +167,8 @@ func checkCommit(t *testing.T, idx int, test testCommit, com *Commit, checkTags
if userName != com.AuthorName {
t.Errorf("#%v: want author name %q, got %q", idx, userName, com.Author)
}
if diff := cmp.Diff(test.cc, com.CC); diff != "" {
t.Logf("%#v", com.CC)
if diff := cmp.Diff(test.cc, com.Recipients.GetEmails(To)); diff != "" {
t.Logf("%#v", com.Recipients)
t.Error(diff)
}
if diff := cmp.Diff(test.tags, com.Tags); checkTags && diff != "" {

View File

@ -37,7 +37,7 @@ Signed-off-by: Linux Master <linux@linux-foundation.org>
Title: "rbtree: include rcu.h",
Author: "foobar@foobar.de",
AuthorName: "Foo Bar",
CC: []string{
Recipients: NewRecipients([]string{
"and@me.com",
"another@email.de",
"foobar@foobar.de",
@ -46,7 +46,7 @@ Signed-off-by: Linux Master <linux@linux-foundation.org>
"name@name.com",
"subsystem@reviewer.com",
"yetanother@email.org",
},
}, To),
Date: time.Date(2018, 5, 11, 16, 02, 14, 0, time.FixedZone("", -7*60*60)),
},
}
@ -70,7 +70,7 @@ Signed-off-by: Linux Master <linux@linux-foundation.org>
if com.Author != res.Author {
t.Fatalf("want author %q, got %q", com.Author, res.Author)
}
if diff := cmp.Diff(com.CC, res.CC); diff != "" {
if diff := cmp.Diff(com.Recipients, res.Recipients); diff != "" {
t.Fatalf("bad CC: %v", diff)
}
if !com.Date.Equal(res.Date) {

View File

@ -12,12 +12,12 @@ import (
"net/mail"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/google/syzkaller/pkg/email"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/prog"
)
@ -214,21 +214,22 @@ func (ctx *linux) Bisect(bad, good string, trace io.Writer, pred func() (BisectR
}
func (ctx *linux) addMaintainers(com *Commit) {
if len(com.CC) > 2 {
if len(com.Recipients) > 2 {
return
}
list := ctx.getMaintainers(com.Hash, false)
if len(list) < 3 {
list = ctx.getMaintainers(com.Hash, true)
mtrs := ctx.getMaintainers(com.Hash, false)
if len(mtrs) < 3 {
mtrs = ctx.getMaintainers(com.Hash, true)
}
com.CC = email.MergeEmailLists(com.CC, list)
com.Recipients = append(com.Recipients, mtrs...)
sort.Sort(com.Recipients)
}
func (ctx *linux) getMaintainers(hash string, blame bool) []string {
func (ctx *linux) getMaintainers(hash string, blame bool) Recipients {
// See #1441 re --git-min-percent.
args := "git show " + hash + " | " +
filepath.FromSlash("scripts/get_maintainer.pl") +
" --no-n --no-rolestats --git-min-percent=20"
" --git-min-percent=20"
if blame {
args += " --git-blame"
}
@ -236,15 +237,42 @@ func (ctx *linux) getMaintainers(hash string, blame bool) []string {
if err != nil {
return nil
}
var list []string
for _, line := range strings.Split(string(output), "\n") {
addr, err := mail.ParseAddress(line)
return ParseMaintainersLinux(output)
}
func ParseMaintainersLinux(text []byte) Recipients {
lines := strings.Split(string(text), "\n")
reRole := regexp.MustCompile(` \([^)]+\)$`)
var mtrs Recipients
// LMKL is To by default, but it changes to Cc if there's also a subsystem list.
lkmlType := To
foundLkml := false
for _, line := range lines {
role := reRole.FindString(line)
address := strings.Replace(line, role, "", 1)
addr, err := mail.ParseAddress(address)
if err != nil {
continue
}
list = append(list, strings.ToLower(addr.Address))
var roleType RecipientType
if addr.Address == "linux-kernel@vger.kernel.org" {
foundLkml = true
continue
} else if strings.Contains(role, "list") {
lkmlType = Cc
roleType = To
} else if strings.Contains(role, "maintainer") || strings.Contains(role, "supporter") {
roleType = To
} else {
roleType = Cc // Reviewer or other role; default to Cc.
}
mtrs = append(mtrs, RecipientInfo{*addr, roleType})
}
return list
if foundLkml {
mtrs = append(mtrs, RecipientInfo{mail.Address{Address: "linux-kernel@vger.kernel.org"}, lkmlType})
}
sort.Sort(mtrs)
return mtrs
}
const configBisectTag = "# Minimized by syzkaller"

View File

@ -8,13 +8,66 @@ import (
"bytes"
"fmt"
"io"
"net/mail"
"regexp"
"sort"
"strings"
"time"
"github.com/google/syzkaller/dashboard/dashapi"
"github.com/google/syzkaller/pkg/osutil"
)
type RecipientType int
const (
To RecipientType = iota
Cc
)
func (t RecipientType) String() string {
return [...]string{"To", "Cc"}[t]
}
type RecipientInfo struct {
Address mail.Address
Type RecipientType
}
type Recipients []RecipientInfo
func (r Recipients) GetEmails(filter RecipientType) []string {
emails := []string{}
for _, user := range r {
if user.Type == filter {
emails = append(emails, user.Address.Address)
}
}
sort.Strings(emails)
return emails
}
func NewRecipients(emails []string, t RecipientType) Recipients {
r := Recipients{}
for _, e := range emails {
r = append(r, RecipientInfo{mail.Address{Address: e}, t})
}
sort.Sort(r)
return r
}
func (r Recipients) Len() int { return len(r) }
func (r Recipients) Less(i, j int) bool { return r[i].Address.Address < r[j].Address.Address }
func (r Recipients) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r Recipients) ToDash() dashapi.Recipients {
d := dashapi.Recipients{}
for _, user := range r {
d = append(d, dashapi.RecipientInfo{Address: user.Address, Type: dashapi.RecipientType(user.Type)})
}
return d
}
type Repo interface {
// Poll checkouts the specified repository/branch.
// This involves fetching/resetting/cloning as necessary to recover from all possible problems.
@ -78,7 +131,7 @@ type Commit struct {
Title string
Author string
AuthorName string
CC []string
Recipients Recipients
Tags []string
Parents []string
Date time.Time

View File

@ -4,7 +4,10 @@
package vcs
import (
"net/mail"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestCanonicalizeCommit(t *testing.T) {
@ -155,3 +158,37 @@ func TestCommitLink(t *testing.T) {
}
}
}
func TestParse(t *testing.T) {
// nolint: lll
test1 := []byte(`Foo (Maintainer) Bar <a@email.com> (maintainer:KERNEL)
Foo Bar(Reviewer) <b@email.com> (reviewer:KERNEL)
<somelist@list.com> (open list:FOO)
"Supporter Foo" <c@email.com> (supporter:KERNEL)
linux-kernel@vger.kernel.org (open list)`)
// nolint: lll
test2 := []byte(`Foo (Maintainer) Bar <a@email.com> (maintainer:KERNEL)
Foo Bar(Reviewer) <b@email.com> (reviewer:KERNEL)
"Supporter Foo" <c@email.com> (supporter:KERNEL)
linux-kernel@vger.kernel.org (open list)`)
maintainers1 := Recipients{{mail.Address{Name: "Foo (Maintainer) Bar", Address: "a@email.com"}, To},
{mail.Address{Name: "Foo Bar(Reviewer)", Address: "b@email.com"}, Cc},
{mail.Address{Name: "Supporter Foo", Address: "c@email.com"}, To},
{mail.Address{Name: "", Address: "linux-kernel@vger.kernel.org"}, Cc},
{mail.Address{Name: "", Address: "somelist@list.com"}, To}}
maintainers2 := Recipients{{mail.Address{Name: "Foo (Maintainer) Bar", Address: "a@email.com"}, To},
{mail.Address{Name: "Foo Bar(Reviewer)", Address: "b@email.com"}, Cc},
{mail.Address{Name: "Supporter Foo", Address: "c@email.com"}, To},
{mail.Address{Name: "", Address: "linux-kernel@vger.kernel.org"}, To}}
if diff := cmp.Diff(ParseMaintainersLinux(test1), maintainers1); diff != "" {
t.Fatal(diff)
}
if diff := cmp.Diff(ParseMaintainersLinux(test2), maintainers2); diff != "" {
t.Fatal(diff)
}
if diff := cmp.Diff(ParseMaintainersLinux([]byte("")), Recipients(nil)); diff != "" {
t.Fatal(diff)
}
}

View File

@ -438,7 +438,7 @@ func (jp *JobProcessor) bisect(job *Job, mgrcfg *mgrconfig.Config) error {
Title: com.Title,
Author: com.Author,
AuthorName: com.AuthorName,
CC: com.CC,
Recipients: com.Recipients.ToDash(),
Date: com.Date,
})
}
@ -469,7 +469,7 @@ func (jp *JobProcessor) bisect(job *Job, mgrcfg *mgrconfig.Config) error {
resp.CrashReport = res.Report.Report
resp.CrashLog = res.Report.Output
if len(resp.Commits) != 0 {
resp.Commits[0].CC = append(resp.Commits[0].CC, res.Report.Maintainers...)
resp.Commits[0].Recipients = append(resp.Commits[0].Recipients, res.Report.Recipients.ToDash()...)
} else {
// If there is a report and there is no commit, it means a crash
// occurred on HEAD(for BisectFix) and oldest tested release(for BisectCause).

View File

@ -313,7 +313,7 @@ func (mgr *Manager) build(kernelCommit *vcs.Commit) error {
case *build.KernelError:
rep.Report = err1.Report
rep.Output = err1.Output
rep.Maintainers = err1.Maintainers
rep.Recipients = err1.Recipients
case *osutil.VerboseError:
rep.Report = []byte(err1.Title)
rep.Output = err1.Output
@ -440,11 +440,11 @@ func (mgr *Manager) reportBuildError(rep *report.Report, info *BuildInfo, imageD
req := &dashapi.BuildErrorReq{
Build: *build,
Crash: dashapi.Crash{
Title: rep.Title,
Corrupted: false, // Otherwise they get merged with other corrupted reports.
Maintainers: rep.Maintainers,
Log: rep.Output,
Report: rep.Report,
Title: rep.Title,
Corrupted: false, // Otherwise they get merged with other corrupted reports.
Recipients: rep.Recipients.ToDash(),
Log: rep.Output,
Report: rep.Report,
},
}
return mgr.dash.ReportBuildError(req)

View File

@ -645,12 +645,12 @@ func (mgr *Manager) saveCrash(crash *Crash) bool {
return true
}
dc := &dashapi.Crash{
BuildID: mgr.cfg.Tag,
Title: crash.Title,
Corrupted: crash.Corrupted,
Maintainers: crash.Maintainers,
Log: crash.Output,
Report: crash.Report.Report,
BuildID: mgr.cfg.Tag,
Title: crash.Title,
Corrupted: crash.Corrupted,
Recipients: crash.Recipients.ToDash(),
Log: crash.Output,
Report: crash.Report.Report,
}
resp, err := mgr.dash.ReportCrash(dc)
if err != nil {
@ -811,14 +811,14 @@ func (mgr *Manager) saveRepro(res *repro.Result, stats *repro.Stats, hub bool) {
// so maybe corrupted report detection is broken.
// 3. Reproduction is expensive so it's good to persist the result.
dc := &dashapi.Crash{
BuildID: mgr.cfg.Tag,
Title: res.Report.Title,
Maintainers: res.Report.Maintainers,
Log: res.Report.Output,
Report: res.Report.Report,
ReproOpts: res.Opts.Serialize(),
ReproSyz: res.Prog.Serialize(),
ReproC: cprogText,
BuildID: mgr.cfg.Tag,
Title: res.Report.Title,
Recipients: res.Report.Recipients.ToDash(),
Log: res.Report.Output,
Report: res.Report.Report,
ReproOpts: res.Opts.Serialize(),
ReproSyz: res.Prog.Serialize(),
ReproC: cprogText,
}
if _, err := mgr.dash.ReportCrash(dc); err != nil {
log.Logf(0, "failed to report repro to dashboard: %v", err)

View File

@ -15,6 +15,7 @@ import (
"github.com/google/syzkaller/pkg/mgrconfig"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/report"
"github.com/google/syzkaller/pkg/vcs"
)
var (
@ -69,7 +70,8 @@ func main() {
}
fmt.Printf("TITLE: %v\n", rep.Title)
fmt.Printf("CORRUPTED: %v (%v)\n", rep.Corrupted, rep.CorruptedReason)
fmt.Printf("MAINTAINERS: %v\n", rep.Maintainers)
fmt.Printf("MAINTAINERS (TO): %v\n", rep.Recipients.GetEmails(vcs.To))
fmt.Printf("MAINTAINERS (CC): %v\n", rep.Recipients.GetEmails(vcs.Cc))
fmt.Printf("\n")
os.Stdout.Write(rep.Report)
fmt.Printf("\n\n")