mirror of
https://github.com/burrowers/garble.git
synced 2026-04-22 15:47:04 +08:00
e693cd0632
Avoid hashing free type parameters by object/name identity in type
hashing and use traversal-local canonical IDs instead.
This keeps obfuscated field names stable across alpha-renamed generic
signatures (e.g. T vs newT), fixing interface/method mismatches.
The added test case fails without the fix:
> exec garble build
[stderr]
# test/main
YiaBFlNH.go:48: cannot use bT6psGs5O (variable of type *QdwRyqV[aEqmF4M, zpotHfQdnNB]) as E_OKfdHoPH[zpotHfQdnNB] value in return statement: *QdwRyqV[aEqmF4M, zpotHfQdnNB] does not implement E_OKfdHoPH[zpotHfQdnNB] (wrong type for method Redirect)
have Redirect(struct{palTavb zpotHfQdnNB})
want Redirect(struct{g3N8A_R zpotHfQdnNB})
The fix is Paul's; this is rebased on an updated version of typeutil,
and the test is now part of typeparams.txtar and much smaller.
Fixes #991.
Co-authored-by: Paul Scheduikat <lu4p@pm.me>
307 lines
8.3 KiB
Go
307 lines
8.3 KiB
Go
// NOTE(garble): bundled as of golang.org/x/tools v0.42.0; see CONTRIBUTING.md.
|
||
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"go/types"
|
||
_ "unsafe"
|
||
)
|
||
|
||
// -- Hasher --
|
||
|
||
// hash returns the hash of type t.
|
||
// TODO(adonovan): replace by types.Hash when Go proposal #69420 is accepted.
|
||
func typeutil_hash(t types.Type) uint32 {
|
||
return typeutil_theHasher.Hash(t)
|
||
}
|
||
|
||
// A Hasher provides a [Hasher.Hash] method to map a type to its hash value.
|
||
// Hashers are stateless, and all are equivalent.
|
||
type typeutil_Hasher struct{}
|
||
|
||
var typeutil_theHasher typeutil_Hasher
|
||
|
||
// Hash computes a hash value for the given type t such that
|
||
// Identical(t, t') => Hash(t) == Hash(t').
|
||
func (h typeutil_Hasher) Hash(t types.Type) uint32 {
|
||
return typeutil_hasher{
|
||
inGenericSig: false,
|
||
typeParamIDs: make(map[*types.TypeParam]uint32),
|
||
}.hash(t)
|
||
}
|
||
|
||
// hasher holds the state of a single Hash traversal: whether we are
|
||
// inside the signature of a generic function; this is used to
|
||
// optimize [hasher.hashTypeParam].
|
||
//
|
||
// typeParamIDs assigns deterministic traversal-local IDs to free type params,
|
||
// making hashes stable across alpha-renaming in equivalent generic contexts.
|
||
type typeutil_hasher struct {
|
||
inGenericSig bool
|
||
typeParamIDs map[*types.TypeParam]uint32
|
||
}
|
||
|
||
// hashString computes the Fowler–Noll–Vo hash of s.
|
||
func typeutil_hashString(s string) uint32 {
|
||
var h uint32
|
||
for i := 0; i < len(s); i++ {
|
||
h ^= uint32(s[i])
|
||
h *= 16777619
|
||
}
|
||
return h
|
||
}
|
||
|
||
// hash computes the hash of t.
|
||
func (h typeutil_hasher) hash(t types.Type) uint32 {
|
||
// See Identical for rationale.
|
||
switch t := t.(type) {
|
||
case *types.Basic:
|
||
return uint32(t.Kind())
|
||
|
||
case *types.Alias:
|
||
return h.hash(types.Unalias(t))
|
||
|
||
case *types.Array:
|
||
return 9043 + 2*uint32(t.Len()) + 3*h.hash(t.Elem())
|
||
|
||
case *types.Slice:
|
||
return 9049 + 2*h.hash(t.Elem())
|
||
|
||
case *types.Struct:
|
||
var hash uint32 = 9059
|
||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||
f := t.Field(i)
|
||
if f.Anonymous() {
|
||
hash += 8861
|
||
}
|
||
// NOTE(garble): we must not hash struct field tags, as they do not affect type identity.
|
||
// hash += typeutil_hashString(t.Tag(i))
|
||
hash += typeutil_hashString(f.Name()) // (ignore f.Pkg)
|
||
hash += h.hash(f.Type())
|
||
}
|
||
return hash
|
||
|
||
case *types.Pointer:
|
||
return 9067 + 2*h.hash(t.Elem())
|
||
|
||
case *types.Signature:
|
||
var hash uint32 = 9091
|
||
if t.Variadic() {
|
||
hash *= 8863
|
||
}
|
||
|
||
tparams := t.TypeParams()
|
||
if n := tparams.Len(); n > 0 {
|
||
h.inGenericSig = true // affects constraints, params, and results
|
||
|
||
for i := range n {
|
||
tparam := tparams.At(i)
|
||
hash += 7 * h.hash(tparam.Constraint())
|
||
}
|
||
}
|
||
|
||
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
|
||
|
||
case *types.Union:
|
||
return h.hashUnion(t)
|
||
|
||
case *types.Interface:
|
||
// Interfaces are identical if they have the same set of methods, with
|
||
// identical names and types, and they have the same set of type
|
||
// restrictions. See go/types.identical for more details.
|
||
var hash uint32 = 9103
|
||
|
||
// Hash methods.
|
||
for i, n := 0, t.NumMethods(); i < n; i++ {
|
||
// Method order is not significant.
|
||
// Ignore m.Pkg().
|
||
m := t.Method(i)
|
||
// Use shallow hash on method signature to
|
||
// avoid anonymous interface cycles.
|
||
hash += 3*typeutil_hashString(m.Name()) + 5*h.shallowHash(m.Type())
|
||
}
|
||
|
||
// Hash type restrictions.
|
||
terms, err := typeparams_InterfaceTermSet(t)
|
||
// if err != nil t has invalid type restrictions.
|
||
if err == nil {
|
||
hash += h.hashTermSet(terms)
|
||
}
|
||
|
||
return hash
|
||
|
||
case *types.Map:
|
||
return 9109 + 2*h.hash(t.Key()) + 3*h.hash(t.Elem())
|
||
|
||
case *types.Chan:
|
||
return 9127 + 2*uint32(t.Dir()) + 3*h.hash(t.Elem())
|
||
|
||
case *types.Named:
|
||
hash := h.hashTypeName(t.Obj())
|
||
targs := t.TypeArgs()
|
||
for targ := range targs.Types() {
|
||
hash += 2 * h.hash(targ)
|
||
}
|
||
return hash
|
||
|
||
case *types.TypeParam:
|
||
return h.hashTypeParam(t)
|
||
|
||
case *types.Tuple:
|
||
return h.hashTuple(t)
|
||
}
|
||
|
||
panic(fmt.Sprintf("%T: %v", t, t))
|
||
}
|
||
|
||
func (h typeutil_hasher) hashTuple(tuple *types.Tuple) uint32 {
|
||
// See go/types.identicalTypes for rationale.
|
||
n := tuple.Len()
|
||
hash := 9137 + 2*uint32(n)
|
||
for i := range n {
|
||
hash += 3 * h.hash(tuple.At(i).Type())
|
||
}
|
||
return hash
|
||
}
|
||
|
||
func (h typeutil_hasher) hashUnion(t *types.Union) uint32 {
|
||
// Hash type restrictions.
|
||
terms, err := typeparams_UnionTermSet(t)
|
||
// if err != nil t has invalid type restrictions. Fall back on a non-zero
|
||
// hash.
|
||
if err != nil {
|
||
return 9151
|
||
}
|
||
return h.hashTermSet(terms)
|
||
}
|
||
|
||
func (h typeutil_hasher) hashTermSet(terms []*types.Term) uint32 {
|
||
hash := 9157 + 2*uint32(len(terms))
|
||
for _, term := range terms {
|
||
// term order is not significant.
|
||
termHash := h.hash(term.Type())
|
||
if term.Tilde() {
|
||
termHash *= 9161
|
||
}
|
||
hash += 3 * termHash
|
||
}
|
||
return hash
|
||
}
|
||
|
||
// hashTypeParam returns the hash of a type parameter.
|
||
func (h typeutil_hasher) hashTypeParam(t *types.TypeParam) uint32 {
|
||
// Within the signature of a generic function, TypeParams are
|
||
// identical if they have the same index and constraint, so we
|
||
// hash them based on index.
|
||
//
|
||
// When we are outside a generic function signature, free TypeParams can
|
||
// come from equivalent generic contexts that only differ by alpha-renaming
|
||
// (e.g. T vs newT). Object identity/name would make those hash differently.
|
||
//
|
||
// We therefore assign each free TypeParam a traversal-local canonical ID.
|
||
// We intentionally keep the same "9173 + 3*x" shape used elsewhere for
|
||
// TypeParams, only swapping x from Index/object-derived info to this ID.
|
||
if !h.inGenericSig {
|
||
// Outside generic function signatures, object identity depends on the
|
||
// specific binder and does not survive alpha-renaming between equivalent
|
||
// type expressions (e.g. T vs newT). Use a deterministic traversal-local
|
||
// ID to keep hashes stable in those equivalent cases.
|
||
if id, ok := h.typeParamIDs[t]; ok {
|
||
// 9173 marks "type param", 3*id matches this hasher's mixing style.
|
||
return 9173 + 3*id
|
||
}
|
||
id := uint32(len(h.typeParamIDs))
|
||
h.typeParamIDs[t] = id
|
||
return 9173 + 3*id
|
||
}
|
||
return 9173 + 3*uint32(t.Index())
|
||
}
|
||
|
||
// hashTypeName hashes the pointer of tname.
|
||
func (typeutil_hasher) hashTypeName(tname *types.TypeName) uint32 {
|
||
// NOTE(garble): we must not hash any pointers,
|
||
// as garble is a toolexec tool so by nature it uses multiple processes.
|
||
return typeutil_hashString(tname.Name())
|
||
// Since types.Identical uses == to compare TypeNames,
|
||
// the Hash function uses maphash.Comparable.
|
||
// hash := maphash.Comparable(typeutil_theSeed, tname)
|
||
// return uint32(hash ^ (hash >> 32))
|
||
}
|
||
|
||
// shallowHash computes a hash of t without looking at any of its
|
||
// element Types, to avoid potential anonymous cycles in the types of
|
||
// interface methods.
|
||
//
|
||
// When an unnamed non-empty interface type appears anywhere among the
|
||
// arguments or results of an interface method, there is a potential
|
||
// for endless recursion. Consider:
|
||
//
|
||
// type X interface { m() []*interface { X } }
|
||
//
|
||
// The problem is that the Methods of the interface in m's result type
|
||
// include m itself; there is no mention of the named type X that
|
||
// might help us break the cycle.
|
||
// (See comment in go/types.identical, case *Interface, for more.)
|
||
func (h typeutil_hasher) shallowHash(t types.Type) uint32 {
|
||
// t is the type of an interface method (Signature),
|
||
// its params or results (Tuples), or their immediate
|
||
// elements (mostly Slice, Pointer, Basic, Named),
|
||
// so there's no need to optimize anything else.
|
||
switch t := t.(type) {
|
||
case *types.Alias:
|
||
return h.shallowHash(types.Unalias(t))
|
||
|
||
case *types.Signature:
|
||
var hash uint32 = 604171
|
||
if t.Variadic() {
|
||
hash *= 971767
|
||
}
|
||
// The Signature/Tuple recursion is always finite
|
||
// and invariably shallow.
|
||
return hash + 1062599*h.shallowHash(t.Params()) + 1282529*h.shallowHash(t.Results())
|
||
|
||
case *types.Tuple:
|
||
n := t.Len()
|
||
hash := 9137 + 2*uint32(n)
|
||
for i := range n {
|
||
hash += 53471161 * h.shallowHash(t.At(i).Type())
|
||
}
|
||
return hash
|
||
|
||
case *types.Basic:
|
||
return 45212177 * uint32(t.Kind())
|
||
|
||
case *types.Array:
|
||
return 1524181 + 2*uint32(t.Len())
|
||
|
||
case *types.Slice:
|
||
return 2690201
|
||
|
||
case *types.Struct:
|
||
return 3326489
|
||
|
||
case *types.Pointer:
|
||
return 4393139
|
||
|
||
case *types.Union:
|
||
return 562448657
|
||
|
||
case *types.Interface:
|
||
return 2124679 // no recursion here
|
||
|
||
case *types.Map:
|
||
return 9109
|
||
|
||
case *types.Chan:
|
||
return 9127
|
||
|
||
case *types.Named:
|
||
return h.hashTypeName(t.Obj())
|
||
|
||
case *types.TypeParam:
|
||
return h.hashTypeParam(t)
|
||
}
|
||
panic(fmt.Sprintf("shallowHash: %T: %v", t, t))
|
||
}
|