From 100d090853074041d9cfc2579fb0df2ca3dd9710 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 26 Jan 2021 18:58:04 +0100 Subject: [PATCH] interp: fix sending object implementing an interface through channel A channel can be used to interchange data with the pre-compiled runtime and therefore objects impletementing interfaces must be wrapped if necessary, using genInterfaceWrapper. A similar treatment could be applied when sending interpreted functions over a channel, to be provided in a new PR. Fixes #1010. --- _test/issue-1010.go | 22 +++++++++++++++++ interp/run.go | 57 ++++++++++++++++++++++++++------------------- interp/type.go | 4 ++++ 3 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 _test/issue-1010.go diff --git a/_test/issue-1010.go b/_test/issue-1010.go new file mode 100644 index 000000000..169deff8c --- /dev/null +++ b/_test/issue-1010.go @@ -0,0 +1,22 @@ +package main + +import ( + "encoding/json" + "fmt" +) + +type MyJsonMarshaler struct{ n int } + +func (m MyJsonMarshaler) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`{"num": %d}`, m.n)), nil +} + +func main() { + ch := make(chan json.Marshaler, 1) + ch <- MyJsonMarshaler{2} + m, err := json.Marshal(<-ch) + fmt.Println(string(m), err) +} + +// Output: +// {"num":2} diff --git a/interp/run.go b/interp/run.go index f80e50917..b762f9050 100644 --- a/interp/run.go +++ b/interp/run.go @@ -2392,7 +2392,7 @@ func doComposite(n *node, hasType bool, keyed bool) { l := n.level n.exec = func(f *frame) bltn { typ.mu.Lock() - // No need to call zero() as doComposite is only called for a structT + // No need to call zero() as doComposite is only called for a structT. a := reflect.New(typ.TypeOf()).Elem() typ.mu.Unlock() for i, v := range values { @@ -3148,35 +3148,44 @@ func convertConstantValue(n *node) { // Write to a channel. func send(n *node) { next := getExec(n.tnext) - value0 := genValue(n.child[0]) // channel - convertLiteralValue(n.child[1], n.child[0].typ.val.TypeOf()) - value1 := genValue(n.child[1]) // value to send + c0, c1 := n.child[0], n.child[1] + value0 := genValue(c0) // Send channel. + convertLiteralValue(c1, c0.typ.val.TypeOf()) - if n.interp.cancelChan { - // Cancellable send - n.exec = func(f *frame) bltn { - ch, data := value0(f), value1(f) - // Fast: send on channel doesn't block - if ok := ch.TrySend(data); ok { - return next - } - // Slow: send on channel blocks, allow cancel - f.mutex.RLock() - done := f.done - f.mutex.RUnlock() + var value1 func(*frame) reflect.Value // Value to send. + switch { + case isInterfaceBin(c0.typ.val): + value1 = genInterfaceWrapper(c1, c0.typ.val.rtype) + default: + value1 = genValue(c1) + } - chosen, _, _ := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectSend, Chan: ch, Send: data}}) - if chosen == 0 { - return nil - } - return next - } - } else { - // Blocking send (less overhead) + if !n.interp.cancelChan { + // Send is non-cancellable, has the least overhead. n.exec = func(f *frame) bltn { value0(f).Send(value1(f)) return next } + return + } + + // Send is cancellable, may have some overhead. + n.exec = func(f *frame) bltn { + ch, data := value0(f), value1(f) + // Fast: send on channel doesn't block. + if ok := ch.TrySend(data); ok { + return next + } + // Slow: send on channel blocks, allow cancel. + f.mutex.RLock() + done := f.done + f.mutex.RUnlock() + + chosen, _, _ := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectSend, Chan: ch, Send: data}}) + if chosen == 0 { + return nil + } + return next } } diff --git a/interp/type.go b/interp/type.go index ff9c87ae6..1da514f6a 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1625,6 +1625,10 @@ func isInterfaceSrc(t *itype) bool { return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val)) } +func isInterfaceBin(t *itype) bool { + return t.cat == valueT && t.rtype.Kind() == reflect.Interface +} + func isInterface(t *itype) bool { return isInterfaceSrc(t) || t.TypeOf() != nil && t.TypeOf().Kind() == reflect.Interface }