// Copyright 2020 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 provides common fuzzing functionality for tests built with// "go test" and for programs that use fuzzing functionality in the testing// package.
package fuzzimport ()// CoordinateFuzzingOpts is a set of arguments for CoordinateFuzzing.// The zero value is valid for each field unless specified otherwise.typeCoordinateFuzzingOptsstruct {// Log is a writer for logging progress messages and warnings. // If nil, io.Discard will be used instead. Log io.Writer// Timeout is the amount of wall clock time to spend fuzzing after the corpus // has loaded. If zero, there will be no time limit. Timeout time.Duration// Limit is the number of random values to generate and test. If zero, // there will be no limit on the number of generated values. Limit int64// MinimizeTimeout is the amount of wall clock time to spend minimizing // after discovering a crasher. If zero, there will be no time limit. If // MinimizeTimeout and MinimizeLimit are both zero, then minimization will // be disabled. MinimizeTimeout time.Duration// MinimizeLimit is the maximum number of calls to the fuzz function to be // made while minimizing after finding a crash. If zero, there will be no // limit. Calls to the fuzz function made when minimizing also count toward // Limit. If MinimizeTimeout and MinimizeLimit are both zero, then // minimization will be disabled. MinimizeLimit int64// parallel is the number of worker processes to run in parallel. If zero, // CoordinateFuzzing will run GOMAXPROCS workers. Parallel int// Seed is a list of seed values added by the fuzz target with testing.F.Add // and in testdata. Seed []CorpusEntry// Types is the list of types which make up a corpus entry. // Types must be set and must match values in Seed. Types []reflect.Type// CorpusDir is a directory where files containing values that crash the // code being tested may be written. CorpusDir must be set. CorpusDir string// CacheDir is a directory containing additional "interesting" values. // The fuzzer may derive new values from these, and may write new values here. CacheDir string}// CoordinateFuzzing creates several worker processes and communicates with// them to test random inputs that could trigger crashes and expose bugs.// The worker processes run the same binary in the same directory with the// same environment variables as the coordinator process. Workers also run// with the same arguments as the coordinator, except with the -test.fuzzworker// flag prepended to the argument list.//// If a crash occurs, the function will return an error containing information// about the crash, which can be reported to the user.func ( context.Context, CoordinateFuzzingOpts) ( error) {if := .Err(); != nil {return }if .Log == nil { .Log = io.Discard }if .Parallel == 0 { .Parallel = runtime.GOMAXPROCS(0) }if .Limit > 0 && int64(.Parallel) > .Limit {// Don't start more workers than we need. .Parallel = int(.Limit) } , := newCoordinator()if != nil {return }if .Timeout > 0 {varfunc() , = context.WithTimeout(, .Timeout)defer () }// fuzzCtx is used to stop workers, for example, after finding a crasher. , := context.WithCancel()defer () := .Done()// stop is called when a worker encounters a fatal error.varerror := false := func( error) {ifshouldPrintDebugInfo() { , , , := runtime.Caller(1)if { .debugLogf("stop called at %s:%d. stopping: %t", , , ) } else { .debugLogf("stop called at unknown. stopping: %t", ) } }if == .Err() || isInterruptError() {// Suppress cancellation errors and terminations due to SIGINT. // The messages are not helpful since either the user triggered the error // (with ^C) or another more helpful message will be printed (a crasher). = nil }if != nil && ( == nil || == .Err()) { = }if {return } = true () = nil }// Ensure that any crash we find is written to the corpus, even if an error // or interruption occurs while minimizing it. := falsedeferfunc() {if .crashMinimizing == nil || {return } := writeToCorpus(&.crashMinimizing.entry, .CorpusDir)if != nil { = fmt.Errorf("%w\n%v", , )return }if == nil { = &crashError{path: .crashMinimizing.entry.Path,err: errors.New(.crashMinimizing.crasherMsg), } } }()// Start workers. // TODO(jayconrod): do we want to support fuzzing different binaries? := ""// same as self := os.Args[0] := append([]string{"-test.fuzzworker"}, os.Args[1:]...) := os.Environ() // same as self := make(chanerror) := make([]*worker, .Parallel)for := range {varerror [], = newWorker(, , , , )if != nil {return } }for := range { := []gofunc() { := .coordinate()if .Err() != nil || isInterruptError() { = nil } := .cleanup()if == nil { = } <- }() }// Main event loop. // Do not return until all workers have terminated. We avoid a deadlock by // receiving messages from workers even after ctx is canceled. := len() := time.NewTicker(3 * time.Second)defer .Stop()defer .logStats() .logStats()for {// If there is an execution limit, and we've reached it, stop.if .opts.Limit > 0 && .count >= .opts.Limit { (nil) }varchanfuzzInput , := .peekInput()if && .crashMinimizing == nil && ! { = .inputC }varchanfuzzMinimizeInput , := .peekMinimizeInput()if && ! { = .minimizeC }select {case<-:// Interrupted, canceled, or timed out. // stop sets doneC to nil, so we don't busy wait here. (.Err())case := <-:// A worker terminated, possibly after encountering a fatal error. () --if == 0 {return }case := <-.resultC:// Received response from worker.if {break } .updateStats()if .crasherMsg != "" {if .warmupRun() && .entry.IsSeed { := filepath.Base(.opts.CorpusDir)fmt.Fprintf(.opts.Log, "failure while testing seed corpus entry: %s/%s\n", , testName(.entry.Parent)) (errors.New(.crasherMsg))break }if .canMinimize() && .canMinimize {if .crashMinimizing != nil {// This crash is not minimized, and another crash is being minimized. // Ignore this one and wait for the other one to finish.ifshouldPrintDebugInfo() { .debugLogf("found unminimized crasher, skipping in favor of minimizable crasher") }break }// Found a crasher but haven't yet attempted to minimize it. // Send it back to a worker for minimization. Disable inputC so // other workers don't continue fuzzing. .crashMinimizing = &fmt.Fprintf(.opts.Log, "fuzz: minimizing %d-byte failing input file\n", len(.entry.Data)) .queueForMinimization(, nil) } elseif ! {// Found a crasher that's either minimized or not minimizable. // Write to corpus and stop. := writeToCorpus(&.entry, .CorpusDir)if == nil { = true = &crashError{path: .entry.Path,err: errors.New(.crasherMsg), } }ifshouldPrintDebugInfo() { .debugLogf("found crasher, id: %s, parent: %s, gen: %d, size: %d, exec time: %s", .entry.Path, .entry.Parent, .entry.Generation,len(.entry.Data), .entryDuration, ) } () } } elseif .coverageData != nil {if .warmupRun() {ifshouldPrintDebugInfo() { .debugLogf("processed an initial input, id: %s, new bits: %d, size: %d, exec time: %s", .entry.Parent,countBits(diffCoverage(.coverageMask, .coverageData)),len(.entry.Data), .entryDuration, ) } .updateCoverage(.coverageData) .warmupInputLeft--if .warmupInputLeft == 0 {fmt.Fprintf(.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed, now fuzzing with %d workers\n", .elapsed(), .warmupInputCount, .warmupInputCount, .opts.Parallel)ifshouldPrintDebugInfo() { .debugLogf("finished processing input corpus, entries: %d, initial coverage bits: %d",len(.corpus.entries),countBits(.coverageMask), ) } } } elseif := diffCoverage(.coverageMask, .coverageData); != nil {// Found a value that expanded coverage. // It's not a crasher, but we may want to add it to the on-disk // corpus and prioritize it for future fuzzing. // TODO(jayconrod, katiehockman): Prioritize fuzzing these // values which expanded coverage, perhaps based on the // number of new edges that this result expanded. // TODO(jayconrod, katiehockman): Don't write a value that's already // in the corpus.if .canMinimize() && .canMinimize && .crashMinimizing == nil {// Send back to workers to find a smaller value that preserves // at least one new coverage bit. .queueForMinimization(, ) } else {// Update the coordinator's coverage mask and save the value. := len(.entry.Data) , := .addCorpusEntries(true, .entry)if != nil { ()break }if ! {ifshouldPrintDebugInfo() { .debugLogf("ignoring duplicate input which increased coverage, id: %s", .entry.Path, ) }break } .updateCoverage() .inputQueue.enqueue(.entry) .interestingCount++ifshouldPrintDebugInfo() { .debugLogf("new interesting input, id: %s, parent: %s, gen: %d, new bits: %d, total bits: %d, size: %d, exec time: %s", .entry.Path, .entry.Parent, .entry.Generation,countBits(),countBits(.coverageMask), , .entryDuration, ) } } } else {ifshouldPrintDebugInfo() { .debugLogf("worker reported interesting input that doesn't expand coverage, id: %s, parent: %s, canMinimize: %t", .entry.Path, .entry.Parent, .canMinimize, ) } } } elseif .warmupRun() {// No error or coverage data was reported for this input during // warmup, so continue processing results. .warmupInputLeft--if .warmupInputLeft == 0 {fmt.Fprintf(.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed, now fuzzing with %d workers\n", .elapsed(), .warmupInputCount, .warmupInputCount, .opts.Parallel)ifshouldPrintDebugInfo() { .debugLogf("finished testing-only phase, entries: %d",len(.corpus.entries), ) } } }case<- :// Sent the next input to a worker. .sentInput()case<- :// Sent the next input for minimization to a worker. .sentMinimizeInput()case<-.C: .logStats() } }// TODO(jayconrod,katiehockman): if a crasher can't be written to the corpus, // write to the cache instead.}// crashError wraps a crasher written to the seed corpus. It saves the name// of the file where the input causing the crasher was saved. The testing// framework uses this to report a command to re-run that specific input.type crashError struct { path string err error}func ( *crashError) () string {return .err.Error()}func ( *crashError) () error {return .err}func ( *crashError) () string {return .path}type corpus struct { entries []CorpusEntry hashes map[[sha256.Size]byte]bool}// addCorpusEntries adds entries to the corpus, and optionally writes the entries// to the cache directory. If an entry is already in the corpus it is skipped. If// all of the entries are unique, addCorpusEntries returns true and a nil error,// if at least one of the entries was a duplicate, it returns false and a nil error.func ( *coordinator) ( bool, ...CorpusEntry) (bool, error) { := truefor , := range { , := corpusEntryData()if != nil {returnfalse, } := sha256.Sum256()if .corpus.hashes[] { = falsecontinue }if {if := writeToCorpus(&, .opts.CacheDir); != nil {returnfalse, }// For entries written to disk, we don't hold onto the bytes, // since the corpus would consume a significant amount of // memory. .Data = nil } .corpus.hashes[] = true .corpus.entries = append(.corpus.entries, ) }return , nil}// CorpusEntry represents an individual input for fuzzing.//// We must use an equivalent type in the testing and testing/internal/testdeps// packages, but testing can't import this package directly, and we don't want// to export this type from testing. Instead, we use the same struct type and// use a type alias (not a defined type) for convenience.typeCorpusEntry = struct { Parent string// Path is the path of the corpus file, if the entry was loaded from disk. // For other entries, including seed values provided by f.Add, Path is the // name of the test, e.g. seed#0 or its hash. Path string// Data is the raw input data. Data should only be populated for seed // values. For on-disk corpus files, Data will be nil, as it will be loaded // from disk using Path. Data []byte// Values is the unmarshaled values from a corpus file. Values []any Generation int// IsSeed indicates whether this entry is part of the seed corpus. IsSeed bool}// corpusEntryData returns the raw input bytes, either from the data struct// field, or from disk.func corpusEntryData( CorpusEntry) ([]byte, error) {if .Data != nil {return .Data, nil }returnos.ReadFile(.Path)}type fuzzInput struct {// entry is the value to test initially. The worker will randomly mutate // values from this starting point. entry CorpusEntry// timeout is the time to spend fuzzing variations of this input, // not including starting or cleaning up. timeout time.Duration// limit is the maximum number of calls to the fuzz function the worker may // make. The worker may make fewer calls, for example, if it finds an // error early. If limit is zero, there is no limit on calls to the // fuzz function. limit int64// warmup indicates whether this is a warmup input before fuzzing begins. If // true, the input should not be fuzzed. warmup bool// coverageData reflects the coordinator's current coverageMask. coverageData []byte}type fuzzResult struct {// entry is an interesting value or a crasher. entry CorpusEntry// crasherMsg is an error message from a crash. It's "" if no crash was found. crasherMsg string// canMinimize is true if the worker should attempt to minimize this result. // It may be false because an attempt has already been made. canMinimize bool// coverageData is set if the worker found new coverage. coverageData []byte// limit is the number of values the coordinator asked the worker // to test. 0 if there was no limit. limit int64// count is the number of values the worker actually tested. count int64// totalDuration is the time the worker spent testing inputs. totalDuration time.Duration// entryDuration is the time the worker spent execution an interesting result entryDuration time.Duration}type fuzzMinimizeInput struct {// entry is an interesting value or crasher to minimize. entry CorpusEntry// crasherMsg is an error message from a crash. It's "" if no crash was found. // If set, the worker will attempt to find a smaller input that also produces // an error, though not necessarily the same error. crasherMsg string// limit is the maximum number of calls to the fuzz function the worker may // make. The worker may make fewer calls, for example, if it can't reproduce // an error. If limit is zero, there is no limit on calls to the fuzz function. limit int64// timeout is the time to spend minimizing this input. // A zero timeout means no limit. timeout time.Duration// keepCoverage is a set of coverage bits that entry found that were not in // the coordinator's combined set. When minimizing, the worker should find an // input that preserves at least one of these bits. keepCoverage is nil for // crashing inputs. keepCoverage []byte}// coordinator holds channels that workers can use to communicate with// the coordinator.type coordinator struct { opts CoordinateFuzzingOpts// startTime is the time we started the workers after loading the corpus. // Used for logging. startTime time.Time// inputC is sent values to fuzz by the coordinator. Any worker may receive // values from this channel. Workers send results to resultC. inputC chanfuzzInput// minimizeC is sent values to minimize by the coordinator. Any worker may // receive values from this channel. Workers send results to resultC. minimizeC chanfuzzMinimizeInput// resultC is sent results of fuzzing by workers. The coordinator // receives these. Multiple types of messages are allowed. resultC chanfuzzResult// count is the number of values fuzzed so far. count int64// countLastLog is the number of values fuzzed when the output was last // logged. countLastLog int64// timeLastLog is the time at which the output was last logged. timeLastLog time.Time// interestingCount is the number of unique interesting values which have // been found this execution. interestingCount int// warmupInputCount is the count of all entries in the corpus which will // need to be received from workers to run once during warmup, but not fuzz. // This could be for coverage data, or only for the purposes of verifying // that the seed corpus doesn't have any crashers. See warmupRun. warmupInputCount int// warmupInputLeft is the number of entries in the corpus which still need // to be received from workers to run once during warmup, but not fuzz. // See warmupInputLeft. warmupInputLeft int// duration is the time spent fuzzing inside workers, not counting time // starting up or tearing down. duration time.Duration// countWaiting is the number of fuzzing executions the coordinator is // waiting on workers to complete. countWaiting int64// corpus is a set of interesting values, including the seed corpus and // generated values that workers reported as interesting. corpus corpus// minimizationAllowed is true if one or more of the types of fuzz // function's parameters can be minimized. minimizationAllowed bool// inputQueue is a queue of inputs that workers should try fuzzing. This is // initially populated from the seed corpus and cached inputs. More inputs // may be added as new coverage is discovered. inputQueue queue// minimizeQueue is a queue of inputs that caused errors or exposed new // coverage. Workers should attempt to find smaller inputs that do the // same thing. minimizeQueue queue// crashMinimizing is the crash that is currently being minimized. crashMinimizing *fuzzResult// coverageMask aggregates coverage that was found for all inputs in the // corpus. Each byte represents a single basic execution block. Each set bit // within the byte indicates that an input has triggered that block at least // 1 << n times, where n is the position of the bit in the byte. For example, a // value of 12 indicates that separate inputs have triggered this block // between 4-7 times and 8-15 times. coverageMask []byte}func newCoordinator( CoordinateFuzzingOpts) (*coordinator, error) {// Make sure all the seed corpus has marshaled data.for := range .Seed {if .Seed[].Data == nil && .Seed[].Values != nil { .Seed[].Data = marshalCorpusFile(.Seed[].Values...) } } := &coordinator{opts: ,startTime: time.Now(),inputC: make(chanfuzzInput),minimizeC: make(chanfuzzMinimizeInput),resultC: make(chanfuzzResult),timeLastLog: time.Now(),corpus: corpus{hashes: make(map[[sha256.Size]byte]bool)}, }if := .readCache(); != nil {returnnil, }if .MinimizeLimit > 0 || .MinimizeTimeout > 0 {for , := range .Types {ifisMinimizable() { .minimizationAllowed = truebreak } } } := len(coverage())if == 0 {fmt.Fprintf(.opts.Log, "warning: the test binary was not built with coverage instrumentation, so fuzzing will run without coverage guidance and may be inefficient\n")// Even though a coverage-only run won't occur, we should still run all // of the seed corpus to make sure there are no existing failures before // we start fuzzing. .warmupInputCount = len(.opts.Seed)for , := range .opts.Seed { .inputQueue.enqueue() } } else { .warmupInputCount = len(.corpus.entries)for , := range .corpus.entries { .inputQueue.enqueue() }// Set c.coverageMask to a clean []byte full of zeros. .coverageMask = make([]byte, ) } .warmupInputLeft = .warmupInputCountiflen(.corpus.entries) == 0 {fmt.Fprintf(.opts.Log, "warning: starting with empty corpus\n")var []anyfor , := range .Types { = append(, zeroValue()) } := marshalCorpusFile(...) := sha256.Sum256() := fmt.Sprintf("%x", [:4]) .addCorpusEntries(false, CorpusEntry{Path: , Data: }) }return , nil}func ( *coordinator) ( fuzzResult) { .count += .count .countWaiting -= .limit .duration += .totalDuration}func ( *coordinator) () { := time.Now()if .warmupRun() { := .warmupInputCount - .warmupInputLeftifcoverageEnabled {fmt.Fprintf(.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed\n", .elapsed(), , .warmupInputCount) } else {fmt.Fprintf(.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed\n", .elapsed(), , .warmupInputCount) } } elseif .crashMinimizing != nil {fmt.Fprintf(.opts.Log, "fuzz: elapsed: %s, minimizing\n", .elapsed()) } else { := float64(.count-.countLastLog) / .Sub(.timeLastLog).Seconds()ifcoverageEnabled { := .warmupInputCount + .interestingCountfmt.Fprintf(.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec), new interesting: %d (total: %d)\n", .elapsed(), .count, , .interestingCount, ) } else {fmt.Fprintf(.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec)\n", .elapsed(), .count, ) } } .countLastLog = .count .timeLastLog = }// peekInput returns the next value that should be sent to workers.// If the number of executions is limited, the returned value includes// a limit for one worker. If there are no executions left, peekInput returns// a zero value and false.//// peekInput doesn't actually remove the input from the queue. The caller// must call sentInput after sending the input.//// If the input queue is empty and the coverage/testing-only run has completed,// queue refills it from the corpus.func ( *coordinator) () (fuzzInput, bool) {if .opts.Limit > 0 && .count+.countWaiting >= .opts.Limit {// Already making the maximum number of calls to the fuzz function. // Don't send more inputs right now.returnfuzzInput{}, false }if .inputQueue.len == 0 {if .warmupRun() {// Wait for coverage/testing-only run to finish before sending more // inputs.returnfuzzInput{}, false } .refillInputQueue() } , := .inputQueue.peek()if ! {panic("input queue empty after refill") } := fuzzInput{entry: .(CorpusEntry),timeout: workerFuzzDuration,warmup: .warmupRun(), }if .coverageMask != nil { .coverageData = bytes.Clone(.coverageMask) }if .warmup {// No fuzzing will occur, but it should count toward the limit set by // -fuzztime. .limit = 1return , true }if .opts.Limit > 0 { .limit = .opts.Limit / int64(.opts.Parallel)if .opts.Limit%int64(.opts.Parallel) > 0 { .limit++ } := .opts.Limit - .count - .countWaitingif .limit > { .limit = } }return , true}// sentInput updates internal counters after an input is sent to c.inputC.func ( *coordinator) ( fuzzInput) { .inputQueue.dequeue() .countWaiting += .limit}// refillInputQueue refills the input queue from the corpus after it becomes// empty.func ( *coordinator) () {for , := range .corpus.entries { .inputQueue.enqueue() }}// queueForMinimization creates a fuzzMinimizeInput from result and adds it// to the minimization queue to be sent to workers.func ( *coordinator) ( fuzzResult, []byte) {ifshouldPrintDebugInfo() { .debugLogf("queueing input for minimization, id: %s, parent: %s, keepCoverage: %t, crasher: %t", .entry.Path, .entry.Parent, != nil, .crasherMsg != "", ) }if .crasherMsg != "" { .minimizeQueue.clear() } := fuzzMinimizeInput{entry: .entry,crasherMsg: .crasherMsg,keepCoverage: , } .minimizeQueue.enqueue()}// peekMinimizeInput returns the next input that should be sent to workers for// minimization.func ( *coordinator) () (fuzzMinimizeInput, bool) {if !.canMinimize() {// Already making the maximum number of calls to the fuzz function. // Don't send more inputs right now.returnfuzzMinimizeInput{}, false } , := .minimizeQueue.peek()if ! {returnfuzzMinimizeInput{}, false } := .(fuzzMinimizeInput)if .opts.MinimizeTimeout > 0 { .timeout = .opts.MinimizeTimeout }if .opts.MinimizeLimit > 0 { .limit = .opts.MinimizeLimit } elseif .opts.Limit > 0 {if .crasherMsg != "" { .limit = .opts.Limit } else { .limit = .opts.Limit / int64(.opts.Parallel)if .opts.Limit%int64(.opts.Parallel) > 0 { .limit++ } } }if .opts.Limit > 0 { := .opts.Limit - .count - .countWaitingif .limit > { .limit = } }return , true}// sentMinimizeInput removes an input from the minimization queue after it's// sent to minimizeC.func ( *coordinator) ( fuzzMinimizeInput) { .minimizeQueue.dequeue() .countWaiting += .limit}// warmupRun returns true while the coordinator is running inputs without// mutating them as a warmup before fuzzing. This could be to gather baseline// coverage data for entries in the corpus, or to test all of the seed corpus// for errors before fuzzing begins.//// The coordinator doesn't store coverage data in the cache with each input// because that data would be invalid when counter offsets in the test binary// change.//// When gathering coverage, the coordinator sends each entry to a worker to// gather coverage for that entry only, without fuzzing or minimizing. This// phase ends when all workers have finished, and the coordinator has a combined// coverage map.func ( *coordinator) () bool {return .warmupInputLeft > 0}// updateCoverage sets bits in c.coverageMask that are set in newCoverage.// updateCoverage returns the number of newly set bits. See the comment on// coverageMask for the format.func ( *coordinator) ( []byte) int {iflen() != len(.coverageMask) {panic(fmt.Sprintf("number of coverage counters changed at runtime: %d, expected %d", len(), len(.coverageMask))) } := 0for := range { := [] &^ .coverageMask[] += bits.OnesCount8() .coverageMask[] |= [] }return}// canMinimize returns whether the coordinator should attempt to find smaller// inputs that reproduce a crash or new coverage.func ( *coordinator) () bool {return .minimizationAllowed && (.opts.Limit == 0 || .count+.countWaiting < .opts.Limit)}func ( *coordinator) () time.Duration {returntime.Since(.startTime).Round(1 * time.Second)}// readCache creates a combined corpus from seed values and values in the cache// (in GOCACHE/fuzz).//// TODO(fuzzing): need a mechanism that can remove values that// aren't useful anymore, for example, because they have the wrong type.func ( *coordinator) () error {if , := .addCorpusEntries(false, .opts.Seed...); != nil {return } , := ReadCorpus(.opts.CacheDir, .opts.Types)if != nil {if , := .(*MalformedCorpusError); ! {// It's okay if some files in the cache directory are malformed and // are not included in the corpus, but fail if it's an I/O error.return }// TODO(jayconrod,katiehockman): consider printing some kind of warning // indicating the number of files which were skipped because they are // malformed. }if , := .addCorpusEntries(false, ...); != nil {return }returnnil}// MalformedCorpusError is an error found while reading the corpus from the// filesystem. All of the errors are stored in the errs list. The testing// framework uses this to report malformed files in testdata.typeMalformedCorpusErrorstruct { errs []error}func ( *MalformedCorpusError) () string {var []stringfor , := range .errs { = append(, .Error()) }returnstrings.Join(, "\n")}// ReadCorpus reads the corpus from the provided dir. The returned corpus// entries are guaranteed to match the given types. Any malformed files will// be saved in a MalformedCorpusError and returned, along with the most recent// error.func ( string, []reflect.Type) ([]CorpusEntry, error) { , := os.ReadDir()ifos.IsNotExist() {returnnil, nil// No corpus to read } elseif != nil {returnnil, fmt.Errorf("reading seed corpus from testdata: %v", ) }var []CorpusEntryvar []errorfor , := range {// TODO(jayconrod,katiehockman): determine when a file is a fuzzing input // based on its name. We should only read files created by writeToCorpus. // If we read ALL files, we won't be able to change the file format by // changing the extension. We also won't be able to add files like // README.txt explaining why the directory exists.if .IsDir() {continue } := filepath.Join(, .Name()) , := os.ReadFile()if != nil {returnnil, fmt.Errorf("failed to read corpus file: %v", ) }var []any , = readCorpusData(, )if != nil { = append(, fmt.Errorf("%q: %v", , ))continue } = append(, CorpusEntry{Path: , Values: }) }iflen() > 0 {return , &MalformedCorpusError{errs: } }return , nil}func readCorpusData( []byte, []reflect.Type) ([]any, error) { , := unmarshalCorpusFile()if != nil {returnnil, fmt.Errorf("unmarshal: %v", ) }if = CheckCorpus(, ); != nil {returnnil, }return , nil}// CheckCorpus verifies that the types in vals match the expected types// provided.func ( []any, []reflect.Type) error {iflen() != len() {returnfmt.Errorf("wrong number of values in corpus entry: %d, want %d", len(), len()) } := make([]reflect.Type, len())for , := range { [] = reflect.TypeOf() }for := range {if [] != [] {returnfmt.Errorf("mismatched types in corpus entry: %v, want %v", , ) } }returnnil}// writeToCorpus atomically writes the given bytes to a new file in testdata. If// the directory does not exist, it will create one. If the file already exists,// writeToCorpus will not rewrite it. writeToCorpus sets entry.Path to the new// file that was just written or an error if it failed.func writeToCorpus( *CorpusEntry, string) ( error) { := fmt.Sprintf("%x", sha256.Sum256(.Data))[:16] .Path = filepath.Join(, )if := os.MkdirAll(, 0777); != nil {return }if := os.WriteFile(.Path, .Data, 0666); != nil {os.Remove(.Path) // remove partially written filereturn }returnnil}func testName( string) string {returnfilepath.Base()}func zeroValue( reflect.Type) any {for , := rangezeroVals {ifreflect.TypeOf() == {return } }panic(fmt.Sprintf("unsupported type: %v", ))}var zeroVals []any = []any{ []byte(""),string(""),false,byte(0),rune(0),float32(0),float64(0),int(0),int8(0),int16(0),int32(0),int64(0),uint(0),uint8(0),uint16(0),uint32(0),uint64(0),}var debugInfo = godebug.New("#fuzzdebug").Value() == "1"func shouldPrintDebugInfo() bool {returndebugInfo}func ( *coordinator) ( string, ...any) { := time.Now().Format("2006-01-02 15:04:05.999999999")fmt.Fprintf(.opts.Log, +" DEBUG "++"\n", ...)}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.