diff --git a/internal/conv/doc.go b/internal/conv/doc.go new file mode 100644 index 000000000..1c86f5c14 --- /dev/null +++ b/internal/conv/doc.go @@ -0,0 +1,2 @@ +// Package conv provides internal functions for convertions and data manipulation +package conv diff --git a/internal/conv/string.go b/internal/conv/string.go new file mode 100644 index 000000000..9a518c578 --- /dev/null +++ b/internal/conv/string.go @@ -0,0 +1,23 @@ +package conv + +import ( + "reflect" + "unsafe" +) + +// 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) + return buf +} + +// UnsafeBytesToStr is meant to make a zero allocation conversion +// from []byte -> string to speed up operations, it is not meant +// 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)) +} diff --git a/internal/conv/string_test.go b/internal/conv/string_test.go new file mode 100644 index 000000000..2a1892b43 --- /dev/null +++ b/internal/conv/string_test.go @@ -0,0 +1,47 @@ +package conv + +import ( + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/suite" +) + +func TestStringSuite(t *testing.T) { + suite.Run(t, new(StringSuite)) +} + +type StringSuite struct{ suite.Suite } + +func unsafeConvertStr() []byte { + return UnsafeStrToBytes("abc") +} + +func (s *StringSuite) TestUnsafeStrToBytes() { + // we convert in other function to trigger GC. We want to check that + // the underlying array in []bytes is accessible after GC will finish swapping. + for i := 0; i < 5; i++ { + b := unsafeConvertStr() + runtime.GC() + <-time.NewTimer(2 * time.Millisecond).C + b2 := append(b, 'd') + s.Equal("abc", string(b)) + s.Equal("abcd", string(b2)) + } +} + +func unsafeConvertBytes() string { + return UnsafeBytesToStr([]byte("abc")) +} + +func (s *StringSuite) TestUnsafeBytesToStr() { + // we convert in other function to trigger GC. We want to check that + // the underlying array in []bytes is accessible after GC will finish swapping. + for i := 0; i < 5; i++ { + str := unsafeConvertBytes() + runtime.GC() + <-time.NewTimer(2 * time.Millisecond).C + s.Equal("abc", str) + } +} diff --git a/store/cachekv/store.go b/store/cachekv/store.go index 42c94370b..a953d4436 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -4,14 +4,13 @@ import ( "bytes" "container/list" "io" - "reflect" "sort" "sync" "time" - "unsafe" dbm "github.com/tendermint/tm-db" + "github.com/cosmos/cosmos-sdk/internal/conv" "github.com/cosmos/cosmos-sdk/store/tracekv" "github.com/cosmos/cosmos-sdk/store/types" "github.com/cosmos/cosmos-sdk/telemetry" @@ -179,35 +178,13 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { return newCacheMergeIterator(parent, cache, ascending) } -// strToByte is meant to make a zero allocation conversion -// from string -> []byte to speed up operations, it is not meant -// to be used generally, but for a specific pattern to check for available -// keys within a domain. -func strToByte(s string) []byte { - var b []byte - hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b)) - hdr.Cap = len(s) - hdr.Len = len(s) - hdr.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data - return b -} - -// byteSliceToStr is meant to make a zero allocation conversion -// from []byte -> string to speed up operations, it is not meant -// to be used generally, but for a specific pattern to delete keys -// from a map. -func byteSliceToStr(b []byte) string { - hdr := (*reflect.StringHeader)(unsafe.Pointer(&b)) - return *(*string)(unsafe.Pointer(hdr)) -} - // Constructs a slice of dirty items, to use w/ memIterator. func (store *Store) dirtyItems(start, end []byte) { unsorted := make([]*kv.Pair, 0) n := len(store.unsortedCache) for key := range store.unsortedCache { - if dbm.IsKeyInDomain(strToByte(key), start, end) { + if dbm.IsKeyInDomain(conv.UnsafeStrToBytes(key), start, end) { cacheValue := store.cache[key] unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) } @@ -219,7 +196,7 @@ func (store *Store) dirtyItems(start, end []byte) { } } else { // Otherwise, normally delete the unsorted keys from the map. for _, kv := range unsorted { - delete(store.unsortedCache, byteSliceToStr(kv.Key)) + delete(store.unsortedCache, conv.UnsafeBytesToStr(kv.Key)) } } diff --git a/types/address/hash.go b/types/address/hash.go index ddfec81df..a6c9fe94c 100644 --- a/types/address/hash.go +++ b/types/address/hash.go @@ -4,10 +4,9 @@ import ( "bytes" "crypto/sha256" "fmt" - "reflect" "sort" - "unsafe" + "github.com/cosmos/cosmos-sdk/internal/conv" "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -21,7 +20,7 @@ type Addressable interface { // Hash creates a new address from address type and key func Hash(typ string, key []byte) []byte { hasher := sha256.New() - hasher.Write(unsafeStrToByteArray(typ)) + hasher.Write(conv.UnsafeStrToBytes(typ)) th := hasher.Sum(nil) hasher.Reset() @@ -63,11 +62,3 @@ func Module(moduleName string, key []byte) []byte { mKey := append([]byte(moduleName), 0) return Hash("module", append(mKey, key...)) } - -// unsafeStrToByteArray 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 unsafeStrToByteArray(s string) []byte { - var buf = *(*[]byte)(unsafe.Pointer(&s)) - (*reflect.SliceHeader)(unsafe.Pointer(&buf)).Cap = len(s) - return buf -} diff --git a/types/address/hash_test.go b/types/address/hash_test.go index 4cc6c6880..be096c357 100644 --- a/types/address/hash_test.go +++ b/types/address/hash_test.go @@ -2,9 +2,7 @@ package address import ( "crypto/sha256" - "runtime" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -77,23 +75,6 @@ func (suite *AddressSuite) TestModule() { assert.NotEqual(addr2, addr3, "changing key must change address") } -func unsafeConvertABC() []byte { - return unsafeStrToByteArray("abc") -} - -func (suite *AddressSuite) TestUnsafeStrToBytes() { - // we convert in other function to trigger GC. We want to check that - // the underlying array in []bytes is accessible after GC will finish swapping. - for i := 0; i < 5; i++ { - b := unsafeConvertABC() - runtime.GC() - <-time.NewTimer(2 * time.Millisecond).C - b2 := append(b, 'd') - suite.Equal("abc", string(b)) - suite.Equal("abcd", string(b2)) - } -} - type addrMock struct { Addr []byte }