github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/pkg/triage/commit.go (about)

     1  // Copyright 2024 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 triage
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/google/syzkaller/pkg/debugtracer"
    10  	"github.com/google/syzkaller/pkg/vcs"
    11  	"github.com/google/syzkaller/syz-cluster/pkg/api"
    12  )
    13  
    14  // TODO: Some further improvements:
    15  //   1. Consider the blob hashes incorporated into the git diff. These may restrict the set of base commits.
    16  //   2. Add support for experimental sessions: these may be way behind the current HEAD.
    17  
    18  type TreeOps interface {
    19  	HeadCommit(tree *api.Tree) (*vcs.Commit, error)
    20  	ApplySeries(commit string, patches [][]byte) error
    21  }
    22  
    23  type CommitSelector struct {
    24  	ops    TreeOps
    25  	tracer debugtracer.DebugTracer
    26  }
    27  
    28  func NewCommitSelector(ops TreeOps, tracer debugtracer.DebugTracer) *CommitSelector {
    29  	return &CommitSelector{ops: ops, tracer: tracer}
    30  }
    31  
    32  type SelectResult struct {
    33  	Commit string
    34  	Reason string // Set if Commit is empty.
    35  }
    36  
    37  const (
    38  	reasonSeriesTooOld = "series lags behind the current HEAD too much"
    39  	reasonNotApplies   = "series does not apply"
    40  )
    41  
    42  // Select returns the best matching commit hash.
    43  func (cs *CommitSelector) Select(series *api.Series, tree *api.Tree, lastBuild *api.Build) (SelectResult, error) {
    44  	head, err := cs.ops.HeadCommit(tree)
    45  	if err != nil || head == nil {
    46  		return SelectResult{}, err
    47  	}
    48  	cs.tracer.Log("current HEAD: %q (commit date: %v)", head.Hash, head.CommitDate)
    49  	// If the series is already too old, it may be incompatible even if it applies cleanly.
    50  	const seriesLagsBehind = time.Hour * 24 * 7
    51  	if diff := head.CommitDate.Sub(series.PublishedAt); series.PublishedAt.Before(head.CommitDate) &&
    52  		diff > seriesLagsBehind {
    53  		cs.tracer.Log("the series is too old: %v before the HEAD", diff)
    54  		return SelectResult{Reason: reasonSeriesTooOld}, nil
    55  	}
    56  
    57  	// Algorithm:
    58  	// 1. If the last successful build is sufficiently new, prefer it over the last master.
    59  	// We should it be renewing it regularly, so the commit should be quite up to date.
    60  	// 2. If the last build is too old / the series does not apply, give a chance to the
    61  	// current HEAD.
    62  
    63  	var hashes []string
    64  	if lastBuild != nil {
    65  		// Check if the commit is still good enough.
    66  		if diff := head.CommitDate.Sub(lastBuild.CommitDate); diff > seriesLagsBehind {
    67  			cs.tracer.Log("the last successful build is already too old: %v, skipping", diff)
    68  		} else {
    69  			hashes = append(hashes, lastBuild.CommitHash)
    70  		}
    71  	}
    72  	for _, hash := range append(hashes, head.Hash) {
    73  		cs.tracer.Log("considering %q", hash)
    74  		err := cs.ops.ApplySeries(hash, series.PatchBodies())
    75  		if err == nil {
    76  			cs.tracer.Log("series can be applied to %q", hash)
    77  			return SelectResult{Commit: hash}, nil
    78  		} else {
    79  			cs.tracer.Log("failed to apply to %q: %v", hash, err)
    80  		}
    81  	}
    82  	return SelectResult{Reason: reasonNotApplies}, nil
    83  }