diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2019-03-12 12:22:13 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2019-03-17 18:06:44 +0100 |
| commit | 5ed211ca96c6e6ab96fad7b5a495d81178ee98eb (patch) | |
| tree | 352711e48e64791cda1615ca2bd6611588a23209 | |
| parent | 5958caeafcf3d467009ae984a9be8302bf852a50 (diff) | |
pkg/vcs: refactor bisection support
In preparation for syz-ci bisection:
- move bisection function into a separate interface
they look out of place in vcs.Repo because most OSes
don't implement it and most users don't case
- extract author name and more CC emails for commits
- move linux-specific PreviousReleaseTags into linux.go
- fix inconclusive bisection (more than 1 potential commits)
- add tests fr bisection
- add maintainers returned from get_maintainers.pl for commits
that don't have enough emails (e.g. only author email)
Update #501
| -rw-r--r-- | pkg/bisect/bisect.go | 13 | ||||
| -rw-r--r-- | pkg/vcs/akaros.go | 18 | ||||
| -rw-r--r-- | pkg/vcs/freebsd.go | 2 | ||||
| -rw-r--r-- | pkg/vcs/fuchsia.go | 11 | ||||
| -rw-r--r-- | pkg/vcs/git.go | 120 | ||||
| -rw-r--r-- | pkg/vcs/git_repo_test.go | 147 | ||||
| -rw-r--r-- | pkg/vcs/git_test.go | 19 | ||||
| -rw-r--r-- | pkg/vcs/linux.go | 154 | ||||
| -rw-r--r-- | pkg/vcs/netbsd.go | 19 | ||||
| -rw-r--r-- | pkg/vcs/openbsd.go | 19 | ||||
| -rw-r--r-- | pkg/vcs/vcs.go | 40 |
11 files changed, 414 insertions, 148 deletions
diff --git a/pkg/bisect/bisect.go b/pkg/bisect/bisect.go index df5dfcd90..10a2eff9e 100644 --- a/pkg/bisect/bisect.go +++ b/pkg/bisect/bisect.go @@ -136,7 +136,7 @@ func (env *env) bisect() (*vcs.Commit, error) { if good == "" { return nil, nil // still not fixed } - return env.repo.Bisect(bad, good, cfg.Trace, func() (vcs.BisectResult, error) { + commits, err := env.repo.(vcs.Bisecter).Bisect(bad, good, cfg.Trace, func() (vcs.BisectResult, error) { res, err := env.test() if cfg.Fix { if res == vcs.BisectBad { @@ -147,6 +147,13 @@ func (env *env) bisect() (*vcs.Commit, error) { } return res, err }) + if err != nil { + return nil, err + } + if len(commits) == 1 { + return commits[0], nil + } + return nil, nil } func (env *env) commitRange() (*vcs.Commit, string, string, error) { @@ -173,7 +180,7 @@ func (env *env) commitRangeForFix() (*vcs.Commit, string, string, error) { func (env *env) commitRangeForBug() (*vcs.Commit, string, string, error) { cfg := env.cfg - tags, err := env.repo.PreviousReleaseTags(cfg.Kernel.Commit) + tags, err := env.repo.(vcs.Bisecter).PreviousReleaseTags(cfg.Kernel.Commit) if err != nil { return nil, "", "", err } @@ -312,7 +319,7 @@ func (env *env) processResults(current *vcs.Commit, results []error) (bad, good // Note: linux-specific. func (env *env) buildEnvForCommit(commit string) (*buildEnv, error) { cfg := env.cfg - tags, err := env.repo.PreviousReleaseTags(commit) + tags, err := env.repo.(vcs.Bisecter).PreviousReleaseTags(commit) if err != nil { return nil, err } diff --git a/pkg/vcs/akaros.go b/pkg/vcs/akaros.go index 8d8d640e6..0d3e701fb 100644 --- a/pkg/vcs/akaros.go +++ b/pkg/vcs/akaros.go @@ -4,8 +4,6 @@ package vcs import ( - "fmt" - "io" "path/filepath" ) @@ -16,8 +14,8 @@ type akaros struct { func newAkaros(vm, dir string) *akaros { return &akaros{ - git: newGit(dir), - dropbear: newGit(filepath.Join(dir, "dropbear")), + git: newGit(dir, nil), + dropbear: newGit(filepath.Join(dir, "dropbear"), nil), } } @@ -27,15 +25,3 @@ func (ctx *akaros) Poll(repo, branch string) (*Commit, error) { } return ctx.git.Poll(repo, branch) } - -func (ctx *akaros) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) { - return ctx.git.ExtractFixTagsFromCommits(baseCommit, email) -} - -func (ctx *akaros) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) { - return nil, fmt.Errorf("not implemented for akaros") -} - -func (ctx *akaros) PreviousReleaseTags(commit string) ([]string, error) { - return nil, fmt.Errorf("not implemented for akaros") -} diff --git a/pkg/vcs/freebsd.go b/pkg/vcs/freebsd.go index 6ef7858ce..f007ab69f 100644 --- a/pkg/vcs/freebsd.go +++ b/pkg/vcs/freebsd.go @@ -14,7 +14,7 @@ type freebsd struct { func newFreeBSD(vm, dir string) *freebsd { return &freebsd{ - git: newGit(dir), + git: newGit(dir, nil), } } diff --git a/pkg/vcs/fuchsia.go b/pkg/vcs/fuchsia.go index 07626cd5e..c04404416 100644 --- a/pkg/vcs/fuchsia.go +++ b/pkg/vcs/fuchsia.go @@ -22,7 +22,7 @@ func newFuchsia(vm, dir string) *fuchsia { return &fuchsia{ vm: vm, dir: dir, - zircon: newGit(filepath.Join(dir, "zircon")), + zircon: newGit(filepath.Join(dir, "zircon"), nil), } } @@ -91,10 +91,7 @@ func (ctx *fuchsia) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Comm return ctx.zircon.ExtractFixTagsFromCommits(baseCommit, email) } -func (ctx *fuchsia) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) { - return nil, fmt.Errorf("not implemented for fuchsia") -} - -func (ctx *fuchsia) PreviousReleaseTags(commit string) ([]string, error) { - return nil, fmt.Errorf("not implemented for fuchsia") +func (ctx *fuchsia) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, interface{}, error)) ( + *Commit, interface{}, error) { + return nil, nil, fmt.Errorf("not implemented for fuchsia") } diff --git a/pkg/vcs/git.go b/pkg/vcs/git.go index 4f23bb16c..210c5b154 100644 --- a/pkg/vcs/git.go +++ b/pkg/vcs/git.go @@ -11,22 +11,25 @@ import ( "net/mail" "os" "os/exec" + "regexp" "sort" - "strconv" "strings" "time" "github.com/google/syzkaller/pkg/hash" + "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" ) type git struct { - dir string + dir string + ignoreCC map[string]bool } -func newGit(dir string) *git { +func newGit(dir string, ignoreCC map[string]bool) *git { return &git{ - dir: dir, + dir: dir, + ignoreCC: ignoreCC, } } @@ -66,7 +69,7 @@ func (git *git) CheckoutBranch(repo, branch string) (*Commit, error) { dir := git.dir runSandboxed(dir, "git", "bisect", "reset") if _, err := runSandboxed(dir, "git", "reset", "--hard"); err != nil { - if err := git.initRepo(); err != nil { + if err := git.initRepo(err); err != nil { return nil, err } } @@ -84,7 +87,7 @@ func (git *git) CheckoutCommit(repo, commit string) (*Commit, error) { dir := git.dir runSandboxed(dir, "git", "bisect", "reset") if _, err := runSandboxed(dir, "git", "reset", "--hard"); err != nil { - if err := git.initRepo(); err != nil { + if err := git.initRepo(err); err != nil { return nil, err } } @@ -111,7 +114,7 @@ func (git *git) SwitchCommit(commit string) (*Commit, error) { } func (git *git) clone(repo, branch string) error { - if err := git.initRepo(); err != nil { + if err := git.initRepo(nil); err != nil { return err } if _, err := runSandboxed(git.dir, "git", "remote", "add", "origin", repo); err != nil { @@ -123,7 +126,10 @@ func (git *git) clone(repo, branch string) error { return nil } -func (git *git) initRepo() error { +func (git *git) initRepo(reason error) error { + if reason != nil { + log.Logf(1, "git: initializing repo at %v: %v", git.dir, reason) + } if err := os.RemoveAll(git.dir); err != nil { return fmt.Errorf("failed to remove repo dir: %v", err) } @@ -144,27 +150,27 @@ func (git *git) HeadCommit() (*Commit, error) { } func (git *git) getCommit(commit string) (*Commit, error) { - output, err := runSandboxed(git.dir, "git", "log", "--format=%H%n%s%n%ae%n%ad%n%b", "-n", "1", commit) + output, err := runSandboxed(git.dir, "git", "log", "--format=%H%n%s%n%ae%n%an%n%ad%n%b", "-n", "1", commit) if err != nil { return nil, err } - return gitParseCommit(output, nil, nil) + return gitParseCommit(output, nil, nil, git.ignoreCC) } -func gitParseCommit(output, user, domain []byte) (*Commit, error) { +func gitParseCommit(output, user, domain []byte, ignoreCC map[string]bool) (*Commit, error) { lines := bytes.Split(output, []byte{'\n'}) if len(lines) < 4 || len(lines[0]) != 40 { return nil, fmt.Errorf("unexpected git log output: %q", output) } const dateFormat = "Mon Jan 2 15:04:05 2006 -0700" - date, err := time.Parse(dateFormat, string(lines[3])) + date, err := time.Parse(dateFormat, string(lines[4])) 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 var tags []string - for _, line := range lines[4:] { + for _, line := range lines[5:] { if user != nil { userPos := bytes.Index(line, user) if userPos != -1 { @@ -195,7 +201,11 @@ func gitParseCommit(output, user, domain []byte) (*Commit, error) { if err != nil { break } - cc[strings.ToLower(addr.Address)] = true + email := strings.ToLower(addr.Address) + if ignoreCC[email] { + continue + } + cc[email] = true break } } @@ -205,12 +215,13 @@ func gitParseCommit(output, user, domain []byte) (*Commit, error) { } sort.Strings(sortedCC) com := &Commit{ - Hash: string(lines[0]), - Title: string(lines[1]), - Author: string(lines[2]), - CC: sortedCC, - Tags: tags, - Date: date, + Hash: string(lines[0]), + Title: string(lines[1]), + Author: string(lines[2]), + AuthorName: string(lines[3]), + CC: sortedCC, + Tags: tags, + Date: date, } return com, nil } @@ -276,7 +287,7 @@ func (git *git) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, func (git *git) fetchCommits(since, base, user, domain string, greps []string, fixedStrings bool) ([]*Commit, error) { const commitSeparator = "---===syzkaller-commit-separator===---" - args := []string{"log", "--since", since, "--format=%H%n%s%n%ae%n%ad%n%b%n" + commitSeparator} + args := []string{"log", "--since", since, "--format=%H%n%s%n%ae%n%an%n%ad%n%b%n" + commitSeparator} if fixedStrings { args = append(args, "--fixed-strings") } @@ -314,7 +325,7 @@ func (git *git) fetchCommits(since, base, user, domain string, greps []string, f buf.WriteByte('\n') continue } - com, err := gitParseCommit(buf.Bytes(), userBytes, domainBytes) + com, err := gitParseCommit(buf.Bytes(), userBytes, domainBytes, git.ignoreCC) if err != nil { return nil, err } @@ -343,7 +354,7 @@ func splitEmail(email string) (user, domain string, err error) { return } -func (git *git) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) { +func (git *git) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) ([]*Commit, error) { dir := git.dir runSandboxed(dir, "git", "bisect", "reset") runSandboxed(dir, "git", "reset", "--hard") @@ -375,59 +386,44 @@ func (git *git) Bisect(bad, good string, trace io.Writer, pred func() (BisectRes firstBad = current } output, err = runSandboxed(dir, "git", "bisect", bisectTerms[res]) + fmt.Fprintf(trace, "# git bisect %v %v\n%s", bisectTerms[res], current.Hash, output) if err != nil { + if bytes.Contains(output, []byte("There are only 'skip'ped commits left to test")) { + return git.bisectInconclusive(output) + } return nil, err } - fmt.Fprintf(trace, "# git bisect %v %v\n%s", bisectTerms[res], current.Hash, output) next, err := git.HeadCommit() if err != nil { return nil, err } if current.Hash == next.Hash { - return firstBad, nil + return []*Commit{firstBad}, nil } current = next } } -// Note: linux-specific. -func (git *git) PreviousReleaseTags(commit string) ([]string, error) { - output, err := runSandboxed(git.dir, "git", "tag", "--no-contains", commit, "--merged", commit, "v*.*") - if err != nil { - return nil, err - } - return gitParseReleaseTags(output) -} - -func gitParseReleaseTags(output []byte) ([]string, error) { - var tags []string - for _, tag := range bytes.Split(output, []byte{'\n'}) { - if releaseTagRe.Match(tag) && gitReleaseTagToInt(string(tag)) != 0 { - tags = append(tags, string(tag)) - } - } - sort.Slice(tags, func(i, j int) bool { - return gitReleaseTagToInt(tags[i]) > gitReleaseTagToInt(tags[j]) - }) - return tags, nil -} - -func gitReleaseTagToInt(tag string) uint64 { - matches := releaseTagRe.FindStringSubmatchIndex(tag) - v1, err := strconv.ParseUint(tag[matches[2]:matches[3]], 10, 64) - if err != nil { - return 0 - } - v2, err := strconv.ParseUint(tag[matches[4]:matches[5]], 10, 64) - if err != nil { - return 0 - } - var v3 uint64 - if matches[6] != -1 { - v3, err = strconv.ParseUint(tag[matches[6]:matches[7]], 10, 64) +func (git *git) bisectInconclusive(output []byte) ([]*Commit, error) { + // For inconclusive bisection git prints the following message: + // + // There are only 'skip'ped commits left to test. + // The first bad commit could be any of: + // 1f43f400a2cbb02f3d34de8fe30075c070254816 + // 4d96e13ee9cd1f7f801e8c7f4b12f09d1da4a5d8 + // 5cd856a5ef9aa189df757c322be34ad735a5b17f + // We cannot bisect more! + // + // For conclusive bisection: + // + // 7c3850adbcccc2c6c9e7ab23a7dcbc4926ee5b96 is the first bad commit + var commits []*Commit + for _, hash := range regexp.MustCompile("[a-f0-9]{40}").FindAll(output, -1) { + com, err := git.getCommit(string(hash)) if err != nil { - return 0 + return nil, err } + commits = append(commits, com) } - return v1*1e6 + v2*1e3 + v3 + return commits, nil } diff --git a/pkg/vcs/git_repo_test.go b/pkg/vcs/git_repo_test.go index f60bfc737..6e8018dfc 100644 --- a/pkg/vcs/git_repo_test.go +++ b/pkg/vcs/git_repo_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "sort" "testing" "time" @@ -22,6 +23,7 @@ func init() { const ( userEmail = `test@syzkaller.com` + userName = `Test Syzkaller` extractFixTagsEmail = `"syzbot" <syzbot@my.mail.com>` ) @@ -34,7 +36,7 @@ func TestGitRepo(t *testing.T) { defer os.RemoveAll(baseDir) repo1 := createTestRepo(t, baseDir, "repo1") repo2 := createTestRepo(t, baseDir, "repo2") - repo := newGit(filepath.Join(baseDir, "repo")) + repo := newGit(filepath.Join(baseDir, "repo"), nil) { com, err := repo.Poll(repo1.dir, "master") if err != nil { @@ -165,6 +167,9 @@ func checkCommit(t *testing.T, idx int, test testCommit, com *Commit, checkTags if test.author != com.Author { t.Errorf("#%v: want author %q, got %q", idx, test.author, com.Author) } + 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) t.Error(diff) @@ -273,11 +278,142 @@ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> `, title: "USB: fix usbmon BUG trigger", author: userEmail, - cc: []string{userEmail}, + cc: []string{"gregkh@linuxfoundation.org", userEmail, "zaitcev@redhat.com"}, tags: []string{"f9831b881b3e849829fc"}, }, } +func TestBisect(t *testing.T) { + t.Parallel() + repoDir, err := ioutil.TempDir("", "syz-git-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(repoDir) + repo := makeTestRepo(t, repoDir) + var commits []string + for i := 0; i < 5; i++ { + repo.commitChange(fmt.Sprintf("commit %v", i)) + com, err := repo.repo.HeadCommit() + if err != nil { + t.Fatal(err) + } + commits = append(commits, com.Hash) + t.Logf("%v %v", com.Hash, com.Title) + } + type Test struct { + pred func() (BisectResult, error) + result []string + } + tests := []Test{ + { + // All are bad. + func() (BisectResult, error) { + return BisectBad, nil + }, + []string{commits[1]}, + }, + { + // All are good. + func() (BisectResult, error) { + return BisectGood, nil + }, + []string{commits[4]}, + }, + { + // All are skipped. + func() (BisectResult, error) { + return BisectSkip, nil + }, + []string{commits[1], commits[2], commits[3], commits[4]}, + }, + { + // Some are skipped. + func() (BisectResult, error) { + current, err := repo.repo.HeadCommit() + if err != nil { + t.Fatal(err) + } + switch current.Hash { + case commits[1]: + return BisectSkip, nil + case commits[2]: + return BisectSkip, nil + case commits[3]: + return BisectGood, nil + default: + return 0, fmt.Errorf("unknown commit %v", current.Hash) + } + }, + []string{commits[4]}, + }, + { + // Some are skipped. + func() (BisectResult, error) { + current, err := repo.repo.HeadCommit() + if err != nil { + t.Fatal(err) + } + switch current.Hash { + case commits[1]: + return BisectGood, nil + case commits[2]: + return BisectSkip, nil + case commits[3]: + return BisectBad, nil + default: + return 0, fmt.Errorf("unknown commit %v", current.Hash) + } + }, + []string{commits[2], commits[3]}, + }, + { + // Some are skipped. + func() (BisectResult, error) { + current, err := repo.repo.HeadCommit() + if err != nil { + t.Fatal(err) + } + switch current.Hash { + case commits[1]: + return BisectSkip, nil + case commits[2]: + return BisectSkip, nil + case commits[3]: + return BisectGood, nil + default: + return 0, fmt.Errorf("unknown commit %v", current.Hash) + } + }, + []string{commits[4]}, + }, + } + for i, test := range tests { + t.Logf("TEST %v", i) + result, err := repo.repo.Bisect(commits[4], commits[0], (*testWriter)(t), test.pred) + if err != nil { + t.Fatal(err) + } + var got []string + for _, com := range result { + got = append(got, com.Hash) + } + sort.Strings(got) // git result order is non-deterministic (wat) + sort.Strings(test.result) + if diff := cmp.Diff(test.result, got); diff != "" { + t.Logf("result: %+v", got) + t.Fatal(diff) + } + } +} + +type testWriter testing.T + +func (t *testWriter) Write(data []byte) (int, error) { + (*testing.T)(t).Log(string(data)) + return len(data), nil +} + func createTestRepo(t *testing.T, baseDir, name string) *testRepo { repo := makeTestRepo(t, filepath.Join(baseDir, name)) repo.git("checkout", "-b", "master") @@ -304,16 +440,19 @@ func makeTestRepo(t *testing.T, dir string) *testRepo { if err := osutil.MkdirAll(dir); err != nil { t.Fatal(err) } + ignoreCC := map[string]bool{ + "stable@vger.kernel.org": true, + } repo := &testRepo{ t: t, dir: dir, name: filepath.Base(dir), commits: make(map[string]map[string]*Commit), - repo: newGit(dir), + repo: newGit(dir, ignoreCC), } repo.git("init") repo.git("config", "--add", "user.email", userEmail) - repo.git("config", "--add", "user.name", "Test Syzkaller") + repo.git("config", "--add", "user.name", userName) return repo } diff --git a/pkg/vcs/git_test.go b/pkg/vcs/git_test.go index a22835a69..b2ad32b6b 100644 --- a/pkg/vcs/git_test.go +++ b/pkg/vcs/git_test.go @@ -7,6 +7,8 @@ import ( "reflect" "testing" "time" + + "github.com/google/go-cmp/cmp" ) func TestGitParseCommit(t *testing.T) { @@ -14,6 +16,7 @@ func TestGitParseCommit(t *testing.T) { `2075b16e32c26e4031b9fd3cbe26c54676a8fcb5 rbtree: include rcu.h foobar@foobar.de +Foo Bar Fri May 11 16:02:14 2018 -0700 Since commit c1adf20052d8 ("Introduce rb_replace_node_rcu()") rbtree_augmented.h uses RCU related data structures but does not include @@ -30,14 +33,18 @@ Reported-and-Tested-by: Name-name <name@name.com> Tested-by: Must be correct <mustbe@correct.com> Signed-off-by: Linux Master <linux@linux-foundation.org> `: { - Hash: "2075b16e32c26e4031b9fd3cbe26c54676a8fcb5", - Title: "rbtree: include rcu.h", - Author: "foobar@foobar.de", + Hash: "2075b16e32c26e4031b9fd3cbe26c54676a8fcb5", + Title: "rbtree: include rcu.h", + Author: "foobar@foobar.de", + AuthorName: "Foo Bar", CC: []string{ "and@me.com", + "another@email.de", "foobar@foobar.de", + "linux@linux-foundation.org", "mustbe@correct.com", "name@name.com", + "somewhere@email.com", "subsystem@reviewer.com", "yetanother@email.org", }, @@ -45,7 +52,7 @@ Signed-off-by: Linux Master <linux@linux-foundation.org> }, } for input, com := range tests { - res, err := gitParseCommit([]byte(input), nil, nil) + res, err := gitParseCommit([]byte(input), nil, nil, nil) if err != nil && com != nil { t.Fatalf("want %+v, got error: %v", com, err) } @@ -64,8 +71,8 @@ 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 !reflect.DeepEqual(com.CC, res.CC) { - t.Fatalf("want CC %q, got %q", com.CC, res.CC) + if diff := cmp.Diff(com.CC, res.CC); diff != "" { + t.Fatalf("bad CC: %v", diff) } if !com.Date.Equal(res.Date) { t.Fatalf("want date %v, got %v", com.Date, res.Date) diff --git a/pkg/vcs/linux.go b/pkg/vcs/linux.go new file mode 100644 index 000000000..f8d97d60e --- /dev/null +++ b/pkg/vcs/linux.go @@ -0,0 +1,154 @@ +// Copyright 2019 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package vcs + +import ( + "bytes" + "io" + "net/mail" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/google/syzkaller/pkg/email" + "github.com/google/syzkaller/pkg/osutil" +) + +type linux struct { + *git +} + +func newLinux(dir string) *linux { + ignoreCC := map[string]bool{ + "stable@vger.kernel.org": true, + } + return &linux{ + git: newGit(dir, ignoreCC), + } +} + +func (ctx *linux) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) ([]*Commit, error) { + commits, err := ctx.git.Bisect(bad, good, trace, pred) + if len(commits) == 1 { + ctx.addMaintainers(commits[0]) + } + return commits, err +} + +func (ctx *linux) addMaintainers(com *Commit) { + if len(com.CC) > 3 { + return + } + list := ctx.getMaintainers(com.Hash, false) + if len(list) < 3 { + list = ctx.getMaintainers(com.Hash, true) + } + com.CC = email.MergeEmailLists(com.CC, list) +} + +func (ctx *linux) getMaintainers(hash string, blame bool) []string { + args := "git show " + hash + " | " + + filepath.FromSlash("scripts/get_maintainer.pl") + " --no-n --no-rolestats" + if blame { + args += " --git-blame" + } + output, err := osutil.RunCmd(time.Minute, ctx.git.dir, "bash", "-c", args) + if err != nil { + return nil + } + var list []string + for _, line := range strings.Split(string(output), "\n") { + addr, err := mail.ParseAddress(line) + if err != nil { + continue + } + list = append(list, strings.ToLower(addr.Address)) + } + return list +} + +func (ctx *linux) PreviousReleaseTags(commit string) ([]string, error) { + output, err := runSandboxed(ctx.dir, "git", "tag", "--no-contains", commit, "--merged", commit, "v*.*") + if err != nil { + return nil, err + } + tags, err := gitParseReleaseTags(output) + if err != nil { + return nil, err + } + for i, tag := range tags { + if tag == "v3.8" { + // v3.8 does not work with modern perl, and as we go further in history + // make stops to work, then binutils, glibc, etc. So we stop at v3.8. + // Up to that point we only need an ancient gcc. + tags = tags[:i] + break + } + } + return tags, nil +} + +func gitParseReleaseTags(output []byte) ([]string, error) { + var tags []string + for _, tag := range bytes.Split(output, []byte{'\n'}) { + if releaseTagRe.Match(tag) && gitReleaseTagToInt(string(tag)) != 0 { + tags = append(tags, string(tag)) + } + } + sort.Slice(tags, func(i, j int) bool { + return gitReleaseTagToInt(tags[i]) > gitReleaseTagToInt(tags[j]) + }) + return tags, nil +} + +func gitReleaseTagToInt(tag string) uint64 { + matches := releaseTagRe.FindStringSubmatchIndex(tag) + v1, err := strconv.ParseUint(tag[matches[2]:matches[3]], 10, 64) + if err != nil { + return 0 + } + v2, err := strconv.ParseUint(tag[matches[4]:matches[5]], 10, 64) + if err != nil { + return 0 + } + var v3 uint64 + if matches[6] != -1 { + v3, err = strconv.ParseUint(tag[matches[6]:matches[7]], 10, 64) + if err != nil { + return 0 + } + } + return v1*1e6 + v2*1e3 + v3 +} + +func (ctx *linux) EnvForCommit(commit string, kernelConfig []byte) (*BisectEnv, error) { + tagList, err := ctx.PreviousReleaseTags(commit) + if err != nil { + return nil, err + } + tags := make(map[string]bool) + for _, tag := range tagList { + tags[tag] = true + } + env := &BisectEnv{ + Compiler: "gcc-" + linuxCompilerVersion(tags), + KernelConfig: kernelConfig, + } + return env, nil +} + +func linuxCompilerVersion(tags map[string]bool) string { + switch { + case tags["v4.12"]: + return "8.1.0" + case tags["v4.11"]: + return "7.3.0" + case tags["v3.19"]: + return "5.5.0" + default: + return "4.9.4" + } +} diff --git a/pkg/vcs/netbsd.go b/pkg/vcs/netbsd.go index b43523756..6b710f4ca 100644 --- a/pkg/vcs/netbsd.go +++ b/pkg/vcs/netbsd.go @@ -3,29 +3,12 @@ package vcs -import ( - "fmt" - "io" -) - type netbsd struct { *git } func newNetBSD(vm, dir string) *netbsd { return &netbsd{ - git: newGit(dir), + git: newGit(dir, nil), } } - -func (ctx *netbsd) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) { - return ctx.git.ExtractFixTagsFromCommits(baseCommit, email) -} - -func (ctx *netbsd) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) { - return nil, fmt.Errorf("not implemented for netbsd") -} - -func (ctx *netbsd) PreviousReleaseTags(commit string) ([]string, error) { - return nil, fmt.Errorf("not implemented for netbsd") -} diff --git a/pkg/vcs/openbsd.go b/pkg/vcs/openbsd.go index 3998ce58a..f97290f99 100644 --- a/pkg/vcs/openbsd.go +++ b/pkg/vcs/openbsd.go @@ -3,29 +3,12 @@ package vcs -import ( - "fmt" - "io" -) - type openbsd struct { *git } func newOpenBSD(vm, dir string) *openbsd { return &openbsd{ - git: newGit(dir), + git: newGit(dir, nil), } } - -func (ctx *openbsd) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) { - return ctx.git.ExtractFixTagsFromCommits(baseCommit, email) -} - -func (ctx *openbsd) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) { - return nil, fmt.Errorf("not implemented for openbsd") -} - -func (ctx *openbsd) PreviousReleaseTags(commit string) ([]string, error) { - return nil, fmt.Errorf("not implemented for openbsd") -} diff --git a/pkg/vcs/vcs.go b/pkg/vcs/vcs.go index 9d779410f..8026bbfd5 100644 --- a/pkg/vcs/vcs.go +++ b/pkg/vcs/vcs.go @@ -49,25 +49,32 @@ type Repo interface { // Given email = "user@domain.com", it searches for tags of the form "user+tag@domain.com" // and returns commits with these tags. ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) +} - // PreviousReleaseTags returns list of preceding release tags that are reachable from the given commit. - PreviousReleaseTags(commit string) ([]string, error) - +// Bisecter may be optionally implemented by Repo. +type Bisecter interface { // Bisect bisects good..bad commit range against the provided predicate (wrapper around git bisect). // The predicate should return an error only if there is no way to proceed // (it will abort the process), if possible it should prefer to return BisectSkip. // Progress of the process is streamed to the provided trace. - // Returns the first commit on which the predicate returns BisectBad. - Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) + // Returns the first commit on which the predicate returns BisectBad, + // or multiple commits if bisection is inconclusive due to BisectSkip. + Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) ([]*Commit, error) + + // PreviousReleaseTags returns list of preceding release tags that are reachable from the given commit. + PreviousReleaseTags(commit string) ([]string, error) + + EnvForCommit(commit string, kernelConfig []byte) (*BisectEnv, error) } type Commit struct { - Hash string - Title string - Author string - CC []string - Tags []string - Date time.Time + Hash string + Title string + Author string + AuthorName string + CC []string + Tags []string + Date time.Time } type BisectResult int @@ -78,10 +85,15 @@ const ( BisectSkip ) +type BisectEnv struct { + Compiler string + KernelConfig []byte +} + func NewRepo(os, vm, dir string) (Repo, error) { switch os { case "linux": - return newGit(dir), nil + return newLinux(dir), nil case "akaros": return newAkaros(vm, dir), nil case "fuchsia": @@ -97,7 +109,7 @@ func NewRepo(os, vm, dir string) (Repo, error) { } func NewSyzkallerRepo(dir string) Repo { - return newGit(dir) + return newGit(dir, nil) } func Patch(dir string, patch []byte) error { @@ -171,6 +183,8 @@ var ( regexp.MustCompile(`^[A-Za-z-]+\-and\-[Aa]cked\-.*: (.*)$`), regexp.MustCompile(`^Tested\-.*: (.*)$`), regexp.MustCompile(`^[A-Za-z-]+\-and\-[Tt]ested\-.*: (.*)$`), + regexp.MustCompile(`^Signed-off-by: (.*)$`), + regexp.MustCompile(`^C[Cc]: (.*)$`), } ) |
