Description
Here are three ways I commonly see developers misuse reflect.SliceHeader
/ reflect.StringHeader
:
package p
import (
"reflect"
"unsafe"
)
// Explicitly allocating a variable of type reflect.SliceHeader.
func a(p *byte, n int) []byte {
var sh reflect.SliceHeader
sh.Data = uintptr(unsafe.Pointer(p))
sh.Len = n
sh.Cap = n
return *(*[]byte)(unsafe.Pointer(&sh))
}
// Implicitly allocating a variable of type reflect.SliceHeader.
func b(p *byte, n int) []byte {
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)),
Len: n,
Cap: n,
}))
}
// Use reflect.SliceHeader as a composite literal value.
func c(p *byte, n int) []byte {
var res []byte
*(*reflect.SliceHeader)(unsafe.Pointer(&res)) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)),
Len: n,
Cap: n,
}
return res
}
All three of these can lead to memory corruption, as escape analysis analyzes these as "p does not escape" (rather than "p leaks to result"), but none of them are currently diagnosed by either cmd/vet or checkptr.
I propose cmd/vet should look for variables and expressions of type reflect.SliceHeader
or reflect.StringHeader
(as opposed to *reflect.SliceHeader
or *reflect.StringHeader
) and warn about them. There should be no false positives for this, and it's consistent with the unsafeptr check that cmd/vet already has.
--
An alternative approach would be to outright disallow reflect.SliceHeader
and reflect.StringHeader
to be used except as pointed-to types. (Notably, this would be similar to the //go:cgo_incomplete
directive suggested in #40507 to prevent misuse of incomplete C struct types.)
I think this would be consistent with Go 1 compat. Go 1 compat is about guaranteeing that programs continue to build and run correctly in the future. I think if we're okay with these programs not running correctly today, we should be okay with them not building either, and I expect users would prefer compilation errors instead of silent memory corruption at runtime.
But perhaps a more friendly approach would be a cmd/vet warning in Go 1.N, and then make it into a compiler error in Go 1.N+1 (and maybe gated by Go language version specified in go.mod
).