Merge pull request #942 from nspcc-dev/feature/slots

Implement NEO3 VM slots mechanism
This commit is contained in:
Roman Khimov 2020-05-12 18:04:18 +03:00 committed by GitHub
commit b9b1066435
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 778 additions and 274 deletions

View file

@ -1,11 +1,14 @@
package compiler
import (
"errors"
"fmt"
"go/ast"
"go/constant"
"go/types"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"golang.org/x/tools/go/loader"
)
@ -47,6 +50,24 @@ func typeAndValueForField(fld *types.Var) (types.TypeAndValue, error) {
return types.TypeAndValue{}, nil
}
// newGlobal creates new global variable.
func (c *codegen) newGlobal(name string) {
c.globals[name] = len(c.globals)
}
// traverseGlobals visits and initializes global variables.
func (c *codegen) traverseGlobals(f ast.Node) {
n := countGlobals(f)
if n != 0 {
if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables")
return
}
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
}
c.convertGlobals(f)
}
// countGlobals counts the global variables in the program to add
// them with the stack size of the function.
func countGlobals(f ast.Node) (i int64) {

View file

@ -43,6 +43,8 @@ type codegen struct {
// Current funcScope being converted.
scope *funcScope
globals map[string]int
// A mapping from label's names to their ids.
labels map[labelWithType]uint16
// A list of nested label names together with evaluation stack depth.
@ -82,6 +84,14 @@ type labelWithStackSize struct {
sz int
}
type varType int
const (
varGlobal varType = iota
varLocal
varArgument
)
// newLabel creates a new label to jump to
func (c *codegen) newLabel() (l uint16) {
li := len(c.l)
@ -147,34 +157,6 @@ func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) {
}
}
func (c *codegen) emitLoadLocal(name string) {
pos := c.scope.loadLocal(name)
if pos < 0 {
c.prog.Err = fmt.Errorf("cannot load local variable with position: %d", pos)
return
}
c.emitLoadLocalPos(pos)
}
func (c *codegen) emitLoadLocalPos(pos int) {
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
emit.Int(c.prog.BinWriter, int64(pos))
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
}
func (c *codegen) emitStoreLocal(pos int) {
emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK)
if pos < 0 {
c.prog.Err = fmt.Errorf("invalid position to store local: %d", pos)
return
}
emit.Int(c.prog.BinWriter, int64(pos))
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
}
func (c *codegen) emitLoadField(i int) {
emit.Int(c.prog.BinWriter, int64(i))
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
@ -186,6 +168,81 @@ func (c *codegen) emitStoreStructField(i int) {
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
}
// getVarIndex returns variable type and position in corresponding slot,
// according to current scope.
func (c *codegen) getVarIndex(name string) (varType, int) {
if c.scope != nil {
if i, ok := c.scope.arguments[name]; ok {
return varArgument, i
} else if i, ok := c.scope.locals[name]; ok {
return varLocal, i
}
}
if i, ok := c.globals[name]; ok {
return varGlobal, i
}
return varLocal, c.scope.newVariable(varLocal, name)
}
func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) {
switch t {
case varGlobal:
return opcode.LDSFLD0, opcode.STSFLD0
case varLocal:
return opcode.LDLOC0, opcode.STLOC0
case varArgument:
return opcode.LDARG0, opcode.STARG0
default:
panic("invalid type")
}
}
// emitLoadVar loads specified variable to the evaluation stack.
func (c *codegen) emitLoadVar(name string) {
t, i := c.getVarIndex(name)
base, _ := getBaseOpcode(t)
if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
} else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
}
}
// emitStoreVar stores top value from the evaluation stack in the specified variable.
func (c *codegen) emitStoreVar(name string) {
t, i := c.getVarIndex(name)
_, base := getBaseOpcode(t)
if i < 7 {
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
} else {
emit.Instruction(c.prog.BinWriter, base+7, []byte{byte(i)})
}
}
func (c *codegen) emitDefault(n ast.Expr) {
tv, ok := c.typeInfo.Types[n]
if !ok {
c.prog.Err = errors.New("invalid type")
return
}
if t, ok := tv.Type.(*types.Basic); ok {
info := t.Info()
switch {
case info&types.IsInteger != 0:
emit.Int(c.prog.BinWriter, 0)
case info&types.IsString != 0:
emit.Bytes(c.prog.BinWriter, []byte{})
case info&types.IsBoolean != 0:
emit.Bool(c.prog.BinWriter, false)
default:
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
}
return
}
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
}
// convertGlobals traverses the AST and only converts global declarations.
// If we call this in convertFuncDecl then it will load all global variables
// into the scope of the function.
@ -231,9 +288,17 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
// All globals copied into the scope of the function need to be added
// to the stack size of the function.
emit.Int(c.prog.BinWriter, f.stackSize()+countGlobals(file))
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
emit.Opcode(c.prog.BinWriter, opcode.TOALTSTACK)
sizeLoc := f.countLocals()
if sizeLoc > 255 {
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
sizeArg := f.countArgs()
if sizeArg > 255 {
c.prog.Err = errors.New("maximum of 255 local variables is allowed")
}
if sizeLoc != 0 || sizeArg != 0 {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(sizeLoc), byte(sizeArg)})
}
// We need to handle methods, which in Go, is just syntactic sugar.
// The method receiver will be passed in as first argument.
@ -250,23 +315,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
c.prog.Err = fmt.Errorf("method receives for non-struct types is not yet supported")
return
}
l := c.scope.newLocal(ident.Name)
c.emitStoreLocal(l)
// only create an argument here, it will be stored via INITSLOT
c.scope.newVariable(varArgument, ident.Name)
}
}
// Load the arguments in scope.
for _, arg := range decl.Type.Params.List {
for _, id := range arg.Names {
l := c.scope.newLocal(id.Name)
c.emitStoreLocal(l)
// only create an argument here, it will be stored via INITSLOT
c.scope.newVariable(varArgument, id.Name)
}
}
// Load in all the global variables in to the scope of the function.
// This is not necessary for syscalls.
if !isSyscall(f) {
c.convertGlobals(file)
}
ast.Walk(c, decl.Body)
@ -275,8 +335,6 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
// This can be the case with void and named-return functions.
if !lastStmtIsReturn(decl) {
c.saveSequencePoint(decl.Body)
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
emit.Opcode(c.prog.BinWriter, opcode.DROP)
emit.Opcode(c.prog.BinWriter, opcode.RET)
}
@ -305,25 +363,32 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch t := spec.(type) {
case *ast.ValueSpec:
for _, id := range t.Names {
c.scope.newLocal(id.Name)
if c.scope == nil {
// it is a global declaration
c.newGlobal(id.Name)
} else {
c.scope.newLocal(id.Name)
}
c.registerDebugVariable(id.Name, t.Type)
}
if len(t.Values) != 0 {
for i, val := range t.Values {
ast.Walk(c, val)
l := c.scope.loadLocal(t.Names[i].Name)
c.emitStoreLocal(l)
c.emitStoreVar(t.Names[i].Name)
}
} else if c.isCompoundArrayType(t.Type) {
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY)
l := c.scope.loadLocal(t.Names[0].Name)
c.emitStoreLocal(l)
c.emitStoreVar(t.Names[0].Name)
} else if n, ok := c.isStructType(t.Type); ok {
emit.Int(c.prog.BinWriter, int64(n))
emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT)
l := c.scope.loadLocal(t.Names[0].Name)
c.emitStoreLocal(l)
c.emitStoreVar(t.Names[0].Name)
} else {
for _, id := range t.Names {
c.emitDefault(t.Type)
c.emitStoreVar(id.Name)
}
}
}
}
@ -337,15 +402,17 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.Ident:
switch n.Tok {
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN:
c.emitLoadLocal(t.Name)
c.emitLoadVar(t.Name)
ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS
c.convertToken(n.Tok)
l := c.scope.loadLocal(t.Name)
c.emitStoreLocal(l)
c.emitStoreVar(t.Name)
case token.DEFINE:
if !multiRet {
c.registerDebugVariable(t.Name, n.Rhs[i])
}
if t.Name != "_" {
c.scope.newLocal(t.Name)
}
fallthrough
default:
if i == 0 || !multiRet {
@ -355,8 +422,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if t.Name == "_" {
emit.Opcode(c.prog.BinWriter, opcode.DROP)
} else {
l := c.scope.loadLocal(t.Name)
c.emitStoreLocal(l)
c.emitStoreVar(t.Name)
}
}
@ -366,7 +432,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.Rhs[i])
typ := c.typeInfo.ObjectOf(expr).Type().Underlying()
if strct, ok := typ.(*types.Struct); ok {
c.emitLoadLocal(expr.Name) // load the struct
c.emitLoadVar(expr.Name) // load the struct
i := indexOfStruct(strct, t.Sel.Name) // get the index of the field
c.emitStoreStructField(i) // store the field
}
@ -380,7 +446,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.IndexExpr:
ast.Walk(c, n.Rhs[i])
name := t.X.(*ast.Ident).Name
c.emitLoadLocal(name)
c.emitLoadVar(name)
switch ind := t.Index.(type) {
case *ast.BasicLit:
indexStr := ind.Value
@ -391,7 +457,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
}
c.emitStoreStructField(index)
case *ast.Ident:
c.emitLoadLocal(ind.Name)
c.emitLoadVar(ind.Name)
emit.Opcode(c.prog.BinWriter, opcode.ROT)
emit.Opcode(c.prog.BinWriter, opcode.SETITEM)
default:
@ -404,7 +470,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.SliceExpr:
name := n.X.(*ast.Ident).Name
c.emitLoadLocal(name)
c.emitLoadVar(name)
if n.Low != nil {
ast.Walk(c, n.Low)
@ -442,7 +508,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
for i := len(results.List) - 1; i >= 0; i-- {
names := results.List[i].Names
for j := len(names) - 1; j >= 0; j-- {
c.emitLoadLocal(names[j].Name)
c.emitLoadVar(names[j].Name)
}
}
}
@ -454,8 +520,6 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
}
c.saveSequencePoint(n)
emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK)
emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack.
emit.Opcode(c.prog.BinWriter, opcode.RET)
return nil
@ -557,7 +621,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
} else if tv := c.typeInfo.Types[n]; tv.Value != nil {
c.emitLoadConst(tv)
} else {
c.emitLoadLocal(n.Name)
c.emitLoadVar(n.Name)
}
return nil
@ -723,7 +787,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.convertBuiltin(n)
case name != "":
// Function was not found thus is can be only an invocation of func-typed variable.
c.emitLoadLocal(name)
c.emitLoadVar(name)
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
case isSyscall(f):
c.convertSyscall(n, f.selector.Name, f.name)
@ -738,7 +802,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.Ident:
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
if strct, ok := typ.(*types.Struct); ok {
c.emitLoadLocal(t.Name) // load the struct
c.emitLoadVar(t.Name) // load the struct
i := indexOfStruct(strct, n.Sel.Name)
c.emitLoadField(i) // load the field
}
@ -777,8 +841,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// for i := 0; i < 10; i++ {}
// Where the post stmt is ( i++ )
if ident, ok := n.X.(*ast.Ident); ok {
pos := c.scope.loadLocal(ident.Name)
c.emitStoreLocal(pos)
c.emitStoreVar(ident.Name)
}
return nil
@ -913,9 +976,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if n.Key != nil {
emit.Opcode(c.prog.BinWriter, opcode.DUP)
pos := c.scope.loadLocal(n.Key.(*ast.Ident).Name)
c.emitStoreLocal(pos)
c.emitStoreVar(n.Key.(*ast.Ident).Name)
}
ast.Walk(c, n.Body)
@ -1315,6 +1376,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
}
}
c.traverseGlobals(mainFile)
// convert the entry point first.
c.convertFuncDecl(mainFile, main)
@ -1354,6 +1417,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
l: []int{},
funcs: map[string]*funcScope{},
lambda: map[string]*funcScope{},
globals: map[string]int{},
labels: map[labelWithType]uint16{},
typeInfo: &pkg.Info,

View file

@ -115,6 +115,10 @@ type Event struct {
}
func (c *codegen) saveSequencePoint(n ast.Node) {
if c.scope == nil {
// do not save globals for now
return
}
fset := c.buildInfo.program.Fset
start := fset.Position(n.Pos())
end := fset.Position(n.End())
@ -143,6 +147,10 @@ func (c *codegen) emitDebugInfo() *DebugInfo {
}
func (c *codegen) registerDebugVariable(name string, expr ast.Expr) {
if c.scope == nil {
// do not save globals for now
return
}
typ := c.scTypeFromExpr(expr)
c.scope.variables = append(c.scope.variables, name+","+typ)
}

View file

@ -27,7 +27,8 @@ type funcScope struct {
variables []string
// Local variables
locals map[string]int
locals map[string]int
arguments map[string]int
// voidCalls are basically functions that return their value
// into nothing. The stack has their return value but there
@ -51,6 +52,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
decl: decl,
label: label,
locals: map[string]int{},
arguments: map[string]int{},
voidCalls: map[*ast.CallExpr]bool{},
variables: []string{},
i: -1,
@ -84,7 +86,7 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
return true
}
func (c *funcScope) stackSize() int64 {
func (c *funcScope) countLocals() int {
size := 0
ast.Inspect(c.decl, func(n ast.Node) bool {
switch n := n.(type) {
@ -110,28 +112,38 @@ func (c *funcScope) stackSize() int64 {
}
return true
})
return size
}
numArgs := len(c.decl.Type.Params.List)
// Also take care of struct methods recv: e.g. (t Token).Foo().
func (c *funcScope) countArgs() int {
n := c.decl.Type.Params.NumFields()
if c.decl.Recv != nil {
numArgs += len(c.decl.Recv.List)
n += c.decl.Recv.NumFields()
}
return n
}
func (c *funcScope) stackSize() int64 {
size := c.countLocals()
numArgs := c.countArgs()
return int64(size + numArgs + len(c.voidCalls))
}
// newVariable creates a new local variable or argument in the scope of the function.
func (c *funcScope) newVariable(t varType, name string) int {
c.i++
switch t {
case varLocal:
c.locals[name] = c.i
case varArgument:
c.arguments[name] = c.i
default:
panic("invalid type")
}
return c.i
}
// newLocal creates a new local variable into the scope of the function.
func (c *funcScope) newLocal(name string) int {
c.i++
c.locals[name] = c.i
return c.i
}
// loadLocal loads the position of a local variable inside the scope of the function.
func (c *funcScope) loadLocal(name string) int {
i, ok := c.locals[name]
if !ok {
// should emit a compiler warning.
return c.newLocal(name)
}
return i
return c.newVariable(varLocal, name)
}

View file

@ -0,0 +1,21 @@
package compiler_test
import (
"math/big"
"testing"
)
func TestChangeGlobal(t *testing.T) {
src := `package foo
var a int
func Main() int {
setLocal()
set42()
setLocal()
return a
}
func set42() { a = 42 }
func setLocal() { a := 10; _ = a }`
eval(t, src, big.NewInt(42))
}

View file

@ -0,0 +1,15 @@
package compiler_test
import (
"math/big"
"testing"
)
func TestFuncLiteral(t *testing.T) {
src := `package foo
func Main() int {
inc := func(x int) int { return x + 1 }
return inc(1) + inc(2)
}`
eval(t, src, big.NewInt(5))
}

View file

@ -48,18 +48,18 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{})
}
const testContractHash = "a4bea0d56fad00a972135d54b381516205d78484"
const testContractHash = "33f3421677fab7f620bd70582f468b4a18df1e5d"
var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": {
{
name: "positive",
params: `["c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08"]`,
params: `["3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("c296c0929350d051b9b40cace54db5a3eac4b730a8851e958795d44918f23c08")
expectedTxHash, err := util.Uint256DecodeStringLE("3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691")
require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions))

Binary file not shown.

Binary file not shown.

View file

@ -34,6 +34,9 @@ type Context struct {
// Alt stack pointer.
astack *Stack
local *Slot
arguments *Slot
// Script hash of the prog.
scriptHash util.Uint160
@ -107,8 +110,11 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
}
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT:
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
numtoread = 1
case opcode.INITSLOT:
numtoread = 2
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:

View file

@ -93,16 +93,63 @@ const (
REVERSE4 Opcode = 0x54
REVERSEN Opcode = 0x55
// Old stack opcodes
DUPFROMALTSTACK Opcode = 0x6A
TOALTSTACK Opcode = 0x6B
FROMALTSTACK Opcode = 0x6C
// Slots
INITSSLOT Opcode = 0x56
INITSLOT Opcode = 0x57
LDSFLD0 Opcode = 0x58
LDSFLD1 Opcode = 0x59
LDSFLD2 Opcode = 0x5A
LDSFLD3 Opcode = 0x5B
LDSFLD4 Opcode = 0x5C
LDSFLD5 Opcode = 0x5D
LDSFLD6 Opcode = 0x5E
LDSFLD Opcode = 0x5F
STSFLD0 Opcode = 0x60
STSFLD1 Opcode = 0x61
STSFLD2 Opcode = 0x62
STSFLD3 Opcode = 0x63
STSFLD4 Opcode = 0x64
STSFLD5 Opcode = 0x65
STSFLD6 Opcode = 0x66
STSFLD Opcode = 0x67
LDLOC0 Opcode = 0x68
LDLOC1 Opcode = 0x69
LDLOC2 Opcode = 0x6A
LDLOC3 Opcode = 0x6B
LDLOC4 Opcode = 0x6C
LDLOC5 Opcode = 0x6D
LDLOC6 Opcode = 0x6E
LDLOC Opcode = 0x6F
STLOC0 Opcode = 0x70
STLOC1 Opcode = 0x71
STLOC2 Opcode = 0x72
STLOC3 Opcode = 0x73
STLOC4 Opcode = 0x74
STLOC5 Opcode = 0x75
STLOC6 Opcode = 0x76
STLOC Opcode = 0x77
LDARG0 Opcode = 0x78
LDARG1 Opcode = 0x79
LDARG2 Opcode = 0x7A
LDARG3 Opcode = 0x7B
LDARG4 Opcode = 0x7C
LDARG5 Opcode = 0x7D
LDARG6 Opcode = 0x7E
LDARG Opcode = 0x7F
STARG0 Opcode = 0x80
STARG1 Opcode = 0x81
STARG2 Opcode = 0x82
STARG3 Opcode = 0x83
STARG4 Opcode = 0x84
STARG5 Opcode = 0x85
STARG6 Opcode = 0x86
STARG Opcode = 0x87
// Splice
CAT Opcode = 0x7E
SUBSTR Opcode = 0x7F
LEFT Opcode = 0x80
RIGHT Opcode = 0x81
CAT Opcode = 0x8B
SUBSTR Opcode = 0x8C
LEFT Opcode = 0x8D
RIGHT Opcode = 0x8E
// Bitwise logic
INVERT Opcode = 0x90

View file

@ -82,13 +82,60 @@ func _() {
_ = x[REVERSE3-83]
_ = x[REVERSE4-84]
_ = x[REVERSEN-85]
_ = x[DUPFROMALTSTACK-106]
_ = x[TOALTSTACK-107]
_ = x[FROMALTSTACK-108]
_ = x[CAT-126]
_ = x[SUBSTR-127]
_ = x[LEFT-128]
_ = x[RIGHT-129]
_ = x[INITSSLOT-86]
_ = x[INITSLOT-87]
_ = x[LDSFLD0-88]
_ = x[LDSFLD1-89]
_ = x[LDSFLD2-90]
_ = x[LDSFLD3-91]
_ = x[LDSFLD4-92]
_ = x[LDSFLD5-93]
_ = x[LDSFLD6-94]
_ = x[LDSFLD-95]
_ = x[STSFLD0-96]
_ = x[STSFLD1-97]
_ = x[STSFLD2-98]
_ = x[STSFLD3-99]
_ = x[STSFLD4-100]
_ = x[STSFLD5-101]
_ = x[STSFLD6-102]
_ = x[STSFLD-103]
_ = x[LDLOC0-104]
_ = x[LDLOC1-105]
_ = x[LDLOC2-106]
_ = x[LDLOC3-107]
_ = x[LDLOC4-108]
_ = x[LDLOC5-109]
_ = x[LDLOC6-110]
_ = x[LDLOC-111]
_ = x[STLOC0-112]
_ = x[STLOC1-113]
_ = x[STLOC2-114]
_ = x[STLOC3-115]
_ = x[STLOC4-116]
_ = x[STLOC5-117]
_ = x[STLOC6-118]
_ = x[STLOC-119]
_ = x[LDARG0-120]
_ = x[LDARG1-121]
_ = x[LDARG2-122]
_ = x[LDARG3-123]
_ = x[LDARG4-124]
_ = x[LDARG5-125]
_ = x[LDARG6-126]
_ = x[LDARG-127]
_ = x[STARG0-128]
_ = x[STARG1-129]
_ = x[STARG2-130]
_ = x[STARG3-131]
_ = x[STARG4-132]
_ = x[STARG5-133]
_ = x[STARG6-134]
_ = x[STARG-135]
_ = x[CAT-139]
_ = x[SUBSTR-140]
_ = x[LEFT-141]
_ = x[RIGHT-142]
_ = x[INVERT-144]
_ = x[AND-145]
_ = x[OR-146]
@ -143,7 +190,7 @@ func _() {
_ = x[CONVERT-219]
}
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
var _Opcode_map = map[Opcode]string{
0: _Opcode_name[0:8],
@ -217,65 +264,112 @@ var _Opcode_map = map[Opcode]string{
83: _Opcode_name[390:398],
84: _Opcode_name[398:406],
85: _Opcode_name[406:414],
106: _Opcode_name[414:429],
107: _Opcode_name[429:439],
108: _Opcode_name[439:451],
126: _Opcode_name[451:454],
127: _Opcode_name[454:460],
128: _Opcode_name[460:464],
129: _Opcode_name[464:469],
144: _Opcode_name[469:475],
145: _Opcode_name[475:478],
146: _Opcode_name[478:480],
147: _Opcode_name[480:483],
151: _Opcode_name[483:488],
152: _Opcode_name[488:496],
153: _Opcode_name[496:500],
154: _Opcode_name[500:503],
155: _Opcode_name[503:509],
156: _Opcode_name[509:512],
157: _Opcode_name[512:515],
158: _Opcode_name[515:518],
159: _Opcode_name[518:521],
160: _Opcode_name[521:524],
161: _Opcode_name[524:527],
162: _Opcode_name[527:530],
168: _Opcode_name[530:533],
169: _Opcode_name[533:536],
170: _Opcode_name[536:539],
171: _Opcode_name[539:546],
172: _Opcode_name[546:552],
177: _Opcode_name[552:554],
179: _Opcode_name[554:562],
180: _Opcode_name[562:573],
181: _Opcode_name[573:575],
182: _Opcode_name[575:578],
183: _Opcode_name[578:580],
184: _Opcode_name[580:583],
185: _Opcode_name[583:586],
186: _Opcode_name[586:589],
187: _Opcode_name[589:595],
192: _Opcode_name[595:599],
193: _Opcode_name[599:605],
194: _Opcode_name[605:614],
195: _Opcode_name[614:622],
196: _Opcode_name[622:631],
197: _Opcode_name[631:641],
198: _Opcode_name[641:650],
200: _Opcode_name[650:656],
202: _Opcode_name[656:660],
203: _Opcode_name[660:666],
204: _Opcode_name[666:670],
205: _Opcode_name[670:676],
206: _Opcode_name[676:684],
207: _Opcode_name[684:690],
208: _Opcode_name[690:697],
209: _Opcode_name[697:709],
210: _Opcode_name[709:715],
211: _Opcode_name[715:725],
216: _Opcode_name[725:731],
217: _Opcode_name[731:737],
219: _Opcode_name[737:744],
86: _Opcode_name[414:423],
87: _Opcode_name[423:431],
88: _Opcode_name[431:438],
89: _Opcode_name[438:445],
90: _Opcode_name[445:452],
91: _Opcode_name[452:459],
92: _Opcode_name[459:466],
93: _Opcode_name[466:473],
94: _Opcode_name[473:480],
95: _Opcode_name[480:486],
96: _Opcode_name[486:493],
97: _Opcode_name[493:500],
98: _Opcode_name[500:507],
99: _Opcode_name[507:514],
100: _Opcode_name[514:521],
101: _Opcode_name[521:528],
102: _Opcode_name[528:535],
103: _Opcode_name[535:541],
104: _Opcode_name[541:547],
105: _Opcode_name[547:553],
106: _Opcode_name[553:559],
107: _Opcode_name[559:565],
108: _Opcode_name[565:571],
109: _Opcode_name[571:577],
110: _Opcode_name[577:583],
111: _Opcode_name[583:588],
112: _Opcode_name[588:594],
113: _Opcode_name[594:600],
114: _Opcode_name[600:606],
115: _Opcode_name[606:612],
116: _Opcode_name[612:618],
117: _Opcode_name[618:624],
118: _Opcode_name[624:630],
119: _Opcode_name[630:635],
120: _Opcode_name[635:641],
121: _Opcode_name[641:647],
122: _Opcode_name[647:653],
123: _Opcode_name[653:659],
124: _Opcode_name[659:665],
125: _Opcode_name[665:671],
126: _Opcode_name[671:677],
127: _Opcode_name[677:682],
128: _Opcode_name[682:688],
129: _Opcode_name[688:694],
130: _Opcode_name[694:700],
131: _Opcode_name[700:706],
132: _Opcode_name[706:712],
133: _Opcode_name[712:718],
134: _Opcode_name[718:724],
135: _Opcode_name[724:729],
139: _Opcode_name[729:732],
140: _Opcode_name[732:738],
141: _Opcode_name[738:742],
142: _Opcode_name[742:747],
144: _Opcode_name[747:753],
145: _Opcode_name[753:756],
146: _Opcode_name[756:758],
147: _Opcode_name[758:761],
151: _Opcode_name[761:766],
152: _Opcode_name[766:774],
153: _Opcode_name[774:778],
154: _Opcode_name[778:781],
155: _Opcode_name[781:787],
156: _Opcode_name[787:790],
157: _Opcode_name[790:793],
158: _Opcode_name[793:796],
159: _Opcode_name[796:799],
160: _Opcode_name[799:802],
161: _Opcode_name[802:805],
162: _Opcode_name[805:808],
168: _Opcode_name[808:811],
169: _Opcode_name[811:814],
170: _Opcode_name[814:817],
171: _Opcode_name[817:824],
172: _Opcode_name[824:830],
177: _Opcode_name[830:832],
179: _Opcode_name[832:840],
180: _Opcode_name[840:851],
181: _Opcode_name[851:853],
182: _Opcode_name[853:856],
183: _Opcode_name[856:858],
184: _Opcode_name[858:861],
185: _Opcode_name[861:864],
186: _Opcode_name[864:867],
187: _Opcode_name[867:873],
192: _Opcode_name[873:877],
193: _Opcode_name[877:883],
194: _Opcode_name[883:892],
195: _Opcode_name[892:900],
196: _Opcode_name[900:909],
197: _Opcode_name[909:919],
198: _Opcode_name[919:928],
200: _Opcode_name[928:934],
202: _Opcode_name[934:938],
203: _Opcode_name[938:944],
204: _Opcode_name[944:948],
205: _Opcode_name[948:954],
206: _Opcode_name[954:962],
207: _Opcode_name[962:968],
208: _Opcode_name[968:975],
209: _Opcode_name[975:987],
210: _Opcode_name[987:993],
211: _Opcode_name[993:1003],
216: _Opcode_name[1003:1009],
217: _Opcode_name[1009:1015],
219: _Opcode_name[1015:1022],
}
func (i Opcode) String() string {

62
pkg/vm/ref_counter.go Normal file
View file

@ -0,0 +1,62 @@
package vm
// refCounter represents reference counter for the VM.
type refCounter struct {
items map[StackItem]int
size int
}
func newRefCounter() *refCounter {
return &refCounter{
items: make(map[StackItem]int),
}
}
// Add adds an item to the reference counter.
func (r *refCounter) Add(item StackItem) {
r.size++
switch item.(type) {
case *ArrayItem, *StructItem, *MapItem:
if r.items[item]++; r.items[item] > 1 {
return
}
switch t := item.(type) {
case *ArrayItem, *StructItem:
for _, it := range item.Value().([]StackItem) {
r.Add(it)
}
case *MapItem:
for i := range t.value {
r.Add(t.value[i].Value)
}
}
}
}
// Remove removes item from the reference counter.
func (r *refCounter) Remove(item StackItem) {
r.size--
switch item.(type) {
case *ArrayItem, *StructItem, *MapItem:
if r.items[item] > 1 {
r.items[item]--
return
}
delete(r.items, item)
switch t := item.(type) {
case *ArrayItem, *StructItem:
for _, it := range item.Value().([]StackItem) {
r.Remove(it)
}
case *MapItem:
for i := range t.value {
r.Remove(t.value[i].Value)
}
}
}
}

View file

@ -0,0 +1,32 @@
package vm
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestRefCounter_Add(t *testing.T) {
r := newRefCounter()
require.Equal(t, 0, r.size)
r.Add(NullItem{})
require.Equal(t, 1, r.size)
r.Add(NullItem{})
require.Equal(t, 2, r.size) // count scalar items twice
arr := NewArrayItem([]StackItem{NewByteArrayItem([]byte{1}), NewBoolItem(false)})
r.Add(arr)
require.Equal(t, 5, r.size) // array + 2 elements
r.Add(arr)
require.Equal(t, 6, r.size) // count only array
r.Remove(arr)
require.Equal(t, 5, r.size)
r.Remove(arr)
require.Equal(t, 2, r.size)
}

43
pkg/vm/slot.go Normal file
View file

@ -0,0 +1,43 @@
package vm
// Slot is a fixed-size slice of stack items.
type Slot struct {
storage []StackItem
refs *refCounter
}
// newSlot returns new slot of n items.
func newSlot(n int, refs *refCounter) *Slot {
return &Slot{
storage: make([]StackItem, n),
refs: refs,
}
}
func (v *VM) newSlot(n int) *Slot {
return newSlot(n, v.refs)
}
// Set sets i-th storage slot.
func (s *Slot) Set(i int, item StackItem) {
if s.storage[i] == item {
return
}
old := s.storage[i]
s.storage[i] = item
if old != nil {
s.refs.Remove(old)
}
s.refs.Add(item)
}
// Get returns item contained in i-th slot.
func (s *Slot) Get(i int) StackItem {
if item := s.storage[i]; item != nil {
return item
}
return NullItem{}
}
// Size returns slot size.
func (s *Slot) Size() int { return len(s.storage) }

21
pkg/vm/slot_test.go Normal file
View file

@ -0,0 +1,21 @@
package vm
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
)
func TestSlot_Get(t *testing.T) {
s := newSlot(3, newRefCounter())
require.NotNil(t, s)
require.Equal(t, 3, s.Size())
// NullItem is the default
item := s.Get(2)
require.Equal(t, NullItem{}, item)
s.Set(1, NewBigIntegerItem(big.NewInt(42)))
require.Equal(t, NewBigIntegerItem(big.NewInt(42)), s.Get(1))
}

View file

@ -125,9 +125,7 @@ type Stack struct {
top Element
name string
len int
itemCount map[StackItem]int
size *int
refs *refCounter
}
// NewStack returns a new stack name by the given name.
@ -138,8 +136,7 @@ func NewStack(n string) *Stack {
s.top.next = &s.top
s.top.prev = &s.top
s.len = 0
s.itemCount = make(map[StackItem]int)
s.size = new(int)
s.refs = newRefCounter()
return s
}
@ -171,58 +168,11 @@ func (s *Stack) insert(e, at *Element) *Element {
e.stack = s
s.len++
s.updateSizeAdd(e.value)
s.refs.Add(e.value)
return e
}
func (s *Stack) updateSizeAdd(item StackItem) {
*s.size++
switch item.(type) {
case *ArrayItem, *StructItem, *MapItem:
if s.itemCount[item]++; s.itemCount[item] > 1 {
return
}
switch t := item.(type) {
case *ArrayItem, *StructItem:
for _, it := range item.Value().([]StackItem) {
s.updateSizeAdd(it)
}
case *MapItem:
for i := range t.value {
s.updateSizeAdd(t.value[i].Value)
}
}
}
}
func (s *Stack) updateSizeRemove(item StackItem) {
*s.size--
switch item.(type) {
case *ArrayItem, *StructItem, *MapItem:
if s.itemCount[item] > 1 {
s.itemCount[item]--
return
}
delete(s.itemCount, item)
switch t := item.(type) {
case *ArrayItem, *StructItem:
for _, it := range item.Value().([]StackItem) {
s.updateSizeRemove(it)
}
case *MapItem:
for i := range t.value {
s.updateSizeRemove(t.value[i].Value)
}
}
}
}
// InsertAt inserts the given item (n) deep on the stack.
// Be very careful using it and _always_ check both e and n before invocation
// as it will silently do wrong things otherwise.
@ -300,7 +250,7 @@ func (s *Stack) Remove(e *Element) *Element {
e.stack = nil
s.len--
s.updateSizeRemove(e.value)
s.refs.Remove(e.value)
return e
}

View file

@ -75,11 +75,12 @@ type VM struct {
estack *Stack // execution stack.
astack *Stack // alt stack.
static *Slot
// Hash to verify in CHECKSIG/CHECKMULTISIG.
checkhash []byte
itemCount map[StackItem]int
size int
refs *refCounter
gasConsumed util.Fixed8
gasLimit util.Fixed8
@ -94,9 +95,8 @@ func New() *VM {
getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
state: haltState,
istack: NewStack("invocation"),
itemCount: make(map[StackItem]int),
keys: make(map[string]*keys.PublicKey),
refs: newRefCounter(),
keys: make(map[string]*keys.PublicKey),
}
vm.estack = vm.newItemStack("evaluation")
@ -108,8 +108,7 @@ func New() *VM {
func (v *VM) newItemStack(n string) *Stack {
s := NewStack(n)
s.size = &v.size
s.itemCount = v.itemCount
s.refs = v.refs
return s
}
@ -499,7 +498,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if errRecover := recover(); errRecover != nil {
v.state = faultState
err = newError(ctx.ip, op, errRecover)
} else if v.size > MaxStackSize {
} else if v.refs.size > MaxStackSize {
v.state = faultState
err = newError(ctx.ip, op, "stack is too big")
}
@ -560,15 +559,80 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
}
v.estack.PushVal(result)
// Stack operations.
case opcode.TOALTSTACK:
v.astack.Push(v.estack.Pop())
case opcode.INITSSLOT:
if v.static != nil {
panic("already initialized")
}
if parameter[0] == 0 {
panic("zero argument")
}
v.static = v.newSlot(int(parameter[0]))
case opcode.FROMALTSTACK:
v.estack.Push(v.astack.Pop())
case opcode.INITSLOT:
if ctx.local != nil || ctx.arguments != nil {
panic("already initialized")
}
if parameter[0] == 0 && parameter[1] == 0 {
panic("zero argument")
}
if parameter[0] > 0 {
ctx.local = v.newSlot(int(parameter[0]))
}
if parameter[1] > 0 {
sz := int(parameter[1])
ctx.arguments = v.newSlot(sz)
for i := 0; i < sz; i++ {
ctx.arguments.Set(i, v.estack.Pop().Item())
}
}
case opcode.DUPFROMALTSTACK:
v.estack.Push(v.astack.Dup(0))
case opcode.LDSFLD0, opcode.LDSFLD1, opcode.LDSFLD2, opcode.LDSFLD3, opcode.LDSFLD4, opcode.LDSFLD5, opcode.LDSFLD6:
item := v.static.Get(int(op - opcode.LDSFLD0))
v.estack.PushVal(item)
case opcode.LDSFLD:
item := v.static.Get(int(parameter[0]))
v.estack.PushVal(item)
case opcode.STSFLD0, opcode.STSFLD1, opcode.STSFLD2, opcode.STSFLD3, opcode.STSFLD4, opcode.STSFLD5, opcode.STSFLD6:
item := v.estack.Pop().Item()
v.static.Set(int(op-opcode.STSFLD0), item)
case opcode.STSFLD:
item := v.estack.Pop().Item()
v.static.Set(int(parameter[0]), item)
case opcode.LDLOC0, opcode.LDLOC1, opcode.LDLOC2, opcode.LDLOC3, opcode.LDLOC4, opcode.LDLOC5, opcode.LDLOC6:
item := ctx.local.Get(int(op - opcode.LDLOC0))
v.estack.PushVal(item)
case opcode.LDLOC:
item := ctx.local.Get(int(parameter[0]))
v.estack.PushVal(item)
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)
case opcode.STLOC:
item := v.estack.Pop().Item()
ctx.local.Set(int(parameter[0]), item)
case opcode.LDARG0, opcode.LDARG1, opcode.LDARG2, opcode.LDARG3, opcode.LDARG4, opcode.LDARG5, opcode.LDARG6:
item := ctx.arguments.Get(int(op - opcode.LDARG0))
v.estack.PushVal(item)
case opcode.LDARG:
item := ctx.arguments.Get(int(parameter[0]))
v.estack.PushVal(item)
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)
case opcode.STARG:
item := v.estack.Pop().Item()
ctx.arguments.Set(int(parameter[0]), item)
case opcode.CAT:
b := v.estack.Pop().Bytes()
@ -955,7 +1019,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
panic("APPEND: not of underlying type Array")
}
v.estack.updateSizeAdd(val)
v.refs.Add(val)
case opcode.PACK:
n := int(v.estack.Pop().BigInt().Int64())
@ -1024,17 +1088,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if index < 0 || index >= len(arr) {
panic("SETITEM: invalid index")
}
v.estack.updateSizeRemove(arr[index])
v.refs.Remove(arr[index])
arr[index] = item
v.estack.updateSizeAdd(arr[index])
v.refs.Add(arr[index])
case *MapItem:
if t.Has(key.value) {
v.estack.updateSizeRemove(item)
v.refs.Remove(item)
} else if len(t.value) >= MaxArraySize {
panic("too big map")
}
t.Add(key.value, item)
v.estack.updateSizeAdd(item)
v.refs.Add(item)
default:
panic(fmt.Sprintf("SETITEM: invalid item type %s", t))
@ -1059,7 +1123,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if k < 0 || k >= len(a) {
panic("REMOVE: invalid index")
}
v.estack.updateSizeRemove(a[k])
v.refs.Remove(a[k])
a = append(a[:k], a[k+1:]...)
t.value = a
case *StructItem:
@ -1068,14 +1132,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if k < 0 || k >= len(a) {
panic("REMOVE: invalid index")
}
v.estack.updateSizeRemove(a[k])
v.refs.Remove(a[k])
a = append(a[:k], a[k+1:]...)
t.value = a
case *MapItem:
index := t.Index(key.Item())
// NEO 2.0 doesn't error on missing key.
if index >= 0 {
v.estack.updateSizeRemove(t.value[index].Value)
v.refs.Remove(t.value[index].Value)
t.Drop(index)
}
default:
@ -1087,17 +1151,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
switch t := elem.value.(type) {
case *ArrayItem:
for _, item := range t.value {
v.estack.updateSizeRemove(item)
v.refs.Remove(item)
}
t.value = t.value[:0]
case *StructItem:
for _, item := range t.value {
v.estack.updateSizeRemove(item)
v.refs.Remove(item)
}
t.value = t.value[:0]
case *MapItem:
for i := range t.value {
v.estack.updateSizeRemove(t.value[i].Value)
v.refs.Remove(t.value[i].Value)
}
t.value = t.value[:0]
default:
@ -1137,8 +1201,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case opcode.CALL, opcode.CALLL:
v.checkInvocationStackSize()
newCtx := ctx.Copy()
newCtx.local = nil
newCtx.arguments = nil
newCtx.rvcount = -1
v.istack.PushVal(newCtx)
@ -1152,6 +1217,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
}
newCtx := ctx.Copy()
newCtx.local = nil
newCtx.arguments = nil
newCtx.rvcount = -1
v.istack.PushVal(newCtx)
v.jumpIf(newCtx, ptr.pos, true)

View file

@ -370,10 +370,12 @@ func appendBigStruct(size uint16) []opcode.Opcode {
}
return append(prog,
opcode.INITSSLOT, 1,
opcode.PUSHINT16, opcode.Opcode(size), opcode.Opcode(size>>8), // LE
opcode.PACK, opcode.NEWSTRUCT,
opcode.STSFLD0, opcode.LDSFLD0,
opcode.DUP,
opcode.PUSH0, opcode.NEWARRAY, opcode.TOALTSTACK, opcode.DUPFROMALTSTACK,
opcode.PUSH0, opcode.NEWARRAY,
opcode.SWAP,
opcode.APPEND, opcode.RET)
}
@ -397,17 +399,17 @@ func TestStackLimit(t *testing.T) {
}{
{opcode.PUSH2, 1},
{opcode.NEWARRAY, 3}, // array + 2 items
{opcode.TOALTSTACK, 3},
{opcode.DUPFROMALTSTACK, 4},
{opcode.STSFLD0, 3},
{opcode.LDSFLD0, 4},
{opcode.NEWSTRUCT, 6}, // all items are copied
{opcode.NEWMAP, 7},
{opcode.DUP, 8},
{opcode.PUSH2, 9},
{opcode.DUPFROMALTSTACK, 10},
{opcode.LDSFLD0, 10},
{opcode.SETITEM, 8}, // -3 items and 1 new element in map
{opcode.DUP, 9},
{opcode.PUSH2, 10},
{opcode.DUPFROMALTSTACK, 11},
{opcode.LDSFLD0, 11},
{opcode.SETITEM, 8}, // -3 items and no new elements in map
{opcode.DUP, 9},
{opcode.PUSH2, 10},
@ -415,15 +417,18 @@ func TestStackLimit(t *testing.T) {
{opcode.DROP, 6}, // DROP map with no elements
}
prog := make([]opcode.Opcode, len(expected))
prog := make([]opcode.Opcode, len(expected)+2)
prog[0] = opcode.INITSSLOT
prog[1] = 1
for i := range expected {
prog[i] = expected[i].inst
prog[i+2] = expected[i].inst
}
vm := load(makeProgram(prog...))
require.NoError(t, vm.Step(), "failed to initialize static slot")
for i := range expected {
require.NoError(t, vm.Step())
require.Equal(t, expected[i].size, vm.size)
require.Equal(t, expected[i].size, vm.refs.size, "i: %d", i)
}
}
@ -477,18 +482,18 @@ func TestPushData4BigN(t *testing.T) {
}
func getEnumeratorProg(n int, isIter bool) (prog []byte) {
prog = append(prog, byte(opcode.TOALTSTACK))
prog = []byte{byte(opcode.INITSSLOT), 1, byte(opcode.STSFLD0)}
for i := 0; i < n; i++ {
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Enumerator.Value")...)
if isIter {
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Iterator.Key")...)
}
}
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...)
return
@ -583,7 +588,6 @@ func TestIteratorConcat(t *testing.T) {
func TestIteratorKeys(t *testing.T) {
prog := getSyscallProg("Neo.Iterator.Create")
prog = append(prog, getSyscallProg("Neo.Iterator.Keys")...)
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
prog = append(prog, getEnumeratorProg(2, false)...)
v := load(prog)
@ -604,7 +608,6 @@ func TestIteratorKeys(t *testing.T) {
func TestIteratorValues(t *testing.T) {
prog := getSyscallProg("Neo.Iterator.Create")
prog = append(prog, getSyscallProg("Neo.Iterator.Values")...)
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
prog = append(prog, getEnumeratorProg(2, false)...)
v := load(prog)
@ -716,11 +719,11 @@ func TestSerializeArrayBad(t *testing.T) {
}
func TestSerializeDupInteger(t *testing.T) {
prog := []byte{
byte(opcode.PUSH0), byte(opcode.NEWARRAY),
byte(opcode.DUP), byte(opcode.PUSH2), byte(opcode.DUP), byte(opcode.TOALTSTACK), byte(opcode.APPEND),
byte(opcode.DUP), byte(opcode.FROMALTSTACK), byte(opcode.APPEND),
}
prog := makeProgram(
opcode.PUSH0, opcode.NEWARRAY, opcode.INITSSLOT, 1,
opcode.DUP, opcode.PUSH2, opcode.DUP, opcode.STSFLD0, opcode.APPEND,
opcode.DUP, opcode.LDSFLD0, opcode.APPEND,
)
vm := load(append(prog, getSerializeProg()...))
runVM(t, vm)
@ -801,9 +804,10 @@ func TestSerializeInterop(t *testing.T) {
func callNTimes(n uint16) []byte {
return makeProgram(
opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK,
opcode.INITSSLOT, 1,
opcode.STSFLD0, opcode.LDSFLD0,
opcode.JMPIF, 0x3, opcode.RET,
opcode.FROMALTSTACK, opcode.DEC,
opcode.LDSFLD0, opcode.DEC,
opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK)
}
@ -1171,9 +1175,10 @@ func testNEWARRAYIssue437(t *testing.T, i1, i2 opcode.Opcode, appended bool) {
prog := makeProgram(
opcode.PUSH2, i1,
opcode.DUP, opcode.PUSH3, opcode.APPEND,
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, i2,
opcode.INITSSLOT, 1,
opcode.STSFLD0, opcode.LDSFLD0, i2,
opcode.DUP, opcode.PUSH4, opcode.APPEND,
opcode.FROMALTSTACK, opcode.PUSH5, opcode.APPEND)
opcode.LDSFLD0, opcode.PUSH5, opcode.APPEND)
vm := load(prog)
vm.Run()
@ -1233,8 +1238,8 @@ func TestAPPEND(t *testing.T) {
}
func TestAPPENDCloneStruct(t *testing.T) {
prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.TOALTSTACK,
opcode.DUPFROMALTSTACK, opcode.APPEND, opcode.FROMALTSTACK, opcode.PUSH1, opcode.APPEND)
prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.INITSSLOT, 1, opcode.STSFLD0,
opcode.LDSFLD0, opcode.APPEND, opcode.LDSFLD0, opcode.PUSH1, opcode.APPEND)
arr := []StackItem{&StructItem{[]StackItem{}}}
runWithArgs(t, prog, NewArrayItem(arr), NewArrayItem(nil))
}
@ -1980,7 +1985,7 @@ func testCLEARITEMS(t *testing.T, item StackItem) {
v.estack.PushVal(item)
runVM(t, v)
require.Equal(t, 2, v.estack.Len())
require.EqualValues(t, 2, v.size) // empty collection + it's size
require.EqualValues(t, 2, v.refs.size) // empty collection + it's size
require.EqualValues(t, 0, v.estack.Pop().BigInt().Int64())
}
@ -2361,6 +2366,42 @@ func TestBitAndNumericOpcodes(t *testing.T) {
}
}
func TestSLOTOpcodes(t *testing.T) {
t.Run("Fail", func(t *testing.T) {
t.Run("EmptyStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 0), nil))
t.Run("EmptyLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 0), nil))
t.Run("NotEnoughArguments", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 0, 2), nil, 1))
t.Run("DoubleStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 1, opcode.INITSSLOT, 1), nil))
t.Run("DoubleLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 1, 0, opcode.INITSLOT, 1, 0), nil))
t.Run("DoubleArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 1, opcode.INITSLOT, 0, 1), nil, 1, 2))
t.Run("LoadBigStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDSFLD2), nil))
t.Run("LoadBigLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.LDLOC2), nil, 1, 2))
t.Run("LoadBigArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.LDARG2), nil, 1, 2))
t.Run("StoreBigStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.STSFLD2), nil, 0))
t.Run("StoreBigLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.STLOC2), nil, 0, 1, 2))
t.Run("StoreBigArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 2, opcode.STARG2), nil, 0, 1, 2))
})
t.Run("Default", func(t *testing.T) {
t.Run("DefaultStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDSFLD1), NullItem{}))
t.Run("DefaultLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 0, opcode.LDLOC1), NullItem{}))
t.Run("DefaultArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.LDARG1), 2, 2, 1))
})
t.Run("Set/Get", func(t *testing.T) {
t.Run("FailCrossLoads", func(t *testing.T) {
t.Run("Static/Local", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDLOC1), nil))
t.Run("Static/Argument", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDARG1), nil))
t.Run("Local/Argument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.LDLOC1), nil))
t.Run("Argument/Local", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 0, opcode.LDARG1), nil))
})
t.Run("Static", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 8, opcode.STSFLD, 7, opcode.LDSFLD, 7), 42, 42))
t.Run("Local", getTestFuncForVM(makeProgram(opcode.INITSLOT, 8, 0, opcode.STLOC, 7, opcode.LDLOC, 7), 42, 42))
t.Run("Argument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.STARG, 1, opcode.LDARG, 1), 42, 42, 1, 2))
})
}
func makeProgram(opcodes ...opcode.Opcode) []byte {
prog := make([]byte, len(opcodes)+1) // RET
for i := 0; i < len(opcodes); i++ {