aboutsummaryrefslogtreecommitdiffstats
path: root/syz-cluster/pkg/triage/commit.go
blob: 81c7f6150d4dc83633720aa680c019cc26e0841c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Copyright 2024 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 triage

import (
	"time"

	"github.com/google/syzkaller/pkg/debugtracer"
	"github.com/google/syzkaller/pkg/vcs"
	"github.com/google/syzkaller/syz-cluster/pkg/api"
)

// TODO: Some further improvements:
//   1. Consider the blob hashes incorporated into the git diff. These may restrict the set of base commits.
//   2. Add support for experimental sessions: these may be way behind the current HEAD.

type TreeOps interface {
	HeadCommit(tree *api.Tree) (*vcs.Commit, error)
	ApplySeries(commit string, patches [][]byte) error
}

type CommitSelector struct {
	ops    TreeOps
	tracer debugtracer.DebugTracer
}

func NewCommitSelector(ops TreeOps, tracer debugtracer.DebugTracer) *CommitSelector {
	return &CommitSelector{ops: ops, tracer: tracer}
}

type SelectResult struct {
	Commit string
	Reason string // Set if Commit is empty.
}

const (
	reasonSeriesTooOld = "series lags behind the current HEAD too much"
	reasonNotApplies   = "series does not apply"
)

// Select returns the best matching commit hash.
func (cs *CommitSelector) Select(series *api.Series, tree *api.Tree, lastBuild *api.Build) (SelectResult, error) {
	head, err := cs.ops.HeadCommit(tree)
	if err != nil || head == nil {
		return SelectResult{}, err
	}
	cs.tracer.Log("current HEAD: %q (commit date: %v)", head.Hash, head.CommitDate)
	// If the series is already too old, it may be incompatible even if it applies cleanly.
	const seriesLagsBehind = time.Hour * 24 * 7
	if diff := head.CommitDate.Sub(series.PublishedAt); series.PublishedAt.Before(head.CommitDate) &&
		diff > seriesLagsBehind {
		cs.tracer.Log("the series is too old: %v before the HEAD", diff)
		return SelectResult{Reason: reasonSeriesTooOld}, nil
	}

	// Algorithm:
	// 1. If the last successful build is sufficiently new, prefer it over the last master.
	// We should it be renewing it regularly, so the commit should be quite up to date.
	// 2. If the last build is too old / the series does not apply, give a chance to the
	// current HEAD.

	var hashes []string
	if lastBuild != nil {
		// Check if the commit is still good enough.
		if diff := head.CommitDate.Sub(lastBuild.CommitDate); diff > seriesLagsBehind {
			cs.tracer.Log("the last successful build is already too old: %v, skipping", diff)
		} else {
			hashes = append(hashes, lastBuild.CommitHash)
		}
	}
	for _, hash := range append(hashes, head.Hash) {
		cs.tracer.Log("considering %q", hash)
		err := cs.ops.ApplySeries(hash, series.PatchBodies())
		if err == nil {
			cs.tracer.Log("series can be applied to %q", hash)
			return SelectResult{Commit: hash}, nil
		} else {
			cs.tracer.Log("failed to apply to %q: %v", hash, err)
		}
	}
	return SelectResult{Reason: reasonNotApplies}, nil
}