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