Repository: go
Issue: unsafe: allow conversion of uintptr to unsafe.Pointer when it points to non-Go memory
Labels: Proposal,Proposal-Accepted,compiler/runtime

### Background

The documentation for [`unsafe.Pointer`](https://pkg.go.dev/unsafe@go1.20.1) currently lists six valid conversion patterns:

1. “Conversion of a `*T1` to `Pointer` to `*T2` … [p]rovided that `T2` is no larger than `T1` and that the two share an equivalent memory layout.”

2. “Conversion of a `Pointer` to a `uintptr` (but not back to `Pointer`).”

3. “Conversion of a `Pointer` to a `uintptr` and back, with arithmetic … in the same expression, with only the intervening arithmetic between them.”

4. “Conversion of a `Pointer` to a `uintptr` when calling `syscall.Syscall` … [or] in the argument list of a call to a function implemented in assembly.” (Compare #34684.)

5. “Conversion of the result of `reflect.Value.Pointer` or `reflect.Value.UnsafeAddr` from `uintptr` to `Pointer` … immediately after making the call, in the same expression.”

6. “Conversion of a `reflect.SliceHeader` or `reflect.StringHeader` `Data` field to or from `Pointer` … when interpreting the content of an actual slice or string value.”

The `unsafeptr` check provided by [`cmd/vet`](https://pkg.go.dev/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:

> <strong>(7) Conversion of a `uintptr` to `Pointer` when the address is allocated outside of Go.</strong>
> 
> A `uintptr` containing a valid memory address allocated outside of Go
> (such as by a system call) may be converted to `Pointer`.
> <strike>The address must remain valid for as long as any Go pointer (of type `Pointer`
> or any other pointer type) refers to it.</strike>
> The address must remain unavailable to the Go runtime (for example, due to an
> allocation using `cgo` or `syscall.Mmap`) for as long as any Go pointer
> (of type `Pointer` 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 to `Pointer`.
> The resulting `Pointer` has the value `nil`.
> 
> ```go
> 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 (#46787), or
- <strike>the `uintptr` refers to an invalid (unmapped) nonzero address.</strike>
   \[edit: per https://github.com/golang/go/issues/58625#issuecomment-1440188404, 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
```go
var addr uintptr = …
p := unsafe.Pointer(addr)
```
(https://go.dev/play/p/ZWZxv6URqTW) as
```go
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)