compiler: disallow comparing slices with nil

NEO VM does not distinguish between empty and nil slices. Supporting
this is not easy and requires changing lots of other opcodes.
Pointers are not supported anyway and slices can be checked for
emptiness by inspecting their `len`.
This commit is contained in:
Evgenii Stratonikov 2020-06-08 10:57:14 +03:00
parent bf6aa02dcf
commit 9d6b0ee4a8
2 changed files with 47 additions and 12 deletions

View file

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

View file

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