Use unsafe.String/StringData to implement string functions

unsafe.String and unsafe.StringData provide compatibility guarantees that don't
require us to verify compatibility in each release.

For #8422.

PiperOrigin-RevId: 505150667
This commit is contained in:
Michael Pratt
2023-01-27 10:28:15 -08:00
committed by gVisor bot
parent a8d6cc4072
commit 16c24b1cec
5 changed files with 182 additions and 27 deletions
+6 -1
View File
@@ -8,6 +8,8 @@ go_library(
"gohacks_unsafe.go",
"slice_go113_unsafe.go",
"slice_go120_unsafe.go",
"string_go113_unsafe.go",
"string_go120_unsafe.go",
],
stateify = False,
visibility = ["//:sandbox"],
@@ -16,7 +18,10 @@ go_library(
go_test(
name = "gohacks_test",
size = "small",
srcs = ["gohacks_test.go"],
srcs = [
"gohacks_test.go",
"string_test.go",
],
library = ":gohacks",
deps = ["@org_golang_x_sys//unix:go_default_library"],
)
-26
View File
@@ -30,14 +30,6 @@ import (
"unsafe"
)
// StringHeader is equivalent to reflect.StringHeader, but represents the
// pointer to the underlying array as unsafe.Pointer rather than uintptr,
// allowing StringHeaders to be directly converted to strings.
type StringHeader struct {
Data unsafe.Pointer
Len int
}
// Noescape hides a pointer from escape analysis. Noescape is the identity
// function but escape analysis doesn't think the output depends on the input.
// Noescape is inlined and currently compiles down to zero instructions.
@@ -51,24 +43,6 @@ func Noescape(p unsafe.Pointer) unsafe.Pointer {
return unsafe.Pointer(x ^ 0)
}
// ImmutableBytesFromString is equivalent to []byte(s), except that it uses the
// same memory backing s instead of making a heap-allocated copy. This is only
// valid if the returned slice is never mutated.
func ImmutableBytesFromString(s string) []byte {
shdr := (*StringHeader)(unsafe.Pointer(&s))
return Slice((*byte)(shdr.Data), shdr.Len)
}
// StringFromImmutableBytes is equivalent to string(bs), except that it uses
// the same memory backing bs instead of making a heap-allocated copy. This is
// only valid if bs is never mutated after StringFromImmutableBytes returns.
func StringFromImmutableBytes(bs []byte) string {
// This is cheaper than messing with StringHeader and SliceHeader, which as
// of this writing produces many dead stores of zeroes. Compare
// strings.Builder.String().
return *(*string)(unsafe.Pointer(&bs))
}
// Note that go:linkname silently doesn't work if the local name is exported,
// necessitating an indirection for exported functions.
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build go1.13 && !go1.20
// +build go1.13,!go1.20
// TODO(go.dev/issue/8422): Remove this file once Go 1.19 is no longer
// supported.
package gohacks
import (
"unsafe"
)
// stringHeader is equivalent to reflect.StringHeader, but represents the
// pointer to the underlying array as unsafe.Pointer rather than uintptr,
// allowing StringHeaders to be directly converted to strings.
type stringHeader struct {
Data unsafe.Pointer
Len int
}
// ImmutableBytesFromString is equivalent to []byte(s), except that it uses the
// same memory backing s instead of making a heap-allocated copy. This is only
// valid if the returned slice is never mutated.
func ImmutableBytesFromString(s string) []byte {
shdr := (*stringHeader)(unsafe.Pointer(&s))
return Slice((*byte)(shdr.Data), shdr.Len)
}
// StringFromImmutableBytes is equivalent to string(bs), except that it uses
// the same memory backing bs instead of making a heap-allocated copy. This is
// only valid if bs is never mutated after StringFromImmutableBytes returns.
func StringFromImmutableBytes(bs []byte) string {
// This is cheaper than messing with StringHeader and SliceHeader, which as
// of this writing produces many dead stores of zeroes. Compare
// strings.Builder.String().
return *(*string)(unsafe.Pointer(&bs))
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build go1.20
package gohacks
import (
"unsafe"
)
// ImmutableBytesFromString is equivalent to []byte(s), except that it uses the
// same memory backing s instead of making a heap-allocated copy. This is only
// valid if the returned slice is never mutated.
func ImmutableBytesFromString(s string) []byte {
b := unsafe.StringData(s)
return unsafe.Slice(b, len(s))
}
// StringFromImmutableBytes is equivalent to string(bs), except that it uses
// the same memory backing bs instead of making a heap-allocated copy. This is
// only valid if bs is never mutated after StringFromImmutableBytes returns.
func StringFromImmutableBytes(bs []byte) string {
if len(bs) == 0 {
return ""
}
return unsafe.String(&bs[0], len(bs))
}
+86
View File
@@ -0,0 +1,86 @@
// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gohacks
import (
"reflect"
"testing"
)
func TestImmutableBytesFromString(t *testing.T) {
tests := []struct {
name string
input string
want []byte
}{
{
name: "abc",
input: "abc",
want: []byte("abc"),
},
{
name: "empty",
input: "",
want: nil,
},
{
name: "subslice-empty",
input: "abc"[:0],
want: []byte(""),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := ImmutableBytesFromString(tc.input)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("got contents %v (len %d cap %d) want %v (len %d cap %d)", got, len(got), cap(got), tc.want, len(tc.want), cap(tc.want))
}
})
}
}
func TestStringFromImmutableBytes(t *testing.T) {
tests := []struct {
name string
input []byte
want string
}{
{
name: "abc",
input: []byte("abc"),
want: "abc",
},
{
name: "empty",
input: []byte{},
want: "",
},
{
name: "nil",
input: nil,
want: "",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := StringFromImmutableBytes(tc.input)
if got != tc.want {
t.Errorf("got %q want %q", got, tc.want)
}
})
}
}