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