From a190e34058ef874fe95d498c060592678539cea3 Mon Sep 17 00:00:00 2001 From: bensynapse <118375461+bensynapse@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:59:54 +0300 Subject: [PATCH] Fix panic on reversed slice bounds --- jsonpath.go | 3 +++ jsonpath_accessor_test.go | 54 ++++++++++++++++++++++++++++++++++++++- jsonpath_extra_test.go | 18 +++---------- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/jsonpath.go b/jsonpath.go index ec19078..24c45f7 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -584,6 +584,9 @@ func get_range(obj, frm, to interface{}) (interface{}, error) { if _to > length { _to = length } + if _frm > _to { + _frm = _to + } //fmt.Println("_frm, _to: ", _frm, _to) res_v := reflect.ValueOf(obj).Slice(_frm, _to) return res_v.Interface(), nil diff --git a/jsonpath_accessor_test.go b/jsonpath_accessor_test.go index 1387548..d7df6b7 100644 --- a/jsonpath_accessor_test.go +++ b/jsonpath_accessor_test.go @@ -6,7 +6,10 @@ package jsonpath -import "testing" +import ( + "reflect" + "testing" +) // Accessor tests - test get_key, get_idx, get_range, get_scan functions @@ -178,6 +181,55 @@ func Test_jsonpath_get_range(t *testing.T) { } } +func Test_jsonpath_reversed_ranges(t *testing.T) { + type intSlice []int + inputs := []struct { + name string + obj interface{} + }{ + {"int_slice", []int{1, 2, 3, 4, 5}}, + {"interface_slice", []interface{}{1, 2, 3, 4, 5}}, + {"named_slice", intSlice{1, 2, 3, 4, 5}}, + } + paths := []string{"$[3:1]", "$[-1:1]", "$[-1:-3]", "$[2:-10]", "$[3:0]", "$[2:2]"} + lookups := []struct { + name string + lookup func(interface{}, string) (interface{}, error) + }{ + {"direct", JsonPathLookup}, + {"compiled", func(obj interface{}, path string) (interface{}, error) { + compiled, err := Compile(path) + if err != nil { + return nil, err + } + return compiled.Lookup(obj) + }}, + } + for _, input := range inputs { + for _, path := range paths { + for _, lookup := range lookups { + t.Run(input.name+"/"+path+"/"+lookup.name, func(t *testing.T) { + defer func() { + if p := recover(); p != nil { + t.Errorf("lookup panicked: %v", p) + } + }() + res, err := lookup.lookup(input.obj, path) + if err != nil { + t.Fatalf("lookup failed: %v", err) + } + if reflect.TypeOf(res) != reflect.TypeOf(input.obj) { + t.Fatalf("result type = %T, want %T", res, input.obj) + } + if reflect.ValueOf(res).Len() != 0 { + t.Errorf("result = %v, want empty slice", res) + } + }) + } + } + } +} + func Test_jsonpath_get_scan(t *testing.T) { obj := map[string]interface{}{ "key": 1, diff --git a/jsonpath_extra_test.go b/jsonpath_extra_test.go index ffe0604..0435b51 100644 --- a/jsonpath_extra_test.go +++ b/jsonpath_extra_test.go @@ -352,20 +352,13 @@ func Test_get_range_uncovered(t *testing.T) { // Test: get_range negative to value (clamping to 0) t.Run("get_range_negative_to_clamp", func(t *testing.T) { obj := []interface{}{1, 2, 3, 4, 5} - // Negative to that would clamp below 0 - // _to = 5 + (-10) + 1 = -4, clamped to 0 - // But _frm = 2 > _to = 0, which causes panic in Slice - // This is actually a bug in the get_range function - it should check _frm > _to - // For now, we test a valid negative to case - res, err := get_range(obj, 0, -2) + res, err := get_range(obj, 2, -10) if err != nil { t.Fatalf("get_range failed: %v", err) } resSlice := res.([]interface{}) - // _to = 5 + (-2) + 1 = 4 - // Result should be [0:4] = 4 elements - if len(resSlice) != 4 { - t.Errorf("Expected 4, got %d", len(resSlice)) + if len(resSlice) != 0 { + t.Errorf("Expected empty slice, got %d elements", len(resSlice)) } }) @@ -1245,10 +1238,7 @@ func Test_get_range_more_coverage(t *testing.T) { // Test: from >= to (returns empty slice) t.Run("from_greater_or_equal_to", func(t *testing.T) { obj := []interface{}{1, 2, 3, 4, 5} - // When from > to, the code clamps from to to, so from=3, to=1 becomes from=1, to=1 - // This results in an empty slice [1:1] - // But the actual code may panic, so let's test a safer case - res, err := get_range(obj, 2, 2) + res, err := get_range(obj, 3, 1) if err != nil { t.Fatalf("get_range failed: %v", err) }