3.如何通过 hack 的方式设置 unaddressable 的值
var x = 47 v := reflect.ValueOf(&x).Elem() fmt.Printf("原始值: %d, CanSet: %v\n", v.Int(), v.CanSet()) // 47, false v.Set(reflect.ValueOf(50))注意这里传入给 reflect.ValueOf 的是 x 的指针 &x, 所以这个 Value 值是 addresable 的,我们可以进行赋值。
var x = 47 // 堆代码 duidaima.com v := reflect.ValueOf(x) fmt.Printf("Original value: %d, CanSet: %v\n", v.Int(), v.CanSet()) // 47, false v.Set(reflect.ValueOf(50))可以看到 panic:
Original value: 47, CanSet: false panic: reflect: reflect.Value.Set using unaddressable value goroutine 1 [running]: reflect.flag.mustBeAssignableSlow(0x1400012c410?) /usr/local/go/src/reflect/value.go:272 +0x74 reflect.flag.mustBeAssignable(...) /usr/local/go/src/reflect/value.go:259 reflect.Value.Set({0x104e13e40?, 0x104e965b8?, 0x104dec7e6?}, {0x104e13e40?, 0x104e0ada0?, 0x2?}) /usr/local/go/src/reflect/value.go:2319 +0x58 main.setUnaddressableValue() /Users/smallnest/workspace/study/private/main.go:27 +0x1c0 main.main() /Users/smallnest/workspace/study/private/main.go:18 +0x1c exit status 2文章最后我会介绍如何通过 hack 的方式解决这个问题。接下来我再介绍访问私有字段的问题。
package model type Person struct { Name string age int } func NewPerson(name string, age int) Person { return Person{ Name: name, age: age, // unexported field } } type Teacher struct { Name string Age int // exported field } func NewTeacher(name string, age int) Teacher { return Teacher{ Name: name, Age: age, } }注意Person的age字段是私有的,Teacher的Age字段是公开的。
package main; import ( "fmt" "reflect" "unsafe" "github.com/smallnest/private/model" ) func main() { p := model.NewPerson("Alice", 30) fmt.Printf("Person: %+v\n", p) // fmt.Println(p.age) // error: p.age undefined (cannot refer to unexported field or method age) t := model.NewTeacher("smallnest", 18) fmt.Printf("Teacher: %+v\n", t) // Teacher: {Name:Alice Age:30} }那么真的就无法访问了吗?也不一定,我们可以通过反射的方式访问私有字段:
p := model.NewPerson("Alice", 30) age := reflect.ValueOf(p).FieldByName("age") fmt.Printf("原始值: %d, CanSet: %v\n", age.Int(), age.CanSet()) // 30, false运行这个程序,可以看到我们获得了这个私有字段age的值:
原始值: 30, CanSet: false panic: reflect: reflect.Value.SetInt using value obtained using unexported field goroutine 1 [running]: reflect.flag.mustBeAssignableSlow(0x2?) /usr/local/go/src/reflect/value.go:269 +0xb4 reflect.flag.mustBeAssignable(...) /usr/local/go/src/reflect/value.go:259 reflect.Value.SetInt({0x1050ac0c0?, 0x14000118f20?, 0x1050830a8?}, 0x32) /usr/local/go/src/reflect/value.go:2398 +0x44 main.setUnexportedField() /Users/smallnest/workspace/study/private/main.go:37 +0x1a0 main.main() /Users/smallnest/workspace/study/private/main.go:18 +0x1c exit status 2实际上,reflect.Value的Set方法会做一系列的检查,包括检查是否是addressable的,以及是否是 exported 的字段:
func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() // do not let unexported x leak ... }v.mustBeAssignable()检查是否是addressable的,而且是 exported 的字段:
func (f flag) mustBeAssignable() { if f&flagRO != 0 || f&flagAddr == 0 { f.mustBeAssignableSlow() } } func (f flag) mustBeAssignableSlow() { if f == 0 { panic(&ValueError{valueMethodName(), Invalid}) } // Assignable if addressable and not read-only. if f&flagRO != 0 { panic("reflect: " + valueMethodName() + " using value obtained using unexported field") } if f&flagAddr == 0 { panic("reflect: " + valueMethodName() + " using unaddressable value") } }f&flagRO == 0 代表是可写的(exported),f&flagAddr != 0 代表是addressable的,当这两个条件任意一个不满足时,就会报错。
type Mutex struct { state int32 sema uint32 }正常情况下你只能通过Mutex.Lock和Mutex.Unlock来间接的修改这两个字段。
func setPrivateField() { var mu sync.Mutex mu.Lock() field := reflect.ValueOf(&mu).Elem().FieldByName("state") state := field.Interface().(*int32) fmt.Println(*state) // ❶ flagField := reflect.ValueOf(&field).Elem().FieldByName("flag") flagPtr := (*uintptr)(unsafe.Pointer(flagField.UnsafeAddr())) // 修改flag字段的值 *flagPtr &= ^uintptr(flagRO) // ❷ field.Set(reflect.ValueOf(int32(0))) mu.Lock() // ❸ fmt.Println(*state) } type flag uintptr const ( flagKindWidth = 5 // there are 27 kinds flagKindMask flag = 1<<flagKindWidth - 1 flagStickyRO flag = 1 << 5 flagEmbedRO flag = 1 << 6 flagIndir flag = 1 << 7 flagAddr flag = 1 << 8 flagMethod flag = 1 << 9 flagMethodShift = 10 flagRO flag = flagStickyRO | flagEmbedRO )❶ 处我们已经介绍过了,访问私有字段的值,这里会打印出 1 ❶ 处我们清除了flag字段的flagRO标志位,这样就不会报reflect: reflect.Value.SetInt using value obtained using unexported field错误了 ❸ 处不会导致二次加锁带来的死锁,因为state字段的值已经被修改为 0 了,所以不会阻塞。最后打印结果还是 1。
func setUnexportedField2() { alice := model.NewPerson("Alice", 30) bob := model.NewTeacher("Bob", 40) bobAgent := reflect.ValueOf(&bob).Elem().FieldByName("Age") aliceAge := reflect.ValueOf(&alice).Elem().FieldByName("age") bobAgent.Set(aliceAge) // ❹ }注意 ❹ 处,我们尝试把alice的私有字段age的值赋值给bob的公开字段Age,这里会报错:
panic: reflect: reflect.Value.Set using value obtained using unexported field goroutine 1 [running]: reflect.flag.mustBeExportedSlow(0x1400012a000?) /usr/local/go/src/reflect/value.go:250 +0x70 reflect.flag.mustBeExported(...) /usr/local/go/src/reflect/value.go:241 reflect.Value.Set({0x102773a60?, 0x1400012a028?, 0x60?}, {0x102773a60?, 0x1400012a010?, 0x1027002b8?}) /usr/local/go/src/reflect/value.go:2320 +0x88 main.setUnexportedField2() /Users/smallnest/workspace/study/private/main.go:50 +0x168 main.main() /Users/smallnest/workspace/study/private/main.go:18 +0x1c exit status 2原因alice的age值被识别为私有字段,它是不能用来赋值给公开字段的。
func setUnexportedField2() { alice := model.NewPerson("Alice", 30) bob := model.NewTeacher("Bob", 40) bobAgent := reflect.ValueOf(&bob).Elem().FieldByName("Age") aliceAge := reflect.ValueOf(&alice).Elem().FieldByName("age") // 修改flag字段的值 flagField := reflect.ValueOf(&aliceAge).Elem().FieldByName("flag") flagPtr := (*uintptr)(unsafe.Pointer(flagField.UnsafeAddr())) *flagPtr &= ^uintptr(flagRO) // ❺ bobAgent.Set(reflect.ValueOf(50)) bobAgent.Set(aliceAge) // ❻ }❺ 处我们修改了aliceAge的flag字段,去掉了flagRO标志位,这样就不会报错了,❻ 处我们成功的把alice的私有字段age的值赋值给bob的公开字段Age。
func setUnaddressableValue() { var x = 47 v := reflect.ValueOf(x) fmt.Printf("原始值: %d, CanSet: %v\n", v.Int(), v.CanSet()) // 47, false // v.Set(reflect.ValueOf(50)) flagField := reflect.ValueOf(&v).Elem().FieldByName("flag") flagPtr := (*uintptr)(unsafe.Pointer(flagField.UnsafeAddr())) // 修改flag字段的值 *flagPtr |= uintptr(flagAddr) // 设置可寻址标志位 fmt.Printf("CanSet: %v\n", v.CanSet()) // true v.SetInt(50) fmt.Printf("修改后的值: %d\n", v.Int()) // 50 }运行这个程序,不会报错,可以看到我们成功的给 unaddressable 的值设置了新的值。