compiler: prohibit to compile type assertion with two return values

Close #2692.
This commit is contained in:
Anna Shaleva 2022-09-28 09:58:59 +03:00
parent 3dbd36ef70
commit b98848bf49
3 changed files with 117 additions and 8 deletions

View file

@ -28,6 +28,9 @@ a dialect of Go rather than a complete port of the language:
* converting value to interface type doesn't change the underlying type,
original value will always be used, therefore it never panics and always "succeeds";
it's up to the programmer whether it's a correct use of a value
* type assertion with two return values is not supported; single return value (of the desired type)
is supported; type assertion panics if value can't be asserted to the desired type, therefore
it's up to the programmer whether assert can be performed successfully.
## VM API (interop layer)
Compiler translates interop function calls into NEO VM syscalls or (for custom

View file

@ -161,6 +161,9 @@ const (
varArgument
)
// ErrUnsupportedTypeAssertion is returned when type assertion statement is not supported by the compiler.
var ErrUnsupportedTypeAssertion = errors.New("type assertion with two return values is not supported")
// newLabel creates a new label to jump to.
func (c *codegen) newLabel() (l uint16) {
li := len(c.l)
@ -584,6 +587,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
for _, spec := range n.Specs {
switch t := spec.(type) {
case *ast.ValueSpec:
// Filter out type assertion with two return values: var i, ok = v.(int)
if len(t.Names) == 2 && len(t.Values) == 1 && n.Tok == token.VAR {
err := checkTypeAssertWithOK(t.Values[0])
if err != nil {
c.prog.Err = err
return nil
}
}
multiRet := n.Tok == token.VAR && len(t.Values) != 0 && len(t.Names) != len(t.Values)
for _, id := range t.Names {
if id.Name != "_" {
@ -630,6 +641,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
return nil
case *ast.AssignStmt:
// Filter out type assertion with two return values: i, ok = v.(int)
if len(n.Lhs) == 2 && len(n.Rhs) == 1 && (n.Tok == token.DEFINE || n.Tok == token.ASSIGN) {
err := checkTypeAssertWithOK(n.Rhs[0])
if err != nil {
c.prog.Err = err
return nil
}
}
multiRet := len(n.Rhs) != len(n.Lhs)
c.saveSequencePoint(n)
// Assign operations are grouped https://github.com/golang/go/blob/master/src/go/types/stmt.go#L160
@ -1346,6 +1365,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
return c
}
func checkTypeAssertWithOK(n ast.Node) error {
if t, ok := n.(*ast.TypeAssertExpr); ok &&
t.Type != nil { // not a type switch
return ErrUnsupportedTypeAssertion
}
return nil
}
// packVarArgs packs variadic arguments into an array
// and returns the amount of arguments packed.
func (c *codegen) packVarArgs(n *ast.CallExpr, typ *types.Signature) int {

View file

@ -81,14 +81,93 @@ func TestConvert(t *testing.T) {
}
func TestTypeAssertion(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u interface{}
u = a
return u.(int)
}`
eval(t, src, big.NewInt(1))
t.Run("inside return statement", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u interface{}
u = a
return u.(int)
}`
eval(t, src, big.NewInt(1))
})
t.Run("inside general declaration", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u interface{}
u = a
var ret = u.(int)
return ret
}`
eval(t, src, big.NewInt(1))
})
t.Run("inside assignment statement", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u interface{}
u = a
var ret int
ret = u.(int)
return ret
}`
eval(t, src, big.NewInt(1))
})
t.Run("inside definition statement", func(t *testing.T) {
src := `package foo
func Main() int {
a := []byte{1}
var u interface{}
u = a
ret := u.(int)
return ret
}`
eval(t, src, big.NewInt(1))
})
}
func TestTypeAssertionWithOK(t *testing.T) {
t.Run("inside general declaration", func(t *testing.T) {
src := `package foo
func Main() bool {
a := 1
var u interface{}
u = a
var _, ok = u.(int) // *ast.GenDecl
return ok
}`
_, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.Error(t, err)
require.ErrorIs(t, err, compiler.ErrUnsupportedTypeAssertion)
})
t.Run("inside assignment statement", func(t *testing.T) {
src := `package foo
func Main() bool {
a := 1
var u interface{}
u = a
var ok bool
_, ok = u.(int) // *ast.AssignStmt
return ok
}`
_, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.Error(t, err)
require.ErrorIs(t, err, compiler.ErrUnsupportedTypeAssertion)
})
t.Run("inside definition statement", func(t *testing.T) {
src := `package foo
func Main() bool {
a := 1
var u interface{}
u = a
_, ok := u.(int) // *ast.AssignStmt
return ok
}`
_, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.Error(t, err)
require.ErrorIs(t, err, compiler.ErrUnsupportedTypeAssertion)
})
}
func TestTypeConversion(t *testing.T) {