Source file src/internal/fuzz/encoding_test.go

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

View as plain text