From fe837316519854c7efe2975d75787261ac9fce88 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 30 Apr 2019 16:10:43 -0700 Subject: [PATCH] reflect: MakeFunc: allow assignment conversions on values returned from the wrapped function Instead of requiring exact type match, allow assignment conversions (those conversions allowed in the language spec without a cast) on the returned values. Particularly useful when the type being returned is an interface type, but the Value actually returned is a concrete value implementing that type (as it is tricky to return a Value which has interface type). RELNOTE=y Fixes #28761 Change-Id: I69eef07ca51690b2086dfa1eb549db5e4724c657 Reviewed-on: https://go-review.googlesource.com/c/go/+/174531 Run-TryBot: Keith Randall TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- src/reflect/all_test.go | 85 +++++++++++++++++++++++++++++++++++++++++ src/reflect/value.go | 11 +++--- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 964d8c6e95..dbee822cea 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1934,6 +1934,91 @@ func TestMakeFuncVariadic(t *testing.T) { } } +// Dummy type that implements io.WriteCloser +type WC struct { +} + +func (w *WC) Write(p []byte) (n int, err error) { + return 0, nil +} +func (w *WC) Close() error { + return nil +} + +func TestMakeFuncValidReturnAssignments(t *testing.T) { + // reflect.Values returned from the wrapped function should be assignment-converted + // to the types returned by the result of MakeFunc. + + // Concrete types should be promotable to interfaces they implement. + var f func() error + f = MakeFunc(TypeOf(f), func([]Value) []Value { + return []Value{ValueOf(io.EOF)} + }).Interface().(func() error) + f() + + // Super-interfaces should be promotable to simpler interfaces. + var g func() io.Writer + g = MakeFunc(TypeOf(g), func([]Value) []Value { + var w io.WriteCloser = &WC{} + return []Value{ValueOf(&w).Elem()} + }).Interface().(func() io.Writer) + g() + + // Channels should be promotable to directional channels. + var h func() <-chan int + h = MakeFunc(TypeOf(h), func([]Value) []Value { + return []Value{ValueOf(make(chan int))} + }).Interface().(func() <-chan int) + h() + + // Unnamed types should be promotable to named types. + type T struct{ a, b, c int } + var i func() T + i = MakeFunc(TypeOf(i), func([]Value) []Value { + return []Value{ValueOf(struct{ a, b, c int }{a: 1, b: 2, c: 3})} + }).Interface().(func() T) + i() +} + +func TestMakeFuncInvalidReturnAssignments(t *testing.T) { + // Type doesn't implement the required interface. + shouldPanic(func() { + var f func() error + f = MakeFunc(TypeOf(f), func([]Value) []Value { + return []Value{ValueOf(int(7))} + }).Interface().(func() error) + f() + }) + // Assigning to an interface with additional methods. + shouldPanic(func() { + var f func() io.ReadWriteCloser + f = MakeFunc(TypeOf(f), func([]Value) []Value { + var w io.WriteCloser = &WC{} + return []Value{ValueOf(&w).Elem()} + }).Interface().(func() io.ReadWriteCloser) + f() + }) + // Directional channels can't be assigned to bidirectional ones. + shouldPanic(func() { + var f func() chan int + f = MakeFunc(TypeOf(f), func([]Value) []Value { + var c <-chan int = make(chan int) + return []Value{ValueOf(c)} + }).Interface().(func() chan int) + f() + }) + // Two named types which are otherwise identical. + shouldPanic(func() { + type T struct{ a, b, c int } + type U struct{ a, b, c int } + var f func() T + f = MakeFunc(TypeOf(f), func([]Value) []Value { + return []Value{ValueOf(U{a: 1, b: 2, c: 3})} + }).Interface().(func() T) + f() + }) +} + type Point struct { x, y int } diff --git a/src/reflect/value.go b/src/reflect/value.go index 79a7c6a204..f0db434009 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -560,11 +560,6 @@ func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer, retValid *bool) { } for i, typ := range ftyp.out() { v := out[i] - if v.typ != typ { - panic("reflect: function created by MakeFunc using " + funcName(f) + - " returned wrong type: have " + - out[i].typ.String() + " for " + typ.String()) - } if v.flag&flagRO != 0 { panic("reflect: function created by MakeFunc using " + funcName(f) + " returned value obtained from unexported field") @@ -574,6 +569,12 @@ func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer, retValid *bool) { continue } addr := add(ptr, off, "typ.size > 0") + + // Convert v to type typ if v is assignable to a variable + // of type t in the language spec. + // See issue 28761. + v = v.assignTo("reflect.MakeFunc", typ, addr) + // We are writing to stack. No write barrier. if v.flag&flagIndir != 0 { memmove(addr, v.ptr, typ.size) -- 2.48.1