Skip to content

Commit 853155f

Browse files
dsnetandybons
authored andcommitted
[release-branch.go1.10] encoding/json: avoid assuming side-effect free reflect.Value.Addr().Elem()
Consider the following: type child struct{ Field string } type parent struct{ child } p := new(parent) v := reflect.ValueOf(p).Elem().Field(0) v.Field(0).SetString("hello") // v.Field = "hello" v = v.Addr().Elem() // v = *(&v) v.Field(0).SetString("goodbye") // v.Field = "goodbye" It would appear that v.Addr().Elem() should have the same value, and that it would be safe to set "goodbye". However, after CL 66331, any interspersed calls between Field calls causes the RO flag to be set. Thus, setting to "goodbye" actually causes a panic. That CL affects decodeState.indirect which assumes that back-to-back Value.Addr().Elem() is side-effect free. We fix that logic to keep track of the Addr() and Elem() calls and set v back to the original after a full round-trip has occured. Fixes #24152 Updates #24153 Change-Id: Ie50f8fe963f00cef8515d89d1d5cbc43b76d9f9c Reviewed-on: https://go-review.googlesource.com/97796 Reviewed-by: Matthew Dempsky <[email protected]> Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-on: https://go-review.googlesource.com/102784 Run-TryBot: Andrew Bonventre <[email protected]> Reviewed-by: Joe Tsai <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent b8c62b1 commit 853155f

File tree

2 files changed

+69
-5
lines changed

2 files changed

+69
-5
lines changed

src/encoding/json/decode.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,25 @@ func (d *decodeState) valueQuoted() interface{} {
443443
// if it encounters an Unmarshaler, indirect stops and returns that.
444444
// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
445445
func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
446+
// Issue #24153 indicates that it is generally not a guaranteed property
447+
// that you may round-trip a reflect.Value by calling Value.Addr().Elem()
448+
// and expect the value to still be settable for values derived from
449+
// unexported embedded struct fields.
450+
//
451+
// The logic below effectively does this when it first addresses the value
452+
// (to satisfy possible pointer methods) and continues to dereference
453+
// subsequent pointers as necessary.
454+
//
455+
// After the first round-trip, we set v back to the original value to
456+
// preserve the original RW flags contained in reflect.Value.
457+
v0 := v
458+
haveAddr := false
459+
446460
// If v is a named type and is addressable,
447461
// start with its address, so that if the type has pointer methods,
448462
// we find them.
449463
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
464+
haveAddr = true
450465
v = v.Addr()
451466
}
452467
for {
@@ -455,6 +470,7 @@ func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler,
455470
if v.Kind() == reflect.Interface && !v.IsNil() {
456471
e := v.Elem()
457472
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
473+
haveAddr = false
458474
v = e
459475
continue
460476
}
@@ -480,7 +496,13 @@ func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler,
480496
}
481497
}
482498
}
483-
v = v.Elem()
499+
500+
if haveAddr {
501+
v = v0 // restore original value after round-trip Value.Addr().Elem()
502+
haveAddr = false
503+
} else {
504+
v = v.Elem()
505+
}
484506
}
485507
return nil, nil, v
486508
}

src/encoding/json/decode_test.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,10 +2089,14 @@ func TestInvalidStringOption(t *testing.T) {
20892089
}
20902090
}
20912091

2092-
// Test unmarshal behavior with regards to embedded pointers to unexported structs.
2093-
// If unallocated, this returns an error because unmarshal cannot set the field.
2094-
// Issue 21357.
2095-
func TestUnmarshalEmbeddedPointerUnexported(t *testing.T) {
2092+
// Test unmarshal behavior with regards to embedded unexported structs.
2093+
//
2094+
// (Issue 21357) If the embedded struct is a pointer and is unallocated,
2095+
// this returns an error because unmarshal cannot set the field.
2096+
//
2097+
// (Issue 24152) If the embedded struct is given an explicit name,
2098+
// ensure that the normal unmarshal logic does not panic in reflect.
2099+
func TestUnmarshalEmbeddedUnexported(t *testing.T) {
20962100
type (
20972101
embed1 struct{ Q int }
20982102
embed2 struct{ Q int }
@@ -2119,6 +2123,18 @@ func TestUnmarshalEmbeddedPointerUnexported(t *testing.T) {
21192123
*embed3
21202124
R int
21212125
}
2126+
S6 struct {
2127+
embed1 `json:"embed1"`
2128+
}
2129+
S7 struct {
2130+
embed1 `json:"embed1"`
2131+
embed2
2132+
}
2133+
S8 struct {
2134+
embed1 `json:"embed1"`
2135+
embed2 `json:"embed2"`
2136+
Q int
2137+
}
21222138
)
21232139

21242140
tests := []struct {
@@ -2154,6 +2170,32 @@ func TestUnmarshalEmbeddedPointerUnexported(t *testing.T) {
21542170
ptr: new(S5),
21552171
out: &S5{R: 2},
21562172
err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"),
2173+
}, {
2174+
// Issue 24152, ensure decodeState.indirect does not panic.
2175+
in: `{"embed1": {"Q": 1}}`,
2176+
ptr: new(S6),
2177+
out: &S6{embed1{1}},
2178+
}, {
2179+
// Issue 24153, check that we can still set forwarded fields even in
2180+
// the presence of a name conflict.
2181+
//
2182+
// This relies on obscure behavior of reflect where it is possible
2183+
// to set a forwarded exported field on an unexported embedded struct
2184+
// even though there is a name conflict, even when it would have been
2185+
// impossible to do so according to Go visibility rules.
2186+
// Go forbids this because it is ambiguous whether S7.Q refers to
2187+
// S7.embed1.Q or S7.embed2.Q. Since embed1 and embed2 are unexported,
2188+
// it should be impossible for an external package to set either Q.
2189+
//
2190+
// It is probably okay for a future reflect change to break this.
2191+
in: `{"embed1": {"Q": 1}, "Q": 2}`,
2192+
ptr: new(S7),
2193+
out: &S7{embed1{1}, embed2{2}},
2194+
}, {
2195+
// Issue 24153, similar to the S7 case.
2196+
in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`,
2197+
ptr: new(S8),
2198+
out: &S8{embed1{1}, embed2{2}, 3},
21572199
}}
21582200

21592201
for i, tt := range tests {

0 commit comments

Comments
 (0)