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:
Roman Khimov 2019-12-18 11:07:41 +03:00 committed by GitHub
commit 1a26548be8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 206 additions and 7 deletions

View file

@ -159,6 +159,11 @@ func (c *Context) Value() interface{} {
return c return c
} }
// Dup implements StackItem interface.
func (c *Context) Dup() StackItem {
return c
}
func (c *Context) atBreakPoint() bool { func (c *Context) atBreakPoint() bool {
for _, n := range c.breakPoints { for _, n := range c.breakPoints {
if n == c.ip { if n == c.ip {

View file

@ -346,7 +346,7 @@ func (s *Stack) Dup(n int) *Element {
} }
return &Element{ return &Element{
value: e.value, value: e.value.Dup(),
} }
} }

View file

@ -15,6 +15,8 @@ import (
type StackItem interface { type StackItem interface {
fmt.Stringer fmt.Stringer
Value() interface{} Value() interface{}
// Dup duplicates current StackItem.
Dup() StackItem
} }
func makeStackItem(v interface{}) StackItem { func makeStackItem(v interface{}) StackItem {
@ -107,6 +109,12 @@ func (i *StructItem) String() string {
return "Struct" 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. // Clone returns a Struct with all Struct fields copied by value.
// Array fields are still copied by reference. // Array fields are still copied by reference.
func (i *StructItem) Clone() *StructItem { func (i *StructItem) Clone() *StructItem {
@ -148,6 +156,12 @@ func (i *BigIntegerItem) String() string {
return "BigInteger" 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. // MarshalJSON implements the json.Marshaler interface.
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value) return json.Marshal(i.value)
@ -179,6 +193,11 @@ func (i *BoolItem) String() string {
return "Bool" return "Bool"
} }
// Dup implements StackItem interface.
func (i *BoolItem) Dup() StackItem {
return &BoolItem{i.value}
}
// ByteArrayItem represents a byte array on the stack. // ByteArrayItem represents a byte array on the stack.
type ByteArrayItem struct { type ByteArrayItem struct {
value []byte value []byte
@ -205,6 +224,13 @@ func (i *ByteArrayItem) String() string {
return "ByteArray" 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. // ArrayItem represents a new ArrayItem object.
type ArrayItem struct { type ArrayItem struct {
value []StackItem value []StackItem
@ -231,6 +257,12 @@ func (i *ArrayItem) String() string {
return "Array" return "Array"
} }
// Dup implements StackItem interface.
func (i *ArrayItem) Dup() StackItem {
// reference type
return i
}
// MapItem represents Map object. // MapItem represents Map object.
type MapItem struct { type MapItem struct {
value map[interface{}]StackItem value map[interface{}]StackItem
@ -259,6 +291,12 @@ func (i *MapItem) Has(key StackItem) (ok bool) {
return return
} }
// Dup implements StackItem interface.
func (i *MapItem) Dup() StackItem {
// reference type
return i
}
// Add adds key-value pair to the map. // Add adds key-value pair to the map.
func (i *MapItem) Add(key, value StackItem) { func (i *MapItem) Add(key, value StackItem) {
i.value[toMapKey(key)] = value i.value[toMapKey(key)] = value
@ -300,6 +338,12 @@ func (i *InteropItem) String() string {
return "InteropItem" return "InteropItem"
} }
// Dup implements StackItem interface.
func (i *InteropItem) Dup() StackItem {
// reference type
return i
}
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
func (i *InteropItem) MarshalJSON() ([]byte, error) { func (i *InteropItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value) return json.Marshal(i.value)

View file

@ -622,7 +622,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
} }
case opcode.OVER: case opcode.OVER:
a := v.estack.Peek(1) a := v.estack.Dup(1)
if a == nil { if a == nil {
panic("no second element found") 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 { if n < 0 {
panic("negative stack item returned") panic("negative stack item returned")
} }
a := v.estack.Peek(n) a := v.estack.Dup(n)
if a == nil { if a == nil {
panic("no nth element found") panic("no nth element found")
} }
@ -677,8 +677,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
// Bit operations. // Bit operations.
case opcode.INVERT: case opcode.INVERT:
// inplace // inplace
a := v.estack.Peek(0).BigInt() e := v.estack.Peek(0)
a.Not(a) i := e.BigInt()
e.value = makeStackItem(i.Not(i))
case opcode.AND: case opcode.AND:
b := v.estack.Pop().BigInt() 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) { if index < 0 || index >= len(arr) {
panic("PICKITEM: invalid index") panic("PICKITEM: invalid index")
} }
item := arr[index] item := arr[index].Dup()
v.estack.PushVal(item) v.estack.PushVal(item)
case *MapItem: case *MapItem:
if !t.Has(key.value) { if !t.Has(key.value) {
panic("invalid key") panic("invalid key")
} }
k := toMapKey(key.value) k := toMapKey(key.value)
v.estack.Push(&Element{value: t.value[k]}) v.estack.Push(&Element{value: t.value[k].Dup()})
default: default:
arr := obj.Bytes() arr := obj.Bytes()
if index < 0 || index >= len(arr) { if index < 0 || index >= len(arr) {

View file

@ -1092,6 +1092,30 @@ func TestPICKITEMByteArray(t *testing.T) {
assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) 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) { func TestPICKITEMMap(t *testing.T) {
prog := makeProgram(opcode.PICKITEM) prog := makeProgram(opcode.PICKITEM)
vm := load(prog) vm := load(prog)
@ -1497,6 +1521,21 @@ func TestPICKgood(t *testing.T) {
assert.Equal(t, int64(result), vm.estack.Pop().BigInt().Int64()) 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) { func TestROTBad(t *testing.T) {
prog := makeProgram(opcode.ROT) prog := makeProgram(opcode.ROT)
vm := load(prog) vm := load(prog)
@ -1663,6 +1702,22 @@ func TestOVERgood(t *testing.T) {
assert.Equal(t, 3, vm.estack.Len()) 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) { func TestNIPBadNoItem(t *testing.T) {
prog := makeProgram(opcode.NIP) prog := makeProgram(opcode.NIP)
vm := load(prog) vm := load(prog)
@ -1759,6 +1814,20 @@ func TestINVERTgood3(t *testing.T) {
assert.Equal(t, int64(-0x6A), vm.estack.Peek(0).BigInt().Int64()) 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) { func TestCATBadNoArgs(t *testing.T) {
prog := makeProgram(opcode.CAT) prog := makeProgram(opcode.CAT)
vm := load(prog) vm := load(prog)
@ -2505,6 +2574,86 @@ func TestXSWAPBad2(t *testing.T) {
checkVMFailed(t, vm) 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 { func makeProgram(opcodes ...opcode.Opcode) []byte {
prog := make([]byte, len(opcodes)+1) // RET prog := make([]byte, len(opcodes)+1) // RET
for i := 0; i < len(opcodes); i++ { for i := 0; i < len(opcodes); i++ {