1
2
3
4
5
6
7
8 package fuzz
9
10 import (
11 "bytes"
12 "context"
13 "crypto/sha256"
14 "errors"
15 "fmt"
16 "internal/godebug"
17 "io"
18 "math/bits"
19 "os"
20 "path/filepath"
21 "reflect"
22 "runtime"
23 "strings"
24 "time"
25 )
26
27
28
29 type CoordinateFuzzingOpts struct {
30
31
32 Log io.Writer
33
34
35
36 Timeout time.Duration
37
38
39
40 Limit int64
41
42
43
44
45
46 MinimizeTimeout time.Duration
47
48
49
50
51
52
53 MinimizeLimit int64
54
55
56
57 Parallel int
58
59
60
61 Seed []CorpusEntry
62
63
64
65 Types []reflect.Type
66
67
68
69 CorpusDir string
70
71
72
73 CacheDir string
74 }
75
76
77
78
79
80
81
82
83
84
85 func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err error) {
86 if err := ctx.Err(); err != nil {
87 return err
88 }
89 if opts.Log == nil {
90 opts.Log = io.Discard
91 }
92 if opts.Parallel == 0 {
93 opts.Parallel = runtime.GOMAXPROCS(0)
94 }
95 if opts.Limit > 0 && int64(opts.Parallel) > opts.Limit {
96
97 opts.Parallel = int(opts.Limit)
98 }
99
100 c, err := newCoordinator(opts)
101 if err != nil {
102 return err
103 }
104
105 if opts.Timeout > 0 {
106 var cancel func()
107 ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
108 defer cancel()
109 }
110
111
112 fuzzCtx, cancelWorkers := context.WithCancel(ctx)
113 defer cancelWorkers()
114 doneC := ctx.Done()
115
116
117 var fuzzErr error
118 stopping := false
119 stop := func(err error) {
120 if shouldPrintDebugInfo() {
121 _, file, line, ok := runtime.Caller(1)
122 if ok {
123 c.debugLogf("stop called at %s:%d. stopping: %t", file, line, stopping)
124 } else {
125 c.debugLogf("stop called at unknown. stopping: %t", stopping)
126 }
127 }
128
129 if err == fuzzCtx.Err() || isInterruptError(err) {
130
131
132
133 err = nil
134 }
135 if err != nil && (fuzzErr == nil || fuzzErr == ctx.Err()) {
136 fuzzErr = err
137 }
138 if stopping {
139 return
140 }
141 stopping = true
142 cancelWorkers()
143 doneC = nil
144 }
145
146
147
148 crashWritten := false
149 defer func() {
150 if c.crashMinimizing == nil || crashWritten {
151 return
152 }
153 werr := writeToCorpus(&c.crashMinimizing.entry, opts.CorpusDir)
154 if werr != nil {
155 err = fmt.Errorf("%w\n%v", err, werr)
156 return
157 }
158 if err == nil {
159 err = &crashError{
160 path: c.crashMinimizing.entry.Path,
161 err: errors.New(c.crashMinimizing.crasherMsg),
162 }
163 }
164 }()
165
166
167
168 dir := ""
169 binPath := os.Args[0]
170 args := append([]string{"-test.fuzzworker"}, os.Args[1:]...)
171 env := os.Environ()
172
173 errC := make(chan error)
174 workers := make([]*worker, opts.Parallel)
175 for i := range workers {
176 var err error
177 workers[i], err = newWorker(c, dir, binPath, args, env)
178 if err != nil {
179 return err
180 }
181 }
182 for i := range workers {
183 w := workers[i]
184 go func() {
185 err := w.coordinate(fuzzCtx)
186 if fuzzCtx.Err() != nil || isInterruptError(err) {
187 err = nil
188 }
189 cleanErr := w.cleanup()
190 if err == nil {
191 err = cleanErr
192 }
193 errC <- err
194 }()
195 }
196
197
198
199
200 activeWorkers := len(workers)
201 statTicker := time.NewTicker(3 * time.Second)
202 defer statTicker.Stop()
203 defer c.logStats()
204
205 c.logStats()
206 for {
207
208 if c.opts.Limit > 0 && c.count >= c.opts.Limit {
209 stop(nil)
210 }
211
212 var inputC chan fuzzInput
213 input, ok := c.peekInput()
214 if ok && c.crashMinimizing == nil && !stopping {
215 inputC = c.inputC
216 }
217
218 var minimizeC chan fuzzMinimizeInput
219 minimizeInput, ok := c.peekMinimizeInput()
220 if ok && !stopping {
221 minimizeC = c.minimizeC
222 }
223
224 select {
225 case <-doneC:
226
227
228 stop(ctx.Err())
229
230 case err := <-errC:
231
232 stop(err)
233 activeWorkers--
234 if activeWorkers == 0 {
235 return fuzzErr
236 }
237
238 case result := <-c.resultC:
239
240 if stopping {
241 break
242 }
243 c.updateStats(result)
244
245 if result.crasherMsg != "" {
246 if c.warmupRun() && result.entry.IsSeed {
247 target := filepath.Base(c.opts.CorpusDir)
248 fmt.Fprintf(c.opts.Log, "failure while testing seed corpus entry: %s/%s\n", target, testName(result.entry.Parent))
249 stop(errors.New(result.crasherMsg))
250 break
251 }
252 if c.canMinimize() && result.canMinimize {
253 if c.crashMinimizing != nil {
254
255
256 if shouldPrintDebugInfo() {
257 c.debugLogf("found unminimized crasher, skipping in favor of minimizable crasher")
258 }
259 break
260 }
261
262
263
264 c.crashMinimizing = &result
265 fmt.Fprintf(c.opts.Log, "fuzz: minimizing %d-byte failing input file\n", len(result.entry.Data))
266 c.queueForMinimization(result, nil)
267 } else if !crashWritten {
268
269
270 err := writeToCorpus(&result.entry, opts.CorpusDir)
271 if err == nil {
272 crashWritten = true
273 err = &crashError{
274 path: result.entry.Path,
275 err: errors.New(result.crasherMsg),
276 }
277 }
278 if shouldPrintDebugInfo() {
279 c.debugLogf(
280 "found crasher, id: %s, parent: %s, gen: %d, size: %d, exec time: %s",
281 result.entry.Path,
282 result.entry.Parent,
283 result.entry.Generation,
284 len(result.entry.Data),
285 result.entryDuration,
286 )
287 }
288 stop(err)
289 }
290 } else if result.coverageData != nil {
291 if c.warmupRun() {
292 if shouldPrintDebugInfo() {
293 c.debugLogf(
294 "processed an initial input, id: %s, new bits: %d, size: %d, exec time: %s",
295 result.entry.Parent,
296 countBits(diffCoverage(c.coverageMask, result.coverageData)),
297 len(result.entry.Data),
298 result.entryDuration,
299 )
300 }
301 c.updateCoverage(result.coverageData)
302 c.warmupInputLeft--
303 if c.warmupInputLeft == 0 {
304 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
305 if shouldPrintDebugInfo() {
306 c.debugLogf(
307 "finished processing input corpus, entries: %d, initial coverage bits: %d",
308 len(c.corpus.entries),
309 countBits(c.coverageMask),
310 )
311 }
312 }
313 } else if keepCoverage := diffCoverage(c.coverageMask, result.coverageData); keepCoverage != nil {
314
315
316
317
318
319
320
321
322 if c.canMinimize() && result.canMinimize && c.crashMinimizing == nil {
323
324
325 c.queueForMinimization(result, keepCoverage)
326 } else {
327
328 inputSize := len(result.entry.Data)
329 entryNew, err := c.addCorpusEntries(true, result.entry)
330 if err != nil {
331 stop(err)
332 break
333 }
334 if !entryNew {
335 if shouldPrintDebugInfo() {
336 c.debugLogf(
337 "ignoring duplicate input which increased coverage, id: %s",
338 result.entry.Path,
339 )
340 }
341 break
342 }
343 c.updateCoverage(keepCoverage)
344 c.inputQueue.enqueue(result.entry)
345 c.interestingCount++
346 if shouldPrintDebugInfo() {
347 c.debugLogf(
348 "new interesting input, id: %s, parent: %s, gen: %d, new bits: %d, total bits: %d, size: %d, exec time: %s",
349 result.entry.Path,
350 result.entry.Parent,
351 result.entry.Generation,
352 countBits(keepCoverage),
353 countBits(c.coverageMask),
354 inputSize,
355 result.entryDuration,
356 )
357 }
358 }
359 } else {
360 if shouldPrintDebugInfo() {
361 c.debugLogf(
362 "worker reported interesting input that doesn't expand coverage, id: %s, parent: %s, canMinimize: %t",
363 result.entry.Path,
364 result.entry.Parent,
365 result.canMinimize,
366 )
367 }
368 }
369 } else if c.warmupRun() {
370
371
372 c.warmupInputLeft--
373 if c.warmupInputLeft == 0 {
374 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
375 if shouldPrintDebugInfo() {
376 c.debugLogf(
377 "finished testing-only phase, entries: %d",
378 len(c.corpus.entries),
379 )
380 }
381 }
382 }
383
384 case inputC <- input:
385
386 c.sentInput(input)
387
388 case minimizeC <- minimizeInput:
389
390 c.sentMinimizeInput(minimizeInput)
391
392 case <-statTicker.C:
393 c.logStats()
394 }
395 }
396
397
398
399 }
400
401
402
403
404 type crashError struct {
405 path string
406 err error
407 }
408
409 func (e *crashError) Error() string {
410 return e.err.Error()
411 }
412
413 func (e *crashError) Unwrap() error {
414 return e.err
415 }
416
417 func (e *crashError) CrashPath() string {
418 return e.path
419 }
420
421 type corpus struct {
422 entries []CorpusEntry
423 hashes map[[sha256.Size]byte]bool
424 }
425
426
427
428
429
430 func (c *coordinator) addCorpusEntries(addToCache bool, entries ...CorpusEntry) (bool, error) {
431 noDupes := true
432 for _, e := range entries {
433 data, err := corpusEntryData(e)
434 if err != nil {
435 return false, err
436 }
437 h := sha256.Sum256(data)
438 if c.corpus.hashes[h] {
439 noDupes = false
440 continue
441 }
442 if addToCache {
443 if err := writeToCorpus(&e, c.opts.CacheDir); err != nil {
444 return false, err
445 }
446
447
448
449 e.Data = nil
450 }
451 c.corpus.hashes[h] = true
452 c.corpus.entries = append(c.corpus.entries, e)
453 }
454 return noDupes, nil
455 }
456
457
458
459
460
461
462
463 type CorpusEntry = struct {
464 Parent string
465
466
467
468
469 Path string
470
471
472
473
474 Data []byte
475
476
477 Values []any
478
479 Generation int
480
481
482 IsSeed bool
483 }
484
485
486
487 func corpusEntryData(ce CorpusEntry) ([]byte, error) {
488 if ce.Data != nil {
489 return ce.Data, nil
490 }
491
492 return os.ReadFile(ce.Path)
493 }
494
495 type fuzzInput struct {
496
497
498 entry CorpusEntry
499
500
501
502 timeout time.Duration
503
504
505
506
507
508 limit int64
509
510
511
512 warmup bool
513
514
515 coverageData []byte
516 }
517
518 type fuzzResult struct {
519
520 entry CorpusEntry
521
522
523 crasherMsg string
524
525
526
527 canMinimize bool
528
529
530 coverageData []byte
531
532
533
534 limit int64
535
536
537 count int64
538
539
540 totalDuration time.Duration
541
542
543 entryDuration time.Duration
544 }
545
546 type fuzzMinimizeInput struct {
547
548 entry CorpusEntry
549
550
551
552
553 crasherMsg string
554
555
556
557
558 limit int64
559
560
561
562 timeout time.Duration
563
564
565
566
567
568 keepCoverage []byte
569 }
570
571
572
573 type coordinator struct {
574 opts CoordinateFuzzingOpts
575
576
577
578 startTime time.Time
579
580
581
582 inputC chan fuzzInput
583
584
585
586 minimizeC chan fuzzMinimizeInput
587
588
589
590 resultC chan fuzzResult
591
592
593 count int64
594
595
596
597 countLastLog int64
598
599
600 timeLastLog time.Time
601
602
603
604 interestingCount int
605
606
607
608
609
610 warmupInputCount int
611
612
613
614
615 warmupInputLeft int
616
617
618
619 duration time.Duration
620
621
622
623 countWaiting int64
624
625
626
627 corpus corpus
628
629
630
631 minimizationAllowed bool
632
633
634
635
636 inputQueue queue
637
638
639
640
641 minimizeQueue queue
642
643
644 crashMinimizing *fuzzResult
645
646
647
648
649
650
651
652 coverageMask []byte
653 }
654
655 func newCoordinator(opts CoordinateFuzzingOpts) (*coordinator, error) {
656
657 for i := range opts.Seed {
658 if opts.Seed[i].Data == nil && opts.Seed[i].Values != nil {
659 opts.Seed[i].Data = marshalCorpusFile(opts.Seed[i].Values...)
660 }
661 }
662 c := &coordinator{
663 opts: opts,
664 startTime: time.Now(),
665 inputC: make(chan fuzzInput),
666 minimizeC: make(chan fuzzMinimizeInput),
667 resultC: make(chan fuzzResult),
668 timeLastLog: time.Now(),
669 corpus: corpus{hashes: make(map[[sha256.Size]byte]bool)},
670 }
671 if err := c.readCache(); err != nil {
672 return nil, err
673 }
674 if opts.MinimizeLimit > 0 || opts.MinimizeTimeout > 0 {
675 for _, t := range opts.Types {
676 if isMinimizable(t) {
677 c.minimizationAllowed = true
678 break
679 }
680 }
681 }
682
683 covSize := len(coverage())
684 if covSize == 0 {
685 fmt.Fprintf(c.opts.Log, "warning: the test binary was not built with coverage instrumentation, so fuzzing will run without coverage guidance and may be inefficient\n")
686
687
688
689 c.warmupInputCount = len(c.opts.Seed)
690 for _, e := range c.opts.Seed {
691 c.inputQueue.enqueue(e)
692 }
693 } else {
694 c.warmupInputCount = len(c.corpus.entries)
695 for _, e := range c.corpus.entries {
696 c.inputQueue.enqueue(e)
697 }
698
699 c.coverageMask = make([]byte, covSize)
700 }
701 c.warmupInputLeft = c.warmupInputCount
702
703 if len(c.corpus.entries) == 0 {
704 fmt.Fprintf(c.opts.Log, "warning: starting with empty corpus\n")
705 var vals []any
706 for _, t := range opts.Types {
707 vals = append(vals, zeroValue(t))
708 }
709 data := marshalCorpusFile(vals...)
710 h := sha256.Sum256(data)
711 name := fmt.Sprintf("%x", h[:4])
712 c.addCorpusEntries(false, CorpusEntry{Path: name, Data: data})
713 }
714
715 return c, nil
716 }
717
718 func (c *coordinator) updateStats(result fuzzResult) {
719 c.count += result.count
720 c.countWaiting -= result.limit
721 c.duration += result.totalDuration
722 }
723
724 func (c *coordinator) logStats() {
725 now := time.Now()
726 if c.warmupRun() {
727 runSoFar := c.warmupInputCount - c.warmupInputLeft
728 if coverageEnabled {
729 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
730 } else {
731 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
732 }
733 } else if c.crashMinimizing != nil {
734 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, minimizing\n", c.elapsed())
735 } else {
736 rate := float64(c.count-c.countLastLog) / now.Sub(c.timeLastLog).Seconds()
737 if coverageEnabled {
738 total := c.warmupInputCount + c.interestingCount
739 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec), new interesting: %d (total: %d)\n", c.elapsed(), c.count, rate, c.interestingCount, total)
740 } else {
741 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec)\n", c.elapsed(), c.count, rate)
742 }
743 }
744 c.countLastLog = c.count
745 c.timeLastLog = now
746 }
747
748
749
750
751
752
753
754
755
756
757
758 func (c *coordinator) peekInput() (fuzzInput, bool) {
759 if c.opts.Limit > 0 && c.count+c.countWaiting >= c.opts.Limit {
760
761
762 return fuzzInput{}, false
763 }
764 if c.inputQueue.len == 0 {
765 if c.warmupRun() {
766
767
768 return fuzzInput{}, false
769 }
770 c.refillInputQueue()
771 }
772
773 entry, ok := c.inputQueue.peek()
774 if !ok {
775 panic("input queue empty after refill")
776 }
777 input := fuzzInput{
778 entry: entry.(CorpusEntry),
779 timeout: workerFuzzDuration,
780 warmup: c.warmupRun(),
781 }
782 if c.coverageMask != nil {
783 input.coverageData = bytes.Clone(c.coverageMask)
784 }
785 if input.warmup {
786
787
788 input.limit = 1
789 return input, true
790 }
791
792 if c.opts.Limit > 0 {
793 input.limit = c.opts.Limit / int64(c.opts.Parallel)
794 if c.opts.Limit%int64(c.opts.Parallel) > 0 {
795 input.limit++
796 }
797 remaining := c.opts.Limit - c.count - c.countWaiting
798 if input.limit > remaining {
799 input.limit = remaining
800 }
801 }
802 return input, true
803 }
804
805
806 func (c *coordinator) sentInput(input fuzzInput) {
807 c.inputQueue.dequeue()
808 c.countWaiting += input.limit
809 }
810
811
812
813 func (c *coordinator) refillInputQueue() {
814 for _, e := range c.corpus.entries {
815 c.inputQueue.enqueue(e)
816 }
817 }
818
819
820
821 func (c *coordinator) queueForMinimization(result fuzzResult, keepCoverage []byte) {
822 if shouldPrintDebugInfo() {
823 c.debugLogf(
824 "queueing input for minimization, id: %s, parent: %s, keepCoverage: %t, crasher: %t",
825 result.entry.Path,
826 result.entry.Parent,
827 keepCoverage != nil,
828 result.crasherMsg != "",
829 )
830 }
831 if result.crasherMsg != "" {
832 c.minimizeQueue.clear()
833 }
834
835 input := fuzzMinimizeInput{
836 entry: result.entry,
837 crasherMsg: result.crasherMsg,
838 keepCoverage: keepCoverage,
839 }
840 c.minimizeQueue.enqueue(input)
841 }
842
843
844
845 func (c *coordinator) peekMinimizeInput() (fuzzMinimizeInput, bool) {
846 if !c.canMinimize() {
847
848
849 return fuzzMinimizeInput{}, false
850 }
851 v, ok := c.minimizeQueue.peek()
852 if !ok {
853 return fuzzMinimizeInput{}, false
854 }
855 input := v.(fuzzMinimizeInput)
856
857 if c.opts.MinimizeTimeout > 0 {
858 input.timeout = c.opts.MinimizeTimeout
859 }
860 if c.opts.MinimizeLimit > 0 {
861 input.limit = c.opts.MinimizeLimit
862 } else if c.opts.Limit > 0 {
863 if input.crasherMsg != "" {
864 input.limit = c.opts.Limit
865 } else {
866 input.limit = c.opts.Limit / int64(c.opts.Parallel)
867 if c.opts.Limit%int64(c.opts.Parallel) > 0 {
868 input.limit++
869 }
870 }
871 }
872 if c.opts.Limit > 0 {
873 remaining := c.opts.Limit - c.count - c.countWaiting
874 if input.limit > remaining {
875 input.limit = remaining
876 }
877 }
878 return input, true
879 }
880
881
882
883 func (c *coordinator) sentMinimizeInput(input fuzzMinimizeInput) {
884 c.minimizeQueue.dequeue()
885 c.countWaiting += input.limit
886 }
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901 func (c *coordinator) warmupRun() bool {
902 return c.warmupInputLeft > 0
903 }
904
905
906
907
908 func (c *coordinator) updateCoverage(newCoverage []byte) int {
909 if len(newCoverage) != len(c.coverageMask) {
910 panic(fmt.Sprintf("number of coverage counters changed at runtime: %d, expected %d", len(newCoverage), len(c.coverageMask)))
911 }
912 newBitCount := 0
913 for i := range newCoverage {
914 diff := newCoverage[i] &^ c.coverageMask[i]
915 newBitCount += bits.OnesCount8(diff)
916 c.coverageMask[i] |= newCoverage[i]
917 }
918 return newBitCount
919 }
920
921
922
923 func (c *coordinator) canMinimize() bool {
924 return c.minimizationAllowed &&
925 (c.opts.Limit == 0 || c.count+c.countWaiting < c.opts.Limit)
926 }
927
928 func (c *coordinator) elapsed() time.Duration {
929 return time.Since(c.startTime).Round(1 * time.Second)
930 }
931
932
933
934
935
936
937 func (c *coordinator) readCache() error {
938 if _, err := c.addCorpusEntries(false, c.opts.Seed...); err != nil {
939 return err
940 }
941 entries, err := ReadCorpus(c.opts.CacheDir, c.opts.Types)
942 if err != nil {
943 if _, ok := err.(*MalformedCorpusError); !ok {
944
945
946 return err
947 }
948
949
950
951 }
952 if _, err := c.addCorpusEntries(false, entries...); err != nil {
953 return err
954 }
955 return nil
956 }
957
958
959
960
961 type MalformedCorpusError struct {
962 errs []error
963 }
964
965 func (e *MalformedCorpusError) Error() string {
966 var msgs []string
967 for _, s := range e.errs {
968 msgs = append(msgs, s.Error())
969 }
970 return strings.Join(msgs, "\n")
971 }
972
973
974
975
976
977 func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) {
978 files, err := os.ReadDir(dir)
979 if os.IsNotExist(err) {
980 return nil, nil
981 } else if err != nil {
982 return nil, fmt.Errorf("reading seed corpus from testdata: %v", err)
983 }
984 var corpus []CorpusEntry
985 var errs []error
986 for _, file := range files {
987
988
989
990
991
992 if file.IsDir() {
993 continue
994 }
995 filename := filepath.Join(dir, file.Name())
996 data, err := os.ReadFile(filename)
997 if err != nil {
998 return nil, fmt.Errorf("failed to read corpus file: %v", err)
999 }
1000 var vals []any
1001 vals, err = readCorpusData(data, types)
1002 if err != nil {
1003 errs = append(errs, fmt.Errorf("%q: %v", filename, err))
1004 continue
1005 }
1006 corpus = append(corpus, CorpusEntry{Path: filename, Values: vals})
1007 }
1008 if len(errs) > 0 {
1009 return corpus, &MalformedCorpusError{errs: errs}
1010 }
1011 return corpus, nil
1012 }
1013
1014 func readCorpusData(data []byte, types []reflect.Type) ([]any, error) {
1015 vals, err := unmarshalCorpusFile(data)
1016 if err != nil {
1017 return nil, fmt.Errorf("unmarshal: %v", err)
1018 }
1019 if err = CheckCorpus(vals, types); err != nil {
1020 return nil, err
1021 }
1022 return vals, nil
1023 }
1024
1025
1026
1027 func CheckCorpus(vals []any, types []reflect.Type) error {
1028 if len(vals) != len(types) {
1029 return fmt.Errorf("wrong number of values in corpus entry: %d, want %d", len(vals), len(types))
1030 }
1031 valsT := make([]reflect.Type, len(vals))
1032 for valsI, v := range vals {
1033 valsT[valsI] = reflect.TypeOf(v)
1034 }
1035 for i := range types {
1036 if valsT[i] != types[i] {
1037 return fmt.Errorf("mismatched types in corpus entry: %v, want %v", valsT, types)
1038 }
1039 }
1040 return nil
1041 }
1042
1043
1044
1045
1046
1047 func writeToCorpus(entry *CorpusEntry, dir string) (err error) {
1048 sum := fmt.Sprintf("%x", sha256.Sum256(entry.Data))[:16]
1049 entry.Path = filepath.Join(dir, sum)
1050 if err := os.MkdirAll(dir, 0777); err != nil {
1051 return err
1052 }
1053 if err := os.WriteFile(entry.Path, entry.Data, 0666); err != nil {
1054 os.Remove(entry.Path)
1055 return err
1056 }
1057 return nil
1058 }
1059
1060 func testName(path string) string {
1061 return filepath.Base(path)
1062 }
1063
1064 func zeroValue(t reflect.Type) any {
1065 for _, v := range zeroVals {
1066 if reflect.TypeOf(v) == t {
1067 return v
1068 }
1069 }
1070 panic(fmt.Sprintf("unsupported type: %v", t))
1071 }
1072
1073 var zeroVals []any = []any{
1074 []byte(""),
1075 string(""),
1076 false,
1077 byte(0),
1078 rune(0),
1079 float32(0),
1080 float64(0),
1081 int(0),
1082 int8(0),
1083 int16(0),
1084 int32(0),
1085 int64(0),
1086 uint(0),
1087 uint8(0),
1088 uint16(0),
1089 uint32(0),
1090 uint64(0),
1091 }
1092
1093 var debugInfo = godebug.New("#fuzzdebug").Value() == "1"
1094
1095 func shouldPrintDebugInfo() bool {
1096 return debugInfo
1097 }
1098
1099 func (c *coordinator) debugLogf(format string, args ...any) {
1100 t := time.Now().Format("2006-01-02 15:04:05.999999999")
1101 fmt.Fprintf(c.opts.Log, t+" DEBUG "+format+"\n", args...)
1102 }
1103
View as plain text