github.com/anchore/syft@v1.38.2/internal/cache/hash_type.go (about) 1 package cache 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "github.com/gohugoio/hashstructure" 8 ) 9 10 // hashType returns a stable hash based on the structure of the type 11 func hashType[T any]() string { 12 // get the base type and hash an empty instance 13 var t T 14 empty := emptyValue(reflect.TypeOf(t)).Interface() 15 hash, err := hashstructure.Hash(empty, &hashstructure.HashOptions{ 16 ZeroNil: false, 17 IgnoreZeroValue: false, 18 SlicesAsSets: false, 19 UseStringer: false, 20 }) 21 if err != nil { 22 panic(fmt.Errorf("unable to use type as cache key: %w", err)) 23 } 24 return fmt.Sprintf("%x", hash) 25 } 26 27 func emptyValue(t reflect.Type) reflect.Value { 28 switch t.Kind() { 29 case reflect.Pointer: 30 e := t.Elem() 31 v := emptyValue(e) 32 if v.CanAddr() { 33 return v.Addr() 34 } 35 ptrv := reflect.New(e) 36 ptrv.Elem().Set(v) 37 return ptrv 38 case reflect.Slice: 39 v := emptyValue(t.Elem()) 40 s := reflect.MakeSlice(t, 1, 1) 41 s.Index(0).Set(v) 42 return s 43 case reflect.Struct: 44 v := reflect.New(t).Elem() 45 // get all empty field values, too 46 for i := 0; i < v.NumField(); i++ { 47 f := t.Field(i) 48 if isIgnored(f) { 49 continue 50 } 51 fv := v.Field(i) 52 if fv.CanSet() { 53 fv.Set(emptyValue(f.Type)) 54 } 55 } 56 return v 57 default: 58 return reflect.New(t).Elem() 59 } 60 } 61 62 func isIgnored(f reflect.StructField) bool { 63 if !f.IsExported() { 64 return true 65 } 66 tag := f.Tag.Get("hash") 67 if tag == "-" || tag == "ignore" { 68 return true 69 } 70 return false 71 }