Go version
go version go1.25rc2 darwin/arm64
Output of go env
in your module/workspace:
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE='on'
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN='***'
GOCACHE='***'
GOCACHEPROG=''
GODEBUG=''
GOENV='***'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/6c/_j2w7w9j23db85y594fl2xmc0000gn/T/go-build3349238838=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='***'
GOMODCACHE='***'
GONOPROXY='***'
GONOSUMDB='***'
GOOS='darwin'
GOPATH='***'
GOPRIVATE='***'
GOPROXY='***'
GOROOT='***'
GOSUMDB='sum.golang.google.cn'
GOTELEMETRY='local'
GOTELEMETRYDIR='***'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='***'
GOVCS=''
GOVERSION='go1.25rc2'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
package main
import (
"reflect"
"unsafe"
)
var stringTypePointer = reflect.ValueOf(reflect.TypeFor[string]()).Pointer()
func main() {
t := reflect.TypeOf(0)
(*[2]uintptr)(unsafe.Pointer(&t))[1] = stringTypePointer
println(t.String())
}
What did you see happen?
Without optimization:
>> go run -gcflags='all=-l -N' main.go
string
Default:
>> go run main.go
int
What did you expect to see?
string
is correct.
Comment From: mateusz834
Not sure how to judge the use of unsafe here, but:
func main() {
stringTypePointer := reflect.ValueOf(reflect.TypeFor[string]()).Pointer()
t := reflect.TypeOf(0)
(*[2]uintptr)(unsafe.Pointer(&t))[1] = stringTypePointer
call(t.String()) // int
call(t.String()) // string
}
//go:noinline
func call(s string) {
fmt.Println(s)
}
# live at call to Value.Pointer:
v146 00044 (10) CALL reflect.Value.Pointer(SB)
v152 00045 (?) NOP
v119 00046 (+11) LEAQ go:itab.*reflect.rtype,reflect.Type(SB), DX
v222 00047 (11) MOVQ DX, main.t-16(SP)
v195 00048 (+12) MOVQ AX, main.t-8(SP) # string type
v199 00049 (+13) TESTB AX, (DX)
v202 00050 (13) MOVQ go:itab.*reflect.rtype,reflect.Type+296(SB), DX
v109 00051 (13) LEAQ type:int(SB), AX # WRONG!
v203 00052 (+13) PCDATA $1, $1
# live at indirect call: t
v203 00053 (+13) CALL DX
# live at call to call: t
v206 00054 (13) CALL main.call(SB)
I believe that the compiler should not generate the v109 LEAQ
.
It seems to happen in late opt
:
-v88 (179) = Load <*abi.Type> v95 v155 (~r0.data[*uint8])
-v192 (11) = Phi <*uint8> v131 v88 (~r0.data[*uint8], ~r0.data[*uint8])
+v149 (?) = Addr <*uint8> {type:int} v3 (i.type[uintptr])
+v192 (11) = Phi <*uint8> v131 v149 (~r0.data[*uint8], ~r0.data[*uint8])
v203 (+13) = InterCall <*uint8,int,mem> {AuxCall{nil}} [8] v202 v192 v195
Comment From: cuonglm
git bisect points to https://go-review.googlesource.com/c/go/+/632176.
cc @randall77 @andreybokhanko
Comment From: randall77
I'm afraid this code is type-punning uintptr
and unsafe.Pointer
in ways that are not allowed.
In particular, the write in (*[2]uintptr)(unsafe.Pointer(&t))[1] = stringTypePointer
is writing a uintptr
to a slot that is typed as an unsafe.Pointer
. That is not allowed. Not the cause of this issue, but doing it this way means a required write barrier is missing, which could cause all sorts of GC corruption later on. For this issue, the compiler assumes that a uintptr
and an unsafe.Pointer
can never have the same address. That allows us to reorder this write with a subsequent read.
This code works:
package main
import (
"reflect"
"unsafe"
)
var stringTypePointer = reflect.ValueOf(reflect.TypeFor[string]()).UnsafePointer()
func main() {
t := reflect.TypeOf(0)
(*[2]unsafe.Pointer)(unsafe.Pointer(&t))[1] = stringTypePointer
println(t.String())
}
Not that I would recommend that either. You are reaching into the internals of interfaces. That could break at any time.
Comment From: AlphaLxy
Although StringHeader
has been deprecated, another type of error can be constructed for the reasons described above.
package main
import (
"reflect"
"unsafe"
)
var A = "A"
var dataToB = unsafe.Pointer(unsafe.StringData("B"))
func main() {
header := *(*reflect.StringHeader)(unsafe.Pointer(&A))
(*[2]unsafe.Pointer)(unsafe.Pointer(&header))[0] = dataToB
printString(header)
}
func printString(header reflect.StringHeader) {
println(*(*string)(unsafe.Pointer(&header)))
}
Comment From: ianlancetaylor
As the docs say, reflect.StringHeader
cannot be used safely. This program is an example of why that is true.
Comment From: AlphaLxy
In the first example, the uintptr
values point to memory regions that are guaranteed not to be garbage collected.
Although this usage is incorrect, it has been working fine in previous versions (<= 1.24). It seems to be a breaking change.