]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/xml: unmarshal allow empty, non-string values
authorSarah Adams <shadams@google.com>
Mon, 20 Mar 2017 23:11:46 +0000 (16:11 -0700)
committerIan Lance Taylor <iant@golang.org>
Wed, 22 Mar 2017 23:33:36 +0000 (23:33 +0000)
When unmarshaling, if an element is empty, eg. '<tag></tag>', and
destination type is int, uint, float or bool, do not attempt to parse
value (""). Set to its zero value instead.

Fixes #13417

Change-Id: I2d79f6d8f39192bb277b1a9129727d5abbb2dd1f
Reviewed-on: https://go-review.googlesource.com/38386
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/encoding/xml/read.go
src/encoding/xml/read_test.go

index b90271fed3ad8db4d2663cddf2775e96d71642ae..000d9fbd0ef509bf5ed48871dbce75e6591f0f53 100644 (file)
@@ -120,6 +120,9 @@ import (
 // Unmarshal maps an XML element to a pointer by setting the pointer
 // to a freshly allocated value and then mapping the element to that value.
 //
+// A missing element or empty attribute value will be unmarshaled as a zero value.
+// If the field is a slice, a zero value will be appended to the field. Otherwise, the
+// field will be set to its zero value.
 func Unmarshal(data []byte, v interface{}) error {
        return NewDecoder(bytes.NewReader(data)).Decode(v)
 }
@@ -607,24 +610,40 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
        default:
                return errors.New("cannot unmarshal into " + dst0.Type().String())
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               if len(src) == 0 {
+                       dst.SetInt(0)
+                       return nil
+               }
                itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())
                if err != nil {
                        return err
                }
                dst.SetInt(itmp)
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               if len(src) == 0 {
+                       dst.SetUint(0)
+                       return nil
+               }
                utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits())
                if err != nil {
                        return err
                }
                dst.SetUint(utmp)
        case reflect.Float32, reflect.Float64:
+               if len(src) == 0 {
+                       dst.SetFloat(0)
+                       return nil
+               }
                ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits())
                if err != nil {
                        return err
                }
                dst.SetFloat(ftmp)
        case reflect.Bool:
+               if len(src) == 0 {
+                       dst.SetBool(false)
+                       return nil
+               }
                value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
                if err != nil {
                        return err
index 273c303d16dd7bdc9cdad7de03a617b341544db6..a1eb51618773c182c18a3eda7f53cdda90dba449 100644 (file)
@@ -752,3 +752,159 @@ func TestInvalidInnerXMLType(t *testing.T) {
                t.Errorf("NotInnerXML = %v, want nil", v.NotInnerXML)
        }
 }
+
+type Child struct {
+       G struct {
+               I int
+       }
+}
+
+type ChildToEmbed struct {
+       X bool
+}
+
+type Parent struct {
+       I        int
+       IPtr     *int
+       Is       []int
+       IPtrs    []*int
+       F        float32
+       FPtr     *float32
+       Fs       []float32
+       FPtrs    []*float32
+       B        bool
+       BPtr     *bool
+       Bs       []bool
+       BPtrs    []*bool
+       Bytes    []byte
+       BytesPtr *[]byte
+       S        string
+       SPtr     *string
+       Ss       []string
+       SPtrs    []*string
+       MyI      MyInt
+       Child    Child
+       Children []Child
+       ChildPtr *Child
+       ChildToEmbed
+}
+
+const (
+       emptyXML = `
+<Parent>
+    <I></I>
+    <IPtr></IPtr>
+    <Is></Is>
+    <IPtrs></IPtrs>
+    <F></F>
+    <FPtr></FPtr>
+    <Fs></Fs>
+    <FPtrs></FPtrs>
+    <B></B>
+    <BPtr></BPtr>
+    <Bs></Bs>
+    <BPtrs></BPtrs>
+    <Bytes></Bytes>
+    <BytesPtr></BytesPtr>
+    <S></S>
+    <SPtr></SPtr>
+    <Ss></Ss>
+    <SPtrs></SPtrs>
+    <MyI></MyI>
+    <Child></Child>
+    <Children></Children>
+    <ChildPtr></ChildPtr>
+    <X></X>
+</Parent>
+`
+)
+
+// github.com/golang/go/issues/13417
+func TestUnmarshalEmptyValues(t *testing.T) {
+       // Test first with a zero-valued dst.
+       v := new(Parent)
+       if err := Unmarshal([]byte(emptyXML), v); err != nil {
+               t.Fatalf("zero: Unmarshal failed: got %v", err)
+       }
+
+       zBytes, zInt, zStr, zFloat, zBool := []byte{}, 0, "", float32(0), false
+       want := &Parent{
+               IPtr:         &zInt,
+               Is:           []int{zInt},
+               IPtrs:        []*int{&zInt},
+               FPtr:         &zFloat,
+               Fs:           []float32{zFloat},
+               FPtrs:        []*float32{&zFloat},
+               BPtr:         &zBool,
+               Bs:           []bool{zBool},
+               BPtrs:        []*bool{&zBool},
+               Bytes:        []byte{},
+               BytesPtr:     &zBytes,
+               SPtr:         &zStr,
+               Ss:           []string{zStr},
+               SPtrs:        []*string{&zStr},
+               Children:     []Child{{}},
+               ChildPtr:     new(Child),
+               ChildToEmbed: ChildToEmbed{},
+       }
+       if !reflect.DeepEqual(v, want) {
+               t.Fatalf("zero: Unmarshal:\nhave:  %#+v\nwant: %#+v", v, want)
+       }
+
+       // Test with a pre-populated dst.
+       // Multiple addressable copies, as pointer-to fields will replace value during unmarshal.
+       vBytes0, vInt0, vStr0, vFloat0, vBool0 := []byte("x"), 1, "x", float32(1), true
+       vBytes1, vInt1, vStr1, vFloat1, vBool1 := []byte("x"), 1, "x", float32(1), true
+       vInt2, vStr2, vFloat2, vBool2 := 1, "x", float32(1), true
+       v = &Parent{
+               I:            vInt0,
+               IPtr:         &vInt1,
+               Is:           []int{vInt0},
+               IPtrs:        []*int{&vInt2},
+               F:            vFloat0,
+               FPtr:         &vFloat1,
+               Fs:           []float32{vFloat0},
+               FPtrs:        []*float32{&vFloat2},
+               B:            vBool0,
+               BPtr:         &vBool1,
+               Bs:           []bool{vBool0},
+               BPtrs:        []*bool{&vBool2},
+               Bytes:        vBytes0,
+               BytesPtr:     &vBytes1,
+               S:            vStr0,
+               SPtr:         &vStr1,
+               Ss:           []string{vStr0},
+               SPtrs:        []*string{&vStr2},
+               MyI:          MyInt(vInt0),
+               Child:        Child{G: struct{ I int }{I: vInt0}},
+               Children:     []Child{{G: struct{ I int }{I: vInt0}}},
+               ChildPtr:     &Child{G: struct{ I int }{I: vInt0}},
+               ChildToEmbed: ChildToEmbed{X: vBool0},
+       }
+       if err := Unmarshal([]byte(emptyXML), v); err != nil {
+               t.Fatalf("populated: Unmarshal failed: got %v", err)
+       }
+
+       want = &Parent{
+               IPtr:     &zInt,
+               Is:       []int{vInt0, zInt},
+               IPtrs:    []*int{&vInt0, &zInt},
+               FPtr:     &zFloat,
+               Fs:       []float32{vFloat0, zFloat},
+               FPtrs:    []*float32{&vFloat0, &zFloat},
+               BPtr:     &zBool,
+               Bs:       []bool{vBool0, zBool},
+               BPtrs:    []*bool{&vBool0, &zBool},
+               Bytes:    []byte{},
+               BytesPtr: &zBytes,
+               SPtr:     &zStr,
+               Ss:       []string{vStr0, zStr},
+               SPtrs:    []*string{&vStr0, &zStr},
+               Child:    Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
+               Children: []Child{{G: struct{ I int }{I: vInt0}}, {}},
+               ChildPtr: &Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
+       }
+       if !reflect.DeepEqual(v, want) {
+               t.Fatalf("populated: Unmarshal:\nhave:  %#+v\nwant: %#+v", v, want)
+       }
+}