From df18da0ac9f584c3d0dea04998e444e73dad733d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 15:06:23 +0300 Subject: [PATCH 1/9] vm: implement NEWMAP opcode --- pkg/vm/stack_item.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ pkg/vm/vm.go | 5 ++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index 6dff26040..26b758e60 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -193,3 +193,50 @@ func (i *ArrayItem) MarshalJSON() ([]byte, error) { func (i *ArrayItem) String() string { return "Array" } + +// MapItem represents Map object. +type MapItem struct { + value map[interface{}]StackItem +} + +// NewMapItem returns new MapItem object. +func NewMapItem() *MapItem { + return &MapItem{ + value: make(map[interface{}]StackItem), + } +} + +// Value implements StackItem interface. +func (i *MapItem) Value() interface{} { + return i.value +} + +// MarshalJSON implements the json.Marshaler interface. +func (i *MapItem) String() string { + return "Map" +} + +// Has checks if map has specified key. +func (i *MapItem) Has(key StackItem) (ok bool) { + _, ok = i.value[toMapKey(key)] + return +} + +// Add adds key-value pair to the map. +func (i *MapItem) Add(key, value StackItem) { + i.value[toMapKey(key)] = value +} + +// toMapKey converts StackItem so that it can be used as a map key. +func toMapKey(key StackItem) interface{} { + switch t := key.(type) { + case *BoolItem: + return t.value + case *BigIntegerItem: + return t.value.Int64() + case *ByteArrayItem: + return string(t.value) + default: + panic("wrong key type") + } +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 23760fbb9..6292e07de 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -912,9 +912,12 @@ func (v *VM) execute(ctx *Context, op Instruction) { } v.estack.PushVal(sigok) - case NEWMAP, HASKEY, KEYS, VALUES: + case HASKEY, KEYS, VALUES: panic("unimplemented") + case NEWMAP: + v.estack.Push(&Element{value: NewMapItem()}) + // Cryptographic operations. case SHA1: b := v.estack.Pop().Bytes() From 0fb4bb05cfe540683774a646881a7fe71f772b6a Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 15:25:57 +0300 Subject: [PATCH 2/9] vm: implement HASKEY opcode --- pkg/vm/vm.go | 33 +++++++++++++++- pkg/vm/vm_test.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 6292e07de..b713fd712 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -912,12 +912,33 @@ func (v *VM) execute(ctx *Context, op Instruction) { } v.estack.PushVal(sigok) - case HASKEY, KEYS, VALUES: + case KEYS, VALUES: panic("unimplemented") case NEWMAP: v.estack.Push(&Element{value: NewMapItem()}) + case HASKEY: + key := v.estack.Pop() + validateMapKey(key) + + c := v.estack.Pop() + if c == nil { + panic("no value found") + } + switch t := c.value.(type) { + case *ArrayItem, *StructItem: + index := key.BigInt().Int64() + if index < 0 { + panic("negative index") + } + v.estack.PushVal(index < int64(len(c.Array()))) + case *MapItem: + v.estack.PushVal(t.Has(key.value)) + default: + panic("wrong collection type") + } + // Cryptographic operations. case SHA1: b := v.estack.Pop().Bytes() @@ -961,6 +982,16 @@ func makeArrayOfFalses(n int) []StackItem { return items } +func validateMapKey(key *Element) { + if key == nil { + panic("no key found") + } + switch key.value.(type) { + case *ArrayItem, *StructItem, *MapItem: + panic("key can't be a collection") + } +} + func init() { log.SetPrefix("NEO-GO-VM > ") log.SetFlags(0) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 11562c339..79b2694ac 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -673,6 +673,101 @@ func TestSIZEBool(t *testing.T) { assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) } +func TestHASKEYArrayTrue(t *testing.T) { + prog := makeProgram(PUSH5, NEWARRAY, PUSH4, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(true), vm.estack.Pop().value) +} + +func TestHASKEYArrayFalse(t *testing.T) { + prog := makeProgram(PUSH5, NEWARRAY, PUSH5, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) +} + +func TestHASKEYStructTrue(t *testing.T) { + prog := makeProgram(PUSH5, NEWSTRUCT, PUSH4, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(true), vm.estack.Pop().value) +} + +func TestHASKEYStructFalse(t *testing.T) { + prog := makeProgram(PUSH5, NEWSTRUCT, PUSH5, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) +} + +func TestHASKEYMapTrue(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(6)) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(5) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(true), vm.estack.Pop().value) +} + +func TestHASKEYMapFalse(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(6)) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(6) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) +} + +func TestHASKEYNoArguments(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestHASKEY1Argument(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestHASKEYWrongKeyType(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.estack.PushVal([]StackItem{}) + vm.estack.PushVal([]StackItem{}) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestHASKEYWrongCollectionType(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + func TestSIGNNoArgument(t *testing.T) { prog := makeProgram(SIGN) vm := load(prog) From 2da64267b028527d6abd27101e2a28dc7fad067e Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 15:41:39 +0300 Subject: [PATCH 3/9] vm: implement KEYS opcode --- pkg/vm/stack_item.go | 8 ++++++++ pkg/vm/vm.go | 19 ++++++++++++++++++- pkg/vm/vm_test.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index 26b758e60..9389178f8 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -19,10 +19,18 @@ func makeStackItem(v interface{}) StackItem { return &BigIntegerItem{ value: big.NewInt(int64(val)), } + case int64: + return &BigIntegerItem{ + value: big.NewInt(val), + } case []byte: return &ByteArrayItem{ value: val, } + case string: + return &ByteArrayItem{ + value: []byte(val), + } case bool: return &BoolItem{ value: val, diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index b713fd712..69df69fb4 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -912,12 +912,29 @@ func (v *VM) execute(ctx *Context, op Instruction) { } v.estack.PushVal(sigok) - case KEYS, VALUES: + case VALUES: panic("unimplemented") case NEWMAP: v.estack.Push(&Element{value: NewMapItem()}) + case KEYS: + item := v.estack.Pop() + if item == nil { + panic("no argument") + } + + m, ok := item.value.(*MapItem) + if !ok { + panic("not a Map") + } + + arr := make([]StackItem, 0, len(m.value)) + for k := range m.value { + arr = append(arr, makeStackItem(k)) + } + v.estack.PushVal(arr) + case HASKEY: key := v.estack.Pop() validateMapKey(key) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 79b2694ac..815a2f9b6 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -673,6 +673,40 @@ func TestSIZEBool(t *testing.T) { assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) } +func TestKEYSMap(t *testing.T) { + prog := makeProgram(KEYS) + vm := load(prog) + + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(6)) + m.Add(makeStackItem([]byte{0, 1}), makeStackItem(6)) + vm.estack.Push(&Element{value: m}) + + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + + top := vm.estack.Pop().value.(*ArrayItem) + assert.Equal(t, 2, len(top.value)) + assert.Contains(t, top.value, makeStackItem(5)) + assert.Contains(t, top.value, makeStackItem([]byte{0, 1})) +} + +func TestKEYSNoArgument(t *testing.T) { + prog := makeProgram(KEYS) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestKEYSWrongType(t *testing.T) { + prog := makeProgram(KEYS) + vm := load(prog) + vm.estack.PushVal([]StackItem{}) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + func TestHASKEYArrayTrue(t *testing.T) { prog := makeProgram(PUSH5, NEWARRAY, PUSH4, HASKEY) vm := load(prog) From d7bb50c60901dc751f234305420fd7f1cf172a4b Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 15:53:55 +0300 Subject: [PATCH 4/9] vm: implement VALUES opcode --- pkg/vm/vm.go | 37 ++++++++++++++++++++++++++++++++++--- pkg/vm/vm_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 69df69fb4..46e60a5f7 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -912,9 +912,6 @@ func (v *VM) execute(ctx *Context, op Instruction) { } v.estack.PushVal(sigok) - case VALUES: - panic("unimplemented") - case NEWMAP: v.estack.Push(&Element{value: NewMapItem()}) @@ -935,6 +932,31 @@ func (v *VM) execute(ctx *Context, op Instruction) { } v.estack.PushVal(arr) + case VALUES: + item := v.estack.Pop() + if item == nil { + panic("no argument") + } + + var arr []StackItem + switch t := item.value.(type) { + case *ArrayItem, *StructItem: + src := t.Value().([]StackItem) + arr = make([]StackItem, len(src)) + for i := range src { + arr[i] = cloneIfStruct(src[i]) + } + case *MapItem: + arr = make([]StackItem, 0, len(t.value)) + for k := range t.value { + arr = append(arr, cloneIfStruct(t.value[k])) + } + default: + panic("not a Map, Array or Struct") + } + + v.estack.PushVal(arr) + case HASKEY: key := v.estack.Pop() validateMapKey(key) @@ -991,6 +1013,15 @@ func (v *VM) execute(ctx *Context, op Instruction) { } } +func cloneIfStruct(item StackItem) StackItem { + switch it := item.(type) { + case *StructItem: + return it.Clone() + default: + return it + } +} + func makeArrayOfFalses(n int) []StackItem { items := make([]StackItem, n) for i := range items { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 815a2f9b6..2c0842bae 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -707,6 +707,50 @@ func TestKEYSWrongType(t *testing.T) { assert.Equal(t, true, vm.HasFailed()) } +func TestVALUESMap(t *testing.T) { + prog := makeProgram(VALUES) + vm := load(prog) + + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem([]byte{2, 3})) + m.Add(makeStackItem([]byte{0, 1}), makeStackItem([]StackItem{})) + vm.estack.Push(&Element{value: m}) + + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + + top := vm.estack.Pop().value.(*ArrayItem) + assert.Equal(t, 2, len(top.value)) + assert.Contains(t, top.value, makeStackItem([]byte{2, 3})) + assert.Contains(t, top.value, makeStackItem([]StackItem{})) +} + +func TestVALUESArray(t *testing.T) { + prog := makeProgram(VALUES) + vm := load(prog) + vm.estack.PushVal([]StackItem{makeStackItem(4)}) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, &ArrayItem{[]StackItem{makeStackItem(4)}}, vm.estack.Pop().value) +} + +func TestVALUESNoArgument(t *testing.T) { + prog := makeProgram(VALUES) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestVALUESWrongType(t *testing.T) { + prog := makeProgram(VALUES) + vm := load(prog) + vm.estack.PushVal(5) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + func TestHASKEYArrayTrue(t *testing.T) { prog := makeProgram(PUSH5, NEWARRAY, PUSH4, HASKEY) vm := load(prog) From 8b6bddca3c3f7ffc8f4005922bbd96022ec5bc5a Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 15:58:31 +0300 Subject: [PATCH 5/9] vm: support Map in PICKITEM and SETITEM --- pkg/vm/vm.go | 31 ++++++++++++++++++++----------- pkg/vm/vm_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 46e60a5f7..584b7c14e 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -705,11 +705,11 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.estack.PushVal(l) case PICKITEM: - var ( - key = v.estack.Pop() - obj = v.estack.Pop() - index = int(key.BigInt().Int64()) - ) + key := v.estack.Pop() + validateMapKey(key) + + obj := v.estack.Pop() + index := int(key.BigInt().Int64()) switch t := obj.value.(type) { // Struct and Array items have their underlying value as []StackItem. @@ -720,6 +720,12 @@ func (v *VM) execute(ctx *Context, op Instruction) { } item := arr[index] 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]}) default: arr := obj.Bytes() if index < 0 || index >= len(arr) { @@ -730,21 +736,24 @@ func (v *VM) execute(ctx *Context, op Instruction) { } case SETITEM: - var ( - item = v.estack.Pop().value - key = v.estack.Pop() - obj = v.estack.Pop() - index = int(key.BigInt().Int64()) - ) + item := v.estack.Pop().value + key := v.estack.Pop() + validateMapKey(key) + + obj := v.estack.Pop() switch t := obj.value.(type) { // Struct and Array items have their underlying value as []StackItem. case *ArrayItem, *StructItem: arr := t.Value().([]StackItem) + index := int(key.BigInt().Int64()) if index < 0 || index >= len(arr) { panic("SETITEM: invalid index") } arr[index] = item + case *MapItem: + t.Add(key.value, item) + default: panic(fmt.Sprintf("SETITEM: invalid item type %s", t)) } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 2c0842bae..ad5aa475d 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -646,6 +646,39 @@ func TestPICKITEMByteArray(t *testing.T) { assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) } +func TestPICKITEMMap(t *testing.T) { + prog := makeProgram(PICKITEM) + vm := load(prog) + + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(3)) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(makeStackItem(5)) + + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(3), vm.estack.Pop().value) +} + +func TestSETITEMMap(t *testing.T) { + prog := makeProgram(SETITEM, PICKITEM) + vm := load(prog) + + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(3)) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(5) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(5) + vm.estack.PushVal([]byte{0, 1}) + + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem([]byte{0, 1}), vm.estack.Pop().value) +} + func TestSIZENoArgument(t *testing.T) { prog := makeProgram(SIZE) vm := load(prog) From c0be2b2a99f959ba187dbcba3cd08638e7770004 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 16:16:24 +0300 Subject: [PATCH 6/9] vm: support Map in REMOVE --- pkg/vm/vm.go | 18 +++++++++++++----- pkg/vm/vm_test.go | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 584b7c14e..658b26bc9 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -766,23 +766,31 @@ func (v *VM) execute(ctx *Context, op Instruction) { } } case REMOVE: - key := int(v.estack.Pop().BigInt().Int64()) + key := v.estack.Pop() + validateMapKey(key) + elem := v.estack.Pop() switch t := elem.value.(type) { case *ArrayItem: a := t.value - if key < 0 || key >= len(a) { + k := int(key.BigInt().Int64()) + if k < 0 || k >= len(a) { panic("REMOVE: invalid index") } - a = append(a[:key], a[key+1:]...) + a = append(a[:k], a[k+1:]...) t.value = a case *StructItem: a := t.value - if key < 0 || key >= len(a) { + k := int(key.BigInt().Int64()) + if k < 0 || k >= len(a) { panic("REMOVE: invalid index") } - a = append(a[:key], a[key+1:]...) + a = append(a[:k], a[k+1:]...) t.value = a + case *MapItem: + m := t.value + k := toMapKey(key.value) + delete(m, k) default: panic("REMOVE: invalid type") } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index ad5aa475d..f46ea67ff 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -1696,6 +1696,23 @@ func TestREMOVEGood(t *testing.T) { assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) } +func TestREMOVEMap(t *testing.T) { + prog := makeProgram(REMOVE, PUSH5, HASKEY) + vm := load(prog) + + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(3)) + m.Add(makeStackItem([]byte{0, 1}), makeStackItem([]byte{2, 3})) + vm.estack.Push(&Element{value: m}) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(makeStackItem(5)) + + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) +} + func TestCHECKSIGNoArgs(t *testing.T) { prog := makeProgram(CHECKSIG) vm := load(prog) From c66f493017793828e7f1d11c3e1bbac250e28120 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 16:45:41 +0300 Subject: [PATCH 7/9] vm: compare Map by reference in EQUAL --- pkg/vm/vm.go | 5 +++++ pkg/vm/vm_test.go | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 658b26bc9..17ac189b1 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -478,6 +478,11 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.estack.PushVal(ta == tb) break } + } else if ma, ok := a.value.(*MapItem); ok { + if mb, ok := b.value.(*MapItem); ok { + v.estack.PushVal(ma == mb) + break + } } v.estack.PushVal(reflect.DeepEqual(a, b)) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index f46ea67ff..4b121d6e3 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -446,6 +446,27 @@ func TestEQUALArrayFalse(t *testing.T) { assert.Equal(t, &BoolItem{false}, vm.estack.Pop().value) } +func TestEQUALMapTrue(t *testing.T) { + prog := makeProgram(DUP, EQUAL) + vm := load(prog) + vm.estack.Push(&Element{value: NewMapItem()}) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, &BoolItem{true}, vm.estack.Pop().value) +} + +func TestEQUALMapFalse(t *testing.T) { + prog := makeProgram(EQUAL) + vm := load(prog) + vm.estack.Push(&Element{value: NewMapItem()}) + vm.estack.Push(&Element{value: NewMapItem()}) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, &BoolItem{false}, vm.estack.Pop().value) +} + func TestNumEqual(t *testing.T) { prog := makeProgram(NUMEQUAL) vm := load(prog) From 13d7770c6843cfaf42e3fc156043f875a6f8a3da Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 24 Sep 2019 16:50:37 +0300 Subject: [PATCH 8/9] vm: support Map in ARRAYSIZE Also make logic the same as in reference implementation and add tests. --- pkg/vm/vm.go | 4 ++-- pkg/vm/vm_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 17ac189b1..38e8b23b9 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -807,10 +807,10 @@ func (v *VM) execute(ctx *Context, op Instruction) { switch t := elem.value.Value().(type) { case []StackItem: v.estack.PushVal(len(t)) - case []uint8: + case map[interface{}]StackItem: v.estack.PushVal(len(t)) default: - panic("ARRAYSIZE: item not of type []StackItem") + v.estack.PushVal(len(elem.Bytes())) } case SIZE: diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 4b121d6e3..428a24735 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -727,6 +727,34 @@ func TestSIZEBool(t *testing.T) { assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) } +func TestARRAYSIZEArray(t *testing.T) { + prog := makeProgram(ARRAYSIZE) + vm := load(prog) + vm.estack.PushVal([]StackItem{ + makeStackItem(1), + makeStackItem([]byte{}), + }) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) +} + +func TestARRAYSIZEMap(t *testing.T) { + prog := makeProgram(ARRAYSIZE) + vm := load(prog) + + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(6)) + m.Add(makeStackItem([]byte{0, 1}), makeStackItem(6)) + vm.estack.Push(&Element{value: m}) + + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) +} + func TestKEYSMap(t *testing.T) { prog := makeProgram(KEYS) vm := load(prog) From c7c42917742c65fd914c53436f88229203a4cc04 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 25 Sep 2019 12:18:37 +0300 Subject: [PATCH 9/9] vm: simplify APPEND implementation a bit --- pkg/vm/vm.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 38e8b23b9..a3ad75215 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -670,10 +670,7 @@ func (v *VM) execute(ctx *Context, op Instruction) { itemElem := v.estack.Pop() arrElem := v.estack.Pop() - val := itemElem.value - if t, ok := itemElem.value.(*StructItem); ok { - val = t.Clone() - } + val := cloneIfStruct(itemElem.value) switch t := arrElem.value.(type) { case *ArrayItem: