From 457aca4500580ec871d03c54cd21eeb704d26365 Mon Sep 17 00:00:00 2001 From: dualfroz Date: Fri, 4 Sep 2026 08:15:15 +0200 Subject: [PATCH] fix: nil-pointer panic from custom type function A DecodeCustomTypeFunc returning nil to mean "leave this pointer field unset" panicked in reflect.Value.Set, since reflect.ValueOf(nil) is the invalid zero Value. Guard against a nil return before Set. This is the panic from #68/#69: the earlier fix routed pointer fields through the generic Ptr recursion instead of guarding nil, which discarded a custom function's own populated pointer result and had to be reverted. Guarding val == nil directly fixes the panic without touching the case that broke. --- decoder.go | 5 +++++ decoder_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/decoder.go b/decoder.go index d746ea0..b0d2430 100644 --- a/decoder.go +++ b/decoder.go @@ -199,6 +199,11 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in return } + // Set would panic on the invalid zero Value, so nil leaves the field alone. + if val == nil { + return + } + v.Set(reflect.ValueOf(val)) set = true return diff --git a/decoder_test.go b/decoder_test.go index 2b631a0..7086a93 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -1881,6 +1881,36 @@ func TestDecoder_RegisterCustomTypeFunc(t *testing.T) { Equal(t, v.Slice, []customString{"customv1", "customv2"}) } +func TestDecoder_CustomTypeFuncPointerNilValue(t *testing.T) { + type User struct { + Name string + } + + type TestStruct struct { + U *User `form:"u"` + } + + d := NewDecoder() + d.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) { + if vals[0] == "" { + // "Leave the field at its zero value" must not panic on a pointer field. + return nil, nil + } + return &User{Name: vals[0]}, nil + }, &User{}) + + var empty TestStruct + err := d.Decode(&empty, url.Values{"u": {""}}) + Equal(t, err, nil) + Equal(t, empty.U, nil) + + var populated TestStruct + err = d.Decode(&populated, url.Values{"u": {"bob"}}) + Equal(t, err, nil) + NotEqual(t, populated.U, nil) + Equal(t, populated.U.Name, "bob") +} + func TestDecoder_EmptyArrayString(t *testing.T) { type T1 struct { F1 string `form:"F1"`