// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build goexperiment.jsonv2 package jsontext import ( "bytes" "errors" "io" "math/rand" "slices" "testing" "encoding/json/internal/jsontest" ) func FuzzCoder(f *testing.F) { // Add a number of inputs to the corpus including valid and invalid data. for _, td := range coderTestdata { f.Add(int64(0), []byte(td.in)) } for _, td := range decoderErrorTestdata { f.Add(int64(0), []byte(td.in)) } for _, td := range encoderErrorTestdata { f.Add(int64(0), []byte(td.wantOut)) } for _, td := range jsontest.Data { f.Add(int64(0), td.Data()) } f.Fuzz(func(t *testing.T, seed int64, b []byte) { var tokVals []tokOrVal rn := rand.NewSource(seed) // Read a sequence of tokens or values. Skip the test for any errors // since we expect this with randomly generated fuzz inputs. src := bytes.NewReader(b) dec := NewDecoder(src) for { if rn.Int63()%8 > 0 { tok, err := dec.ReadToken() if err != nil { if err == io.EOF { break } t.Skipf("Decoder.ReadToken error: %v", err) } tokVals = append(tokVals, tok.Clone()) } else { val, err := dec.ReadValue() if err != nil { expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']' if expectError && errors.As(err, new(*SyntacticError)) { continue } if err == io.EOF { break } t.Skipf("Decoder.ReadValue error: %v", err) } tokVals = append(tokVals, append(zeroValue, val...)) } } // Write a sequence of tokens or values. Fail the test for any errors // since the previous stage guarantees that the input is valid. dst := new(bytes.Buffer) enc := NewEncoder(dst) for _, tokVal := range tokVals { switch tokVal := tokVal.(type) { case Token: if err := enc.WriteToken(tokVal); err != nil { t.Fatalf("Encoder.WriteToken error: %v", err) } case Value: if err := enc.WriteValue(tokVal); err != nil { t.Fatalf("Encoder.WriteValue error: %v", err) } } } // Encoded output and original input must decode to the same thing. var got, want []Token for dec := NewDecoder(bytes.NewReader(b)); dec.PeekKind() > 0; { tok, err := dec.ReadToken() if err != nil { t.Fatalf("Decoder.ReadToken error: %v", err) } got = append(got, tok.Clone()) } for dec := NewDecoder(dst); dec.PeekKind() > 0; { tok, err := dec.ReadToken() if err != nil { t.Fatalf("Decoder.ReadToken error: %v", err) } want = append(want, tok.Clone()) } if !equalTokens(got, want) { t.Fatalf("mismatching output:\ngot %v\nwant %v", got, want) } }) } func FuzzResumableDecoder(f *testing.F) { for _, td := range resumableDecoderTestdata { f.Add(int64(0), []byte(td)) } f.Fuzz(func(t *testing.T, seed int64, b []byte) { rn := rand.NewSource(seed) // Regardless of how many bytes the underlying io.Reader produces, // the provided tokens, values, and errors should always be identical. t.Run("ReadToken", func(t *testing.T) { decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn}) decWant := NewDecoder(bytes.NewReader(b)) gotTok, gotErr := decGot.ReadToken() wantTok, wantErr := decWant.ReadToken() if gotTok.String() != wantTok.String() || !equalError(gotErr, wantErr) { t.Errorf("Decoder.ReadToken = (%v, %v), want (%v, %v)", gotTok, gotErr, wantTok, wantErr) } }) t.Run("ReadValue", func(t *testing.T) { decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn}) decWant := NewDecoder(bytes.NewReader(b)) gotVal, gotErr := decGot.ReadValue() wantVal, wantErr := decWant.ReadValue() if !slices.Equal(gotVal, wantVal) || !equalError(gotErr, wantErr) { t.Errorf("Decoder.ReadValue = (%s, %v), want (%s, %v)", gotVal, gotErr, wantVal, wantErr) } }) }) } func FuzzValueFormat(f *testing.F) { for _, td := range valueTestdata { f.Add(int64(0), []byte(td.in)) } // isValid reports whether b is valid according to the specified options. isValid := func(b []byte, opts ...Options) bool { d := NewDecoder(bytes.NewReader(b), opts...) _, errVal := d.ReadValue() _, errEOF := d.ReadToken() return errVal == nil && errEOF == io.EOF } // stripWhitespace removes all JSON whitespace characters from the input. stripWhitespace := func(in []byte) (out []byte) { out = make([]byte, 0, len(in)) for _, c := range in { switch c { case ' ', '\n', '\r', '\t': default: out = append(out, c) } } return out } allOptions := []Options{ AllowDuplicateNames(true), AllowInvalidUTF8(true), EscapeForHTML(true), EscapeForJS(true), PreserveRawStrings(true), CanonicalizeRawInts(true), CanonicalizeRawFloats(true), ReorderRawObjects(true), SpaceAfterColon(true), SpaceAfterComma(true), Multiline(true), WithIndent("\t"), WithIndentPrefix(" "), } f.Fuzz(func(t *testing.T, seed int64, b []byte) { validRFC7159 := isValid(b, AllowInvalidUTF8(true), AllowDuplicateNames(true)) validRFC8259 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(true)) validRFC7493 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(false)) switch { case !validRFC7159 && validRFC8259: t.Errorf("invalid input per RFC 7159 implies invalid per RFC 8259") case !validRFC8259 && validRFC7493: t.Errorf("invalid input per RFC 8259 implies invalid per RFC 7493") } gotValid := Value(b).IsValid() wantValid := validRFC7493 if gotValid != wantValid { t.Errorf("Value.IsValid = %v, want %v", gotValid, wantValid) } gotCompacted := Value(string(b)) gotCompactOk := gotCompacted.Compact() == nil wantCompactOk := validRFC7159 if !bytes.Equal(stripWhitespace(gotCompacted), stripWhitespace(b)) { t.Errorf("stripWhitespace(Value.Compact) = %s, want %s", stripWhitespace(gotCompacted), stripWhitespace(b)) } if gotCompactOk != wantCompactOk { t.Errorf("Value.Compact success mismatch: got %v, want %v", gotCompactOk, wantCompactOk) } gotIndented := Value(string(b)) gotIndentOk := gotIndented.Indent() == nil wantIndentOk := validRFC7159 if !bytes.Equal(stripWhitespace(gotIndented), stripWhitespace(b)) { t.Errorf("stripWhitespace(Value.Indent) = %s, want %s", stripWhitespace(gotIndented), stripWhitespace(b)) } if gotIndentOk != wantIndentOk { t.Errorf("Value.Indent success mismatch: got %v, want %v", gotIndentOk, wantIndentOk) } gotCanonicalized := Value(string(b)) gotCanonicalizeOk := gotCanonicalized.Canonicalize() == nil wantCanonicalizeOk := validRFC7493 if gotCanonicalizeOk != wantCanonicalizeOk { t.Errorf("Value.Canonicalize success mismatch: got %v, want %v", gotCanonicalizeOk, wantCanonicalizeOk) } // Random options should not result in a panic. var opts []Options rn := rand.New(rand.NewSource(seed)) for _, opt := range allOptions { if rn.Intn(len(allOptions)/4) == 0 { opts = append(opts, opt) } } v := Value(b) v.Format(opts...) // should not panic }) }