Description
Background
The documentation for unsafe.Pointer
currently lists six valid conversion patterns:
-
“Conversion of a
*T1
toPointer
to*T2
… [p]rovided thatT2
is no larger thanT1
and that the two share an equivalent memory layout.” -
“Conversion of a
Pointer
to auintptr
(but not back toPointer
).” -
“Conversion of a
Pointer
to auintptr
and back, with arithmetic … in the same expression, with only the intervening arithmetic between them.” -
“Conversion of a
Pointer
to auintptr
when callingsyscall.Syscall
… [or] in the argument list of a call to a function implemented in assembly.” (Compare proposal: unsafe: clarify unsafe.Pointer rules for package syscall #34684.) -
“Conversion of the result of
reflect.Value.Pointer
orreflect.Value.UnsafeAddr
fromuintptr
toPointer
… immediately after making the call, in the same expression.” -
“Conversion of a
reflect.SliceHeader
orreflect.StringHeader
Data
field to or fromPointer
… when interpreting the content of an actual slice or string value.”
The unsafeptr
check provided by cmd/vet
warns about uses that do not follow the above patterns. However, it currently flags many violations in x/sys/unix
(see #41205) when addresses returned by system calls such as mmap
are converted to Go pointers, and in code generated by ebitengine/purego (see #56487) when hard-coded addresses provided by the operating system or linker are converted to Go pointers. In both cases, the program is attempting to create Go pointers that refer to known-valid addresses that are not managed by the Go runtime; notably, the compiler's -d=checkptr
mode does not flag them as invalid at run-time.
Ideally, the unsafe.Pointer
documentation, the unsafeptr
check in cmd/vet
, the -d=checkptr
mode in cmd/compile
, and the real-world usage in syscall
, x/sys
, and similar low-level libraries should all agree on what is valid. This proposal aims to narrow that gap.
Proposal
I propose that we add another allowed case in the unsafe.Pointer
documentation:
(7) Conversion of a
uintptr
toPointer
when the address is allocated outside of Go.A
uintptr
containing a valid memory address allocated outside of Go
(such as by a system call) may be converted toPointer
.
The address must remain valid for as long as any Go pointer (of typePointer
or any other pointer type) refers to it.
The address must remain unavailable to the Go runtime (for example, due to an
allocation usingcgo
orsyscall.Mmap
) for as long as any Go pointer
(of typePointer
or any other pointer type) refers to it.
[edited per https://github.com/golang/go/issues/58625#issuecomment-1440188404\]The uintptr constant
0
may be converted toPointer
.
The resultingPointer
has the valuenil
.addr, _, err := syscall.Syscall(…) … p := unsafe.Pointer(addr)
The unsafeptr
check in cmd/vet
would be changed to allow the new case, resolving the warnings in x/sys
and ebitengine/purego
.
The compiler's -d=checkptr
mode would check conversions from uintptr
to unsafe.Pointer
. The conversion may throw at run-time if:
- The
uintptr
refers to an address managed by Go that is not explicitly pinned (runtime: provide Pinner API for object pinning #46787), or theuintptr
refers to an invalid (unmapped) nonzero address.
[edit: per unsafe: allow conversion of uintptr to unsafe.Pointer when it points to non-Go memory #58625 (comment), unmapped addresses should be allowed as long as they cannot become Go addresses]
Alternatives
The vet
warning can be worked around today by relying on a liberal reading of the “equivalent memory layout” rule, rewriting
var addr uintptr = …
p := unsafe.Pointer(addr)
(https://go.dev/play/p/ZWZxv6URqTW) as
var addr uintptr = …
p := *(*unsafe.Pointer)(unsafe.Pointer(&addr))
(https://go.dev/play/p/Wh5f8k0_yyR), on the theory that the memory layout of a uintptr
must be in some sense “equivalent” to the memory layout of unsafe.Pointer
. However, I believe that such a rewrite does not capture the intent of the code as accurately, and should not be necessary.
(CC @golang/runtime)
Metadata
Metadata
Assignees
Type
Projects
Status
Status