From d35495540df2b6d3ba16c84ce21627d9dbae000c Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 10 Feb 2023 23:38:26 -0700 Subject: Fuzz for equivalence between stdlib and lowmemjson --- compat/json/equiv_test.go | 160 +++++++++++++++++++++ .../json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b | 2 + .../json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1 | 2 + .../json/testdata/fuzz/FuzzEquiv/57365320c0968611 | 2 + .../json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae | 2 + .../json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32 | 2 + .../json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84 | 2 + .../json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81 | 2 + .../json/testdata/fuzz/FuzzEquiv/96aac43014471adc | 2 + .../json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f | 2 + .../json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d | 2 + .../json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c | 2 + .../json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8 | 2 + .../json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00 | 2 + .../json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6 | 2 + .../json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f | 2 + 16 files changed, 190 insertions(+) create mode 100644 compat/json/equiv_test.go create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f diff --git a/compat/json/equiv_test.go b/compat/json/equiv_test.go new file mode 100644 index 0000000..246e4b3 --- /dev/null +++ b/compat/json/equiv_test.go @@ -0,0 +1,160 @@ +// Copyright (C) 2023 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package json_test + +import ( + "bytes" + std "encoding/json" + "errors" + "io" + "strconv" + "strings" + "testing" + "unicode/utf8" + + "github.com/stretchr/testify/assert" + + low "git.lukeshu.com/go/lowmemjson/compat/json" +) + +func assertEquivErr(t *testing.T, stdErr, lowErr error) { + if (stdErr == nil) || (lowErr == nil) { + // Nil-equal. + assert.Equal(t, stdErr, lowErr) + return + } + switch stdErr.(type) { + case *std.SyntaxError: + if lowErr != nil { + stdMsg := stdErr.Error() + lowMsg := lowErr.Error() + + // https://github.com/golang/go/issues/58680 + if strings.HasPrefix(stdMsg, `invalid character ' ' `) && + (errors.Is(lowErr, io.ErrUnexpectedEOF) || lowMsg == "unexpected end of JSON input") { + return + } + + // https://github.com/golang/go/issues/58713 + prefix := `invalid character '` + if stdMsg != lowMsg && strings.HasPrefix(stdMsg, prefix) && strings.HasPrefix(lowMsg, prefix) { + stdRune, stdRuneSize := utf8.DecodeRuneInString(stdMsg[len(prefix):]) + lowByte := lowMsg[len(prefix)] + if lowByte == '\\' { + switch lowMsg[len(prefix)+1] { + case 'u': + lowRune, _ := strconv.ParseUint(lowMsg[len(prefix)+2:][:4], 16, 32) + var buf [4]byte + utf8.EncodeRune(buf[:], rune(lowRune)) + lowByte = buf[0] + case 'U': + lowRune, _ := strconv.ParseUint(lowMsg[len(prefix)+2:][:8], 16, 32) + var buf [4]byte + utf8.EncodeRune(buf[:], rune(lowRune)) + lowByte = buf[0] + } + } + if stdRune == rune(lowByte) { + lowRuneStr := lowMsg[len(prefix):] + lowRuneStr = lowRuneStr[:strings.IndexByte(lowRuneStr, '\'')] + stdMsg = prefix + lowRuneStr + stdMsg[len(prefix)+stdRuneSize:] + stdErr = errors.New(stdMsg) + } + } + } + // Text-equal. + assert.Equal(t, stdErr.Error(), lowErr.Error()) + // TODO: Assert that they are deep-equal (but be permissive of these not being type aliases). + case *std.MarshalerError: + // Text-equal. + assert.Equal(t, stdErr.Error(), lowErr.Error()) + // TODO: Assert that they are deep-equal (but be permissive of these not being type aliases). + default: + // Text-equal. + assert.Equal(t, stdErr.Error(), lowErr.Error()) + // TODO: Assert that they are deep-equal. + } +} + +func FuzzEquiv(f *testing.F) { + f.Fuzz(func(t *testing.T, str []byte) { + t.Logf("str=%q", str) + t.Run("HTMLEscape", func(t *testing.T) { + var stdOut bytes.Buffer + std.HTMLEscape(&stdOut, str) + + var lowOut bytes.Buffer + low.HTMLEscape(&lowOut, str) + + assert.Equal(t, stdOut.String(), lowOut.String()) + }) + t.Run("Compact", func(t *testing.T) { + var stdOut bytes.Buffer + stdErr := std.Compact(&stdOut, str) + + var lowOut bytes.Buffer + lowErr := low.Compact(&lowOut, str) + + assert.Equal(t, stdOut.String(), lowOut.String()) + assertEquivErr(t, stdErr, lowErr) + }) + t.Run("Indent", func(t *testing.T) { + var stdOut bytes.Buffer + stdErr := std.Indent(&stdOut, str, "»", "\t") + + var lowOut bytes.Buffer + lowErr := low.Indent(&lowOut, str, "»", "\t") + + assert.Equal(t, stdOut.String(), lowOut.String()) + assertEquivErr(t, stdErr, lowErr) + }) + t.Run("Valid", func(t *testing.T) { + stdValid := std.Valid(str) && utf8.Valid(str) // https://github.com/golang/go/issues/58517 + lowValid := low.Valid(str) + assert.Equal(t, stdValid, lowValid) + }) + t.Run("Decode-Encode", func(t *testing.T) { + var stdObj any + stdErr := std.NewDecoder(bytes.NewReader(str)).Decode(&stdObj) + + var lowObj any + lowErr := low.NewDecoder(bytes.NewReader(str)).Decode(&lowObj) + + assert.Equal(t, stdObj, lowObj) + assertEquivErr(t, stdErr, lowErr) + if t.Failed() { + return + } + + var stdOut bytes.Buffer + stdErr = std.NewEncoder(&stdOut).Encode(stdObj) + + var lowOut bytes.Buffer + lowErr = low.NewEncoder(&lowOut).Encode(lowObj) + + assert.Equal(t, stdOut.String(), lowOut.String()) + assertEquivErr(t, stdErr, lowErr) + }) + t.Run("Unmarshal-Marshal", func(t *testing.T) { + var stdObj any + stdErr := std.Unmarshal(str, &stdObj) + + var lowObj any + lowErr := low.Unmarshal(str, &lowObj) + + assert.Equal(t, stdObj, lowObj) + assertEquivErr(t, stdErr, lowErr) + if t.Failed() { + return + } + + stdOut, stdErr := std.Marshal(stdObj) + lowOut, lowErr := low.Marshal(lowObj) + + assert.Equal(t, string(stdOut), string(lowOut)) + assertEquivErr(t, stdErr, lowErr) + }) + }) +} diff --git a/compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b b/compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b new file mode 100644 index 0000000..96e9e53 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("𐠁") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1 b/compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1 new file mode 100644 index 0000000..ecbe8af --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("A") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611 b/compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611 new file mode 100644 index 0000000..5aace7f --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae b/compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae new file mode 100644 index 0000000..a51778b --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0E00") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32 b/compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32 new file mode 100644 index 0000000..fe2e128 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\"\\uD800\"") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84 b/compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84 new file mode 100644 index 0000000..e3c530f --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\uebae") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81 b/compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81 new file mode 100644 index 0000000..e8000f3 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc b/compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc new file mode 100644 index 0000000..9461c7a --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\"\\") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f b/compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f new file mode 100644 index 0000000..1edfb06 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\"") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d b/compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d new file mode 100644 index 0000000..b3c523c --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0.") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c b/compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c new file mode 100644 index 0000000..ca6f6f5 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\"\\u") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8 b/compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8 new file mode 100644 index 0000000..778cc61 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0 ") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00 b/compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00 new file mode 100644 index 0000000..9644b51 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\"0\x85\xcd\xc0\xf3\xcb\xc1\xb3\xf2\xf5\xa4\xc1\xd40\xba\xe9\"") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6 b/compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6 new file mode 100644 index 0000000..712fab9 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\U00054516") diff --git a/compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f b/compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f new file mode 100644 index 0000000..9dc2675 --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("Ǒ") -- cgit v1.1-4-g5e80