From a72503866be81551b2fbca7a8d2832a0c36cfaeb Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 23 Feb 2023 11:08:11 +0100 Subject: [PATCH 01/12] interp: improve handling of methods defined on interfaces For methods defined on interfaces (vs concrete methods), the resolution of the method is necessarily delayed at the run time and can not be completed at compile time. The selectorExpr processing has been changed to correctly identify calls on interface methods which were confused as fields rather than methods (due to the fact that in a interface definition, methods are fields of the interface). Then at runtime, method lookup has been fixed to correctly recurse in nested valueInterface wrappers and to find embedded interface fields in case of struct or pointer to structs. Fixes #1515. --- _test/issue-1515.go | 43 ++++++++++++++++++++++++++ interp/cfg.go | 3 ++ interp/run.go | 73 +++++++++++++++++++++++++++++++-------------- interp/type.go | 36 ++++++++++++++++++++++ interp/value.go | 15 ++++++++-- 5 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 _test/issue-1515.go diff --git a/_test/issue-1515.go b/_test/issue-1515.go new file mode 100644 index 000000000..07657184e --- /dev/null +++ b/_test/issue-1515.go @@ -0,0 +1,43 @@ +package main + +type I1 interface { + I2 + Wrap() *S3 +} + +type I2 interface { + F() +} + +type S2 struct { + I2 +} + +func newS2(i2 I2) I1 { + return &S2{i2} +} + +type S3 struct { + base *S2 +} + +func (s *S2) Wrap() *S3 { + i2 := s + return &S3{i2} +} + +type T struct { + name string +} + +func (t *T) F() { println("in F", t.name) } + +func main() { + t := &T{"test"} + s2 := newS2(t) + s3 := s2.Wrap() + s3.base.F() +} + +// Output: +// in F test diff --git a/interp/cfg.go b/interp/cfg.go index 47e646c66..a3a6441bd 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -1992,6 +1992,9 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string n.recv = &receiver{node: n.child[0], index: lind} n.val = append([]int{m.Index}, lind...) n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ)) + } else if n.typ.hasInterfaceMethod(n.child[1].ident) { + n.action = aGetMethod + n.gen = getMethodByName } else { err = n.cfgErrorf("undefined selector: %s", n.child[1].ident) } diff --git a/interp/run.go b/interp/run.go index a8cc80b7b..03d073b95 100644 --- a/interp/run.go +++ b/interp/run.go @@ -1985,7 +1985,28 @@ func getMethodByName(n *node) { l := n.level n.exec = func(f *frame) bltn { - val := value0(f).Interface().(valueInterface) + // The interface object must be directly accessible, or embedded in a struct (exported anonymous field). + val0 := value0(f) + val, ok := value0(f).Interface().(valueInterface) + if !ok { + // Search the first embedded valueInterface. + for val0.Kind() == reflect.Ptr { + val0 = val0.Elem() + } + for i := 0; i < val0.NumField(); i++ { + fld := val0.Type().Field(i) + if !fld.Anonymous || !fld.IsExported() { + continue + } + if val, ok = val0.Field(i).Interface().(valueInterface); ok { + break + } + } + if !ok { + panic(n.cfgErrorf("invalid interface value %v", val0)) + } + } + // Traverse nested interface values to get the concrete value. for { v, ok := val.value.Interface().(valueInterface) if !ok { @@ -2001,7 +2022,7 @@ func getMethodByName(n *node) { typ := val.node.typ if typ.node == nil && typ.cat == valueT { - // happens with a var of empty interface type, that has value of concrete type + // It happens with a var of empty interface type, that has value of concrete type // from runtime, being asserted to "user-defined" interface. if _, ok := typ.rtype.MethodByName(name); !ok { panic(n.cfgErrorf("method not found: %s", name)) @@ -2009,27 +2030,8 @@ func getMethodByName(n *node) { return next } - m, li := typ.lookupMethod(name) - - // Try harder to find a matching embedded valueInterface. - // TODO (marc): make sure it works for arbitrary depth and breadth. - if m == nil && isStruct(val.node.typ) { - v := val.value - for v.Type().Kind() == reflect.Ptr { - v = v.Elem() - } - nf := v.NumField() - for i := 0; i < nf; i++ { - var ok bool - if val, ok = v.Field(i).Interface().(valueInterface); !ok { - continue - } - if m, li = val.node.typ.lookupMethod(name); m != nil { - break - } - } - } - + // Finally search method recursively in embedded valueInterfaces. + m, li := lookupMethodValue(val, name) if m == nil { panic(n.cfgErrorf("method not found: %s", name)) } @@ -2044,6 +2046,31 @@ func getMethodByName(n *node) { } } +func lookupMethodValue(val valueInterface, name string) (m *node, li []int) { + if m, li = val.node.typ.lookupMethod(name); m != nil { + return + } + if !isStruct(val.node.typ) { + return + } + v := val.value + for v.Type().Kind() == reflect.Ptr { + v = v.Elem() + } + nf := v.NumField() + for i := 0; i < nf; i++ { + vi, ok := v.Field(i).Interface().(valueInterface) + if !ok { + continue + } + if m, li = lookupMethodValue(vi, name); m != nil { + li = append([]int{i}, li...) + return + } + } + return +} + func getIndexSeq(n *node) { value := genValue(n.child[0]) index := n.val.([]int) diff --git a/interp/type.go b/interp/type.go index 7b036f9cb..83523834e 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1705,6 +1705,7 @@ func (t *itype) fieldSeq(seq []int) *itype { func (t *itype) lookupField(name string) []int { seen := map[*itype]bool{} var lookup func(*itype) []int + tias := isStruct(t) lookup = func(typ *itype) []int { if seen[typ] { @@ -1723,6 +1724,11 @@ func (t *itype) lookupField(name string) []int { for i, f := range typ.field { switch f.typ.cat { case ptrT, structT, interfaceT, linkedT: + if tias != isStruct(f.typ) { + // Interface fields are not valid embedded struct fields. + // Struct fields are not valid interface fields. + break + } if index2 := lookup(f.typ); len(index2) > 0 { return append([]int{i}, index2...) } @@ -1832,6 +1838,36 @@ func (t *itype) lookupMethod2(name string, seen map[*itype]bool) (*node, []int) return m, index } +// hasInterfaceMethod returns true if type contains an interface method name (not as a concrete method). +func (t *itype) hasInterfaceMethod(name string) bool { + return t.hasInterfaceMethod2(name, nil) +} + +func (t *itype) hasInterfaceMethod2(name string, seen map[*itype]bool) bool { + if seen == nil { + seen = map[*itype]bool{} + } + if seen[t] { + return false + } + seen[t] = true + if t.cat == ptrT { + return t.val.hasInterfaceMethod2(name, seen) + } + for _, f := range t.field { + if f.name == name && isInterface(t) { + return true + } + if f.embed && f.typ.hasInterfaceMethod2(name, seen) { + return true + } + } + if t.cat == linkedT || isInterfaceSrc(t) && t.val != nil { + return t.val.hasInterfaceMethod2(name, seen) + } + return false +} + // methodDepth returns a depth greater or equal to 0, or -1 if no match. func (t *itype) methodDepth(name string) int { if m, lint := t.lookupMethod(name); m != nil { diff --git a/interp/value.go b/interp/value.go index 93ff0f5f2..e179b7508 100644 --- a/interp/value.go +++ b/interp/value.go @@ -55,10 +55,19 @@ func genValueRecv(n *node) func(*frame) reflect.Value { return func(f *frame) reflect.Value { r := v(f) - if r.Kind() == reflect.Ptr { - r = r.Elem() + for _, i := range fi { + if r.Kind() == reflect.Ptr { + r = r.Elem() + } + // Note that we can't use reflect FieldByIndex method, as we may + // traverse valueInterface wrappers to access the embedded receiver. + r = r.Field(i) + vi, ok := r.Interface().(valueInterface) + if ok { + r = vi.value + } } - return r.FieldByIndex(fi) + return r } } From 3a2187a46112202530fd1235659aa414df69e0a7 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 23 Feb 2023 14:06:44 +0100 Subject: [PATCH 02/12] add matchSelectorMethod to reduce complexity and make lint happy --- interp/cfg.go | 216 +++++++++++++++++++++++++++----------------------- 1 file changed, 116 insertions(+), 100 deletions(-) diff --git a/interp/cfg.go b/interp/cfg.go index a3a6441bd..e85d0bbc0 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -1898,106 +1898,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string tryMethods: fallthrough default: - // Find a matching method. - // TODO (marc): simplify the following if/elseif blocks. - if n.typ.cat == valueT || n.typ.cat == errorT { - switch method, ok := n.typ.rtype.MethodByName(n.child[1].ident); { - case ok: - hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface - n.val = method.Index - n.gen = getIndexBinMethod - n.action = aGetMethod - n.recv = &receiver{node: n.child[0]} - n.typ = valueTOf(method.Type, isBinMethod()) - if hasRecvType { - n.typ.recv = n.typ - } - case n.typ.TypeOf().Kind() == reflect.Ptr: - if field, ok := n.typ.rtype.Elem().FieldByName(n.child[1].ident); ok { - n.typ = valueTOf(field.Type) - n.val = field.Index - n.gen = getPtrIndexSeq - break - } - err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident) - case n.typ.TypeOf().Kind() == reflect.Struct: - if field, ok := n.typ.rtype.FieldByName(n.child[1].ident); ok { - n.typ = valueTOf(field.Type) - n.val = field.Index - n.gen = getIndexSeq - break - } - fallthrough - default: - // method lookup failed on type, now lookup on pointer to type - pt := reflect.PtrTo(n.typ.rtype) - if m2, ok2 := pt.MethodByName(n.child[1].ident); ok2 { - n.val = m2.Index - n.gen = getIndexBinPtrMethod - n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt))) - n.recv = &receiver{node: n.child[0]} - n.action = aGetMethod - break - } - err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident) - } - } else if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) { - // Handle pointer on object defined in runtime - if method, ok := n.typ.val.rtype.MethodByName(n.child[1].ident); ok { - n.val = method.Index - n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ)) - n.recv = &receiver{node: n.child[0]} - n.gen = getIndexBinElemMethod - n.action = aGetMethod - } else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok { - n.val = method.Index - n.gen = getIndexBinMethod - n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod()))) - n.recv = &receiver{node: n.child[0]} - n.action = aGetMethod - } else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok { - n.typ = valueTOf(field.Type) - n.val = field.Index - n.gen = getPtrIndexSeq - } else { - err = n.cfgErrorf("undefined selector: %s", n.child[1].ident) - } - } else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil { - n.action = aGetMethod - if n.child[0].isType(sc) { - // Handle method as a function with receiver in 1st argument. - n.val = m - n.findex = notInFrame - n.gen = nop - n.typ = &itype{} - *n.typ = *m.typ - n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...) - } else { - // Handle method with receiver. - n.gen = getMethod - n.val = m - n.typ = m.typ - n.recv = &receiver{node: n.child[0], index: lind} - } - } else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok { - n.action = aGetMethod - switch { - case isPtr && n.typ.fieldSeq(lind).cat != ptrT: - n.gen = getIndexSeqPtrMethod - case isInterfaceSrc(n.typ): - n.gen = getMethodByName - default: - n.gen = getIndexSeqMethod - } - n.recv = &receiver{node: n.child[0], index: lind} - n.val = append([]int{m.Index}, lind...) - n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ)) - } else if n.typ.hasInterfaceMethod(n.child[1].ident) { - n.action = aGetMethod - n.gen = getMethodByName - } else { - err = n.cfgErrorf("undefined selector: %s", n.child[1].ident) - } + err = matchSelectorMethod(sc, n, n.child[1].ident) } if err == nil && n.findex != -1 && n.typ.cat != genericT { n.findex = sc.add(n.typ) @@ -3027,6 +2928,121 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat return gen } +func matchSelectorMethod(sc *scope, n *node, name string) (err error) { + if n.typ.cat == valueT || n.typ.cat == errorT { + switch method, ok := n.typ.rtype.MethodByName(name); { + case ok: + hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface + n.val = method.Index + n.gen = getIndexBinMethod + n.action = aGetMethod + n.recv = &receiver{node: n.child[0]} + n.typ = valueTOf(method.Type, isBinMethod()) + if hasRecvType { + n.typ.recv = n.typ + } + case n.typ.TypeOf().Kind() == reflect.Ptr: + if field, ok := n.typ.rtype.Elem().FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getPtrIndexSeq + break + } + err = n.cfgErrorf("undefined field or method: %s", name) + case n.typ.TypeOf().Kind() == reflect.Struct: + if field, ok := n.typ.rtype.FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getIndexSeq + break + } + fallthrough + default: + // method lookup failed on type, now lookup on pointer to type + pt := reflect.PtrTo(n.typ.rtype) + if m2, ok2 := pt.MethodByName(name); ok2 { + n.val = m2.Index + n.gen = getIndexBinPtrMethod + n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt))) + n.recv = &receiver{node: n.child[0]} + n.action = aGetMethod + break + } + err = n.cfgErrorf("undefined field or method: %s", name) + } + return + } + + if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) { + // Handle pointer on object defined in runtime + if method, ok := n.typ.val.rtype.MethodByName(name); ok { + n.val = method.Index + n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ)) + n.recv = &receiver{node: n.child[0]} + n.gen = getIndexBinElemMethod + n.action = aGetMethod + } else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(name); ok { + n.val = method.Index + n.gen = getIndexBinMethod + n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod()))) + n.recv = &receiver{node: n.child[0]} + n.action = aGetMethod + } else if field, ok := n.typ.val.rtype.FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getPtrIndexSeq + } else { + err = n.cfgErrorf("undefined selector: %s", name) + } + return + } + + if m, lind := n.typ.lookupMethod(name); m != nil { + n.action = aGetMethod + if n.child[0].isType(sc) { + // Handle method as a function with receiver in 1st argument. + n.val = m + n.findex = notInFrame + n.gen = nop + n.typ = &itype{} + *n.typ = *m.typ + n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...) + } else { + // Handle method with receiver. + n.gen = getMethod + n.val = m + n.typ = m.typ + n.recv = &receiver{node: n.child[0], index: lind} + } + return + } + + if m, lind, isPtr, ok := n.typ.lookupBinMethod(name); ok { + n.action = aGetMethod + switch { + case isPtr && n.typ.fieldSeq(lind).cat != ptrT: + n.gen = getIndexSeqPtrMethod + case isInterfaceSrc(n.typ): + n.gen = getMethodByName + default: + n.gen = getIndexSeqMethod + } + n.recv = &receiver{node: n.child[0], index: lind} + n.val = append([]int{m.Index}, lind...) + n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ)) + return + } + + if n.typ.hasInterfaceMethod(name) { + n.action = aGetMethod + n.gen = getMethodByName + return + } + + err = n.cfgErrorf("undefined selector: %s", name) + return +} + // arrayTypeLen returns the node's array length. If the expression is an // array variable it is determined from the value's type, otherwise it is // computed from the source definition. From 050378d636ac30777a743c4de39991cc16592350 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 23 Feb 2023 14:13:09 +0100 Subject: [PATCH 03/12] lint --- interp/cfg.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/interp/cfg.go b/interp/cfg.go index e85d0bbc0..f26c43a8a 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -2970,7 +2970,7 @@ func matchSelectorMethod(sc *scope, n *node, name string) (err error) { } err = n.cfgErrorf("undefined field or method: %s", name) } - return + return err } if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) { @@ -2994,7 +2994,7 @@ func matchSelectorMethod(sc *scope, n *node, name string) (err error) { } else { err = n.cfgErrorf("undefined selector: %s", name) } - return + return err } if m, lind := n.typ.lookupMethod(name); m != nil { @@ -3014,7 +3014,7 @@ func matchSelectorMethod(sc *scope, n *node, name string) (err error) { n.typ = m.typ n.recv = &receiver{node: n.child[0], index: lind} } - return + return nil } if m, lind, isPtr, ok := n.typ.lookupBinMethod(name); ok { @@ -3030,17 +3030,16 @@ func matchSelectorMethod(sc *scope, n *node, name string) (err error) { n.recv = &receiver{node: n.child[0], index: lind} n.val = append([]int{m.Index}, lind...) n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ)) - return + return nil } if n.typ.hasInterfaceMethod(name) { n.action = aGetMethod n.gen = getMethodByName - return + return nil } - err = n.cfgErrorf("undefined selector: %s", name) - return + return n.cfgErrorf("undefined selector: %s", name) } // arrayTypeLen returns the node's array length. If the expression is an From ded0780dc27a12ab53156ae5ccafe6ccd50bca00 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 23 Feb 2023 16:10:00 +0100 Subject: [PATCH 04/12] set interface selector type to matched selector --- interp/cfg.go | 3 ++- interp/type.go | 25 ++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/interp/cfg.go b/interp/cfg.go index f26c43a8a..34bcbb2fa 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -3033,7 +3033,8 @@ func matchSelectorMethod(sc *scope, n *node, name string) (err error) { return nil } - if n.typ.hasInterfaceMethod(name) { + if typ := n.typ.interfaceMethod(name); typ != nil { + n.typ = typ n.action = aGetMethod n.gen = getMethodByName return nil diff --git a/interp/type.go b/interp/type.go index 83523834e..14ea071e9 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1838,34 +1838,37 @@ func (t *itype) lookupMethod2(name string, seen map[*itype]bool) (*node, []int) return m, index } -// hasInterfaceMethod returns true if type contains an interface method name (not as a concrete method). -func (t *itype) hasInterfaceMethod(name string) bool { - return t.hasInterfaceMethod2(name, nil) +// interfaceMethod returns type of method matching an interface method name (not as a concrete method). +func (t *itype) interfaceMethod(name string) *itype { + return t.interfaceMethod2(name, nil) } -func (t *itype) hasInterfaceMethod2(name string, seen map[*itype]bool) bool { +func (t *itype) interfaceMethod2(name string, seen map[*itype]bool) *itype { if seen == nil { seen = map[*itype]bool{} } if seen[t] { - return false + return nil } seen[t] = true if t.cat == ptrT { - return t.val.hasInterfaceMethod2(name, seen) + return t.val.interfaceMethod2(name, seen) } for _, f := range t.field { if f.name == name && isInterface(t) { - return true + return f.typ } - if f.embed && f.typ.hasInterfaceMethod2(name, seen) { - return true + if !f.embed { + continue + } + if typ := f.typ.interfaceMethod2(name, seen); typ != nil { + return typ } } if t.cat == linkedT || isInterfaceSrc(t) && t.val != nil { - return t.val.hasInterfaceMethod2(name, seen) + return t.val.interfaceMethod2(name, seen) } - return false + return nil } // methodDepth returns a depth greater or equal to 0, or -1 if no match. From 34ae1b6d7d7668df38033f3891173bfe7c073013 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Fri, 24 Feb 2023 09:59:21 +0100 Subject: [PATCH 05/12] lookupMethodValue returns also a reflect.Value --- interp/run.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/interp/run.go b/interp/run.go index 03d073b95..5503b2b3b 100644 --- a/interp/run.go +++ b/interp/run.go @@ -2031,7 +2031,11 @@ func getMethodByName(n *node) { } // Finally search method recursively in embedded valueInterfaces. - m, li := lookupMethodValue(val, name) + r, m, li := lookupMethodValue(val, name) + if r.IsValid() { + getFrame(f, l).data[i] = r + return next + } if m == nil { panic(n.cfgErrorf("method not found: %s", name)) } @@ -2046,7 +2050,10 @@ func getMethodByName(n *node) { } } -func lookupMethodValue(val valueInterface, name string) (m *node, li []int) { +func lookupMethodValue(val valueInterface, name string) (r reflect.Value, m *node, li []int) { + if r = val.value.MethodByName(name); r.IsValid() { + return + } if m, li = val.node.typ.lookupMethod(name); m != nil { return } @@ -2063,7 +2070,7 @@ func lookupMethodValue(val valueInterface, name string) (m *node, li []int) { if !ok { continue } - if m, li = lookupMethodValue(vi, name); m != nil { + if r, m, li = lookupMethodValue(vi, name); m != nil { li = append([]int{i}, li...) return } From 7c2e5d69e4a5cc8689c76d0250115e4c5f00124d Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 28 Feb 2023 11:59:06 +0100 Subject: [PATCH 06/12] Do not process receiver value in call The receiver is already processed at method resolution and in genFunctionWrapper. Removing redundant processing in call fixes handling of variadic method, simplifies the code and makes it faster. With this commit, it is now possible to run coraza-waf plugin in traefik. --- interp/run.go | 66 ++------------------------------------------------- 1 file changed, 2 insertions(+), 64 deletions(-) diff --git a/interp/run.go b/interp/run.go index 5503b2b3b..5f4ba935d 100644 --- a/interp/run.go +++ b/interp/run.go @@ -1146,23 +1146,10 @@ func checkFieldIndex(typ reflect.Type, index []int) bool { func call(n *node) { goroutine := n.anc.kind == goStmt - var method bool c0 := n.child[0] value := genValue(c0) var values []func(*frame) reflect.Value - recvIndexLater := false - switch { - case c0.recv != nil: - // Compute method receiver value. - values = append(values, genValueRecv(c0)) - method = true - case c0.action == aMethod: - // Add a place holder for interface method receiver. - values = append(values, nil) - method = true - } - numRet := len(c0.typ.ret) variadic := variadicPos(n) child := n.child[1:] @@ -1262,10 +1249,6 @@ func call(n *node) { if n.anc.kind == deferStmt { // Store function call in frame for deferred execution. value = genFunctionWrapper(c0) - if method { - // The receiver is already passed in the function wrapper, skip it. - values = values[1:] - } n.exec = func(f *frame) bltn { val := make([]reflect.Value, len(values)+1) val[0] = value(f) @@ -1301,11 +1284,6 @@ func call(n *node) { callf = func(in []reflect.Value) []reflect.Value { return bf.Call(in) } } - if method && len(values) > bf.Type().NumIn() { - // The receiver is already passed in the function wrapper, skip it. - values = values[1:] - } - if goroutine { // Goroutine's arguments should be copied. in := make([]reflect.Value, len(values)) @@ -1358,55 +1336,15 @@ func call(n *node) { } // Init variadic argument vector - varIndex := variadic if variadic >= 0 { - if method { - vararg = nf.data[numRet+variadic+1] - varIndex++ - } else { - vararg = nf.data[numRet+variadic] - } + vararg = nf.data[numRet+variadic] } // Copy input parameters from caller if dest := nf.data[numRet:]; len(dest) > 0 { for i, v := range values { switch { - case method && i == 0: - // compute receiver - var src reflect.Value - if v == nil { - src = def.recv.val - } else { - src = v(f) - for src.IsValid() { - // traverse interface indirections to find out concrete type - vi, ok := src.Interface().(valueInterface) - if !ok { - break - } - src = vi.value - } - } - if recvIndexLater && def.recv != nil && len(def.recv.index) > 0 { - if src.Kind() == reflect.Ptr { - src = src.Elem().FieldByIndex(def.recv.index) - } else { - src = src.FieldByIndex(def.recv.index) - } - } - // Accommodate to receiver type - d := dest[0] - if ks, kd := src.Kind(), d.Kind(); ks != kd { - if kd == reflect.Ptr { - d.Set(src.Addr()) - } else { - d.Set(src.Elem()) - } - } else { - d.Set(src) - } - case variadic >= 0 && i >= varIndex: + case variadic >= 0 && i >= variadic: if v(f).Type() == vararg.Type() { vararg.Set(v(f)) } else { From 536019ff53c5a0638e565e5cf819007bd01e8513 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 6 Mar 2023 12:10:02 +0100 Subject: [PATCH 07/12] Update interp/cfg.go Co-authored-by: mpl --- interp/cfg.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interp/cfg.go b/interp/cfg.go index 34bcbb2fa..d25e1f34f 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -2928,7 +2928,10 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat return gen } -func matchSelectorMethod(sc *scope, n *node, name string) (err error) { +// matchSelectorMethod, given that n represents a selector for a method, tries +// to find the corresponding method, and populates n accordingly. +func matchSelectorMethod(sc *scope, n *node) (err error) { + name := n.child[1].ident if n.typ.cat == valueT || n.typ.cat == errorT { switch method, ok := n.typ.rtype.MethodByName(name); { case ok: From 708fbd6e769e78c7f81796b47be0dbd40817694b Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 6 Mar 2023 12:13:11 +0100 Subject: [PATCH 08/12] fix previous --- interp/cfg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interp/cfg.go b/interp/cfg.go index d25e1f34f..234a7c2c1 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -1898,7 +1898,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string tryMethods: fallthrough default: - err = matchSelectorMethod(sc, n, n.child[1].ident) + err = matchSelectorMethod(sc, n) } if err == nil && n.findex != -1 && n.typ.cat != genericT { n.findex = sc.add(n.typ) From 36418b703fbb734ea86b56be768874d5da9785e8 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 6 Mar 2023 12:14:14 +0100 Subject: [PATCH 09/12] Update interp/run.go Co-authored-by: mpl --- interp/run.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interp/run.go b/interp/run.go index 5f4ba935d..fbdfbf9dc 100644 --- a/interp/run.go +++ b/interp/run.go @@ -1938,6 +1938,8 @@ func getMethodByName(n *node) { } if val, ok = val0.Field(i).Interface().(valueInterface); ok { break + // TODO: should we keep track of all the the vals that are indeed valueInterface, + // so that later on we can call MethodByName on all of them until one matches? } } if !ok { From a65f9d4d47e38b6db09f26ac20b88758b09453ee Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 6 Mar 2023 12:14:43 +0100 Subject: [PATCH 10/12] Update interp/run.go Co-authored-by: mpl --- interp/run.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interp/run.go b/interp/run.go index fbdfbf9dc..86eb1645d 100644 --- a/interp/run.go +++ b/interp/run.go @@ -1990,6 +1990,9 @@ func getMethodByName(n *node) { } } +// lookupMethodValue recursively looks within val for the method with the given +// name. If a runtime value is found, it is returned in r, otherwise it is returned +// in m, with li as the list of recursive field indexes. func lookupMethodValue(val valueInterface, name string) (r reflect.Value, m *node, li []int) { if r = val.value.MethodByName(name); r.IsValid() { return From 0505594c14cc69ade2081354b624a34ca27273a4 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 6 Mar 2023 16:37:25 +0100 Subject: [PATCH 11/12] Update interp/cfg.go Co-authored-by: mpl --- interp/cfg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interp/cfg.go b/interp/cfg.go index 234a7c2c1..ba58bc157 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -2951,7 +2951,7 @@ func matchSelectorMethod(sc *scope, n *node) (err error) { n.gen = getPtrIndexSeq break } - err = n.cfgErrorf("undefined field or method: %s", name) + err = n.cfgErrorf("undefined method: %s", name) case n.typ.TypeOf().Kind() == reflect.Struct: if field, ok := n.typ.rtype.FieldByName(name); ok { n.typ = valueTOf(field.Type) From 1952a76c53356d03c81086cfe49507a4d2a411d6 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 6 Mar 2023 16:37:43 +0100 Subject: [PATCH 12/12] Update interp/cfg.go Co-authored-by: mpl --- interp/cfg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interp/cfg.go b/interp/cfg.go index ba58bc157..e293e1659 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -2971,7 +2971,7 @@ func matchSelectorMethod(sc *scope, n *node) (err error) { n.action = aGetMethod break } - err = n.cfgErrorf("undefined field or method: %s", name) + err = n.cfgErrorf("undefined method: %s", name) } return err }