Decrease binary size for -literals

Only string literals over 8 characters in length are now being
obfuscated. This leads to around 20% smaller binaries when building with
-literals.

Fixes #618
pull/645/head
lu4p 1 year ago
parent 5dd6b2dc43
commit 5b2193351f

@ -15,18 +15,20 @@ import (
ah "mvdan.cc/garble/internal/asthelper" ah "mvdan.cc/garble/internal/asthelper"
) )
// maxSizeBytes is the limit, in bytes, of the size of string-like literals // minSize is the lower bound limit, of the size of string-like literals
// which we will obfuscate. This is needed in order for binary size to stay relatively
// moderate, this also decreases the likelihood for performance slowdowns.
const minSize = 8
// maxSize is the upper bound limit, of the size of string-like literals
// which we will obfuscate. This is important, because otherwise garble can take // which we will obfuscate. This is important, because otherwise garble can take
// a very long time to obfuscate huge code-generated literals, such as those // a very long time to obfuscate huge code-generated literals, such as those
// corresponding to large assets. // corresponding to large assets.
// //
// Note that this is the size of the literal in source code. For example, "\xab"
// counts as four bytes.
//
// If someone truly wants to obfuscate those, they should do that when they // If someone truly wants to obfuscate those, they should do that when they
// generate the code, not at build time. Plus, with Go 1.16 that technique // generate the code, not at build time. Plus, with Go 1.16 that technique
// should largely stop being used. // should largely stop being used.
const maxSizeBytes = 2 << 10 // KiB const maxSize = 2 << 10 // KiB
// Obfuscate replaces literals with obfuscated anonymous functions. // Obfuscate replaces literals with obfuscated anonymous functions.
func Obfuscate(obfRand *mathrand.Rand, file *ast.File, info *types.Info, linkStrings map[*types.Var]string) *ast.File { func Obfuscate(obfRand *mathrand.Rand, file *ast.File, info *types.Info, linkStrings map[*types.Var]string) *ast.File {
@ -63,7 +65,7 @@ func Obfuscate(obfRand *mathrand.Rand, file *ast.File, info *types.Info, linkStr
if typeAndValue.Type == types.Typ[types.String] && typeAndValue.Value != nil { if typeAndValue.Type == types.Typ[types.String] && typeAndValue.Value != nil {
value := constant.StringVal(typeAndValue.Value) value := constant.StringVal(typeAndValue.Value)
if len(value) == 0 || len(value) > maxSizeBytes { if len(value) < minSize || len(value) > maxSize {
return true return true
} }
@ -119,10 +121,9 @@ func Obfuscate(obfRand *mathrand.Rand, file *ast.File, info *types.Info, linkStr
// calls the appropriate obfuscation method, returning a new node that should // calls the appropriate obfuscation method, returning a new node that should
// be used to replace it. // be used to replace it.
// //
// If the input is not a byte slice or array, the node is returned as-is and // If the input node cannot be obfuscated nil is returned.
// the second return value will be false.
func handleCompositeLiteral(obfRand *mathrand.Rand, isPointer bool, node *ast.CompositeLit, info *types.Info) ast.Node { func handleCompositeLiteral(obfRand *mathrand.Rand, isPointer bool, node *ast.CompositeLit, info *types.Info) ast.Node {
if len(node.Elts) == 0 || len(node.Elts) > maxSizeBytes { if len(node.Elts) < minSize || len(node.Elts) > maxSize {
return nil return nil
} }

@ -2,8 +2,8 @@ garble -literals build
exec ./main$exe exec ./main$exe
cmp stderr main.stderr cmp stderr main.stderr
binsubstr main$exe 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const' binsubstr main$exe 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const' 'sz<min'
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'obfuscated with shadowed builtins' '1: literal in' 'an array' '2: literal in' 'a slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this' ! binsubstr main$exe 'garbleDecrypt' 'Lorem Ipsum' 'dolor sit amet' 'first assign' 'second assign' 'First Line' 'Second Line' 'secret map value' 'obfuscated with shadowed builtins' '1: literal in' 'an secret array' '2: literal in' 'a secret slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this'
[short] stop # checking that the build is reproducible is slow [short] stop # checking that the build is reproducible is slow
@ -17,7 +17,7 @@ bincmp main$exe main_old$exe
go build go build
exec ./main$exe exec ./main$exe
cmp stderr main.stderr cmp stderr main.stderr
binsubstr main$exe 'Lorem' 'dolor' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String' binsubstr main$exe 'Lorem Ipsum' 'dolor sit amet' 'second assign' 'First Line' 'Second Line' 'secret map value' 'obfuscated with shadowed builtins' '1: literal in' 'an secret array' '2: literal in' 'a secret slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this'
# Generate and write random literals into a separate file. # Generate and write random literals into a separate file.
# Some of them will be huge; assuming that we don't try to obfuscate them, the # Some of them will be huge; assuming that we don't try to obfuscate them, the
@ -70,7 +70,7 @@ type structTest struct {
} }
const ( const (
cnst string = "Lorem" cnst string = "Lorem Ipsum"
multiline string = `First Line multiline string = `First Line
Second Line` Second Line`
) )
@ -102,19 +102,21 @@ type typeAlias [arrayLen]byte
func main() { func main() {
empty := "" empty := ""
tooSmall := "sz<min"
localVar := "dolor" localVar := "dolor sit amet"
reassign := "first assign" reassign := "first assign"
reassign = "second assign" reassign = "second assign"
add := "total" + " string" add := "totally long" + "unnecessarily added string"
println(cnst, boolean) println(cnst, boolean)
println(multiline, add) println(multiline, add)
println(localVar) println(localVar)
println(reassign) println(reassign)
println(empty) println(empty)
println(tooSmall)
x := structTest{ x := structTest{
field: "to obfuscate", field: "to obfuscate",
@ -122,15 +124,15 @@ func main() {
} }
lambda := func() string { lambda := func() string {
return "😅 😅" return "happy faces 😅 😅"
}() }()
println(lambda) println(lambda)
println(x.field, x.anotherField) println(x.field, x.anotherField)
testMap := map[string]string{"map key": "map value"} testMap := map[string]string{"secret map key": "secret map value"}
testMap["map key"] = "new value" testMap["secret map key"] = "secret new value"
println(testMap["map key"]) println(testMap["secret map key"])
println("another literal") println("another literal")
println(mixedBlock, iotaBlock) println(mixedBlock, iotaBlock)
println(i, foo, bar) println(i, foo, bar)
@ -139,9 +141,9 @@ func main() {
byteTest() byteTest()
shadowTest() shadowTest()
strArray := [2]string{"1: literal in", "an array"} strArray := [2]string{"1: literal in", "an secret array"}
println(strArray[0], strArray[1]) println(strArray[0], strArray[1])
strSlice := []string{"2: literal in", "a slice"} strSlice := []string{"2: literal in", "a secret slice"}
println(strSlice[0], strSlice[1]) println(strSlice[0], strSlice[1])
emptyStrSlice := []string{""} emptyStrSlice := []string{""}
print(emptyStrSlice[0]) print(emptyStrSlice[0])
@ -192,32 +194,32 @@ func typedTest() {
// constantTest tests that string constants which need to be constant are skipped // constantTest tests that string constants which need to be constant are skipped
func constantTest() { func constantTest() {
const a = "foo" // skip const a = "foo bar bar" // skip
const length = len(a) const length = len(a)
const b = "bar" // skip const b = "bar foo foo" // skip
type T [len(b)]byte type T [len(b)]byte
const c = "foo" // skip const c = "foo bar bar" // skip
var _ [len(c)]byte var _ [len(c)]byte
const d = "foo" // skip const d = "foo bar bar" // skip
var arr = [5]string{len(d): "foo"} var arr = [12]string{len(d): "foo bar bar"}
for _, elm := range arr { for _, elm := range arr {
if elm != "" { if elm != "" {
println(elm) println(elm)
} }
} }
const e = "foo" // skip const e = "foo bar bar" // skip
var slice = []string{len(e): "foo"} var slice = []string{len(e): "foo bar bar"}
for _, elm := range slice { for _, elm := range slice {
if elm != "" { if elm != "" {
println(elm) println(elm)
} }
} }
const f = "foo" // skip const f = "foo bar bar" // skip
const i = length + len(f) const i = length + len(f)
println(length, i) println(length, i)
@ -234,57 +236,57 @@ func constantTest() {
// We should figure out a way to test for the byte sequences. // We should figure out a way to test for the byte sequences.
// For now, we manually tested these when they got added. // For now, we manually tested these when they got added.
func byteTest() { func byteTest() {
a := []byte{12, 13} a := []byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
for _, elm := range a { for _, elm := range a {
print(elm, ",") print(elm, ",")
} }
println() println()
var b = []byte{12, 13} var b = []byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
for _, elm := range b { for _, elm := range b {
print(elm, ",") print(elm, ",")
} }
println() println()
var c = [2]byte{12, 13} var c = [10]byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
for _, elm := range c { for _, elm := range c {
print(elm, ",") print(elm, ",")
} }
println() println()
d := func() [4]byte { d := func() [12]byte {
return [4]byte{12, 13} return [12]byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
}() }()
for _, elm := range d { for _, elm := range d {
print(elm, ",") print(elm, ",")
} }
println() println()
e := []byte{0x43, 11_1, 0b01101101, 'p', 'l', 'e', 'x'} e := []byte{0x43, 11_1, 0b01101101, 'p', 'l', 'e', 'x', ' ', 'l', 'i', 't'}
println(string(e)) println(string(e))
// Testing for issue #520. // Testing for issue #520.
func(s []byte) { func(s []byte) {
print(string(s)) print(string(s))
}([]byte("chungus")) }([]byte("big chungus"))
println() println()
func(s *[]byte) { func(s *[]byte) {
print(string(*s)) print(string(*s))
}(&[]byte{99, 104, 117, 110, 103, 117, 115}) }(&[]byte{99, 104, 117, 110, 103, 117, 115, '!', '!'})
println() println()
func(s [7]byte) { func(s [9]byte) {
for _, elm := range s { for _, elm := range s {
print(elm, ",") print(elm, ",")
} }
}([7]byte{99, 104, 117, 110, 103, 117, 115}) }([9]byte{99, 104, 117, 110, 103, 117, 115, 117, 115})
println() println()
func(s *[7]byte) { func(s *[9]byte) {
for _, elm := range s { for _, elm := range s {
print(elm, ",") print(elm, ",")
} }
}(&[7]byte{99, 104, 117, 110, 103, 117, 115}) }(&[9]byte{99, 104, 117, 110, 103, 117, 115, 117, 115})
println() println()
} }
@ -330,57 +332,58 @@ type ImportedType int
package main package main
//go:noinline //go:noinline
func str0() { println("foo") } func str0() { println("foo bar bar") }
//go:noinline //go:noinline
func str1() { println("foo") } func str1() { println("foo bar bar") }
//go:noinline //go:noinline
func str2() { println("foo") } func str2() { println("foo bar bar") }
//go:noinline //go:noinline
func str3() { println("foo") } func str3() { println("foo bar bar") }
//go:noinline //go:noinline
func str4() { println("foo") } func str4() { println("foo bar bar") }
//go:noinline //go:noinline
func arr0() { println(len([...]byte{0, 1, 2})) } func arr0() { println(len([...]byte{0, 1, 2, 3, 4, 5, 6, 7, 8})) }
//go:noinline //go:noinline
func arr1() { println(len([...]byte{0, 1, 2})) } func arr1() { println(len([...]byte{0, 1, 2, 3, 4, 5, 6, 7, 8})) }
//go:noinline //go:noinline
func slc0() { println([]byte{0, 1, 2}) } func slc0() { println([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8}) }
//go:noinline //go:noinline
func slc1() { println([]byte{0, 1, 2}) } func slc1() { println([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8}) }
//go:noinline //go:noinline
func str5() { println("foo") } func str5() { println("foo bar bar") }
//go:noinline //go:noinline
func str6() { println("foo") } func str6() { println("foo bar bar") }
//go:noinline //go:noinline
func str7() { println("foo") } func str7() { println("foo bar bar") }
//go:noinline //go:noinline
func str8() { println("foo") } func str8() { println("foo bar bar") }
//go:noinline //go:noinline
func str9() { println("foo") } func str9() { println("foo bar bar") }
-- main.stderr -- -- main.stderr --
Lorem true Lorem Ipsum true
First Line First Line
Second Line total string Second Line totally longunnecessarily added string
dolor dolor sit amet
second assign second assign
😅 😅 sz<min
happy faces 😅 😅
to obfuscate also obfuscate to obfuscate also obfuscate
new value secret new value
another literal another literal
Obfuscate this block also obfuscate this Obfuscate this block also obfuscate this
1 0 1 1 0 1
@ -390,19 +393,19 @@ stringTypeField String stringTypeField strType
stringType lambda func return stringType lambda func return
stringType func param stringType func param
stringType return stringType return
foo foo bar bar
foo foo bar bar
3 6 11 22
12,13, 12,13,12,13,12,13,12,13,12,13,
12,13, 12,13,12,13,12,13,12,13,12,13,
12,13, 12,13,12,13,12,13,12,13,12,13,
12,13,0,0, 12,13,12,13,12,13,12,13,12,13,0,0,
Complex Complex lit
chungus big chungus
chungus chungus!!
99,104,117,110,103,117,115, 99,104,117,110,103,117,115,117,115,
99,104,117,110,103,117,115, 99,104,117,110,103,117,115,117,115,
obfuscated with shadowed builtins (vars) obfuscated with shadowed builtins (vars)
obfuscated with shadowed builtins (types) obfuscated with shadowed builtins (types)
1: literal in an array 1: literal in an secret array
2: literal in a slice 2: literal in a secret slice

Loading…
Cancel
Save