compiler: prohibit to compile type assertion with two return values
Close #2692.
This commit is contained in:
parent
3dbd36ef70
commit
b98848bf49
3 changed files with 117 additions and 8 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue