What version of Go are you using (go version
)?
$ go version go version go1.19.2 darwin/amd64
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (go env
)?
go env
Output
$ go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/Users/hajimehoshi/Library/Caches/go-build" GOENV="/Users/hajimehoshi/Library/Application Support/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOINSECURE="" GOMODCACHE="/Users/hajimehoshi/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="darwin" GOPATH="/Users/hajimehoshi/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64" GOVCS="" GOVERSION="go1.19.2" GCCGO="gccgo" GOAMD64="v1" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="/Users/hajimehoshi/test/vet/go.mod" GOWORK="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/60/khbk2xqn1c5bml1byjn89dwc0000gn/T/go-build1170881183=/tmp/go-build -gno-record-gcc-switches -fno-common"
What did you do?
package main
import (
"unsafe"
)
func main() {
var foo uintptr = 0x12345678
_ = unsafe.Slice((*byte)(unsafe.Pointer(foo)), 0)
}
module foo
go 1.19
and run go vet
What did you expect to see?
No complaints
What did you see instead?
$ go vet
# foo
./main.go:9:27: possible misuse of unsafe.Pointer
When reflect.SliceHeader
is used, there is no such a problem since the Data
field is uintptr
and we could use a uintptr value without warnings. So this sounds like a kind of regression.
Comment From: D1CED
When reading the documentation for unsafe.Pointer
it states that
(2) Conversion of a Pointer to a uintptr (but not back to Pointer). ... Conversion of a uintptr back to Pointer is not valid in general. ... The remaining patterns enumerate the only valid conversions from uintptr to Pointer.
There is no pattern listed that allows what you did in the example. Hence the given conversion of uintptr to unsafe.Pointer is invalid. You are creating a string that points to no sensible content (from the pov of the Go runtime).
But lets look what you actually trying to do.
// The lifetime of the underlying C string is shorter than s [1].
// Copy the bytes to ensure that the returned string's lifetime is unrelated to s.
// [1] https://developer.apple.com/documentation/foundation/nsstring/1411189-utf8string?language=objc
src := unsafe.Slice((*byte)(unsafe.Pointer(s.Send(sel_UTF8String))), s.Send(sel_length))
As can be extracted from the comment you attempt to convert a "C string" to a Go string. Thankfully there is already a function that does this for you as part of cgo in C.GoString.
In the second case
func (b Buffer) CopyToContents(data unsafe.Pointer, lengthInBytes uintptr) {
contents := b.buffer.Send(sel_contents)
copy(unsafe.Slice((*byte)(unsafe.Pointer(contents)), lengthInBytes), unsafe.Slice((*byte)(data), lengthInBytes))
if runtime.GOOS != "ios" {
b.buffer.Send(sel_didModifyRange, 0, lengthInBytes)
}
}
you copy the contents of a buffer from the Objective-C heap to Go. There is also a cgo function for that called C.GoBytes but that would return a newly allocated copy so instead I'd suggest you simply use C.memcpy instead of a Go copy here. This way you avoid having to create Go slices.
Hope this was helpful. J.M.H.
Comment From: hajimehoshi
As can be extracted from the comment you attempt to convert a "C string" to a Go string. Thankfully there is already a function that does this for you as part of cgo in C.GoString.
Unfortunately Cgo is not our option. We aim to avoid Cgo by using purego. So we want to treat a uintptr value as a data source. We could do it with reflect.SliceHeader
without warnings, which is my point.
Comment From: D1CED
I'm pretty sure the reflect.SliceHeader
was also invalid as you also create a Go slice that points neither into the Go heap or static memory. Don't get me wrong both versions probably work just fine in the current implementation but it is like undefined behavior in C.
As an alternative you can copy each byte manually but this will likely perform worse because these loops will not be vectorized/unrolled.
So to get an optimized copy you have options 1. write your own copy function (best in assembly), 2. link to an internal Go runtime function (runtime.memmove, runtime.duffcopy) or 3. link to an (Objective-)C function (memcpy).
Comment From: hajimehoshi
I'm pretty sure the reflect.SliceHeader was also invalid as you also create a Go slice that points neither into the Go heap or static memory. Don't get me wrong both versions probably work just fine in the current implementation but it is like undefined behavior in C.
In the above NSString case, the value points to a C heap, but the type is uintptr. A uintptr as a C address is very often used e.g. when low-level APIs like syscall.Syscall is used. It's OK that this can be like an undefined behavior in C, as this is an 'unsafe' package.
Using an alternative way to just avoid go-vet warnings doesn't solve the root problem. Actually if we just wanted to avoid warnings, we could use reflect.SliceHeader
. The root problem here is that go-vet's warnings are false positives, and there was not a problem with the old API. We should be able to use unsafe.Slice
without warnings.
Comment From: ianlancetaylor
It's true that the unsafe package, and therefore vet, has an exception for reflect.SliceHeader
. It might be appropriate for vet to not warn about passing a converted uintptr
to unsafe.Slice
, as that operation is clearly unsafe, and that is one of the uses of unsafe.Slice
.
CC @adonovan
Comment From: bcmills
It's true that the unsafe package, and therefore vet, has an exception for
reflect.SliceHeader
.
The exception for reflect.SliceHeader
does not cover this use-case, though: the lack of a vet
warning seems to be a false-negative. Per https://pkg.go.dev/unsafe#Slice, “SliceHeader and StringHeader are only valid when interpreting the content of an actual slice or string value. … In this usage hdr.Data is really an alternate way to refer to the underlying pointer in the string header, not a uintptr variable itself.”
Note that it is, in fact, possible to silence this vet
warning when using unsafe.Slice
, by adding another level of indirection: essentially, telling the compiler “you may think this variable is a uintptr
but it's actually a *byte
.”
var foo uintptr = 0x12345678
_ = unsafe.Slice(*(**byte)(unsafe.Pointer(&foo)), 0)
Comment From: ianlancetaylor
I agree that the use is unsafe. That said, one of the goals of unsafe.Slice
is to permit people to create slices out of things that aren't ordinary pointers. The vet warnings for unsafe.Pointer
in general are useful because it's easy to misunderstand unsafe.Pointer
. But passing a non-pointer to unsafe.Slice
by converting it to unsafe.Pointer
is not, in my opinion, one of the cases that is easy to misunderstand. If the call to unsafe.Slice
works at all, then it's probably doing what the user expects. The vet warning doesn't help.
Comment From: bcmills
The vet
warning here is from converting a variable of type uintptr
to a variable of type unsafe.Pointer
. It is almost completely orthogonal to unsafe.Slice
.
Moreover, silencing the unsafe.Pointer
warning in general for arguments to unsafe.Slice
would miss real misuses: https://go.dev/play/p/xprDEzQCM7G
Especially given how trivial the workaround is, I don't think it's worth trying to fine-tune the unsafe.Pointer
definition (and the associated vet
warnings) with more special cases for unsafe.Slice
.