@ -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.