Skip to content

Commit

Permalink
all: fix reflect.Value.Interface races
Browse files Browse the repository at this point in the history
The reflect.Value.Interface method shallow copies the underlying value,
which may copy mutexes and atomically-accessed fields.
Some usages of the Interface method is only to check if the interface value
implements an interface. In which case the shallow copy was unnecessary.
Change those usages to use the reflect.Value.Implements method instead.

Fixes #838
  • Loading branch information
dsnet committed Jul 27, 2019
1 parent 6c65a55 commit 4f0f324
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 5 deletions.
2 changes: 2 additions & 0 deletions go.mod
@@ -1 +1,3 @@
module github.com/golang/protobuf

go 1.12
12 changes: 9 additions & 3 deletions jsonpb/jsonpb.go
Expand Up @@ -165,6 +165,11 @@ type wkt interface {
XXX_WellKnownType() string
}

var (
wktType = reflect.TypeOf((*wkt)(nil)).Elem()
messageType = reflect.TypeOf((*proto.Message)(nil)).Elem()
)

// marshalObject writes a struct to the Writer.
func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeURL string) error {
if jsm, ok := v.(JSONPBMarshaler); ok {
Expand Down Expand Up @@ -531,7 +536,8 @@ func (m *Marshaler) marshalValue(out *errWriter, prop *proto.Properties, v refle

// Handle well-known types.
// Most are handled up in marshalObject (because 99% are messages).
if wkt, ok := v.Interface().(wkt); ok {
if v.Type().Implements(wktType) {
wkt := v.Interface().(wkt)
switch wkt.XXX_WellKnownType() {
case "NullValue":
out.write("null")
Expand Down Expand Up @@ -1277,8 +1283,8 @@ func checkRequiredFields(pb proto.Message) error {
}

func checkRequiredFieldsInValue(v reflect.Value) error {
if pm, ok := v.Interface().(proto.Message); ok {
return checkRequiredFields(pm)
if v.Type().Implements(messageType) {
return checkRequiredFields(v.Interface().(proto.Message))
}
return nil
}
32 changes: 32 additions & 0 deletions proto/all_test.go
Expand Up @@ -45,9 +45,11 @@ import (
"testing"
"time"

"github.com/golang/protobuf/jsonpb"
. "github.com/golang/protobuf/proto"
pb3 "github.com/golang/protobuf/proto/proto3_proto"
. "github.com/golang/protobuf/proto/test_proto"
descriptorpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
)

var globalO *Buffer
Expand Down Expand Up @@ -2490,3 +2492,33 @@ func BenchmarkUnmarshalUnrecognizedFields(b *testing.B) {
p2.Unmarshal(pbd)
}
}

// TestRace tests whether there are races among the different marshalers.
func TestRace(t *testing.T) {
m := &descriptorpb.FileDescriptorProto{
Options: &descriptorpb.FileOptions{
GoPackage: String("path/to/my/package"),
},
}

wg := &sync.WaitGroup{}
defer wg.Wait()

wg.Add(1)
go func() {
defer wg.Done()
Marshal(m)
}()

wg.Add(1)
go func() {
defer wg.Done()
(&jsonpb.Marshaler{}).MarshalToString(m)
}()

wg.Add(1)
go func() {
defer wg.Done()
m.String()
}()
}
6 changes: 4 additions & 2 deletions proto/text.go
Expand Up @@ -456,6 +456,8 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
return nil
}

var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()

// writeAny writes an arbitrary field.
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
v = reflect.Indirect(v)
Expand Down Expand Up @@ -519,8 +521,8 @@ func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Propert
// mutating this value.
v = v.Addr()
}
if etm, ok := v.Interface().(encoding.TextMarshaler); ok {
text, err := etm.MarshalText()
if v.Type().Implements(textMarshalerType) {
text, err := v.Interface().(encoding.TextMarshaler).MarshalText()
if err != nil {
return err
}
Expand Down

0 comments on commit 4f0f324

Please sign in to comment.