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 t.setOutputWriter()
324 if t.chatty != nil {
325 t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
326 }
327 f.common.inFuzzFn, f.inFuzzFn = true, true
328 go tRunner(t, func(t *T) {
329 args := []reflect.Value{reflect.ValueOf(t)}
330 for _, v := range e.Values {
331 args = append(args, reflect.ValueOf(v))
332 }
333
334
335
336
337 if f.tstate.isFuzzing {
338 defer f.fstate.deps.SnapshotCoverage()
339 f.fstate.deps.ResetCoverage()
340 }
341 fn.Call(args)
342 })
343 <-t.signal
344 if t.chatty != nil && t.chatty.json {
345 t.chatty.Updatef(t.parent.name, "=== NAME %s\n", t.parent.name)
346 }
347 f.common.inFuzzFn, f.inFuzzFn = false, false
348 return !t.Failed()
349 }
350
351 switch f.fstate.mode {
352 case fuzzCoordinator:
353
354
355
356 corpusTargetDir := filepath.Join(corpusDir, f.name)
357 cacheTargetDir := filepath.Join(*fuzzCacheDir, f.name)
358 err := f.fstate.deps.CoordinateFuzzing(
359 fuzzDuration.d,
360 int64(fuzzDuration.n),
361 minimizeDuration.d,
362 int64(minimizeDuration.n),
363 *parallel,
364 f.corpus,
365 types,
366 corpusTargetDir,
367 cacheTargetDir)
368 if err != nil {
369 f.result = fuzzResult{Error: err}
370 f.Fail()
371 fmt.Fprintf(f.w, "%v\n", err)
372 if crashErr, ok := err.(fuzzCrashError); ok {
373 crashPath := crashErr.CrashPath()
374 fmt.Fprintf(f.w, "Failing input written to %s\n", crashPath)
375 testName := filepath.Base(crashPath)
376 fmt.Fprintf(f.w, "To re-run:\ngo test -run=%s/%s\n", f.name, testName)
377 }
378 }
379
380
381
382 case fuzzWorker:
383
384
385 if err := f.fstate.deps.RunFuzzWorker(func(e corpusEntry) error {
386
387
388
389
390 var buf strings.Builder
391 if ok := run(&buf, e); !ok {
392 return errors.New(buf.String())
393 }
394 return nil
395 }); err != nil {
396
397
398
399 f.Errorf("communicating with fuzzing coordinator: %v", err)
400 }
401
402 default:
403
404
405 for _, e := range f.corpus {
406 name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path))
407 if _, ok, _ := f.tstate.match.fullName(nil, name); ok {
408 run(f.w, e)
409 }
410 }
411 }
412 }
413
414 func (f *F) report() {
415 if *isFuzzWorker || f.parent == nil {
416 return
417 }
418 dstr := fmtDuration(f.duration)
419 format := "--- %s: %s (%s)\n"
420 if f.Failed() {
421 f.flushToParent(f.name, format, "FAIL", f.name, dstr)
422 } else if f.chatty != nil {
423 if f.Skipped() {
424 f.flushToParent(f.name, format, "SKIP", f.name, dstr)
425 } else {
426 f.flushToParent(f.name, format, "PASS", f.name, dstr)
427 }
428 }
429 }
430
431
432 type fuzzResult struct {
433 N int
434 T time.Duration
435 Error error
436 }
437
438 func (r fuzzResult) String() string {
439 if r.Error == nil {
440 return ""
441 }
442 return r.Error.Error()
443 }
444
445
446
447
448
449 type fuzzCrashError interface {
450 error
451 Unwrap() error
452
453
454
455
456
457 CrashPath() string
458 }
459
460
461 type fuzzState struct {
462 deps testDeps
463 mode fuzzMode
464 }
465
466 type fuzzMode uint8
467
468 const (
469 seedCorpusOnly fuzzMode = iota
470 fuzzCoordinator
471 fuzzWorker
472 )
473
474
475
476
477 func runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.Time) (ran, ok bool) {
478 ok = true
479 if len(fuzzTests) == 0 || *isFuzzWorker {
480 return ran, ok
481 }
482 m := newMatcher(deps.MatchString, *match, "-test.run", *skip)
483 var mFuzz *matcher
484 if *matchFuzz != "" {
485 mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
486 }
487
488 for _, procs := range cpuList {
489 runtime.GOMAXPROCS(procs)
490 for i := uint(0); i < *count; i++ {
491 if shouldFailFast() {
492 break
493 }
494
495 tstate := newTestState(*parallel, m)
496 tstate.deadline = deadline
497 fstate := &fuzzState{deps: deps, mode: seedCorpusOnly}
498 root := common{w: os.Stdout}
499 if Verbose() {
500 root.chatty = newChattyPrinter(root.w)
501 }
502 for _, ft := range fuzzTests {
503 if shouldFailFast() {
504 break
505 }
506 testName, matched, _ := tstate.match.fullName(nil, ft.Name)
507 if !matched {
508 continue
509 }
510 if mFuzz != nil {
511 if _, fuzzMatched, _ := mFuzz.fullName(nil, ft.Name); fuzzMatched {
512
513
514 continue
515 }
516 }
517 ctx, cancelCtx := context.WithCancel(context.Background())
518 f := &F{
519 common: common{
520 signal: make(chan bool),
521 barrier: make(chan bool),
522 name: testName,
523 parent: &root,
524 level: root.level + 1,
525 chatty: root.chatty,
526 ctx: ctx,
527 cancelCtx: cancelCtx,
528 },
529 tstate: tstate,
530 fstate: fstate,
531 }
532 f.w = indenter{&f.common}
533 f.setOutputWriter()
534 if f.chatty != nil {
535 f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
536 }
537 go fRunner(f, ft.Fn)
538 <-f.signal
539 if f.chatty != nil && f.chatty.json {
540 f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)
541 }
542 ok = ok && !f.Failed()
543 ran = ran || f.ran
544 }
545 if !ran {
546
547
548 break
549 }
550 }
551 }
552
553 return ran, ok
554 }
555
556
557
558
559
560
561
562 func runFuzzing(deps testDeps, fuzzTests []InternalFuzzTarget) (ok bool) {
563 if len(fuzzTests) == 0 || *matchFuzz == "" {
564 return true
565 }
566 m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
567 tstate := newTestState(1, m)
568 tstate.isFuzzing = true
569 fstate := &fuzzState{
570 deps: deps,
571 }
572 root := common{w: os.Stdout}
573 if *isFuzzWorker {
574 root.w = io.Discard
575 fstate.mode = fuzzWorker
576 } else {
577 fstate.mode = fuzzCoordinator
578 }
579 if Verbose() && !*isFuzzWorker {
580 root.chatty = newChattyPrinter(root.w)
581 }
582 var fuzzTest *InternalFuzzTarget
583 var testName string
584 var matched []string
585 for i := range fuzzTests {
586 name, ok, _ := tstate.match.fullName(nil, fuzzTests[i].Name)
587 if !ok {
588 continue
589 }
590 matched = append(matched, name)
591 fuzzTest = &fuzzTests[i]
592 testName = name
593 }
594 if len(matched) == 0 {
595 fmt.Fprintln(os.Stderr, "testing: warning: no fuzz tests to fuzz")
596 return true
597 }
598 if len(matched) > 1 {
599 fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one fuzz test: %v\n", matched)
600 return false
601 }
602
603 ctx, cancelCtx := context.WithCancel(context.Background())
604 f := &F{
605 common: common{
606 signal: make(chan bool),
607 barrier: nil,
608 name: testName,
609 parent: &root,
610 level: root.level + 1,
611 chatty: root.chatty,
612 ctx: ctx,
613 cancelCtx: cancelCtx,
614 },
615 fstate: fstate,
616 tstate: tstate,
617 }
618 f.w = indenter{&f.common}
619 f.setOutputWriter()
620 if f.chatty != nil {
621 f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
622 }
623 go fRunner(f, fuzzTest.Fn)
624 <-f.signal
625 if f.chatty != nil {
626 f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)
627 }
628 return !f.failed
629 }
630
631
632
633
634
635
636
637
638
639
640
641 func fRunner(f *F, fn func(*F)) {
642
643
644
645 defer func() {
646
647
648
649
650
651
652
653 f.checkRaces()
654 if f.Failed() {
655 numFailed.Add(1)
656 }
657 err := recover()
658 if err == nil {
659 f.mu.RLock()
660 fuzzNotCalled := !f.fuzzCalled && !f.skipped && !f.failed
661 if !f.finished && !f.skipped && !f.failed {
662 err = errNilPanicOrGoexit
663 }
664 f.mu.RUnlock()
665 if fuzzNotCalled && err == nil {
666 f.Error("returned without calling F.Fuzz, F.Fail, or F.Skip")
667 }
668 }
669
670
671
672 didPanic := false
673 defer func() {
674 if !didPanic {
675
676
677
678 f.signal <- true
679 }
680 }()
681
682
683
684 doPanic := func(err any) {
685 f.Fail()
686 if r := f.runCleanup(recoverAndReturnPanic); r != nil {
687 f.Logf("cleanup panicked with %v", r)
688 }
689 for root := &f.common; root.parent != nil; root = root.parent {
690 root.mu.Lock()
691 root.duration += highPrecisionTimeSince(root.start)
692 d := root.duration
693 root.mu.Unlock()
694 root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))
695 }
696 didPanic = true
697 panic(err)
698 }
699 if err != nil {
700 doPanic(err)
701 }
702
703
704 f.duration += highPrecisionTimeSince(f.start)
705
706 if len(f.sub) > 0 {
707
708
709
710
711 f.tstate.release()
712 close(f.barrier)
713
714 for _, sub := range f.sub {
715 <-sub.signal
716 }
717 cleanupStart := highPrecisionTimeNow()
718 err := f.runCleanup(recoverAndReturnPanic)
719 f.duration += highPrecisionTimeSince(cleanupStart)
720 if err != nil {
721 doPanic(err)
722 }
723 }
724
725
726 f.report()
727 f.done = true
728 f.setRan()
729 }()
730 defer func() {
731 if len(f.sub) == 0 {
732 f.runCleanup(normalPanic)
733 }
734 }()
735
736 f.start = highPrecisionTimeNow()
737 f.resetRaces()
738 fn(f)
739
740
741
742 f.mu.Lock()
743 f.finished = true
744 f.mu.Unlock()
745 }
746
View as plain text