405 lines
8.2 KiB
Go
405 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)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|