Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()))
}
}
Expand Down
35 changes: 35 additions & 0 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading