xref: /aosp_15_r20/build/blueprint/proptools/hash_provider.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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