You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
6.9 KiB
236 lines
6.9 KiB
// 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 |
|
}) |
|
}
|
|
|