| # TODO(jayconrod): support shared memory on more platforms. |
| [!darwin] [!linux] [!windows] skip |
| |
| # Instrumentation only supported on 64-bit architectures. |
| [!amd64] [!arm64] skip |
| |
| # Test that when an interesting value is discovered (one that expands coverage), |
| # the fuzzing engine minimizes it before writing it to the cache. |
| # |
| # The program below starts with a seed value of length 100, but more coverage |
| # will be found for any value other than the seed. We should end with a value |
| # in the cache of length 1 (the minimizer currently does not produce empty |
| # strings). check_cache.go confirms that. |
| # |
| # We would like to verify that ALL values in the cache were minimized to a |
| # length of 1, but this isn't always possible when new coverage is found in |
| # functions called by testing or internal/fuzz in the background. |
| |
| go test -c -fuzz=. # Build using shared build cache for speed. |
| env GOCACHE=$WORK/gocache |
| exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=. -test.fuzztime=1000x |
| go run check_cache.go $GOCACHE/fuzz/FuzzMin |
| |
| -- go.mod -- |
| module fuzz |
| |
| go 1.17 |
| -- fuzz_test.go -- |
| package fuzz |
| |
| import ( |
| "bytes" |
| "testing" |
| ) |
| |
| func FuzzMin(f *testing.F) { |
| seed := bytes.Repeat([]byte("a"), 20) |
| f.Add(seed) |
| f.Fuzz(func(t *testing.T, buf []byte) { |
| if bytes.Equal(buf, seed) { |
| return |
| } |
| if n := sum(buf); n < 0 { |
| t.Error("sum cannot be negative") |
| } |
| }) |
| } |
| |
| func sum(buf []byte) int { |
| n := 0 |
| for _, b := range buf { |
| n += int(b) |
| } |
| return n |
| } |
| -- check_cache.go -- |
| //go:build ignore |
| // +build ignore |
| |
| // check_cache.go checks that each file in the cached corpus has a []byte |
| // of length at most 1. This verifies that at least one cached input is minimized. |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strconv" |
| ) |
| |
| func main() { |
| dir := os.Args[1] |
| ents, err := os.ReadDir(dir) |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } |
| for _, ent := range ents { |
| name := filepath.Join(dir, ent.Name()) |
| if good, err := checkCacheFile(name); err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } else if good { |
| os.Exit(0) |
| } |
| } |
| fmt.Fprintln(os.Stderr, "no cached inputs were minimized") |
| os.Exit(1) |
| } |
| |
| func checkCacheFile(name string) (good bool, err error) { |
| data, err := os.ReadFile(name) |
| if err != nil { |
| return false, err |
| } |
| for _, line := range bytes.Split(data, []byte("\n")) { |
| m := valRe.FindSubmatch(line) |
| if m == nil { |
| continue |
| } |
| if s, err := strconv.Unquote(string(m[1])); err != nil { |
| return false, err |
| } else if len(s) <= 1 { |
| return true, nil |
| } |
| } |
| return false, nil |
| } |
| |
| var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`) |