mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-22 20:01:31 -05:00
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
414 lines
8.5 KiB
Go
414 lines
8.5 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"
|
||
"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)
|
||
}
|
||
})
|
||
}
|