From d3769b2fbccf4febe750c51dfca30c38fa4ad18e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 19 Apr 2021 20:51:05 +0700 Subject: [PATCH] internal/conv: fix wrong string to bytes implementation (#9141) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/conv/string.go | 11 +++++++---- internal/conv/string_test.go | 7 +++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/internal/conv/string.go b/internal/conv/string.go index 9a518c578..ab2b7f44b 100644 --- a/internal/conv/string.go +++ b/internal/conv/string.go @@ -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 } @@ -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)) } diff --git a/internal/conv/string_test.go b/internal/conv/string_test.go index 2a1892b43..3a1451753 100644 --- a/internal/conv/string_test.go +++ b/internal/conv/string_test.go @@ -2,6 +2,7 @@ package conv import ( "runtime" + "strconv" "testing" "time" @@ -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)) + } +}