Commit 95c3c430 authored by Daniel Martí's avatar Daniel Martí

encoding/json: fix the broken "overwriting of data" tests

Because TestUnmarshal actually allocates a new value to decode into
using ptr's pointer type, any existing data is thrown away. This was
harmless in alomst all of the test cases, minus the "overwriting of
data" ones added in 2015 in CL 12209.

I spotted that nothing covered decoding a JSON array with few elements
into a slice which already had many elements. I initially assumed that
the code was buggy or that some code could be removed, when in fact
there simply wasn't any code covering the edge case.

Move those two tests to TestPrefilled, which already served a very
similar purpose. Remove the map case, as TestPrefilled already has
plenty of prefilled map cases. Moreover, we no longer reset an entire
map when decoding, as per the godoc:

	To unmarshal a JSON object into a map, Unmarshal first
	establishes a map to use. If the map is nil, Unmarshal allocates
	a new map. Otherwise Unmarshal reuses the existing map, keeping
	existing entries.

Finally, to ensure that ptr is used correctly in the future, make
TestUnmarshal error if it's anything other than a pointer to a zero
value. That is, the only correct use should be new(type). Don't rename
the ptr field, as that would be extremely noisy and cause unwanted merge
conflicts.

Change-Id: I41e3ecfeae42d877ac5443a6bd622ac3d6c8120c
Reviewed-on: https://go-review.googlesource.com/c/go/+/185738
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarEmmanuel Odeke <emm.odeke@gmail.com>
parent 1a53915c
...@@ -145,22 +145,14 @@ func (u8 *u8marshal) UnmarshalText(b []byte) error { ...@@ -145,22 +145,14 @@ func (u8 *u8marshal) UnmarshalText(b []byte) error {
var _ encoding.TextUnmarshaler = (*u8marshal)(nil) var _ encoding.TextUnmarshaler = (*u8marshal)(nil)
var ( var (
um0, um1 unmarshaler // target2 of unmarshaling
ump = &um1
umtrue = unmarshaler{true} umtrue = unmarshaler{true}
umslice = []unmarshaler{{true}} umslice = []unmarshaler{{true}}
umslicep = new([]unmarshaler)
umstruct = ustruct{unmarshaler{true}} umstruct = ustruct{unmarshaler{true}}
um0T, um1T unmarshalerText // target2 of unmarshaling
umpType = &um1T
umtrueXY = unmarshalerText{"x", "y"} umtrueXY = unmarshalerText{"x", "y"}
umsliceXY = []unmarshalerText{{"x", "y"}} umsliceXY = []unmarshalerText{{"x", "y"}}
umslicepType = new([]unmarshalerText)
umstructType = new(ustructText)
umstructXY = ustructText{unmarshalerText{"x", "y"}} umstructXY = ustructText{unmarshalerText{"x", "y"}}
ummapType = map[unmarshalerText]bool{}
ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} ummapXY = map[unmarshalerText]bool{{"x", "y"}: true}
) )
...@@ -279,9 +271,6 @@ type unexportedWithMethods struct{} ...@@ -279,9 +271,6 @@ type unexportedWithMethods struct{}
func (unexportedWithMethods) F() {} func (unexportedWithMethods) F() {}
func sliceAddr(x []int) *[]int { return &x }
func mapAddr(x map[string]int) *map[string]int { return &x }
type byteWithMarshalJSON byte type byteWithMarshalJSON byte
func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) {
...@@ -400,7 +389,7 @@ type mapStringToStringData struct { ...@@ -400,7 +389,7 @@ type mapStringToStringData struct {
type unmarshalTest struct { type unmarshalTest struct {
in string in string
ptr interface{} ptr interface{} // new(type)
out interface{} out interface{}
err error err error
useNumber bool useNumber bool
...@@ -493,18 +482,18 @@ var unmarshalTests = []unmarshalTest{ ...@@ -493,18 +482,18 @@ var unmarshalTests = []unmarshalTest{
{in: pallValueCompact, ptr: new(*All), out: &pallValue}, {in: pallValueCompact, ptr: new(*All), out: &pallValue},
// unmarshal interface test // unmarshal interface test
{in: `{"T":false}`, ptr: &um0, out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called {in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called
{in: `{"T":false}`, ptr: &ump, out: &umtrue}, {in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue},
{in: `[{"T":false}]`, ptr: &umslice, out: umslice}, {in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice},
{in: `[{"T":false}]`, ptr: &umslicep, out: &umslice}, {in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice},
{in: `{"M":{"T":"x:y"}}`, ptr: &umstruct, out: umstruct}, {in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct},
// UnmarshalText interface test // UnmarshalText interface test
{in: `"x:y"`, ptr: &um0T, out: umtrueXY}, {in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY},
{in: `"x:y"`, ptr: &umpType, out: &umtrueXY}, {in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY},
{in: `["x:y"]`, ptr: &umsliceXY, out: umsliceXY}, {in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY},
{in: `["x:y"]`, ptr: &umslicepType, out: &umsliceXY}, {in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY},
{in: `{"M":"x:y"}`, ptr: umstructType, out: umstructXY}, {in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY},
// integer-keyed map test // integer-keyed map test
{ {
...@@ -579,15 +568,9 @@ var unmarshalTests = []unmarshalTest{ ...@@ -579,15 +568,9 @@ var unmarshalTests = []unmarshalTest{
}, },
// Map keys can be encoding.TextUnmarshalers. // Map keys can be encoding.TextUnmarshalers.
{in: `{"x:y":true}`, ptr: &ummapType, out: ummapXY}, {in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY},
// If multiple values for the same key exists, only the most recent value is used. // If multiple values for the same key exists, only the most recent value is used.
{in: `{"x:y":false,"x:y":true}`, ptr: &ummapType, out: ummapXY}, {in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY},
// Overwriting of data.
// This is different from package xml, but it's what we've always done.
// Now documented and tested.
{in: `[2]`, ptr: sliceAddr([]int{1}), out: []int{2}},
{in: `{"key": 2}`, ptr: mapAddr(map[string]int{"old": 0, "key": 1}), out: map[string]int{"key": 2}},
{ {
in: `{ in: `{
...@@ -713,19 +696,19 @@ var unmarshalTests = []unmarshalTest{ ...@@ -713,19 +696,19 @@ var unmarshalTests = []unmarshalTest{
// Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now.
{ {
in: `{"2009-11-10T23:00:00Z": "hello world"}`, in: `{"2009-11-10T23:00:00Z": "hello world"}`,
ptr: &map[time.Time]string{}, ptr: new(map[time.Time]string),
out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"},
}, },
// issue 8305 // issue 8305
{ {
in: `{"2009-11-10T23:00:00Z": "hello world"}`, in: `{"2009-11-10T23:00:00Z": "hello world"}`,
ptr: &map[Point]string{}, ptr: new(map[Point]string),
err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1}, err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1},
}, },
{ {
in: `{"asdf": "hello world"}`, in: `{"asdf": "hello world"}`,
ptr: &map[unmarshaler]string{}, ptr: new(map[unmarshaler]string),
err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1}, err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1},
}, },
...@@ -1077,8 +1060,27 @@ func TestUnmarshal(t *testing.T) { ...@@ -1077,8 +1060,27 @@ func TestUnmarshal(t *testing.T) {
continue continue
} }
typ := reflect.TypeOf(tt.ptr)
if typ.Kind() != reflect.Ptr {
t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr)
continue
}
typ = typ.Elem()
// v = new(right-type) // v = new(right-type)
v := reflect.New(reflect.TypeOf(tt.ptr).Elem()) v := reflect.New(typ)
if !reflect.DeepEqual(tt.ptr, v.Interface()) {
// There's no reason for ptr to point to non-zero data,
// as we decode into new(right-type), so the data is
// discarded.
// This can easily mean tests that silently don't test
// what they should. To test decoding into existing
// data, see TestPrefilled.
t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr)
continue
}
dec := NewDecoder(bytes.NewReader(in)) dec := NewDecoder(bytes.NewReader(in))
if tt.useNumber { if tt.useNumber {
dec.UseNumber() dec.UseNumber()
...@@ -2086,11 +2088,10 @@ func TestSkipArrayObjects(t *testing.T) { ...@@ -2086,11 +2088,10 @@ func TestSkipArrayObjects(t *testing.T) {
} }
} }
// Test semantics of pre-filled struct fields and pre-filled map fields. // Test semantics of pre-filled data, such as struct fields, map elements,
// Issue 4900. // slices, and arrays.
// Issues 4900 and 8837, among others.
func TestPrefilled(t *testing.T) { func TestPrefilled(t *testing.T) {
ptrToMap := func(m map[string]interface{}) *map[string]interface{} { return &m }
// Values here change, cannot reuse table across runs. // Values here change, cannot reuse table across runs.
var prefillTests = []struct { var prefillTests = []struct {
in string in string
...@@ -2104,8 +2105,28 @@ func TestPrefilled(t *testing.T) { ...@@ -2104,8 +2105,28 @@ func TestPrefilled(t *testing.T) {
}, },
{ {
in: `{"X": 1, "Y": 2}`, in: `{"X": 1, "Y": 2}`,
ptr: ptrToMap(map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5}), ptr: &map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5},
out: ptrToMap(map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5}), out: &map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5},
},
{
in: `[2]`,
ptr: &[]int{1},
out: &[]int{2},
},
{
in: `[2, 3]`,
ptr: &[]int{1},
out: &[]int{2, 3},
},
{
in: `[2, 3]`,
ptr: &[...]int{1},
out: &[...]int{2},
},
{
in: `[3]`,
ptr: &[...]int{1, 2},
out: &[...]int{3, 0},
}, },
} }
......
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