mirror of
https://github.com/burrowers/garble.git
synced 2026-04-22 15:47:04 +08:00
stabilize generic type param hashing for anonymous struct fields
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>
This commit is contained in:
+29
-10
@@ -25,13 +25,22 @@ 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}.hash(t)
|
||||
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].
|
||||
type typeutil_hasher struct{ inGenericSig bool }
|
||||
//
|
||||
// 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 {
|
||||
@@ -186,15 +195,25 @@ func (h typeutil_hasher) hashTypeParam(t *types.TypeParam) uint32 {
|
||||
// identical if they have the same index and constraint, so we
|
||||
// hash them based on index.
|
||||
//
|
||||
// When we are outside a generic function, free TypeParams are
|
||||
// identical iff they are the same object, so we can use a
|
||||
// more discriminating hash consistent with object identity.
|
||||
// This optimization saves [Map] about 4% when hashing all the
|
||||
// types.Info.Types in the forward closure of net/http.
|
||||
// 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 {
|
||||
// Optimization: outside a generic function signature,
|
||||
// use a more discrimating hash consistent with object identity.
|
||||
return h.hashTypeName(t.Obj())
|
||||
// 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())
|
||||
}
|
||||
|
||||
Vendored
+13
@@ -53,3 +53,16 @@ func sliceOfPointer() Slice[*any] {
|
||||
type Map[K, V comparable] map[K]V
|
||||
|
||||
var _ = Map[string, struct{}]{}
|
||||
|
||||
type Result[T any] interface {
|
||||
AsResult() Result[T]
|
||||
Redirect(struct { ret T })
|
||||
}
|
||||
|
||||
type AsyncResult[oldT, newT any] struct {}
|
||||
|
||||
func (w *AsyncResult[oldT, newT]) AsResult() Result[newT] {
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *AsyncResult[oldT, newT]) Redirect(struct {ret newT}) {}
|
||||
|
||||
Reference in New Issue
Block a user