diff --git a/crypto.go b/crypto.go deleted file mode 100644 index d6923ea..0000000 --- a/crypto.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "fmt" - mathrand "math/rand" - "strings" -) - -// If math/rand.Seed() is not called, the generator behaves as if seeded by rand.Seed(1), -// so the generator is deterministic. - -// genAesKey generates a 128-bit AES Key. -func genAesKey() []byte { - return genRandBytes(16) -} - -// genAesKey generates a 128-bit nonce. -func genNonce() []byte { - return genRandBytes(12) -} - -// genRandBytes return a random []byte with the length of size. -func genRandBytes(size int) []byte { - buffer := make([]byte, size) - - if strings.HasPrefix(envGarbleSeed, "random;") { - _, err := rand.Read(buffer) - if err != nil { - panic(fmt.Sprintf("couldn't generate random key: %v", err)) - } - } else { - mathrand.Read(buffer) // error is always nil so save to ignore - } - - return buffer -} - -// encAES encrypt data with key in AES GCM mode. -func encAES(data, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - nonce := genNonce() - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - ciphertext := aesgcm.Seal(nil, nonce, data, nil) - encData := append(nonce, ciphertext...) - return encData, nil -} diff --git a/internal/literals/literals.go b/internal/literals/literals.go new file mode 100644 index 0000000..fc6c9b9 --- /dev/null +++ b/internal/literals/literals.go @@ -0,0 +1,207 @@ +package literals + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + mathrand "math/rand" + "strconv" + + "golang.org/x/tools/go/ast/astutil" +) + +func callExpr(resultType ast.Expr, block *ast.BlockStmt) *ast.CallExpr { + return &ast.CallExpr{Fun: &ast.FuncLit{ + Type: &ast.FuncType{ + Params: &ast.FieldList{}, + Results: &ast.FieldList{ + List: []*ast.Field{ + {Type: resultType}, + }, + }, + }, + Body: block, + }} +} + +func randObfuscator() obfuscator { + randPos := mathrand.Intn(len(obfuscators)) + return obfuscators[randPos] +} + +func returnStmt(result ast.Expr) *ast.ReturnStmt { + return &ast.ReturnStmt{ + Results: []ast.Expr{result}, + } +} + +// Obfuscate replace literals with obfuscated lambda functions +func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blacklist map[types.Object]struct{}) []*ast.File { + pre := func(cursor *astutil.Cursor) bool { + switch x := cursor.Node().(type) { + + case *ast.GenDecl: + if x.Tok != token.CONST { + return true + } + for _, spec := range x.Specs { + spec, ok := spec.(*ast.ValueSpec) + if !ok { + return false + } + + for _, name := range spec.Names { + obj := info.ObjectOf(name) + + // The object itself is blacklisted, e.g. a value that needs to be constant + if _, ok := blacklist[obj]; ok { + return false + } + } + + for _, val := range spec.Values { + if _, ok := val.(*ast.BasicLit); !ok { + return false // skip the block if it contains non basic literals + } + } + } + + x.Tok = token.VAR + // constants are not possible if we want to obfuscate literals, therefore + // move all constant blocks which only contain strings to variables + } + return true + } + + post := func(cursor *astutil.Cursor) bool { + switch x := cursor.Node().(type) { + case *ast.CompositeLit: + byteType := types.Universe.Lookup("byte").Type() + + switch x := info.TypeOf(x.Type).(type) { + case *types.Array: + if x.Elem() != byteType { + return true + } + case *types.Slice: + if x.Elem() != byteType { + return true + } + default: + return true + } + + var data []byte + for _, el := range x.Elts { + lit, ok := el.(*ast.BasicLit) + if !ok { + return true + } + + value, err := strconv.Atoi(lit.Value) + if err != nil { + return true + } + + data = append(data, byte(value)) + } + + cursor.Replace(obfuscateBytes(data)) + case *ast.BasicLit: + switch cursor.Name() { + case "Values", "Rhs", "Value", "Args", "X", "Y", "Results": + default: + return true // we don't want to obfuscate imports etc. + } + + switch x.Kind { + case token.STRING: + typeInfo := info.TypeOf(x) + if typeInfo != types.Typ[types.String] && typeInfo != types.Typ[types.UntypedString] { + return true + } + value, err := strconv.Unquote(x.Value) + if err != nil { + panic(fmt.Sprintf("cannot unquote string: %v", err)) + } + + cursor.Replace(obfuscateString(value)) + } + } + + return true + } + + for i := range files { + files[i] = astutil.Apply(files[i], pre, post).(*ast.File) + } + return files +} + +func obfuscateString(data string) *ast.CallExpr { + obfuscator := randObfuscator() + block := obfuscator.obfuscate([]byte(data)) + block.List = append(block.List, &ast.ReturnStmt{ + Results: []ast.Expr{&ast.CallExpr{ + Fun: &ast.Ident{Name: "string"}, + Args: []ast.Expr{&ast.Ident{Name: "data"}}, + }}, + }) + + return callExpr(&ast.Ident{Name: "string"}, block) +} + +func obfuscateBytes(data []byte) *ast.CallExpr { + obfuscator := randObfuscator() + block := obfuscator.obfuscate(data) + block.List = append(block.List, &ast.ReturnStmt{ + Results: []ast.Expr{&ast.Ident{Name: "data"}}, + }) + return callExpr(&ast.ArrayType{Elt: &ast.Ident{Name: "byte"}}, block) +} + +// ConstBlacklist blacklist identifieres used in constant expressions +func ConstBlacklist(node ast.Node, info *types.Info, blacklist map[types.Object]struct{}) { + blacklistObjects := func(node ast.Node) bool { + ident, ok := node.(*ast.Ident) + if !ok { + return true + } + + obj := info.ObjectOf(ident) + blacklist[obj] = struct{}{} + + return true + } + + switch x := node.(type) { + // in a slice or array composite literal all explicit keys must be constant representable + case *ast.CompositeLit: + if _, ok := x.Type.(*ast.ArrayType); !ok { + break + } + for _, elt := range x.Elts { + if kv, ok := elt.(*ast.KeyValueExpr); ok { + ast.Inspect(kv.Key, blacklistObjects) + } + } + // in an array type the length must be a constant representable + case *ast.ArrayType: + if x.Len != nil { + ast.Inspect(x.Len, blacklistObjects) + } + // in a const declaration all values must be constant representable + case *ast.GenDecl: + if x.Tok != token.CONST { + break + } + for _, spec := range x.Specs { + spec := spec.(*ast.ValueSpec) + + for _, val := range spec.Values { + ast.Inspect(val, blacklistObjects) + } + } + } +} diff --git a/internal/literals/obfuscators.go b/internal/literals/obfuscators.go new file mode 100644 index 0000000..bd86bf6 --- /dev/null +++ b/internal/literals/obfuscators.go @@ -0,0 +1,60 @@ +package literals + +import ( + cryptrand "crypto/rand" + "fmt" + "go/ast" + "go/token" + mathrand "math/rand" + "os" + "strings" +) + +// obfuscator takes a byte slice and converts it to a ast.BlockStmt +type obfuscator interface { + obfuscate(data []byte) *ast.BlockStmt +} + +var ( + // obfuscators contains all types which implement the obfuscator Interface + obfuscators = []obfuscator{ + xor{}, + } + envGarbleSeed = os.Getenv("GARBLE_SEED") +) + +// dataToByteSlice turns a byte slice like []byte{1, 2, 3} into an AST +// expression +func dataToByteSlice(data []byte) *ast.CallExpr { + return &ast.CallExpr{ + Fun: &ast.ArrayType{ + Elt: &ast.Ident{Name: "byte"}, + }, + Args: []ast.Expr{&ast.BasicLit{ + Kind: token.STRING, + Value: fmt.Sprintf("%q", data), + }}, + } +} + +// If math/rand.Seed() is not called, the generator behaves as if seeded by rand.Seed(1), +// so the generator is deterministic. + +// genRandBytes return a random []byte with the length of size. +func genRandBytes(buffer []byte) { + if strings.HasPrefix(envGarbleSeed, "random;") { + _, err := cryptrand.Read(buffer) + if err != nil { + panic(fmt.Sprintf("couldn't generate random key: %v", err)) + } + } else { + _, err := mathrand.Read(buffer) + if err != nil { + panic(fmt.Sprintf("couldn't generate random key: %v", err)) + } + } +} + +func genRandInt() int { + return mathrand.Int() +} diff --git a/internal/literals/xor.go b/internal/literals/xor.go new file mode 100644 index 0000000..312a602 --- /dev/null +++ b/internal/literals/xor.go @@ -0,0 +1,56 @@ +package literals + +import ( + "go/ast" + "go/token" +) + +type xor struct{} + +// check that the obfuscator interface is implemented +var _ obfuscator = xor{} + +func (x xor) obfuscate(data []byte) *ast.BlockStmt { + key := make([]byte, len(data)) + genRandBytes(key) + + for i, b := range key { + data[i] = data[i] ^ b + } + + return &ast.BlockStmt{List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "key"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{dataToByteSlice(key)}, + }, + &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.Ident{Name: "data"}}, + Tok: token.DEFINE, + Rhs: []ast.Expr{dataToByteSlice(data)}, + }, + &ast.RangeStmt{ + Key: &ast.Ident{Name: "i"}, + Value: &ast.Ident{Name: "b"}, + Tok: token.DEFINE, + X: &ast.Ident{Name: "key"}, + Body: &ast.BlockStmt{List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{&ast.IndexExpr{ + X: &ast.Ident{Name: "data"}, + Index: &ast.Ident{Name: "i"}, + }}, + Tok: token.ASSIGN, + Rhs: []ast.Expr{&ast.BinaryExpr{ + X: &ast.IndexExpr{ + X: &ast.Ident{Name: "data"}, + Index: &ast.Ident{Name: "i"}, + }, + Op: token.XOR, + Y: &ast.Ident{Name: "b"}, + }}, + }, + }}, + }, + }} +} diff --git a/main.go b/main.go index e8ff15a..ac07229 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ import ( "strings" "golang.org/x/tools/go/ast/astutil" + "mvdan.cc/garble/internal/literals" ) var flagSet = flag.NewFlagSet("garble", flag.ContinueOnError) @@ -383,8 +384,9 @@ func transformCompile(args []string) ([]string, error) { } info := &types.Info{ - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), } pkg, err := origTypesConfig.Check(pkgPath, fset, files, info) if err != nil { @@ -394,7 +396,7 @@ func transformCompile(args []string) ([]string, error) { blacklist := buildBlacklist(files, info, pkg) if envGarbleLiterals { - files = obfuscateLiterals(files, info, blacklist) + files = literals.Obfuscate(files, info, fset, blacklist) // ast changed so we need to typecheck again pkg, err = origTypesConfig.Check(pkgPath, fset, files, info) if err != nil { @@ -575,38 +577,6 @@ func readBuildIDs(flags []string) error { } // log.Printf("%#v", buildInfo) - // Since string obfuscation adds crypto dependencies, ensure they are - // also part of the importcfg. Otherwise, the compiler or linker might - // error when trying to locate them. - // TODO: this means these packages can't be garbled. never garble std? - if envGarbleLiterals { - toAdd := []string{ - "crypto/aes", - "crypto/cipher", - } - for len(toAdd) > 0 { - // Use a stack, to reuse memory. - path := toAdd[len(toAdd)-1] - toAdd = toAdd[:len(toAdd)-1] - if _, ok := buildInfo.imports[path]; ok { - continue - } - pkg, err := listPackage(path) - if err != nil { - return err - } - if pkg.Export == "" { - continue // e.g. unsafe - } - if _, err := fmt.Fprintf(f, "packagefile %s=%s\n", path, pkg.Export); err != nil { - return err - } - // Add their dependencies too, without adding duplicates. - buildInfo.imports[path] = importedPkg{packagefile: pkg.Export} - toAdd = append(toAdd, pkg.Deps...) - } - } - if err := f.Close(); err != nil { return err } @@ -682,7 +652,7 @@ func buildBlacklist(files []*ast.File, info *types.Info, pkg *types.Package) map visit := func(node ast.Node) bool { if envGarbleLiterals { - constBlacklist(node, info, blacklist) + literals.ConstBlacklist(node, info, blacklist) } call, ok := node.(*ast.CallExpr) diff --git a/strings.go b/strings.go deleted file mode 100644 index bc2e1de..0000000 --- a/strings.go +++ /dev/null @@ -1,362 +0,0 @@ -package main - -import ( - "fmt" - "go/ast" - "go/token" - "go/types" - "strconv" - - "golang.org/x/tools/go/ast/astutil" -) - -func isTypeDefStr(typ types.Type) bool { - strType := types.Typ[types.String] - - if named, ok := typ.(*types.Named); ok { - return types.Identical(named.Underlying(), strType) - } - - return false -} - -func containsTypeDefStr(expr ast.Expr, info *types.Info) bool { - typ := info.TypeOf(expr) - // log.Println(expr, typ, reflect.TypeOf(expr), reflect.TypeOf(typ)) - - if sig, ok := typ.(*types.Signature); ok { - for i := 0; i < sig.Params().Len(); i++ { - if isTypeDefStr(sig.Params().At(i).Type()) { - return true - } - } - } - - if mapT, ok := typ.(*types.Map); ok { - return isTypeDefStr(mapT.Elem()) || isTypeDefStr(mapT.Key()) - } - - if named, ok := typ.(*types.Named); ok { - return isTypeDefStr(named) - } - - return false -} - -func obfuscateLiterals(files []*ast.File, info *types.Info, blacklist map[types.Object]struct{}) []*ast.File { - pre := func(cursor *astutil.Cursor) bool { - switch x := cursor.Node().(type) { - case *ast.ValueSpec: - return !containsTypeDefStr(x.Type, info) - - case *ast.AssignStmt: - for _, expr := range x.Lhs { - if index, ok := expr.(*ast.IndexExpr); ok { - return !containsTypeDefStr(index.X, info) - } - - if ident, ok := expr.(*ast.Ident); ok { - return !containsTypeDefStr(ident, info) - } - } - case *ast.CallExpr: - return !containsTypeDefStr(x.Fun, info) - - case *ast.CompositeLit: - if t, ok := x.Type.(*ast.MapType); ok { - return !(containsTypeDefStr(t.Key, info) || containsTypeDefStr(t.Value, info)) - } - - case *ast.FuncDecl: - if x.Type.Results == nil { - return true - } - for _, result := range x.Type.Results.List { - for _, name := range result.Names { - return !containsTypeDefStr(name, info) - } - } - - case *ast.KeyValueExpr: - if ident, ok := x.Key.(*ast.Ident); ok { - return !containsTypeDefStr(ident, info) - } - case *ast.GenDecl: - if x.Tok != token.CONST { - return true - } - for _, spec := range x.Specs { - spec, ok := spec.(*ast.ValueSpec) - if !ok { - return false - } - - for _, name := range spec.Names { - obj := info.ObjectOf(name) - - // The object itself is blacklisted, e.g. a value that needs to be constant - if _, ok := blacklist[obj]; ok { - return false - } - } - - for _, val := range spec.Values { - if v, ok := val.(*ast.BasicLit); !ok || v.Kind != token.STRING { - return false // skip the block if it contains non basic literals - } - } - - } - - x.Tok = token.VAR - // constants are not possible if we want to obfuscate literals, therefore - // move all constant blocks which only contain strings to variables - - } - return true - } - - key := genAesKey() - addedToPkg := false // we only want to inject the code and imports once - post := func(cursor *astutil.Cursor) bool { - switch x := cursor.Node().(type) { - case *ast.File: - if addedToPkg { - break - } - x.Decls = append(x.Decls, funcStmt) - x.Decls = append(x.Decls, keyStmt(key)) - astutil.AddImport(fset, x, "crypto/aes") - astutil.AddImport(fset, x, "crypto/cipher") - - addedToPkg = true - case *ast.BasicLit: - switch cursor.Name() { - case "Values", "Rhs", "Value", "Args": - default: - return true // we don't want to obfuscate imports etc. - } - if x.Kind != token.STRING { - return true // TODO: garble literals other than strings - } - - value, err := strconv.Unquote(x.Value) - if err != nil { - panic(fmt.Sprintf("cannot unquote string: %v", err)) - } - ciphertext, err := encAES([]byte(value), key) - if err != nil { - panic(fmt.Sprintf("cannot encrypt string: %v", err)) - } - - cursor.Replace(ciphertextStmt(ciphertext)) - } - return true - } - - for i := range files { - files[i] = astutil.Apply(files[i], pre, post).(*ast.File) - } - return files -} - -// AST definitions for injection -var ( - aesCipherStmt = &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{Name: "block"}, - &ast.Ident{Name: "err"}, - }, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{Name: "aes"}, - Sel: &ast.Ident{Name: "NewCipher"}, - }, - Args: []ast.Expr{&ast.Ident{Name: "garbleKey"}}, - }}, - } - - aesGcmCipherStmt = &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{Name: "aesgcm"}, - &ast.Ident{Name: "err"}, - }, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{Name: "cipher"}, - Sel: &ast.Ident{Name: "NewGCM"}, - }, - Args: []ast.Expr{&ast.Ident{Name: "block"}}, - }}, - } - - plaintextStmt = &ast.AssignStmt{ - Lhs: []ast.Expr{ - &ast.Ident{Name: "plaintext"}, - &ast.Ident{Name: "err"}, - }, - Tok: token.DEFINE, - Rhs: []ast.Expr{&ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{Name: "aesgcm"}, - Sel: &ast.Ident{Name: "Open"}, - }, - Args: []ast.Expr{ - &ast.Ident{Name: "nil"}, - &ast.SliceExpr{ - X: &ast.Ident{Name: "ciphertext"}, - High: &ast.BasicLit{ - Kind: token.INT, - Value: "12", - }, - }, - &ast.SliceExpr{ - X: &ast.Ident{Name: "ciphertext"}, - Low: &ast.BasicLit{ - Kind: token.INT, - Value: "12", - }, - }, - &ast.Ident{Name: "nil"}, - }, - }}, - } - - returnStmt = &ast.ReturnStmt{Results: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.Ident{Name: "string"}, - Args: []ast.Expr{&ast.Ident{Name: "plaintext"}}, - }, - }} -) - -func decErrStmt() *ast.IfStmt { - return &ast.IfStmt{ - Cond: &ast.BinaryExpr{ - X: &ast.Ident{Name: "err"}, - Op: token.NEQ, - Y: &ast.Ident{Name: "nil"}, - }, - Body: &ast.BlockStmt{List: []ast.Stmt{ - &ast.ExprStmt{X: &ast.CallExpr{ - Fun: &ast.Ident{Name: "panic"}, - Args: []ast.Expr{&ast.BinaryExpr{ - X: &ast.BasicLit{ - Kind: token.STRING, - Value: `"garble: literal couldn't be decrypted: "`, - }, - Op: token.ADD, - Y: &ast.CallExpr{Fun: &ast.SelectorExpr{ - X: &ast.Ident{Name: "err"}, - Sel: &ast.Ident{Name: "Error"}, - }}, - }}, - }}, - }}, - } -} - -var funcStmt = &ast.FuncDecl{ - Name: &ast.Ident{Name: "garbleDecrypt"}, - Type: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{ - Names: []*ast.Ident{{Name: "ciphertext"}}, - Type: &ast.ArrayType{ - Elt: &ast.Ident{Name: "byte"}, - }, - }}}, - Results: &ast.FieldList{List: []*ast.Field{{ - Type: &ast.Ident{Name: "string"}, - }}}, - }, - Body: &ast.BlockStmt{List: []ast.Stmt{ - aesCipherStmt, - decErrStmt(), - aesGcmCipherStmt, - decErrStmt(), - plaintextStmt, - decErrStmt(), - returnStmt, - }}, -} - -func ciphertextStmt(ciphertext []byte) *ast.CallExpr { - ciphertextLit := dataToByteSlice(ciphertext) - - return &ast.CallExpr{ - Fun: &ast.Ident{Name: "garbleDecrypt"}, - Args: []ast.Expr{ciphertextLit}, - } -} - -// dataToByteSlice turns a byte slice like []byte{1, 2, 3} into an AST -// expression -func dataToByteSlice(data []byte) *ast.CallExpr { - return &ast.CallExpr{ - Fun: &ast.ArrayType{ - Elt: &ast.Ident{Name: "byte"}, - }, - Args: []ast.Expr{&ast.BasicLit{ - Kind: token.STRING, - Value: fmt.Sprintf("%q", data), - }}, - } -} - -func keyStmt(key []byte) *ast.GenDecl { - keyLit := dataToByteSlice(key) - return &ast.GenDecl{ - Tok: token.VAR, - Specs: []ast.Spec{&ast.ValueSpec{ - Names: []*ast.Ident{{Name: "garbleKey"}}, - Values: []ast.Expr{keyLit}, - }}, - } -} - -func constBlacklist(node ast.Node, info *types.Info, blacklist map[types.Object]struct{}) { - blacklistObjects := func(node ast.Node) bool { - ident, ok := node.(*ast.Ident) - if !ok { - return true - } - - obj := info.ObjectOf(ident) - blacklist[obj] = struct{}{} - - return true - } - - switch x := node.(type) { - // in a slice or array composite literal all explicit keys must be constant representable - case *ast.CompositeLit: - if _, ok := x.Type.(*ast.ArrayType); !ok { - break - } - for _, elt := range x.Elts { - if kv, ok := elt.(*ast.KeyValueExpr); ok { - ast.Inspect(kv.Key, blacklistObjects) - } - } - // in an array type the length must be a constant representable - case *ast.ArrayType: - if x.Len != nil { - ast.Inspect(x.Len, blacklistObjects) - } - - // in a const declaration all values must be constant representable - case *ast.GenDecl: - if x.Tok != token.CONST { - break - } - for _, spec := range x.Specs { - spec := spec.(*ast.ValueSpec) - - for _, val := range spec.Values { - ast.Inspect(val, blacklistObjects) - } - } - } -} diff --git a/testdata/scripts/strings.txt b/testdata/scripts/literals.txt similarity index 77% rename from testdata/scripts/strings.txt rename to testdata/scripts/literals.txt index 85f675b..8d93eee 100644 --- a/testdata/scripts/strings.txt +++ b/testdata/scripts/literals.txt @@ -1,10 +1,13 @@ - +go run . +cmp stderr main.stderr +cp stderr normal.stderr garble -literals build exec ./main$exe cmp stderr main.stderr +cmp stderr normal.stderr ! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'ipsum' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'stringTypeStruct' -binsubstr main$exe 'Skip this block,' 'also skip this' 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap1 key' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap2 value' 'testMap3 value' 'testMap1 new value' 'testMap2 new value' 'testMap3 new value' 'stringType func param' 'stringType return' +binsubstr main$exe 'also skip this' 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap1 key' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' [short] stop # checking that the build is reproducible is slow # Also check that the binary is reproducible. @@ -18,9 +21,6 @@ module test/main -- main.go -- package main -// The lack of imports is an edge case, since we have to add imports like -// crypto/aes. - type strucTest struct { field string anotherfield string @@ -33,8 +33,8 @@ Second Line` ) const ( - skip1 = "Skip this block," - i = 1 + i = 1 + boolean = true ) const ( @@ -58,8 +58,10 @@ func main() { reassign := "first assign" reassign = "second assign" - println(cnst) - println(multiline) + add := "total" + " string" + + println(cnst, boolean) + println(multiline, add) println(localVar) println(reassign) println(empty) @@ -68,6 +70,11 @@ func main() { field: "to obfuscate", anotherfield: "also obfuscate", } + + lambda := func() string { + return "😅 😅" + }() + println(lambda) println(x.field, x.anotherfield) @@ -75,10 +82,11 @@ func main() { testMap["map key"] = "new value" println(testMap["map key"]) println("another literal") - println(skip1, skip2) + println(skip2) println(i, foo, bar) typedTest() constantTest() + byteTest() } type stringType string @@ -112,8 +120,8 @@ func typedTest() { testMap1 := map[string]stringType{"testMap1 key": "testMap1 value"} // skip testMap1["testMap1 key"] = "testMap1 new value" // skip - testMap2 := map[stringType]string{"testMap2 key": "testMap2 value"} // skip - testMap2["testMap2 key"] = "testMap2 new value" // skip + testMap2 := map[stringType]string{"testMap2 key": "testMap2 value"} // skip key + testMap2["testMap2 key"] = "testMap2 new value" // skip key testMap3 := map[stringType]stringType{"testMap3 key": "testMap3 value"} // skip testMap3["testMap3 key"] = "testMap3 new value" // skip @@ -152,22 +160,42 @@ func constantTest() { const i = length + len(f) } +func byteTest() { + a := []byte{12, 13} + for _, elm := range a { + print(elm, ", ") + } + println() + var b = []byte{12, 13} + for _, elm := range b { + print(elm, ", ") + } + println() + + var c = [2]byte{12, 13} + for _, elm := range c { + print(elm, ", ") + } + println() +} + func stringTypeFunc(s stringType) stringType { println(s) return "stringType return" // skip } -- main.stderr -- -Lorem +Lorem true First Line -Second Line +Second Line total string dolor second assign +😅 😅 to obfuscate also obfuscate new value another literal -Skip this block, also skip this +also skip this 1 0 1 skip typed const skip typed var skip typed var assign stringTypeField String stringTypeField strType @@ -176,3 +204,6 @@ stringType func param stringType return foo foo +12, 13, +12, 13, +12, 13,