go `cmd/compile: 类型Assert不正确,存在冲突的包名`

vkc1a9a2  于 6个月前  发布在  Go
关注(0)|答案(5)|浏览(48)

这是一个自Go1.0以来就存在的bug。请考虑以下包:

package iface // import "github.com/dsnet/example/iface1/iface"

import "fmt"
import "reflect"

func CheckInterface() {
	type iface interface { unexported() }
	var v interface{} = foo{}
	t := reflect.TypeOf(v)

	t1 := reflect.TypeOf((*iface)(nil)).Elem()
	t2 := reflect.TypeOf((*interface { unexported() })(nil)).Elem()
	fmt.Println("reflect implements named interface:  ", t.Implements(t1))
	fmt.Println("reflect implements interface literal:", t.Implements(t2))

	_, ok1 := v.(iface)
	_, ok2 := v.(interface { unexported() })
	fmt.Println("type assertion on named interface:   ", ok1)
	fmt.Println("type assertion on interface literal: ", ok2)

	fmt.Println()
}

type foo struct{}
func (foo) unexported() {}
package iface // import "github.com/dsnet/example/iface2/iface"

import "fmt"
import "reflect"

func CheckInterface() {
	type iface interface { unexported() }
	var v interface{} = foo{}
	t := reflect.TypeOf(v)

	t1 := reflect.TypeOf((*iface)(nil)).Elem()
	t2 := reflect.TypeOf((*interface { unexported() })(nil)).Elem()
	fmt.Println("reflect implements named interface:  ", t.Implements(t1))
	fmt.Println("reflect implements interface literal:", t.Implements(t2))

	_, ok1 := v.(iface)
	_, ok2 := v.(interface { unexported() })
	fmt.Println("type assertion on named interface:   ", ok1)
	fmt.Println("type assertion on interface literal: ", ok2)

	fmt.Println()
}

type foo struct{}
func (foo) unexported() {}

在设置中,我们有两个名为iface的包,它们的源代码完全相同,但它们具有不同的绝对包路径。
运行以下程序:

package main
import (
	iface1 "github.com/dsnet/example/iface1/iface"
	iface2 "github.com/dsnet/example/iface2/iface"
)

func main() {
	iface1.CheckInterface()
	iface2.CheckInterface()
}

打印以下内容(在Go1.0到Go1.8之间):

reflect implements named interface:   true
reflect implements interface literal: true
type assertion on named interface:    true
type assertion on interface literal:  true

reflect implements named interface:   true
reflect implements interface literal: false
type assertion on named interface:    true
type assertion on interface literal:  false

Go1.9的输出略有不同,但仍然是错误的。
我们期望调用iface1.CheckInterface()iface2.CheckInterface()会产生完全相同的结果。然而,我们看到的并非如此。在iface2中,通过接口字面量对未导出字段进行类型Assert不再正常工作,但在iface1中它按预期工作。

rhfm7lfc

rhfm7lfc1#

iface1.CheckInterface() 的末尾添加以下两行是没问题的:

var x interface { unexported() } = foo{}
	_ = reflect.TypeOf(x)

但是在 iface2.CheckInterface() 的末尾添加它们会导致运行时恐慌。

package iface // import "github.com/dsnet/example/iface2/iface"

import "fmt"
import "reflect"

type Iface interface { unexported() }

func CheckInterface() {
	var v interface{} = foo{}
	t := reflect.TypeOf(v)

	t1 := reflect.TypeOf((*Iface)(nil)).Elem()
	t2 := reflect.TypeOf((*interface { unexported() })(nil)).Elem()
	fmt.Println("reflect implements named interface:  ", t.Implements(t1))
	fmt.Println("reflect implements interface literal:", t.Implements(t2))

	_, ok1 := v.(Iface)
	_, ok2 := v.(interface { unexported() })
	fmt.Println("type assertion on named interface:   ", ok1)
	fmt.Println("type assertion on interface literal: ", ok2)

	fmt.Println()
	
	//>> panic
	var x interface { unexported() } = foo{}
	_ = reflect.TypeOf(x)
	//<<
}

type foo struct{}
func (foo) unexported() {}

这种恐慌有些奇怪,当它被触发时,iface1.CheckInterface() 中打印的文本不会显示(转到 1.9 alpha 1)。

$ go run main.go 
panic: interface conversion: iface.foo is not interface { iface.unexported() }: missing method unexported
fatal error: panic on system stack

runtime stack:
runtime.throw(0x4ba54a, 0x15)
	/go/src/runtime/panic.go:605 +0x95 fp=0x7fff6c62f698 sp=0x7fff6c62f678 pc=0x4272e5
panic(0x4a0d60, 0xc42000e000)
	/go/src/runtime/panic.go:420 +0x7b6 fp=0x7fff6c62f740 sp=0x7fff6c62f698 pc=0x427126
runtime.additab(0x51c120, 0x1)
	/go/src/runtime/iface.go:131 +0x63e fp=0x7fff6c62f820 sp=0x7fff6c62f740 pc=0x40c3ee
runtime.itabsinit()
	/go/src/runtime/iface.go:156 +0x84 fp=0x7fff6c62f870 sp=0x7fff6c62f820 pc=0x40c4c4
runtime.schedinit()
	/go/src/runtime/proc.go:476 +0x73 fp=0x7fff6c62f8b0 sp=0x7fff6c62f870 pc=0x429c53
runtime.rt0_go(0x7fff6c62f8e8, 0x1, 0x7fff6c62f8e8, 0x0, 0x0, 0x1, 0x7fff6c6303db, 0x0, 0x7fff6c630417, 0x7fff6c63042d, ...)
	/go/src/runtime/asm_amd64.s:175 +0x1eb fp=0x7fff6c62f8b8 sp=0x7fff6c62f8b0 pc=0x44e9db
exit status 2
6qqygrtg

6qqygrtg2#

问题是 interface { unexported() } 在不同包中描述时会显示不同的类型,因为 unexported 是一个未导出的标识符。然而,我们为两者生成的运行时类型描述符都具有一个链接器符号 type.interface { iface.unexported() }。也就是说,它是包名限定的,而不是包路径限定的。它还被标记为 dupok,因此链接器无法检测到冲突。

这里的修复方法是我们需要通过它们的包路径来限定这些方法名,而不仅仅是它们的包名。

fslejnso

fslejnso3#

这显然是不希望看到的,但我认为在发布周期的后期修复这个问题过于微妙。我们显然已经习惯了1.0版本中的这个问题,所以我认为可以再等一个版本再进行修复。

2wnc66cl

2wnc66cl4#

https://golang.org/cl/106175提到了这个问题:cmd/compile: omit unnecessary interface method expression wrappers

oiopk7p5

oiopk7p55#

这个问题一直存在。显然我们应该修复它,但它也不是发布障碍。

相关问题