exec garble build exec ./main cmp stdout main.stdout ! binsubstr main$exe 'garble_main.go' 'test/main' 'importedpkg.' 'DownstreamObfuscated' 'SiblingObfuscated' 'IndirectObfuscated' 'IndirectNamedWithoutReflect' 'AliasIndirectNamedWithReflect' 'AliasIndirectNamedWithoutReflect' 'FmtTypeField' binsubstr main$exe 'ReflectInDefined' 'ExportedField2' 'unexportedField2' 'IndirectUnobfuscated' 'IndirectNamedWithReflect' [short] stop # no need to verify this with -short # Check that the program works as expected without garble. go build exec ./main cmp stdout main.stdout -- go.mod -- module test/main go 1.20 -- garble_main.go -- package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/json" "fmt" "math/big" "os" "reflect" "strings" "text/template" "test/main/importedpkg" "test/main/importedpkg2" ) var Sink interface{} func main() { // Fields still work fine when they are not obfuscated. fmt.Println(importedpkg.ReflectInDefinedVar.ExportedField2) fmt.Println(importedpkg.ReflectInDefined{ExportedField2: 5}) // Type names are not obfuscated either, when reflection is used. printfWithoutPackage("%T\n", importedpkg.ReflectTypeOf(2)) printfWithoutPackage("%T\n", importedpkg.ReflectTypeOfIndirect(4)) // More complex use of reflect. v := importedpkg.ReflectValueOfVar printfWithoutPackage("%#v\n", v) method := reflect.ValueOf(&v).MethodByName("ExportedMethodName") if method.IsValid() { fmt.Println(method.Call(nil)) } else { fmt.Println("method not found") } // Use of a common library using reflect, encoding/json. enc, _ := json.Marshal(EncodingT{Foo: 3}) fmt.Println(string(enc)) // Another common library, text/template. tmpl := template.Must(template.New("").Parse("Hello {{.Name}}.")) _ = tmpl.Execute(os.Stdout, struct{Name string}{Name: "Dave"}) fmt.Println() // Always print a newline. // Another complex case, involving embedding and another package. outer := &importedpkg.EmbeddingOuter{} outer.InnerField = 3 enc, _ = json.Marshal(outer) fmt.Println(string(enc)) // An edge case; the struct type is defined in a different package. // Note that the struct type is unnamed, but it still has named fields. // We only use reflection on it here, not the declaring package. // As such, we should obfuscate the field name. // Simply using the field name here used to cause build failures. _ = reflect.TypeOf(importedpkg.UnnamedWithDownstreamReflect{}) fmt.Printf("%v\n", importedpkg.UnnamedWithDownstreamReflect{ DownstreamObfuscated: "downstream", }) // An edge case; the struct type is defined in package importedpkg2. // importedpkg2 does not use reflection on it, so it's not obfuscated there. // importedpkg uses reflection on a type containing ReflectInSiblingImport. // If our logic is incorrect, we might inconsistently obfuscate the type. // We should not obfuscate it when building any package. fmt.Printf("%v\n", importedpkg2.ReflectInSiblingImport{ SiblingObfuscated: "sibling", }) // Using type aliases as both regular fields, and embedded fields. var emb EmbeddingIndirect emb.With.IndirectUnobfuscated = "indirect-with" emb.With.DuplicateFieldName = 3 emb.Without.IndirectObfuscated = "indirect-without" emb.Without.DuplicateFieldName = 4 fmt.Printf("%v\n", emb) printfWithoutPackage("%#v\n", emb.With) // TODO: don't obfuscate the embedded field name here // printfWithoutPackage("%#v\n", importedpkg.ReflectEmbeddingAlias{}) indirectReflection(IndirectReflection{}) fmt.Println(FmtType{}) // Variadic functions are a bit tricky as the number of parameters is variable. // We want to notice indirect uses of reflection via all variadic arguments. _ = importedpkg.VariadicReflect(0, 1, 2, 3) _ = importedpkg.VariadicReflect(0) variadic := VariadicReflection{ReflectionField: "variadic"} _ = importedpkg.VariadicReflect("foo", 1, variadic, false) printfWithoutPackage("%#v\n", variadic) testx509() // Very complex reflection used by gorm user := StatUser{} find(&user) // Similar to gorm with composite literals instead of direct assignments userComp := StatCompUser{} findComp(&userComp) x := UnnamedStructInterface(importedpkg.ReflectUnnamedStruct(0)) x.UnnamedStructMethod(struct{ UnnamedStructField string }{UnnamedStructField: "field value"}) } type EmbeddingIndirect struct { // With field names, to test selectors above. With importedpkg.AliasIndirectNamedWithReflect Without importedpkg.AliasIndirectNamedWithoutReflect // Embedding used to crash garble, too. importedpkg.AliasIndirectNamedWithReflect } func printfWithoutPackage(format string, v any) { s := fmt.Sprintf(format, v) if _, without, found := strings.Cut(s, "."); found { s = without } fmt.Print(s) } type EncodingT struct { Foo int } type RecursiveStruct struct { *RecursiveStruct list []RecursiveStruct } // This could crash or hang if we don't deal with loops. var _ = reflect.TypeOf(RecursiveStruct{}) type IndirectReflection struct { ReflectionField string } func indirectReflection(v any) { fmt.Println(reflect.TypeOf(v).Field(0).Name) } type VariadicReflection struct { ReflectionField string } type FmtType struct { FmtTypeField int } // copied from github.com/davecgh/go-spew, which reaches into reflect's internals var _ = func() uintptr { field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") if !ok { panic("reflect.Value has no flag field") } return field.Offset }() // encoding/x509 uses encoding/asn1, which uses reflect. // In one place it depends on field names; that used to be broken by garble. func testx509() { priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) if err != nil { panic(err) } template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{Organization: []string{"Acme Co"}}, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { panic(err) } _, err = x509.ParseCertificate(derBytes) if err != nil { panic(err) } } type StatUser struct { Id int64 `gorm:"primaryKey"` User_Id int64 } type StatCompUser struct { Id int64 `gorm:"primaryKey"` User_Id int64 } type Transaction struct { Statement Statement } type Statement struct { Dest interface{} Model string } func find(dest interface{}) { tx := Transaction{} tx.Statement.Dest = dest execute(tx) } func findComp(dest interface{}) { tx := Transaction{ Statement: Statement{ Dest: dest, }, } execute(tx) } func execute(db Transaction) { stmt := db.Statement v := reflect.TypeOf(stmt.Dest) fmt.Println(v) } type UnnamedStructInterface interface { UnnamedStructMethod(struct{ UnnamedStructField string }) } -- importedpkg/imported.go -- package importedpkg import ( "fmt" "reflect" "test/main/importedpkg/indirect" "test/main/importedpkg2" ) type ReflectTypeOf int var _ = reflect.TypeOf(ReflectTypeOf(0)) type ReflectTypeOfIndirect int var _ = reflect.TypeOf(new([]*ReflectTypeOfIndirect)) type ReflectValueOf struct { ExportedField string unexportedField string } func (r *ReflectValueOf) ExportedMethodName() string { return "method: " + r.ExportedField } var ReflectValueOfVar = ReflectValueOf{ExportedField: "abc"} var _ = reflect.TypeOf(ReflectValueOfVar) type ReflectInDefined struct { ExportedField2 int unexportedField2 int importedpkg2.ReflectInSiblingImport } var ReflectInDefinedVar = ReflectInDefined{ExportedField2: 9000} var _ = reflect.TypeOf(ReflectInDefinedVar) var _ = reflect.TypeOf([]*struct{EmbeddingOuter}{}) type EmbeddingOuter struct { EmbeddingInner Anon struct { AnonField int } } type EmbeddingInner struct { InnerField int } type UnnamedWithDownstreamReflect = struct { DownstreamObfuscated string } type ( AliasIndirectNamedWithReflect = indirect.IndirectNamedWithReflect AliasIndirectNamedWithoutReflect = indirect.IndirectNamedWithoutReflect ) var _ = reflect.TypeOf(ReflectEmbeddingAlias{}) type ReflectEmbeddingAlias struct { ReflectEmbeddedAlias } type ReflectEmbeddedAlias = ReflectEmbeddingNamed type ReflectEmbeddingNamed struct{} func VariadicReflect(x any, ys ...any) int { _ = reflect.TypeOf(x) for _, y := range ys { _ = reflect.TypeOf(y) } return len(ys) } type ReflectUnnamedStruct int func (ReflectUnnamedStruct) UnnamedStructMethod(s struct{ UnnamedStructField string }) { fmt.Println(reflect.TypeOf(s)) } -- importedpkg2/imported2.go -- package importedpkg2 type ReflectInSiblingImport struct { SiblingObfuscated string } -- importedpkg/indirect/indirect.go -- package indirect import "reflect" var _ = reflect.TypeOf(IndirectNamedWithReflect{}) type IndirectNamedWithReflect struct { IndirectUnobfuscated string DuplicateFieldName int } type IndirectNamedWithoutReflect struct { IndirectObfuscated string DuplicateFieldName int } -- main.stdout -- 9000 {5 0 {}} ReflectTypeOf ReflectTypeOfIndirect ReflectValueOf{ExportedField:"abc", unexportedField:""} [method: abc] {"Foo":3} Hello Dave. {"InnerField":3,"Anon":{"AnonField":0}} {downstream} {sibling} {{indirect-with 3} {indirect-without 4} { 0}} IndirectNamedWithReflect{IndirectUnobfuscated:"indirect-with", DuplicateFieldName:3} ReflectionField {0} VariadicReflection{ReflectionField:"variadic"} *main.StatUser *main.StatCompUser struct { UnnamedStructField string }