绕过Go的反射类型安全系统给未导出的结构体成员赋值

陪她去流浪 桃子 阅读次数:33

反射的时候尝试给未导出的结构体成员(不管是自己包的、还是别人包的),都不被Go标准库的reflect反射库允许,会直接崩溃。

如下代码,尝试给name反射赋值会runtime panic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type User struct {
	name string
}

func main() {
	var user User
	ra := reflect.ValueOf(&user).Elem()
	field := ra.FieldByName(`name`)
	field.SetString(`peach`)
}

错误如下:

1
panic: reflect: reflect.Value.SetString using value obtained using unexported field

我要用反射给我自己的包的成员赋值,并且不想它们被公开导出。1 难道真没有办法了?有的:unsafe.Pointer一顿操作就完事了,管它“Package unsafe contains operations that step around the type safety of Go programs.”呢。因为这其实是reflect包的软限制,最暴力的办法大不了把reflect拎出来魔改一下就好了,但是我并不想因为一个简单需求就如此大动刀戈。[doge]

解法:虽然不让Set,但是是允许取地址(take address) 的,即func UnsafeAddr() uintptr。取到地址后,那我就像C语言一样deref一下就能随便写了。

1
2
3
4
5
6
7
8
9
var user User
ra := reflect.ValueOf(&user).Elem()
field := ra.FieldByName(`name`)

// ptr := &user.name
ptr := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr()))
// *ptr = "peach"
ptr.Elem().SetString(`peach`)
fmt.Println(user.name)

非常unsafe的代码,使劲造吧。[doge]


  1. 真实的需求是:我给我的多个私有结构体成员标注了名为css的tag,我要用反射执行document.querySelector查DOM树并赋值给它们,减少初始化时手写太多代码。 ^