1*1fa6dee9SAndroid Build Coastguard Worker// Copyright 2023 Google Inc. All rights reserved. 2*1fa6dee9SAndroid Build Coastguard Worker// 3*1fa6dee9SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*1fa6dee9SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*1fa6dee9SAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*1fa6dee9SAndroid Build Coastguard Worker// 7*1fa6dee9SAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*1fa6dee9SAndroid Build Coastguard Worker// 9*1fa6dee9SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*1fa6dee9SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*1fa6dee9SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*1fa6dee9SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*1fa6dee9SAndroid Build Coastguard Worker// limitations under the License. 14*1fa6dee9SAndroid Build Coastguard Worker 15*1fa6dee9SAndroid Build Coastguard Workerpackage proptools 16*1fa6dee9SAndroid Build Coastguard Worker 17*1fa6dee9SAndroid Build Coastguard Workerimport ( 18*1fa6dee9SAndroid Build Coastguard Worker "cmp" 19*1fa6dee9SAndroid Build Coastguard Worker "encoding/binary" 20*1fa6dee9SAndroid Build Coastguard Worker "fmt" 21*1fa6dee9SAndroid Build Coastguard Worker "hash" 22*1fa6dee9SAndroid Build Coastguard Worker "hash/fnv" 23*1fa6dee9SAndroid Build Coastguard Worker "math" 24*1fa6dee9SAndroid Build Coastguard Worker "reflect" 25*1fa6dee9SAndroid Build Coastguard Worker "sort" 26*1fa6dee9SAndroid Build Coastguard Worker "unsafe" 27*1fa6dee9SAndroid Build Coastguard Worker) 28*1fa6dee9SAndroid Build Coastguard Worker 29*1fa6dee9SAndroid Build Coastguard Worker// byte to insert between elements of lists, fields of structs/maps, etc in order 30*1fa6dee9SAndroid Build Coastguard Worker// to try and make sure the hash is different when values are moved around between 31*1fa6dee9SAndroid Build Coastguard Worker// elements. 36 is arbitrary, but it's the ascii code for a record separator 32*1fa6dee9SAndroid Build Coastguard Workervar recordSeparator []byte = []byte{36} 33*1fa6dee9SAndroid Build Coastguard Worker 34*1fa6dee9SAndroid Build Coastguard Workerfunc CalculateHash(value interface{}) (uint64, error) { 35*1fa6dee9SAndroid Build Coastguard Worker hasher := fnv.New64() 36*1fa6dee9SAndroid Build Coastguard Worker ptrs := make(map[uintptr]bool) 37*1fa6dee9SAndroid Build Coastguard Worker v := reflect.ValueOf(value) 38*1fa6dee9SAndroid Build Coastguard Worker var err error 39*1fa6dee9SAndroid Build Coastguard Worker if v.IsValid() { 40*1fa6dee9SAndroid Build Coastguard Worker err = calculateHashInternal(hasher, v, ptrs) 41*1fa6dee9SAndroid Build Coastguard Worker } 42*1fa6dee9SAndroid Build Coastguard Worker return hasher.Sum64(), err 43*1fa6dee9SAndroid Build Coastguard Worker} 44*1fa6dee9SAndroid Build Coastguard Worker 45*1fa6dee9SAndroid Build Coastguard Workerfunc calculateHashInternal(hasher hash.Hash64, v reflect.Value, ptrs map[uintptr]bool) error { 46*1fa6dee9SAndroid Build Coastguard Worker var int64Array [8]byte 47*1fa6dee9SAndroid Build Coastguard Worker int64Buf := int64Array[:] 48*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, uint64(v.Kind())) 49*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 50*1fa6dee9SAndroid Build Coastguard Worker v.IsValid() 51*1fa6dee9SAndroid Build Coastguard Worker switch v.Kind() { 52*1fa6dee9SAndroid Build Coastguard Worker case reflect.Struct: 53*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, uint64(v.NumField())) 54*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 55*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < v.NumField(); i++ { 56*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(recordSeparator) 57*1fa6dee9SAndroid Build Coastguard Worker err := calculateHashInternal(hasher, v.Field(i), ptrs) 58*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 59*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("in field %s: %s", v.Type().Field(i).Name, err.Error()) 60*1fa6dee9SAndroid Build Coastguard Worker } 61*1fa6dee9SAndroid Build Coastguard Worker } 62*1fa6dee9SAndroid Build Coastguard Worker case reflect.Map: 63*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, uint64(v.Len())) 64*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 65*1fa6dee9SAndroid Build Coastguard Worker indexes := make([]int, v.Len()) 66*1fa6dee9SAndroid Build Coastguard Worker keys := make([]reflect.Value, v.Len()) 67*1fa6dee9SAndroid Build Coastguard Worker values := make([]reflect.Value, v.Len()) 68*1fa6dee9SAndroid Build Coastguard Worker iter := v.MapRange() 69*1fa6dee9SAndroid Build Coastguard Worker for i := 0; iter.Next(); i++ { 70*1fa6dee9SAndroid Build Coastguard Worker indexes[i] = i 71*1fa6dee9SAndroid Build Coastguard Worker keys[i] = iter.Key() 72*1fa6dee9SAndroid Build Coastguard Worker values[i] = iter.Value() 73*1fa6dee9SAndroid Build Coastguard Worker } 74*1fa6dee9SAndroid Build Coastguard Worker sort.SliceStable(indexes, func(i, j int) bool { 75*1fa6dee9SAndroid Build Coastguard Worker return compare_values(keys[indexes[i]], keys[indexes[j]]) < 0 76*1fa6dee9SAndroid Build Coastguard Worker }) 77*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < v.Len(); i++ { 78*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(recordSeparator) 79*1fa6dee9SAndroid Build Coastguard Worker err := calculateHashInternal(hasher, keys[indexes[i]], ptrs) 80*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 81*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("in map: %s", err.Error()) 82*1fa6dee9SAndroid Build Coastguard Worker } 83*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(recordSeparator) 84*1fa6dee9SAndroid Build Coastguard Worker err = calculateHashInternal(hasher, keys[indexes[i]], ptrs) 85*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 86*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("in map: %s", err.Error()) 87*1fa6dee9SAndroid Build Coastguard Worker } 88*1fa6dee9SAndroid Build Coastguard Worker } 89*1fa6dee9SAndroid Build Coastguard Worker case reflect.Slice, reflect.Array: 90*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, uint64(v.Len())) 91*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 92*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < v.Len(); i++ { 93*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(recordSeparator) 94*1fa6dee9SAndroid Build Coastguard Worker err := calculateHashInternal(hasher, v.Index(i), ptrs) 95*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 96*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("in %s at index %d: %s", v.Kind().String(), i, err.Error()) 97*1fa6dee9SAndroid Build Coastguard Worker } 98*1fa6dee9SAndroid Build Coastguard Worker } 99*1fa6dee9SAndroid Build Coastguard Worker case reflect.Pointer: 100*1fa6dee9SAndroid Build Coastguard Worker if v.IsNil() { 101*1fa6dee9SAndroid Build Coastguard Worker int64Buf[0] = 0 102*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf[:1]) 103*1fa6dee9SAndroid Build Coastguard Worker return nil 104*1fa6dee9SAndroid Build Coastguard Worker } 105*1fa6dee9SAndroid Build Coastguard Worker // Hardcoded value to indicate it is a pointer 106*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, uint64(0x55)) 107*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 108*1fa6dee9SAndroid Build Coastguard Worker addr := v.Pointer() 109*1fa6dee9SAndroid Build Coastguard Worker if _, ok := ptrs[addr]; ok { 110*1fa6dee9SAndroid Build Coastguard Worker // We could make this an error if we want to disallow pointer cycles in the future 111*1fa6dee9SAndroid Build Coastguard Worker return nil 112*1fa6dee9SAndroid Build Coastguard Worker } 113*1fa6dee9SAndroid Build Coastguard Worker ptrs[addr] = true 114*1fa6dee9SAndroid Build Coastguard Worker err := calculateHashInternal(hasher, v.Elem(), ptrs) 115*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 116*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("in pointer: %s", err.Error()) 117*1fa6dee9SAndroid Build Coastguard Worker } 118*1fa6dee9SAndroid Build Coastguard Worker case reflect.Interface: 119*1fa6dee9SAndroid Build Coastguard Worker if v.IsNil() { 120*1fa6dee9SAndroid Build Coastguard Worker int64Buf[0] = 0 121*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf[:1]) 122*1fa6dee9SAndroid Build Coastguard Worker } else { 123*1fa6dee9SAndroid Build Coastguard Worker // The only way get the pointer out of an interface to hash it or check for cycles 124*1fa6dee9SAndroid Build Coastguard Worker // would be InterfaceData(), but that's deprecated and seems like it has undefined behavior. 125*1fa6dee9SAndroid Build Coastguard Worker err := calculateHashInternal(hasher, v.Elem(), ptrs) 126*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 127*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("in interface: %s", err.Error()) 128*1fa6dee9SAndroid Build Coastguard Worker } 129*1fa6dee9SAndroid Build Coastguard Worker } 130*1fa6dee9SAndroid Build Coastguard Worker case reflect.String: 131*1fa6dee9SAndroid Build Coastguard Worker strLen := len(v.String()) 132*1fa6dee9SAndroid Build Coastguard Worker if strLen == 0 { 133*1fa6dee9SAndroid Build Coastguard Worker // unsafe.StringData is unspecified in this case 134*1fa6dee9SAndroid Build Coastguard Worker int64Buf[0] = 0 135*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf[:1]) 136*1fa6dee9SAndroid Build Coastguard Worker return nil 137*1fa6dee9SAndroid Build Coastguard Worker } 138*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(unsafe.Slice(unsafe.StringData(v.String()), strLen)) 139*1fa6dee9SAndroid Build Coastguard Worker case reflect.Bool: 140*1fa6dee9SAndroid Build Coastguard Worker if v.Bool() { 141*1fa6dee9SAndroid Build Coastguard Worker int64Buf[0] = 1 142*1fa6dee9SAndroid Build Coastguard Worker } else { 143*1fa6dee9SAndroid Build Coastguard Worker int64Buf[0] = 0 144*1fa6dee9SAndroid Build Coastguard Worker } 145*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf[:1]) 146*1fa6dee9SAndroid Build Coastguard Worker case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 147*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, v.Uint()) 148*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 149*1fa6dee9SAndroid Build Coastguard Worker case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 150*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, uint64(v.Int())) 151*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 152*1fa6dee9SAndroid Build Coastguard Worker case reflect.Float32, reflect.Float64: 153*1fa6dee9SAndroid Build Coastguard Worker binary.LittleEndian.PutUint64(int64Buf, math.Float64bits(v.Float())) 154*1fa6dee9SAndroid Build Coastguard Worker hasher.Write(int64Buf) 155*1fa6dee9SAndroid Build Coastguard Worker default: 156*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("data may only contain primitives, strings, arrays, slices, structs, maps, and pointers, found: %s", v.Kind().String()) 157*1fa6dee9SAndroid Build Coastguard Worker } 158*1fa6dee9SAndroid Build Coastguard Worker return nil 159*1fa6dee9SAndroid Build Coastguard Worker} 160*1fa6dee9SAndroid Build Coastguard Worker 161*1fa6dee9SAndroid Build Coastguard Workerfunc compare_values(x, y reflect.Value) int { 162*1fa6dee9SAndroid Build Coastguard Worker if x.Type() != y.Type() { 163*1fa6dee9SAndroid Build Coastguard Worker panic("Expected equal types") 164*1fa6dee9SAndroid Build Coastguard Worker } 165*1fa6dee9SAndroid Build Coastguard Worker 166*1fa6dee9SAndroid Build Coastguard Worker switch x.Kind() { 167*1fa6dee9SAndroid Build Coastguard Worker case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 168*1fa6dee9SAndroid Build Coastguard Worker return cmp.Compare(x.Uint(), y.Uint()) 169*1fa6dee9SAndroid Build Coastguard Worker case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 170*1fa6dee9SAndroid Build Coastguard Worker return cmp.Compare(x.Int(), y.Int()) 171*1fa6dee9SAndroid Build Coastguard Worker case reflect.Float32, reflect.Float64: 172*1fa6dee9SAndroid Build Coastguard Worker return cmp.Compare(x.Float(), y.Float()) 173*1fa6dee9SAndroid Build Coastguard Worker case reflect.String: 174*1fa6dee9SAndroid Build Coastguard Worker return cmp.Compare(x.String(), y.String()) 175*1fa6dee9SAndroid Build Coastguard Worker case reflect.Bool: 176*1fa6dee9SAndroid Build Coastguard Worker if x.Bool() == y.Bool() { 177*1fa6dee9SAndroid Build Coastguard Worker return 0 178*1fa6dee9SAndroid Build Coastguard Worker } else if x.Bool() { 179*1fa6dee9SAndroid Build Coastguard Worker return 1 180*1fa6dee9SAndroid Build Coastguard Worker } else { 181*1fa6dee9SAndroid Build Coastguard Worker return -1 182*1fa6dee9SAndroid Build Coastguard Worker } 183*1fa6dee9SAndroid Build Coastguard Worker case reflect.Pointer: 184*1fa6dee9SAndroid Build Coastguard Worker return cmp.Compare(x.Pointer(), y.Pointer()) 185*1fa6dee9SAndroid Build Coastguard Worker case reflect.Array: 186*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < x.Len(); i++ { 187*1fa6dee9SAndroid Build Coastguard Worker if result := compare_values(x.Index(i), y.Index(i)); result != 0 { 188*1fa6dee9SAndroid Build Coastguard Worker return result 189*1fa6dee9SAndroid Build Coastguard Worker } 190*1fa6dee9SAndroid Build Coastguard Worker } 191*1fa6dee9SAndroid Build Coastguard Worker return 0 192*1fa6dee9SAndroid Build Coastguard Worker case reflect.Struct: 193*1fa6dee9SAndroid Build Coastguard Worker for i := 0; i < x.NumField(); i++ { 194*1fa6dee9SAndroid Build Coastguard Worker if result := compare_values(x.Field(i), y.Field(i)); result != 0 { 195*1fa6dee9SAndroid Build Coastguard Worker return result 196*1fa6dee9SAndroid Build Coastguard Worker } 197*1fa6dee9SAndroid Build Coastguard Worker } 198*1fa6dee9SAndroid Build Coastguard Worker return 0 199*1fa6dee9SAndroid Build Coastguard Worker case reflect.Interface: 200*1fa6dee9SAndroid Build Coastguard Worker if x.IsNil() && y.IsNil() { 201*1fa6dee9SAndroid Build Coastguard Worker return 0 202*1fa6dee9SAndroid Build Coastguard Worker } else if x.IsNil() { 203*1fa6dee9SAndroid Build Coastguard Worker return 1 204*1fa6dee9SAndroid Build Coastguard Worker } else if y.IsNil() { 205*1fa6dee9SAndroid Build Coastguard Worker return -1 206*1fa6dee9SAndroid Build Coastguard Worker } 207*1fa6dee9SAndroid Build Coastguard Worker return compare_values(x.Elem(), y.Elem()) 208*1fa6dee9SAndroid Build Coastguard Worker default: 209*1fa6dee9SAndroid Build Coastguard Worker panic(fmt.Sprintf("Could not compare types %s and %s", x.Type().String(), y.Type().String())) 210*1fa6dee9SAndroid Build Coastguard Worker } 211*1fa6dee9SAndroid Build Coastguard Worker} 212