env GOPRIVATE=test/main

garble -literals build
exec ./main$exe
cmp stderr main.stderr

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' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const'
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String'

[short] stop # checking that the build is reproducible is slow

# Also check that the binary is reproducible.
cp main$exe main_old$exe
rm main$exe
garble -literals build
bincmp main$exe main_old$exe

# Check that the program works as expected without garble.
go build
exec ./main$exe
cmp stderr main.stderr
binsubstr main$exe 'Lorem' 'dolor' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String'

# 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
# test should generally run in under a second. If this test hangs for over ten
# seconds, it means we're trying to obfuscate them.
generate-literals extra_literals.go

# Also check that the binary is different from previous builds.
rm main$exe
garble -literals -debugdir=.obf-src -seed=8J+Ri/Cfh6fwn4e+ build
! bincmp main$exe main_old$exe

exec ./main$exe
cmp stderr main.stderr

# Check obfuscators

# Xor obfuscator. Detect a[i] = a[i] (^|-|+) b[i]
grep '^\s+\w+\[\w+\] = \w+\[\w+\] [\^\-+] \w+$' .obf-src/main/extra_literals.go

# Swap obfuscator. Detect [...]byte|uint16|uint32|uint64{...}
grep '^\s+\w+ := \[\.{3}\](byte|uint16|uint32|uint64)\{[0-9\s,]+\}$' .obf-src/main/extra_literals.go

# Split obfuscator. Detect decryptKey ^= i * counter
grep '^\s+\w+ \^= \w+ \* \w+$' .obf-src/main/extra_literals.go

# XorShuffle obfuscator. Detect data = append(data, x (^|-|+) y...)
grep '^\s+\w+ = append\(\w+,(\s+\w+\[\d+\][\^\-+]\w+\[\d+\],?)+\)$' .obf-src/main/extra_literals.go

# XorSeed obfuscator. Detect type decFunc func(byte) decFunc
grep '^\s+type \w+ func\(byte\) \w+$' .obf-src/main/extra_literals.go

-- go.mod --
module test/main

go 1.15
-- main.go --
package main

type strucTest struct {
	field        string
	anotherfield string
}

const (
	cnst      string = "Lorem"
	multiline string = `First Line
Second Line`
)

const (
	i       = 1
	boolean = true

	skip1 = "Skip this block"
)

const (
	foo = iota
	bar

	skip2 = "also skip this"
)

const arrayLen = 4

var array [arrayLen]byte

type typeAlias [arrayLen]byte

func main() {
	empty := ""

	localVar := "dolor"

	reassign := "first assign"
	reassign = "second assign"

	add := "total" + " string"

	println(cnst, boolean)
	println(multiline, add)
	println(localVar)
	println(reassign)
	println(empty)

	x := strucTest{
		field:        "to obfuscate",
		anotherfield: "also obfuscate",
	}

	lambda := func() string {
		return "😅 😅"
	}()
	println(lambda)

	println(x.field, x.anotherfield)

	testMap := map[string]string{"map key": "map value"}
	testMap["map key"] = "new value"
	println(testMap["map key"])
	println("another literal")
	println(skip1, skip2)
	println(i, foo, bar)
	typedTest()
	constantTest()
	byteTest()
}

type stringType string

type stringTypeStruct struct {
	str     string
	strType stringType
}

// typedTest types defined from string broke previously
func typedTest() {
	const skipUntypedConst = "skip untyped const"
	stringTypeFunc(skipUntypedConst)

	const skipTypedConst stringType = "skip typed const" // skip
	var skipTypedVar stringType = "skip typed var"       // skip

	var skipTypedVarAssign stringType
	skipTypedVarAssign = "skip typed var assign" // skip

	println(skipTypedConst, skipTypedVar, skipTypedVarAssign)

	y := stringTypeStruct{
		str:     "stringTypeField String",  // obfuscate
		strType: "stringTypeField strType", // skip
	}
	println(y.str, y.strType)

	z := func(s stringType) stringType {
		return "stringType lambda func return" // skip
	}("lambda call") // skip
	println(z)

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

	println(stringTypeFunc("stringType func param")) // skip
}

// constantTest tests that string constants which need to be constant are skipped
func constantTest() {
	const a = "foo" // skip
	const length = len(a)

	const b = "bar" // skip
	type T [len(b)]byte

	const c = "foo" // skip
	var _ [len(c)]byte

	const d = "foo" // skip
	var arr = [5]string{len(d): "foo"}
	for _, elm := range arr {
		if elm != "" {
			println(elm)
		}
	}

	const e = "foo" // skip
	var slice = []string{len(e): "foo"}
	for _, elm := range slice {
		if elm != "" {
			println(elm)
		}
	}

	const f = "foo" // skip
	const i = length + len(f)
	println(length, i)
}

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()

	d := func() [4]byte {
		return [4]byte{12, 13}
	}()
	for _, elm := range d {
		print(elm, ",")
	}
	println()
}

func stringTypeFunc(s stringType) stringType {
	println(s)
	return "stringType return" // skip
}

// obfuscating this broke before
const (
	iota0 uint8 = iota
	iota1
)

-- main.stderr --
Lorem true
First Line
Second Line total string
dolor
second assign

😅 😅
to obfuscate also obfuscate
new value
another literal
Skip this block also skip this
1 0 1
skip untyped const
skip typed const skip typed var skip typed var assign
stringTypeField String stringTypeField strType
stringType lambda func return
stringType func param
stringType return
foo
foo
3 6
12,13,
12,13,
12,13,
12,13,0,0,
