mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-05 03:58:23 +00:00
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:
parent
bf6aa02dcf
commit
9d6b0ee4a8
2 changed files with 47 additions and 12 deletions
|
@ -617,19 +617,24 @@ 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:
|
||||
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)
|
||||
case n.Op == token.NEQ:
|
||||
} else {
|
||||
// VM has separate opcodes for number and string equality
|
||||
if isStringType(c.typeInfo.Types[n.X].Type) {
|
||||
emit.Opcode(c.prog.BinWriter, opcode.EQUAL)
|
||||
|
@ -637,6 +642,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
} else {
|
||||
emit.Opcode(c.prog.BinWriter, opcode.NUMNOTEQUAL)
|
||||
}
|
||||
}
|
||||
default:
|
||||
c.convertToken(n.Op)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue