Merge pull request #561 from nspcc-dev/fix-duping-and-and-tests
Fix duping and add tests. C# node actually implements DUP in the same way we did, but it does create a new element when accessing some particular value (like BigInt() or Bytes()) so in the end this DUP implementation doesn't lead to any visible side-effects. In our case I think it's more appropriate to fix the DUP (and its variants) itself avoiding useless allocations in the VM.
This commit is contained in:
commit
1a26548be8
5 changed files with 206 additions and 7 deletions
|
@ -159,6 +159,11 @@ func (c *Context) Value() interface{} {
|
|||
return c
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (c *Context) Dup() StackItem {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) atBreakPoint() bool {
|
||||
for _, n := range c.breakPoints {
|
||||
if n == c.ip {
|
||||
|
|
|
@ -346,7 +346,7 @@ func (s *Stack) Dup(n int) *Element {
|
|||
}
|
||||
|
||||
return &Element{
|
||||
value: e.value,
|
||||
value: e.value.Dup(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
type StackItem interface {
|
||||
fmt.Stringer
|
||||
Value() interface{}
|
||||
// Dup duplicates current StackItem.
|
||||
Dup() StackItem
|
||||
}
|
||||
|
||||
func makeStackItem(v interface{}) StackItem {
|
||||
|
@ -107,6 +109,12 @@ func (i *StructItem) String() string {
|
|||
return "Struct"
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *StructItem) Dup() StackItem {
|
||||
// it's a reference type, so no copying here.
|
||||
return i
|
||||
}
|
||||
|
||||
// Clone returns a Struct with all Struct fields copied by value.
|
||||
// Array fields are still copied by reference.
|
||||
func (i *StructItem) Clone() *StructItem {
|
||||
|
@ -148,6 +156,12 @@ func (i *BigIntegerItem) String() string {
|
|||
return "BigInteger"
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *BigIntegerItem) Dup() StackItem {
|
||||
n := new(big.Int)
|
||||
return &BigIntegerItem{n.Set(i.value)}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.value)
|
||||
|
@ -179,6 +193,11 @@ func (i *BoolItem) String() string {
|
|||
return "Bool"
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *BoolItem) Dup() StackItem {
|
||||
return &BoolItem{i.value}
|
||||
}
|
||||
|
||||
// ByteArrayItem represents a byte array on the stack.
|
||||
type ByteArrayItem struct {
|
||||
value []byte
|
||||
|
@ -205,6 +224,13 @@ func (i *ByteArrayItem) String() string {
|
|||
return "ByteArray"
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *ByteArrayItem) Dup() StackItem {
|
||||
a := make([]byte, len(i.value))
|
||||
copy(a, i.value)
|
||||
return &ByteArrayItem{a}
|
||||
}
|
||||
|
||||
// ArrayItem represents a new ArrayItem object.
|
||||
type ArrayItem struct {
|
||||
value []StackItem
|
||||
|
@ -231,6 +257,12 @@ func (i *ArrayItem) String() string {
|
|||
return "Array"
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *ArrayItem) Dup() StackItem {
|
||||
// reference type
|
||||
return i
|
||||
}
|
||||
|
||||
// MapItem represents Map object.
|
||||
type MapItem struct {
|
||||
value map[interface{}]StackItem
|
||||
|
@ -259,6 +291,12 @@ func (i *MapItem) Has(key StackItem) (ok bool) {
|
|||
return
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *MapItem) Dup() StackItem {
|
||||
// reference type
|
||||
return i
|
||||
}
|
||||
|
||||
// Add adds key-value pair to the map.
|
||||
func (i *MapItem) Add(key, value StackItem) {
|
||||
i.value[toMapKey(key)] = value
|
||||
|
@ -300,6 +338,12 @@ func (i *InteropItem) String() string {
|
|||
return "InteropItem"
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *InteropItem) Dup() StackItem {
|
||||
// reference type
|
||||
return i
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *InteropItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.value)
|
||||
|
|
13
pkg/vm/vm.go
13
pkg/vm/vm.go
|
@ -622,7 +622,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
}
|
||||
|
||||
case opcode.OVER:
|
||||
a := v.estack.Peek(1)
|
||||
a := v.estack.Dup(1)
|
||||
if a == nil {
|
||||
panic("no second element found")
|
||||
}
|
||||
|
@ -633,7 +633,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
if n < 0 {
|
||||
panic("negative stack item returned")
|
||||
}
|
||||
a := v.estack.Peek(n)
|
||||
a := v.estack.Dup(n)
|
||||
if a == nil {
|
||||
panic("no nth element found")
|
||||
}
|
||||
|
@ -677,8 +677,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
// Bit operations.
|
||||
case opcode.INVERT:
|
||||
// inplace
|
||||
a := v.estack.Peek(0).BigInt()
|
||||
a.Not(a)
|
||||
e := v.estack.Peek(0)
|
||||
i := e.BigInt()
|
||||
e.value = makeStackItem(i.Not(i))
|
||||
|
||||
case opcode.AND:
|
||||
b := v.estack.Pop().BigInt()
|
||||
|
@ -957,14 +958,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
if index < 0 || index >= len(arr) {
|
||||
panic("PICKITEM: invalid index")
|
||||
}
|
||||
item := arr[index]
|
||||
item := arr[index].Dup()
|
||||
v.estack.PushVal(item)
|
||||
case *MapItem:
|
||||
if !t.Has(key.value) {
|
||||
panic("invalid key")
|
||||
}
|
||||
k := toMapKey(key.value)
|
||||
v.estack.Push(&Element{value: t.value[k]})
|
||||
v.estack.Push(&Element{value: t.value[k].Dup()})
|
||||
default:
|
||||
arr := obj.Bytes()
|
||||
if index < 0 || index >= len(arr) {
|
||||
|
|
|
@ -1092,6 +1092,30 @@ func TestPICKITEMByteArray(t *testing.T) {
|
|||
assert.Equal(t, makeStackItem(2), vm.estack.Pop().value)
|
||||
}
|
||||
|
||||
func TestPICKITEMDupArray(t *testing.T) {
|
||||
prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.PICKITEM, opcode.ABS)
|
||||
vm := load(prog)
|
||||
vm.estack.PushVal([]StackItem{makeStackItem(-1)})
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 2, vm.estack.Len())
|
||||
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
|
||||
items := vm.estack.Pop().Value().([]StackItem)
|
||||
assert.Equal(t, big.NewInt(-1), items[0].Value())
|
||||
}
|
||||
|
||||
func TestPICKITEMDupMap(t *testing.T) {
|
||||
prog := makeProgram(opcode.DUP, opcode.PUSHBYTES1, 42, opcode.PICKITEM, opcode.ABS)
|
||||
vm := load(prog)
|
||||
m := NewMapItem()
|
||||
m.Add(makeStackItem([]byte{42}), makeStackItem(-1))
|
||||
vm.estack.Push(&Element{value: m})
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 2, vm.estack.Len())
|
||||
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
|
||||
items := vm.estack.Pop().Value().(map[interface{}]StackItem)
|
||||
assert.Equal(t, big.NewInt(-1), items[string([]byte{42})].Value())
|
||||
}
|
||||
|
||||
func TestPICKITEMMap(t *testing.T) {
|
||||
prog := makeProgram(opcode.PICKITEM)
|
||||
vm := load(prog)
|
||||
|
@ -1497,6 +1521,21 @@ func TestPICKgood(t *testing.T) {
|
|||
assert.Equal(t, int64(result), vm.estack.Pop().BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestPICKDup(t *testing.T) {
|
||||
prog := makeProgram(opcode.PUSHM1, opcode.PUSH0,
|
||||
opcode.PUSH1,
|
||||
opcode.PUSH2,
|
||||
opcode.PICK,
|
||||
opcode.ABS)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 4, vm.estack.Len())
|
||||
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
|
||||
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
|
||||
assert.Equal(t, int64(0), vm.estack.Pop().BigInt().Int64())
|
||||
assert.Equal(t, int64(-1), vm.estack.Pop().BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestROTBad(t *testing.T) {
|
||||
prog := makeProgram(opcode.ROT)
|
||||
vm := load(prog)
|
||||
|
@ -1663,6 +1702,22 @@ func TestOVERgood(t *testing.T) {
|
|||
assert.Equal(t, 3, vm.estack.Len())
|
||||
}
|
||||
|
||||
func TestOVERDup(t *testing.T) {
|
||||
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
|
||||
opcode.PUSH1,
|
||||
opcode.OVER,
|
||||
opcode.PUSH1,
|
||||
opcode.LEFT,
|
||||
opcode.PUSHBYTES1, 2,
|
||||
opcode.CAT)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 3, vm.estack.Len())
|
||||
assert.Equal(t, []byte{0x01, 0x02}, vm.estack.Pop().Bytes())
|
||||
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
|
||||
assert.Equal(t, []byte{0x01, 0x00}, vm.estack.Pop().Bytes())
|
||||
}
|
||||
|
||||
func TestNIPBadNoItem(t *testing.T) {
|
||||
prog := makeProgram(opcode.NIP)
|
||||
vm := load(prog)
|
||||
|
@ -1759,6 +1814,20 @@ func TestINVERTgood3(t *testing.T) {
|
|||
assert.Equal(t, int64(-0x6A), vm.estack.Peek(0).BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestINVERTWithConversion1(t *testing.T) {
|
||||
prog := makeProgram(opcode.PUSHDATA2, 0, 0, opcode.INVERT)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, int64(-1), vm.estack.Peek(0).BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestINVERTWithConversion2(t *testing.T) {
|
||||
prog := makeProgram(opcode.PUSH0, opcode.PUSH1, opcode.NUMEQUAL, opcode.INVERT)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, int64(-1), vm.estack.Peek(0).BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestCATBadNoArgs(t *testing.T) {
|
||||
prog := makeProgram(opcode.CAT)
|
||||
vm := load(prog)
|
||||
|
@ -2505,6 +2574,86 @@ func TestXSWAPBad2(t *testing.T) {
|
|||
checkVMFailed(t, vm)
|
||||
}
|
||||
|
||||
func TestDupInt(t *testing.T) {
|
||||
prog := makeProgram(opcode.DUP, opcode.ABS)
|
||||
vm := load(prog)
|
||||
vm.estack.PushVal(-1)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 2, vm.estack.Len())
|
||||
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
|
||||
assert.Equal(t, int64(-1), vm.estack.Pop().BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestDupByteArray(t *testing.T) {
|
||||
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
|
||||
opcode.DUP,
|
||||
opcode.PUSH1,
|
||||
opcode.LEFT,
|
||||
opcode.PUSHBYTES1, 2,
|
||||
opcode.CAT)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 2, vm.estack.Len())
|
||||
assert.Equal(t, []byte{0x01, 0x02}, vm.estack.Pop().Bytes())
|
||||
assert.Equal(t, []byte{0x01, 0x00}, vm.estack.Pop().Bytes())
|
||||
}
|
||||
|
||||
func TestDupBool(t *testing.T) {
|
||||
prog := makeProgram(opcode.PUSH0, opcode.NOT,
|
||||
opcode.DUP,
|
||||
opcode.PUSH1, opcode.NOT,
|
||||
opcode.BOOLAND)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 2, vm.estack.Len())
|
||||
assert.Equal(t, false, vm.estack.Pop().Bool())
|
||||
assert.Equal(t, true, vm.estack.Pop().Bool())
|
||||
}
|
||||
|
||||
func TestSHA1(t *testing.T) {
|
||||
// 0x0100 hashes to 0e356ba505631fbf715758bed27d503f8b260e3a
|
||||
res := "0e356ba505631fbf715758bed27d503f8b260e3a"
|
||||
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
|
||||
opcode.SHA1)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 1, vm.estack.Len())
|
||||
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes()))
|
||||
}
|
||||
|
||||
func TestSHA256(t *testing.T) {
|
||||
// 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254
|
||||
res := "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254"
|
||||
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
|
||||
opcode.SHA256)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 1, vm.estack.Len())
|
||||
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes()))
|
||||
}
|
||||
|
||||
func TestHASH160(t *testing.T) {
|
||||
// 0x0100 hashes to fbc22d517f38e7612798ece8e5957cf6c41d8caf
|
||||
res := "fbc22d517f38e7612798ece8e5957cf6c41d8caf"
|
||||
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
|
||||
opcode.HASH160)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 1, vm.estack.Len())
|
||||
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes()))
|
||||
}
|
||||
|
||||
func TestHASH256(t *testing.T) {
|
||||
// 0x0100 hashes to 677b2d718464ee0121475600b929c0b4155667486577d1320b18c2dc7d4b4f99
|
||||
res := "677b2d718464ee0121475600b929c0b4155667486577d1320b18c2dc7d4b4f99"
|
||||
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
|
||||
opcode.HASH256)
|
||||
vm := load(prog)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 1, vm.estack.Len())
|
||||
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes()))
|
||||
}
|
||||
|
||||
func makeProgram(opcodes ...opcode.Opcode) []byte {
|
||||
prog := make([]byte, len(opcodes)+1) // RET
|
||||
for i := 0; i < len(opcodes); i++ {
|
||||
|
|
Loading…
Reference in a new issue