vm: clear references on context unload
Remove argument and local references from the reference counter when returning from function.
This commit is contained in:
parent
aec9111961
commit
c0d7b9d234
3 changed files with 62 additions and 1 deletions
|
@ -45,3 +45,10 @@ func (s *Slot) Get(i int) stackitem.Item {
|
||||||
|
|
||||||
// Size returns slot size.
|
// Size returns slot size.
|
||||||
func (s *Slot) Size() int { return len(s.storage) }
|
func (s *Slot) Size() int { return len(s.storage) }
|
||||||
|
|
||||||
|
// Clear removes all slot variables from reference counter.
|
||||||
|
func (s *Slot) Clear() {
|
||||||
|
for _, item := range s.storage {
|
||||||
|
s.refs.Remove(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
pkg/vm/vm.go
16
pkg/vm/vm.go
|
@ -1262,9 +1262,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
}
|
}
|
||||||
|
|
||||||
case opcode.RET:
|
case opcode.RET:
|
||||||
v.istack.Pop()
|
oldCtx := v.istack.Pop().Value().(*Context)
|
||||||
oldEstack := v.estack
|
oldEstack := v.estack
|
||||||
|
|
||||||
|
v.unloadContext(oldCtx)
|
||||||
if v.istack.Len() == 0 {
|
if v.istack.Len() == 0 {
|
||||||
v.state = haltState
|
v.state = haltState
|
||||||
break
|
break
|
||||||
|
@ -1372,6 +1373,19 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *VM) unloadContext(ctx *Context) {
|
||||||
|
if ctx.local != nil {
|
||||||
|
ctx.local.Clear()
|
||||||
|
}
|
||||||
|
if ctx.arguments != nil {
|
||||||
|
ctx.arguments.Clear()
|
||||||
|
}
|
||||||
|
currCtx := v.Context()
|
||||||
|
if ctx.static != nil && currCtx != nil && ctx.static != currCtx.static {
|
||||||
|
ctx.static.Clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getJumpCondition performs opcode specific comparison of a and b
|
// getJumpCondition performs opcode specific comparison of a and b
|
||||||
func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
|
func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
|
||||||
cmp := a.Cmp(b)
|
cmp := a.Cmp(b)
|
||||||
|
|
|
@ -1142,6 +1142,46 @@ func getTestFuncForVM(prog []byte, result interface{}, args ...interface{}) func
|
||||||
return getCustomTestFuncForVM(prog, f, args...)
|
return getCustomTestFuncForVM(prog, f, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeRETProgram(t *testing.T, argCount, localCount int) []byte {
|
||||||
|
require.True(t, argCount+localCount <= 255)
|
||||||
|
|
||||||
|
fProg := []opcode.Opcode{opcode.INITSLOT, opcode.Opcode(localCount), opcode.Opcode(argCount)}
|
||||||
|
for i := 0; i < localCount; i++ {
|
||||||
|
fProg = append(fProg, opcode.PUSH8, opcode.STLOC, opcode.Opcode(i))
|
||||||
|
}
|
||||||
|
fProg = append(fProg, opcode.RET)
|
||||||
|
|
||||||
|
offset := uint32(len(fProg) + 5)
|
||||||
|
param := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(param, offset)
|
||||||
|
|
||||||
|
ops := []opcode.Opcode{
|
||||||
|
opcode.INITSSLOT, 0x01,
|
||||||
|
opcode.PUSHA, 11, 0, 0, 0,
|
||||||
|
opcode.STSFLD0,
|
||||||
|
opcode.JMPL, opcode.Opcode(param[0]), opcode.Opcode(param[1]), opcode.Opcode(param[2]), opcode.Opcode(param[3]),
|
||||||
|
}
|
||||||
|
ops = append(ops, fProg...)
|
||||||
|
|
||||||
|
// execute func multiple times to ensure total reference count is less than max
|
||||||
|
callCount := MaxStackSize/(argCount+localCount) + 1
|
||||||
|
args := make([]opcode.Opcode, argCount)
|
||||||
|
for i := range args {
|
||||||
|
args[i] = opcode.PUSH7
|
||||||
|
}
|
||||||
|
for i := 0; i < callCount; i++ {
|
||||||
|
ops = append(ops, args...)
|
||||||
|
ops = append(ops, opcode.LDSFLD0, opcode.CALLA)
|
||||||
|
}
|
||||||
|
return makeProgram(ops...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRETReferenceClear(t *testing.T) {
|
||||||
|
// 42 is a canary
|
||||||
|
t.Run("Argument", getTestFuncForVM(makeRETProgram(t, 100, 0), 42, 42))
|
||||||
|
t.Run("Local", getTestFuncForVM(makeRETProgram(t, 0, 100), 42, 42))
|
||||||
|
}
|
||||||
|
|
||||||
func TestNOTEQUALByteArray(t *testing.T) {
|
func TestNOTEQUALByteArray(t *testing.T) {
|
||||||
prog := makeProgram(opcode.NOTEQUAL)
|
prog := makeProgram(opcode.NOTEQUAL)
|
||||||
t.Run("True", getTestFuncForVM(prog, true, []byte{1, 2}, []byte{0, 1, 2}))
|
t.Run("True", getTestFuncForVM(prog, true, []byte{1, 2}, []byte{0, 1, 2}))
|
||||||
|
|
Loading…
Reference in a new issue