Files
Daniel Martí 0b1cbdf300 salt struct field names by position rather than by field type
Field names are obfuscated with the identity of their containing struct as
a salt, which must be stable across packages so that converting between
identical struct types keeps working. Hashing field types into that salt
breaks for an anonymous struct{F Q} returned by a generic function: the
declaring package hashes the type parameter Q while a consuming package only
sees an instantiation such as struct{F int}, with no origin to recover Q.
The previous commit fixed this for a named generic field type, but not for
a bare type parameter.

Drop field types and fold in the field position instead, which is stable
across instantiations and conversions yet still keeps reordered structs
distinct. This only makes more fields share a salt, which is harmless as
non-identical structs are never converted between one another.

Since field types are no longer hashed, the salt never recurses into the
rest of the type graph, so the type-argument NOTE(garble) tweak to the
*types.Named case added by the previous commit is now dead code; revert it
to keep the bundled hasher faithful to upstream.
2026-06-03 10:44:41 +02:00

148 lines
2.7 KiB
Plaintext

exec garble build
! binsubstr main$exe ${WORK} 'garble_main.go' 'GenericFunc' 'GenericVector' 'PredeclaredSignedInteger' 'StringableSignedInteger' 'CombineEmbeds' 'GenericParam'
-- go.mod --
module test/main
go 1.23
-- garble_main.go --
package main
import "test/main/lib"
func main() {
GenericFunc[int, int](1, 2)
var _ GenericVector[int]
g1 := GenericGraph[string]{Content: "Foo"}
g1.Edges = make([]GenericGraph[string], 1)
g2 := GenericGraph[*[]byte]{Content: new([]byte)}
g2.Edges = make([]GenericGraph[*[]byte], 1)
var ga genericAlias
ga.list = nil
var gan genericAliasNamed
gan.list = nil
e := lib.Entry[string, int]{Key: "foo", Data: 123}
r := lib.Load[string, int](e)
_ = r.Data
_ = lib.SelectWhere.Users.Name
_ = lib.SelectIdentity.Value
// A plain struct is convertible to a generic instantiation whose origin
// underlying uses a type parameter; both fields must obfuscate alike.
_ = lib.Box[int](struct{ Contents int }{})
}
func GenericFunc[GenericParamA, B any](x GenericParamA, y B) {}
type GenericVector[GenericParamT any] []GenericParamT
type GenericGraph[T any] struct {
Content T
Edges []GenericGraph[T]
}
type PredeclaredSignedInteger interface {
int | int8 | int16 | int32 | int64
}
type StringableSignedInteger interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
String() string
}
type CombineEmbeds interface {
string | int
interface{ EmbeddedMethod() }
RegularMethod()
}
type Slice[T any] []T
func sliceOfPointer() Slice[*any] {
return []*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}) {}
type genericAlias = generic[int]
type generic[T any] struct {
list *T
}
type genericAliasNamed genericAlias
func byKeys[T any](m map[string]T) []struct {
K string
V T
} {
vs := make([]struct {
K string
V T
}, 0, len(m))
for k, v := range m {
vs = append(vs, struct {
K string
V T
}{k, v})
}
return vs
}
var _ = byKeys(map[string]int{"one": 1})
-- lib/lib.go --
package lib
type Result[Data any] struct {
Data Data
}
type Entry[Key ~string, Data any] struct {
Key Key
Data Data
}
func Load[Key ~string, Data any](e Entry[Key, Data]) Result[Data] {
return Result[Data]{Data: e.Data}
}
type Filter[Q any] struct{ Name Q }
func Where[Q any]() struct {
Users Filter[Q]
} {
return struct{ Users Filter[Q] }{}
}
var SelectWhere = Where[int]()
func Identity[Q any]() struct {
Value Q
} {
return struct{ Value Q }{}
}
var SelectIdentity = Identity[int]()
type Box[T any] struct{ Contents T }