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.
|
||||
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:
|
||||
v.istack.Pop()
|
||||
oldCtx := v.istack.Pop().Value().(*Context)
|
||||
oldEstack := v.estack
|
||||
|
||||
v.unloadContext(oldCtx)
|
||||
if v.istack.Len() == 0 {
|
||||
v.state = haltState
|
||||
break
|
||||
|
@ -1372,6 +1373,19 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
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
|
||||
func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
|
||||
cmp := a.Cmp(b)
|
||||
|
|
|
@ -1142,6 +1142,46 @@ func getTestFuncForVM(prog []byte, result interface{}, args ...interface{}) func
|
|||
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) {
|
||||
prog := makeProgram(opcode.NOTEQUAL)
|
||||
t.Run("True", getTestFuncForVM(prog, true, []byte{1, 2}, []byte{0, 1, 2}))
|
||||
|
|
Loading…
Reference in a new issue