Skip to content

Commit

Permalink
interp: improve handling of methods defined on interfaces
Browse files Browse the repository at this point in the history
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 struct.

Finally, remove receiver processing 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 those fixes, it is now possible to load and run `go.uber.org/zap` in yaegi. In turn, it makes possible for yaegi to run plugins dependent on zap, such as coraza-waf.

Fixes #1515, 
Fixes #1172,
Fixes #1275,
Fixes #1485.
  • Loading branch information
mvertes authored Mar 6, 2023
1 parent 6aa4f45 commit 8efc4f0
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 186 deletions.
43 changes: 43 additions & 0 deletions _test/issue-1515.go
Original file line number Diff line number Diff line change
@@ -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
216 changes: 119 additions & 97 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -1898,103 +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 {
err = n.cfgErrorf("undefined selector: %s", n.child[1].ident)
}
err = matchSelectorMethod(sc, n)
}
if err == nil && n.findex != -1 && n.typ.cat != genericT {
n.findex = sc.add(n.typ)
Expand Down Expand Up @@ -3024,6 +2928,124 @@ func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerat
return gen
}

// 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:
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 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 method: %s", name)
}
return err
}

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 err
}

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 nil
}

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 nil
}

if typ := n.typ.interfaceMethod(name); typ != nil {
n.typ = typ
n.action = aGetMethod
n.gen = getMethodByName
return nil
}

return n.cfgErrorf("undefined selector: %s", name)
}

// 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.
Expand Down
Loading

0 comments on commit 8efc4f0

Please sign in to comment.