vm: convert items to UTF-8 strings

Add `stackitem.ToString` for seamless string conversion.
This commit is contained in:
Evgenii Stratonikov 2020-07-29 11:18:51 +03:00
parent 82692d8d21
commit f40aba4cd0
9 changed files with 41 additions and 26 deletions

View file

@ -62,7 +62,7 @@ func getLogHandler(logs *[]string) vm.SyscallHandler {
return errors.New("syscall not found")
}
msg := string(v.Estack().Pop().Bytes())
msg := v.Estack().Pop().String()
*logs = append(*logs, msg)
return nil
}

View file

@ -128,7 +128,7 @@ func (s *storagePlugin) syscallHandler(v *vm.VM, id uint32) error {
}
func (s *storagePlugin) Notify(v *vm.VM) error {
name := string(v.Estack().Pop().Bytes())
name := v.Estack().Pop().String()
item := stackitem.NewArray(v.Estack().Pop().Array())
s.events = append(s.events, state.NotificationEvent{
Name: name,

View file

@ -206,7 +206,7 @@ func runtimeEncode(_ *interop.Context, v *vm.VM) error {
// runtimeDecode decodes top stack item from base64 string to byte array.
func runtimeDecode(_ *interop.Context, v *vm.VM) error {
src := string(v.Estack().Pop().Bytes())
src := v.Estack().Pop().String()
result, err := base64.StdEncoding.DecodeString(src)
if err != nil {
return err

View file

@ -7,7 +7,6 @@ import (
"math"
"math/big"
"strings"
"unicode/utf8"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
@ -265,13 +264,10 @@ func runtimeGetTrigger(ic *interop.Context, v *vm.VM) error {
// runtimeNotify should pass stack item to the notify plugin to handle it, but
// in neo-go the only meaningful thing to do here is to log.
func runtimeNotify(ic *interop.Context, v *vm.VM) error {
name := v.Estack().Pop().Bytes()
name := v.Estack().Pop().String()
if len(name) > MaxEventNameLen {
return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
}
if !utf8.Valid(name) {
return errors.New("event name should be UTF8-encoded")
}
elem := v.Estack().Pop()
args := elem.Array()
// But it has to be serializable, otherwise we either have some broken
@ -285,7 +281,7 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error {
}
ne := state.NotificationEvent{
ScriptHash: v.GetCurrentScriptHash(),
Name: string(name),
Name: name,
Item: stackitem.NewArray(args),
}
ic.Notifications = append(ic.Notifications, ne)
@ -294,13 +290,10 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error {
// runtimeLog logs the message passed.
func runtimeLog(ic *interop.Context, v *vm.VM) error {
state := v.Estack().Pop().Bytes()
state := v.Estack().Pop().String()
if len(state) > MaxNotificationSize {
return fmt.Errorf("message length shouldn't exceed %v", MaxNotificationSize)
}
if !utf8.Valid(state) {
return errors.New("log message should be UTF8-encoded")
}
msg := fmt.Sprintf("%q", state)
ic.Log.Info("runtime log",
zap.Stringer("script", v.GetCurrentScriptHash()),
@ -490,11 +483,10 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac
if err != nil {
return errors.New("contract not found")
}
bs, err := method.TryBytes()
name, err := stackitem.ToString(method)
if err != nil {
return err
}
name := string(bs)
if strings.HasPrefix(name, "_") {
return errors.New("invalid method name (starts with '_')")
}
@ -504,7 +496,7 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac
}
curr, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(&cs.Manifest, string(bs)) {
if !curr.Manifest.CanCall(&cs.Manifest, name) {
return errors.New("disallowed method call")
}
}

View file

@ -264,9 +264,9 @@ func TestRuntimeGetNotifications(t *testing.T) {
for i := range arr {
elem := arr[i].Value().([]stackitem.Item)
require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value())
name, err := elem[1].TryBytes()
name, err := stackitem.ToString(elem[1])
require.NoError(t, err)
require.Equal(t, ic.Notifications[i].Name, string(name))
require.Equal(t, ic.Notifications[i].Name, name)
require.Equal(t, ic.Notifications[i].Item, elem[2])
}
})
@ -280,9 +280,9 @@ func TestRuntimeGetNotifications(t *testing.T) {
require.Equal(t, 1, len(arr))
elem := arr[0].Value().([]stackitem.Item)
require.Equal(t, h, elem[0].Value())
name, err := elem[1].TryBytes()
name, err := stackitem.ToString(elem[1])
require.NoError(t, err)
require.Equal(t, ic.Notifications[1].Name, string(name))
require.Equal(t, ic.Notifications[1].Name, name)
require.Equal(t, ic.Notifications[1].Item, elem[2])
})
}

View file

@ -35,7 +35,7 @@ func Deploy(ic *interop.Context, _ *vm.VM) error {
// Call calls specified native contract method.
func Call(ic *interop.Context, v *vm.VM) error {
name := string(v.Estack().Pop().Bytes())
name := v.Estack().Pop().String()
var c interop.Contract
for _, ctr := range ic.Natives {
if ctr.Metadata().Name == name {
@ -50,7 +50,7 @@ func Call(ic *interop.Context, v *vm.VM) error {
if !h.Equals(c.Metadata().Hash) {
return errors.New("it is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used")
}
operation := string(v.Estack().Pop().Bytes())
operation := v.Estack().Pop().String()
args := v.Estack().Pop().Array()
m, ok := c.Metadata().Methods[operation]
if !ok {

View file

@ -67,16 +67,16 @@ func defaultSyscallHandler(v *VM, id uint32) error {
// runtimeLog handles the syscall "System.Runtime.Log" for printing and logging stuff.
func runtimeLog(vm *VM) error {
item := vm.Estack().Pop()
fmt.Printf("NEO-GO-VM (log) > %s\n", item.Value())
msg := vm.Estack().Pop().String()
fmt.Printf("NEO-GO-VM (log) > %s\n", msg)
return nil
}
// runtimeNotify handles the syscall "System.Runtime.Notify" for printing and logging stuff.
func runtimeNotify(vm *VM) error {
name := vm.Estack().Pop().Bytes()
name := vm.Estack().Pop().String()
item := vm.Estack().Pop()
fmt.Printf("NEO-GO-VM (notify) > [%s] %s\n", string(name), item.Value())
fmt.Printf("NEO-GO-VM (notify) > [%s] %s\n", name, item.Value())
return nil
}

View file

@ -96,6 +96,16 @@ func (e *Element) Bytes() []byte {
return bs
}
// String attempts to get string from the element value.
// It is assumed to be use in interops and panics if string is not a valid UTF-8 byte sequence.
func (e *Element) String() string {
s, err := stackitem.ToString(e.value)
if err != nil {
panic(err)
}
return s
}
// Array attempts to get the underlying value of the element as an array of
// other items. Will panic if the item type is different which will be caught
// by the VM.

View file

@ -9,6 +9,7 @@ import (
"fmt"
"math/big"
"reflect"
"unicode/utf8"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
@ -120,6 +121,18 @@ func Make(v interface{}) Item {
}
}
// ToString converts Item to string if it is a valid UTF-8.
func ToString(item Item) (string, error) {
bs, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(bs) {
return "", errors.New("not a valid UTF-8")
}
return string(bs), nil
}
// convertPrimitive converts primitive item to a specified type.
func convertPrimitive(item Item, typ Type) (Item, error) {
if item.Type() == typ {