Commit 4b36e129 authored by Daniel Martí's avatar Daniel Martí

encoding/json: always verify we can get a field's value

Calling .Interface on a struct field's reflect.Value isn't always safe.
For example, if that field is an unexported anonymous struct.

We only descended into this branch if the struct type had any methods,
so this bug had gone unnoticed for a few release cycles.

Add the check, and add a simple test case.

Fixes #28145.

Change-Id: I02f7e0ab9a4a0c18a5e2164211922fe9c3d30f64
Reviewed-on: https://go-review.googlesource.com/c/141537
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 5eff6bfd
...@@ -473,7 +473,7 @@ func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnm ...@@ -473,7 +473,7 @@ func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnm
if v.IsNil() { if v.IsNil() {
v.Set(reflect.New(v.Type().Elem())) v.Set(reflect.New(v.Type().Elem()))
} }
if v.Type().NumMethod() > 0 { if v.Type().NumMethod() > 0 && v.CanInterface() {
if u, ok := v.Interface().(Unmarshaler); ok { if u, ok := v.Interface().(Unmarshaler); ok {
return u, nil, reflect.Value{} return u, nil, reflect.Value{}
} }
......
...@@ -266,6 +266,10 @@ type XYZ struct { ...@@ -266,6 +266,10 @@ type XYZ struct {
Z interface{} Z interface{}
} }
type unexportedWithMethods struct{}
func (unexportedWithMethods) F() {}
func sliceAddr(x []int) *[]int { return &x } func sliceAddr(x []int) *[]int { return &x }
func mapAddr(x map[string]int) *map[string]int { return &x } func mapAddr(x map[string]int) *map[string]int { return &x }
...@@ -2151,6 +2155,9 @@ func TestInvalidStringOption(t *testing.T) { ...@@ -2151,6 +2155,9 @@ func TestInvalidStringOption(t *testing.T) {
// //
// (Issue 24152) If the embedded struct is given an explicit name, // (Issue 24152) If the embedded struct is given an explicit name,
// ensure that the normal unmarshal logic does not panic in reflect. // ensure that the normal unmarshal logic does not panic in reflect.
//
// (Issue 28145) If the embedded struct is given an explicit name and has
// exported methods, don't cause a panic trying to get its value.
func TestUnmarshalEmbeddedUnexported(t *testing.T) { func TestUnmarshalEmbeddedUnexported(t *testing.T) {
type ( type (
embed1 struct{ Q int } embed1 struct{ Q int }
...@@ -2190,6 +2197,9 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { ...@@ -2190,6 +2197,9 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) {
embed2 `json:"embed2"` embed2 `json:"embed2"`
Q int Q int
} }
S9 struct {
unexportedWithMethods `json:"embed"`
}
) )
tests := []struct { tests := []struct {
...@@ -2251,6 +2261,11 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { ...@@ -2251,6 +2261,11 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) {
in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`,
ptr: new(S8), ptr: new(S8),
out: &S8{embed1{1}, embed2{2}, 3}, out: &S8{embed1{1}, embed2{2}, 3},
}, {
// Issue 228145, similar to the cases above.
in: `{"embed": {}}`,
ptr: new(S9),
out: &S9{},
}} }}
for i, tt := range tests { for i, tt := range tests {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment