diff --git a/decoder.go b/decoder.go index 54c88ec..f0128e0 100644 --- a/decoder.go +++ b/decoder.go @@ -279,6 +279,11 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values for _, name := range parts[0].path { if v.Type().Kind() == reflect.Ptr { if v.IsNil() { + // Allocating into an unexported pointer panics; the path + // targets a field we can't set anyway, so bail early. + if !v.CanSet() { + return nil + } v.Set(reflect.New(v.Type().Elem())) } v = v.Elem() @@ -288,7 +293,7 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values if v.Type().Kind() == reflect.Struct { for i := 0; i < v.NumField(); i++ { field := v.Field(i) - if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous { + if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous && field.CanSet() { field.Set(reflect.New(field.Type().Elem())) } } diff --git a/decoder_test.go b/decoder_test.go index d01569e..4b107ea 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -717,6 +717,41 @@ func TestUnexportedField(t *testing.T) { } } +// Regression: walking through an unexported pointer field used to panic +// with "reflect.Value.Set using value obtained using unexported field" +// because Decoder.decode tried to allocate the pointer before realizing +// the target was unreachable. +type s6WithNestedUnexported struct { + A string + c *struct { + X string + } +} + +func TestUnexportedPointerFieldNestedPathDoesNotPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("Decode panicked on unexported pointer path: %v", r) + } + }() + dec := NewDecoder() + dec.IgnoreUnknownKeys(true) + s := &s6WithNestedUnexported{} + err := dec.Decode(s, map[string][]string{ + "A": {"hi"}, + "c.X": {"should not panic"}, + }) + if err != nil { + t.Fatalf("Decode returned an error: %v", err) + } + if s.A != "hi" { + t.Errorf("A: got %q, want %q", s.A, "hi") + } + if s.c != nil { + t.Errorf("c expected to be left nil, got %+v", s.c) + } +} + // ---------------------------------------------------------------------------- type S7 struct {