From 7ec0c1155c3cc7f75bbf6749c83cdf758dcb84ad Mon Sep 17 00:00:00 2001 From: ixje Date: Fri, 15 Nov 2024 11:05:42 +0100 Subject: [PATCH] vm: expose Context slots, hide Set/ClearRefs on Slot, deprecate Dump*Slot methods Signed-off-by: ixje --- ROADMAP.md | 12 +++++++++++- cli/vm/cli.go | 14 +++++++++++--- pkg/vm/context.go | 27 +++++++++++++++++++++++---- pkg/vm/json_test.go | 2 +- pkg/vm/slot.go | 22 +++++++++++----------- pkg/vm/slot_test.go | 6 +++--- pkg/vm/vm.go | 20 ++++++++++---------- 7 files changed, 70 insertions(+), 33 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index baae16fba..90b3dbb29 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -68,4 +68,14 @@ event-based waiter fallbacks to the old way of block monitoring with `block_added` notifications subscription. Removal of stale RPC server compatibility code from `waiter.EventWaiter` is -scheduled for Jun-Jul 2024 (~0.107.0 release). \ No newline at end of file +scheduled for Jun-Jul 2024 (~0.107.0 release). + +## Dump*Slot() methods of `vm.Context` + +The following new methods have been exposed to give access to VM context slot contents +with greater flexibility: +- `ArgumentsSlot` +- `LocalsSlot` +- `StaticsSlot`. + +Removal of the `Dump*Slot()` methods are scheduled for the 0.108.0 release. \ No newline at end of file diff --git a/cli/vm/cli.go b/cli/vm/cli.go index 21125ed67..ab6475408 100644 --- a/cli/vm/cli.go +++ b/cli/vm/cli.go @@ -648,11 +648,11 @@ func handleSlots(c *cli.Context) error { var rawSlot string switch c.Command.Name { case "sslot": - rawSlot = vmCtx.DumpStaticSlot() + rawSlot = dumpSlot(vmCtx.StaticsSlot()) case "lslot": - rawSlot = vmCtx.DumpLocalSlot() + rawSlot = dumpSlot(vmCtx.LocalsSlot()) case "aslot": - rawSlot = vmCtx.DumpArgumentsSlot() + rawSlot = dumpSlot(vmCtx.ArgumentsSlot()) default: return errors.New("unknown slot") } @@ -660,6 +660,14 @@ func handleSlots(c *cli.Context) error { return nil } +func dumpSlot(s *vm.Slot) string { + if s == nil { + return "[]" + } + b, _ := json.MarshalIndent(s, "", " ") + return string(b) +} + // prepareVM retrieves --historic flag from context (if set) and resets app state // (to the specified historic height if given). func prepareVM(c *cli.Context, tx *transaction.Transaction) error { diff --git a/pkg/vm/context.go b/pkg/vm/context.go index ca21d6d30..c3a4a027a 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -30,7 +30,7 @@ type scriptContext struct { // Evaluation stack pointer. estack *Stack - static slot + static Slot // Script hash of the prog. scriptHash util.Uint160 @@ -65,8 +65,8 @@ type Context struct { sc *scriptContext - local slot - arguments slot + local Slot + arguments Slot // Exception context stack. tryStack Stack @@ -278,22 +278,26 @@ func (c *Context) IsDeployed() bool { } // DumpStaticSlot returns json formatted representation of the given slot. +// Deprecated: to be removed in next version. Use [Context.StaticsSlot] instead. func (c *Context) DumpStaticSlot() string { return dumpSlot(&c.sc.static) } // DumpLocalSlot returns json formatted representation of the given slot. +// Deprecated: to be removed in next version. Use [Context.LocalsSlot] instead. func (c *Context) DumpLocalSlot() string { return dumpSlot(&c.local) } // DumpArgumentsSlot returns json formatted representation of the given slot. +// Deprecated: to be removed in next version. Use [Context.ArgumentsSlot] instead. func (c *Context) DumpArgumentsSlot() string { return dumpSlot(&c.arguments) } // dumpSlot returns json formatted representation of the given slot. -func dumpSlot(s *slot) string { +// Deprecated: to be removed in next version. +func dumpSlot(s *Slot) string { if s == nil || *s == nil { return "[]" } @@ -354,3 +358,18 @@ func DynamicOnUnload(v *VM, ctx *Context, commit bool) error { func (c *Context) BreakPoints() []int { return slices.Clone(c.sc.breakPoints) } + +// ArgumentsSlot returns the arguments slot of Context. +func (c *Context) ArgumentsSlot() *Slot { + return &c.arguments +} + +// LocalsSlot returns the local slot of Context. +func (c *Context) LocalsSlot() *Slot { + return &c.local +} + +// StaticsSlot returns the static slot of Context. +func (c *Context) StaticsSlot() *Slot { + return &c.sc.static +} diff --git a/pkg/vm/json_test.go b/pkg/vm/json_test.go index 1f80451f7..56fd42fe8 100644 --- a/pkg/vm/json_test.go +++ b/pkg/vm/json_test.go @@ -240,7 +240,7 @@ func compareStacks(t *testing.T, expected []vmUTStackItem, actual *Stack) { compareItemArrays(t, expected, actual.Len(), func(i int) stackitem.Item { return actual.Peek(i).Item() }) } -func compareSlots(t *testing.T, expected []vmUTStackItem, actual slot) { +func compareSlots(t *testing.T, expected []vmUTStackItem, actual Slot) { if actual == nil && len(expected) == 0 { return } diff --git a/pkg/vm/slot.go b/pkg/vm/slot.go index 661e796d8..38603211e 100644 --- a/pkg/vm/slot.go +++ b/pkg/vm/slot.go @@ -6,11 +6,11 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) -// slot is a fixed-size slice of stack items. -type slot []stackitem.Item +// Slot is a fixed-size slice of stack items. +type Slot []stackitem.Item // init sets static slot size to n. It is intended to be used only by INITSSLOT. -func (s *slot) init(n int, rc *refCounter) { +func (s *Slot) init(n int, rc *refCounter) { if *s != nil { panic("already initialized") } @@ -18,8 +18,8 @@ func (s *slot) init(n int, rc *refCounter) { *rc += refCounter(n) // Virtual "Null" elements. } -// Set sets i-th storage slot. -func (s slot) Set(i int, item stackitem.Item, refs *refCounter) { +// set sets i-th storage slot. +func (s Slot) set(i int, item stackitem.Item, refs *refCounter) { if s[i] == item { return } @@ -29,30 +29,30 @@ func (s slot) Set(i int, item stackitem.Item, refs *refCounter) { } // Get returns the item contained in the i-th slot. -func (s slot) Get(i int) stackitem.Item { +func (s Slot) Get(i int) stackitem.Item { if item := s[i]; item != nil { return item } return stackitem.Null{} } -// ClearRefs removes all slot variables from the reference counter. -func (s slot) ClearRefs(refs *refCounter) { +// clearRefs removes all slot variables from the reference counter. +func (s Slot) clearRefs(refs *refCounter) { for _, item := range s { refs.Remove(item) } } // Size returns the slot size. -func (s slot) Size() int { +func (s Slot) Size() int { if s == nil { - panic("not initialized") + return 0 } return len(s) } // MarshalJSON implements the JSON marshalling interface. -func (s slot) MarshalJSON() ([]byte, error) { +func (s Slot) MarshalJSON() ([]byte, error) { arr := make([]json.RawMessage, len(s)) for i := range s { data, err := stackitem.ToJSONWithTypes(s[i]) diff --git a/pkg/vm/slot_test.go b/pkg/vm/slot_test.go index ec10a7ffc..44582c1ef 100644 --- a/pkg/vm/slot_test.go +++ b/pkg/vm/slot_test.go @@ -10,8 +10,8 @@ import ( func TestSlot_Get(t *testing.T) { rc := newRefCounter() - var s slot - require.Panics(t, func() { s.Size() }) + var s Slot + require.Equal(t, 0, s.Size()) s.init(3, rc) require.Equal(t, 3, s.Size()) @@ -21,7 +21,7 @@ func TestSlot_Get(t *testing.T) { item := s.Get(2) require.Equal(t, stackitem.Null{}, item) - s.Set(1, stackitem.NewBigInteger(big.NewInt(42)), rc) + s.set(1, stackitem.NewBigInteger(big.NewInt(42)), rc) require.Equal(t, stackitem.NewBigInteger(big.NewInt(42)), s.Get(1)) require.Equal(t, 3, int(*rc)) } diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 1608ebb36..7b2908e85 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -697,7 +697,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro sz := int(parameter[1]) ctx.arguments.init(sz, &v.refs) for i := range sz { - ctx.arguments.Set(i, v.estack.Pop().Item(), &v.refs) + ctx.arguments.set(i, v.estack.Pop().Item(), &v.refs) } } @@ -711,11 +711,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.STSFLD0, opcode.STSFLD1, opcode.STSFLD2, opcode.STSFLD3, opcode.STSFLD4, opcode.STSFLD5, opcode.STSFLD6: item := v.estack.Pop().Item() - ctx.sc.static.Set(int(op-opcode.STSFLD0), item, &v.refs) + ctx.sc.static.set(int(op-opcode.STSFLD0), item, &v.refs) case opcode.STSFLD: item := v.estack.Pop().Item() - ctx.sc.static.Set(int(parameter[0]), item, &v.refs) + ctx.sc.static.set(int(parameter[0]), item, &v.refs) case opcode.LDLOC0, opcode.LDLOC1, opcode.LDLOC2, opcode.LDLOC3, opcode.LDLOC4, opcode.LDLOC5, opcode.LDLOC6: item := ctx.local.Get(int(op - opcode.LDLOC0)) @@ -727,11 +727,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.STLOC0, opcode.STLOC1, opcode.STLOC2, opcode.STLOC3, opcode.STLOC4, opcode.STLOC5, opcode.STLOC6: item := v.estack.Pop().Item() - ctx.local.Set(int(op-opcode.STLOC0), item, &v.refs) + ctx.local.set(int(op-opcode.STLOC0), item, &v.refs) case opcode.STLOC: item := v.estack.Pop().Item() - ctx.local.Set(int(parameter[0]), item, &v.refs) + ctx.local.set(int(parameter[0]), item, &v.refs) case opcode.LDARG0, opcode.LDARG1, opcode.LDARG2, opcode.LDARG3, opcode.LDARG4, opcode.LDARG5, opcode.LDARG6: item := ctx.arguments.Get(int(op - opcode.LDARG0)) @@ -743,11 +743,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.STARG0, opcode.STARG1, opcode.STARG2, opcode.STARG3, opcode.STARG4, opcode.STARG5, opcode.STARG6: item := v.estack.Pop().Item() - ctx.arguments.Set(int(op-opcode.STARG0), item, &v.refs) + ctx.arguments.set(int(op-opcode.STARG0), item, &v.refs) case opcode.STARG: item := v.estack.Pop().Item() - ctx.arguments.Set(int(parameter[0]), item, &v.refs) + ctx.arguments.set(int(parameter[0]), item, &v.refs) case opcode.NEWBUFFER: n := toInt(v.estack.Pop().BigInt()) @@ -1692,15 +1692,15 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro func (v *VM) unloadContext(ctx *Context) { if ctx.local != nil { - ctx.local.ClearRefs(&v.refs) + ctx.local.clearRefs(&v.refs) } if ctx.arguments != nil { - ctx.arguments.ClearRefs(&v.refs) + ctx.arguments.clearRefs(&v.refs) } currCtx := v.Context() if currCtx == nil || ctx.sc != currCtx.sc { if ctx.sc.static != nil { - ctx.sc.static.ClearRefs(&v.refs) + ctx.sc.static.clearRefs(&v.refs) } if ctx.sc.onUnload != nil { err := ctx.sc.onUnload(v, ctx, v.uncaughtException == nil)