mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 03:25:35 +00:00
types/views: remove alloc in hot path
Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
4c75605e23
commit
6dae9e47f9
@ -9,8 +9,6 @@
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
@ -100,11 +98,8 @@ type Slice[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SliceOf returns a Slice for the provided slice for immutable values.
|
// SliceOf returns a Slice for the provided slice for immutable values.
|
||||||
// It panics if the value type contains pointers.
|
// It is the caller's responsibility to make sure V is immutable.
|
||||||
func SliceOf[T any](x []T) Slice[T] {
|
func SliceOf[T any](x []T) Slice[T] {
|
||||||
if ev := reflect.TypeOf(x).Elem(); containsMutable(ev) {
|
|
||||||
panic(fmt.Sprintf("slice value type %q has pointers", ev.Name()))
|
|
||||||
}
|
|
||||||
return Slice[T]{x}
|
return Slice[T]{x}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,56 +189,13 @@ func (v *IPPrefixSlice) UnmarshalJSON(b []byte) error {
|
|||||||
return v.ж.UnmarshalJSON(b)
|
return v.ж.UnmarshalJSON(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// containsMutable reports whether the provided type has anything mutable.
|
// MapOf returns a view over m. It is the caller's responsibility to make sure K
|
||||||
func containsMutable(t reflect.Type) bool {
|
// and V is immutable, if this is being used to provide a read-only view over m.
|
||||||
switch x := fmt.Sprintf("%v.%v", t.PkgPath(), t.Name()); x {
|
|
||||||
case "time.Time",
|
|
||||||
"inet.af/netaddr.IP":
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
k := t.Kind()
|
|
||||||
switch k {
|
|
||||||
case reflect.Bool,
|
|
||||||
reflect.Int,
|
|
||||||
reflect.Int8,
|
|
||||||
reflect.Int16,
|
|
||||||
reflect.Int32,
|
|
||||||
reflect.Int64,
|
|
||||||
reflect.Uint,
|
|
||||||
reflect.Uint8,
|
|
||||||
reflect.Uint16,
|
|
||||||
reflect.Uint32,
|
|
||||||
reflect.Uint64,
|
|
||||||
reflect.Float32,
|
|
||||||
reflect.Float64,
|
|
||||||
reflect.Complex64,
|
|
||||||
reflect.Complex128,
|
|
||||||
reflect.String:
|
|
||||||
return false
|
|
||||||
case reflect.Array: // Not a slice.
|
|
||||||
return containsMutable(t.Elem()) && t.Len() > 0
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
f := t.Field(i)
|
|
||||||
if containsMutable(f.Type) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapOf returns a read-only view over m for immutable values.
|
|
||||||
// It panics if the value type contains pointers.
|
|
||||||
func MapOf[K comparable, V comparable](m map[K]V) Map[K, V] {
|
func MapOf[K comparable, V comparable](m map[K]V) Map[K, V] {
|
||||||
if ev := reflect.TypeOf(m).Elem(); containsMutable(ev) {
|
|
||||||
panic(fmt.Sprintf("map value type %q has pointers", ev.Name()))
|
|
||||||
}
|
|
||||||
return Map[K, V]{m}
|
return Map[K, V]{m}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map is a read-only accessor over a map whose values are immutable.
|
// Map is a view over a map whose values are immutable.
|
||||||
type Map[K comparable, V any] struct {
|
type Map[K comparable, V any] struct {
|
||||||
// ж is the underlying mutable value, named with a hard-to-type
|
// ж is the underlying mutable value, named with a hard-to-type
|
||||||
// character that looks pointy like a pointer.
|
// character that looks pointy like a pointer.
|
||||||
|
@ -10,43 +10,10 @@
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go4.org/mem"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/types/structs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainsPointers(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
in any
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{name: "string", in: "foo", want: false},
|
|
||||||
{name: "int", in: 42, want: false},
|
|
||||||
{name: "struct", in: struct{ string }{"foo"}, want: false},
|
|
||||||
{name: "mem.RO", in: mem.B([]byte{1}), want: false},
|
|
||||||
{name: "time.Time", in: time.Now(), want: false},
|
|
||||||
{name: "netaddr.IP", in: netaddr.MustParseIP("1.1.1.1"), want: false},
|
|
||||||
{name: "netaddr.IPPrefix", in: netaddr.MustParseIP("1.1.1.1"), want: false},
|
|
||||||
{name: "structs.Incomparable", in: structs.Incomparable{}, want: false},
|
|
||||||
|
|
||||||
{name: "*int", in: (*int)(nil), want: true},
|
|
||||||
{name: "*string", in: (*string)(nil), want: true},
|
|
||||||
{name: "struct-with-pointer", in: struct{ X *string }{}, want: true},
|
|
||||||
{name: "slice-with-pointer", in: []struct{ X *string }{}, want: true},
|
|
||||||
{name: "slice-of-struct", in: []struct{ string }{}, want: true},
|
|
||||||
}
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if containsMutable(reflect.TypeOf(tc.in)) != tc.want {
|
|
||||||
t.Errorf("containsPointers %T; want %v", tc.in, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestViewsJSON(t *testing.T) {
|
func TestViewsJSON(t *testing.T) {
|
||||||
mustCIDR := func(cidrs ...string) (out []netaddr.IPPrefix) {
|
mustCIDR := func(cidrs ...string) (out []netaddr.IPPrefix) {
|
||||||
for _, cidr := range cidrs {
|
for _, cidr := range cidrs {
|
||||||
|
Loading…
Reference in New Issue
Block a user