1// Copyright 2021 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package fuzz
6
7import (
8	"math"
9	"strconv"
10	"testing"
11	"unicode"
12)
13
14func TestUnmarshalMarshal(t *testing.T) {
15	var tests = []struct {
16		desc   string
17		in     string
18		reject bool
19		want   string // if different from in
20	}{
21		{
22			desc:   "missing version",
23			in:     "int(1234)",
24			reject: true,
25		},
26		{
27			desc: "malformed string",
28			in: `go test fuzz v1
29string("a"bcad")`,
30			reject: true,
31		},
32		{
33			desc: "empty value",
34			in: `go test fuzz v1
35int()`,
36			reject: true,
37		},
38		{
39			desc: "negative uint",
40			in: `go test fuzz v1
41uint(-32)`,
42			reject: true,
43		},
44		{
45			desc: "int8 too large",
46			in: `go test fuzz v1
47int8(1234456)`,
48			reject: true,
49		},
50		{
51			desc: "multiplication in int value",
52			in: `go test fuzz v1
53int(20*5)`,
54			reject: true,
55		},
56		{
57			desc: "double negation",
58			in: `go test fuzz v1
59int(--5)`,
60			reject: true,
61		},
62		{
63			desc: "malformed bool",
64			in: `go test fuzz v1
65bool(0)`,
66			reject: true,
67		},
68		{
69			desc: "malformed byte",
70			in: `go test fuzz v1
71byte('aa)`,
72			reject: true,
73		},
74		{
75			desc: "byte out of range",
76			in: `go test fuzz v1
77byte('☃')`,
78			reject: true,
79		},
80		{
81			desc: "extra newline",
82			in: `go test fuzz v1
83string("has extra newline")
84`,
85			want: `go test fuzz v1
86string("has extra newline")`,
87		},
88		{
89			desc: "trailing spaces",
90			in: `go test fuzz v1
91string("extra")
92[]byte("spacing")
93    `,
94			want: `go test fuzz v1
95string("extra")
96[]byte("spacing")`,
97		},
98		{
99			desc: "float types",
100			in: `go test fuzz v1
101float64(0)
102float32(0)`,
103		},
104		{
105			desc: "various types",
106			in: `go test fuzz v1
107int(-23)
108int8(-2)
109int64(2342425)
110uint(1)
111uint16(234)
112uint32(352342)
113uint64(123)
114rune('œ')
115byte('K')
116byte('ÿ')
117[]byte("hello¿")
118[]byte("a")
119bool(true)
120string("hello\\xbd\\xb2=\\xbc")
121float64(-12.5)
122float32(2.5)`,
123		},
124		{
125			desc: "float edge cases",
126			// The two IEEE 754 bit patterns used for the math.Float{64,32}frombits
127			// encodings are non-math.NAN quiet-NaN values. Since they are not equal
128			// to math.NaN(), they should be re-encoded to their bit patterns. They
129			// are, respectively:
130			//   * math.Float64bits(math.NaN())+1
131			//   * math.Float32bits(float32(math.NaN()))+1
132			in: `go test fuzz v1
133float32(-0)
134float64(-0)
135float32(+Inf)
136float32(-Inf)
137float32(NaN)
138float64(+Inf)
139float64(-Inf)
140float64(NaN)
141math.Float64frombits(0x7ff8000000000002)
142math.Float32frombits(0x7fc00001)`,
143		},
144		{
145			desc: "int variations",
146			// Although we arbitrarily choose default integer bases (0 or 16), we may
147			// want to change those arbitrary choices in the future and should not
148			// break the parser. Verify that integers in the opposite bases still
149			// parse correctly.
150			in: `go test fuzz v1
151int(0x0)
152int32(0x41)
153int64(0xfffffffff)
154uint32(0xcafef00d)
155uint64(0xffffffffffffffff)
156uint8(0b0000000)
157byte(0x0)
158byte('\000')
159byte('\u0000')
160byte('\'')
161math.Float64frombits(9221120237041090562)
162math.Float32frombits(2143289345)`,
163			want: `go test fuzz v1
164int(0)
165rune('A')
166int64(68719476735)
167uint32(3405705229)
168uint64(18446744073709551615)
169byte('\x00')
170byte('\x00')
171byte('\x00')
172byte('\x00')
173byte('\'')
174math.Float64frombits(0x7ff8000000000002)
175math.Float32frombits(0x7fc00001)`,
176		},
177		{
178			desc: "rune validation",
179			in: `go test fuzz v1
180rune(0)
181rune(0x41)
182rune(-1)
183rune(0xfffd)
184rune(0xd800)
185rune(0x10ffff)
186rune(0x110000)
187`,
188			want: `go test fuzz v1
189rune('\x00')
190rune('A')
191int32(-1)
192rune('�')
193int32(55296)
194rune('\U0010ffff')
195int32(1114112)`,
196		},
197		{
198			desc: "int overflow",
199			in: `go test fuzz v1
200int(0x7fffffffffffffff)
201uint(0xffffffffffffffff)`,
202			want: func() string {
203				switch strconv.IntSize {
204				case 32:
205					return `go test fuzz v1
206int(-1)
207uint(4294967295)`
208				case 64:
209					return `go test fuzz v1
210int(9223372036854775807)
211uint(18446744073709551615)`
212				default:
213					panic("unreachable")
214				}
215			}(),
216		},
217		{
218			desc: "windows new line",
219			in:   "go test fuzz v1\r\nint(0)\r\n",
220			want: "go test fuzz v1\nint(0)",
221		},
222	}
223	for _, test := range tests {
224		t.Run(test.desc, func(t *testing.T) {
225			vals, err := unmarshalCorpusFile([]byte(test.in))
226			if test.reject {
227				if err == nil {
228					t.Fatalf("unmarshal unexpected success")
229				}
230				return
231			}
232			if err != nil {
233				t.Fatalf("unmarshal unexpected error: %v", err)
234			}
235			newB := marshalCorpusFile(vals...)
236			if newB[len(newB)-1] != '\n' {
237				t.Error("didn't write final newline to corpus file")
238			}
239
240			want := test.want
241			if want == "" {
242				want = test.in
243			}
244			want += "\n"
245			got := string(newB)
246			if got != want {
247				t.Errorf("unexpected marshaled value\ngot:\n%s\nwant:\n%s", got, want)
248			}
249		})
250	}
251}
252
253// BenchmarkMarshalCorpusFile measures the time it takes to serialize byte
254// slices of various sizes to a corpus file. The slice contains a repeating
255// sequence of bytes 0-255 to mix escaped and non-escaped characters.
256func BenchmarkMarshalCorpusFile(b *testing.B) {
257	buf := make([]byte, 1024*1024)
258	for i := 0; i < len(buf); i++ {
259		buf[i] = byte(i)
260	}
261
262	for sz := 1; sz <= len(buf); sz <<= 1 {
263		sz := sz
264		b.Run(strconv.Itoa(sz), func(b *testing.B) {
265			for i := 0; i < b.N; i++ {
266				b.SetBytes(int64(sz))
267				marshalCorpusFile(buf[:sz])
268			}
269		})
270	}
271}
272
273// BenchmarkUnmarshalCorpusfile measures the time it takes to deserialize
274// files encoding byte slices of various sizes. The slice contains a repeating
275// sequence of bytes 0-255 to mix escaped and non-escaped characters.
276func BenchmarkUnmarshalCorpusFile(b *testing.B) {
277	buf := make([]byte, 1024*1024)
278	for i := 0; i < len(buf); i++ {
279		buf[i] = byte(i)
280	}
281
282	for sz := 1; sz <= len(buf); sz <<= 1 {
283		sz := sz
284		data := marshalCorpusFile(buf[:sz])
285		b.Run(strconv.Itoa(sz), func(b *testing.B) {
286			for i := 0; i < b.N; i++ {
287				b.SetBytes(int64(sz))
288				unmarshalCorpusFile(data)
289			}
290		})
291	}
292}
293
294func TestByteRoundTrip(t *testing.T) {
295	for x := 0; x < 256; x++ {
296		b1 := byte(x)
297		buf := marshalCorpusFile(b1)
298		vs, err := unmarshalCorpusFile(buf)
299		if err != nil {
300			t.Fatal(err)
301		}
302		b2 := vs[0].(byte)
303		if b2 != b1 {
304			t.Fatalf("unmarshaled %v, want %v:\n%s", b2, b1, buf)
305		}
306	}
307}
308
309func TestInt8RoundTrip(t *testing.T) {
310	for x := -128; x < 128; x++ {
311		i1 := int8(x)
312		buf := marshalCorpusFile(i1)
313		vs, err := unmarshalCorpusFile(buf)
314		if err != nil {
315			t.Fatal(err)
316		}
317		i2 := vs[0].(int8)
318		if i2 != i1 {
319			t.Fatalf("unmarshaled %v, want %v:\n%s", i2, i1, buf)
320		}
321	}
322}
323
324func FuzzFloat64RoundTrip(f *testing.F) {
325	f.Add(math.Float64bits(0))
326	f.Add(math.Float64bits(math.Copysign(0, -1)))
327	f.Add(math.Float64bits(math.MaxFloat64))
328	f.Add(math.Float64bits(math.SmallestNonzeroFloat64))
329	f.Add(math.Float64bits(math.NaN()))
330	f.Add(uint64(0x7FF0000000000001)) // signaling NaN
331	f.Add(math.Float64bits(math.Inf(1)))
332	f.Add(math.Float64bits(math.Inf(-1)))
333
334	f.Fuzz(func(t *testing.T, u1 uint64) {
335		x1 := math.Float64frombits(u1)
336
337		b := marshalCorpusFile(x1)
338		t.Logf("marshaled math.Float64frombits(0x%x):\n%s", u1, b)
339
340		xs, err := unmarshalCorpusFile(b)
341		if err != nil {
342			t.Fatal(err)
343		}
344		if len(xs) != 1 {
345			t.Fatalf("unmarshaled %d values", len(xs))
346		}
347		x2 := xs[0].(float64)
348		u2 := math.Float64bits(x2)
349		if u2 != u1 {
350			t.Errorf("unmarshaled %v (bits 0x%x)", x2, u2)
351		}
352	})
353}
354
355func FuzzRuneRoundTrip(f *testing.F) {
356	f.Add(rune(-1))
357	f.Add(rune(0xd800))
358	f.Add(rune(0xdfff))
359	f.Add(rune(unicode.ReplacementChar))
360	f.Add(rune(unicode.MaxASCII))
361	f.Add(rune(unicode.MaxLatin1))
362	f.Add(rune(unicode.MaxRune))
363	f.Add(rune(unicode.MaxRune + 1))
364	f.Add(rune(-0x80000000))
365	f.Add(rune(0x7fffffff))
366
367	f.Fuzz(func(t *testing.T, r1 rune) {
368		b := marshalCorpusFile(r1)
369		t.Logf("marshaled rune(0x%x):\n%s", r1, b)
370
371		rs, err := unmarshalCorpusFile(b)
372		if err != nil {
373			t.Fatal(err)
374		}
375		if len(rs) != 1 {
376			t.Fatalf("unmarshaled %d values", len(rs))
377		}
378		r2 := rs[0].(rune)
379		if r2 != r1 {
380			t.Errorf("unmarshaled rune(0x%x)", r2)
381		}
382	})
383}
384
385func FuzzStringRoundTrip(f *testing.F) {
386	f.Add("")
387	f.Add("\x00")
388	f.Add(string([]rune{unicode.ReplacementChar}))
389
390	f.Fuzz(func(t *testing.T, s1 string) {
391		b := marshalCorpusFile(s1)
392		t.Logf("marshaled %q:\n%s", s1, b)
393
394		rs, err := unmarshalCorpusFile(b)
395		if err != nil {
396			t.Fatal(err)
397		}
398		if len(rs) != 1 {
399			t.Fatalf("unmarshaled %d values", len(rs))
400		}
401		s2 := rs[0].(string)
402		if s2 != s1 {
403			t.Errorf("unmarshaled %q", s2)
404		}
405	})
406}
407