aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/encoding
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2019-01-18 19:04:36 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2019-01-18 19:04:36 +0000
commit4f4a855d82a889cebcfca150a7a43909bcb6a346 (patch)
treef12bae0781920fa34669fe30b6f4615a86d9fb80 /libgo/go/encoding
parent225220d668dafb8262db7012bced688acbe63b33 (diff)
downloadgcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.zip
gcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.tar.gz
gcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.tar.bz2
libgo: update to Go1.12beta2
Reviewed-on: https://go-review.googlesource.com/c/158019 gotools/: * Makefile.am (go_cmd_vet_files): Update for Go1.12beta2 release. (GOTOOLS_TEST_TIMEOUT): Increase to 600. (check-runtime): Export LD_LIBRARY_PATH before computing GOARCH and GOOS. (check-vet): Copy golang.org/x/tools into check-vet-dir. * Makefile.in: Regenerate. gcc/testsuite/: * go.go-torture/execute/names-1.go: Stop using debug/xcoff, which is no longer externally visible. From-SVN: r268084
Diffstat (limited to 'libgo/go/encoding')
-rw-r--r--libgo/go/encoding/asn1/asn1.go2
-rw-r--r--libgo/go/encoding/base32/base32_test.go10
-rw-r--r--libgo/go/encoding/base64/base64.go68
-rw-r--r--libgo/go/encoding/base64/base64_test.go8
-rw-r--r--libgo/go/encoding/binary/binary.go40
-rw-r--r--libgo/go/encoding/gob/decoder.go11
-rw-r--r--libgo/go/encoding/gob/encoder.go3
-rw-r--r--libgo/go/encoding/gob/encoder_test.go22
-rw-r--r--libgo/go/encoding/hex/hex.go13
-rw-r--r--libgo/go/encoding/hex/hex_test.go13
-rw-r--r--libgo/go/encoding/json/bench_test.go28
-rw-r--r--libgo/go/encoding/json/decode.go194
-rw-r--r--libgo/go/encoding/json/decode_test.go57
-rw-r--r--libgo/go/encoding/json/encode.go164
-rw-r--r--libgo/go/encoding/json/encode_test.go28
-rw-r--r--libgo/go/encoding/json/example_text_marshaling_test.go69
-rw-r--r--libgo/go/encoding/json/scanner.go2
-rw-r--r--libgo/go/encoding/json/stream.go24
-rw-r--r--libgo/go/encoding/json/stream_test.go34
-rw-r--r--libgo/go/encoding/pem/pem_test.go8
-rw-r--r--libgo/go/encoding/xml/example_marshaling_test.go86
-rw-r--r--libgo/go/encoding/xml/example_text_marshaling_test.go81
-rw-r--r--libgo/go/encoding/xml/marshal.go4
-rw-r--r--libgo/go/encoding/xml/read.go5
24 files changed, 660 insertions, 314 deletions
diff --git a/libgo/go/encoding/asn1/asn1.go b/libgo/go/encoding/asn1/asn1.go
index 1ed357a..3cfd9d1 100644
--- a/libgo/go/encoding/asn1/asn1.go
+++ b/libgo/go/encoding/asn1/asn1.go
@@ -633,7 +633,7 @@ var (
bigIntType = reflect.TypeOf(new(big.Int))
)
-// invalidLength returns true iff offset + length > sliceLength, or if the
+// invalidLength reports whether offset + length > sliceLength, or if the
// addition would overflow.
func invalidLength(offset, length, sliceLength int) bool {
return offset+length < offset || offset+length > sliceLength
diff --git a/libgo/go/encoding/base32/base32_test.go b/libgo/go/encoding/base32/base32_test.go
index c5506ed..b74054b 100644
--- a/libgo/go/encoding/base32/base32_test.go
+++ b/libgo/go/encoding/base32/base32_test.go
@@ -425,7 +425,7 @@ IZJAOZSWY2LUEBSXG43FEBRWS3DMOVWSAZDPNRXXEZJAMV2SAZTVM5UWC5BANZ2WY3DBBJYGC4TJMF
NZ2CYIDTOVXHIIDJNYFGG5LMOBQSA4LVNEQG6ZTGNFRWSYJAMRSXGZLSOVXHIIDNN5WGY2LUEBQW42
LNEBUWIIDFON2CA3DBMJXXE5LNFY==
====`
- encodedShort := strings.Replace(encoded, "\n", "", -1)
+ encodedShort := strings.ReplaceAll(encoded, "\n", "")
dec := NewDecoder(StdEncoding, strings.NewReader(encoded))
res1, err := ioutil.ReadAll(dec)
@@ -465,7 +465,7 @@ func TestWithCustomPadding(t *testing.T) {
for _, testcase := range pairs {
defaultPadding := StdEncoding.EncodeToString([]byte(testcase.decoded))
customPadding := StdEncoding.WithPadding('@').EncodeToString([]byte(testcase.decoded))
- expected := strings.Replace(defaultPadding, "=", "@", -1)
+ expected := strings.ReplaceAll(defaultPadding, "=", "@")
if expected != customPadding {
t.Errorf("Expected custom %s, got %s", expected, customPadding)
@@ -675,7 +675,7 @@ func TestWithoutPaddingClose(t *testing.T) {
expected := testpair.encoded
if encoding.padChar == NoPadding {
- expected = strings.Replace(expected, "=", "", -1)
+ expected = strings.ReplaceAll(expected, "=", "")
}
res := buf.String()
@@ -697,7 +697,7 @@ func TestDecodeReadAll(t *testing.T) {
for encIndex, encoding := range encodings {
encoded := pair.encoded
if encoding.padChar == NoPadding {
- encoded = strings.Replace(encoded, "=", "", -1)
+ encoded = strings.ReplaceAll(encoded, "=", "")
}
decReader, err := ioutil.ReadAll(NewDecoder(encoding, strings.NewReader(encoded)))
@@ -723,7 +723,7 @@ func TestDecodeSmallBuffer(t *testing.T) {
for encIndex, encoding := range encodings {
encoded := pair.encoded
if encoding.padChar == NoPadding {
- encoded = strings.Replace(encoded, "=", "", -1)
+ encoded = strings.ReplaceAll(encoded, "=", "")
}
decoder := NewDecoder(encoding, strings.NewReader(encoded))
diff --git a/libgo/go/encoding/base64/base64.go b/libgo/go/encoding/base64/base64.go
index 9a99370..0bb37b3 100644
--- a/libgo/go/encoding/base64/base64.go
+++ b/libgo/go/encoding/base64/base64.go
@@ -270,7 +270,7 @@ func (e CorruptInputError) Error() string {
return "illegal base64 data at input byte " + strconv.FormatInt(int64(e), 10)
}
-// decodeQuantum decodes up to 4 base64 bytes. It takes for parameters
+// decodeQuantum decodes up to 4 base64 bytes. The received parameters are
// the destination buffer dst, the source buffer src and an index in the
// source buffer si.
// It returns the number of bytes read from src, the number of bytes written
@@ -465,10 +465,9 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) {
}
si := 0
- ilen := len(src)
- olen := len(dst)
- for strconv.IntSize >= 64 && ilen-si >= 8 && olen-n >= 8 {
- if ok := enc.decode64(dst[n:], src[si:]); ok {
+ for strconv.IntSize >= 64 && len(src)-si >= 8 && len(dst)-n >= 8 {
+ if dn, ok := enc.decode64(src[si:]); ok {
+ binary.BigEndian.PutUint64(dst[n:], dn)
n += 6
si += 8
} else {
@@ -481,8 +480,9 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) {
}
}
- for ilen-si >= 4 && olen-n >= 4 {
- if ok := enc.decode32(dst[n:], src[si:]); ok {
+ for len(src)-si >= 4 && len(dst)-n >= 4 {
+ if dn, ok := enc.decode32(src[si:]); ok {
+ binary.BigEndian.PutUint32(dst[n:], dn)
n += 3
si += 4
} else {
@@ -506,72 +506,70 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) {
return n, err
}
-// decode32 tries to decode 4 base64 char into 3 bytes.
-// len(dst) and len(src) must both be >= 4.
-// Returns true if decode succeeded.
-func (enc *Encoding) decode32(dst, src []byte) bool {
- var dn, n uint32
+// decode32 tries to decode 4 base64 characters into 3 bytes, and returns those
+// bytes. len(src) must be >= 4.
+// Returns (0, false) if decoding failed.
+func (enc *Encoding) decode32(src []byte) (dn uint32, ok bool) {
+ var n uint32
+ _ = src[3]
if n = uint32(enc.decodeMap[src[0]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 26
if n = uint32(enc.decodeMap[src[1]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 20
if n = uint32(enc.decodeMap[src[2]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 14
if n = uint32(enc.decodeMap[src[3]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 8
-
- binary.BigEndian.PutUint32(dst, dn)
- return true
+ return dn, true
}
-// decode64 tries to decode 8 base64 char into 6 bytes.
-// len(dst) and len(src) must both be >= 8.
-// Returns true if decode succeeded.
-func (enc *Encoding) decode64(dst, src []byte) bool {
- var dn, n uint64
+// decode64 tries to decode 8 base64 characters into 6 bytes, and returns those
+// bytes. len(src) must be >= 8.
+// Returns (0, false) if decoding failed.
+func (enc *Encoding) decode64(src []byte) (dn uint64, ok bool) {
+ var n uint64
+ _ = src[7]
if n = uint64(enc.decodeMap[src[0]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 58
if n = uint64(enc.decodeMap[src[1]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 52
if n = uint64(enc.decodeMap[src[2]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 46
if n = uint64(enc.decodeMap[src[3]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 40
if n = uint64(enc.decodeMap[src[4]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 34
if n = uint64(enc.decodeMap[src[5]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 28
if n = uint64(enc.decodeMap[src[6]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 22
if n = uint64(enc.decodeMap[src[7]]); n == 0xff {
- return false
+ return 0, false
}
dn |= n << 16
-
- binary.BigEndian.PutUint64(dst, dn)
- return true
+ return dn, true
}
type newlineFilteringReader struct {
diff --git a/libgo/go/encoding/base64/base64_test.go b/libgo/go/encoding/base64/base64_test.go
index f019654..f7f312c 100644
--- a/libgo/go/encoding/base64/base64_test.go
+++ b/libgo/go/encoding/base64/base64_test.go
@@ -53,8 +53,8 @@ func stdRef(ref string) string {
// Convert a reference string to URL-encoding
func urlRef(ref string) string {
- ref = strings.Replace(ref, "+", "-", -1)
- ref = strings.Replace(ref, "/", "_", -1)
+ ref = strings.ReplaceAll(ref, "+", "-")
+ ref = strings.ReplaceAll(ref, "/", "_")
return ref
}
@@ -72,7 +72,7 @@ func rawURLRef(ref string) string {
var funnyEncoding = NewEncoding(encodeStd).WithPadding(rune('@'))
func funnyRef(ref string) string {
- return strings.Replace(ref, "=", "@", -1)
+ return strings.ReplaceAll(ref, "=", "@")
}
type encodingTest struct {
@@ -418,7 +418,7 @@ j+mSARB/17pKVXYWHXjsj7yIex0PadzXMO1zT5KHoNA3HT8ietoGhgjsfA+CSnvvqh/jJtqsrwOv
2b6NGNzXfTYexzJ+nU7/ALkf4P8Awv6P9KvTQQ4AgyDqCF85Pho3CTB7eHwXoH+LT65uZbX9X+o2
bqbPb06551Y4
`
- encodedShort := strings.Replace(encoded, "\n", "", -1)
+ encodedShort := strings.ReplaceAll(encoded, "\n", "")
dec := NewDecoder(StdEncoding, strings.NewReader(encoded))
res1, err := ioutil.ReadAll(dec)
diff --git a/libgo/go/encoding/binary/binary.go b/libgo/go/encoding/binary/binary.go
index 85b3bc2..8c2d1d9 100644
--- a/libgo/go/encoding/binary/binary.go
+++ b/libgo/go/encoding/binary/binary.go
@@ -161,23 +161,17 @@ func (bigEndian) GoString() string { return "binary.BigEndian" }
func Read(r io.Reader, order ByteOrder, data interface{}) error {
// Fast path for basic types and slices.
if n := intDataSize(data); n != 0 {
- var b [8]byte
- var bs []byte
- if n > len(b) {
- bs = make([]byte, n)
- } else {
- bs = b[:n]
- }
+ bs := make([]byte, n)
if _, err := io.ReadFull(r, bs); err != nil {
return err
}
switch data := data.(type) {
case *bool:
- *data = b[0] != 0
+ *data = bs[0] != 0
case *int8:
- *data = int8(b[0])
+ *data = int8(bs[0])
case *uint8:
- *data = b[0]
+ *data = bs[0]
case *int16:
*data = int16(order.Uint16(bs))
case *uint16:
@@ -260,25 +254,19 @@ func Read(r io.Reader, order ByteOrder, data interface{}) error {
func Write(w io.Writer, order ByteOrder, data interface{}) error {
// Fast path for basic types and slices.
if n := intDataSize(data); n != 0 {
- var b [8]byte
- var bs []byte
- if n > len(b) {
- bs = make([]byte, n)
- } else {
- bs = b[:n]
- }
+ bs := make([]byte, n)
switch v := data.(type) {
case *bool:
if *v {
- b[0] = 1
+ bs[0] = 1
} else {
- b[0] = 0
+ bs[0] = 0
}
case bool:
if v {
- b[0] = 1
+ bs[0] = 1
} else {
- b[0] = 0
+ bs[0] = 0
}
case []bool:
for i, x := range v {
@@ -289,19 +277,19 @@ func Write(w io.Writer, order ByteOrder, data interface{}) error {
}
}
case *int8:
- b[0] = byte(*v)
+ bs[0] = byte(*v)
case int8:
- b[0] = byte(v)
+ bs[0] = byte(v)
case []int8:
for i, x := range v {
bs[i] = byte(x)
}
case *uint8:
- b[0] = *v
+ bs[0] = *v
case uint8:
- b[0] = v
+ bs[0] = v
case []uint8:
- bs = v
+ bs = v // TODO(josharian): avoid allocating bs in this case?
case *int16:
order.PutUint16(bs, uint16(*v))
case int16:
diff --git a/libgo/go/encoding/gob/decoder.go b/libgo/go/encoding/gob/decoder.go
index 5ef0388..b52aabe 100644
--- a/libgo/go/encoding/gob/decoder.go
+++ b/libgo/go/encoding/gob/decoder.go
@@ -12,13 +12,14 @@ import (
"sync"
)
-// tooBig provides a sanity check for sizes; used in several places.
-// Upper limit of 1GB, allowing room to grow a little without overflow.
-// TODO: make this adjustable?
-const tooBig = 1 << 30
+// tooBig provides a sanity check for sizes; used in several places. Upper limit
+// of is 1GB on 32-bit systems, 8GB on 64-bit, allowing room to grow a little
+// without overflow.
+const tooBig = (1 << 30) << (^uint(0) >> 62)
// A Decoder manages the receipt of type and data information read from the
-// remote side of a connection.
+// remote side of a connection. It is safe for concurrent use by multiple
+// goroutines.
//
// The Decoder does only basic sanity checking on decoded input sizes,
// and its limits are not configurable. Take caution when decoding gob data
diff --git a/libgo/go/encoding/gob/encoder.go b/libgo/go/encoding/gob/encoder.go
index 40ec81b..53e2cac 100644
--- a/libgo/go/encoding/gob/encoder.go
+++ b/libgo/go/encoding/gob/encoder.go
@@ -12,7 +12,8 @@ import (
)
// An Encoder manages the transmission of type and data information to the
-// other side of a connection.
+// other side of a connection. It is safe for concurrent use by multiple
+// goroutines.
type Encoder struct {
mutex sync.Mutex // each item must be sent atomically
w []io.Writer // where to send the data
diff --git a/libgo/go/encoding/gob/encoder_test.go b/libgo/go/encoding/gob/encoder_test.go
index dc9bbcf..825f0d6 100644
--- a/libgo/go/encoding/gob/encoder_test.go
+++ b/libgo/go/encoding/gob/encoder_test.go
@@ -10,7 +10,6 @@ import (
"fmt"
"io/ioutil"
"reflect"
- "runtime"
"strings"
"testing"
)
@@ -1128,24 +1127,3 @@ func TestBadData(t *testing.T) {
}
}
}
-
-// TestHugeWriteFails tests that enormous messages trigger an error.
-func TestHugeWriteFails(t *testing.T) {
- if runtime.GOARCH == "wasm" {
- t.Skip("out of memory on wasm")
- }
- if testing.Short() {
- // Requires allocating a monster, so don't do this from all.bash.
- t.Skip("skipping huge allocation in short mode")
- }
- huge := make([]byte, tooBig)
- huge[0] = 7 // Make sure it's not all zeros.
- buf := new(bytes.Buffer)
- err := NewEncoder(buf).Encode(huge)
- if err == nil {
- t.Fatalf("expected error for huge slice")
- }
- if !strings.Contains(err.Error(), "message too big") {
- t.Fatalf("expected 'too big' error; got %s\n", err.Error())
- }
-}
diff --git a/libgo/go/encoding/hex/hex.go b/libgo/go/encoding/hex/hex.go
index aee5aec..2bb2b57 100644
--- a/libgo/go/encoding/hex/hex.go
+++ b/libgo/go/encoding/hex/hex.go
@@ -6,10 +6,10 @@
package hex
import (
- "bytes"
"errors"
"fmt"
"io"
+ "strings"
)
const hextable = "0123456789abcdef"
@@ -116,7 +116,16 @@ func DecodeString(s string) ([]byte, error) {
// Dump returns a string that contains a hex dump of the given data. The format
// of the hex dump matches the output of `hexdump -C` on the command line.
func Dump(data []byte) string {
- var buf bytes.Buffer
+ if len(data) == 0 {
+ return ""
+ }
+
+ var buf strings.Builder
+ // Dumper will write 79 bytes per complete 16 byte chunk, and at least
+ // 64 bytes for whatever remains. Round the allocation up, since only a
+ // maximum of 15 bytes will be wasted.
+ buf.Grow((1 + ((len(data) - 1) / 16)) * 79)
+
dumper := Dumper(&buf)
dumper.Write(data)
dumper.Close()
diff --git a/libgo/go/encoding/hex/hex_test.go b/libgo/go/encoding/hex/hex_test.go
index 6ba054e..e9f4b3a 100644
--- a/libgo/go/encoding/hex/hex_test.go
+++ b/libgo/go/encoding/hex/hex_test.go
@@ -248,3 +248,16 @@ func BenchmarkEncode(b *testing.B) {
})
}
}
+
+func BenchmarkDump(b *testing.B) {
+ for _, size := range []int{256, 1024, 4096, 16384} {
+ src := bytes.Repeat([]byte{2, 3, 5, 7, 9, 11, 13, 17}, size/8)
+ sink = make([]byte, 2*size)
+
+ b.Run(fmt.Sprintf("%v", size), func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Dump(src)
+ }
+ })
+ }
+}
diff --git a/libgo/go/encoding/json/bench_test.go b/libgo/go/encoding/json/bench_test.go
index bd322db..72cb349 100644
--- a/libgo/go/encoding/json/bench_test.go
+++ b/libgo/go/encoding/json/bench_test.go
@@ -114,6 +114,34 @@ func BenchmarkCodeMarshal(b *testing.B) {
b.SetBytes(int64(len(codeJSON)))
}
+func benchMarshalBytes(n int) func(*testing.B) {
+ sample := []byte("hello world")
+ // Use a struct pointer, to avoid an allocation when passing it as an
+ // interface parameter to Marshal.
+ v := &struct {
+ Bytes []byte
+ }{
+ bytes.Repeat(sample, (n/len(sample))+1)[:n],
+ }
+ return func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ if _, err := Marshal(v); err != nil {
+ b.Fatal("Marshal:", err)
+ }
+ }
+ }
+}
+
+func BenchmarkMarshalBytes(b *testing.B) {
+ // 32 fits within encodeState.scratch.
+ b.Run("32", benchMarshalBytes(32))
+ // 256 doesn't fit in encodeState.scratch, but is small enough to
+ // allocate and avoid the slower base64.NewEncoder.
+ b.Run("256", benchMarshalBytes(256))
+ // 4096 is large enough that we want to avoid allocating for it.
+ b.Run("4096", benchMarshalBytes(4096))
+}
+
func BenchmarkCodeDecoder(b *testing.B) {
if codeJSON == nil {
b.StopTimer()
diff --git a/libgo/go/encoding/json/decode.go b/libgo/go/encoding/json/decode.go
index 7d23508..731553d 100644
--- a/libgo/go/encoding/json/decode.go
+++ b/libgo/go/encoding/json/decode.go
@@ -11,7 +11,6 @@ import (
"bytes"
"encoding"
"encoding/base64"
- "errors"
"fmt"
"reflect"
"strconv"
@@ -179,7 +178,7 @@ func (d *decodeState) unmarshal(v interface{}) error {
// test must be applied at the top level of the value.
err := d.value(rv)
if err != nil {
- return err
+ return d.addErrorContext(err)
}
return d.savedError
}
@@ -267,7 +266,7 @@ type decodeState struct {
opcode int // last read result
scan scanner
errorContext struct { // provides context for type errors
- Struct string
+ Struct reflect.Type
Field string
}
savedError error
@@ -280,16 +279,16 @@ func (d *decodeState) readIndex() int {
return d.off - 1
}
-// errPhase is used for errors that should not happen unless
-// there is a bug in the JSON decoder or something is editing
-// the data slice while the decoder executes.
-var errPhase = errors.New("JSON decoder out of sync - data changing underfoot?")
+// phasePanicMsg is used as a panic message when we end up with something that
+// shouldn't happen. It can indicate a bug in the JSON decoder, or that
+// something is editing the data slice while the decoder executes.
+const phasePanicMsg = "JSON decoder out of sync - data changing underfoot?"
func (d *decodeState) init(data []byte) *decodeState {
d.data = data
d.off = 0
d.savedError = nil
- d.errorContext.Struct = ""
+ d.errorContext.Struct = nil
d.errorContext.Field = ""
return d
}
@@ -304,10 +303,10 @@ func (d *decodeState) saveError(err error) {
// addErrorContext returns a new error enhanced with information from d.errorContext
func (d *decodeState) addErrorContext(err error) error {
- if d.errorContext.Struct != "" || d.errorContext.Field != "" {
+ if d.errorContext.Struct != nil || d.errorContext.Field != "" {
switch err := err.(type) {
case *UnmarshalTypeError:
- err.Struct = d.errorContext.Struct
+ err.Struct = d.errorContext.Struct.Name()
err.Field = d.errorContext.Field
return err
}
@@ -332,13 +331,12 @@ func (d *decodeState) skip() {
// scanNext processes the byte at d.data[d.off].
func (d *decodeState) scanNext() {
- s, data, i := &d.scan, d.data, d.off
- if i < len(data) {
- d.opcode = s.step(s, data[i])
- d.off = i + 1
+ if d.off < len(d.data) {
+ d.opcode = d.scan.step(&d.scan, d.data[d.off])
+ d.off++
} else {
- d.opcode = s.eof()
- d.off = len(data) + 1 // mark processed EOF with len+1
+ d.opcode = d.scan.eof()
+ d.off = len(d.data) + 1 // mark processed EOF with len+1
}
}
@@ -346,7 +344,7 @@ func (d *decodeState) scanNext() {
// receives a scan code not equal to op.
func (d *decodeState) scanWhile(op int) {
s, data, i := &d.scan, d.data, d.off
- for i < len(d.data) {
+ for i < len(data) {
newOp := s.step(s, data[i])
i++
if newOp != op {
@@ -356,7 +354,7 @@ func (d *decodeState) scanWhile(op int) {
}
}
- d.off = len(d.data) + 1 // mark processed EOF with len+1
+ d.off = len(data) + 1 // mark processed EOF with len+1
d.opcode = d.scan.eof()
}
@@ -366,7 +364,7 @@ func (d *decodeState) scanWhile(op int) {
func (d *decodeState) value(v reflect.Value) error {
switch d.opcode {
default:
- return errPhase
+ panic(phasePanicMsg)
case scanBeginArray:
if v.IsValid() {
@@ -408,30 +406,23 @@ type unquotedValue struct{}
// quoted string literal or literal null into an interface value.
// If it finds anything other than a quoted string literal or null,
// valueQuoted returns unquotedValue{}.
-func (d *decodeState) valueQuoted() (interface{}, error) {
+func (d *decodeState) valueQuoted() interface{} {
switch d.opcode {
default:
- return nil, errPhase
+ panic(phasePanicMsg)
- case scanBeginArray:
- d.skip()
- d.scanNext()
-
- case scanBeginObject:
+ case scanBeginArray, scanBeginObject:
d.skip()
d.scanNext()
case scanBeginLiteral:
- v, err := d.literalInterface()
- if err != nil {
- return nil, err
- }
+ v := d.literalInterface()
switch v.(type) {
case nil, string:
- return v, nil
+ return v
}
}
- return unquotedValue{}, nil
+ return unquotedValue{}
}
// indirect walks down v allocating pointers as needed,
@@ -482,7 +473,7 @@ func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnm
if v.IsNil() {
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 {
return u, nil, reflect.Value{}
}
@@ -525,10 +516,7 @@ func (d *decodeState) array(v reflect.Value) error {
case reflect.Interface:
if v.NumMethod() == 0 {
// Decoding into nil interface? Switch to non-reflect code.
- ai, err := d.arrayInterface()
- if err != nil {
- return err
- }
+ ai := d.arrayInterface()
v.Set(reflect.ValueOf(ai))
return nil
}
@@ -538,8 +526,7 @@ func (d *decodeState) array(v reflect.Value) error {
d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)})
d.skip()
return nil
- case reflect.Array:
- case reflect.Slice:
+ case reflect.Array, reflect.Slice:
break
}
@@ -589,7 +576,7 @@ func (d *decodeState) array(v reflect.Value) error {
break
}
if d.opcode != scanArrayValue {
- return errPhase
+ panic(phasePanicMsg)
}
}
@@ -611,7 +598,7 @@ func (d *decodeState) array(v reflect.Value) error {
}
var nullLiteral = []byte("null")
-var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
+var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
// object consumes an object from d.data[d.off-1:], decoding into v.
// The first byte ('{') of the object has been read already.
@@ -629,17 +616,17 @@ func (d *decodeState) object(v reflect.Value) error {
return nil
}
v = pv
+ t := v.Type()
// Decoding into nil interface? Switch to non-reflect code.
if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
- oi, err := d.objectInterface()
- if err != nil {
- return err
- }
+ oi := d.objectInterface()
v.Set(reflect.ValueOf(oi))
return nil
}
+ var fields []field
+
// Check type of target:
// struct or
// map[T1]T2 where T1 is string, an integer type,
@@ -648,14 +635,13 @@ func (d *decodeState) object(v reflect.Value) error {
case reflect.Map:
// Map key must either have string kind, have an integer kind,
// or be an encoding.TextUnmarshaler.
- t := v.Type()
switch t.Key().Kind() {
case reflect.String,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
default:
if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) {
- d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
+ d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)})
d.skip()
return nil
}
@@ -664,9 +650,10 @@ func (d *decodeState) object(v reflect.Value) error {
v.Set(reflect.MakeMap(t))
}
case reflect.Struct:
+ fields = cachedTypeFields(t)
// ok
default:
- d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
+ d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)})
d.skip()
return nil
}
@@ -682,7 +669,7 @@ func (d *decodeState) object(v reflect.Value) error {
break
}
if d.opcode != scanBeginLiteral {
- return errPhase
+ panic(phasePanicMsg)
}
// Read key.
@@ -691,7 +678,7 @@ func (d *decodeState) object(v reflect.Value) error {
item := d.data[start:d.readIndex()]
key, ok := unquoteBytes(item)
if !ok {
- return errPhase
+ panic(phasePanicMsg)
}
// Figure out field corresponding to key.
@@ -699,7 +686,7 @@ func (d *decodeState) object(v reflect.Value) error {
destring := false // whether the value is wrapped in a string to be decoded first
if v.Kind() == reflect.Map {
- elemType := v.Type().Elem()
+ elemType := t.Elem()
if !mapElem.IsValid() {
mapElem = reflect.New(elemType).Elem()
} else {
@@ -708,7 +695,6 @@ func (d *decodeState) object(v reflect.Value) error {
subv = mapElem
} else {
var f *field
- fields := cachedTypeFields(v.Type())
for i := range fields {
ff := &fields[i]
if bytes.Equal(ff.nameBytes, key) {
@@ -745,7 +731,7 @@ func (d *decodeState) object(v reflect.Value) error {
subv = subv.Field(i)
}
d.errorContext.Field = f.name
- d.errorContext.Struct = v.Type().Name()
+ d.errorContext.Struct = t
} else if d.disallowUnknownFields {
d.saveError(fmt.Errorf("json: unknown field %q", key))
}
@@ -756,16 +742,12 @@ func (d *decodeState) object(v reflect.Value) error {
d.scanWhile(scanSkipSpace)
}
if d.opcode != scanObjectKey {
- return errPhase
+ panic(phasePanicMsg)
}
d.scanWhile(scanSkipSpace)
if destring {
- q, err := d.valueQuoted()
- if err != nil {
- return err
- }
- switch qv := q.(type) {
+ switch qv := d.valueQuoted().(type) {
case nil:
if err := d.literalStore(nullLiteral, subv, false); err != nil {
return err
@@ -786,13 +768,13 @@ func (d *decodeState) object(v reflect.Value) error {
// Write value back to map;
// if using struct, subv points into struct already.
if v.Kind() == reflect.Map {
- kt := v.Type().Key()
+ kt := t.Key()
var kv reflect.Value
switch {
case kt.Kind() == reflect.String:
kv = reflect.ValueOf(key).Convert(kt)
case reflect.PtrTo(kt).Implements(textUnmarshalerType):
- kv = reflect.New(v.Type().Key())
+ kv = reflect.New(kt)
if err := d.literalStore(item, kv, true); err != nil {
return err
}
@@ -804,7 +786,7 @@ func (d *decodeState) object(v reflect.Value) error {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil || reflect.Zero(kt).OverflowInt(n) {
d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)})
- return nil
+ break
}
kv = reflect.ValueOf(n).Convert(kt)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -812,14 +794,16 @@ func (d *decodeState) object(v reflect.Value) error {
n, err := strconv.ParseUint(s, 10, 64)
if err != nil || reflect.Zero(kt).OverflowUint(n) {
d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)})
- return nil
+ break
}
kv = reflect.ValueOf(n).Convert(kt)
default:
panic("json: Unexpected key type") // should never occur
}
}
- v.SetMapIndex(kv, subv)
+ if kv.IsValid() {
+ v.SetMapIndex(kv, subv)
+ }
}
// Next token must be , or }.
@@ -830,7 +814,7 @@ func (d *decodeState) object(v reflect.Value) error {
break
}
if d.opcode != scanObjectValue {
- return errPhase
+ panic(phasePanicMsg)
}
d.errorContext = originalErrorContext
@@ -874,18 +858,16 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
if item[0] != '"' {
if fromQuoted {
d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- } else {
- var val string
- switch item[0] {
- case 'n':
- val = "null"
- case 't', 'f':
- val = "bool"
- default:
- val = "number"
- }
- d.saveError(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.readIndex())})
+ return nil
+ }
+ val := "number"
+ switch item[0] {
+ case 'n':
+ val = "null"
+ case 't', 'f':
+ val = "bool"
}
+ d.saveError(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.readIndex())})
return nil
}
s, ok := unquoteBytes(item)
@@ -893,7 +875,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
if fromQuoted {
return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())
}
- return errPhase
+ panic(phasePanicMsg)
}
return ut.UnmarshalText(s)
}
@@ -944,7 +926,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
if fromQuoted {
return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())
}
- return errPhase
+ panic(phasePanicMsg)
}
switch v.Kind() {
default:
@@ -976,7 +958,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
if fromQuoted {
return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())
}
- return errPhase
+ panic(phasePanicMsg)
}
s := string(item)
switch v.Kind() {
@@ -1037,24 +1019,24 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
// but they avoid the weight of reflection in this common case.
// valueInterface is like value but returns interface{}
-func (d *decodeState) valueInterface() (val interface{}, err error) {
+func (d *decodeState) valueInterface() (val interface{}) {
switch d.opcode {
default:
- err = errPhase
+ panic(phasePanicMsg)
case scanBeginArray:
- val, err = d.arrayInterface()
+ val = d.arrayInterface()
d.scanNext()
case scanBeginObject:
- val, err = d.objectInterface()
+ val = d.objectInterface()
d.scanNext()
case scanBeginLiteral:
- val, err = d.literalInterface()
+ val = d.literalInterface()
}
return
}
// arrayInterface is like array but returns []interface{}.
-func (d *decodeState) arrayInterface() ([]interface{}, error) {
+func (d *decodeState) arrayInterface() []interface{} {
var v = make([]interface{}, 0)
for {
// Look ahead for ] - can only happen on first iteration.
@@ -1063,11 +1045,7 @@ func (d *decodeState) arrayInterface() ([]interface{}, error) {
break
}
- vi, err := d.valueInterface()
- if err != nil {
- return nil, err
- }
- v = append(v, vi)
+ v = append(v, d.valueInterface())
// Next token must be , or ].
if d.opcode == scanSkipSpace {
@@ -1077,14 +1055,14 @@ func (d *decodeState) arrayInterface() ([]interface{}, error) {
break
}
if d.opcode != scanArrayValue {
- return nil, errPhase
+ panic(phasePanicMsg)
}
}
- return v, nil
+ return v
}
// objectInterface is like object but returns map[string]interface{}.
-func (d *decodeState) objectInterface() (map[string]interface{}, error) {
+func (d *decodeState) objectInterface() map[string]interface{} {
m := make(map[string]interface{})
for {
// Read opening " of string key or closing }.
@@ -1094,7 +1072,7 @@ func (d *decodeState) objectInterface() (map[string]interface{}, error) {
break
}
if d.opcode != scanBeginLiteral {
- return nil, errPhase
+ panic(phasePanicMsg)
}
// Read string key.
@@ -1103,7 +1081,7 @@ func (d *decodeState) objectInterface() (map[string]interface{}, error) {
item := d.data[start:d.readIndex()]
key, ok := unquote(item)
if !ok {
- return nil, errPhase
+ panic(phasePanicMsg)
}
// Read : before value.
@@ -1111,16 +1089,12 @@ func (d *decodeState) objectInterface() (map[string]interface{}, error) {
d.scanWhile(scanSkipSpace)
}
if d.opcode != scanObjectKey {
- return nil, errPhase
+ panic(phasePanicMsg)
}
d.scanWhile(scanSkipSpace)
// Read value.
- vi, err := d.valueInterface()
- if err != nil {
- return nil, err
- }
- m[key] = vi
+ m[key] = d.valueInterface()
// Next token must be , or }.
if d.opcode == scanSkipSpace {
@@ -1130,16 +1104,16 @@ func (d *decodeState) objectInterface() (map[string]interface{}, error) {
break
}
if d.opcode != scanObjectValue {
- return nil, errPhase
+ panic(phasePanicMsg)
}
}
- return m, nil
+ return m
}
// literalInterface consumes and returns a literal from d.data[d.off-1:] and
// it reads the following byte ahead. The first byte of the literal has been
// read already (that's how the caller knows it's a literal).
-func (d *decodeState) literalInterface() (interface{}, error) {
+func (d *decodeState) literalInterface() interface{} {
// All bytes inside literal return scanContinue op code.
start := d.readIndex()
d.scanWhile(scanContinue)
@@ -1148,27 +1122,27 @@ func (d *decodeState) literalInterface() (interface{}, error) {
switch c := item[0]; c {
case 'n': // null
- return nil, nil
+ return nil
case 't', 'f': // true, false
- return c == 't', nil
+ return c == 't'
case '"': // string
s, ok := unquote(item)
if !ok {
- return nil, errPhase
+ panic(phasePanicMsg)
}
- return s, nil
+ return s
default: // number
if c != '-' && (c < '0' || c > '9') {
- return nil, errPhase
+ panic(phasePanicMsg)
}
n, err := d.convertNumber(string(item))
if err != nil {
d.saveError(err)
}
- return n, nil
+ return n
}
}
diff --git a/libgo/go/encoding/json/decode_test.go b/libgo/go/encoding/json/decode_test.go
index 5746ddf..5443260 100644
--- a/libgo/go/encoding/json/decode_test.go
+++ b/libgo/go/encoding/json/decode_test.go
@@ -41,6 +41,16 @@ type VOuter struct {
V V
}
+type W struct {
+ S SS
+}
+
+type SS string
+
+func (*SS) UnmarshalJSON(data []byte) error {
+ return &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS(""))}
+}
+
// ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and
// without UseNumber
var ifaceNumAsFloat64 = map[string]interface{}{
@@ -142,7 +152,7 @@ var (
umstructXY = ustructText{unmarshalerText{"x", "y"}}
ummapType = map[unmarshalerText]bool{}
- ummapXY = map[unmarshalerText]bool{unmarshalerText{"x", "y"}: true}
+ ummapXY = map[unmarshalerText]bool{{"x", "y"}: true}
)
// Test data structures for anonymous fields.
@@ -256,6 +266,10 @@ type XYZ struct {
Z interface{}
}
+type unexportedWithMethods struct{}
+
+func (unexportedWithMethods) F() {}
+
func sliceAddr(x []int) *[]int { return &x }
func mapAddr(x map[string]int) *map[string]int { return &x }
@@ -408,6 +422,7 @@ var unmarshalTests = []unmarshalTest{
{in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(""), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}},
{in: `{"x": 1}`, ptr: new(tx), out: tx{}},
{in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true},
+ {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(SS("")), 0, "W", "S"}},
{in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}},
{in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true},
{in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64},
@@ -434,6 +449,7 @@ var unmarshalTests = []unmarshalTest{
{in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}},
{in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}},
{in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true},
+ {in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}},
// raw value errors
{in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}},
@@ -449,6 +465,7 @@ var unmarshalTests = []unmarshalTest{
{in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}},
{in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}},
{in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}},
+ {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")},
// empty array to interface test
{in: `[]`, ptr: new([]interface{}), out: []interface{}{}},
@@ -541,6 +558,16 @@ var unmarshalTests = []unmarshalTest{
ptr: new(map[uint8]string),
err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2},
},
+ {
+ in: `{"F":{"a":2,"3":4}}`,
+ ptr: new(map[string]map[int]int),
+ err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(int(0)), Offset: 7},
+ },
+ {
+ in: `{"F":{"a":2,"3":4}}`,
+ ptr: new(map[string]map[uint]int),
+ err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(uint(0)), Offset: 7},
+ },
// Map keys can be encoding.TextUnmarshalers.
{in: `{"x:y":true}`, ptr: &ummapType, out: ummapXY},
@@ -815,6 +842,7 @@ var unmarshalTests = []unmarshalTest{
{in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)},
{in: `{"B": "null"}`, ptr: new(B), out: B{false}},
{in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)},
+ {in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)},
// additional tests for disallowUnknownFields
{
@@ -883,6 +911,18 @@ var unmarshalTests = []unmarshalTest{
ptr: new(mapStringToStringData),
err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 21, Struct: "mapStringToStringData", Field: "data"},
},
+
+ // trying to decode JSON arrays or objects via TextUnmarshaler
+ {
+ in: `[1, 2, 3]`,
+ ptr: new(MustNotUnmarshalText),
+ err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1},
+ },
+ {
+ in: `{"foo": "bar"}`,
+ ptr: new(MustNotUnmarshalText),
+ err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1},
+ },
}
func TestMarshal(t *testing.T) {
@@ -1944,10 +1984,12 @@ type unexportedFields struct {
Name string
m map[string]interface{} `json:"-"`
m2 map[string]interface{} `json:"abcd"`
+
+ s []int `json:"-"`
}
func TestUnmarshalUnexported(t *testing.T) {
- input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}}`
+ input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}`
want := &unexportedFields{Name: "Bob"}
out := &unexportedFields{}
@@ -2113,6 +2155,9 @@ func TestInvalidStringOption(t *testing.T) {
//
// (Issue 24152) If the embedded struct is given an explicit name,
// 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) {
type (
embed1 struct{ Q int }
@@ -2152,6 +2197,9 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) {
embed2 `json:"embed2"`
Q int
}
+ S9 struct {
+ unexportedWithMethods `json:"embed"`
+ }
)
tests := []struct {
@@ -2213,6 +2261,11 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) {
in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`,
ptr: new(S8),
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 {
diff --git a/libgo/go/encoding/json/encode.go b/libgo/go/encoding/json/encode.go
index 28ca5fe..f10124e 100644
--- a/libgo/go/encoding/json/encode.go
+++ b/libgo/go/encoding/json/encode.go
@@ -381,8 +381,8 @@ func typeEncoder(t reflect.Type) encoderFunc {
}
var (
- marshalerType = reflect.TypeOf(new(Marshaler)).Elem()
- textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
+ marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
+ textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
)
// newTypeEncoder constructs an encoderFunc for a type.
@@ -624,40 +624,49 @@ func unsupportedTypeEncoder(e *encodeState, v reflect.Value, _ encOpts) {
}
type structEncoder struct {
- fields []field
- fieldEncs []encoderFunc
+ fields []field
}
-func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
- e.WriteByte('{')
- first := true
- for i, f := range se.fields {
- fv := fieldByIndex(v, f.index)
- if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) {
+func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
+ next := byte('{')
+FieldLoop:
+ for i := range se.fields {
+ f := &se.fields[i]
+
+ // Find the nested struct field by following f.index.
+ fv := v
+ for _, i := range f.index {
+ if fv.Kind() == reflect.Ptr {
+ if fv.IsNil() {
+ continue FieldLoop
+ }
+ fv = fv.Elem()
+ }
+ fv = fv.Field(i)
+ }
+
+ if f.omitEmpty && isEmptyValue(fv) {
continue
}
- if first {
- first = false
+ e.WriteByte(next)
+ next = ','
+ if opts.escapeHTML {
+ e.WriteString(f.nameEscHTML)
} else {
- e.WriteByte(',')
+ e.WriteString(f.nameNonEsc)
}
- e.string(f.name, opts.escapeHTML)
- e.WriteByte(':')
opts.quoted = f.quoted
- se.fieldEncs[i](e, fv, opts)
+ f.encoder(e, fv, opts)
+ }
+ if next == '{' {
+ e.WriteString("{}")
+ } else {
+ e.WriteByte('}')
}
- e.WriteByte('}')
}
func newStructEncoder(t reflect.Type) encoderFunc {
- fields := cachedTypeFields(t)
- se := &structEncoder{
- fields: fields,
- fieldEncs: make([]encoderFunc, len(fields)),
- }
- for i, f := range fields {
- se.fieldEncs[i] = typeEncoder(typeByIndex(t, f.index))
- }
+ se := structEncoder{fields: cachedTypeFields(t)}
return se.encode
}
@@ -665,7 +674,7 @@ type mapEncoder struct {
elemEnc encoderFunc
}
-func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
+func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if v.IsNil() {
e.WriteString("null")
return
@@ -704,7 +713,7 @@ func newMapEncoder(t reflect.Type) encoderFunc {
return unsupportedTypeEncoder
}
}
- me := &mapEncoder{typeEncoder(t.Elem())}
+ me := mapEncoder{typeEncoder(t.Elem())}
return me.encode
}
@@ -715,14 +724,22 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) {
}
s := v.Bytes()
e.WriteByte('"')
- if len(s) < 1024 {
- // for small buffers, using Encode directly is much faster.
- dst := make([]byte, base64.StdEncoding.EncodedLen(len(s)))
+ encodedLen := base64.StdEncoding.EncodedLen(len(s))
+ if encodedLen <= len(e.scratch) {
+ // If the encoded bytes fit in e.scratch, avoid an extra
+ // allocation and use the cheaper Encoding.Encode.
+ dst := e.scratch[:encodedLen]
+ base64.StdEncoding.Encode(dst, s)
+ e.Write(dst)
+ } else if encodedLen <= 1024 {
+ // The encoded bytes are short enough to allocate for, and
+ // Encoding.Encode is still cheaper.
+ dst := make([]byte, encodedLen)
base64.StdEncoding.Encode(dst, s)
e.Write(dst)
} else {
- // for large buffers, avoid unnecessary extra temporary
- // buffer space.
+ // The encoded bytes are too long to cheaply allocate, and
+ // Encoding.Encode is no longer noticeably cheaper.
enc := base64.NewEncoder(base64.StdEncoding, e)
enc.Write(s)
enc.Close()
@@ -735,7 +752,7 @@ type sliceEncoder struct {
arrayEnc encoderFunc
}
-func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
+func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if v.IsNil() {
e.WriteString("null")
return
@@ -751,7 +768,7 @@ func newSliceEncoder(t reflect.Type) encoderFunc {
return encodeByteSlice
}
}
- enc := &sliceEncoder{newArrayEncoder(t)}
+ enc := sliceEncoder{newArrayEncoder(t)}
return enc.encode
}
@@ -759,7 +776,7 @@ type arrayEncoder struct {
elemEnc encoderFunc
}
-func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
+func (ae arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
e.WriteByte('[')
n := v.Len()
for i := 0; i < n; i++ {
@@ -772,7 +789,7 @@ func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
}
func newArrayEncoder(t reflect.Type) encoderFunc {
- enc := &arrayEncoder{typeEncoder(t.Elem())}
+ enc := arrayEncoder{typeEncoder(t.Elem())}
return enc.encode
}
@@ -780,7 +797,7 @@ type ptrEncoder struct {
elemEnc encoderFunc
}
-func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
+func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if v.IsNil() {
e.WriteString("null")
return
@@ -789,7 +806,7 @@ func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
}
func newPtrEncoder(t reflect.Type) encoderFunc {
- enc := &ptrEncoder{typeEncoder(t.Elem())}
+ enc := ptrEncoder{typeEncoder(t.Elem())}
return enc.encode
}
@@ -797,7 +814,7 @@ type condAddrEncoder struct {
canAddrEnc, elseEnc encoderFunc
}
-func (ce *condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
+func (ce condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if v.CanAddr() {
ce.canAddrEnc(e, v, opts)
} else {
@@ -808,7 +825,7 @@ func (ce *condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts)
// newCondAddrEncoder returns an encoder that checks whether its value
// CanAddr and delegates to canAddrEnc if so, else to elseEnc.
func newCondAddrEncoder(canAddrEnc, elseEnc encoderFunc) encoderFunc {
- enc := &condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc}
+ enc := condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc}
return enc.encode
}
@@ -822,28 +839,13 @@ func isValidTag(s string) bool {
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
- default:
- if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
- return false
- }
+ case !unicode.IsLetter(c) && !unicode.IsDigit(c):
+ return false
}
}
return true
}
-func fieldByIndex(v reflect.Value, index []int) reflect.Value {
- for _, i := range index {
- if v.Kind() == reflect.Ptr {
- if v.IsNil() {
- return reflect.Value{}
- }
- v = v.Elem()
- }
- v = v.Field(i)
- }
- return v
-}
-
func typeByIndex(t reflect.Type, index []int) reflect.Type {
for _, i := range index {
if t.Kind() == reflect.Ptr {
@@ -893,18 +895,15 @@ func (e *encodeState) string(s string, escapeHTML bool) {
if start < i {
e.WriteString(s[start:i])
}
+ e.WriteByte('\\')
switch b {
case '\\', '"':
- e.WriteByte('\\')
e.WriteByte(b)
case '\n':
- e.WriteByte('\\')
e.WriteByte('n')
case '\r':
- e.WriteByte('\\')
e.WriteByte('r')
case '\t':
- e.WriteByte('\\')
e.WriteByte('t')
default:
// This encodes bytes < 0x20 except for \t, \n and \r.
@@ -912,7 +911,7 @@ func (e *encodeState) string(s string, escapeHTML bool) {
// because they can lead to security holes when
// user-controlled strings are rendered into JSON
// and served to some browsers.
- e.WriteString(`\u00`)
+ e.WriteString(`u00`)
e.WriteByte(hex[b>>4])
e.WriteByte(hex[b&0xF])
}
@@ -968,18 +967,15 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) {
if start < i {
e.Write(s[start:i])
}
+ e.WriteByte('\\')
switch b {
case '\\', '"':
- e.WriteByte('\\')
e.WriteByte(b)
case '\n':
- e.WriteByte('\\')
e.WriteByte('n')
case '\r':
- e.WriteByte('\\')
e.WriteByte('r')
case '\t':
- e.WriteByte('\\')
e.WriteByte('t')
default:
// This encodes bytes < 0x20 except for \t, \n and \r.
@@ -987,7 +983,7 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) {
// because they can lead to security holes when
// user-controlled strings are rendered into JSON
// and served to some browsers.
- e.WriteString(`\u00`)
+ e.WriteString(`u00`)
e.WriteByte(hex[b>>4])
e.WriteByte(hex[b&0xF])
}
@@ -1036,17 +1032,16 @@ type field struct {
nameBytes []byte // []byte(name)
equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
+ nameNonEsc string // `"` + name + `":`
+ nameEscHTML string // `"` + HTMLEscape(name) + `":`
+
tag bool
index []int
typ reflect.Type
omitEmpty bool
quoted bool
-}
-func fillField(f field) field {
- f.nameBytes = []byte(f.name)
- f.equalFold = foldFunc(f.nameBytes)
- return f
+ encoder encoderFunc
}
// byIndex sorts field by index sequence.
@@ -1086,6 +1081,9 @@ func typeFields(t reflect.Type) []field {
// Fields found.
var fields []field
+ // Buffer to run HTMLEscape on field names.
+ var nameEscBuf bytes.Buffer
+
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
@@ -1152,14 +1150,26 @@ func typeFields(t reflect.Type) []field {
if name == "" {
name = sf.Name
}
- fields = append(fields, fillField(field{
+ field := field{
name: name,
tag: tagged,
index: index,
typ: ft,
omitEmpty: opts.Contains("omitempty"),
quoted: quoted,
- }))
+ }
+ field.nameBytes = []byte(field.name)
+ field.equalFold = foldFunc(field.nameBytes)
+
+ // Build nameEscHTML and nameNonEsc ahead of time.
+ nameEscBuf.Reset()
+ nameEscBuf.WriteString(`"`)
+ HTMLEscape(&nameEscBuf, field.nameBytes)
+ nameEscBuf.WriteString(`":`)
+ field.nameEscHTML = nameEscBuf.String()
+ field.nameNonEsc = `"` + field.name + `":`
+
+ fields = append(fields, field)
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
@@ -1173,7 +1183,7 @@ func typeFields(t reflect.Type) []field {
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
- next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
+ next = append(next, field{name: ft.Name(), index: index, typ: ft})
}
}
}
@@ -1227,6 +1237,10 @@ func typeFields(t reflect.Type) []field {
fields = out
sort.Sort(byIndex(fields))
+ for i := range fields {
+ f := &fields[i]
+ f.encoder = typeEncoder(typeByIndex(t, f.index))
+ }
return fields
}
diff --git a/libgo/go/encoding/json/encode_test.go b/libgo/go/encoding/json/encode_test.go
index b90483c..cd5eadf 100644
--- a/libgo/go/encoding/json/encode_test.go
+++ b/libgo/go/encoding/json/encode_test.go
@@ -405,6 +405,19 @@ func TestAnonymousFields(t *testing.T) {
return S{s1{1, 2, s2{3, 4}}, 6}
},
want: `{"MyInt1":1,"MyInt2":3}`,
+ }, {
+ // If an anonymous struct pointer field is nil, we should ignore
+ // the embedded fields behind it. Not properly doing so may
+ // result in the wrong output or reflect panics.
+ label: "EmbeddedFieldBehindNilPointer",
+ makeInput: func() interface{} {
+ type (
+ S2 struct{ Field string }
+ S struct{ *S2 }
+ )
+ return S{}
+ },
+ want: `{}`,
}}
for _, tt := range tests {
@@ -995,3 +1008,18 @@ func TestMarshalPanic(t *testing.T) {
Marshal(&marshalPanic{})
t.Error("Marshal should have panicked")
}
+
+func TestMarshalUncommonFieldNames(t *testing.T) {
+ v := struct {
+ A0, À, Aβ int
+ }{}
+ b, err := Marshal(v)
+ if err != nil {
+ t.Fatal("Marshal:", err)
+ }
+ want := `{"A0":0,"À":0,"Aβ":0}`
+ got := string(b)
+ if got != want {
+ t.Fatalf("Marshal: got %s want %s", got, want)
+ }
+}
diff --git a/libgo/go/encoding/json/example_text_marshaling_test.go b/libgo/go/encoding/json/example_text_marshaling_test.go
new file mode 100644
index 0000000..a8a3f83
--- /dev/null
+++ b/libgo/go/encoding/json/example_text_marshaling_test.go
@@ -0,0 +1,69 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package json_test
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "strings"
+)
+
+type Size int
+
+const (
+ Unrecognized Size = iota
+ Small
+ Large
+)
+
+func (s *Size) UnmarshalText(text []byte) error {
+ switch strings.ToLower(string(text)) {
+ default:
+ *s = Unrecognized
+ case "small":
+ *s = Small
+ case "large":
+ *s = Large
+ }
+ return nil
+}
+
+func (s Size) MarshalText() ([]byte, error) {
+ var name string
+ switch s {
+ default:
+ name = "unrecognized"
+ case Small:
+ name = "small"
+ case Large:
+ name = "large"
+ }
+ return []byte(name), nil
+}
+
+func Example_textMarshalJSON() {
+ blob := `["small","regular","large","unrecognized","small","normal","small","large"]`
+ var inventory []Size
+ if err := json.Unmarshal([]byte(blob), &inventory); err != nil {
+ log.Fatal(err)
+ }
+
+ counts := make(map[Size]int)
+ for _, size := range inventory {
+ counts[size] += 1
+ }
+
+ fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n",
+ counts[Small], counts[Large], counts[Unrecognized])
+
+ // Output:
+ // Inventory Counts:
+ // * Small: 3
+ // * Large: 2
+ // * Unrecognized: 3
+}
diff --git a/libgo/go/encoding/json/scanner.go b/libgo/go/encoding/json/scanner.go
index 9e6d482..8857224 100644
--- a/libgo/go/encoding/json/scanner.go
+++ b/libgo/go/encoding/json/scanner.go
@@ -289,7 +289,7 @@ func stateEndValue(s *scanner, c byte) int {
// such as after reading `{}` or `[1,2,3]`.
// Only space characters should be seen now.
func stateEndTop(s *scanner, c byte) int {
- if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
+ if !isSpace(c) {
// Complain about non-space byte on next call.
s.error(c, "after top-level value")
}
diff --git a/libgo/go/encoding/json/stream.go b/libgo/go/encoding/json/stream.go
index 75a4270..7d5137f 100644
--- a/libgo/go/encoding/json/stream.go
+++ b/libgo/go/encoding/json/stream.go
@@ -96,19 +96,19 @@ Input:
// Look in the buffer for a new value.
for i, c := range dec.buf[scanp:] {
dec.scan.bytes++
- v := dec.scan.step(&dec.scan, c)
- if v == scanEnd {
+ switch dec.scan.step(&dec.scan, c) {
+ case scanEnd:
scanp += i
break Input
- }
- // scanEnd is delayed one byte.
- // We might block trying to get that byte from src,
- // so instead invent a space byte.
- if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd {
- scanp += i + 1
- break Input
- }
- if v == scanError {
+ case scanEndObject, scanEndArray:
+ // scanEnd is delayed one byte.
+ // We might block trying to get that byte from src,
+ // so instead invent a space byte.
+ if stateEndValue(&dec.scan, ' ') == scanEnd {
+ scanp += i + 1
+ break Input
+ }
+ case scanError:
dec.err = dec.scan.err
return 0, dec.scan.err
}
@@ -471,7 +471,7 @@ func (dec *Decoder) tokenError(c byte) (Token, error) {
case tokenObjectComma:
context = " after object key:value pair"
}
- return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, dec.offset()}
+ return nil, &SyntaxError{"invalid character " + quoteChar(c) + context, dec.offset()}
}
// More reports whether there is another element in the
diff --git a/libgo/go/encoding/json/stream_test.go b/libgo/go/encoding/json/stream_test.go
index 83c01d1..aaf32e0 100644
--- a/libgo/go/encoding/json/stream_test.go
+++ b/libgo/go/encoding/json/stream_test.go
@@ -93,6 +93,10 @@ func TestEncoderIndent(t *testing.T) {
func TestEncoderSetEscapeHTML(t *testing.T) {
var c C
var ct CText
+ var tagStruct struct {
+ Valid int `json:"<>&#! "`
+ Invalid int `json:"\\"`
+ }
for _, tt := range []struct {
name string
v interface{}
@@ -102,6 +106,11 @@ func TestEncoderSetEscapeHTML(t *testing.T) {
{"c", c, `"\u003c\u0026\u003e"`, `"<&>"`},
{"ct", ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
{`"<&>"`, "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
+ {
+ "tagStruct", tagStruct,
+ `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`,
+ `{"<>&#! ":0,"Invalid":0}`,
+ },
} {
var buf bytes.Buffer
enc := NewEncoder(&buf)
@@ -192,10 +201,9 @@ func nlines(s string, n int) string {
}
func TestRawMessage(t *testing.T) {
- // TODO(rsc): Should not need the * in *RawMessage
var data struct {
X float64
- Id *RawMessage
+ Id RawMessage
Y float32
}
const raw = `["\u0056",null]`
@@ -204,8 +212,8 @@ func TestRawMessage(t *testing.T) {
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
- if string([]byte(*data.Id)) != raw {
- t.Fatalf("Raw mismatch: have %#q want %#q", []byte(*data.Id), raw)
+ if string([]byte(data.Id)) != raw {
+ t.Fatalf("Raw mismatch: have %#q want %#q", []byte(data.Id), raw)
}
b, err := Marshal(&data)
if err != nil {
@@ -217,20 +225,22 @@ func TestRawMessage(t *testing.T) {
}
func TestNullRawMessage(t *testing.T) {
- // TODO(rsc): Should not need the * in *RawMessage
var data struct {
- X float64
- Id *RawMessage
- Y float32
+ X float64
+ Id RawMessage
+ IdPtr *RawMessage
+ Y float32
}
- data.Id = new(RawMessage)
- const msg = `{"X":0.1,"Id":null,"Y":0.2}`
+ const msg = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}`
err := Unmarshal([]byte(msg), &data)
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
- if data.Id != nil {
- t.Fatalf("Raw mismatch: have non-nil, want nil")
+ if want, got := "null", string(data.Id); want != got {
+ t.Fatalf("Raw mismatch: have %q, want %q", got, want)
+ }
+ if data.IdPtr != nil {
+ t.Fatalf("Raw pointer mismatch: have non-nil, want nil")
}
b, err := Marshal(&data)
if err != nil {
diff --git a/libgo/go/encoding/pem/pem_test.go b/libgo/go/encoding/pem/pem_test.go
index 6a17516..204611b 100644
--- a/libgo/go/encoding/pem/pem_test.go
+++ b/libgo/go/encoding/pem/pem_test.go
@@ -26,6 +26,10 @@ var getLineTests = []GetLineTest{
{"abc\r\nd", "abc", "d"},
{"\nabc", "", "abc"},
{"\r\nabc", "", "abc"},
+ {"abc\t \nd", "abc", "d"},
+ {"\t abc\nd", "\t abc", "d"},
+ {"abc\n\t d", "abc", "\t d"},
+ {"abc\nd\t ", "abc", "d\t "},
}
func TestGetLine(t *testing.T) {
@@ -213,7 +217,9 @@ func TestFuzz(t *testing.T) {
}
testRoundtrip := func(block Block) bool {
- if isBad(block.Type) {
+ // Reject bad Type
+ // Type with colons will proceed as key/val pair and cause an error.
+ if isBad(block.Type) || strings.Contains(block.Type, ":") {
return true
}
for key, val := range block.Headers {
diff --git a/libgo/go/encoding/xml/example_marshaling_test.go b/libgo/go/encoding/xml/example_marshaling_test.go
new file mode 100644
index 0000000..04d3470
--- /dev/null
+++ b/libgo/go/encoding/xml/example_marshaling_test.go
@@ -0,0 +1,86 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package xml_test
+
+import (
+ "encoding/xml"
+ "fmt"
+ "log"
+ "strings"
+)
+
+type Animal int
+
+const (
+ Unknown Animal = iota
+ Gopher
+ Zebra
+)
+
+func (a *Animal) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ var s string
+ if err := d.DecodeElement(&s, &start); err != nil {
+ return err
+ }
+ switch strings.ToLower(s) {
+ default:
+ *a = Unknown
+ case "gopher":
+ *a = Gopher
+ case "zebra":
+ *a = Zebra
+ }
+
+ return nil
+}
+
+func (a Animal) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ var s string
+ switch a {
+ default:
+ s = "unknown"
+ case Gopher:
+ s = "gopher"
+ case Zebra:
+ s = "zebra"
+ }
+ return e.EncodeElement(s, start)
+}
+
+func Example_customMarshalXML() {
+ blob := `
+ <animals>
+ <animal>gopher</animal>
+ <animal>armadillo</animal>
+ <animal>zebra</animal>
+ <animal>unknown</animal>
+ <animal>gopher</animal>
+ <animal>bee</animal>
+ <animal>gopher</animal>
+ <animal>zebra</animal>
+ </animals>`
+ var zoo struct {
+ Animals []Animal `xml:"animal"`
+ }
+ if err := xml.Unmarshal([]byte(blob), &zoo); err != nil {
+ log.Fatal(err)
+ }
+
+ census := make(map[Animal]int)
+ for _, animal := range zoo.Animals {
+ census[animal] += 1
+ }
+
+ fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n",
+ census[Gopher], census[Zebra], census[Unknown])
+
+ // Output:
+ // Zoo Census:
+ // * Gophers: 3
+ // * Zebras: 2
+ // * Unknown: 3
+}
diff --git a/libgo/go/encoding/xml/example_text_marshaling_test.go b/libgo/go/encoding/xml/example_text_marshaling_test.go
new file mode 100644
index 0000000..8d1f332
--- /dev/null
+++ b/libgo/go/encoding/xml/example_text_marshaling_test.go
@@ -0,0 +1,81 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package xml_test
+
+import (
+ "encoding/xml"
+ "fmt"
+ "log"
+ "strings"
+)
+
+type Size int
+
+const (
+ Unrecognized Size = iota
+ Small
+ Large
+)
+
+func (s *Size) UnmarshalText(text []byte) error {
+ switch strings.ToLower(string(text)) {
+ default:
+ *s = Unrecognized
+ case "small":
+ *s = Small
+ case "large":
+ *s = Large
+ }
+ return nil
+}
+
+func (s Size) MarshalText() ([]byte, error) {
+ var name string
+ switch s {
+ default:
+ name = "unrecognized"
+ case Small:
+ name = "small"
+ case Large:
+ name = "large"
+ }
+ return []byte(name), nil
+}
+
+func Example_textMarshalXML() {
+ blob := `
+ <sizes>
+ <size>small</size>
+ <size>regular</size>
+ <size>large</size>
+ <size>unrecognized</size>
+ <size>small</size>
+ <size>normal</size>
+ <size>small</size>
+ <size>large</size>
+ </sizes>`
+ var inventory struct {
+ Sizes []Size `xml:"size"`
+ }
+ if err := xml.Unmarshal([]byte(blob), &inventory); err != nil {
+ log.Fatal(err)
+ }
+
+ counts := make(map[Size]int)
+ for _, size := range inventory.Sizes {
+ counts[size] += 1
+ }
+
+ fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n",
+ counts[Small], counts[Large], counts[Unrecognized])
+
+ // Output:
+ // Inventory Counts:
+ // * Small: 3
+ // * Large: 2
+ // * Unrecognized: 3
+}
diff --git a/libgo/go/encoding/xml/marshal.go b/libgo/go/encoding/xml/marshal.go
index d393d06..add5ece 100644
--- a/libgo/go/encoding/xml/marshal.go
+++ b/libgo/go/encoding/xml/marshal.go
@@ -61,6 +61,10 @@ const (
// string of length zero.
// - an anonymous struct field is handled as if the fields of its
// value were part of the outer struct.
+// - a field implementing Marshaler is written by calling its MarshalXML
+// method.
+// - a field implementing encoding.TextMarshaler is written by encoding the
+// result of its MarshalText method as text.
//
// If a field uses a tag "a>b>c", then the element c will be nested inside
// parent elements a and b. Fields that appear next to each other that name
diff --git a/libgo/go/encoding/xml/read.go b/libgo/go/encoding/xml/read.go
index 36c7ba6..12102bc 100644
--- a/libgo/go/encoding/xml/read.go
+++ b/libgo/go/encoding/xml/read.go
@@ -92,6 +92,11 @@ import (
//
// * A struct field with tag "-" is never unmarshaled into.
//
+// If Unmarshal encounters a field type that implements the Unmarshaler
+// interface, Unmarshal calls its UnmarshalXML method to produce the value from
+// the XML element. Otherwise, if the value implements
+// encoding.TextUnmarshaler, Unmarshal calls that value's UnmarshalText method.
+//
// Unmarshal maps an XML element to a string or []byte by saving the
// concatenation of that element's character data in the string or
// []byte. The saved []byte is never nil.