Skip to content

cmd/vet: warn about variables/values of type reflect.{Slice,String}Header #40701

Closed
@mdempsky

Description

@mdempsky

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).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions