diff --git a/hash.go b/hash.go index 0b0357e..90d07a8 100644 --- a/hash.go +++ b/hash.go @@ -13,6 +13,7 @@ import ( "go/types" "io" "os/exec" + "strconv" "strings" "mvdan.cc/garble/internal/literals" @@ -236,63 +237,23 @@ func hashWithPackage(pkg *listedPackage, name string) string { return hashWithCustomSalt([]byte(pkg.ImportPath+"|"), name) } -// stripStructTags takes the bytes produced by [types.WriteType] -// and removes any struct tags in-place, such as rewriting -// -// struct{Foo int; Bar string "json:\"bar\""} -// -// into -// -// struct{Foo int; Bar string} -// -// Note that, unlike most Go source, WriteType uses double quotes for tags. -// -// Reusing WriteType does require a second pass over its output here, -// which we could save by implementing our own modified version of WriteType. -// However, that would be a significant amount of code to maintain. -func stripStructTags(p []byte) []byte { - i := 0 - for i < len(p) { - b := p[i] - start := i - 1 // a struct tag is preceded by a space - i++ - if b != '"' { - continue - } - // Find the closing double quote, skipping over escaped characters. - // Note that we should probably iterate over runes and not bytes, - // but this byte implementation is probably good enough in practice. - for { - b = p[i] - i++ - if b == '\\' { - i++ - } else if b == '"' { - break - } - } - end := i - // Remove the bytes between start and end, - // and reset i to start, since we just shortened p. - p = append(p[:start], p[end:]...) - i = start - } - return p -} - -var typeIdentityBuf bytes.Buffer - // hashWithStruct is separate from hashWithPackage since Go // allows converting between struct types across packages. // Hashing struct field names differently between packages would break that. // -// We hash field names with the identity struct type as a salt +// We hash field names with the "identity" struct type as a salt // so that the same field name used in different struct types is obfuscated differently. -// Note that "identity" means omitting struct tags since conversions ignore them. +// In practice this means omitting struct field tags and unaliasing field types, +// given that those do not affect whether two types are identical. func hashWithStruct(strct *types.Struct, field *types.Var) string { - typeIdentityBuf.Reset() - types.WriteType(&typeIdentityBuf, strct, nil) - salt := stripStructTags(typeIdentityBuf.Bytes()) + // Here we use a bundled and modified version of x/tools/go/types/typeutil.NewHasher.Hash + // which includes two crucial modifications: + // + // * struct field tags are not hashed + // * named types are hashed by name rather than by pointer + // + // TODO: rethink once the proposed go/types.Hash API in https://go.dev/issue/69420 is merged. + salt := strconv.AppendUint(nil, uint64(typeutil_hash(strct)), 32) // If the user provided us with an obfuscation seed, // we only use the identity struct type as a salt. diff --git a/testdata/script/implement.txtar b/testdata/script/implement.txtar index 54b8e96..0b7d75f 100644 --- a/testdata/script/implement.txtar +++ b/testdata/script/implement.txtar @@ -1,4 +1,5 @@ -exec garble build +go build +exec garble -debugdir=/tmp/test build exec ./main cmp stdout main.stdout @@ -45,12 +46,15 @@ var _ privateInterface = T("") type StructUnnamed = struct { Foo int - Bar struct { + Bar []*struct { Nested *[]string + NestedTagged string + NestedAlias int64 // we can skip the alias too } Named lib3.Named lib3.StructEmbed Tagged string // no field tag + Alias int64 // we can skip the alias too } var _ = lib1.Struct1(lib2.Struct2{}) @@ -83,14 +87,19 @@ import "test/main/lib3" type Struct1 struct { Foo int - Bar struct { + Bar []*struct { Nested *[]string + NestedTagged string `json:"tagged1"` + NestedAlias alias1 } Named lib3.Named lib3.StructEmbed Tagged string `json:"tagged1"` + Alias alias1 } +type alias1 = int64 + -- lib2/lib2.go -- package lib2 @@ -98,13 +107,18 @@ import "test/main/lib3" type Struct2 struct { Foo int - Bar struct { + Bar []*struct { Nested *[]string + NestedTagged string `json:"tagged2"` + NestedAlias alias2 } Named lib3.Named lib3.StructEmbed Tagged string `json:"tagged2"` + Alias alias2 } + +type alias2 = int64 -- lib3/lib3.go -- package lib3