Skip to content

Commit

Permalink
internal/conv: fix wrong string to bytes implementation (#9141)
Browse files Browse the repository at this point in the history
UnsafeStrToBytes is currently not safe for -d=checkptr=2, since when it
cast from smaller struct (string) to bigger struct ([]byte). That causes
checkptr complains as the casting straddle multiple heap objects.

To fix this, we have to get the string header first, then use its fields
to construct the slice.

New implementation performs the same speed with the old (wrong) one.

name                old time/op    new time/op    delta
UnsafeStrToBytes-8    25.7ns ± 1%    25.7ns ± 3%   ~     (p=0.931 n=10+17)

name                old alloc/op   new alloc/op   delta
UnsafeStrToBytes-8     7.00B ± 0%     7.00B ± 0%   ~     (all equal)

name                old allocs/op  new allocs/op  delta
UnsafeStrToBytes-8      0.00           0.00        ~     (all equal)

While at it, also simplify UnsafeBytesToStr implementation, since when
we can pass the slice directly to unsafe.Pointer, instead of getting the
slice header first.
  • Loading branch information
Cuong Manh Le authored Apr 19, 2021
1 parent 1bb71f8 commit d3769b2
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 4 deletions.
11 changes: 7 additions & 4 deletions internal/conv/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import (
// UnsafeStrToBytes uses unsafe to convert string into byte array. Returned bytes
// must not be altered after this function is called as it will cause a segmentation fault.
func UnsafeStrToBytes(s string) []byte {
var buf = *(*[]byte)(unsafe.Pointer(&s))
(*reflect.SliceHeader)(unsafe.Pointer(&buf)).Cap = len(s)
var buf []byte
sHdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
bufHdr.Data = sHdr.Data
bufHdr.Cap = sHdr.Len
bufHdr.Len = sHdr.Len
return buf
}

Expand All @@ -18,6 +22,5 @@ func UnsafeStrToBytes(s string) []byte {
// to be used generally, but for a specific pattern to delete keys
// from a map.
func UnsafeBytesToStr(b []byte) string {
hdr := (*reflect.StringHeader)(unsafe.Pointer(&b))
return *(*string)(unsafe.Pointer(hdr))
return *(*string)(unsafe.Pointer(&b))
}
7 changes: 7 additions & 0 deletions internal/conv/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package conv

import (
"runtime"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -45,3 +46,9 @@ func (s *StringSuite) TestUnsafeBytesToStr() {
s.Equal("abc", str)
}
}

func BenchmarkUnsafeStrToBytes(b *testing.B) {
for i := 0; i < b.N; i++ {
UnsafeStrToBytes(strconv.Itoa(i))
}
}

0 comments on commit d3769b2

Please sign in to comment.