Commit 0f3ea750 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

encoding/json: don't cache value addressability when building first encoder

newTypeEncoder (called once per type and then cached) was
looking at the first value seen of that type's addressability
and caching the encoder decision.  If the first value seen was
addressable and a future one wasn't, it would panic.

Instead, introduce a new wrapper encoder type that checks
CanAddr at runtime.

Fixes #6458

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/13839045
parent e8bbbe08
...@@ -305,7 +305,7 @@ func (e *encodeState) reflectValue(v reflect.Value) { ...@@ -305,7 +305,7 @@ func (e *encodeState) reflectValue(v reflect.Value) {
valueEncoder(v)(e, v, false) valueEncoder(v)(e, v, false)
} }
type encoderFunc func(e *encodeState, v reflect.Value, _ bool) type encoderFunc func(e *encodeState, v reflect.Value, quoted bool)
var encoderCache struct { var encoderCache struct {
sync.RWMutex sync.RWMutex
...@@ -316,11 +316,10 @@ func valueEncoder(v reflect.Value) encoderFunc { ...@@ -316,11 +316,10 @@ func valueEncoder(v reflect.Value) encoderFunc {
if !v.IsValid() { if !v.IsValid() {
return invalidValueEncoder return invalidValueEncoder
} }
t := v.Type() return typeEncoder(v.Type())
return typeEncoder(t, v)
} }
func typeEncoder(t reflect.Type, vx reflect.Value) encoderFunc { func typeEncoder(t reflect.Type) encoderFunc {
encoderCache.RLock() encoderCache.RLock()
f := encoderCache.m[t] f := encoderCache.m[t]
encoderCache.RUnlock() encoderCache.RUnlock()
...@@ -346,7 +345,7 @@ func typeEncoder(t reflect.Type, vx reflect.Value) encoderFunc { ...@@ -346,7 +345,7 @@ func typeEncoder(t reflect.Type, vx reflect.Value) encoderFunc {
// Compute fields without lock. // Compute fields without lock.
// Might duplicate effort but won't hold other computations back. // Might duplicate effort but won't hold other computations back.
f = newTypeEncoder(t, vx) f = newTypeEncoder(t, true)
wg.Done() wg.Done()
encoderCache.Lock() encoderCache.Lock()
encoderCache.m[t] = f encoderCache.m[t] = f
...@@ -354,38 +353,33 @@ func typeEncoder(t reflect.Type, vx reflect.Value) encoderFunc { ...@@ -354,38 +353,33 @@ func typeEncoder(t reflect.Type, vx reflect.Value) encoderFunc {
return f return f
} }
// newTypeEncoder constructs an encoderFunc for a type. var (
// The provided vx is an example value of type t. It's the first seen marshalerType = reflect.TypeOf(new(Marshaler)).Elem()
// value of that type and should not be used to encode. It may be textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
// zero. )
func newTypeEncoder(t reflect.Type, vx reflect.Value) encoderFunc {
if !vx.IsValid() {
vx = reflect.New(t).Elem()
}
_, ok := vx.Interface().(Marshaler) // newTypeEncoder constructs an encoderFunc for a type.
if ok { // The returned encoder only checks CanAddr when allowAddr is true.
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
if t.Implements(marshalerType) {
return marshalerEncoder return marshalerEncoder
} }
if vx.Kind() != reflect.Ptr && vx.CanAddr() { if t.Kind() != reflect.Ptr && allowAddr {
_, ok = vx.Addr().Interface().(Marshaler) if reflect.PtrTo(t).Implements(marshalerType) {
if ok { return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
return addrMarshalerEncoder
} }
} }
_, ok = vx.Interface().(encoding.TextMarshaler) if t.Implements(textMarshalerType) {
if ok {
return textMarshalerEncoder return textMarshalerEncoder
} }
if vx.Kind() != reflect.Ptr && vx.CanAddr() { if t.Kind() != reflect.Ptr && allowAddr {
_, ok = vx.Addr().Interface().(encoding.TextMarshaler) if reflect.PtrTo(t).Implements(textMarshalerType) {
if ok { return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
return addrTextMarshalerEncoder
} }
} }
switch vx.Kind() { switch t.Kind() {
case reflect.Bool: case reflect.Bool:
return boolEncoder return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
...@@ -401,15 +395,15 @@ func newTypeEncoder(t reflect.Type, vx reflect.Value) encoderFunc { ...@@ -401,15 +395,15 @@ func newTypeEncoder(t reflect.Type, vx reflect.Value) encoderFunc {
case reflect.Interface: case reflect.Interface:
return interfaceEncoder return interfaceEncoder
case reflect.Struct: case reflect.Struct:
return newStructEncoder(t, vx) return newStructEncoder(t)
case reflect.Map: case reflect.Map:
return newMapEncoder(t, vx) return newMapEncoder(t)
case reflect.Slice: case reflect.Slice:
return newSliceEncoder(t, vx) return newSliceEncoder(t)
case reflect.Array: case reflect.Array:
return newArrayEncoder(t, vx) return newArrayEncoder(t)
case reflect.Ptr: case reflect.Ptr:
return newPtrEncoder(t, vx) return newPtrEncoder(t)
default: default:
return unsupportedTypeEncoder return unsupportedTypeEncoder
} }
...@@ -593,27 +587,19 @@ func (se *structEncoder) encode(e *encodeState, v reflect.Value, quoted bool) { ...@@ -593,27 +587,19 @@ func (se *structEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
} }
e.string(f.name) e.string(f.name)
e.WriteByte(':') e.WriteByte(':')
if tenc := se.fieldEncs[i]; tenc != nil { se.fieldEncs[i](e, fv, f.quoted)
tenc(e, fv, f.quoted)
} else {
// Slower path.
e.reflectValue(fv)
}
} }
e.WriteByte('}') e.WriteByte('}')
} }
func newStructEncoder(t reflect.Type, vx reflect.Value) encoderFunc { func newStructEncoder(t reflect.Type) encoderFunc {
fields := cachedTypeFields(t) fields := cachedTypeFields(t)
se := &structEncoder{ se := &structEncoder{
fields: fields, fields: fields,
fieldEncs: make([]encoderFunc, len(fields)), fieldEncs: make([]encoderFunc, len(fields)),
} }
for i, f := range fields { for i, f := range fields {
vxf := fieldByIndex(vx, f.index) se.fieldEncs[i] = typeEncoder(typeByIndex(t, f.index))
if vxf.IsValid() {
se.fieldEncs[i] = typeEncoder(vxf.Type(), vxf)
}
} }
return se.encode return se.encode
} }
...@@ -641,11 +627,11 @@ func (me *mapEncoder) encode(e *encodeState, v reflect.Value, _ bool) { ...@@ -641,11 +627,11 @@ func (me *mapEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
e.WriteByte('}') e.WriteByte('}')
} }
func newMapEncoder(t reflect.Type, vx reflect.Value) encoderFunc { func newMapEncoder(t reflect.Type) encoderFunc {
if t.Key().Kind() != reflect.String { if t.Key().Kind() != reflect.String {
return unsupportedTypeEncoder return unsupportedTypeEncoder
} }
me := &mapEncoder{typeEncoder(vx.Type().Elem(), reflect.Value{})} me := &mapEncoder{typeEncoder(t.Elem())}
return me.encode return me.encode
} }
...@@ -684,12 +670,12 @@ func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, _ bool) { ...@@ -684,12 +670,12 @@ func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
se.arrayEnc(e, v, false) se.arrayEnc(e, v, false)
} }
func newSliceEncoder(t reflect.Type, vx reflect.Value) encoderFunc { func newSliceEncoder(t reflect.Type) encoderFunc {
// Byte slices get special treatment; arrays don't. // Byte slices get special treatment; arrays don't.
if vx.Type().Elem().Kind() == reflect.Uint8 { if t.Elem().Kind() == reflect.Uint8 {
return encodeByteSlice return encodeByteSlice
} }
enc := &sliceEncoder{newArrayEncoder(t, vx)} enc := &sliceEncoder{newArrayEncoder(t)}
return enc.encode return enc.encode
} }
...@@ -709,8 +695,8 @@ func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, _ bool) { ...@@ -709,8 +695,8 @@ func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
e.WriteByte(']') e.WriteByte(']')
} }
func newArrayEncoder(t reflect.Type, vx reflect.Value) encoderFunc { func newArrayEncoder(t reflect.Type) encoderFunc {
enc := &arrayEncoder{typeEncoder(t.Elem(), reflect.Value{})} enc := &arrayEncoder{typeEncoder(t.Elem())}
return enc.encode return enc.encode
} }
...@@ -726,8 +712,27 @@ func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, _ bool) { ...@@ -726,8 +712,27 @@ func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
pe.elemEnc(e, v.Elem(), false) pe.elemEnc(e, v.Elem(), false)
} }
func newPtrEncoder(t reflect.Type, vx reflect.Value) encoderFunc { func newPtrEncoder(t reflect.Type) encoderFunc {
enc := &ptrEncoder{typeEncoder(t.Elem(), reflect.Value{})} 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)
}
}
// 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 return enc.encode
} }
...@@ -763,6 +768,16 @@ func fieldByIndex(v reflect.Value, index []int) reflect.Value { ...@@ -763,6 +768,16 @@ func fieldByIndex(v reflect.Value, index []int) reflect.Value {
return v 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. // stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string. // It implements the methods to sort by string.
type stringValues []reflect.Value type stringValues []reflect.Value
......
...@@ -401,3 +401,27 @@ func TestStringBytes(t *testing.T) { ...@@ -401,3 +401,27 @@ func TestStringBytes(t *testing.T) {
t.Errorf("encodings differ at %#q vs %#q", enc, encBytes) 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)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment