| // Copyright 2021 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 | |
| import ( | |
| "math" | |
| "strconv" | |
| "testing" | |
| "unicode" | |
| ) | |
| func TestUnmarshalMarshal(t *testing.T) { | |
| var tests = []struct { | |
| desc string | |
| in string | |
| reject bool | |
| want string // if different from in | |
| }{ | |
| { | |
| desc: "missing version", | |
| in: "int(1234)", | |
| reject: true, | |
| }, | |
| { | |
| desc: "malformed string", | |
| in: `go test fuzz v1 | |
| string("a"bcad")`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "empty value", | |
| in: `go test fuzz v1 | |
| int()`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "negative uint", | |
| in: `go test fuzz v1 | |
| uint(-32)`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "int8 too large", | |
| in: `go test fuzz v1 | |
| int8(1234456)`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "multiplication in int value", | |
| in: `go test fuzz v1 | |
| int(20*5)`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "double negation", | |
| in: `go test fuzz v1 | |
| int(--5)`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "malformed bool", | |
| in: `go test fuzz v1 | |
| bool(0)`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "malformed byte", | |
| in: `go test fuzz v1 | |
| byte('aa)`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "byte out of range", | |
| in: `go test fuzz v1 | |
| byte('☃')`, | |
| reject: true, | |
| }, | |
| { | |
| desc: "extra newline", | |
| in: `go test fuzz v1 | |
| string("has extra newline") | |
| `, | |
| want: `go test fuzz v1 | |
| string("has extra newline")`, | |
| }, | |
| { | |
| desc: "trailing spaces", | |
| in: `go test fuzz v1 | |
| string("extra") | |
| []byte("spacing") | |
| `, | |
| want: `go test fuzz v1 | |
| string("extra") | |
| []byte("spacing")`, | |
| }, | |
| { | |
| desc: "float types", | |
| in: `go test fuzz v1 | |
| float64(0) | |
| float32(0)`, | |
| }, | |
| { | |
| desc: "various types", | |
| in: `go test fuzz v1 | |
| int(-23) | |
| int8(-2) | |
| int64(2342425) | |
| uint(1) | |
| uint16(234) | |
| uint32(352342) | |
| uint64(123) | |
| rune('œ') | |
| byte('K') | |
| byte('ÿ') | |
| []byte("hello¿") | |
| []byte("a") | |
| bool(true) | |
| string("hello\\xbd\\xb2=\\xbc ⌘") | |
| float64(-12.5) | |
| float32(2.5)`, | |
| }, | |
| { | |
| desc: "float edge cases", | |
| // The two IEEE 754 bit patterns used for the math.Float{64,32}frombits | |
| // encodings are non-math.NAN quiet-NaN values. Since they are not equal | |
| // to math.NaN(), they should be re-encoded to their bit patterns. They | |
| // are, respectively: | |
| // * math.Float64bits(math.NaN())+1 | |
| // * math.Float32bits(float32(math.NaN()))+1 | |
| in: `go test fuzz v1 | |
| float32(-0) | |
| float64(-0) | |
| float32(+Inf) | |
| float32(-Inf) | |
| float32(NaN) | |
| float64(+Inf) | |
| float64(-Inf) | |
| float64(NaN) | |
| math.Float64frombits(0x7ff8000000000002) | |
| math.Float32frombits(0x7fc00001)`, | |
| }, | |
| { | |
| desc: "int variations", | |
| // Although we arbitrarily choose default integer bases (0 or 16), we may | |
| // want to change those arbitrary choices in the future and should not | |
| // break the parser. Verify that integers in the opposite bases still | |
| // parse correctly. | |
| in: `go test fuzz v1 | |
| int(0x0) | |
| int32(0x41) | |
| int64(0xfffffffff) | |
| uint32(0xcafef00d) | |
| uint64(0xffffffffffffffff) | |
| uint8(0b0000000) | |
| byte(0x0) | |
| byte('\000') | |
| byte('\u0000') | |
| byte('\'') | |
| math.Float64frombits(9221120237041090562) | |
| math.Float32frombits(2143289345)`, | |
| want: `go test fuzz v1 | |
| int(0) | |
| rune('A') | |
| int64(68719476735) | |
| uint32(3405705229) | |
| uint64(18446744073709551615) | |
| byte('\x00') | |
| byte('\x00') | |
| byte('\x00') | |
| byte('\x00') | |
| byte('\'') | |
| math.Float64frombits(0x7ff8000000000002) | |
| math.Float32frombits(0x7fc00001)`, | |
| }, | |
| { | |
| desc: "rune validation", | |
| in: `go test fuzz v1 | |
| rune(0) | |
| rune(0x41) | |
| rune(-1) | |
| rune(0xfffd) | |
| rune(0xd800) | |
| rune(0x10ffff) | |
| rune(0x110000) | |
| `, | |
| want: `go test fuzz v1 | |
| rune('\x00') | |
| rune('A') | |
| int32(-1) | |
| rune('�') | |
| int32(55296) | |
| rune('\U0010ffff') | |
| int32(1114112)`, | |
| }, | |
| { | |
| desc: "int overflow", | |
| in: `go test fuzz v1 | |
| int(0x7fffffffffffffff) | |
| uint(0xffffffffffffffff)`, | |
| want: func() string { | |
| switch strconv.IntSize { | |
| case 32: | |
| return `go test fuzz v1 | |
| int(-1) | |
| uint(4294967295)` | |
| case 64: | |
| return `go test fuzz v1 | |
| int(9223372036854775807) | |
| uint(18446744073709551615)` | |
| default: | |
| panic("unreachable") | |
| } | |
| }(), | |
| }, | |
| { | |
| desc: "windows new line", | |
| in: "go test fuzz v1\r\nint(0)\r\n", | |
| want: "go test fuzz v1\nint(0)", | |
| }, | |
| } | |
| for _, test := range tests { | |
| t.Run(test.desc, func(t *testing.T) { | |
| vals, err := unmarshalCorpusFile([]byte(test.in)) | |
| if test.reject { | |
| if err == nil { | |
| t.Fatalf("unmarshal unexpected success") | |
| } | |
| return | |
| } | |
| if err != nil { | |
| t.Fatalf("unmarshal unexpected error: %v", err) | |
| } | |
| newB := marshalCorpusFile(vals...) | |
| if newB[len(newB)-1] != '\n' { | |
| t.Error("didn't write final newline to corpus file") | |
| } | |
| want := test.want | |
| if want == "" { | |
| want = test.in | |
| } | |
| want += "\n" | |
| got := string(newB) | |
| if got != want { | |
| t.Errorf("unexpected marshaled value\ngot:\n%s\nwant:\n%s", got, want) | |
| } | |
| }) | |
| } | |
| } | |
| // BenchmarkMarshalCorpusFile measures the time it takes to serialize byte | |
| // slices of various sizes to a corpus file. The slice contains a repeating | |
| // sequence of bytes 0-255 to mix escaped and non-escaped characters. | |
| func BenchmarkMarshalCorpusFile(b *testing.B) { | |
| buf := make([]byte, 1024*1024) | |
| for i := 0; i < len(buf); i++ { | |
| buf[i] = byte(i) | |
| } | |
| for sz := 1; sz <= len(buf); sz <<= 1 { | |
| b.Run(strconv.Itoa(sz), func(b *testing.B) { | |
| for i := 0; i < b.N; i++ { | |
| b.SetBytes(int64(sz)) | |
| marshalCorpusFile(buf[:sz]) | |
| } | |
| }) | |
| } | |
| } | |
| // BenchmarkUnmarshalCorpusFile measures the time it takes to deserialize | |
| // files encoding byte slices of various sizes. The slice contains a repeating | |
| // sequence of bytes 0-255 to mix escaped and non-escaped characters. | |
| func BenchmarkUnmarshalCorpusFile(b *testing.B) { | |
| buf := make([]byte, 1024*1024) | |
| for i := 0; i < len(buf); i++ { | |
| buf[i] = byte(i) | |
| } | |
| for sz := 1; sz <= len(buf); sz <<= 1 { | |
| data := marshalCorpusFile(buf[:sz]) | |
| b.Run(strconv.Itoa(sz), func(b *testing.B) { | |
| for i := 0; i < b.N; i++ { | |
| b.SetBytes(int64(sz)) | |
| unmarshalCorpusFile(data) | |
| } | |
| }) | |
| } | |
| } | |
| func TestByteRoundTrip(t *testing.T) { | |
| for x := 0; x < 256; x++ { | |
| b1 := byte(x) | |
| buf := marshalCorpusFile(b1) | |
| vs, err := unmarshalCorpusFile(buf) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| b2 := vs[0].(byte) | |
| if b2 != b1 { | |
| t.Fatalf("unmarshaled %v, want %v:\n%s", b2, b1, buf) | |
| } | |
| } | |
| } | |
| func TestInt8RoundTrip(t *testing.T) { | |
| for x := -128; x < 128; x++ { | |
| i1 := int8(x) | |
| buf := marshalCorpusFile(i1) | |
| vs, err := unmarshalCorpusFile(buf) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| i2 := vs[0].(int8) | |
| if i2 != i1 { | |
| t.Fatalf("unmarshaled %v, want %v:\n%s", i2, i1, buf) | |
| } | |
| } | |
| } | |
| func FuzzFloat64RoundTrip(f *testing.F) { | |
| f.Add(math.Float64bits(0)) | |
| f.Add(math.Float64bits(math.Copysign(0, -1))) | |
| f.Add(math.Float64bits(math.MaxFloat64)) | |
| f.Add(math.Float64bits(math.SmallestNonzeroFloat64)) | |
| f.Add(math.Float64bits(math.NaN())) | |
| f.Add(uint64(0x7FF0000000000001)) // signaling NaN | |
| f.Add(math.Float64bits(math.Inf(1))) | |
| f.Add(math.Float64bits(math.Inf(-1))) | |
| f.Fuzz(func(t *testing.T, u1 uint64) { | |
| x1 := math.Float64frombits(u1) | |
| b := marshalCorpusFile(x1) | |
| t.Logf("marshaled math.Float64frombits(0x%x):\n%s", u1, b) | |
| xs, err := unmarshalCorpusFile(b) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if len(xs) != 1 { | |
| t.Fatalf("unmarshaled %d values", len(xs)) | |
| } | |
| x2 := xs[0].(float64) | |
| u2 := math.Float64bits(x2) | |
| if u2 != u1 { | |
| t.Errorf("unmarshaled %v (bits 0x%x)", x2, u2) | |
| } | |
| }) | |
| } | |
| func FuzzRuneRoundTrip(f *testing.F) { | |
| f.Add(rune(-1)) | |
| f.Add(rune(0xd800)) | |
| f.Add(rune(0xdfff)) | |
| f.Add(rune(unicode.ReplacementChar)) | |
| f.Add(rune(unicode.MaxASCII)) | |
| f.Add(rune(unicode.MaxLatin1)) | |
| f.Add(rune(unicode.MaxRune)) | |
| f.Add(rune(unicode.MaxRune + 1)) | |
| f.Add(rune(-0x80000000)) | |
| f.Add(rune(0x7fffffff)) | |
| f.Fuzz(func(t *testing.T, r1 rune) { | |
| b := marshalCorpusFile(r1) | |
| t.Logf("marshaled rune(0x%x):\n%s", r1, b) | |
| rs, err := unmarshalCorpusFile(b) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if len(rs) != 1 { | |
| t.Fatalf("unmarshaled %d values", len(rs)) | |
| } | |
| r2 := rs[0].(rune) | |
| if r2 != r1 { | |
| t.Errorf("unmarshaled rune(0x%x)", r2) | |
| } | |
| }) | |
| } | |
| func FuzzStringRoundTrip(f *testing.F) { | |
| f.Add("") | |
| f.Add("\x00") | |
| f.Add(string([]rune{unicode.ReplacementChar})) | |
| f.Fuzz(func(t *testing.T, s1 string) { | |
| b := marshalCorpusFile(s1) | |
| t.Logf("marshaled %q:\n%s", s1, b) | |
| rs, err := unmarshalCorpusFile(b) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if len(rs) != 1 { | |
| t.Fatalf("unmarshaled %d values", len(rs)) | |
| } | |
| s2 := rs[0].(string) | |
| if s2 != s1 { | |
| t.Errorf("unmarshaled %q", s2) | |
| } | |
| }) | |
| } |