Source file
src/testing/fuzz.go
1
2
3
4
5 package testing
6
7 import (
8 "context"
9 "errors"
10 "flag"
11 "fmt"
12 "io"
13 "os"
14 "path/filepath"
15 "reflect"
16 "runtime"
17 "strings"
18 "time"
19 )
20
21 func initFuzzFlags() {
22 matchFuzz = flag.String("test.fuzz", "", "run the fuzz test matching `regexp`")
23 flag.Var(&fuzzDuration, "test.fuzztime", "time to spend fuzzing; default is to run indefinitely")
24 flag.Var(&minimizeDuration, "test.fuzzminimizetime", "time to spend minimizing a value after finding a failing input")
25
26 fuzzCacheDir = flag.String("test.fuzzcachedir", "", "directory where interesting fuzzing inputs are stored (for use only by cmd/go)")
27 isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values (for use only by cmd/go)")
28 }
29
30 var (
31 matchFuzz *string
32 fuzzDuration durationOrCountFlag
33 minimizeDuration = durationOrCountFlag{d: 60 * time.Second, allowZero: true}
34 fuzzCacheDir *string
35 isFuzzWorker *bool
36
37
38
39 corpusDir = "testdata/fuzz"
40 )
41
42
43
44
45 const fuzzWorkerExitCode = 70
46
47
48
49 type InternalFuzzTarget struct {
50 Name string
51 Fn func(f *F)
52 }
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 type F struct {
70 common
71 fstate *fuzzState
72 tstate *testState
73
74
75
76 inFuzzFn bool
77
78
79
80 corpus []corpusEntry
81
82 result fuzzResult
83 fuzzCalled bool
84 }
85
86 var _ TB = (*F)(nil)
87
88
89
90
91 type corpusEntry = struct {
92 Parent string
93 Path string
94 Data []byte
95 Values []any
96 Generation int
97 IsSeed bool
98 }
99
100
101
102
103 func (f *F) Helper() {
104 if f.inFuzzFn {
105 panic("testing: f.Helper was called inside the fuzz target, use t.Helper instead")
106 }
107
108
109
110
111 f.mu.Lock()
112 defer f.mu.Unlock()
113 if f.helperPCs == nil {
114 f.helperPCs = make(map[uintptr]struct{})
115 }
116
117 var pc [1]uintptr
118 n := runtime.Callers(2, pc[:])
119 if n == 0 {
120 panic("testing: zero callers found")
121 }
122 if _, found := f.helperPCs[pc[0]]; !found {
123 f.helperPCs[pc[0]] = struct{}{}
124 f.helperNames = nil
125 }
126 }
127
128
129 func (f *F) Fail() {
130
131
132 if f.inFuzzFn {
133 panic("testing: f.Fail was called inside the fuzz target, use t.Fail instead")
134 }
135 f.common.Helper()
136 f.common.Fail()
137 }
138
139
140 func (f *F) Skipped() bool {
141
142
143 if f.inFuzzFn {
144 panic("testing: f.Skipped was called inside the fuzz target, use t.Skipped instead")
145 }
146 f.common.Helper()
147 return f.common.Skipped()
148 }
149
150
151
152
153 func (f *F) Add(args ...any) {
154 var values []any
155 for i := range args {
156 if t := reflect.TypeOf(args[i]); !supportedTypes[t] {
157 panic(fmt.Sprintf("testing: unsupported type to Add %v", t))
158 }
159 values = append(values, args[i])
160 }
161 f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.corpus))})
162 }
163
164
165 var supportedTypes = map[reflect.Type]bool{
166 reflect.TypeOf(([]byte)("")): true,
167 reflect.TypeOf((string)("")): true,
168 reflect.TypeOf((bool)(false)): true,
169 reflect.TypeOf((byte)(0)): true,
170 reflect.TypeOf((rune)(0)): true,
171 reflect.TypeOf((float32)(0)): true,
172 reflect.TypeOf((float64)(0)): true,
173 reflect.TypeOf((int)(0)): true,
174 reflect.TypeOf((int8)(0)): true,
175 reflect.TypeOf((int16)(0)): true,
176 reflect.TypeOf((int32)(0)): true,
177 reflect.TypeOf((int64)(0)): true,
178 reflect.TypeOf((uint)(0)): true,
179 reflect.TypeOf((uint8)(0)): true,
180 reflect.TypeOf((uint16)(0)): true,
181 reflect.TypeOf((uint32)(0)): true,
182 reflect.TypeOf((uint64)(0)): true,
183 }
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 func (f *F) Fuzz(ff any) {
212 if f.fuzzCalled {
213 panic("testing: F.Fuzz called more than once")
214 }
215 f.fuzzCalled = true
216 if f.failed {
217 return
218 }
219 f.Helper()
220
221
222 fn := reflect.ValueOf(ff)
223 fnType := fn.Type()
224 if fnType.Kind() != reflect.Func {
225 panic("testing: F.Fuzz must receive a function")
226 }
227 if fnType.NumIn() < 2 || fnType.In(0) != reflect.TypeOf((*T)(nil)) {
228 panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T")
229 }
230 if fnType.NumOut() != 0 {
231 panic("testing: fuzz target must not return a value")
232 }
233
234
235 var types []reflect.Type
236 for i := 1; i < fnType.NumIn(); i++ {
237 t := fnType.In(i)
238 if !supportedTypes[t] {
239 panic(fmt.Sprintf("testing: unsupported type for fuzzing %v", t))
240 }
241 types = append(types, t)
242 }
243
244
245
246
247
248 if f.fstate.mode != fuzzWorker {
249 for _, c := range f.corpus {
250 if err := f.fstate.deps.CheckCorpus(c.Values, types); err != nil {
251
252 f.Fatal(err)
253 }
254 }
255
256
257 c, err := f.fstate.deps.ReadCorpus(filepath.Join(corpusDir, f.name), types)
258 if err != nil {
259 f.Fatal(err)
260 }
261 for i := range c {
262 c[i].IsSeed = true
263 if f.fstate.mode == fuzzCoordinator {
264
265
266 c[i].Values = nil
267 }
268 }
269
270 f.corpus = append(f.corpus, c...)
271 }
272
273
274
275
276 run := func(captureOut io.Writer, e corpusEntry) (ok bool) {
277 if e.Values == nil {
278
279
280 panic(fmt.Sprintf("corpus file %q was not unmarshaled", e.Path))
281 }
282 if shouldFailFast() {
283 return true
284 }
285 testName := f.name
286 if e.Path != "" {
287 testName = fmt.Sprintf("%s/%s", testName, filepath.Base(e.Path))
288 }
289 if f.tstate.isFuzzing {
290
291
292
293
294 f.tstate.match.clearSubNames()
295 }
296
297 ctx, cancelCtx := context.WithCancel(f.ctx)
298
299
300
301
302 var pc [maxStackLen]uintptr
303 n := runtime.Callers(2, pc[:])
304 t := &T{
305 common: common{
306 barrier: make(chan bool),
307 signal: make(chan bool),
308 name: testName,
309 parent: &f.common,
310 level: f.level + 1,
311 creator: pc[:n],
312 chatty: f.chatty,
313 ctx: ctx,
314 cancelCtx: cancelCtx,
315 },
316 tstate: f.tstate,
317 }
318 if captureOut != nil {
319
320 t.parent.w = captureOut
321 }
322 t.w = indenter{&t.common}
323 if t.chatty != nil {
324 t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
325 }
326 f.common.inFuzzFn, f.inFuzzFn = true, true
327 go tRunner(t, func(t *T) {
328 args := []reflect.Value{reflect.ValueOf(t)}
329 for _, v := range e.Values {
330 args = append(args, reflect.ValueOf(v))
331 }
332
333
334
335
336 if f.tstate.isFuzzing {
337 defer f.fstate.deps.SnapshotCoverage()
338 f.fstate.deps.ResetCoverage()
339 }
340 fn.Call(args)
341 })
342 <-t.signal
343 if t.chatty != nil && t.chatty.json {
344 t.chatty.Updatef(t.parent.name, "=== NAME %s\n", t.parent.name)
345 }
346 f.common.inFuzzFn, f.inFuzzFn = false, false
347 return !t.Failed()
348 }
349
350 switch f.fstate.mode {
351 case fuzzCoordinator:
352
353
354
355 corpusTargetDir := filepath.Join(corpusDir, f.name)
356 cacheTargetDir := filepath.Join(*fuzzCacheDir, f.name)
357 err := f.fstate.deps.CoordinateFuzzing(
358 fuzzDuration.d,
359 int64(fuzzDuration.n),
360 minimizeDuration.d,
361 int64(minimizeDuration.n),
362 *parallel,
363 f.corpus,
364 types,
365 corpusTargetDir,
366 cacheTargetDir)
367 if err != nil {
368 f.result = fuzzResult{Error: err}
369 f.Fail()
370 fmt.Fprintf(f.w, "%v\n", err)
371 if crashErr, ok := err.(fuzzCrashError); ok {
372 crashPath := crashErr.CrashPath()
373 fmt.Fprintf(f.w, "Failing input written to %s\n", crashPath)
374 testName := filepath.Base(crashPath)
375 fmt.Fprintf(f.w, "To re-run:\ngo test -run=%s/%s\n", f.name, testName)
376 }
377 }
378
379
380
381 case fuzzWorker:
382
383
384 if err := f.fstate.deps.RunFuzzWorker(func(e corpusEntry) error {
385
386
387
388
389 var buf strings.Builder
390 if ok := run(&buf, e); !ok {
391 return errors.New(buf.String())
392 }
393 return nil
394 }); err != nil {
395
396
397
398 f.Errorf("communicating with fuzzing coordinator: %v", err)
399 }
400
401 default:
402
403
404 for _, e := range f.corpus {
405 name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path))
406 if _, ok, _ := f.tstate.match.fullName(nil, name); ok {
407 run(f.w, e)
408 }
409 }
410 }
411 }
412
413 func (f *F) report() {
414 if *isFuzzWorker || f.parent == nil {
415 return
416 }
417 dstr := fmtDuration(f.duration)
418 format := "--- %s: %s (%s)\n"
419 if f.Failed() {
420 f.flushToParent(f.name, format, "FAIL", f.name, dstr)
421 } else if f.chatty != nil {
422 if f.Skipped() {
423 f.flushToParent(f.name, format, "SKIP", f.name, dstr)
424 } else {
425 f.flushToParent(f.name, format, "PASS", f.name, dstr)
426 }
427 }
428 }
429
430
431 type fuzzResult struct {
432 N int
433 T time.Duration
434 Error error
435 }
436
437 func (r fuzzResult) String() string {
438 if r.Error == nil {
439 return ""
440 }
441 return r.Error.Error()
442 }
443
444
445
446
447
448 type fuzzCrashError interface {
449 error
450 Unwrap() error
451
452
453
454
455
456 CrashPath() string
457 }
458
459
460 type fuzzState struct {
461 deps testDeps
462 mode fuzzMode
463 }
464
465 type fuzzMode uint8
466
467 const (
468 seedCorpusOnly fuzzMode = iota
469 fuzzCoordinator
470 fuzzWorker
471 )
472
473
474
475
476 func runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.Time) (ran, ok bool) {
477 ok = true
478 if len(fuzzTests) == 0 || *isFuzzWorker {
479 return ran, ok
480 }
481 m := newMatcher(deps.MatchString, *match, "-test.run", *skip)
482 var mFuzz *matcher
483 if *matchFuzz != "" {
484 mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
485 }
486
487 for _, procs := range cpuList {
488 runtime.GOMAXPROCS(procs)
489 for i := uint(0); i < *count; i++ {
490 if shouldFailFast() {
491 break
492 }
493
494 tstate := newTestState(*parallel, m)
495 tstate.deadline = deadline
496 fstate := &fuzzState{deps: deps, mode: seedCorpusOnly}
497 root := common{w: os.Stdout}
498 if Verbose() {
499 root.chatty = newChattyPrinter(root.w)
500 }
501 for _, ft := range fuzzTests {
502 if shouldFailFast() {
503 break
504 }
505 testName, matched, _ := tstate.match.fullName(nil, ft.Name)
506 if !matched {
507 continue
508 }
509 if mFuzz != nil {
510 if _, fuzzMatched, _ := mFuzz.fullName(nil, ft.Name); fuzzMatched {
511
512
513 continue
514 }
515 }
516 ctx, cancelCtx := context.WithCancel(context.Background())
517 f := &F{
518 common: common{
519 signal: make(chan bool),
520 barrier: make(chan bool),
521 name: testName,
522 parent: &root,
523 level: root.level + 1,
524 chatty: root.chatty,
525 ctx: ctx,
526 cancelCtx: cancelCtx,
527 },
528 tstate: tstate,
529 fstate: fstate,
530 }
531 f.w = indenter{&f.common}
532 if f.chatty != nil {
533 f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
534 }
535 go fRunner(f, ft.Fn)
536 <-f.signal
537 if f.chatty != nil && f.chatty.json {
538 f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)
539 }
540 ok = ok && !f.Failed()
541 ran = ran || f.ran
542 }
543 if !ran {
544
545
546 break
547 }
548 }
549 }
550
551 return ran, ok
552 }
553
554
555
556
557
558
559
560 func runFuzzing(deps testDeps, fuzzTests []InternalFuzzTarget) (ok bool) {
561 if len(fuzzTests) == 0 || *matchFuzz == "" {
562 return true
563 }
564 m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
565 tstate := newTestState(1, m)
566 tstate.isFuzzing = true
567 fstate := &fuzzState{
568 deps: deps,
569 }
570 root := common{w: os.Stdout}
571 if *isFuzzWorker {
572 root.w = io.Discard
573 fstate.mode = fuzzWorker
574 } else {
575 fstate.mode = fuzzCoordinator
576 }
577 if Verbose() && !*isFuzzWorker {
578 root.chatty = newChattyPrinter(root.w)
579 }
580 var fuzzTest *InternalFuzzTarget
581 var testName string
582 var matched []string
583 for i := range fuzzTests {
584 name, ok, _ := tstate.match.fullName(nil, fuzzTests[i].Name)
585 if !ok {
586 continue
587 }
588 matched = append(matched, name)
589 fuzzTest = &fuzzTests[i]
590 testName = name
591 }
592 if len(matched) == 0 {
593 fmt.Fprintln(os.Stderr, "testing: warning: no fuzz tests to fuzz")
594 return true
595 }
596 if len(matched) > 1 {
597 fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one fuzz test: %v\n", matched)
598 return false
599 }
600
601 ctx, cancelCtx := context.WithCancel(context.Background())
602 f := &F{
603 common: common{
604 signal: make(chan bool),
605 barrier: nil,
606 name: testName,
607 parent: &root,
608 level: root.level + 1,
609 chatty: root.chatty,
610 ctx: ctx,
611 cancelCtx: cancelCtx,
612 },
613 fstate: fstate,
614 tstate: tstate,
615 }
616 f.w = indenter{&f.common}
617 if f.chatty != nil {
618 f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
619 }
620 go fRunner(f, fuzzTest.Fn)
621 <-f.signal
622 if f.chatty != nil {
623 f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)
624 }
625 return !f.failed
626 }
627
628
629
630
631
632
633
634
635
636
637
638 func fRunner(f *F, fn func(*F)) {
639
640
641
642 defer func() {
643
644
645
646
647
648
649
650 f.checkRaces()
651 if f.Failed() {
652 numFailed.Add(1)
653 }
654 err := recover()
655 if err == nil {
656 f.mu.RLock()
657 fuzzNotCalled := !f.fuzzCalled && !f.skipped && !f.failed
658 if !f.finished && !f.skipped && !f.failed {
659 err = errNilPanicOrGoexit
660 }
661 f.mu.RUnlock()
662 if fuzzNotCalled && err == nil {
663 f.Error("returned without calling F.Fuzz, F.Fail, or F.Skip")
664 }
665 }
666
667
668
669 didPanic := false
670 defer func() {
671 if !didPanic {
672
673
674
675 f.signal <- true
676 }
677 }()
678
679
680
681 doPanic := func(err any) {
682 f.Fail()
683 if r := f.runCleanup(recoverAndReturnPanic); r != nil {
684 f.Logf("cleanup panicked with %v", r)
685 }
686 for root := &f.common; root.parent != nil; root = root.parent {
687 root.mu.Lock()
688 root.duration += highPrecisionTimeSince(root.start)
689 d := root.duration
690 root.mu.Unlock()
691 root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))
692 }
693 didPanic = true
694 panic(err)
695 }
696 if err != nil {
697 doPanic(err)
698 }
699
700
701 f.duration += highPrecisionTimeSince(f.start)
702
703 if len(f.sub) > 0 {
704
705
706
707
708 f.tstate.release()
709 close(f.barrier)
710
711 for _, sub := range f.sub {
712 <-sub.signal
713 }
714 cleanupStart := highPrecisionTimeNow()
715 err := f.runCleanup(recoverAndReturnPanic)
716 f.duration += highPrecisionTimeSince(cleanupStart)
717 if err != nil {
718 doPanic(err)
719 }
720 }
721
722
723 f.report()
724 f.done = true
725 f.setRan()
726 }()
727 defer func() {
728 if len(f.sub) == 0 {
729 f.runCleanup(normalPanic)
730 }
731 }()
732
733 f.start = highPrecisionTimeNow()
734 f.resetRaces()
735 fn(f)
736
737
738
739 f.mu.Lock()
740 f.finished = true
741 f.mu.Unlock()
742 }
743
View as plain text