Skip to content

Commit 0a0186f

Browse files
Sarah Adamsianlancetaylor
Sarah Adams
authored andcommitted
encoding/xml: unmarshal allow empty, non-string values
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 <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 1295b74 commit 0a0186f

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

src/encoding/xml/read.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ import (
120120
// Unmarshal maps an XML element to a pointer by setting the pointer
121121
// to a freshly allocated value and then mapping the element to that value.
122122
//
123+
// A missing element or empty attribute value will be unmarshaled as a zero value.
124+
// If the field is a slice, a zero value will be appended to the field. Otherwise, the
125+
// field will be set to its zero value.
123126
func Unmarshal(data []byte, v interface{}) error {
124127
return NewDecoder(bytes.NewReader(data)).Decode(v)
125128
}
@@ -607,24 +610,40 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
607610
default:
608611
return errors.New("cannot unmarshal into " + dst0.Type().String())
609612
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
613+
if len(src) == 0 {
614+
dst.SetInt(0)
615+
return nil
616+
}
610617
itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())
611618
if err != nil {
612619
return err
613620
}
614621
dst.SetInt(itmp)
615622
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
623+
if len(src) == 0 {
624+
dst.SetUint(0)
625+
return nil
626+
}
616627
utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits())
617628
if err != nil {
618629
return err
619630
}
620631
dst.SetUint(utmp)
621632
case reflect.Float32, reflect.Float64:
633+
if len(src) == 0 {
634+
dst.SetFloat(0)
635+
return nil
636+
}
622637
ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits())
623638
if err != nil {
624639
return err
625640
}
626641
dst.SetFloat(ftmp)
627642
case reflect.Bool:
643+
if len(src) == 0 {
644+
dst.SetBool(false)
645+
return nil
646+
}
628647
value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
629648
if err != nil {
630649
return err

src/encoding/xml/read_test.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,3 +752,159 @@ func TestInvalidInnerXMLType(t *testing.T) {
752752
t.Errorf("NotInnerXML = %v, want nil", v.NotInnerXML)
753753
}
754754
}
755+
756+
type Child struct {
757+
G struct {
758+
I int
759+
}
760+
}
761+
762+
type ChildToEmbed struct {
763+
X bool
764+
}
765+
766+
type Parent struct {
767+
I int
768+
IPtr *int
769+
Is []int
770+
IPtrs []*int
771+
F float32
772+
FPtr *float32
773+
Fs []float32
774+
FPtrs []*float32
775+
B bool
776+
BPtr *bool
777+
Bs []bool
778+
BPtrs []*bool
779+
Bytes []byte
780+
BytesPtr *[]byte
781+
S string
782+
SPtr *string
783+
Ss []string
784+
SPtrs []*string
785+
MyI MyInt
786+
Child Child
787+
Children []Child
788+
ChildPtr *Child
789+
ChildToEmbed
790+
}
791+
792+
const (
793+
emptyXML = `
794+
<Parent>
795+
<I></I>
796+
<IPtr></IPtr>
797+
<Is></Is>
798+
<IPtrs></IPtrs>
799+
<F></F>
800+
<FPtr></FPtr>
801+
<Fs></Fs>
802+
<FPtrs></FPtrs>
803+
<B></B>
804+
<BPtr></BPtr>
805+
<Bs></Bs>
806+
<BPtrs></BPtrs>
807+
<Bytes></Bytes>
808+
<BytesPtr></BytesPtr>
809+
<S></S>
810+
<SPtr></SPtr>
811+
<Ss></Ss>
812+
<SPtrs></SPtrs>
813+
<MyI></MyI>
814+
<Child></Child>
815+
<Children></Children>
816+
<ChildPtr></ChildPtr>
817+
<X></X>
818+
</Parent>
819+
`
820+
)
821+
822+
// github.com/golang/go/issues/13417
823+
func TestUnmarshalEmptyValues(t *testing.T) {
824+
// Test first with a zero-valued dst.
825+
v := new(Parent)
826+
if err := Unmarshal([]byte(emptyXML), v); err != nil {
827+
t.Fatalf("zero: Unmarshal failed: got %v", err)
828+
}
829+
830+
zBytes, zInt, zStr, zFloat, zBool := []byte{}, 0, "", float32(0), false
831+
want := &Parent{
832+
IPtr: &zInt,
833+
Is: []int{zInt},
834+
IPtrs: []*int{&zInt},
835+
FPtr: &zFloat,
836+
Fs: []float32{zFloat},
837+
FPtrs: []*float32{&zFloat},
838+
BPtr: &zBool,
839+
Bs: []bool{zBool},
840+
BPtrs: []*bool{&zBool},
841+
Bytes: []byte{},
842+
BytesPtr: &zBytes,
843+
SPtr: &zStr,
844+
Ss: []string{zStr},
845+
SPtrs: []*string{&zStr},
846+
Children: []Child{{}},
847+
ChildPtr: new(Child),
848+
ChildToEmbed: ChildToEmbed{},
849+
}
850+
if !reflect.DeepEqual(v, want) {
851+
t.Fatalf("zero: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
852+
}
853+
854+
// Test with a pre-populated dst.
855+
// Multiple addressable copies, as pointer-to fields will replace value during unmarshal.
856+
vBytes0, vInt0, vStr0, vFloat0, vBool0 := []byte("x"), 1, "x", float32(1), true
857+
vBytes1, vInt1, vStr1, vFloat1, vBool1 := []byte("x"), 1, "x", float32(1), true
858+
vInt2, vStr2, vFloat2, vBool2 := 1, "x", float32(1), true
859+
v = &Parent{
860+
I: vInt0,
861+
IPtr: &vInt1,
862+
Is: []int{vInt0},
863+
IPtrs: []*int{&vInt2},
864+
F: vFloat0,
865+
FPtr: &vFloat1,
866+
Fs: []float32{vFloat0},
867+
FPtrs: []*float32{&vFloat2},
868+
B: vBool0,
869+
BPtr: &vBool1,
870+
Bs: []bool{vBool0},
871+
BPtrs: []*bool{&vBool2},
872+
Bytes: vBytes0,
873+
BytesPtr: &vBytes1,
874+
S: vStr0,
875+
SPtr: &vStr1,
876+
Ss: []string{vStr0},
877+
SPtrs: []*string{&vStr2},
878+
MyI: MyInt(vInt0),
879+
Child: Child{G: struct{ I int }{I: vInt0}},
880+
Children: []Child{{G: struct{ I int }{I: vInt0}}},
881+
ChildPtr: &Child{G: struct{ I int }{I: vInt0}},
882+
ChildToEmbed: ChildToEmbed{X: vBool0},
883+
}
884+
if err := Unmarshal([]byte(emptyXML), v); err != nil {
885+
t.Fatalf("populated: Unmarshal failed: got %v", err)
886+
}
887+
888+
want = &Parent{
889+
IPtr: &zInt,
890+
Is: []int{vInt0, zInt},
891+
IPtrs: []*int{&vInt0, &zInt},
892+
FPtr: &zFloat,
893+
Fs: []float32{vFloat0, zFloat},
894+
FPtrs: []*float32{&vFloat0, &zFloat},
895+
BPtr: &zBool,
896+
Bs: []bool{vBool0, zBool},
897+
BPtrs: []*bool{&vBool0, &zBool},
898+
Bytes: []byte{},
899+
BytesPtr: &zBytes,
900+
SPtr: &zStr,
901+
Ss: []string{vStr0, zStr},
902+
SPtrs: []*string{&vStr0, &zStr},
903+
Child: Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
904+
Children: []Child{{G: struct{ I int }{I: vInt0}}, {}},
905+
ChildPtr: &Child{G: struct{ I int }{I: vInt0}}, // I should == zInt0? (zero value)
906+
}
907+
if !reflect.DeepEqual(v, want) {
908+
t.Fatalf("populated: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
909+
}
910+
}

0 commit comments

Comments
 (0)