FuzzPathMutation mutates BOTH the JSON bytes and the key-path bytes, then exercises the full parser surface. This is the harness that would have caught the 8th empty-key panic site (parser.go:981's predecessor hazards in searchKeys/EachKey/createInsertComponent/calcAllocateSpace, now fixed) and whic
(f *testing.F)
| 163 | // Verifies: SYS-REQ-035 [no-panic on malformed input] |
| 164 | // reqproof:proptest Get, Set, Delete, GetString, GetInt, GetFloat, GetBoolean, ArrayEach, ObjectEach, EachKey |
| 165 | func FuzzPathMutation(f *testing.F) { |
| 166 | // Seed corpus: a mix of valid JSON + non-empty paths, AND adversarial |
| 167 | // path shapes (empty, trailing-slash, array-index-then-empty). The |
| 168 | // mutation engine will explore combinations of these. |
| 169 | f.Add([]byte(`{"a":1}`), []byte("a")) |
| 170 | f.Add([]byte(`[1,2,3]`), []byte("")) |
| 171 | f.Add([]byte(`{"a":{"b":1}}`), []byte("a")) |
| 172 | f.Add([]byte(`{"a":[{"x":1}]}`), []byte("a")) // 8th-site repro shape |
| 173 | f.Add([]byte(`{"a":1}`), []byte("[0]")) // array-index path on object |
| 174 | f.Add([]byte(`{"a":1}`), []byte("[99]")) // out-of-range index |
| 175 | f.Add([]byte(`{"":1}`), []byte("")) // object with empty key |
| 176 | f.Add([]byte(`{"a":[1,2,3]}`), []byte("a/")) // trailing empty |
| 177 | f.Add([]byte(`{"a":[{"b":1}]}`), []byte("a/[0]")) // nested array index |
| 178 | f.Add([]byte(`{"a":[{"b":1}]}`), []byte("a/[0]/")) // D9 repro: arr-of-obj + empty trailing |
| 179 | f.Add([]byte(`null`), []byte("a")) |
| 180 | f.Add([]byte(``), []byte("a")) |
| 181 | f.Add([]byte(`{`), []byte("a")) |
| 182 | f.Add([]byte(`[}`), []byte("[0]")) |
| 183 | |
| 184 | f.Fuzz(func(t *testing.T, data []byte, path []byte) { |
| 185 | components := splitPathComponents(path) |
| 186 | inputValid := json.Valid(data) |
| 187 | pathShapeMatches := pathShapeMatchesRoot(data, components) |
| 188 | |
| 189 | // --- Get family --- |
| 190 | runPathOp("Get", data, path, func() { |
| 191 | _, _, _, _ = Get(data, components...) |
| 192 | }) |
| 193 | runPathOp("GetString", data, path, func() { |
| 194 | _, _ = GetString(data, components...) |
| 195 | }) |
| 196 | runPathOp("GetInt", data, path, func() { |
| 197 | _, _ = GetInt(data, components...) |
| 198 | }) |
| 199 | runPathOp("GetFloat", data, path, func() { |
| 200 | _, _ = GetFloat(data, components...) |
| 201 | }) |
| 202 | runPathOp("GetBoolean", data, path, func() { |
| 203 | _, _ = GetBoolean(data, components...) |
| 204 | }) |
| 205 | |
| 206 | // --- ArrayEach / ObjectEach --- |
| 207 | runPathOp("ArrayEach", data, path, func() { |
| 208 | _, _ = ArrayEach(data, func(value []byte, dataType ValueType, offset int, err error) { |
| 209 | // callback error is informational; do not propagate. |
| 210 | }, components...) |
| 211 | }) |
| 212 | runPathOp("ObjectEach", data, path, func() { |
| 213 | _ = ObjectEach(data, func(key, value []byte, dataType ValueType, offset int) error { |
| 214 | return nil |
| 215 | }, components...) |
| 216 | }) |
| 217 | |
| 218 | // --- EachKey with the same components as a single path --- |
| 219 | if len(components) > 0 { |
| 220 | runPathOp("EachKey", data, path, func() { |
| 221 | EachKey(data, func(idx int, value []byte, vt ValueType, err error) { |
| 222 | // informational callback |
nothing calls this directly
no test coverage detected