From f40aba4cd0b547d45f545ee29f97729987d9f7d7 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 29 Jul 2020 11:18:51 +0300 Subject: [PATCH] vm: convert items to UTF-8 strings Add `stackitem.ToString` for seamless string conversion. --- pkg/compiler/panic_test.go | 2 +- pkg/compiler/vm_test.go | 2 +- pkg/core/interop_neo.go | 2 +- pkg/core/interop_system.go | 18 +++++------------- pkg/core/interop_system_test.go | 8 ++++---- pkg/core/native/interop.go | 4 ++-- pkg/vm/interop.go | 8 ++++---- pkg/vm/stack.go | 10 ++++++++++ pkg/vm/stackitem/item.go | 13 +++++++++++++ 9 files changed, 41 insertions(+), 26 deletions(-) diff --git a/pkg/compiler/panic_test.go b/pkg/compiler/panic_test.go index 8504f72d4..646bca04e 100644 --- a/pkg/compiler/panic_test.go +++ b/pkg/compiler/panic_test.go @@ -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 } diff --git a/pkg/compiler/vm_test.go b/pkg/compiler/vm_test.go index 237ee8c12..0e7eb1aaa 100644 --- a/pkg/compiler/vm_test.go +++ b/pkg/compiler/vm_test.go @@ -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, diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 43e46dd6e..2ef20c90b 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -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 diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index b7aaa116a..3c08590f7 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -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") } } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index aab9e5159..fd1ef11d1 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -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]) }) } diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 63e3b1da4..5e1d23515 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -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 { diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index bf04a2e8e..1d68c46aa 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -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 } diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index 44e9d6d94..a66058d3f 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -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. diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index 0d6c86851..4cd138e93 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -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 {