404 lines
8.2 KiB
Go
404 lines
8.2 KiB
Go
// 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('<27>')
|
||
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")
|
||
}
|
||
}(),
|
||
},
|
||
}
|
||
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 err != nil {
|
||
t.Fatalf("marshal unexpected error: %v", err)
|
||
}
|
||
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 {
|
||
sz := sz
|
||
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 {
|
||
sz := sz
|
||
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)
|
||
}
|
||
})
|
||
}
|