From 3e2f6edc8667fe96de771b8429c15f8d8024e3ec Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 05:39:49 +0000 Subject: [PATCH] Update module github.com/buger/jsonparser to v1.2.0 Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- go.mod | 8 +- go.sum | 4 +- vendor/github.com/buger/jsonparser/.gitignore | 6 + .../github.com/buger/jsonparser/.travis.yml | 11 +- vendor/github.com/buger/jsonparser/README.md | 23 ++- vendor/github.com/buger/jsonparser/bytes.go | 37 ++-- .../github.com/buger/jsonparser/bytes_safe.go | 3 +- .../buger/jsonparser/bytes_unsafe.go | 3 +- vendor/github.com/buger/jsonparser/escape.go | 15 +- vendor/github.com/buger/jsonparser/fuzz.go | 14 ++ vendor/github.com/buger/jsonparser/parser.go | 177 +++++++++++++----- vendor/github.com/buger/jsonparser/proof.yaml | 96 ++++++++++ vendor/modules.txt | 2 +- 13 files changed, 313 insertions(+), 86 deletions(-) create mode 100644 vendor/github.com/buger/jsonparser/proof.yaml diff --git a/go.mod b/go.mod index e6c7bddb9..afc6ce002 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,7 @@ module github.com/redhat-appstudio/tssc-cli go 1.25.7 -require ( - github.com/redhat-appstudio/helmet v0.0.0-20260319215325-e665a08127fc - golang.org/x/term v0.41.0 -) +require github.com/redhat-appstudio/helmet v0.0.0-20260319215325-e665a08127fc require ( cel.dev/expr v0.25.1 // indirect @@ -23,7 +20,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/buger/jsonparser v1.1.1 // indirect + github.com/buger/jsonparser v1.2.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/containerd/containerd v1.7.30 // indirect @@ -130,6 +127,7 @@ require ( golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.41.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c // indirect diff --git a/go.sum b/go.sum index c05c5d58c..7ca1c7178 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/buger/jsonparser v1.2.0 h1:4EFcvK1kD4jyj6YqNK6skK6w+y7FHHBR+XBCtxwu/6g= +github.com/buger/jsonparser v1.2.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= diff --git a/vendor/github.com/buger/jsonparser/.gitignore b/vendor/github.com/buger/jsonparser/.gitignore index 5598d8a56..af589f860 100644 --- a/vendor/github.com/buger/jsonparser/.gitignore +++ b/vendor/github.com/buger/jsonparser/.gitignore @@ -10,3 +10,9 @@ vendor/github.com/buger/goterm/ prof.cpu prof.mem +.proof/ +tests/ +PROOF_*.md +REQPROOF_*.md +proof-ux-log.md +PROOF_UNDER_MODELED_REQUIREMENTS_PROPOSAL.md diff --git a/vendor/github.com/buger/jsonparser/.travis.yml b/vendor/github.com/buger/jsonparser/.travis.yml index dbfb7cf98..56f9c9c42 100644 --- a/vendor/github.com/buger/jsonparser/.travis.yml +++ b/vendor/github.com/buger/jsonparser/.travis.yml @@ -3,9 +3,10 @@ arch: - amd64 - ppc64le go: - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x + - 1.13.x + - 1.14.x + - 1.15.x + - 1.16.x + - 1.17.x + - 1.18.x script: go test -v ./. diff --git a/vendor/github.com/buger/jsonparser/README.md b/vendor/github.com/buger/jsonparser/README.md index d7e0ec397..391edba1e 100644 --- a/vendor/github.com/buger/jsonparser/README.md +++ b/vendor/github.com/buger/jsonparser/README.md @@ -90,10 +90,6 @@ jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, er // For more information see docs below ``` -## Need to speedup your app? - -I'm available for consulting and can help you push your app performance to the limits. Ping me at: leonsbox@gmail.com. - ## Reference Library API is really simple. You just need the `Get` method to perform any operation. The rest is just helpers around it. @@ -341,6 +337,25 @@ https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_large_payloa Also last benchmark did not included `EachKey` test, because in this particular case we need to read lot of Array values, and using `ArrayEach` is more efficient. +## Formal Verification + + + +This project uses [ReqProof](https://reqproof.com) for formal requirements verification, achieving: + +- **92 formally specified requirements** covering all public API behavior including edge cases, malformed input, boundary values, and error propagation +- **100% MC/DC coverage** (Modified Condition/Decision Coverage) — every boolean decision in the code is independently proven exercised +- **Kind2 model checking** — mathematical proof that the specification is realizable and consistent +- **Z3 SMT proofs** — data-level properties verified for all possible inputs, not just test samples + +ReqProof found **2 real bugs** during the verification process ([see PR #281](https://github.com/buger/jsonparser/pull/281)): +1. `Delete` panic on truncated JSON input — bounds check missing after internal sentinel value +2. `ArrayEach` callback silently swallowing parse errors — the callback's `err` parameter was always nil + +It also identified and safely removed **7 dead code blocks** that MC/DC analysis proved unreachable from any input. + +The verification runs on every PR via [probelabs/proof-action](https://github.com/probelabs/proof-action). + ## Questions and support All bug-reports and suggestions should go though Github Issues. diff --git a/vendor/github.com/buger/jsonparser/bytes.go b/vendor/github.com/buger/jsonparser/bytes.go index 0bb0ff395..f5414b6f9 100644 --- a/vendor/github.com/buger/jsonparser/bytes.go +++ b/vendor/github.com/buger/jsonparser/bytes.go @@ -1,11 +1,9 @@ +// SYS-REQ-015, SYS-REQ-058, SYS-REQ-059, SYS-REQ-064: integer parsing internals package jsonparser -import ( - bio "bytes" -) - -// minInt64 '-9223372036854775808' is the smallest representable number in int64 -const minInt64 = `9223372036854775808` +const absMinInt64 = 1 << 63 +const maxInt64 = 1<<63 - 1 +const maxUint64 = 1<<64 - 1 // About 2x faster then strconv.ParseInt because it only supports base 10, which is enough for JSON func parseInt(bytes []byte) (v int64, ok bool, overflow bool) { @@ -19,29 +17,32 @@ func parseInt(bytes []byte) (v int64, ok bool, overflow bool) { bytes = bytes[1:] } - var b int64 = 0 + var n uint64 = 0 for _, c := range bytes { - if c >= '0' && c <= '9' { - b = (10 * v) + int64(c-'0') - } else { + if c < '0' || c > '9' { return 0, false, false } - if overflow = (b < v); overflow { - break + if n > maxUint64/10 { + return 0, false, true + } + n *= 10 + n1 := n + uint64(c-'0') + if n1 < n { + return 0, false, true } - v = b + n = n1 } - if overflow { - if neg && bio.Equal(bytes, []byte(minInt64)) { - return b, true, false + if n > maxInt64 { + if neg && n == absMinInt64 { + return -absMinInt64, true, false } return 0, false, true } if neg { - return -v, true, false + return -int64(n), true, false } else { - return v, true, false + return int64(n), true, false } } diff --git a/vendor/github.com/buger/jsonparser/bytes_safe.go b/vendor/github.com/buger/jsonparser/bytes_safe.go index ff16a4a19..7aa73c42f 100644 --- a/vendor/github.com/buger/jsonparser/bytes_safe.go +++ b/vendor/github.com/buger/jsonparser/bytes_safe.go @@ -1,5 +1,6 @@ -// +build appengine appenginevm +// +build appengine appenginevm tinygo +// SYS-REQ-001, SYS-REQ-013, SYS-REQ-014: safe build-tag byte utilities package jsonparser import ( diff --git a/vendor/github.com/buger/jsonparser/bytes_unsafe.go b/vendor/github.com/buger/jsonparser/bytes_unsafe.go index 589fea87e..8a44d7205 100644 --- a/vendor/github.com/buger/jsonparser/bytes_unsafe.go +++ b/vendor/github.com/buger/jsonparser/bytes_unsafe.go @@ -1,5 +1,6 @@ -// +build !appengine,!appenginevm +// +build !appengine,!appenginevm, !tinygo +// SYS-REQ-001, SYS-REQ-013, SYS-REQ-014: unsafe build-tag byte utilities package jsonparser import ( diff --git a/vendor/github.com/buger/jsonparser/escape.go b/vendor/github.com/buger/jsonparser/escape.go index 49669b942..62be30d21 100644 --- a/vendor/github.com/buger/jsonparser/escape.go +++ b/vendor/github.com/buger/jsonparser/escape.go @@ -1,3 +1,4 @@ +// SYS-REQ-014, SYS-REQ-060, SYS-REQ-061, SYS-REQ-062, SYS-REQ-063: string escape and Unicode handling package jsonparser import ( @@ -63,8 +64,12 @@ func decodeUnicodeEscape(in []byte) (rune, int) { if r, ok := decodeSingleUnicodeEscape(in); !ok { // Invalid Unicode escape return utf8.RuneError, -1 - } else if r <= basicMultilingualPlaneOffset && !isUTF16EncodedRune(r) { - // Valid Unicode escape in Basic Multilingual Plane + } else if !isUTF16EncodedRune(r) { + // Valid Unicode escape in Basic Multilingual Plane. + // Note: a single \uXXXX escape produces r in [0, 0xFFFF], so r is always + // within the BMP. The former r <= basicMultilingualPlaneOffset guard was + // tautological and has been removed — the real discriminator is whether r + // falls in the UTF-16 surrogate range. return r, 6 } else if r2, ok := decodeSingleUnicodeEscape(in[6:]); !ok { // Note: previous decodeSingleUnicodeEscape success guarantees at least 6 bytes remain // UTF16 "high surrogate" without manditory valid following Unicode escape for the "low surrogate" @@ -145,7 +150,11 @@ func Unescape(in, out []byte) ([]byte, error) { in = in[firstBackslash:] buf := out[firstBackslash:] - for len(in) > 0 { + // The loop always exits via break: either on error (MalformedStringEscapeError) + // or after copying the final non-escaped tail. The former `for len(in) > 0` + // guard was structurally always true on re-entry since the else branch always + // leaves at least the backslash character in `in`. + for { // Unescape the next escaped character inLen, bufLen := unescapeToUTF8(in, buf) if inLen == -1 { diff --git a/vendor/github.com/buger/jsonparser/fuzz.go b/vendor/github.com/buger/jsonparser/fuzz.go index 854bd11b2..75bbedbf2 100644 --- a/vendor/github.com/buger/jsonparser/fuzz.go +++ b/vendor/github.com/buger/jsonparser/fuzz.go @@ -1,5 +1,6 @@ package jsonparser +// SYS-REQ-014 func FuzzParseString(data []byte) int { r, err := ParseString(data) if err != nil || r == "" { @@ -8,6 +9,7 @@ func FuzzParseString(data []byte) int { return 1 } +// SYS-REQ-008 func FuzzEachKey(data []byte) int { paths := [][]string{ {"name"}, @@ -27,11 +29,13 @@ func FuzzEachKey(data []byte) int { return 1 } +// SYS-REQ-010 func FuzzDelete(data []byte) int { Delete(data, "test") return 1 } +// SYS-REQ-009 func FuzzSet(data []byte) int { _, err := Set(data, []byte(`"new value"`), "test") if err != nil { @@ -40,6 +44,7 @@ func FuzzSet(data []byte) int { return 1 } +// SYS-REQ-007 func FuzzObjectEach(data []byte) int { _ = ObjectEach(data, func(key, value []byte, valueType ValueType, off int) error { return nil @@ -47,6 +52,7 @@ func FuzzObjectEach(data []byte) int { return 1 } +// SYS-REQ-013 func FuzzParseFloat(data []byte) int { _, err := ParseFloat(data) if err != nil { @@ -55,6 +61,7 @@ func FuzzParseFloat(data []byte) int { return 1 } +// SYS-REQ-015 func FuzzParseInt(data []byte) int { _, err := ParseInt(data) if err != nil { @@ -63,6 +70,7 @@ func FuzzParseInt(data []byte) int { return 1 } +// SYS-REQ-012 func FuzzParseBool(data []byte) int { _, err := ParseBoolean(data) if err != nil { @@ -71,11 +79,13 @@ func FuzzParseBool(data []byte) int { return 1 } +// SYS-REQ-001 func FuzzTokenStart(data []byte) int { _ = tokenStart(data) return 1 } +// SYS-REQ-002 func FuzzGetString(data []byte) int { _, err := GetString(data, "test") if err != nil { @@ -84,6 +94,7 @@ func FuzzGetString(data []byte) int { return 1 } +// SYS-REQ-004 func FuzzGetFloat(data []byte) int { _, err := GetFloat(data, "test") if err != nil { @@ -92,6 +103,7 @@ func FuzzGetFloat(data []byte) int { return 1 } +// SYS-REQ-003 func FuzzGetInt(data []byte) int { _, err := GetInt(data, "test") if err != nil { @@ -100,6 +112,7 @@ func FuzzGetInt(data []byte) int { return 1 } +// SYS-REQ-005 func FuzzGetBoolean(data []byte) int { _, err := GetBoolean(data, "test") if err != nil { @@ -108,6 +121,7 @@ func FuzzGetBoolean(data []byte) int { return 1 } +// SYS-REQ-011 func FuzzGetUnsafeString(data []byte) int { _, err := GetUnsafeString(data, "test") if err != nil { diff --git a/vendor/github.com/buger/jsonparser/parser.go b/vendor/github.com/buger/jsonparser/parser.go index 14b80bc48..d8df1678d 100644 --- a/vendor/github.com/buger/jsonparser/parser.go +++ b/vendor/github.com/buger/jsonparser/parser.go @@ -18,12 +18,14 @@ var ( MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol") OverflowIntegerError = errors.New("Value is number, but overflowed while parsing") MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string") + NullValueError = errors.New("Value is null") ) // How much stack space to allocate for unescaping JSON strings; if a string longer // than this needs to be escaped, it will result in a heap allocation const unescapeStackBufSize = 64 +// SYS-REQ-044 func tokenEnd(data []byte) int { for i, c := range data { switch c { @@ -35,6 +37,7 @@ func tokenEnd(data []byte) int { return len(data) } +// SYS-REQ-001 func findTokenStart(data []byte, token byte) int { for i := len(data) - 1; i >= 0; i-- { switch data[i] { @@ -48,11 +51,17 @@ func findTokenStart(data []byte, token byte) int { return 0 } +// SYS-REQ-001, SYS-REQ-020, SYS-REQ-024 func findKeyStart(data []byte, key string) (int, error) { - i := 0 + i := nextToken(data) + if i == -1 { + return i, KeyPathNotFoundError + } ln := len(data) - if ln > 0 && (data[0] == '{' || data[0] == '[') { - i = 1 + // Note: nextToken returning non-negative (checked above) guarantees ln > 0, + // so the former ln > 0 guard was tautological and has been removed. + if data[i] == '{' || data[i] == '[' { + i += 1 } var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings @@ -113,6 +122,7 @@ func findKeyStart(data []byte, key string) (int, error) { return -1, KeyPathNotFoundError } +// SYS-REQ-001 func tokenStart(data []byte) int { for i := len(data) - 1; i >= 0; i-- { switch data[i] { @@ -124,6 +134,7 @@ func tokenStart(data []byte) int { return 0 } +// SYS-REQ-001 // Find position of next character which is not whitespace func nextToken(data []byte) int { for i, c := range data { @@ -138,6 +149,7 @@ func nextToken(data []byte) int { return -1 } +// SYS-REQ-001 // Find position of last character which is not whitespace func lastToken(data []byte) int { for i := len(data) - 1; i >= 0; i-- { @@ -152,6 +164,7 @@ func lastToken(data []byte) int { return -1 } +// SYS-REQ-045 // Tries to find the end of string // Support if string contains escaped quote symbols. func stringEnd(data []byte) (int, bool) { @@ -182,6 +195,7 @@ func stringEnd(data []byte) (int, bool) { return -1, escaped } +// SYS-REQ-046 // Find end of the data structure, array or object. // For array openSym and closeSym will be '[' and ']', for object '{' and '}' func blockEnd(data []byte, openSym byte, closeSym byte) int { @@ -213,6 +227,7 @@ func blockEnd(data []byte, openSym byte, closeSym byte) int { return -1 } +// SYS-REQ-001, SYS-REQ-020, SYS-REQ-021, SYS-REQ-022, SYS-REQ-023, SYS-REQ-047 func searchKeys(data []byte, keys ...string) int { keyLevel := 0 level := 0 @@ -308,8 +323,11 @@ func searchKeys(data []byte, keys ...string) int { case '[': // If we want to get array element by index if keyLevel == level && keys[level][0] == '[' { - var keyLen = len(keys[level]) - if keyLen < 3 || keys[level][0] != '[' || keys[level][keyLen-1] != ']' { + keyLen := len(keys[level]) + // Note: keys[level][0] == '[' is guaranteed by the outer if-guard, + // so the former middle term `keys[level][0] != '['` was always false + // (dead code) and has been removed. + if keyLen < 3 || keys[level][keyLen-1] != ']' { return -1 } aIdx, err := strconv.Atoi(keys[level][1 : keyLen-1]) @@ -319,7 +337,7 @@ func searchKeys(data []byte, keys ...string) int { var curIdx int var valueFound []byte var valueOffset int - var curI = i + curI := i ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) { if curIdx == aIdx { valueFound = value @@ -359,6 +377,7 @@ func searchKeys(data []byte, keys ...string) int { return -1 } +// SYS-REQ-008 func sameTree(p1, p2 []string) bool { minLen := len(p1) if len(p2) < minLen { @@ -374,12 +393,20 @@ func sameTree(p1, p2 []string) bool { return true } +const stackArraySize = 128 + +// SYS-REQ-008, SYS-REQ-085 func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int { var x struct{} - pathFlags := make([]bool, len(paths)) var level, pathsMatched, i int ln := len(data) + pathFlags := make([]bool, stackArraySize)[:] + if len(paths) > cap(pathFlags) { + pathFlags = make([]bool, len(paths))[:] + } + pathFlags = pathFlags[0:len(paths)] + var maxPath int for _, p := range paths { if len(p) > maxPath { @@ -387,7 +414,11 @@ func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]str } } - pathsBuf := make([]string, maxPath) + pathsBuf := make([]string, stackArraySize)[:] + if maxPath > cap(pathsBuf) { + pathsBuf = make([]string, maxPath)[:] + } + pathsBuf = pathsBuf[0:maxPath] for i < ln { switch data[i] { @@ -461,18 +492,17 @@ func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]str if match == -1 { tokenOffset := nextToken(data[i+1:]) i += tokenOffset - - if data[i] == '{' { - blockSkip := blockEnd(data[i:], '{', '}') - i += blockSkip + 1 - } + // Note: i is now at the character BEFORE the value (the colon + // when tokenOffset==0, or the last whitespace character otherwise). + // The former `if data[i] == '{'` block-skip was structurally dead + // code because i never reaches the opening brace — the outer loop's + // i++ advances to it on the next iteration. Likewise, the former + // `if i < ln` guard was tautological since i remains within bounds. } - if i < ln { - switch data[i] { - case '{', '}', '[', '"': - i-- - } + switch data[i] { + case '{', '}', '[', '"': + i-- } } else { i-- @@ -484,7 +514,12 @@ func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]str case '[': var ok bool arrIdxFlags := make(map[int]struct{}) - pIdxFlags := make([]bool, len(paths)) + + pIdxFlags := make([]bool, stackArraySize)[:] + if len(paths) > cap(pIdxFlags) { + pIdxFlags = make([]bool, len(paths))[:] + } + pIdxFlags = pIdxFlags[0:len(paths)] if level < 0 { cb(-1, nil, Unknown, MalformedJsonError) @@ -567,6 +602,7 @@ const ( Unknown ) +// SYS-REQ-001 func (vt ValueType) String() string { switch vt { case NotExist: @@ -594,6 +630,7 @@ var ( nullLiteral = []byte("null") ) +// SYS-REQ-009 func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte { isIndex := string(keys[0][0]) == "[" offset := 0 @@ -641,6 +678,7 @@ func createInsertComponent(keys []string, setValue []byte, comma, object bool) [ return buffer } +// SYS-REQ-009 func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int { isIndex := string(keys[0][0]) == "[" lk := 0 @@ -662,7 +700,6 @@ func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int { } } - lk += len(setValue) for i := 1; i < len(keys); i++ { if string(keys[i][0]) == "[" { @@ -682,6 +719,7 @@ func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int { return lk } +// SYS-REQ-009 func WriteToBuffer(buffer []byte, str string) int { copy(buffer, str) return len(str) @@ -695,6 +733,7 @@ Returns: `data` - return modified data */ +// SYS-REQ-010, SYS-REQ-033, SYS-REQ-034, SYS-REQ-035, SYS-REQ-048, SYS-REQ-049, SYS-REQ-050, SYS-REQ-056 func Delete(data []byte, keys ...string) []byte { lk := len(keys) if lk == 0 { @@ -712,7 +751,7 @@ func Delete(data []byte, keys ...string) []byte { if !array { if len(keys) > 1 { _, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...) - if err == KeyPathNotFoundError { + if err != nil { // problem parsing the data return data } @@ -724,21 +763,30 @@ func Delete(data []byte, keys ...string) []byte { return data } keyOffset += startOffset - _, _, _, subEndOffset, _ := internalGet(data[startOffset:endOffset], keys[lk-1]) + var subEndOffset int + _, _, _, subEndOffset, err = internalGet(data[startOffset:endOffset], keys[lk-1]) + if err != nil { + return data + } endOffset = startOffset + subEndOffset tokEnd := tokenEnd(data[endOffset:]) tokStart := findTokenStart(data[:keyOffset], ","[0]) - if data[endOffset+tokEnd] == ","[0] { + if endOffset+tokEnd >= len(data) { + // tokenEnd sentinel: no delimiter found, input is truncated + return data + } + + if data[endOffset+tokEnd] == ',' { endOffset += tokEnd + 1 - } else if data[endOffset+tokEnd] == " "[0] && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ","[0] { + } else if data[endOffset+tokEnd] == ' ' && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ',' { endOffset += tokEnd + 2 - } else if data[endOffset+tokEnd] == "}"[0] && data[tokStart] == ","[0] { + } else if data[endOffset+tokEnd] == '}' && data[tokStart] == ',' { keyOffset = tokStart } } else { _, _, keyOffset, endOffset, err = internalGet(data, keys...) - if err == KeyPathNotFoundError { + if err != nil { // problem parsing the data return data } @@ -746,19 +794,26 @@ func Delete(data []byte, keys ...string) []byte { tokEnd := tokenEnd(data[endOffset:]) tokStart := findTokenStart(data[:keyOffset], ","[0]) - if data[endOffset+tokEnd] == ","[0] { + if endOffset+tokEnd >= len(data) { + // tokenEnd sentinel: no delimiter found, input is truncated + return data + } + + if data[endOffset+tokEnd] == ',' { endOffset += tokEnd + 1 - } else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] { + } else if data[endOffset+tokEnd] == ']' && data[tokStart] == ',' { keyOffset = tokStart } } - // We need to remove remaining trailing comma if we delete las element in the object + // We need to remove remaining trailing comma if we delete last element in the object. + // Extract nextToken once to avoid the redundant double call in the original code. prevTok := lastToken(data[:keyOffset]) remainedValue := data[endOffset:] + remainedTok := nextToken(remainedValue) var newOffset int - if nextToken(remainedValue) > -1 && remainedValue[nextToken(remainedValue)] == '}' && data[prevTok] == ',' { + if remainedTok > -1 && remainedValue[remainedTok] == '}' && data[prevTok] == ',' { newOffset = prevTok } else { newOffset = prevTok + 1 @@ -782,6 +837,7 @@ Returns: `err` - On any parsing error */ +// SYS-REQ-009, SYS-REQ-051, SYS-REQ-068, SYS-REQ-069, SYS-REQ-070 func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) { // ensure keys are set if len(keys) == 0 { @@ -855,6 +911,7 @@ func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) return value, nil } +// SYS-REQ-001, SYS-REQ-027 func getType(data []byte, offset int) ([]byte, ValueType, int, error) { var dataType ValueType endOffset := offset @@ -889,12 +946,10 @@ func getType(data []byte, offset int) ([]byte, ValueType, int, error) { endOffset += offset } else { // Number, Boolean or None + // tokenEnd returns len(data) when no delimiter is found, never -1, + // so the old end == -1 guard was dead code and has been removed. end := tokenEnd(data[endOffset:]) - if end == -1 { - return nil, dataType, offset, MalformedValueError - } - value := data[offset : endOffset+end] switch data[offset] { @@ -933,11 +988,13 @@ Returns: Accept multiple keys to specify path to JSON value (in case of quering nested structures). If no keys provided it will try to extract closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation. */ +// SYS-REQ-001, SYS-REQ-016, SYS-REQ-017, SYS-REQ-018, SYS-REQ-019, SYS-REQ-025, SYS-REQ-026, SYS-REQ-041, SYS-REQ-042, SYS-REQ-043 func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) { a, b, _, d, e := internalGet(data, keys...) return a, b, d, e } +// SYS-REQ-001 func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) { if len(keys) > 0 { if offset = searchKeys(data, keys...); offset == -1 { @@ -965,6 +1022,7 @@ func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, return value[:len(value):len(value)], dataType, offset, endOffset, nil } +// SYS-REQ-006, SYS-REQ-028, SYS-REQ-029, SYS-REQ-052, SYS-REQ-053, SYS-REQ-055, SYS-REQ-083 // ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`. func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) { if len(data) == 0 { @@ -1009,23 +1067,23 @@ func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int return offset, nil } - for true { + for { v, t, o, e := Get(data[offset:]) - if e != nil { - return offset, e - } - if o == 0 { - break + // When Get returns endOffset==0, it always means a parse error + // (no valid value found at the current position). The former + // e==nil/break branch was structurally unreachable because Get + // never returns endOffset==0 without an error. + return offset, e } - if t != NotExist { - cb(v, t, offset+o-len(v), e) - } + // Pass the error to the callback — the callback signature declares + // an err parameter, so callers who check it should see real errors. + cb(v, t, offset+o-len(v), e) if e != nil { - break + return offset, e } offset += o @@ -1050,6 +1108,7 @@ func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int return offset, nil } +// SYS-REQ-007, SYS-REQ-030, SYS-REQ-031, SYS-REQ-032, SYS-REQ-054, SYS-REQ-084 // ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) { offset := 0 @@ -1079,8 +1138,13 @@ func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType Va return nil } - // Loop pre-condition: data[offset] points to what should be either the next entry's key, or the closing brace (if it's anything else, the JSON is malformed) - for offset < len(data) { + // Loop pre-condition: data[offset] points to what should be either the next entry's key, + // or the closing brace (if it's anything else, the JSON is malformed). + // Every iteration either returns or advances offset past a token, so the loop + // always exits via return; the former `offset < len(data)` guard was structurally + // always true because internal nextToken/stringEnd calls return errors before + // offset can reach len(data). + for { // Step 1: find the next key var key []byte @@ -1157,6 +1221,7 @@ func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType Va return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace } +// SYS-REQ-011, SYS-REQ-080, SYS-REQ-081, SYS-REQ-082 // GetUnsafeString returns the value retrieved by `Get`, use creates string without memory allocation by mapping string to slice memory. It does not handle escape symbols. func GetUnsafeString(data []byte, keys ...string) (val string, err error) { v, _, _, e := Get(data, keys...) @@ -1168,6 +1233,7 @@ func GetUnsafeString(data []byte, keys ...string) (val string, err error) { return bytesToString(&v), nil } +// SYS-REQ-002, SYS-REQ-071, SYS-REQ-072, SYS-REQ-073, SYS-REQ-074 // GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols // If key data type do not match, it will return an error. func GetString(data []byte, keys ...string) (val string, err error) { @@ -1178,6 +1244,9 @@ func GetString(data []byte, keys ...string) (val string, err error) { } if t != String { + if t == Null { + return "", NullValueError + } return "", fmt.Errorf("Value is not a string: %s", string(v)) } @@ -1192,6 +1261,7 @@ func GetString(data []byte, keys ...string) (val string, err error) { // GetFloat returns the value retrieved by `Get`, cast to a float64 if possible. // The offset is the same as in `Get`. // If key data type do not match, it will return an error. +// SYS-REQ-004 func GetFloat(data []byte, keys ...string) (val float64, err error) { v, t, _, e := Get(data, keys...) @@ -1200,6 +1270,9 @@ func GetFloat(data []byte, keys ...string) (val float64, err error) { } if t != Number { + if t == Null { + return 0, NullValueError + } return 0, fmt.Errorf("Value is not a number: %s", string(v)) } @@ -1208,6 +1281,7 @@ func GetFloat(data []byte, keys ...string) (val float64, err error) { // GetInt returns the value retrieved by `Get`, cast to a int64 if possible. // If key data type do not match, it will return an error. +// SYS-REQ-003, SYS-REQ-075, SYS-REQ-076, SYS-REQ-077, SYS-REQ-078 func GetInt(data []byte, keys ...string) (val int64, err error) { v, t, _, e := Get(data, keys...) @@ -1216,6 +1290,9 @@ func GetInt(data []byte, keys ...string) (val int64, err error) { } if t != Number { + if t == Null { + return 0, NullValueError + } return 0, fmt.Errorf("Value is not a number: %s", string(v)) } @@ -1225,6 +1302,7 @@ func GetInt(data []byte, keys ...string) (val int64, err error) { // GetBoolean returns the value retrieved by `Get`, cast to a bool if possible. // The offset is the same as in `Get`. // If key data type do not match, it will return error. +// SYS-REQ-005, SYS-REQ-079 func GetBoolean(data []byte, keys ...string) (val bool, err error) { v, t, _, e := Get(data, keys...) @@ -1233,6 +1311,9 @@ func GetBoolean(data []byte, keys ...string) (val bool, err error) { } if t != Boolean { + if t == Null { + return false, NullValueError + } return false, fmt.Errorf("Value is not a boolean: %s", string(v)) } @@ -1240,6 +1321,7 @@ func GetBoolean(data []byte, keys ...string) (val bool, err error) { } // ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness) +// SYS-REQ-012, SYS-REQ-036, SYS-REQ-057, SYS-REQ-066 func ParseBoolean(b []byte) (bool, error) { switch { case bytes.Equal(b, trueLiteral): @@ -1252,6 +1334,7 @@ func ParseBoolean(b []byte) (bool, error) { } // ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string) +// SYS-REQ-014, SYS-REQ-038, SYS-REQ-060, SYS-REQ-063, SYS-REQ-067 func ParseString(b []byte) (string, error) { var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings if bU, err := Unescape(b, stackbuf[:]); err != nil { @@ -1262,6 +1345,7 @@ func ParseString(b []byte) (string, error) { } // ParseNumber parses a Number ValueType into a Go float64 +// SYS-REQ-013, SYS-REQ-037, SYS-REQ-065 func ParseFloat(b []byte) (float64, error) { if v, err := parseFloat(&b); err != nil { return 0, MalformedValueError @@ -1271,6 +1355,7 @@ func ParseFloat(b []byte) (float64, error) { } // ParseInt parses a Number ValueType into a Go int64 +// SYS-REQ-015, SYS-REQ-039, SYS-REQ-040, SYS-REQ-058, SYS-REQ-059, SYS-REQ-064 func ParseInt(b []byte) (int64, error) { if v, ok, overflow := parseInt(b); !ok { if overflow { diff --git a/vendor/github.com/buger/jsonparser/proof.yaml b/vendor/github.com/buger/jsonparser/proof.yaml new file mode 100644 index 000000000..4d82bc210 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/proof.yaml @@ -0,0 +1,96 @@ +project: + name: jsonparser + specs: + - path: specs/stakeholder + prefix: STK-REQ + type: stakeholder + - path: specs/system + prefix: SYS-REQ + type: system + parent_spec: specs/stakeholder + obligation_classes: + - nominal + - missing_path + - malformed_input + - truncated_at_value_boundary + - truncated_mid_structure + - truncated_mid_key + - empty_input + - boundary + - type_mismatch + - negative_array_index + - sentinel_value_boundary + - error_propagation + - no_path_provided + - nested_mutation + - callback_error_propagation + - truncated_escape_sequence + - partial_literal + - truncated_mid_element + - determinism + - idempotency + - nil_safety + - encoding_safety + - edge_case + commands: + build: go build ./... + test: mkdir -p .proof/coverage .proof/test-results && go test ./... -count=1 -coverprofile=.proof/coverage/unit.coverprofile -json > .proof/test-results/go-test.json 2>&1 + # Fixtures are generated artifacts, not committed to the repo. + # MC/DC coverage is enforced through test annotations instead. + # To regenerate locally: + # proof testgen specs/system parser --output tests/ + # proof proptest specs/system parser --source z3 --output tests/parser/ + checks: + solver_latency_clean: + threshold: 360 + coverage_threshold: + threshold: 80 + auto_link: false + report_path: .proof/coverage/unit.coverprofile + format: go-cover + test_results: + auto_link: true + report_path: .proof/test-results/go-test.json + slow_tests: + enabled: true + code_mcdc: + severity: warn + engine: go + package_pattern: ./... + coverpkg: ./... + go_test_args: + - -race + min_decision_percent: 100 + min_condition_percent: 100 + max_incomplete_decisions: 0 + targets: + - id: parser + enabled: true + language: go + scope: ./... + test_mcdc_annotations: + enabled: true + mcdc_coverage: {} + proof_complexity_clean: + max_formalized_requirements: 120 + max_variables: 280 + max_guarantees: 120 + evidence_diversity: + min_classes: + A: 3 + B: 2 + C: 1 + approval: + required_for: + assurance_levels: + - A + - B + roles: + - system_owner + - lead_engineer + comment_required: true + documentation: + sources: + - path: . + type: auto + threshold: 0 diff --git a/vendor/modules.txt b/vendor/modules.txt index ab93d7fc9..e8142a0b1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -45,7 +45,7 @@ github.com/bahlo/generic-list-go # github.com/blang/semver/v4 v4.0.0 ## explicit; go 1.14 github.com/blang/semver/v4 -# github.com/buger/jsonparser v1.1.1 +# github.com/buger/jsonparser v1.2.0 ## explicit; go 1.13 github.com/buger/jsonparser # github.com/chai2010/gettext-go v1.0.3