// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package deephash hashes a Go value recursively, in a predictable order,
// without looping. The hash is only valid within the lifetime of a program.
// Users should not store the hash on disk or send it over the network.
// The hash is sufficiently strong and unique such that
// Hash(x) == Hash(y) is an appropriate replacement for x == y.
//
// The definition of equality is identical to reflect.DeepEqual except:
//	* Floating-point values are compared based on the raw bits,
//	  which means that NaNs (with the same bit pattern) are treated as equal.
//	* Types which implement interface { AppendTo([]byte) []byte } use
//	  the AppendTo method to produce a textual representation of the value.
//	  Thus, two values are equal if AppendTo produces the same bytes.
//
// WARNING: This package, like most of the tailscale.com Go module,
// should be considered Tailscale-internal; we make no API promises.
package deephash

import (
	"bufio"
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"hash"
	"log"
	"math"
	"reflect"
	"sync"
	"time"
	"unsafe"
)

// There is much overlap between the theory of serialization and hashing.
// A hash (useful for determining equality) can be produced by printing a value
// and hashing the output. The format must:
//	* be deterministic such that the same value hashes to the same output, and
//	* be parsable such that the same value can be reproduced by the output.
//
// The logic below hashes a value by printing it to a hash.Hash.
// To be parsable, it assumes that we know the Go type of each value:
//	* scalar types (e.g., bool or int32) are printed as fixed-width fields.
//	* list types (e.g., strings, slices, and AppendTo buffers) are prefixed
//	  by a fixed-width length field, followed by the contents of the list.
//	* slices, arrays, and structs print each element/field consecutively.
//	* interfaces print with a 1-byte prefix indicating whether it is nil.
//	  If non-nil, it is followed by a fixed-width field of the type index,
//	  followed by the format of the underlying value.
//	* pointers print with a 1-byte prefix indicating whether the pointer is
//	  1) nil, 2) previously seen, or 3) newly seen. Previously seen pointers are
//	  followed by a fixed-width field with the index of the previous pointer.
//	  Newly seen pointers are followed by the format of the underlying value.
//	* maps print with a 1-byte prefix indicating whether the map pointer is
//	  1) nil, 2) previously seen, or 3) newly seen. Previously seen pointers
//	  are followed by a fixed-width field of the index of the previous pointer.
//	  Newly seen maps are printed as a fixed-width field with the XOR of the
//	  hash of every map entry. With a sufficiently strong hash, this value is
//	  theoretically "parsable" by looking up the hash in a magical map that
//	  returns the set of entries for that given hash.

const scratchSize = 128

// hasher is reusable state for hashing a value.
// Get one via hasherPool.
type hasher struct {
	h          hash.Hash
	bw         *bufio.Writer
	scratch    [scratchSize]byte
	visitStack visitStack
}

func (h *hasher) reset() {
	if h.h == nil {
		h.h = sha256.New()
	}
	if h.bw == nil {
		h.bw = bufio.NewWriterSize(h.h, h.h.BlockSize())
	}
	h.bw.Flush()
	h.h.Reset()
}

// Sum is an opaque checksum type that is comparable.
type Sum struct {
	sum [sha256.Size]byte
}

func (s1 *Sum) xor(s2 Sum) {
	for i := 0; i < sha256.Size; i++ {
		s1.sum[i] ^= s2.sum[i]
	}
}

func (s Sum) String() string {
	return hex.EncodeToString(s.sum[:])
}

var (
	seedOnce sync.Once
	seed     uint64
)

func initSeed() {
	seed = uint64(time.Now().UnixNano())
}

func (h *hasher) sum() (s Sum) {
	h.bw.Flush()
	// Sum into scratch & copy out, as hash.Hash is an interface
	// so the slice necessarily escapes, and there's no sha256
	// concrete type exported and we don't want the 'hash' result
	// parameter to escape to the heap:
	copy(s.sum[:], h.h.Sum(h.scratch[:0]))
	return s
}

var hasherPool = &sync.Pool{
	New: func() any { return new(hasher) },
}

// Hash returns the hash of v.
func Hash(v any) (s Sum) {
	h := hasherPool.Get().(*hasher)
	defer hasherPool.Put(h)
	h.reset()
	seedOnce.Do(initSeed)
	h.hashUint64(seed)

	rv := reflect.ValueOf(v)
	if rv.IsValid() {
		// Always treat the Hash input as an interface (it is), including hashing
		// its type, otherwise two Hash calls of different types could hash to the
		// same bytes off the different types and get equivalent Sum values. This is
		// the same thing that we do for reflect.Kind Interface in hashValue, but
		// the initial reflect.ValueOf from an interface value effectively strips
		// the interface box off so we have to do it at the top level by hand.
		h.hashType(rv.Type())
		h.hashValue(rv, false)
	}
	return h.sum()
}

// HasherForType is like Hash, but it returns a Hash func that's specialized for
// the provided reflect type, avoiding a map lookup per value.
func HasherForType[T any]() func(T) Sum {
	var zeroT T
	ti := getTypeInfo(reflect.TypeOf(zeroT))
	seedOnce.Do(initSeed)

	return func(v T) Sum {
		h := hasherPool.Get().(*hasher)
		defer hasherPool.Put(h)
		h.reset()
		h.hashUint64(seed)

		rv := reflect.ValueOf(v)

		if rv.IsValid() {
			// Always treat the Hash input as an interface (it is), including hashing
			// its type, otherwise two Hash calls of different types could hash to the
			// same bytes off the different types and get equivalent Sum values. This is
			// the same thing that we do for reflect.Kind Interface in hashValue, but
			// the initial reflect.ValueOf from an interface value effectively strips
			// the interface box off so we have to do it at the top level by hand.
			h.hashType(rv.Type())
			h.hashValueWithType(rv, ti, false)
		}
		return h.sum()
	}
}

// Update sets last to the hash of v and reports whether its value changed.
func Update(last *Sum, v ...any) (changed bool) {
	sum := Hash(v)
	if sum == *last {
		// unchanged.
		return false
	}
	*last = sum
	return true
}

var appenderToType = reflect.TypeOf((*appenderTo)(nil)).Elem()

type appenderTo interface {
	AppendTo([]byte) []byte
}

func (h *hasher) hashUint8(i uint8) {
	h.bw.WriteByte(i)
}
func (h *hasher) hashUint16(i uint16) {
	binary.LittleEndian.PutUint16(h.scratch[:2], i)
	h.bw.Write(h.scratch[:2])
}
func (h *hasher) hashUint32(i uint32) {
	binary.LittleEndian.PutUint32(h.scratch[:4], i)
	h.bw.Write(h.scratch[:4])
}
func (h *hasher) hashLen(n int) {
	binary.LittleEndian.PutUint64(h.scratch[:8], uint64(n))
	h.bw.Write(h.scratch[:8])
}
func (h *hasher) hashUint64(i uint64) {
	binary.LittleEndian.PutUint64(h.scratch[:8], i)
	h.bw.Write(h.scratch[:8])
}

var (
	uint8Type    = reflect.TypeOf(byte(0))
	timeTimeType = reflect.TypeOf(time.Time{})
)

// typeInfo describes properties of a type.
//
// A non-nil typeInfo is populated into the typeHasher map
// when its type is first requested, before its func is created.
// Its func field fn is only populated once the type has been created.
// This is used for recursive types.
type typeInfo struct {
	rtype       reflect.Type
	canMemHash  bool
	isRecursive bool

	// elemTypeInfo is the element type's typeInfo.
	// It's set when rtype is of Kind Ptr, Slice, Array, Map.
	elemTypeInfo *typeInfo

	// keyTypeInfo is the map key type's typeInfo.
	// It's set when rtype is of Kind Map.
	keyTypeInfo *typeInfo

	hashFuncOnce sync.Once
	hashFuncLazy typeHasherFunc // nil until created
}

// returns ok if it was handled; else slow path runs
type typeHasherFunc func(h *hasher, v reflect.Value) (ok bool)

var typeInfoMap sync.Map           // map[reflect.Type]*typeInfo
var typeInfoMapPopulate sync.Mutex // just for adding to typeInfoMap

func (ti *typeInfo) hasher() typeHasherFunc {
	ti.hashFuncOnce.Do(ti.buildHashFuncOnce)
	return ti.hashFuncLazy
}

func (ti *typeInfo) buildHashFuncOnce() {
	ti.hashFuncLazy = genTypeHasher(ti.rtype)
}

func (h *hasher) hashBoolv(v reflect.Value) bool {
	var b byte
	if v.Bool() {
		b = 1
	}
	h.hashUint8(b)
	return true
}

func (h *hasher) hashUint8v(v reflect.Value) bool {
	h.hashUint8(uint8(v.Uint()))
	return true
}

func (h *hasher) hashInt8v(v reflect.Value) bool {
	h.hashUint8(uint8(v.Int()))
	return true
}

func (h *hasher) hashUint16v(v reflect.Value) bool {
	h.hashUint16(uint16(v.Uint()))
	return true
}

func (h *hasher) hashInt16v(v reflect.Value) bool {
	h.hashUint16(uint16(v.Int()))
	return true
}

func (h *hasher) hashUint32v(v reflect.Value) bool {
	h.hashUint32(uint32(v.Uint()))
	return true
}

func (h *hasher) hashInt32v(v reflect.Value) bool {
	h.hashUint32(uint32(v.Int()))
	return true
}

func (h *hasher) hashUint64v(v reflect.Value) bool {
	h.hashUint64(v.Uint())
	return true
}

func (h *hasher) hashInt64v(v reflect.Value) bool {
	h.hashUint64(uint64(v.Int()))
	return true
}

func hashStructAppenderTo(h *hasher, v reflect.Value) bool {
	if !v.CanInterface() {
		return false // slow path
	}
	var a appenderTo
	if v.CanAddr() {
		a = v.Addr().Interface().(appenderTo)
	} else {
		a = v.Interface().(appenderTo)
	}
	size := h.scratch[:8]
	record := a.AppendTo(size)
	binary.LittleEndian.PutUint64(record, uint64(len(record)-len(size)))
	h.bw.Write(record)
	return true
}

// hashPointerAppenderTo hashes v, a reflect.Ptr, that implements appenderTo.
func hashPointerAppenderTo(h *hasher, v reflect.Value) bool {
	if !v.CanInterface() {
		return false // slow path
	}
	if v.IsNil() {
		h.hashUint8(0) // indicates nil
		return true
	}
	h.hashUint8(1) // indicates visiting a pointer
	a := v.Interface().(appenderTo)
	size := h.scratch[:8]
	record := a.AppendTo(size)
	binary.LittleEndian.PutUint64(record, uint64(len(record)-len(size)))
	h.bw.Write(record)
	return true
}

// fieldInfo describes a struct field.
type fieldInfo struct {
	index      int // index of field for reflect.Value.Field(n)
	typeInfo   *typeInfo
	canMemHash bool
	offset     uintptr // when we can memhash the field
	size       uintptr // when we can memhash the field
}

// mergeContiguousFieldsCopy returns a copy of f with contiguous memhashable fields
// merged together. Such fields get a bogus index and fu value.
func mergeContiguousFieldsCopy(in []fieldInfo) []fieldInfo {
	ret := make([]fieldInfo, 0, len(in))
	var last *fieldInfo
	for _, f := range in {
		// Combine two fields if they're both contiguous & memhash-able.
		if f.canMemHash && last != nil && last.canMemHash && last.offset+last.size == f.offset {
			last.size += f.size
			last.index = -1
			last.typeInfo = nil
		} else {
			ret = append(ret, f)
			last = &ret[len(ret)-1]
		}
	}
	return ret
}

// genHashStructFields generates a typeHasherFunc for t, which must be of kind Struct.
func genHashStructFields(t reflect.Type) typeHasherFunc {
	fields := make([]fieldInfo, 0, t.NumField())
	for i, n := 0, t.NumField(); i < n; i++ {
		sf := t.Field(i)
		if sf.Type.Size() == 0 {
			continue
		}
		fields = append(fields, fieldInfo{
			index:      i,
			typeInfo:   getTypeInfo(sf.Type),
			canMemHash: canMemHash(sf.Type),
			offset:     sf.Offset,
			size:       sf.Type.Size(),
		})
	}
	fieldsIfCanAddr := mergeContiguousFieldsCopy(fields)
	return structHasher{fields, fieldsIfCanAddr}.hash
}

type structHasher struct {
	fields, fieldsIfCanAddr []fieldInfo
}

func (sh structHasher) hash(h *hasher, v reflect.Value) bool {
	var base unsafe.Pointer
	if v.CanAddr() {
		base = v.Addr().UnsafePointer()
		for _, f := range sh.fieldsIfCanAddr {
			if f.canMemHash {
				h.bw.Write(unsafe.Slice((*byte)(unsafe.Pointer(uintptr(base)+f.offset)), f.size))
			} else if !f.typeInfo.hasher()(h, v.Field(f.index)) {
				return false
			}
		}
	} else {
		for _, f := range sh.fields {
			if !f.typeInfo.hasher()(h, v.Field(f.index)) {
				return false
			}
		}
	}
	return true
}

// genHashPtrToMemoryRange returns a hasher where the reflect.Value is a Ptr to
// the provided eleType.
func genHashPtrToMemoryRange(eleType reflect.Type) typeHasherFunc {
	size := eleType.Size()
	return func(h *hasher, v reflect.Value) bool {
		if v.IsNil() {
			h.hashUint8(0) // indicates nil
		} else {
			h.hashUint8(1) // indicates visiting a pointer
			h.bw.Write(unsafe.Slice((*byte)(v.UnsafePointer()), size))
		}
		return true
	}
}

const debug = false

func genTypeHasher(t reflect.Type) typeHasherFunc {
	if debug {
		log.Printf("generating func for %v", t)
	}

	switch t.Kind() {
	case reflect.Bool:
		return (*hasher).hashBoolv
	case reflect.Int8:
		return (*hasher).hashInt8v
	case reflect.Int16:
		return (*hasher).hashInt16v
	case reflect.Int32:
		return (*hasher).hashInt32v
	case reflect.Int, reflect.Int64:
		return (*hasher).hashInt64v
	case reflect.Uint8:
		return (*hasher).hashUint8v
	case reflect.Uint16:
		return (*hasher).hashUint16v
	case reflect.Uint32:
		return (*hasher).hashUint32v
	case reflect.Uint, reflect.Uintptr, reflect.Uint64:
		return (*hasher).hashUint64v
	case reflect.Float32:
		return (*hasher).hashFloat32v
	case reflect.Float64:
		return (*hasher).hashFloat64v
	case reflect.Complex64:
		return (*hasher).hashComplex64v
	case reflect.Complex128:
		return (*hasher).hashComplex128v
	case reflect.String:
		return (*hasher).hashString
	case reflect.Slice:
		et := t.Elem()
		if canMemHash(et) {
			return (*hasher).hashSliceMem
		}
		eti := getTypeInfo(et)
		return genHashSliceElements(eti)
	case reflect.Array:
		et := t.Elem()
		eti := getTypeInfo(et)
		return genHashArray(t, eti)
	case reflect.Struct:
		if t == timeTimeType {
			return (*hasher).hashTimev
		}
		if t.Implements(appenderToType) {
			return hashStructAppenderTo
		}
		return genHashStructFields(t)
	case reflect.Pointer:
		et := t.Elem()
		if canMemHash(et) {
			return genHashPtrToMemoryRange(et)
		}
		if t.Implements(appenderToType) {
			return hashPointerAppenderTo
		}
		if !typeIsRecursive(t) {
			eti := getTypeInfo(et)
			return func(h *hasher, v reflect.Value) bool {
				if v.IsNil() {
					h.hashUint8(0) // indicates nil
					return true
				}
				h.hashUint8(1) // indicates visiting a pointer
				return eti.hasher()(h, v.Elem())
			}
		}
	}

	return func(h *hasher, v reflect.Value) bool {
		if debug {
			log.Printf("unhandled type %v", v.Type())
		}
		return false
	}
}

// hashString hashes v, of kind String.
func (h *hasher) hashString(v reflect.Value) bool {
	s := v.String()
	h.hashLen(len(s))
	h.bw.WriteString(s)
	return true
}

func (h *hasher) hashFloat32v(v reflect.Value) bool {
	h.hashUint32(math.Float32bits(float32(v.Float())))
	return true
}

func (h *hasher) hashFloat64v(v reflect.Value) bool {
	h.hashUint64(math.Float64bits(v.Float()))
	return true
}

func (h *hasher) hashComplex64v(v reflect.Value) bool {
	c := complex64(v.Complex())
	h.hashUint32(math.Float32bits(real(c)))
	h.hashUint32(math.Float32bits(imag(c)))
	return true
}

func (h *hasher) hashComplex128v(v reflect.Value) bool {
	c := v.Complex()
	h.hashUint64(math.Float64bits(real(c)))
	h.hashUint64(math.Float64bits(imag(c)))
	return true
}

// hashString hashes v, of kind time.Time.
func (h *hasher) hashTimev(v reflect.Value) bool {
	var t time.Time
	if v.CanAddr() {
		t = *(*time.Time)(v.Addr().UnsafePointer())
	} else if v.CanInterface() {
		t = v.Interface().(time.Time)
	} else {
		return false
	}
	b := t.AppendFormat(h.scratch[:1], time.RFC3339Nano)
	b[0] = byte(len(b) - 1) // more than sufficient width; if not, good enough.
	h.bw.Write(b)
	return true
}

// hashSliceMem hashes v, of kind Slice, with a memhash-able element type.
func (h *hasher) hashSliceMem(v reflect.Value) bool {
	vLen := v.Len()
	h.hashUint64(uint64(vLen))
	if vLen == 0 {
		return true
	}
	h.bw.Write(unsafe.Slice((*byte)(v.UnsafePointer()), v.Type().Elem().Size()*uintptr(vLen)))
	return true
}

func genHashArrayMem(n int, arraySize uintptr, efu *typeInfo) typeHasherFunc {
	byElement := genHashArrayElements(n, efu)
	return func(h *hasher, v reflect.Value) bool {
		if v.CanAddr() {
			h.bw.Write(unsafe.Slice((*byte)(v.Addr().UnsafePointer()), arraySize))
			return true
		}
		return byElement(h, v)
	}
}

func genHashArrayElements(n int, eti *typeInfo) typeHasherFunc {
	return func(h *hasher, v reflect.Value) bool {
		for i := 0; i < n; i++ {
			if !eti.hasher()(h, v.Index(i)) {
				return false
			}
		}
		return true
	}
}

func noopHasherFunc(h *hasher, v reflect.Value) bool { return true }

func genHashArray(t reflect.Type, eti *typeInfo) typeHasherFunc {
	if t.Size() == 0 {
		return noopHasherFunc
	}
	et := t.Elem()
	if canMemHash(et) {
		return genHashArrayMem(t.Len(), t.Size(), eti)
	}
	n := t.Len()
	return genHashArrayElements(n, eti)
}

func genHashSliceElements(eti *typeInfo) typeHasherFunc {
	return sliceElementHasher{eti}.hash
}

type sliceElementHasher struct {
	eti *typeInfo
}

func (seh sliceElementHasher) hash(h *hasher, v reflect.Value) bool {
	vLen := v.Len()
	h.hashUint64(uint64(vLen))
	for i := 0; i < vLen; i++ {
		if !seh.eti.hasher()(h, v.Index(i)) {
			return false
		}
	}
	return true
}

func getTypeInfo(t reflect.Type) *typeInfo {
	if f, ok := typeInfoMap.Load(t); ok {
		return f.(*typeInfo)
	}
	typeInfoMapPopulate.Lock()
	defer typeInfoMapPopulate.Unlock()
	newTypes := map[reflect.Type]*typeInfo{}
	ti := getTypeInfoLocked(t, newTypes)
	for t, ti := range newTypes {
		typeInfoMap.Store(t, ti)
	}
	return ti
}

func getTypeInfoLocked(t reflect.Type, incomplete map[reflect.Type]*typeInfo) *typeInfo {
	if v, ok := typeInfoMap.Load(t); ok {
		return v.(*typeInfo)
	}
	if ti, ok := incomplete[t]; ok {
		return ti
	}
	ti := &typeInfo{
		rtype:       t,
		isRecursive: typeIsRecursive(t),
		canMemHash:  canMemHash(t),
	}
	incomplete[t] = ti

	switch t.Kind() {
	case reflect.Map:
		ti.keyTypeInfo = getTypeInfoLocked(t.Key(), incomplete)
		fallthrough
	case reflect.Ptr, reflect.Slice, reflect.Array:
		ti.elemTypeInfo = getTypeInfoLocked(t.Elem(), incomplete)
	}

	return ti
}

// typeIsRecursive reports whether t has a path back to itself.
//
// For interfaces, it currently always reports true.
func typeIsRecursive(t reflect.Type) bool {
	inStack := map[reflect.Type]bool{}

	var stack []reflect.Type

	var visitType func(t reflect.Type) (isRecursiveSoFar bool)
	visitType = func(t reflect.Type) (isRecursiveSoFar bool) {
		switch t.Kind() {
		case reflect.Bool,
			reflect.Int,
			reflect.Int8,
			reflect.Int16,
			reflect.Int32,
			reflect.Int64,
			reflect.Uint,
			reflect.Uint8,
			reflect.Uint16,
			reflect.Uint32,
			reflect.Uint64,
			reflect.Uintptr,
			reflect.Float32,
			reflect.Float64,
			reflect.Complex64,
			reflect.Complex128,
			reflect.String,
			reflect.UnsafePointer,
			reflect.Func:
			return false
		}
		if t.Size() == 0 {
			return false
		}
		if inStack[t] {
			return true
		}
		stack = append(stack, t)
		inStack[t] = true
		defer func() {
			delete(inStack, t)
			stack = stack[:len(stack)-1]
		}()

		switch t.Kind() {
		default:
			panic("unhandled kind " + t.Kind().String())
		case reflect.Interface:
			// Assume the worst for now. TODO(bradfitz): in some cases
			// we should be able to prove that it's not recursive. Not worth
			// it for now.
			return true
		case reflect.Array, reflect.Chan, reflect.Pointer, reflect.Slice:
			return visitType(t.Elem())
		case reflect.Map:
			if visitType(t.Key()) {
				return true
			}
			if visitType(t.Elem()) {
				return true
			}
		case reflect.Struct:
			if t.String() == "intern.Value" {
				// Otherwise its interface{} makes this return true.
				return false
			}
			for i, numField := 0, t.NumField(); i < numField; i++ {
				if visitType(t.Field(i).Type) {
					return true
				}
			}
			return false
		}
		return false
	}
	return visitType(t)
}

// canMemHash reports whether a slice of t can be hashed by looking at its
// contiguous bytes in memory alone. (e.g. structs with gaps aren't memhashable)
func canMemHash(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float64, reflect.Float32, reflect.Complex128, reflect.Complex64:
		return true
	case reflect.Array:
		return canMemHash(t.Elem())
	case reflect.Struct:
		var sumFieldSize uintptr
		for i, numField := 0, t.NumField(); i < numField; i++ {
			sf := t.Field(i)
			if !canMemHash(sf.Type) {
				// Special case for 0-width fields that aren't at the end.
				if sf.Type.Size() == 0 && i < numField-1 {
					continue
				}
				return false
			}
			sumFieldSize += sf.Type.Size()
		}
		return sumFieldSize == t.Size() // else there are gaps
	}
	return false
}

func (h *hasher) hashValue(v reflect.Value, forceCycleChecking bool) {
	if !v.IsValid() {
		return
	}
	ti := getTypeInfo(v.Type())
	h.hashValueWithType(v, ti, forceCycleChecking)
}

func (h *hasher) hashValueWithType(v reflect.Value, ti *typeInfo, forceCycleChecking bool) {
	w := h.bw
	doCheckCycles := forceCycleChecking || ti.isRecursive

	if !doCheckCycles {
		hf := ti.hasher()
		if hf(h, v) {
			return
		}
	}

	// Generic handling.
	switch v.Kind() {
	default:
		panic(fmt.Sprintf("unhandled kind %v for type %v", v.Kind(), v.Type()))
	case reflect.Ptr:
		if v.IsNil() {
			h.hashUint8(0) // indicates nil
			return
		}

		if doCheckCycles {
			ptr := pointerOf(v)
			if idx, ok := h.visitStack.seen(ptr); ok {
				h.hashUint8(2) // indicates cycle
				h.hashUint64(uint64(idx))
				return
			}
			h.visitStack.push(ptr)
			defer h.visitStack.pop(ptr)
		}

		h.hashUint8(1) // indicates visiting a pointer
		h.hashValueWithType(v.Elem(), ti.elemTypeInfo, doCheckCycles)
	case reflect.Struct:
		for i, n := 0, v.NumField(); i < n; i++ {
			h.hashValue(v.Field(i), doCheckCycles)
		}
	case reflect.Slice, reflect.Array:
		vLen := v.Len()
		if v.Kind() == reflect.Slice {
			h.hashUint64(uint64(vLen))
		}
		if v.Type().Elem() == uint8Type && v.CanInterface() {
			if vLen > 0 && vLen <= scratchSize {
				// If it fits in scratch, avoid the Interface allocation.
				// It seems tempting to do this for all sizes, doing
				// scratchSize bytes at a time, but reflect.Slice seems
				// to allocate, so it's not a win.
				n := reflect.Copy(reflect.ValueOf(&h.scratch).Elem(), v)
				w.Write(h.scratch[:n])
				return
			}
			fmt.Fprintf(w, "%s", v.Interface())
			return
		}
		for i := 0; i < vLen; i++ {
			// TODO(dsnet): Perform cycle detection for slices,
			// which is functionally a list of pointers.
			// See https://github.com/google/go-cmp/blob/402949e8139bb890c71a707b6faf6dd05c92f4e5/cmp/compare.go#L438-L450
			h.hashValueWithType(v.Index(i), ti.elemTypeInfo, doCheckCycles)
		}
	case reflect.Interface:
		if v.IsNil() {
			h.hashUint8(0) // indicates nil
			return
		}
		v = v.Elem()

		h.hashUint8(1) // indicates visiting interface value
		h.hashType(v.Type())
		h.hashValue(v, doCheckCycles)
	case reflect.Map:
		// Check for cycle.
		if doCheckCycles {
			ptr := pointerOf(v)
			if idx, ok := h.visitStack.seen(ptr); ok {
				h.hashUint8(2) // indicates cycle
				h.hashUint64(uint64(idx))
				return
			}
			h.visitStack.push(ptr)
			defer h.visitStack.pop(ptr)
		}
		h.hashUint8(1) // indicates visiting a map
		h.hashMap(v, ti, doCheckCycles)
	case reflect.String:
		s := v.String()
		h.hashUint64(uint64(len(s)))
		w.WriteString(s)
	case reflect.Bool:
		if v.Bool() {
			h.hashUint8(1)
		} else {
			h.hashUint8(0)
		}
	case reflect.Int8:
		h.hashUint8(uint8(v.Int()))
	case reflect.Int16:
		h.hashUint16(uint16(v.Int()))
	case reflect.Int32:
		h.hashUint32(uint32(v.Int()))
	case reflect.Int64, reflect.Int:
		h.hashUint64(uint64(v.Int()))
	case reflect.Uint8:
		h.hashUint8(uint8(v.Uint()))
	case reflect.Uint16:
		h.hashUint16(uint16(v.Uint()))
	case reflect.Uint32:
		h.hashUint32(uint32(v.Uint()))
	case reflect.Uint64, reflect.Uint, reflect.Uintptr:
		h.hashUint64(uint64(v.Uint()))
	case reflect.Float32:
		h.hashUint32(math.Float32bits(float32(v.Float())))
	case reflect.Float64:
		h.hashUint64(math.Float64bits(float64(v.Float())))
	case reflect.Complex64:
		h.hashUint32(math.Float32bits(real(complex64(v.Complex()))))
		h.hashUint32(math.Float32bits(imag(complex64(v.Complex()))))
	case reflect.Complex128:
		h.hashUint64(math.Float64bits(real(complex128(v.Complex()))))
		h.hashUint64(math.Float64bits(imag(complex128(v.Complex()))))
	}
}

type mapHasher struct {
	h               hasher
	valKey, valElem valueCache      // re-usable values for map iteration
	iter            reflect.MapIter // re-usable map iterator
}

var mapHasherPool = &sync.Pool{
	New: func() any { return new(mapHasher) },
}

type valueCache map[reflect.Type]reflect.Value

func (c *valueCache) get(t reflect.Type) reflect.Value {
	v, ok := (*c)[t]
	if !ok {
		v = reflect.New(t).Elem()
		if *c == nil {
			*c = make(valueCache)
		}
		(*c)[t] = v
	}
	return v
}

// hashMap hashes a map in a sort-free manner.
// It relies on a map being a functionally an unordered set of KV entries.
// So long as we hash each KV entry together, we can XOR all
// of the individual hashes to produce a unique hash for the entire map.
func (h *hasher) hashMap(v reflect.Value, ti *typeInfo, checkCycles bool) {
	mh := mapHasherPool.Get().(*mapHasher)
	defer mapHasherPool.Put(mh)

	iter := &mh.iter
	iter.Reset(v)
	defer iter.Reset(reflect.Value{}) // avoid pinning v from mh.iter when we return

	var sum Sum
	if v.IsNil() {
		sum.sum[0] = 1 // something non-zero
	}

	k := mh.valKey.get(v.Type().Key())
	e := mh.valElem.get(v.Type().Elem())
	mh.h.visitStack = h.visitStack // always use the parent's visit stack to avoid cycles
	for iter.Next() {
		k.SetIterKey(iter)
		e.SetIterValue(iter)
		mh.h.reset()
		mh.h.hashValueWithType(k, ti.keyTypeInfo, checkCycles)
		mh.h.hashValueWithType(e, ti.elemTypeInfo, checkCycles)
		sum.xor(mh.h.sum())
	}
	h.bw.Write(append(h.scratch[:0], sum.sum[:]...)) // append into scratch to avoid heap allocation
}

// visitStack is a stack of pointers visited.
// Pointers are pushed onto the stack when visited, and popped when leaving.
// The integer value is the depth at which the pointer was visited.
// The length of this stack should be zero after every hashing operation.
type visitStack map[pointer]int

func (v visitStack) seen(p pointer) (int, bool) {
	idx, ok := v[p]
	return idx, ok
}

func (v *visitStack) push(p pointer) {
	if *v == nil {
		*v = make(map[pointer]int)
	}
	(*v)[p] = len(*v)
}

func (v visitStack) pop(p pointer) {
	delete(v, p)
}

// pointer is a thin wrapper over unsafe.Pointer.
// We only rely on comparability of pointers; we cannot rely on uintptr since
// that would break if Go ever switched to a moving GC.
type pointer struct{ p unsafe.Pointer }

func pointerOf(v reflect.Value) pointer {
	return pointer{unsafe.Pointer(v.Pointer())}
}

// hashType hashes a reflect.Type.
// The hash is only consistent within the lifetime of a program.
func (h *hasher) hashType(t reflect.Type) {
	// This approach relies on reflect.Type always being backed by a unique
	// *reflect.rtype pointer. A safer approach is to use a global sync.Map
	// that maps reflect.Type to some arbitrary and unique index.
	// While safer, it requires global state with memory that can never be GC'd.
	rtypeAddr := reflect.ValueOf(t).Pointer() // address of *reflect.rtype
	h.hashUint64(uint64(rtypeAddr))
}
