Skip to content

Commit 4325386

Browse files
authored
fix: prevent panic in unique validation with nil pointer elements (#1532)
## Fixes Or Enhances This is an alternative approach to resolve the panic issue in the unique validator. Releated to: #1530 **Make sure that you've checked the boxes below before you submit PR:** - [ ] Tests exist or have been written that cover this particular change. @go-playground/validator-maintainers
1 parent d3f35da commit 4325386

File tree

2 files changed

+193
-36
lines changed

2 files changed

+193
-36
lines changed

baked_in.go

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -336,61 +336,110 @@ func isOneOfCI(fl FieldLevel) bool {
336336
func isUnique(fl FieldLevel) bool {
337337
field := fl.Field()
338338
param := fl.Param()
339-
v := reflect.ValueOf(struct{}{})
339+
340+
// sentinel used as map key for nil values
341+
var nilKey = struct{}{}
340342

341343
switch field.Kind() {
342344
case reflect.Slice, reflect.Array:
343-
elem := field.Type().Elem()
344-
if elem.Kind() == reflect.Ptr {
345-
elem = elem.Elem()
346-
}
345+
seen := make(map[interface{}]struct{})
347346

348-
if param == "" {
349-
m := reflect.MakeMap(reflect.MapOf(elem, v.Type()))
347+
for i := 0; i < field.Len(); i++ {
348+
elem := field.Index(i)
349+
350+
// -------- unique (no param) --------
351+
if param == "" {
352+
var key interface{}
353+
354+
if elem.Kind() == reflect.Ptr {
355+
if elem.IsNil() {
356+
key = nilKey
357+
} else {
358+
key = elem.Elem().Interface() // <-- compare underlying value
359+
}
360+
} else {
361+
key = elem.Interface()
362+
}
350363

351-
for i := 0; i < field.Len(); i++ {
352-
m.SetMapIndex(reflect.Indirect(field.Index(i)), v)
364+
if _, ok := seen[key]; ok {
365+
return false
366+
}
367+
seen[key] = struct{}{}
368+
continue
353369
}
354-
return field.Len() == m.Len()
355-
}
356370

357-
sf, ok := elem.FieldByName(param)
358-
if !ok {
359-
panic(fmt.Sprintf("Bad field name %s", param))
360-
}
371+
// -------- unique=Field --------
361372

362-
sfTyp := sf.Type
363-
if sfTyp.Kind() == reflect.Ptr {
364-
sfTyp = sfTyp.Elem()
365-
}
373+
if elem.Kind() == reflect.Ptr {
374+
if elem.IsNil() {
375+
if _, ok := seen[nilKey]; ok {
376+
return false
377+
}
378+
seen[nilKey] = struct{}{}
379+
continue
380+
}
381+
elem = elem.Elem()
382+
}
366383

367-
m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
368-
var fieldlen int
369-
for i := 0; i < field.Len(); i++ {
370-
key := reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param))
371-
if key.IsValid() {
372-
fieldlen++
373-
m.SetMapIndex(key, v)
384+
if elem.Kind() != reflect.Struct {
385+
panic(fmt.Sprintf("Bad field type %s", elem.Type()))
386+
}
387+
388+
sf := elem.FieldByName(param)
389+
if !sf.IsValid() {
390+
panic(fmt.Sprintf("Bad field name %s", param))
391+
}
392+
393+
var key interface{}
394+
395+
if sf.Kind() == reflect.Ptr {
396+
if sf.IsNil() {
397+
key = nilKey
398+
} else {
399+
key = sf.Elem().Interface()
400+
}
401+
} else {
402+
key = sf.Interface()
374403
}
404+
405+
if _, ok := seen[key]; ok {
406+
return false
407+
}
408+
seen[key] = struct{}{}
375409
}
376-
return fieldlen == m.Len()
410+
411+
return true
412+
377413
case reflect.Map:
378-
var m reflect.Value
379-
if field.Type().Elem().Kind() == reflect.Ptr {
380-
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem().Elem(), v.Type()))
381-
} else {
382-
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
383-
}
414+
seen := make(map[interface{}]struct{})
384415

385416
for _, k := range field.MapKeys() {
386-
m.SetMapIndex(reflect.Indirect(field.MapIndex(k)), v)
417+
val := field.MapIndex(k)
418+
419+
var key interface{}
420+
421+
if val.Kind() == reflect.Ptr {
422+
if val.IsNil() {
423+
key = nilKey
424+
} else {
425+
key = val.Elem().Interface() // <-- compare underlying value
426+
}
427+
} else {
428+
key = val.Interface()
429+
}
430+
431+
if _, ok := seen[key]; ok {
432+
return false
433+
}
434+
seen[key] = struct{}{}
387435
}
388436

389-
return field.Len() == m.Len()
437+
return true
438+
390439
default:
391440
if parent := fl.Parent(); parent.Kind() == reflect.Struct {
392441
uniqueField := parent.FieldByName(param)
393-
if uniqueField == reflect.ValueOf(nil) {
442+
if !uniqueField.IsValid() {
394443
panic(fmt.Sprintf("Bad field name provided %s", param))
395444
}
396445

validator_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10931,6 +10931,114 @@ func TestUniqueValidationStructPtrSlice(t *testing.T) {
1093110931
PanicMatches(t, func() { _ = validate.Var(testStructs, "unique=C") }, "Bad field name C")
1093210932
}
1093310933

10934+
func TestUniqueValidationNilPtrSlice(t *testing.T) {
10935+
validate := New()
10936+
10937+
type Inner struct {
10938+
Name string
10939+
}
10940+
10941+
t.Run("unique_field_single_nil_ptr", func(t *testing.T) {
10942+
// A single nil element should not panic and should pass (it's unique by itself)
10943+
s := struct {
10944+
F1 []*Inner `validate:"unique=Name"`
10945+
}{F1: []*Inner{nil}}
10946+
errs := validate.Struct(s)
10947+
if errs != nil {
10948+
t.Fatalf("single nil element should pass unique=Name validation, got: %v", errs)
10949+
}
10950+
})
10951+
10952+
t.Run("unique_field_duplicate_nil_ptrs", func(t *testing.T) {
10953+
// Two nil elements should not panic and should fail (not unique)
10954+
s := struct {
10955+
F1 []*Inner `validate:"unique=Name"`
10956+
}{F1: []*Inner{nil, nil}}
10957+
errs := validate.Struct(s)
10958+
if errs == nil {
10959+
t.Fatal("duplicate nil elements should fail unique=Name validation")
10960+
}
10961+
})
10962+
10963+
t.Run("unique_field_nil_and_non_nil", func(t *testing.T) {
10964+
// One nil and one non-nil should pass (they are different)
10965+
s := struct {
10966+
F1 []*Inner `validate:"unique=Name"`
10967+
}{F1: []*Inner{nil, {Name: "abc"}}}
10968+
errs := validate.Struct(s)
10969+
if errs != nil {
10970+
t.Fatalf("nil and non-nil elements should pass unique=Name validation, got: %v", errs)
10971+
}
10972+
})
10973+
10974+
t.Run("unique_no_param_single_nil_ptr", func(t *testing.T) {
10975+
// A single nil element without param should not panic
10976+
s := struct {
10977+
F1 []*Inner `validate:"unique"`
10978+
}{F1: []*Inner{nil}}
10979+
errs := validate.Struct(s)
10980+
if errs != nil {
10981+
t.Fatalf("single nil element should pass unique validation, got: %v", errs)
10982+
}
10983+
})
10984+
10985+
t.Run("unique_no_param_duplicate_nil_ptrs", func(t *testing.T) {
10986+
// Two nil elements without param should fail
10987+
s := struct {
10988+
F1 []*Inner `validate:"unique"`
10989+
}{F1: []*Inner{nil, nil}}
10990+
errs := validate.Struct(s)
10991+
if errs == nil {
10992+
t.Fatal("duplicate nil elements should fail unique validation")
10993+
}
10994+
})
10995+
10996+
t.Run("unique_no_param_nil_and_non_nil", func(t *testing.T) {
10997+
// One nil and one non-nil should pass
10998+
s := struct {
10999+
F1 []*Inner `validate:"unique"`
11000+
}{F1: []*Inner{nil, {Name: "abc"}}}
11001+
errs := validate.Struct(s)
11002+
if errs != nil {
11003+
t.Fatalf("nil and non-nil elements should pass unique validation, got: %v", errs)
11004+
}
11005+
})
11006+
11007+
t.Run("unique_map_nil_values", func(t *testing.T) {
11008+
// Map with nil pointer values should not panic
11009+
m := map[string]*string{"one": nil, "two": nil}
11010+
errs := validate.Var(m, "unique")
11011+
if errs == nil {
11012+
t.Fatal("duplicate nil map values should fail unique validation")
11013+
}
11014+
})
11015+
11016+
t.Run("unique_map_single_nil_value", func(t *testing.T) {
11017+
m := map[string]*string{"one": nil}
11018+
errs := validate.Var(m, "unique")
11019+
if errs != nil {
11020+
t.Fatalf("single nil map value should pass unique validation, got: %v", errs)
11021+
}
11022+
})
11023+
11024+
t.Run("unique_map_nil_and_non_nil", func(t *testing.T) {
11025+
a := "hello"
11026+
m := map[string]*string{"one": nil, "two": &a}
11027+
errs := validate.Var(m, "unique")
11028+
if errs != nil {
11029+
t.Fatalf("nil and non-nil map values should pass unique validation, got: %v", errs)
11030+
}
11031+
})
11032+
11033+
t.Run("unique_slice_with_nil_and_zero_value_struct", func(t *testing.T) {
11034+
s := []*Inner{nil, {Name: ""}}
11035+
errs := validate.Var(s, "unique")
11036+
if errs != nil {
11037+
t.Fatalf("nil and zero value struct should pass unique validation, got: %v", errs)
11038+
}
11039+
})
11040+
}
11041+
1093411042
func TestHTMLValidation(t *testing.T) {
1093511043
tests := []struct {
1093611044
param string

0 commit comments

Comments
 (0)