2

What are the problems with using

Approach 1:

func Encode(v *float32) []byte {
    return unsafe.Slice((*byte)(unsafe.Pointer(v)), 4)
}

over

Approach 2

func Encode(v float32) []byte {
    return unsafe.Slice((*byte)(unsafe.Pointer(&v)), 4)
}

Both are run on Little Endian System. The only difference is that Approach 1 accepts a pointer as an argument, while Approach 2 accepts a value as an argument.

I was debugging an issue related to

fatal error: found bad pointer in Go heap (incorrect use of unsafe or cgo?)

found pointer to free object | bad use of unsafe.Pointer?

and I finally found the root cause could be due to Approach 1 usage. Could someone please let me know what could be the issue with Approach 1? Is it generally recommended to use Approach 2?

Please note that float32 is one of the functions. There are other encode functions that accept byte arrays, int8 etc as well.

type Uuid [16]byte

func EncodeUuid(v *Uuid) []byte {
    return unsafe.Slice((*byte)(unsafe.Pointer(v)), UuidSize)
}

This SO answer here mentioned that the original variable could be GC'ed before the Encode() function is called. Attempting to access the memory pointed to by an already freed pointer can result in a "found pointer to free object" error.

Below are the error logs found during execution.

Error Log 1

runtime: marked free object in span 0x7fef20c4c8a8, elemsize=24 freeindex=0 (bad use of unsafe.Pointer? try -d=checkptr)
0xc14a028000 free  unmarked
0xc14a028018 free  unmarked
0xc14a028030 free  unmarked
0xc14a028048 free  unmarked
0xc14a028060 free  unmarked
0xc14a028078 free  marked   zombie
0x000000c14a028078:  0x000000c0003f2800  0x0000000000000800 
0x000000c14a028088:  0x0000000000000800 
0xc14a028090 free  unmarked
0xc14a029fc8 free  unmarked
0xc14a029fe0 free  unmarked
fatal error: found pointer to free object

goroutine 1482447 [running]:
runtime.throw({0x29b9a01?, 0xc14a028090?})
    /usr/lib/golang/src/runtime/panic.go:1047 +0x5d fp=0xc2555a2ae0 sp=0xc2555a2ab0 pc=0x44775d
runtime.(*mspan).reportZombies(0x7fef20c4c8a8)
    /usr/lib/golang/src/runtime/mgcsweep.go:788 +0x2e5 fp=0xc2555a2b60 sp=0xc2555a2ae0 pc=0x435dc5

Error Log 2

runtime: pointer 0xc079e0001d to unallocated span span.base()=0xc079dfe000 span.limit=0xc079e2e010 span.state=0
runtime: found in object at *(0xc032b5c180+0x28)
object=0xc032b5c180 s.base()=0xc032b5c000 s.limit=0xc032b5e000 s.spanclass=20 s.elemsize=128 s.state=mSpanInUse
 *(object+0) = 0x0
 *(object+8) = 0x400000016
 *(object+16) = 0xffffffff00000020
 *(object+24) = 0x3a665e0
 *(object+32) = 0xc0dde3d110
 *(object+40) = 0xc079e0001d <==
 *(object+48) = 0x8000
 *(object+56) = 0x8008
 *(object+64) = 0x0
 *(object+72) = 0x0
 *(object+80) = 0x0
 *(object+88) = 0x2
 *(object+96) = 0x2000
 *(object+104) = 0x6174620
 *(object+112) = 0x101
 *(object+120) = 0x0
fatal error: found bad pointer in Go heap (incorrect use of unsafe or cgo?)

Error Log 3

fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x2 addr=0x450608 pc=0x468c46]

goroutine 1202 [running]:
runtime.throw({0x3f96b81?, 0x0?})
    /usr/local/go/src/runtime/panic.go:1047 +0x5d fp=0xc006433cd0 sp=0xc006433ca0 pc=0x454c7d
runtime.sigpanic()
    /usr/local/go/src/runtime/signal_unix.go:819 +0x369 fp=0xc006433d20 sp=0xc006433cd0 pc=0x46ce09
runtime.(*waitq).enqueue(...)
    /usr/local/go/src/runtime/chan.go:766
runtime.selectgo(0xc006433fb0, 0xc006433ed8, 0x2710?, 0x0, 0x0?, 0x1)
    /usr/local/go/src/runtime/select.go:317 +0x7c6 fp=0xc006433e80 sp=0xc006433d20 pc=0x468c46

Any suggestion would be really helpful!

PS: The issue occurs rarely when the GC kicks in. So in order to have aggressive GC to test the scenario, I am currently using

GODEBUG=invalidptr=1,cgocheck=1,madvdontneed=1 GOGC=2 GOMEMLIMIT=10MiB ./my-executable
blackgreen
  • 34,072
  • 23
  • 111
  • 129
Arjun Sunil Kumar
  • 1,781
  • 3
  • 28
  • 46
  • 2
    I don’t think Encode is directly causing the problem, rather it could be how you are using the result. Can you create a [mre]? – JimB May 17 '23 at 00:45
  • 2
    note that the other answer you linked talks about early garbage collection because the parameter there is a `uintptr`. Your `Encode` function takes a regular `*float32` – blackgreen May 17 '23 at 00:58
  • I don't think converting a `*float` to a `*byte` is legal, the unsafe package allows conversion of `*T1` to `*T2` only if "T2 is no larger than T1 and that the two share an equivalent memory layout". – Quân Anh Mai May 17 '23 at 02:48
  • Also the second approach is no better than `res := make([]byte, 4); binary.LittleEndian.PutUint32(res, math.Float32bits(v))` – Quân Anh Mai May 17 '23 at 02:51
  • Write EncodeUuid as `func EncodeUuid(v *Uuid) []byte { return v[:] }`. I don't know if this will fix the issue, but here's a version of Encode that reduces use of the `unsafe` package: `func Encode(v *float32) []byte { return (*[4]byte)(unsafe.Pointer(v))[:] }`. – Charlie Tumahai May 17 '23 at 03:15
  • 2
    @CeriseLimón, `UnsafeSlice` was written so you don’t have to use that type of array conversion any more. It is equivalent but does not require a constant-typed size. – JimB May 17 '23 at 04:38

0 Answers0