From e916782d5f8ce15ba07b16506a8b87ab4eff8d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ngh=C4=A9a=20Nguy=E1=BB=85n=20Ng=E1=BB=8Dc?= Date: Sat, 9 May 2026 14:54:02 +0700 Subject: [PATCH 1/2] struct: return error instead of panic when MapTo receives a nil pointer Calling MapTo (or Section.MapTo) with a nil struct pointer caused a panic in mapToField because val.Elem() on a nil pointer returns a zero reflect.Value, and the subsequent val.Type() call on that zero value triggers a runtime panic. Add an IsValid check after the Pointer dereference and return a descriptive error, matching the expected behaviour of the method (returns error) instead of crashing the caller. Fixes #369 --- struct.go | 3 +++ struct_test.go | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/struct.go b/struct.go index 819a6e5..c34cda9 100644 --- a/struct.go +++ b/struct.go @@ -281,6 +281,9 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, if val.Kind() == reflect.Pointer { val = val.Elem() } + if !val.IsValid() { + return fmt.Errorf("cannot map to nil pointer") + } typ := val.Type() for i := 0; i < typ.NumField(); i++ { diff --git a/struct_test.go b/struct_test.go index ad6ad04..27262ca 100644 --- a/struct_test.go +++ b/struct_test.go @@ -385,6 +385,18 @@ names=alice, bruce`)) }) } +// Regression test for https://github.com/go-ini/ini/issues/369: +// MapTo with a nil pointer must return an error, not panic. +func Test_MapToNilPointer(t *testing.T) { + f, err := Load([]byte("[section]\nkey = val\n")) + assert.NoError(t, err) + + type S struct{ Key string } + var s *S + err = f.MapTo(&s) + assert.Error(t, err, "MapTo with nil pointer should return an error, not panic") +} + func Test_MapToStructNonUniqueSections(t *testing.T) { t.Run("map to struct non unique", func(t *testing.T) { t.Run("map file to struct non unique", func(t *testing.T) { From 621bdad002214b90e8729ab778443896107ca7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ngh=C4=A9a=20Nguy=E1=BB=85n=20Ng=E1=BB=8Dc?= Date: Sat, 9 May 2026 14:59:45 +0700 Subject: [PATCH 2/2] struct: auto-initialize nil pointers in mapToField, fix slice of pointer structs - Change single if to for loop so chained/double pointers are fully dereferenced - Auto-initialize nil pointers via reflect.New instead of returning an error - Fixes #369 (nil pointer panic on MapTo) and #370 (slice of *Struct panic) Previously mapToField would panic when encountering a nil pointer inside a slice of struct pointers ([]*Peer pattern common in WireGuard-style configs). The fix mirrors encoding/json behavior: walk the pointer chain, allocating any nil links, then map into the concrete value. --- struct.go | 10 ++++++++-- struct_test.go | 44 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/struct.go b/struct.go index c34cda9..45864f7 100644 --- a/struct.go +++ b/struct.go @@ -278,11 +278,17 @@ func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bo // mapToField maps the given value to the matching field of the given section. // The sectionIndex is the index (if non unique sections are enabled) to which the value should be added. func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error { - if val.Kind() == reflect.Pointer { + for val.Kind() == reflect.Pointer { + if val.IsNil() { + if !val.CanSet() { + return fmt.Errorf("cannot initialize nil pointer: value is not settable") + } + val.Set(reflect.New(val.Type().Elem())) + } val = val.Elem() } if !val.IsValid() { - return fmt.Errorf("cannot map to nil pointer") + return fmt.Errorf("cannot map to invalid value") } typ := val.Type() diff --git a/struct_test.go b/struct_test.go index 27262ca..ac12308 100644 --- a/struct_test.go +++ b/struct_test.go @@ -385,16 +385,48 @@ names=alice, bruce`)) }) } -// Regression test for https://github.com/go-ini/ini/issues/369: -// MapTo with a nil pointer must return an error, not panic. +// Regression test for https://github.com/go-ini/ini/issues/370: +// Mapping non-unique sections into a slice of struct pointers ([]*T) must +// not panic. +func Test_MapToSliceOfStructPointers(t *testing.T) { + const data = `[Peer] +PublicKey = key1 +[Peer] +PublicKey = key2 +` + type Peer struct { + PublicKey string `ini:"PublicKey"` + } + type Config struct { + Peers []*Peer `ini:"Peer,nonunique"` + } + f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(data)) + assert.NoError(t, err) + cfg := new(Config) + assert.NoError(t, f.MapTo(cfg)) + assert.Len(t, cfg.Peers, 2) + if len(cfg.Peers) == 2 { + assert.Equal(t, "key1", cfg.Peers[0].PublicKey) + assert.Equal(t, "key2", cfg.Peers[1].PublicKey) + } +} + +// Regression test for https://github.com/go-ini/ini/issues/369 and #370: +// MapTo with a nil struct pointer must not panic; it should auto-initialize +// the pointer (analogous to encoding/json behaviour) and populate the struct. func Test_MapToNilPointer(t *testing.T) { - f, err := Load([]byte("[section]\nkey = val\n")) + f, err := Load([]byte("user = alice\npass = secret\n")) assert.NoError(t, err) - type S struct{ Key string } + type S struct { + User string `ini:"user"` + Pass string `ini:"pass"` + } var s *S - err = f.MapTo(&s) - assert.Error(t, err, "MapTo with nil pointer should return an error, not panic") + assert.NoError(t, f.MapTo(&s), "MapTo with nil pointer should not return an error") + assert.NotNil(t, s, "nil pointer should have been initialized by MapTo") + assert.Equal(t, "alice", s.User) + assert.Equal(t, "secret", s.Pass) } func Test_MapToStructNonUniqueSections(t *testing.T) {