Merge pull request #3677 from ixje/expose-slots

vm: make slots public
This commit is contained in:
Roman Khimov 2024-11-15 13:16:48 +03:00 committed by GitHub
commit 895a0ae624
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 70 additions and 33 deletions

View file

@ -68,4 +68,14 @@ event-based waiter fallbacks to the old way of block monitoring with
`block_added` notifications subscription. `block_added` notifications subscription.
Removal of stale RPC server compatibility code from `waiter.EventWaiter` is Removal of stale RPC server compatibility code from `waiter.EventWaiter` is
scheduled for Jun-Jul 2024 (~0.107.0 release). 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.

View file

@ -695,11 +695,11 @@ func handleSlots(c *cli.Context) error {
var rawSlot string var rawSlot string
switch c.Command.Name { switch c.Command.Name {
case "sslot": case "sslot":
rawSlot = vmCtx.DumpStaticSlot() rawSlot = dumpSlot(vmCtx.StaticsSlot())
case "lslot": case "lslot":
rawSlot = vmCtx.DumpLocalSlot() rawSlot = dumpSlot(vmCtx.LocalsSlot())
case "aslot": case "aslot":
rawSlot = vmCtx.DumpArgumentsSlot() rawSlot = dumpSlot(vmCtx.ArgumentsSlot())
default: default:
return errors.New("unknown slot") return errors.New("unknown slot")
} }
@ -707,6 +707,14 @@ func handleSlots(c *cli.Context) error {
return nil 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 // prepareVM retrieves --historic flag from context (if set) and resets app state
// (to the specified historic height if given). // (to the specified historic height if given).
func prepareVM(c *cli.Context, tx *transaction.Transaction) error { func prepareVM(c *cli.Context, tx *transaction.Transaction) error {

View file

@ -30,7 +30,7 @@ type scriptContext struct {
// Evaluation stack pointer. // Evaluation stack pointer.
estack *Stack estack *Stack
static slot static Slot
// Script hash of the prog. // Script hash of the prog.
scriptHash util.Uint160 scriptHash util.Uint160
@ -65,8 +65,8 @@ type Context struct {
sc *scriptContext sc *scriptContext
local slot local Slot
arguments slot arguments Slot
// Exception context stack. // Exception context stack.
tryStack Stack tryStack Stack
@ -278,22 +278,26 @@ func (c *Context) IsDeployed() bool {
} }
// DumpStaticSlot returns json formatted representation of the given slot. // 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 { func (c *Context) DumpStaticSlot() string {
return dumpSlot(&c.sc.static) return dumpSlot(&c.sc.static)
} }
// DumpLocalSlot returns json formatted representation of the given slot. // 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 { func (c *Context) DumpLocalSlot() string {
return dumpSlot(&c.local) return dumpSlot(&c.local)
} }
// DumpArgumentsSlot returns json formatted representation of the given slot. // 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 { func (c *Context) DumpArgumentsSlot() string {
return dumpSlot(&c.arguments) return dumpSlot(&c.arguments)
} }
// dumpSlot returns json formatted representation of the given slot. // 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 { if s == nil || *s == nil {
return "[]" return "[]"
} }
@ -354,3 +358,18 @@ func DynamicOnUnload(v *VM, ctx *Context, commit bool) error {
func (c *Context) BreakPoints() []int { func (c *Context) BreakPoints() []int {
return slices.Clone(c.sc.breakPoints) 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
}

View file

@ -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() }) 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 { if actual == nil && len(expected) == 0 {
return return
} }

View file

@ -6,11 +6,11 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
// slot is a fixed-size slice of stack items. // Slot is a fixed-size slice of stack items.
type slot []stackitem.Item type Slot []stackitem.Item
// init sets static slot size to n. It is intended to be used only by INITSSLOT. // 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 { if *s != nil {
panic("already initialized") panic("already initialized")
} }
@ -18,8 +18,8 @@ func (s *slot) init(n int, rc *refCounter) {
*rc += refCounter(n) // Virtual "Null" elements. *rc += refCounter(n) // Virtual "Null" elements.
} }
// Set sets i-th storage slot. // set sets i-th storage slot.
func (s slot) Set(i int, item stackitem.Item, refs *refCounter) { func (s Slot) set(i int, item stackitem.Item, refs *refCounter) {
if s[i] == item { if s[i] == item {
return 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. // 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 { if item := s[i]; item != nil {
return item return item
} }
return stackitem.Null{} return stackitem.Null{}
} }
// ClearRefs removes all slot variables from the reference counter. // clearRefs removes all slot variables from the reference counter.
func (s slot) ClearRefs(refs *refCounter) { func (s Slot) clearRefs(refs *refCounter) {
for _, item := range s { for _, item := range s {
refs.Remove(item) refs.Remove(item)
} }
} }
// Size returns the slot size. // Size returns the slot size.
func (s slot) Size() int { func (s Slot) Size() int {
if s == nil { if s == nil {
panic("not initialized") return 0
} }
return len(s) return len(s)
} }
// MarshalJSON implements the JSON marshalling interface. // MarshalJSON implements the JSON marshalling interface.
func (s slot) MarshalJSON() ([]byte, error) { func (s Slot) MarshalJSON() ([]byte, error) {
arr := make([]json.RawMessage, len(s)) arr := make([]json.RawMessage, len(s))
for i := range s { for i := range s {
data, err := stackitem.ToJSONWithTypes(s[i]) data, err := stackitem.ToJSONWithTypes(s[i])

View file

@ -10,8 +10,8 @@ import (
func TestSlot_Get(t *testing.T) { func TestSlot_Get(t *testing.T) {
rc := newRefCounter() rc := newRefCounter()
var s slot var s Slot
require.Panics(t, func() { s.Size() }) require.Equal(t, 0, s.Size())
s.init(3, rc) s.init(3, rc)
require.Equal(t, 3, s.Size()) require.Equal(t, 3, s.Size())
@ -21,7 +21,7 @@ func TestSlot_Get(t *testing.T) {
item := s.Get(2) item := s.Get(2)
require.Equal(t, stackitem.Null{}, item) 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, stackitem.NewBigInteger(big.NewInt(42)), s.Get(1))
require.Equal(t, 3, int(*rc)) require.Equal(t, 3, int(*rc))
} }

View file

@ -705,7 +705,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
sz := int(parameter[1]) sz := int(parameter[1])
ctx.arguments.init(sz, &v.refs) ctx.arguments.init(sz, &v.refs)
for i := range sz { for i := range sz {
ctx.arguments.Set(i, v.estack.Pop().Item(), &v.refs) ctx.arguments.set(i, v.estack.Pop().Item(), &v.refs)
} }
} }
@ -719,11 +719,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: case opcode.STSFLD0, opcode.STSFLD1, opcode.STSFLD2, opcode.STSFLD3, opcode.STSFLD4, opcode.STSFLD5, opcode.STSFLD6:
item := v.estack.Pop().Item() 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: case opcode.STSFLD:
item := v.estack.Pop().Item() 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: case opcode.LDLOC0, opcode.LDLOC1, opcode.LDLOC2, opcode.LDLOC3, opcode.LDLOC4, opcode.LDLOC5, opcode.LDLOC6:
item := ctx.local.Get(int(op - opcode.LDLOC0)) item := ctx.local.Get(int(op - opcode.LDLOC0))
@ -735,11 +735,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: case opcode.STLOC0, opcode.STLOC1, opcode.STLOC2, opcode.STLOC3, opcode.STLOC4, opcode.STLOC5, opcode.STLOC6:
item := v.estack.Pop().Item() 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: case opcode.STLOC:
item := v.estack.Pop().Item() 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: case opcode.LDARG0, opcode.LDARG1, opcode.LDARG2, opcode.LDARG3, opcode.LDARG4, opcode.LDARG5, opcode.LDARG6:
item := ctx.arguments.Get(int(op - opcode.LDARG0)) item := ctx.arguments.Get(int(op - opcode.LDARG0))
@ -751,11 +751,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: case opcode.STARG0, opcode.STARG1, opcode.STARG2, opcode.STARG3, opcode.STARG4, opcode.STARG5, opcode.STARG6:
item := v.estack.Pop().Item() 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: case opcode.STARG:
item := v.estack.Pop().Item() 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: case opcode.NEWBUFFER:
n := toInt(v.estack.Pop().BigInt()) n := toInt(v.estack.Pop().BigInt())
@ -1700,15 +1700,15 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
func (v *VM) unloadContext(ctx *Context) { func (v *VM) unloadContext(ctx *Context) {
if ctx.local != nil { if ctx.local != nil {
ctx.local.ClearRefs(&v.refs) ctx.local.clearRefs(&v.refs)
} }
if ctx.arguments != nil { if ctx.arguments != nil {
ctx.arguments.ClearRefs(&v.refs) ctx.arguments.clearRefs(&v.refs)
} }
currCtx := v.Context() currCtx := v.Context()
if currCtx == nil || ctx.sc != currCtx.sc { if currCtx == nil || ctx.sc != currCtx.sc {
if ctx.sc.static != nil { if ctx.sc.static != nil {
ctx.sc.static.ClearRefs(&v.refs) ctx.sc.static.clearRefs(&v.refs)
} }
if ctx.sc.onUnload != nil { if ctx.sc.onUnload != nil {
err := ctx.sc.onUnload(v, ctx, v.uncaughtException == nil) err := ctx.sc.onUnload(v, ctx, v.uncaughtException == nil)