From 108848a6aa4f1e3e9cc98fa7421c5f32de7cb274 Mon Sep 17 00:00:00 2001 From: Francis Stephens Date: Sun, 9 Feb 2025 19:36:33 +1300 Subject: [PATCH 1/4] containsNoPointer() func takes reflect.Type arg Previously we passed the type in via a generic parameter. Passing it in as a struct allows us to build the reflect type and cache it outside of the containsNoPointer() function. The store itself will cache the results to this function call to avoid calling it every time we allocate a type. --- offheap/object_reference.go | 4 ++- offheap/pointer_checker.go | 3 +-- offheap/pointer_checker_test.go | 43 +++++++++++++++++---------------- offheap/slice_reference.go | 4 ++- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/offheap/object_reference.go b/offheap/object_reference.go index 8a5f1eb..eebb6b5 100644 --- a/offheap/object_reference.go +++ b/offheap/object_reference.go @@ -6,6 +6,7 @@ package offheap import ( "fmt" + "reflect" "unsafe" "github.com/fmstephe/memorymanager/offheap/internal/pointerstore" @@ -20,7 +21,8 @@ import ( // zeroed out. func AllocObject[T any](s *Store) RefObject[T] { // TODO this is not fast - we _need_ to cache this type data - if err := containsNoPointers[T](); err != nil { + t := reflect.TypeFor[T]() + if err := containsNoPointers(t); err != nil { panic(fmt.Errorf("cannot allocate generic type containing pointers %w", err)) } diff --git a/offheap/pointer_checker.go b/offheap/pointer_checker.go index 7c3142a..a124c2a 100644 --- a/offheap/pointer_checker.go +++ b/offheap/pointer_checker.go @@ -35,8 +35,7 @@ func (p *typePaths) String() string { return result[:len(result)-1] } -func containsNoPointers[O any]() error { - t := reflect.TypeFor[O]() +func containsNoPointers(t reflect.Type) error { paths := &typePaths{} searchForPointers(t, "", paths) if paths.Len() != 0 { diff --git a/offheap/pointer_checker_test.go b/offheap/pointer_checker_test.go index 88f4285..4df5bc8 100644 --- a/offheap/pointer_checker_test.go +++ b/offheap/pointer_checker_test.go @@ -5,6 +5,7 @@ package offheap import ( + "reflect" "testing" "unsafe" @@ -50,29 +51,29 @@ type manyPointers struct { func TestBadTypes(t *testing.T) { // No arrays with pointers in them - assert.EqualError(t, containsNoPointers[[32]badStruct](), "found pointer(s): [32](offheap.badStruct)badField") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[[32]badStruct]()), "found pointer(s): [32](offheap.badStruct)badField") // No channels - assert.EqualError(t, containsNoPointers[chan int](), "found pointer(s): ") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[chan int]()), "found pointer(s): ") // No functions - assert.EqualError(t, containsNoPointers[func(int) int](), "found pointer(s): ") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[func(int) int]()), "found pointer(s): ") // No interfaces - assert.EqualError(t, containsNoPointers[any](), "found pointer(s): ") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[any]()), "found pointer(s): ") // No maps - assert.EqualError(t, containsNoPointers[map[int]int](), "found pointer(s): ") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[map[int]int]()), "found pointer(s): ") // No pointer(s) - assert.EqualError(t, containsNoPointers[*int](), "found pointer(s): <*int>") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[*int]()), "found pointer(s): <*int>") // No slices - assert.EqualError(t, containsNoPointers[[]int](), "found pointer(s): <[]int>") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[[]int]()), "found pointer(s): <[]int>") // No strings - assert.EqualError(t, containsNoPointers[string](), "found pointer(s): ") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[string]()), "found pointer(s): ") // No structs with any pointerful fields - assert.EqualError(t, containsNoPointers[badStruct](), "found pointer(s): (offheap.badStruct)badField") - assert.EqualError(t, containsNoPointers[deepBadStruct](), "found pointer(s): (offheap.deepBadStruct)badInt<*int>,(offheap.deepBadStruct)deepBadField(offheap.badStruct)badField") - // assert.EqualError(t, containsNoPointers[stringSmugglerStruct](), "found pointer(s): ") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[badStruct]()), "found pointer(s): (offheap.badStruct)badField") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[deepBadStruct]()), "found pointer(s): (offheap.deepBadStruct)badInt<*int>,(offheap.deepBadStruct)deepBadField(offheap.badStruct)badField") + // assert.EqualError(t, containsNoPointers(reflect.TypeFor[stringSmugglerStruct]()), "found pointer(s): ") // No unsafe pointer(s) - assert.EqualError(t, containsNoPointers[unsafe.Pointer](), "found pointer(s): ") + assert.EqualError(t, containsNoPointers(reflect.TypeFor[unsafe.Pointer]()), "found pointer(s): ") // We should find all of the bad fields in this struct - assert.EqualError(t, containsNoPointers[manyPointers](), "found pointer(s): "+ + assert.EqualError(t, containsNoPointers(reflect.TypeFor[manyPointers]()), "found pointer(s): "+ "(offheap.manyPointers)chanField,"+ "(offheap.manyPointers)funcField,"+ "(offheap.manyPointers)interfaceField,"+ @@ -100,18 +101,18 @@ type goodStruct struct { func TestGoodTypes(t *testing.T) { // bool is fine - assert.Nil(t, containsNoPointers[bool]()) + assert.Nil(t, containsNoPointers(reflect.TypeFor[bool]())) // ints are fine - assert.Nil(t, containsNoPointers[int]()) + assert.Nil(t, containsNoPointers(reflect.TypeFor[int]())) // uints are fine - assert.Nil(t, containsNoPointers[uint]()) + assert.Nil(t, containsNoPointers(reflect.TypeFor[uint]())) // floats are fine - assert.Nil(t, containsNoPointers[float32]()) + assert.Nil(t, containsNoPointers(reflect.TypeFor[float32]())) // complex numbers are fine - assert.Nil(t, containsNoPointers[complex64]()) + assert.Nil(t, containsNoPointers(reflect.TypeFor[complex64]())) // arrays are fine - assert.Nil(t, containsNoPointers[[32]int]()) + assert.Nil(t, containsNoPointers(reflect.TypeFor[[32]int]())) // structs with no pointerful fields are fine - assert.Nil(t, containsNoPointers[goodStruct]()) - assert.Nil(t, containsNoPointers[deepGoodStruct]()) + assert.Nil(t, containsNoPointers(reflect.TypeFor[goodStruct]())) + assert.Nil(t, containsNoPointers(reflect.TypeFor[deepGoodStruct]())) } diff --git a/offheap/slice_reference.go b/offheap/slice_reference.go index 60c1c90..a1de2e0 100644 --- a/offheap/slice_reference.go +++ b/offheap/slice_reference.go @@ -6,6 +6,7 @@ package offheap import ( "fmt" + "reflect" "unsafe" "github.com/fmstephe/memorymanager/offheap/internal/pointerstore" @@ -19,7 +20,8 @@ import ( // AllocSlice do _not_ have their contents zeroed out. func AllocSlice[T any](s *Store, length, requestedCapacity int) RefSlice[T] { // TODO this is not fast - we _need_ to cache this type data - if err := containsNoPointers[T](); err != nil { + t := reflect.TypeFor[T]() + if err := containsNoPointers(t); err != nil { panic(fmt.Errorf("cannot allocate generic type containing pointers %w", err)) } From 3ac72238c47559ccf1905c11edc14bb3cfe8e00e Mon Sep 17 00:00:00 2001 From: Francis Stephens Date: Sun, 9 Feb 2025 19:43:30 +1300 Subject: [PATCH 2/4] Add cache for type check Previously every allocation would perform a check on whether the allocation type contains pointers. This work is very slow and allocates a remarkable amount of memory. We introduce a cache which simply stores types which have been successfully checked previously. This cache is protected by a read/write lock - which does put another lock on the allocation path. --- offheap/object_reference.go | 6 +---- offheap/object_store.go | 47 ++++++++++++++++++++++++++++++++++--- offheap/slice_reference.go | 5 +--- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/offheap/object_reference.go b/offheap/object_reference.go index eebb6b5..a117320 100644 --- a/offheap/object_reference.go +++ b/offheap/object_reference.go @@ -5,7 +5,6 @@ package offheap import ( - "fmt" "reflect" "unsafe" @@ -20,11 +19,8 @@ import ( // Go allocations objects acquired via AllocObject do _not_ have their contents // zeroed out. func AllocObject[T any](s *Store) RefObject[T] { - // TODO this is not fast - we _need_ to cache this type data t := reflect.TypeFor[T]() - if err := containsNoPointers(t); err != nil { - panic(fmt.Errorf("cannot allocate generic type containing pointers %w", err)) - } + s.checkType(t) idx := indexForType[T]() diff --git a/offheap/object_store.go b/offheap/object_store.go index d1f9673..0aab9ce 100644 --- a/offheap/object_store.go +++ b/offheap/object_store.go @@ -5,12 +5,20 @@ package offheap import ( + "fmt" + "reflect" + "sync" + "github.com/fmstephe/memorymanager/offheap/internal/pointerstore" ) const defaultSlabSize = 1 << 13 type Store struct { + // protects access to typeCache + typeCacheLock sync.RWMutex + typeCache map[reflect.Type]struct{} + sizedStores []*pointerstore.Store } @@ -18,9 +26,7 @@ type Store struct { // // This store manages allocation and freeing of any offheap allocated objects. func New() *Store { - return &Store{ - sizedStores: initSizeStore(defaultSlabSize), - } + return newSized(defaultSlabSize) } // Returns a new *Store. @@ -35,7 +41,13 @@ func New() *Store { // small slab sizes to allow faster tests with reduced memory usage. Most users // will probably prefer to use the default New() above. func NewSized(slabSize int) *Store { + return newSized(slabSize) +} + +// Returns a new *Store +func newSized(slabSize int) *Store { return &Store{ + typeCache: map[reflect.Type]struct{}{}, sizedStores: initSizeStore(slabSize), } } @@ -101,3 +113,32 @@ func (s *Store) AllocConfigs() []pointerstore.AllocConfig { } return sizedAllocConfigs } + +func (s *Store) checkType(t reflect.Type) { + if s.lookupTypeCache(t) { + return + } + + if err := containsNoPointers(t); err != nil { + panic(fmt.Errorf("cannot allocate generic type containing pointers %w", err)) + } + + s.writeTypeToCache(t) + return +} + +func (s *Store) lookupTypeCache(t reflect.Type) bool { + s.typeCacheLock.RLock() + defer s.typeCacheLock.RUnlock() + + _, ok := s.typeCache[t] + return ok +} + +func (s *Store) writeTypeToCache(t reflect.Type) { + s.typeCacheLock.Lock() + defer s.typeCacheLock.Unlock() + + s.typeCache[t] = struct{}{} + return +} diff --git a/offheap/slice_reference.go b/offheap/slice_reference.go index a1de2e0..caf48aa 100644 --- a/offheap/slice_reference.go +++ b/offheap/slice_reference.go @@ -19,11 +19,8 @@ import ( // The contents of the slice will be arbitrary. Unlike Go slices acquired via // AllocSlice do _not_ have their contents zeroed out. func AllocSlice[T any](s *Store, length, requestedCapacity int) RefSlice[T] { - // TODO this is not fast - we _need_ to cache this type data t := reflect.TypeFor[T]() - if err := containsNoPointers(t); err != nil { - panic(fmt.Errorf("cannot allocate generic type containing pointers %w", err)) - } + s.checkType(t) // Round the requested capacity up to a power of 2 actualCapacity := capacityForSlice(requestedCapacity) From 941b2f7fb126cc6b2f0bef290bf5d2b42c8177c9 Mon Sep 17 00:00:00 2001 From: Francis Stephens Date: Sun, 9 Feb 2025 19:46:37 +1300 Subject: [PATCH 3/4] Adding typeCheck struct This struct is responsible for checking whether types contain pointers. It caches acceptable types to avoid repeated, expensive, type checks. --- offheap/object_reference.go | 2 +- offheap/object_store.go | 40 ++--------------------------- offheap/slice_reference.go | 2 +- offheap/type_checker.go | 50 +++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 offheap/type_checker.go diff --git a/offheap/object_reference.go b/offheap/object_reference.go index a117320..cbcb9ff 100644 --- a/offheap/object_reference.go +++ b/offheap/object_reference.go @@ -20,7 +20,7 @@ import ( // zeroed out. func AllocObject[T any](s *Store) RefObject[T] { t := reflect.TypeFor[T]() - s.checkType(t) + s.checker.checkType(t) idx := indexForType[T]() diff --git a/offheap/object_store.go b/offheap/object_store.go index 0aab9ce..e7ef279 100644 --- a/offheap/object_store.go +++ b/offheap/object_store.go @@ -5,20 +5,13 @@ package offheap import ( - "fmt" - "reflect" - "sync" - "github.com/fmstephe/memorymanager/offheap/internal/pointerstore" ) const defaultSlabSize = 1 << 13 type Store struct { - // protects access to typeCache - typeCacheLock sync.RWMutex - typeCache map[reflect.Type]struct{} - + checker typeChecker sizedStores []*pointerstore.Store } @@ -47,7 +40,7 @@ func NewSized(slabSize int) *Store { // Returns a new *Store func newSized(slabSize int) *Store { return &Store{ - typeCache: map[reflect.Type]struct{}{}, + checker: newTypeChecker(), sizedStores: initSizeStore(slabSize), } } @@ -113,32 +106,3 @@ func (s *Store) AllocConfigs() []pointerstore.AllocConfig { } return sizedAllocConfigs } - -func (s *Store) checkType(t reflect.Type) { - if s.lookupTypeCache(t) { - return - } - - if err := containsNoPointers(t); err != nil { - panic(fmt.Errorf("cannot allocate generic type containing pointers %w", err)) - } - - s.writeTypeToCache(t) - return -} - -func (s *Store) lookupTypeCache(t reflect.Type) bool { - s.typeCacheLock.RLock() - defer s.typeCacheLock.RUnlock() - - _, ok := s.typeCache[t] - return ok -} - -func (s *Store) writeTypeToCache(t reflect.Type) { - s.typeCacheLock.Lock() - defer s.typeCacheLock.Unlock() - - s.typeCache[t] = struct{}{} - return -} diff --git a/offheap/slice_reference.go b/offheap/slice_reference.go index caf48aa..2ad3820 100644 --- a/offheap/slice_reference.go +++ b/offheap/slice_reference.go @@ -20,7 +20,7 @@ import ( // AllocSlice do _not_ have their contents zeroed out. func AllocSlice[T any](s *Store, length, requestedCapacity int) RefSlice[T] { t := reflect.TypeFor[T]() - s.checkType(t) + s.checker.checkType(t) // Round the requested capacity up to a power of 2 actualCapacity := capacityForSlice(requestedCapacity) diff --git a/offheap/type_checker.go b/offheap/type_checker.go new file mode 100644 index 0000000..920e50a --- /dev/null +++ b/offheap/type_checker.go @@ -0,0 +1,50 @@ +// Copyright 2025 Francis Michael Stephens. All rights reserved. Use of this +// source code is governed by an MIT license that can be found in the LICENSE +// file. + +package offheap + +import ( + "fmt" + "reflect" + "sync" +) + +type typeChecker struct { + typeCacheLock sync.RWMutex + typeCache map[reflect.Type]struct{} +} + +func newTypeChecker() typeChecker { + return typeChecker{ + typeCacheLock: sync.RWMutex{}, + typeCache: map[reflect.Type]struct{}{}, + } +} + +func (c *typeChecker) checkType(t reflect.Type) { + if c.lookupTypeCache(t) { + return + } + + if err := containsNoPointers(t); err != nil { + panic(fmt.Errorf("cannot allocate generic type containing pointers %w", err)) + } + + c.writeTypeToCache(t) +} + +func (c *typeChecker) lookupTypeCache(t reflect.Type) bool { + c.typeCacheLock.RLock() + defer c.typeCacheLock.RUnlock() + + _, ok := c.typeCache[t] + return ok +} + +func (c *typeChecker) writeTypeToCache(t reflect.Type) { + c.typeCacheLock.Lock() + defer c.typeCacheLock.Unlock() + + c.typeCache[t] = struct{}{} +} From ff40120e8d69f97b532a7c1699fdf618810be5a7 Mon Sep 17 00:00:00 2001 From: Francis Stephens Date: Sun, 9 Feb 2025 19:49:32 +1300 Subject: [PATCH 4/4] Merge pointer_checker.go into type_checker.go There is only one file which contains all of the type checking code in it. We also add tests which directly test the pointerCheck type as well as the containsNoPointers() function. We preserve the lower level function tests because they are able to verify that the exact type failures we expect are being found, and described in the error text. --- offheap/pointer_checker.go | 96 ------------------- offheap/type_checker.go | 86 +++++++++++++++++ ...r_checker_test.go => type_checker_test.go} | 78 ++++++++++++++- 3 files changed, 162 insertions(+), 98 deletions(-) delete mode 100644 offheap/pointer_checker.go rename offheap/{pointer_checker_test.go => type_checker_test.go} (62%) diff --git a/offheap/pointer_checker.go b/offheap/pointer_checker.go deleted file mode 100644 index a124c2a..0000000 --- a/offheap/pointer_checker.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2024 Francis Michael Stephens. All rights reserved. Use of this -// source code is governed by an MIT license that can be found in the LICENSE -// file. - -package offheap - -import ( - "fmt" - "reflect" - "strconv" -) - -type typePaths struct { - paths []string -} - -func (p *typePaths) addPath(path string) { - p.paths = append(p.paths, path) -} - -func (p *typePaths) Len() int { - return len(p.paths) -} - -func (p *typePaths) String() string { - if p.Len() == 0 { - return "" - } - - result := "" - for _, path := range p.paths { - result += path + "," - } - // Quietly strip off the trailing , - return result[:len(result)-1] -} - -func containsNoPointers(t reflect.Type) error { - paths := &typePaths{} - searchForPointers(t, "", paths) - if paths.Len() != 0 { - return fmt.Errorf("found pointer(s): %s", paths) - } - return nil -} - -func searchForPointers(t reflect.Type, path string, paths *typePaths) { - switch t.Kind() { - case reflect.Bool: - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - - case reflect.Float32, reflect.Float64: - - case reflect.Complex64, reflect.Complex128: - - case reflect.Array: - size := strconv.Itoa(t.Len()) - searchForPointers(t.Elem(), path+"["+size+"]", paths) - - case reflect.Chan: - paths.addPath(path + "<" + t.String() + ">") - - case reflect.Func: - paths.addPath(path + "<" + t.String() + ">") - - case reflect.Interface: - paths.addPath(path + "<" + t.String() + ">") - - case reflect.Map: - paths.addPath(path + "<" + t.String() + ">") - - case reflect.Pointer: - paths.addPath(path + "<" + t.String() + ">") - - case reflect.Slice: - paths.addPath(path + "<" + t.String() + ">") - - case reflect.String: - paths.addPath(path + "<" + t.String() + ">") - - case reflect.Struct: - for i := 0; i < t.NumField(); i++ { - sV := t.Field(i) - searchForPointers(sV.Type, path+"("+t.String()+")"+sV.Name, paths) - } - - case reflect.UnsafePointer: - paths.addPath(path + "<" + t.String() + ">") - - default: - paths.addPath(path + "<" + t.String() + ">") - } -} diff --git a/offheap/type_checker.go b/offheap/type_checker.go index 920e50a..f994487 100644 --- a/offheap/type_checker.go +++ b/offheap/type_checker.go @@ -7,6 +7,7 @@ package offheap import ( "fmt" "reflect" + "strconv" "sync" ) @@ -48,3 +49,88 @@ func (c *typeChecker) writeTypeToCache(t reflect.Type) { c.typeCache[t] = struct{}{} } + +type typePaths struct { + paths []string +} + +func (p *typePaths) addPath(path string) { + p.paths = append(p.paths, path) +} + +func (p *typePaths) Len() int { + return len(p.paths) +} + +func (p *typePaths) String() string { + if p.Len() == 0 { + return "" + } + + result := "" + for _, path := range p.paths { + result += path + "," + } + // Quietly strip off the trailing , + return result[:len(result)-1] +} + +func containsNoPointers(t reflect.Type) error { + paths := &typePaths{} + searchForPointers(t, "", paths) + if paths.Len() != 0 { + return fmt.Errorf("found pointer(s): %s", paths) + } + return nil +} + +func searchForPointers(t reflect.Type, path string, paths *typePaths) { + switch t.Kind() { + case reflect.Bool: + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + + case reflect.Float32, reflect.Float64: + + case reflect.Complex64, reflect.Complex128: + + case reflect.Array: + size := strconv.Itoa(t.Len()) + searchForPointers(t.Elem(), path+"["+size+"]", paths) + + case reflect.Chan: + paths.addPath(path + "<" + t.String() + ">") + + case reflect.Func: + paths.addPath(path + "<" + t.String() + ">") + + case reflect.Interface: + paths.addPath(path + "<" + t.String() + ">") + + case reflect.Map: + paths.addPath(path + "<" + t.String() + ">") + + case reflect.Pointer: + paths.addPath(path + "<" + t.String() + ">") + + case reflect.Slice: + paths.addPath(path + "<" + t.String() + ">") + + case reflect.String: + paths.addPath(path + "<" + t.String() + ">") + + case reflect.Struct: + for i := 0; i < t.NumField(); i++ { + sV := t.Field(i) + searchForPointers(sV.Type, path+"("+t.String()+")"+sV.Name, paths) + } + + case reflect.UnsafePointer: + paths.addPath(path + "<" + t.String() + ">") + + default: + paths.addPath(path + "<" + t.String() + ">") + } +} diff --git a/offheap/pointer_checker_test.go b/offheap/type_checker_test.go similarity index 62% rename from offheap/pointer_checker_test.go rename to offheap/type_checker_test.go index 4df5bc8..17be822 100644 --- a/offheap/pointer_checker_test.go +++ b/offheap/type_checker_test.go @@ -49,7 +49,7 @@ type manyPointers struct { stringField string } -func TestBadTypes(t *testing.T) { +func TestBadTypes_containsNoPointers(t *testing.T) { // No arrays with pointers in them assert.EqualError(t, containsNoPointers(reflect.TypeFor[[32]badStruct]()), "found pointer(s): [32](offheap.badStruct)badField") // No channels @@ -83,6 +83,35 @@ func TestBadTypes(t *testing.T) { "(offheap.manyPointers)stringField") } +func TestBadTypes_checkTypes(t *testing.T) { + checker := newTypeChecker() + + // No arrays with pointers in them} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[[32]badStruct]()) }) + // No channels} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[chan int]()) }) + // No functions} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[func(int) int]()) }) + // No interfaces} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[any]()) }) + // No maps} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[map[int]int]()) }) + // No pointer(s)} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[*int]()) }) + // No slices} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[[]int]()) }) + // No strings} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[string]()) }) + // No structs with any pointerful fields} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[badStruct]()) }) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[deepBadStruct]()) }) + // assert.Panics(t, func () { checker.checkType(reflect.TypeFor[stringSmugglerStruct]())} ) + // No unsafe pointer(s)} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[unsafe.Pointer]()) }) + // We should find all of the bad fields in this struct} ) + assert.Panics(t, func() { checker.checkType(reflect.TypeFor[manyPointers]()) }) +} + type deepGoodStruct struct { //lint:ignore U1000 this field looks unused but is observed by reflection boolField bool @@ -99,7 +128,7 @@ type goodStruct struct { referenceField RefObject[goodStruct] } -func TestGoodTypes(t *testing.T) { +func TestGoodTypes_containsNoPointers(t *testing.T) { // bool is fine assert.Nil(t, containsNoPointers(reflect.TypeFor[bool]())) // ints are fine @@ -116,3 +145,48 @@ func TestGoodTypes(t *testing.T) { assert.Nil(t, containsNoPointers(reflect.TypeFor[goodStruct]())) assert.Nil(t, containsNoPointers(reflect.TypeFor[deepGoodStruct]())) } + +func TestGoodTypes_checkType(t *testing.T) { + checker := newTypeChecker() + + // bool is fine + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[bool]()) }) + // ints are fine + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[int]()) }) + // uints are fine + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[uint]()) }) + // floats are fine + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[float32]()) }) + // complex numbers are fine + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[complex64]()) }) + // arrays are fine + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[[32]int]()) }) + // structs with no pointerful fields are fine + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[goodStruct]()) }) + assert.NotPanics(t, func() { checker.checkType(reflect.TypeFor[deepGoodStruct]()) }) +} + +func TestTypeCaching(t *testing.T) { + checker := newTypeChecker() + + types := []reflect.Type{ + reflect.TypeFor[bool](), + reflect.TypeFor[int](), + reflect.TypeFor[uint](), + reflect.TypeFor[float32](), + reflect.TypeFor[complex64](), + reflect.TypeFor[goodStruct](), + reflect.TypeFor[deepGoodStruct](), + } + + for _, typ := range types { + // Initially the type is not in the cache + assert.NotContains(t, checker.typeCache, typ) + // Check the type + checker.checkType(typ) + // The type is now in the cache + assert.Contains(t, checker.typeCache, typ) + // Check the type again, still works + checker.checkType(typ) + } +}