diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index d1e36f75c..7c4cfa30b 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -617,25 +617,31 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.X) ast.Walk(c, n.Y) - switch { - case n.Op == token.ADD: + switch n.Op { + case token.ADD: // VM has separate opcodes for number and string concatenation if isStringType(tinfo.Type) { emit.Opcode(c.prog.BinWriter, opcode.CAT) } else { emit.Opcode(c.prog.BinWriter, opcode.ADD) } - case n.Op == token.EQL: - // VM has separate opcodes for number and string equality - op := c.getEqualityOpcode(n.X) - emit.Opcode(c.prog.BinWriter, op) - case n.Op == token.NEQ: - // VM has separate opcodes for number and string equality - if isStringType(c.typeInfo.Types[n.X].Type) { - emit.Opcode(c.prog.BinWriter, opcode.EQUAL) - emit.Opcode(c.prog.BinWriter, opcode.NOT) + case token.EQL, token.NEQ: + if isExprNil(n.X) || isExprNil(n.Y) { + c.prog.Err = errors.New("comparison with `nil` is not supported, use `len(..) == 0` instead") + return nil + } + if n.Op == token.EQL { + // VM has separate opcodes for number and string equality + op := c.getEqualityOpcode(n.X) + emit.Opcode(c.prog.BinWriter, op) } else { - emit.Opcode(c.prog.BinWriter, opcode.NUMNOTEQUAL) + // VM has separate opcodes for number and string equality + if isStringType(c.typeInfo.Types[n.X].Type) { + emit.Opcode(c.prog.BinWriter, opcode.EQUAL) + emit.Opcode(c.prog.BinWriter, opcode.NOT) + } else { + emit.Opcode(c.prog.BinWriter, opcode.NUMNOTEQUAL) + } } default: c.convertToken(n.Op) diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index ca4c25798..d1f77a369 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -1,10 +1,14 @@ package compiler_test import ( + "fmt" "math/big" + "strings" "testing" + "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/stretchr/testify/require" ) var sliceTestCases = []testCase{ @@ -175,6 +179,31 @@ func TestSliceOperations(t *testing.T) { runTestCases(t, sliceTestCases) } +func TestSliceEmpty(t *testing.T) { + srcTmpl := `package foo + func Main() int { + var a []int + %s + if %s { + return 1 + } + return 2 + }` + t.Run("WithNil", func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, "", "a == nil") + _, err := compiler.Compile(strings.NewReader(src)) + require.Error(t, err) + }) + t.Run("WithLen", func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, "", "len(a) == 0") + eval(t, src, big.NewInt(1)) + }) + t.Run("NonEmpty", func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, "a = []int{1}", "len(a) == 0") + eval(t, src, big.NewInt(2)) + }) +} + func TestJumps(t *testing.T) { src := ` package foo