neo-go/pkg/compiler/global_test.go
2022-09-01 13:39:22 +03:00

939 lines
25 KiB
Go

package compiler_test
import (
"bytes"
"fmt"
"math/big"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require"
)
func TestUnusedGlobal(t *testing.T) {
t.Run("simple unused", func(t *testing.T) {
src := `package foo
const (
_ int = iota
a
)
func Main() int {
return 1
}`
prog := eval(t, src, big.NewInt(1))
require.Equal(t, 2, len(prog)) // PUSH1 + RET
})
t.Run("unused with function call inside", func(t *testing.T) {
t.Run("specification names count matches values count", func(t *testing.T) {
src := `package foo
var control int
var _ = f()
func Main() int {
return control
}
func f() int {
control = 1
return 5
}`
eval(t, src, big.NewInt(1))
})
t.Run("specification names count differs from values count", func(t *testing.T) {
src := `package foo
var control int
var _, _ = f()
func Main() int {
return control
}
func f() (int, int) {
control = 1
return 5, 6
}`
eval(t, src, big.NewInt(1))
})
t.Run("used", func(t *testing.T) {
src := `package foo
var _, A = f()
func Main() int {
return A
}
func f() (int, int) {
return 5, 6
}`
eval(t, src, big.NewInt(6))
checkInstrCount(t, src, 1, 1, 0, 0) // sslot for A, single call to f
})
})
t.Run("unused without function call", func(t *testing.T) {
src := `package foo
var _ = 1
var (
_ = 2 + 3
_, _ = 3 + 4, 5
)
func Main() int {
return 1
}`
prog := eval(t, src, big.NewInt(1))
require.Equal(t, 2, len(prog)) // PUSH1 + RET
})
}
func TestUnusedOptimizedGlobalVar(t *testing.T) {
t.Run("unused, no initialization", func(t *testing.T) {
src := `package foo
var A int
var (
B int
C, D, E int
)
func Main() int {
return 1
}`
prog := eval(t, src, big.NewInt(1))
require.Equal(t, 2, len(prog)) // Main
})
t.Run("used, no initialization", func(t *testing.T) {
src := `package foo
var A int
func Main() int {
return A
}`
eval(t, src, big.NewInt(0))
checkInstrCount(t, src, 1, 0, 0, 0) // sslot for A
})
t.Run("used by unused var, no initialization", func(t *testing.T) {
src := `package foo
var Unused int
var Unused2 = Unused + 1
func Main() int {
return 1
}`
prog := eval(t, src, big.NewInt(1))
require.Equal(t, 2, len(prog)) // Main
})
t.Run("unused, with initialization", func(t *testing.T) {
src := `package foo
var Unused = 1
func Main() int {
return 2
}`
prog := eval(t, src, big.NewInt(2))
require.Equal(t, 2, len(prog)) // Main
})
t.Run("unused, with initialization by used var", func(t *testing.T) {
src := `package foo
var (
A = 1
B, Unused, C = f(), A + 2, 3 // the code for Unused initialization won't be emitted as it's a pure expression without function calls
Unused2 = 4
)
var Unused3 = 5
func Main() int {
return A + C
}
func f() int {
return 4
}`
eval(t, src, big.NewInt(4), []interface{}{opcode.INITSSLOT, []byte{2}}, // sslot for A and C
opcode.PUSH1, opcode.STSFLD0, // store A
[]interface{}{opcode.CALL, []byte{10}}, opcode.DROP, // evaluate B and drop
opcode.PUSH3, opcode.STSFLD1, opcode.RET, // store C
opcode.LDSFLD0, opcode.LDSFLD1, opcode.ADD, opcode.RET, // Main
opcode.PUSH4, opcode.RET) // f
})
t.Run("used by unused var, with initialization", func(t *testing.T) {
src := `package foo
var (
Unused1 = 1
Unused2 = Unused1 + 1
)
func Main() int {
return 1
}`
prog := eval(t, src, big.NewInt(1))
require.Equal(t, 2, len(prog)) // Main
})
t.Run("used with combination of nested unused", func(t *testing.T) {
src := `package foo
var (
A = 1
Unused1 = 2
Unused2 = Unused1 + 1
)
func Main() int {
return A
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for A
opcode.PUSH1, opcode.STSFLD0, opcode.RET, // store A
opcode.LDSFLD0, opcode.RET) // Main
})
t.Run("single var stmt with both used and unused vars", func(t *testing.T) {
src := `package foo
var A, Unused1, B, Unused2 = 1, 2, 3, 4
func Main() int {
return A + B
}`
eval(t, src, big.NewInt(4), []interface{}{opcode.INITSSLOT, []byte{2}}, // sslot for A and B
opcode.PUSH1, opcode.STSFLD0, // store A
opcode.PUSH3, opcode.STSFLD1, opcode.RET, // store B
opcode.LDSFLD0, opcode.LDSFLD1, opcode.ADD, opcode.RET) // Main
})
t.Run("single var decl token with multiple var specifications", func(t *testing.T) {
src := `package foo
var (
A, Unused1, B, Unused2 = 1, 2, 3, 4
C, Unused3 int
)
func Main() int {
return A + B + C
}`
eval(t, src, big.NewInt(4), []interface{}{opcode.INITSSLOT, []byte{3}}, // sslot for A, B, C
opcode.PUSH1, opcode.STSFLD0, // store A
opcode.PUSH3, opcode.STSFLD1, // store B
opcode.PUSH0, opcode.STSFLD2, opcode.RET, // store C
opcode.LDSFLD0, opcode.LDSFLD1, opcode.ADD, opcode.LDSFLD2, opcode.ADD, opcode.RET) // Main
})
t.Run("function as unused var value", func(t *testing.T) {
src := `package foo
var A, Unused1 = 1, f()
func Main() int {
return A
}
func f() int {
return 2
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for A
opcode.PUSH1, opcode.STSFLD0, // store A
[]interface{}{opcode.CALL, []byte{6}}, opcode.DROP, opcode.RET, // evaluate Unused1 (call to f) and drop its value
opcode.LDSFLD0, opcode.RET, // Main
opcode.PUSH2, opcode.RET) // f
})
t.Run("function as unused struct field", func(t *testing.T) {
src := `package foo
type Str struct { Int int }
var _ = Str{Int: f()}
func Main() int {
return 1
}
func f() int {
return 2
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.CALL, []byte{8}}, opcode.PUSH1, opcode.PACKSTRUCT, opcode.DROP, opcode.RET, // evaluate struct val
opcode.PUSH1, opcode.RET, // Main
opcode.PUSH2, opcode.RET) // f
})
t.Run("used in unused function", func(t *testing.T) {
src := `package foo
var Unused1, Unused2, Unused3 = 1, 2, 3
func Main() int {
return 1
}
func unused1() int {
return Unused1
}
func unused2() int {
return Unused1 + unused1()
}
func unused3() int {
return Unused2 + unused2()
}`
prog := eval(t, src, big.NewInt(1))
require.Equal(t, 2, len(prog)) // Main
})
t.Run("used in used function", func(t *testing.T) {
src := `package foo
var A = 1
func Main() int {
return f()
}
func f() int {
return A
}`
eval(t, src, big.NewInt(1))
checkInstrCount(t, src, 1, 1, 0, 0)
})
t.Run("unused, initialized via init", func(t *testing.T) {
src := `package foo
var A int
func Main() int {
return 2
}
func init() {
A = 1 // Although A is unused from exported functions, it's used from init(), so it should be mark as "used" and stored.
}`
eval(t, src, big.NewInt(2))
checkInstrCount(t, src, 1, 0, 0, 0)
})
t.Run("used, initialized via init", func(t *testing.T) {
src := `package foo
var A int
func Main() int {
return A
}
func init() {
A = 1
}`
eval(t, src, big.NewInt(1))
checkInstrCount(t, src, 1, 0, 0, 0)
})
t.Run("unused, initialized by function call", func(t *testing.T) {
t.Run("unnamed", func(t *testing.T) {
src := `package foo
var _ = f()
func Main() int {
return 1
}
func f() int {
return 2
}`
eval(t, src, big.NewInt(1))
checkInstrCount(t, src, 0, 1, 0, 0)
})
t.Run("named", func(t *testing.T) {
src := `package foo
var A = f()
func Main() int {
return 1
}
func f() int {
return 2
}`
eval(t, src, big.NewInt(1))
checkInstrCount(t, src, 0, 1, 0, 0)
})
t.Run("named, with dependency on unused var", func(t *testing.T) {
src := `package foo
var (
A = 1
B = A + 1 // To check nested ident values.
C = 3
D = B + f() + C // To check that both idents (before and after the call to f) will be marked as "used".
E = C + 1 // Unused, no code expected.
)
func Main() int {
return 1
}
func f() int {
return 2
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{3}}, // sslot for A
opcode.PUSH1, opcode.STSFLD0, // store A
opcode.LDSFLD0, opcode.PUSH1, opcode.ADD, opcode.STSFLD1, // store B
opcode.PUSH3, opcode.STSFLD2, // store C
opcode.LDSFLD1, []interface{}{opcode.CALL, []byte{9}}, opcode.ADD, opcode.LDSFLD2, opcode.ADD, opcode.DROP, opcode.RET, // evaluate D and drop
opcode.PUSH1, opcode.RET, // Main
opcode.PUSH2, opcode.RET) // f
})
t.Run("named, with dependency on unused var ident inside function call", func(t *testing.T) {
src := `package foo
var A = 1
var B = f(A)
func Main() int {
return 1
}
func f(a int) int {
return a
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for A
opcode.PUSH1, opcode.STSFLD0, // store A
opcode.LDSFLD0, []interface{}{opcode.CALL, []byte{6}}, opcode.DROP, opcode.RET, // evaluate B and drop
opcode.PUSH1, opcode.RET, // Main
[]interface{}{opcode.INITSLOT, []byte{0, 1}}, opcode.LDARG0, opcode.RET) // f
})
t.Run("named, inside multi-specs and multi-vals var declaration", func(t *testing.T) {
src := `package foo
var (
Unused = 1
Unused1, A, Unused2 = 2, 3 + f(), 4
)
func Main() int {
return 1
}
func f() int {
return 5
}`
eval(t, src, big.NewInt(1), opcode.PUSH3, []interface{}{opcode.CALL, []byte{7}}, opcode.ADD, opcode.DROP, opcode.RET, // evaluate and drop A
opcode.PUSH1, opcode.RET, // Main
opcode.PUSH5, opcode.RET) // f
})
t.Run("unnamed + unused", func(t *testing.T) {
src := `package foo
var A = 1 // At least one global variable is used, thus, the whole set of package variables will be walked.
var B = 2
var _ = B + 1 // This variable is unnamed and doesn't contain call, thus its children won't be marked as "used".
func Main() int {
return A
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for A
opcode.PUSH1, opcode.STSFLD0, opcode.RET, // store A
opcode.LDSFLD0, opcode.RET) // Main
})
t.Run("mixed value", func(t *testing.T) {
src := `package foo
var control int // At least one global variable is used, thus the whole set of package variables will be walked.
var B = 2
var _ = 1 + f() + B // This variable is unnamed but contains call, thus its children will be marked as "used".
func Main() int {
return control
}
func f() int {
control = 1
return 3
}`
eval(t, src, big.NewInt(1))
checkInstrCount(t, src, 2 /* control + B */, 1, 0, 0)
})
t.Run("multiple function return values", func(t *testing.T) {
src := `package foo
var A, B = f()
func Main() int {
return A
}
func f() (int, int) {
return 3, 4
}`
eval(t, src, big.NewInt(3), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for A
[]interface{}{opcode.CALL, []byte{7}}, opcode.STSFLD0, opcode.DROP, opcode.RET, // evaluate and store A, drop B
opcode.LDSFLD0, opcode.RET, // Main
opcode.PUSH4, opcode.PUSH3, opcode.RET) // f
})
t.Run("constant in declaration", func(t *testing.T) {
src := `package foo
const A = 5
var Unused = 1 + A
func Main() int {
return 1
}`
prog := eval(t, src, big.NewInt(1))
require.Equal(t, 2, len(prog)) // Main
})
t.Run("mixed expression", func(t *testing.T) {
src := `package foo
type CustomInt struct {
Int int
}
var A = CustomInt{Int: 2}
var B = f(3) + A.f(1)
func Main() int {
return 1
}
func f(a int) int {
return a
}
func (i CustomInt) f(a int) int { // has the same name as f
return i.Int + a
}`
eval(t, src, big.NewInt(1))
checkInstrCount(t, src, 1 /* A */, 2, 2, 0)
})
})
t.Run("mixed nested expressions", func(t *testing.T) {
src := `package foo
type CustomInt struct { Int int} // has the same field name as Int variable, important for test
var A = CustomInt{Int: 2}
var B = f(A.Int)
var Unused = 4
var Int = 5 // unused and MUST NOT be treated as "used"
var C = CustomInt{Int: Unused}.Int + f(1) // uses Unused => Unused should be marked as "used"
func Main() int {
return 1
}
func f(a int) int {
return a
}
func (i CustomInt) f(a int) int { // has the same name as f
return i.Int + a
}`
eval(t, src, big.NewInt(1))
})
t.Run("composite literal", func(t *testing.T) {
src := `package foo
var A = 2
var B = []int{1, A, 3}[1]
var C = f(1) + B
func Main() int {
return 1
}
func f(a int) int {
return a
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{2}}, // sslot for A, B
opcode.PUSH2, opcode.STSFLD0, // store A
opcode.PUSH3, opcode.LDSFLD0, opcode.PUSH1, opcode.PUSH3, opcode.PACK, opcode.PUSH1, opcode.PICKITEM, opcode.STSFLD1, // evaluate B
opcode.PUSH1, []interface{}{opcode.CALL, []byte{8}}, opcode.LDSFLD1, opcode.ADD, opcode.DROP, opcode.RET, // evalute C and drop
opcode.PUSH1, opcode.RET, // Main
[]interface{}{opcode.INITSLOT, []byte{0, 1}}, opcode.LDARG0, opcode.RET) // f
})
t.Run("index expression", func(t *testing.T) {
src := `package foo
var Unused = 2
var A = f(1) + []int{1, 2, 3}[Unused] // index expression
func Main() int {
return 1
}
func f(a int) int {
return a
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for Unused
opcode.PUSH2, opcode.STSFLD0, // store Unused
opcode.PUSH1, []interface{}{opcode.CALL, []byte{14}}, // call f(1)
opcode.PUSH3, opcode.PUSH2, opcode.PUSH1, opcode.PUSH3, opcode.PACK, opcode.LDSFLD0, opcode.PICKITEM, // eval index expression
opcode.ADD, opcode.DROP, opcode.RET, // eval and drop A
opcode.PUSH1, opcode.RET, // Main
[]interface{}{opcode.INITSLOT, []byte{0, 1}}, opcode.LDARG0, opcode.RET) // f(a)
})
t.Run("used via nested function calls", func(t *testing.T) {
src := `package foo
var A = 1
func Main() int {
return f()
}
func f() int {
return g()
}
func g() int {
return A
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for A
opcode.PUSH1, opcode.STSFLD0, opcode.RET, // store A
[]interface{}{opcode.CALL, []byte{3}}, opcode.RET, // Main
[]interface{}{opcode.CALL, []byte{3}}, opcode.RET, // f
opcode.LDSFLD0, opcode.RET) // g
})
t.Run("struct field name matches global var name", func(t *testing.T) {
src := `package foo
type CustomStr struct { Int int }
var str = CustomStr{Int: 2}
var Int = 5 // Unused and the code must not be emitted.
func Main() int {
return str.Int
}`
eval(t, src, big.NewInt(2), []interface{}{opcode.INITSSLOT, []byte{1}}, // sslot for str
opcode.PUSH2, opcode.PUSH1, opcode.PACKSTRUCT, opcode.STSFLD0, opcode.RET, // store str
opcode.LDSFLD0, opcode.PUSH0, opcode.PICKITEM, opcode.RET) // Main
})
t.Run("var as a struct field initializer", func(t *testing.T) {
src := `package foo
type CustomStr struct { Int int }
var A = 5
var Int = 6 // Unused
func Main() int {
return CustomStr{Int: A}.Int
}`
eval(t, src, big.NewInt(5))
})
t.Run("argument of globally called function", func(t *testing.T) {
src := `package foo
var Unused = 5
var control int
var _, A = f(Unused)
func Main() int {
return control
}
func f(int) (int, int) {
control = 5
return 1, 2
}`
eval(t, src, big.NewInt(5))
})
t.Run("argument of locally called function", func(t *testing.T) {
src := `package foo
var Unused = 5
func Main() int {
var _, a = f(Unused)
return a
}
func f(i int) (int, int) {
return i, i
}`
eval(t, src, big.NewInt(5))
})
t.Run("used in globally called defer", func(t *testing.T) {
src := `package foo
var control1, control2 int
var Unused = 5
var _ = f()
func Main() int {
return control1 + control2
}
func f() int {
control1 = 1
defer func(){
control2 = Unused
}()
return 2
}`
eval(t, src, big.NewInt(6))
})
t.Run("used in locally called defer", func(t *testing.T) {
src := `package foo
var control1, control2 int
var Unused = 5
func Main() int {
_ = f()
return control1 + control2
}
func f() int {
control1 = 1
defer func(){
control2 = Unused
}()
return 2
}`
eval(t, src, big.NewInt(6))
})
t.Run("imported", func(t *testing.T) {
t.Run("init by func call", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/globalvar"
func Main() int {
return globalvar.Default
}`
eval(t, src, big.NewInt(0))
checkInstrCount(t, src, 1 /* Default */, 1 /* f */, 0, 0)
})
t.Run("nested var call", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/globalvar/nested1"
func Main() int {
return nested1.C
}`
eval(t, src, big.NewInt(81))
checkInstrCount(t, src, 6 /* dependant vars of nested1.C */, 3, 1, 1)
})
t.Run("nested func call", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/globalvar/funccall"
func Main() int {
return funccall.F()
}`
eval(t, src, big.NewInt(56))
checkInstrCount(t, src, 2 /* nested2.Argument + nested1.Argument */, -1, -1, -1)
})
t.Run("nested method call", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/globalvar/funccall"
func Main() int {
return funccall.GetAge()
}`
eval(t, src, big.NewInt(24))
checkInstrCount(t, src, 3, /* nested3.Anna + nested2.Argument + nested3.Argument */
5, /* funccall.GetAge() + Anna.GetAge() + nested1.f + nested1.f + nested2.f */
2 /* nested1.f + nested2.f */, 0)
})
})
}
func TestChangeGlobal(t *testing.T) {
t.Run("from Main", func(t *testing.T) {
src := `package foo
var a int
func Main() int {
setLocal()
set42()
setLocal()
return a
}
func set42() { a = 42 }
func setLocal() { a := 10; _ = a }`
eval(t, src, big.NewInt(42))
})
t.Run("from other global", func(t *testing.T) {
t.Skip("see https://github.com/nspcc-dev/neo-go/issues/2661")
src := `package foo
var A = f()
var B int
func Main() int {
return B
}
func f() int {
B = 3
return B
}`
eval(t, src, big.NewInt(3))
})
}
func TestMultiDeclaration(t *testing.T) {
src := `package foo
var a, b, c int
func Main() int {
a = 1
b = 2
c = 3
return a + b + c
}`
eval(t, src, big.NewInt(6))
}
func TestCountLocal(t *testing.T) {
src := `package foo
func Main() int {
a, b, c, d := f()
return a + b + c + d
}
func f() (int, int, int, int) {
return 1, 2, 3, 4
}`
eval(t, src, big.NewInt(10))
}
func TestMultiDeclarationLocal(t *testing.T) {
src := `package foo
func Main() int {
var a, b, c int
a = 1
b = 2
c = 3
return a + b + c
}`
eval(t, src, big.NewInt(6))
}
func TestMultiDeclarationLocalCompound(t *testing.T) {
src := `package foo
func Main() int {
var a, b, c []int
a = append(a, 1)
b = append(b, 2)
c = append(c, 3)
return a[0] + b[0] + c[0]
}`
eval(t, src, big.NewInt(6))
}
func TestShadow(t *testing.T) {
srcTmpl := `package foo
func Main() int {
x := 1
y := 10
%s
x += 1 // increase old local
x := 30 // introduce new local
y += x // make sure is means something
}
return x+y
}`
runCase := func(b string) func(t *testing.T) {
return func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, b)
eval(t, src, big.NewInt(42))
}
}
t.Run("If", runCase("if true {"))
t.Run("For", runCase("for i := 0; i < 1; i++ {"))
t.Run("Range", runCase("for range []int{1} {"))
t.Run("Switch", runCase("switch true {\ncase false: x += 2\ncase true:"))
t.Run("Block", runCase("{"))
}
func TestArgumentLocal(t *testing.T) {
srcTmpl := `package foo
func some(a int) int {
if a > 42 {
a := 24
_ = a
}
return a
}
func Main() int {
return some(%d)
}`
t.Run("Override", func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, 50)
eval(t, src, big.NewInt(50))
})
t.Run("NoOverride", func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, 40)
eval(t, src, big.NewInt(40))
})
}
func TestContractWithNoMain(t *testing.T) {
src := `package foo
var someGlobal int = 1
func Add3(a int) int {
someLocal := 2
return someGlobal + someLocal + a
}`
b, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.NoError(t, err)
v := vm.New()
invokeMethod(t, "Add3", b.Script, v, di)
v.Estack().PushVal(39)
require.NoError(t, v.Run())
require.Equal(t, 1, v.Estack().Len())
require.Equal(t, big.NewInt(42), v.PopResult())
}
func TestMultipleFiles(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
func Main() int {
return multi.Sum()
}`
eval(t, src, big.NewInt(42))
}
func TestExportedVariable(t *testing.T) {
t.Run("Use", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
func Main() int {
return multi.SomeVar12
}`
eval(t, src, big.NewInt(12))
})
t.Run("ChangeAndUse", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
func Main() int {
multi.SomeVar12 = 10
return multi.Sum()
}`
eval(t, src, big.NewInt(40))
})
t.Run("PackageAlias", func(t *testing.T) {
src := `package foo
import kek "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
func Main() int {
kek.SomeVar12 = 10
return kek.Sum()
}`
eval(t, src, big.NewInt(40))
})
t.Run("DifferentName", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/strange"
func Main() int {
normal.NormalVar = 42
return normal.NormalVar
}`
eval(t, src, big.NewInt(42))
})
t.Run("MultipleEqualNames", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
var SomeVar12 = 1
func Main() int {
SomeVar30 := 3
sum := SomeVar12 + multi.SomeVar30
sum += SomeVar30
sum += multi.SomeVar12
return sum
}`
eval(t, src, big.NewInt(46))
})
}
func TestExportedConst(t *testing.T) {
t.Run("with vars", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
func Main() int {
return multi.SomeConst
}`
eval(t, src, big.NewInt(42))
})
t.Run("const only", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/constonly"
func Main() int {
return constonly.Answer
}`
eval(t, src, big.NewInt(42))
})
}
func TestMultipleFuncSameName(t *testing.T) {
t.Run("Simple", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
func Main() int {
return multi.Sum() + Sum()
}
func Sum() int {
return 11
}`
eval(t, src, big.NewInt(53))
})
t.Run("WithMethod", func(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/foo"
type Foo struct{}
func (f Foo) Bar() int { return 11 }
func Bar() int { return 22 }
func Main() int {
var a Foo
var b foo.Foo
return a.Bar() + // 11
foo.Bar() + // 1
b.Bar() + // 8
Bar() // 22
}`
eval(t, src, big.NewInt(42))
})
}
func TestConstDontUseSlots(t *testing.T) {
const count = 256
buf := bytes.NewBufferString("package foo\n")
for i := 0; i < count; i++ {
buf.WriteString(fmt.Sprintf("const n%d = 1\n", i))
}
buf.WriteString("func Main() int { sum := 0\n")
for i := 0; i < count; i++ {
buf.WriteString(fmt.Sprintf("sum += n%d\n", i))
}
buf.WriteString("return sum }")
src := buf.String()
eval(t, src, big.NewInt(count))
}
func TestUnderscoreVarsDontUseSlots(t *testing.T) {
const count = 128
buf := bytes.NewBufferString("package foo\n")
for i := 0; i < count; i++ {
buf.WriteString(fmt.Sprintf("var _, n%d = 1, 1\n", i))
}
buf.WriteString("func Main() int { sum := 0\n")
for i := 0; i < count; i++ {
buf.WriteString(fmt.Sprintf("sum += n%d\n", i))
}
buf.WriteString("return sum }")
src := buf.String()
eval(t, src, big.NewInt(count))
}
func TestUnderscoreGlobalVarDontEmitCode(t *testing.T) {
src := `package foo
var _ int
var _ = 1
var (
A = 2
_ = A + 3
_, B, _ = 4, 5, 6
_, C, _ = f(A, B)
)
var D = 7 // named unused, after global codegen optimisation no code expected
func Main() int {
return 1
}
func f(a, b int) (int, int, int) {
return 8, 9, 10
}`
eval(t, src, big.NewInt(1), []interface{}{opcode.INITSSLOT, []byte{2}}, // sslot for A, B
opcode.PUSH2, opcode.STSFLD0, // store A
opcode.PUSH5, opcode.STSFLD1, // store B
opcode.LDSFLD0, opcode.LDSFLD1, opcode.SWAP, []interface{}{opcode.CALL, []byte{8}}, // evaluate f(A,B)
opcode.DROP, opcode.DROP, opcode.DROP, opcode.RET, // drop result of f(A,B)
opcode.PUSH1, opcode.RET, // Main
[]interface{}{opcode.INITSLOT, []byte{0, 2}}, opcode.PUSH10, opcode.PUSH9, opcode.PUSH8, opcode.RET) // f
}