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  			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.
   256  func 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.
   276  func 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  
   294  func 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  
   309  func 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  
   324  func 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  
   355  func 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  
   385  func 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  

View as plain text