-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ReferenceCounterV2
#3526
base: master
Are you sure you want to change the base?
ReferenceCounterV2
#3526
Changes from 6 commits
22c2107
51ce4ba
a4b936b
01445c2
bc2e52e
93c42fc
cab91ca
71cc724
e158c5f
37c1c00
c0efc7c
6352c5b
8276180
21e5434
066cc8b
9347754
57f122e
75ccbae
e2a10e5
0a85aad
e38f73e
a79dd96
4986c54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Copyright (C) 2015-2024 The Neo Project. | ||
// | ||
// ReferenceCounterV2.cs file belongs to the neo project and is free | ||
// software distributed under the MIT software license, see the | ||
// accompanying file LICENSE in the main directory of the | ||
// repository or http://www.opensource.org/licenses/mit-license.php | ||
// for more details. | ||
// | ||
// Redistribution and use in source and binary forms with or without | ||
// modifications are permitted. | ||
|
||
using Neo.VM.Types; | ||
using System; | ||
|
||
namespace Neo.VM | ||
{ | ||
/// <summary> | ||
/// Used for reference counting of objects in the VM. | ||
/// </summary> | ||
public sealed class ReferenceCounterV2 : IReferenceCounter | ||
{ | ||
/// <summary> | ||
/// Gets the count of references. | ||
/// </summary> | ||
public int Count { get; private set; } | ||
|
||
/// <summary> | ||
/// Adds item to Reference Counter | ||
/// </summary> | ||
/// <param name="item">The item to add.</param> | ||
/// <param name="count">Number of similar entries</param> | ||
public void AddStackReference(StackItem item, int count = 1) | ||
{ | ||
Count += count; | ||
} | ||
|
||
/// <summary> | ||
/// Removes item from Reference Counter | ||
/// </summary> | ||
/// <param name="item">The item to remove.</param> | ||
public void RemoveStackReference(StackItem item) | ||
{ | ||
if (Count == 0) | ||
{ | ||
throw new InvalidOperationException("Reference was not added before"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This version is not compatible with circular references, but at the same it works like a protection |
||
} | ||
|
||
Count--; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, what if you are removing a compound type? for instance, you have an array with 2047 sub items, then you remove the array, the rc will only remove one reference, and the vm will still be full. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [TestMethod]
public void TestNewArrayThenDrop()
{
using ScriptBuilder sb = new();
sb.EmitPush(2040);
sb.Emit(OpCode.NEWARRAY);
sb.Emit(OpCode.DROP);
using ExecutionEngine engine = new();
engine.LoadScript(sb.ToArray());
Assert.AreEqual(0, engine.ReferenceCounter.Count);
Assert.AreEqual(VMState.HALT, engine.Execute());
Assert.AreEqual(0, engine.ReferenceCounter.Count);
} Assert.AreEqual failed. Expected:<0>. Actual:<2040>. op:[0000]PUSHINT16 [] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [TestMethod]
public void TestNewArrayThenDrop()
{
using ScriptBuilder sb = new();
sb.EmitPush(2040);
sb.Emit(OpCode.NEWARRAY);
sb.Emit(OpCode.DROP);
sb.EmitPush(10);
sb.Emit(OpCode.NEWARRAY);
using ExecutionEngine engine = new();
engine.LoadScript(sb.ToArray());
Assert.AreEqual(0, engine.ReferenceCounter.Count);
var res = engine.Execute();
Assert.AreEqual(0, engine.ReferenceCounter.Count);
Assert.AreEqual(VMState.HALT, res);
} Assert.AreEqual failed. Expected:<0>. Actual:<2051>. op:[0000]PUSHINT16 [] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes, we need to propagate this change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add these UT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assert.AreEqual(0, engine.ReferenceCounter.Count);
Assert.AreEqual(VMState.HALT, res); it's 11 in the previous version There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
this use the previous version also |
||
} | ||
|
||
public void AddReference(StackItem item, CompoundType parent) => AddStackReference(item); | ||
public void RemoveReference(StackItem item, CompoundType parent) => RemoveStackReference(item); | ||
public void AddZeroReferred(StackItem item) | ||
{ | ||
// This version don't use this method | ||
} | ||
|
||
/// <summary> | ||
/// Checks and processes items that have zero references. | ||
/// </summary> | ||
/// <returns>The current reference count.</returns> | ||
public int CheckZeroReferred() | ||
{ | ||
return Count; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
// Copyright (C) 2015-2024 The Neo Project. | ||
// | ||
// UT_ReferenceCounter.cs file belongs to the neo project and is free | ||
// software distributed under the MIT software license, see the | ||
// accompanying file LICENSE in the main directory of the | ||
// repository or http://www.opensource.org/licenses/mit-license.php | ||
// for more details. | ||
// | ||
// Redistribution and use in source and binary forms with or without | ||
// modifications are permitted. | ||
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Neo.VM; | ||
using Neo.VM.Types; | ||
using System; | ||
using Array = Neo.VM.Types.Array; | ||
|
||
namespace Neo.Test | ||
{ | ||
[TestClass] | ||
public class UT_ReferenceCounterV2 | ||
{ | ||
[TestMethod] | ||
public void TestCircularReferences() | ||
{ | ||
using ScriptBuilder sb = new(); | ||
sb.Emit(OpCode.INITSSLOT, new byte[] { 1 }); //{}|{null}:1 | ||
sb.EmitPush(0); //{0}|{null}:2 | ||
sb.Emit(OpCode.NEWARRAY); //{A[]}|{null}:2 | ||
sb.Emit(OpCode.DUP); //{A[],A[]}|{null}:3 | ||
sb.Emit(OpCode.DUP); //{A[],A[],A[]}|{null}:4 | ||
sb.Emit(OpCode.APPEND); //{A[A]}|{null}:3 | ||
sb.Emit(OpCode.DUP); //{A[A],A[A]}|{null}:4 | ||
sb.EmitPush(0); //{A[A],A[A],0}|{null}:5 | ||
sb.Emit(OpCode.NEWARRAY); //{A[A],A[A],B[]}|{null}:5 | ||
sb.Emit(OpCode.STSFLD0); //{A[A],A[A]}|{B[]}:4 | ||
sb.Emit(OpCode.LDSFLD0); //{A[A],A[A],B[]}|{B[]}:5 | ||
sb.Emit(OpCode.APPEND); //{A[A,B]}|{B[]}:4 | ||
sb.Emit(OpCode.LDSFLD0); //{A[A,B],B[]}|{B[]}:5 | ||
sb.EmitPush(0); //{A[A,B],B[],0}|{B[]}:6 | ||
sb.Emit(OpCode.NEWARRAY); //{A[A,B],B[],C[]}|{B[]}:6 | ||
sb.Emit(OpCode.TUCK); //{A[A,B],C[],B[],C[]}|{B[]}:7 | ||
sb.Emit(OpCode.APPEND); //{A[A,B],C[]}|{B[C]}:6 | ||
sb.EmitPush(0); //{A[A,B],C[],0}|{B[C]}:7 | ||
sb.Emit(OpCode.NEWARRAY); //{A[A,B],C[],D[]}|{B[C]}:7 | ||
sb.Emit(OpCode.TUCK); //{A[A,B],D[],C[],D[]}|{B[C]}:8 | ||
sb.Emit(OpCode.APPEND); //{A[A,B],D[]}|{B[C[D]]}:7 | ||
sb.Emit(OpCode.LDSFLD0); //{A[A,B],D[],B[C]}|{B[C[D]]}:8 | ||
sb.Emit(OpCode.APPEND); //{A[A,B]}|{B[C[D[B]]]}:7 | ||
sb.Emit(OpCode.PUSHNULL); //{A[A,B],null}|{B[C[D[B]]]}:8 | ||
sb.Emit(OpCode.STSFLD0); //{A[A,B[C[D[B]]]]}|{null}:7 | ||
sb.Emit(OpCode.DUP); //{A[A,B[C[D[B]]]],A[A,B]}|{null}:8 | ||
sb.EmitPush(1); //{A[A,B[C[D[B]]]],A[A,B],1}|{null}:9 | ||
sb.Emit(OpCode.REMOVE); //{A[A]}|{null}:3 | ||
sb.Emit(OpCode.STSFLD0); //{}|{A[A]}:2 | ||
sb.Emit(OpCode.RET); //{}:0 | ||
|
||
using ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default); | ||
Debugger debugger = new(engine); | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(1, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(2, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(2, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(3, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(3, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(5, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(5, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(5, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(5, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(6, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(6, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(7, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(6, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(7, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(7, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(8, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(7, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(8, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(7, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(8, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(7, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(8, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(9, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(6, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(5, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.HALT, debugger.Execute()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
} | ||
|
||
[TestMethod] | ||
public void TestRemoveReferrer() | ||
{ | ||
using ScriptBuilder sb = new(); | ||
sb.Emit(OpCode.INITSSLOT, new byte[] { 1 }); //{}|{null}:1 | ||
sb.EmitPush(0); //{0}|{null}:2 | ||
sb.Emit(OpCode.NEWARRAY); //{A[]}|{null}:2 | ||
sb.Emit(OpCode.DUP); //{A[],A[]}|{null}:3 | ||
sb.EmitPush(0); //{A[],A[],0}|{null}:4 | ||
sb.Emit(OpCode.NEWARRAY); //{A[],A[],B[]}|{null}:4 | ||
sb.Emit(OpCode.STSFLD0); //{A[],A[]}|{B[]}:3 | ||
sb.Emit(OpCode.LDSFLD0); //{A[],A[],B[]}|{B[]}:4 | ||
sb.Emit(OpCode.APPEND); //{A[B]}|{B[]}:3 | ||
sb.Emit(OpCode.DROP); //{}|{B[]}:1 | ||
sb.Emit(OpCode.RET); //{}:0 | ||
|
||
using ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default); | ||
Debugger debugger = new(engine); | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(1, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(2, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(2, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(3, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(3, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(4, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(3, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.BREAK, debugger.StepInto()); | ||
Assert.AreEqual(2, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.HALT, debugger.Execute()); | ||
Assert.AreEqual(1, engine.ReferenceCounter.Count); | ||
} | ||
|
||
[TestMethod] | ||
public void TestCheckZeroReferredWithArray() | ||
{ | ||
using ScriptBuilder sb = new(); | ||
|
||
sb.EmitPush(ExecutionEngineLimits.Default.MaxStackSize - 1); | ||
sb.Emit(OpCode.NEWARRAY); | ||
|
||
// Good with MaxStackSize | ||
|
||
using (ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default)) | ||
{ | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(0, engine.ReferenceCounter.Count); | ||
|
||
Assert.AreEqual(VMState.HALT, engine.Execute()); | ||
Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize, engine.ReferenceCounter.Count); | ||
} | ||
|
||
// Fault with MaxStackSize+1 | ||
|
||
sb.Emit(OpCode.PUSH1); | ||
|
||
using (ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default)) | ||
{ | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(0, engine.ReferenceCounter.Count); | ||
|
||
Assert.AreEqual(VMState.FAULT, engine.Execute()); | ||
Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize + 1, engine.ReferenceCounter.Count); | ||
} | ||
} | ||
|
||
[TestMethod] | ||
public void TestCheckZeroReferred() | ||
{ | ||
using ScriptBuilder sb = new(); | ||
|
||
for (int x = 0; x < ExecutionEngineLimits.Default.MaxStackSize; x++) | ||
sb.Emit(OpCode.PUSH1); | ||
|
||
// Good with MaxStackSize | ||
|
||
using (ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default)) | ||
{ | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(0, engine.ReferenceCounter.Count); | ||
|
||
Assert.AreEqual(VMState.HALT, engine.Execute()); | ||
Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize, engine.ReferenceCounter.Count); | ||
} | ||
|
||
// Fault with MaxStackSize+1 | ||
|
||
sb.Emit(OpCode.PUSH1); | ||
|
||
using (ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default)) | ||
{ | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(0, engine.ReferenceCounter.Count); | ||
|
||
Assert.AreEqual(VMState.FAULT, engine.Execute()); | ||
Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize + 1, engine.ReferenceCounter.Count); | ||
} | ||
} | ||
|
||
[TestMethod] | ||
public void TestArrayNoPush() | ||
{ | ||
using ScriptBuilder sb = new(); | ||
sb.Emit(OpCode.RET); | ||
|
||
using ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default); | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(0, engine.ReferenceCounter.Count); | ||
|
||
Array array = new(engine.ReferenceCounter, new StackItem[] { 1, 2, 3, 4 }); | ||
Assert.AreEqual(array.Count, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.HALT, engine.Execute()); | ||
Assert.AreEqual(array.Count, engine.ReferenceCounter.Count); | ||
} | ||
|
||
[TestMethod] | ||
[ExpectedException(typeof(InvalidOperationException))] | ||
public void TestInvalidReferenceStackItem() | ||
{ | ||
var reference = new ReferenceCounterV2(); | ||
var arr = new Array(reference); | ||
var arr2 = new Array(); | ||
|
||
for (var i = 0; i < 10; i++) | ||
{ | ||
arr2.Add(i); | ||
} | ||
|
||
arr.Add(arr2); | ||
Assert.AreEqual(11, reference.Count); | ||
} | ||
|
||
[TestMethod] | ||
public void TestDup() | ||
{ | ||
using ScriptBuilder sb = new(); | ||
sb.Emit(OpCode.PUSH1); | ||
sb.Emit(OpCode.DUP); | ||
|
||
using ExecutionEngine engine = new(null, new ReferenceCounterV2(), ExecutionEngineLimits.Default); | ||
engine.LoadScript(sb.ToArray()); | ||
Assert.AreEqual(0, engine.ReferenceCounter.Count); | ||
engine.ExecuteNext(); | ||
Assert.AreEqual(1, engine.ReferenceCounter.Count); | ||
engine.ExecuteNext(); | ||
Assert.AreEqual(2, engine.ReferenceCounter.Count); | ||
engine.ExecuteNext(); | ||
Assert.AreEqual(2, engine.ReferenceCounter.Count); | ||
Assert.AreEqual(VMState.HALT, engine.Execute()); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @roman-khimov suggested, take a look at compound types handling in https://github.com/nspcc-dev/neo-go/blob/b8a65d3c37cfa429b8a5e135422bb1fa78762056/pkg/vm/ref_counter.go#L15.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've got some tests as well: https://github.com/nspcc-dev/neo-go/blob/b8a65d3c37cfa429b8a5e135422bb1fa78762056/pkg/vm/ref_counter_test.go