compiler: support panic in source

In situations where VM's FAULT state needs to be reached,
panic function can be used. It compiles to THROW instruction.
This commit is contained in:
Evgenii Stratonikov 2020-01-28 17:18:38 +03:00
parent a839efb35e
commit d2326a8b96
3 changed files with 89 additions and 0 deletions

View file

@ -16,6 +16,7 @@ var (
"SHA1", "Hash256", "Hash160", "SHA1", "Hash256", "Hash160",
"VerifySignature", "AppCall", "VerifySignature", "AppCall",
"FromAddress", "Equals", "FromAddress", "Equals",
"panic",
} }
) )
@ -69,6 +70,12 @@ func isIdentBool(ident *ast.Ident) bool {
return ident.Name == "true" || ident.Name == "false" return ident.Name == "true" || ident.Name == "false"
} }
// isExprNil looks if the given expression is a `nil`.
func isExprNil(e ast.Expr) bool {
v, ok := e.(*ast.Ident)
return ok && v.Name == "nil"
}
// makeBoolFromIdent creates a bool type from an *ast.Ident. // makeBoolFromIdent creates a bool type from an *ast.Ident.
func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) (types.TypeAndValue, error) { func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) (types.TypeAndValue, error) {
var b bool var b bool

View file

@ -770,6 +770,17 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
emitOpcode(c.prog.BinWriter, opcode.XSWAP) emitOpcode(c.prog.BinWriter, opcode.XSWAP)
emitOpcode(c.prog.BinWriter, opcode.APPEND) emitOpcode(c.prog.BinWriter, opcode.APPEND)
} }
case "panic":
arg := expr.Args[0]
if isExprNil(arg) {
emitOpcode(c.prog.BinWriter, opcode.DROP)
emitOpcode(c.prog.BinWriter, opcode.THROW)
} else if isStringType(c.typeInfo.Types[arg].Type) {
emitSyscall(c.prog.BinWriter, "Neo.Runtime.Log")
emitOpcode(c.prog.BinWriter, opcode.THROW)
} else {
c.prog.Err = errors.New("panic should have string or nil argument")
}
case "SHA256": case "SHA256":
emitOpcode(c.prog.BinWriter, opcode.SHA256) emitOpcode(c.prog.BinWriter, opcode.SHA256)
case "SHA1": case "SHA1":

View file

@ -0,0 +1,71 @@
package compiler_test
import (
"fmt"
"math/big"
"testing"
"github.com/CityOfZion/neo-go/pkg/vm"
"github.com/stretchr/testify/require"
)
func TestPanic(t *testing.T) {
t.Run("no panic", func(t *testing.T) {
src := getPanicSource(false, `"execution fault"`)
eval(t, src, big.NewInt(7))
})
t.Run("panic with message", func(t *testing.T) {
var logs []string
src := getPanicSource(true, `"execution fault"`)
v := vmAndCompile(t, src)
v.RegisterInteropGetter(logGetter(&logs))
require.Error(t, v.Run())
require.True(t, v.HasFailed())
require.Equal(t, 1, len(logs))
require.Equal(t, "execution fault", logs[0])
})
t.Run("panic with nil", func(t *testing.T) {
var logs []string
src := getPanicSource(true, `nil`)
v := vmAndCompile(t, src)
v.RegisterInteropGetter(logGetter(&logs))
require.Error(t, v.Run())
require.True(t, v.HasFailed())
require.Equal(t, 0, len(logs))
})
}
func getPanicSource(need bool, message string) string {
return fmt.Sprintf(`
package main
func Main() int {
needPanic := %#v
if needPanic {
panic(%s)
return 5
}
return 7
}
`, need, message)
}
func logGetter(logs *[]string) vm.InteropGetterFunc {
logID := vm.InteropNameToID([]byte("Neo.Runtime.Log"))
return func(id uint32) *vm.InteropFuncPrice {
if id != logID {
return nil
}
return &vm.InteropFuncPrice{
Func: func(v *vm.VM) error {
msg := string(v.Estack().Pop().Bytes())
*logs = append(*logs, msg)
return nil
},
}
}
}