-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit adds logic to gracefully handle the new internal reflect.Value structure on tip as of golang commit 82f48826c6c7 as well as the internal reflect.Value flag bit changes as of golang commit 90a7c3c86944. It accomplishes this by doing some inspection at init time and choosing the appropriate offsets and flag positions accordingly. There was some previous logic which dealt with a similar issue for golang commit ecccf07e7f9d. However, since the more recent commits essentially reverted the change and also modify the flag bit positions, it made more sense to rework the detection logic. In particular, the new logic examines the size of the reflect.Value struct to determine the difference and extracts the kind from the flags to determine if the flags have been changed. As a result, this commit allows spew to work properly with tip all the back to Go 1.0.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,58 +25,71 @@ import ( | |
"unsafe" | ||
) | ||
|
||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the internal | ||
// reflect.Value fields. | ||
var offsetPtr, offsetScalar, offsetFlag uintptr | ||
|
||
// reflectValueOld mirrors the struct layout of the reflect package Value type | ||
// before golang commit ecccf07e7f9d. | ||
var reflectValueOld struct { | ||
typ unsafe.Pointer | ||
val unsafe.Pointer | ||
flag uintptr | ||
} | ||
const ( | ||
// ptrSize is the size of a pointer on the current arch. | ||
ptrSize = unsafe.Sizeof((*byte)(nil)) | ||
) | ||
|
||
// reflectValueNew mirrors the struct layout of the reflect package Value type | ||
// after golang commit ecccf07e7f9d. | ||
var reflectValueNew struct { | ||
typ unsafe.Pointer | ||
ptr unsafe.Pointer | ||
scalar uintptr | ||
flag uintptr | ||
} | ||
var ( | ||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the | ||
// internal reflect.Value fields. These values are valid before golang | ||
// commit ecccf07e7f9d which changed the format. The are also valid | ||
// after commit 82f48826c6c7 which changed the format again to mirror | ||
// the original format. Code in the init function updates these offsets | ||
// as necessary. | ||
offsetPtr = uintptr(ptrSize) | ||
offsetScalar = uintptr(0) | ||
offsetFlag = uintptr(ptrSize * 2) | ||
|
||
// flagKindWidth and flagKindShift indicate various bits that the | ||
// reflect package uses internally to track kind information. | ||
// | ||
// flagRO indicates whether or not the value field of a reflect.Value is | ||
// read-only. | ||
// | ||
// flagIndir indicates whether the value field of a reflect.Value is | ||
// the actual data or a pointer to the data. | ||
// | ||
// These values are valid before golang commit 90a7c3c86944 which | ||
// changed their positions. Code in the init function updates these | ||
// flags as necessary. | ||
flagKindWidth = uintptr(5) | ||
flagKindShift = uintptr(flagKindWidth - 1) | ||
flagRO = uintptr(1 << 0) | ||
flagIndir = uintptr(1 << 1) | ||
) | ||
|
||
func init() { | ||
// Older versions of reflect.Value stored small integers directly in the | ||
// ptr field (which is named val in the older versions). Newer versions | ||
// added a new field named scalar for this purpose which unfortuantely | ||
// comes before the flag field. Further the new field is before the | ||
// flag field, so the offset of the flag field is different as well. | ||
// ptr field (which is named val in the older versions). Versions | ||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named | ||
// scalar for this purpose which unfortunately came before the flag | ||
// field, so the offset of the flag field is different for those | ||
// versions. | ||
// | ||
// This code constructs a new reflect.Value from a known small integer | ||
// and checks if the val field within it matches. When it matches, the | ||
// old style reflect.Value is being used. Otherwise it's the new style. | ||
v := 0xf00 | ||
vv := reflect.ValueOf(v) | ||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + | ||
unsafe.Offsetof(reflectValueOld.val)) | ||
|
||
// Assume the old style by default. | ||
offsetPtr = unsafe.Offsetof(reflectValueOld.val) | ||
offsetScalar = 0 | ||
offsetFlag = unsafe.Offsetof(reflectValueOld.flag) | ||
|
||
// Use the new style offsets if the ptr field doesn't match the value | ||
// since it must be in the new scalar field. | ||
if int(*(*uintptr)(upv)) != v { | ||
offsetPtr = unsafe.Offsetof(reflectValueNew.ptr) | ||
offsetScalar = unsafe.Offsetof(reflectValueNew.scalar) | ||
offsetFlag = unsafe.Offsetof(reflectValueNew.flag) | ||
// and checks if the size of the reflect.Value struct indicates it has | ||
// the scalar field. When it does, the offsets are updated accordingly. | ||
vv := reflect.ValueOf(0xf00) | ||
if unsafe.Sizeof(vv) == (ptrSize * 4) { | ||
offsetScalar = ptrSize * 2 | ||
offsetFlag = ptrSize * 3 | ||
} | ||
} | ||
|
||
// flagIndir indicates whether the value field of a reflect.Value is the actual | ||
// data or a pointer to the data. | ||
const flagIndir = 1 << 1 | ||
// Commit 90a7c3c86944 changed the flag positions such that the low | ||
// order bits are the kind. This code extracts the kind from the flags | ||
// field and ensures it's the correct type. When it's not, the flag | ||
// order has been changed to the newer format, so the flags are updated | ||
// accordingly. | ||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) | ||
upfv := *(*uintptr)(upf) | ||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift) | ||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) { | ||
flagKindShift = 0 | ||
flagRO = 1 << 5 | ||
flagIndir = 1 << 6 | ||
} | ||
} | ||
|
||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses | ||
// the typical safety restrictions preventing access to unaddressable and | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
davecgh
Author
Owner
|
||
|
2 comments
on commit 1288542
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice work!
This works in go1.4beta1
. I will apply the same change to my (exported) code for this functionality if you don't have objections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I have no objections as I'm sure you already have the license anyways.
A question about the
unsafeReflectValue
func. When you say "unaddressable and unexported data", is that one thing, or are there two separate concepts of "unaddressable data" and "unexported data"? I'm only aware of the latter, so if there is something elseunsafeReflectValue
does except allow access to unexported fields, can you give an example please?If all
unsafeReflectValue
does is allow access to unexported fields, I want to ask another question. Have you considered and what are your thoughts on Roger Peppe's (@rogpeppe) approach of modifying the reflect.Value value, instead of "generating a new unprotected (unsafe) reflect.Value"?I don't have the original source, but here's a copy that I played/tested here, to show you what I'm referring to.
Anyway, it's probably equivalent and either approach works, so it's not a big deal. But I wanted to ask and perhaps learn something new. Thank you.