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 err != nil {
   237  				t.Fatalf("marshal unexpected error: %v", err)
   238  			}
   239  			if newB[len(newB)-1] != '\n' {
   240  				t.Error("didn't write final newline to corpus file")
   241  			}
   242  
   243  			want := test.want
   244  			if want == "" {
   245  				want = test.in
   246  			}
   247  			want += "\n"
   248  			got := string(newB)
   249  			if got != want {
   250  				t.Errorf("unexpected marshaled value\ngot:\n%s\nwant:\n%s", got, want)
   251  			}
   252  		})
   253  	}
   254  }
   255  
   256  // BenchmarkMarshalCorpusFile measures the time it takes to serialize byte
   257  // slices of various sizes to a corpus file. The slice contains a repeating
   258  // sequence of bytes 0-255 to mix escaped and non-escaped characters.
   259  func BenchmarkMarshalCorpusFile(b *testing.B) {
   260  	buf := make([]byte, 1024*1024)
   261  	for i := 0; i < len(buf); i++ {
   262  		buf[i] = byte(i)
   263  	}
   264  
   265  	for sz := 1; sz <= len(buf); sz <<= 1 {
   266  		sz := sz
   267  		b.Run(strconv.Itoa(sz), func(b *testing.B) {
   268  			for i := 0; i < b.N; i++ {
   269  				b.SetBytes(int64(sz))
   270  				marshalCorpusFile(buf[:sz])
   271  			}
   272  		})
   273  	}
   274  }
   275  
   276  // BenchmarkUnmarshalCorpusfile measures the time it takes to deserialize
   277  // files encoding byte slices of various sizes. The slice contains a repeating
   278  // sequence of bytes 0-255 to mix escaped and non-escaped characters.
   279  func BenchmarkUnmarshalCorpusFile(b *testing.B) {
   280  	buf := make([]byte, 1024*1024)
   281  	for i := 0; i < len(buf); i++ {
   282  		buf[i] = byte(i)
   283  	}
   284  
   285  	for sz := 1; sz <= len(buf); sz <<= 1 {
   286  		sz := sz
   287  		data := marshalCorpusFile(buf[:sz])
   288  		b.Run(strconv.Itoa(sz), func(b *testing.B) {
   289  			for i := 0; i < b.N; i++ {
   290  				b.SetBytes(int64(sz))
   291  				unmarshalCorpusFile(data)
   292  			}
   293  		})
   294  	}
   295  }
   296  
   297  func TestByteRoundTrip(t *testing.T) {
   298  	for x := 0; x < 256; x++ {
   299  		b1 := byte(x)
   300  		buf := marshalCorpusFile(b1)
   301  		vs, err := unmarshalCorpusFile(buf)
   302  		if err != nil {
   303  			t.Fatal(err)
   304  		}
   305  		b2 := vs[0].(byte)
   306  		if b2 != b1 {
   307  			t.Fatalf("unmarshaled %v, want %v:\n%s", b2, b1, buf)
   308  		}
   309  	}
   310  }
   311  
   312  func TestInt8RoundTrip(t *testing.T) {
   313  	for x := -128; x < 128; x++ {
   314  		i1 := int8(x)
   315  		buf := marshalCorpusFile(i1)
   316  		vs, err := unmarshalCorpusFile(buf)
   317  		if err != nil {
   318  			t.Fatal(err)
   319  		}
   320  		i2 := vs[0].(int8)
   321  		if i2 != i1 {
   322  			t.Fatalf("unmarshaled %v, want %v:\n%s", i2, i1, buf)
   323  		}
   324  	}
   325  }
   326  
   327  func FuzzFloat64RoundTrip(f *testing.F) {
   328  	f.Add(math.Float64bits(0))
   329  	f.Add(math.Float64bits(math.Copysign(0, -1)))
   330  	f.Add(math.Float64bits(math.MaxFloat64))
   331  	f.Add(math.Float64bits(math.SmallestNonzeroFloat64))
   332  	f.Add(math.Float64bits(math.NaN()))
   333  	f.Add(uint64(0x7FF0000000000001)) // signaling NaN
   334  	f.Add(math.Float64bits(math.Inf(1)))
   335  	f.Add(math.Float64bits(math.Inf(-1)))
   336  
   337  	f.Fuzz(func(t *testing.T, u1 uint64) {
   338  		x1 := math.Float64frombits(u1)
   339  
   340  		b := marshalCorpusFile(x1)
   341  		t.Logf("marshaled math.Float64frombits(0x%x):\n%s", u1, b)
   342  
   343  		xs, err := unmarshalCorpusFile(b)
   344  		if err != nil {
   345  			t.Fatal(err)
   346  		}
   347  		if len(xs) != 1 {
   348  			t.Fatalf("unmarshaled %d values", len(xs))
   349  		}
   350  		x2 := xs[0].(float64)
   351  		u2 := math.Float64bits(x2)
   352  		if u2 != u1 {
   353  			t.Errorf("unmarshaled %v (bits 0x%x)", x2, u2)
   354  		}
   355  	})
   356  }
   357  
   358  func FuzzRuneRoundTrip(f *testing.F) {
   359  	f.Add(rune(-1))
   360  	f.Add(rune(0xd800))
   361  	f.Add(rune(0xdfff))
   362  	f.Add(rune(unicode.ReplacementChar))
   363  	f.Add(rune(unicode.MaxASCII))
   364  	f.Add(rune(unicode.MaxLatin1))
   365  	f.Add(rune(unicode.MaxRune))
   366  	f.Add(rune(unicode.MaxRune + 1))
   367  	f.Add(rune(-0x80000000))
   368  	f.Add(rune(0x7fffffff))
   369  
   370  	f.Fuzz(func(t *testing.T, r1 rune) {
   371  		b := marshalCorpusFile(r1)
   372  		t.Logf("marshaled rune(0x%x):\n%s", r1, b)
   373  
   374  		rs, err := unmarshalCorpusFile(b)
   375  		if err != nil {
   376  			t.Fatal(err)
   377  		}
   378  		if len(rs) != 1 {
   379  			t.Fatalf("unmarshaled %d values", len(rs))
   380  		}
   381  		r2 := rs[0].(rune)
   382  		if r2 != r1 {
   383  			t.Errorf("unmarshaled rune(0x%x)", r2)
   384  		}
   385  	})
   386  }
   387  
   388  func FuzzStringRoundTrip(f *testing.F) {
   389  	f.Add("")
   390  	f.Add("\x00")
   391  	f.Add(string([]rune{unicode.ReplacementChar}))
   392  
   393  	f.Fuzz(func(t *testing.T, s1 string) {
   394  		b := marshalCorpusFile(s1)
   395  		t.Logf("marshaled %q:\n%s", s1, b)
   396  
   397  		rs, err := unmarshalCorpusFile(b)
   398  		if err != nil {
   399  			t.Fatal(err)
   400  		}
   401  		if len(rs) != 1 {
   402  			t.Fatalf("unmarshaled %d values", len(rs))
   403  		}
   404  		s2 := rs[0].(string)
   405  		if s2 != s1 {
   406  			t.Errorf("unmarshaled %q", s2)
   407  		}
   408  	})
   409  }
   410  

View as plain text