]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: split exported/non-exported methods for interface type
authorCuong Manh Le <cuong@orijtech.com>
Fri, 2 Oct 2020 18:23:47 +0000 (01:23 +0700)
committerCuong Manh Le <cuong.manhle.vn@gmail.com>
Fri, 9 Oct 2020 02:14:32 +0000 (02:14 +0000)
Currently, mhdr/methods is emitted with the same len/cap. There's no way
to distinguish between exported and non-exported methods statically.

This CL splits mhdr/methods into two parts, use "len" for number of
exported methods, and "cap" for all methods. This fixes the bug in
issue #22075, which intends to return the number of exported methods but
currently return all methods.

Note that with this encoding, we still can access either
all/exported-only/non-exported-only methods:

mhdr[:cap(mhdr)]          // all methods
mhdr                      // exported methods
mhdr[len(mhdr):cap(mhdr)] // non-exported methods

Thank to Matthew Dempsky (@mdempsky) for suggesting this encoding.

Fixes #22075

Change-Id: If662adb03ccff27407d55a5578a0ed05a15e7cdd
Reviewed-on: https://go-review.googlesource.com/c/go/+/259237
Trust: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Run-TryBot: Cuong Manh Le <cuong.manhle.vn@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
doc/go1.16.html
src/cmd/compile/internal/gc/reflect.go
src/internal/reflectlite/type.go
src/internal/reflectlite/value.go
src/reflect/all_test.go
src/reflect/type.go
src/reflect/value.go
src/runtime/alg.go
src/runtime/iface.go
src/runtime/mfinal.go
src/runtime/type.go

index 720acc757a67e40d3c651a39691a2fef881fe5b3..509956fbf2f87fa1314a2c19ae920b3228075bd8 100644 (file)
@@ -213,6 +213,14 @@ Do not send CLs removing the interior tags from such phrases.
   with <code>"use of closed network connection"</code>.
 </p>
 
+<h3 id="reflect"><a href="/pkg/reflect/">reflect</a></h3>
+
+<p><!-- CL 259237, golang.org/issue/22075 -->
+  For interface types and values, <a href="/pkg/reflect/#Value.Method">Method</a>,
+  <a href="/pkg/reflect/#Value.MethodByName">MethodByName</a>, and
+  <a href="/pkg/reflect/#Value.NumMethod">NumMethod</a> now
+  operate on the interface's exported method set, rather than its full method set.
+</p>
 
 <h3 id="text/template/parse"><a href="/pkg/text/template/parse/">text/template/parse</a></h3>
 
index 21429af782eb963a7ae6e822c273689732dd56fa..229fcfeaee4316c614fc2980ef54314ede58583d 100644 (file)
@@ -1275,8 +1275,9 @@ func dtypesym(t *types.Type) *obj.LSym {
                }
                ot = dgopkgpath(lsym, ot, tpkg)
 
+               xcount := sort.Search(n, func(i int) bool { return !types.IsExported(m[i].name.Name) })
                ot = dsymptr(lsym, ot, lsym, ot+3*Widthptr+uncommonSize(t))
-               ot = duintptr(lsym, ot, uint64(n))
+               ot = duintptr(lsym, ot, uint64(xcount))
                ot = duintptr(lsym, ot, uint64(n))
                dataAdd := imethodSize() * n
                ot = dextratype(lsym, ot, t, dataAdd)
index 15ba30da36cf6b7449dfd4616e5e5cbd7a334e16..37cf03594f8d161e112ec8b54c579bed75cc66a1 100644 (file)
@@ -234,10 +234,13 @@ type imethod struct {
 // interfaceType represents an interface type.
 type interfaceType struct {
        rtype
-       pkgPath name      // import path
-       methods []imethod // sorted by hash
+       pkgPath    name      // import path
+       expMethods []imethod // sorted by name, see runtime/type.go:interfacetype to see how it is encoded.
 }
 
+func (t *interfaceType) methods() []imethod { return t.expMethods[:cap(t.expMethods)] }
+func (t *interfaceType) isEmpty() bool      { return cap(t.expMethods) == 0 }
+
 // mapType represents a map type.
 type mapType struct {
        rtype
@@ -695,7 +698,7 @@ func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
 }
 
 // NumMethod returns the number of interface methods in the type's method set.
-func (t *interfaceType) NumMethod() int { return len(t.methods) }
+func (t *interfaceType) NumMethod() int { return len(t.expMethods) }
 
 // TypeOf returns the reflection Type that represents the dynamic type of i.
 // If i is a nil interface value, TypeOf returns nil.
@@ -732,9 +735,10 @@ func implements(T, V *rtype) bool {
                return false
        }
        t := (*interfaceType)(unsafe.Pointer(T))
-       if len(t.methods) == 0 {
+       if t.isEmpty() {
                return true
        }
+       tmethods := t.methods()
 
        // The same algorithm applies in both cases, but the
        // method tables for an interface type and a concrete type
@@ -751,10 +755,11 @@ func implements(T, V *rtype) bool {
        if V.Kind() == Interface {
                v := (*interfaceType)(unsafe.Pointer(V))
                i := 0
-               for j := 0; j < len(v.methods); j++ {
-                       tm := &t.methods[i]
+               vmethods := v.methods()
+               for j := 0; j < len(vmethods); j++ {
+                       tm := &tmethods[i]
                        tmName := t.nameOff(tm.name)
-                       vm := &v.methods[j]
+                       vm := &vmethods[j]
                        vmName := V.nameOff(vm.name)
                        if vmName.name() == tmName.name() && V.typeOff(vm.typ) == t.typeOff(tm.typ) {
                                if !tmName.isExported() {
@@ -770,7 +775,7 @@ func implements(T, V *rtype) bool {
                                                continue
                                        }
                                }
-                               if i++; i >= len(t.methods) {
+                               if i++; i >= len(tmethods) {
                                        return true
                                }
                        }
@@ -785,7 +790,7 @@ func implements(T, V *rtype) bool {
        i := 0
        vmethods := v.methods()
        for j := 0; j < int(v.mcount); j++ {
-               tm := &t.methods[i]
+               tm := &tmethods[i]
                tmName := t.nameOff(tm.name)
                vm := vmethods[j]
                vmName := V.nameOff(vm.name)
@@ -803,7 +808,7 @@ func implements(T, V *rtype) bool {
                                        continue
                                }
                        }
-                       if i++; i >= len(t.methods) {
+                       if i++; i >= len(tmethods) {
                                return true
                        }
                }
@@ -897,7 +902,7 @@ func haveIdenticalUnderlyingType(T, V *rtype, cmpTags bool) bool {
        case Interface:
                t := (*interfaceType)(unsafe.Pointer(T))
                v := (*interfaceType)(unsafe.Pointer(V))
-               if len(t.methods) == 0 && len(v.methods) == 0 {
+               if t.isEmpty() && v.isEmpty() {
                        return true
                }
                // Might have the same methods but still
@@ -962,3 +967,11 @@ func toType(t *rtype) Type {
 func ifaceIndir(t *rtype) bool {
        return t.kind&kindDirectIface == 0
 }
+
+func isEmptyIface(t *rtype) bool {
+       if t.Kind() != Interface {
+               return false
+       }
+       tt := (*interfaceType)(unsafe.Pointer(t))
+       return tt.isEmpty()
+}
index 0365eeeabf634f0ad2c1a88709b49bbc66ae70bb..fb0ec77b588c4eb6e7944adb733a52a0e4979e95 100644 (file)
@@ -228,7 +228,7 @@ func (v Value) Elem() Value {
        switch k {
        case Interface:
                var eface interface{}
-               if v.typ.NumMethod() == 0 {
+               if isEmptyIface(v.typ) {
                        eface = *(*interface{})(v.ptr)
                } else {
                        eface = (interface{})(*(*interface {
@@ -433,7 +433,7 @@ func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value
                        return Value{dst, nil, flag(Interface)}
                }
                x := valueInterface(v)
-               if dst.NumMethod() == 0 {
+               if isEmptyIface(dst) {
                        *(*interface{})(target) = x
                } else {
                        ifaceE2I(dst, x, target)
index a12712d2540f611968380f9d19320ee2f634b240..be15362aaed491f72e67253b7d03666696f095a9 100644 (file)
@@ -2995,6 +2995,14 @@ func TestUnexportedMethods(t *testing.T) {
        if got := typ.NumMethod(); got != 0 {
                t.Errorf("NumMethod=%d, want 0 satisfied methods", got)
        }
+
+       var i unexpI
+       if got := TypeOf(&i).Elem().NumMethod(); got != 0 {
+               t.Errorf("NumMethod=%d, want 0 satisfied methods", got)
+       }
+       if got := ValueOf(&i).Elem().NumMethod(); got != 0 {
+               t.Errorf("NumMethod=%d, want 0 satisfied methods", got)
+       }
 }
 
 type InnerInt struct {
@@ -3648,21 +3656,21 @@ func TestCallPanic(t *testing.T) {
        v := ValueOf(T{i, i, i, i, T2{i, i}, i, i, T2{i, i}})
        badCall(func() { call(v.Field(0).Method(0)) })          // .t0.W
        badCall(func() { call(v.Field(0).Elem().Method(0)) })   // .t0.W
-       badCall(func() { call(v.Field(0).Method(1)) })          // .t0.w
+       badMethod(func() { call(v.Field(0).Method(1)) })        // .t0.w
        badMethod(func() { call(v.Field(0).Elem().Method(2)) }) // .t0.w
        ok(func() { call(v.Field(1).Method(0)) })               // .T1.Y
        ok(func() { call(v.Field(1).Elem().Method(0)) })        // .T1.Y
-       badCall(func() { call(v.Field(1).Method(1)) })          // .T1.y
+       badMethod(func() { call(v.Field(1).Method(1)) })        // .T1.y
        badMethod(func() { call(v.Field(1).Elem().Method(2)) }) // .T1.y
 
        ok(func() { call(v.Field(2).Method(0)) })               // .NamedT0.W
        ok(func() { call(v.Field(2).Elem().Method(0)) })        // .NamedT0.W
-       badCall(func() { call(v.Field(2).Method(1)) })          // .NamedT0.w
+       badMethod(func() { call(v.Field(2).Method(1)) })        // .NamedT0.w
        badMethod(func() { call(v.Field(2).Elem().Method(2)) }) // .NamedT0.w
 
        ok(func() { call(v.Field(3).Method(0)) })               // .NamedT1.Y
        ok(func() { call(v.Field(3).Elem().Method(0)) })        // .NamedT1.Y
-       badCall(func() { call(v.Field(3).Method(1)) })          // .NamedT1.y
+       badMethod(func() { call(v.Field(3).Method(1)) })        // .NamedT1.y
        badMethod(func() { call(v.Field(3).Elem().Method(3)) }) // .NamedT1.y
 
        ok(func() { call(v.Field(4).Field(0).Method(0)) })             // .NamedT2.T1.Y
@@ -3672,7 +3680,7 @@ func TestCallPanic(t *testing.T) {
 
        badCall(func() { call(v.Field(5).Method(0)) })          // .namedT0.W
        badCall(func() { call(v.Field(5).Elem().Method(0)) })   // .namedT0.W
-       badCall(func() { call(v.Field(5).Method(1)) })          // .namedT0.w
+       badMethod(func() { call(v.Field(5).Method(1)) })        // .namedT0.w
        badMethod(func() { call(v.Field(5).Elem().Method(2)) }) // .namedT0.w
 
        badCall(func() { call(v.Field(6).Method(0)) })        // .namedT1.Y
index a3a616701b9abf61c53664d82f1e85884a2ca0ab..0b34ca0c945145b14deb7d8f3bca63e2f510b5fc 100644 (file)
@@ -386,10 +386,14 @@ type imethod struct {
 // interfaceType represents an interface type.
 type interfaceType struct {
        rtype
-       pkgPath name      // import path
-       methods []imethod // sorted by hash
+       pkgPath    name      // import path
+       expMethods []imethod // sorted by name, see runtime/type.go:interfacetype to see how it is encoded.
 }
 
+// methods returns t's full method set, both exported and non-exported.
+func (t *interfaceType) methods() []imethod { return t.expMethods[:cap(t.expMethods)] }
+func (t *interfaceType) isEmpty() bool      { return cap(t.expMethods) == 0 }
+
 // mapType represents a map type.
 type mapType struct {
        rtype
@@ -1049,25 +1053,22 @@ func (d ChanDir) String() string {
 
 // Method returns the i'th method in the type's method set.
 func (t *interfaceType) Method(i int) (m Method) {
-       if i < 0 || i >= len(t.methods) {
-               return
+       if i < 0 || i >= len(t.expMethods) {
+               panic("reflect: Method index out of range")
        }
-       p := &t.methods[i]
+       p := &t.expMethods[i]
        pname := t.nameOff(p.name)
        m.Name = pname.name()
        if !pname.isExported() {
-               m.PkgPath = pname.pkgPath()
-               if m.PkgPath == "" {
-                       m.PkgPath = t.pkgPath.name()
-               }
+               panic("reflect: unexported method: " + pname.name())
        }
        m.Type = toType(t.typeOff(p.typ))
        m.Index = i
        return
 }
 
-// NumMethod returns the number of interface methods in the type's method set.
-func (t *interfaceType) NumMethod() int { return len(t.methods) }
+// NumMethod returns the number of exported interface methods in the type's method set.
+func (t *interfaceType) NumMethod() int { return len(t.expMethods) }
 
 // MethodByName method with the given name in the type's method set.
 func (t *interfaceType) MethodByName(name string) (m Method, ok bool) {
@@ -1075,8 +1076,8 @@ func (t *interfaceType) MethodByName(name string) (m Method, ok bool) {
                return
        }
        var p *imethod
-       for i := range t.methods {
-               p = &t.methods[i]
+       for i := range t.expMethods {
+               p = &t.expMethods[i]
                if t.nameOff(p.name).name() == name {
                        return t.Method(i), true
                }
@@ -1485,9 +1486,10 @@ func implements(T, V *rtype) bool {
                return false
        }
        t := (*interfaceType)(unsafe.Pointer(T))
-       if len(t.methods) == 0 {
+       if t.isEmpty() {
                return true
        }
+       tmethods := t.methods()
 
        // The same algorithm applies in both cases, but the
        // method tables for an interface type and a concrete type
@@ -1504,10 +1506,11 @@ func implements(T, V *rtype) bool {
        if V.Kind() == Interface {
                v := (*interfaceType)(unsafe.Pointer(V))
                i := 0
-               for j := 0; j < len(v.methods); j++ {
-                       tm := &t.methods[i]
+               vmethods := v.methods()
+               for j := 0; j < len(vmethods); j++ {
+                       tm := &tmethods[i]
                        tmName := t.nameOff(tm.name)
-                       vm := &v.methods[j]
+                       vm := &vmethods[j]
                        vmName := V.nameOff(vm.name)
                        if vmName.name() == tmName.name() && V.typeOff(vm.typ) == t.typeOff(tm.typ) {
                                if !tmName.isExported() {
@@ -1523,7 +1526,7 @@ func implements(T, V *rtype) bool {
                                                continue
                                        }
                                }
-                               if i++; i >= len(t.methods) {
+                               if i++; i >= len(tmethods) {
                                        return true
                                }
                        }
@@ -1538,7 +1541,7 @@ func implements(T, V *rtype) bool {
        i := 0
        vmethods := v.methods()
        for j := 0; j < int(v.mcount); j++ {
-               tm := &t.methods[i]
+               tm := &tmethods[i]
                tmName := t.nameOff(tm.name)
                vm := vmethods[j]
                vmName := V.nameOff(vm.name)
@@ -1556,7 +1559,7 @@ func implements(T, V *rtype) bool {
                                        continue
                                }
                        }
-                       if i++; i >= len(t.methods) {
+                       if i++; i >= len(tmethods) {
                                return true
                        }
                }
@@ -1658,7 +1661,7 @@ func haveIdenticalUnderlyingType(T, V *rtype, cmpTags bool) bool {
        case Interface:
                t := (*interfaceType)(unsafe.Pointer(T))
                v := (*interfaceType)(unsafe.Pointer(V))
-               if len(t.methods) == 0 && len(v.methods) == 0 {
+               if t.isEmpty() && v.isEmpty() {
                        return true
                }
                // Might have the same methods but still
@@ -2442,7 +2445,7 @@ func StructOf(fields []StructField) Type {
                        switch f.typ.Kind() {
                        case Interface:
                                ift := (*interfaceType)(unsafe.Pointer(ft))
-                               for im, m := range ift.methods {
+                               for im, m := range ift.methods() {
                                        if ift.nameOff(m.name).pkgPath() != "" {
                                                // TODO(sbinet).  Issue 15924.
                                                panic("reflect: embedded interface with unexported method(s) not implemented")
@@ -3149,3 +3152,11 @@ func addTypeBits(bv *bitVector, offset uintptr, t *rtype) {
                }
        }
 }
+
+func isEmptyIface(rt *rtype) bool {
+       if rt.Kind() != Interface {
+               return false
+       }
+       tt := (*interfaceType)(unsafe.Pointer(rt))
+       return len(tt.methods()) == 0
+}
index a14131e1f8344229219204fc5693017f428132df..bb6371b867b5129eb080277812245e90701c322d 100644 (file)
@@ -635,10 +635,11 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *rtype, t *fu
        i := methodIndex
        if v.typ.Kind() == Interface {
                tt := (*interfaceType)(unsafe.Pointer(v.typ))
-               if uint(i) >= uint(len(tt.methods)) {
+               ttmethods := tt.methods()
+               if uint(i) >= uint(len(ttmethods)) {
                        panic("reflect: internal error: invalid method index")
                }
-               m := &tt.methods[i]
+               m := &ttmethods[i]
                if !tt.nameOff(m.name).isExported() {
                        panic("reflect: " + op + " of unexported method")
                }
@@ -812,7 +813,7 @@ func (v Value) Elem() Value {
        switch k {
        case Interface:
                var eface interface{}
-               if v.typ.NumMethod() == 0 {
+               if isEmptyIface(v.typ) {
                        eface = *(*interface{})(v.ptr)
                } else {
                        eface = (interface{})(*(*interface {
@@ -1033,7 +1034,7 @@ func valueInterface(v Value, safe bool) interface{} {
                // Special case: return the element inside the interface.
                // Empty interface has one layout, all interfaces with
                // methods have a second layout.
-               if v.NumMethod() == 0 {
+               if isEmptyIface(v.typ) {
                        return *(*interface{})(v.ptr)
                }
                return *(*interface {
@@ -1908,10 +1909,11 @@ func (v Value) Type() Type {
        if v.typ.Kind() == Interface {
                // Method on interface.
                tt := (*interfaceType)(unsafe.Pointer(v.typ))
-               if uint(i) >= uint(len(tt.methods)) {
+               ttmethods := tt.methods()
+               if uint(i) >= uint(len(ttmethods)) {
                        panic("reflect: internal error: invalid method index")
                }
-               m := &tt.methods[i]
+               m := &ttmethods[i]
                return v.typ.typeOff(m.typ)
        }
        // Method on concrete type.
@@ -2429,7 +2431,7 @@ func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value
                        return Value{dst, nil, flag(Interface)}
                }
                x := valueInterface(v, false)
-               if dst.NumMethod() == 0 {
+               if isEmptyIface(dst) {
                        *(*interface{})(target) = x
                } else {
                        ifaceE2I(dst, x, target)
@@ -2718,10 +2720,11 @@ func cvtDirect(v Value, typ Type) Value {
 func cvtT2I(v Value, typ Type) Value {
        target := unsafe_New(typ.common())
        x := valueInterface(v, false)
-       if typ.NumMethod() == 0 {
+       rt := typ.(*rtype)
+       if isEmptyIface(rt) {
                *(*interface{})(target) = x
        } else {
-               ifaceE2I(typ.(*rtype), x, target)
+               ifaceE2I(rt, x, target)
        }
        return Value{typ.common(), target, v.flag.ro() | flagIndir | flag(Interface)}
 }
index 0af48ab25cc3b13b9be31c2683e4f7647269243a..4a98b84e4a4881db4ac84e9ab1553cee76326499 100644 (file)
@@ -185,7 +185,7 @@ func typehash(t *_type, p unsafe.Pointer, h uintptr) uintptr {
                return strhash(p, h)
        case kindInterface:
                i := (*interfacetype)(unsafe.Pointer(t))
-               if len(i.mhdr) == 0 {
+               if i.isEmpty() {
                        return nilinterhash(p, h)
                }
                return interhash(p, h)
index 0504b89363e05efd6f6e4414a4af5e7fa7425a2b..f8b7d429a3380fd6122dfab02c9b314d415367e6 100644 (file)
@@ -31,16 +31,17 @@ func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
 }
 
 func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
-       if len(inter.mhdr) == 0 {
+       if inter.isEmpty() {
                throw("internal error - misuse of itab")
        }
+       imethods := inter.methods()
 
        // easy case
        if typ.tflag&tflagUncommon == 0 {
                if canfail {
                        return nil
                }
-               name := inter.typ.nameOff(inter.mhdr[0].name)
+               name := inter.typ.nameOff(imethods[0].name)
                panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
        }
 
@@ -63,7 +64,7 @@ func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
        }
 
        // Entry doesn't exist yet. Make a new entry & add it.
-       m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
+       m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(imethods)-1)*sys.PtrSize, 0, &memstats.other_sys))
        m.inter = inter
        m._type = typ
        // The hash is used in type switches. However, compiler statically generates itab's
@@ -197,7 +198,8 @@ func (m *itab) init() string {
        // and interface names are unique,
        // so can iterate over both in lock step;
        // the loop is O(ni+nt) not O(ni*nt).
-       ni := len(inter.mhdr)
+       imethods := inter.methods()
+       ni := len(imethods)
        nt := int(x.mcount)
        xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
        j := 0
@@ -205,7 +207,7 @@ func (m *itab) init() string {
        var fun0 unsafe.Pointer
 imethods:
        for k := 0; k < ni; k++ {
-               i := &inter.mhdr[k]
+               i := &imethods[k]
                itype := inter.typ.typeOff(i.ityp)
                name := inter.typ.nameOff(i.name)
                iname := name.name()
index cd6196dcabd1f3032031c93759e2dd21de2c85a4..6676ae6736449b85e36f4f3f0a34108798872119 100644 (file)
@@ -210,7 +210,7 @@ func runfinq() {
                                        // set up with empty interface
                                        (*eface)(frame)._type = &f.ot.typ
                                        (*eface)(frame).data = f.arg
-                                       if len(ityp.mhdr) != 0 {
+                                       if !ityp.isEmpty() {
                                                // convert to interface with methods
                                                // this conversion is guaranteed to succeed - we checked in SetFinalizer
                                                *(*iface)(frame) = assertE2I(ityp, *(*eface)(frame))
@@ -394,7 +394,7 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
                }
        case fint.kind&kindMask == kindInterface:
                ityp := (*interfacetype)(unsafe.Pointer(fint))
-               if len(ityp.mhdr) == 0 {
+               if ityp.isEmpty() {
                        // ok - satisfies empty interface
                        goto okarg
                }
index 81455f3532db83ed481d0c55afa54a2dc088271c..36492619e108888d5245a852d4dd810a7eebd418 100644 (file)
@@ -366,7 +366,19 @@ type imethod struct {
 type interfacetype struct {
        typ     _type
        pkgpath name
-       mhdr    []imethod
+       // expMethods contains all interface methods.
+       //
+       // - len(expMethods) returns number of exported methods.
+       // - cap(expMethods) returns all interface methods, including both exported/non-exported methods.
+       expMethods []imethod
+}
+
+func (it *interfacetype) methods() []imethod {
+       return it.expMethods[:cap(it.expMethods)]
+}
+
+func (it *interfacetype) isEmpty() bool {
+       return cap(it.expMethods) == 0
 }
 
 type maptype struct {
@@ -664,13 +676,15 @@ func typesEqual(t, v *_type, seen map[_typePair]struct{}) bool {
                if it.pkgpath.name() != iv.pkgpath.name() {
                        return false
                }
-               if len(it.mhdr) != len(iv.mhdr) {
+               itmethods := it.methods()
+               ivmethods := iv.methods()
+               if len(itmethods) != len(ivmethods) {
                        return false
                }
-               for i := range it.mhdr {
-                       tm := &it.mhdr[i]
-                       vm := &iv.mhdr[i]
-                       // Note the mhdr array can be relocated from
+               for i := range itmethods {
+                       tm := &itmethods[i]
+                       vm := &ivmethods[i]
+                       // Note the expMethods array can be relocated from
                        // another module. See #17724.
                        tname := resolveNameOff(unsafe.Pointer(tm), tm.name)
                        vname := resolveNameOff(unsafe.Pointer(vm), vm.name)