Files
gcc-reflection/libgo/go/internal/fuzz/encoding_test.go
Ian Lance Taylor bc6bd0d608 libgo/testsuite: add benchmarks and examples to list
In CL 384695 I simplified the code that built lists of benchmarks,
examples, and fuzz tests, and managed to break it. This CL corrects
the code to once again make the benchmarks available, and to run
the examples with output and the fuzz targets.

Doing this revealed a test failure in internal/fuzz on 32-bit x86:
a signalling NaN is turned into a quiet NaN on the 387 floating-point
stack that GCC uses by default. This CL skips the test.

Fixes golang/go#60826

Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/503798
2023-06-16 12:29:04 -07:00

414 lines
8.5 KiB
Go
Raw Permalink Blame History

// 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"
"runtime"
"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) {
// The signaling NaN test fails on 32-bit x86 with gccgo,
// which uses the 387 floating-point stack by default.
// Converting a signaling NaN in and out of the stack
// changes the NaN to a quiet NaN.
if runtime.GOARCH == "386" && u1 == 0x7FF0000000000001 {
t.Skip("skipping signalling NaN test on 386 with gccgo")
}
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)
}
})
}