go/src/internal/fuzz/queue.go
Jay Conrod 5bc273aca5 [dev.fuzz] internal/fuzz: minimize inputs that expand coverage
When a fuzz worker discovers an input that activates coverage counters
that weren't previously activated, it sends that input back to the
coordinator, as before. If the coordinator also finds that input
provides new coverage (that is, some other input hasn't won the race),
the coordinator now sends the input back to workers for minimization.

The minimization procedure now supports minimizing these interesting
inputs. It attempts to find smaller inputs that preserve at least one
new coverage bit. If minimization succeeds, the coordinator adds the
smaller input to the corpus instead of the original. If minimization
fails, the coordinator adds the original input. If minimization finds
that the original input didn't provide new coverage after all (for
example, a counter was activated by an unrelated background goroutine
and was considered flaky), the input is ignored and not recorded.

Change-Id: I81d98d6ec28abb0ac2a476f73480ceeaff674c08
Reviewed-on: https://go-review.googlesource.com/c/go/+/342997
Trust: Jay Conrod <jayconrod@google.com>
Trust: Katie Hockman <katie@golang.org>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
2021-09-02 17:58:51 +00:00

71 lines
1.5 KiB
Go

// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fuzz
// queue holds a growable sequence of inputs for fuzzing and minimization.
//
// For now, this is a simple ring buffer
// (https://en.wikipedia.org/wiki/Circular_buffer).
//
// TODO(golang.org/issue/46224): use a priotization algorithm based on input
// size, previous duration, coverage, and any other metrics that seem useful.
type queue struct {
// elems holds a ring buffer.
// The queue is empty when begin = end.
// The queue is full (until grow is called) when end = begin + N - 1 (mod N)
// where N = cap(elems).
elems []interface{}
head, len int
}
func (q *queue) cap() int {
return len(q.elems)
}
func (q *queue) grow() {
oldCap := q.cap()
newCap := oldCap * 2
if newCap == 0 {
newCap = 8
}
newElems := make([]interface{}, newCap)
oldLen := q.len
for i := 0; i < oldLen; i++ {
newElems[i] = q.elems[(q.head+i)%oldCap]
}
q.elems = newElems
q.head = 0
}
func (q *queue) enqueue(e interface{}) {
if q.len+1 > q.cap() {
q.grow()
}
i := (q.head + q.len) % q.cap()
q.elems[i] = e
q.len++
}
func (q *queue) dequeue() (interface{}, bool) {
if q.len == 0 {
return nil, false
}
e := q.elems[q.head]
q.elems[q.head] = nil
q.head = (q.head + 1) % q.cap()
q.len--
return e, true
}
func (q *queue) peek() (interface{}, bool) {
if q.len == 0 {
return nil, false
}
return q.elems[q.head], true
}
func (q *queue) clear() {
*q = queue{}
}