126 lines
2.6 KiB
Go
126 lines
2.6 KiB
Go
package litter
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
)
|
|
|
|
// mapReusedPointers takes a structure, and recursively maps all pointers mentioned in the tree,
|
|
// detecting circular references, and providing a list of all pointers that was referenced at
|
|
// least twice by the provided structure.
|
|
func mapReusedPointers(v reflect.Value) ptrmap {
|
|
pm := &pointerVisitor{}
|
|
pm.consider(v)
|
|
return pm.reused
|
|
}
|
|
|
|
// A map of pointers.
|
|
type (
|
|
ptrinfo struct {
|
|
order int
|
|
}
|
|
ptrmap map[uintptr]ptrinfo
|
|
)
|
|
|
|
// Returns true if contains a pointer.
|
|
func (pm *ptrmap) contains(p uintptr) bool {
|
|
if *pm != nil {
|
|
_, ok := (*pm)[p]
|
|
return ok
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Removes a pointer.
|
|
func (pm *ptrmap) remove(p uintptr) {
|
|
if *pm != nil {
|
|
delete(*pm, p)
|
|
}
|
|
}
|
|
|
|
// Adds a pointer.
|
|
func (pm *ptrmap) add(p uintptr) bool {
|
|
if pm.contains(p) {
|
|
return false
|
|
}
|
|
pm.put(p)
|
|
return true
|
|
}
|
|
|
|
// Adds a pointer (slow path).
|
|
func (pm *ptrmap) put(p uintptr) {
|
|
if *pm == nil {
|
|
*pm = make(map[uintptr]ptrinfo, 31)
|
|
}
|
|
(*pm)[p] = ptrinfo{order: len(*pm)}
|
|
}
|
|
|
|
type pointerVisitor struct {
|
|
pointers ptrmap
|
|
reused ptrmap
|
|
}
|
|
|
|
// Recursively consider v and each of its children, updating the map according to the
|
|
// semantics of MapReusedPointers
|
|
func (pv *pointerVisitor) consider(v reflect.Value) {
|
|
if v.Kind() == reflect.Invalid {
|
|
return
|
|
}
|
|
if isPointerValue(v) && v.Pointer() != 0 { // pointer is 0 for unexported fields
|
|
if pv.tryAddPointer(v.Pointer()) {
|
|
// No use descending inside this value, since it have been seen before and all its descendants
|
|
// have been considered
|
|
return
|
|
}
|
|
}
|
|
|
|
// Now descend into any children of this value
|
|
switch v.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
numEntries := v.Len()
|
|
for i := 0; i < numEntries; i++ {
|
|
pv.consider(v.Index(i))
|
|
}
|
|
|
|
case reflect.Interface:
|
|
pv.consider(v.Elem())
|
|
|
|
case reflect.Ptr:
|
|
pv.consider(v.Elem())
|
|
|
|
case reflect.Map:
|
|
keys := v.MapKeys()
|
|
sort.Sort(mapKeySorter{
|
|
keys: keys,
|
|
options: &Config,
|
|
})
|
|
for _, key := range keys {
|
|
pv.consider(v.MapIndex(key))
|
|
}
|
|
|
|
case reflect.Struct:
|
|
numFields := v.NumField()
|
|
for i := 0; i < numFields; i++ {
|
|
pv.consider(v.Field(i))
|
|
}
|
|
}
|
|
}
|
|
|
|
// addPointer to the pointerMap, update reusedPointers. Returns true if pointer was reused
|
|
func (pv *pointerVisitor) tryAddPointer(p uintptr) bool {
|
|
// Is this allready known to be reused?
|
|
if pv.reused.contains(p) {
|
|
return true
|
|
}
|
|
|
|
// Have we seen it once before?
|
|
if pv.pointers.contains(p) {
|
|
// Add it to the register of pointers we have seen more than once
|
|
pv.reused.add(p)
|
|
return true
|
|
}
|
|
|
|
// This pointer was new to us
|
|
pv.pointers.add(p)
|
|
return false
|
|
}
|