diff options
author | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2013-11-06 19:49:01 +0000 |
---|---|---|
committer | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2013-11-06 19:49:01 +0000 |
commit | 0ce10ea1348e9afd5d0eec6bca986bfe58bac5ac (patch) | |
tree | 39530b071991b2326f881b2a30a2d82d6c133fd6 /libgo/go/encoding/json | |
parent | 57a8bf1b0c6057ccbacb0cf79eb84d1985c2c1fe (diff) | |
download | gcc-0ce10ea1348e9afd5d0eec6bca986bfe58bac5ac.tar.gz |
libgo: Update to October 24 version of master library.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@204466 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libgo/go/encoding/json')
-rw-r--r-- | libgo/go/encoding/json/decode.go | 70 | ||||
-rw-r--r-- | libgo/go/encoding/json/decode_test.go | 167 | ||||
-rw-r--r-- | libgo/go/encoding/json/encode.go | 714 | ||||
-rw-r--r-- | libgo/go/encoding/json/encode_test.go | 123 | ||||
-rw-r--r-- | libgo/go/encoding/json/indent.go | 9 | ||||
-rw-r--r-- | libgo/go/encoding/json/scanner.go | 2 | ||||
-rw-r--r-- | libgo/go/encoding/json/scanner_test.go | 19 | ||||
-rw-r--r-- | libgo/go/encoding/json/stream.go | 11 | ||||
-rw-r--r-- | libgo/go/encoding/json/stream_test.go | 13 | ||||
-rw-r--r-- | libgo/go/encoding/json/tags.go | 2 |
10 files changed, 923 insertions, 207 deletions
diff --git a/libgo/go/encoding/json/decode.go b/libgo/go/encoding/json/decode.go index 62ac294b89f..458fb39ec01 100644 --- a/libgo/go/encoding/json/decode.go +++ b/libgo/go/encoding/json/decode.go @@ -8,6 +8,7 @@ package json import ( + "encoding" "encoding/base64" "errors" "fmt" @@ -37,9 +38,7 @@ import ( // keys to the keys used by Marshal (either the struct field name or its tag), // preferring an exact match but also accepting a case-insensitive match. // -// To unmarshal JSON into an interface value, Unmarshal unmarshals -// the JSON into the concrete value contained in the interface value. -// If the interface value is nil, that is, has no concrete value stored in it, +// To unmarshal JSON into an interface value, // Unmarshal stores one of these in the interface value: // // bool, for JSON booleans @@ -293,7 +292,7 @@ func (d *decodeState) value(v reflect.Value) { // until it gets to a non-pointer. // if it encounters an Unmarshaler, indirect stops and returns that. // if decodingNull is true, indirect stops at the last pointer so it can be set to nil. -func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, reflect.Value) { +func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { // If v is a named type and is addressable, // start with its address, so that if the type has pointer methods, // we find them. @@ -322,28 +321,38 @@ func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, v.Set(reflect.New(v.Type().Elem())) } if v.Type().NumMethod() > 0 { - if unmarshaler, ok := v.Interface().(Unmarshaler); ok { - return unmarshaler, reflect.Value{} + if u, ok := v.Interface().(Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} } } v = v.Elem() } - return nil, v + return nil, nil, v } // array consumes an array from d.data[d.off-1:], decoding into the value v. // the first byte of the array ('[') has been read already. func (d *decodeState) array(v reflect.Value) { // Check for unmarshaler. - unmarshaler, pv := d.indirect(v, false) - if unmarshaler != nil { + u, ut, pv := d.indirect(v, false) + if u != nil { d.off-- - err := unmarshaler.UnmarshalJSON(d.next()) + err := u.UnmarshalJSON(d.next()) if err != nil { d.error(err) } return } + if ut != nil { + d.saveError(&UnmarshalTypeError{"array", v.Type()}) + d.off-- + d.next() + return + } + v = pv // Check type of target. @@ -434,15 +443,21 @@ func (d *decodeState) array(v reflect.Value) { // the first byte of the object ('{') has been read already. func (d *decodeState) object(v reflect.Value) { // Check for unmarshaler. - unmarshaler, pv := d.indirect(v, false) - if unmarshaler != nil { + u, ut, pv := d.indirect(v, false) + if u != nil { d.off-- - err := unmarshaler.UnmarshalJSON(d.next()) + err := u.UnmarshalJSON(d.next()) if err != nil { d.error(err) } return } + if ut != nil { + d.saveError(&UnmarshalTypeError{"object", v.Type()}) + d.off-- + d.next() // skip over { } in input + return + } v = pv // Decoding into nil interface? Switch to non-reflect code. @@ -611,14 +626,37 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool return } wantptr := item[0] == 'n' // null - unmarshaler, pv := d.indirect(v, wantptr) - if unmarshaler != nil { - err := unmarshaler.UnmarshalJSON(item) + u, ut, pv := d.indirect(v, wantptr) + if u != nil { + err := u.UnmarshalJSON(item) + if err != nil { + d.error(err) + } + return + } + if ut != nil { + 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 { + d.saveError(&UnmarshalTypeError{"string", v.Type()}) + } + } + s, ok := unquoteBytes(item) + if !ok { + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } + } + err := ut.UnmarshalText(s) if err != nil { d.error(err) } return } + v = pv switch c := item[0]; c { diff --git a/libgo/go/encoding/json/decode_test.go b/libgo/go/encoding/json/decode_test.go index f845f69ab7f..22c5f89f798 100644 --- a/libgo/go/encoding/json/decode_test.go +++ b/libgo/go/encoding/json/decode_test.go @@ -6,6 +6,7 @@ package json import ( "bytes" + "encoding" "fmt" "image" "reflect" @@ -50,8 +51,6 @@ type tx struct { x int } -var txType = reflect.TypeOf((*tx)(nil)).Elem() - // A type that can unmarshal itself. type unmarshaler struct { @@ -59,7 +58,7 @@ type unmarshaler struct { } func (u *unmarshaler) UnmarshalJSON(b []byte) error { - *u = unmarshaler{true} // All we need to see that UnmarshalJson is called. + *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. return nil } @@ -67,6 +66,26 @@ type ustruct struct { M unmarshaler } +type unmarshalerText struct { + T bool +} + +// needed for re-marshaling tests +func (u *unmarshalerText) MarshalText() ([]byte, error) { + return []byte(""), nil +} + +func (u *unmarshalerText) UnmarshalText(b []byte) error { + *u = unmarshalerText{true} // All we need to see that UnmarshalText is called. + return nil +} + +var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) + +type ustructText struct { + M unmarshalerText +} + var ( um0, um1 unmarshaler // target2 of unmarshaling ump = &um1 @@ -74,6 +93,13 @@ var ( umslice = []unmarshaler{{true}} umslicep = new([]unmarshaler) umstruct = ustruct{unmarshaler{true}} + + um0T, um1T unmarshalerText // target2 of unmarshaling + umpT = &um1T + umtrueT = unmarshalerText{true} + umsliceT = []unmarshalerText{{true}} + umslicepT = new([]unmarshalerText) + umstructT = ustructText{unmarshalerText{true}} ) // Test data structures for anonymous fields. @@ -184,6 +210,12 @@ type Ambig struct { Second int `json:"Hello"` } +type XYZ struct { + X interface{} + Y interface{} + Z interface{} +} + var unmarshalTests = []unmarshalTest{ // basic types {in: `true`, ptr: new(bool), out: true}, @@ -263,6 +295,13 @@ var unmarshalTests = []unmarshalTest{ {in: `[{"T":false}]`, ptr: &umslicep, out: &umslice}, {in: `{"M":{"T":false}}`, ptr: &umstruct, out: umstruct}, + // UnmarshalText interface test + {in: `"X"`, ptr: &um0T, out: umtrueT}, // use "false" so test will fail if custom unmarshaler is not called + {in: `"X"`, ptr: &umpT, out: &umtrueT}, + {in: `["X"]`, ptr: &umsliceT, out: umsliceT}, + {in: `["X"]`, ptr: &umslicepT, out: &umsliceT}, + {in: `{"M":"X"}`, ptr: &umstructT, out: umstructT}, + { in: `{ "Level0": 1, @@ -391,17 +430,23 @@ func TestMarshal(t *testing.T) { } } +var badUTF8 = []struct { + in, out string +}{ + {"hello\xffworld", `"hello\ufffdworld"`}, + {"", `""`}, + {"\xff", `"\ufffd"`}, + {"\xff\xff", `"\ufffd\ufffd"`}, + {"a\xffb", `"a\ufffdb"`}, + {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, +} + func TestMarshalBadUTF8(t *testing.T) { - s := "hello\xffworld" - b, err := Marshal(s) - if err == nil { - t.Fatal("Marshal bad UTF8: no error") - } - if len(b) != 0 { - t.Fatal("Marshal returned data") - } - if _, ok := err.(*InvalidUTF8Error); !ok { - t.Fatalf("Marshal did not return InvalidUTF8Error: %T %v", err, err) + for _, tt := range badUTF8 { + b, err := Marshal(tt.in) + if string(b) != tt.out || err != nil { + t.Errorf("Marshal(%q) = %#q, %v, want %#q, nil", tt.in, b, err, tt.out) + } } } @@ -417,6 +462,45 @@ func TestMarshalNumberZeroVal(t *testing.T) { } } +func TestMarshalEmbeds(t *testing.T) { + top := &Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + } + b, err := Marshal(top) + if err != nil { + t.Fatal(err) + } + want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17}" + if string(b) != want { + t.Errorf("Wrong marshal result.\n got: %q\nwant: %q", b, want) + } +} + func TestUnmarshal(t *testing.T) { for i, tt := range unmarshalTests { var scan scanner @@ -432,7 +516,7 @@ func TestUnmarshal(t *testing.T) { } // v = new(right-type) v := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec := NewDecoder(bytes.NewBuffer(in)) + dec := NewDecoder(bytes.NewReader(in)) if tt.useNumber { dec.UseNumber() } @@ -457,16 +541,18 @@ func TestUnmarshal(t *testing.T) { continue } vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = NewDecoder(bytes.NewBuffer(enc)) + dec = NewDecoder(bytes.NewReader(enc)) if tt.useNumber { dec.UseNumber() } if err := dec.Decode(vv.Interface()); err != nil { - t.Errorf("#%d: error re-unmarshaling: %v", i, err) + t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err) continue } if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) + t.Errorf(" In: %q", strings.Map(noSpace, string(in))) + t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc))) continue } } @@ -568,14 +654,14 @@ func TestUnmarshalPtrPtr(t *testing.T) { } func TestEscape(t *testing.T) { - const input = `"foobar"<html>` - const expected = `"\"foobar\"\u003chtml\u003e"` + const input = `"foobar"<html>` + " [\u2028 \u2029]" + const expected = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` b, err := Marshal(input) if err != nil { t.Fatalf("Marshal error: %v", err) } if s := string(b); s != expected { - t.Errorf("Encoding of [%s] was [%s], want [%s]", input, s, expected) + t.Errorf("Encoding of [%s]:\n got [%s]\nwant [%s]", input, s, expected) } } @@ -934,15 +1020,20 @@ func TestRefUnmarshal(t *testing.T) { // Ref is defined in encode_test.go. R0 Ref R1 *Ref + R2 RefText + R3 *RefText } want := S{ R0: 12, R1: new(Ref), + R2: 13, + R3: new(RefText), } *want.R1 = 12 + *want.R3 = 13 var got S - if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref"}`), &got); err != nil { + if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { t.Fatalf("Unmarshal: %v", err) } if !reflect.DeepEqual(got, want) { @@ -1064,7 +1155,6 @@ func TestUnmarshalNulls(t *testing.T) { func TestStringKind(t *testing.T) { type stringKind string - type aMap map[stringKind]int var m1, m2 map[stringKind]int m1 = map[stringKind]int{ @@ -1191,3 +1281,38 @@ func TestSkipArrayObjects(t *testing.T) { t.Errorf("got error %q, want nil", err) } } + +// Test semantics of pre-filled struct fields and pre-filled map fields. +// Issue 4900. +func TestPrefilled(t *testing.T) { + ptrToMap := func(m map[string]interface{}) *map[string]interface{} { return &m } + + // Values here change, cannot reuse table across runs. + var prefillTests = []struct { + in string + ptr interface{} + out interface{} + }{ + { + in: `{"X": 1, "Y": 2}`, + ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, + out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, + }, + { + in: `{"X": 1, "Y": 2}`, + ptr: ptrToMap(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}), + }, + } + + for _, tt := range prefillTests { + ptrstr := fmt.Sprintf("%v", tt.ptr) + err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here + if err != nil { + t.Errorf("Unmarshal: %v", err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) + } + } +} diff --git a/libgo/go/encoding/json/encode.go b/libgo/go/encoding/json/encode.go index 85727ba61c0..7d6c71d7a90 100644 --- a/libgo/go/encoding/json/encode.go +++ b/libgo/go/encoding/json/encode.go @@ -12,6 +12,7 @@ package json import ( "bytes" + "encoding" "encoding/base64" "math" "reflect" @@ -149,14 +150,14 @@ func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { return buf.Bytes(), nil } -// HTMLEscape appends to dst the JSON-encoded src with <, >, and & -// characters inside string literals changed to \u003c, \u003e, \u0026 +// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 +// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 // so that the JSON will be safe to embed inside HTML <script> tags. // For historical reasons, web browsers don't honor standard HTML // escaping within <script> tags, so an alternative JSON encoding must // be used. func HTMLEscape(dst *bytes.Buffer, src []byte) { - // < > & can only appear in string literals, + // The characters can only appear in string literals, // so just scan the string one byte at a time. start := 0 for i, c := range src { @@ -169,6 +170,15 @@ func HTMLEscape(dst *bytes.Buffer, src []byte) { dst.WriteByte(hex[c&0xF]) start = i + 1 } + // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9). + if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 { + if start < i { + dst.Write(src[start:i]) + } + dst.WriteString(`\u202`) + dst.WriteByte(hex[src[i+2]&0xF]) + start = i + 3 + } } if start < len(src) { dst.Write(src[start:]) @@ -200,8 +210,12 @@ func (e *UnsupportedValueError) Error() string { return "json: unsupported value: " + e.Str } -// An InvalidUTF8Error is returned by Marshal when attempting -// to encode a string value with invalid UTF-8 sequences. +// Before Go 1.2, an InvalidUTF8Error was returned by Marshal when +// attempting to encode a string value with invalid UTF-8 sequences. +// As of Go 1.2, Marshal instead coerces the string to valid UTF-8 by +// replacing invalid bytes with the Unicode replacement rune U+FFFD. +// This error is no longer generated but is kept for backwards compatibility +// with programs that might mention it. type InvalidUTF8Error struct { S string // the whole string value that caused the error } @@ -227,12 +241,35 @@ type encodeState struct { scratch [64]byte } +// TODO(bradfitz): use a sync.Cache here +var encodeStatePool = make(chan *encodeState, 8) + +func newEncodeState() *encodeState { + select { + case e := <-encodeStatePool: + e.Reset() + return e + default: + return new(encodeState) + } +} + +func putEncodeState(e *encodeState) { + select { + case encodeStatePool <- e: + default: + } +} + func (e *encodeState) marshal(v interface{}) (err error) { defer func() { if r := recover(); r != nil { if _, ok := r.(runtime.Error); ok { panic(r) } + if s, ok := r.(string); ok { + panic(s) + } err = r.(error) } }() @@ -265,186 +302,438 @@ func isEmptyValue(v reflect.Value) bool { } func (e *encodeState) reflectValue(v reflect.Value) { - e.reflectValueQuoted(v, false) + valueEncoder(v)(e, v, false) +} + +type encoderFunc func(e *encodeState, v reflect.Value, quoted bool) + +var encoderCache struct { + sync.RWMutex + m map[reflect.Type]encoderFunc } -// reflectValueQuoted writes the value in v to the output. -// If quoted is true, the serialization is wrapped in a JSON string. -func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) { +func valueEncoder(v reflect.Value) encoderFunc { if !v.IsValid() { - e.WriteString("null") - return + return invalidValueEncoder } + return typeEncoder(v.Type()) +} - m, ok := v.Interface().(Marshaler) - if !ok { - // T doesn't match the interface. Check against *T too. - if v.Kind() != reflect.Ptr && v.CanAddr() { - m, ok = v.Addr().Interface().(Marshaler) - if ok { - v = v.Addr() - } - } +func typeEncoder(t reflect.Type) encoderFunc { + encoderCache.RLock() + f := encoderCache.m[t] + encoderCache.RUnlock() + if f != nil { + return f + } + + // To deal with recursive types, populate the map with an + // indirect func before we build it. This type waits on the + // real func (f) to be ready and then calls it. This indirect + // func is only used for recursive types. + encoderCache.Lock() + if encoderCache.m == nil { + encoderCache.m = make(map[reflect.Type]encoderFunc) + } + var wg sync.WaitGroup + wg.Add(1) + encoderCache.m[t] = func(e *encodeState, v reflect.Value, quoted bool) { + wg.Wait() + f(e, v, quoted) + } + encoderCache.Unlock() + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = newTypeEncoder(t, true) + wg.Done() + encoderCache.Lock() + encoderCache.m[t] = f + encoderCache.Unlock() + return f +} + +var ( + marshalerType = reflect.TypeOf(new(Marshaler)).Elem() + textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() +) + +// newTypeEncoder constructs an encoderFunc for a type. +// The returned encoder only checks CanAddr when allowAddr is true. +func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { + if t.Implements(marshalerType) { + return marshalerEncoder } - if ok && (v.Kind() != reflect.Ptr || !v.IsNil()) { - b, err := m.MarshalJSON() - if err == nil { - // copy JSON into buffer, checking validity. - err = compact(&e.Buffer, b, true) + if t.Kind() != reflect.Ptr && allowAddr { + if reflect.PtrTo(t).Implements(marshalerType) { + return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) } - if err != nil { - e.error(&MarshalerError{v.Type(), err}) + } + + if t.Implements(textMarshalerType) { + return textMarshalerEncoder + } + if t.Kind() != reflect.Ptr && allowAddr { + if reflect.PtrTo(t).Implements(textMarshalerType) { + return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false)) } + } + + switch t.Kind() { + case reflect.Bool: + return boolEncoder + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intEncoder + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintEncoder + case reflect.Float32: + return float32Encoder + case reflect.Float64: + return float64Encoder + case reflect.String: + return stringEncoder + case reflect.Interface: + return interfaceEncoder + case reflect.Struct: + return newStructEncoder(t) + case reflect.Map: + return newMapEncoder(t) + case reflect.Slice: + return newSliceEncoder(t) + case reflect.Array: + return newArrayEncoder(t) + case reflect.Ptr: + return newPtrEncoder(t) + default: + return unsupportedTypeEncoder + } +} + +func invalidValueEncoder(e *encodeState, v reflect.Value, quoted bool) { + e.WriteString("null") +} + +func marshalerEncoder(e *encodeState, v reflect.Value, quoted bool) { + if v.Kind() == reflect.Ptr && v.IsNil() { + e.WriteString("null") + return + } + m := v.Interface().(Marshaler) + b, err := m.MarshalJSON() + if err == nil { + // copy JSON into buffer, checking validity. + err = compact(&e.Buffer, b, true) + } + if err != nil { + e.error(&MarshalerError{v.Type(), err}) + } +} + +func addrMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) { + va := v.Addr() + if va.IsNil() { + e.WriteString("null") return } + m := va.Interface().(Marshaler) + b, err := m.MarshalJSON() + if err == nil { + // copy JSON into buffer, checking validity. + err = compact(&e.Buffer, b, true) + } + if err != nil { + e.error(&MarshalerError{v.Type(), err}) + } +} - writeString := (*encodeState).WriteString +func textMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) { + if v.Kind() == reflect.Ptr && v.IsNil() { + e.WriteString("null") + return + } + m := v.Interface().(encoding.TextMarshaler) + b, err := m.MarshalText() + if err == nil { + _, err = e.stringBytes(b) + } + if err != nil { + e.error(&MarshalerError{v.Type(), err}) + } +} + +func addrTextMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) { + va := v.Addr() + if va.IsNil() { + e.WriteString("null") + return + } + m := va.Interface().(encoding.TextMarshaler) + b, err := m.MarshalText() + if err == nil { + _, err = e.stringBytes(b) + } + if err != nil { + e.error(&MarshalerError{v.Type(), err}) + } +} + +func boolEncoder(e *encodeState, v reflect.Value, quoted bool) { + if quoted { + e.WriteByte('"') + } + if v.Bool() { + e.WriteString("true") + } else { + e.WriteString("false") + } + if quoted { + e.WriteByte('"') + } +} + +func intEncoder(e *encodeState, v reflect.Value, quoted bool) { + b := strconv.AppendInt(e.scratch[:0], v.Int(), 10) + if quoted { + e.WriteByte('"') + } + e.Write(b) if quoted { - writeString = (*encodeState).string + e.WriteByte('"') } +} - switch v.Kind() { - case reflect.Bool: - x := v.Bool() - if x { - writeString(e, "true") - } else { - writeString(e, "false") - } +func uintEncoder(e *encodeState, v reflect.Value, quoted bool) { + b := strconv.AppendUint(e.scratch[:0], v.Uint(), 10) + if quoted { + e.WriteByte('"') + } + e.Write(b) + if quoted { + e.WriteByte('"') + } +} - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - b := strconv.AppendInt(e.scratch[:0], v.Int(), 10) - if quoted { - writeString(e, string(b)) - } else { - e.Write(b) +type floatEncoder int // number of bits + +func (bits floatEncoder) encode(e *encodeState, v reflect.Value, quoted bool) { + f := v.Float() + if math.IsInf(f, 0) || math.IsNaN(f) { + e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))}) + } + b := strconv.AppendFloat(e.scratch[:0], f, 'g', -1, int(bits)) + if quoted { + e.WriteByte('"') + } + e.Write(b) + if quoted { + e.WriteByte('"') + } +} + +var ( + float32Encoder = (floatEncoder(32)).encode + float64Encoder = (floatEncoder(64)).encode +) + +func stringEncoder(e *encodeState, v reflect.Value, quoted bool) { + if v.Type() == numberType { + numStr := v.String() + if numStr == "" { + numStr = "0" // Number's zero-val } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - b := strconv.AppendUint(e.scratch[:0], v.Uint(), 10) - if quoted { - writeString(e, string(b)) - } else { - e.Write(b) + e.WriteString(numStr) + return + } + if quoted { + sb, err := Marshal(v.String()) + if err != nil { + e.error(err) } - case reflect.Float32, reflect.Float64: - f := v.Float() - if math.IsInf(f, 0) || math.IsNaN(f) { - e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, v.Type().Bits())}) + e.string(string(sb)) + } else { + e.string(v.String()) + } +} + +func interfaceEncoder(e *encodeState, v reflect.Value, quoted bool) { + if v.IsNil() { + e.WriteString("null") + return + } + e.reflectValue(v.Elem()) +} + +func unsupportedTypeEncoder(e *encodeState, v reflect.Value, quoted bool) { + e.error(&UnsupportedTypeError{v.Type()}) +} + +type structEncoder struct { + fields []field + fieldEncs []encoderFunc +} + +func (se *structEncoder) encode(e *encodeState, v reflect.Value, quoted bool) { + e.WriteByte('{') + first := true + for i, f := range se.fields { + fv := fieldByIndex(v, f.index) + if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) { + continue } - b := strconv.AppendFloat(e.scratch[:0], f, 'g', -1, v.Type().Bits()) - if quoted { - writeString(e, string(b)) + if first { + first = false } else { - e.Write(b) - } - case reflect.String: - if v.Type() == numberType { - numStr := v.String() - if numStr == "" { - numStr = "0" // Number's zero-val - } - e.WriteString(numStr) - break - } - if quoted { - sb, err := Marshal(v.String()) - if err != nil { - e.error(err) - } - e.string(string(sb)) - } else { - e.string(v.String()) + e.WriteByte(',') } + e.string(f.name) + e.WriteByte(':') + se.fieldEncs[i](e, fv, f.quoted) + } + e.WriteByte('}') +} - case reflect.Struct: - e.WriteByte('{') - first := true - for _, f := range cachedTypeFields(v.Type()) { - fv := fieldByIndex(v, f.index) - if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) { - continue - } - if first { - first = false - } else { - e.WriteByte(',') - } - e.string(f.name) - e.WriteByte(':') - e.reflectValueQuoted(fv, f.quoted) - } - 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)) + } + return se.encode +} - case reflect.Map: - if v.Type().Key().Kind() != reflect.String { - e.error(&UnsupportedTypeError{v.Type()}) - } - if v.IsNil() { - e.WriteString("null") - break - } - e.WriteByte('{') - var sv stringValues = v.MapKeys() - sort.Sort(sv) - for i, k := range sv { - if i > 0 { - e.WriteByte(',') - } - e.string(k.String()) - e.WriteByte(':') - e.reflectValue(v.MapIndex(k)) - } - e.WriteByte('}') +type mapEncoder struct { + elemEnc encoderFunc +} - case reflect.Slice: - if v.IsNil() { - e.WriteString("null") - break - } - if v.Type().Elem().Kind() == reflect.Uint8 { - // Byte slices get special treatment; arrays don't. - 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))) - base64.StdEncoding.Encode(dst, s) - e.Write(dst) - } else { - // for large buffers, avoid unnecessary extra temporary - // buffer space. - enc := base64.NewEncoder(base64.StdEncoding, e) - enc.Write(s) - enc.Close() - } - e.WriteByte('"') - break - } - // Slices can be marshalled as nil, but otherwise are handled - // as arrays. - fallthrough - case reflect.Array: - e.WriteByte('[') - n := v.Len() - for i := 0; i < n; i++ { - if i > 0 { - e.WriteByte(',') - } - e.reflectValue(v.Index(i)) +func (me *mapEncoder) encode(e *encodeState, v reflect.Value, _ bool) { + if v.IsNil() { + e.WriteString("null") + return + } + e.WriteByte('{') + var sv stringValues = v.MapKeys() + sort.Sort(sv) + for i, k := range sv { + if i > 0 { + e.WriteByte(',') } - e.WriteByte(']') + e.string(k.String()) + e.WriteByte(':') + me.elemEnc(e, v.MapIndex(k), false) + } + e.WriteByte('}') +} - case reflect.Interface, reflect.Ptr: - if v.IsNil() { - e.WriteString("null") - return +func newMapEncoder(t reflect.Type) encoderFunc { + if t.Key().Kind() != reflect.String { + return unsupportedTypeEncoder + } + me := &mapEncoder{typeEncoder(t.Elem())} + return me.encode +} + +func encodeByteSlice(e *encodeState, v reflect.Value, _ bool) { + if v.IsNil() { + e.WriteString("null") + return + } + 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))) + base64.StdEncoding.Encode(dst, s) + e.Write(dst) + } else { + // for large buffers, avoid unnecessary extra temporary + // buffer space. + enc := base64.NewEncoder(base64.StdEncoding, e) + enc.Write(s) + enc.Close() + } + e.WriteByte('"') +} + +// sliceEncoder just wraps an arrayEncoder, checking to make sure the value isn't nil. +type sliceEncoder struct { + arrayEnc encoderFunc +} + +func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, _ bool) { + if v.IsNil() { + e.WriteString("null") + return + } + se.arrayEnc(e, v, false) +} + +func newSliceEncoder(t reflect.Type) encoderFunc { + // Byte slices get special treatment; arrays don't. + if t.Elem().Kind() == reflect.Uint8 { + return encodeByteSlice + } + enc := &sliceEncoder{newArrayEncoder(t)} + return enc.encode +} + +type arrayEncoder struct { + elemEnc encoderFunc +} + +func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, _ bool) { + e.WriteByte('[') + n := v.Len() + for i := 0; i < n; i++ { + if i > 0 { + e.WriteByte(',') } - e.reflectValue(v.Elem()) + ae.elemEnc(e, v.Index(i), false) + } + e.WriteByte(']') +} - default: - e.error(&UnsupportedTypeError{v.Type()}) +func newArrayEncoder(t reflect.Type) encoderFunc { + enc := &arrayEncoder{typeEncoder(t.Elem())} + return enc.encode +} + +type ptrEncoder struct { + elemEnc encoderFunc +} + +func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, _ bool) { + if v.IsNil() { + e.WriteString("null") + return + } + pe.elemEnc(e, v.Elem(), false) +} + +func newPtrEncoder(t reflect.Type) encoderFunc { + enc := &ptrEncoder{typeEncoder(t.Elem())} + return enc.encode +} + +type condAddrEncoder struct { + canAddrEnc, elseEnc encoderFunc +} + +func (ce *condAddrEncoder) encode(e *encodeState, v reflect.Value, quoted bool) { + if v.CanAddr() { + ce.canAddrEnc(e, v, quoted) + } else { + ce.elseEnc(e, v, quoted) } - return +} + +// 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} + return enc.encode } func isValidTag(s string) bool { @@ -479,6 +768,16 @@ func fieldByIndex(v reflect.Value, index []int) reflect.Value { return v } +func typeByIndex(t reflect.Type, index []int) reflect.Type { + for _, i := range index { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + t = t.Field(i).Type + } + return t +} + // stringValues is a slice of reflect.Value holding *reflect.StringValue. // It implements the methods to sort by string. type stringValues []reflect.Value @@ -488,13 +787,14 @@ func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) } func (sv stringValues) get(i int) string { return sv[i].String() } +// NOTE: keep in sync with stringBytes below. func (e *encodeState) string(s string) (int, error) { len0 := e.Len() e.WriteByte('"') start := 0 for i := 0; i < len(s); { if b := s[i]; b < utf8.RuneSelf { - if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' { + if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { i++ continue } @@ -526,7 +826,30 @@ func (e *encodeState) string(s string) (int, error) { } c, size := utf8.DecodeRuneInString(s[i:]) if c == utf8.RuneError && size == 1 { - e.error(&InvalidUTF8Error{s}) + if start < i { + e.WriteString(s[start:i]) + } + e.WriteString(`\ufffd`) + i += size + start = i + continue + } + // U+2028 is LINE SEPARATOR. + // U+2029 is PARAGRAPH SEPARATOR. + // They are both technically valid characters in JSON strings, + // but don't work in JSONP, which has to be evaluated as JavaScript, + // and can lead to security holes there. It is valid JSON to + // escape them, so we do so unconditionally. + // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. + if c == '\u2028' || c == '\u2029' { + if start < i { + e.WriteString(s[start:i]) + } + e.WriteString(`\u202`) + e.WriteByte(hex[c&0xF]) + i += size + start = i + continue } i += size } @@ -537,6 +860,79 @@ func (e *encodeState) string(s string) (int, error) { return e.Len() - len0, nil } +// NOTE: keep in sync with string above. +func (e *encodeState) stringBytes(s []byte) (int, error) { + len0 := e.Len() + e.WriteByte('"') + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { + i++ + continue + } + if start < i { + e.Write(s[start:i]) + } + switch b { + case '\\', '"': + e.WriteByte('\\') + e.WriteByte(b) + case '\n': + e.WriteByte('\\') + e.WriteByte('n') + case '\r': + e.WriteByte('\\') + e.WriteByte('r') + default: + // This encodes bytes < 0x20 except for \n and \r, + // as well as < and >. The latter are escaped because they + // can lead to security holes when user-controlled strings + // are rendered into JSON and served to some browsers. + e.WriteString(`\u00`) + e.WriteByte(hex[b>>4]) + e.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRune(s[i:]) + if c == utf8.RuneError && size == 1 { + if start < i { + e.Write(s[start:i]) + } + e.WriteString(`\ufffd`) + i += size + start = i + continue + } + // U+2028 is LINE SEPARATOR. + // U+2029 is PARAGRAPH SEPARATOR. + // They are both technically valid characters in JSON strings, + // but don't work in JSONP, which has to be evaluated as JavaScript, + // and can lead to security holes there. It is valid JSON to + // escape them, so we do so unconditionally. + // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. + if c == '\u2028' || c == '\u2029' { + if start < i { + e.Write(s[start:i]) + } + e.WriteString(`\u202`) + e.WriteByte(hex[c&0xF]) + i += size + start = i + continue + } + i += size + } + if start < len(s) { + e.Write(s[start:]) + } + e.WriteByte('"') + return e.Len() - len0, nil +} + // A field represents a single field found in a struct. type field struct { name string diff --git a/libgo/go/encoding/json/encode_test.go b/libgo/go/encoding/json/encode_test.go index 5be0a992e1c..9395db7cb6f 100644 --- a/libgo/go/encoding/json/encode_test.go +++ b/libgo/go/encoding/json/encode_test.go @@ -9,6 +9,7 @@ import ( "math" "reflect" "testing" + "unicode" ) type Optionals struct { @@ -146,19 +147,46 @@ func (Val) MarshalJSON() ([]byte, error) { return []byte(`"val"`), nil } +// RefText has Marshaler and Unmarshaler methods with pointer receiver. +type RefText int + +func (*RefText) MarshalText() ([]byte, error) { + return []byte(`"ref"`), nil +} + +func (r *RefText) UnmarshalText([]byte) error { + *r = 13 + return nil +} + +// ValText has Marshaler methods with value receiver. +type ValText int + +func (ValText) MarshalText() ([]byte, error) { + return []byte(`"val"`), nil +} + func TestRefValMarshal(t *testing.T) { var s = struct { R0 Ref R1 *Ref + R2 RefText + R3 *RefText V0 Val V1 *Val + V2 ValText + V3 *ValText }{ R0: 12, R1: new(Ref), + R2: 14, + R3: new(RefText), V0: 13, V1: new(Val), + V2: 15, + V3: new(ValText), } - const want = `{"R0":"ref","R1":"ref","V0":"val","V1":"val"}` + const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` b, err := Marshal(&s) if err != nil { t.Fatalf("Marshal: %v", err) @@ -175,15 +203,32 @@ func (C) MarshalJSON() ([]byte, error) { return []byte(`"<&>"`), nil } +// CText implements Marshaler and returns unescaped text. +type CText int + +func (CText) MarshalText() ([]byte, error) { + return []byte(`"<&>"`), nil +} + func TestMarshalerEscaping(t *testing.T) { var c C - const want = `"\u003c\u0026\u003e"` + want := `"\u003c\u0026\u003e"` b, err := Marshal(c) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal(c): %v", err) } if got := string(b); got != want { - t.Errorf("got %q, want %q", got, want) + t.Errorf("Marshal(c) = %#q, want %#q", got, want) + } + + var ct CText + want = `"\"\u003c\u0026\u003e\""` + b, err = Marshal(ct) + if err != nil { + t.Fatalf("Marshal(ct): %v", err) + } + if got := string(b); got != want { + t.Errorf("Marshal(ct) = %#q, want %#q", got, want) } } @@ -310,3 +355,73 @@ func TestDuplicatedFieldDisappears(t *testing.T) { t.Fatalf("Marshal: got %s want %s", got, want) } } + +func TestStringBytes(t *testing.T) { + // Test that encodeState.stringBytes and encodeState.string use the same encoding. + es := &encodeState{} + var r []rune + for i := '\u0000'; i <= unicode.MaxRune; i++ { + r = append(r, i) + } + s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too + _, err := es.string(s) + if err != nil { + t.Fatal(err) + } + + esBytes := &encodeState{} + _, err = esBytes.stringBytes([]byte(s)) + if err != nil { + t.Fatal(err) + } + + enc := es.Buffer.String() + encBytes := esBytes.Buffer.String() + if enc != encBytes { + i := 0 + for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { + i++ + } + enc = enc[i:] + encBytes = encBytes[i:] + i = 0 + for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] { + i++ + } + enc = enc[:len(enc)-i] + encBytes = encBytes[:len(encBytes)-i] + + if len(enc) > 20 { + enc = enc[:20] + "..." + } + if len(encBytes) > 20 { + encBytes = encBytes[:20] + "..." + } + + t.Errorf("encodings differ at %#q vs %#q", enc, encBytes) + } +} + +func TestIssue6458(t *testing.T) { + type Foo struct { + M RawMessage + } + x := Foo{RawMessage(`"foo"`)} + + b, err := Marshal(&x) + if err != nil { + t.Fatal(err) + } + if want := `{"M":"foo"}`; string(b) != want { + t.Errorf("Marshal(&x) = %#q; want %#q", b, want) + } + + b, err = Marshal(x) + if err != nil { + t.Fatal(err) + } + + if want := `{"M":"ImZvbyI="}`; string(b) != want { + t.Errorf("Marshal(x) = %#q; want %#q", b, want) + } +} diff --git a/libgo/go/encoding/json/indent.go b/libgo/go/encoding/json/indent.go index e8dfa4ec436..11ef709cce7 100644 --- a/libgo/go/encoding/json/indent.go +++ b/libgo/go/encoding/json/indent.go @@ -27,6 +27,15 @@ func compact(dst *bytes.Buffer, src []byte, escape bool) error { dst.WriteByte(hex[c&0xF]) start = i + 1 } + // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9). + if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 { + if start < i { + dst.Write(src[start:i]) + } + dst.WriteString(`\u202`) + dst.WriteByte(hex[src[i+2]&0xF]) + start = i + 3 + } v := scan.step(&scan, int(c)) if v >= scanSkipSpace { if v == scanError { diff --git a/libgo/go/encoding/json/scanner.go b/libgo/go/encoding/json/scanner.go index 054b6b3d564..a4609c89505 100644 --- a/libgo/go/encoding/json/scanner.go +++ b/libgo/go/encoding/json/scanner.go @@ -390,7 +390,7 @@ func stateInStringEscU123(s *scanner, c int) int { return s.error(c, "in \\u hexadecimal character escape") } -// stateInStringEscU123 is the state after reading `-` during a number. +// stateNeg is the state after reading `-` during a number. func stateNeg(s *scanner, c int) int { if c == '0' { s.step = state0 diff --git a/libgo/go/encoding/json/scanner_test.go b/libgo/go/encoding/json/scanner_test.go index 77d3455d307..90e45ff0369 100644 --- a/libgo/go/encoding/json/scanner_test.go +++ b/libgo/go/encoding/json/scanner_test.go @@ -63,6 +63,25 @@ func TestCompact(t *testing.T) { } } +func TestCompactSeparators(t *testing.T) { + // U+2028 and U+2029 should be escaped inside strings. + // They should not appear outside strings. + tests := []struct { + in, compact string + }{ + {"{\"\u2028\": 1}", `{"\u2028":1}`}, + {"{\"\u2029\" :2}", `{"\u2029":2}`}, + } + for _, tt := range tests { + var buf bytes.Buffer + if err := Compact(&buf, []byte(tt.in)); err != nil { + t.Errorf("Compact(%q): %v", tt.in, err) + } else if s := buf.String(); s != tt.compact { + t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact) + } + } +} + func TestIndent(t *testing.T) { var buf bytes.Buffer for _, tt := range examples { diff --git a/libgo/go/encoding/json/stream.go b/libgo/go/encoding/json/stream.go index 00f4726cf7f..1928abadb7d 100644 --- a/libgo/go/encoding/json/stream.go +++ b/libgo/go/encoding/json/stream.go @@ -148,7 +148,7 @@ func NewEncoder(w io.Writer) *Encoder { return &Encoder{w: w} } -// Encode writes the JSON encoding of v to the connection. +// Encode writes the JSON encoding of v to the stream. // // See the documentation for Marshal for details about the // conversion of Go values to JSON. @@ -156,8 +156,8 @@ func (enc *Encoder) Encode(v interface{}) error { if enc.err != nil { return enc.err } - enc.e.Reset() - err := enc.e.marshal(v) + e := newEncodeState() + err := e.marshal(v) if err != nil { return err } @@ -168,11 +168,12 @@ func (enc *Encoder) Encode(v interface{}) error { // is required if the encoded value was a number, // so that the reader knows there aren't more // digits coming. - enc.e.WriteByte('\n') + e.WriteByte('\n') - if _, err = enc.w.Write(enc.e.Bytes()); err != nil { + if _, err = enc.w.Write(e.Bytes()); err != nil { enc.err = err } + putEncodeState(e) return err } diff --git a/libgo/go/encoding/json/stream_test.go b/libgo/go/encoding/json/stream_test.go index 07c9e1d390c..b562e87690d 100644 --- a/libgo/go/encoding/json/stream_test.go +++ b/libgo/go/encoding/json/stream_test.go @@ -191,3 +191,16 @@ func TestBlocking(t *testing.T) { w.Close() } } + +func BenchmarkEncoderEncode(b *testing.B) { + b.ReportAllocs() + type T struct { + X, Y string + } + v := &T{"foo", "bar"} + for i := 0; i < b.N; i++ { + if err := NewEncoder(ioutil.Discard).Encode(v); err != nil { + b.Fatal(err) + } + } +} diff --git a/libgo/go/encoding/json/tags.go b/libgo/go/encoding/json/tags.go index 58cda2027c6..c38fd5102f6 100644 --- a/libgo/go/encoding/json/tags.go +++ b/libgo/go/encoding/json/tags.go @@ -21,7 +21,7 @@ func parseTag(tag string) (string, tagOptions) { return tag, tagOptions("") } -// Contains returns whether checks that a comma-separated list of options +// Contains reports whether a comma-separated list of options // contains a particular substr flag. substr must be surrounded by a // string boundary or commas. func (o tagOptions) Contains(optionName string) bool { |