Source file src/encoding/json/jsontext/fuzz_test.go

     1  // Copyright 2023 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  //go:build goexperiment.jsonv2
     6  
     7  package jsontext
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"io"
    13  	"math/rand"
    14  	"slices"
    15  	"testing"
    16  
    17  	"encoding/json/internal/jsontest"
    18  )
    19  
    20  func FuzzCoder(f *testing.F) {
    21  	// Add a number of inputs to the corpus including valid and invalid data.
    22  	for _, td := range coderTestdata {
    23  		f.Add(int64(0), []byte(td.in))
    24  	}
    25  	for _, td := range decoderErrorTestdata {
    26  		f.Add(int64(0), []byte(td.in))
    27  	}
    28  	for _, td := range encoderErrorTestdata {
    29  		f.Add(int64(0), []byte(td.wantOut))
    30  	}
    31  	for _, td := range jsontest.Data {
    32  		f.Add(int64(0), td.Data())
    33  	}
    34  
    35  	f.Fuzz(func(t *testing.T, seed int64, b []byte) {
    36  		var tokVals []tokOrVal
    37  		rn := rand.NewSource(seed)
    38  
    39  		// Read a sequence of tokens or values. Skip the test for any errors
    40  		// since we expect this with randomly generated fuzz inputs.
    41  		src := bytes.NewReader(b)
    42  		dec := NewDecoder(src)
    43  		for {
    44  			if rn.Int63()%8 > 0 {
    45  				tok, err := dec.ReadToken()
    46  				if err != nil {
    47  					if err == io.EOF {
    48  						break
    49  					}
    50  					t.Skipf("Decoder.ReadToken error: %v", err)
    51  				}
    52  				tokVals = append(tokVals, tok.Clone())
    53  			} else {
    54  				val, err := dec.ReadValue()
    55  				if err != nil {
    56  					if expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']'; expectError {
    57  						if _, ok := errors.AsType[*SyntacticError](err); ok {
    58  							continue
    59  						}
    60  					}
    61  					if err == io.EOF {
    62  						break
    63  					}
    64  					t.Skipf("Decoder.ReadValue error: %v", err)
    65  				}
    66  				tokVals = append(tokVals, append(zeroValue, val...))
    67  			}
    68  		}
    69  
    70  		// Write a sequence of tokens or values. Fail the test for any errors
    71  		// since the previous stage guarantees that the input is valid.
    72  		dst := new(bytes.Buffer)
    73  		enc := NewEncoder(dst)
    74  		for _, tokVal := range tokVals {
    75  			switch tokVal := tokVal.(type) {
    76  			case Token:
    77  				if err := enc.WriteToken(tokVal); err != nil {
    78  					t.Fatalf("Encoder.WriteToken error: %v", err)
    79  				}
    80  			case Value:
    81  				if err := enc.WriteValue(tokVal); err != nil {
    82  					t.Fatalf("Encoder.WriteValue error: %v", err)
    83  				}
    84  			}
    85  		}
    86  
    87  		// Encoded output and original input must decode to the same thing.
    88  		var got, want []Token
    89  		for dec := NewDecoder(bytes.NewReader(b)); dec.PeekKind() > 0; {
    90  			tok, err := dec.ReadToken()
    91  			if err != nil {
    92  				t.Fatalf("Decoder.ReadToken error: %v", err)
    93  			}
    94  			got = append(got, tok.Clone())
    95  		}
    96  		for dec := NewDecoder(dst); dec.PeekKind() > 0; {
    97  			tok, err := dec.ReadToken()
    98  			if err != nil {
    99  				t.Fatalf("Decoder.ReadToken error: %v", err)
   100  			}
   101  			want = append(want, tok.Clone())
   102  		}
   103  		if !equalTokens(got, want) {
   104  			t.Fatalf("mismatching output:\ngot  %v\nwant %v", got, want)
   105  		}
   106  	})
   107  }
   108  
   109  func FuzzResumableDecoder(f *testing.F) {
   110  	for _, td := range resumableDecoderTestdata {
   111  		f.Add(int64(0), []byte(td))
   112  	}
   113  
   114  	f.Fuzz(func(t *testing.T, seed int64, b []byte) {
   115  		rn := rand.NewSource(seed)
   116  
   117  		// Regardless of how many bytes the underlying io.Reader produces,
   118  		// the provided tokens, values, and errors should always be identical.
   119  		t.Run("ReadToken", func(t *testing.T) {
   120  			decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn})
   121  			decWant := NewDecoder(bytes.NewReader(b))
   122  			gotTok, gotErr := decGot.ReadToken()
   123  			wantTok, wantErr := decWant.ReadToken()
   124  			if gotTok.String() != wantTok.String() || !equalError(gotErr, wantErr) {
   125  				t.Errorf("Decoder.ReadToken = (%v, %v), want (%v, %v)", gotTok, gotErr, wantTok, wantErr)
   126  			}
   127  		})
   128  		t.Run("ReadValue", func(t *testing.T) {
   129  			decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn})
   130  			decWant := NewDecoder(bytes.NewReader(b))
   131  			gotVal, gotErr := decGot.ReadValue()
   132  			wantVal, wantErr := decWant.ReadValue()
   133  			if !slices.Equal(gotVal, wantVal) || !equalError(gotErr, wantErr) {
   134  				t.Errorf("Decoder.ReadValue = (%s, %v), want (%s, %v)", gotVal, gotErr, wantVal, wantErr)
   135  			}
   136  		})
   137  	})
   138  }
   139  
   140  func FuzzValueFormat(f *testing.F) {
   141  	for _, td := range valueTestdata {
   142  		f.Add(int64(0), []byte(td.in))
   143  	}
   144  
   145  	// isValid reports whether b is valid according to the specified options.
   146  	isValid := func(b []byte, opts ...Options) bool {
   147  		d := NewDecoder(bytes.NewReader(b), opts...)
   148  		_, errVal := d.ReadValue()
   149  		_, errEOF := d.ReadToken()
   150  		return errVal == nil && errEOF == io.EOF
   151  	}
   152  
   153  	// stripWhitespace removes all JSON whitespace characters from the input.
   154  	stripWhitespace := func(in []byte) (out []byte) {
   155  		out = make([]byte, 0, len(in))
   156  		for _, c := range in {
   157  			switch c {
   158  			case ' ', '\n', '\r', '\t':
   159  			default:
   160  				out = append(out, c)
   161  			}
   162  		}
   163  		return out
   164  	}
   165  
   166  	allOptions := []Options{
   167  		AllowDuplicateNames(true),
   168  		AllowInvalidUTF8(true),
   169  		EscapeForHTML(true),
   170  		EscapeForJS(true),
   171  		PreserveRawStrings(true),
   172  		CanonicalizeRawInts(true),
   173  		CanonicalizeRawFloats(true),
   174  		ReorderRawObjects(true),
   175  		SpaceAfterColon(true),
   176  		SpaceAfterComma(true),
   177  		Multiline(true),
   178  		WithIndent("\t"),
   179  		WithIndentPrefix("    "),
   180  	}
   181  
   182  	f.Fuzz(func(t *testing.T, seed int64, b []byte) {
   183  		validRFC7159 := isValid(b, AllowInvalidUTF8(true), AllowDuplicateNames(true))
   184  		validRFC8259 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(true))
   185  		validRFC7493 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(false))
   186  		switch {
   187  		case !validRFC7159 && validRFC8259:
   188  			t.Errorf("invalid input per RFC 7159 implies invalid per RFC 8259")
   189  		case !validRFC8259 && validRFC7493:
   190  			t.Errorf("invalid input per RFC 8259 implies invalid per RFC 7493")
   191  		}
   192  
   193  		gotValid := Value(b).IsValid()
   194  		wantValid := validRFC7493
   195  		if gotValid != wantValid {
   196  			t.Errorf("Value.IsValid = %v, want %v", gotValid, wantValid)
   197  		}
   198  
   199  		gotCompacted := Value(string(b))
   200  		gotCompactOk := gotCompacted.Compact() == nil
   201  		wantCompactOk := validRFC7159
   202  		if !bytes.Equal(stripWhitespace(gotCompacted), stripWhitespace(b)) {
   203  			t.Errorf("stripWhitespace(Value.Compact) = %s, want %s", stripWhitespace(gotCompacted), stripWhitespace(b))
   204  		}
   205  		if gotCompactOk != wantCompactOk {
   206  			t.Errorf("Value.Compact success mismatch: got %v, want %v", gotCompactOk, wantCompactOk)
   207  		}
   208  
   209  		gotIndented := Value(string(b))
   210  		gotIndentOk := gotIndented.Indent() == nil
   211  		wantIndentOk := validRFC7159
   212  		if !bytes.Equal(stripWhitespace(gotIndented), stripWhitespace(b)) {
   213  			t.Errorf("stripWhitespace(Value.Indent) = %s, want %s", stripWhitespace(gotIndented), stripWhitespace(b))
   214  		}
   215  		if gotIndentOk != wantIndentOk {
   216  			t.Errorf("Value.Indent success mismatch: got %v, want %v", gotIndentOk, wantIndentOk)
   217  		}
   218  
   219  		gotCanonicalized := Value(string(b))
   220  		gotCanonicalizeOk := gotCanonicalized.Canonicalize() == nil
   221  		wantCanonicalizeOk := validRFC7493
   222  		if gotCanonicalizeOk != wantCanonicalizeOk {
   223  			t.Errorf("Value.Canonicalize success mismatch: got %v, want %v", gotCanonicalizeOk, wantCanonicalizeOk)
   224  		}
   225  
   226  		// Random options should not result in a panic.
   227  		var opts []Options
   228  		rn := rand.New(rand.NewSource(seed))
   229  		for _, opt := range allOptions {
   230  			if rn.Intn(len(allOptions)/4) == 0 {
   231  				opts = append(opts, opt)
   232  			}
   233  		}
   234  		v := Value(b)
   235  		v.Format(opts...) // should not panic
   236  	})
   237  }
   238  

View as plain text